From 0af98226c1e35dcb5b74126344461304872b88dc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 3 Jan 2007 04:23:48 +0000 Subject: [PATCH] DATABASE FORMAT CHANGED: Users must delete library.db Cover data moved into its own table for efficiency. Uncompressed size columns added. Fix #15 --- libprs500/gui/database.py | 117 +++++++++++++++++++++++++++----------- libprs500/gui/editbook.py | 7 ++- libprs500/gui/main.py | 6 +- 3 files changed, 92 insertions(+), 38 deletions(-) diff --git a/libprs500/gui/database.py b/libprs500/gui/database.py index 1098a93dc9..40e3c87cbf 100644 --- a/libprs500/gui/database.py +++ b/libprs500/gui/database.py @@ -13,20 +13,24 @@ ## with this program; if not, write to the Free Software Foundation, Inc., ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import sqlite3 as sqlite -import os, os.path +import os from zlib import compress, decompress from stat import ST_SIZE -from libprs500.lrf.meta import LRFMetaFile +from libprs500.lrf.meta import LRFMetaFile, LRFException from cStringIO import StringIO as cStringIO class LibraryDatabase(object): - BOOKS_SQL = """ + BOOKS_SQL = \ + """ create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT, authors TEXT, publisher TEXT, size INTEGER, tags TEXT, - cover BLOB, date DATE DEFAULT CURRENT_TIMESTAMP, + date DATE DEFAULT CURRENT_TIMESTAMP, comments TEXT, rating INTEGER); - create table if not exists books_data(id INTEGER, extension TEXT, data BLOB); + create table if not exists books_data(id INTEGER, extension TEXT, + uncompressed_size INTEGER, data BLOB); + create table if not exists books_cover(id INTEGER, + uncompressed_size INTEGER, data BLOB); """ def __init__(self, dbpath): @@ -36,13 +40,14 @@ class LibraryDatabase(object): self.con.executescript(LibraryDatabase.BOOKS_SQL) def get_cover(self, _id): - raw = self.con.execute("select cover from books_meta where id=?", (_id,))\ - .next()["cover"] + raw = self.con.execute("select data from books_cover where id=?", \ + (_id,)).next()["data"] return decompress(str(raw)) if raw else None def get_extensions(self, _id): exts = [] - cur = self.con.execute("select extension from books_data where id=?", (_id,)) + cur = self.con.execute("select extension from books_data where id=?", \ + (_id,)) for row in cur: exts.append(row["extension"]) return exts @@ -60,21 +65,30 @@ class LibraryDatabase(object): publisher = None if "unknown" in author.lower(): author = None - _file = compress(open(_file).read()) - if cover: + data = open(_file).read() + usize = len(data) + data = compress(data) + csize = 0 + if cover: + csize = len(cover) cover = sqlite.Binary(compress(cover)) self.con.execute("insert into books_meta (title, authors, publisher, "+\ - "size, tags, cover, comments, rating) values "+\ - "(?,?,?,?,?,?,?,?)", \ - (title, author, publisher, size, None, cover, None, None)) + "size, tags, comments, rating) values "+\ + "(?,?,?,?,?,?,?)", \ + (title, author, publisher, size, None, None, None)) _id = self.con.execute("select max(id) from books_meta").next()[0] - self.con.execute("insert into books_data values (?,?,?)", \ - (_id, ext, sqlite.Binary(_file))) + self.con.execute("insert into books_data values (?,?,?,?)", \ + (_id, ext, usize, sqlite.Binary(data))) + self.con.execute("insert into books_cover values (?,?,?)", \ + (_id, csize, cover)) self.con.commit() return _id def get_row_by_id(self, _id, columns): - """ @param columns: list of column names """ + """ + Return C{columns} of meta data as a dict. + @param columns: list of column names + """ cols = ",".join([ c for c in columns]) cur = self.con.execute("select " + cols + " from books_meta where id=?"\ , (_id,)) @@ -83,13 +97,17 @@ class LibraryDatabase(object): r[c] = row[c] return r - def commit(self): self.con.commit() + def commit(self): + self.con.commit() def delete_by_id(self, _id): self.con.execute("delete from books_meta where id=?", (_id,)) self.con.execute("delete from books_data where id=?", (_id,)) + self.con.execute("delete from books_cover where id=?", (_id,)) + self.commit() def get_table(self, columns): + """ Return C{columns} of the metadata table as a list of dicts. """ cols = ",".join([ c for c in columns]) cur = self.con.execute("select " + cols + " from books_meta") rows = [] @@ -121,21 +139,36 @@ class LibraryDatabase(object): """ Remove format C{ext} from book C{_id} """ self.con.execute("delete from books_data where id=? and extension=?", \ (_id, ext)) + self.update_max_size(_id) self.con.commit() def add_format(self, _id, ext, data): """ If data for format ext already exists, it is replaced @type ext: string or None - @type data: string + @type data: string or file object """ try: data.seek(0) data = data.read() - except AttributeError: pass - size = len(data) + except AttributeError: + pass + metadata = self.get_metadata(_id) if ext: ext = ext.strip().lower() + if ext == "lrf": + s = cStringIO() + print >> s, data + try: + lrf = LRFMetaFile(s) + lrf.author = metadata["authors"] + lrf.title = metadata["title"] + except LRFException: + pass + data = s.getvalue() + s.close() + size = len(data) + data = sqlite.Binary(compress(data)) cur = self.con.execute("select extension from books_data where id=? "+\ "and extension=?", (_id, ext)) @@ -145,18 +178,22 @@ class LibraryDatabase(object): except: present = False if present: + self.con.execute("update books_data set uncompressed_size=? \ + where id=? and extension=?", (size, _id, ext)) self.con.execute("update books_data set data=? where id=? "+\ "and extension=?", (data, _id, ext)) else: - self.con.execute("insert into books_data (id, extension, data) "+\ - "values (?, ?, ?)", (_id, ext, data)) + self.con.execute("insert into books_data \ + (id, extension, uncompressed_size, data) values (?, ?, ?, ?)", \ + (_id, ext, size, data)) oldsize = self.get_row_by_id(_id, ['size'])['size'] if size > oldsize: self.con.execute("update books_meta set size=? where id=? ", \ (size, _id)) self.con.commit() - def get_meta_data(self, _id): + def get_metadata(self, _id): + """ Return metadata in a dict """ try: row = self.con.execute("select * from books_meta where id=?", \ (_id,)).next() @@ -164,13 +201,16 @@ class LibraryDatabase(object): return None data = {} for field in ("id", "title", "authors", "publisher", "size", "tags", - "cover", "date"): + "date"): data[field] = row[field] return data def set_metadata(self, _id, title=None, authors=None, rating=None, \ - publisher=None, tags=None, cover=None, \ - comments=None): + publisher=None, tags=None, comments=None): + """ + Update metadata fields for book C{_id}. Metadata is not updated + in formats. See L{set_metadata_item}. + """ if authors and not len(authors): authors = None if publisher and not len(publisher): @@ -179,16 +219,20 @@ class LibraryDatabase(object): tags = None if comments and not len(comments): comments = None - if cover: - cover = sqlite.Binary(compress(cover)) self.con.execute('update books_meta set title=?, authors=?, '+\ - 'publisher=?, tags=?, cover=?, comments=?, rating=? '+\ + 'publisher=?, tags=?, comments=?, rating=? '+\ 'where id=?', \ - (title, authors, publisher, tags, cover, comments, \ + (title, authors, publisher, tags, comments, \ rating, _id)) self.con.commit() def set_metadata_item(self, _id, col, val): + """ + Convenience method used to set metadata. Metadata is updated + automatically in supported formats. + @param col: If it is either 'title' or 'authors' the value is updated + in supported formats as well. + """ self.con.execute('update books_meta set '+col+'=? where id=?', \ (val, _id)) if col in ["authors", "title"]: @@ -205,14 +249,19 @@ class LibraryDatabase(object): def update_cover(self, _id, cover, scaled=None): """ + Update the stored cover. The cover is updated in supported formats + as well. @param cover: The cover data @param scaled: scaled version of cover that shoould be written to format files. If None, cover is used. """ data = None - if cover: + size = 0 + if cover: + size = len(cover) data = sqlite.Binary(compress(cover)) - self.con.execute('update books_meta set cover=? where id=?', (data, _id)) + self.con.execute('update books_cover set uncompressed_size=?, data=? \ + where id=?', (size, data, _id)) if not scaled: scaled = cover if scaled: @@ -227,8 +276,8 @@ class LibraryDatabase(object): self.commit() def update_max_size(self, _id): - cur = self.con.execute("select length(data) from books_data where id=?", \ - (_id,)) + cur = self.con.execute("select uncompressed_size from books_data \ + where id=?", (_id,)) maxsize = 0 for row in cur: maxsize = row[0] if row[0] > maxsize else maxsize diff --git a/libprs500/gui/editbook.py b/libprs500/gui/editbook.py index 1d39beecae..c7cdfc9e2e 100644 --- a/libprs500/gui/editbook.py +++ b/libprs500/gui/editbook.py @@ -90,7 +90,7 @@ class EditBookDialog(Ui_BookEditDialog): for row in range(self.formats.count()): fmt = self.formats.item(row) if fmt.ext == ext: - self.formats.takeItem(fmt) + self.formats.takeItem(row) break Format(self.formats, ext, path=_file) self.formats_changed = True @@ -153,7 +153,10 @@ class EditBookDialog(Ui_BookEditDialog): if cover: pm = QPixmap() pm.loadFromData(cover, "", Qt.AutoColor) - self.cover.setPixmap(pm) + if not pm.isNull(): + self.cover.setPixmap(pm) + else: + self.cover.setPixmap(QPixmap(":/default_cover")) else: self.cover.setPixmap(QPixmap(":/default_cover")) exts = self.db.get_extensions(self.id) diff --git a/libprs500/gui/main.py b/libprs500/gui/main.py index 770c43e3c7..9cc2653266 100644 --- a/libprs500/gui/main.py +++ b/libprs500/gui/main.py @@ -220,8 +220,10 @@ class Main(QObject, Ui_MainWindow): for _file in files: _file = os.path.abspath(_file) self.library_view.model().add_book(_file) - if self.library_view.isVisible(): self.search.clear() - else: self.library_model.search("") + if self.library_view.isVisible(): + self.search.clear() + else: + self.library_model.search("") hv = self.library_view.horizontalHeader() col = hv.sortIndicatorSection() order = hv.sortIndicatorOrder()