diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index ee19f07644..59c8085d4b 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -526,7 +526,7 @@ class ResultCache(SearchQueryParser): self._map.sort(cmp=fcmp, reverse=not ascending) self._map_filtered = [id for id in self._map if id in self._map_filtered] - def search(self, query, return_matches = False): + def search(self, query, return_matches=False): if not query or not query.strip(): q = self.search_restriction else: diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index 6442db4a73..8a20e66a60 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -45,6 +45,7 @@ class CustomColumns(object): DROP TRIGGER IF EXISTS fkc_insert_{table}; DROP TRIGGER IF EXISTS fkc_delete_{table}; DROP VIEW IF EXISTS tag_browser_{table}; + DROP VIEW IF EXISTS tag_browser_filtered_{table}; DROP TABLE IF EXISTS {table}; DROP TABLE IF EXISTS {lt}; '''.format(table=table, lt=lt) @@ -137,7 +138,14 @@ class CustomColumns(object): 'comments': lambda x,d: adapt_text(x, {'is_multiple':False}), 'datetime' : adapt_datetime, 'text':adapt_text - } + } + + # Create Tag Browser categories for custom columns + for i, v in self.custom_column_num_map.items(): + if v['normalized']: + tn = 'custom_column_{0}'.format(i) + self.tag_browser_categories[tn] = [v['label'], 'value'] + def get_custom(self, idx, label=None, num=None, index_is_id=False): if label is not None: @@ -396,6 +404,13 @@ class CustomColumns(object): (SELECT COUNT(id) FROM {lt} WHERE value={table}.id) count FROM {table}; + CREATE VIEW tag_browser_filtered_{table} AS SELECT + id, + value, + (SELECT COUNT({lt}.id) FROM {lt} WHERE value={table}.id AND + books_list_filter(book)) count + FROM {table}; + '''.format(lt=lt, table=table), ] diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 623a29159f..fd4ca7aa6b 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -106,6 +106,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): 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 __init__(self, library_path, row_factory=False): if not os.path.exists(library_path): @@ -118,6 +119,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.dbpath) if isinstance(self.dbpath, unicode): self.dbpath = self.dbpath.encode(filesystem_encoding) + + self.tag_browser_categories = { + 'tags' : ['tag', 'name'], + 'series' : ['series', 'name'], + 'publishers': ['publisher', 'name'], + 'authors' : ['author', 'name'], + 'news' : ['news', 'name'], + } + self.connect() self.is_case_sensitive = not iswindows and not isosx and \ not os.path.exists(self.dbpath.replace('metadata.db', 'MeTAdAtA.dB')) @@ -125,6 +135,30 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.initialize_dynamic() def initialize_dynamic(self): + self.conn.executescript(u''' + CREATE TEMP VIEW IF NOT EXISTS tag_browser_news AS SELECT DISTINCT + id, + name, + (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count + FROM tags as x WHERE name!="{0}" AND id IN + (SELECT DISTINCT tag FROM books_tags_link WHERE book IN + (SELECT DISTINCT book FROM books_tags_link WHERE tag IN + (SELECT id FROM tags WHERE name="{0}"))); + '''.format(_('News'))) + + self.conn.executescript(u''' + CREATE TEMP VIEW IF NOT EXISTS tag_browser_filtered_news AS SELECT DISTINCT + id, + name, + (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count + FROM tags as x WHERE name!="{0}" AND id IN + (SELECT DISTINCT tag FROM books_tags_link WHERE book IN + (SELECT DISTINCT book FROM books_tags_link WHERE tag IN + (SELECT id FROM tags WHERE name="{0}"))); + '''.format(_('News'))) + self.conn.commit() + + CustomColumns.__init__(self) template = '''\ (SELECT {query} FROM books_{table}_link AS link INNER JOIN @@ -576,68 +610,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return self.conn.get('SELECT script FROM feeds WHERE id=?', (id,), all=False) def get_categories(self, sort_on_count=False, ids=None, icon_map=None): - - orig_category_columns = {'tags': ['tag', 'name'], - 'series': ['series', 'name'], - 'publishers': ['publisher', 'name'], - 'authors': ['author', 'name']} # 'news' is added below - cat_cols = {} - - def create_filtered_views(self, ids): - def create_tag_browser_view(table_name, column_name, view_column_name): - script = (''' - CREATE TEMP VIEW IF NOT EXISTS tag_browser_filtered_{tn} AS SELECT - id, - {vcn}, - (SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE {cn}={tn}.id and books_list_filter(book)) count - FROM {tn}; - '''.format(tn=table_name, cn=column_name, vcn=view_column_name)) - self.conn.executescript(script) - - self.cat_cols = {} - for tn,cn in orig_category_columns.iteritems(): - create_tag_browser_view(tn, cn[0], cn[1]) - cat_cols[tn] = cn - for i,v in self.custom_column_num_map.iteritems(): - if v['datatype'] == 'text': - tn = 'custom_column_{0}'.format(i) - create_tag_browser_view(tn, 'value', 'value') - cat_cols[tn] = [v['label'], 'value'] - cat_cols['news'] = ['news', 'name'] - - self.conn.executescript(u''' - CREATE TEMP VIEW IF NOT EXISTS tag_browser_news AS SELECT DISTINCT - id, - name, - (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id) count - FROM tags as x WHERE name!="{0}" AND id IN - (SELECT DISTINCT tag FROM books_tags_link WHERE book IN - (SELECT DISTINCT book FROM books_tags_link WHERE tag IN - (SELECT id FROM tags WHERE name="{0}"))); - '''.format(_('News'))) - self.conn.commit() - - self.conn.executescript(u''' - CREATE TEMP VIEW IF NOT EXISTS tag_browser_filtered_news AS SELECT DISTINCT - id, - name, - (SELECT COUNT(books_tags_link.id) FROM books_tags_link WHERE tag=x.id and books_list_filter(book)) count - FROM tags as x WHERE name!="{0}" AND id IN - (SELECT DISTINCT tag FROM books_tags_link WHERE book IN - (SELECT DISTINCT book FROM books_tags_link WHERE tag IN - (SELECT id FROM tags WHERE name="{0}"))); - '''.format(_('News'))) - self.conn.commit() - - if ids is not None: - s_ids = set(ids) - else: - s_ids = None - self.conn.create_function('books_list_filter', 1, lambda(id): 1 if id in s_ids else 0) - create_filtered_views(self, ids) + self.books_list_filter.change([] if not ids else ids) categories = {} - for tn,cn in cat_cols.iteritems(): + for tn, cn in self.tag_browser_categories.items(): if ids is None: query = 'SELECT id, {0}, count FROM tag_browser_{1}'.format(cn[1], tn) else: @@ -648,12 +624,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): query += ' ORDER BY {0} ASC'.format(cn[1]) data = self.conn.get(query) category = cn[0] - if category in icon_map: - icon = icon_map[category] - tooltip = '' - else: - icon = icon_map['*custom'] - tooltip = self.custom_column_label_map[category]['name'] + icon, tooltip = None, '' + if icon_map: + if category in icon_map: + icon = icon_map[category] + tooltip = '' + else: + icon = icon_map['*custom'] + tooltip = self.custom_column_label_map[category]['name'] if ids is None: # no filtering categories[category] = [Tag(r[1], count=r[2], id=r[0], icon=icon, tooltip = tooltip) for r in data] @@ -666,14 +644,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if ids is not None: count = self.conn.get('''SELECT COUNT(id) FROM data - WHERE format="%s" and books_list_filter(id)'''%fmt, + WHERE format="%s" AND books_list_filter(id)'''%fmt, all=False) else: count = self.conn.get('''SELECT COUNT(id) FROM data WHERE format="%s"'''%fmt, all=False) - categories['format'].append(Tag(fmt, count=count)) + if count > 0: + categories['format'].append(Tag(fmt, count=count)) if sort_on_count: categories['format'].sort(cmp=lambda x,y:cmp(x.count, y.count), @@ -1475,6 +1454,7 @@ books_series_link feeds conn = ndb.conn conn.execute('create table temp_sequence(id INTEGER PRIMARY KEY AUTOINCREMENT)') conn.commit() + conn.create_function(self.books_list_filter.name, 1, lambda x: 1) conn.executescript(sql) conn.commit() conn.execute('pragma user_version=%d'%user_version) diff --git a/src/calibre/library/schema_upgrades.py b/src/calibre/library/schema_upgrades.py index b5733723b4..d4b4d3f9ad 100644 --- a/src/calibre/library/schema_upgrades.py +++ b/src/calibre/library/schema_upgrades.py @@ -269,3 +269,22 @@ class SchemaUpgrade(object): CREATE INDEX IF NOT EXISTS formats_idx ON data (format); ''') + def upgrade_version_10(self): + 'Add restricted Tag Browser views' + def create_tag_browser_view(table_name, column_name, view_column_name): + script = (''' + DROP VIEW IF EXISTS tag_browser_filtered_{tn}; + CREATE VIEW tag_browser_filtered_{tn} AS SELECT + id, + {vcn}, + (SELECT COUNT(books_{tn}_link.id) FROM books_{tn}_link WHERE + {cn}={tn}.id AND books_list_filter(book)) count + FROM {tn}; + '''.format(tn=table_name, cn=column_name, vcn=view_column_name)) + self.conn.executescript(script) + + for tn, cn in self.tag_browser_categories.items(): + if tn != 'news': + create_tag_browser_view(tn, cn[0], cn[1]) + + diff --git a/src/calibre/library/sqlite.py b/src/calibre/library/sqlite.py index 9718cab872..755d8e64b4 100644 --- a/src/calibre/library/sqlite.py +++ b/src/calibre/library/sqlite.py @@ -36,6 +36,18 @@ def convert_bool(val): sqlite.register_adapter(bool, lambda x : 1 if x else 0) sqlite.register_converter('bool', convert_bool) +class DynamicFilter(object): + + def __init__(self, name): + self.name = name + self.ids = frozenset([]) + + def __call__(self, id_): + return int(id_ in self.ids) + + def change(self, ids): + self.ids = frozenset(ids) + class Concatenate(object): '''String concatenation aggregator for sqlite''' @@ -119,6 +131,13 @@ class DBThread(Thread): ok, res = True, '\n'.join(self.conn.iterdump()) except Exception, err: ok, res = False, (err, traceback.format_exc()) + elif func == 'create_dynamic_filter': + try: + f = DynamicFilter(args[0]) + self.conn.create_function(args[0], 1, f) + ok, res = True, f + except Exception, err: + ok, res = False, (err, traceback.format_exc()) else: func = getattr(self.conn, func) try: @@ -203,6 +222,9 @@ class ConnectionProxy(object): @proxy def dump(self): pass + @proxy + def create_dynamic_filter(self): pass + def connect(dbpath, row_factory=None): conn = ConnectionProxy(DBThread(dbpath, row_factory)) conn.proxy.start()