1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
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.
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.
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
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
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.
29
"""convert20database.py -- Convert version 2.0 (and before) databases.
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.
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.
42
from cStringIO import StringIO
49
from pysqlite2 import dbapi2 as sqlite3
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
57
from miro.plat.utils import filenameToUnicode
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()
65
def _find_global(module, name):
66
"""Does the work required for _loads."""
67
if module == 'storedatabase' and name == 'SavableObject':
69
elif module == 'feedparser' and name == 'FeedParserDict':
70
return feedparser.FeedParserDict
73
mod = sys.modules[module]
74
klass = getattr(mod, name)
78
"""Convert an old-style database to a new-style one.
80
cursor is an SQLite cursor.
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")
90
def _get_old_savables(cursor):
91
"""Get a list of SavableObjects given a cursor pointing to an old-style
94
cursor.execute("SELECT serialized_object FROM dtv_objects")
95
return [_loads(str(r[0])) for r in cursor]
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)
104
def _run_databasesanity(objects):
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)
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)))
123
def _calc_sqlite_types(object_schema):
125
for name, schema_item in object_schema.fields:
126
type = _sqlite_type_map[schema_item.__class__]
128
types.append('%s %s' % (name, type))
130
types.append('%s %s PRIMARY KEY' % (name, type))
131
return ', '.join(types)
133
def _execute_insert_sql(cursor, savable):
134
table_name = savable.classString.replace("-", "_")
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):
146
elif isinstance(schema_item, schema_mod.SchemaFilename):
147
value = filenameToUnicode(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)
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)
161
# Figure out which SQLITE type to use for SchemaItem classes.
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
179
_class_to_schema = {}
180
for schema in schema_mod.objectSchemas:
181
_class_to_schema[schema.classString] = schema
184
"""Holdover from 2.0 databases. We need this around to be able
189
classString -- specifies the class this object was converted from.
190
savedData -- dict that stores the data we've saved.
192
The SavableObject class is guaranteed to never change. This means we can
193
always safely unpickle them.
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.
202
# In both cases "storedatabase" works, because we try to unpickle it from
203
# inside the miro directory.
204
__module__ = 'storedatabase'
206
def __init__(self, classString):
207
self.classString = classString
211
return '<SavableObject: %s>' % self.classString