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 ¬ifications 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):