From be026b8d2bf56f1878b7de275c2e5df45a55231f Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Tue, 14 Jun 2011 23:14:00 -0300 Subject: [PATCH 01/31] KTouch - Image file names have changed - fix display of covers --- src/calibre/devices/kobo/driver.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 04fb3c37b0..1efce88f27 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -85,6 +85,16 @@ class KOBO(USBMS): except: self.fwversion = 'unknown' + # Determine Hardware version differences + product_id = self.detected_device.idProduct + debug_print ("device: ", product_id) + if product_id == 0x4161: #Original and KWifi + image_suffix = ' - NickelBookCover.parsed' + else: #KTouch + image_suffix = ' - N3_LIBRARY_FULL.parsed' + + debug_print("Image Suffix: ", image_suffix) + if self.fwversion != '1.0' and self.fwversion != '1.4': self.has_kepubs = True debug_print('Version of firmware: ', self.fwversion, 'Has kepubs:', self.has_kepubs) @@ -125,8 +135,9 @@ class KOBO(USBMS): if idx is not None: bl_cache[lpath] = None if ImageID is not None: - imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + ' - NickelBookCover.parsed') + imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + image_suffix) #print "Image name Normalized: " + imagename + if imagename is not None: bl[idx].thumbnail = ImageWrapper(imagename) if (ContentType != '6' and MimeType != 'Shortcover'): From c9d34ca631a564b1d78c18b9b905ce30c7f662d6 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Tue, 14 Jun 2011 23:23:32 -0300 Subject: [PATCH 02/31] KTouch - Image file names have changed - fix deleting images --- src/calibre/devices/kobo/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 1efce88f27..25ef30edbd 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -297,7 +297,7 @@ class KOBO(USBMS): path_prefix = '.kobo/images/' path = self._main_prefix + path_prefix + ImageID - file_endings = (' - iPhoneThumbnail.parsed', ' - bbMediumGridList.parsed', ' - NickelBookCover.parsed',) + file_endings = (' - iPhoneThumbnail.parsed', ' - bbMediumGridList.parsed', ' - NickelBookCover.parsed', ' - N3_LIBRARY_FULL.parsed', ' - N3_LIBRARY_GRID.parsed', ' - N3_LIBRARY_LIST.parsed', ' - N3_SOCIAL_CURRENTREAD.parsed',) for ending in file_endings: fpath = path + ending From e8b723fb9900d8e9e448caf641cc1a6f5c220768 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Wed, 15 Jun 2011 21:53:40 -0300 Subject: [PATCH 03/31] KTouch - Image file names have changed - modify fix for display of covers --- src/calibre/devices/kobo/driver.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 25ef30edbd..6076aa35eb 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -85,16 +85,6 @@ class KOBO(USBMS): except: self.fwversion = 'unknown' - # Determine Hardware version differences - product_id = self.detected_device.idProduct - debug_print ("device: ", product_id) - if product_id == 0x4161: #Original and KWifi - image_suffix = ' - NickelBookCover.parsed' - else: #KTouch - image_suffix = ' - N3_LIBRARY_FULL.parsed' - - debug_print("Image Suffix: ", image_suffix) - if self.fwversion != '1.0' and self.fwversion != '1.4': self.has_kepubs = True debug_print('Version of firmware: ', self.fwversion, 'Has kepubs:', self.has_kepubs) @@ -135,7 +125,11 @@ class KOBO(USBMS): if idx is not None: bl_cache[lpath] = None if ImageID is not None: - imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + image_suffix) + imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + ' - NickelBookCover.parsed') + if not os.path.exists(imagename): + # Try the Touch version if the image does not exist + imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + ' - N3_LIBRARY_FULL.parsed') + #print "Image name Normalized: " + imagename if imagename is not None: From cb66ec304d43609281fe93242b557e018816f568 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Wed, 15 Jun 2011 22:17:52 -0300 Subject: [PATCH 04/31] KTouch - Hide the internal help files that don't exist on the device storage --- src/calibre/devices/kobo/driver.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 6076aa35eb..978a848707 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -199,7 +199,9 @@ class KOBO(USBMS): changed = False for i, row in enumerate(cursor): # self.report_progress((i+1) / float(numrows), _('Getting list of books on device...')) - + if row[3].startswith("file:///usr/local/Kobo/help/"): + # These are internal to the Kobo device and do not exist + continue path = self.path_from_contentid(row[3], row[5], row[4], oncard) mime = mime_type_ext(path_to_ext(path)) if path.find('kepub') == -1 else 'application/epub+zip' # debug_print("mime:", mime) From 515bc88d595c93c47912e47eb26d2805c21ce163 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Wed, 15 Jun 2011 22:31:12 -0300 Subject: [PATCH 05/31] KTouch - Why oh why does the content id keep changing format? --- src/calibre/devices/kobo/driver.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 978a848707..e606f65e38 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -457,7 +457,10 @@ class KOBO(USBMS): path = self._main_prefix + path + '.kobo' # print "Path: " + path elif (ContentType == "6" or ContentType == "10") and MimeType == 'application/x-kobo-epub+zip': - path = self._main_prefix + '.kobo/kepub/' + path + if path.startswith("file:///mnt/onboard/"): + path = self._main_prefix + path.replace("file:///mnt/onboard/", '') + else: + path = self._main_prefix + '.kobo/kepub/' + path # print "Internal: " + path else: # if path.startswith("file:///mnt/onboard/"): From 83634d8a542aadfefbd74767aac15f6286af5a22 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Mon, 20 Jun 2011 23:33:54 -0300 Subject: [PATCH 06/31] Expired books should be reported as a Collection - The next server sync should delete them --- src/calibre/devices/kobo/driver.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index e606f65e38..6a9ea56ceb 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -100,7 +100,7 @@ class KOBO(USBMS): for idx,b in enumerate(bl): bl_cache[b.lpath] = idx - def update_booklist(prefix, path, title, authors, mime, date, ContentType, ImageID, readstatus, MimeType): + def update_booklist(prefix, path, title, authors, mime, date, ContentType, ImageID, readstatus, MimeType, expired): changed = False try: lpath = path.partition(self.normalize_path(prefix))[2] @@ -118,6 +118,11 @@ class KOBO(USBMS): elif readstatus == 3: playlist_map[lpath]= "Closed" + # Related to a bug in the Kobo firmware that leaves an expired row for deleted books + # this shows an expired Collection so the user can decide to delete the book + if expired == 3: + playlist_map[lpath] = "Expired" + path = self.normalize_path(path) # print "Normalized FileName: " + path @@ -131,7 +136,8 @@ class KOBO(USBMS): imagename = self.normalize_path(self._main_prefix + '.kobo/images/' + ImageID + ' - N3_LIBRARY_FULL.parsed') #print "Image name Normalized: " + imagename - + if not os.path.exists(imagename): + debug_print("Strange - The image name does not exist - title: ", title) if imagename is not None: bl[idx].thumbnail = ImageWrapper(imagename) if (ContentType != '6' and MimeType != 'Shortcover'): @@ -192,7 +198,7 @@ class KOBO(USBMS): self.dbversion = result[0] query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ - 'ImageID, ReadStatus from content where BookID is Null' + 'ImageID, ReadStatus, ___ExpirationStatus from content where BookID is Null' cursor.execute (query) @@ -207,10 +213,10 @@ class KOBO(USBMS): # debug_print("mime:", mime) if oncard != 'carda' and oncard != 'cardb' and not row[3].startswith("file:///mnt/sd/"): - changed = update_booklist(self._main_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7], row[4]) + changed = update_booklist(self._main_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7], row[4], row[8]) # print "shortbook: " + path elif oncard == 'carda' and row[3].startswith("file:///mnt/sd/"): - changed = update_booklist(self._card_a_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7], row[4]) + changed = update_booklist(self._card_a_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7], row[4], row[8]) if changed: need_sync = True From 43c9ebe77f3294f14f13036c59d1c58a290f346a Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Tue, 21 Jun 2011 00:06:25 -0300 Subject: [PATCH 07/31] Emulate the Kobo device delete more closely. Leave the Book record, the next server sync should delete them --- src/calibre/devices/kobo/driver.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 6a9ea56ceb..b523b7f296 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -280,8 +280,12 @@ class KOBO(USBMS): cursor.execute('delete from content_keys where volumeid = ?', t) # Delete the chapters associated with the book next - t = (ContentID,ContentID,) - cursor.execute('delete from content where BookID = ? or ContentID = ?', t) + t = (ContentID,) + # Kobo does not delete the Book row (ie the row where the BookID is Null) + # The next server sync should remove the row + cursor.execute('delete from content where BookID = ?', t) + cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0, ___ExpirationStatus=3 ' \ + 'where BookID is Null and ContentID =?',t) connection.commit() From f9dc7d978011f09fc1ef046da316b3ee38871fc2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 20 Jun 2011 21:48:18 -0600 Subject: [PATCH 08/31] EOD on the new db api --- resources/metadata_sqlite.sql | 6 +- src/calibre/db/backend.py | 39 +++++++++- src/calibre/db/tables.py | 143 ++++++++++++++++++++++++++++++++++ 3 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 src/calibre/db/tables.py diff --git a/resources/metadata_sqlite.sql b/resources/metadata_sqlite.sql index 9c4f666449..aa29d4b8de 100644 --- a/resources/metadata_sqlite.sql +++ b/resources/metadata_sqlite.sql @@ -13,8 +13,10 @@ CREATE TABLE books ( id INTEGER PRIMARY KEY AUTOINCREMENT, isbn TEXT DEFAULT "" COLLATE NOCASE, lccn TEXT DEFAULT "" COLLATE NOCASE, path TEXT NOT NULL DEFAULT "", - flags INTEGER NOT NULL DEFAULT 1 - , uuid TEXT, has_cover BOOL DEFAULT 0, last_modified TIMESTAMP NOT NULL DEFAULT "2000-01-01 00:00:00+00:00"); + flags INTEGER NOT NULL DEFAULT 1, + uuid TEXT, + has_cover BOOL DEFAULT 0, + last_modified TIMESTAMP NOT NULL DEFAULT "2000-01-01 00:00:00+00:00"); CREATE TABLE books_authors_link ( id INTEGER PRIMARY KEY, book INTEGER NOT NULL, author INTEGER NOT NULL, diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 4e6c028b93..159612e52d 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -23,6 +23,8 @@ from calibre.ebooks.metadata import title_sort, author_to_author_sort from calibre.utils.icu import strcmp from calibre.utils.config import to_json, from_json, prefs, tweaks from calibre.utils.date import utcfromtimestamp +from calibre.db.tables import (OneToOneTable, ManyToOneTable, ManyToManyTable, + SizeTable, FormatsTable, AuthorsTable, IdentifiersTable) # }}} ''' @@ -167,7 +169,7 @@ class Connection(apsw.Connection): # {{{ return self.cursor().execute(sql) # }}} -class DB(SchemaUpgrade): +class DB(object, SchemaUpgrade): PATH_LIMIT = 40 if iswindows else 100 WINDOWS_LIBRARY_PATH_LIMIT = 75 @@ -400,5 +402,40 @@ class DB(SchemaUpgrade): ''' Return last modified time as a UTC datetime object ''' return utcfromtimestamp(os.stat(self.dbpath).st_mtime) + def read_tables(self): + tables = {} + for col in ('title', 'sort', 'author_sort', 'series_index', 'comments', + 'timestamp', 'published', 'uuid', 'path', 'cover', + 'last_modified'): + metadata = self.field_metadata[col].copy() + if metadata['table'] is None: + metadata['table'], metadata['column'] == 'books', ('has_cover' + if col == 'cover' else col) + tables[col] = OneToOneTable(col, metadata) + + for col in ('series', 'publisher', 'rating'): + tables[col] = ManyToOneTable(col, self.field_metadata[col].copy()) + + for col in ('authors', 'tags', 'formats', 'identifiers'): + cls = { + 'authors':AuthorsTable, + 'formats':FormatsTable, + 'identifiers':IdentifiersTable, + }.get(col, ManyToManyTable) + tables[col] = cls(col, self.field_metadata[col].copy()) + + tables['size'] = SizeTable('size', self.field_metadata['size'].copy()) + + with self.conn: # Use a single transaction, to ensure nothing modifies + # the db while we are reading + for table in tables.itervalues(): + try: + table.read() + except: + prints('Failed to read table:', table.name) + raise + + return tables + # }}} diff --git a/src/calibre/db/tables.py b/src/calibre/db/tables.py new file mode 100644 index 0000000000..7240b3ec6e --- /dev/null +++ b/src/calibre/db/tables.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 (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from datetime import datetime + +from dateutil.tz import tzoffset + +from calibre.constants import plugins +from calibre.utils.date import parse_date, local_tz +from calibre.ebooks.metadata import author_to_author_sort + +_c_speedup = plugins['speedup'][0] + +def _c_convert_timestamp(val): + if not val: + return None + try: + ret = _c_speedup.parse_date(val.strip()) + except: + ret = None + if ret is None: + return parse_date(val, as_utc=False) + year, month, day, hour, minutes, seconds, tzsecs = ret + return datetime(year, month, day, hour, minutes, seconds, + tzinfo=tzoffset(None, tzsecs)).astimezone(local_tz) + +class Table(object): + + def __init__(self, name, metadata): + self.name, self.metadata = name, metadata + + # self.adapt() maps values from the db to python objects + self.adapt = \ + { + 'datetime': _c_convert_timestamp, + 'bool': bool + }.get( + metadata['datatype'], lambda x: x) + if name == 'authors': + # Legacy + self.adapt = lambda x: x.replace('|', ',') if x else None + +class OneToOneTable(Table): + + def read(self, db): + self.book_col_map = {} + idcol = 'id' if self.metadata['table'] == 'books' else 'book' + for row in db.conn.execute('SELECT {0}, {1} FROM {2}'.format(idcol, + self.metadata['column'], self.metadata['table'])): + self.book_col_map[row[0]] = self.adapt(row[1]) + +class SizeTable(OneToOneTable): + + def read(self, db): + self.book_col_map = {} + for row in db.conn.execute( + 'SELECT books.id, (SELECT MAX(uncompressed_size) FROM data ' + 'WHERE data.book=books.id) FROM books'): + self.book_col_map[row[0]] = self.adapt(row[1]) + +class ManyToOneTable(Table): + + def read(self, db): + self.id_map = {} + self.extra_map = {} + self.col_book_map = {} + self.book_col_map = {} + self.read_id_maps(db) + self.read_maps(db) + + def read_id_maps(self, db): + for row in db.conn.execute('SELECT id, {0} FROM {1}'.format( + self.metadata['name'], self.metadata['table'])): + if row[1]: + self.id_map[row[0]] = self.adapt(row[1]) + + def read_maps(self, db): + for row in db.conn.execute( + 'SELECT book, {0} FROM books_{1}_link'.format( + self.metadata['link_column'], self.metadata['table'])): + if row[1] not in self.col_book_map: + self.col_book_map[row[1]] = [] + self.col_book_map.append(row[0]) + self.book_col_map[row[0]] = row[1] + +class ManyToManyTable(ManyToOneTable): + + def read_maps(self, db): + for row in db.conn.execute( + 'SELECT book, {0} FROM books_{1}_link'.format( + self.metadata['link_column'], self.metadata['table'])): + if row[1] not in self.col_book_map: + self.col_book_map[row[1]] = [] + self.col_book_map.append(row[0]) + if row[0] not in self.book_col_map: + self.book_col_map[row[0]] = [] + self.book_col_map[row[0]].append(row[1]) + +class AuthorsTable(ManyToManyTable): + + def read_id_maps(self, db): + for row in db.conn.execute( + 'SELECT id, name, sort FROM authors'): + self.id_map[row[0]] = row[1] + self.extra_map[row[0]] = (row[2] if row[2] else + author_to_author_sort(row[1])) + +class FormatsTable(ManyToManyTable): + + def read_id_maps(self, db): + pass + + def read_maps(self, db): + for row in db.conn.execute('SELECT book, format, name FROM data'): + if row[1] is not None: + if row[1] not in self.col_book_map: + self.col_book_map[row[1]] = [] + self.col_book_map.append(row[0]) + if row[0] not in self.book_col_map: + self.book_col_map[row[0]] = [] + self.book_col_map[row[0]].append((row[1], row[2])) + +class IdentifiersTable(ManyToManyTable): + + def read_id_maps(self, db): + pass + + def read_maps(self, db): + for row in db.conn.execute('SELECT book, type, val FROM identifiers'): + if row[1] is not None and row[2] is not None: + if row[1] not in self.col_book_map: + self.col_book_map[row[1]] = [] + self.col_book_map.append(row[0]) + if row[0] not in self.book_col_map: + self.book_col_map[row[0]] = [] + self.book_col_map[row[0]].append((row[1], row[2])) + From f2d20aebaa0e9236c222cb840bc218a871e51759 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Jun 2011 14:15:30 +0100 Subject: [PATCH 09/31] Allow custom comments as a search/replace target field --- src/calibre/gui2/dialogs/metadata_bulk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 8829dc97c0..ce21eba00e 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -361,7 +361,7 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog): fm = self.db.field_metadata for f in fm: if (f in ['author_sort'] or - (fm[f]['datatype'] in ['text', 'series', 'enumeration'] + (fm[f]['datatype'] in ['text', 'series', 'enumeration', 'comments'] and fm[f].get('search_terms', None) and f not in ['formats', 'ondevice']) or (fm[f]['datatype'] in ['int', 'float', 'bool'] and From 56f15785102590c4b03d95dfeef58f0f787d8851 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Tue, 21 Jun 2011 14:15:56 +0100 Subject: [PATCH 10/31] The quickview window described in http://www.mobileread.com/forums/showpost.php?p=1620979&postcount=60 --- src/calibre/customize/builtins.py | 8 +- src/calibre/gui2/actions/show_quickview.py | 41 +++++ src/calibre/gui2/dialogs/quickview.py | 171 +++++++++++++++++++++ src/calibre/gui2/dialogs/quickview.ui | 131 ++++++++++++++++ src/calibre/library/database2.py | 17 +- 5 files changed, 362 insertions(+), 6 deletions(-) create mode 100644 src/calibre/gui2/actions/show_quickview.py create mode 100644 src/calibre/gui2/dialogs/quickview.py create mode 100644 src/calibre/gui2/dialogs/quickview.ui diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index d1c5b6ccd5..116493e9db 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -791,6 +791,10 @@ class ActionFetchNews(InterfaceActionBase): name = 'Fetch News' actual_plugin = 'calibre.gui2.actions.fetch_news:FetchNewsAction' +class ActionQuickview(InterfaceActionBase): + name = 'Show Quickview' + actual_plugin = 'calibre.gui2.actions.show_quickview:ShowQuickviewAction' + class ActionSaveToDisk(InterfaceActionBase): name = 'Save To Disk' actual_plugin = 'calibre.gui2.actions.save_to_disk:SaveToDiskAction' @@ -875,8 +879,8 @@ class ActionPluginUpdater(InterfaceActionBase): plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, ActionConvert, ActionDelete, ActionEditMetadata, ActionView, - ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails, - ActionRestart, ActionOpenFolder, ActionConnectShare, + ActionFetchNews, ActionSaveToDisk, ActionQuickview, + ActionShowBookDetails,ActionRestart, ActionOpenFolder, ActionConnectShare, ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks, ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary, ActionCopyToLibrary, ActionTweakEpub, ActionNextMatch, ActionStore, diff --git a/src/calibre/gui2/actions/show_quickview.py b/src/calibre/gui2/actions/show_quickview.py new file mode 100644 index 0000000000..61a41b08ae --- /dev/null +++ b/src/calibre/gui2/actions/show_quickview.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +from calibre.gui2.actions import InterfaceAction +from calibre.gui2.dialogs.quickview import Quickview +from calibre.gui2 import error_dialog + +class ShowQuickviewAction(InterfaceAction): + + name = 'Show quickview' + action_spec = (_('Show quickview'), 'user_profile.png', None, + _('Q')) + dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) + action_type = 'current' + + current_instance = None + + def genesis(self): + self.qaction.triggered.connect(self.show_quickview) + + def show_quickview(self, *args): + if self.current_instance: + if not self.current_instance.is_closed: + return + self.current_instance = None + if self.gui.current_view() is not self.gui.library_view: + error_dialog(self.gui, _('No quickview available'), + _('Quickview is not available for books ' + 'on the device.')).exec_() + return + index = self.gui.library_view.currentIndex() + if index.isValid(): + self.current_instance = \ + Quickview(self.gui, self.gui.library_view, index) + self.current_instance.show() + diff --git a/src/calibre/gui2/dialogs/quickview.py b/src/calibre/gui2/dialogs/quickview.py new file mode 100644 index 0000000000..5dcdbf04fa --- /dev/null +++ b/src/calibre/gui2/dialogs/quickview.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' +__docformat__ = 'restructuredtext en' + + +from PyQt4.Qt import (Qt, QDialog, QAbstractItemView, QTableWidgetItem, + QListWidgetItem, QByteArray, QModelIndex) + +from calibre.gui2.dialogs.quickview_ui import Ui_Quickview +from calibre.utils.icu import sort_key +from calibre.gui2 import gprefs + +class tableItem(QTableWidgetItem): + + def __init__(self, val): + QTableWidgetItem.__init__(self, val) + self.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable) + + def __ge__(self, other): + return sort_key(unicode(self.text())) >= sort_key(unicode(other.text())) + + def __lt__(self, other): + return sort_key(unicode(self.text())) < sort_key(unicode(other.text())) + +class Quickview(QDialog, Ui_Quickview): + + def __init__(self, gui, view, row): + QDialog.__init__(self, gui, flags=Qt.Window) + Ui_Quickview.__init__(self) + self.setupUi(self) + self.isClosed = False + + try: + geom = gprefs.get('quickview_dialog_geometry', bytearray('')) + self.restoreGeometry(QByteArray(geom)) + except: + pass + + icon = self.windowIcon() + self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) + self.setWindowIcon(icon) + + self.db = view.model().db + self.view = view + self.gui = gui + + self.items.setSelectionMode(QAbstractItemView.SingleSelection) + self.items.currentTextChanged.connect(self.item_selected) +# self.items.setFixedWidth(150) + + self.books_table.setSelectionBehavior(QAbstractItemView.SelectRows) + self.books_table.setSelectionMode(QAbstractItemView.SingleSelection) + self.books_table.setColumnCount(3) + t = QTableWidgetItem(_('Title')) + self.books_table.setHorizontalHeaderItem(0, t) + t = QTableWidgetItem(_('Authors')) + self.books_table.setHorizontalHeaderItem(1, t) + t = QTableWidgetItem(_('Series')) + self.books_table.setHorizontalHeaderItem(2, t) + self.books_table_header_height = self.books_table.height() + self.books_table.cellDoubleClicked.connect(self.book_doubleclicked) + + self.is_closed = False + self.current_book_id = None + self.current_key = None + self.use_current_key_for_next_refresh = False + self.last_search = None + + self.refresh(row) +# self.connect(self.view.selectionModel(), SIGNAL('currentChanged(QModelIndex,QModelIndex)'), self.slave) + self.view.selectionModel().currentChanged[QModelIndex,QModelIndex].connect(self.slave) + self.search_button.clicked.connect(self.do_search) + + def do_search(self): + if self.last_search is not None: + self.use_current_key_for_next_refresh = True + self.gui.search.set_search_string(self.last_search) + + def item_selected(self, txt): + self.fill_in_books_box(unicode(txt)) + + def refresh(self, idx): + bv_row = idx.row() + key = self.view.model().column_map[idx.column()] + + book_id = self.view.model().id(bv_row) + + if self.use_current_key_for_next_refresh: + key = self.current_key + self.use_current_key_for_next_refresh = False + else: + if not self.db.field_metadata[key]['is_category']: + if self.current_key is None: + return + key = self.current_key + self.items_label.setText('{0} ({1})'.format( + self.db.field_metadata[key]['name'], key)) + self.items.clear() + self.books_table.setRowCount(0) + + mi = self.db.get_metadata(book_id, index_is_id=True, get_user_categories=False) + vals = mi.get(key, None) + if not vals: + return + + if not isinstance(vals, list): + vals = [vals] + vals.sort(key=sort_key) + + self.items.blockSignals(True) + for v in vals: + a = QListWidgetItem(v) + self.items.addItem(a) + self.items.setCurrentRow(0) + self.items.blockSignals(False) + + self.current_book_id = book_id + self.current_key = key + + self.fill_in_books_box(vals[0]) + + def fill_in_books_box(self, selected_item): + if selected_item.startswith('.'): + sv = '.' + selected_item + else: + sv = selected_item + sv = sv.replace('"', r'\"') + self.last_search = self.current_key+':"=' + sv + '"' + books = self.db.search_getting_ids(self.last_search, + self.db.data.search_restriction) + self.books_table.setRowCount(len(books)) + self.books_label.setText(_('Books with selected item: {0}').format(len(books))) + + select_row = None + self.books_table.setSortingEnabled(False) + for row, b in enumerate(books): + mi = self.db.get_metadata(b, index_is_id=True, get_user_categories=False) + a = tableItem(mi.title) + a.setData(Qt.UserRole, b) + self.books_table.setItem(row, 0, a) + a = tableItem(' & '.join(mi.authors)) + self.books_table.setItem(row, 1, a) + series = mi.format_field('series')[1] + if series is None: + series = '' + a = tableItem(series) + self.books_table.setItem(row, 2, a) + if b == self.current_book_id: + select_row = row + + self.books_table.resizeColumnsToContents() +# self.books_table.resizeRowsToContents() + + if select_row is not None: + self.books_table.selectRow(select_row) + self.books_table.setSortingEnabled(True) + + def book_doubleclicked(self, row, column): + self.use_current_key_for_next_refresh = True + self.view.select_rows([self.books_table.item(row, 0).data(Qt.UserRole).toInt()[0]]) + + def slave(self, current, previous): + self.refresh(current) + self.view.activateWindow() + + def done(self, r): + geom = bytearray(self.saveGeometry()) + gprefs['quickview_dialog_geometry'] = geom + self.is_closed = True + QDialog.done(self, r) diff --git a/src/calibre/gui2/dialogs/quickview.ui b/src/calibre/gui2/dialogs/quickview.ui new file mode 100644 index 0000000000..2cdc7b7379 --- /dev/null +++ b/src/calibre/gui2/dialogs/quickview.ui @@ -0,0 +1,131 @@ + + + Quickview + + + + 0 + 0 + 768 + 342 + + + + + 0 + 0 + + + + Quickview + + + + + + Items + + + + + + + + 1 + 0 + + + + + + + + + + + + + 4 + 0 + + + + 0 + + + 0 + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + + Search + + + Search in the library view for the selected item + + + + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + QDialogButtonBox::Close + + + false + + + + + + + + + + + buttonBox + rejected() + Quickview + reject() + + + 297 + 217 + + + 286 + 234 + + + + + diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 67c67b1ff7..530d617f9a 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1931,13 +1931,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): def authors_with_sort_strings(self, id, index_is_id=False): id = id if index_is_id else self.id(id) aut_strings = self.conn.get(''' - SELECT authors.name, authors.sort + SELECT authors.id, authors.name, authors.sort FROM authors, books_authors_link as bl WHERE bl.book=? and authors.id=bl.author ORDER BY bl.id''', (id,)) result = [] - for (author, sort,) in aut_strings: - result.append((author.replace('|', ','), sort)) + for (id_, author, sort,) in aut_strings: + result.append((id_, author.replace('|', ','), sort)) return result # Given a book, return the author_sort string for authors of the book @@ -1945,6 +1945,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): auts = self.authors_sort_strings(id, index_is_id) return ' & '.join(auts).replace('|', ',') + # Given an author, return a list of books with that author + def books_for_author(self, id_, index_is_id=False): + id_ = id_ if index_is_id else self.id(id_) + books = self.conn.get(''' + SELECT bl.book + FROM books_authors_link as bl + WHERE bl.author=?''', (id_,)) + return [b[0] for b in books] + # Given a list of authors, return the author_sort string for the authors, # preferring the author sort associated with the author over the computed # string @@ -1968,7 +1977,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): aum = self.authors_with_sort_strings(id_, index_is_id=True) self.data.set(id_, self.FIELD_MAP['au_map'], - ':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (au, aus) in aum]), + ':#:'.join([':::'.join((au.replace(',', '|'), aus)) for (_, au, aus) in aum]), row_is_id=True) def _set_authors(self, id, authors, allow_case_change=False): From ab44da6f50cc3490bd9e86a9d00005ddc8acce49 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Jun 2011 15:56:38 -0600 Subject: [PATCH 11/31] El club del ebook by DM. Fixes #800406 (New recipe for ebook related blog in spanish - El club del ebook) --- recipes/elclubdelebook.recipe | 61 +++++++++++++++++++++++++++++++ recipes/icons/elclubdelebook.png | Bin 0 -> 5423 bytes 2 files changed, 61 insertions(+) create mode 100644 recipes/elclubdelebook.recipe create mode 100644 recipes/icons/elclubdelebook.png diff --git a/recipes/elclubdelebook.recipe b/recipes/elclubdelebook.recipe new file mode 100644 index 0000000000..e05b176cc5 --- /dev/null +++ b/recipes/elclubdelebook.recipe @@ -0,0 +1,61 @@ + +__license__ = 'GPL v3' +__copyright__ = '2011, Darko Miletic ' +''' +www.clubdelebook.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class ElClubDelEbook(BasicNewsRecipe): + title = 'El club del ebook' + __author__ = 'Darko Miletic' + description = 'El Club del eBook, es la primera fuente de informacion sobre ebooks de Argentina. Aca vas a encontrar noticias, tips, tutoriales, recursos y opiniones sobre el mundo de los libros electronicos.' + tags = 'ebook, libro electronico, e-book, ebooks, libros electronicos, e-books' + oldest_article = 7 + max_articles_per_feed = 100 + language = 'es_AR' + encoding = 'utf-8' + no_stylesheets = True + use_embedded_content = True + publication_type = 'blog' + masthead_url = 'http://dl.dropbox.com/u/2845131/elclubdelebook.png' + extra_css = """ + body{font-family: Arial,Helvetica,sans-serif} + img{ margin-bottom: 0.8em; + border: 1px solid #333333; + padding: 4px; display: block + } + """ + + conversion_options = { + 'comment' : description + , 'tags' : tags + , 'publisher': title + , 'language' : language + } + + remove_tags = [dict(attrs={'id':'crp_related'})] + remove_tags_after = dict(attrs={'id':'crp_related'}) + + feeds = [(u'Articulos', u'http://feeds.feedburner.com/ElClubDelEbook')] + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + for item in soup.findAll('a'): + limg = item.find('img') + if item.string is not None: + str = item.string + item.replaceWith(str) + else: + if limg: + item.name = 'div' + item.attrs = [] + else: + str = self.tag_to_string(item) + item.replaceWith(str) + for item in soup.findAll('img'): + if not item.has_key('alt'): + item['alt'] = 'image' + return soup diff --git a/recipes/icons/elclubdelebook.png b/recipes/icons/elclubdelebook.png new file mode 100644 index 0000000000000000000000000000000000000000..c43f0454848f640c5d035596b9dc2705d15e38ba GIT binary patch literal 5423 zcmV+~70~L5P)7L&AwYL1XdSuDV_r9dQ;!<5*bL+qVIp;s;+-4GLf)J7< z7KRXo5Rs65dpp;zU2D->$yY~~(__qNqrm7AiG;x@Fbu;Bax4}ruP*g^y%viJ->M8t z|3DSAkzSkfI*bgFv{9t@*&O{+O0q25tEP1D;KBD#x=m7uBoeEhxQR@kjH_{9)aP{c z!-utRfAp8X{6){Z4F-dlmi{FyC9j%Hree3#9SSi$J>F_6q0ddfvhKC-yzsyYk znn|mwqLMYQZ`rqR-?v7Mn>=}(QBQc5sPeU$a_lu&))X-sNh(du=ALgwg>Wo^6|N$> z0oJwE-2hAY>O)Q>sW_QG|H(umcJICSJ~(kCurjfv`uXR+msUMLdi3b2lgC&rB$H0- z^?F_?7?Gk=2&;`8Mm`}EgCXYk*c}d{V-lf|EK82EGR-ru9^7@8q6V~pPPjUz(`qi4 zvzQG$PyaHNA~PPDkM~Zz|IRUEMzQHM*|~EkKHqZ>#Ux2Yr_=E~M^qK3O8)r-rbHkB z=bQGx1K%1s(&};nQ{3+-it;bd8$PJosr_d(G7g6UZ_i{%GD*2#x^!tM6#C$U4?G?} zv+2(r8#iv8IsJQM$2Rh!BFl0v-5HO^O;*EIhKS13Wh->WyshTF_ugOr!t-@?bskSA zTz=oj@5kf0OeRImbiMtw!$ACW9;RSi%9El6L?W3-L=SHKS|*c$*K6-0D^|Sp!O@oa z^XD@QpML+;sZ-nET7>0T=-Wv??bQK1BVT6@?F$XoPiFq&^5x4myCs=S4jee(^Upsw z>UFz!?@%>B{}&cZ!q>S~AwuaWNh~i2Af}?CG@mE3Omqgacklnc@W%ED6DBa@=B)4T z?%uxsXErm@XH(E2o+M$tw9LV>l4=Qu!*$ht^?Gt--`}5m?z!q=Lr$DH;Vv$I_St7~ zv3>jYQ^!w?9Xs}!Ws8ErG!G?^B#vjncag)6d8AYV3J10@sv2sQq23`W5_C&pRooZ! zIuOS5JPbT(;ak+@2aJ8HzP^6rifQQ_Dbq=KC=uzfI-T*9Xf<0ULbXaxk(Xayy?@sS z4Gj&uj~$&lb?OhMPQ?@z<+e9AZr!u%gVU!^H$U3c+&o>C2*(joB7#5^i3Ee;GN+wt z3Cn1JuEAnCRtqRalo84*oB1PtE!f7r$c2c=rEk+fS#A9X-6hu2{eKe_lWK z;ch|6Y+kj}clvK^%rAOd%A8DhNbC;gyZu>u22wbEfv7(iyWH8;;qCVNypc#Gn?t4% z1y(WXIo4LDn%&Gz)1LvlJ!_r-c5^mjFc^udB%@KivjR&y+m?i}rrkIB-FM$@J8^W) znl%kWO3xfWZZ(_FoHIO+fN?zWLFo01F&p18%MZwIxX;~Xf$dy zTKo0umyX9EJZDZFL+b6y?et2o_Xe(5bwbHj(mAx_3G7w`&v6XI;7xPJbx+e z!ZJ`tz=(qyZbI!KXGJ_&TUiCrf7-sKrKP3R?YQf%yTYMvm{Qf{K^4rSDJzQ^Si)h< zrYKt+;W2=F1EgF7I%4K38W33el`B_3QAsfeOu0Z{*|KGj3BTVDC_8p+EiH9_@x>Pi zcpA@&f;GTN@c?|t=ks9-%z*L2g$uoU^%^y56jltI;^A@Q#^Eui-RW|=O57!Gx4Wc- zj`***q@=1>ukz~ZlFABKaWTcj^`tsDE7A;`jY|$wG19h@AfA>}caHi-2tjMJG&c5m z=biP=SZn^vgAO@w&ZYuNAH{i#Rg^7xNr?umYTl^C4RTCOb=eHrT*lkgacbaz-amch zf%3A-_3Jk5-hH5=q7Ref68@y;l0VoPjf7G-&tJHhNM$c}w6}XbT|sXb;xZG;s98lP zD@HkuGy@R^hgdhF0fO1y-VSdlE-r@OJoeaQNDk0a)K$>8R4P?gRt8VXLy`&2n^+d~ z7Cr$P1UHqHmC*gp&d%?C_q$*VAHnf!udRc(+gz@SnwrweN>{N9u3cYWhnM!Mt*xr8 zEH5j^>ns)v=0U=hd+HX#;x%}spk;+2Lx#+rJ^Or1OHFO<*|TQ>7r7J6!!G^%_lHgK z$N79#Rb^h_a57XF5o2&1(P%Uz67k~VqN7KTJlfpkaN6+krI%J6ICK~u4j+Ioc6NEg z(O7qPARbS4bar~XyS)KlI2KLkax%@Bm{90}1>mZtD%q@RC@C5B{qMv2U~A>dl}C;o z89H<*)(2v`y1KwkHk+lMOCyVxC3p-YKqJB90YFFaLNXABSRQ;1XdZw3ai6yv7lQ^5 zs;VkS9a~oBE-fvt#s^%v-H;wi71jscQhG{aR#p|HBvB?NRP(4X2tnw?YG~K3&=W_4m|^5x4R8AT3fZFLR2 z{*_l==|7;sVYSH;QUp&s2NpdK3wjE{3{gs@5?E|ekpsq}9TCrK?%&?fP(ShhDMpJe znacjOX^!1)3WwuLUcUHcTl?k9p3Y96*AtIK@|i57AcHeJ%lE*79wHV1RV&3vbwdO6 z2EKxMZ@u+aSQ8N6+qxC%f@@8YX}ZArKoT%%4h3P!2^sZ_7=7<>-XqB0vlemry^ zHRI8vM`z8N?{ZOx>)Wp{L=I`Kyu2q8d5*uKN;BE4u~ZU?GpF&VCKAJj4O_TyAtr)R zkcP`kN;kjx$G;sqe9O?G5;`8j6cxGJS}*0Y6e8%9WwpM(!RHGg=>d}lT5q>I&1UO> z0RuO0e!IKdFXr=g{rhiN_u8Mg?Xa8lawe5eMB6W%kNCXtpg$V)vqVOnPOceIrz_+m zL4(V%G(X_jvSkax3Kx(oxZ2AvzZ{7~d-v{*=OIYLhYtrIkPL`^pFVw%r12z3f}p^E z@l0)PEv8+tV8O0kyP!3o4Jl#d$dR*V%|a}5n$&@Qr`wGa&m%R$w>Y+^H(^tmG)~$T zWoTN@WHW_h@#4kEC%bp=zT=KNV0fp^{>xu1cG?{RZ?IY%UXKqkRa4z3oyvc4?37M0 zNusLAj6rYF>&!#HHp0{8U-ZP!eBD8Rw{Ob*lN;*lW;Zvx?UtG{cdv3c3Ugl18i|;X z`lFu9Vk*wbc~%w~^((euSHyx&ASU>hAQ+*c4!g~6!)pHUhZR$%O!@TFPtnZ^wC?u2 z_~MK32Bd1R0$anYU_{Ufm%>FU2cLs)fguzyU=~^ctjK_fEPVU)(@*c%v7?|>U=D17 z9uS7g5&kgYd*h*e?`__U7{s?wIIxaB;MlQattU=ko}MZeu#)K%d<#{620-*PLmRFRPR;*pS{=%0RZ~yu?Sq8331`oaoq9Nwhg$thi z;)|12Rkb{4a2J=G%@&U1kzz_*MOWHdhuwVBupy17PaHdY{OHElUfK8Iu52RG(7(^X zy1oLVc6YWX!hx&ZIL(PXtpSJu8pQMFuY9YjsuYfoK!*{3IAaFH20Wqhf-T@un>K9% zbkI}y7zhOB&Yg>t4+7vK)LAuZ;Y|1zJWZN336Y3G7hM*l2Qi4?-M@c7s0ERDCweOA z5%NHfZ;hDwItc%0^+O0ow-}BGgDj!dW=!Y8i&MNEZ*uS|W>0stz)6pt_o3Y|o9!^o z{CNuy3Lk&G4>_ukh(e(N^n&FWEGeCiqb@4p!nD-MXO15~USzSrd?pi;HiNX-ia|z+hSrYbi~=D0;V~R@LyxtH`r9;sebY5myoXpVa ztH9RB>mCI%o1CU>aa#GMnqZe4OS>mtq#Ha z$1|F02Mj!arNa}6>FrKOS%tZ%h%p$VnKXJem4FDAxnm5oNkrpx0`LWy_Xt-MY1|{{XNB`=Z^QJmtRM{qEU(UO{InN?BCW z+CBsNLruXm2|5wap&^u_5mdhjLpTePI{Yl1&di)Ucj%&>)aCDL`aR-q>#K87K{zE5 z53wXeJ2it1!JpSQeFUU;1e!wk^>Pe)PT6Lo5gLsoX3m^>`BFO~;nbN^-Y#!hxht8> z01#X_pU)u^=*$)b0Y?<1AYP^OSadArH`;9lE$C`({ph2;$mQuw?)eofhR^vUh4uAm z&mz_A+^`5A>@=?%lip+jrkRy!j0}!MA_B z1hBSkTtcTy$p~Xk&47=>#%y8FlhLkO%?>h~r*$`@qpr$mO3czK1qqJsT1hlXpckSw zFhfU)r*$@`?ZI+dZeg`DK^7&bZay!;{Vi69!|C+-{K!amHvb+AX2v)D7BjxJ{^xkV zMO$hvmC|bV0xX7H-O4K@cvk$1L2aw*UmgrC%^7V!F{kYS7LrPVv%(%dIu%8hSzf0| zQZ^?+mIa*-?lu1LwaBnc^RLz+>HcBmb4aXWLecA4DHBIRwCGuFWy)2~3L_`bm#GSE z<5Wd^ze-2YrZslg_GG94F-%s{<|zOwY-DQJg3f?an>WyIPiJ&MhYd!uVdJ)~yFVR0 zdNlL#$?(#pOD9hn|HCPFatdw9iftq>s-}XMs67<*xC|ZsEw;4(B<-JYVq~7~yGsef=&xqtHE4(zBg(b9%IlVo;x2~vh-Kb=y~&+9S+vUlXOOL8VehEg%{{FYBWE)eh0chiuzCQ?HN_g z@mZGtr>_f-=VgeOnj?(KKz|=;yOjL(@)r=NPc}^*Jg6TN%BcwX-~RrcZQHgDx~T!B z%jnyO+O2|4qTl+7DF< zLxE;QVldEOll(z>&;Gxo6G@BmJ@?%6=(r*1oR}mQCDX+pJKnu!-D*hRtvB}@HEPuD zL+iDfG*8lCDU8ODLLIsA>zB4Q(nC;BUw*@{O==I`Yj@bItOHgwn%aHfZwC$>2&Yu2 z!L*;uxZ{q#mB5qF(;&hv^wp>@PG0B$>I0{{=%zsF!1L$NyQ=G;VQQXMXD+jVqD$s= zDGw%Z!YEoee-ZQv1!@Ud(zYt(*T Z{Rh?U!$@|sqe}n)002ovPDHLkV1mm`cL)Fg literal 0 HcmV?d00001 From 767f0d15841319efb5b9a888bdeab977dd3126b1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Jun 2011 16:07:34 -0600 Subject: [PATCH 12/31] Daytona Beach Journal by BRGriff --- recipes/daytona_beach.recipe | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 recipes/daytona_beach.recipe diff --git a/recipes/daytona_beach.recipe b/recipes/daytona_beach.recipe new file mode 100644 index 0000000000..1230c1d8ed --- /dev/null +++ b/recipes/daytona_beach.recipe @@ -0,0 +1,78 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class DaytonBeachNewsJournal(BasicNewsRecipe): + title ='Daytona Beach News Journal' + __author__ = 'BRGriff' + pubisher = 'News-JournalOnline.com' + description = 'Daytona Beach, Florida, Newspaper' + category = 'News, Daytona Beach, Florida' + oldest_article = 1 + max_articles_per_feed = 100 + remove_javascript = True + use_embedded_content = False + no_stylesheets = True + language = 'en' + filterDuplicates = True + remove_attributes = ['style'] + + keep_only_tags = [dict(name='div', attrs={'class':'page-header'}), + dict(name='div', attrs={'class':'asset-body'}) + ] + remove_tags = [dict(name='div', attrs={'class':['byline-section', 'asset-meta']}) + ] + + feeds = [ + #####NEWS##### + (u"News", u"http://www.news-journalonline.com/rss.xml"), + (u"Breaking News", u"http://www.news-journalonline.com/breakingnews/rss.xml"), + (u"Local - East Volusia", u"http://www.news-journalonline.com/news/local/east-volusia/rss.xml"), + (u"Local - West Volusia", u"http://www.news-journalonline.com/news/local/west-volusia/rss.xml"), + (u"Local - Southeast", u"http://www.news-journalonline.com/news/local/southeast-volusia/rss.xml"), + (u"Local - Flagler", u"http://www.news-journalonline.com/news/local/flagler/rss.xml"), + (u"Florida", u"http://www.news-journalonline.com/news/florida/rss.xml"), + (u"National/World", u"http://www.news-journalonline.com/news/nationworld/rss.xml"), + (u"Politics", u"http://www.news-journalonline.com/news/politics/rss.xml"), + (u"News of Record", u"http://www.news-journalonline.com/news/news-of-record/rss.xml"), + ####BUSINESS#### + (u"Business", u"http://www.news-journalonline.com/business/rss.xml"), + #(u"Jobs", u"http://www.news-journalonline.com/business/jobs/rss.xml"), + #(u"Markets", u"http://www.news-journalonline.com/business/markets/rss.xml"), + #(u"Real Estate", u"http://www.news-journalonline.com/business/real-estate/rss.xml"), + #(u"Technology", u"http://www.news-journalonline.com/business/technology/rss.xml"), + ####SPORTS#### + (u"Sports", u"http://www.news-journalonline.com/sports/rss.xml"), + (u"Racing", u"http://www.news-journalonline.com/racing/rss.xml"), + (u"Highschool", u"http://www.news-journalonline.com/sports/highschool/rss.xml"), + (u"College", u"http://www.news-journalonline.com/sports/college/rss.xml"), + (u"Basketball", u"http://www.news-journalonline.com/sports/basketball/rss.xml"), + (u"Football", u"http://www.news-journalonline.com/sports/football/rss.xml"), + (u"Golf", u"http://www.news-journalonline.com/sports/golf/rss.xml"), + (u"Other Sports", u"http://www.news-journalonline.com/sports/other/rss.xml"), + ####LIFESTYLE#### + (u"Lifestyle", u"http://www.news-journalonline.com/lifestyle/rss.xml"), + #(u"Fashion", u"http://www.news-journalonline.com/lifestyle/fashion/rss.xml"), + (u"Food", u"http://www.news-journalonline.com/lifestyle/food/rss.xml"), + #(u"Health", u"http://www.news-journalonline.com/lifestyle/health/rss.xml"), + (u"Home and Garden", u"http://www.news-journalonline.com/lifestyle/home-and-garden/rss.xml"), + (u"Living", u"http://www.news-journalonline.com/lifestyle/living/rss.xml"), + (u"Religion", u"http://www.news-journalonline.com/lifestyle/religion/rss.xml"), + #(u"Travel", u"http://www.news-journalonline.com/lifestyle/travel/rss.xml"), + ####OPINION#### + #(u"Opinion", u"http://www.news-journalonline.com/opinion/rss.xml"), + #(u"Letters to Editor", u"http://www.news-journalonline.com/opinion/letters-to-the-editor/rss.xml"), + #(u"Columns", u"http://www.news-journalonline.com/columns/rss.xml"), + #(u"Podcasts", u"http://www.news-journalonline.com/podcasts/rss.xml"), + ####ENTERTAINMENT#### ##Weekly Feature## + (u"Entertainment", u"http://www.go386.com/rss.xml"), + (u"Go Out", u"http://www.go386.com/go/rss.xml"), + (u"Music", u"http://www.go386.com/music/rss.xml"), + (u"Movies", u"http://www.go386.com/movies/rss.xml"), + #(u"Culture", u"http://www.go386.com/culture/rss.xml"), + + ] + + extra_css = ''' + .page-header{font-family:Arial,Helvetica,sans-serif; font-style:bold;font-size:22pt;} + .asset-body{font-family:Helvetica,Arial,sans-serif; font-size:16pt;} + + ''' From cdd6637598fec9af389c13d6580e279ef6651e35 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Tue, 21 Jun 2011 23:05:57 -0300 Subject: [PATCH 13/31] Add support to display Favoutite as a collection for KTouch --- src/calibre/devices/kobo/driver.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index b523b7f296..4ccff13dd6 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -100,7 +100,7 @@ class KOBO(USBMS): for idx,b in enumerate(bl): bl_cache[b.lpath] = idx - def update_booklist(prefix, path, title, authors, mime, date, ContentType, ImageID, readstatus, MimeType, expired): + def update_booklist(prefix, path, title, authors, mime, date, ContentType, ImageID, readstatus, MimeType, expired, favouritesindex): changed = False try: lpath = path.partition(self.normalize_path(prefix))[2] @@ -111,17 +111,23 @@ class KOBO(USBMS): playlist_map = {} + if lpath not in playlist_map: + playlist_map[lpath] = [] + if readstatus == 1: - playlist_map[lpath]= "Im_Reading" + playlist_map[lpath].append('Im_Reading') elif readstatus == 2: - playlist_map[lpath]= "Read" + playlist_map[lpath].append('Read') elif readstatus == 3: - playlist_map[lpath]= "Closed" + playlist_map[lpath].append('Closed') # Related to a bug in the Kobo firmware that leaves an expired row for deleted books # this shows an expired Collection so the user can decide to delete the book if expired == 3: - playlist_map[lpath] = "Expired" + playlist_map[lpath].append('Expired') + # Favourites are supported on the touch but the data field is there on most earlier models + if favouritesindex == 1: + playlist_map[lpath].append('Favourite') path = self.normalize_path(path) # print "Normalized FileName: " + path @@ -149,7 +155,7 @@ class KOBO(USBMS): debug_print(" Strange: The file: ", prefix, lpath, " does mot exist!") if lpath in playlist_map and \ playlist_map[lpath] not in bl[idx].device_collections: - bl[idx].device_collections.append(playlist_map[lpath]) + bl[idx].device_collections = playlist_map.get(lpath,[]) else: if ContentType == '6' and MimeType == 'Shortcover': book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID, size=1048576) @@ -168,7 +174,7 @@ class KOBO(USBMS): raise # print 'Update booklist' - book.device_collections = [playlist_map[lpath]] if lpath in playlist_map else [] + book.device_collections = playlist_map.get(lpath,[])# if lpath in playlist_map else [] if bl.add_book(book, replace_metadata=False): changed = True @@ -198,7 +204,7 @@ class KOBO(USBMS): self.dbversion = result[0] query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ - 'ImageID, ReadStatus, ___ExpirationStatus from content where BookID is Null' + 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex from content where BookID is Null' cursor.execute (query) @@ -213,10 +219,10 @@ class KOBO(USBMS): # debug_print("mime:", mime) if oncard != 'carda' and oncard != 'cardb' and not row[3].startswith("file:///mnt/sd/"): - changed = update_booklist(self._main_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7], row[4], row[8]) + changed = update_booklist(self._main_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7], row[4], row[8], row[9]) # print "shortbook: " + path elif oncard == 'carda' and row[3].startswith("file:///mnt/sd/"): - changed = update_booklist(self._card_a_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7], row[4], row[8]) + changed = update_booklist(self._card_a_prefix, path, row[0], row[1], mime, row[2], row[5], row[6], row[7], row[4], row[8], row[9]) if changed: need_sync = True From a8a2db1313ee3b90b366f3a4fec2585af8feb3e3 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Tue, 21 Jun 2011 23:20:52 -0300 Subject: [PATCH 14/31] Query for FavouritesIndex only for KTouch and database versions that support it --- src/calibre/devices/kobo/driver.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 4ccff13dd6..650e965941 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -203,8 +203,12 @@ class KOBO(USBMS): result = cursor.fetchone() self.dbversion = result[0] - query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ + if self.dbversion >= 14: + query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex from content where BookID is Null' + else: + query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ + 'ImageID, ReadStatus, ___ExpirationStatus, "-1" as FavouritesIndex from content where BookID is Null' cursor.execute (query) From f1a4d06e512a1ab63a727a148005fdacf4a52e36 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 21 Jun 2011 20:43:48 -0600 Subject: [PATCH 15/31] Try to ensure that all file I/O on library files happes in the LibraryDatabase2 class. This is in preparation for the new multi-threaded db backend. Also: Content server now sends the Content-Disposition header when sending ebook files. After uploading books to the device, delete the associated temp files. If the user tries to drag and drop more than 25 books out of calibre, only the first 25 will be sent. --- src/calibre/db/backend.py | 4 +- src/calibre/db/errors.py | 13 ++ src/calibre/devices/apple/driver.py | 1 + src/calibre/devices/interface.py | 7 +- src/calibre/ebooks/metadata/worker.py | 30 ++-- src/calibre/gui2/actions/copy_to_library.py | 16 +- src/calibre/gui2/actions/edit_metadata.py | 46 ++++-- src/calibre/gui2/actions/tweak_epub.py | 7 +- src/calibre/gui2/add.py | 1 + src/calibre/gui2/convert/regex_builder.py | 9 +- src/calibre/gui2/convert/single.py | 6 +- src/calibre/gui2/device.py | 28 +++- src/calibre/gui2/dialogs/metadata_bulk.py | 13 +- src/calibre/gui2/email.py | 3 +- src/calibre/gui2/library/models.py | 45 ++---- src/calibre/gui2/library/views.py | 12 +- src/calibre/gui2/metadata/basic_widgets.py | 3 +- src/calibre/gui2/tools.py | 18 ++- src/calibre/library/__init__.py | 18 --- src/calibre/library/catalog.py | 6 + src/calibre/library/database2.py | 155 ++++++++++++++------ src/calibre/library/save_to_disk.py | 30 ++-- src/calibre/library/server/content.py | 35 ++--- 23 files changed, 319 insertions(+), 187 deletions(-) create mode 100644 src/calibre/db/errors.py diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index 159612e52d..ba683dde50 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -222,7 +222,9 @@ class DB(object, SchemaUpgrade): if self.user_version == 0: self.initialize_database() - SchemaUpgrade.__init__(self) + with self.conn: + SchemaUpgrade.__init__(self) + # Guarantee that the library_id is set self.library_id diff --git a/src/calibre/db/errors.py b/src/calibre/db/errors.py new file mode 100644 index 0000000000..d2657f9904 --- /dev/null +++ b/src/calibre/db/errors.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + + +class NoSuchFormat(ValueError): + pass + diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index dea5844028..cc531b7476 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -107,6 +107,7 @@ class DriverBase(DeviceConfig, DevicePlugin): # Needed for config_widget to work FORMATS = ['epub', 'pdf'] USER_CAN_ADD_NEW_FORMATS = False + KEEP_TEMP_FILES_AFTER_UPLOAD = True # Hide the standard customization widgets SUPPORTS_SUB_DIRS = False diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index b265331ace..925f9eafd0 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -327,12 +327,7 @@ class DevicePlugin(Plugin): free space on the device. The text of the FreeSpaceError must contain the word "card" if ``on_card`` is not None otherwise it must contain the word "memory". - :param files: A list of paths and/or file-like objects. If they are paths and - the paths point to temporary files, they may have an additional - attribute, original_file_path pointing to the originals. They may have - another optional attribute, deleted_after_upload which if True means - that the file pointed to by original_file_path will be deleted after - being uploaded to the device. + :param files: A list of paths :param names: A list of file names that the books should have once uploaded to the device. len(names) == len(files) :param metadata: If not None, it is a list of :class:`Metadata` objects. diff --git a/src/calibre/ebooks/metadata/worker.py b/src/calibre/ebooks/metadata/worker.py index c335cc4c13..ca8707258b 100644 --- a/src/calibre/ebooks/metadata/worker.py +++ b/src/calibre/ebooks/metadata/worker.py @@ -15,7 +15,7 @@ from calibre.utils.ipc.server import Server from calibre.ptempfile import PersistentTemporaryDirectory, TemporaryDirectory from calibre import prints, isbytestring from calibre.constants import filesystem_encoding - +from calibre.db.errors import NoSuchFormat def debug(*args): prints(*args) @@ -201,27 +201,35 @@ class SaveWorker(Thread): self.spare_server = spare_server self.start() - def collect_data(self, ids): + def collect_data(self, ids, tdir): from calibre.ebooks.metadata.opf2 import metadata_to_opf data = {} for i in set(ids): - mi = self.db.get_metadata(i, index_is_id=True, get_cover=True) + mi = self.db.get_metadata(i, index_is_id=True, get_cover=True, + cover_as_data=True) opf = metadata_to_opf(mi) if isbytestring(opf): opf = opf.decode('utf-8') cpath = None - if mi.cover: - cpath = mi.cover + if mi.cover_data and mi.cover_data[1]: + cpath = os.path.join(tdir, 'cover_%s.jpg'%i) + with lopen(cpath, 'wb') as f: + f.write(mi.cover_data[1]) if isbytestring(cpath): cpath = cpath.decode(filesystem_encoding) formats = {} if mi.formats: for fmt in mi.formats: - fpath = self.db.format_abspath(i, fmt, index_is_id=True) - if fpath is not None: - if isbytestring(fpath): - fpath = fpath.decode(filesystem_encoding) - formats[fmt.lower()] = fpath + fpath = os.path.join(tdir, 'fmt_%s.%s'%(i, fmt.lower())) + with lopen(fpath, 'wb') as f: + try: + self.db.copy_format_to(i, fmt, f, index_is_id=True) + except NoSuchFormat: + continue + else: + if isbytestring(fpath): + fpath = fpath.decode(filesystem_encoding) + formats[fmt.lower()] = fpath data[i] = [opf, cpath, formats, mi.last_modified.isoformat()] return data @@ -244,7 +252,7 @@ class SaveWorker(Thread): for i, task in enumerate(tasks): tids = [x[-1] for x in task] - data = self.collect_data(tids) + data = self.collect_data(tids, tdir) dpath = os.path.join(tdir, '%d.json'%i) with open(dpath, 'wb') as f: f.write(json.dumps(data, ensure_ascii=False).encode('utf-8')) diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py index 7190d1486f..97880faaa1 100644 --- a/src/calibre/gui2/actions/copy_to_library.py +++ b/src/calibre/gui2/actions/copy_to_library.py @@ -53,13 +53,18 @@ class Worker(Thread): # {{{ from calibre.library.database2 import LibraryDatabase2 newdb = LibraryDatabase2(self.loc) for i, x in enumerate(self.ids): - mi = self.db.get_metadata(x, index_is_id=True, get_cover=True) + mi = self.db.get_metadata(x, index_is_id=True, get_cover=True, + cover_as_data=True) self.progress(i, mi.title) fmts = self.db.formats(x, index_is_id=True) if not fmts: fmts = [] else: fmts = fmts.split(',') - paths = [self.db.format_abspath(x, fmt, index_is_id=True) for fmt in - fmts] + paths = [] + for fmt in fmts: + p = self.db.format(x, fmt, index_is_id=True, + as_path=True) + if p: + paths.append(p) added = False if prefs['add_formats_to_existing']: identical_book_list = newdb.find_identical_books(mi) @@ -75,6 +80,11 @@ class Worker(Thread): # {{{ if co is not None: newdb.set_conversion_options(x, 'PIPE', co) self.processed.add(x) + for path in paths: + try: + os.remove(path) + except: + pass # }}} class CopyToLibraryAction(InterfaceAction): diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index 650947100e..718ece46d2 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -17,6 +17,7 @@ from calibre.gui2.dialogs.tag_list_editor import TagListEditor from calibre.gui2.actions import InterfaceAction from calibre.ebooks.metadata import authors_to_string from calibre.utils.icu import sort_key +from calibre.db.errors import NoSuchFormat class EditMetadataAction(InterfaceAction): @@ -265,7 +266,7 @@ class EditMetadataAction(InterfaceAction): +'

