~ubuntu-branches/ubuntu/trusty/miro/trusty

« back to all changes in this revision

Viewing changes to portable/olddatabaseupgrade.py

  • Committer: Daniel Hahler
  • Date: 2010-04-13 18:51:35 UTC
  • mfrom: (1.2.10 upstream)
  • Revision ID: ubuntu-launchpad@thequod.de-20100413185135-xi24v1diqg8w406x
Merging shared upstream rev into target branch.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Miro - an RSS based video player application
2
 
# Copyright (C) 2005-2009 Participatory Culture Foundation
3
 
#
4
 
# This program is free software; you can redistribute it and/or modify
5
 
# it under the terms of the GNU General Public License as published by
6
 
# the Free Software Foundation; either version 2 of the License, or
7
 
# (at your option) any later version.
8
 
#
9
 
# This program is distributed in the hope that it will be useful,
10
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 
# GNU General Public License for more details.
13
 
#
14
 
# You should have received a copy of the GNU General Public License
15
 
# along with this program; if not, write to the Free Software
16
 
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17
 
#
18
 
# In addition, as a special exception, the copyright holders give
19
 
# permission to link the code of portions of this program with the OpenSSL
20
 
# library.
21
 
#
22
 
# You must obey the GNU General Public License in all respects for all of
23
 
# the code used other than OpenSSL. If you modify file(s) with this
24
 
# exception, you may extend this exception to your version of the file(s),
25
 
# but you are not obligated to do so. If you do not wish to do so, delete
26
 
# this exception statement from your version. If you delete this exception
27
 
# statement from all source files in the program, then also delete it here.
28
 
 
29
 
"""Module used to upgrade from databases before we had our current scheme.
30
 
 
31
 
Strategy:
32
 
* Unpickle old databases using a subclass of pickle.Unpickle that loads
33
 
    fake class objects for all our DDBObjects.  The fake classes are just
34
 
    empty shells with the upgrade code that existed when we added the schema
35
 
    module.
36
 
 
37
 
* Save those objects to disk, using the initial schema of the new system.
38
 
 
39
 
"""
40
 
 
41
 
from new import classobj
42
 
from copy import copy
43
 
from datetime import datetime
44
 
import pickle
45
 
import shutil
46
 
import threading
47
 
import types
48
 
import time
49
 
 
50
 
from miro.schema import ObjectSchema, SchemaInt, SchemaFloat, SchemaSimpleItem
51
 
from miro.schema import SchemaObject, SchemaBool, SchemaDateTime, SchemaTimeDelta
52
 
from miro.schema import SchemaList, SchemaDict
53
 
from fasttypes import LinkedList
54
 
from types import NoneType
55
 
from miro import storedatabase
56
 
 
57
 
######################### STAGE 1 helpers  #############################
58
 
# Below is a snapshot of what the database looked like at 0.8.2.  DDBObject
59
 
# classes and other classes that get saved in the database are present only as
60
 
# skeletons, all we want from them is their __setstate__ method.  
61
 
#
62
 
# The __setstate_ methods are almost exactly like they were in 0.8.2.  I
63
 
# removed some things that don't apply to us simple restoring, then saving the
64
 
# database (starting a Thread, sending messages to the downloader daemon,
65
 
# etc.).  I added some things to make things compatible with our schema,
66
 
# mostly this means setting attributes to None, where before we used the fact
67
 
# that access the attribute would throw an AttributeError (ugh!).
68
 
#
69
 
# We prepend "Old" to the DDBObject so they're easy to recognize if
70
 
# somehow they slip through to a real database
71
 
#
72
 
# ObjectSchema
73
 
# classes are exactly as they appeared in version 6 of the schema.
74
 
#
75
 
# Why version 6?
76
 
# Previous versions were in RC's.  They dropped some of the data that we
77
 
# need to import from old databases By making olddatabaseupgrade start on
78
 
