From 8072415891ff100a74cbfaf07a9445b398e18a7a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 29 May 2009 00:11:59 -0700 Subject: [PATCH] Welcome wizard --- src/calibre/__init__.py | 7 + src/calibre/gui2/convert/page_setup.py | 2 +- src/calibre/gui2/dialogs/config.py | 117 +----- src/calibre/gui2/dialogs/config.ui | 235 +----------- src/calibre/gui2/main.py | 92 ++--- src/calibre/gui2/wizard/__init__.py | 500 +++++++++++++++++++++++++ src/calibre/gui2/wizard/device.ui | 75 ++++ src/calibre/gui2/wizard/finish.ui | 108 ++++++ src/calibre/gui2/wizard/kindle.ui | 80 ++++ src/calibre/gui2/wizard/library.ui | 74 ++++ src/calibre/gui2/wizard/send_email.py | 143 +++++++ src/calibre/gui2/wizard/send_email.ui | 234 ++++++++++++ src/calibre/gui2/wizard/stanza.ui | 97 +++++ src/calibre/library/database2.py | 18 +- src/calibre/library/move.py | 63 ++++ src/calibre/library/static/calibre.png | Bin 27150 -> 25399 bytes src/calibre/trac/plugins/download.py | 4 +- src/calibre/utils/ipc/worker.py | 5 + src/calibre/web/fetch/simple.py | 2 +- todo | 2 - 20 files changed, 1466 insertions(+), 392 deletions(-) create mode 100644 src/calibre/gui2/wizard/__init__.py create mode 100644 src/calibre/gui2/wizard/device.ui create mode 100644 src/calibre/gui2/wizard/finish.ui create mode 100644 src/calibre/gui2/wizard/kindle.ui create mode 100644 src/calibre/gui2/wizard/library.ui create mode 100644 src/calibre/gui2/wizard/send_email.py create mode 100644 src/calibre/gui2/wizard/send_email.ui create mode 100644 src/calibre/gui2/wizard/stanza.ui create mode 100644 src/calibre/library/move.py diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index a08f0417ee..79dc659f34 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -45,6 +45,13 @@ def to_unicode(raw, encoding='utf-8', errors='strict'): return raw return raw.decode(encoding, errors) +def patheq(p1, p2): + p = os.path + d = lambda x : p.normcase(p.normpath(p.realpath(p.normpath(x)))) + if not p1 or not p2: + return False + return d(p1) == d(p2) + def unicode_path(path, abs=False): if not isinstance(path, unicode): path = path.decode(sys.getfilesystemencoding()) diff --git a/src/calibre/gui2/convert/page_setup.py b/src/calibre/gui2/convert/page_setup.py index 0d2ce91dd1..3f59537db0 100644 --- a/src/calibre/gui2/convert/page_setup.py +++ b/src/calibre/gui2/convert/page_setup.py @@ -35,7 +35,7 @@ class PageSetupWidget(Widget, Ui_Form): TITLE = _('Page Setup') def __init__(self, parent, get_option, get_help, db=None, book_id=None): - Widget.__init__(self, parent, 'lrf_output', + Widget.__init__(self, parent, 'page_setup', ['margin_top', 'margin_left', 'margin_right', 'margin_bottom', 'input_profile', 'output_profile'] ) diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py index 8b1e61f546..0493ce73aa 100644 --- a/src/calibre/gui2/dialogs/config.py +++ b/src/calibre/gui2/dialogs/config.py @@ -1,7 +1,6 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' -import os, re, time, textwrap, sys, cStringIO -from binascii import hexlify, unhexlify +import os, re, time, textwrap from PyQt4.Qt import QDialog, QMessageBox, QListWidgetItem, QIcon, \ QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit, \ @@ -12,7 +11,6 @@ from PyQt4.Qt import QDialog, QMessageBox, QListWidgetItem, QIcon, \ from calibre.constants import islinux, iswindows from calibre.gui2.dialogs.config_ui import Ui_Dialog -from calibre.gui2.dialogs.test_email_ui import Ui_Dialog as TE_Dialog from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \ ALL_COLUMNS, NONE, info_dialog, choose_files from calibre.utils.config import prefs @@ -202,34 +200,6 @@ class CategoryModel(QStringListModel): return self.icons[index.row()] return QStringListModel.data(self, index, role) -class TestEmail(QDialog, TE_Dialog): - - def __init__(self, accounts, parent): - QDialog.__init__(self, parent) - TE_Dialog.__init__(self) - self.setupUi(self) - opts = smtp_prefs().parse() - self.test_func = parent.test_email_settings - self.connect(self.test_button, SIGNAL('clicked(bool)'), self.test) - self.from_.setText(unicode(self.from_.text())%opts.from_) - if accounts: - self.to.setText(list(accounts.keys())[0]) - if opts.relay_host: - self.label.setText(_('Using: %s:%s@%s:%s and %s encryption')% - (opts.relay_username, unhexlify(opts.relay_password), - opts.relay_host, opts.relay_port, opts.encryption)) - - def test(self): - self.log.setPlainText(_('Sending...')) - self.test_button.setEnabled(False) - try: - tb = self.test_func(unicode(self.to.text())) - if not tb: - tb = _('Mail successfully sent') - self.log.setPlainText(tb) - finally: - self.test_button.setEnabled(True) - class EmailAccounts(QAbstractTableModel): def __init__(self, accounts): @@ -477,32 +447,19 @@ class ConfigDialog(QDialog, Ui_Dialog): self.stackedWidget.insertWidget(2, self.conversion_options) def setup_email_page(self): - opts = smtp_prefs().parse() - if opts.from_: - self.email_from.setText(opts.from_) + def x(): + if self._email_accounts.account_order: + return self._email_accounts.account_order[0] + self.send_email_widget.initialize(x) + opts = self.send_email_widget.smtp_opts self._email_accounts = EmailAccounts(opts.accounts) self.email_view.setModel(self._email_accounts) - if opts.relay_host: - self.relay_host.setText(opts.relay_host) - self.relay_port.setValue(opts.relay_port) - if opts.relay_username: - self.relay_username.setText(opts.relay_username) - if opts.relay_password: - self.relay_password.setText(unhexlify(opts.relay_password)) - (self.relay_tls if opts.encryption == 'TLS' else self.relay_ssl).setChecked(True) - self.connect(self.relay_use_gmail, SIGNAL('clicked(bool)'), - self.create_gmail_relay) - self.connect(self.relay_show_password, SIGNAL('stateChanged(int)'), - lambda - state:self.relay_password.setEchoMode(self.relay_password.Password if - state == 0 else self.relay_password.Normal)) + self.connect(self.email_add, SIGNAL('clicked(bool)'), self.add_email_account) self.connect(self.email_make_default, SIGNAL('clicked(bool)'), lambda c: self._email_accounts.make_default(self.email_view.currentIndex())) self.email_view.resizeColumnsToContents() - self.connect(self.test_email_button, SIGNAL('clicked(bool)'), - self.test_email) self.connect(self.email_remove, SIGNAL('clicked()'), self.remove_email_account) @@ -516,68 +473,14 @@ class ConfigDialog(QDialog, Ui_Dialog): idx = self.email_view.currentIndex() self._email_accounts.remove(idx) - def create_gmail_relay(self, *args): - self.relay_username.setText('@gmail.com') - self.relay_password.setText('') - self.relay_host.setText('smtp.gmail.com') - self.relay_port.setValue(587) - self.relay_tls.setChecked(True) - - info_dialog(self, _('Finish gmail setup'), - _('Dont forget to enter your gmail username and password')).exec_() - self.relay_username.setFocus(Qt.OtherFocusReason) - self.relay_username.setCursorPosition(0) - def set_email_settings(self): - from_ = unicode(self.email_from.text()).strip() - if self._email_accounts.accounts and not from_: - error_dialog(self, _('Bad configuration'), - _('You must set the From email address')).exec_() - return False - username = unicode(self.relay_username.text()).strip() - password = unicode(self.relay_password.text()).strip() - host = unicode(self.relay_host.text()).strip() - if host and not (username and password): - error_dialog(self, _('Bad configuration'), - _('You must set the username and password for ' - 'the mail server.')).exec_() + to_set = bool(self._email_accounts.accounts) + if not self.send_email_widget.set_email_settings(to_set): return False conf = smtp_prefs() - conf.set('from_', from_) conf.set('accounts', self._email_accounts.accounts) - conf.set('relay_host', host if host else None) - conf.set('relay_port', self.relay_port.value()) - conf.set('relay_username', username if username else None) - conf.set('relay_password', hexlify(password)) - conf.set('encryption', 'TLS' if self.relay_tls.isChecked() else 'SSL') return True - def test_email(self, *args): - if self.set_email_settings(): - TestEmail(self._email_accounts.accounts, self).exec_() - - def test_email_settings(self, to): - opts = smtp_prefs().parse() - from calibre.utils.smtp import sendmail, create_mail - buf = cStringIO.StringIO() - oout, oerr = sys.stdout, sys.stderr - sys.stdout = sys.stderr = buf - tb = None - try: - msg = create_mail(opts.from_, to, 'Test mail from calibre', - 'Test mail from calibre') - sendmail(msg, from_=opts.from_, to=[to], - verbose=3, timeout=30, relay=opts.relay_host, - username=opts.relay_username, - password=unhexlify(opts.relay_password), - encryption=opts.encryption, port=opts.relay_port) - except: - import traceback - tb = traceback.format_exc() - tb += '\n\nLog:\n' + buf.getvalue() - finally: - sys.stdout, sys.stderr = oout, oerr - return tb def add_plugin(self): path = unicode(self.plugin_path.text()) @@ -722,7 +625,7 @@ class ConfigDialog(QDialog, Ui_Dialog): def browse(self): dir = choose_dir(self, 'database location dialog', - _('Select database location')) + _('Select location for books')) if dir: self.location.setText(dir) diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui index 0ee6629f91..90a53364ca 100644 --- a/src/calibre/gui2/dialogs/config.ui +++ b/src/calibre/gui2/dialogs/config.ui @@ -541,38 +541,7 @@ - - - - calibre can send your books to you (or your reader) by email - - - true - - - - - - - - Send email &from: - - - email_from - - - - - - - <p>This is what will be present in the From: field of emails sent by calibre.<br> Set it to your email address - - - - - - @@ -640,195 +609,18 @@ - - - - <p>A mail server is useful if the service you are sending mail to only accepts email from well know mail services. + + + + calibre can send your books to you (or your reader) by email - - Mail &Server + + true - - - - - calibre can <b>optionally</b> use a server to send mail - - - true - - - - - - - &Hostname: - - - relay_host - - - - - - - The hostname of your mail server. For e.g. smtp.gmail.com - - - - - - - - - &Port: - - - relay_port - - - - - - - The port your mail server listens for connections on. The default is 25 - - - 1 - - - 65555 - - - 25 - - - - - - - - - &Username: - - - relay_username - - - - - - - Your username on the mail server - - - - - - - &Password: - - - relay_password - - - - - - - Your password on the mail server - - - QLineEdit::Password - - - - - - - &Show - - - - - - - &Encryption: - - - relay_tls - - - - - - - Use TLS encryption when connecting to the mail server. This is the most common. - - - &TLS - - - true - - - - - - - Use SSL encryption when connecting to the mail server. - - - &SSL - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - Use Gmail - - - - :/images/gmail_logo.png:/images/gmail_logo.png - - - - 48 - 48 - - - - Qt::ToolButtonTextUnderIcon - - - - - - - &Test email - - - - + + @@ -1062,7 +854,8 @@ - If you want to use the content server to access your ebook collection on your iphone with Stanza, you will need to add the URL http://myhostname:8080/stanza as a new catalog in the stanza reader on your iphone. Here myhostname should be the fully qualified hostname or the IP address of this computer. + <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 @@ -1216,6 +1009,14 @@ + + + SendEmail + QWidget +
calibre/gui2/wizard/send_email.h
+ 1 +
+
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 03c5f1dbdf..af04207863 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -11,11 +11,11 @@ from PyQt4.Qt import Qt, SIGNAL, QObject, QCoreApplication, QUrl, QTimer, \ QModelIndex, QPixmap, QColor, QPainter, QMenu, QIcon, \ QToolButton, QDialog, QDesktopServices, QFileDialog, \ QSystemTrayIcon, QApplication, QKeySequence, QAction, \ - QProgressDialog, QMessageBox, QStackedLayout + QMessageBox, QStackedLayout from PyQt4.QtSvg import QSvgRenderer from calibre import __version__, __appname__, sanitize_file_name, \ - iswindows, isosx, prints + iswindows, isosx, prints, patheq from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import prefs, dynamic from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ @@ -27,6 +27,7 @@ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ available_width, GetMetadata from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror from calibre.gui2.widgets import ProgressIndicator +from calibre.gui2.wizard import move_library from calibre.gui2.dialogs.scheduler import Scheduler from calibre.gui2.update import CheckForUpdates from calibre.gui2.main_window import MainWindow, option_parser as _option_parser @@ -297,6 +298,14 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): QObject.connect(self.action_convert, SIGNAL('triggered(bool)'), self.convert_single) self.convert_menu = cm + pm = QMenu() + pm.addAction(self.action_preferences) + pm.addAction(_('Run welcome wizard')) + self.connect(pm.actions()[1], SIGNAL('triggered(bool)'), + self.run_wizard) + self.action_preferences.setMenu(pm) + self.preferences_menu = pm + self.tool_bar.widgetForAction(self.action_news).\ setPopupMode(QToolButton.MenuButtonPopup) self.tool_bar.widgetForAction(self.action_edit).\ @@ -311,6 +320,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): setPopupMode(QToolButton.MenuButtonPopup) self.tool_bar.widgetForAction(self.action_view).\ setPopupMode(QToolButton.MenuButtonPopup) + self.tool_bar.widgetForAction(self.action_preferences).\ + setPopupMode(QToolButton.MenuButtonPopup) self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu) self.connect(self.preferences_action, SIGNAL('triggered(bool)'), @@ -1376,56 +1387,27 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.save_menu.actions()[2].setText( _('Save only %s format to disk')% prefs['output_format'].upper()) - if self.library_path != d.database_location: - try: - newloc = d.database_location - if not os.path.exists(os.path.join(newloc, 'metadata.db')): - if os.access(self.library_path, os.R_OK): - pd = QProgressDialog('', '', 0, 100, self) - pd.setWindowModality(Qt.ApplicationModal) - pd.setCancelButton(None) - pd.setWindowTitle(_('Copying database')) - pd.show() - self.status_bar.showMessage( - _('Copying library to ')+newloc) - self.setCursor(Qt.BusyCursor) - self.library_view.setEnabled(False) - self.library_view.model().db.move_library_to( - newloc, pd) - else: - try: - db = LibraryDatabase2(newloc) - self.library_view.set_database(db) - except Exception, err: - traceback.print_exc() - d = error_dialog(self, _('Invalid database'), - _('

An invalid database already exists at ' - '%s, delete it before trying to move the ' - 'existing database.
Error: %s')%(newloc, - str(err))) - d.exec_() - self.library_path = \ - self.library_view.model().db.library_path - prefs['library_path'] = self.library_path - except Exception, err: - traceback.print_exc() - d = error_dialog(self, _('Could not move database'), - unicode(err)) - d.exec_() - finally: - self.unsetCursor() - self.library_view.setEnabled(True) - self.status_bar.clearMessage() - self.search.clear_to_help() - self.status_bar.reset_info() - self.library_view.sortByColumn(3, Qt.DescendingOrder) - self.library_view.resizeRowsToContents() if hasattr(d, 'directories'): set_sidebar_directories(d.directories) self.library_view.model().read_config() self.create_device_menu() + if not patheq(self.library_path, d.database_location): + newloc = d.database_location + move_library(self.library_path, newloc, self, + self.library_moved) + + + def library_moved(self, newloc): + if newloc is None: return + db = LibraryDatabase2(newloc) + self.library_view.set_database(db) + self.status_bar.clearMessage() + self.search.clear_to_help() + self.status_bar.reset_info() + self.library_view.sortByColumn(3, Qt.DescendingOrder) + ############################################################################ ################################ Book info ################################# @@ -1652,6 +1634,17 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.hide() return True + def run_wizard(self, *args): + if self.confirm_quit(): + self.run_wizard_b4_shutdown = True + self.restart_after_quit = True + try: + self.shutdown(write_settings=False) + except: + pass + QApplication.instance().quit() + + def closeEvent(self, e): self.write_settings() @@ -1726,12 +1719,19 @@ def init_qt(args): def run_gui(opts, args, actions, listener, app): initialize_file_icon_provider() + if not dynamic.get('welcome_wizard_was_run', False): + from calibre.gui2.wizard import wizard + wizard().exec_() + dynamic.set('welcome_wizard_was_run', True) main = Main(listener, opts, actions) sys.excepthook = main.unhandled_exception if len(args) > 1: args[1] = os.path.abspath(args[1]) main.add_filesystem_book(args[1]) ret = app.exec_() + if getattr(main, 'run_wizard_b4_shutdown', False): + from calibre.gui2.wizard import wizard + wizard().exec_() if getattr(main, 'restart_after_quit', False): e = sys.executable if getattr(sys, 'froze', False) else sys.argv[0] print 'Restarting with:', e, sys.argv diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py new file mode 100644 index 0000000000..3e18a638aa --- /dev/null +++ b/src/calibre/gui2/wizard/__init__.py @@ -0,0 +1,500 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import os, traceback, re +from Queue import Empty, Queue +from contextlib import closing + + +from PyQt4.Qt import QWizard, QWizardPage, QPixmap, Qt, QAbstractListModel, \ + QVariant, QItemSelectionModel, SIGNAL, QObject, QTimer +from calibre import __appname__, patheq +from calibre.library.database2 import LibraryDatabase2 +from calibre.library.move import MoveLibrary +from calibre.resources import server_resources +from calibre.gui2.wizard.send_email import smtp_prefs +from calibre.gui2.wizard.device_ui import Ui_WizardPage as DeviceUI +from calibre.gui2.wizard.library_ui import Ui_WizardPage as LibraryUI +from calibre.gui2.wizard.finish_ui import Ui_WizardPage as FinishUI +from calibre.gui2.wizard.kindle_ui import Ui_WizardPage as KindleUI +from calibre.gui2.wizard.stanza_ui import Ui_WizardPage as StanzaUI + +from calibre.utils.config import dynamic, prefs +from calibre.gui2 import NONE, choose_dir, error_dialog +from calibre.gui2.dialogs.progress import ProgressDialog + +class Device(object): + + output_profile = 'default' + output_format = 'EPUB' + name = _('Default') + manufacturer = _('Default') + id = 'default' + + @classmethod + def set_output_profile(cls): + if cls.output_profile: + from calibre.ebooks.conversion.config import load_defaults, save_defaults + recs = load_defaults('page_setup') + recs['output_profile'] = cls.output_profile + save_defaults('page_setup', recs) + + @classmethod + def set_output_format(cls): + if cls.output_format: + prefs.set('output_format', cls.output_format) + + @classmethod + def commit(cls): + cls.set_output_profile() + cls.set_output_format() + +class Kindle(Device): + + output_profile = 'kindle' + output_format = 'MOBI' + name = 'Kindle 1 or 2' + manufacturer = 'Amazon' + id = 'kindle' + +class Sony500(Device): + + output_profile = 'sony' + name = 'SONY PRS 500' + output_format = 'LRF' + manufacturer = 'SONY' + id = 'prs500' + +class Sony505(Sony500): + + output_format = 'EPUB' + name = 'SONY PRS 505/700' + id = 'prs505' + +class CybookG3(Device): + + name = 'Cybook Gen 3' + output_format = 'MOBI' + output_profile = 'cybookg3' + manufacturer = 'Booken' + id = 'cybookg3' + +class BeBook(Device): + + name = 'BeBook or BeBook Mini' + output_format = 'EPUB' + output_profile = 'sony' + manufacturer = 'Endless Ideas' + id = 'bebook' + +class iPhone(Device): + + name = 'iPhone/iTouch + Stanza' + output_format = 'EPUB' + manufacturer = 'Apple' + id = 'iphone' + +class Hanlin(Device): + + name = 'Hanlin V3' + output_format = 'MOBI' + output_profile = 'hanlinv3' + manufacturer = 'Hanlin' + id = 'hanlinv3' + +def get_devices(): + for x in globals().values(): + if isinstance(x, type) and issubclass(x, Device): + yield x + +def get_manufacturers(): + mans = set([]) + for x in get_devices(): + mans.add(x.manufacturer) + mans.remove(_('Default')) + return [_('Default')] + sorted(mans) + +def get_devices_of(manufacturer): + ans = [d for d in get_devices() if d.manufacturer == manufacturer] + return sorted(ans, cmp=lambda x,y:cmp(x.name, y.name)) + +class ManufacturerModel(QAbstractListModel): + + def __init__(self): + QAbstractListModel.__init__(self) + self.manufacturers = get_manufacturers() + + def rowCount(self, p): + return len(self.manufacturers) + + def columnCount(self, p): + return 1 + + def data(self, index, role): + if role == Qt.DisplayRole: + return QVariant(self.manufacturers[index.row()]) + if role == Qt.UserRole: + return self.manufacturers[index.row()] + return NONE + + def index_of(self, man): + for i, x in enumerate(self.manufacturers): + if x == man: + return self.index(i) + +class DeviceModel(QAbstractListModel): + + def __init__(self, manufacturer): + QAbstractListModel.__init__(self) + self.devices = get_devices_of(manufacturer) + + def rowCount(self, p): + return len(self.devices) + + def columnCount(self, p): + return 1 + + def data(self, index, role): + if role == Qt.DisplayRole: + return QVariant(self.devices[index.row()].name) + if role == Qt.UserRole: + return self.devices[index.row()] + return NONE + + def index_of(self, dev): + for i, device in enumerate(self.devices): + if device is dev: + return self.index(i) + +class KindlePage(QWizardPage, KindleUI): + + ID = 3 + + def __init__(self): + QWizardPage.__init__(self) + self.setupUi(self) + + def initializePage(self): + opts = smtp_prefs().parse() + for x in opts.accounts.keys(): + if x.strip().endswith('@kindle.com'): + self.to_address.setText(x) + def x(): + t = unicode(self.to_address.text()) + if t.strip(): + return t.strip() + + self.send_email_widget.initialize(x) + + def commit(self): + x = unicode(self.to_address.text()).strip() + parts = x.split('@') + if len(parts) < 2 or not parts[0]: return + + if self.send_email_widget.set_email_settings(True): + conf = smtp_prefs() + accounts = conf.get('accounts', {}) + if not accounts: accounts = {} + for y in accounts.values(): + y[2] = False + accounts[x] = ['AZW, MOBI, TPZ, PRC, AZW1', True, True] + conf.set('accounts', accounts) + + def nextId(self): + return FinishPage.ID + +class StanzaPage(QWizardPage, StanzaUI): + + ID = 5 + + def __init__(self): + QWizardPage.__init__(self) + self.setupUi(self) + self.connect(self.content_server, SIGNAL('stateChanged(int)'), self.set_port) + + def initializePage(self): + from calibre.gui2 import config + yes = config['autolaunch_server'] + self.content_server.setChecked(yes) + self.set_port() + + def nextId(self): + return FinishPage.ID + + def commit(self): + p = self.set_port() + if p is not None: + from calibre.library import server_config + c = server_config() + c.set('port', p) + + + def set_port(self, *args): + if not self.content_server.isChecked(): return + import socket + s = socket.socket() + with closing(s): + for p in range(8080, 8100): + try: + s.bind(('0.0.0.0', p)) + t = unicode(self.instructions.text()) + t = re.sub(r':\d+', ':'+str(p), t) + self.instructions.setText(t) + return p + except: + continue + + + + +class DevicePage(QWizardPage, DeviceUI): + + ID = 2 + + def __init__(self): + QWizardPage.__init__(self) + self.setupUi(self) + self.registerField("manufacturer", self.manufacturer_view) + self.registerField("device", self.device_view) + + def initializePage(self): + self.man_model = ManufacturerModel() + self.manufacturer_view.setModel(self.man_model) + previous = dynamic.get('welcome_wizard_device', False) + if previous: + previous = [x for x in get_devices() if \ + x.id == previous] + if not previous: + previous = [Device] + previous = previous[0] + else: + previous = Device + idx = self.man_model.index_of(previous.manufacturer) + if idx is None: + idx = self.man_model.index_of(Device.manufacturer) + previous = Device + self.manufacturer_view.selectionModel().select(idx, + QItemSelectionModel.Select) + self.dev_model = DeviceModel(self.man_model.data(idx, Qt.UserRole)) + idx = self.dev_model.index_of(previous) + self.device_view.setModel(self.dev_model) + self.device_view.selectionModel().select(idx, + QItemSelectionModel.Select) + self.connect(self.manufacturer_view.selectionModel(), + SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), + self.manufacturer_changed) + + def manufacturer_changed(self, current, previous): + new = list(current.indexes())[0] + man = self.man_model.data(new, Qt.UserRole) + self.dev_model = DeviceModel(man) + self.device_view.setModel(self.dev_model) + self.device_view.selectionModel().select(self.dev_model.index(0), + QItemSelectionModel.Select) + + def commit(self): + idx = list(self.device_view.selectionModel().selectedIndexes())[0] + dev = self.dev_model.data(idx, Qt.UserRole) + dev.commit() + dynamic.set('welcome_wizard_device', dev.id) + + def nextId(self): + idx = list(self.device_view.selectionModel().selectedIndexes())[0] + dev = self.dev_model.data(idx, Qt.UserRole) + if dev is Kindle: + return KindlePage.ID + if dev is iPhone: + return StanzaPage.ID + return FinishPage.ID + +class MoveMonitor(QObject): + + def __init__(self, worker, rq, callback, parent): + QObject.__init__(self, parent) + self.worker = worker + self.rq = rq + self.callback = callback + self.parent = parent + + self.worker.start() + self.dialog = ProgressDialog(_('Moving library...'), '', + max=self.worker.total, parent=parent) + self.dialog.button_box.setDisabled(True) + self.dialog.setModal(True) + self.dialog.show() + self.timer = QTimer(self) + self.connect(self.timer, SIGNAL('timeout()'), self.check) + self.timer.start(200) + + def check(self): + if self.worker.is_alive(): + self.update() + else: + self.timer.stop() + self.dialog.hide() + if self.worker.failed: + error_dialog(self.parent, _('Failed to move library'), + _('Failed to move library'), self.worker.details, show=True) + return self.callback(None) + else: + return self.callback(self.worker.to) + + def update(self): + try: + title = self.rq.get_nowait()[-1] + self.dialog.value += 1 + self.dialog.set_msg(_('Copied') + ' '+title) + except Empty: + pass + + +class Callback(object): + + def __init__(self, callback): + self.callback = callback + + def __call__(self, newloc): + if newloc is not None: + prefs['library_path'] = newloc + self.callback(newloc) + +_mm = None +def move_library(oldloc, newloc, parent, callback_on_complete): + callback = Callback(callback_on_complete) + try: + if not os.path.exists(os.path.join(newloc, 'metadata.db')): + if oldloc and os.access(os.path.join(oldloc, 'metadata.db'), os.R_OK): + # Move old library to new location + try: + db = LibraryDatabase2(oldloc) + except: + return move_library(None, newloc, parent, + callback) + else: + rq = Queue() + m = MoveLibrary(oldloc, newloc, db.data.count(), rq) + global _mm + _mm = MoveMonitor(m, rq, callback, parent) + return + else: + # Create new library at new location + db = LibraryDatabase2(newloc) + callback(newloc) + return + + # Try to load existing library at new location + try: + ndb = LibraryDatabase2(newloc) + except Exception, err: + det = traceback.format_exc() + error_dialog(parent, _('Invalid database'), + _('

An invalid library already exists at ' + '%s, delete it before trying to move the ' + 'existing library.
Error: %s')%(newloc, + str(err)), det, show=True) + callback(None) + return + else: + callback(newloc) + return + except Exception, err: + det = traceback.format_exc() + error_dialog(parent, _('Could not move library'), + unicode(err), det, show=True) + callback(None) + +class LibraryPage(QWizardPage, LibraryUI): + + ID = 1 + + def __init__(self): + QWizardPage.__init__(self) + self.setupUi(self) + self.registerField('library_location', self.location) + self.connect(self.button_change, SIGNAL('clicked()'), self.change) + + def change(self): + dir = choose_dir(self, 'database location dialog', + _('Select location for books')) + if dir: + self.location.setText(dir) + + def initializePage(self): + lp = prefs['library_path'] + if not lp: + lp = os.path.expanduser('~') + self.location.setText(lp) + + def isComplete(self): + lp = unicode(self.location.text()) + return lp and os.path.exists(lp) and os.path.isdir(lp) and os.access(lp, + os.W_OK) + + def commit(self, completed): + oldloc = prefs['library_path'] + newloc = unicode(self.location.text()) + if not patheq(oldloc, newloc): + move_library(oldloc, newloc, self.wizard(), completed) + return True + return False + + def nextId(self): + return DevicePage.ID + +class FinishPage(QWizardPage, FinishUI): + + ID = 4 + + def __init__(self): + QWizardPage.__init__(self) + self.setupUi(self) + + def nextId(self): + return -1 + + + +class Wizard(QWizard): + + def __init__(self, parent): + QWizard.__init__(self, parent) + self.setWindowTitle(__appname__+' '+_('welcome wizard')) + p = QPixmap() + p.loadFromData(server_resources['calibre.png']) + self.setPixmap(self.LogoPixmap, p.scaledToHeight(80, + Qt.SmoothTransformation)) + self.device_page = DevicePage() + self.library_page = LibraryPage() + self.finish_page = FinishPage() + self.kindle_page = KindlePage() + self.stanza_page = StanzaPage() + self.setPage(self.library_page.ID, self.library_page) + self.setPage(self.device_page.ID, self.device_page) + self.setPage(self.finish_page.ID, self.finish_page) + self.setPage(self.kindle_page.ID, self.kindle_page) + self.setPage(self.stanza_page.ID, self.stanza_page) + + self.device_extra_page = None + + def accept(self): + self.device_page.commit() + if not self.library_page.commit(self.completed): + self.completed(None) + + def completed(self, newloc): + return QWizard.accept(self) + +def wizard(parent=None): + w = Wizard(parent) + return w + +if __name__ == '__main__': + from PyQt4.Qt import QApplication + app = QApplication([]) + wizard().exec_() + diff --git a/src/calibre/gui2/wizard/device.ui b/src/calibre/gui2/wizard/device.ui new file mode 100644 index 0000000000..27af13b3ed --- /dev/null +++ b/src/calibre/gui2/wizard/device.ui @@ -0,0 +1,75 @@ + + + WizardPage + + + + 0 + 0 + 400 + 300 + + + + Welcome to calibre + + + + :/images/wizard.svg:/images/wizard.svg + + + Welcome to calibre + + + The one stop solution to all your e-book needs. + + + + + + Choose your book reader. This will set the conversion options to produce books optimized for your device. + + + true + + + + + + + &Manufacturers + + + + + + QAbstractItemView::SelectRows + + + + + + + + + + &Devices + + + + + + QAbstractItemView::SelectRows + + + + + + + + + + + + + diff --git a/src/calibre/gui2/wizard/finish.ui b/src/calibre/gui2/wizard/finish.ui new file mode 100644 index 0000000000..b42e8b1c32 --- /dev/null +++ b/src/calibre/gui2/wizard/finish.ui @@ -0,0 +1,108 @@ + + + WizardPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Welcome to calibre + + + The one stop solution to all your e-book needs. + + + + + + <h2>Congratulations!</h2> You have succesfully setup calibre. Press the Finish button to apply your settings. + + + true + + + + + + + Qt::Vertical + + + + 20 + 56 + + + + + + + + <h2>Demo videos</h2>Videos demonstrating the various features of calibre are available <a href="http://calibre.kovidgoyal.net/downloads/videos/">online</a>. + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + + + + + Qt::Vertical + + + + 20 + 56 + + + + + + + + <h2>User Manual</h2>A User Manual is also available <a href="http://calibre.kovidgoyal.net/user_manual">online</a>. + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/calibre/gui2/wizard/kindle.ui b/src/calibre/gui2/wizard/kindle.ui new file mode 100644 index 0000000000..7bafc6d618 --- /dev/null +++ b/src/calibre/gui2/wizard/kindle.ui @@ -0,0 +1,80 @@ + + + WizardPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Welcome to calibre + + + The one stop solution to all your e-book needs. + + + + + + <p>calibre can automatically send books by email to your Kindle. To do that you have to setup email delivery below. The easiest way is to setup a free <a href="http://gmail.com">gmail account</a> and click the Use gmail button below. You will also have to register your gmail address in your Amazon account. + + + true + + + true + + + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse + + + + + + + &Kindle email: + + + to_address + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + SendEmail + QWidget +

calibre/gui2/wizard/send_email.h
+ 1 + + + + + diff --git a/src/calibre/gui2/wizard/library.ui b/src/calibre/gui2/wizard/library.ui new file mode 100644 index 0000000000..d3c93bbd3c --- /dev/null +++ b/src/calibre/gui2/wizard/library.ui @@ -0,0 +1,74 @@ + + + WizardPage + + + + 0 + 0 + 481 + 300 + + + + WizardPage + + + Welcome to calibre + + + The one stop solution to all your e-book needs. + + + + + + Choose a location for your books. When you add books to calibre, they will be stored here: + + + true + + + + + + + true + + + + + + + &Change + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + If you have an existing calibre library, it will be copied to the new location. If a calibre library already exists at the new location, calibre will switch to using it. + + + true + + + + + + + + diff --git a/src/calibre/gui2/wizard/send_email.py b/src/calibre/gui2/wizard/send_email.py new file mode 100644 index 0000000000..5650279c15 --- /dev/null +++ b/src/calibre/gui2/wizard/send_email.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import cStringIO, sys +from binascii import hexlify, unhexlify + +from PyQt4.Qt import QWidget, SIGNAL, QDialog, Qt + +from calibre.gui2.wizard.send_email_ui import Ui_Form +from calibre.utils.smtp import config as smtp_prefs +from calibre.gui2.dialogs.test_email_ui import Ui_Dialog as TE_Dialog +from calibre.gui2 import error_dialog, info_dialog + +class TestEmail(QDialog, TE_Dialog): + + def __init__(self, pa, parent): + QDialog.__init__(self, parent) + TE_Dialog.__init__(self) + self.setupUi(self) + opts = smtp_prefs().parse() + self.test_func = parent.test_email_settings + self.connect(self.test_button, SIGNAL('clicked(bool)'), self.test) + self.from_.setText(unicode(self.from_.text())%opts.from_) + if pa: + self.to.setText(pa) + if opts.relay_host: + self.label.setText(_('Using: %s:%s@%s:%s and %s encryption')% + (opts.relay_username, unhexlify(opts.relay_password), + opts.relay_host, opts.relay_port, opts.encryption)) + + def test(self): + self.log.setPlainText(_('Sending...')) + self.test_button.setEnabled(False) + try: + tb = self.test_func(unicode(self.to.text())) + if not tb: + tb = _('Mail successfully sent') + self.log.setPlainText(tb) + finally: + self.test_button.setEnabled(True) + + +class SendEmail(QWidget, Ui_Form): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + self.setupUi(self) + + def initialize(self, preferred_to_address): + self.preferred_to_address = preferred_to_address + opts = smtp_prefs().parse() + self.smtp_opts = opts + if opts.from_: + self.email_from.setText(opts.from_) + if opts.relay_host: + self.relay_host.setText(opts.relay_host) + self.relay_port.setValue(opts.relay_port) + if opts.relay_username: + self.relay_username.setText(opts.relay_username) + if opts.relay_password: + self.relay_password.setText(unhexlify(opts.relay_password)) + (self.relay_tls if opts.encryption == 'TLS' else self.relay_ssl).setChecked(True) + self.connect(self.relay_use_gmail, SIGNAL('clicked(bool)'), + self.create_gmail_relay) + self.connect(self.relay_show_password, SIGNAL('stateChanged(int)'), + lambda + state:self.relay_password.setEchoMode(self.relay_password.Password if + state == 0 else self.relay_password.Normal)) + self.connect(self.test_email_button, SIGNAL('clicked(bool)'), + self.test_email) + + + def test_email(self, *args): + pa = self.preferred_to_address() + to_set = pa is not None + if self.set_email_settings(to_set): + TestEmail(pa, self).exec_() + + def test_email_settings(self, to): + opts = smtp_prefs().parse() + from calibre.utils.smtp import sendmail, create_mail + buf = cStringIO.StringIO() + oout, oerr = sys.stdout, sys.stderr + sys.stdout = sys.stderr = buf + tb = None + try: + msg = create_mail(opts.from_, to, 'Test mail from calibre', + 'Test mail from calibre') + sendmail(msg, from_=opts.from_, to=[to], + verbose=3, timeout=30, relay=opts.relay_host, + username=opts.relay_username, + password=unhexlify(opts.relay_password), + encryption=opts.encryption, port=opts.relay_port) + except: + import traceback + tb = traceback.format_exc() + tb += '\n\nLog:\n' + buf.getvalue() + finally: + sys.stdout, sys.stderr = oout, oerr + return tb + + def create_gmail_relay(self, *args): + self.relay_username.setText('@gmail.com') + self.relay_password.setText('') + self.relay_host.setText('smtp.gmail.com') + self.relay_port.setValue(587) + self.relay_tls.setChecked(True) + + info_dialog(self, _('Finish gmail setup'), + _('Dont forget to enter your gmail username and password')).exec_() + self.relay_username.setFocus(Qt.OtherFocusReason) + self.relay_username.setCursorPosition(0) + + def set_email_settings(self, to_set): + from_ = unicode(self.email_from.text()).strip() + if to_set and not from_: + error_dialog(self, _('Bad configuration'), + _('You must set the From email address')).exec_() + return False + username = unicode(self.relay_username.text()).strip() + password = unicode(self.relay_password.text()).strip() + host = unicode(self.relay_host.text()).strip() + if host and not (username and password): + error_dialog(self, _('Bad configuration'), + _('You must set the username and password for ' + 'the mail server.')).exec_() + return False + conf = smtp_prefs() + conf.set('from_', from_) + conf.set('relay_host', host if host else None) + conf.set('relay_port', self.relay_port.value()) + conf.set('relay_username', username if username else None) + conf.set('relay_password', hexlify(password)) + conf.set('encryption', 'TLS' if self.relay_tls.isChecked() else 'SSL') + return True + + + diff --git a/src/calibre/gui2/wizard/send_email.ui b/src/calibre/gui2/wizard/send_email.ui new file mode 100644 index 0000000000..3802d7f451 --- /dev/null +++ b/src/calibre/gui2/wizard/send_email.ui @@ -0,0 +1,234 @@ + + + Form + + + + 0 + 0 + 585 + 238 + + + + Form + + + + + + + + Send email &from: + + + email_from + + + + + + + <p>This is what will be present in the From: field of emails sent by calibre.<br> Set it to your email address + + + + + + + + + <p>A mail server is useful if the service you are sending mail to only accepts email from well know mail services. + + + Mail &Server + + + + + + calibre can <b>optionally</b> use a server to send mail + + + true + + + + + + + &Hostname: + + + relay_host + + + + + + + The hostname of your mail server. For e.g. smtp.gmail.com + + + + + + + + + &Port: + + + relay_port + + + + + + + The port your mail server listens for connections on. The default is 25 + + + 1 + + + 65555 + + + 25 + + + + + + + + + &Username: + + + relay_username + + + + + + + Your username on the mail server + + + + + + + &Password: + + + relay_password + + + + + + + Your password on the mail server + + + QLineEdit::Password + + + + + + + &Show + + + + + + + &Encryption: + + + relay_tls + + + + + + + Use TLS encryption when connecting to the mail server. This is the most common. + + + &TLS + + + true + + + + + + + Use SSL encryption when connecting to the mail server. + + + &SSL + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Use Gmail + + + + :/images/gmail_logo.png:/images/gmail_logo.png + + + + 48 + 48 + + + + Qt::ToolButtonTextUnderIcon + + + + + + + &Test email + + + + + + + + + + + + diff --git a/src/calibre/gui2/wizard/stanza.ui b/src/calibre/gui2/wizard/stanza.ui new file mode 100644 index 0000000000..6dee91f8fc --- /dev/null +++ b/src/calibre/gui2/wizard/stanza.ui @@ -0,0 +1,97 @@ + + + WizardPage + + + + 0 + 0 + 400 + 300 + + + + WizardPage + + + Welcome to calibre + + + The one stop solution to all your e-book needs. + + + + + + <p>If you use the <a href="http://www.lexcycle.com/download">Stanza</a> e-book app on your iPhone/iTouch, you can access your calibre book collection directly on the device. To do this you have to turn on the calibre content server. + + + true + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Turn on the &content server + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + <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 + 40 + + + + + + + + + diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 9135182258..1b373bf738 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1282,21 +1282,12 @@ class LibraryDatabase2(LibraryDatabase): if notify: self.notify('add', [id]) - def move_library_to(self, newloc, progress=None): - header = _(u'

Copying books to %s

')%newloc + def move_library_to(self, newloc, progress=lambda x: x): books = self.conn.get('SELECT id, path, title FROM books') - if progress is not None: - progress.setValue(0) - progress.setLabelText(header) - QCoreApplication.processEvents() - progress.setAutoReset(False) - progress.setRange(0, len(books)) if not os.path.exists(newloc): os.makedirs(newloc) old_dirs = set([]) for i, book in enumerate(books): - if progress is not None: - progress.setLabelText(header+_(u'Copying %s')%book[2]) path = book[1] if not path: continue @@ -1308,8 +1299,7 @@ class LibraryDatabase2(LibraryDatabase): if os.path.exists(srcdir): shutil.copytree(srcdir, tdir) old_dirs.add(srcdir) - if progress is not None: - progress.setValue(i+1) + progress(book[2]) dbpath = os.path.join(newloc, os.path.basename(self.dbpath)) shutil.copyfile(self.dbpath, dbpath) @@ -1323,10 +1313,6 @@ class LibraryDatabase2(LibraryDatabase): shutil.rmtree(dir) except: pass - if progress is not None: - progress.reset() - progress.hide() - def __iter__(self): for record in self.data._data: diff --git a/src/calibre/library/move.py b/src/calibre/library/move.py new file mode 100644 index 0000000000..d162d962fe --- /dev/null +++ b/src/calibre/library/move.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import time, os +from threading import Thread +from Queue import Empty + +from calibre.library.database2 import LibraryDatabase2 +from calibre.utils.ipc.server import Server +from calibre.utils.ipc.job import ParallelJob + + +def move_library(from_, to, notification = lambda x:x): + time.sleep(1) + old = LibraryDatabase2(from_) + old.move_library_to(to, notification) + return True + +class MoveLibrary(Thread): + + def __init__(self, from_, to, count, result_queue): + Thread.__init__(self) + self.total = count + self.result_queue = result_queue + self.from_ = from_ + self.to = to + self.count = 0 + self.failed = False + self.details = None + + def run(self): + job = ParallelJob('move_library', + 'Move library from %s to %s'%(self.from_, self.to), + lambda x,y:x, + args=[self.from_, self.to]) + server = Server(pool_size=1) + server.add_job(job) + + while not job.is_finished: + time.sleep(0.2) + job.update(consume_notifications=False) + while True: + try: + title = job.notifications.get_nowait()[0] + self.count += 1 + self.result_queue.put((float(self.count)/self.total, title)) + except Empty: + break + + job.update() + server.close() + if not job.result: + self.failed = True + self.details = job.details + + if os.path.exists(job.log_path): + os.remove(job.log_path) + diff --git a/src/calibre/library/static/calibre.png b/src/calibre/library/static/calibre.png index f42e4926cab58d97b12c7864a22bee74a6ae8047..871a5e31a802e1fd907f3a5407cd3ddc026aa244 100644 GIT binary patch literal 25399 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', re.DOTALL).sub('', dsrc).strip()) == 0: raise ValueError('No content at URL %s'%iurl) if self.encoding is not None: - dsrc = dsrc.decode(self.encoding, 'ignore') + dsrc = dsrc.decode(self.encoding, 'replace') else: dsrc = xml_to_unicode(dsrc, self.verbose)[0] diff --git a/todo b/todo index 2777a54070..bff97ad7e8 100644 --- a/todo +++ b/todo @@ -5,7 +5,5 @@ * Testing framework -* Welcome wizard - * MOBI navigation indexing support