@@ -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