# version 6 we avoid that bug, while still giving the people using version 1
79
 
# and 2 an upgrade path that does something.
80
 
 
81
 
 
82
 
def defaultFeedIconURL():
83
 
    from miro.plat import resources
84
 
    return resources.url("images/feedicon.png")
85
 
 
86
 
#Dummy class for removing bogus FileItem instances
87
 
class DropItLikeItsHot(object):
88
 
    __DropMeLikeItsHot = True
89
 
    def __slurp(self, *args, **kwargs):
90
 
        pass
91
 
    def __getattr__(self, attr):
92
 
        if attr == '__DropMeLikeItsHot':
93
 
            return self.__DropMeLikeItsHot
94
 
        else:
95
 
            print "DTV: WARNING! Attempt to call '%s' on DropItLikeItsHot instance" % attr
96
 
            import traceback
97
 
            traceback.print_stack()
98
 
            return self.__slurp
99
 
    __setstate__ = __slurp
100
 
    def __repr__(self):
101
 
        return "DropMeLikeItsHot"
102
 
    def __str__(self):
103
 
        return "DropMeLikeItsHot"
104
 
 
105
 
class OldDDBObject(object):
106
 
    pass
107
 
 
108
 
 
109
 
class OldItem(OldDDBObject):
110
 
    # allOldItems is a hack to get around the fact that old databases can have
111
 
    # items that aren't at the top level.  In fact, they can be in fairly
112
 
    # crazy places.  See bug #2515.  So we need to keep track of the items
113
 
    # when we unpickle the objects.
114
 
    allOldItems = set()
115
 
 
116
 
    def __setstate__(self, state):
117
 
        (version, data) = state
118
 
        if version == 0:
119
 
            data['pendingManualDL'] = False
120
 
            if not data.has_key('linkNumber'):
121
 
                data['linkNumber'] = 0
122
 
            version += 1
123
 
        if version == 1:
124
 
            data['keep'] = False
125
 
            data['pendingReason'] = ""
126
 
            version += 1
127
 
        if version == 2:
128
 
            data['creationTime'] = datetime.now()
129
 
            version += 1
130
 
        assert(version == 3)
131
 
        data['startingDownload'] = False
132
 
        self.__dict__ = data
133
 
 
134
 
        # Older versions of the database allowed Feed Implementations
135
 
        # to act as feeds. If that's the case, change feed attribute
136
 
        # to contain the actual feed.
137
 
        # NOTE: This assumes that the feed object is decoded
138
 
        # before its items. That appears to be generally true
139
 
        if not issubclass(self.feed.__class__, OldDDBObject):
140
 
            try:
141
 
                self.feed = self.feed.ufeed
142
 
            except (SystemExit, KeyboardInterrupt):
143
 
                raise
144
 
            except:
145
 
                self.__class__ = DropItLikeItsHot
146
 
            if self.__class__ is OldFileItem:
147
 
                self.__class__ = DropItLikeItsHot
148
 
 
149
 
        self.iconCache = None
150
 
        if not 'downloadedTime' in data:
151
 
            self.downloadedTime = None
152
 
        OldItem.allOldItems.add(self)
153
 
 
154
 
class OldFileItem(OldItem):
155
 
    pass
156
 
 
157
 
class OldFeed(OldDDBObject):
158
 
    def __setstate__(self,state):
159
 
        (version, data) = state
160
 
        if version == 0:
161
 
            version += 1
162
 
        if version == 1:
163
 
            data['thumbURL'] = defaultFeedIconURL()
164
 
            version += 1
165
 
        if version == 2:
166
 
            data['lastViewed'] = datetime.min
167
 
            data['unwatched'] = 0
168
 
            data['available'] = 0
169
 
            version += 1
170
 
        assert(version == 3)
171
 
        data['updating'] = False
172
 
        if not data.has_key('initiallyAutoDownloadable'):
173
 
            data['initiallyAutoDownloadable'] = True
