diff --git a/manual/template_lang.rst b/manual/template_lang.rst index d059b7360e..29c23b0aa0 100644 --- a/manual/template_lang.rst +++ b/manual/template_lang.rst @@ -254,6 +254,7 @@ The following functions are available in addition to those described in single-f returns "yes" if the yes/no field ``"#bool"`` is either undefined (neither True nor False) or True. More than one of ``is_undefined``, ``is_false``, or ``is_true`` can be set to 1. This function is usually used by the ``test()`` or ``is_empty()`` functions. * ``ceiling(x)`` -- returns the smallest integer greater than or equal to x. Throws an exception if x is not a number. * ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. + * ``connected_device_name(storage_location)`` -- if a device is connected then return the device name, otherwise return the empty string. Each storage location on a device can have a different name. The location names are 'main', 'carda' and 'cardb'. This function works only in the GUI. * ``current_library_name()`` -- return the last name on the path to the current calibre library. This function can be called in template program mode using the template ``{:'current_library_name()'}``. * ``current_library_path()`` -- return the path to the current calibre library. This function can be called in template program mode using the template ``{:'current_library_path()'}``. * ``days_between(date1, date2)`` -- return the number of days between ``date1`` and ``date2``. The number is positive if ``date1`` is greater than ``date2``, otherwise negative. If either ``date1`` or ``date2`` are not dates, the function returns the empty string. diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 1fc01b1ca0..287b2bc40a 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -10,7 +10,7 @@ from threading import Thread, Event from PyQt5.Qt import ( QMenu, QAction, QActionGroup, QIcon, Qt, pyqtSignal, QDialog, QObject, QVBoxLayout, QDialogButtonBox, QCursor, QCoreApplication, - QApplication, QEventLoop) + QApplication, QEventLoop, QTimer) from calibre.customize.ui import (available_input_formats, available_output_formats, device_plugins, disabled_device_plugins) @@ -1100,7 +1100,12 @@ class DeviceMixin(object): # {{{ # Empty any device view information for v in dviews: v.set_database([]) - self.refresh_ondevice() + # Use a singleShot timer to ensure that the job event queue has + # emptied before the ondevice column is removed from the booklist. + # This deals with race conditions when repainting the booklist + # causing incorrect evaluation of the connected_device_name + # formatter function + QTimer.singleShot(0, self.refresh_ondevice) device_signals.device_connection_changed.emit(connected) def info_read(self, job): diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 34843fe08e..3628e1938e 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -1668,6 +1668,39 @@ class BuiltinAuthorSorts(BuiltinFormatterFunction): return val_sep.join(n for n in names) +class BuiltinConnectedDeviceName(BuiltinFormatterFunction): + name = 'connected_device_name' + arg_count = 1 + category = 'Get values from metadata' + __doc__ = doc = _("connected_device_name(storage_location) -- if a device is " + "connected then return the device name, otherwise return " + "the empty string. Each storage location on a device can " + "have a different name. The location names are 'main', " + "'carda' and 'cardb'. This function works only in the GUI.") + + def evaluate(self, formatter, kwargs, mi, locals, storage_location): + if hasattr(mi, '_proxy_metadata'): + # Do the import here so that we don't entangle the GUI when using + # command line functions + from calibre.gui2.ui import get_gui + info = get_gui().device_manager.get_current_device_information() + if info is None: + return '' + try: + if storage_location not in {'main', 'carda', 'cardb'}: + raise ValueError( + _('connected_device_name: invalid storage location "{0}"' + .format(storage_location))) + info = info['info'][4] + if storage_location not in info: + return '' + return info[storage_location]['device_name'] + except: + traceback.print_exc() + raise + return _('This function can be used only in the GUI') + + class BuiltinCheckYesNo(BuiltinFormatterFunction): name = 'check_yes_no' arg_count = 4 @@ -1754,7 +1787,7 @@ _formatter_builtins = [ BuiltinAdd(), BuiltinAnd(), BuiltinApproximateFormats(), BuiltinAssign(), BuiltinAuthorLinks(), BuiltinAuthorSorts(), BuiltinBooksize(), BuiltinCapitalize(), BuiltinCheckYesNo(), BuiltinCeiling(), - BuiltinCmp(), BuiltinContains(), + BuiltinCmp(), BuiltinConnectedDeviceName(), BuiltinContains(), BuiltinCount(), BuiltinCurrentLibraryName(), BuiltinCurrentLibraryPath(), BuiltinDaysBetween(), BuiltinDivide(), BuiltinEval(), BuiltinFirstNonEmpty(), BuiltinField(), BuiltinFinishFormatting(), BuiltinFirstMatchingCmp(), BuiltinFloor(),