', 'merge_too_many_books', self.gui): return - dest_id, src_books, src_ids = self.books_to_merge(rows) + dest_id, src_ids = self.books_to_merge(rows) title = self.gui.library_view.model().db.title(dest_id, index_is_id=True) if safe_merge: if not confirm('

'+_( @@ -277,7 +278,7 @@ class EditMetadataAction(InterfaceAction): 'Please confirm you want to proceed.')%title +'

', 'merge_books_safe', self.gui): return - self.add_formats(dest_id, src_books) + self.add_formats(dest_id, self.formats_for_books(rows)) self.merge_metadata(dest_id, src_ids) elif merge_only_formats: if not confirm('

'+_( @@ -293,7 +294,7 @@ class EditMetadataAction(InterfaceAction): 'Are you sure you want to proceed?')%title +'

', 'merge_only_formats', self.gui): return - self.add_formats(dest_id, src_books) + self.add_formats(dest_id, self.formats_for_books(rows)) self.delete_books_after_merge(src_ids) else: if not confirm('

'+_( @@ -308,7 +309,7 @@ class EditMetadataAction(InterfaceAction): 'Are you sure you want to proceed?')%title +'

', 'merge_books', self.gui): return - self.add_formats(dest_id, src_books) + self.add_formats(dest_id, self.formats_for_books(rows)) self.merge_metadata(dest_id, src_ids) self.delete_books_after_merge(src_ids) # leave the selection highlight on first selected book @@ -329,8 +330,22 @@ class EditMetadataAction(InterfaceAction): self.gui.library_view.model().db.add_format(dest_id, fmt, f, index_is_id=True, notify=False, replace=replace) + def formats_for_books(self, rows): + m = self.gui.library_view.model() + ans = [] + for id_ in map(m.id, rows): + dbfmts = m.db.formats(id_, index_is_id=True) + if dbfmts: + for fmt in dbfmts.split(','): + try: + path = m.db.format(id_, fmt, index_is_id=True, + as_path=True) + ans.append(path) + except NoSuchFormat: + continue + return ans + def books_to_merge(self, rows): - src_books = [] src_ids = [] m = self.gui.library_view.model() for i, row in enumerate(rows): @@ -339,22 +354,19 @@ class EditMetadataAction(InterfaceAction): dest_id = id_ else: src_ids.append(id_) - dbfmts = m.db.formats(id_, index_is_id=True) - if dbfmts: - for fmt in dbfmts.split(','): - src_books.append(m.db.format_abspath(id_, fmt, - index_is_id=True)) - return [dest_id, src_books, src_ids] + return [dest_id, src_ids] def delete_books_after_merge(self, ids_to_delete): self.gui.library_view.model().delete_books_by_id(ids_to_delete) def merge_metadata(self, dest_id, src_ids): db = self.gui.library_view.model().db - dest_mi = db.get_metadata(dest_id, index_is_id=True, get_cover=True) + dest_mi = db.get_metadata(dest_id, index_is_id=True) orig_dest_comments = dest_mi.comments + dest_cover = db.cover(dest_id, index_is_id=True) + had_orig_cover = bool(dest_cover) for src_id in src_ids: - src_mi = db.get_metadata(src_id, index_is_id=True, get_cover=True) + src_mi = db.get_metadata(src_id, index_is_id=True) if src_mi.comments and orig_dest_comments != src_mi.comments: if not dest_mi.comments: dest_mi.comments = src_mi.comments @@ -372,8 +384,10 @@ class EditMetadataAction(InterfaceAction): dest_mi.tags = src_mi.tags else: dest_mi.tags.extend(src_mi.tags) - if src_mi.cover and not dest_mi.cover: - dest_mi.cover = src_mi.cover + if not dest_cover: + src_cover = db.cover(src_id, index_is_id=True) + if src_cover: + dest_cover = src_cover if not dest_mi.publisher: dest_mi.publisher = src_mi.publisher if not dest_mi.rating: @@ -382,6 +396,8 @@ class EditMetadataAction(InterfaceAction): dest_mi.series = src_mi.series dest_mi.series_index = src_mi.series_index db.set_metadata(dest_id, dest_mi, ignore_errors=False) + if not had_orig_cover and dest_cover: + db.set_cover(dest_id, dest_cover) for key in db.field_metadata: #loop thru all defined fields if db.field_metadata[key]['is_custom']: diff --git a/src/calibre/gui2/actions/tweak_epub.py b/src/calibre/gui2/actions/tweak_epub.py index c9f2d7a8c6..d3924e7cd3 100755 --- a/src/calibre/gui2/actions/tweak_epub.py +++ b/src/calibre/gui2/actions/tweak_epub.py @@ -5,6 +5,8 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' +import os + from calibre.gui2 import error_dialog from calibre.gui2.actions import InterfaceAction from calibre.gui2.dialogs.tweak_epub import TweakEpub @@ -30,8 +32,8 @@ class TweakEpubAction(InterfaceAction): # Confirm 'EPUB' in formats book_id = self.gui.library_view.model().id(row) try: - path_to_epub = self.gui.library_view.model().db.format_abspath( - book_id, 'EPUB', index_is_id=True) + path_to_epub = self.gui.library_view.model().db.format( + book_id, 'EPUB', index_is_id=True, as_path=True) except: path_to_epub = None @@ -45,6 +47,7 @@ class TweakEpubAction(InterfaceAction): if dlg.exec_() == dlg.Accepted: self.update_db(book_id, dlg._output) dlg.cleanup() + os.remove(path_to_epub) def update_db(self, book_id, rebuilt): ''' diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py index 44b5bb446b..3dbf4b94df 100644 --- a/src/calibre/gui2/add.py +++ b/src/calibre/gui2/add.py @@ -445,6 +445,7 @@ class Saver(QObject): # {{{ self.pd.setModal(True) self.pd.show() self.pd.set_min(0) + self.pd.set_msg(_('Collecting data, please wait...')) self._parent = parent self.callback = callback self.callback_called = False diff --git a/src/calibre/gui2/convert/regex_builder.py b/src/calibre/gui2/convert/regex_builder.py index bf32bf472a..f79e6df3fe 100644 --- a/src/calibre/gui2/convert/regex_builder.py +++ b/src/calibre/gui2/convert/regex_builder.py @@ -4,7 +4,7 @@ __license__ = 'GPL 3' __copyright__ = '2009, John Schember ' __docformat__ = 'restructuredtext en' -import re +import re, os from PyQt4.QtCore import SIGNAL, Qt, pyqtSignal from PyQt4.QtGui import QDialog, QWidget, QDialogButtonBox, \ @@ -134,7 +134,12 @@ class RegexBuilder(QDialog, Ui_RegexBuilder): _('Cannot build regex using the GUI builder without a book.'), show=True) return False - self.open_book(db.format_abspath(book_id, format, index_is_id=True)) + fpath = db.format(book_id, format, index_is_id=True, + as_path=True) + try: + self.open_book(fpath) + finally: + os.remove(fpath) return True def open_book(self, pathtoebook): diff --git a/src/calibre/gui2/convert/single.py b/src/calibre/gui2/convert/single.py index 3575fb5ffb..15e670ee32 100644 --- a/src/calibre/gui2/convert/single.py +++ b/src/calibre/gui2/convert/single.py @@ -106,7 +106,6 @@ class Config(ResizableDialog, Ui_Dialog): Configuration dialog for single book conversion. If accepted, has the following important attributes - input_path - Path to input file output_format - Output format (without a leading .) input_format - Input format (without a leading .) opf_path - Path to OPF file with user specified metadata @@ -156,13 +155,10 @@ class Config(ResizableDialog, Ui_Dialog): oidx = self.groups.currentIndex().row() input_format = self.input_format output_format = self.output_format - input_path = self.db.format_abspath(self.book_id, input_format, - index_is_id=True) - self.input_path = input_path output_path = 'dummy.'+output_format log = Log() log.outputs = [] - self.plumber = Plumber(input_path, output_path, log) + self.plumber = Plumber('dummy.'+input_format, output_path, log) def widget_factory(cls): return cls(self.stack, self.plumber.get_option_by_name, diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index a527cc2e27..d5805a8e09 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -396,8 +396,17 @@ class DeviceManager(Thread): # {{{ if DEBUG: prints(traceback.format_exc(), file=sys.__stdout__) - return self.device.upload_books(files, names, on_card, - metadata=metadata, end_session=False) + try: + return self.device.upload_books(files, names, on_card, + metadata=metadata, end_session=False) + finally: + if metadata: + for mi in metadata: + try: + if mi.cover: + os.remove(mi.cover) + except: + pass def upload_books(self, done, files, names, on_card=None, titles=None, metadata=None, plugboards=None, add_as_step_to_job=None): @@ -1072,8 +1081,6 @@ class DeviceMixin(object): # {{{ 'the device?'), autos): self.iactions['Convert Books'].auto_convert_news(auto, format) files = [f for f in files if f is not None] - for f in files: - f.deleted_after_upload = del_on_upload if not files: self.news_to_be_synced = set([]) return @@ -1315,8 +1322,17 @@ class DeviceMixin(object): # {{{ self.card_b_view if on_card == 'cardb' else self.memory_view view.model().resort(reset=False) view.model().research() - for f in files: - getattr(f, 'close', lambda : True)() + if files: + for f in files: + # Remove temporary files + try: + rem = not getattr( + self.device_manager.device, + 'KEEP_TEMP_FILES_AFTER_UPLOAD', False) + if rem and 'caltmpfmt.' in f: + os.remove(f) + except: + pass def book_on_device(self, id, reset=False): ''' diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index ce21eba00e..7c7c78629c 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -24,7 +24,7 @@ from calibre.utils.config import prefs, tweaks from calibre.utils.magick.draw import identify_data from calibre.utils.date import qt_to_dt -def get_cover_data(path): # {{{ +def get_cover_data(stream, ext): # {{{ from calibre.ebooks.metadata.meta import get_metadata old = prefs['read_file_metadata'] if not old: @@ -32,8 +32,8 @@ def get_cover_data(path): # {{{ cdata = area = None try: - mi = get_metadata(open(path, 'rb'), - os.path.splitext(path)[1][1:].lower()) + with stream: + mi = get_metadata(stream, ext) if mi.cover and os.access(mi.cover, os.R_OK): cdata = open(mi.cover).read() elif mi.cover_data[1] is not None: @@ -186,9 +186,10 @@ class MyBlockingBusy(QDialog): # {{{ if fmts: covers = [] for fmt in fmts.split(','): - fmt = self.db.format_abspath(id, fmt, index_is_id=True) - if not fmt: continue - cdata, area = get_cover_data(fmt) + fmtf = self.db.format(id, fmt, index_is_id=True, + as_file=True) + if fmtf is None: continue + cdata, area = get_cover_data(fmtf, fmt) if cdata: covers.append((cdata, area)) covers.sort(key=lambda x: x[1]) diff --git a/src/calibre/gui2/email.py b/src/calibre/gui2/email.py index b82f421e1e..b9c760abff 100644 --- a/src/calibre/gui2/email.py +++ b/src/calibre/gui2/email.py @@ -174,7 +174,8 @@ class EmailMixin(object): # {{{ else: _auto_ids = [] - full_metadata = self.library_view.model().metadata_for(ids) + full_metadata = self.library_view.model().metadata_for(ids, + get_cover=False) bad, remove_ids, jobnames = [], [], [] texts, subjects, attachments, attachment_names = [], [], [], [] diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index f49c6db59a..40d6e2b6cf 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -5,8 +5,7 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import shutil, functools, re, os, traceback -from contextlib import closing +import functools, re, os, traceback from collections import defaultdict from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, @@ -36,14 +35,6 @@ TIME_FMT = '%d %b %Y' ALIGNMENT_MAP = {'left': Qt.AlignLeft, 'right': Qt.AlignRight, 'center': Qt.AlignHCenter} -class FormatPath(unicode): - - def __new__(cls, path, orig_file_path): - ans = unicode.__new__(cls, path) - ans.orig_file_path = orig_file_path - ans.deleted_after_upload = False - return ans - _default_image = None def default_image(): @@ -391,10 +382,14 @@ class BooksModel(QAbstractTableModel): # {{{ data = self.current_changed(index, None, False) return data - def metadata_for(self, ids): + def metadata_for(self, ids, get_cover=True): + ''' + WARNING: if get_cover=True temp files are created for mi.cover. + Remember to delete them once you are done with them. + ''' ans = [] for id in ids: - mi = self.db.get_metadata(id, index_is_id=True, get_cover=True) + mi = self.db.get_metadata(id, index_is_id=True, get_cover=get_cover) ans.append(mi) return ans @@ -449,18 +444,14 @@ class BooksModel(QAbstractTableModel): # {{{ format = f break if format is not None: - pt = PersistentTemporaryFile(suffix='.'+format) - with closing(self.db.format(id, format, index_is_id=True, - as_file=True)) as src: - shutil.copyfileobj(src, pt) - pt.flush() - if getattr(src, 'name', None): - pt.orig_file_path = os.path.abspath(src.name) + pt = PersistentTemporaryFile(suffix='caltmpfmt.'+format) + self.db.copy_format_to(id, format, pt, index_is_id=True) pt.seek(0) if set_metadata: try: - _set_metadata(pt, self.db.get_metadata(id, get_cover=True, index_is_id=True), - format) + _set_metadata(pt, self.db.get_metadata( + id, get_cover=True, index_is_id=True, + cover_as_data=True), format) except: traceback.print_exc() pt.close() @@ -468,9 +459,7 @@ class BooksModel(QAbstractTableModel): # {{{ if isbytestring(x): x = x.decode(filesystem_encoding) return x - name, op = map(to_uni, map(os.path.abspath, (pt.name, - pt.orig_file_path))) - ans.append(FormatPath(name, op)) + ans.append(to_uni(os.path.abspath(pt.name))) else: need_auto.append(id) if not exclude_auto: @@ -499,13 +488,11 @@ class BooksModel(QAbstractTableModel): # {{{ break if format is not None: pt = PersistentTemporaryFile(suffix='.'+format) - with closing(self.db.format(row, format, as_file=True)) as src: - shutil.copyfileobj(src, pt) - pt.flush() + self.db.copy_format_to(id, format, pt, index_is_id=True) pt.seek(0) if set_metadata: - _set_metadata(pt, self.db.get_metadata(row, get_cover=True), - format) + _set_metadata(pt, self.db.get_metadata(row, get_cover=True, + cover_as_data=True), format) pt.close() if paths else pt.seek(0) ans.append(pt) else: diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 3ca898d15a..9aa926f5c5 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -584,14 +584,15 @@ class BooksView(QTableView): # {{{ m = self.model() db = m.db rows = self.selectionModel().selectedRows() - selected = map(m.id, rows) + selected = list(map(m.id, rows)) ids = ' '.join(map(str, selected)) md = QMimeData() md.setData('application/calibre+from_library', ids) fmt = prefs['output_format'] def url_for_id(i): - ans = db.format_abspath(i, fmt, index_is_id=True) + ans = db.format(i, fmt, index_is_id=True, as_path=True, + preserve_filename=True) if ans is None: fmts = db.formats(i, index_is_id=True) if fmts: @@ -599,14 +600,13 @@ class BooksView(QTableView): # {{{ else: fmts = [] for f in fmts: - ans = db.format_abspath(i, f, index_is_id=True) - if ans is not None: - break + ans = db.format(i, f, index_is_id=True, as_path=True, + preserve_filename=True) if ans is None: ans = db.abspath(i, index_is_id=True) return QUrl.fromLocalFile(ans) - md.setUrls([url_for_id(i) for i in selected]) + md.setUrls([url_for_id(i) for i in selected[:25]]) drag = QDrag(self) col = self.selectionModel().currentIndex().column() md.column_name = self.column_map[col] diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index f865a5c62c..303cc51c74 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -688,7 +688,8 @@ class FormatsManager(QWidget): # {{{ else: stream = open(fmt.path, 'r+b') try: - mi = get_metadata(stream, ext) + with stream: + mi = get_metadata(stream, ext) return mi, ext except: error_dialog(self, _('Could not read metadata'), diff --git a/src/calibre/gui2/tools.py b/src/calibre/gui2/tools.py index 39224c8b35..03b32ca2fb 100644 --- a/src/calibre/gui2/tools.py +++ b/src/calibre/gui2/tools.py @@ -51,12 +51,15 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, # {{{ # continue mi = db.get_metadata(book_id, True) - in_file = db.format_abspath(book_id, d.input_format, True) + in_file = PersistentTemporaryFile('.'+d.input_format) + with in_file: + db.copy_format_to(book_id, d.input_format, in_file, + index_is_id=True) out_file = PersistentTemporaryFile('.' + d.output_format) out_file.write(d.output_format) out_file.close() - temp_files = [] + temp_files = [in_file] try: dtitle = unicode(mi.title) @@ -74,7 +77,7 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, # {{{ recs.append(('cover', d.cover_file.name, OptionRecommendation.HIGH)) temp_files.append(d.cover_file) - args = [in_file, out_file.name, recs] + args = [in_file.name, out_file.name, recs] temp_files.append(out_file) jobs.append(('gui_convert_override', args, desc, d.output_format.upper(), book_id, temp_files)) @@ -142,12 +145,15 @@ class QueueBulk(QProgressDialog): try: input_format = get_input_format_for_book(self.db, book_id, None)[0] mi, opf_file = create_opf_file(self.db, book_id) - in_file = self.db.format_abspath(book_id, input_format, True) + in_file = PersistentTemporaryFile('.'+input_format) + with in_file: + self.db.copy_format_to(book_id, input_format, in_file, + index_is_id=True) out_file = PersistentTemporaryFile('.' + self.output_format) out_file.write(self.output_format) out_file.close() - temp_files = [] + temp_files = [in_file] combined_recs = GuiRecommendations() default_recs = bulk_defaults_for_input_format(input_format) @@ -183,7 +189,7 @@ class QueueBulk(QProgressDialog): self.setLabelText(_('Queueing ')+dtitle) desc = _('Convert book %d of %d (%s)') % (self.i, len(self.book_ids), dtitle) - args = [in_file, out_file.name, lrecs] + args = [in_file.name, out_file.name, lrecs] temp_files.append(out_file) self.jobs.append(('gui_convert_override', args, desc, self.output_format.upper(), book_id, temp_files)) diff --git a/src/calibre/library/__init__.py b/src/calibre/library/__init__.py index 2e00db32c4..84a7acbc73 100644 --- a/src/calibre/library/__init__.py +++ b/src/calibre/library/__init__.py @@ -61,22 +61,4 @@ def generate_test_db(library_path, # {{{ print 'Time per record:', t/float(num_of_records) # }}} -def cover_load_timing(path=None): - from PyQt4.Qt import QApplication, QImage - import os, time - app = QApplication([]) - app - d = db(path) - paths = [d.cover(i, index_is_id=True, as_path=True) for i in - d.data.iterallids()] - paths = [p for p in paths if (p and os.path.exists(p) and os.path.isfile(p))] - - start = time.time() - - for p in paths: - with open(p, 'rb') as f: - img = QImage() - img.loadFromData(f.read()) - - print 'Average load time:', (time.time() - start)/len(paths), 'seconds' diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 8508fb266f..1b8bb365ab 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -5137,6 +5137,7 @@ Author '{0}': OptionRecommendation.HIGH)) # If cover exists, use it + cpath = None try: search_text = 'title:"%s" author:%s' % ( opts.catalog_title.replace('"', '\\"'), 'calibre') @@ -5157,5 +5158,10 @@ Author '{0}': plumber.merge_ui_recommendations(recommendations) plumber.run() + try: + os.remove(cpath) + except: + pass + # returns to gui2.actions.catalog:catalog_generated() return catalog.error diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index efb130676a..4c61438e35 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -12,8 +12,6 @@ import threading, random from itertools import repeat from math import ceil -from PyQt4.QtGui import QImage - from calibre import prints from calibre.ebooks.metadata import (title_sort, author_to_author_sort, string_to_authors, authors_to_string) @@ -27,7 +25,7 @@ from calibre.library.sqlite import connect, IntegrityError from calibre.library.prefs import DBPrefs from calibre.ebooks.metadata.book.base import Metadata from calibre.constants import preferred_encoding, iswindows, filesystem_encoding -from calibre.ptempfile import PersistentTemporaryFile +from calibre.ptempfile import PersistentTemporaryFile, base_dir from calibre.customize.ui import run_plugins_on_import from calibre import isbytestring from calibre.utils.filenames import ascii_filename @@ -39,8 +37,10 @@ from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format from calibre.utils.magick.draw import save_cover_data_to from calibre.utils.recycle_bin import delete_file, delete_tree from calibre.utils.formatter_functions import load_user_template_functions +from calibre.db.errors import NoSuchFormat copyfile = os.link if hasattr(os, 'link') else shutil.copyfile +SPOOL_SIZE = 30*1024*1024 class Tag(object): @@ -601,14 +601,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): with lopen(os.path.join(tpath, 'cover.jpg'), 'wb') as f: f.write(cdata) for format in formats: - # Get data as string (can't use file as source and target files may be the same) - f = self.format(id, format, index_is_id=True, as_file=True) - if f is None: - continue - with tempfile.SpooledTemporaryFile(max_size=30*(1024**2)) as stream: - with f: - shutil.copyfileobj(f, stream) - stream.seek(0) + with tempfile.SpooledTemporaryFile(max_size=SPOOL_SIZE) as stream: + try: + self.copy_format_to(id, format, stream, index_is_id=True) + stream.seek(0) + except NoSuchFormat: + continue self.add_format(id, format, stream, index_is_id=True, path=tpath, notify=False) self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id)) @@ -661,32 +659,53 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): continue def cover(self, index, index_is_id=False, as_file=False, as_image=False, - as_path=False): + as_path=False): ''' Return the cover image as a bytestring (in JPEG format) or None. - `as_file` : If True return the image as an open file object - `as_image`: If True return the image as a QImage object + WARNING: Using as_path will copy the cover to a temp file and return + the path to the temp file. You should delete the temp file when you are + done with it. + + :param as_file: If True return the image as an open file object (a SpooledTemporaryFile) + :param as_image: If True return the image as a QImage object ''' - id = index if index_is_id else self.id(index) + id = index if index_is_id else self.id(index) path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg') if os.access(path, os.R_OK): - if as_path: - return path try: f = lopen(path, 'rb') except (IOError, OSError): time.sleep(0.2) f = lopen(path, 'rb') - if as_image: - img = QImage() - img.loadFromData(f.read()) - f.close() - return img - ans = f if as_file else f.read() - if ans is not f: - f.close() - return ans + with f: + if as_path: + pt = PersistentTemporaryFile('_dbcover.jpg') + with pt: + shutil.copyfileobj(f, pt) + return pt.name + if as_file: + ret = tempfile.SpooledTemporaryFile(SPOOL_SIZE) + shutil.copyfileobj(f, ret) + ret.seek(0) + else: + ret = f.read() + if as_image: + from PyQt4.Qt import QImage + i = QImage() + i.loadFromData(ret) + ret = i + return ret + + def cover_last_modified(self, index, index_is_id=False): + id = index if index_is_id else self.id(index) + path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg') + try: + return utcfromtimestamp(os.stat(path).st_mtime) + except: + # Cover doesn't exist + pass + return self.last_modified() ### The field-style interface. These use field keys. @@ -859,7 +878,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return (path, mi, sequence) def get_metadata(self, idx, index_is_id=False, get_cover=False, - get_user_categories=True): + get_user_categories=True, cover_as_data=False): ''' Convenience method to return metadata as a :class:`Metadata` object. Note that the list of formats is not verified. @@ -934,7 +953,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): mi.user_categories = user_cat_vals if get_cover: - mi.cover = self.cover(id, index_is_id=True, as_path=True) + if cover_as_data: + cdata = self.cover(id, index_is_id=True) + if cdata: + mi.cover_data = ('jpeg', cdata) + else: + mi.cover = self.cover(id, index_is_id=True, as_path=True) return mi def has_book(self, mi): @@ -1099,7 +1123,18 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return utcfromtimestamp(os.stat(path).st_mtime) def format_abspath(self, index, format, index_is_id=False): - 'Return absolute path to the ebook file of format `format`' + ''' + Return absolute path to the ebook file of format `format` + + WARNING: This method will return a dummy path for a network backend DB, + so do not rely on it, use format(..., as_path=True) instead. + + Currently used only in calibredb list, the viewer and the catalogs (via + get_data_as_dict()). + + Apart from the viewer, I don't believe any of the others do any file + I/O with the results of this call. + ''' id = index if index_is_id else self.id(index) try: name = self.conn.get('SELECT name FROM data WHERE book=? AND format=?', (id, format), all=False) @@ -1119,25 +1154,63 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): shutil.copyfile(candidates[0], fmt_path) return fmt_path - def format(self, index, format, index_is_id=False, as_file=False, mode='r+b'): + def copy_format_to(self, index, fmt, dest, index_is_id=False): + ''' + Copy the format ``fmt`` to the file like object ``dest``. If the + specified format does not exist, raises :class:`NoSuchFormat` error. + ''' + path = self.format_abspath(index, fmt, index_is_id=index_is_id) + if path is None: + id_ = index if index_is_id else self.id(index) + raise NoSuchFormat('Record %d has no %s file'%(id_, fmt)) + with lopen(path, 'rb') as f: + shutil.copyfileobj(f, dest) + if hasattr(dest, 'flush'): + dest.flush() + + def format(self, index, format, index_is_id=False, as_file=False, + mode='r+b', as_path=False, preserve_filename=False): ''' Return the ebook format as a bytestring or `None` if the format doesn't exist, or we don't have permission to write to the ebook file. - `as_file`: If True the ebook format is returned as a file object opened in `mode` + :param as_file: If True the ebook format is returned as a file object. Note + that the file object is a SpooledTemporaryFile, so if what you want to + do is copy the format to another file, use :method:`copy_format_to` + instead for performance. + :param as_path: Copies the format file to a temp file and returns the + path to the temp file + :param preserve_filename: If True and returning a path the filename is + the same as that used in the library. Note that using + this means that repeated calls yield the same + temp file (which is re-created each time) + :param mode: This is ignored (present for legacy compatibility) ''' path = self.format_abspath(index, format, index_is_id=index_is_id) if path is not None: - f = lopen(path, mode) - try: - ret = f if as_file else f.read() - except IOError: - f.seek(0) - out = cStringIO.StringIO() - shutil.copyfileobj(f, out) - ret = out.getvalue() - if not as_file: - f.close() + with lopen(path, mode) as f: + if as_path: + if preserve_filename: + bd = base_dir() + d = os.path.join(bd, 'format_abspath') + try: + os.makedirs(d) + except: + pass + fname = os.path.basename(path) + ret = os.path.join(d, fname) + with lopen(ret, 'wb') as f2: + shutil.copyfileobj(f, f2) + else: + with PersistentTemporaryFile('.'+format.lower()) as pt: + shutil.copyfileobj(f, pt) + ret = pt.name + elif as_file: + ret = tempfile.SpooledTemporaryFile(max_size=SPOOL_SIZE) + shutil.copyfileobj(f, ret) + ret.seek(0) + else: + ret = f.read() return ret def add_format_with_hooks(self, index, format, fpath, index_is_id=False, diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index 5f49833564..b5c4e2faf3 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -6,7 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, traceback, cStringIO, re, shutil +import os, traceback, cStringIO, re from calibre.constants import DEBUG from calibre.utils.config import Config, StringConfig, tweaks @@ -238,7 +238,7 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250, def save_book_to_disk(id_, db, root, opts, length): mi = db.get_metadata(id_, index_is_id=True) - cover = db.cover(id_, index_is_id=True, as_path=True) + cover = db.cover(id_, index_is_id=True) plugboards = db.prefs.get('plugboards', {}) available_formats = db.formats(id_, index_is_id=True) @@ -252,12 +252,20 @@ def save_book_to_disk(id_, db, root, opts, length): if fmts: fmts = fmts.split(',') for fmt in fmts: - fpath = db.format_abspath(id_, fmt, index_is_id=True) + fpath = db.format(id_, fmt, index_is_id=True, as_path=True) if fpath is not None: formats[fmt.lower()] = fpath - return do_save_book_to_disk(id_, mi, cover, plugboards, + try: + return do_save_book_to_disk(id_, mi, cover, plugboards, formats, root, opts, length) + finally: + for temp in formats.itervalues(): + try: + os.remove(temp) + except: + pass + def do_save_book_to_disk(id_, mi, cover, plugboards, @@ -289,10 +297,9 @@ def do_save_book_to_disk(id_, mi, cover, plugboards, raise ocover = mi.cover - if opts.save_cover and cover and os.access(cover, os.R_OK): + if opts.save_cover and cover: with open(base_path+'.jpg', 'wb') as f: - with open(cover, 'rb') as s: - shutil.copyfileobj(s, f) + f.write(cover) mi.cover = base_name+'.jpg' else: mi.cover = None @@ -395,8 +402,13 @@ def save_serialized_to_disk(ids, data, plugboards, root, opts, callback): pass tb = '' try: - failed, id, title = do_save_book_to_disk(x, mi, cover, plugboards, - format_map, root, opts, length) + with open(cover, 'rb') as f: + cover = f.read() + except: + cover = None + try: + failed, id, title = do_save_book_to_disk(x, mi, cover, + plugboards, format_map, root, opts, length) tb = _('Requested formats not available') except: failed, id, title = True, x, mi.title diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py index 08de4faecd..853ec7829c 100644 --- a/src/calibre/library/server/content.py +++ b/src/calibre/library/server/content.py @@ -10,12 +10,13 @@ import re, os, posixpath import cherrypy from calibre import fit_image, guess_type -from calibre.utils.date import fromtimestamp +from calibre.utils.date import fromtimestamp, utcnow from calibre.library.caches import SortKeyGenerator from calibre.library.save_to_disk import find_plugboard - -from calibre.utils.magick.draw import save_cover_data_to, Image, \ - thumbnail as generate_thumbnail +from calibre.ebooks.metadata import authors_to_string +from calibre.utils.magick.draw import (save_cover_data_to, Image, + thumbnail as generate_thumbnail) +from calibre.utils.filenames import ascii_filename plugboard_content_server_value = 'content_server' plugboard_content_server_formats = ['epub'] @@ -46,7 +47,7 @@ class ContentServer(object): # Utility methods {{{ def last_modified(self, updated): ''' - Generates a local independent, english timestamp from a datetime + Generates a locale independent, english timestamp from a datetime object ''' lm = updated.strftime('day, %d month %Y %H:%M:%S GMT') @@ -151,14 +152,12 @@ class ContentServer(object): try: cherrypy.response.headers['Content-Type'] = 'image/jpeg' cherrypy.response.timeout = 3600 - cover = self.db.cover(id, index_is_id=True, as_file=True) + cover = self.db.cover(id, index_is_id=True) if cover is None: cover = self.default_cover updated = self.build_time else: - with cover as f: - updated = fromtimestamp(os.fstat(f.fileno()).st_mtime) - cover = f.read() + updated = self.db.cover_last_modified(id, index_is_id=True) cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) if thumbnail: @@ -187,9 +186,9 @@ class ContentServer(object): mode='rb') if fmt is None: raise cherrypy.HTTPError(404, 'book: %d does not have format: %s'%(id, format)) + mi = self.db.get_metadata(id, index_is_id=True) if format == 'EPUB': # Get the original metadata - mi = self.db.get_metadata(id, index_is_id=True) # Get any EPUB plugboards for the content server plugboards = self.db.prefs.get('plugboards', {}) @@ -203,24 +202,22 @@ class ContentServer(object): newmi = mi # Write the updated file - from tempfile import TemporaryFile from calibre.ebooks.metadata.meta import set_metadata - raw = fmt.read() - fmt = TemporaryFile() - fmt.write(raw) - fmt.seek(0) set_metadata(fmt, newmi, 'epub') fmt.seek(0) mt = guess_type('dummy.'+format.lower())[0] if mt is None: mt = 'application/octet-stream' + au = authors_to_string(mi.authors if mi.authors else [_('Unknown')]) + title = mi.title if mi.title else _('Unknown') + fname = u'%s - %s_%s.%s'%(title[:30], au[:30], id, format.lower()) + fname = ascii_filename(fname).replace('"', '_') cherrypy.response.headers['Content-Type'] = mt + cherrypy.response.headers['Content-Disposition'] = \ + b'attachment; filename="%s"'%fname cherrypy.response.timeout = 3600 - path = getattr(fmt, 'name', None) - if path and os.path.exists(path): - updated = fromtimestamp(os.stat(path).st_mtime) - cherrypy.response.headers['Last-Modified'] = self.last_modified(updated) + cherrypy.response.headers['Last-Modified'] = self.last_modified(utcnow()) return fmt # }}} From f3211275f253f20b40ce927788be74ac4873ab6d Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Wed, 22 Jun 2011 00:16:28 -0300 Subject: [PATCH 16/31] Initial support to manage SHORTLIST for Ktouch - May rename from Favourite --- src/calibre/devices/kobo/driver.py | 48 ++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 650e965941..481b9f4e39 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -557,6 +557,7 @@ class KOBO(USBMS): if collections: # Process any collections that exist for category, books in collections.items(): + debug_print (category) if category == 'Im_Reading': # Reset Im_Reading list in the database if oncard == 'carda': @@ -575,7 +576,8 @@ class KOBO(USBMS): for book in books: # debug_print('Title:', book.title, 'lpath:', book.path) - book.device_collections = ['Im_Reading'] + if 'Im_Reading' not in book.device_collections: + book.device_collections.append('Im_Reading') extension = os.path.splitext(book.path)[1] ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path) @@ -618,7 +620,8 @@ class KOBO(USBMS): for book in books: # debug_print('Title:', book.title, 'lpath:', book.path) - book.device_collections = ['Read'] + if 'Read' not in book.device_collections: + book.device_collections.append('Read') extension = os.path.splitext(book.path)[1] ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path) @@ -654,7 +657,8 @@ class KOBO(USBMS): for book in books: # debug_print('Title:', book.title, 'lpath:', book.path) - book.device_collections = ['Closed'] + if 'Closed' not in book.device_collections: + book.device_collections.append('Closed') extension = os.path.splitext(book.path)[1] ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path) @@ -672,6 +676,44 @@ class KOBO(USBMS): else: connection.commit() # debug_print('Database: Commit set ReadStatus as Closed') + if category == 'Favourite': + # Reset FavouritesIndex list in the database + if oncard == 'carda': + query= 'update content set FavouritesIndex=-1 where BookID is Null and ContentID like \'file:///mnt/sd/%\'' + elif oncard != 'carda' and oncard != 'cardb': + query= 'update content set FavouritesIndex=-1 where BookID is Null and ContentID not like \'file:///mnt/sd/%\'' + + try: + cursor.execute (query) + except: + debug_print('Database Exception: Unable to reset Favourites list') + raise + else: +# debug_print('Commit: Reset Favourites list') + connection.commit() + + for book in books: +# debug_print('Title:', book.title, 'lpath:', book.path) + if 'Favourite' not in book.device_collections: + book.device_collections.append('Favourite') + debug_print ("Favourite found for: ", book.title) + extension = os.path.splitext(book.path)[1] + ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path) + + ContentID = self.contentid_from_path(book.path, ContentType) +# datelastread = time.strftime("%Y-%m-%dT%H:%M:%S", time.gmtime()) + + t = (ContentID,) + + try: + cursor.execute('update content set FavouritesIndex=1 where BookID is Null and ContentID = ?', t) + except: + debug_print('Database Exception: Unable set book as Favourite') + raise + else: + connection.commit() +# debug_print('Database: Commit set FavouritesIndex as Favourite') + else: # No collections # Since no collections exist the ReadStatus needs to be reset to 0 (Unread) print "Reseting ReadStatus to 0" From 52edcbe991aa8bb763b2815d39f678fbd6f069b5 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Wed, 22 Jun 2011 10:48:31 +0100 Subject: [PATCH 17/31] Small fixes to Quickview --- src/calibre/gui2/actions/show_quickview.py | 3 +- src/calibre/gui2/dialogs/quickview.py | 150 +++++++++++++++------ 2 files changed, 109 insertions(+), 44 deletions(-) diff --git a/src/calibre/gui2/actions/show_quickview.py b/src/calibre/gui2/actions/show_quickview.py index 61a41b08ae..78352e6da8 100644 --- a/src/calibre/gui2/actions/show_quickview.py +++ b/src/calibre/gui2/actions/show_quickview.py @@ -13,8 +13,7 @@ from calibre.gui2 import error_dialog class ShowQuickviewAction(InterfaceAction): name = 'Show quickview' - action_spec = (_('Show quickview'), 'user_profile.png', None, - _('Q')) + action_spec = (_('Show quickview'), 'search.png', None, _('Q')) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device']) action_type = 'current' diff --git a/src/calibre/gui2/dialogs/quickview.py b/src/calibre/gui2/dialogs/quickview.py index 5dcdbf04fa..aff37ea152 100644 --- a/src/calibre/gui2/dialogs/quickview.py +++ b/src/calibre/gui2/dialogs/quickview.py @@ -5,23 +5,27 @@ __docformat__ = 'restructuredtext en' from PyQt4.Qt import (Qt, QDialog, QAbstractItemView, QTableWidgetItem, - QListWidgetItem, QByteArray, QModelIndex) + QListWidgetItem, QByteArray, QModelIndex, QCoreApplication) from calibre.gui2.dialogs.quickview_ui import Ui_Quickview from calibre.utils.icu import sort_key from calibre.gui2 import gprefs -class tableItem(QTableWidgetItem): +class TableItem(QTableWidgetItem): + ''' + A QTableWidgetItem that sorts on a separate string and uses ICU rules + ''' - def __init__(self, val): + def __init__(self, val, sort): + self.sort = sort QTableWidgetItem.__init__(self, val) self.setFlags(Qt.ItemIsEnabled|Qt.ItemIsSelectable) def __ge__(self, other): - return sort_key(unicode(self.text())) >= sort_key(unicode(other.text())) + return sort_key(self.sort) >= sort_key(other.sort) def __lt__(self, other): - return sort_key(unicode(self.text())) < sort_key(unicode(other.text())) + return sort_key(self.sort) < sort_key(other.sort) class Quickview(QDialog, Ui_Quickview): @@ -31,12 +35,16 @@ class Quickview(QDialog, Ui_Quickview): self.setupUi(self) self.isClosed = False + self.books_table_column_widths = None try: + self.books_table_column_widths = \ + gprefs.get('quickview_dialog_books_table_widths', None) geom = gprefs.get('quickview_dialog_geometry', bytearray('')) self.restoreGeometry(QByteArray(geom)) except: pass + # Remove the help button from the window title bar icon = self.windowIcon() self.setWindowFlags(self.windowFlags()&(~Qt.WindowContextHelpButtonHint)) self.setWindowIcon(icon) @@ -44,11 +52,16 @@ class Quickview(QDialog, Ui_Quickview): self.db = view.model().db self.view = view self.gui = gui + self.is_closed = False + self.current_book_id = None + self.current_key = None + self.use_current_key_for_next_refresh = False + self.last_search = None self.items.setSelectionMode(QAbstractItemView.SingleSelection) self.items.currentTextChanged.connect(self.item_selected) -# self.items.setFixedWidth(150) + # Set up the books table columns self.books_table.setSelectionBehavior(QAbstractItemView.SelectRows) self.books_table.setSelectionMode(QAbstractItemView.SingleSelection) self.books_table.setColumnCount(3) @@ -60,67 +73,81 @@ class Quickview(QDialog, Ui_Quickview): self.books_table.setHorizontalHeaderItem(2, t) self.books_table_header_height = self.books_table.height() self.books_table.cellDoubleClicked.connect(self.book_doubleclicked) + self.books_table.sortByColumn(0, Qt.AscendingOrder) - self.is_closed = False - self.current_book_id = None - self.current_key = None - self.use_current_key_for_next_refresh = False - self.last_search = None + # get the standard table row height. Do this here because calling + # resizeRowsToContents can word wrap long cell contents, creating + # double-high rows + self.books_table.setRowCount(1) + self.books_table.setItem(0, 0, TableItem('A', '')) + self.books_table.resizeRowsToContents() + self.books_table_row_height = self.books_table.rowHeight(0) + self.books_table.setRowCount(0) + # Add the data self.refresh(row) -# self.connect(self.view.selectionModel(), SIGNAL('currentChanged(QModelIndex,QModelIndex)'), self.slave) + self.view.selectionModel().currentChanged[QModelIndex,QModelIndex].connect(self.slave) + QCoreApplication.instance().aboutToQuit.connect(self.save_state) self.search_button.clicked.connect(self.do_search) + # search button def do_search(self): if self.last_search is not None: self.use_current_key_for_next_refresh = True self.gui.search.set_search_string(self.last_search) + # clicks on the items listWidget def item_selected(self, txt): self.fill_in_books_box(unicode(txt)) + # Given a cell in the library view, display the information def refresh(self, idx): bv_row = idx.row() key = self.view.model().column_map[idx.column()] book_id = self.view.model().id(bv_row) + # Double-clicking on a book to show it in the library view will result + # in a signal emitted for column 1 of the book row. Use the original + # column for this signal. if self.use_current_key_for_next_refresh: key = self.current_key self.use_current_key_for_next_refresh = False else: + # Only show items for categories if not self.db.field_metadata[key]['is_category']: if self.current_key is None: return key = self.current_key self.items_label.setText('{0} ({1})'.format( self.db.field_metadata[key]['name'], key)) + + self.items.blockSignals(True) self.items.clear() self.books_table.setRowCount(0) mi = self.db.get_metadata(book_id, index_is_id=True, get_user_categories=False) vals = mi.get(key, None) - if not vals: - return - if not isinstance(vals, list): - vals = [vals] - vals.sort(key=sort_key) + if vals: + if not isinstance(vals, list): + vals = [vals] + vals.sort(key=sort_key) - self.items.blockSignals(True) - for v in vals: - a = QListWidgetItem(v) - self.items.addItem(a) - self.items.setCurrentRow(0) + for v in vals: + a = QListWidgetItem(v) + self.items.addItem(a) + self.items.setCurrentRow(0) + + self.current_book_id = book_id + self.current_key = key + + self.fill_in_books_box(vals[0]) self.items.blockSignals(False) - self.current_book_id = book_id - self.current_key = key - - self.fill_in_books_box(vals[0]) - def fill_in_books_box(self, selected_item): + # Do a bit of fix-up on the items so that the search works. if selected_item.startswith('.'): sv = '.' + selected_item else: @@ -129,43 +156,82 @@ class Quickview(QDialog, Ui_Quickview): self.last_search = self.current_key+':"=' + sv + '"' books = self.db.search_getting_ids(self.last_search, self.db.data.search_restriction) + self.books_table.setRowCount(len(books)) self.books_label.setText(_('Books with selected item: {0}').format(len(books))) - select_row = None + select_item = None self.books_table.setSortingEnabled(False) for row, b in enumerate(books): mi = self.db.get_metadata(b, index_is_id=True, get_user_categories=False) - a = tableItem(mi.title) + a = TableItem(mi.title, mi.title_sort) a.setData(Qt.UserRole, b) self.books_table.setItem(row, 0, a) - a = tableItem(' & '.join(mi.authors)) + if b == self.current_book_id: + select_item = a + a = TableItem(' & '.join(mi.authors), mi.author_sort) self.books_table.setItem(row, 1, a) series = mi.format_field('series')[1] if series is None: series = '' - a = tableItem(series) + a = TableItem(series, series) self.books_table.setItem(row, 2, a) - if b == self.current_book_id: - select_row = row + self.books_table.setRowHeight(row, self.books_table_row_height) - self.books_table.resizeColumnsToContents() -# self.books_table.resizeRowsToContents() - - if select_row is not None: - self.books_table.selectRow(select_row) self.books_table.setSortingEnabled(True) + if select_item is not None: + self.books_table.setCurrentItem(select_item) + self.books_table.scrollToItem(select_item, QAbstractItemView.PositionAtCenter) + + # Deal with sizing the table columns. Done here because the numbers are not + # correct until the first paint. + def resizeEvent(self, *args): + QDialog.resizeEvent(self, *args) + if self.books_table_column_widths is not None: + for c,w in enumerate(self.books_table_column_widths): + self.books_table.setColumnWidth(c, w) + else: + # the vertical scroll bar might not be rendered, so might not yet + # have a width. Assume 25. Not a problem because user-changed column + # widths will be remembered + w = self.books_table.width() - 25 - self.books_table.verticalHeader().width() + w /= self.books_table.columnCount() + for c in range(0, self.books_table.columnCount()): + self.books_table.setColumnWidth(c, w) + self.save_state() def book_doubleclicked(self, row, column): self.use_current_key_for_next_refresh = True self.view.select_rows([self.books_table.item(row, 0).data(Qt.UserRole).toInt()[0]]) + # called when a book is clicked on the library view def slave(self, current, previous): + if self.is_closed: + return self.refresh(current) self.view.activateWindow() - def done(self, r): - geom = bytearray(self.saveGeometry()) - gprefs['quickview_dialog_geometry'] = geom + def save_state(self): + if self.is_closed: + return + self.books_table_column_widths = [] + for c in range(0, self.books_table.columnCount()): + self.books_table_column_widths.append(self.books_table.columnWidth(c)) + gprefs['quickview_dialog_books_table_widths'] = self.books_table_column_widths + gprefs['quickview_dialog_geometry'] = bytearray(self.saveGeometry()) + + def close(self): + self.save_state() + # clean up to prevent memory leaks + self.db = self.view = self.gui = None self.is_closed = True - QDialog.done(self, r) + + # called by the window system + def closeEvent(self, *args): + self.close() + QDialog.closeEvent(self, *args) + + # called by the close button + def reject(self): + self.close() + QDialog.reject(self) \ No newline at end of file From 45cf24206d986119ed529e5cadc524682ee3a470 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Jun 2011 12:58:49 -0600 Subject: [PATCH 18/31] API for kiwidude --- src/calibre/library/database2.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 4c61438e35..6848e3c6ae 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' The database used to store ebook metadata ''' import os, sys, shutil, cStringIO, glob, time, functools, traceback, re, \ - json, uuid, tempfile + json, uuid, tempfile, hashlib import threading, random from itertools import repeat from math import ceil @@ -1118,9 +1118,26 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return self.format_abspath(index, format, index_is_id) is not None def format_last_modified(self, id_, fmt): + m = self.format_metadata(id_, fmt) + if m: + return m['mtime'] + + def format_metadata(self, id_, fmt): path = self.format_abspath(id_, fmt, index_is_id=True) + ans = {} if path is not None: - return utcfromtimestamp(os.stat(path).st_mtime) + stat = os.stat(path) + ans['size'] = stat.st_size + ans['mtime'] = utcfromtimestamp(stat.st_mtime) + return ans + + def format_hash(self, id_, fmt): + data = self.format(id_, fmt, index_is_id=True) + if data is None: + raise NoSuchFormat('Record %d has no fmt: %s'%(id_, fmt)) + sha = hashlib.sha256() + sha.update(data) + return sha.hexdigest() def format_abspath(self, index, format, index_is_id=False): ''' From 8da8075ee29118ed99ca9c0aa16415ff12819a1a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Jun 2011 14:20:05 -0600 Subject: [PATCH 19/31] Show the currently used network proxies in Preferences->Miscellaneous --- src/calibre/gui2/preferences/misc.py | 8 ++++++++ src/calibre/gui2/preferences/misc.ui | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/preferences/misc.py b/src/calibre/gui2/preferences/misc.py index 80bfdffcd8..60a4540b99 100644 --- a/src/calibre/gui2/preferences/misc.py +++ b/src/calibre/gui2/preferences/misc.py @@ -10,6 +10,7 @@ from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting from calibre.gui2.preferences.misc_ui import Ui_Form from calibre.gui2 import error_dialog, config, open_local_file, info_dialog from calibre.constants import isosx +from calibre import get_proxies class WorkersSetting(Setting): @@ -33,6 +34,13 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): self.user_defined_device_button.clicked.connect(self.user_defined_device) self.button_osx_symlinks.clicked.connect(self.create_symlinks) self.button_osx_symlinks.setVisible(isosx) + proxies = get_proxies(debug=False) + txt = _('No proxies used') + if proxies: + lines = ['
%s: %s'%(t, p) for t, p in + proxies.iteritems()] + txt = _('Using proxies:') + ''.join(lines) + self.proxies.setText(txt) def debug_device_detection(self, *args): from calibre.gui2.preferences.device_debug import DebugDevice diff --git a/src/calibre/gui2/preferences/misc.ui b/src/calibre/gui2/preferences/misc.ui index 843f0f01b7..dd9b984d48 100644 --- a/src/calibre/gui2/preferences/misc.ui +++ b/src/calibre/gui2/preferences/misc.ui @@ -118,7 +118,7 @@ - + Qt::Vertical @@ -131,6 +131,13 @@ + + + + + + + From a94b78cfedc84e8cc84d5bc0ac2ee2e6096c0852 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Jun 2011 16:11:53 -0600 Subject: [PATCH 20/31] format_hash() should not consume unbounded memory --- src/calibre/library/database2.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 6848e3c6ae..c9ed5c250a 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1132,11 +1132,16 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return ans def format_hash(self, id_, fmt): - data = self.format(id_, fmt, index_is_id=True) - if data is None: + path = self.format_abspath(id_, fmt, index_is_id=True) + if path is None: raise NoSuchFormat('Record %d has no fmt: %s'%(id_, fmt)) sha = hashlib.sha256() - sha.update(data) + with lopen(path, 'rb') as f: + while True: + raw = f.read(SPOOL_SIZE) + sha.update(raw) + if len(raw) < SPOOL_SIZE: + break return sha.hexdigest() def format_abspath(self, index, format, index_is_id=False): From 62d74dc011f4f5ee3d1bc094b48b45e9291cdcd3 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Wed, 22 Jun 2011 21:03:42 -0300 Subject: [PATCH 21/31] Change Favourite to Shortlist --- src/calibre/devices/kobo/driver.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 481b9f4e39..e3019b8ced 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -125,9 +125,9 @@ class KOBO(USBMS): # this shows an expired Collection so the user can decide to delete the book if expired == 3: playlist_map[lpath].append('Expired') - # Favourites are supported on the touch but the data field is there on most earlier models + # A SHORTLIST is supported on the touch but the data field is there on most earlier models if favouritesindex == 1: - playlist_map[lpath].append('Favourite') + playlist_map[lpath].append('Shortlist') path = self.normalize_path(path) # print "Normalized FileName: " + path @@ -557,7 +557,7 @@ class KOBO(USBMS): if collections: # Process any collections that exist for category, books in collections.items(): - debug_print (category) + # debug_print (category) if category == 'Im_Reading': # Reset Im_Reading list in the database if oncard == 'carda': @@ -676,7 +676,7 @@ class KOBO(USBMS): else: connection.commit() # debug_print('Database: Commit set ReadStatus as Closed') - if category == 'Favourite': + if category == 'Shortlist': # Reset FavouritesIndex list in the database if oncard == 'carda': query= 'update content set FavouritesIndex=-1 where BookID is Null and ContentID like \'file:///mnt/sd/%\'' @@ -686,17 +686,17 @@ class KOBO(USBMS): try: cursor.execute (query) except: - debug_print('Database Exception: Unable to reset Favourites list') + debug_print('Database Exception: Unable to reset Shortlist list') raise else: -# debug_print('Commit: Reset Favourites list') +# debug_print('Commit: Reset Shortlist list') connection.commit() for book in books: # debug_print('Title:', book.title, 'lpath:', book.path) - if 'Favourite' not in book.device_collections: - book.device_collections.append('Favourite') - debug_print ("Favourite found for: ", book.title) + if 'Shortlist' not in book.device_collections: + book.device_collections.append('Shortlist') + # debug_print ("Shortlist found for: ", book.title) extension = os.path.splitext(book.path)[1] ContentType = self.get_content_type_from_extension(extension) if extension != '' else self.get_content_type_from_path(book.path) @@ -708,11 +708,11 @@ class KOBO(USBMS): try: cursor.execute('update content set FavouritesIndex=1 where BookID is Null and ContentID = ?', t) except: - debug_print('Database Exception: Unable set book as Favourite') + debug_print('Database Exception: Unable set book as Shortlist') raise else: connection.commit() -# debug_print('Database: Commit set FavouritesIndex as Favourite') +# debug_print('Database: Commit set Shortlist as Shortlist') else: # No collections # Since no collections exist the ReadStatus needs to be reset to 0 (Unread) From 7a867a4367c1e133136250b046283e979ecb8d53 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Jun 2011 18:57:21 -0600 Subject: [PATCH 22/31] Fix #799885 (Impossible to translate with an empty string) --- src/calibre/customize/conversion.py | 2 +- src/calibre/gui2/preferences/coloring.py | 14 +- src/calibre/translations/calibre.pot | 1235 ++++++++++++---------- 3 files changed, 710 insertions(+), 541 deletions(-) diff --git a/src/calibre/customize/conversion.py b/src/calibre/customize/conversion.py index 88f19df7af..d035ad9666 100644 --- a/src/calibre/customize/conversion.py +++ b/src/calibre/customize/conversion.py @@ -261,7 +261,7 @@ class OutputFormatPlugin(Plugin): @property def description(self): - return _('Convert ebooks to the %s format'%self.file_type) + return _('Convert ebooks to the %s format')%self.file_type def __init__(self, *args): Plugin.__init__(self, *args) diff --git a/src/calibre/gui2/preferences/coloring.py b/src/calibre/gui2/preferences/coloring.py index 3b581be701..ad636bd600 100644 --- a/src/calibre/gui2/preferences/coloring.py +++ b/src/calibre/gui2/preferences/coloring.py @@ -72,19 +72,27 @@ class ConditionEditor(QWidget): # {{{ self.l = l = QGridLayout(self) self.setLayout(l) - self.l1 = l1 = QLabel(_('If the ')) + texts = _('If the ___ column ___ values') + try: + one, two, three = texts.split('___') + except: + one, two, three = 'If the ', ' column ', ' value ' + + self.l1 = l1 = QLabel(one) l.addWidget(l1, 0, 0) self.column_box = QComboBox(self) l.addWidget(self.column_box, 0, 1) - self.l2 = l2 = QLabel(_(' column ')) + + + self.l2 = l2 = QLabel(two) l.addWidget(l2, 0, 2) self.action_box = QComboBox(self) l.addWidget(self.action_box, 0, 3) - self.l3 = l3 = QLabel(_(' value ')) + self.l3 = l3 = QLabel(three) l.addWidget(l3, 0, 4) self.value_box = QLineEdit(self) diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot index bb43ff39be..c371179704 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.6\n" -"POT-Creation-Date: 2011-06-17 11:28+MDT\n" -"PO-Revision-Date: 2011-06-17 11:28+MDT\n" +"POT-Creation-Date: 2011-06-22 18:55+MDT\n" +"PO-Revision-Date: 2011-06-22 18:55+MDT\n" "Last-Translator: Automatically generated\n" "Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" @@ -25,7 +25,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/jetbook/driver.py:74 #: /home/kovid/work/calibre/src/calibre/devices/kindle/driver.py:77 #: /home/kovid/work/calibre/src/calibre/devices/kobo/books.py:24 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:488 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:518 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:70 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:71 #: /home/kovid/work/calibre/src/calibre/devices/prs500/books.py:267 @@ -93,7 +93,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/odt/input.py:101 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1001 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1006 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1072 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1078 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:144 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/reader.py:151 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:65 @@ -126,8 +126,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:362 #: /home/kovid/work/calibre/src/calibre/gui2/__init__.py:370 #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:156 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:364 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:367 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:379 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:160 #: /home/kovid/work/calibre/src/calibre/gui2/add.py:167 #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:519 @@ -135,21 +135,21 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:122 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:151 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:153 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1131 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1134 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1168 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1171 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/add_empty_book.py:68 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/book_info.py:127 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf.py:47 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:366 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:184 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:199 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:408 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1023 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1203 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1206 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1209 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1294 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:185 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:403 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1010 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1190 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1193 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1196 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1281 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:82 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:212 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:231 @@ -162,14 +162,16 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:200 #: /home/kovid/work/calibre/src/calibre/library/cli.py:217 #: /home/kovid/work/calibre/src/calibre/library/database.py:914 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:534 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:542 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:553 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1839 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1976 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2983 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2985 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:3118 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:532 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:540 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:551 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1932 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2078 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3085 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3087 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3220 +#: /home/kovid/work/calibre/src/calibre/library/server/content.py:212 +#: /home/kovid/work/calibre/src/calibre/library/server/content.py:213 #: /home/kovid/work/calibre/src/calibre/library/server/mobile.py:233 #: /home/kovid/work/calibre/src/calibre/library/server/opds.py:156 #: /home/kovid/work/calibre/src/calibre/library/server/opds.py:159 @@ -303,191 +305,296 @@ msgstr "" msgid "Set metadata from %s files" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:892 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:765 +msgid "Add books to calibre or the connected device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:770 +msgid "Fetch annotations from a connected Kindle (experimental)" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:775 +msgid "Generate a catalog of the books in your calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:780 +msgid "Convert books to various ebook formats" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:785 +msgid "Delete books from your calibre library or connected device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:790 +msgid "Edit the metadata of books in your calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:795 +msgid "Read books in your calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:800 +msgid "Download news from the internet in ebook form" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:805 +msgid "Show a list of related books quickly" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:810 +msgid "Export books from your calibre library to the hard disk" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:815 +msgid "Show book details in a separate popup" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:820 +msgid "Restart calibre" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:825 +msgid "Open the folder that contains the book files in your calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:831 +msgid "Send books to the connected device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:836 +msgid "Send books via email or the web also connect to iTunes or folders on your computer as if they are devices" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:842 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/help.py:16 +msgid "Browse the calibre User Manual" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:847 +msgid "Customize calibre" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:852 +msgid "Easily find books similar to the currently selected one" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:857 +msgid "Switch between different calibre libraries and perform maintenance on them" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:863 +msgid "Copy books from the devce to your calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:868 +msgid "Edit the collections in which books are placed on your device" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:873 +msgid "Copy a book from one calibre library to another" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:878 +msgid "Make small twekas to epub files in your calibre library" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:883 +msgid "Find the next or previous match when searching in your calibre library in highlight mode" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:890 +msgid "Search for books from different book sellers" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:906 +msgid "Get new calibre plugins or update your existing ones" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:925 msgid "Look and Feel" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:894 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:906 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:917 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:928 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:940 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:927 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:939 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:950 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:961 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:973 msgid "Interface" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:898 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:931 msgid "Adjust the look and feel of the calibre interface to suit your tastes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:904 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:937 msgid "Behavior" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:910 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:943 msgid "Change the way calibre behaves" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:915 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:948 #: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:225 msgid "Add your own columns" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:921 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:954 msgid "Add/remove your own columns to the calibre book list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:926 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:959 msgid "Toolbar" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:932 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:965 msgid "Customize the toolbars and context menus, changing which actions are available in each" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:938 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:971 msgid "Searching" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:944 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:977 msgid "Customize the way searching for books works in calibre" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:949 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:982 msgid "Input Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:951 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:962 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:973 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:984 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:995 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1006 msgid "Conversion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:955 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:988 msgid "Set conversion options specific to each input format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:960 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:993 msgid "Common Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:966 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:999 msgid "Set conversion options common to all formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:971 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1004 msgid "Output Options" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:977 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1010 msgid "Set conversion options specific to each output format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:982 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1015 msgid "Adding books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:984 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:996 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1008 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1020 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1017 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1029 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1041 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1053 msgid "Import/Export" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:988 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1021 msgid "Control how calibre reads metadata from files when adding books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:994 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1027 msgid "Saving books to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1000 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1033 msgid "Control how calibre exports files from its database to disk when using Save to disk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1006 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1039 msgid "Sending books to devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1012 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1045 msgid "Control how calibre transfers files to your ebook reader" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1018 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1051 msgid "Metadata plugboards" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1024 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1057 msgid "Change metadata fields before saving/sending" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1029 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1062 msgid "Template Functions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1031 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1078 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1090 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1101 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1064 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1111 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1123 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1134 msgid "Advanced" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1035 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1068 msgid "Create your own template functions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1040 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1073 msgid "Sharing books by email" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1042 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1054 -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1067 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1075 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1087 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1100 msgid "Sharing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1046 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1079 msgid "Setup sharing of books via email. Can be used for automatic sending of downloaded news to your devices" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1052 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1085 msgid "Sharing over the net" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1058 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1091 msgid "Setup the calibre Content Server which will give you access to your calibre library from anywhere, on any device, over the internet" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1065 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1098 msgid "Metadata download" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1071 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1104 msgid "Control how calibre downloads ebook metadata from the net" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1076 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1109 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:275 msgid "Plugins" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1082 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1115 msgid "Add/remove/customize various bits of calibre functionality" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1088 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1121 msgid "Tweaks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1094 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1127 msgid "Fine tune how calibre behaves in various contexts" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1099 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1132 msgid "Miscellaneous" msgstr "" -#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1105 +#: /home/kovid/work/calibre/src/calibre/customize/builtins.py:1138 msgid "Miscellaneous advanced configuration" msgstr "" @@ -507,6 +614,10 @@ msgstr "" msgid "If specified, the output plugin will try to create output that is as human readable as possible. May not have any effect for some output plugins." msgstr "" +#: /home/kovid/work/calibre/src/calibre/customize/conversion.py:264 +msgid "Convert ebooks to the %s format" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/customize/profiles.py:49 msgid "Input profile" msgstr "" @@ -696,6 +807,16 @@ msgstr "" msgid "Disable the named plugin" msgstr "" +#: /home/kovid/work/calibre/src/calibre/db/backend.py:195 +#: /home/kovid/work/calibre/src/calibre/db/backend.py:204 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:236 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:662 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:127 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:136 +msgid "Path to library too long. Must be less than %d characters." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/debug.py:154 msgid "Debug log" msgstr "" @@ -724,73 +845,73 @@ msgstr "" msgid "Enable Apple driver" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:117 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:118 msgid "Use Series as Category in iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:118 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:119 msgid "Enable to use the series name as the iTunes Genre, iBooks Category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:120 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:121 msgid "Cache covers from iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:122 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:123 msgid "Enable to cache and display covers from iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:179 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:180 msgid "Apple device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:181 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:182 msgid "Communicate with iTunes/iBooks." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:193 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:194 msgid "Apple device detected, launching iTunes, please wait ..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:195 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:196 msgid "Cannot copy books directly from iDevice. Drag from iTunes Library to desktop, then add to calibre's Library window." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:358 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:361 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:359 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:362 msgid "Updating device metadata listing..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:437 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:476 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1058 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1102 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3108 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3148 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:438 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:477 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1059 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1103 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3109 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3149 msgid "%d of %d" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:483 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1107 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3154 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:484 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1108 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3155 #: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:106 msgid "finished" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:668 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:669 msgid "" "Some books not found in iTunes database.\n" "Delete using the iBooks app.\n" "Click 'Show Details' for a list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1019 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1020 msgid "" "Some cover art could not be converted.\n" "Click 'Show Details' for a list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2679 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2680 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:102 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:447 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:470 @@ -799,22 +920,24 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:951 #: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:73 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:445 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:330 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:343 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2847 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1657 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1659 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:328 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:341 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2949 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:170 msgid "News" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2680 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2681 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65 #: /home/kovid/work/calibre/src/calibre/library/catalog.py:652 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2807 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:2825 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2909 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:2927 msgid "Catalog" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3010 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3011 msgid "Communicate with iTunes." msgstr "" @@ -853,7 +976,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:67 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:70 #: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:73 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:232 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:255 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:122 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:125 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:128 @@ -873,8 +996,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:285 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:299 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:349 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:384 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:376 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:411 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:279 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:310 msgid "Adding books to device metadata listing..." @@ -884,8 +1007,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:309 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:114 #: /home/kovid/work/calibre/src/calibre/devices/hanvon/driver.py:125 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:301 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:333 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:328 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:360 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:316 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:334 msgid "Removing books from device..." @@ -893,8 +1016,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:324 #: /home/kovid/work/calibre/src/calibre/devices/bambook/driver.py:329 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:337 -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:344 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:364 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:371 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:341 #: /home/kovid/work/calibre/src/calibre/devices/usbms/driver.py:346 msgid "Removing books from device metadata listing..." @@ -1112,12 +1235,12 @@ msgstr "" msgid "The Kobo supports only one collection currently: the \"Im_Reading\" list. Create a tag called \"Im_Reading\" " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:468 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:498 #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:388 msgid "Not Implemented" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:469 +#: /home/kovid/work/calibre/src/calibre/devices/kobo/driver.py:499 msgid "\".kobo\" files do not exist on the device as books instead, they are rows in the sqlite database. Currently they cannot be exported or viewed." msgstr "" @@ -1950,7 +2073,7 @@ msgstr "" msgid "Fix unmanifested files. epub-fix can either add them to the manifest or delete them as specified by the delete unmanifested option." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/epub/fix/unmanifested.py:32 +#: /home/kovid/work/calibre/src/calibre/ebooks/epub/fix/unmanifested.py:36 msgid "Delete unmanifested files instead of adding them to the manifest" msgstr "" @@ -2328,8 +2451,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:733 #: /home/kovid/work/calibre/src/calibre/ebooks/pdf/manipulate/info.py:45 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:75 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1028 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1015 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:133 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:152 #: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 @@ -2340,14 +2464,14 @@ msgid "Title" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:734 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:67 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1029 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:58 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1016 #: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/models.py:23 msgid "Author(s)" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:735 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:63 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:149 msgid "Publisher" msgstr "" @@ -2367,7 +2491,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:739 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:170 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:64 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:151 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:184 @@ -2377,8 +2501,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:741 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:168 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:72 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:74 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:70 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:153 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:123 @@ -2396,7 +2521,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:746 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:167 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:61 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:133 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:288 msgid "Published" @@ -2497,7 +2622,7 @@ msgid "" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1363 -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1493 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1499 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:891 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:36 msgid "Cover" @@ -2629,70 +2754,70 @@ msgstr "" msgid "This is an Amazon Topaz book. It cannot be processed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1494 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1500 msgid "Title Page" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1495 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1501 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/htmltoc.py:15 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main.py:56 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/main_ui.py:199 msgid "Table of Contents" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1496 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1502 msgid "Index" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1497 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1503 msgid "Glossary" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1498 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1504 msgid "Acknowledgements" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1499 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1505 msgid "Bibliography" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1500 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1506 msgid "Colophon" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1501 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1507 msgid "Copyright" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1502 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1508 msgid "Dedication" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1503 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1509 msgid "Epigraph" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1504 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1510 msgid "Foreword" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1505 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1511 msgid "List of Illustrations" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1506 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1512 msgid "List of Tables" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1507 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1513 msgid "Notes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1508 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1514 msgid "Preface" msgstr "" -#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1509 +#: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1515 msgid "Main Text" msgstr "" @@ -2702,7 +2827,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:805 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:806 msgid "Book %s of %s" msgstr "" @@ -2711,7 +2836,7 @@ msgid "HTML TOC generation options." msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/jacket.py:169 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:62 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:71 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:150 #: /home/kovid/work/calibre/src/calibre/library/server/browse.py:759 @@ -3373,7 +3498,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:83 #: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:102 #: /home/kovid/work/calibre/src/calibre/gui2/actions/store.py:111 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:30 #: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:139 #: /home/kovid/work/calibre/src/calibre/gui2/actions/view.py:185 msgid "No book selected" @@ -3396,7 +3521,7 @@ msgid "Add books to your calibre library from the connected device" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:20 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:553 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:591 msgid "Fetch annotations (experimental)" msgstr "" @@ -3421,11 +3546,11 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/annotate.py:69 #: /home/kovid/work/calibre/src/calibre/gui2/actions/catalog.py:33 #: /home/kovid/work/calibre/src/calibre/gui2/actions/convert.py:87 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:72 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:156 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:220 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:257 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:157 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:221 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:258 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:91 msgid "No books selected" msgstr "" @@ -3567,14 +3692,6 @@ msgstr "" msgid "Too long" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:236 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/choose_library.py:71 -#: /home/kovid/work/calibre/src/calibre/gui2/wizard/__init__.py:662 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:127 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:136 -msgid "Path to library too long. Must be less than %d characters." -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:243 msgid "Rename failed" msgstr "" @@ -3589,8 +3706,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/plugin_updater.py:674 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:78 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/user_profiles.py:368 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:459 -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:465 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:463 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:469 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/columns.py:102 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:281 #: /home/kovid/work/calibre/src/calibre/gui2/wizard/send_email.py:242 @@ -3639,9 +3756,9 @@ msgid "Found no errors in your calibre library database. Do you want calibre to msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:336 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:703 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:965 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:160 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:741 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:966 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:101 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:277 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/tweaks.py:317 @@ -3670,7 +3787,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:419 #: /home/kovid/work/calibre/src/calibre/gui2/actions/choose_library.py:424 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:167 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:177 #: /home/kovid/work/calibre/src/calibre/gui2/actions/save_to_disk.py:100 #: /home/kovid/work/calibre/src/calibre/gui2/library/views.py:862 msgid "Not allowed" @@ -3713,44 +3830,44 @@ msgstr "" msgid "Empty output file, probably the conversion process crashed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:93 msgid "Copy to library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:84 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:94 msgid "Copy selected books to the specified library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:117 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:127 msgid "(delete after copy)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:126 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:136 msgid "Cannot copy" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:131 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:141 msgid "No library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:142 msgid "No library found at %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:135 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:139 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:149 msgid "Copying" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:150 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:160 msgid "Could not copy books: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:153 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:163 msgid "Copied %d books to %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:168 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/copy_to_library.py:178 msgid "You cannot use other libraries while using the environment variable CALIBRE_OVERRIDE_DATABASE_PATH." msgstr "" @@ -3835,14 +3952,14 @@ msgid "Main memory" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:216 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:478 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:487 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:525 msgid "Storage Card A" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/delete.py:217 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:480 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:489 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:518 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:527 msgid "Storage Card B" msgstr "" @@ -3939,133 +4056,133 @@ msgstr "" msgid "Manage the collections on this device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:24 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:25 msgid "E" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:24 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:25 msgid "Edit metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:28 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:29 msgid "Merge book records" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:29 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:30 msgid "M" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:31 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:32 msgid "Edit metadata individually" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:34 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:35 msgid "Edit metadata in bulk" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:37 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:38 msgid "Download metadata and covers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:42 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:43 msgid "Merge into first selected book - delete others" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:45 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:46 msgid "Merge into first selected book - keep others" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:49 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:50 msgid "Merge only formats into first selected book - delete others" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:71 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:72 msgid "Cannot download metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:81 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:82 msgid "Failed to download metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:87 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:88 #: /home/kovid/work/calibre/src/calibre/gui2/dnd.py:84 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:468 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:716 msgid "Download failed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:88 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:89 msgid "Failed to download metadata or covers for any of the %d book(s)." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:92 msgid "Metadata download completed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:93 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:94 msgid "Finished downloading metadata for %d book(s). Proceed with updating the metadata in your library?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:100 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:101 msgid "Could not download metadata and/or covers for %d of the books. Click \"Show details\" to see which books." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:108 msgid "Download complete" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:107 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:108 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:777 msgid "Download log" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:137 msgid "Some books changed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:137 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:138 msgid "The metadata for some books in your library has changed since you started the download. If you proceed, some of those changes may be overwritten. Click \"Show details\" to see the list of changed books. Do you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:155 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:219 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:156 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:220 msgid "Cannot edit metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:256 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:259 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:257 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:260 msgid "Cannot merge books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:261 msgid "At least two books must be selected for merging" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:264 msgid "You are about to merge more than 5 books. Are you sure you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:271 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:272 msgid "Book formats and metadata from the selected books will be added to the first selected book (%s). ISBN will not be merged.