174
 
        self.__dict__ = data
175
 
        # This object is useless without a FeedImpl associated with it
176
 
        if not data.has_key('actualFeed'):
177
 
            self.__class__ = DropItLikeItsHot
178
 
 
179
 
        self.iconCache = None
180
 
 
181
 
class OldFolder(OldDDBObject):
182
 
    pass
183
 
 
184
 
class OldHTTPAuthPassword(OldDDBObject):
185
 
    pass
186
 
 
187
 
class OldFeedImpl:
188
 
    def __setstate__(self, data):
189
 
        self.__dict__ = data
190
 
        if 'expireTime' not in data:
191
 
            self.expireTime = None
192
 
 
193
 
        # Some feeds had invalid updating freq.  Catch that error here, so we
194
 
        # don't lose the dabatase when we restore it.
195
 
        try:
196
 
            self.updateFreq = int(self.updateFreq)
197
 
        except ValueError:
198
 
            self.updateFreq = -1
199
 
 
200
 
class OldScraperFeedImpl(OldFeedImpl):
201
 
    def __setstate__(self,state):
202
 
        (version, data) = state
203
 
        assert(version == 0)
204
 
        data['updating'] = False
205
 
        data['tempHistory'] = {}
206
 
        OldFeedImpl.__setstate__(self, data)
207
 
 
208
 
class OldRSSFeedImpl(OldFeedImpl):
209
 
    def __setstate__(self,state):
210
 
        (version, data) = state
211
 
        assert(version == 0)
212
 
        data['updating'] = False
213
 
        OldFeedImpl.__setstate__(self, data)
214
 
 
215
 
class OldSearchFeedImpl(OldRSSFeedImpl):
216
 
    pass
217
 
 
218
 
class OldSearchDownloadsFeedImpl(OldFeedImpl):
219
 
    pass
220
 
 
221
 
class OldDirectoryFeedImpl(OldFeedImpl):
222
 
    def __setstate__(self,state):
223
 
        (version, data) = state
224
 
        assert(version == 0)
225
 
        data['updating'] = False
226
 
        if not data.has_key('initialUpdate'):
227
 
            data['initialUpdate'] = False
228
 
        OldFeedImpl.__setstate__(self, data)
229
 
 
230
 
class OldRemoteDownloader(OldDDBObject):
231
 
    def __setstate__(self,state):
232
 
        (version, data) = state
233
 
        self.__dict__ = copy(data)
234
 
        self.status = {}
235
 
        for key in ('startTime', 'endTime', 'filename', 'state',
236
 
                'currentSize', 'totalSize', 'reasonFailed'):
237
 
            self.status[key] = self.__dict__[key]
238
 
            del self.__dict__[key]
239
 
        # force the download daemon to create a new downloader object.
240
 
        self.dlid = 'noid'
241
 
 
242
 
class OldChannelGuide(OldDDBObject):
243
 
    def __setstate__(self,state):
244
 
        (version, data) = state
245
 
 
246
 
        if version == 0:
247
 
            self.sawIntro = data['viewed']
248
 
            self.cachedGuideBody = None
249
 
            self.loadedThisSession = False
250
 
            self.cond = threading.Condition()
251
 
        else:
252
 
            assert(version == 1)
253
 
            self.__dict__ = data
254
 
            self.cond = threading.Condition()
255
 
            self.loadedThisSession = False
256
 
        if not data.has_key('id'):
257
 
            self.__class__ = DropItLikeItsHot
258
 
 
259
 
        # No need to load a fresh channel guide here.
260
 
 
261
 
class OldMetainfo(OldDDBObject):
262
 
    pass
263
 
 
264
 
