mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 18:24:30 -04:00
New db: Implement sorting for all field types. Also allow use of field_for() with composite columns
This commit is contained in:
parent
5c9c46382f
commit
2290a06894
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import wraps
|
from functools import wraps, partial
|
||||||
|
|
||||||
from calibre.db.locking import create_locks, RecordLock
|
from calibre.db.locking import create_locks, RecordLock
|
||||||
from calibre.db.fields import create_field
|
from calibre.db.fields import create_field
|
||||||
@ -43,6 +43,7 @@ class Cache(object):
|
|||||||
def __init__(self, backend):
|
def __init__(self, backend):
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
self.fields = {}
|
self.fields = {}
|
||||||
|
self.composites = set()
|
||||||
self.read_lock, self.write_lock = create_locks()
|
self.read_lock, self.write_lock = create_locks()
|
||||||
self.record_lock = RecordLock(self.read_lock)
|
self.record_lock = RecordLock(self.read_lock)
|
||||||
self.format_metadata_cache = defaultdict(dict)
|
self.format_metadata_cache = defaultdict(dict)
|
||||||
@ -82,7 +83,7 @@ class Cache(object):
|
|||||||
if name and path:
|
if name and path:
|
||||||
return self.backend.format_abspath(book_id, fmt, name, path)
|
return self.backend.format_abspath(book_id, fmt, name, path)
|
||||||
|
|
||||||
def _get_metadata(self, book_id, get_user_categories=True):
|
def _get_metadata(self, book_id, get_user_categories=True): # {{{
|
||||||
mi = Metadata(None)
|
mi = Metadata(None)
|
||||||
author_ids = self._field_ids_for('authors', book_id)
|
author_ids = self._field_ids_for('authors', book_id)
|
||||||
aut_list = [self._author_data(i) for i in author_ids]
|
aut_list = [self._author_data(i) for i in author_ids]
|
||||||
@ -162,6 +163,7 @@ class Cache(object):
|
|||||||
mi.user_categories = user_cat_vals
|
mi.user_categories = user_cat_vals
|
||||||
|
|
||||||
return mi
|
return mi
|
||||||
|
# }}}
|
||||||
|
|
||||||
# Cache Layer API {{{
|
# Cache Layer API {{{
|
||||||
|
|
||||||
@ -175,6 +177,8 @@ class Cache(object):
|
|||||||
|
|
||||||
for field, table in self.backend.tables.iteritems():
|
for field, table in self.backend.tables.iteritems():
|
||||||
self.fields[field] = create_field(field, table)
|
self.fields[field] = create_field(field, table)
|
||||||
|
if table.metadata['datatype'] == 'composite':
|
||||||
|
self.composites.add(field)
|
||||||
|
|
||||||
self.fields['ondevice'] = create_field('ondevice', None)
|
self.fields['ondevice'] = create_field('ondevice', None)
|
||||||
|
|
||||||
@ -187,19 +191,26 @@ class Cache(object):
|
|||||||
|
|
||||||
The returned value for is_multiple fields are always tuples.
|
The returned value for is_multiple fields are always tuples.
|
||||||
'''
|
'''
|
||||||
|
if self.composites and name in self.composites:
|
||||||
|
return self.composite_for(name, book_id,
|
||||||
|
default_value=default_value)
|
||||||
try:
|
try:
|
||||||
return self.fields[name].for_book(book_id, default_value=default_value)
|
return self.fields[name].for_book(book_id, default_value=default_value)
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
return default_value
|
return default_value
|
||||||
|
|
||||||
@read_api
|
@read_api
|
||||||
def composite_for(self, name, book_id, mi, default_value=''):
|
def composite_for(self, name, book_id, mi=None, default_value=''):
|
||||||
try:
|
try:
|
||||||
f = self.fields[name]
|
f = self.fields[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return default_value
|
return default_value
|
||||||
|
|
||||||
f.render_composite(book_id, mi)
|
if mi is None:
|
||||||
|
return f.get_value_with_cache(book_id, partial(self._get_metadata,
|
||||||
|
get_user_categories=False))
|
||||||
|
else:
|
||||||
|
return f.render_composite(book_id, mi)
|
||||||
|
|
||||||
@read_api
|
@read_api
|
||||||
def field_ids_for(self, name, book_id):
|
def field_ids_for(self, name, book_id):
|
||||||
|
@ -2,12 +2,14 @@
|
|||||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
from __future__ import (unicode_literals, division, absolute_import,
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
print_function)
|
print_function)
|
||||||
|
from future_builtins import map
|
||||||
|
|
||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
from calibre.db.tables import ONE_ONE, MANY_ONE, MANY_MANY
|
from calibre.db.tables import ONE_ONE, MANY_ONE, MANY_MANY
|
||||||
|
from calibre.utils.icu import sort_key
|
||||||
|
|
||||||
class Field(object):
|
class Field(object):
|
||||||
|
|
||||||
@ -16,6 +18,8 @@ class Field(object):
|
|||||||
self.has_text_data = self.metadata['datatype'] in ('text', 'comments',
|
self.has_text_data = self.metadata['datatype'] in ('text', 'comments',
|
||||||
'series', 'enumeration')
|
'series', 'enumeration')
|
||||||
self.table_type = self.table.table_type
|
self.table_type = self.table.table_type
|
||||||
|
dt = self.metadata['datatype']
|
||||||
|
self._sort_key = (sort_key if dt == 'text' else lambda x: x)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def metadata(self):
|
def metadata(self):
|
||||||
@ -49,6 +53,18 @@ class Field(object):
|
|||||||
'''
|
'''
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def sort_books(self, get_metadata, all_book_ids, ascending=True):
|
||||||
|
'''
|
||||||
|
Sort books by this field. Returns a sorted list of book_ids
|
||||||
|
|
||||||
|
:param _get_metadata: A callable which when called with the book_id
|
||||||
|
returns the Metadata object for that book. Needed for sorting composite
|
||||||
|
columns.
|
||||||
|
|
||||||
|
:param all_book_ids: The set of ids for all books.
|
||||||
|
'''
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
class OneToOneField(Field):
|
class OneToOneField(Field):
|
||||||
|
|
||||||
def for_book(self, book_id, default_value=None):
|
def for_book(self, book_id, default_value=None):
|
||||||
@ -66,6 +82,10 @@ class OneToOneField(Field):
|
|||||||
def iter_book_ids(self):
|
def iter_book_ids(self):
|
||||||
return self.table.book_col_map.iterkeys()
|
return self.table.book_col_map.iterkeys()
|
||||||
|
|
||||||
|
def sort_books(self, get_metadata, all_book_ids, ascending=True):
|
||||||
|
return sorted(self.iter_book_ids(), reverse=not ascending,
|
||||||
|
key=lambda i: self._sort_key(self.book_col_map[i]))
|
||||||
|
|
||||||
class CompositeField(OneToOneField):
|
class CompositeField(OneToOneField):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@ -86,6 +106,19 @@ class CompositeField(OneToOneField):
|
|||||||
def pop_cache(self, book_id):
|
def pop_cache(self, book_id):
|
||||||
self._render_cache.pop(book_id, None)
|
self._render_cache.pop(book_id, None)
|
||||||
|
|
||||||
|
def get_value_with_cache(self, book_id, get_metadata):
|
||||||
|
ans = self._render_cache.get(book_id, None)
|
||||||
|
if ans is None:
|
||||||
|
mi = get_metadata(book_id)
|
||||||
|
ans = mi.get(self.metadata['label'])
|
||||||
|
self._render_cache[book_id] = ans
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def sort_books(self, get_metadata, all_book_ids, ascending=True):
|
||||||
|
return sorted(all_book_ids, reverse=not ascending,
|
||||||
|
key=lambda i: sort_key(self.get_value_with_cache(i,
|
||||||
|
get_metadata)))
|
||||||
|
|
||||||
class OnDeviceField(OneToOneField):
|
class OnDeviceField(OneToOneField):
|
||||||
|
|
||||||
def __init__(self, name, table):
|
def __init__(self, name, table):
|
||||||
@ -120,6 +153,10 @@ class OnDeviceField(OneToOneField):
|
|||||||
def iter_book_ids(self):
|
def iter_book_ids(self):
|
||||||
return iter(())
|
return iter(())
|
||||||
|
|
||||||
|
def sort_books(self, get_metadata, all_book_ids, ascending=True):
|
||||||
|
return sorted(all_book_ids, reverse=not ascending,
|
||||||
|
key=self.for_book)
|
||||||
|
|
||||||
class ManyToOneField(Field):
|
class ManyToOneField(Field):
|
||||||
|
|
||||||
def for_book(self, book_id, default_value=None):
|
def for_book(self, book_id, default_value=None):
|
||||||
@ -131,10 +168,10 @@ class ManyToOneField(Field):
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
def ids_for_book(self, book_id):
|
def ids_for_book(self, book_id):
|
||||||
ids = self.table.book_col_map.get(book_id, None)
|
id_ = self.table.book_col_map.get(book_id, None)
|
||||||
if ids is None:
|
if id_ is None:
|
||||||
return ()
|
return ()
|
||||||
return ids
|
return (id_,)
|
||||||
|
|
||||||
def books_for(self, item_id):
|
def books_for(self, item_id):
|
||||||
return self.table.col_book_map.get(item_id, ())
|
return self.table.col_book_map.get(item_id, ())
|
||||||
@ -142,8 +179,21 @@ class ManyToOneField(Field):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self.table.id_map.iterkeys()
|
return self.table.id_map.iterkeys()
|
||||||
|
|
||||||
|
def sort_books(self, get_metadata, all_book_ids, ascending=True):
|
||||||
|
ids = sorted(self.id_map,
|
||||||
|
key=lambda i:self._sort_key(self.id_map[i]))
|
||||||
|
sm = {id_ : idx for idx, id_ in enumerate(ids)}
|
||||||
|
return sorted(all_book_ids, reverse=not ascending,
|
||||||
|
key=lambda book_id : sm.get(
|
||||||
|
self.book_col_map.get(book_id, None),
|
||||||
|
-1))
|
||||||
|
|
||||||
class ManyToManyField(Field):
|
class ManyToManyField(Field):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
Field.__init__(self, *args, **kwargs)
|
||||||
|
self.alphabetical_sort = self.name != 'authors'
|
||||||
|
|
||||||
def for_book(self, book_id, default_value=None):
|
def for_book(self, book_id, default_value=None):
|
||||||
ids = self.table.book_col_map.get(book_id, ())
|
ids = self.table.book_col_map.get(book_id, ())
|
||||||
if ids:
|
if ids:
|
||||||
@ -161,6 +211,20 @@ class ManyToManyField(Field):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return self.table.id_map.iterkeys()
|
return self.table.id_map.iterkeys()
|
||||||
|
|
||||||
|
def sort_books(self, get_metadata, all_book_ids, ascending=True):
|
||||||
|
ids = sorted(self.id_map,
|
||||||
|
key=lambda i:self._sort_key(self.id_map[i]))
|
||||||
|
sm = {id_ : idx for idx, id_ in enumerate(ids)}
|
||||||
|
|
||||||
|
def sort_key_for_book(book_id):
|
||||||
|
item_ids = self.table.book_col_map.get(book_id, ())
|
||||||
|
if self.alphabetical_sort:
|
||||||
|
item_ids = sorted(item_ids, key=sm.get)
|
||||||
|
return tuple(map(sm.get, item_ids))
|
||||||
|
|
||||||
|
return sorted(all_book_ids, reverse=not ascending,
|
||||||
|
key=sort_key_for_book)
|
||||||
|
|
||||||
class AuthorsField(ManyToManyField):
|
class AuthorsField(ManyToManyField):
|
||||||
|
|
||||||
def author_data(self, author_id):
|
def author_data(self, author_id):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user