The second and subsequently selected books will not be deleted or changed.

Please confirm you want to proceed." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:283 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:284 msgid "Book formats from the selected books will be merged into the first selected book (%s). Metadata in the first selected book will not be changed.Author, Title, ISBN and all other metadata will not be merged.

After merger the second and subsequently selected books, with any metadata they have will be deleted.

All book formats of the first selected book will be kept and any duplicate formats in the second and subsequently selected books will be permanently deleted from your calibre library.

Are you sure you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:299 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:300 msgid "Book formats and metadata from the selected books will be merged into the first selected book (%s). ISBN will not be merged.

After merger the second and subsequently selected books will be deleted.

All book formats of the first selected book will be kept and any duplicate formats in the second and subsequently selected books will be permanently deleted from your calibre library.

Are you sure you want to proceed?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:456 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:472 msgid "Applying changed metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:529 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:545 msgid "Some failures" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:530 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/edit_metadata.py:546 msgid "Failed to apply updated metadata for some books in your library. Click \"Show Details\" to see details." msgstr "" @@ -4085,10 +4202,6 @@ msgstr "" msgid " fetched." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/help.py:16 -msgid "Browse the calibre User Manual" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/actions/help.py:16 msgid "F1" msgstr "" @@ -4256,6 +4369,22 @@ msgstr "" msgid "No detailed information is available for books on the device." msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/actions/show_quickview.py:16 +msgid "Q" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/show_quickview.py:16 +msgid "Show quickview" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/show_quickview.py:31 +msgid "No quickview available" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/actions/show_quickview.py:32 +msgid "Quickview is not available for books on the device." +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/actions/similar_books.py:17 msgid "Similar books..." msgstr "" @@ -4350,25 +4479,25 @@ msgstr "" msgid "About Get Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:15 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:17 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tweak_epub_ui.py:60 msgid "Tweak ePub" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:16 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:18 msgid "Make small changes to ePub format books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:17 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:19 msgid "T" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:27 -#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:39 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:29 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:41 msgid "Cannot tweak ePub" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:40 +#: /home/kovid/work/calibre/src/calibre/gui2/actions/tweak_epub.py:42 msgid "No ePub available. First convert the book to ePub." msgstr "" @@ -4457,7 +4586,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:856 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:895 msgid "No books" msgstr "" @@ -4493,7 +4622,11 @@ msgstr "" msgid "Saving..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/add.py:448 +msgid "Collecting data, please wait..." +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/add.py:517 msgid "Saved" msgstr "" @@ -4651,7 +4784,7 @@ msgid "Book %s of %s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/book_details.py:144 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1032 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1019 msgid "Collections" msgstr "" @@ -4765,7 +4898,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/email_ui.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/look_feel_ui.py:197 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:115 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:65 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugboard_ui.py:113 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins_ui.py:105 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/save_template_ui.py:46 @@ -5203,7 +5336,7 @@ msgid "Bulk Convert" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/bulk.py:89 -#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:189 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:185 msgid "Options specific to the output format." msgstr "" @@ -5659,33 +5792,33 @@ msgid "Set the metadata. The output file will contain as much of this metadata a msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:180 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:748 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:749 msgid "Choose cover for " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:756 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:757 msgid "Cannot read" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:757 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:758 msgid "You do not have permission to read the file: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:196 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:765 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:766 msgid "Error reading file" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:766 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:767 msgid "