fakeClasses = {
265
 
    'item.Item': OldItem,
266
 
    'item.FileItem': OldFileItem,
267
 
    'feed.Feed': OldFeed,
268
 
    'feed.FeedImpl': OldFeedImpl,
269
 
    'feed.RSSFeedImpl': OldRSSFeedImpl,
270
 
    'feed.ScraperFeedImpl': OldScraperFeedImpl,
271
 
    'feed.SearchFeedImpl': OldSearchFeedImpl,
272
 
    'feed.DirectoryFeedImpl': OldDirectoryFeedImpl,
273
 
    'feed.SearchDownloadsFeedImpl': OldSearchDownloadsFeedImpl,
274
 
    'downloader.HTTPAuthPassword': OldHTTPAuthPassword,
275
 
    'downloader.RemoteDownloader': OldRemoteDownloader,
276
 
    'guide.ChannelGuide': OldChannelGuide,
277
 
 
278
 
    # Drop these classes like they're hot!
279
 
    #
280
 
    # YahooSearchFeedImpl is a leftover class that we don't use anymore.
281
 
    #
282
 
    # The HTTPDownloader and BTDownloader classes were removed in 0.8.2.  The
283
 
    # cleanest way to handle them is to just drop them.  If the user still has
284
 
    # these in their database, too bad.  BTDownloaders may contain BTDisplay
285
 
    # and BitTorrent.ConvertedMetainfo.ConvertedMetainfo objects, drop those
286
 
    # too.
287
 
    #
288
 
    # We use BitTornado now, so drop the metainfo... We should recreate it
289
 
    # after the upgrade.
290
 
    #
291
 
    # DownloaderFactory and StaticTab shouldn't be pickled, but I've seen
292
 
    # databases where it is.
293
 
    # 
294
 
    # We used to have classes called RSSFeed, ScraperFeed, etc.  Now we have
295
 
    # the Feed class which contains a FeedImpl subclass.  Since this only
296
 
    # happens on really old databases, we should just drop the old ones.
297
 
    'BitTorrent.ConvertedMetainfo.ConvertedMetainfo': DropItLikeItsHot,
298
 
    'downloader.DownloaderFactory': DropItLikeItsHot,
299
 
    'app.StaticTab': DropItLikeItsHot,
300
 
    'feed.YahooSearchFeedImpl': DropItLikeItsHot,
301
 
    'downloader.BTDownloader': DropItLikeItsHot,
302
 
    'downloader.BTDisplay': DropItLikeItsHot,
303
 
    'downloader.HTTPDownloader': DropItLikeItsHot,
304
 
    'scheduler.ScheduleEvent': DropItLikeItsHot,
305
 
    'feed.UniversalFeed' : DropItLikeItsHot,
306
 
    'feed.RSSFeed': DropItLikeItsHot,
307
 
    'feed.ScraperFeed': DropItLikeItsHot,
308
 
    'feed.SearchFeed': DropItLikeItsHot,
309
 
    'feed.DirectoryFeed': DropItLikeItsHot,
310
 
    'feed.SearchDownloadsFeed': DropItLikeItsHot,
311
 
}
312
 
 
313
 
 
314
 
class FakeClassUnpickler(pickle.Unpickler):
315
 
    unpickleNormallyWhitelist = [
316
 
        'datetime.datetime', 
317
 
        'datetime.timedelta', 
318
 
        'time.struct_time',
319
 
        'miro.feedparser.FeedParserDict',
320
 
        '__builtin__.unicode',
321
 
    ]
322
 
 
323
 
    def find_class(self, module, name):
324
 
        if module == 'feedparser':
325
 
            # hack to handle the fact that everything is inside the miro
326
 
            # package nowadays
327
 
            module = 'miro.feedparser'
328
 
        fullyQualifiedName = "%s.%s" % (module, name)
329
 
        if fullyQualifiedName in fakeClasses:
330
 
            return fakeClasses[fullyQualifiedName]
331
 
        elif fullyQualifiedName in self.unpickleNormallyWhitelist:
332
 
            return pickle.Unpickler.find_class(self, module, name)
333
 
        else:
334
 
            raise ValueError("Unrecognized class: %s" % fullyQualifiedName)
