~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to portable/convert20database.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Miro - an RSS based video player application
2
 
# Copyright (C) 2005-2010 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
 
"""convert20database.py -- Convert version 2.0 (and before) databases.
30
 
 
31
 
In this module "old-style" means before version 80, where objects were are
32
 
stored in pickled form in a single table.  "new-style" means version 80, where
33
 
objects were separated into different tables based on their class and each
34
 
attribute was stored in a separate column.
35
 
"""
36
 
 
37
 
# Some of the code is duplicated between this module and storedatabase.py.
38
 
# This is intentional, it allows us to make changes to storedatabase
39
 
# without worrying about old databases.
40
 
 
41
 
import cPickle
42
 
from cStringIO import StringIO
43
 
import logging
44
 
import sys
45
 
 
46
 
try:
47
 
    import sqlite3
48
 
except ImportError:
49
 
    from pysqlite2 import dbapi2 as sqlite3
50
 
 
51
 
from miro import databaseupgrade
52
 
from miro import databasesanity
53
 
from miro import dbupgradeprogress
54
 
from miro import feedparser
55
 
from miro import schemav79 as schema_mod
56
 
from miro import util
57
 
from miro.plat.utils import filenameToUnicode
58
 
 
59
 
def _loads(str):
60
 
    """Version of cPickle.loads() that can handle the SavableObject class."""
61
 
    unpickler = cPickle.Unpickler(StringIO(str))
62
 
    unpickler.find_global = _find_global
63
 
    return unpickler.load()
64
 
 
65
 
def _find_global(module, name):
66
 
    """Does the work required for _loads."""
67
 
    if module == 'storedatabase' and name == 'SavableObject':
68
 
        return SavableObject
69
 
    elif module == 'feedparser' and name == 'FeedParserDict':
70
 
        return feedparser.FeedParserDict
71
 
    else:
72
 
        __import__(module)
73
 
        mod = sys.modules[module]
74
 
        klass = getattr(mod, name)
75
 
        return klass
76
 
 
77
 
def convert(cursor):
78
 
    """Convert an old-style database to a new-style one.
79
 
 
80
 
    cursor is an SQLite cursor.
81
 
    """
82
 
 
83
 
    savable_objects = _get_old_savables(cursor)
84
 
    _upgrate_old_savables(cursor, savable_objects)
85
 
    _run_databasesanity(savable_objects)
86
 
    _create_db_schema(cursor)
87
 
    _migrate_old_data(cursor, savable_objects)
88
 
    cursor.execute("DROP TABLE dtv_objects")
89
 
 
90
 
def _get_old_savables(cursor):
91
 
    """Get a list of SavableObjects given a cursor pointing to an old-style
92
 
    database.
93
 
    """
94
 
    cursor.execute("SELECT serialized_object FROM dtv_objects")
95
 
    return [_loads(str(r[0])) for r in cursor]
96
 
 
97
 
def _upgrate_old_savables(cursor, savables):
98
 
    cursor.execute("SELECT serialized_value FROM dtv_variables "
99
 
            "WHERE name=?", ("Democracy Version",))
100
 
    row = cursor.fetchone()
101
 
    version = cPickle.loads(str(row[0]))
102
 
    databaseupgrade.upgrade(savables, version, schema_mod.VERSION)
103
 
 
104
 
def _run_databasesanity(objects):
105
 
    try:
106
 
        databasesanity.check_sanity(objects, quiet=True,
107
 
                                    really_quiet=(not util.chatter))
108
 
    except databasesanity.DatabaseInsaneError, e:
109
 
        logging.warn("Old database fails sanity test: %s", e)
110
 
        objects[:] = []
111
 
 
112
 
def _create_db_schema(cursor):
113
 
    for schema in schema_mod.objectSchemas:
114
 
        table_name = schema.classString.replace('-', '_')
115
 
        cursor.execute("SELECT COUNT(*) FROM sqlite_master "
116
 
                "WHERE name=? and type='table'", (table_name,))
117
 
        if cursor.fetchone()[0] > 0:
118
 
            logging.warn("dropping %s in 2.0 upgrade", table_name)
119
 
            cursor.execute("DROP TABLE %s " % table_name)
120
 
        cursor.execute("CREATE TABLE %s (%s)" %
121
 
                (table_name, _calc_sqlite_types(schema)))
