Skip to content

Commit 6d113e6

Browse files
committed
Fix blank archive month index and firstdate None after migration.
Repair DumbBTree sort/length handling, normalize bytes/str msgids in imported archive databases, and compute firstdate/lastdate robustly. Fixes #37
1 parent fdc30f0 commit 6d113e6

1 file changed

Lines changed: 84 additions & 30 deletions

File tree

Mailman/Archiver/HyperDatabase.py

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,9 @@ def clear(self):
119119
# bulk clearing much faster than deleting each item, esp. with the
120120
# implementation of __delitem__() above :(
121121
self.dict = {}
122+
self.sorted = []
123+
self.__dirty = 0
124+
self.current_index = 0
122125

123126
def first(self):
124127
self.__sort() # guarantee that the list is sorted
@@ -130,13 +133,40 @@ def first(self):
130133
return key, self.dict[key]
131134

132135
def last(self):
136+
self.__sort()
133137
if not self.sorted:
134138
raise KeyError
135139
else:
136140
key = self.sorted[-1]
137141
self.current_index = len(self.sorted) - 1
138142
return key, self.dict[key]
139143

144+
def keys(self):
145+
self.__sort()
146+
return list(self.sorted)
147+
148+
def _resolve_key(self, key):
149+
if key in self.dict:
150+
return key
151+
if isinstance(key, str):
152+
alt = key.encode('utf-8', 'replace')
153+
if alt in self.dict:
154+
return alt
155+
elif isinstance(key, bytes):
156+
alt = key.decode('utf-8', 'replace')
157+
if alt in self.dict:
158+
return alt
159+
return key
160+
161+
def __getitem__(self, item):
162+
return self.dict[self._resolve_key(item)]
163+
164+
def has_key(self, key):
165+
return self._resolve_key(key) in self.dict
166+
167+
def __contains__(self, key):
168+
return self._resolve_key(key) in self.dict
169+
140170
def __next__(self):
141171
try:
142172
key = self.sorted[self.current_index]
@@ -145,12 +175,6 @@ def __next__(self):
145175
self.current_index = self.current_index + 1
146176
return key, self.dict[key]
147177

148-
def has_key(self, key):
149-
return key in self.dict
150-
151-
def __contains__(self, key):
152-
return key in self.dict
153-
154178
def set_location(self, loc):
155179
index = 0
156180
self.__sort()
@@ -161,9 +185,6 @@ def set_location(self, loc):
161185
index = index + 1
162186
raise KeyError(loc)
163187

164-
def __getitem__(self, item):
165-
return self.dict[item]
166-
167188
def __setitem__(self, item, val):
168189
# if first hasn't been called, then we don't need to worry
169190
# about sorting again
@@ -180,7 +201,8 @@ def __setitem__(self, item, val):
180201
self.current_index = self.sorted.index(current_item)
181202

182203
def __len__(self):
183-
return len(self.sorted)
204+
self.__sort()
205+
return len(self.dict)
184206

185207
def load(self):
186208
try:
@@ -207,6 +229,42 @@ def close(self):
207229
fp.close()
208230
self.unlock()
209231

232+
233+
def _timestamp_from_datekey(datekey):
234+
"""Extract a Unix timestamp from a date index key."""
235+
if isinstance(datekey, (tuple, list)):
236+
if not datekey:
237+
raise ValueError('empty date key')
238+
raw = datekey[0]
239+
else:
240+
raw = datekey
241+
if isinstance(raw, bytes):
242+
raw = raw.decode('ascii', 'replace')
243+
return float(raw)
244+
245+
246+
def _asctime_from_timestamp(ts):
247+
return time.asctime(time.localtime(ts))
248+
249+
250+
def _archive_date_extremum(date_index, extreme):
251+
timestamps = []
252+
for datekey in date_index.keys():
253+
try:
254+
timestamps.append(_timestamp_from_datekey(datekey))
255+
except (ValueError, TypeError, IndexError, OSError):
256+
continue
257+
if not timestamps:
258+
return 'None'
259+
return _asctime_from_timestamp(extreme(timestamps))
260+
261+
262+
def _normalize_msgid(msgid):
263+
if isinstance(msgid, bytes):
264+
return msgid.decode('utf-8', 'replace')
265+
return msgid
266+
267+
210268

211269
# this is lifted straight out of pipermail with
212270
# the bsddb.btree replaced with above class.
@@ -226,23 +284,19 @@ def __init__(self, basedir, mlist):
226284

227285
def firstdate(self, archive):
228286
self.__openIndices(archive)
229-
date = 'None'
230287
try:
231288
datekey, msgid = self.dateIndex.first()
232-
date = time.asctime(time.localtime(float(datekey[0])))
233-
except KeyError:
234-
pass
235-
return date
289+
return _asctime_from_timestamp(_timestamp_from_datekey(datekey))
290+
except (KeyError, ValueError, TypeError, IndexError, OSError):
291+
return _archive_date_extremum(self.dateIndex, min)
236292

237293
def lastdate(self, archive):
238294
self.__openIndices(archive)
239-
date = 'None'
240295
try:
241296
datekey, msgid = self.dateIndex.last()
242-
date = time.asctime(time.localtime(float(datekey[0])))
243-
except KeyError:
244-
pass
245-
return date
297+
return _asctime_from_timestamp(_timestamp_from_datekey(datekey))
298+
except (KeyError, ValueError, TypeError, IndexError, OSError):
299+
return _archive_date_extremum(self.dateIndex, max)
246300

247301
def numArticles(self, archive):
248302
self.__openIndices(archive)
@@ -298,22 +352,22 @@ def setThreadKey(self, archive, key, msgid):
298352

299353
def getArticle(self, archive, msgid):
300354
self.__openIndices(archive)
301-
if msgid not in self.__cache:
302-
# get the pickled object out of the DumbBTree
303-
buf = self.articleIndex[msgid]
304-
article = self.__cache[msgid] = Utils.load_pickle(buf)
305-
# For upgrading older archives
355+
resolved = self.articleIndex._resolve_key(msgid)
356+
if resolved not in self.__cache:
357+
buf = self.articleIndex[resolved]
358+
article = Utils.load_pickle(buf)
359+
if article is None:
360+
raise KeyError(msgid)
306361
article.setListIfUnset(self._mlist)
307-
else:
308-
article = self.__cache[msgid]
309-
return article
362+
self.__cache[resolved] = article
363+
return self.__cache[resolved]
310364

311365
def first(self, archive, index):
312366
self.__openIndices(archive)
313367
index = getattr(self, index + 'Index')
314368
try:
315369
key, msgid = index.first()
316-
return msgid
370+
return _normalize_msgid(msgid)
317371
except KeyError:
318372
return None
319373

@@ -322,7 +376,7 @@ def next(self, archive, index):
322376
index = getattr(self, index + 'Index')
323377
try:
324378
key, msgid = next(index)
325-
return msgid
379+
return _normalize_msgid(msgid)
326380
except KeyError:
327381
return None
328382

0 commit comments

Comments
 (0)