335
 
 
336
 
class IconCache:
337
 
    # We need to define this class for the ItemSchema.  In practice we will
338
 
    # always use None instead of one of these objects.
339
 
    pass
340
 
 
341
 
 
342
 
######################### STAGE 2 helpers #############################
343
 
 
344
 
class DDBObjectSchema(ObjectSchema):
345
 
    klass = OldDDBObject
346
 
    classString = 'ddb-object'
347
 
    fields = [
348
 
        ('id', SchemaInt())
349
 
    ]
350
 
 
351
 
# Unlike the SchemaString in schema.py, this allows binary strings or
352
 
# unicode strings
353
 
class SchemaString(SchemaSimpleItem):
354
 
    def validate(self, data):
355
 
        super(SchemaSimpleItem, self).validate(data)
356
 
        self.validateTypes(data, (unicode, str))
357
 
 
358
 
# Unlike the simple container in schema.py, this allows binary strings
359
 
class SchemaSimpleContainer(SchemaSimpleItem):
360
 
    """Allows nested dicts, lists and tuples, however the only thing they can
361
 
    store are simple objects.  This currently includes bools, ints, longs,
362
 
    floats, strings, unicode, None, datetime and struct_time objects.
363
 
    """
364
 
 
365
 
    def validate(self, data):
366
 
        super(SchemaSimpleContainer, self).validate(data)
367
 
        self.validateTypes(data, (dict, list, tuple))
368
 
        self.memory = set()
369
 
        toValidate = LinkedList()
370
 
        while data:
371
 
            if id(data) in self.memory:
372
 
                return
373
 
            else:
374
 
                self.memory.add(id(data))
375
 
    
376
 
            if isinstance(data, list) or isinstance(data, tuple):
377
 
                for item in data:
378
 
                    toValidate.append(item)
379
 
            elif isinstance(data, dict):
380
 
                for key, value in data.items():
381
 
                    self.validateTypes(key, [bool, int, long, float, unicode,
382
 
                        str, NoneType, datetime, time.struct_time])
383
 
                    toValidate.append(value)
384
 
            else:
385
 
                self.validateTypes(data, [bool, int, long, float, unicode,str,
386
 
                        NoneType, datetime, time.struct_time])
387
 
            try:
388
 
                data = toValidate.pop()
389
 
            except (SystemExit, KeyboardInterrupt):
390
 
                raise
391
 
            except:
392
 
                data = None
393
 
 
394
 
 
395
 
class ItemSchema(DDBObjectSchema):
396
 
    klass = OldItem
397
 
    classString = 'item'
398
 
    fields = DDBObjectSchema.fields + [
399
 
        ('feed', SchemaObject(OldFeed)),
400
 
        ('seen', SchemaBool()),
401
 
        ('downloaders', SchemaList(SchemaObject(OldRemoteDownloader))),
402
 
        ('autoDownloaded', SchemaBool()),
403
 
        ('startingDownload', SchemaBool()),
404
 
        ('lastDownloadFailed', SchemaBool()),
405
 
        ('pendingManualDL', SchemaBool()),
406
 
        ('pendingReason', SchemaString()),
407
 
        ('entry', SchemaSimpleContainer()),
408
 
        ('expired', SchemaBool()),
409
 
        ('keep', SchemaBool()),
410
 
        ('creationTime', SchemaDateTime()),
411
 
        ('linkNumber', SchemaInt(noneOk=True)),
412
 
        ('iconCache', SchemaObject(IconCache, noneOk=True)),
413
 
        ('downloadedTime', SchemaDateTime(noneOk=True)),
414
 
    ]
415
 
 
416
 
class FileItemSchema(ItemSchema):
417
 
    klass = OldFileItem
418
 
    classString = 'file-item'
419
 
    fields = ItemSchema.fields + [
420
 
        ('filename', SchemaString()),
421
 
    ]
422
 
 
423
 
