mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
cead54c959
commit
0af98226c1
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user