There was an error reading from file:
" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:204 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:776 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:777 msgid " is not a valid picture" msgstr "" @@ -5738,7 +5871,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:931 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:932 msgid "Tags categorize the book. This is particularly useful while searching.

They can be any words or phrases, separated by commas." msgstr "" @@ -5921,7 +6054,7 @@ msgstr "" msgid "Cannot build regex using the GUI builder without a book." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:153 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/regex_builder.py:158 msgid "Open book" msgstr "" @@ -6019,11 +6152,11 @@ msgstr "" msgid "

Search and replace uses regular expressions. See the regular expressions tutorial to get started with regular expressions. Also clicking the wizard buttons below will allow you to test your regular expression against the current input document." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:173 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:169 msgid "Convert" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/convert/single.py:196 msgid "Options specific to the input format." msgstr "" @@ -6353,14 +6486,14 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:606 #: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:629 #: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:680 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:306 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:311 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:307 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:312 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:128 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:148 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:230 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:279 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:283 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1197 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1198 msgid "Undefined" msgstr "" @@ -6429,177 +6562,177 @@ msgstr "" msgid "No details available." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:177 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:188 msgid "Device no longer connected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:301 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:325 msgid "Get device information" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:315 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:340 msgid "Get list of books on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:325 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:350 msgid "Get annotations from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:337 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:362 msgid "Send metadata to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:342 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:367 msgid "Send collections to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:378 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:413 msgid "Upload %d books to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:393 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:429 msgid "Delete books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:410 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:447 msgid "Download books from device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:420 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:458 msgid "View book on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:461 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:499 msgid "Set default send to device action" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:467 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:505 msgid "Send to main memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:469 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:507 msgid "Send to storage card A" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:471 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:509 msgid "Send to storage card B" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:476 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:485 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:523 msgid "Main Memory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:497 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:535 msgid "Send specific format to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:498 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:536 msgid "Send and delete from library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:541 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:579 msgid "Eject device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:622 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:55 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:660 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:63 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:315 #: /home/kovid/work/calibre/src/calibre/utils/ipc/job.py:54 msgid "Error" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:623 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:661 msgid "Error communicating with device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:642 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1181 -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:253 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:680 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1218 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:254 msgid "No suitable formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:658 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:696 msgid "Select folder to open as device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:709 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:747 msgid "Error talking to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:710 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:748 msgid "There was a temporary error talking to the device. Please unplug and reconnect the device and or reboot." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:753 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:791 msgid "Device: " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:755 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:793 msgid " detected." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:857 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:896 msgid "selected to send" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:876 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:915 msgid "%i of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:879 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:918 msgid "0 of %i Books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:880 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:919 msgid "Choose format to send to device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:888 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:927 msgid "No device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:889 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:928 msgid "Cannot send: No device is connected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:892 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:896 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:931 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:935 msgid "No card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:893 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:897 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:932 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:936 msgid "Cannot send: Device has no storage card" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:958 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1041 -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1175 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:997 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1080 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1212 msgid "Auto convert the following books before uploading to the device?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:987 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1026 msgid "Sending catalogs to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1088 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1125 msgid "Sending news to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1142 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1179 msgid "Sending books to device." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1182 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1219 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:1254 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1292 msgid "No space on device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1255 +#: /home/kovid/work/calibre/src/calibre/gui2/device.py:1293 msgid "