class FeedSchema(DDBObjectSchema):
424
 
    klass = OldFeed
425
 
    classString = 'feed'
426
 
    fields = DDBObjectSchema.fields + [
427
 
        ('origURL', SchemaString()),
428
 
        ('errorState', SchemaBool()),
429
 
        ('initiallyAutoDownloadable', SchemaBool()),
430
 
        ('loading', SchemaBool()),
431
 
        ('actualFeed', SchemaObject(OldFeedImpl)),
432
 
        ('iconCache', SchemaObject(IconCache, noneOk=True)),
433
 
    ]
434
 
 
435
 
class FeedImplSchema(ObjectSchema):
436
 
    klass = OldFeedImpl
437
 
    classString = 'field-impl'
438
 
    fields = [
439
 
        ('available', SchemaInt()),
440
 
        ('unwatched', SchemaInt()),
441
 
        ('url', SchemaString()),
442
 
        ('ufeed', SchemaObject(OldFeed)),
443
 
        ('items', SchemaList(SchemaObject(OldItem))),
444
 
        ('title', SchemaString()),
445
 
        ('created', SchemaDateTime()),
446
 
        ('autoDownloadable', SchemaBool()),
447
 
        ('startfrom', SchemaDateTime()),
448
 
        ('getEverything', SchemaBool()),
449
 
        ('maxNew', SchemaInt()),
450
 
        ('fallBehind', SchemaInt()),
451
 
        ('expire', SchemaString()),
452
 
        ('visible', SchemaBool()),
453
 
        ('updating', SchemaBool()),
454
 
        ('lastViewed', SchemaDateTime()),
455
 
        ('thumbURL', SchemaString()),
456
 
        ('updateFreq', SchemaInt()),
457
 
        ('expireTime', SchemaTimeDelta(noneOk=True)),
458
 
    ]
459
 
 
460
 
class RSSFeedImplSchema(FeedImplSchema):
461
 
    klass = OldRSSFeedImpl
462
 
    classString = 'rss-feed-impl'
463
 
    fields = FeedImplSchema.fields + [
464
 
        ('initialHTML', SchemaString(noneOk=True)),
465
 
        ('etag', SchemaString(noneOk=True)),
466
 
        ('modified', SchemaString(noneOk=True)),
467
 
    ]
468
 
 
469
 
class ScraperFeedImplSchema(FeedImplSchema):
470
 
    klass = OldScraperFeedImpl
471
 
    classString = 'scraper-feed-impl'
472
 
    fields = FeedImplSchema.fields + [
473
 
        ('initialHTML', SchemaString(noneOk=True)),
474
 
        ('initialCharset', SchemaString(noneOk=True)),
475
 
        ('linkHistory', SchemaSimpleContainer()),
476
 
    ]
477
 
 
478
 
class SearchFeedImplSchema(FeedImplSchema):
479
 
    klass = OldSearchFeedImpl
480
 
    classString = 'search-feed-impl'
481
 
    fields = FeedImplSchema.fields + [
482
 
        ('searching', SchemaBool()),
483
 
        ('lastEngine', SchemaString()),
484
 
        ('lastQuery', SchemaString()),
485
 
    ]
486
 
 
487
 
class DirectoryFeedImplSchema(FeedImplSchema):
488
 
    klass = OldDirectoryFeedImpl
489
 
    classString = 'directory-feed-impl'
490
 
    # DirectoryFeedImpl doesn't have any addition fields over FeedImpl
491
 
 
492
 
class SearchDownloadsFeedImplSchema(FeedImplSchema):
493
 
    klass = OldSearchDownloadsFeedImpl
494
 
    classString = 'search-downloads-feed-impl'
495
 
    # SearchDownloadsFeedImpl doesn't have any addition fields over FeedImpl
496
 
 
497
 
class RemoteDownloaderSchema(DDBObjectSchema):
498
 
    klass = OldRemoteDownloader
