diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index d6f134f724..07aee5c6fa 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -81,3 +81,11 @@ title_series_sorting = 'library_order' # strictly_alphabetic, it would remain "The Client". save_template_title_series_sorting = 'library_order' +# Specify a folder that calibre should connect to at startup using +# connect_to_folder. This must be a full path to the folder. If the folder does +# not exist when calibre starts, it is ignored. If there are '\' characters in +# the path (such as in Windows paths), you must double them. +# Examples: +# auto_connect_to_folder = 'C:\\Users\\someone\\Desktop\\testlib' +# auto_connect_to_folder = '/home/dropbox/My Dropbox/someone/library' +auto_connect_to_folder = '' \ No newline at end of file diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index a9beb317a2..bc79a3c9de 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -33,7 +33,7 @@ from calibre.devices.apple.driver import ITUNES_ASYNC from calibre.devices.folder_device.driver import FOLDER_DEVICE from calibre.ebooks.metadata.meta import set_metadata from calibre.constants import DEBUG -from calibre.utils.config import prefs +from calibre.utils.config import prefs, tweaks # }}} @@ -613,6 +613,8 @@ class DeviceMixin(object): # {{{ self.device_manager = DeviceManager(Dispatcher(self.device_detected), self.job_manager, Dispatcher(self.status_bar.show_message)) self.device_manager.start() + if tweaks['auto_connect_to_folder']: + self.connect_to_folder_named(tweaks['auto_connect_to_folder']) def set_default_thumbnail(self, height): r = QSvgRenderer(I('book.svg')) @@ -624,6 +626,11 @@ class DeviceMixin(object): # {{{ self.default_thumbnail = (pixmap.width(), pixmap.height(), pixmap_to_data(pixmap)) + def connect_to_folder_named(self, folder): + if os.path.exists(folder) and os.path.isdir(folder): + self.device_manager.mount_device(kls=FOLDER_DEVICE, kind='folder', + path=folder) + def connect_to_folder(self): dir = choose_dir(self, 'Select Device Folder', _('Select folder to open as device')) diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py index 20c7843aa8..f183d0871b 100644 --- a/src/calibre/gui2/dialogs/config/__init__.py +++ b/src/calibre/gui2/dialogs/config/__init__.py @@ -36,6 +36,7 @@ from calibre.gui2.convert.structure_detection import StructureDetectionWidget from calibre.ebooks.conversion.plumber import Plumber from calibre.utils.logging import Log from calibre.gui2.convert.toc import TOCWidget +from calibre.utils.search_query_parser import saved_searches class ConfigTabs(QTabWidget): @@ -493,6 +494,14 @@ class ConfigDialog(ResizableDialog, Ui_Dialog): if x == config['gui_layout']: li = i self.opt_gui_layout.setCurrentIndex(li) + restrictions = sorted(saved_searches().names(), + cmp=lambda x,y: cmp(x.lower(), y.lower())) + restrictions.insert(0, '') + for x in ('gui', 'cs'): + w = getattr(self, 'opt_%s_restriction'%x) + w.addItems(restrictions) + idx = w.findText(self.db.prefs.get(x+'_restriction', '')) + w.setCurrentIndex(0 if idx < 0 else idx) self.opt_disable_animations.setChecked(config['disable_animations']) self.opt_show_donate_button.setChecked(config['show_donate_button']) idx = 0 @@ -927,6 +936,9 @@ class ConfigDialog(ResizableDialog, Ui_Dialog): config['internally_viewed_formats'] = fmts val = self.opt_gui_layout.itemData(self.opt_gui_layout.currentIndex()).toString() config['gui_layout'] = unicode(val) + for x in ('gui', 'cs'): + w = getattr(self, 'opt_%s_restriction'%x) + self.db.prefs.set(x+'_restriction', unicode(w.currentText())) if must_restart: warning_dialog(self, _('Must restart'), diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui index 1359278512..79917760ab 100644 --- a/src/calibre/gui2/dialogs/config/config.ui +++ b/src/calibre/gui2/dialogs/config/config.ui @@ -7,7 +7,7 @@ 0 0 - 1000 + 1001 730 @@ -89,8 +89,8 @@ 0 0 - 720 - 679 + 725 + 683 @@ -295,7 +295,7 @@ - + Use &Roman numerals for series number @@ -305,35 +305,35 @@ - + Enable system &tray icon (needs restart) - + Show &notifications in system tray - + Show &splash screen at startup - + Show cover &browser in a separate window (needs restart) - + Show &average ratings in the tags browser @@ -343,7 +343,7 @@ - + Search as you type @@ -353,21 +353,21 @@ - + Automatically send downloaded &news to ebook reader - + &Delete news from library when it is automatically sent to reader - + @@ -384,7 +384,7 @@ - + @@ -570,7 +570,36 @@ + + + + Restriction to apply when the current library is opened: + + + opt_gui_restriction + + + + + + + 250 + 16777215 + + + + Apply this restriction on calibre startup if the current library is being used. Also applied when switching to this library. Note that this setting is per library. + + + QComboBox::AdjustToMinimumContentsLengthWithIcon + + + 20 + + + + Disable all animations. Useful if you have a slow/old computer. @@ -580,14 +609,14 @@ - + Show &donate button (restart) - + &Toolbar @@ -1040,6 +1069,26 @@ + + + + Restriction (saved search) to apply: + + + + + + + This restriction (based on a saved search) will restrict the books the content server makes available to those matching the search. This setting is per library (i.e. you can have a different restriction per library). + + + QComboBox::AdjustToMinimumContentsLengthWithIcon + + + 20 + + + diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 95667379a1..7169eb5fd3 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -402,8 +402,7 @@ class SavedSearchBoxMixin(object): b.setStatusTip(b.toolTip()) def saved_searches_changed(self): - p = saved_searches().names() - p.sort() + p = sorted(saved_searches().names(), cmp=lambda x,y: cmp(x.lower(), y.lower())) t = unicode(self.search_restriction.currentText()) self.search_restriction.clear() # rebuild the restrictions combobox using current saved searches self.search_restriction.addItem('') diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index f521bceaf9..41b166b13f 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -230,6 +230,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ ######################### Search Restriction ########################## SearchRestrictionMixin.__init__(self) + self.apply_named_search_restriction(db.prefs.get('gui_restriction', '')) ########################### Cover Flow ################################ @@ -373,6 +374,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ self.set_window_title() self.apply_named_search_restriction('') # reset restriction to null self.saved_searches_changed() # reload the search restrictions combo box + self.apply_named_search_restriction(db.prefs.get('gui_restriction', '')) def set_window_title(self): self.setWindowTitle(__appname__ + u' - ||%s||'%self.iactions['Choose Library'].library_name()) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index fa07ed8b83..ca66d28ddb 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -609,26 +609,24 @@ 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, - ignore_search_restriction=False): - q = '' - if not query or not query.strip(): - if not ignore_search_restriction: - q = self.search_restriction - else: - q = query - if not ignore_search_restriction and self.search_restriction: - q = u'%s (%s)' % (self.search_restriction, query) - if not q: - if return_matches: - return list(self._map) # when return_matches, do not update the maps! - self._map_filtered = list(self._map) - return - matches = sorted(self.parse(q)) - ans = [id for id in self._map if id in matches] + def search(self, query, return_matches=False): + ans = self.search_getting_ids(query, self.search_restriction) if return_matches: return ans self._map_filtered = ans + def search_getting_ids(self, query, search_restriction): + q = '' + if not query or not query.strip(): + q = search_restriction + else: + q = query + if search_restriction: + q = u'%s (%s)' % (search_restriction, query) + if not q: + return list(self._map) + matches = sorted(self.parse(q)) + return [id for id in self._map if id in matches] + def set_search_restriction(self, s): self.search_restriction = s diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index b8ac065760..6728735eb3 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -296,6 +296,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.book_on_device_func = None self.data = ResultCache(self.FIELD_MAP, self.field_metadata) self.search = self.data.search + self.search_getting_ids = self.data.search_getting_ids self.refresh = functools.partial(self.data.refresh, self) self.sort = self.data.sort self.index = self.data.index diff --git a/src/calibre/library/server/base.py b/src/calibre/library/server/base.py index 0097276348..7f808b008c 100644 --- a/src/calibre/library/server/base.py +++ b/src/calibre/library/server/base.py @@ -95,9 +95,19 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache): 'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()}, } + sr = db.prefs.get('cs_restriction', '') if opts.restriction is None \ + else opts.restriction + self.set_search_restriction(sr) + self.is_running = False self.exception = None + def set_search_restriction(self, restriction): + if restriction: + self.search_restriction = 'search:"%s"'%restriction + else: + self.search_restriction = '' + def setup_loggers(self): access_file = log_access_file error_file = log_error_file diff --git a/src/calibre/library/server/cache.py b/src/calibre/library/server/cache.py index 9fec2c2737..94e4a1c041 100644 --- a/src/calibre/library/server/cache.py +++ b/src/calibre/library/server/cache.py @@ -17,8 +17,7 @@ class Cache(object): def search_cache(self, search): old = self._search_cache.pop(search, None) if old is None or old[0] <= self.db.last_modified(): - matches = self.db.data.search(search, return_matches=True, - ignore_search_restriction=True) + matches = self.db.data.search_getting_ids(search, self.search_restriction) if not matches: matches = [] self._search_cache[search] = (utcnow(), frozenset(matches)) diff --git a/src/calibre/library/server/main.py b/src/calibre/library/server/main.py index 5ca82c6b98..54dd205b35 100644 --- a/src/calibre/library/server/main.py +++ b/src/calibre/library/server/main.py @@ -32,6 +32,10 @@ def option_parser(): help=_('Write process PID to the specified file')) parser.add_option('--daemonize', default=False, action='store_true', help='Run process in background as a daemon. No effect on windows.') + parser.add_option('--restriction', default=None, + help=_('Specifies a restriction to be used for this invocation. ' + 'This option overrides any per-library settings specified' + ' in the GUI')) return parser def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py index c3667a2077..aa7a740972 100644 --- a/src/calibre/library/server/mobile.py +++ b/src/calibre/library/server/mobile.py @@ -181,7 +181,9 @@ class MobileServer(object): num = int(num) except ValueError: raise cherrypy.HTTPError(400, 'num: %s is not an integer'%num) - ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set() + if not search: + search = '' + ids = self.db.search_getting_ids(search.strip(), self.search_restriction) FM = self.db.FIELD_MAP items = [r for r in iter(self.db) if r[FM['id']] in ids] if sort is not None: diff --git a/src/calibre/library/server/xml.py b/src/calibre/library/server/xml.py index 036a2051bf..9db786953e 100644 --- a/src/calibre/library/server/xml.py +++ b/src/calibre/library/server/xml.py @@ -45,7 +45,10 @@ class XMLServer(object): order = order.lower().strip() == 'ascending' - ids = self.db.data.parse(search) if search and search.strip() else self.db.data.universal_set() + if not search: + search = '' + + ids = self.db.search_getting_ids(search.strip(), self.search_restriction) FM = self.db.FIELD_MAP @@ -53,7 +56,6 @@ class XMLServer(object): if sort is not None: self.sort(items, sort, order) - books = [] def serialize(x):