Integrate content server into the GUI

This commit is contained in:
Kovid Goyal 2008-11-05 11:09:00 -08:00
parent 1c4a68e1d4
commit d13c39c87e
6 changed files with 4583 additions and 45 deletions

View File

@ -1,6 +1,6 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, re
import os, re, time
from PyQt4.QtGui import QDialog, QMessageBox, QListWidgetItem, QIcon, \
QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit
@ -29,7 +29,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.item3 = QListWidgetItem(QIcon(':/images/view.svg'), _('Advanced'), self.category_list)
self.item4 = QListWidgetItem(QIcon(':/images/network-server.svg'), _('Content\nServer'), self.category_list)
self.db = db
self.server = None
self.server = server
path = prefs['library_path']
self.location.setText(path if path else '')
self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse)
@ -104,7 +104,6 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.viewer.item(self.viewer.count()-1).setCheckState(Qt.Checked if ext.upper() in config['internally_viewed_formats'] else Qt.Unchecked)
added_html = ext == 'html'
self.viewer.sortItems()
self.start.setEnabled(not getattr(self.server, 'is_running', False))
self.test.setEnabled(not self.start.isEnabled())
self.stop.setDisabled(self.start.isEnabled())
@ -159,6 +158,12 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.set_server_options()
from calibre.library.server import start_threaded_server
self.server = start_threaded_server(self.db, server_config().parse())
while not self.server.is_running and self.server.exception is None:
time.sleep(1)
if self.server.exception is not None:
error_dialog(self, _('Failed to start content server'),
unicode(self.server.exception)).exec_()
return
self.start.setEnabled(False)
self.test.setEnabled(True)
self.stop.setEnabled(True)

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 168 KiB

View File

@ -529,7 +529,7 @@ class BooksView(TableView):
#QObject.connect(self.model(), SIGNAL('rowsRemoved(QModelIndex, int, int)'), self.resizeRowsToContents)
#QObject.connect(self.model(), SIGNAL('rowsInserted(QModelIndex, int, int)'), self.resizeRowsToContents)
self.set_visible_columns()
def columns_sorted(self):
if self.__class__.__name__ == 'BooksView':
try:

View File

@ -3,7 +3,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, sys, textwrap, collections, traceback, time, re
from xml.parsers.expat import ExpatError
from functools import partial
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QUrl
from PyQt4.QtCore import Qt, SIGNAL, QObject, QCoreApplication, QUrl, QTimer
from PyQt4.QtGui import QPixmap, QColor, QPainter, QMenu, QIcon, QMessageBox, \
QToolButton, QDialog, QDesktopServices, QFileDialog
from PyQt4.QtSvg import QSvgRenderer
@ -287,8 +287,13 @@ class Main(MainWindow, Ui_MainWindow):
if config['autolaunch_server']:
from calibre.library.server import start_threaded_server
from calibre.library import server_config
self.server = start_threaded_server(db, server_config().parse())
self.content_server = start_threaded_server(db, server_config().parse())
self.test_server_timer = QTimer.singleShot(10000, self.test_server)
def test_server(self, *args):
if self.content_server.exception is not None:
error_dialog(self, _('Failed to start content server'),
unicode(self.content_server.exception)).exec_()
def toggle_cover_flow(self, show):
if show:
@ -1011,7 +1016,7 @@ class Main(MainWindow, Ui_MainWindow):
d = error_dialog(self, _('Cannot configure'), _('Cannot configure while there are running jobs.'))
d.exec_()
return
d = ConfigDialog(self, self.library_view.model().db, self.content_server)
d = ConfigDialog(self, self.library_view.model().db, server=self.content_server)
d.exec_()
self.content_server = d.server
if d.result() == d.Accepted:
@ -1222,11 +1227,14 @@ in which you want to store your books files. Any existing books will be automati
self.hide()
self.cover_cache.terminate()
try:
if self.server is not None:
self.server.exit()
except:
try:
if self.content_server is not None:
self.content_server.exit()
except:
pass
time.sleep(2)
except KeyboardInterrupt:
pass
time.sleep(2)
e.accept()
def update_found(self, version):

View File