499
 
    classString = 'remote-downloader'
500
 
    fields = DDBObjectSchema.fields + [
501
 
        ('url', SchemaString()),
502
 
        ('itemList', SchemaList(SchemaObject(OldItem))),
503
 
        ('dlid', SchemaString()),
504
 
        ('contentType', SchemaString(noneOk=True)),
505
 
        ('status', SchemaSimpleContainer()),
506
 
    ]
507
 
 
508
 
class HTTPAuthPasswordSchema(DDBObjectSchema):
509
 
    klass = OldHTTPAuthPassword
510
 
    classString = 'http-auth-password'
511
 
    fields = DDBObjectSchema.fields + [
512
 
        ('username', SchemaString()),
513
 
        ('password', SchemaString()),
514
 
        ('host', SchemaString()),
515
 
        ('realm', SchemaString()),
516
 
        ('path', SchemaString()),
517
 
        ('authScheme', SchemaString()),
518
 
    ]
519
 
 
520
 
class FolderSchema(DDBObjectSchema):
521
 
    klass = OldFolder
522
 
    classString = 'folder'
523
 
    fields = DDBObjectSchema.fields + [
524
 
        ('feeds', SchemaList(SchemaInt())),
525
 
        ('title', SchemaString()),
526
 
    ]
527
 
 
528
 
class ChannelGuideSchema(DDBObjectSchema):
529
 
    klass = OldChannelGuide
530
 
    classString = 'channel-guide'
531
 
    fields = DDBObjectSchema.fields + [
532
 
        ('sawIntro', SchemaBool()),
533
 
        ('cachedGuideBody', SchemaString(noneOk=True)),
534
 
        ('loadedThisSession', SchemaBool()),
535
 
    ]
536
 
 
537
 
objectSchemas = [ 
538
 
    DDBObjectSchema, ItemSchema, FileItemSchema, FeedSchema, FeedImplSchema,
539
 
    RSSFeedImplSchema, ScraperFeedImplSchema, SearchFeedImplSchema,
540
 
    DirectoryFeedImplSchema, SearchDownloadsFeedImplSchema,
541
 
    RemoteDownloaderSchema, HTTPAuthPasswordSchema, FolderSchema,
542
 
    ChannelGuideSchema, 
543
 
]
544
 
 
545
 
def convertOldDatabase(databasePath):
546
 
    OldItem.allOldItems = set()
547
 
    shutil.copyfile(databasePath, databasePath + '.old')
548
 
    f = open(databasePath, 'rb')
549
 
    p = FakeClassUnpickler(f)
550
 
    data = p.load()
551
 
    if type(data) == types.ListType:
552
 
        # version 0 database
553
 
        objects = data
554
 
    else:
555
 
        # version 1 database
556
 
        (version, objects) = data
557
 
    # Objects used to be stored as (object, object) tuples.  Remove the dup
558
 
    objects = [o[0] for o in objects]
559
 
    # drop any top-level DropItLikeItsHot instances
560
 
    objects = [o for o in objects if not hasattr(o, '__DropMeLikeItsHot')]
561
 
    # Set obj.id for any objects missing it
562
 
    idMissing = set()
563
 
    lastId = 0
564
 
    for o in objects:
565
 
        if hasattr(o, 'id'):
566
 
            if o.id > lastId:
567
 
                lastId = o.id
568
 
        else:
569
 
            idMissing.add(o)
570
 
    for o in idMissing:
571
 
        lastId += 1
572
 
        o.id = lastId
573
 
    # drop any downloaders that are referenced by items
574
 
    def dropItFilter(obj):
575
 
        return not hasattr(obj, '__DropMeLikeItsHot')
576
 
    for i in OldItem.allOldItems:
577
 
        i.downloaders = filter(dropItFilter, i.downloaders)
578
 
 
579
 
    storedatabase.saveObjectList(objects, databasePath,
580
 
            objectSchemas=objectSchemas, version=6)