122
 
 
123
 
def _calc_sqlite_types(object_schema):
124
 
    types = []
125
 
    for name, schema_item in object_schema.fields:
126
 
        type = _sqlite_type_map[schema_item.__class__]
127
 
        if name != 'id':
128
 
            types.append('%s %s' % (name, type))
129
 
        else:
130
 
            types.append('%s %s PRIMARY KEY' % (name, type))
131
 
    return ', '.join(types)
132
 
 
133
 
def _execute_insert_sql(cursor, savable):
134
 
    table_name = savable.classString.replace("-", "_")
135
 
    column_names = []
136
 
    values = []
137
 
    schema = _class_to_schema[savable.classString]
138
 
    for name, schema_item in schema.fields:
139
 
        value = savable.savedData[name]
140
 
        column_names.append(name)
141
 
        if value is not None:
142
 
            if isinstance(schema_item, schema_mod.SchemaBinary):
143
 
                value = sqlite3.Binary(value)
144
 
            elif isinstance(schema_item, schema_mod.SchemaTimeDelta):
145
 
                value = repr(value)
146
 
            elif isinstance(schema_item, schema_mod.SchemaFilename):
147
 
                value = filenameToUnicode(value)
148
 
        values.append(value)
149
 
    sql = "REPLACE INTO %s (%s) VALUES(%s)" % (table_name,
150
 
            ', '.join(column_names),
151
 
            ', '.join('?' for i in xrange(len(column_names))))
152
 
    cursor.execute(sql, values)
153
 
 
154
 
def _migrate_old_data(cursor, savable_objects):
155
 
    total = len(savable_objects)
156
 
    for i, savable in enumerate(savable_objects):
157
 
        dbupgradeprogress.convert20_progress(i, total)
158
 
        _execute_insert_sql(cursor, savable)
159
 
    dbupgradeprogress.convert20_progress(total, total)
160
 
 
161
 
# Figure out which SQLITE type to use for SchemaItem classes.
162
 
_sqlite_type_map = {
163
 
        schema_mod.SchemaBool: 'integer',
164
 
        schema_mod.SchemaFloat: 'real',
165
 
        schema_mod.SchemaString: 'text',
166
 
        schema_mod.SchemaBinary:  'blob',
167
 
        schema_mod.SchemaURL: 'text',
168
 
        schema_mod.SchemaInt: 'integer',
169
 
        schema_mod.SchemaDateTime: 'timestamp',
170
 
        schema_mod.SchemaTimeDelta: 'pythonrepr',
171
 
        schema_mod.SchemaReprContainer: 'pythonrepr',
172
 
        schema_mod.SchemaDict: 'pythonrepr',
173
 
        schema_mod.SchemaList: 'pythonrepr',
174
 
        schema_mod.SchemaStatusContainer: 'pythonrepr',
175
 
        schema_mod.SchemaFilename: 'text',
176
 
        # we always store the unicode version of filenames
177
 
}
178
 
 
179
 
_class_to_schema = {}
180
 
for schema in schema_mod.objectSchemas:
181
 
    _class_to_schema[schema.classString] = schema
182
 
 
183
 
class SavableObject:
184
 
    """Holdover from 2.0 databases.  We need this around to be able
185
 
    to unpickle it.
186
 
 
187
 
    Member variables:
188
 
 
189
 
    classString -- specifies the class this object was converted from.
190
 
    savedData -- dict that stores the data we've saved.
191
 
 
192
 
    The SavableObject class is guaranteed to never change.  This means we can
193
 
    always safely unpickle them.
194
 
    """
195
 
 
196
 
    # This is a complete hack to prevent problems if data is saved with a
197
 
    # newer version of Miro and an older version of Miro tries to open it.
198
 
    # Now adays the name of this module is "miro.storedatabase", but for older
199
 
    # versions it's just "storedatabase".  Hacking the module name here
200
 
    # changes where pickle tries to unpickle it from.
201
 
    #
202
 
    # In both cases "storedatabase" works, because we try to unpickle it from
203
 
    # inside the miro directory.
204
 
    __module__ = 'storedatabase'
205
 
 
206
 
    def __init__(self, classString):
207
 
        self.classString = classString
208
 
        self.savedData = {}
209
 
 
210
 
    def __str__(self):
211
 
        return '<SavableObject: %s>' % self.classString