home *** CD-ROM | disk | FTP | other *** search
Wrap
# Source Generated with Decompyle++ # File: in.pyc (Python 2.6) from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' import os import sys import shutil import cStringIO import glob import time import functools import traceback from itertools import repeat from math import floor from PyQt4.QtGui import QImage from calibre.ebooks.metadata import title_sort, author_to_author_sort from calibre.library.database import LibraryDatabase from calibre.library.field_metadata import FieldMetadata, TagsIcons from calibre.library.schema_upgrades import SchemaUpgrade from calibre.library.caches import ResultCache from calibre.library.custom_columns import CustomColumns from calibre.library.sqlite import connect, IntegrityError, DBThread from calibre.library.prefs import DBPrefs from calibre.ebooks.metadata import string_to_authors, authors_to_string, MetaInformation from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding from calibre.ptempfile import PersistentTemporaryFile from calibre.customize.ui import run_plugins_on_import from calibre.utils.filenames import ascii_filename from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp from calibre.utils.config import prefs, tweaks from calibre.utils.search_query_parser import saved_searches, set_saved_searches from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format from calibre.utils.magick.draw import save_cover_data_to if iswindows: import calibre.utils.winshell as winshell def delete_file(path): try: winshell.delete_file(path, silent = True, no_confirm = True) except: os.remove(path) def delete_tree(path, permanent = False): if permanent: shutil.rmtree(path) else: try: if not permanent: winshell.delete_file(path, silent = True, no_confirm = True) except: shutil.rmtree(path) copyfile = None if hasattr(os, 'link') else shutil.copyfile class Tag(object): def __init__(self, name, id = None, count = 0, state = 0, avg = 0, sort = None, tooltip = None, icon = None): self.name = name self.id = id self.count = count self.state = state self.avg_rating = None if avg is not None else 0 self.sort = sort if self.avg_rating > 0: if tooltip: tooltip = tooltip + ': ' tooltip = _('%sAverage rating is %3.1f') % (tooltip, self.avg_rating) self.tooltip = tooltip self.icon = icon def __unicode__(self): return u'%s:%s:%s:%s:%s' % (self.name, self.count, self.id, self.state, self.tooltip) def __str__(self): return unicode(self).encode('utf-8') def __repr__(self): return str(self) class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): PATH_LIMIT = None if 'win32' in sys.platform else 100 def user_version(self): doc = 'The user version of this database' def fget(self): return self.conn.get('pragma user_version;', all = False) def fset(self, val): self.conn.execute('pragma user_version=%d' % int(val)) self.conn.commit() return property(doc = doc, fget = fget, fset = fset) user_version = dynamic_property(user_version) def connect(self): if 'win32' in sys.platform and len(self.library_path) + 4 * self.PATH_LIMIT + 10 > 259: raise ValueError('Path to library too long. Must be less than %d characters.' % (259 - 4 * self.PATH_LIMIT - 10)) len(self.library_path) + 4 * self.PATH_LIMIT + 10 > 259 exists = os.path.exists(self.dbpath) self.conn = connect(self.dbpath, self.row_factory) if exists and self.user_version == 0: self.conn.close() os.remove(self.dbpath) self.conn = connect(self.dbpath, self.row_factory) if self.user_version == 0: self.initialize_database() self.books_list_filter = self.conn.create_dynamic_filter('books_list_filter') def exists_at(cls, path): if path: pass return os.path.exists(os.path.join(path, 'metadata.db')) exists_at = classmethod(exists_at) def __init__(self, library_path, row_factory = False): self.field_metadata = FieldMetadata() if not os.path.exists(library_path): os.makedirs(library_path) self.listeners = set([]) self.library_path = os.path.abspath(library_path) self.row_factory = row_factory self.dbpath = os.path.join(library_path, 'metadata.db') self.dbpath = os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', self.dbpath) if isinstance(self.dbpath, unicode) and not iswindows: self.dbpath = self.dbpath.encode(filesystem_encoding) self.connect() if not iswindows and not isosx: pass self.is_case_sensitive = not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB')) SchemaUpgrade.__init__(self) self.initialize_dynamic() def initialize_dynamic(self): self.prefs = DBPrefs(self) def migrate_preference(key, default): oldval = prefs[key] if oldval != default: self.prefs[key] = oldval prefs[key] = default if key not in self.prefs: self.prefs[key] = default migrate_preference('user_categories', { }) migrate_preference('saved_searches', { }) set_saved_searches(self, 'saved_searches') self.conn.executescript('\n DROP TRIGGER IF EXISTS author_insert_trg;\n CREATE TEMP TRIGGER author_insert_trg\n AFTER INSERT ON authors\n BEGIN\n UPDATE authors SET sort=author_to_author_sort(NEW.name) WHERE id=NEW.id;\n END;\n DROP TRIGGER IF EXISTS author_update_trg;\n CREATE TEMP TRIGGER author_update_trg\n BEFORE UPDATE ON authors\n BEGIN\n UPDATE authors SET sort=author_to_author_sort(NEW.name)\n WHERE id=NEW.id AND name <> NEW.name;\n END;\n ') self.conn.execute('UPDATE authors SET sort=author_to_author_sort(name) WHERE sort IS NULL') self.conn.executescript(u'\n CREATE TEMP VIEW IF NOT EXISTS tag_browser_news AS SELECT DISTINCT\n id,\n name,\n (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count,\n (0) as avg_rating,\n name as sort\n FROM tags as x WHERE name!="{0}" AND id IN\n (SELECT DISTINCT tag FROM books_tags_link WHERE book IN\n (SELECT DISTINCT book FROM books_tags_link WHERE tag IN\n (SELECT id FROM tags WHERE name="{0}")));\n '.format(_('News'))) self.conn.executescript(u'\n CREATE TEMP VIEW IF NOT EXISTS tag_browser_filtered_news AS SELECT DISTINCT\n id,\n name,\n (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count,\n (0) as avg_rating,\n name as sort\n FROM tags as x WHERE name!="{0}" AND id IN\n (SELECT DISTINCT tag FROM books_tags_link WHERE book IN\n (SELECT DISTINCT book FROM books_tags_link WHERE tag IN\n (SELECT id FROM tags WHERE name="{0}")));\n '.format(_('News'))) self.conn.commit() CustomColumns.__init__(self) template = ' (SELECT {query} FROM books_{table}_link AS link INNER JOIN\n {table} ON(link.{link_col}={table}.id) WHERE link.book=books.id)\n {col}\n ' columns = [ 'id', 'title', ('authors', 'authors', 'author', 'sortconcat(link.id, name)'), 'timestamp', '(SELECT MAX(uncompressed_size) FROM data WHERE book=books.id) size', ('rating', 'ratings', 'rating', 'ratings.rating'), ('tags', 'tags', 'tag', 'group_concat(name)'), '(SELECT text FROM comments WHERE book=books.id) comments', ('series', 'series', 'series', 'name'), ('publisher', 'publishers', 'publisher', 'name'), 'series_index', 'sort', 'author_sort', '(SELECT group_concat(format) FROM data WHERE data.book=books.id) formats', 'isbn', 'path', 'lccn', 'pubdate', 'flags', 'uuid'] lines = [] for col in columns: line = col if isinstance(col, tuple): line = template.format(col = col[0], table = col[1], link_col = col[2], query = col[3]) lines.append(line) custom_map = self.custom_columns_in_meta() custom_cols = list(sorted(custom_map.keys())) []([ custom_map[x] for x in custom_cols ]) self.FIELD_MAP = { 'id': 0, 'title': 1, 'authors': 2, 'timestamp': 3, 'size': 4, 'rating': 5, 'tags': 6, 'comments': 7, 'series': 8, 'publisher': 9, 'series_index': 10, 'sort': 11, 'author_sort': 12, 'formats': 13, 'isbn': 14, 'path': 15, 'lccn': 16, 'pubdate': 17, 'flags': 18, 'uuid': 19 } for k, v in self.FIELD_MAP.iteritems(): self.field_metadata.set_field_record_index(k, v, prefer_custom = False) base = max(self.FIELD_MAP.values()) for col in custom_cols: self.field_metadata.set_field_record_index(self.custom_column_num_map[col]['label'], base, prefer_custom = True) if self.custom_column_num_map[col]['datatype'] == 'series': continue self.FIELD_MAP[str(col) + '_s_index'] = base = base + 1 self.FIELD_MAP['cover'] = base + 1 self.field_metadata.set_field_record_index('cover', base + 1, prefer_custom = False) self.FIELD_MAP['ondevice'] = base + 2 self.field_metadata.set_field_record_index('ondevice', base + 2, prefer_custom = False) script = '\n DROP VIEW IF EXISTS meta2;\n CREATE TEMP VIEW meta2 AS\n SELECT\n {0}\n FROM books;\n '.format(', \n'.join(lines)) self.conn.executescript(script) self.conn.commit() tb_cats = self.field_metadata for k in tb_cats.keys(): if tb_cats[k]['kind'] in ('user', 'search'): del tb_cats[k] continue self.FIELD_MAP[col] = base = base + 1 for user_cat in sorted(self.prefs.get('user_categories', { }).keys()): cat_name = user_cat + ':' tb_cats.add_user_category(label = cat_name, name = user_cat) self.book_on_device_func = None self.data = ResultCache(self.FIELD_MAP, self.field_metadata) self.search = self.data.search self.refresh = functools.partial(self.data.refresh, self) self.sort = self.data.sort self.index = self.data.index self.refresh_ids = functools.partial(self.data.refresh_ids, self) self.row = self.data.row self.has_id = self.data.has_id self.count = self.data.count self.refresh_ondevice = functools.partial(self.data.refresh_ondevice, self) self.refresh() self.last_update_check = self.last_modified() def get_property(idx, index_is_id = False, loc = (-1,)): row = None if index_is_id else self.data[idx] if row is not None: return row[loc] for prop in ('author_sort', 'authors', 'comment', 'comments', 'isbn', 'publisher', 'rating', 'series', 'series_index', 'tags', 'title', 'timestamp', 'uuid', 'pubdate'): None if len(saved_searches().names()) else lines.extend(setattr, self, prop(functools.partial, get_property = 'loc'[self.FIELD_MAP if prop == 'comment' else prop])) def initialize_database(self): metadata_sqlite = open(P('metadata_sqlite.sql'), 'rb').read() self.conn.executescript(metadata_sqlite) self.user_version = 1 def last_modified(self): return utcfromtimestamp(os.stat(self.dbpath).st_mtime) def check_if_modified(self): if self.last_modified() > self.last_update_check: self.refresh() self.last_update_check = utcnow() def path(self, index, index_is_id = False): row = None if index_is_id else self.data[index] return row[self.FIELD_MAP['path']].replace('/', os.sep) def abspath(self, index, index_is_id = False): path = os.path.join(self.library_path, self.path(index, index_is_id = index_is_id)) if not os.path.exists(path): os.makedirs(path) return path def construct_path_name(self, id): authors = self.authors(id, index_is_id = True) if not authors: authors = _('Unknown') author = ascii_filename(authors.split(',')[0][:self.PATH_LIMIT]).decode(filesystem_encoding, 'ignore') title = ascii_filename(self.title(id, index_is_id = True)[:self.PATH_LIMIT]).decode(filesystem_encoding, 'ignore') path = author + '/' + title + ' (%d)' % id return path def construct_file_name(self, id): authors = self.authors(id, index_is_id = True) if not authors: authors = _('Unknown') author = ascii_filename(authors.split(',')[0][:self.PATH_LIMIT]).decode(filesystem_encoding, 'replace') title = ascii_filename(self.title(id, index_is_id = True)[:self.PATH_LIMIT]).decode(filesystem_encoding, 'replace') name = title + ' - ' + author while name.endswith('.'): name = name[:-1] return name def rmtree(self, path, permanent = False): if not self.normpath(self.library_path).startswith(self.normpath(path)): delete_tree(path, permanent = permanent) def normpath(self, path): path = os.path.abspath(os.path.realpath(path)) if not self.is_case_sensitive: path = path.lower() return path def set_path(self, index, index_is_id = False): id = None if index_is_id else self.id(index) path = self.construct_path_name(id) current_path = self.path(id, index_is_id = True).replace(os.sep, '/') formats = self.formats(id, index_is_id = True) formats = None if formats else [] fname = self.construct_file_name(id) changed = False for format in formats: name = self.conn.get('SELECT name FROM data WHERE book=? AND format=?', (id, format), all = False) if name and name != fname: changed = True break continue if path == current_path and not changed: return None tpath = os.path.join(self.library_path, *path.split('/')) if not os.path.exists(tpath): os.makedirs(tpath) spath = os.path.join(self.library_path, *current_path.split('/')) if current_path and os.path.exists(spath): cdata = self.cover(id, index_is_id = True) if cdata is not None: open(os.path.join(tpath, 'cover.jpg'), 'wb').write(cdata) for format in formats: f = self.format(id, format, index_is_id = True, as_file = False) if not f: continue stream = cStringIO.StringIO(f) self.add_format(id, format, stream, index_is_id = True, path = tpath) self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id)) self.conn.commit() self.data.set(id, self.FIELD_MAP['path'], path, row_is_id = True) if current_path and os.path.exists(spath): if self.normpath(spath) != self.normpath(tpath): self.rmtree(spath, permanent = True) parent = os.path.dirname(spath) if len(os.listdir(parent)) == 0: self.rmtree(parent, permanent = True) def add_listener(self, listener): self.listeners.add(listener) def notify(self, event, ids = []): for listener in self.listeners: try: listener(event, ids) continue traceback.print_exc() continue continue def cover(self, index, index_is_id = False, as_file = False, as_image = False, as_path = False): id = None if index_is_id else self.id(index) path = os.path.join(self.library_path, self.path(id, index_is_id = True), 'cover.jpg') if os.access(path, os.R_OK): if as_path: return path try: f = open(path, 'rb') except (IOError, OSError): as_path as_path time.sleep(0.2) f = open(path, 'rb') except: as_path if as_image: img = QImage() img.loadFromData(f.read()) f.close() return img ans = as_image if as_file else f.read() if ans is not f: f.close() return ans def get_metadata(self, idx, index_is_id = False, get_cover = False): aum = self.authors(idx, index_is_id = index_is_id) mi = MetaInformation(self.title(idx, index_is_id = index_is_id), aum) mi.author_sort = self.author_sort(idx, index_is_id = index_is_id) if mi.authors: mi.author_sort_map = { } for name, sort in zip(mi.authors, self.authors_sort_strings(idx, index_is_id)): mi.author_sort_map[name] = sort mi.comments = self.comments(idx, index_is_id = index_is_id) mi.publisher = self.publisher(idx, index_is_id = index_is_id) mi.timestamp = self.timestamp(idx, index_is_id = index_is_id) mi.pubdate = self.pubdate(idx, index_is_id = index_is_id) mi.uuid = self.uuid(idx, index_is_id = index_is_id) tags = self.tags(idx, index_is_id = index_is_id) mi.series = self.series(idx, index_is_id = index_is_id) if mi.series: mi.series_index = self.series_index(idx, index_is_id = index_is_id) mi.rating = self.rating(idx, index_is_id = index_is_id) mi.isbn = self.isbn(idx, index_is_id = index_is_id) id = None if index_is_id else self.id(idx) mi.application_id = id if get_cover: mi.cover = self.cover(id, index_is_id = True, as_path = True) return mi def has_book(self, mi): title = mi.title if title: if not isinstance(title, unicode): title = title.decode(preferred_encoding, 'replace') return bool(self.conn.get('SELECT id FROM books where title=?', (title,), all = False)) return False def has_cover(self, index, index_is_id = False): id = None if index_is_id else self.id(index) path = os.path.join(self.library_path, self.path(id, index_is_id = True), 'cover.jpg') return os.access(path, os.R_OK) def remove_cover(self, id, notify = True): path = os.path.join(self.library_path, self.path(id, index_is_id = True), 'cover.jpg') if os.path.exists(path): try: os.remove(path) except (IOError, OSError): time.sleep(0.2) os.remove(path) except: None<EXCEPTION MATCH>(IOError, OSError) None<EXCEPTION MATCH>(IOError, OSError) if notify: self.notify('cover', [ id]) def set_cover(self, id, data, notify = True): path = os.path.join(self.library_path, self.path(id, index_is_id = True), 'cover.jpg') if callable(getattr(data, 'save', None)): data.save(path) elif callable(getattr(data, 'read', None)): data = data.read() try: save_cover_data_to(data, path) except (IOError, OSError): time.sleep(0.2) save_cover_data_to(data, path) if notify: self.notify('cover', [ id]) def book_on_device(self, id): if callable(self.book_on_device_func): return self.book_on_device_func(id) def book_on_device_string(self, id): loc = [] on = self.book_on_device(id) if on is not None: (m, a, b) = on if m is not None: loc.append(_('Main')) if a is not None: loc.append(_('Card A')) if b is not None: loc.append(_('Card B')) return ', '.join(loc) def set_book_on_device_func(self, func): self.book_on_device_func = func def all_formats(self): formats = self.conn.get('SELECT DISTINCT format from data') if not formats: return set([]) return []([ f[0] for f in formats ]) def formats(self, index, index_is_id = False): id = None if index_is_id else self.id(index) try: formats = self.conn.get('SELECT format FROM data WHERE book=?', (id,)) formats = map((lambda x: x[0]), formats) except: return None ans = [] for format in formats: if self.format_abspath(id, format, index_is_id = True) is not None: ans.append(format) continue if not ans: return None return ','.join(ans) def has_format(self, index, format, index_is_id = False): return self.format_abspath(index, format, index_is_id) is not None def format_last_modified(self, id_, fmt): path = self.format_abspath(id_, fmt, index_is_id = True) if path is not None: return utcfromtimestamp(os.stat(path).st_mtime) def format_abspath(self, index, format, index_is_id = False): id = None if index_is_id else self.id(index) try: name = self.conn.get('SELECT name FROM data WHERE book=? AND format=?', (id, format), all = False) except: return None def format(self, index, format, index_is_id = False, as_file = False, mode = 'r+b'): path = self.format_abspath(index, format, index_is_id = index_is_id) if path is not None: f = open(path, mode) ret = None if as_file else f.read() if not as_file: f.close() return ret def add_format_with_hooks(self, index, format, fpath, index_is_id = False, path = None, notify = True): npath = self.run_import_plugins(fpath, format) format = os.path.splitext(npath)[-1].lower().replace('.', '').upper() stream = open(npath, 'rb') format = check_ebook_format(stream, format) return self.add_format(index, format, stream, index_is_id = index_is_id, path = path, notify = notify) def add_format(self, index, format, stream, index_is_id = False, path = None, notify = True, replace = True): id = None if index_is_id else self.id(index) if path is None: path = os.path.join(self.library_path, self.path(id, index_is_id = True)) name = self.conn.get('SELECT name FROM data WHERE book=? AND format=?', (id, format), all = False) if name: if not replace: return False self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, format)) name = self.construct_file_name(id) ext = None if format else '' dest = os.path.join(path, name + ext) pdir = os.path.dirname(dest) if not os.path.exists(pdir): os.makedirs(pdir) try: f = _[1] shutil.copyfileobj(stream, f) finally: pass stream.seek(0, 2) size = stream.tell() self.conn.execute('INSERT INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)', (id, format.upper(), size, name)) self.conn.commit() self.refresh_ids([ id]) return True def delete_book(self, id, notify = True): try: path = os.path.join(self.library_path, self.path(id, index_is_id = True)) except: path = None self.data.remove(id) if path and os.path.exists(path): try: winshell.delete_file(path, no_confirm = True, silent = True) except: self.rmtree(path) parent = os.path.dirname(path) if len(os.listdir(parent)) == 0: self.rmtree(parent) self.conn.execute('DELETE FROM books WHERE id=?', (id,)) self.conn.commit() self.clean() self.data.books_deleted([ id]) if notify: self.notify('delete', [ id]) def remove_format(self, index, format, index_is_id = False, notify = True): id = None if index_is_id else self.id(index) name = self.conn.get('SELECT name FROM data WHERE book=? AND format=?', (id, format), all = False) if name: path = self.format_abspath(id, format, index_is_id = True) try: delete_file(path) except: traceback.print_exc() self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, format.upper())) self.conn.commit() self.refresh_ids([ id]) if notify: self.notify('metadata', [ id]) def clean(self): def doit(ltable, table, ltable_col): st = 'DELETE FROM books_%s_link WHERE (SELECT COUNT(id) FROM books WHERE id=book) < 1;' % ltable self.conn.execute(st) st = 'DELETE FROM %(table)s WHERE (SELECT COUNT(id) FROM books_%(ltable)s_link WHERE %(ltable_col)s=%(table)s.id) < 1;' % dict(ltable = ltable, table = table, ltable_col = ltable_col) self.conn.execute(st) for ltable, table, ltable_col in [ ('authors', 'authors', 'author'), ('publishers', 'publishers', 'publisher'), ('tags', 'tags', 'tag'), ('series', 'series', 'series')]: doit(ltable, table, ltable_col) for id_, tag in self.conn.get('SELECT id, name FROM tags', all = True): if not tag.strip(): self.conn.execute('DELETE FROM books_tags_link WHERE tag=?', (id_,)) self.conn.execute('DELETE FROM tags WHERE id=?', (id_,)) continue (None,) self.clean_custom() self.conn.commit() def get_recipes(self): return self.conn.get('SELECT id, script FROM feeds') def get_recipe(self, id): return self.conn.get('SELECT script FROM feeds WHERE id=?', (id,), all = False) def get_books_for_category(self, category, id_): ans = set([]) if category not in self.field_metadata: return ans field = self.field_metadata[category] ans = self.conn.get('SELECT book FROM books_{tn}_link WHERE {col}=?'.format(tn = field['table'], col = field['link_column']), (id_,)) return set((lambda .0: for x in .0: x[0])(ans)) CATEGORY_SORTS = ('name', 'popularity', 'rating') def get_categories(self, sort = 'name', ids = None, icon_map = None): None(self.books_list_filter.change if not ids else ids) categories = { } if icon_map is not None and type(icon_map) != TagsIcons: raise TypeError('icon_map passed to get_categories must be of type TagIcons') type(icon_map) != TagsIcons tb_cats = self.field_metadata for category in tb_cats.keys(): cat = tb_cats[category] if not cat['is_category'] or cat['kind'] in ('user', 'search'): continue tn = cat['table'] categories[category] = [] if tn is None: continue cn = cat['column'] if ids is None: query = 'SELECT id, {0}, count, avg_rating, sort\n FROM tag_browser_{1}'.format(cn, tn) else: query = 'SELECT id, {0}, count, avg_rating, sort\n FROM tag_browser_filtered_{1}'.format(cn, tn) if sort == 'popularity': query += ' ORDER BY count DESC, sort ASC' elif sort == 'name': query += ' ORDER BY sort ASC' else: query += ' ORDER BY avg_rating DESC, sort ASC' data = self.conn.get(query) (icon, tooltip) = (None, '') label = tb_cats.key_to_label(category) if icon_map: if not tb_cats.is_custom_field(category): if category in icon_map: icon = icon_map[label] else: icon = icon_map[':custom'] icon_map[category] = icon tooltip = self.custom_column_label_map[label]['name'] datatype = cat['datatype'] if datatype == 'rating': item_not_zero_func = lambda x: if x[1] > 0: passx[2] > 0 formatter = lambda x: u'Γÿà ' * int(x / 2) elif category == 'authors': item_not_zero_func = lambda x: x[2] > 0 formatter = lambda x: x.replace('|', ',') else: item_not_zero_func = lambda x: x[2] > 0 formatter = lambda x: unicode(x) categories[category] = _[1] for r in categories['rating']: for x in categories['rating']: if r.name == x.name and r.id != x.id: r.count = r.count + x.count categories['rating'].remove(x) break continue [] categories['formats'] = [] icon = None if icon_map and 'formats' in icon_map: icon = icon_map['formats'] for fmt in self.conn.get('SELECT DISTINCT format FROM data'): fmt = fmt[0] if ids is not None: count = self.conn.get('SELECT COUNT(id)\n FROM data\n WHERE format="%s" AND\n books_list_filter(book)' % fmt, all = False) else: count = self.conn.get('SELECT COUNT(id)\n FROM data\n WHERE format="%s"' % fmt, all = False) if count > 0: categories['formats'].append(Tag(fmt, count = count, icon = icon)) continue if sort == 'popularity': categories['formats'].sort(key = (lambda x: x.count), reverse = True) else: categories['formats'].sort(key = (lambda x: x.name)) user_categories = self.prefs['user_categories'] taglist = { } for c in categories.keys(): taglist[c] = dict(map((lambda t: (t.name, t)), categories[c])) for user_cat in sorted(user_categories.keys()): items = [] for name, label, ign in user_categories[user_cat]: if label in taglist and name in taglist[label]: items.append(taglist[label][name]) continue if len(items): cat_name = user_cat + ':' if icon_map is not None: icon_map[cat_name] = icon_map[':user'] if sort == 'popularity': categories[cat_name] = sorted(items, key = (lambda x: x.count), reverse = True) elif sort == 'name': categories[cat_name] = sorted(items, key = (lambda x: x.sort.lower())) else: categories[cat_name] = sorted(items, key = (lambda x: x.avg_rating), reverse = True) sort == 'popularity' items = [] icon = None if icon_map and 'search' in icon_map: icon = icon_map['search'] for srch in saved_searches().names(): items.append(Tag(srch, tooltip = saved_searches().lookup(srch), icon = icon)) if len(items): if icon_map is not None: icon_map['search'] = icon_map['search'] categories['search'] = items return categories def tags_older_than(self, tag, delta): tag = tag.lower().strip() now = nowf() for r in self.data._data: if r is not None: if now - r[self.FIELD_MAP['timestamp']] > delta: tags = r[self.FIELD_MAP['tags']] if tags and tag in tags.lower(): yield r[self.FIELD_MAP['id']] now - r[self.FIELD_MAP['timestamp']] > delta def get_next_series_num_for(self, series): series_id = self.conn.get('SELECT id from series WHERE name=?', (series,), all = False) if series_id is None: return 1 series_num = self.conn.get('SELECT MAX(series_index) FROM books WHERE id IN (SELECT book FROM books_series_link where series=?)', (series_id,), all = False) if series_num is None: return 1 return floor(series_num + 1) def set(self, row, column, val): id = self.data[row][0] col = { 'title': 1, 'authors': 2, 'publisher': 3, 'rating': 4, 'tags': 7 }[column] self.data.set(row, col, val) if column == 'authors': val = string_to_authors(val) self.set_authors(id, val, notify = False) elif column == 'title': self.set_title(id, val, notify = False) elif column == 'publisher': self.set_publisher(id, val, notify = False) elif column == 'rating': self.set_rating(id, val, notify = False) elif column == 'tags': []([], _[1], append = False, notify = False) self.data.refresh_ids(self, [ id]) self.set_path(id, True) self.notify('metadata', [ id]) def set_metadata(self, id, mi, ignore_errors = False): def doit(func, *args, **kwargs): try: func(*args, **kwargs) except: if ignore_errors: traceback.print_exc() else: raise if mi.title: self.set_title(id, mi.title) if not mi.authors: mi.authors = [ _('Unknown')] authors = [] for a in mi.authors: authors += string_to_authors(a) self.set_authors(id, authors, notify = False) if mi.author_sort: doit(self.set_author_sort, id, mi.author_sort, notify = False) if mi.publisher: doit(self.set_publisher, id, mi.publisher, notify = False) if mi.rating: doit(self.set_rating, id, mi.rating, notify = False) if mi.series: doit(self.set_series, id, mi.series, notify = False) if mi.cover_data[1] is not None: doit(self.set_cover, id, mi.cover_data[1]) elif mi.cover is not None and os.access(mi.cover, os.R_OK): doit(self.set_cover, id, open(mi.cover, 'rb')) if mi.tags: doit(self.set_tags, id, mi.tags, notify = False) if mi.comments: doit(self.set_comment, id, mi.comments, notify = False) if mi.isbn and mi.isbn.strip(): doit(self.set_isbn, id, mi.isbn, notify = False) if mi.series_index: doit(self.set_series_index, id, mi.series_index, notify = False) if mi.pubdate: doit(self.set_pubdate, id, mi.pubdate, notify = False) if getattr(mi, 'timestamp', None) is not None: doit(self.set_timestamp, id, mi.timestamp, notify = False) self.set_path(id, True) self.notify('metadata', [ id]) def authors_sort_strings(self, id, index_is_id = False): id = None if index_is_id else self.id(id) aut_strings = self.conn.get('\n SELECT sort\n FROM authors, books_authors_link as bl\n WHERE bl.book=? and authors.id=bl.author\n ORDER BY bl.id', (id,)) result = [] for sort, in aut_strings: result.append(sort) return result def author_sort_from_book(self, id, index_is_id = False): auts = self.authors_sort_strings(id, index_is_id) return ' & '.join(auts).replace('|', ',') def author_sort_from_authors(self, authors): result = [] for aut in authors: r = self.conn.get('SELECT sort FROM authors WHERE name=?', (aut.replace(',', '|'),), all = False) if r is None: result.append(author_to_author_sort(aut)) continue result.append(r) return ' & '.join(result).replace('|', ',') def set_authors(self, id, authors, notify = True): if not authors: authors = [ _('Unknown')] self.conn.execute('DELETE FROM books_authors_link WHERE book=?', (id,)) self.conn.execute('DELETE FROM authors WHERE (SELECT COUNT(id) FROM books_authors_link WHERE author=authors.id) < 1') for a in authors: if not a: continue a = a.strip().replace(',', '|') if not isinstance(a, unicode): a = a.decode(preferred_encoding, 'replace') author = self.conn.get('SELECT id from authors WHERE name=?', (a,), all = False) if author: aid = author self.conn.execute('UPDATE authors SET name=? WHERE id=?', (a, aid)) else: aid = self.conn.execute('INSERT INTO authors(name) VALUES (?)', (a,)).lastrowid try: self.conn.execute('INSERT INTO books_authors_link(book, author) VALUES (?,?)', (id, aid)) continue except IntegrityError: continue self.conn.commit() ss = self.author_sort_from_book(id, index_is_id = True) self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (ss, id)) self.conn.commit() self.FIELD_MAP['authors'](','.join, [], []([ a.replace(',', '|') for a in authors ]), row_is_id = True) self.data.set(id, self.FIELD_MAP['author_sort'], ss, row_is_id = True) self.set_path(id, True) def set_title(self, id, title, notify = True): if not title: return None if not isinstance(title, unicode): title = title.decode(preferred_encoding, 'replace') self.conn.execute('UPDATE books SET title=? WHERE id=?', (title, id)) self.data.set(id, self.FIELD_MAP['title'], title, row_is_id = True) if tweaks['title_series_sorting'] == 'library_order': self.data.set(id, self.FIELD_MAP['sort'], title_sort(title), row_is_id = True) else: self.data.set(id, self.FIELD_MAP['sort'], title, row_is_id = True) self.set_path(id, True) self.conn.commit() if notify: self.notify('metadata', [ id]) def set_timestamp(self, id, dt, notify = True): if dt: self.conn.execute('UPDATE books SET timestamp=? WHERE id=?', (dt, id)) self.data.set(id, self.FIELD_MAP['timestamp'], dt, row_is_id = True) self.conn.commit() if notify: self.notify('metadata', [ id]) def set_pubdate(self, id, dt, notify = True): if dt: self.conn.execute('UPDATE books SET pubdate=? WHERE id=?', (dt, id)) self.data.set(id, self.FIELD_MAP['pubdate'], dt, row_is_id = True) self.conn.commit() if notify: self.notify('metadata', [ id]) def set_publisher(self, id, publisher, notify = True): self.conn.execute('DELETE FROM books_publishers_link WHERE book=?', (id,)) self.conn.execute('DELETE FROM publishers WHERE (SELECT COUNT(id) FROM books_publishers_link WHERE publisher=publishers.id) < 1') if publisher: if not isinstance(publisher, unicode): publisher = publisher.decode(preferred_encoding, 'replace') pub = self.conn.get('SELECT id from publishers WHERE name=?', (publisher,), all = False) if pub: aid = pub else: aid = self.conn.execute('INSERT INTO publishers(name) VALUES (?)', (publisher,)).lastrowid self.conn.execute('INSERT INTO books_publishers_link(book, publisher) VALUES (?,?)', (id, aid)) self.conn.commit() self.data.set(id, self.FIELD_MAP['publisher'], publisher, row_is_id = True) if notify: self.notify('metadata', [ id]) def get_tags_with_ids(self): result = self.conn.get('SELECT id,name FROM tags') if not result: return [] return result def rename_tag(self, old_id, new_name): new_name = new_name.strip() new_id = self.conn.get('SELECT id from tags\n WHERE name=?', (new_name,), all = False) if new_id is None or old_id == new_id: self.conn.execute('UPDATE tags SET name=?\n WHERE id=?', (new_name, old_id)) else: books = self.conn.get('SELECT book from books_tags_link\n WHERE tag=?', (old_id,)) for book_id, in books: self.conn.execute('DELETE FROM books_tags_link\n WHERE book=? and tag=?', (book_id, new_id)) self.conn.execute('UPDATE books_tags_link SET tag=?\n WHERE tag=?', (new_id, old_id)) self.conn.execute('DELETE FROM tags WHERE id=?', (old_id,)) self.conn.commit() def delete_tag_using_id(self, id): self.conn.execute('DELETE FROM books_tags_link WHERE tag=?', (id,)) self.conn.execute('DELETE FROM tags WHERE id=?', (id,)) self.conn.commit() def get_series_with_ids(self): result = self.conn.get('SELECT id,name FROM series') if not result: return [] return result def rename_series(self, old_id, new_name): new_name = new_name.strip() new_id = self.conn.get('SELECT id from series\n WHERE name=?', (new_name,), all = False) if new_id is None or old_id == new_id: self.conn.execute('UPDATE series SET name=? WHERE id=?', (new_name, old_id)) else: books = self.conn.get('SELECT books.id\n FROM books, books_series_link as lt\n WHERE books.id = lt.book AND lt.series=?\n ORDER BY books.series_index', (old_id,)) index = self.get_next_series_num_for(new_name) self.conn.execute('UPDATE books_series_link\n SET series=?\n WHERE series=?', (new_id, old_id)) for book_id, in books: self.conn.execute('UPDATE books\n SET series_index=?\n WHERE id=?', (index, book_id)) index = index + 1 self.conn.commit() def delete_series_using_id(self, id): books = self.conn.get('SELECT book from books_series_link WHERE series=?', (id,)) self.conn.execute('DELETE FROM books_series_link WHERE series=?', (id,)) self.conn.execute('DELETE FROM series WHERE id=?', (id,)) self.conn.commit() for book_id, in books: self.conn.execute('UPDATE books SET series_index=1.0 WHERE id=?', (book_id,)) def get_publishers_with_ids(self): result = self.conn.get('SELECT id,name FROM publishers') if not result: return [] return result def rename_publisher(self, old_id, new_name): new_name = new_name.strip() new_id = self.conn.get('SELECT id from publishers\n WHERE name=?', (new_name,), all = False) if new_id is None or old_id == new_id: self.conn.execute('UPDATE publishers SET name=? WHERE id=?', (new_name, old_id)) else: self.conn.execute('UPDATE books_publishers_link\n SET publisher=?\n WHERE publisher=?', (new_id, old_id)) self.conn.execute('DELETE FROM publishers WHERE id=?', (old_id,)) self.conn.commit() def delete_publisher_using_id(self, old_id): self.conn.execute('DELETE FROM books_publishers_link\n WHERE publisher=?', (old_id,)) self.conn.execute('DELETE FROM publishers WHERE id=?', (old_id,)) self.conn.commit() def get_authors_with_ids(self): result = self.conn.get('SELECT id,name,sort FROM authors') if not result: return [] return result def set_sort_field_for_author(self, old_id, new_sort): self.conn.execute('UPDATE authors SET sort=? WHERE id=?', (new_sort.strip(), old_id)) self.conn.commit() bks = self.conn.get('SELECT book from books_authors_link WHERE author=?', (old_id,)) for book_id, in bks: ss = self.author_sort_from_book(book_id, index_is_id = True) self.set_author_sort(book_id, ss) def rename_author(self, old_id, new_name): new_name = new_name.replace(',', '|').strip() bks = self.conn.get('SELECT book from books_authors_link WHERE author=?', (old_id,)) books = [] for book_id, in bks: books.append(book_id) new_id = self.conn.get('SELECT id from authors WHERE name=?', (new_name,), all = False) if new_id is None or old_id == new_id: self.conn.execute('UPDATE authors SET name=? WHERE id=?', (new_name, old_id)) elif old_id == new_id: self.conn.execute('UPDATE authors SET name=? WHERE id=?', (new_name, old_id)) self.conn.commit() return new_id for book_id in books: authors = self.conn.get('\n SELECT author from books_authors_link\n WHERE book=?\n ORDER BY id', (book_id,)) for i, aut in enumerate(authors): authors[i] = None if aut[0] != old_id else new_id self.conn.execute('DELETE FROM books_authors_link\n WHERE book=?', (book_id,)) for aid in authors: try: self.conn.execute('\n INSERT INTO books_authors_link(book, author)\n VALUES (?,?)', (book_id, aid)) continue except IntegrityError: continue bks = self.conn.get('SELECT book FROM books_authors_link WHERE author=?', (old_id,)) self.conn.execute('DELETE FROM authors WHERE id=?', (old_id,)) self.conn.commit() for book_id in books: self.data.refresh_ids(self, [ book_id]) self.set_path(book_id, index_is_id = True) ss = self.author_sort_from_book(book_id, index_is_id = True) self.set_author_sort(book_id, ss) return new_id def get_tags(self, id): result = self.conn.get('SELECT name FROM tags WHERE id IN (SELECT tag FROM books_tags_link WHERE book=?)', (id,), all = True) if not result: return set([]) return []([ r[0] for r in result ]) def set_tags(self, id, tags, append = False, notify = True): if not append: self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,)) self.conn.execute('DELETE FROM tags WHERE (SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) < 1') otags = self.get_tags(id) tags = _[1] tags = [ _[2] if not isinstance(x, unicode) else x for x in tags ] tags = [ u' '.join(x.split()) for x in tags ] for tag in set(tags) - otags: tag = tag.strip() existing_tags = self.all_tags() lt = [ t.lower() for t in existing_tags ] try: idx = lt.index(tag.lower()) except ValueError: [] [] [] idx = -1 except: [] if not isinstance(tag, unicode) else [] if idx > -1: etag = existing_tags[idx] tid = self.conn.get('SELECT id FROM tags WHERE name=?', (etag,), all = False) if etag != tag: self.conn.execute('UPDATE tags SET name=? WHERE id=?', (tag, tid)) else: tid = self.conn.execute('INSERT INTO tags(name) VALUES(?)', (tag,)).lastrowid if not self.conn.get('SELECT book FROM books_tags_link WHERE book=? AND tag=?', (id, tid), all = False): self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)', (id, tid)) continue self.conn.commit() tags = ','.join(self.get_tags(id)) self.data.set(id, self.FIELD_MAP['tags'], tags, row_is_id = True) if notify: self.notify('metadata', [ id]) def unapply_tags(self, book_id, tags, notify = True): for tag in tags: id = self.conn.get('SELECT id FROM tags WHERE name=?', (tag,), all = False) if id: self.conn.execute('DELETE FROM books_tags_link WHERE tag=? AND book=?', (id, book_id)) continue self.conn.commit() self.data.refresh_ids(self, [ book_id]) if notify: self.notify('metadata', [ id]) def is_tag_used(self, tag): existing_tags = self.all_tags() lt = [ t.lower() for t in existing_tags ] try: lt.index(tag.lower()) return True except ValueError: [] [] [] return False def delete_tag(self, tag): existing_tags = self.all_tags() lt = [ t.lower() for t in existing_tags ] try: idx = lt.index(tag.lower()) except ValueError: [] [] [] idx = -1 except: [] def set_series(self, id, series, notify = True): self.conn.execute('DELETE FROM books_series_link WHERE book=?', (id,)) self.conn.execute('DELETE FROM series WHERE (SELECT COUNT(id) FROM books_series_link WHERE series=series.id) < 1') if series: if not isinstance(series, unicode): series = series.decode(preferred_encoding, 'replace') series = series.strip() series = u' '.join(series.split()) s = self.conn.get('SELECT id from series WHERE name=?', (series,), all = False) if s: aid = s else: aid = self.conn.execute('INSERT INTO series(name) VALUES (?)', (series,)).lastrowid self.conn.execute('INSERT INTO books_series_link(book, series) VALUES (?,?)', (id, aid)) self.conn.commit() self.data.set(id, self.FIELD_MAP['series'], series, row_is_id = True) if notify: self.notify('metadata', [ id]) def set_series_index(self, id, idx, notify = True): if idx is None: idx = 1 try: idx = float(idx) except: idx = 1 self.conn.execute('UPDATE books SET series_index=? WHERE id=?', (idx, id)) self.conn.commit() self.data.set(id, self.FIELD_MAP['series_index'], idx, row_is_id = True) if notify: self.notify('metadata', [ id]) def set_rating(self, id, rating, notify = True): rating = int(rating) self.conn.execute('DELETE FROM books_ratings_link WHERE book=?', (id,)) rat = self.conn.get('SELECT id FROM ratings WHERE rating=?', (rating,), all = False) rat = None if rat else self.conn.execute('INSERT INTO ratings(rating) VALUES (?)', (rating,)).lastrowid self.conn.execute('INSERT INTO books_ratings_link(book, rating) VALUES (?,?)', (id, rat)) self.conn.commit() self.data.set(id, self.FIELD_MAP['rating'], rating, row_is_id = True) if notify: self.notify('metadata', [ id]) def set_comment(self, id, text, notify = True): self.conn.execute('DELETE FROM comments WHERE book=?', (id,)) self.conn.execute('INSERT INTO comments(book,text) VALUES (?,?)', (id, text)) self.conn.commit() self.data.set(id, self.FIELD_MAP['comments'], text, row_is_id = True) if notify: self.notify('metadata', [ id]) def set_author_sort(self, id, sort, notify = True): self.conn.execute('UPDATE books SET author_sort=? WHERE id=?', (sort, id)) self.conn.commit() self.data.set(id, self.FIELD_MAP['author_sort'], sort, row_is_id = True) if notify: self.notify('metadata', [ id]) def set_isbn(self, id, isbn, notify = True): self.conn.execute('UPDATE books SET isbn=? WHERE id=?', (isbn, id)) self.conn.commit() self.data.set(id, self.FIELD_MAP['isbn'], isbn, row_is_id = True) if notify: self.notify('metadata', [ id]) def add_catalog(self, path, title): format = os.path.splitext(path)[1][1:].lower() try: stream = _[1] matches = self.data.get_matches('title', '=' + title) db_id = None if matches: db_id = list(matches)[0] if db_id is None: obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)', (title, 'calibre')) db_id = obj.lastrowid self.data.books_added([ db_id], self) self.set_path(db_id, index_is_id = True) self.conn.commit() try: mi = get_metadata(stream, format) except: mi = MetaInformation(title, [ 'calibre']) stream.seek(0) mi.title = title mi.authors = [ 'calibre'] mi.tags = [ _('Catalog')] mi.pubdate = mi.timestamp = utcnow() if format == 'mobi': mi.cover = None mi.cover_data = (None, None) self.set_metadata(db_id, mi) self.add_format(db_id, format, stream, index_is_id = True) finally: pass self.conn.commit() self.data.refresh_ids(self, [ db_id]) return db_id def add_news(self, path, arg): format = os.path.splitext(path)[1][1:].lower() stream = None if hasattr(path, 'read') else open(path, 'rb') stream.seek(0) mi = get_metadata(stream, format, use_libprs_metadata = False) stream.seek(0) mi.series_index = 1 mi.tags = [ _('News')] if arg['add_title_tag']: mi.tags += [ arg['title']] if arg['custom_tags']: mi.tags += arg['custom_tags'] obj = self.conn.execute('INSERT INTO books(title, author_sort) VALUES (?, ?)', (mi.title, mi.authors[0])) id = obj.lastrowid self.data.books_added([ id], self) self.set_path(id, index_is_id = True) self.conn.commit() if mi.pubdate is None: mi.pubdate = utcnow() if mi.timestamp is None: mi.timestamp = utcnow() self.set_metadata(id, mi) self.add_format(id, format, stream, index_is_id = True) if not hasattr(path, 'read'): stream.close() self.conn.commit() self.data.refresh_ids(self, [ id]) return id def run_import_plugins(self, path_or_stream, format): format = format.lower() if hasattr(path_or_stream, 'seek'): path_or_stream.seek(0) pt = PersistentTemporaryFile('_import_plugin.' + format) shutil.copyfileobj(path_or_stream, pt, 1048576) pt.close() path = pt.name else: path = path_or_stream return run_plugins_on_import(path, format) def create_book_entry(self, mi, cover = None, add_duplicates = True): if not add_duplicates and self.has_book(mi): return None series_index = self.has_book(mi) if mi.series_index is None else mi.series_index aus = None if mi.author_sort else self.author_sort_from_authors(mi.authors) title = mi.title if isinstance(aus, str): aus = aus.decode(preferred_encoding, 'replace') if isinstance(title, str): title = title.decode(preferred_encoding) obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)', (title, series_index, aus)) id = obj.lastrowid self.data.books_added([ id], self) self.set_path(id, True) self.conn.commit() if mi.timestamp is None: mi.timestamp = utcnow() if mi.pubdate is None: mi.pubdate = utcnow() self.set_metadata(id, mi) if cover is not None: try: self.set_cover(id, cover) traceback.print_exc() return id def add_books(self, paths, formats, metadata, add_duplicates = True): formats = iter(formats) metadata = iter(metadata) duplicates = [] ids = [] for path in paths: mi = metadata.next() format = formats.next() if not add_duplicates and self.has_book(mi): duplicates.append((path, format, mi)) continue series_index = None if mi.series_index is None else mi.series_index aus = None if mi.author_sort else self.author_sort_from_authors(mi.authors) title = mi.title if isinstance(aus, str): aus = aus.decode(preferred_encoding, 'replace') if isinstance(title, str): title = title.decode(preferred_encoding) obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)', (title, series_index, aus)) id = obj.lastrowid self.data.books_added([ id], self) ids.append(id) self.set_path(id, True) self.conn.commit() if mi.timestamp is None: mi.timestamp = utcnow() if mi.pubdate is None: mi.pubdate = utcnow() self.set_metadata(id, mi) npath = self.run_import_plugins(path, format) format = os.path.splitext(npath)[-1].lower().replace('.', '').upper() stream = open(npath, 'rb') format = check_ebook_format(stream, format) self.add_format(id, format, stream, index_is_id = True) stream.close() self.conn.commit() self.data.refresh_ids(self, ids) if duplicates: paths = list((lambda .0: for duplicate in .0: duplicate[0])(duplicates)) formats = list((lambda .0: for duplicate in .0: duplicate[1])(duplicates)) metadata = list((lambda .0: for duplicate in .0: duplicate[2])(duplicates)) return ((paths, formats, metadata), len(ids)) return (None, len(ids)) def import_book(self, mi, formats, notify = True): series_index = None if mi.series_index is None else mi.series_index if not mi.title: mi.title = _('Unknown') if not mi.authors: mi.authors = [ _('Unknown')] aus = None if mi.author_sort else self.author_sort_from_authors(mi.authors) if isinstance(aus, str): aus = aus.decode(preferred_encoding, 'replace') title = None if isinstance(mi.title, unicode) else mi.title.decode(preferred_encoding, 'replace') obj = self.conn.execute('INSERT INTO books(title, series_index, author_sort) VALUES (?, ?, ?)', (title, series_index, aus)) id = obj.lastrowid self.data.books_added([ id], self) self.set_path(id, True) if mi.timestamp is None: mi.timestamp = utcnow() if mi.pubdate is None: mi.pubdate = utcnow() self.set_metadata(id, mi, ignore_errors = True) for path in formats: ext = os.path.splitext(path)[1][1:].lower() if ext == 'opf': continue self.add_format_with_hooks(id, ext, path, index_is_id = True) self.conn.commit() self.data.refresh_ids(self, [ id]) if notify: self.notify('add', [ id]) def get_top_level_move_items(self): items = set(os.listdir(self.library_path)) paths = set([]) for x in self.data.universal_set(): path = self.path(x, index_is_id = True) path = path.split(os.sep)[0] paths.add(path) paths.add('metadata.db') path_map = { } for x in paths: path_map[x] = x items = items.intersection(paths) return (items, path_map) def move_library_to(self, newloc, progress = (lambda x: x)): if not os.path.exists(newloc): os.makedirs(newloc) old_dirs = set([]) (items, path_map) = self.get_top_level_move_items() for x in items: src = os.path.join(self.library_path, x) dest = os.path.join(newloc, path_map[x]) if os.path.isdir(src): if os.path.exists(dest): shutil.rmtree(dest) shutil.copytree(src, dest) old_dirs.add(src) elif os.path.exists(dest): os.remove(dest) shutil.copyfile(src, dest) x = path_map[x] if not isinstance(x, unicode): x = x.decode(filesystem_encoding, 'replace') progress(x) dbpath = os.path.join(newloc, os.path.basename(self.dbpath)) opath = self.dbpath self.conn.close() self.library_path = newloc self.dbpath = dbpath self.connect() try: os.unlink(opath) except: pass for dir in old_dirs: try: shutil.rmtree(dir) continue continue def __iter__(self): for record in self.data._data: if record is not None: yield record continue def all_ids(self): x = self.FIELD_MAP['id'] for i in iter(self): yield i[x] def get_data_as_dict(self, prefix = None, authors_as_string = False, ids = None): if prefix is None: prefix = self.library_path FIELDS = set([ 'title', 'authors', 'author_sort', 'publisher', 'rating', 'timestamp', 'size', 'tags', 'comments', 'series', 'series_index', 'isbn', 'uuid', 'pubdate']) for x in self.custom_column_num_map: FIELDS.add(x) data = [] for record in self.data: if record is None: continue db_id = record[self.FIELD_MAP['id']] if ids is not None and db_id not in ids: continue x = { } for field in FIELDS: x[field] = record[self.FIELD_MAP[field]] data.append(x) x['id'] = db_id x['formats'] = [] if not x['authors']: x['authors'] = _('Unknown') x['authors'] = [ i.replace('|', ',') for i in x['authors'].split(',') ] x['tags'] = [] if x['tags'] else [] path = os.path.join(prefix, self.path(record[self.FIELD_MAP['id']], index_is_id = True)) x['cover'] = os.path.join(path, 'cover.jpg') formats = self.formats(record[self.FIELD_MAP['id']], index_is_id = True) if formats: for fmt in formats.split(','): path = self.format_abspath(x['id'], fmt, index_is_id = True) if path is None: continue if prefix != self.library_path: path = os.path.relpath(path, self.library_path) path = os.path.join(prefix, path) x['formats'].append(path) x['fmt_' + fmt.lower()] = path x['available_formats'] = [ i.upper() for i in formats.split(',') ] continue [] return data def migrate_old(self, db, progress): QCoreApplication = QCoreApplication import PyQt4.QtCore header = _(u'<p>Migrating old database to ebook library in %s<br><center>') % self.library_path progress.setValue(0) progress.setLabelText(header) QCoreApplication.processEvents() db.conn.row_factory = lambda cursor, row: tuple(row) db.conn.text_factory = lambda x: unicode(x, 'utf-8', 'replace') books = db.conn.get('SELECT id, title, sort, timestamp, series_index, author_sort, isbn FROM books ORDER BY id ASC') progress.setAutoReset(False) progress.setRange(0, len(books)) for book in books: self.conn.execute('INSERT INTO books(id, title, sort, timestamp, series_index, author_sort, isbn) VALUES(?, ?, ?, ?, ?, ?, ?, ?);', book) tables = '\nauthors ratings tags series books_tags_link\ncomments publishers\nbooks_authors_link conversion_options\nbooks_publishers_link\nbooks_ratings_link\nbooks_series_link feeds\n'.split() for table in tables: rows = db.conn.get('SELECT * FROM %s ORDER BY id ASC' % table) for row in rows: self.conn.execute('INSERT INTO %s VALUES(%s)' % (table, ','.join(repeat('?', len(row)))), row) self.conn.commit() self.refresh('timestamp', True) for i, book in enumerate(books): progress.setLabelText(header + _(u'Copying <b>%s</b>') % book[1]) id = book[0] self.set_path(id, True) formats = db.formats(id, index_is_id = True) if not formats: formats = [] else: formats = formats.split(',') for format in formats: data = db.format(id, format, index_is_id = True) if data: self.add_format(id, format, cStringIO.StringIO(data), index_is_id = True) continue cover = db.cover(id, index_is_id = True) if cover: self.set_cover(id, cover) progress.setValue(i + 1) self.conn.commit() progress.setLabelText(_('Compacting database')) self.vacuum() progress.reset() return len(books) def find_books_in_directory(self, dirpath, single_book_per_directory): dirpath = os.path.abspath(dirpath) if single_book_per_directory: formats = [] for path in os.listdir(dirpath): path = os.path.abspath(os.path.join(dirpath, path)) if os.path.isdir(path) or not os.access(path, os.R_OK): continue ext = os.path.splitext(path)[1] if not ext: continue ext = ext[1:].lower() if ext not in BOOK_EXTENSIONS and ext != 'opf': continue formats.append(path) yield formats else: books = { } for path in os.listdir(dirpath): path = os.path.abspath(os.path.join(dirpath, path)) if os.path.isdir(path) or not os.access(path, os.R_OK): continue ext = os.path.splitext(path)[1] if not ext: continue ext = ext[1:].lower() if ext not in BOOK_EXTENSIONS: continue key = os.path.splitext(path)[0] if not books.has_key(key): books[key] = [] books[key].append(path) for formats in books.values(): yield formats def import_book_directory_multiple(self, dirpath, callback = None): duplicates = [] for formats in self.find_books_in_directory(dirpath, False): mi = metadata_from_formats(formats) if mi.title is None: continue if self.has_book(mi): duplicates.append((mi, formats)) continue self.import_book(mi, formats) if callable(callback): if callback(mi.title): break callback(mi.title) return duplicates def import_book_directory(self, dirpath, callback = None): dirpath = os.path.abspath(dirpath) formats = self.find_books_in_directory(dirpath, True) formats = list(formats)[0] if not formats: return None mi = metadata_from_formats(formats) if mi.title is None: return None if self.has_book(mi): return [ (mi, formats)] self.import_book(mi, formats) def recursive_import(self, root, single_book_per_directory = True, callback = None): root = os.path.abspath(root) duplicates = [] for dirpath in os.walk(root): res = None if single_book_per_directory else self.import_book_directory_multiple(dirpath[0], callback = callback) if res is not None: duplicates.extend(res) if callable(callback): if callback(''): break callback('') return duplicates def get_custom_recipes(self): for id, title, script in self.conn.get('SELECT id,title,script FROM feeds'): yield (id, title, script) def check_integrity(self, callback): callback(0, _('Checking SQL integrity...')) self.clean() user_version = self.user_version sql = '\n'.join(self.conn.dump()) self.conn.close() dest = self.dbpath + '.tmp' if os.path.exists(dest): os.remove(dest) conn = None try: ndb = DBThread(dest, None) ndb.connect() conn = ndb.conn conn.execute('create table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)') conn.commit() conn.executescript(sql) conn.commit() conn.execute('pragma user_version=%d' % user_version) conn.commit() conn.execute('drop table temp_sequence') conn.commit() conn.close() except: if conn is not None: try: conn.close() if os.path.exists(dest): os.remove(dest) raise os.remove(self.dbpath) shutil.copyfile(dest, self.dbpath) self.connect() self.field_metadata.remove_dynamic_categories() self.field_metadata.remove_custom_fields() self.initialize_dynamic() self.refresh() if os.path.exists(dest): os.remove(dest) callback(0.1, _('Checking for missing files.')) bad = { } us = self.data.universal_set() total = float(len(us)) for i, id in enumerate(us): formats = self.data.get(id, self.FIELD_MAP['formats'], row_is_id = True) actual_formats = self.formats(id, index_is_id = True) for fmt in formats: if fmt in actual_formats: continue if id not in bad: bad[id] = [] bad[id].append(fmt) callback(0.1 + 0.9 * (1 + i) / total, _('Checked id') + ' %d' % id) for id in bad: for fmt in bad[id]: self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, fmt.upper())) self.conn.commit() self.refresh_ids(list(bad.keys())) return bad