@ -2,6 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' Code to manage ebook library'''
import re
from calibre.utils.config import Config, StringConfig
title_pat = re.compile('^(A|The|An)\s+', re.IGNORECASE)
def title_sort(title):
@ -9,4 +10,24 @@ def title_sort(title):
if match:
prep = match.group(1)
title = title.replace(prep, '') + ', ' + prep
return title.strip()
return title.strip()
def server_config(defaults=None):
desc=_('Settings to control the calibre content server')
c = Config('server', desc) if defaults is None else StringConfig(defaults, desc)
c.add_opt('port', ['-p', '--port'], default=8080,
help=_('The port on which to listen. Default is %default'))
c.add_opt('timeout', ['-t', '--timeout'], default=120,
help=_('The server timeout in seconds. Default is %default'))
c.add_opt('thread_pool', ['--thread-pool'], default=30,
help=_('The max number of worker threads to use. Default is %default'))
c.add_opt('hostname', ['--hostname'], default='localhost',
help=_('The hostname of the machine the server is running on. Used when generating the stanza feeds. Default is %default'))
c.add_opt('password', ['--password'], default=None,
help=_('Set a password to restrict access. By default access is unrestricted.'))
c.add_opt('username', ['--username'], default='calibre',
help=_('Username for access. By default, it is: %default'))
c.add_opt('develop', ['--develop'], default=False,
help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.')
return c

View File

@ -7,17 +7,21 @@ __docformat__ = 'restructuredtext en'
HTTP server for remote access to the calibre database.
'''
import sys, textwrap, cStringIO, mimetypes, operator, os, re
import sys, textwrap, cStringIO, mimetypes, operator, os, re, logging
from logging.handlers import RotatingFileHandler
from datetime import datetime
from threading import Thread
import cherrypy
from PIL import Image
from calibre.constants import __version__, __appname__
from calibre.utils.config import StringConfig, Config
from calibre.utils.genshi.template import MarkupTemplate
from calibre import fit_image
from calibre.resources import jquery, server_resources, build_time
from calibre.library import server_config as config
from calibre.library.database2 import LibraryDatabase2, FIELD_MAP
from calibre.utils.config import config_dir
build_time = datetime.strptime(build_time, '%d %m %Y %H%M%S')
server_resources['jquery.js'] = jquery
@ -30,6 +34,10 @@ def expose(func):
return cherrypy.expose(do)
log_access_file = os.path.join(config_dir, 'server_access_log.txt')
log_error_file = os.path.join(config_dir, 'server_error_log.txt')
class LibraryServer(object):
server_name = __appname__ + '/' + __version__
@ -97,7 +105,7 @@ class LibraryServer(object):
'''))
def __init__(self, db, opts):
def __init__(self, db, opts, embedded=False, show_tracebacks=True):
self.db = db
for item in self.db:
item
@ -105,20 +113,68 @@ class LibraryServer(object):
self.opts = opts
cherrypy.config.update({
'server.socket_host': '0.0.0.0',
'server.socket_port': opts.port,
'server.socket_timeout': opts.timeout, #seconds
'server.thread_pool': opts.thread_pool, # number of threads
'log.screen' : opts.develop,
'engine.autoreload_on' : opts.develop,
'tools.log_headers.on' : opts.develop,
'checker.on' : opts.develop,
'request.show_tracebacks': show_tracebacks,
'server.socket_host' : '0.0.0.0',
'server.socket_port' : opts.port,
'server.socket_timeout' : opts.timeout, #seconds
'server.thread_pool' : opts.thread_pool, # number of threads
})
self.config = textwrap.dedent('''\
[global]
engine.autoreload_on = %(autoreload)s
tools.gzip.on = True
tools.gzip.mime_types = ['text/html', 'text/plain', 'text/xml', 'text/javascript', 'text/css']
''')%dict(autoreload=opts.develop)
if embedded:
cherrypy.config.update({'engine.SIGHUP' : None,
'engine.SIGTERM' : None,})
self.config = {'global': {
'tools.gzip.on' : True,
'tools.gzip.mime_types': ['text/html', 'text/plain', 'text/xml', 'text/javascript', 'text/css'],
}}
if opts.password:
g = self.config['global']
g['tools.digest_auth.on'] = True
g['tools.digest_auth.realm'] = _('Password to access your calibre library. Username is ') + opts.username.strip()
g['tools.digest_auth.users'] = {opts.username.strip():opts.password.strip()}
self.is_running = False
self.exception = None
def setup_loggers(self):
access_file = log_access_file
error_file = log_error_file
log = cherrypy.log
maxBytes = getattr(log, "rot_maxBytes", 10000000)
backupCount = getattr(log, "rot_backupCount", 1000)
# Make a new RotatingFileHandler for the error log.
h = RotatingFileHandler(error_file, 'a', maxBytes, backupCount)
h.setLevel(logging.DEBUG)
h.setFormatter(cherrypy._cplogging.logfmt)
log.error_log.addHandler(h)
# Make a new RotatingFileHandler for the access log.
h = RotatingFileHandler(access_file, 'a', maxBytes, backupCount)
h.setLevel(logging.DEBUG)
h.setFormatter(cherrypy._cplogging.logfmt)
log.access_log.addHandler(h)
def start(self):
cherrypy.quickstart(self, config=cStringIO.StringIO(self.config))
self.is_running = False
self.setup_loggers()
cherrypy.tree.mount(self, '', config=self.config)
try:
cherrypy.engine.start()
self.is_running = True
cherrypy.engine.block()
except Exception, e:
self.exception = e
finally:
self.is_running = False
def exit(self):
cherrypy.engine.exit()
def get_cover(self, id, thumbnail=False):
cover = self.db.cover(id, index_is_id=True, as_file=True)
@ -283,26 +339,18 @@ class LibraryServer(object):
if server_resources.has_key(name):
return server_resources[name]
raise cherrypy.HTTPError(404, '%s not found'%name)
def config(defaults=None):
desc=_('Settings to control the calibre content server')
c = Config('server', desc) if defaults is None else StringConfig(defaults, desc)
c.add_opt('port', ['-p', '--port'], default=8080,
help=_('The port on which to listen. Default is %default'))
c.add_opt('timeout', ['-t', '--timeout'], default=120,
help=_('The server timeout in seconds. Default is %default'))
c.add_opt('thread_pool', ['--thread-pool'], default=30,
help=_('The max number of worker threads to use. Default is %default'))
c.add_opt('hostname', ['--hostname'], default='localhost',
help=_('The hostname of the machine the server is running on. Used when generating the stanza feeds. Default is %default'))
c.add_opt('develop', ['--develop'], default=False,
help='Development mode. Server automatically restarts on file changes and serves code files (html, css, js) from the file system instead of calibre\'s resource system.')
return c
def start_threaded_server(db, opts):
server = LibraryServer(db, opts, embedded=True)
server.thread = Thread(target=server.start)
server.thread.setDaemon(True)
server.thread.start()
return server
def stop_threaded_server(server):
server.exit()
server.thread = None
def option_parser():
return config().option_parser('%prog '+ _('[options]\n\nStart the calibre content server.'))