From 9106694fd0cc3857419554b7a42311e3c6f8e475 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 17 Sep 2010 20:03:32 +0100 Subject: [PATCH 01/14] Clean up label names --- src/calibre/gui2/dialogs/metadata_bulk.ui | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index d01d3e2ea8..c12182476c 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -47,7 +47,7 @@ - + &Author(s): @@ -128,7 +128,7 @@ - + &Publisher: @@ -148,7 +148,7 @@ - + Add ta&gs: @@ -244,7 +244,7 @@ - + Remove &format: @@ -338,7 +338,7 @@ Future conversion of these books will use the default settings. - + Search &field: @@ -348,7 +348,7 @@ Future conversion of these books will use the default settings. - + &Search for: @@ -358,7 +358,7 @@ Future conversion of these books will use the default settings. - + &Replace with: @@ -377,7 +377,7 @@ Future conversion of these books will use the default settings. - + Apply function &after replace: @@ -390,7 +390,7 @@ Future conversion of these books will use the default settings. - + Test &text @@ -400,7 +400,7 @@ Future conversion of these books will use the default settings. - + Test re&sult @@ -410,7 +410,7 @@ Future conversion of these books will use the default settings. - + Your test: From 122c2deffce9a7851cdfae674a95ad34e8e40c7b Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 17 Sep 2010 23:00:29 +0100 Subject: [PATCH 02/14] Reset selection in bulk metadata edit --- src/calibre/gui2/actions/edit_metadata.py | 3 +++ src/calibre/gui2/library/views.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index ac04652efa..ebac7c9a4f 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -173,6 +173,8 @@ class EditMetadataAction(InterfaceAction): ''' rows = [r.row() for r in \ self.gui.library_view.selectionModel().selectedRows()] + db = self.gui.library_view.model().db + ids = [db.id(r) for r in rows] if not rows or len(rows) == 0: d = error_dialog(self.gui, _('Cannot edit metadata'), _('No books selected')) @@ -191,6 +193,7 @@ class EditMetadataAction(InterfaceAction): self.gui.tags_view.recount() if self.gui.cover_flow: self.gui.cover_flow.dataChanged() + self.gui.library_view.select_rows_with_id(ids) # Merge books {{{ def merge_books(self, safe_merge=False): diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index d67d286aeb..b364bd5f70 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -479,6 +479,21 @@ class BooksView(QTableView): # {{{ sm = self.selectionModel() sm.select(index, sm.ClearAndSelect|sm.Rows) + def select_rows_with_id(self, ids): + ''' + Loop through the visible rows, selecting any that have db_id in ids + ''' + ids = set(ids) + selmode = self.selectionMode() + self.setSelectionMode(QAbstractItemView.MultiSelection) + self.clearSelection() + db = self.model().db + loc = db.FIELD_MAP['id'] + for i in range(0, len(db.data)): + if db.get_property(i, index_is_id=False, loc=loc) in ids: + self.selectRow(i) + self.setSelectionMode(selmode) + def close(self): self._model.close() From ade36c94fa1b2991774316dea30f9ddd1db64548 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 17 Sep 2010 23:03:00 +0100 Subject: [PATCH 03/14] Fix a comment. Sigh... --- src/calibre/gui2/library/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index b364bd5f70..77d15e69c6 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -481,7 +481,7 @@ class BooksView(QTableView): # {{{ def select_rows_with_id(self, ids): ''' - Loop through the visible rows, selecting any that have db_id in ids + Loop through the visible rows, selecting any that have db_id in list ids ''' ids = set(ids) selmode = self.selectionMode() From 6d2c0268daf595fb87bdf31c1594c5ce774aba58 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Fri, 17 Sep 2010 23:17:25 +0100 Subject: [PATCH 04/14] Fix sorting problems (author_sort) --- src/calibre/library/caches.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/library/caches.py b/src/calibre/library/caches.py index 4f795ab733..211baeb634 100644 --- a/src/calibre/library/caches.py +++ b/src/calibre/library/caches.py @@ -589,9 +589,9 @@ class ResultCache(SearchQueryParser): if field not in self.field_metadata.iterkeys(): if field in ('author', 'tag', 'comment'): field += 's' - if field == 'date': field = 'timestamp' - elif field == 'title': field = 'sort' - elif field == 'authors': field = 'author_sort' + if field == 'date': field = 'timestamp' + elif field == 'title': field = 'sort' + elif field == 'authors': field = 'author_sort' return field def sort(self, field, ascending, subsort=False): From 9c911e1d94288780bfc805968015fc9371a7f5fa Mon Sep 17 00:00:00 2001 From: John Schember Date: Fri, 17 Sep 2010 22:02:09 -0400 Subject: [PATCH 05/14] =?UTF-8?q?Fix=20problem=201=20in=20#6826:=20Remove?= =?UTF-8?q?=20space=20before=20=C4=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/calibre/ebooks/conversion/preprocess.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/ebooks/conversion/preprocess.py b/src/calibre/ebooks/conversion/preprocess.py index 256bcce6fc..eada85fc56 100644 --- a/src/calibre/ebooks/conversion/preprocess.py +++ b/src/calibre/ebooks/conversion/preprocess.py @@ -215,8 +215,8 @@ class HTMLPreProcessor(object): (re.compile(u'¸\s*()*\s*C', re.UNICODE), lambda match: u'Ç'), # ˛ - (re.compile(u'˛\s*()*\s*a', re.UNICODE), lambda match: u'ą'), - (re.compile(u'˛\s*()*\s*A', re.UNICODE), lambda match: u'Ą'), + (re.compile(u'\s*˛\s*()*\s*a', re.UNICODE), lambda match: u'ą'), + (re.compile(u'\s*˛\s*()*\s*A', re.UNICODE), lambda match: u'Ą'), (re.compile(u'˛\s*()*\s*e', re.UNICODE), lambda match: u'ę'), (re.compile(u'˛\s*()*\s*E', re.UNICODE), lambda match: u'Ę'), From ad8911b65f3e8a940abc5b01e9dc878ce0f616c3 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Sat, 18 Sep 2010 08:01:22 -0300 Subject: [PATCH 06/14] Fix 6850 - Kobo driver did not properly handle SD Cards in Im_Reading list finctionality --- src/calibre/devices/kobo/driver.py | 52 ++++++++++++++++++------------ 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index a2be629449..762a05d193 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -424,7 +424,7 @@ class KOBO(USBMS): paths[source_id] = os.path.join(prefix, *(path.split('/'))) return paths - def update_device_database_collections(self, booklists, collections_attributes): + def update_device_database_collections(self, booklists, collections_attributes, oncard): # debug_print('Starting update_device_database_collections', collections_attributes) # Force collections_attributes to be 'tags' as no other is currently supported @@ -433,23 +433,31 @@ class KOBO(USBMS): collections = booklists.get_collections(collections_attributes) # debug_print('Collections', collections) + + # Create a connection to the sqlite database + # Needs to be outside books collection as in the case of removing + # the last book from the collection the list of books is empty + # and the removal of the last book would not occur + connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite') + cursor = connection.cursor() + + # Reset Im_Reading list in the database + if oncard == 'carda': + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID like \'file:///mnt/sd/%\'' + elif oncard != 'carda' and oncard != 'cardb': + query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null and ContentID not like \'file:///mnt/sd/%\'' + + try: + cursor.execute (query) + except: + debug_print('Database Exception: Unable to reset Im_Reading list') + raise + else: +# debug_print('Commit: Reset Im_Reading list') + connection.commit() + for category, books in collections.items(): if category == 'Im_Reading': - # Create a connection to the sqlite database - connection = sqlite.connect(self._main_prefix + '.kobo/KoboReader.sqlite') - cursor = connection.cursor() - - # Reset Im_Reading list in the database - query= 'update content set ReadStatus=0, FirstTimeReading = \'true\' where BookID is Null' - try: - cursor.execute (query) - except: - debug_print('Database Exception: Unable to reset Im_Reading list') - raise - else: -# debug_print('Commit: Reset Im_Reading list') - connection.commit() - for book in books: # debug_print('Title:', book.title, 'lpath:', book.path) book.device_collections = ['Im_Reading'] @@ -471,8 +479,8 @@ class KOBO(USBMS): connection.commit() # debug_print('Database: Commit create Im_Reading list') - cursor.close() - connection.close() + cursor.close() + connection.close() # debug_print('Finished update_device_database_collections', collections_attributes) @@ -494,12 +502,16 @@ class KOBO(USBMS): #debug_print('KOBO: collection fields:', collections) for i, blist in blists.items(): - self.update_device_database_collections(blist, collections) + if i == 0: + oncard = 'main' + else: + oncard = 'carda' + self.update_device_database_collections(blist, collections, oncard) USBMS.sync_booklists(self, booklists, end_session=end_session) #debug_print('KOBO: finished sync_booklists') def rebuild_collections(self, booklist, oncard): collections_attributes = [] - self.update_device_database_collections(booklist, collections_attributes) + self.update_device_database_collections(booklist, collections_attributes, oncard) From 21006be2926620a7e307adb4001b2d30abbac7f6 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 18 Sep 2010 12:35:03 +0100 Subject: [PATCH 07/14] Fix click on link in search/replace not opening browser --- src/calibre/gui2/dialogs/metadata_bulk.ui | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.ui b/src/calibre/gui2/dialogs/metadata_bulk.ui index 18d6760ea7..aca7b0cb75 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.ui +++ b/src/calibre/gui2/dialogs/metadata_bulk.ui @@ -328,6 +328,9 @@ Future conversion of these books will use the default settings. true + + true + From 9139da12eb488262521a6be0e250e802023a9a6d Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Sat, 18 Sep 2010 15:01:31 +0100 Subject: [PATCH 08/14] Change vertical bars to commas in author names --- src/calibre/gui2/dialogs/metadata_bulk.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index b0ba049a7f..2e5c7838ca 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -220,6 +220,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): if val: val.sort(cmp=lambda x,y: cmp(x.lower(), y.lower())) val = val[0] + if txt == 'authors': + val = val.replace('|', ',') else: val = '' else: @@ -303,6 +305,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): # The standard tags and authors values want to be lists. # All custom columns are to be strings val = fm['is_multiple'].join(val) + elif field == 'authors': + val = [v.replace('|', ',') for v in val] else: val = apply_pattern(val) From 163b9156ec4862194af5175eae428e0c340649d6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 18 Sep 2010 10:19:30 -0600 Subject: [PATCH 09/14] ... --- src/calibre/manual/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 781048666b..beea30acb2 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -81,7 +81,7 @@ Device Integration What devices does |app| support? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -At the moment |app| has full support for the SONY PRS 300/500/505/600/700/900, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle 1/2/3/DX/DXG, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, SpringDesign Alex, Kobo Reader, various Android phones and the iPhone/iPad. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk. +At the moment |app| has full support for the SONY PRS line, Barnes & Noble Nook, Cybook Gen 3/Opus, Amazon Kindle line, Entourage Edge, Longshine ShineBook, Ectaco Jetbook, BeBook/BeBook Mini, Irex Illiad/DR1000, Foxit eSlick, PocketBook 360, Italica, eClicto, Iriver Story, Airis dBook, Hanvon N515, Binatone Readme, Teclast K3, SpringDesign Alex, Kobo Reader, various Android phones and the iPhone/iPad. In addition, using the :guilabel:`Save to disk` function you can use it with any ebook reader that exports itself as a USB disk. How can I help get my device supported in |app|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 7a8c447e53a0e2152e0368128fcc87045426d090 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 18 Sep 2010 10:30:26 -0600 Subject: [PATCH 10/14] Fix #6854 (Please strip leading and trailing spaces from edited metadata) --- src/calibre/gui2/dialogs/metadata_single.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index d07eac7670..26dbda6ca4 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -751,20 +751,22 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): try: if self.formats_changed: self.sync_formats() - title = unicode(self.title.text()) + title = unicode(self.title.text()).strip() self.db.set_title(self.id, title, notify=False) - au = unicode(self.authors.text()) + au = unicode(self.authors.text()).strip() if au: self.db.set_authors(self.id, string_to_authors(au), notify=False) - aus = unicode(self.author_sort.text()) + aus = unicode(self.author_sort.text()).strip() if aus: self.db.set_author_sort(self.id, aus, notify=False, commit=False) self.db.set_isbn(self.id, - re.sub(r'[^0-9a-zA-Z]', '', unicode(self.isbn.text())), + re.sub(r'[^0-9a-zA-Z]', '', + unicode(self.isbn.text()).strip()), notify=False, commit=False) self.db.set_rating(self.id, 2*self.rating.value(), notify=False, commit=False) - self.db.set_publisher(self.id, unicode(self.publisher.currentText()), + self.db.set_publisher(self.id, + unicode(self.publisher.currentText()).strip(), notify=False, commit=False) self.db.set_tags(self.id, [x.strip() for x in unicode(self.tags.text()).split(',')], notify=False, commit=False) @@ -773,7 +775,8 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): commit=False) self.db.set_series_index(self.id, self.series_index.value(), notify=False, commit=False) - self.db.set_comment(self.id, unicode(self.comments.toPlainText()), + self.db.set_comment(self.id, + unicode(self.comments.toPlainText()).strip(), notify=False, commit=False) d = self.pubdate.date() d = qt_to_dt(d) From b85bc6517a017bd420b195c6d91d4c4c02fc2821 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 18 Sep 2010 10:35:41 -0600 Subject: [PATCH 11/14] Fix #6860 (Harvard Business Reviw) --- resources/recipes/hbr.recipe | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/recipes/hbr.recipe b/resources/recipes/hbr.recipe index 3d1e8ccfac..cd7dcd2061 100644 --- a/resources/recipes/hbr.recipe +++ b/resources/recipes/hbr.recipe @@ -33,9 +33,9 @@ class HBR(BasicNewsRecipe): def get_browser(self): br = BasicNewsRecipe.get_browser(self) br.open(self.LOGIN_URL) - br.select_form(name='signInForm') - br['signInForm:username'] = self.username - br['signInForm:password'] = self.password + br.select_form(name='signin-form') + br['signin-form:username'] = self.username + br['signin-form:password'] = self.password raw = br.submit().read() if 'My Account' not in raw: raise Exception('Failed to login, are you sure your username and password are correct?') From 5f2c784cb0f86b3dcf5ad6a1b3c9f71bde1cf4cd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 18 Sep 2010 12:59:10 -0600 Subject: [PATCH 12/14] Fix #6861 (0.7.19 stanza catalog not working. Giving 400 Bad Request) --- src/calibre/library/server/content.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py index ecb467b4c2..95794a8c1d 100644 --- a/src/calibre/library/server/content.py +++ b/src/calibre/library/server/content.py @@ -56,7 +56,7 @@ class ContentServer(object): def sort(self, items, field, order): field = self.db.data.sanitize_sort_field_name(field) - if field not in ('title', 'authors', 'rating', 'timestamp', 'tags', 'size', 'series'): + if field not in self.db.field_metadata.field_keys(): raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field) keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata) items.sort(key=keyg, reverse=not order) From 7d5da9f48d59254db290c6bf87a4111a0d633fbc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 18 Sep 2010 19:34:40 -0600 Subject: [PATCH 13/14] GUI deveice detection: Handle case when user yanks connected device before device connection handler is called. Fixes #6864 (Unhandled exception, probably when trying to connect to iTunes/iPad) --- src/calibre/gui2/device.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index b20cd7594f..ae3141db56 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -707,6 +707,10 @@ class DeviceMixin(object): # {{{ ''' Called when a device is connected to the computer. ''' + # This can happen as this function is called in a queued connection and + # the user could have yanked the device in the meantime + if connected and not self.device_manager.is_device_connected: + connected = False self.set_device_menu_items_state(connected) if connected: self.device_manager.get_device_information(\ From 25bacf9a97f34a360e150487b65cc940bc01988f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 18 Sep 2010 19:59:31 -0600 Subject: [PATCH 14/14] Initialize interface actions in the order they are defined to prevent interaction dependencies from creating a problem --- src/calibre/gui2/ui.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index f40eed1fcd..f8d50d1cd2 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -38,6 +38,7 @@ from calibre.gui2.init import LibraryViewMixin, LayoutMixin from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin from calibre.gui2.tag_view import TagBrowserMixin +from calibre.utils.ordered_dict import OrderedDict class Listener(Thread): # {{{ @@ -97,7 +98,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ MainWindow.__init__(self, opts, parent) self.opts = opts self.device_connected = None - acmap = {} + acmap = OrderedDict() for action in interface_actions(): mod, cls = action.actual_plugin.split(':') ac = getattr(__import__(mod, fromlist=['1'], level=0), cls)(self,