mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Integrate content server into the GUI
This commit is contained in:
parent
1c4a68e1d4
commit
d13c39c87e
@ -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)
|
||||
|
4456
src/calibre/gui2/images/network-server.svg
Normal file
4456
src/calibre/gui2/images/network-server.svg
Normal file
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 168 KiB |
@ -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:
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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.'))
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user