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.,
## 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

View File

@ -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)

View File

@ -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()