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.,
|
## 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
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user