8 class ExceptionCorruptData(Exception): pass
10 # TODO: maybe when there's more time; for better readability.
11 class History(ctypes.Structure):
14 HISTORY_length = 30*24 # 30 days once an hour
17 # NOTE: assumes the first DIGEST_length bytes are a hexdigest checksum.
20 bytes = buffer(self)[:]
21 d = self.digest(bytes)
22 ctypes.memmove(ctypes.addressof(self), d, len(d))
23 bytes = buffer(self)[:]
26 def digest(self, bytes):
28 m.update(bytes[History.DIGEST_length:])
32 def verify(self, bytes, hexdigest):
33 d = self.digest(bytes)
34 #return d == hexdigest
37 def restore(self, bytes):
38 fit = min(len(bytes), ctypes.sizeof(self))
39 hexdigest = bytes[:History.DIGEST_length]
40 if self.verify(bytes, hexdigest):
41 ctypes.memmove(ctypes.addressof(self), bytes, fit)
43 raise ExceptionCorruptData()
47 def __init__(self, filename, subtype):
48 self.subtype = subtype
49 self.struct = self.subtype()
50 self.filename = filename
51 if not os.path.exists(self.filename):
52 # create for the first time, with empty data
57 def __getattr__(self, name):
58 # NOTE: if the given name is not part of this instance, try looking in
59 # the structure object.
60 return getattr(self.struct, name)
65 def read(self, check_for_file=True):
67 This function guarantees that space is preserved.
68 If one of the file operations fail, it will throw an exception.
70 # the file should already exist
72 assert os.path.exists(self.filename)
74 fd = os.open(self.filename, os.O_RDONLY)
75 a = os.read(fd, os.path.getsize(self.filename))
78 self.struct.restore(a)
79 except ExceptionCorruptData:
80 raise Exception("Corrupt data in %s; remove and try again." % self.filename)
82 assert self.struct.version >= History.HISTORY_version
84 print "Old version found; updating data file."
85 self.upgrade(self.filename)
86 # create for the first time, with empty data
87 self.struct = self.subtype()
92 def write(self, check_for_file=True):
93 # the file should already exist
95 assert os.path.exists(self.filename)
97 # open without TRUNC nor APPEND, then seek to beginning to preserve space on disk
98 fd = os.open(self.filename, os.O_WRONLY|os.O_CREAT)
100 ret = os.write(fd, self.struct.save())
104 def upgrade(self, filename):
105 # TODO: in the future a more clever version migration might be nice.
106 print [ h for h in self.struct.history ]
107 os.remove(filename) # just nuke the old version
109 class DNSHistory(History):
110 _fields_ = [ ("checksum", ctypes.c_char * History.DIGEST_length),
111 ("version", ctypes.c_int),
112 ("index", ctypes.c_int),
113 ("history", ctypes.c_float * History.HISTORY_length), ]
115 def __init__(self, *args, **kwargs):
116 super(DNSHistory, self).__init__(*args, **kwargs)
118 self.checksum = "0"*History.DIGEST_length
119 self.version = History.HISTORY_version
123 summary = self.history[self.index:] + self.history[:self.index]
124 measured = filter(lambda x: x != 0, summary)
127 def append(self, data):
129 # note, this won't be the case when reboot occurs, or on first run.
130 #assert last_value > 0.0
131 #assert data > last_value
132 #print "Recording: %s"% (data-last_value)
133 #history[i] = data-last_value
134 self.history[self.index] = data
136 self.index = self.index % History.HISTORY_length
138 # on init when last_value is 0, or reboot when counter resets.
139 # do not record data except for last_value, do not increment index
145 if __name__ == "__main__":
146 d = HistoryFile('test.dat', DNSHistory)