1 #!/usr/bin/env python 2 3 """ 4 Generic file access. 5 6 Copyright (C) 2009, 2010 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT ANY 14 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 15 PARTICULAR PURPOSE. See the GNU General Public License for more details. 16 17 You should have received a copy of the GNU General Public License along 18 with this program. If not, see <http://www.gnu.org/licenses/>. 19 """ 20 21 from iixr.data import vint, vint_to_array 22 from array import array 23 import zlib 24 25 # Constants. 26 27 class File: 28 29 "A basic file abstraction." 30 31 def __init__(self, f): 32 self.f = f 33 self.reset() 34 35 def reset(self): 36 37 "To be used to reset the state of the reader or writer between records." 38 39 pass 40 41 def seek(self, offset): 42 self.f.seek(offset) 43 self.reset() 44 45 def rewind(self): 46 self.f.seek(0) 47 self.reset() 48 49 def close(self): 50 if self.f is not None: 51 self.f.close() 52 self.f = None 53 54 def get_value_size(self, value): 55 if isinstance(value, (list, tuple)): 56 return len(value) 57 else: 58 return 0 59 60 def get_initial_value(self, size): 61 if size: 62 return [0] * size 63 else: 64 return 0 65 66 class FileWriter(File): 67 68 "Writing basic data types to files." 69 70 def __init__(self, f): 71 File.__init__(self, f) 72 73 def write_number(self, number): 74 75 "Write 'number' to the file using a variable length encoding." 76 77 self.f.write(vint(number)) 78 79 def write_string(self, s, compress=0): 80 81 """ 82 Write 's' to the file, recording its length and compressing the string 83 if 'compress' is set to a true value. 84 """ 85 86 # Convert Unicode objects to strings. 87 88 if isinstance(s, unicode): 89 s = s.encode("utf-8") 90 91 # Compress the string if requested. 92 93 if compress: 94 cs = zlib.compress(s) 95 96 # Take any shorter than the original. 97 98 if len(cs) < len(s): 99 flag = "z" 100 s = cs 101 else: 102 flag = "-" 103 104 else: 105 flag = "" 106 107 # Write the length of the data before the data itself. 108 109 length = len(s) 110 self.f.write("".join([flag, vint(length), s])) 111 112 def write_sequence(self, output, value, last, size, monotonic=1): 113 if size: 114 emit_delta = 1 115 for v, l in map(None, value, last)[:size]: 116 if v is None: 117 v = l 118 if monotonic or emit_delta: 119 v_out = v - l 120 if emit_delta and v_out != 0: 121 emit_delta = 0 122 else: 123 v_out = v + 1 124 vint_to_array(v_out, output) 125 else: 126 vint_to_array(value - last, output) 127 128 return value 129 130 class FileReader(File): 131 132 "Reading basic data types from files." 133 134 def __init__(self, f): 135 File.__init__(self, f) 136 137 def read_number(self): 138 139 "Read a number from the file." 140 141 # Read each byte, adding it to the number. 142 143 a = array('B') 144 fromfile = a.fromfile 145 f = self.f 146 147 fromfile(f, 1) 148 csd = a[-1] 149 if csd < 128: 150 return csd 151 else: 152 while csd & 128: 153 fromfile(f, 1) 154 csd = a[-1] 155 return sum([((csd & 127) << (number * 7)) for (number, csd) in enumerate(a)]) 156 157 def read_string(self, decompress=0): 158 159 """ 160 Read a string from the file, decompressing the stored data if 161 'decompress' is set to a true value. 162 """ 163 164 read = self.f.read 165 166 # Decompress the data if requested. 167 168 if decompress: 169 flag = read(1) 170 else: 171 flag = "-" 172 173 length = self.read_number() 174 s = read(length) 175 176 # Perform decompression if applicable. 177 178 if flag == "z": 179 s = zlib.decompress(s) 180 181 # Convert strings to Unicode objects. 182 183 return unicode(s, "utf-8") 184 185 def read_sequence(self, last, size, monotonic=1): 186 if size: 187 expect_delta = 1 188 value = [] 189 for v in last: 190 v_in = self.read_number() 191 if monotonic or expect_delta: 192 value.append(v + v_in) 193 if expect_delta and v_in != 0: 194 expect_delta = 0 195 else: 196 value.append(v_in - 1) 197 return tuple(value) 198 else: 199 return last + self.read_number() 200 201 # vim: tabstop=4 expandtab shiftwidth=4