diff --git a/resources/content_server/browse/browse.js b/resources/content_server/browse/browse.js index 29b84ac2d7..5e3cee14c0 100644 --- a/resources/content_server/browse/browse.js +++ b/resources/content_server/browse/browse.js @@ -109,7 +109,7 @@ function toplevel_layout() { var last = $(".toplevel li").last(); var title = $('.toplevel h3').first(); var bottom = last.position().top + last.height() - title.position().top; - $("#main").height(Math.max(200, bottom)); + $("#main").height(Math.max(200, bottom+75)); } function toplevel() { diff --git a/resources/content_server/mobile.css b/resources/content_server/mobile.css index 0022b2a134..a887684841 100644 --- a/resources/content_server/mobile.css +++ b/resources/content_server/mobile.css @@ -53,6 +53,7 @@ div.navigation { } #listing td { padding: 0.25em; + vertical-align: middle; } #listing td.thumbnail { @@ -73,6 +74,7 @@ div.navigation { overflow: hidden; text-align: center; text-decoration: none; + vertical-align: middle; } #logo { diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 47bb61a7dc..8398257bde 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -514,7 +514,7 @@ class FileDialog(QObject): if f and os.path.exists(f): self.selected_files.append(f) else: - opts = QFileDialog.ShowDirsOnly if mode == QFileDialog.DirectoryOnly else QFileDialog.Option() + opts = QFileDialog.ShowDirsOnly if mode == QFileDialog.Directory else QFileDialog.Option() f = unicode(QFileDialog.getExistingDirectory(parent, title, initial_dir, opts)) if os.path.exists(f): self.selected_files.append(f) @@ -534,7 +534,7 @@ class FileDialog(QObject): def choose_dir(window, name, title, default_dir='~'): fd = FileDialog(title=title, filters=[], add_all_files_filter=False, - parent=window, name=name, mode=QFileDialog.DirectoryOnly, + parent=window, name=name, mode=QFileDialog.Directory, default_dir=default_dir) dir = fd.get_files() if dir: diff --git a/src/calibre/gui2/dialogs/check_library.py b/src/calibre/gui2/dialogs/check_library.py index f07a25c51a..cf51999559 100644 --- a/src/calibre/gui2/dialogs/check_library.py +++ b/src/calibre/gui2/dialogs/check_library.py @@ -12,6 +12,7 @@ from PyQt4.Qt import QDialog, QVBoxLayout, QHBoxLayout, QTreeWidget, QLabel, \ from calibre.gui2.dialogs.confirm_delete import confirm from calibre.library.check_library import CheckLibrary, CHECKS from calibre.library.database2 import delete_file, delete_tree +from calibre import prints class Item(QTreeWidgetItem): pass @@ -126,14 +127,12 @@ class CheckLibraryDialog(QDialog): self.text_results = '\n'.join(plaintext) def item_changed(self, item, column): - print 'item_changed' for it in self.all_items: if it.checkState(1): self.delete.setEnabled(True) return def delete_marked(self): - print 'delete marked' if not confirm('

'+_('The marked files and folders will be ' 'permanently deleted. Are you sure?') +'

', 'check_library_editor_delete', self): @@ -153,7 +152,9 @@ class CheckLibraryDialog(QDialog): else: delete_file(p) except: - print 'failed to delete', os.path.join(self.db.library_path ,unicode(it.text(1))) + prints('failed to delete', + os.path.join(self.db.library_path, + unicode(it.text(1)))) self.run_the_check() def copy_to_clipboard(self): diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 6ada31418a..0ae15b0caa 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -365,6 +365,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ except: olddb = None db = LibraryDatabase2(newloc) + if self.content_server is not None: + self.content_server.set_database(db) self.library_path = newloc self.book_on_device(None, reset=True) db.set_book_on_device_func(self.book_on_device) diff --git a/src/calibre/library/server/base.py b/src/calibre/library/server/base.py index f6d8e68a04..84e748a949 100644 --- a/src/calibre/library/server/base.py +++ b/src/calibre/library/server/base.py @@ -10,6 +10,7 @@ import logging from logging.handlers import RotatingFileHandler import cherrypy +from cherrypy.process.plugins import SimplePlugin from calibre.constants import __appname__, __version__ from calibre.utils.date import fromtimestamp @@ -54,16 +55,43 @@ class DispatchController(object): # {{{ # }}} +class BonJour(SimplePlugin): + + def __init__(self, engine, port=8080): + SimplePlugin.__init__(self, engine) + self.port = port + + def start(self): + try: + publish_zeroconf('Books in calibre', '_stanza._tcp', + self.port, {'path':'/stanza'}) + except: + import traceback + cherrypy.log.error('Failed to start BonJour:') + cherrypy.log.error(traceback.format_exc()) + + start.priority = 90 + + def stop(self): + try: + stop_zeroconf() + except: + import traceback + cherrypy.log.error('Failed to stop BonJour:') + cherrypy.log.error(traceback.format_exc()) + + + stop.priority = 10 + +cherrypy.engine.bonjour = BonJour(cherrypy.engine) + + class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, BrowseServer): server_name = __appname__ + '/' + __version__ def __init__(self, db, opts, embedded=False, show_tracebacks=True): - self.db = db - for item in self.db: - item - break self.opts = opts self.embedded = embedded self.state_callback = None @@ -71,7 +99,14 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, map(int, self.opts.max_cover.split('x')) path = P('content_server') self.build_time = fromtimestamp(os.stat(path).st_mtime) - self.default_cover = open(P('content_server/default_cover.jpg'), 'rb').read() + self.default_cover = open(P('content_server/default_cover.jpg'), 'rb').read() + + cherrypy.engine.bonjour.port = opts.port + + Cache.__init__(self) + + self.set_database(db) + cherrypy.config.update({ 'log.screen' : opts.develop, 'engine.autoreload_on' : opts.develop, @@ -97,18 +132,27 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, 'tools.digest_auth.users' : {opts.username.strip():opts.password.strip()}, } - sr = getattr(opts, 'restriction', None) - sr = db.prefs.get('cs_restriction', '') if sr is None else sr - self.set_search_restriction(sr) self.is_running = False self.exception = None + self.setup_loggers() + cherrypy.engine.bonjour.subscribe() + + def set_database(self, db): + self.db = db + sr = getattr(self.opts, 'restriction', None) + sr = db.prefs.get('cs_restriction', '') if sr is None else sr + self.set_search_restriction(sr) + + def graceful(self): + cherrypy.engine.graceful() def set_search_restriction(self, restriction): if restriction: self.search_restriction = 'search:"%s"'%restriction else: self.search_restriction = '' + self.reset_caches() def setup_loggers(self): access_file = log_access_file @@ -140,7 +184,6 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, root_conf['request.dispatch'] = d.dispatcher self.config['/'] = root_conf - self.setup_loggers() cherrypy.tree.mount(root=None, config=self.config) try: try: @@ -154,24 +197,14 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache, cherrypy.engine.start() self.is_running = True - try: - publish_zeroconf('Books in calibre', '_stanza._tcp', - self.opts.port, {'path':'/stanza'}) - except: - import traceback - cherrypy.log.error('Failed to start BonJour:') - cherrypy.log.error(traceback.format_exc()) + #if hasattr(cherrypy.engine, 'signal_handler'): + # cherrypy.engine.signal_handler.subscribe() + cherrypy.engine.block() except Exception, e: self.exception = e finally: self.is_running = False - try: - stop_zeroconf() - except: - import traceback - cherrypy.log.error('Failed to stop BonJour:') - cherrypy.log.error(traceback.format_exc()) try: if callable(self.state_callback): self.state_callback(self.is_running) diff --git a/src/calibre/library/server/browse.py b/src/calibre/library/server/browse.py index 247e6945e6..ea69ad77ef 100644 --- a/src/calibre/library/server/browse.py +++ b/src/calibre/library/server/browse.py @@ -7,6 +7,7 @@ __docformat__ = 'restructuredtext en' import operator, os, json from binascii import hexlify, unhexlify +from urllib import quote import cherrypy @@ -136,7 +137,7 @@ def get_category_items(category, items, db, datatype): # {{{ q = i.category if not q: q = category - href = '/browse/matches/%s/%s'%(q, id_) + href = '/browse/matches/%s/%s'%(quote(q), quote(id_)) return templ.format(xml(name), rating, xml(desc), xml(href), rstring) @@ -329,7 +330,7 @@ class BrowseServer(object): cats = [('
  • {0}' '{0}' '/browse/category/{1}
  • ') - .format(xml(x, True), xml(y), xml(_('Browse books by')), + .format(xml(x, True), xml(quote(y)), xml(_('Browse books by')), src='/browse/icon/'+z) for x, y, z in cats] diff --git a/src/calibre/library/server/cache.py b/src/calibre/library/server/cache.py index 29602a114c..cc4f7a3886 100644 --- a/src/calibre/library/server/cache.py +++ b/src/calibre/library/server/cache.py @@ -10,7 +10,10 @@ from calibre.utils.ordered_dict import OrderedDict class Cache(object): - def add_routes(self, c): + def __init__(self): + self.reset_caches() + + def reset_caches(self): self._category_cache = OrderedDict() self._search_cache = OrderedDict() diff --git a/src/calibre/library/server/main.py b/src/calibre/library/server/main.py index 54dd205b35..6d01080886 100644 --- a/src/calibre/library/server/main.py +++ b/src/calibre/library/server/main.py @@ -5,7 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, sys +import sys from threading import Thread from calibre.library.server import server_config as config @@ -38,50 +38,18 @@ def option_parser(): ' in the GUI')) return parser -def daemonize(stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): - try: - pid = os.fork() - if pid > 0: - # exit first parent - sys.exit(0) - except OSError, e: - print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) - sys.exit(1) - - # decouple from parent environment - os.chdir("/") - os.setsid() - os.umask(0) - - # do second fork - try: - pid = os.fork() - if pid > 0: - # exit from second parent - sys.exit(0) - except OSError, e: - print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) - sys.exit(1) - - # Redirect standard file descriptors. - si = file(stdin, 'r') - so = file(stdout, 'a+') - se = file(stderr, 'a+', 0) - os.dup2(si.fileno(), sys.stdin.fileno()) - os.dup2(so.fileno(), sys.stdout.fileno()) - os.dup2(se.fileno(), sys.stderr.fileno()) - - def main(args=sys.argv): from calibre.library.database2 import LibraryDatabase2 parser = option_parser() opts, args = parser.parse_args(args) if opts.daemonize and not iswindows: - daemonize() + from cherrypy.process.plugins import Daemonizer + d = Daemonizer(cherrypy.engine) + d.subscribe() if opts.pidfile is not None: - with open(opts.pidfile, 'wb') as f: - f.write(str(os.getpid())) + from cherrypy.process.plugins import PIDFile + PIDFile(cherrypy.engine, opts.pidfile).subscribe() cherrypy.log.screen = True from calibre.utils.config import prefs if opts.with_library is None: diff --git a/src/calibre/utils/mdns.py b/src/calibre/utils/mdns.py index 74547b9573..b7cc8757d3 100644 --- a/src/calibre/utils/mdns.py +++ b/src/calibre/utils/mdns.py @@ -58,11 +58,12 @@ def publish(desc, type, port, properties=None, add_hostname=True): ''' port = int(port) server = start_server() + try: + hostname = socket.gethostname().partition('.')[0] + except: + hostname = 'Unknown' + if add_hostname: - try: - hostname = socket.gethostname().partition('.')[0] - except: - hostname = 'Unknown' desc += ' (on %s)'%hostname local_ip = get_external_ip() type = type+'.local.'