Cannot upload books to device there is no more free space available " msgstr "" @@ -6699,7 +6832,7 @@ msgid "My Books" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/catalog_ui.py:92 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:314 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:320 msgid "Generate catalog" msgstr "" @@ -7057,8 +7190,8 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/delete_matching_from_device.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:69 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1030 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1017 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:35 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/create_custom_column.py:76 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:338 @@ -7102,14 +7235,14 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:121 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 #: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:429 msgid "Upper Case" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:122 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:261 #: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:430 msgid "Lower Case" msgstr "" @@ -7122,14 +7255,14 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:124 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:262 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 #: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:432 msgid "Title Case" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:125 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:264 #: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:433 msgid "Capitalize" msgstr "" @@ -7146,13 +7279,13 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:271 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1439 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1449 msgid "Invalid author name" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:272 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1440 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1450 msgid "Author names cannot contain & characters." msgstr "" @@ -7170,7 +7303,7 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:90 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2111 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2130 msgid "F&ind" msgstr "" @@ -7285,109 +7418,109 @@ msgstr "" msgid "Working" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:266 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:267 msgid "Character match" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:267 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:268 msgid "Regular Expression" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:270 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:271 msgid "Replace field" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:271 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:272 msgid "Prepend to field" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:272 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:273 msgid "Append to field" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:282 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:283 msgid "Editing meta information for %d books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:323 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:324 msgid "Immediately make all changes without closing the dialog. This operation cannot be canceled or undone" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:382 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:383 msgid "Book %d:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:400 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:401 msgid "Enter an identifier type" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:405 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:406 msgid "You can destroy your library using this feature. Changes are permanent. There is no undo function. You are strongly encouraged to back up your library before proceeding.

