From f37f5126a23097e07fba9b08c5c2fde6f1601b0a Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Wed, 25 Aug 2010 23:17:17 -0300 Subject: [PATCH 1/9] Fix issue where driver does not properly save attributes to metadata.calibre on adding a book --- src/calibre/devices/kobo/driver.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 35fceb80f7..88d69a6399 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -320,6 +320,7 @@ class KOBO(USBMS): book = Book(prefix, lpath, '', '', '', '', '', '', other=info) if book.size is None: book.size = os.stat(self.normalize_path(path)).st_size + book._new_book = True # Must be before add_book booklists[blist].add_book(book, replace_metadata=True) self.report_progress(1.0, _('Adding books to device metadata listing...')) From abf9f63281341e2de263c2af36b39d50c6ef8048 Mon Sep 17 00:00:00 2001 From: Starson17 Date: Sat, 28 Aug 2010 16:35:44 -0400 Subject: [PATCH 2/9] Add autosort to copy to library function --- src/calibre/gui2/actions/copy_to_library.py | 67 +++++++++++++++++++-- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py index 0dcee7507e..7f42e3b410 100644 --- a/src/calibre/gui2/actions/copy_to_library.py +++ b/src/calibre/gui2/actions/copy_to_library.py @@ -13,6 +13,9 @@ from PyQt4.Qt import QMenu, QToolButton from calibre.gui2.actions import InterfaceAction from calibre.gui2 import error_dialog, Dispatcher from calibre.gui2.dialogs.progress import ProgressDialog +from calibre.utils.config import prefs +import os, re + class Worker(Thread): @@ -24,6 +27,14 @@ class Worker(Thread): self.error = None self.progress = progress self.done = done + self.fuzzy_title_patterns = [(re.compile(pat), repl) for pat, repl in + [ + (r'[\[\](){}<>\'";,:#]', ''), + (r'^(the|a|an) ', ''), + (r'[-._]', ' '), + (r'\s+', ' ') + ] + ] def run(self): try: @@ -38,6 +49,41 @@ class Worker(Thread): self.done() + def add_formats(self, id, paths, newdb, replace=True): + for path in paths: + fmt = os.path.splitext(path)[-1].replace('.', '').upper() + with open(path, 'rb') as f: + newdb.add_format(id, fmt, f, index_is_id=True, + notify=False, replace=replace) + + def fuzzy_title(self, title): + title = title.strip().lower() + for pat, repl in self.fuzzy_title_patterns: + title = pat.sub(repl, title) + return title + + def find_identical_books(self, mi, newdb): + identical_book_ids = set([]) + if mi.authors: + try: + query = u' and '.join([u'author:"=%s"'%(a.replace('"', '')) for a in + mi.authors]) + except ValueError: + return identical_book_ids + try: + book_ids = newdb.data.parse(query) + except: + import traceback + traceback.print_exc() + return identical_book_ids + for book_id in book_ids: + fbook_title = newdb.title(book_id, index_is_id=True) + fbook_title = self.fuzzy_title(fbook_title) + mbook_title = self.fuzzy_title(mi.title) + if fbook_title == mbook_title: + identical_book_ids.add(book_id) + return identical_book_ids + def doit(self): from calibre.library.database2 import LibraryDatabase2 newdb = LibraryDatabase2(self.loc) @@ -49,12 +95,21 @@ class Worker(Thread): else: fmts = fmts.split(',') paths = [self.db.format_abspath(x, fmt, index_is_id=True) for fmt in fmts] - newdb.import_book(mi, paths, notify=False, import_hooks=False) - co = self.db.conversion_options(x, 'PIPE') - if co is not None: - newdb.set_conversion_options(x, 'PIPE', co) - - + if prefs['add_formats_to_existing']: + identical_book_list = self.find_identical_books(mi, newdb) + if identical_book_list: # books with same author and nearly same title exist in newdb + for identical_book in identical_book_list: + self.add_formats(identical_book, paths, newdb, replace=False) + else: + newdb.import_book(mi, paths, notify=False, import_hooks=False) + co = self.db.conversion_options(x, 'PIPE') + if co is not None: + newdb.set_conversion_options(x, 'PIPE', co) + else: + newdb.import_book(mi, paths, notify=False, import_hooks=False) + co = self.db.conversion_options(x, 'PIPE') + if co is not None: + newdb.set_conversion_options(x, 'PIPE', co) class CopyToLibraryAction(InterfaceAction): From bac3965847a87742d674cb003f7c86d588c51c77 Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Mon, 30 Aug 2010 23:15:47 -0300 Subject: [PATCH 3/9] Fix bug 6015 - Metadata was not being resaved to the metadata file after the device was reconnected --- src/calibre/devices/kobo/books.py | 8 +++----- src/calibre/devices/kobo/driver.py | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/calibre/devices/kobo/books.py b/src/calibre/devices/kobo/books.py index 124a10b380..9da99d75c8 100644 --- a/src/calibre/devices/kobo/books.py +++ b/src/calibre/devices/kobo/books.py @@ -23,7 +23,7 @@ class Book(MetaInformation): 'uuid', 'device_collections', ] - def __init__(self, prefix, lpath, title, authors, mime, date, ContentType, thumbnail_name, other=None): + def __init__(self, prefix, lpath, title, authors, mime, date, ContentType, thumbnail_name, size=None, other=None): MetaInformation.__init__(self, '') self.device_collections = [] @@ -42,10 +42,8 @@ class Book(MetaInformation): else: self.authors = [authors] self.mime = mime - try: - self.size = os.path.getsize(self.path) - except OSError: - self.size = 0 + + self.size = size # will be set later if None try: if ContentType == '6': self.datetime = time.strptime(date, "%Y-%m-%dT%H:%M:%S.%f") diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 88d69a6399..310f642adc 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -94,19 +94,19 @@ class KOBO(USBMS): idx = bl_cache.get(lpath, None) 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') #print "Image name Normalized: " + imagename if imagename is not None: bl[idx].thumbnail = ImageWrapper(imagename) - bl_cache[lpath] = None if ContentType != '6': if self.update_metadata_item(bl[idx]): # print 'update_metadata_item returned true' changed = True bl[idx].device_collections = playlist_map.get(lpath, []) else: - book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID) + book = self.book_from_path(prefix, lpath, title, authors, mime, date, ContentType, ImageID) # print 'Update booklist' if bl.add_book(book, replace_metadata=False): changed = True @@ -381,3 +381,19 @@ class KOBO(USBMS): return USBMS.get_file(self, path, *args, **kwargs) + @classmethod + def book_from_path(cls, prefix, lpath, title, authors, mime, date, ContentType, ImageID): + from calibre.ebooks.metadata import MetaInformation + + if cls.settings().read_metadata or cls.MUST_READ_METADATA: + mi = cls.metadata_from_path(cls.normalize_path(os.path.join(prefix, lpath))) + else: + from calibre.ebooks.metadata.meta import metadata_from_filename + mi = metadata_from_filename(cls.normalize_path(os.path.basename(lpath)), + cls.build_template_regexp()) + if mi is None: + mi = MetaInformation(os.path.splitext(os.path.basename(lpath))[0], + [_('Unknown')]) + size = os.stat(cls.normalize_path(os.path.join(prefix, lpath))).st_size + book = Book(prefix, lpath, title, authors, mime, date, ContentType, ImageID, size=size, other=mi) + return book From e4377bf734d78e3d22ad616d607d9d85f8cd6282 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 31 Aug 2010 12:48:13 -0600 Subject: [PATCH 4/9] Winnipeg Free Press by buyo --- resources/recipes/winnipeg_free_press.recipe | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 resources/recipes/winnipeg_free_press.recipe diff --git a/resources/recipes/winnipeg_free_press.recipe b/resources/recipes/winnipeg_free_press.recipe new file mode 100644 index 0000000000..8c59dff645 --- /dev/null +++ b/resources/recipes/winnipeg_free_press.recipe @@ -0,0 +1,30 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class WinnipegFreePress(BasicNewsRecipe): + title = u'Winnipeg Free Press' + __author__ = 'buyo' + description = 'News from Winnipeg, Manitoba, Canada' + oldest_article = 1 + max_articles_per_feed = 15 + category = 'News, Winnipeg, Canada' + cover_url = 'http://media.winnipegfreepress.com/designimages/winnipegfreepress_WFP.gif' + no_stylesheets = True + encoding = 'UTF-8' + remove_javascript = True + use_embedded_content = False + language = 'en_CA' + + feeds = [(u'Breaking News', u'http://www.winnipegfreepress.com/rss?path=/breakingnews'), + (u'Local News',u'http://www.winnipegfreepress.com/rss?path=/local'), + (u'Breaking Business News',u'http://www.winnipegfreepress.com/rss?path=/business/finance'), + (u'Business',u'http://www.winnipegfreepress.com/rss?path=/business'), + (u'Editorials',u'http://www.winnipegfreepress.com/rss?path=/opinion/editorials'), + (u'Views from the West',u'http://www.winnipegfreepress.com/rss?path=/opinion/westview'), + (u'Life & Style',u'http://www.winnipegfreepress.com/rss?path=/life'), + (u'Food & Drink',u'http://www.winnipegfreepress.com/rss?path=/life/food') + ] + + keep_only_tags = [ + dict(name='div', attrs={'id':'article_header'}), + dict(name='div', attrs={'class':'article'}), + ] From 76867d5da5130698dc93d57e14e62a7235eac53e Mon Sep 17 00:00:00 2001 From: Timothy Legge Date: Tue, 31 Aug 2010 21:04:36 -0300 Subject: [PATCH 5/9] Fix bug that sent full path to metadata instead of lpath. Caused metadata to get incorrectly replaced. --- src/calibre/devices/kobo/driver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 310f642adc..5e1c752c76 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -316,7 +316,6 @@ class KOBO(USBMS): lpath = lpath[1:] #print "path: " + lpath #book = self.book_class(prefix, lpath, other=info) - lpath = self.normalize_path(prefix + lpath) book = Book(prefix, lpath, '', '', '', '', '', '', other=info) if book.size is None: book.size = os.stat(self.normalize_path(path)).st_size From 3c4ab7fb96fcb73ebaf68e4e7d1c272b6c7776bb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Sep 2010 10:31:53 -0600 Subject: [PATCH 6/9] Fix bug in email sending when using an SSL connection --- src/calibre/utils/smtp.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/calibre/utils/smtp.py b/src/calibre/utils/smtp.py index 3246810010..350d762e59 100644 --- a/src/calibre/utils/smtp.py +++ b/src/calibre/utils/smtp.py @@ -81,12 +81,7 @@ def sendmail(msg, from_, to, localhost=None, verbose=0, timeout=30, for x in to: return sendmail_direct(from_, x, msg, timeout, localhost, verbose) import smtplib - class SMTP_SSL(smtplib.SMTP_SSL): # Workaround for bug in smtplib.py - def _get_socket(self, host, port, timeout): - smtplib.SMTP_SSL._get_socket(self, host, port, timeout) - return self.sock - - cls = smtplib.SMTP if encryption == 'TLS' else SMTP_SSL + cls = smtplib.SMTP if encryption == 'TLS' else smtplib.SMTP_SSL timeout = None # Non-blocking sockets sometimes don't work port = int(port) s = cls(timeout=timeout, local_hostname=localhost) From 4e73c440926da622f805f29126236e216b5a069e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Sep 2010 11:29:46 -0600 Subject: [PATCH 7/9] JetBook driver: Only use JetBook naming scheme for txt, pdf and fb2 files. Fixes #6638 (Improve the naming scheme for Aluratek Libre (and perhaps Jetbook)) --- src/calibre/devices/jetbook/driver.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/devices/jetbook/driver.py b/src/calibre/devices/jetbook/driver.py index 6a3bc635ff..0d2e35e4fc 100644 --- a/src/calibre/devices/jetbook/driver.py +++ b/src/calibre/devices/jetbook/driver.py @@ -50,6 +50,8 @@ class JETBOOK(USBMS): def filename_callback(self, fname, mi): fileext = os.path.splitext(os.path.basename(fname))[1] + if fileext.lower() not in ('txt', 'pdf', 'fb2'): + return fname title = mi.title if mi.title else 'Unknown' title = title.replace(' ', '_') au = mi.format_authors() From e3384952708aee3fbad2cf8fd9ac8ba0f662083b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Sep 2010 16:10:30 -0600 Subject: [PATCH 8/9] Fix #6644 (Add Support for Dell Streak Running Android 2.1) --- src/calibre/devices/android/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index f5736ae6b1..7a451112c0 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -41,7 +41,7 @@ class ANDROID(USBMS): 0x502 : { 0x3203 : [0x0100]}, # Dell - 0x413c : { 0xb007 : [0x0100]}, + 0x413c : { 0xb007 : [0x0100, 0x0224]}, # Eken? 0x040d : { 0x0851 : [0x0001]}, From a2f46d86af8ecf0deb6154fb0344d096cfd3a3ea Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 1 Sep 2010 20:34:47 -0600 Subject: [PATCH 9/9] Managing multiple libraries: Allow renaming/deleting libraries from the Choose library menu --- src/calibre/gui2/actions/choose_library.py | 68 ++++++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index a5184dfcca..955c751d19 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -5,15 +5,16 @@ __license__ = 'GPL v3' __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os +import os, shutil from functools import partial -from PyQt4.Qt import QMenu, Qt +from PyQt4.Qt import QMenu, Qt, QInputDialog from calibre import isbytestring from calibre.constants import filesystem_encoding from calibre.utils.config import prefs -from calibre.gui2 import gprefs, warning_dialog, Dispatcher +from calibre.gui2 import gprefs, warning_dialog, Dispatcher, error_dialog, \ + question_dialog from calibre.gui2.actions import InterfaceAction class LibraryUsageStats(object): @@ -66,6 +67,13 @@ class LibraryUsageStats(object): loc = loc[:-1] return loc.split('/')[-1] + def rename(self, location, newloc): + newloc = self.canonicalize_path(newloc) + stats = self.stats.pop(location, None) + if stats is not None: + self.stats[newloc] = stats + self.write_stats() + class ChooseLibraryAction(InterfaceAction): @@ -80,7 +88,7 @@ class ChooseLibraryAction(InterfaceAction): type=Qt.QueuedConnection) self.stats = LibraryUsageStats() - self.create_action(spec=(_('Switch to library...'), 'lt.png', None, + self.create_action(spec=(_('Switch/create library...'), 'lt.png', None, None), attr='action_choose') self.action_choose.triggered.connect(self.choose_library, type=Qt.QueuedConnection) @@ -90,7 +98,13 @@ class ChooseLibraryAction(InterfaceAction): self.quick_menu = QMenu(_('Quick switch')) self.quick_menu_action = self.choose_menu.addMenu(self.quick_menu) - self.qs_separator = self.choose_menu.addSeparator() + self.rename_menu = QMenu(_('Rename library')) + self.rename_menu_action = self.choose_menu.addMenu(self.rename_menu) + self.delete_menu = QMenu(_('Delete library')) + self.delete_menu_action = self.choose_menu.addMenu(self.delete_menu) + + self.rename_separator = self.choose_menu.addSeparator() + self.switch_actions = [] for i in range(5): ac = self.create_action(spec=('', None, None, None), @@ -123,9 +137,15 @@ class ChooseLibraryAction(InterfaceAction): ac.setVisible(False) self.quick_menu.clear() self.qs_locations = [i[1] for i in locations] + self.rename_menu.clear() + self.delete_menu.clear() for name, loc in locations: self.quick_menu.addAction(name, Dispatcher(partial(self.switch_requested, loc))) + self.rename_menu.addAction(name, Dispatcher(partial(self.rename_requested, + name, loc))) + self.delete_menu.addAction(name, Dispatcher(partial(self.delete_requested, + name, loc))) for i, x in enumerate(locations[:len(self.switch_actions)]): name, loc = x @@ -134,12 +154,50 @@ class ChooseLibraryAction(InterfaceAction): ac.setVisible(True) self.quick_menu_action.setVisible(bool(locations)) + self.rename_menu_action.setVisible(bool(locations)) + self.delete_menu_action.setVisible(bool(locations)) def location_selected(self, loc): enabled = loc == 'library' self.qaction.setEnabled(enabled) + def rename_requested(self, name, location): + loc = location.replace('/', os.sep) + base = os.path.dirname(loc) + newname, ok = QInputDialog.getText(self.gui, _('Rename') + ' ' + name, + '

'+_('Choose a new name for the library %s. ')%name + + '

'+_('Note that the actual library folder will be renamed.'), + text=name) + newname = unicode(newname) + if not ok or not newname or newname == name: + return + newloc = os.path.join(base, newname) + if os.path.exists(newloc): + return error_dialog(self.gui, _('Already exists'), + _('The folder %s already exists. Delete it first.') % + newloc, show=True) + os.rename(loc, newloc) + self.stats.rename(location, newloc) + self.build_menus() + + def delete_requested(self, name, location): + loc = location.replace('/', os.sep) + if not question_dialog(self.gui, _('Are you sure?'), '

'+ + _('All files from %s will be ' + 'permanently deleted. Are you sure?') % loc, + show_copy_button=False): + return + exists = self.gui.library_view.model().db.exists_at(loc) + if exists: + try: + shutil.rmtree(loc, ignore_errors=True) + except: + pass + self.stats.remove(location) + self.build_menus() + + def switch_requested(self, location): if not self.change_library_allowed(): return