mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Speed up book list rendering by using the new api to get values
This commit is contained in:
parent
f2f411f939
commit
26fcfc70f1
@ -318,6 +318,16 @@ class Cache(object):
|
|||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
return default_value
|
return default_value
|
||||||
|
|
||||||
|
@read_api
|
||||||
|
def fast_field_for(self, field_obj, book_id, default_value=None):
|
||||||
|
' Same as field_for, except that it avoids the extra lookup to get the field object '
|
||||||
|
if field_obj.is_composite:
|
||||||
|
return field_obj.get_value_with_cache(book_id, partial(self._get_metadata, get_user_categories=False))
|
||||||
|
try:
|
||||||
|
return field_obj.for_book(book_id, default_value=default_value)
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
return default_value
|
||||||
|
|
||||||
@read_api
|
@read_api
|
||||||
def composite_for(self, name, book_id, mi=None, default_value=''):
|
def composite_for(self, name, book_id, mi=None, default_value=''):
|
||||||
try:
|
try:
|
||||||
|
@ -23,6 +23,7 @@ class Field(object):
|
|||||||
|
|
||||||
is_many = False
|
is_many = False
|
||||||
is_many_many = False
|
is_many_many = False
|
||||||
|
is_composite = False
|
||||||
|
|
||||||
def __init__(self, name, table):
|
def __init__(self, name, table):
|
||||||
self.name, self.table = name, table
|
self.name, self.table = name, table
|
||||||
@ -148,6 +149,8 @@ class OneToOneField(Field):
|
|||||||
|
|
||||||
class CompositeField(OneToOneField):
|
class CompositeField(OneToOneField):
|
||||||
|
|
||||||
|
is_composite = True
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
OneToOneField.__init__(self, *args, **kwargs)
|
OneToOneField.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
@ -229,6 +232,14 @@ class OnDeviceField(OneToOneField):
|
|||||||
self.is_multiple = False
|
self.is_multiple = False
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
self._lock = Lock()
|
self._lock = Lock()
|
||||||
|
self._metadata = {
|
||||||
|
'table':None, 'column':None, 'datatype':'text', 'is_multiple':{},
|
||||||
|
'kind':'field', 'name':_('On Device'), 'search_terms':['ondevice'],
|
||||||
|
'is_custom':False, 'is_category':False, 'is_csp': False, 'display':{}}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def metadata(self):
|
||||||
|
return self._metadata
|
||||||
|
|
||||||
def clear_caches(self, book_ids=None):
|
def clear_caches(self, book_ids=None):
|
||||||
with self._lock:
|
with self._lock:
|
||||||
@ -330,7 +341,6 @@ class ManyToManyField(Field):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
Field.__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, ())
|
||||||
|
@ -113,7 +113,7 @@ class SizeTable(OneToOneTable):
|
|||||||
for row in db.conn.execute(
|
for row in db.conn.execute(
|
||||||
'SELECT books.id, (SELECT MAX(uncompressed_size) FROM data '
|
'SELECT books.id, (SELECT MAX(uncompressed_size) FROM data '
|
||||||
'WHERE data.book=books.id) FROM books'):
|
'WHERE data.book=books.id) FROM books'):
|
||||||
self.book_col_map[row[0]] = self.unserialize(row[1])
|
self.book_col_map[row[0]] = self.unserialize(row[1] or 0)
|
||||||
|
|
||||||
def update_sizes(self, size_map):
|
def update_sizes(self, size_map):
|
||||||
self.book_col_map.update(size_map)
|
self.book_col_map.update(size_map)
|
||||||
|
@ -227,6 +227,9 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
elif col in self.custom_columns:
|
elif col in self.custom_columns:
|
||||||
self.headers[col] = self.custom_columns[col]['name']
|
self.headers[col] = self.custom_columns[col]['name']
|
||||||
|
|
||||||
|
if hasattr(self.db, 'new_api'):
|
||||||
|
self.build_new_data_convertors()
|
||||||
|
else:
|
||||||
self.build_data_convertors()
|
self.build_data_convertors()
|
||||||
self.reset()
|
self.reset()
|
||||||
self.database_changed.emit(db)
|
self.database_changed.emit(db)
|
||||||
@ -634,6 +637,112 @@ class BooksModel(QAbstractTableModel): # {{{
|
|||||||
img = self.default_image
|
img = self.default_image
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
def build_new_data_convertors(self):
|
||||||
|
|
||||||
|
def renderer(field, decorator=False):
|
||||||
|
idfunc = self.db.id
|
||||||
|
fffunc = self.db.new_api.fast_field_for
|
||||||
|
field_obj = self.db.new_api.fields[field]
|
||||||
|
m = field_obj.metadata.copy()
|
||||||
|
if 'display' not in m:
|
||||||
|
m['display'] = {}
|
||||||
|
dt = m['datatype']
|
||||||
|
|
||||||
|
if decorator == 'bool':
|
||||||
|
bt = self.db.new_api.pref('bools_are_tristate')
|
||||||
|
bn = self.bool_no_icon
|
||||||
|
by = self.bool_yes_icon
|
||||||
|
def func(idx):
|
||||||
|
val = force_to_bool(fffunc(field_obj, idfunc(idx)))
|
||||||
|
if val is None:
|
||||||
|
return NONE if bt else bn
|
||||||
|
return by if val else bn
|
||||||
|
elif field == 'size':
|
||||||
|
sz_mult = 1.0/(1024**2)
|
||||||
|
def func(idx):
|
||||||
|
val = fffunc(field_obj, idfunc(idx), default_value=0)
|
||||||
|
ans = u'%.1f' % (val * sz_mult)
|
||||||
|
if val > 0 and ans == u'0.0':
|
||||||
|
ans = u'<0.1'
|
||||||
|
return QVariant(ans)
|
||||||
|
elif field == 'languages':
|
||||||
|
def func(idx):
|
||||||
|
return QVariant(', '.join(calibre_langcode_to_name(x) for x in fffunc(field_obj, idfunc(idx))))
|
||||||
|
elif field == 'ondevice' and decorator:
|
||||||
|
by = self.bool_yes_icon
|
||||||
|
bb = self.bool_blank_icon
|
||||||
|
def func(idx):
|
||||||
|
return by if fffunc(field_obj, idfunc(idx)) else bb
|
||||||
|
elif dt in {'text', 'comments', 'composite', 'enumeration'}:
|
||||||
|
if m['is_multiple']:
|
||||||
|
jv = m['is_multiple']['list_to_ui']
|
||||||
|
do_sort = field == 'tags'
|
||||||
|
if do_sort:
|
||||||
|
def func(idx):
|
||||||
|
return QVariant(jv.join(sorted(fffunc(field_obj, idfunc(idx), default_value=()), key=sort_key)))
|
||||||
|
else:
|
||||||
|
def func(idx):
|
||||||
|
return QVariant(jv.join(fffunc(field_obj, idfunc(idx), default_value=())))
|
||||||
|
else:
|
||||||
|
if dt in {'text', 'composite', 'enumeration'} and m['display'].get('use_decorations', False):
|
||||||
|
def func(idx):
|
||||||
|
text = fffunc(field_obj, idfunc(idx))
|
||||||
|
return QVariant(text) if force_to_bool(text) is None else NONE
|
||||||
|
else:
|
||||||
|
def func(idx):
|
||||||
|
return QVariant(fffunc(field_obj, idfunc(idx), default_value=''))
|
||||||
|
elif dt == 'datetime':
|
||||||
|
def func(idx):
|
||||||
|
return QVariant(fffunc(field_obj, idfunc(idx), default_value=UNDEFINED_QDATETIME))
|
||||||
|
elif dt == 'rating':
|
||||||
|
def func(idx):
|
||||||
|
return QVariant(int(fffunc(field_obj, idfunc(idx), default_value=0)/2.0))
|
||||||
|
elif dt == 'series':
|
||||||
|
sidx_field = self.db.new_api.fields[field + '_index']
|
||||||
|
fffunc = self.db.new_api._fast_field_for
|
||||||
|
read_lock = self.db.new_api.read_lock
|
||||||
|
def func(idx):
|
||||||
|
book_id = idfunc(idx)
|
||||||
|
with read_lock:
|
||||||
|
series = fffunc(field_obj, book_id, default_value=False)
|
||||||
|
if series:
|
||||||
|
return QVariant('%s [%s]' % (series, fmt_sidx(fffunc(sidx_field, book_id, default_value=1.0))))
|
||||||
|
return NONE
|
||||||
|
elif dt in {'int', 'float'}:
|
||||||
|
fmt = m['display'].get('number_format', None)
|
||||||
|
def func(idx):
|
||||||
|
val = fffunc(field_obj, idfunc(idx))
|
||||||
|
if val is None:
|
||||||
|
return NONE
|
||||||
|
if fmt:
|
||||||
|
try:
|
||||||
|
return QVariant(fmt.format(val))
|
||||||
|
except (TypeError, ValueError, AttributeError, IndexError):
|
||||||
|
pass
|
||||||
|
return QVariant(val)
|
||||||
|
else:
|
||||||
|
def func(idx):
|
||||||
|
return NONE
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
self.dc = {f:renderer(f) for f in 'title authors size timestamp pubdate last_modified rating publisher tags series ondevice languages'.split()}
|
||||||
|
self.dc_decorator = {f:renderer(f, True) for f in ('ondevice',)}
|
||||||
|
|
||||||
|
for col in self.custom_columns:
|
||||||
|
self.dc[col] = renderer(col)
|
||||||
|
m = self.custom_columns[col]
|
||||||
|
dt = m['datatype']
|
||||||
|
mult = m['is_multiple']
|
||||||
|
if dt in {'text', 'composite', 'enumeration'} and not mult and m['display'].get('use_decorations', False):
|
||||||
|
self.dc_decorator[col] = renderer(col, 'bool')
|
||||||
|
elif dt == 'bool':
|
||||||
|
self.dc_decorator[col] = renderer(col, 'bool')
|
||||||
|
|
||||||
|
# build a index column to data converter map, to remove the string lookup in the data loop
|
||||||
|
self.column_to_dc_map = [self.dc[col] for col in self.column_map]
|
||||||
|
self.column_to_dc_decorator_map = [self.dc_decorator.get(col, None) for col in self.column_map]
|
||||||
|
|
||||||
def build_data_convertors(self):
|
def build_data_convertors(self):
|
||||||
def authors(r, idx=-1):
|
def authors(r, idx=-1):
|
||||||
au = self.db.data[r][idx]
|
au = self.db.data[r][idx]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user