mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
The formatter functions database code we have been discussing
This commit is contained in:
parent
288a22d438
commit
f0677655f8
@ -140,9 +140,11 @@ class Cache:
|
|||||||
EventType = EventType
|
EventType = EventType
|
||||||
fts_indexing_sleep_time = 4 # seconds
|
fts_indexing_sleep_time = 4 # seconds
|
||||||
|
|
||||||
def __init__(self, backend):
|
def __init__(self, backend, library_database_instance=None):
|
||||||
self.shutting_down = False
|
self.shutting_down = False
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
|
self.library_database_instance = (None if library_database_instance is None else
|
||||||
|
weakref.ref(library_database_instance))
|
||||||
self.event_dispatcher = EventDispatcher()
|
self.event_dispatcher = EventDispatcher()
|
||||||
self.fields = {}
|
self.fields = {}
|
||||||
self.composites = {}
|
self.composites = {}
|
||||||
|
@ -188,7 +188,7 @@ class LibraryDatabase:
|
|||||||
read_only=read_only, restore_all_prefs=restore_all_prefs,
|
read_only=read_only, restore_all_prefs=restore_all_prefs,
|
||||||
progress_callback=progress_callback,
|
progress_callback=progress_callback,
|
||||||
load_user_formatter_functions=not is_second_db)
|
load_user_formatter_functions=not is_second_db)
|
||||||
cache = self.new_api = Cache(backend)
|
cache = self.new_api = Cache(backend, library_database_instance=self)
|
||||||
cache.init()
|
cache.init()
|
||||||
self.data = View(cache)
|
self.data = View(cache)
|
||||||
self.id = self.data.index_to_id
|
self.id = self.data.index_to_id
|
||||||
|
@ -15,6 +15,7 @@ from math import modf
|
|||||||
from calibre import prints
|
from calibre import prints
|
||||||
from calibre.constants import DEBUG
|
from calibre.constants import DEBUG
|
||||||
from calibre.ebooks.metadata.book.base import field_metadata
|
from calibre.ebooks.metadata.book.base import field_metadata
|
||||||
|
from calibre.utils.config import tweaks
|
||||||
from calibre.utils.formatter_functions import formatter_functions
|
from calibre.utils.formatter_functions import formatter_functions
|
||||||
from calibre.utils.icu import strcmp
|
from calibre.utils.icu import strcmp
|
||||||
from polyglot.builtins import error_message
|
from polyglot.builtins import error_message
|
||||||
@ -1697,8 +1698,9 @@ class TemplateFormatter(string.Formatter):
|
|||||||
except StopException as e:
|
except StopException as e:
|
||||||
ans = error_message(e)
|
ans = error_message(e)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if DEBUG: # and getattr(e, 'is_locking_error', False):
|
if DEBUG:
|
||||||
traceback.print_exc()
|
if tweaks.get('show_stack_traces_in_formatter', True):
|
||||||
|
traceback.print_exc()
|
||||||
if column_name:
|
if column_name:
|
||||||
prints('Error evaluating column named:', column_name)
|
prints('Error evaluating column named:', column_name)
|
||||||
ans = error_value + ' ' + error_message(e)
|
ans = error_value + ' ' + error_message(e)
|
||||||
|
@ -12,10 +12,10 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
|||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import inspect, re, traceback, numbers
|
import inspect, re, traceback, numbers
|
||||||
|
from contextlib import contextmanager, suppress
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from math import trunc, floor, ceil, modf
|
from math import trunc, floor, ceil, modf
|
||||||
from contextlib import suppress
|
|
||||||
|
|
||||||
from calibre import human_readable, prints, prepare_string_for_xml
|
from calibre import human_readable, prints, prepare_string_for_xml
|
||||||
from calibre.constants import DEBUG
|
from calibre.constants import DEBUG
|
||||||
@ -77,6 +77,8 @@ class FormatterFunctions:
|
|||||||
# Change the body of the template function to one that will
|
# Change the body of the template function to one that will
|
||||||
# return an error message. Also change the arg count to
|
# return an error message. Also change the arg count to
|
||||||
# -1 (variable) to avoid template compilation errors
|
# -1 (variable) to avoid template compilation errors
|
||||||
|
if DEBUG:
|
||||||
|
print(f'attempt to replace formatter function {f.name} with a different body')
|
||||||
replace = True
|
replace = True
|
||||||
func = [cls.name, '', -1, self.error_function_body.format(cls.name)]
|
func = [cls.name, '', -1, self.error_function_body.format(cls.name)]
|
||||||
cls = compile_user_function(*func)
|
cls = compile_user_function(*func)
|
||||||
@ -142,6 +144,28 @@ class FormatterFunction:
|
|||||||
if isinstance(ret, (numbers.Number, bool)):
|
if isinstance(ret, (numbers.Number, bool)):
|
||||||
return str(ret)
|
return str(ret)
|
||||||
|
|
||||||
|
def only_in_gui_error(self):
|
||||||
|
raise ValueError(_('The function {} can be used only in the GUI').format(self.name))
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def get_database(self, mi):
|
||||||
|
proxy = mi.get('_proxy_metadata', None)
|
||||||
|
if proxy is None:
|
||||||
|
self.only_in_gui_error()
|
||||||
|
wr = proxy.get('_db', None)
|
||||||
|
if wr is None:
|
||||||
|
raise ValueError(_('In function {}: The database has been closed').format(self.name))
|
||||||
|
cache = wr()
|
||||||
|
if cache is None:
|
||||||
|
raise ValueError(_('In function {}: The database has been closed').format(self.name))
|
||||||
|
wr = getattr(cache, 'library_database_instance', None)
|
||||||
|
if wr is None:
|
||||||
|
self.only_in_gui_error()
|
||||||
|
db = wr()
|
||||||
|
if db is None:
|
||||||
|
raise ValueError(_('In function {}: The database has been closed').format(self.name))
|
||||||
|
yield db
|
||||||
|
|
||||||
|
|
||||||
class BuiltinFormatterFunction(FormatterFunction):
|
class BuiltinFormatterFunction(FormatterFunction):
|
||||||
|
|
||||||
@ -928,7 +952,7 @@ class BuiltinApproximateFormats(BuiltinFormatterFunction):
|
|||||||
return ''
|
return ''
|
||||||
data = sorted(fmt_data)
|
data = sorted(fmt_data)
|
||||||
return ','.join(v.upper() for v in data)
|
return ','.join(v.upper() for v in data)
|
||||||
return _('This function can be used only in the GUI')
|
self.only_in_gui_error()
|
||||||
|
|
||||||
|
|
||||||
class BuiltinFormatsModtimes(BuiltinFormatterFunction):
|
class BuiltinFormatsModtimes(BuiltinFormatterFunction):
|
||||||
@ -1236,7 +1260,7 @@ class BuiltinBooksize(BuiltinFormatterFunction):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return ''
|
return ''
|
||||||
return _('This function can be used only in the GUI')
|
self.only_in_gui_error()
|
||||||
|
|
||||||
|
|
||||||
class BuiltinOndevice(BuiltinFormatterFunction):
|
class BuiltinOndevice(BuiltinFormatterFunction):
|
||||||
@ -1255,7 +1279,7 @@ class BuiltinOndevice(BuiltinFormatterFunction):
|
|||||||
if mi._proxy_metadata.ondevice_col:
|
if mi._proxy_metadata.ondevice_col:
|
||||||
return _('Yes')
|
return _('Yes')
|
||||||
return ''
|
return ''
|
||||||
return _('This function can be used only in the GUI')
|
self.only_in_gui_error()
|
||||||
|
|
||||||
|
|
||||||
class BuiltinAnnotationCount(BuiltinFormatterFunction):
|
class BuiltinAnnotationCount(BuiltinFormatterFunction):
|
||||||
@ -1267,11 +1291,9 @@ class BuiltinAnnotationCount(BuiltinFormatterFunction):
|
|||||||
'This function works only in the GUI.')
|
'This function works only in the GUI.')
|
||||||
|
|
||||||
def evaluate(self, formatter, kwargs, mi, locals):
|
def evaluate(self, formatter, kwargs, mi, locals):
|
||||||
with suppress(Exception):
|
with self.get_database(mi) as db:
|
||||||
from calibre.gui2.ui import get_gui
|
c = db.new_api.annotation_count_for_book(mi.id)
|
||||||
c = get_gui().current_db.new_api.annotation_count_for_book(mi.id)
|
|
||||||
return '' if c == 0 else str(c)
|
return '' if c == 0 else str(c)
|
||||||
return _('This function can be used only in the GUI')
|
|
||||||
|
|
||||||
|
|
||||||
class BuiltinIsMarked(BuiltinFormatterFunction):
|
class BuiltinIsMarked(BuiltinFormatterFunction):
|
||||||
@ -1284,11 +1306,9 @@ class BuiltinIsMarked(BuiltinFormatterFunction):
|
|||||||
"marks. Returns '' if the book is not marked.")
|
"marks. Returns '' if the book is not marked.")
|
||||||
|
|
||||||
def evaluate(self, formatter, kwargs, mi, locals):
|
def evaluate(self, formatter, kwargs, mi, locals):
|
||||||
with suppress(Exception):
|
with self.get_database(mi) as db:
|
||||||
from calibre.gui2.ui import get_gui
|
c = db.data.get_marked(mi.id)
|
||||||
c = get_gui().current_db.data.get_marked(mi.id)
|
|
||||||
return c if c else ''
|
return c if c else ''
|
||||||
return _('This function can be used only in the GUI')
|
|
||||||
|
|
||||||
|
|
||||||
class BuiltinSeriesSort(BuiltinFormatterFunction):
|
class BuiltinSeriesSort(BuiltinFormatterFunction):
|
||||||
@ -1850,14 +1870,12 @@ class BuiltinVirtualLibraries(BuiltinFormatterFunction):
|
|||||||
'column\'s value in your save/send templates')
|
'column\'s value in your save/send templates')
|
||||||
|
|
||||||
def evaluate(self, formatter, kwargs, mi, locals_):
|
def evaluate(self, formatter, kwargs, mi, locals_):
|
||||||
with suppress(Exception):
|
with self.get_database(mi) as db:
|
||||||
try:
|
try:
|
||||||
from calibre.gui2.ui import get_gui
|
a = db.data.get_virtual_libraries_for_books((mi.id,))
|
||||||
a = get_gui().current_db.data.get_virtual_libraries_for_books((mi.id,))
|
|
||||||
return ', '.join(a[mi.id])
|
return ', '.join(a[mi.id])
|
||||||
except ValueError as v:
|
except ValueError as v:
|
||||||
return str(v)
|
return str(v)
|
||||||
return _('This function can be used only in the GUI')
|
|
||||||
|
|
||||||
|
|
||||||
class BuiltinCurrentVirtualLibraryName(BuiltinFormatterFunction):
|
class BuiltinCurrentVirtualLibraryName(BuiltinFormatterFunction):
|
||||||
@ -1870,10 +1888,8 @@ class BuiltinCurrentVirtualLibraryName(BuiltinFormatterFunction):
|
|||||||
'Example: "program: current_virtual_library_name()".')
|
'Example: "program: current_virtual_library_name()".')
|
||||||
|
|
||||||
def evaluate(self, formatter, kwargs, mi, locals):
|
def evaluate(self, formatter, kwargs, mi, locals):
|
||||||
with suppress(Exception):
|
with self.get_database(mi) as db:
|
||||||
from calibre.gui2.ui import get_gui
|
return db.data.get_base_restriction_name()
|
||||||
return get_gui().current_db.data.get_base_restriction_name()
|
|
||||||
return _('This function can be used only in the GUI')
|
|
||||||
|
|
||||||
|
|
||||||
class BuiltinUserCategories(BuiltinFormatterFunction):
|
class BuiltinUserCategories(BuiltinFormatterFunction):
|
||||||
@ -1893,7 +1909,7 @@ class BuiltinUserCategories(BuiltinFormatterFunction):
|
|||||||
cats = {k for k, v in iteritems(mi._proxy_metadata.user_categories) if v}
|
cats = {k for k, v in iteritems(mi._proxy_metadata.user_categories) if v}
|
||||||
cats = sorted(cats, key=sort_key)
|
cats = sorted(cats, key=sort_key)
|
||||||
return ', '.join(cats)
|
return ', '.join(cats)
|
||||||
return _('This function can be used only in the GUI')
|
self.only_in_gui_error()
|
||||||
|
|
||||||
|
|
||||||
class BuiltinTransliterate(BuiltinFormatterFunction):
|
class BuiltinTransliterate(BuiltinFormatterFunction):
|
||||||
@ -1934,7 +1950,7 @@ class BuiltinAuthorLinks(BuiltinFormatterFunction):
|
|||||||
return ''
|
return ''
|
||||||
names = sorted(link_data.keys(), key=sort_key)
|
names = sorted(link_data.keys(), key=sort_key)
|
||||||
return pair_sep.join(n + val_sep + link_data[n] for n in names)
|
return pair_sep.join(n + val_sep + link_data[n] for n in names)
|
||||||
return _('This function can be used only in the GUI')
|
self.only_in_gui_error()
|
||||||
|
|
||||||
|
|
||||||
class BuiltinAuthorSorts(BuiltinFormatterFunction):
|
class BuiltinAuthorSorts(BuiltinFormatterFunction):
|
||||||
@ -1970,6 +1986,8 @@ class BuiltinConnectedDeviceName(BuiltinFormatterFunction):
|
|||||||
"'carda' and 'cardb'. This function works only in the GUI.")
|
"'carda' and 'cardb'. This function works only in the GUI.")
|
||||||
|
|
||||||
def evaluate(self, formatter, kwargs, mi, locals, storage_location):
|
def evaluate(self, formatter, kwargs, mi, locals, storage_location):
|
||||||
|
# We can't use get_database() here because we need the device manager.
|
||||||
|
# In other words, the function really does need the GUI
|
||||||
with suppress(Exception):
|
with suppress(Exception):
|
||||||
# Do the import here so that we don't entangle the GUI when using
|
# Do the import here so that we don't entangle the GUI when using
|
||||||
# command line functions
|
# command line functions
|
||||||
@ -1989,7 +2007,7 @@ class BuiltinConnectedDeviceName(BuiltinFormatterFunction):
|
|||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise
|
raise
|
||||||
return _('This function can be used only in the GUI')
|
self.only_in_gui_error()
|
||||||
|
|
||||||
|
|
||||||
class BuiltinConnectedDeviceUUID(BuiltinFormatterFunction):
|
class BuiltinConnectedDeviceUUID(BuiltinFormatterFunction):
|
||||||
@ -2004,6 +2022,8 @@ class BuiltinConnectedDeviceUUID(BuiltinFormatterFunction):
|
|||||||
"the GUI.")
|
"the GUI.")
|
||||||
|
|
||||||
def evaluate(self, formatter, kwargs, mi, locals, storage_location):
|
def evaluate(self, formatter, kwargs, mi, locals, storage_location):
|
||||||
|
# We can't use get_database() here because we need the device manager.
|
||||||
|
# In other words, the function really does need the GUI
|
||||||
with suppress(Exception):
|
with suppress(Exception):
|
||||||
# Do the import here so that we don't entangle the GUI when using
|
# Do the import here so that we don't entangle the GUI when using
|
||||||
# command line functions
|
# command line functions
|
||||||
@ -2023,7 +2043,7 @@ class BuiltinConnectedDeviceUUID(BuiltinFormatterFunction):
|
|||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise
|
raise
|
||||||
return _('This function can be used only in the GUI')
|
self.only_in_gui_error()
|
||||||
|
|
||||||
|
|
||||||
class BuiltinCheckYesNo(BuiltinFormatterFunction):
|
class BuiltinCheckYesNo(BuiltinFormatterFunction):
|
||||||
@ -2044,6 +2064,10 @@ class BuiltinCheckYesNo(BuiltinFormatterFunction):
|
|||||||
'is usually used by the test() or is_empty() functions.')
|
'is usually used by the test() or is_empty() functions.')
|
||||||
|
|
||||||
def evaluate(self, formatter, kwargs, mi, locals, field, is_undefined, is_false, is_true):
|
def evaluate(self, formatter, kwargs, mi, locals, field, is_undefined, is_false, is_true):
|
||||||
|
# 'field' is a lookup name, not a value
|
||||||
|
with self.get_database(mi) as db:
|
||||||
|
if field not in db.field_metadata:
|
||||||
|
raise ValueError(_("The column {} doesn't exist").format(field))
|
||||||
res = getattr(mi, field, None)
|
res = getattr(mi, field, None)
|
||||||
if res is None:
|
if res is None:
|
||||||
if is_undefined == '1':
|
if is_undefined == '1':
|
||||||
@ -2239,15 +2263,13 @@ class BuiltinBookCount(BuiltinFormatterFunction):
|
|||||||
if (not tweaks.get('allow_template_database_functions_in_composites', False) and
|
if (not tweaks.get('allow_template_database_functions_in_composites', False) and
|
||||||
formatter.global_vars.get(rendering_composite_name, None)):
|
formatter.global_vars.get(rendering_composite_name, None)):
|
||||||
raise ValueError(_('The book_count() function cannot be used in a composite column'))
|
raise ValueError(_('The book_count() function cannot be used in a composite column'))
|
||||||
with suppress(Exception):
|
with self.get_database(mi) as db:
|
||||||
try:
|
try:
|
||||||
from calibre.gui2.ui import get_gui
|
ids = db.search_getting_ids(query, None, use_virtual_library=use_vl != '0')
|
||||||
ids = get_gui().current_db.search_getting_ids(query, None,
|
|
||||||
use_virtual_library=use_vl != '0')
|
|
||||||
return len(ids)
|
return len(ids)
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return _('This function can be used only in the GUI')
|
self.only_in_gui_error()
|
||||||
|
|
||||||
|
|
||||||
class BuiltinBookValues(BuiltinFormatterFunction):
|
class BuiltinBookValues(BuiltinFormatterFunction):
|
||||||
@ -2265,25 +2287,21 @@ class BuiltinBookValues(BuiltinFormatterFunction):
|
|||||||
if (not tweaks.get('allow_template_database_functions_in_composites', False) and
|
if (not tweaks.get('allow_template_database_functions_in_composites', False) and
|
||||||
formatter.global_vars.get(rendering_composite_name, None)):
|
formatter.global_vars.get(rendering_composite_name, None)):
|
||||||
raise ValueError(_('The book_values() function cannot be used in a composite column'))
|
raise ValueError(_('The book_values() function cannot be used in a composite column'))
|
||||||
try:
|
with self.get_database(mi) as db:
|
||||||
from calibre.gui2.ui import get_gui
|
if column not in db.field_metadata:
|
||||||
db = get_gui().current_db
|
raise ValueError(_("The column {} doesn't exist").format(column))
|
||||||
except:
|
try:
|
||||||
return _('This function can be used only in the GUI')
|
ids = db.search_getting_ids(query, None, use_virtual_library=use_vl != '0')
|
||||||
if column not in db.field_metadata:
|
s = set()
|
||||||
raise ValueError(_("The column {} doesn't exist").format(column))
|
for id_ in ids:
|
||||||
try:
|
f = db.new_api.get_proxy_metadata(id_).get(column, None)
|
||||||
ids = db.search_getting_ids(query, None, use_virtual_library=use_vl != '0')
|
if isinstance(f, (tuple, list)):
|
||||||
s = set()
|
s.update(f)
|
||||||
for id_ in ids:
|
elif f:
|
||||||
f = db.new_api.get_proxy_metadata(id_).get(column, None)
|
s.add(str(f))
|
||||||
if isinstance(f, (tuple, list)):
|
return sep.join(s)
|
||||||
s.update(f)
|
except Exception as e:
|
||||||
elif f:
|
raise ValueError(e)
|
||||||
s.add(str(f))
|
|
||||||
return sep.join(s)
|
|
||||||
except Exception as e:
|
|
||||||
raise ValueError(e)
|
|
||||||
|
|
||||||
|
|
||||||
_formatter_builtins = [
|
_formatter_builtins = [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user