Search and replace in text fields using character matching or regular expressions. " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:413 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:414 msgid "In character mode, the field is searched for the entered search text. The text is replaced by the specified replacement text everywhere it is found in the specified field. After replacement is finished, the text can be changed to upper-case, lower-case, or title-case. If the case-sensitive check box is checked, the search text must match exactly. If it is unchecked, the search text will match both upper- and lower-case letters" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:424 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:425 msgid "In regular expression mode, the search text is an arbitrary python-compatible regular expression. The replacement text can contain backreferences to parenthesized expressions in the pattern. The search is not anchored, and can match and replace multiple times on the same string. The modification functions (lower-case etc) are applied to the matched text, not to the field as a whole. The destination box specifies the field where the result after matching and replacement is to be assigned. You can replace the text in the field, or prepend or append the matched text. See this reference for more information on python's regular expressions, and in particular the 'sub' function." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:502 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:503 msgid "S/R TEMPLATE ERROR" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:648 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:649 msgid "You must specify a destination when source is a composite field" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:654 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:655 msgid "You must specify a destination identifier type" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:752 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:771 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:898 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:753 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:772 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:899 msgid "Search/replace invalid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:753 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:754 msgid "Authors cannot be set to the empty string. Book title %s not processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:772 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:773 msgid "Title cannot be set to the empty string. Book title %s not processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:899 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:900 msgid "Search pattern is invalid: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:951 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:952 msgid "" "Applying changes to %d books.\n" "Phase {0} {1}%%." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:981 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:982 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:587 msgid "Delete saved search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:982 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:983 msgid "The selected saved search/replace will be deleted. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:999 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1007 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1000 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1008 msgid "Save search/replace" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1000 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1001 msgid "Search/replace name:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1008 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:1009 msgid "That saved search/replace already exists and will be overwritten. Are you sure?" msgstr "" @@ -7412,13 +7545,13 @@ msgid "Specify how the author(s) of this book should be sorted. For example Char msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:889 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:890 msgid "&Rating:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:890 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:891 msgid "Rating of this book. 0-5 stars" msgstr "" @@ -7488,7 +7621,7 @@ msgid "&Force numbers to start with:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1181 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1182 msgid "&Date:" msgstr "" @@ -8034,6 +8167,41 @@ msgstr "" msgid "Aborting..." msgstr "" +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:70 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:146 +#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:111 +msgid "Authors" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview.py:161 +msgid "Books with selected item: {0}" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview_ui.py:75 +msgid "Quickview" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview_ui.py:76 +msgid "Items" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview_ui.py:77 +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:95 +#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:279 +#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:80 +#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:109 +#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:670 +#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:280 +msgid "Search" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/quickview_ui.py:78 +msgid "Search in the library view for the selected item" +msgstr "" + #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/restore_library.py:23 msgid "Restoring database from backups, do not interrupt, this will happen in three stages" msgstr "" @@ -8460,7 +8628,7 @@ msgid "&Author:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:930 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:931 msgid "Ta&gs:" msgstr "" @@ -8499,12 +8667,6 @@ msgstr "" msgid "Choose formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:146 -#: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:111 -msgid "Authors" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_categories.py:60 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:147 msgid "Publishers" @@ -8645,12 +8807,12 @@ msgid "%s (was %s)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1385 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1395 msgid "Item is blank" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1386 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1396 msgid "An item cannot be set to nothing. Delete it instead." msgstr "" @@ -8724,7 +8886,7 @@ msgid "The template box cannot be empty" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/template_dialog_ui.py:97 -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:244 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:252 msgid "Set the color of the column:" msgstr "" @@ -9034,44 +9196,44 @@ msgstr "" msgid "Attached is the %s periodical downloaded by calibre." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:193 msgid "E-book:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:200 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:201 msgid "Attached, you will find the e-book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:201 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:202 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/plugins.py:189 msgid "by" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:202 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:203 msgid "in the %s format." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:217 msgid "Sending email to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:247 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:248 msgid "Auto convert the following books before sending via email?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:254 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:255 msgid "Could not email the following books as no suitable formats were found:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:260 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:261 msgid "Failed to email book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:263 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:264 msgid "sent" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/email.py:286 +#: /home/kovid/work/calibre/src/calibre/gui2/email.py:287 msgid "Sent news to" msgstr "" @@ -9139,7 +9301,7 @@ msgid "Regular expression (?P)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:149 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1086 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1087 msgid "ISBN:" msgstr "" @@ -9273,11 +9435,11 @@ msgstr "" msgid " - Jobs" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:459 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:463 msgid "Do you really want to stop the selected job?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:465 +#: /home/kovid/work/calibre/src/calibre/gui2/jobs.py:469 msgid "Do you really want to stop all non-device jobs?" msgstr "" @@ -9294,7 +9456,7 @@ msgid "Show books in the main memory of the device" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/layout.py:72 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1055 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1077 msgid "Card A" msgstr "" @@ -9303,7 +9465,7 @@ msgid "Show books in storage card A" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/layout.py:74 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1057 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1079 msgid "Card B" msgstr "" @@ -9347,45 +9509,45 @@ msgstr "" msgid "Y" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:57 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:268 msgid "On Device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:68 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:59 msgid "Size (MB)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:75 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:66 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:258 msgid "Modified" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:771 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1330 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:797 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:758 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1317 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:806 msgid "The lookup/search name is \"{0}\"" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:777 -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1332 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:764 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1319 msgid "This book's UUID is \"{0}\"" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1027 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1014 msgid "In Library" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1031 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1018 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:328 msgid "Size" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1312 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1299 msgid "Marked for deletion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1315 +#: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1302 msgid "Double click to edit me

" msgstr "" @@ -9737,117 +9899,117 @@ msgstr "" msgid "No format selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:694 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:695 msgid "Could not read metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:695 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:696 msgid "Could not read metadata from %s format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:714 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:715 msgid "&Browse" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:716 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:717 msgid "T&rim" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:718 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:719 msgid "&Remove" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:725 msgid "Download co&ver" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:725 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:726 msgid "&Generate cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:775 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:776 msgid "Not a valid picture" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:799 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:800 msgid "Specify title and author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:800 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:801 msgid "You must specify a title and author before generating a cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:818 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:819 msgid "Invalid cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:819 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:820 msgid "Could not change cover as the image is invalid." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:846 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:847 msgid "This book has no cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:848 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:849 msgid "Cover size: %dx%d pixels" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:897 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:898 msgid "stars" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:966 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:967 msgid "Tags changed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:967 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:968 msgid "You have changed the tags. In order to use the tags editor, you must either discard or apply these changes. Apply changes?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:993 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:994 msgid "I&ds:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:994 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:995 msgid "" "Edit the identifiers for this book. For example: \n" "\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1055 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1117 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1056 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1118 msgid "This ISBN number is valid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1058 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1120 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1059 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1121 msgid "This ISBN number is invalid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1083 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1105 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1084 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1106 msgid "Invalid ISBN" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1084 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1085 msgid "Enter an ISBN" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1106 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1107 msgid "The ISBN you entered is not valid. Try again." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1130 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1131 msgid "&Publisher:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1200 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1201 msgid "Clear date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1232 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1233 msgid "Publishe&d:" msgstr "" @@ -10347,82 +10509,74 @@ msgid "does not match pattern" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:75 -msgid "If the " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:81 -msgid " column " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:87 -msgid " value " -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:202 -msgid "Enter either an identifier type or an identifier type and value of the form identifier:value" -msgstr "" - -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:205 -msgid "Enter a number" +msgid "If the ___ column ___ values" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:210 +msgid "Enter either an identifier type or an identifier type and value of the form identifier:value" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:213 +msgid "Enter a number" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:218 msgid "Enter a date in the format YYYY-MM-DD" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:212 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:220 msgid "Enter a string." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:214 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:222 msgid "Enter a regular expression" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:216 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:224 msgid "You can match multiple values by separating them with %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:239 msgid "Create/edit a column coloring rule" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:236 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:244 msgid "Create a coloring rule by filling in the boxes below" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:250 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:258 msgid "to" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:258 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:266 msgid "Only if the following conditions are all satisfied:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:268 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:276 msgid "Add another condition" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:272 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:280 msgid "You can disable a condition by blanking all of its boxes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:344 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:352 msgid "Invalid condition" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:345 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:353 msgid "One of the conditions for this rule is invalid: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:350 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:358 msgid "No conditions" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:351 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:359 msgid "You must specify at least one non-empty condition for this rule" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:439 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:447 msgid "" "\n" "

Advanced Rule for column %s:\n" @@ -10430,7 +10584,7 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:444 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:452 msgid "" "

Set the color of %s to %s if the following\n" " conditions are met:

