diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 6f5a858d49..4158adfd9d 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -775,8 +775,17 @@ class Email(PreferencesPlugin): name_order = 1 config_widget = 'calibre.gui2.preferences.emailp' +class Server(PreferencesPlugin): + name = 'Server' + gui_name = _('Sharing over the net') + category = 'Sharing' + gui_category = _('Sharing') + category_order = 4 + name_order = 2 + config_widget = 'calibre.gui2.preferences.server' + plugins += [LookAndFeel, Behavior, Columns, Toolbar, InputOptions, - CommonOptions, OutputOptions, Adding, Saving, Sending, Email] + CommonOptions, OutputOptions, Adding, Saving, Sending, Email, Server] #}}} diff --git a/src/calibre/gui2/preferences/__init__.py b/src/calibre/gui2/preferences/__init__.py index 4f895c97c4..cc5fa4fa8c 100644 --- a/src/calibre/gui2/preferences/__init__.py +++ b/src/calibre/gui2/preferences/__init__.py @@ -175,9 +175,10 @@ class ConfigWidgetBase(QWidget, ConfigWidgetInterface): self.settings = {} def register(self, name, config_obj, gui_name=None, choices=None, - restart_required=False, setting=Setting): + restart_required=False, empty_string_is_None=True, setting=Setting): setting = setting(name, config_obj, self, gui_name=gui_name, - choices=choices, restart_required=restart_required) + choices=choices, restart_required=restart_required, + empty_string_is_None=empty_string_is_None) return self.register_setting(setting) def register_setting(self, setting): @@ -229,7 +230,7 @@ def test_widget(category, name, gui=None): def set_widget(self, w): self.w = w def accept(self): try: - self.w.commit() + self.restart_required = self.w.commit() except AbortCommit: return QDialog.accept(self) @@ -259,10 +260,8 @@ def test_widget(category, name, gui=None): mygui = True w.genesis(gui) w.initialize() - restart_required = False - if d.exec_() == QDialog.Accepted: - restart_required = w.commit() - if restart_required: + d.exec_() + if getattr(d, 'restart_required', False): from calibre.gui2 import warning_dialog warning_dialog(gui, 'Restart required', 'Restart required', show=True) if mygui: diff --git a/src/calibre/gui2/preferences/server.py b/src/calibre/gui2/preferences/server.py new file mode 100644 index 0000000000..6777b4c49a --- /dev/null +++ b/src/calibre/gui2/preferences/server.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import time + +from PyQt4.Qt import Qt, QUrl, QDialog, QSize, QVBoxLayout, QLabel, \ + QPlainTextEdit, QDialogButtonBox + +from calibre.gui2.preferences import ConfigWidgetBase, test_widget +from calibre.gui2.preferences.server_ui import Ui_Form +from calibre.utils.search_query_parser import saved_searches +from calibre.library.server import server_config +from calibre.utils.config import ConfigProxy +from calibre.gui2 import error_dialog, config, open_url, warning_dialog + +class ConfigWidget(ConfigWidgetBase, Ui_Form): + + def genesis(self, gui): + self.gui = gui + self.proxy = ConfigProxy(server_config()) + db = self.db = gui.library_view.model().db + self.server = self.gui.content_server + + r = self.register + + r('port', self.proxy) + r('username', self.proxy) + r('password', self.proxy) + r('max_cover', self.proxy) + r('max_opds_items', self.proxy) + r('max_opds_ungrouped_items', self.proxy) + + self.show_server_password.stateChanged[int].connect( + lambda s: self.opt_password.setEchoMode( + self.opt_password.Normal if s == Qt.Checked + else self.opt_password.Password)) + self.opt_password.setEchoMode(self.opt_password.Password) + + restrictions = sorted(saved_searches().names(), + cmp=lambda x,y: cmp(x.lower(), y.lower())) + choices = [('', '')] + [(x, x) for x in restrictions] + r('cs_restriction', db.prefs, choices=choices) + + self.start_button.setEnabled(not getattr(self.server, 'is_running', False)) + self.test_button.setEnabled(not self.start_button.isEnabled()) + self.stop_button.setDisabled(self.start_button.isEnabled()) + self.start_button.clicked.connect(self.start_server) + self.stop_button.clicked.connect(self.stop_server) + self.test_button.clicked.connect(self.test_server) + self.view_logs.clicked.connect(self.view_server_logs) + + r('autolaunch_server', config) + + def set_server_options(self): + c = self.proxy + c.set('port', self.opt_port.value()) + c.set('username', unicode(self.opt_username.text()).strip()) + p = unicode(self.opt_password.text()).strip() + if not p: + p = None + c.set('password', p) + + def start_server(self): + self.set_server_options() + from calibre.library.server.main 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_button.setEnabled(False) + self.test_button.setEnabled(True) + self.stop_button.setEnabled(True) + + def stop_server(self): + from calibre.library.server.main import stop_threaded_server + stop_threaded_server(self.server) + self.server = None + self.start_button.setEnabled(True) + self.test_button.setEnabled(False) + self.stop_button.setEnabled(False) + + def test_server(self): + open_url(QUrl('http://127.0.0.1:'+str(self.opt_port.value()))) + + def view_server_logs(self): + from calibre.library.server import log_access_file, log_error_file + d = QDialog(self) + d.resize(QSize(800, 600)) + layout = QVBoxLayout() + d.setLayout(layout) + layout.addWidget(QLabel(_('Error log:'))) + el = QPlainTextEdit(d) + layout.addWidget(el) + try: + el.setPlainText(open(log_error_file, 'rb').read().decode('utf8', 'replace')) + except IOError: + el.setPlainText('No error log found') + layout.addWidget(QLabel(_('Access log:'))) + al = QPlainTextEdit(d) + layout.addWidget(al) + try: + al.setPlainText(open(log_access_file, 'rb').read().decode('utf8', 'replace')) + except IOError: + al.setPlainText('No access log found') + bx = QDialogButtonBox(QDialogButtonBox.Ok) + layout.addWidget(bx) + bx.accepted.connect(d.accept) + d.show() + + def commit(self): + ConfigWidgetBase.commit(self) + warning_dialog(self, _('Restart needed'), + _('You need to restart the server for changes to' + ' take effect'), show=True) + return False + + +if __name__ == '__main__': + from PyQt4.Qt import QApplication + app = QApplication([]) + test_widget('Sharing', 'Server') + diff --git a/src/calibre/gui2/preferences/server.ui b/src/calibre/gui2/preferences/server.ui new file mode 100644 index 0000000000..d043f6f52c --- /dev/null +++ b/src/calibre/gui2/preferences/server.ui @@ -0,0 +1,261 @@ + + + Form + + + + 0 + 0 + 641 + 563 + + + + Form + + + + + + + + Server &port: + + + opt_port + + + + + + + 65535 + + + 8080 + + + + + + + &Username: + + + opt_username + + + + + + + + + + &Password: + + + opt_password + + + + + + + If you leave the password blank, anyone will be able to access your book collection using the web interface. + + + + + + + The maximum size (widthxheight) for displayed covers. Larger covers are resized. + + + + + + + + + + Max. &cover size: + + + opt_max_cover + + + + + + + &Show password + + + + + + + Max. &OPDS items per query: + + + opt_max_opds_items + + + + + + + 10 + + + 10000 + + + + + + + 25 + + + 1000000 + + + + + + + Max. OPDS &ungrouped items: + + + opt_max_opds_ungrouped_items + + + + + + + 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 + + + + + + + + + + + &Start Server + + + + + + + St&op Server + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Test Server + + + + + + + + + calibre contains a network server that allows you to access your book collection using a browser from anywhere in the world. Any changes to the settings will only take effect after a server restart. + + + true + + + + + + + Run server &automatically on startup + + + + + + + View &server logs + + + + + + + Qt::Vertical + + + + 20 + 36 + + + + + + + + <p>Remember to leave calibre running as the server only runs as long as calibre is running. +<p>Stanza should see your calibre collection automatically. If not, try adding the URL http://myhostname:8080 as a new catalog in the Stanza reader on your iPhone. Here myhostname should be the fully qualified hostname or the IP address of the computer calibre is running on. + + + true + + + + + + + Qt::Vertical + + + + 20 + 37 + + + + + + + + +