-
+
-
- -
+
-
Enable sharing of book content via Facebook, etc. WARNING: Disables last read syncing
+ -
+
+
+ <b>WARNING:</b> Various Kindle devices have trouble displaying the new or both MOBI filetypes. If you wish to use the new format on your device, convert to AZW3 instead of MOBI.
+
+
+ true
+
+
+
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index 6d638ef9c2..8466fe9320 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -24,7 +24,8 @@ from calibre.gui2 import (config, error_dialog, Dispatcher, dynamic,
from calibre.ebooks.metadata import authors_to_string
from calibre import preferred_encoding, prints, force_unicode, as_unicode
from calibre.utils.filenames import ascii_filename
-from calibre.devices.errors import FreeSpaceError, WrongDestinationError
+from calibre.devices.errors import (FreeSpaceError, WrongDestinationError,
+ BlacklistedDevice)
from calibre.devices.apple.driver import ITUNES_ASYNC
from calibre.devices.folder_device.driver import FOLDER_DEVICE
from calibre.devices.bambook.driver import BAMBOOK, BAMBOOKWifi
@@ -252,6 +253,9 @@ class DeviceManager(Thread): # {{{
if cd is not None:
try:
dev.open(cd, self.current_library_uuid)
+ except BlacklistedDevice as e:
+ prints('Ignoring blacklisted device: %s'%
+ as_unicode(e))
except:
prints('Error while trying to open %s (Driver: %s)'%
(cd, dev))
diff --git a/src/calibre/gui2/device_drivers/mtp_config.py b/src/calibre/gui2/device_drivers/mtp_config.py
index b6628f4e65..7481cbf19c 100644
--- a/src/calibre/gui2/device_drivers/mtp_config.py
+++ b/src/calibre/gui2/device_drivers/mtp_config.py
@@ -11,11 +11,13 @@ import weakref
from PyQt4.Qt import (QWidget, QListWidgetItem, Qt, QToolButton, QLabel,
QTabWidget, QGridLayout, QListWidget, QIcon, QLineEdit, QVBoxLayout,
- QPushButton)
+ QPushButton, QGroupBox, QScrollArea, QHBoxLayout, QComboBox,
+ pyqtSignal, QSizePolicy, QDialog, QDialogButtonBox)
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.template_dialog import TemplateDialog
+from calibre.utils.date import parse_date
class FormatsConfig(QWidget): # {{{
@@ -85,7 +87,7 @@ class TemplateConfig(QWidget): # {{{
m.setBuddy(t)
l.addWidget(m, 0, 0, 1, 2)
l.addWidget(t, 1, 0, 1, 1)
- b = self.b = QPushButton(_('Template editor'))
+ b = self.b = QPushButton(_('&Template editor'))
l.addWidget(b, 1, 1, 1, 1)
b.clicked.connect(self.edit_template)
@@ -136,6 +138,152 @@ class SendToConfig(QWidget): # {{{
# }}}
+class IgnoredDevices(QWidget): # {{{
+
+ def __init__(self, devs, blacklist):
+ QWidget.__init__(self)
+ self.l = l = QVBoxLayout()
+ self.setLayout(l)
+ self.la = la = QLabel(''+_(
+ '''Select the devices to be ignored. calibre will not
+ connect to devices with a checkmark next to their names.'''))
+ la.setWordWrap(True)
+ l.addWidget(la)
+ self.f = f = QListWidget(self)
+ l.addWidget(f)
+
+ devs = [(snum, (x[0], parse_date(x[1]))) for snum, x in
+ devs.iteritems()]
+ for dev, x in sorted(devs, key=lambda x:x[1][1], reverse=True):
+ name = x[0]
+ name = '%s [%s]'%(name, dev)
+ item = QListWidgetItem(name, f)
+ item.setData(Qt.UserRole, dev)
+ item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
+ item.setCheckState(Qt.Checked if dev in blacklist else Qt.Unchecked)
+
+ @property
+ def blacklist(self):
+ return [unicode(self.f.item(i).data(Qt.UserRole).toString()) for i in
+ xrange(self.f.count()) if self.f.item(i).checkState()==Qt.Checked]
+
+ def ignore_device(self, snum):
+ for i in xrange(self.f.count()):
+ i = self.f.item(i)
+ c = unicode(i.data(Qt.UserRole).toString())
+ if c == snum:
+ i.setCheckState(Qt.Checked)
+ break
+
+# }}}
+
+# Rules {{{
+
+class Rule(QWidget):
+
+ remove = pyqtSignal(object)
+
+ def __init__(self, rule=None):
+ QWidget.__init__(self)
+
+ self.l = l = QHBoxLayout()
+ self.setLayout(l)
+
+ self.l1 = l1 = QLabel(_('Send the '))
+ l.addWidget(l1)
+ self.fmt = f = QComboBox(self)
+ l.addWidget(f)
+ self.l2 = l2 = QLabel(_(' format to the folder: '))
+ l.addWidget(l2)
+ self.folder = f = QLineEdit(self)
+ f.setPlaceholderText(_('Folder on the device'))
+ l.addWidget(f)
+ self.rb = rb = QPushButton(QIcon(I('list_remove.png')),
+ _('&Remove rule'), self)
+ l.addWidget(rb)
+ rb.clicked.connect(self.removed)
+
+ for fmt in sorted(BOOK_EXTENSIONS):
+ self.fmt.addItem(fmt.upper(), fmt.lower())
+
+ self.fmt.setCurrentIndex(0)
+
+ if rule is not None:
+ fmt, folder = rule
+ idx = self.fmt.findText(fmt.upper())
+ if idx > -1:
+ self.fmt.setCurrentIndex(idx)
+ self.folder.setText(folder)
+
+ self.ignore = False
+
+ def removed(self):
+ self.remove.emit(self)
+
+ @property
+ def rule(self):
+ folder = unicode(self.folder.text()).strip()
+ if folder:
+ return (
+ unicode(self.fmt.itemData(self.fmt.currentIndex()).toString()),
+ folder
+ )
+ return None
+
+class FormatRules(QGroupBox):
+
+ def __init__(self, rules):
+ QGroupBox.__init__(self, _('Format specific sending'))
+ self.l = l = QVBoxLayout()
+ self.setLayout(l)
+ self.la = la = QLabel('
'+_(
+ '''You can create rules that control where ebooks of a specific
+ format are sent to on the device. These will take precedence over
+ the folders specified above.'''))
+ la.setWordWrap(True)
+ l.addWidget(la)
+ self.sa = sa = QScrollArea(self)
+ sa.setWidgetResizable(True)
+ self.w = w = QWidget(self)
+ w.l = QVBoxLayout()
+ w.setLayout(w.l)
+ sa.setWidget(w)
+ l.addWidget(sa)
+ self.widgets = []
+ for rule in rules:
+ r = Rule(rule)
+ self.widgets.append(r)
+ w.l.addWidget(r)
+ r.remove.connect(self.remove_rule)
+
+ if not self.widgets:
+ self.add_rule()
+
+ self.b = b = QPushButton(QIcon(I('plus.png')), _('Add a &new rule'))
+ l.addWidget(b)
+ b.clicked.connect(self.add_rule)
+ self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)
+
+ def add_rule(self):
+ r = Rule()
+ self.widgets.append(r)
+ self.w.l.addWidget(r)
+ r.remove.connect(self.remove_rule)
+ self.sa.verticalScrollBar().setValue(self.sa.verticalScrollBar().maximum())
+
+ def remove_rule(self, rule):
+ rule.setVisible(False)
+ rule.ignore = True
+
+ @property
+ def rules(self):
+ for w in self.widgets:
+ if not w.ignore:
+ r = w.rule
+ if r is not None:
+ yield r
+# }}}
+
class MTPConfig(QTabWidget):
def __init__(self, device, parent=None):
@@ -145,8 +293,8 @@ class MTPConfig(QTabWidget):
cd = msg = None
if device.current_friendly_name is not None:
if device.current_serial_num is None:
- msg = '
' + _('The %s device has no serial number, '
- 'it cannot be configured'%device.current_friendly_name)
+ msg = '
' + (_('The %s device has no serial number, '
+ 'it cannot be configured')%device.current_friendly_name)
else:
cd = 'device-'+device.current_serial_num
else:
@@ -162,6 +310,8 @@ class MTPConfig(QTabWidget):
l = QLabel(msg)
l.setWordWrap(True)
l.setStyleSheet('QLabel { margin-left: 2em }')
+ l.setMinimumWidth(500)
+ l.setMinimumHeight(400)
self.insertTab(0, l, _('Cannot configure'))
else:
self.base = QWidget(self)
@@ -169,20 +319,42 @@ class MTPConfig(QTabWidget):
l = self.base.l = QGridLayout(self.base)
self.base.setLayout(l)
+ self.rules = r = FormatRules(self.get_pref('rules'))
self.formats = FormatsConfig(set(BOOK_EXTENSIONS),
self.get_pref('format_map'))
self.send_to = SendToConfig(self.get_pref('send_to'))
self.template = TemplateConfig(self.get_pref('send_template'))
- self.base.la = la = QLabel(_('Choose the formats to send to the %s')%self.device.current_friendly_name)
+ self.base.la = la = QLabel(_(
+ 'Choose the formats to send to the %s')%self.device.current_friendly_name)
la.setWordWrap(True)
- l.addWidget(la, 0, 0, 1, 1)
- l.addWidget(self.formats, 1, 0, 3, 1)
- l.addWidget(self.send_to, 1, 1, 1, 1)
- l.addWidget(self.template, 2, 1, 1, 1)
- l.setRowStretch(2, 10)
+ self.base.b = b = QPushButton(QIcon(I('list_remove.png')),
+ _('&Ignore the %s in calibre')%device.current_friendly_name,
+ self.base)
+ b.clicked.connect(self.ignore_device)
+
+ l.addWidget(b, 0, 0, 1, 2)
+ l.addWidget(la, 1, 0, 1, 1)
+ l.addWidget(self.formats, 2, 0, 3, 1)
+ l.addWidget(self.send_to, 2, 1, 1, 1)
+ l.addWidget(self.template, 3, 1, 1, 1)
+ l.setRowStretch(4, 10)
+ l.addWidget(r, 5, 0, 1, 2)
+ l.setRowStretch(5, 100)
+
+ self.igntab = IgnoredDevices(self.device.prefs['history'],
+ self.device.prefs['blacklist'])
+ self.addTab(self.igntab, _('Ignored devices'))
self.setCurrentIndex(0)
+ def ignore_device(self):
+ self.igntab.ignore_device(self.device.current_serial_num)
+ self.base.b.setEnabled(False)
+ self.base.b.setText(_('The %s will be ignored in calibre')%
+ self.device.current_friendly_name)
+ self.base.b.setStyleSheet('QPushButton { font-weight: bold }')
+ self.base.setEnabled(False)
+
def get_pref(self, key):
p = self.device.prefs.get(self.current_device_key, {})
if not p:
@@ -194,31 +366,40 @@ class MTPConfig(QTabWidget):
return self._device()
def validate(self):
- if not self.formats.validate():
- return False
- if not self.template.validate():
- return False
+ if hasattr(self, 'formats'):
+ if not self.formats.validate():
+ return False
+ if not self.template.validate():
+ return False
return True
def commit(self):
p = self.device.prefs.get(self.current_device_key, {})
- p.pop('format_map', None)
- f = self.formats.format_map
- if f and f != self.device.prefs['format_map']:
- p['format_map'] = f
+ if hasattr(self, 'formats'):
+ p.pop('format_map', None)
+ f = self.formats.format_map
+ if f and f != self.device.prefs['format_map']:
+ p['format_map'] = f
- p.pop('send_template', None)
- t = self.template.template
- if t and t != self.device.prefs['send_template']:
- p['send_template'] = t
+ p.pop('send_template', None)
+ t = self.template.template
+ if t and t != self.device.prefs['send_template']:
+ p['send_template'] = t
- p.pop('send_to', None)
- s = self.send_to.value
- if s and s != self.device.prefs['send_to']:
- p['send_to'] = s
+ p.pop('send_to', None)
+ s = self.send_to.value
+ if s and s != self.device.prefs['send_to']:
+ p['send_to'] = s
- self.device.prefs[self.current_device_key] = p
+ p.pop('rules', None)
+ r = list(self.rules.rules)
+ if r and r != self.device.prefs['rules']:
+ p['rules'] = r
+
+ self.device.prefs[self.current_device_key] = p
+
+ self.device.prefs['blacklist'] = self.igntab.blacklist
if __name__ == '__main__':
from calibre.gui2 import Application
@@ -232,8 +413,16 @@ if __name__ == '__main__':
cd = dev.detect_managed_devices(s.devices)
dev.open(cd, 'test')
cw = dev.config_widget()
- cw.show()
- app.exec_()
+ d = QDialog()
+ d.l = QVBoxLayout()
+ d.setLayout(d.l)
+ d.l.addWidget(cw)
+ bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
+ d.l.addWidget(bb)
+ bb.accepted.connect(d.accept)
+ bb.rejected.connect(d.reject)
+ if d.exec_() == d.Accepted:
+ cw.commit()
dev.shutdown()
diff --git a/src/calibre/gui2/proceed.py b/src/calibre/gui2/proceed.py
index 1074792096..9bdf48e086 100644
--- a/src/calibre/gui2/proceed.py
+++ b/src/calibre/gui2/proceed.py
@@ -11,18 +11,18 @@ from collections import namedtuple
from PyQt4.Qt import (QDialog, Qt, QLabel, QGridLayout, QPixmap,
QDialogButtonBox, QApplication, QSize, pyqtSignal, QIcon,
- QPlainTextEdit)
+ QPlainTextEdit, QCheckBox)
from calibre.constants import __version__
from calibre.gui2.dialogs.message_box import ViewLog
Question = namedtuple('Question', 'payload callback cancel_callback '
'title msg html_log log_viewer_title log_is_file det_msg '
- 'show_copy_button')
+ 'show_copy_button checkbox_msg checkbox_checked')
class ProceedQuestion(QDialog):
- ask_question = pyqtSignal(object, object)
+ ask_question = pyqtSignal(object, object, object)
def __init__(self, parent):
QDialog.__init__(self, parent)
@@ -62,10 +62,13 @@ class ProceedQuestion(QDialog):
self.bb.setStandardButtons(self.bb.Yes|self.bb.No)
self.bb.button(self.bb.Yes).setDefault(True)
+ self.checkbox = QCheckBox('', self)
+
l.addWidget(ic, 0, 0, 1, 1)
l.addWidget(msg, 0, 1, 1, 1)
- l.addWidget(self.det_msg, 1, 0, 1, 2)
- l.addWidget(self.bb, 2, 0, 1, 2)
+ l.addWidget(self.checkbox, 1, 0, 1, 2)
+ l.addWidget(self.det_msg, 2, 0, 1, 2)
+ l.addWidget(self.bb, 3, 0, 1, 2)
self.ask_question.connect(self.do_ask_question,
type=Qt.QueuedConnection)
@@ -82,19 +85,28 @@ class ProceedQuestion(QDialog):
if self.questions:
payload, callback, cancel_callback = self.questions[0][:3]
self.questions = self.questions[1:]
- self.ask_question.emit(callback, payload)
+ cb = None
+ if self.checkbox.isVisible():
+ cb = bool(self.checkbox.isChecked())
+ self.ask_question.emit(callback, payload, cb)
self.hide()
def reject(self):
if self.questions:
payload, callback, cancel_callback = self.questions[0][:3]
self.questions = self.questions[1:]
- self.ask_question.emit(cancel_callback, payload)
+ cb = None
+ if self.checkbox.isVisible():
+ cb = bool(self.checkbox.isChecked())
+ self.ask_question.emit(cancel_callback, payload, cb)
self.hide()
- def do_ask_question(self, callback, payload):
+ def do_ask_question(self, callback, payload, checkbox_checked):
if callable(callback):
- callback(payload)
+ args = [payload]
+ if checkbox_checked is not None:
+ args.append(checkbox_checked)
+ callback(*args)
self.show_question()
def toggle_det_msg(self, *args):
@@ -122,6 +134,10 @@ class ProceedQuestion(QDialog):
self.det_msg.setVisible(False)
self.det_msg_toggle.setVisible(bool(question.det_msg))
self.det_msg_toggle.setText(self.show_det_msg)
+ self.checkbox.setVisible(question.checkbox_msg is not None)
+ if question.checkbox_msg is not None:
+ self.checkbox.setText(question.checkbox_msg)
+ self.checkbox.setChecked(question.checkbox_checked)
self.do_resize()
self.show()
self.bb.button(self.bb.Yes).setDefault(True)
@@ -129,10 +145,10 @@ class ProceedQuestion(QDialog):
def __call__(self, callback, payload, html_log, log_viewer_title, title,
msg, det_msg='', show_copy_button=False, cancel_callback=None,
- log_is_file=False):
+ log_is_file=False, checkbox_msg=None, checkbox_checked=False):
'''
A non modal popup that notifies the user that a background task has
- been completed. This class guarantees that onlya single popup is
+ been completed. This class guarantees that only a single popup is
visible at any one time. Other requests are queued and displayed after
the user dismisses the current popup.
@@ -147,11 +163,18 @@ class ProceedQuestion(QDialog):
:param msg: The msg to display
:param det_msg: Detailed message
:param log_is_file: If True the html_log parameter is interpreted as
- the path to a file on disk containing the log encoded with utf-8
+ the path to a file on disk containing the log
+ encoded with utf-8
+ :param checkbox_msg: If not None, a checkbox is displayed in the
+ dialog, showing this message. The callback is
+ called with both the payload and the state of the
+ checkbox as arguments.
+ :param checkbox_checked: If True the checkbox is checked by default.
+
'''
question = Question(payload, callback, cancel_callback, title, msg,
html_log, log_viewer_title, log_is_file, det_msg,
- show_copy_button)
+ show_copy_button, checkbox_msg, checkbox_checked)
self.questions.append(question)
self.show_question()
@@ -169,7 +192,8 @@ def main():
from calibre.gui2 import Application
app = Application([])
p = ProceedQuestion(None)
- p(lambda p:None, None, 'ass', 'ass', 'testing', 'testing')
+ p(lambda p:None, None, 'ass', 'ass', 'testing', 'testing',
+ checkbox_msg='testing the ruddy checkbox', det_msg='details')
p.exec_()
app
diff --git a/src/calibre/gui2/update.py b/src/calibre/gui2/update.py
index a7bc341a96..0b685e2fd2 100644
--- a/src/calibre/gui2/update.py
+++ b/src/calibre/gui2/update.py
@@ -75,7 +75,7 @@ class UpdateNotification(QDialog):
self.logo.setPixmap(QPixmap(I('lt.png')).scaled(100, 100,
Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
self.label = QLabel(('
'+
- _('%(app)s has been updated to version %(ver)s. '
+ _('New version %(ver)s of %(app)s is available for download. '
'See the new features.'))%dict(
app=__appname__, ver=calibre_version))
diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py
index 3fc0df58b2..f592437916 100644
--- a/src/calibre/library/catalogs/epub_mobi_builder.py
+++ b/src/calibre/library/catalogs/epub_mobi_builder.py
@@ -211,15 +211,15 @@ class CatalogBuilder(object):
(str): sort key
"""
if not book['series']:
- fs = '{:<%d}!{!s}' % longest_author_sort
+ fs = u'{:<%d}!{!s}' % longest_author_sort
key = fs.format(capitalize(book['author_sort']),
capitalize(book['title_sort']))
else:
index = book['series_index']
integer = int(index)
fraction = index-integer
- series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0'))
- fs = '{:<%d}~{!s}{!s}' % longest_author_sort
+ series_index = u'%04d%s' % (integer, str(u'%0.4f' % fraction).lstrip(u'0'))
+ fs = u'{:<%d}~{!s}{!s}' % longest_author_sort
key = fs.format(capitalize(book['author_sort']),
self.generate_sort_title(book['series']),
series_index)
@@ -2464,7 +2464,9 @@ class CatalogBuilder(object):
title_str=title_str,
xmlns=XHTML_NS,
)
-
+ for k, v in args.iteritems():
+ if isbytestring(v):
+ args[k] = v.decode('utf-8')
generated_html = P('catalog/template.xhtml',
data=True).decode('utf-8').format(**args)
generated_html = substitute_entites(generated_html)
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 17c01a6f56..0b23e3f0a4 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -1432,6 +1432,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
pdir = os.path.dirname(dest)
if not os.path.exists(pdir):
os.makedirs(pdir)
+ size = 0
if copy_function is not None:
copy_function(dest)
size = os.path.getsize(dest)
@@ -1441,6 +1442,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
with lopen(dest, 'wb') as f:
shutil.copyfileobj(stream, f)
size = f.tell()
+ elif os.path.exists(dest):
+ size = os.path.getsize(dest)
self.conn.execute('INSERT OR REPLACE INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)',
(id, format.upper(), size, name))
self.update_last_modified([id], commit=False)
diff --git a/src/calibre/library/server/base.py b/src/calibre/library/server/base.py
index 3554268c3b..884d273ea9 100644
--- a/src/calibre/library/server/base.py
+++ b/src/calibre/library/server/base.py
@@ -5,7 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import os
+import os, socket
import logging
from logging.handlers import RotatingFileHandler
@@ -17,7 +17,7 @@ from calibre.utils.date import fromtimestamp
from calibre.library.server import listen_on, log_access_file, log_error_file
from calibre.library.server.utils import expose, AuthController
from calibre.utils.mdns import publish as publish_zeroconf, \
- unpublish as unpublish_zeroconf, get_external_ip
+ unpublish as unpublish_zeroconf, get_external_ip, verify_ipV4_address
from calibre.library.server.content import ContentServer
from calibre.library.server.mobile import MobileServer
from calibre.library.server.xml import XMLServer
@@ -78,6 +78,7 @@ class BonJour(SimplePlugin): # {{{
SimplePlugin.__init__(self, engine)
self.port = port
self.prefix = prefix
+ self.ip_address = '0.0.0.0'
@property
def mdns_services(self):
@@ -90,9 +91,10 @@ class BonJour(SimplePlugin): # {{{
def start(self):
+ zeroconf_ip_address = verify_ipV4_address(self.ip_address)
try:
for s in self.mdns_services:
- publish_zeroconf(*s)
+ publish_zeroconf(*s, use_ip_address=zeroconf_ip_address)
except:
import traceback
cherrypy.log.error('Failed to start BonJour:')
@@ -140,6 +142,7 @@ class LibraryServer(ContentServer, MobileServer, XMLServer, OPDSServer, Cache,
if not opts.url_prefix:
opts.url_prefix = ''
+ cherrypy.engine.bonjour.ip_address = listen_on
cherrypy.engine.bonjour.port = opts.port
cherrypy.engine.bonjour.prefix = opts.url_prefix
diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot
index 631e78aa95..c2f5e1b38c 100644
--- a/src/calibre/translations/calibre.pot
+++ b/src/calibre/translations/calibre.pot
@@ -5,8 +5,8 @@
msgid ""
msgstr ""
"Project-Id-Version: calibre 0.8.68\n"
-"POT-Creation-Date: 2012-09-07 08:49+IST\n"
-"PO-Revision-Date: 2012-09-07 08:49+IST\n"
+"POT-Creation-Date: 2012-09-08 17:09+IST\n"
+"PO-Revision-Date: 2012-09-08 17:09+IST\n"
"Last-Translator: Automatically generated\n"
"Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n"
@@ -131,8 +131,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/ebooks/pdb/ztxt/writer.py:27
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:108
#: /home/kovid/work/calibre/src/calibre/ebooks/pdf/writer.py:109
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:426
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:434
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:439
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:447
#: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:166
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:397
#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:400
@@ -143,8 +143,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:124
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:143
#: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:145
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1366
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1369
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1367
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1370
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:55
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:60
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:128
@@ -173,12 +173,12 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/library/database2.py:585
#: /home/kovid/work/calibre/src/calibre/library/database2.py:593
#: /home/kovid/work/calibre/src/calibre/library/database2.py:604
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2189
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2343
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:2768
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:3415
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:3417
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:3554
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2192
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2346
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:2771
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:3418
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:3420
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:3557
#: /home/kovid/work/calibre/src/calibre/library/server/content.py:250
#: /home/kovid/work/calibre/src/calibre/library/server/content.py:251
#: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:247
@@ -1034,14 +1034,14 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/tag_browser/model.py:1199
#: /home/kovid/work/calibre/src/calibre/library/database2.py:370
#: /home/kovid/work/calibre/src/calibre/library/database2.py:383
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:3272
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:3275
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:187
msgid "News"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2770
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:3228
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:3246
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:3231
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:3249
msgid "Catalog"
msgstr ""
@@ -3352,8 +3352,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:70
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:163
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:401
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2232
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:292
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2142
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:140
msgid "Series"
msgid_plural "Series"
@@ -3880,7 +3880,17 @@ msgstr ""
msgid "Show this confirmation again"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:546
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:332
+#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:134
+msgid "Restart needed"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:334
+#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:741
+msgid "Restart calibre now"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:559
msgid "Choose Files"
msgstr ""
@@ -4112,7 +4122,7 @@ msgid "Merging user annotations into database"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:63
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:744
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:745
msgid "Fetch annotations (experimental)"
msgstr ""
@@ -4357,7 +4367,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:403
#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:197
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:933
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:934
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1004
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:114
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:128
@@ -4590,14 +4600,14 @@ msgid "Main memory"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:239
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:669
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:678
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:670
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:679
msgid "Storage Card A"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:240
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:671
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:680
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:672
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:681
msgid "Storage Card B"
msgstr ""
@@ -5373,7 +5383,7 @@ msgid "The specified directory could not be processed."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/add.py:274
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1087
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
msgid "No books"
msgstr ""
@@ -5794,6 +5804,18 @@ msgstr ""
msgid "E-book options"
msgstr ""
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:90
+msgid "Catalogs"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:99
+msgid "Read book"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:105
+msgid "Wishlist item"
+msgstr ""
+
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:133
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:769
msgid "any date"
@@ -5831,7 +5853,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:615
#, python-format
-msgid "Are you sure you want to delete rules #%d-%d?"
+msgid "Are you sure you want to delete rules #%(first)d-%(last)d?"
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:616
@@ -7685,226 +7707,222 @@ msgstr ""
msgid "tags to remove"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:50
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:51
#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:148
msgid "No details available."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:202
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:203
msgid "Device no longer connected."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:413
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:414
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/device_debug.py:27
msgid "Debug device detection"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:429
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:430
msgid "Get device information"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:444
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:445
msgid "Get list of books on device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:451
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:452
msgid "Prepare files for transfer from device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:462
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:463
msgid "Get annotations from device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:474
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:475
msgid "Send metadata to device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:479
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:480
msgid "Send collections to device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:529
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:530
#, python-format
msgid "Upload %d books to device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:545
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:546
msgid "Delete books from device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:563
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:564
msgid "Download books from device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:573
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:574
msgid "View book on device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:652
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:653
msgid "Set default send to device action"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:658
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:659
msgid "Send to main memory"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:660
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:661
msgid "Send to storage card A"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:662
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:663
msgid "Send to storage card B"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:667
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:676
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:668
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:677
msgid "Main Memory"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:688
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:689
msgid "Send specific format to"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:689
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:690
msgid "Send and delete from library"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:732
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:733
msgid "Eject device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:813
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:814
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:71
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:332
#: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:58
msgid "Error"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:814
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:815
msgid "Error communicating with device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:843
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1416
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:844
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1417
#: /home/kovid/work/calibre/src/calibre/gui2/email.py:260
msgid "No suitable formats"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:859
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:860
msgid "Select folder to open as device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:877
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878
msgid "Running jobs"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:878
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:879
msgid "Cannot configure the device while there are running device jobs."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:883
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:884
#: /home/kovid/work/calibre/src/calibre/gui2/device_drivers/mtp_config.py:168
#, python-format
msgid "Configure %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:898
-msgid "Disconnect device"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:899
#, python-format
-msgid "Disconnect and re-connect the %s for your changes to be applied."
-msgstr ""
-
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:939
-msgid "Error talking to device"
+msgid "Restart calibre for the changes to %s to be applied."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:940
+msgid "Error talking to device"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:941
msgid "There was a temporary error talking to the device. Please unplug and reconnect the device or reboot."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:984
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:985
msgid "Device: "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:986
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:987
msgid " detected."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1089
msgid "selected to send"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1095
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1125
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1096
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1126
msgid "No device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1096
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1097
msgid "No device connected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1112
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1113
#, python-format
msgid "%(num)i of %(total)i Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1116
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1117
#, python-format
msgid "0 of %i Books"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1117
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1118
msgid "Choose format to send to device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1126
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1127
msgid "Cannot send: No device is connected"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1129
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1133
-msgid "No card"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1130
#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1134
+msgid "No card"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1131
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1135
msgid "Cannot send: Device has no storage card"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1195
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1278
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1410
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1196
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1279
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1411
msgid "Auto convert the following books before uploading to the device?"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1224
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1225
msgid "Sending catalogs to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1323
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1324
msgid "Sending news to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1377
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1378
msgid "Sending books to device."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1417
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1418
msgid "Could not upload the following books to the device, as no suitable formats were found. Convert the book(s) to a format supported by your device first."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1490
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1491
msgid "No space on device"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1491
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1492
msgid "Cannot upload books to device there is no more free space available "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1496
+#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1497
msgid "Incorrect destination"
msgstr ""
@@ -9471,11 +9489,6 @@ msgstr ""
msgid "Plugin {0} successfully installed under {1} plugins. You may have to restart calibre for the plugin to take effect."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:741
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:352
-msgid "Restart calibre now"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:760
msgid "A problem occurred while installing this plugin. This plugin will now be uninstalled. Please post the error message in details below into the forum thread for this plugin and restart Calibre."
msgstr ""
@@ -9529,8 +9542,8 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:87
#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:156
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:397
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1342
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:288
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1254
#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:113
msgid "Authors"
msgstr ""
@@ -13072,11 +13085,6 @@ msgstr ""
msgid "The changes you have made require calibre be restarted immediately. You will not be allowed to set any more preferences, until you restart."
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/main.py:350
-#: /home/kovid/work/calibre/src/calibre/gui2/preferences/server.py:134
-msgid "Restart needed"
-msgstr ""
-
#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:48
msgid "Source"
msgstr ""
@@ -14986,7 +14994,7 @@ msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:78
#, python-format
-msgid "%(app)s has been updated to version %(ver)s. See the new features."
+msgid "New version %(ver)s of %(app)s is available for download. See the new features."
msgstr ""
#: /home/kovid/work/calibre/src/calibre/gui2/update.py:84
@@ -16370,153 +16378,168 @@ msgid ""
"*** Adding 'By Authors' Section required for MOBI output ***"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:46
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:47
msgid "Symbols"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:382
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:273
msgid "No genres to catalog.\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:384
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:275
msgid "Check 'Excluded genres' regex in E-book options.\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:386
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:277
msgid "No books available to catalog"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:399
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2429
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:290
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2313
msgid "Titles"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:403
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:294
msgid "Genres"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:405
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1703
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:296
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1615
msgid "Recently Added"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:407
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1902
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:298
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1814
msgid "Recently Read"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:409
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:300
msgid "Descriptions"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:634
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:525
msgid "
Inconsistent Author Sort values for Author
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:651
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:542
msgid "Warning: Inconsistent Author Sort values for Author '{!s}':\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:785
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:676
msgid "Sorting database"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:983
-msgid "Fetching database"
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:751
+msgid "Sorting titles"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1023
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:763
msgid ""
"No books to catalog.\n"
"Check 'Excluded books' rules in E-book options.\n"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1025
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:765
msgid "No books available to include in catalog"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1983
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:1895
msgid "Genres HTML"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2409
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2293
msgid "Titles HTML"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2604
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2606
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2608
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2488
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2490
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2492
msgid "by "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2745
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2629
msgid "Descriptions HTML"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2749
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2633
msgid "Description HTML"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2884
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2768
msgid "NCX header"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2959
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2843
msgid "NCX for Descriptions"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3080
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:2964
msgid "NCX for Series"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3156
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3041
#, python-format
msgid "Series beginning with %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3199
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3043
+#, python-format
+msgid "Series beginning with '%s'"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3087
msgid "NCX for Titles"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3277
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3166
#, python-format
msgid "Titles beginning with %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3318
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3168
+#, python-format
+msgid "Titles beginning with '%s'"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3210
msgid "NCX for Authors"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3388
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3281
+#, python-format
+msgid "Authors beginning with %s"
+msgstr ""
+
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3283
#, python-format
msgid "Authors beginning with '%s'"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3428
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3324
msgid "NCX for Recently Added"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3615
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3511
msgid "NCX for Recently Read"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3752
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3648
msgid "NCX for Genres"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3870
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:3766
msgid "Generating OPF"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4242
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4138
msgid "Thumbnails"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4248
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4144
msgid "Thumbnail"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4743
+#: /home/kovid/work/calibre/src/calibre/library/catalogs/epub_mobi_builder.py:4641
msgid "Saving NCX"
msgstr ""
@@ -17107,17 +17130,17 @@ msgstr ""
msgid "creating custom column "
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:3580
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:3583
#, python-format
msgid "
Migrating old database to ebook library in %s
"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:3609
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:3612
#, python-format
msgid "Copying %s"
msgstr ""
-#: /home/kovid/work/calibre/src/calibre/library/database2.py:3626
+#: /home/kovid/work/calibre/src/calibre/library/database2.py:3629
msgid "Compacting database"
msgstr ""
diff --git a/src/calibre/utils/mdns.py b/src/calibre/utils/mdns.py
index abbd6c2247..48027791ab 100644
--- a/src/calibre/utils/mdns.py
+++ b/src/calibre/utils/mdns.py
@@ -39,6 +39,19 @@ def _get_external_ip():
#print 'ipaddr: %s' % ipaddr
return ipaddr
+def verify_ipV4_address(ip_address):
+ result = None
+ if ip_address != '0.0.0.0' and ip_address != '::':
+ # do some more sanity checks on the address
+ try:
+ socket.inet_aton(ip_address)
+ if len(ip_address.split('.')) == 4:
+ result = ip_address
+ except socket.error:
+ # Not legal ip address
+ pass
+ return result
+
_ext_ip = None
def get_external_ip():
global _ext_ip
@@ -93,7 +106,8 @@ def publish(desc, type, port, properties=None, add_hostname=True, use_ip_address
into the TXT record.
'''
server = start_server()
- service = create_service(desc, type, port, properties, add_hostname)
+ service = create_service(desc, type, port, properties, add_hostname,
+ use_ip_address)
server.registerService(service)
def unpublish(desc, type, port, properties=None, add_hostname=True):
diff --git a/src/calibre/utils/windows/winutil.c b/src/calibre/utils/windows/winutil.c
index 42b6462313..64c943ffa5 100644
--- a/src/calibre/utils/windows/winutil.c
+++ b/src/calibre/utils/windows/winutil.c
@@ -168,9 +168,9 @@ winutil_set_debug(PyObject *self, PyObject *args) {
return Py_None;
}
-static LPTSTR
+static LPWSTR
get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iterate) {
- /* Get a the property specified by `property` from the registry for the
+ /* Get the property specified by `property` from the registry for the
* device enumerated by `index` in the collection `hDevInfo`. `iterate`
* will be set to `FALSE` if `index` points outside `hDevInfo`.
* :return: A string allocated on the heap containing the property or
@@ -178,7 +178,7 @@ get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iter
*/
SP_DEVINFO_DATA DeviceInfoData;
DWORD DataT;
- LPTSTR buffer = NULL;
+ LPWSTR buffer = NULL;
DWORD buffersize = 0;
DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
@@ -187,7 +187,7 @@ get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iter
return NULL;
}
- while(!SetupDiGetDeviceRegistryProperty(
+ while(!SetupDiGetDeviceRegistryPropertyW(
hDevInfo,
&DeviceInfoData,
property,
@@ -196,11 +196,11 @@ get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iter
buffersize,
&buffersize)) {
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
- buffer = (LPTSTR)PyMem_Malloc(2*buffersize); // Twice for bug in Win2k
+ if (buffer != NULL) { PyMem_Free(buffer); buffer = NULL; }
+ buffer = (LPWSTR)PyMem_Malloc(2*buffersize); // Twice for bug in Win2k
} else {
- PyMem_Free(buffer);
+ if (buffer != NULL) { PyMem_Free(buffer); buffer = NULL; }
PyErr_SetFromWindowsErr(0);
- buffer = NULL;
break;
}
} //while
@@ -209,7 +209,7 @@ get_registry_property(HDEVINFO hDevInfo, DWORD index, DWORD property, BOOL *iter
}
static BOOL
-check_device_id(LPTSTR buffer, unsigned int vid, unsigned int pid) {
+check_device_id(LPWSTR buffer, unsigned int vid, unsigned int pid) {
WCHAR xVid[9], dVid[9], xPid[9], dPid[9];
unsigned int j;
_snwprintf_s(xVid, 9, _TRUNCATE, L"vid_%4.4x", vid);
@@ -607,31 +607,28 @@ winutil_get_removable_drives(PyObject *self, PyObject *args) {
return NULL;
}
- ddebug = PyObject_IsTrue(pdebug);
+ // Find all removable drives
+ for (j = 0; j < MAX_DRIVES; j++) g_drives[j].letter = 0;
+ if (!get_all_removable_disks(g_drives)) return NULL;
volumes = PyDict_New();
- if (volumes == NULL) return NULL;
-
-
- for (j = 0; j < MAX_DRIVES; j++) g_drives[j].letter = 0;
-
- // Find all removable drives
- if (!get_all_removable_disks(g_drives)) {
- return NULL;
- }
+ if (volumes == NULL) return PyErr_NoMemory();
+ ddebug = PyObject_IsTrue(pdebug);
hDevInfo = create_device_info_set((LPGUID)&GUID_DEVINTERFACE_VOLUME,
NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
- if (hDevInfo == INVALID_HANDLE_VALUE) return NULL;
+ if (hDevInfo == INVALID_HANDLE_VALUE) { Py_DECREF(volumes); return NULL; }
// Enumerate through the set
for (i=0; iterate; i++) {
candidates = PyList_New(0);
- if (candidates == NULL) return PyErr_NoMemory();
+ if (candidates == NULL) { Py_DECREF(volumes); return PyErr_NoMemory();}
interfaceDetailData = get_device_ancestors(hDevInfo, i, candidates, &iterate, ddebug);
if (interfaceDetailData == NULL) {
- PyErr_Print(); continue;
+ PyErr_Print();
+ Py_DECREF(candidates); candidates = NULL;
+ continue;
}
length = wcslen(interfaceDetailData->DevicePath);
@@ -653,12 +650,13 @@ winutil_get_removable_drives(PyObject *self, PyObject *args) {
key = PyBytes_FromFormat("%c", (char)g_drives[j].letter);
if (key == NULL) return PyErr_NoMemory();
PyDict_SetItem(volumes, key, candidates);
- Py_DECREF(candidates);
+ Py_DECREF(key); key = NULL;
break;
}
}
}
+ Py_XDECREF(candidates); candidates = NULL;
PyMem_Free(interfaceDetailData);
} //for
@@ -672,7 +670,8 @@ winutil_get_usb_devices(PyObject *self, PyObject *args) {
HDEVINFO hDevInfo;
DWORD i; BOOL iterate = TRUE;
PyObject *devices, *temp = (PyObject *)1;
- LPTSTR buffer;
+ LPWSTR buffer;
+ BOOL ok = 1;
if (!PyArg_ParseTuple(args, "")) return NULL;
@@ -682,8 +681,10 @@ winutil_get_usb_devices(PyObject *self, PyObject *args) {
// Create a Device information set with all USB devices
hDevInfo = create_device_info_set(NULL, L"USB", 0,
DIGCF_PRESENT | DIGCF_ALLCLASSES);
- if (hDevInfo == INVALID_HANDLE_VALUE)
+ if (hDevInfo == INVALID_HANDLE_VALUE) {
+ Py_DECREF(devices);
return NULL;
+ }
// Enumerate through the set
for (i=0; iterate; i++) {
buffer = get_registry_property(hDevInfo, i, SPDRP_HARDWAREID, &iterate);
@@ -691,16 +692,17 @@ winutil_get_usb_devices(PyObject *self, PyObject *args) {
PyErr_Print(); continue;
}
buffersize = wcslen(buffer);
- for (j = 0; j < buffersize; j++) buffer[j] = tolower(buffer[j]);
+ for (j = 0; j < buffersize; j++) buffer[j] = towlower(buffer[j]);
temp = PyUnicode_FromWideChar(buffer, buffersize);
PyMem_Free(buffer);
if (temp == NULL) {
PyErr_NoMemory();
+ ok = 0;
break;
}
- PyList_Append(devices, temp);
+ PyList_Append(devices, temp); Py_DECREF(temp); temp = NULL;
} //for
- if (temp == NULL) { Py_DECREF(devices); devices = NULL; }
+ if (!ok) { Py_DECREF(devices); devices = NULL; }
SetupDiDestroyDeviceInfoList(hDevInfo);
return devices;
}
@@ -711,7 +713,7 @@ winutil_is_usb_device_connected(PyObject *self, PyObject *args) {
unsigned int vid, pid;
HDEVINFO hDevInfo;
DWORD i; BOOL iterate = TRUE;
- LPTSTR buffer;
+ LPWSTR buffer;
int found = FALSE;
PyObject *ans;