\n" @@ -10438,43 +10592,43 @@ msgid "" " " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:459 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:467 msgid "
  • If the %s column %s value: %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:474 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:482 msgid "You can control the color of columns in the book list by creating \"rules\" that tell calibre what color to use. Click the Add Rule button below to get started.

    You can change an existing rule by double clicking it." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:483 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:491 msgid "Add Rule" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:486 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:494 msgid "Remove Rule" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:503 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:511 msgid "Move the selected rule up" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:508 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:516 msgid "Move the selected rule down" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:516 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:524 msgid "Add Advanced Rule" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:564 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:572 msgid "No rule selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:565 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:573 msgid "No rule selected for %s." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:570 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/coloring.py:578 msgid "removal" msgstr "" @@ -11283,90 +11437,108 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:126 -msgid "&Set as default" +msgid "Restore your own subset of checked fields that you define using the 'Set as default' button" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:127 -msgid "Convert all downloaded comments to plain &text" +msgid "&Set as default" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:128 -msgid "Swap author names from FN LN to LN, FN" +msgid "Store the currently checked fields as a default you can restore using the 'Select default' button" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:129 -msgid "Max. number of &tags to download:" +msgid "Convert all downloaded comments to plain &text" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:130 -msgid "Max. &time to wait after first match is found:" +msgid "Swap author names from FN LN to LN, FN" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:131 +msgid "Max. number of &tags to download:" +msgstr "" + +#: +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:132 +msgid "Max. &time to wait after first match is found:" +msgstr "" + +#: #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:133 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:135 #: /home/kovid/work/calibre/src/calibre/gui2/viewer/config_ui.py:199 msgid " secs" msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:134 msgid "Max. time to wait after first &cover is found:" msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:136 msgid "" "

    Different metadata sources have different sets of tags for the same book. If this option is checked, then calibre will use the smaller tag sets. These tend to be more like genres, while the larger tag sets tend to describe the books content.\n" "

    Note that this option will only make a practical difference if one of the metadata sources has a genre like tag set for the book you are searching for. Most often, they all have large tag sets." msgstr "" #: -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:136 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources_ui.py:138 msgid "Prefer &fewer tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:56 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:38 +msgid "No proxies used" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:42 +msgid "Using proxies:" +msgstr "" + +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:64 msgid "Failed to install command line tools." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:67 msgid "Command line tools installed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:60 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:68 msgid "Command line tools installed in" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:61 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc.py:69 msgid "If you move calibre.app, you have to re-install the command line tools." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:62 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:66 msgid "Max. simultaneous conversion/news download jobs:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:63 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:67 msgid "Limit the max. simultaneous jobs to the available CPU &cores" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:64 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:68 msgid "Debug &device detection" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:69 msgid "Get information to setup the &user defined device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:70 msgid "Open calibre &configuration directory" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:67 +#: /home/kovid/work/calibre/src/calibre/gui2/preferences/misc_ui.py:71 msgid "&Install command line tools" msgstr "" @@ -12092,17 +12264,6 @@ msgstr "" msgid "Apply any changes you made to this tweak" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:95 -#: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:279 -#: /home/kovid/work/calibre/src/calibre/gui2/store/config/chooser/chooser_widget_ui.py:80 -#: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/store_dialog_ui.py:76 -#: /home/kovid/work/calibre/src/calibre/gui2/store/search/search_ui.py:134 -#: /home/kovid/work/calibre/src/calibre/gui2/store/search_ui.py:109 -#: /home/kovid/work/calibre/src/calibre/gui2/widgets.py:670 -#: /home/kovid/work/calibre/src/calibre/library/server/browse.py:280 -msgid "Search" -msgstr "" - #: /home/kovid/work/calibre/src/calibre/gui2/search_box.py:339 msgid "Delete current search" msgstr "" @@ -12678,13 +12839,13 @@ msgid "Manage %s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:454 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1850 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1869 msgid "Manage Saved Searches" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:462 #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:466 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1848 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1867 msgid "Manage User Categories" msgstr "" @@ -12696,108 +12857,108 @@ msgstr "" msgid "Change sub-categorization scheme" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:792 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:801 msgid "The grouped search term name is \"{0}\"" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1065 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1074 msgid "Changing the authors for several books can take a while. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1070 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1079 msgid "Changing the metadata for that many books can take a while. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1157 -#: /home/kovid/work/calibre/src/calibre/library/database2.py:449 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1166 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:447 msgid "Searches" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1391 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1411 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1420 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1401 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1421 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1430 msgid "Rename user category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1392 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1402 msgid "You cannot use periods in the name when renaming user categories" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1412 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1421 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1422 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1431 msgid "The name %s is already used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1444 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1454 msgid "Duplicate search name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1445 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1455 msgid "The saved search name %s is already used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1840 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1859 msgid "Manage Authors" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1842 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1861 msgid "Manage Series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1844 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1863 msgid "Manage Publishers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1846 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1865 msgid "Manage Tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1858 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1877 msgid "Invalid search restriction" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1859 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1878 msgid "The current search restriction is invalid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1875 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1894 msgid "New Category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1926 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1929 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1945 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1948 msgid "Delete user category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1927 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1946 msgid "%s is not a user category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1930 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1949 msgid "%s contains items. Do you really want to delete it?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1951 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1970 msgid "Remove category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1952 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1971 msgid "User category %s does not exist" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1971 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1990 msgid "Add to user category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1972 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1991 msgid "A user category %s does not exist" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2095 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2114 msgid "Find item in tag browser" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2098 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2117 msgid "" "Search for items. This is a \"contains\" search; items containing the\n" "text anywhere in the name will be found. You can limit the search\n" @@ -12807,93 +12968,93 @@ msgid "" "containing the text \"foo\"" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2107 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2126 msgid "ALT+f" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2112 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2131 msgid "Find the first/next matching item" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2117 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2136 msgid "Collapse all categories" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2141 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2160 msgid "No More Matches.

    Click Find again to go to first match" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2154 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2173 msgid "Sort by name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2154 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2173 msgid "Sort by popularity" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2155 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2174 msgid "Sort by average rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2158 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2177 msgid "Set the sort order for entries in the Tag Browser" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2165 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2184 msgid "Match all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2165 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2184 msgid "Match any" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2170 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2189 msgid "When selecting multiple entries in the Tag Browser match any or all of them" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2177 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2196 msgid "Manage authors, tags, etc" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2178 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2197 msgid "All of these category_managers are available by right-clicking on items in the tag browser above" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:65 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:68 msgid "Convert book %(num)d of %(total)d (%(title)s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:93 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:205 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:96 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:211 msgid "Could not convert some books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:94 -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:206 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:97 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:212 msgid "Could not convert %d of %d books, because no suitable source format was found." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:125 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:128 msgid "Queueing books for bulk conversion" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:183 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:189 msgid "Queueing " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:184 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:190 msgid "Convert book %d of %d (%s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:254 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:260 msgid "Fetch news from " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:327 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:333 msgid "Convert existing" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:328 +#: /home/kovid/work/calibre/src/calibre/gui2/tools.py:334 msgid "The following books have already been converted to %s format. Do you wish to reconvert them?" msgstr "" @@ -14087,7 +14248,7 @@ msgstr "" msgid "No books available to include in catalog" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/catalog.py:5051 +#: /home/kovid/work/calibre/src/calibre/library/catalog.py:5063 msgid "" "\n" "*** Adding 'By Authors' Section required for MOBI output ***" @@ -14598,19 +14759,19 @@ msgstr "" msgid "%sAverage rating is %3.1f" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:1053 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:1075 msgid "Main" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:3144 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3246 msgid "

    Migrating old database to ebook library in %s

    " msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:3173 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3275 msgid "Copying %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/database2.py:3190 +#: /home/kovid/work/calibre/src/calibre/library/database2.py:3292 msgid "Compacting database" msgstr "" @@ -14630,11 +14791,11 @@ msgstr "" msgid "Title Sort" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/restore.py:126 +#: /home/kovid/work/calibre/src/calibre/library/restore.py:127 msgid "Processed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/restore.py:192 +#: /home/kovid/work/calibre/src/calibre/library/restore.py:193 msgid "creating custom column " msgstr "" @@ -14735,8 +14896,8 @@ msgstr "" msgid "Replace whitespace with underscores." msgstr "" -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:372 -#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:400 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:379 +#: /home/kovid/work/calibre/src/calibre/library/save_to_disk.py:412 msgid "Requested formats not available" msgstr "" From 1d1016d0b0285d62568c3d031630079fafd11ba3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Jun 2011 19:00:33 -0600 Subject: [PATCH 23/31] Fix #800642 (New Device - Imagin IMEB5) --- src/calibre/devices/teclast/driver.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/teclast/driver.py b/src/calibre/devices/teclast/driver.py index 1bbab8e120..625c53ffb4 100644 --- a/src/calibre/devices/teclast/driver.py +++ b/src/calibre/devices/teclast/driver.py @@ -19,8 +19,9 @@ class TECLAST_K3(USBMS): PRODUCT_ID = [0x3203] BCD = [0x0000, 0x0100] - VENDOR_NAME = 'TECLAST' - WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5'] + VENDOR_NAME = ['TECLAST', 'IMAGIN'] + WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5', + 'EREADER'] MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory' STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card' From 8fb4561ff4090bf56afaaa4aa7f5c6f68b2ce408 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Jun 2011 19:25:56 -0600 Subject: [PATCH 24/31] Fix #799777 (Error retrieving date out of epub) --- src/calibre/gui2/metadata/basic_widgets.py | 6 ++++-- src/calibre/utils/date.py | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 303cc51c74..858aafafc6 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -21,9 +21,10 @@ from calibre.utils.config import tweaks, prefs from calibre.ebooks.metadata import (title_sort, authors_to_string, string_to_authors, check_isbn, authors_to_sort_string) from calibre.ebooks.metadata.meta import get_metadata -from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, +from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATE, choose_files, error_dialog, choose_images) -from calibre.utils.date import local_tz, qt_to_dt +from calibre.utils.date import (local_tz, qt_to_dt, as_local_time, + UNDEFINED_DATE) from calibre import strftime from calibre.ebooks import BOOK_EXTENSIONS from calibre.customize.ui import run_plugins_on_import @@ -1211,6 +1212,7 @@ class DateEdit(QDateEdit): # {{{ def fset(self, val): if val is None: val = UNDEFINED_DATE + val = as_local_time(val) self.setDate(QDate(val.year, val.month, val.day)) return property(fget=fget, fset=fset) diff --git a/src/calibre/utils/date.py b/src/calibre/utils/date.py index b368c0ed9b..c93e69874c 100644 --- a/src/calibre/utils/date.py +++ b/src/calibre/utils/date.py @@ -123,6 +123,14 @@ def isoformat(date_time, assume_utc=False, as_utc=True, sep='T'): date_time = date_time.astimezone(_utc_tz if as_utc else _local_tz) return unicode(date_time.isoformat(sep)) +def as_local_time(date_time, assume_utc=True): + if not hasattr(date_time, 'tzinfo'): + return date_time + if date_time.tzinfo is None: + date_time = date_time.replace(tzinfo=_utc_tz if assume_utc else + _local_tz) + return date_time.astimezone(_local_tz) + def now(): return datetime.now().replace(tzinfo=_local_tz) From c4afc7289cd47e83c898204d25bcc0e848600241 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Jun 2011 19:33:12 -0600 Subject: [PATCH 25/31] Save to disk: Show some feedback while in the collecting data phase --- src/calibre/gui2/add.py | 5 ++++- src/calibre/gui2/dialogs/progress.py | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/add.py b/src/calibre/gui2/add.py index 3dbf4b94df..3538f019ab 100644 --- a/src/calibre/gui2/add.py +++ b/src/calibre/gui2/add.py @@ -451,7 +451,8 @@ class Saver(QObject): # {{{ self.callback_called = False self.rq = Queue() self.ids = [x for x in map(db.id, [r.row() for r in rows]) if x is not None] - self.pd.set_max(len(self.ids)) + self.pd_max = len(self.ids) + self.pd.set_max(0) self.pd.value = 0 self.failures = set([]) @@ -510,6 +511,8 @@ class Saver(QObject): # {{{ id, title, ok, tb = self.rq.get_nowait() except Empty: return + if self.pd.max != self.pd_max: + self.pd.max = self.pd_max self.pd.value += 1 self.ids.remove(id) if not isinstance(title, unicode): diff --git a/src/calibre/gui2/dialogs/progress.py b/src/calibre/gui2/dialogs/progress.py index 553ee4b03b..552172e4b2 100644 --- a/src/calibre/gui2/dialogs/progress.py +++ b/src/calibre/gui2/dialogs/progress.py @@ -53,6 +53,13 @@ class ProgressDialog(QDialog, Ui_Dialog): def set_max(self, max): self.bar.setMaximum(max) + @dynamic_property + def max(self): + def fget(self): return self.bar.maximum() + def fset(self, val): self.bar.setMaximum(val) + return property(fget=fget, fset=fset) + + def _canceled(self, *args): self.canceled = True self.button_box.setDisabled(True) From caa54a218ffa50c26947841921be63190937a183 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 23 Jun 2011 15:06:38 +0100 Subject: [PATCH 26/31] Mem leak changes --- src/calibre/gui2/actions/edit_metadata.py | 7 ++++ src/calibre/gui2/custom_column_widgets.py | 5 ++- src/calibre/gui2/metadata/basic_widgets.py | 40 ++++++++++++++++++++++ src/calibre/gui2/metadata/single.py | 7 ++++ src/calibre/gui2/tag_view.py | 13 +++++-- 5 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index 718ece46d2..fc56d3d997 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -170,6 +170,10 @@ class EditMetadataAction(InterfaceAction): list(range(self.gui.library_view.model().rowCount(QModelIndex()))) current_row = row_list.index(cr) + from calibre.utils.mem import memory + + import gc + print 'start of edit metadata:', memory()/1024**2 changed, rows_to_refresh = self.do_edit_metadata(row_list, current_row) m = self.gui.library_view.model() @@ -184,6 +188,9 @@ class EditMetadataAction(InterfaceAction): self.gui.cover_flow.dataChanged() m.current_changed(current, previous) self.gui.tags_view.recount() + for i in range(5): gc.collect(); + print 'end of edit metadata:', memory()/1024**2 + print def do_edit_metadata(self, row_list, current_row): from calibre.gui2.metadata.single import edit_metadata diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 4706cce4c9..732d30e7fb 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -25,7 +25,7 @@ class Base(object): def __init__(self, db, col_id, parent=None): self.db, self.col_id = db, col_id self.col_metadata = db.custom_column_num_map[col_id] - self.initial_val = None + self.initial_val = self.widgets = None self.setup_ui(parent) def initialize(self, book_id): @@ -54,6 +54,9 @@ class Base(object): def normalize_ui_val(self, val): return val + def break_cycles(self): + self.db = self.widgets = self.initial_val = None + class Bool(Base): def setup_ui(self, parent): diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 858aafafc6..e00af37d33 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -126,6 +126,9 @@ class TitleEdit(EnLineEdit): return property(fget=fget, fset=fset) + def break_cycles(self): + self.dialog = None + class TitleSortEdit(TitleEdit): TITLE_ATTR = 'title_sort' @@ -151,6 +154,7 @@ class TitleSortEdit(TitleEdit): self.title_edit.textChanged.connect(self.update_state) self.textChanged.connect(self.update_state) + self.autogen_button = autogen_button autogen_button.clicked.connect(self.auto_generate) self.update_state() @@ -169,6 +173,9 @@ class TitleSortEdit(TitleEdit): def auto_generate(self, *args): self.current_val = title_sort(self.title_edit.current_val) + self.title_edit.textChanged.disconnect() + self.textChanged.disconnect() + self.autogen_button.clicked.disconnect() # }}} @@ -186,6 +193,7 @@ class AuthorsEdit(MultiCompleteComboBox): self.setWhatsThis(self.TOOLTIP) self.setEditable(True) self.setSizeAdjustPolicy(self.AdjustToMinimumContentsLengthWithIcon) + self.manage_authors_signal = manage_authors manage_authors.triggered.connect(self.manage_authors) def manage_authors(self): @@ -270,6 +278,10 @@ class AuthorsEdit(MultiCompleteComboBox): return property(fget=fget, fset=fset) + def break_cycles(self): + self.db = self.dialog = None + self.manage_authors_signal.triggered.disconnect() + class AuthorSortEdit(EnLineEdit): TOOLTIP = _('Specify how the author(s) of this book should be sorted. ' @@ -298,6 +310,10 @@ class AuthorSortEdit(EnLineEdit): self.authors_edit.editTextChanged.connect(self.update_state_and_val) self.textChanged.connect(self.update_state) + self.autogen_button = autogen_button + self.copy_a_to_as_action = copy_a_to_as_action + self.copy_as_to_a_action = copy_as_to_a_action + autogen_button.clicked.connect(self.auto_generate) copy_a_to_as_action.triggered.connect(self.auto_generate) copy_as_to_a_action.triggered.connect(self.copy_to_authors) @@ -369,6 +385,15 @@ class AuthorSortEdit(EnLineEdit): db.set_author_sort(id_, aus, notify=False, commit=False) return True + def break_cycles(self): + self.db = None + self.authors_edit.editTextChanged.disconnect() + self.textChanged.disconnect() + self.autogen_button.clicked.disconnect() + self.copy_a_to_as_action.triggered.disconnect() + self.copy_as_to_a_action.triggered.disconnect() + self.authors_edit = None + # }}} # Series {{{ @@ -428,6 +453,10 @@ class SeriesEdit(MultiCompleteComboBox): commit=True, allow_case_change=True) return True + def break_cycles(self): + self.dialog = None + + class SeriesIndexEdit(QDoubleSpinBox): TOOLTIP = '' @@ -489,6 +518,11 @@ class SeriesIndexEdit(QDoubleSpinBox): import traceback traceback.print_exc() + def break_cycles(self): + self.series_edit.currentIndexChanged.disconnect() + self.series_edit.editTextChanged.disconnect() + self.series_edit.lineEdit().editingFinished.disconnect() + self.db = self.series_edit = self.dialog = None # }}} @@ -700,6 +734,8 @@ class FormatsManager(QWidget): # {{{ if old != prefs['read_file_metadata']: prefs['read_file_metadata'] = old + def break_cycles(self): + self.dialog = None # }}} class Cover(ImageView): # {{{ @@ -861,6 +897,10 @@ class Cover(ImageView): # {{{ db.remove_cover(id_, notify=False, commit=False) return True + def break_cycles(self): + self.cover_changed.disconnect() + self.dialog = self._cdata = self.current_val = self.original_val = None + # }}} class CommentsEdit(Editor): # {{{ diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index d818f2db2a..950d3722e5 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -481,6 +481,13 @@ class MetadataSingleDialogBase(ResizableDialog): x = getattr(self, b, None) if x is not None: disconnect(x.clicked) + for widget in self.basic_metadata_widgets: + bc = getattr(widget, 'break_cycles', None) + if bc is not None and callable(bc): + bc() + for widget in getattr(self, 'custom_metadata_widgets', []): + widget.break_cycles() + # }}} class Splitter(QSplitter): diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 3b8c27866c..f2c6e38050 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -627,7 +627,8 @@ class TagTreeItem(object): # {{{ except: pass self.parent = self.icon_state_map = self.bold_font = self.tag = \ - self.icon = self.children = None + self.icon = self.children = self.tooltip = \ + self.py_name = self.id_set = self.category_key = None def __str__(self): if self.type == self.ROOT: @@ -1121,7 +1122,7 @@ class TagsModel(QAbstractItemModel): # {{{ self.search_restriction = s def get_node_tree(self, sort): - old_row_map = self.row_map[:] + old_row_map_len = len(self.row_map) self.row_map = [] self.categories = {} @@ -1176,13 +1177,15 @@ class TagsModel(QAbstractItemModel): # {{{ self.row_map.append(category) self.categories[category] = tb_categories[category]['name'] - if len(old_row_map) != 0 and len(old_row_map) != len(self.row_map): + if old_row_map_len != 0 and old_row_map_len != len(self.row_map): # A category has been added or removed. We must force a rebuild of # the model return None return data def refresh(self, data=None): + from calibre.utils.mem import memory + print 'start of refresh:', memory()/1024**2 sort_by = config['sort_tags_by'] if data is None: data = self.get_node_tree(sort_by) # get category data @@ -1367,11 +1370,15 @@ class TagsModel(QAbstractItemModel): # {{{ self.beginRemoveRows(self.createIndex(category.row(), 0, category), start, len(child_map)-1) category.children = ctags + for i in range(start, len(child_map)): + child_map[i].break_cycles() + child_map = None self.endRemoveRows() else: state_map = {} process_one_node(category, state_map) + print 'end of refresh:', memory()/1024**2 return True def columnCount(self, parent): From f525f30700d175791097dea0614725ca5fda7205 Mon Sep 17 00:00:00 2001 From: GRiker Date: Thu, 23 Jun 2011 10:12:21 -0600 Subject: [PATCH 27/31] Change to using iTunes storage for files instead of local database. FAQ added to forum at http://www.mobileread.com/forums/showthread.php?t=140260 --- src/calibre/devices/apple/driver.py | 314 ++++++++++++++-------------- 1 file changed, 152 insertions(+), 162 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index cc531b7476..86e65f2f60 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -13,8 +13,7 @@ from calibre.devices.errors import OpenFeedback, UserFeedback from calibre.devices.usbms.deviceconfig import DeviceConfig from calibre.devices.interface import DevicePlugin from calibre.ebooks.BeautifulSoup import BeautifulSoup -from calibre.ebooks.metadata import authors_to_string, MetaInformation, \ - title_sort +from calibre.ebooks.metadata import authors_to_string, MetaInformation, title_sort from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.epub import set_metadata from calibre.library.server.utils import strftime @@ -165,8 +164,12 @@ class ITUNES(DriverBase): settings() set_progress_reporter() upload_books() - _get_fpath() - _update_epub_metadata() + _remove_existing_copy() + _remove_from_device() + _remove_from_iTunes() + _add_new_copy() + _add_library_book() + _update_iTunes_metadata() add_books_to_metadata() use_plugboard_ext() set_plugboard() @@ -183,7 +186,7 @@ class ITUNES(DriverBase): supported_platforms = ['osx','windows'] author = 'GRiker' #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (1,0,0) + version = (1,1,0) DISPLAY_DISABLE_DIALOG = "display_disable_apple_driver_dialog" @@ -275,10 +278,10 @@ class ITUNES(DriverBase): cache_dir = os.path.join(config_dir, 'caches', 'itunes') calibre_library_path = prefs['library_path'] archive_path = os.path.join(cache_dir, "thumbs.zip") + db = None description_prefix = "added by calibre" ejected = False iTunes= None - iTunes_media = None library_orphans = None log = Log() manual_sync_mode = False @@ -414,11 +417,11 @@ class ITUNES(DriverBase): this_book.datetime = parse_date(str(book.date_added())).timetuple() except: this_book.datetime = time.gmtime() - this_book.db_id = None this_book.device_collections = [] this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None this_book.size = book.size() this_book.uuid = book.composer() + this_book.cid = None # Hack to discover if we're running in GUI environment if self.report_progress is not None: this_book.thumbnail = self._generate_thumbnail(this_book.path, book) @@ -453,10 +456,10 @@ class ITUNES(DriverBase): this_book.datetime = parse_date(str(book.DateAdded)).timetuple() except: this_book.datetime = time.gmtime() - this_book.db_id = None this_book.device_collections = [] this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None this_book.size = book.Size + this_book.cid = None # Hack to discover if we're running in GUI environment if self.report_progress is not None: this_book.thumbnail = self._generate_thumbnail(this_book.path, book) @@ -492,7 +495,7 @@ class ITUNES(DriverBase): def can_handle(self, device_info, debug=False): ''' - Unix version of :method:`can_handle_windows` + OSX version of :method:`can_handle_windows` :param device_info: Is a tupe of (vid, pid, bcd, manufacturer, product, serial number) @@ -1022,17 +1025,14 @@ class ITUNES(DriverBase): if DEBUG: self.log.info("ITUNES.upload_books()") - self._dump_files(files, header='upload_books()',indent=2) - self._dump_update_list(header='upload_books()',indent=2) if isosx: - for (i,file) in enumerate(files): - format = file.rpartition('.')[2].lower() + for (i,fpath) in enumerate(files): + format = fpath.rpartition('.')[2].lower() path = self.path_template % (metadata[i].title, authors_to_string(metadata[i].authors), format) self._remove_existing_copy(path, metadata[i]) - fpath = self._get_fpath(file, metadata[i], format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added, format) this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb, format) @@ -1063,13 +1063,12 @@ class ITUNES(DriverBase): pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") - for (i,file) in enumerate(files): - format = file.rpartition('.')[2].lower() + for (i,fpath) in enumerate(files): + format = fpath.rpartition('.')[2].lower() path = self.path_template % (metadata[i].title, authors_to_string(metadata[i].authors), format) self._remove_existing_copy(path, metadata[i]) - fpath = self._get_fpath(file, metadata[i],format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) if self.manual_sync_mode and not db_added: @@ -1276,24 +1275,59 @@ class ITUNES(DriverBase): def _add_new_copy(self, fpath, metadata): ''' + fp = cached_book['lib_book'].location().path + fp = cached_book['lib_book'].Location ''' if DEBUG: self.log.info(" ITUNES._add_new_copy()") + def _save_last_known_iTunes_storage(lb_added): + if isosx: + fp = lb_added.location().path + index = fp.rfind('/Books') + len('/Books') + last_known_iTunes_storage = fp[:index] + elif iswindows: + fp = lb_added.Location + index = fp.rfind('\Books') + len('\Books') + last_known_iTunes_storage = fp[:index] + dynamic['last_known_iTunes_storage'] = last_known_iTunes_storage + self.log.warning(" last_known_iTunes_storage: %s" % last_known_iTunes_storage) + db_added = None lb_added = None if self.manual_sync_mode: + ''' + This is the unsupported direct-connect mode. + In an attempt to avoid resetting the iTunes library Media folder, don't try to + add the book to iTunes if the last_known_iTunes_storage path is inaccessible. + This means that the path has to be set at least once, probably by using + 'Connect to iTunes' and doing a transfer. + ''' + self.log.warning(" unsupported direct connect mode") db_added = self._add_device_book(fpath, metadata) - if not getattr(fpath, 'deleted_after_upload', False): - lb_added = self._add_library_book(fpath, metadata) - if lb_added: + last_known_iTunes_storage = dynamic.get('last_known_iTunes_storage', None) + if last_known_iTunes_storage is not None: + if os.path.exists(last_known_iTunes_storage): if DEBUG: - self.log.info(" file added to Library|Books for iTunes<->iBooks tracking") + self.log.warning(" iTunes storage online, adding to library") + lb_added = self._add_library_book(fpath, metadata) + else: + if DEBUG: + self.log.warning(" iTunes storage not online, can't add to library") + + if lb_added: + _save_last_known_iTunes_storage(lb_added) + if not lb_added and DEBUG: + self.log.warn(" failed to add '%s' to iTunes, iTunes Media folder inaccessible" % metadata.title) else: lb_added = self._add_library_book(fpath, metadata) - if DEBUG: - self.log.info(" file added to Library|Books for pending sync") + if lb_added: + _save_last_known_iTunes_storage(lb_added) + else: + raise UserFeedback("iTunes Media folder inaccessible", + details="Failed to add '%s' to iTunes" % metadata.title, + level=UserFeedback.WARN) return db_added, lb_added @@ -1308,8 +1342,10 @@ class ITUNES(DriverBase): if metadata.cover: if format == 'epub': - # Pre-shrink cover - # self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT + ''' + Pre-shrink cover + self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT + ''' try: img = PILImage.open(metadata.cover) width = img.size[0] @@ -1317,8 +1353,8 @@ class ITUNES(DriverBase): scaled, nwidth, nheight = fit_image(width, height, self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT) if scaled: if DEBUG: - self.log.info(" '%s' scaled from %sx%s to %sx%s" % - (metadata.cover,width,height,nwidth,nheight)) + self.log.info(" cover scaled from %sx%s to %sx%s" % + (width,height,nwidth,nheight)) img = img.resize((nwidth, nheight), PILImage.ANTIALIAS) cd = cStringIO.StringIO() img.convert('RGB').save(cd, 'JPEG') @@ -1337,9 +1373,11 @@ class ITUNES(DriverBase): return thumb if isosx: - # The following commands generate an error, but the artwork does in fact - # get sent to the device. Seems like a bug in Apple's automation interface? - # Could also be a problem with the integrity of the cover data? + ''' + The following commands generate an error, but the artwork does in fact + get sent to the device. Seems like a bug in Apple's automation interface? + Could also be a problem with the integrity of the cover data? + ''' if lb_added: try: lb_added.artworks[1].data_.set(cover_data) @@ -1362,9 +1400,8 @@ class ITUNES(DriverBase): #ipython(user_ns=locals()) pass - elif iswindows: - # Write the data to a real file for Windows iTunes + ''' Write the data to a real file for Windows iTunes ''' tc = os.path.join(tempfile.gettempdir(), "cover.jpg") with open(tc,'wb') as tmp_cover: tmp_cover.write(cover_data) @@ -1423,7 +1460,8 @@ class ITUNES(DriverBase): this_book = Book(metadata.title, authors_to_string(metadata.authors)) this_book.datetime = time.gmtime() - this_book.db_id = None + #this_book.cid = metadata.id + this_book.cid = None this_book.device_collections = [] this_book.format = format this_book.library_id = lb_added # ??? GR @@ -1431,7 +1469,6 @@ class ITUNES(DriverBase): this_book.thumbnail = thumb this_book.iTunes_id = lb_added # ??? GR this_book.uuid = metadata.uuid - if isosx: if lb_added: this_book.size = self._get_device_book_size(fpath, lb_added.size()) @@ -1462,24 +1499,6 @@ class ITUNES(DriverBase): return this_book - def _delete_iTunesMetadata_plist(self,fpath): - ''' - Delete the plist file from the file to force recache - ''' - zf = ZipFile(fpath,'a') - fnames = zf.namelist() - pl_name = 'iTunesMetadata.plist' - try: - plist = [x for x in fnames if pl_name in x][0] - except: - plist = None - if plist: - if DEBUG: - self.log.info(" _delete_iTunesMetadata_plist():") - self.log.info(" deleting '%s'\n from '%s'" % (pl_name,fpath)) - zf.delete(pl_name) - zf.close() - def _discover_manual_sync_mode(self, wait=0): ''' Assumes pythoncom for windows @@ -1664,18 +1683,6 @@ class ITUNES(DriverBase): zf.close() return (title, author, timestamp) - def _dump_files(self, files, header=None,indent=0): - if header: - msg = '\n%sfiles passed to %s:' % (' '*indent,header) - self.log.info(msg) - self.log.info( "%s%s" % (' '*indent,'-' * len(msg))) - for file in files: - if getattr(file, 'orig_file_path', None) is not None: - self.log.info(" %s%s" % (' '*indent,file.orig_file_path)) - elif getattr(file, 'name', None) is not None: - self.log.info(" %s%s" % (' '*indent,file.name)) - self.log.info() - def _dump_hex(self, src, length=16): ''' ''' @@ -1699,7 +1706,7 @@ class ITUNES(DriverBase): self.log.info() def _dump_update_list(self,header=None,indent=0): - if header: + if header and self.update_list: msg = '\n%sself.update_list %s' % (' '*indent,header) self.log.info(msg) self.log.info( "%s%s" % (' '*indent,'-' * len(msg))) @@ -1718,7 +1725,6 @@ class ITUNES(DriverBase): (' '*indent, ub['title'], ub['author'])) - self.log.info() def _find_device_book(self, search): ''' @@ -2117,35 +2123,6 @@ class ITUNES(DriverBase): self.log.error(" no iPad|Books playlist found") return pl - def _get_fpath(self,file, metadata, format, update_md=False): - ''' - If the database copy will be deleted after upload, we have to - use file (the PersistentTemporaryFile), which will be around until - calibre exits. - If we're using the database copy, delete the plist - ''' - if DEBUG: - self.log.info(" ITUNES._get_fpath()") - - fpath = file - if not getattr(fpath, 'deleted_after_upload', False): - if getattr(file, 'orig_file_path', None) is not None: - # Database copy - fpath = file.orig_file_path - self._delete_iTunesMetadata_plist(fpath) - elif getattr(file, 'name', None) is not None: - # PTF - fpath = file.name - else: - # Recipe - PTF - if DEBUG: - self.log.info(" file will be deleted after upload") - - if format == 'epub' and update_md: - self._update_epub_metadata(fpath, metadata) - - return fpath - def _get_library_books(self): ''' Populate a dict of paths from iTunes Library|Books @@ -2349,6 +2326,7 @@ class ITUNES(DriverBase): self.iTunes = appscript.app('iTunes') self.initial_status = 'already running' + ''' # Read the current storage path for iTunes media cmd = "defaults read com.apple.itunes NSNavLastRootDirectory" proc = subprocess.Popen( cmd, shell=True, cwd=os.curdir, stdout=subprocess.PIPE) @@ -2359,12 +2337,13 @@ class ITUNES(DriverBase): else: self.log.error(" could not confirm valid iTunes.media_dir from %s" % 'com.apple.itunes') self.log.error(" media_dir: %s" % media_dir) + ''' + if DEBUG: self.log.info(" %s %s" % (__appname__, __version__)) self.log.info(" [OSX %s - %s (%s), driver version %d.%d.%d]" % (self.iTunes.name(), self.iTunes.version(), self.initial_status, self.version[0],self.version[1],self.version[2])) - self.log.info(" iTunes_media: %s" % self.iTunes_media) self.log.info(" calibre_library_path: %s" % self.calibre_library_path) if iswindows: @@ -2404,6 +2383,7 @@ class ITUNES(DriverBase): ' iTunes automation interface non-responsive, ' + 'recommend reinstalling iTunes') + ''' # Read the current storage path for iTunes media from the XML file media_dir = '' string = None @@ -2422,13 +2402,13 @@ class ITUNES(DriverBase): self.log.error(" '%s' not found" % media_dir) else: self.log.error(" no media dir found: string: %s" % string) + ''' if DEBUG: self.log.info(" %s %s" % (__appname__, __version__)) self.log.info(" [Windows %s - %s (%s), driver version %d.%d.%d]" % (self.iTunes.Windows[0].name, self.iTunes.Version, self.initial_status, self.version[0],self.version[1],self.version[2])) - self.log.info(" iTunes_media: %s" % self.iTunes_media) self.log.info(" calibre_library_path: %s" % self.calibre_library_path) def _purge_orphans(self,library_books, cached_books): @@ -2478,13 +2458,14 @@ class ITUNES(DriverBase): (self.cached_books[book]['title'] == metadata.title and \ self.cached_books[book]['author'] == authors_to_string(metadata.authors)): self.update_list.append(self.cached_books[book]) - self._remove_from_device(self.cached_books[book]) + if DEBUG: self.log.info( " deleting device book '%s'" % (metadata.title)) - if not getattr(file, 'deleted_after_upload', False): - self._remove_from_iTunes(self.cached_books[book]) - if DEBUG: - self.log.info(" deleting library book '%s'" % metadata.title) + self._remove_from_device(self.cached_books[book]) + + if DEBUG: + self.log.info(" deleting library book '%s'" % metadata.title) + self._remove_from_iTunes(self.cached_books[book]) break else: if DEBUG: @@ -2497,9 +2478,9 @@ class ITUNES(DriverBase): (self.cached_books[book]['title'] == metadata.title and \ self.cached_books[book]['author'] == authors_to_string(metadata.authors)): self.update_list.append(self.cached_books[book]) - self._remove_from_iTunes(self.cached_books[book]) if DEBUG: self.log.info( " deleting library book '%s'" % metadata.title) + self._remove_from_iTunes(self.cached_books[book]) break else: if DEBUG: @@ -2530,96 +2511,105 @@ class ITUNES(DriverBase): def _remove_from_iTunes(self, cached_book): ''' - iTunes does not delete books from storage when removing from database - We only want to delete stored copies if the file is stored in iTunes - We don't want to delete files stored outside of iTunes. - Also confirm that storage_path does not point into calibre's storage. + iTunes does not delete books from storage when removing from database via automation ''' if DEBUG: self.log.info(" ITUNES._remove_from_iTunes():") if isosx: + ''' Manually remove the book from iTunes storage ''' try: - storage_path = os.path.split(cached_book['lib_book'].location().path) - if cached_book['lib_book'].location().path.startswith(self.iTunes_media) and \ - not storage_path[0].startswith(prefs['library_path']): - title_storage_path = storage_path[0] - if DEBUG: - self.log.info(" removing title_storage_path: %s" % title_storage_path) - try: - shutil.rmtree(title_storage_path) - except: - self.log.info(" '%s' not empty" % title_storage_path) - - # Clean up title/author directories - author_storage_path = os.path.split(title_storage_path)[0] - self.log.info(" author_storage_path: %s" % author_storage_path) - author_files = os.listdir(author_storage_path) - if '.DS_Store' in author_files: - author_files.pop(author_files.index('.DS_Store')) - if not author_files: - shutil.rmtree(author_storage_path) - if DEBUG: - self.log.info(" removing empty author_storage_path") - else: - if DEBUG: - self.log.info(" author_storage_path not empty (%d objects):" % len(author_files)) - self.log.info(" %s" % '\n'.join(author_files)) + fp = cached_book['lib_book'].location().path + if DEBUG: + self.log.info(" processing %s" % fp) + if fp.startswith(prefs['library_path']): + self.log.info(" '%s' stored in calibre database, not removed" % cached_book['title']) else: - self.log.info(" '%s' (stored external to iTunes, no files deleted)" % cached_book['title']) + if os.path.exists(fp): + os.remove(fp) + if DEBUG: + self.log.info(" deleting from iTunes storage") + author_storage_path = os.path.split(fp)[0] + try: + os.rmdir(author_storage_path) + if DEBUG: + self.log.info(" removing empty author directory") + except: + author_files = os.listdir(author_storage_path) + if '.DS_Store' in author_files: + author_files.pop(author_files.index('.DS_Store')) + if not author_files: + os.rmdir(author_storage_path) + if DEBUG: + self.log.info(" removing empty author directory") + ''' + else: + if DEBUG: + self.log.info(" author_storage_path not empty:") + self.log.info(" %s" % '\n'.join(author_files)) + ''' + else: + self.log.info(" '%s' does not exist at storage location" % cached_book['title']) except: # We get here if there was an error with .location().path if DEBUG: - self.log.info(" '%s' not in iTunes storage" % cached_book['title']) + self.log.info(" '%s' not found in iTunes storage" % cached_book['title']) + # Delete the book from the iTunes database try: self.iTunes.delete(cached_book['lib_book']) + if DEBUG: + self.log.info(" removing from iTunes database") except: if DEBUG: - self.log.info(" unable to remove '%s' from iTunes" % cached_book['title']) + self.log.info(" unable to remove from iTunes database") elif iswindows: ''' Assume we're wrapped in a pythoncom Windows stores the book under a common author directory, so we just delete the .epub ''' + fp = None try: book = cached_book['lib_book'] - path = book.Location + fp = book.Location except: book = self._find_library_book(cached_book) if book: - path = book.Location + fp = book.Location if book: - if self.iTunes_media and path.startswith(self.iTunes_media) and \ - not path.startswith(prefs['library_path']): - storage_path = os.path.split(path) - if DEBUG: - self.log.info(" removing '%s' at %s" % - (cached_book['title'], path)) - try: - os.remove(path) - except: - self.log.warning(" '%s' not in iTunes storage" % path) - try: - os.rmdir(storage_path[0]) - self.log.info(" removed folder '%s'" % storage_path[0]) - except: - self.log.info(" folder '%s' not found or not empty" % storage_path[0]) - - # Delete from iTunes database + if DEBUG: + self.log.info(" processing %s" % fp) + if fp.startswith(prefs['library_path']): + self.log.info(" '%s' stored in calibre database, not removed" % cached_book['title']) else: - self.log.info(" '%s' (stored external to iTunes, no files deleted)" % cached_book['title']) + if os.path.exists(fp): + os.remove(fp) + if DEBUG: + self.log.info(" deleting from iTunes storage") + author_storage_path = os.path.split(fp)[0] + try: + os.rmdir(author_storage_path) + if DEBUG: + self.log.info(" removing empty author directory") + except: + pass + else: + self.log.info(" '%s' does not exist at storage location" % cached_book['title']) else: if DEBUG: - self.log.info(" '%s' not found in iTunes" % cached_book['title']) + self.log.info(" '%s' not found in iTunes storage" % cached_book['title']) + + # Delete the book from the iTunes database try: book.Delete() + if DEBUG: + self.log.info(" removing from iTunes database") except: if DEBUG: - self.log.info(" unable to remove '%s' from iTunes" % cached_book['title']) + self.log.info(" unable to remove from iTunes database") def title_sorter(self, title): return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', title).rstrip() @@ -2798,7 +2788,7 @@ class ITUNES(DriverBase): if metadata_x.series and self.settings().extra_customization[self.USE_SERIES_AS_CATEGORY]: if DEBUG: self.log.info(" ITUNES._update_iTunes_metadata()") - self.log.info(" using Series name as Genre") + self.log.info(" using Series name '%s' as Genre" % metadata_x.series) # Format the index as a sort key index = metadata_x.series_index @@ -2978,8 +2968,8 @@ class ITUNES(DriverBase): newmi = book.deepcopy_metadata() newmi.template_to_attribute(book, pb) if pb is not None and DEBUG: - self.log.info(" transforming %s using %s:" % (format, pb)) - self.log.info(" title: %s %s" % (book.title, ">>> %s" % + #self.log.info(" transforming %s using %s:" % (format, pb)) + self.log.info(" title: '%s' %s" % (book.title, ">>> '%s'" % newmi.title if book.title != newmi.title else '')) self.log.info(" title_sort: %s %s" % (book.title_sort, ">>> %s" % newmi.title_sort if book.title_sort != newmi.title_sort else '')) @@ -3083,12 +3073,12 @@ class ITUNES_ASYNC(ITUNES): this_book.datetime = parse_date(str(library_books[book].date_added())).timetuple() except: this_book.datetime = time.gmtime() - this_book.db_id = None this_book.device_collections = [] #this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None this_book.library_id = library_books[book] this_book.size = library_books[book].size() this_book.uuid = library_books[book].composer() + this_book.cid = None # Hack to discover if we're running in GUI environment if self.report_progress is not None: this_book.thumbnail = self._generate_thumbnail(this_book.path, library_books[book]) @@ -3124,11 +3114,11 @@ class ITUNES_ASYNC(ITUNES): this_book.datetime = parse_date(str(library_books[book].DateAdded)).timetuple() except: this_book.datetime = time.gmtime() - this_book.db_id = None this_book.device_collections = [] this_book.library_id = library_books[book] this_book.size = library_books[book].Size this_book.uuid = library_books[book].Composer + this_book.cid = None # Hack to discover if we're running in GUI environment if self.report_progress is not None: this_book.thumbnail = self._generate_thumbnail(this_book.path, library_books[book]) From d9a6a0295cc147333329e288d1903d717240a955 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 23 Jun 2011 12:01:02 -0600 Subject: [PATCH 28/31] Fix #801266 (howto suggest original translation changes) --- src/calibre/customize/builtins.py | 2 +- src/calibre/translations/calibre.pot | 436 +++++++++++++-------------- src/calibre/utils/mem.py | 2 + 3 files changed, 221 insertions(+), 219 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index ce7297ef9a..e3b7bef5d8 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -875,7 +875,7 @@ class ActionCopyToLibrary(InterfaceActionBase): class ActionTweakEpub(InterfaceActionBase): name = 'Tweak ePub' actual_plugin = 'calibre.gui2.actions.tweak_epub:TweakEpubAction' - description = _('Make small twekas to epub files in your calibre library') + description = _('Make small tweaks to epub files in your calibre library') class ActionNextMatch(InterfaceActionBase): name = 'Next Match' diff --git a/src/calibre/translations/calibre.pot b/src/calibre/translations/calibre.pot index c371179704..5f080cf28d 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.6\n" -"POT-Creation-Date: 2011-06-22 18:55+MDT\n" -"PO-Revision-Date: 2011-06-22 18:55+MDT\n" +"POT-Creation-Date: 2011-06-23 12:00+MDT\n" +"PO-Revision-Date: 2011-06-23 12:00+MDT\n" "Last-Translator: Automatically generated\n" "Language-Team: LANGUAGE\n" "MIME-Version: 1.0\n" @@ -150,9 +150,9 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1193 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1196 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1281 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:82 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:212 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:231 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:83 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:221 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:240 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/bulk_download.py:119 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:362 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single_download.py:161 @@ -395,7 +395,7 @@ msgid "Copy a book from one calibre library to another" msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:878 -msgid "Make small twekas to epub files in your calibre library" +msgid "Make small tweaks to epub files in your calibre library" msgstr "" #: /home/kovid/work/calibre/src/calibre/customize/builtins.py:883 @@ -833,85 +833,85 @@ msgstr "" msgid "Communicate with S60 phones." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:47 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:46 msgid "

    If you do not want calibre to recognize your Apple iDevice when it is connected to your computer, click Disable Apple Driver.

    To transfer books to your iDevice, click Disable Apple Driver, then use the 'Connect to iTunes' method recommended in the Calibre + iDevices FAQ, using the Connect/Share|Connect to iTunes menu item.

    Enabling the Apple driver for direct connection to iDevices is an unsupported advanced user mode.

    " msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:65 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:64 msgid "Disable Apple driver" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:69 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:68 msgid "Enable Apple driver" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:118 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:117 msgid "Use Series as Category in iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:119 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:118 msgid "Enable to use the series name as the iTunes Genre, iBooks Category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:121 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:120 msgid "Cache covers from iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:123 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:122 msgid "Enable to cache and display covers from iTunes/iBooks" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:180 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:183 msgid "Apple device" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:182 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:185 msgid "Communicate with iTunes/iBooks." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:194 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:197 msgid "Apple device detected, launching iTunes, please wait ..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:196 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:199 msgid "Cannot copy books directly from iDevice. Drag from iTunes Library to desktop, then add to calibre's Library window." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:359 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:362 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:361 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:364 msgid "Updating device metadata listing..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:438 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:477 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1059 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1103 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3109 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3149 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:440 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:479 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1058 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1101 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3098 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3138 msgid "%d of %d" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:484 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1108 -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3155 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:486 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1106 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3144 #: /home/kovid/work/calibre/src/calibre/gui2/ebook_download.py:106 msgid "finished" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:669 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:671 msgid "" "Some books not found in iTunes database.\n" "Delete using the iBooks app.\n" "Click 'Show Details' for a list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1020 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:1022 msgid "" "Some cover art could not be converted.\n" "Click 'Show Details' for a list." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2680 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2669 #: /home/kovid/work/calibre/src/calibre/devices/nook/driver.py:102 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:447 #: /home/kovid/work/calibre/src/calibre/devices/prs505/sony_cache.py:470 @@ -920,8 +920,8 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/devices/usbms/device.py:951 #: /home/kovid/work/calibre/src/calibre/gui2/actions/fetch_news.py:73 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/scheduler.py:445 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1657 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1659 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1661 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1663 #: /home/kovid/work/calibre/src/calibre/library/database2.py:328 #: /home/kovid/work/calibre/src/calibre/library/database2.py:341 #: /home/kovid/work/calibre/src/calibre/library/database2.py:2949 @@ -929,7 +929,7 @@ msgstr "" msgid "News" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2681 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:2670 #: /home/kovid/work/calibre/src/calibre/gui2/catalog/catalog_epub_mobi.py:65 #: /home/kovid/work/calibre/src/calibre/library/catalog.py:652 #: /home/kovid/work/calibre/src/calibre/library/database2.py:2909 @@ -937,7 +937,7 @@ msgstr "" msgid "Catalog" msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3011 +#: /home/kovid/work/calibre/src/calibre/devices/apple/driver.py:3000 msgid "Communicate with iTunes." msgstr "" @@ -1390,35 +1390,35 @@ msgstr "" msgid "Communicate with the Teclast K3/K5 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:36 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:37 msgid "Communicate with the Newsmy reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:47 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:48 msgid "Communicate with the Archos reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:57 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:58 msgid "Communicate with the Pico reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:67 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:68 msgid "Communicate with the iPapyrus reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:78 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:79 msgid "Communicate with the Sovos reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:88 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:89 msgid "Communicate with the Sunstech EB700 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:99 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:100 msgid "Communicate with the Stash W950 reader." msgstr "" -#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:111 +#: /home/kovid/work/calibre/src/calibre/devices/teclast/driver.py:112 msgid "Communicate with the Wexler reader." msgstr "" @@ -2436,14 +2436,14 @@ msgid "TEMPLATE ERROR" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:632 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:564 msgid "No" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:632 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:561 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:564 #: /home/kovid/work/calibre/src/calibre/utils/formatter_functions.py:667 msgid "Yes" msgstr "" @@ -2482,7 +2482,7 @@ msgid "Producer" msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/book/base.py:737 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:879 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:886 #: /home/kovid/work/calibre/src/calibre/gui2/preferences/metadata_sources.py:147 #: /home/kovid/work/calibre/src/calibre/library/field_metadata.py:228 msgid "Comments" @@ -2623,7 +2623,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/metadata/opf2.py:1363 #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/base.py:1499 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:891 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:898 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/models.py:36 msgid "Cover" msgstr "" @@ -2827,7 +2827,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/ebooks/oeb/transforms/cover.py:98 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:176 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:806 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:843 msgid "Book %s of %s" msgstr "" @@ -3346,7 +3346,7 @@ msgid "Choose Files" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/actions/add.py:29 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:621 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:656 msgid "Books" msgstr "" @@ -4626,7 +4626,7 @@ msgstr "" msgid "Collecting data, please wait..." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/add.py:517 +#: /home/kovid/work/calibre/src/calibre/gui2/add.py:520 msgid "Saved" msgstr "" @@ -5792,33 +5792,33 @@ msgid "Set the metadata. The output file will contain as much of this metadata a msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:180 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:749 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:786 msgid "Choose cover for " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:187 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:757 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:794 msgid "Cannot read" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:188 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:758 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:795 msgid "You do not have permission to read the file: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:196 #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:203 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:766 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:803 msgid "Error reading file" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:197 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:767 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:804 msgid "

    There was an error reading from file:
    " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata.py:204 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:777 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:814 msgid " is not a valid picture" msgstr "" @@ -5843,7 +5843,7 @@ msgid "&Title: " msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:165 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:72 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:73 msgid "Change the title of this book" msgstr "" @@ -5871,14 +5871,14 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:171 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:537 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:932 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:973 msgid "Tags categorize the book. This is particularly useful while searching.

    They can be any words or phrases, separated by commas." msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:172 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:544 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:214 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:377 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:403 msgid "&Series:" msgstr "" @@ -5886,7 +5886,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/convert/metadata_ui.py:174 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:545 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:546 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:376 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:402 msgid "List of known series. You can add new series." msgstr "" @@ -6476,16 +6476,16 @@ msgstr "" msgid "Cover browser could not be loaded" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:63 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:88 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:112 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:149 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:183 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:300 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:565 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:606 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:629 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:680 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:66 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:91 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:115 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:152 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:186 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:303 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:568 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:609 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:632 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:683 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:307 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:312 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:128 @@ -6493,67 +6493,67 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:230 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:279 #: /home/kovid/work/calibre/src/calibre/gui2/library/delegates.py:283 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1198 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1239 msgid "Undefined" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:126 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:637 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:129 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:640 msgid "star(s)" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:127 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:638 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:130 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:641 msgid "Unrated" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:170 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:667 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:173 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:670 msgid "Set '%s' to today" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:172 -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:669 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:175 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:672 msgid "Clear '%s'" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:296 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:299 msgid " index:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:365 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:368 msgid "The enumeration \"{0}\" contains an invalid value that will be set to the default" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:520 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:523 msgid "Apply changes" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:713 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:716 msgid "Remove series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:716 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:719 msgid "Automatically number books" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:719 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:722 msgid "Force numbers to start with " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:790 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:793 msgid "The enumeration \"{0}\" contains invalid values that will not appear in the list" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:834 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:837 msgid "Remove all tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:854 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:857 msgid "tags to add" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:861 +#: /home/kovid/work/calibre/src/calibre/gui2/custom_column_widgets.py:864 msgid "tags to remove" msgstr "" @@ -7115,14 +7115,14 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:97 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:211 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:73 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:74 #: /home/kovid/work/calibre/src/calibre/gui2/store/mobileread/adv_search_builder_ui.py:181 #: /home/kovid/work/calibre/src/calibre/gui2/store/search/adv_search_builder_ui.py:199 msgid "&Title:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/comicconf_ui.py:98 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:178 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:186 msgid "&Author(s):" msgstr "" @@ -7279,13 +7279,13 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:271 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1449 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1453 msgid "Invalid author name" msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog.py:272 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1450 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1454 msgid "Author names cannot contain & characters." msgstr "" @@ -7303,7 +7303,7 @@ msgstr "" #: #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/edit_authors_dialog_ui.py:90 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2130 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2134 msgid "F&ind" msgstr "" @@ -7405,7 +7405,7 @@ msgid "Standard metadata" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:60 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:860 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:867 msgid "Custom metadata" msgstr "" @@ -7414,7 +7414,7 @@ msgid "Search/Replace" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk.py:65 -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress.py:76 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress.py:83 msgid "Working" msgstr "" @@ -7545,13 +7545,13 @@ msgid "Specify how the author(s) of this book should be sorted. For example Char msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:530 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:890 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:931 msgid "&Rating:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:531 #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:532 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:891 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:932 msgid "Rating of this book. 0-5 stars" msgstr "" @@ -7621,7 +7621,7 @@ msgid "&Force numbers to start with:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:558 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1182 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1223 msgid "&Date:" msgstr "" @@ -7692,13 +7692,13 @@ msgid "Set from &ebook file(s)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:581 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:503 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:667 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:674 msgid "&Basic metadata" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/metadata_bulk_ui.py:582 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:510 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:517 msgid "&Custom metadata" msgstr "" @@ -8163,7 +8163,7 @@ msgstr "" msgid "The plugin: %s cannot be disabled" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress.py:59 +#: /home/kovid/work/calibre/src/calibre/gui2/dialogs/progress.py:66 msgid "Aborting..." msgstr "" @@ -8628,7 +8628,7 @@ msgid "&Author:" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/search_ui.py:215 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:931 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:972 msgid "Ta&gs:" msgstr "" @@ -8807,12 +8807,12 @@ msgid "%s (was %s)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:85 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1395 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1399 msgid "Item is blank" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/dialogs/tag_list_editor.py:86 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1396 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1400 msgid "An item cannot be set to nothing. Delete it instead." msgstr "" @@ -9301,7 +9301,7 @@ msgid "Regular expression (?P)" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/filename_pattern_ui.py:149 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1087 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1128 msgid "ISBN:" msgstr "" @@ -9525,7 +9525,7 @@ msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:758 #: /home/kovid/work/calibre/src/calibre/gui2/library/models.py:1317 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:806 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:807 msgid "The lookup/search name is \"{0}\"" msgstr "" @@ -9801,215 +9801,215 @@ msgstr "" msgid "Unhandled exception" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:101 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:246 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:102 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:255 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:405 msgid "Permission denied" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:102 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:247 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:103 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:256 #: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:406 msgid "Could not open %s. Is it being used by another program?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:132 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:136 msgid "Specify how this book should be sorted when by title. For example, The Exorcist might be sorted as Exorcist, The." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:134 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:138 msgid "Title &sort:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:142 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:146 msgid " The green color indicates that the current title sort matches the current title" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:145 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:149 msgid " The red color warns that the current title sort does not match the current title. No action is required if this is what you want." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:192 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:201 msgid "Authors changed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:193 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:202 msgid "You have changed the authors for this book. You must save these changes before you can use Manage authors. Do you want to save these changes?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:274 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:287 msgid "" "Specify how the author(s) of this book should be sorted. For example Charles Dickens should be sorted as Dickens, Charles.\n" "If the box is colored green, then text matches the individual author's sort strings. If it is colored red, then the authors and this text do not match." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:279 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:292 msgid "Author s&ort:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:289 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:302 msgid " The green color indicates that the current author sort matches the current author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:292 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:305 msgid " The red color indicates that the current author sort does not match the current author. No action is required if this is what you want." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:433 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:463 msgid "&Number:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:514 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:549 msgid "" "Last modified: %s\n" "\n" "Double click to view" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:531 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:566 msgid "Set the cover for the book from the selected format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:539 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:574 msgid "Set metadata for the book from the selected format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:546 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:581 msgid "Add a format to this book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:553 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:588 msgid "Remove the selected format from this book" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:619 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:654 msgid "Choose formats for " msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:651 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:686 msgid "No permission" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:652 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:687 msgid "You do not have permission to read the following files:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:682 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:683 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:717 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:718 msgid "No format selected" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:695 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:730 msgid "Could not read metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:696 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:731 msgid "Could not read metadata from %s format" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:715 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:752 msgid "&Browse" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:717 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:754 msgid "T&rim" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:719 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:756 msgid "&Remove" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:725 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:762 msgid "Download co&ver" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:726 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:763 msgid "&Generate cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:776 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:813 msgid "Not a valid picture" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:800 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:837 msgid "Specify title and author" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:801 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:838 msgid "You must specify a title and author before generating a cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:819 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:856 msgid "Invalid cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:820 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:857 msgid "Could not change cover as the image is invalid." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:847 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:884 msgid "This book has no cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:849 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:886 msgid "Cover size: %dx%d pixels" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:898 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:939 msgid "stars" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:967 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1008 msgid "Tags changed" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:968 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1009 msgid "You have changed the tags. In order to use the tags editor, you must either discard or apply these changes. Apply changes?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:994 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1035 msgid "I&ds:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:995 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1036 msgid "" "Edit the identifiers for this book. For example: \n" "\n" "%s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1056 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1118 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1097 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1159 msgid "This ISBN number is valid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1059 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1121 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1100 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1162 msgid "This ISBN number is invalid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1084 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1106 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1125 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1147 msgid "Invalid ISBN" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1085 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1126 msgid "Enter an ISBN" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1107 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1148 msgid "The ISBN you entered is not valid. Try again." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1131 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1172 msgid "&Publisher:" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1201 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1242 msgid "Clear date" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1233 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/basic_widgets.py:1275 msgid "Publishe&d:" msgstr "" @@ -10154,33 +10154,33 @@ msgstr "" msgid "Save changes and edit the metadata of %s" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:553 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:755 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:560 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:762 msgid "Change cover" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:610 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:617 msgid "Co&mments" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:650 -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:796 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:657 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:803 msgid "&Metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:655 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:662 msgid "&Cover and formats" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:724 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:731 msgid "C&ustom metadata" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:736 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:743 msgid "&Comments" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:802 +#: /home/kovid/work/calibre/src/calibre/gui2/metadata/single.py:809 msgid "Basic metadata" msgstr "" @@ -12839,13 +12839,13 @@ msgid "Manage %s" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:454 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1869 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1873 msgid "Manage Saved Searches" msgstr "" #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:462 #: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:466 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1867 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1871 msgid "Manage User Categories" msgstr "" @@ -12857,108 +12857,108 @@ msgstr "" msgid "Change sub-categorization scheme" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:801 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:802 msgid "The grouped search term name is \"{0}\"" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1074 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1075 msgid "Changing the authors for several books can take a while. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1079 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1080 msgid "Changing the metadata for that many books can take a while. Are you sure?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1166 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1167 #: /home/kovid/work/calibre/src/calibre/library/database2.py:447 msgid "Searches" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1401 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1421 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1430 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1405 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1425 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1434 msgid "Rename user category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1402 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1406 msgid "You cannot use periods in the name when renaming user categories" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1422 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1431 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1426 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1435 msgid "The name %s is already used" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1454 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1458 msgid "Duplicate search name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1455 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1459 msgid "The saved search name %s is already used." msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1859 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1863 msgid "Manage Authors" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1861 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1865 msgid "Manage Series" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1863 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1867 msgid "Manage Publishers" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1865 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1869 msgid "Manage Tags" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1877 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1881 msgid "Invalid search restriction" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1878 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1882 msgid "The current search restriction is invalid" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1894 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1898 msgid "New Category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1945 -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1948 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1949 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1952 msgid "Delete user category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1946 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1950 msgid "%s is not a user category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1949 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1953 msgid "%s contains items. Do you really want to delete it?" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1970 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1974 msgid "Remove category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1971 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1975 msgid "User category %s does not exist" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1990 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1994 msgid "Add to user category" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1991 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:1995 msgid "A user category %s does not exist" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2114 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2118 msgid "Find item in tag browser" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2117 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2121 msgid "" "Search for items. This is a \"contains\" search; items containing the\n" "text anywhere in the name will be found. You can limit the search\n" @@ -12968,55 +12968,55 @@ msgid "" "containing the text \"foo\"" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2126 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2130 msgid "ALT+f" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2131 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2135 msgid "Find the first/next matching item" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2136 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2140 msgid "Collapse all categories" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2160 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2164 msgid "No More Matches.

    Click Find again to go to first match" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2173 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2177 msgid "Sort by name" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2173 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2177 msgid "Sort by popularity" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2174 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2178 msgid "Sort by average rating" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2177 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2181 msgid "Set the sort order for entries in the Tag Browser" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2184 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2188 msgid "Match all" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2184 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2188 msgid "Match any" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2189 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2193 msgid "When selecting multiple entries in the Tag Browser match any or all of them" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2196 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2200 msgid "Manage authors, tags, etc" msgstr "" -#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2197 +#: /home/kovid/work/calibre/src/calibre/gui2/tag_view.py:2201 msgid "All of these category_managers are available by right-clicking on items in the tag browser above" msgstr "" diff --git a/src/calibre/utils/mem.py b/src/calibre/utils/mem.py index 7dad5e4d0d..4358ec7522 100644 --- a/src/calibre/utils/mem.py +++ b/src/calibre/utils/mem.py @@ -208,6 +208,8 @@ def gc_histogram(): def diff_hists(h1, h2): """Prints differences between two results of gc_histogram().""" for k in h1: + if k not in h2: + h2[k] = 0 if h1[k] != h2[k]: print "%s: %d -> %d (%s%d)" % ( k, h1[k], h2[k], h2[k] > h1[k] and "+" or "", h2[k] - h1[k]) From 9b7363c283c40009517277c61a1719b6cb9de62c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 23 Jun 2011 12:23:55 -0600 Subject: [PATCH 29/31] ... --- src/calibre/manual/conversion.rst | 1 + src/calibre/manual/faq.rst | 44 ++++++++++++++++--------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/calibre/manual/conversion.rst b/src/calibre/manual/conversion.rst index 9244109ba7..029cc636ea 100644 --- a/src/calibre/manual/conversion.rst +++ b/src/calibre/manual/conversion.rst @@ -633,6 +633,7 @@ TXT input supports a number of options to differentiate how paragraphs are detec :guilabel:`Formatting Style: None` Applies no special formatting to the text, the document is converted to html with no other changes. +.. _pdfconversion: Convert PDF documents ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 733adb65ee..97551b403f 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -35,29 +35,11 @@ What are the best source formats to convert? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In order of decreasing preference: LIT, MOBI, EPUB, FB2, HTML, PRC, RTF, PDB, TXT, PDF -Why does the PDF conversion lose some images/tables? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The PDF conversion tries to extract the text and images from the PDF file and convert them to and HTML based ebook. Some PDF files have images in a format that cannot be extracted (vector images). All tables -are also represented as vector diagrams, thus they cannot be extracted. +I converted a PDF file, but the result has various problems? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -How do I convert a collection of HTML files in a specific order? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -In order to convert a collection of HTML files in a specific oder, you have to create a table of contents file. That is, another HTML file that contains links to all the other files in the desired order. Such a file looks like:: +PDF is a terrible format to convert from. For a list of the various issues you will encounter when converting PDF, see: :ref:`pdfconversion`. - - -

    Table of Contents

    -

    - First File
    - Second File
    - . - . - . -

    - - - -Then just add this HTML file to the GUI and use the convert button to create your ebook. .. _char-encoding-faq: @@ -85,6 +67,26 @@ If you have a hand edited TOC in the input document, you can use the TOC detecti Finally, I encourage you to ditch the content TOC and only have a metadata TOC in your ebooks. Metadata TOCs will give the people reading your ebooks a much superior navigation experience (except on the Kindle, where they are essentially the same as a content TOC). +How do I convert a collection of HTML files in a specific order? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In order to convert a collection of HTML files in a specific oder, you have to create a table of contents file. That is, another HTML file that contains links to all the other files in the desired order. Such a file looks like:: + + + +

    Table of Contents

    +

    + First File
    + Second File
    + . + . + . +

    + + + +Then just add this HTML file to the GUI and use the convert button to create your ebook. + + How do I use some of the advanced features of the conversion tools? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You can get help on any individual feature of the converters by mousing over it in the GUI or running ``ebook-convert dummy.html .epub -h`` at a terminal. A good place to start is to look at the following demo files that demonstrate some of the advanced features: From ddfc313efdaec4f326a4d19557cd1d39d65f56ed Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 23 Jun 2011 12:36:19 -0600 Subject: [PATCH 30/31] Fix #800952 (Editing metadata appears to cause a memory leak) --- src/calibre/gui2/tag_view.py | 128 ++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 54 deletions(-) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index c3f17105dc..730c91d37f 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -149,7 +149,8 @@ class TagsView(QTreeView): # {{{ hidden_categories=self.hidden_categories, search_restriction=None, drag_drop_finished=self.drag_drop_finished, - collapse_model=self.collapse_model) + collapse_model=self.collapse_model, + state_map={}) self.pane_is_visible = True # because TagsModel.init did a recount self.sort_by = sort_by self.tag_match = tag_match @@ -173,6 +174,7 @@ class TagsView(QTreeView): # {{{ self.made_connections = True self.refresh_signal_processed = True db.add_listener(self.database_changed) + self.expanded.connect(self.item_expanded) def database_changed(self, event, ids): if self.refresh_signal_processed: @@ -541,6 +543,10 @@ class TagsView(QTreeView): # {{{ return self.isExpanded(idx) def recount(self, *args): + ''' + Rebuild the category tree, expand any categories that were expanded, + reset the search states, and reselect the current node. + ''' if self.disable_recounting or not self.pane_is_visible: return self.refresh_signal_processed = True @@ -548,18 +554,23 @@ class TagsView(QTreeView): # {{{ if not ci.isValid(): ci = self.indexAt(QPoint(10, 10)) path = self.model().path_for_index(ci) if self.is_visible(ci) else None - try: - if not self.model().refresh(): # categories changed! - self.set_new_model() - path = None - except: #Database connection could be closed if an integrity check is happening - pass + expanded_categories, state_map = self.model().get_state() + self.set_new_model(state_map=state_map) + for category in expanded_categories: + self.expand(self.model().index_for_category(category)) self._model.show_item_at_path(path) - # If the number of user categories changed, if custom columns have come or - # gone, or if columns have been hidden or restored, we must rebuild the - # model. Reason: it is much easier than reconstructing the browser tree. - def set_new_model(self, filter_categories_by=None): + def item_expanded(self, idx): + ''' + Called by the expanded signal + ''' + self.setCurrentIndex(idx) + + def set_new_model(self, filter_categories_by=None, state_map={}): + ''' + There are cases where we need to rebuild the category tree without + attempting to reposition the current node. + ''' try: old = getattr(self, '_model', None) if old is not None: @@ -569,7 +580,8 @@ class TagsView(QTreeView): # {{{ search_restriction=self.search_restriction, drag_drop_finished=self.drag_drop_finished, filter_categories_by=filter_categories_by, - collapse_model=self.collapse_model) + collapse_model=self.collapse_model, + state_map=state_map) self.setModel(self._model) except: # The DB must be gone. Set the model to None and hope that someone @@ -752,7 +764,8 @@ class TagsModel(QAbstractItemModel): # {{{ def __init__(self, db, parent, hidden_categories=None, search_restriction=None, drag_drop_finished=None, - filter_categories_by=None, collapse_model='disable'): + filter_categories_by=None, collapse_model='disable', + state_map={}): QAbstractItemModel.__init__(self, parent) # must do this here because 'QPixmap: Must construct a QApplication @@ -776,10 +789,10 @@ class TagsModel(QAbstractItemModel): # {{{ self.filter_categories_by = filter_categories_by self.collapse_model = collapse_model - # get_node_tree cannot return None here, because row_map is empty. Note - # that get_node_tree can indirectly change the user_categories dict. + # Note that _get_category_nodes can indirectly change the + # user_categories dict. - data = self.get_node_tree(config['sort_tags_by']) + data = self._get_category_nodes(config['sort_tags_by']) gst = db.prefs.get('grouped_search_terms', {}) self.root_item = TagTreeItem(icon_map=self.icon_state_map) self.category_nodes = [] @@ -844,7 +857,7 @@ class TagsModel(QAbstractItemModel): # {{{ category_node_map[key] = node last_category_node = node self.category_nodes.append(node) - self.refresh(data=data) + self._create_node_tree(data, state_map) def break_cycles(self): self.root_item.break_cycles() @@ -1121,8 +1134,10 @@ class TagsModel(QAbstractItemModel): # {{{ def set_search_restriction(self, s): self.search_restriction = s - def get_node_tree(self, sort): - old_row_map_len = len(self.row_map) + def _get_category_nodes(self, sort): + ''' + Called by __init__. Do not directly call this method. + ''' self.row_map = [] self.categories = {} @@ -1176,19 +1191,27 @@ class TagsModel(QAbstractItemModel): # {{{ if category in data: # The search category can come and go self.row_map.append(category) self.categories[category] = tb_categories[category]['name'] - - if old_row_map_len != 0 and old_row_map_len != len(self.row_map): - # A category has been added or removed. We must force a rebuild of - # the model - return None return data def refresh(self, data=None): + ''' + Here to trap usages of refresh in the old architecture. Can eventually + be removed. + ''' + print 'TagsModel: refresh called!' + traceback.print_stack() + return False + + def _create_node_tree(self, data, state_map): + ''' + Called by __init__. Do not directly call this method. + ''' sort_by = config['sort_tags_by'] + if data is None: - data = self.get_node_tree(sort_by) # get category data - if data is None: - return False + print '_create_node_tree: no data!' + traceback.print_stack() + return collapse = gprefs['tags_browser_collapse_at'] collapse_model = self.collapse_model @@ -1354,29 +1377,23 @@ class TagsModel(QAbstractItemModel): # {{{ # }}} for category in self.category_nodes: - if len(category.children) > 0: - child_map = category.children - states = [c.tag.state for c in category.child_tags()] - names = [(c.tag.name, c.tag.category) for c in category.child_tags()] - state_map = dict(izip(names, states)) - # temporary sub-categories (the partitioning ones) must follow - # the permanent sub-categories. This will happen naturally if - # the temp ones are added by process_node - ctags = [c for c in child_map if - c.type == TagTreeItem.CATEGORY and not c.temporary] - start = len(ctags) - self.beginRemoveRows(self.createIndex(category.row(), 0, category), - start, len(child_map)-1) - category.children = ctags - for i in range(start, len(child_map)): - child_map[i].break_cycles() - child_map = None - self.endRemoveRows() - else: - state_map = {} + process_one_node(category, state_map.get(category.py_name, {})) - process_one_node(category, state_map) - return True + def get_state(self): + state_map = {} + expanded_categories = [] + for row, category in enumerate(self.category_nodes): + if self.tags_view.isExpanded(self.index(row, 0, QModelIndex())): + expanded_categories.append(category.py_name) + states = [c.tag.state for c in category.child_tags()] + names = [(c.tag.name, c.tag.category) for c in category.child_tags()] + state_map[category.py_name] = dict(izip(names, states)) + return expanded_categories, state_map + + def index_for_category(self, name): + for row, category in enumerate(self.category_nodes): + if category.py_name == name: + return self.index(row, 0, QModelIndex()) def columnCount(self, parent): return 1 @@ -1476,7 +1493,7 @@ class TagsModel(QAbstractItemModel): # {{{ self.tags_view.tag_item_renamed.emit() item.tag.name = val self.rename_item_in_all_user_categories(name, key, val) - self.refresh() # Should work, because no categories can have disappeared + self.refresh_required.emit() self.show_item_at_path(path) return True @@ -1789,19 +1806,22 @@ class TagsModel(QAbstractItemModel): # {{{ return v return None - def show_item_at_path(self, path, box=False): + def show_item_at_path(self, path, box=False, + position=QTreeView.PositionAtCenter): ''' Scroll the browser and open categories to show the item referenced by path. If possible, the item is placed in the center. If box=True, a box is drawn around the item. ''' if path: - self.show_item_at_index(self.index_for_path(path), box) + self.show_item_at_index(self.index_for_path(path), box=box, + position=position) - def show_item_at_index(self, idx, box=False): + def show_item_at_index(self, idx, box=False, + position=QTreeView.PositionAtCenter): if idx.isValid(): self.tags_view.setCurrentIndex(idx) - self.tags_view.scrollTo(idx, QTreeView.PositionAtCenter) + self.tags_view.scrollTo(idx, position) if box: tag_item = idx.internalPointer() tag_item.boxed = True From 62cfdc8023d7acfa83f70249690910fa7a9ee856 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 23 Jun 2011 13:24:54 -0600 Subject: [PATCH 31/31] ... --- src/calibre/gui2/library/views.py | 14 +++++++++----- src/calibre/library/database2.py | 14 ++++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 9aa926f5c5..d25325be17 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -591,8 +591,10 @@ class BooksView(QTableView): # {{{ fmt = prefs['output_format'] def url_for_id(i): - ans = db.format(i, fmt, index_is_id=True, as_path=True, - preserve_filename=True) + try: + ans = db.format_path(i, fmt, index_is_id=True) + except: + ans = None if ans is None: fmts = db.formats(i, index_is_id=True) if fmts: @@ -600,13 +602,15 @@ class BooksView(QTableView): # {{{ else: fmts = [] for f in fmts: - ans = db.format(i, f, index_is_id=True, as_path=True, - preserve_filename=True) + try: + ans = db.format_path(i, f, index_is_id=True) + except: + ans = None if ans is None: ans = db.abspath(i, index_is_id=True) return QUrl.fromLocalFile(ans) - md.setUrls([url_for_id(i) for i in selected[:25]]) + md.setUrls([url_for_id(i) for i in selected]) drag = QDrag(self) col = self.selectionModel().currentIndex().column() md.column_name = self.column_map[col] diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index c9ed5c250a..a3211b3817 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1144,6 +1144,20 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): break return sha.hexdigest() + def format_path(self, index, fmt, index_is_id=False): + ''' + This method is intended to be used only in those rare situations, like + Drag'n Drop, when you absolutely need the path to the original file. + Otherwise, use format(..., as_path=True). + + Note that a networked backend will always return None. + ''' + path = self.format_abspath(index, fmt, index_is_id=index_is_id) + if path is None: + id_ = index if index_is_id else self.id(index) + raise NoSuchFormat('Record %d has no format: %s'%(id_, fmt)) + return path + def format_abspath(self, index, format, index_is_id=False): ''' Return absolute path to the ebook file of format `format`