DATABASE FORMAT CHANGED:

Users must delete library.db
Cover data moved into its own table for efficiency. Uncompressed size columns added.

Fix #15
This commit is contained in:
Kovid Goyal 2007-01-03 04:23:48 +00:00
parent cead54c959
commit 0af98226c1
3 changed files with 92 additions and 38 deletions

View File

@ -13,20 +13,24 @@
## with this program; if not, write to the Free Software Foundation, Inc., ## with this program; if not, write to the Free Software Foundation, Inc.,
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
import sqlite3 as sqlite import sqlite3 as sqlite
import os, os.path import os
from zlib import compress, decompress from zlib import compress, decompress
from stat import ST_SIZE from stat import ST_SIZE
from libprs500.lrf.meta import LRFMetaFile from libprs500.lrf.meta import LRFMetaFile, LRFException
from cStringIO import StringIO as cStringIO from cStringIO import StringIO as cStringIO
class LibraryDatabase(object): class LibraryDatabase(object):
BOOKS_SQL = """ BOOKS_SQL = \
"""
create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT, create table if not exists books_meta(id INTEGER PRIMARY KEY, title TEXT,
authors TEXT, publisher TEXT, size INTEGER, tags 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); 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): def __init__(self, dbpath):
@ -36,13 +40,14 @@ class LibraryDatabase(object):
self.con.executescript(LibraryDatabase.BOOKS_SQL) self.con.executescript(LibraryDatabase.BOOKS_SQL)
def get_cover(self, _id): def get_cover(self, _id):
raw = self.con.execute("select cover from books_meta where id=?", (_id,))\ raw = self.con.execute("select data from books_cover where id=?", \
.next()["cover"] (_id,)).next()["data"]
return decompress(str(raw)) if raw else None return decompress(str(raw)) if raw else None
def get_extensions(self, _id): def get_extensions(self, _id):
exts = [] 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: for row in cur:
exts.append(row["extension"]) exts.append(row["extension"])
return exts return exts
@ -60,21 +65,30 @@ class LibraryDatabase(object):
publisher = None publisher = None
if "unknown" in author.lower(): if "unknown" in author.lower():
author = None author = None
_file = compress(open(_file).read()) data = open(_file).read()
if cover: usize = len(data)
data = compress(data)
csize = 0
if cover:
csize = len(cover)
cover = sqlite.Binary(compress(cover)) cover = sqlite.Binary(compress(cover))
self.con.execute("insert into books_meta (title, authors, publisher, "+\ self.con.execute("insert into books_meta (title, authors, publisher, "+\
"size, tags, cover, comments, rating) values "+\ "size, tags, comments, rating) values "+\
"(?,?,?,?,?,?,?,?)", \ "(?,?,?,?,?,?,?)", \
(title, author, publisher, size, None, cover, None, None)) (title, author, publisher, size, None, None, None))
_id = self.con.execute("select max(id) from books_meta").next()[0] _id = self.con.execute("select max(id) from books_meta").next()[0]
self.con.execute("insert into books_data values (?,?,?)", \ self.con.execute("insert into books_data values (?,?,?,?)", \
(_id, ext, sqlite.Binary(_file))) (_id, ext, usize, sqlite.Binary(data)))
self.con.execute("insert into books_cover values (?,?,?)", \
(_id, csize, cover))
self.con.commit() self.con.commit()
return _id return _id
def get_row_by_id(self, _id, columns): 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]) cols = ",".join([ c for c in columns])
cur = self.con.execute("select " + cols + " from books_meta where id=?"\ cur = self.con.execute("select " + cols + " from books_meta where id=?"\
, (_id,)) , (_id,))
@ -83,13 +97,17 @@ class LibraryDatabase(object):
r[c] = row[c] r[c] = row[c]
return r return r
def commit(self): self.con.commit() def commit(self):
self.con.commit()
def delete_by_id(self, _id): def delete_by_id(self, _id):
self.con.execute("delete from books_meta where id=?", (_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_data where id=?", (_id,))
self.con.execute("delete from books_cover where id=?", (_id,))
self.commit()
def get_table(self, columns): def get_table(self, columns):
""" Return C{columns} of the metadata table as a list of dicts. """
cols = ",".join([ c for c in columns]) cols = ",".join([ c for c in columns])
cur = self.con.execute("select " + cols + " from books_meta") cur = self.con.execute("select " + cols + " from books_meta")
rows = [] rows = []
@ -121,21 +139,36 @@ class LibraryDatabase(object):
""" Remove format C{ext} from book C{_id} """ """ Remove format C{ext} from book C{_id} """
self.con.execute("delete from books_data where id=? and extension=?", \ self.con.execute("delete from books_data where id=? and extension=?", \
(_id, ext)) (_id, ext))
self.update_max_size(_id)
self.con.commit() self.con.commit()
def add_format(self, _id, ext, data): def add_format(self, _id, ext, data):
""" """
If data for format ext already exists, it is replaced If data for format ext already exists, it is replaced
@type ext: string or None @type ext: string or None
@type data: string @type data: string or file object
""" """
try: try:
data.seek(0) data.seek(0)
data = data.read() data = data.read()
except AttributeError: pass except AttributeError:
size = len(data) pass
metadata = self.get_metadata(_id)
if ext: if ext:
ext = ext.strip().lower() 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)) data = sqlite.Binary(compress(data))
cur = self.con.execute("select extension from books_data where id=? "+\ cur = self.con.execute("select extension from books_data where id=? "+\
"and extension=?", (_id, ext)) "and extension=?", (_id, ext))
@ -145,18 +178,22 @@ class LibraryDatabase(object):
except: except:
present = False present = False
if present: 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=? "+\ self.con.execute("update books_data set data=? where id=? "+\
"and extension=?", (data, _id, ext)) "and extension=?", (data, _id, ext))
else: else:
self.con.execute("insert into books_data (id, extension, data) "+\ self.con.execute("insert into books_data \
"values (?, ?, ?)", (_id, ext, data)) (id, extension, uncompressed_size, data) values (?, ?, ?, ?)", \
(_id, ext, size, data))
oldsize = self.get_row_by_id(_id, ['size'])['size'] oldsize = self.get_row_by_id(_id, ['size'])['size']
if size > oldsize: if size > oldsize:
self.con.execute("update books_meta set size=? where id=? ", \ self.con.execute("update books_meta set size=? where id=? ", \
(size, _id)) (size, _id))
self.con.commit() self.con.commit()
def get_meta_data(self, _id): def get_metadata(self, _id):
""" Return metadata in a dict """
try: try:
row = self.con.execute("select * from books_meta where id=?", \ row = self.con.execute("select * from books_meta where id=?", \
(_id,)).next() (_id,)).next()
@ -164,13 +201,16 @@ class LibraryDatabase(object):
return None return None
data = {} data = {}
for field in ("id", "title", "authors", "publisher", "size", "tags", for field in ("id", "title", "authors", "publisher", "size", "tags",
"cover", "date"): "date"):
data[field] = row[field] data[field] = row[field]
return data return data
def set_metadata(self, _id, title=None, authors=None, rating=None, \ def set_metadata(self, _id, title=None, authors=None, rating=None, \
publisher=None, tags=None, cover=None, \ publisher=None, tags=None, comments=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): if authors and not len(authors):
authors = None authors = None
if publisher and not len(publisher): if publisher and not len(publisher):
@ -179,16 +219,20 @@ class LibraryDatabase(object):
tags = None tags = None
if comments and not len(comments): if comments and not len(comments):
comments = None comments = None
if cover:
cover = sqlite.Binary(compress(cover))
self.con.execute('update books_meta set title=?, authors=?, '+\ self.con.execute('update books_meta set title=?, authors=?, '+\
'publisher=?, tags=?, cover=?, comments=?, rating=? '+\ 'publisher=?, tags=?, comments=?, rating=? '+\
'where id=?', \ 'where id=?', \
(title, authors, publisher, tags, cover, comments, \ (title, authors, publisher, tags, comments, \
rating, _id)) rating, _id))
self.con.commit() self.con.commit()
def set_metadata_item(self, _id, col, val): 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=?', \ self.con.execute('update books_meta set '+col+'=? where id=?', \
(val, _id)) (val, _id))
if col in ["authors", "title"]: if col in ["authors", "title"]:
@ -205,14 +249,19 @@ class LibraryDatabase(object):
def update_cover(self, _id, cover, scaled=None): 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 cover: The cover data
@param scaled: scaled version of cover that shoould be written to @param scaled: scaled version of cover that shoould be written to
format files. If None, cover is used. format files. If None, cover is used.
""" """
data = None data = None
if cover: size = 0
if cover:
size = len(cover)
data = sqlite.Binary(compress(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: if not scaled:
scaled = cover scaled = cover
if scaled: if scaled:
@ -227,8 +276,8 @@ class LibraryDatabase(object):
self.commit() self.commit()
def update_max_size(self, _id): def update_max_size(self, _id):
cur = self.con.execute("select length(data) from books_data where id=?", \ cur = self.con.execute("select uncompressed_size from books_data \
(_id,)) where id=?", (_id,))
maxsize = 0 maxsize = 0
for row in cur: for row in cur:
maxsize = row[0] if row[0] > maxsize else maxsize maxsize = row[0] if row[0] > maxsize else maxsize

View File

@ -90,7 +90,7 @@ class EditBookDialog(Ui_BookEditDialog):
for row in range(self.formats.count()): for row in range(self.formats.count()):
fmt = self.formats.item(row) fmt = self.formats.item(row)
if fmt.ext == ext: if fmt.ext == ext:
self.formats.takeItem(fmt) self.formats.takeItem(row)
break break
Format(self.formats, ext, path=_file) Format(self.formats, ext, path=_file)
self.formats_changed = True self.formats_changed = True
@ -153,7 +153,10 @@ class EditBookDialog(Ui_BookEditDialog):
if cover: if cover:
pm = QPixmap() pm = QPixmap()
pm.loadFromData(cover, "", Qt.AutoColor) 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: else:
self.cover.setPixmap(QPixmap(":/default_cover")) self.cover.setPixmap(QPixmap(":/default_cover"))
exts = self.db.get_extensions(self.id) exts = self.db.get_extensions(self.id)

View File

@ -220,8 +220,10 @@ class Main(QObject, Ui_MainWindow):
for _file in files: for _file in files:
_file = os.path.abspath(_file) _file = os.path.abspath(_file)
self.library_view.model().add_book(_file) self.library_view.model().add_book(_file)
if self.library_view.isVisible(): self.search.clear() if self.library_view.isVisible():
else: self.library_model.search("") self.search.clear()
else:
self.library_model.search("")
hv = self.library_view.horizontalHeader() hv = self.library_view.horizontalHeader()
col = hv.sortIndicatorSection() col = hv.sortIndicatorSection()
order = hv.sortIndicatorOrder() order = hv.sortIndicatorOrder()