diff --git a/recipes/aksiyon_derigisi.recipe b/recipes/aksiyon_derigisi.recipe index bc15b39095..d7be418413 100644 --- a/recipes/aksiyon_derigisi.recipe +++ b/recipes/aksiyon_derigisi.recipe @@ -20,6 +20,7 @@ class Aksiyon (BasicNewsRecipe): auto_cleanup = True cover_img_url = 'http://www.aksiyon.com.tr/aksiyon/images/aksiyon/top-page/aksiyon_top_r2_c1.jpg' masthead_url = 'http://aksiyon.com.tr/aksiyon/images/aksiyon/top-page/aksiyon_top_r2_c1.jpg' + ignore_duplicate_articles = { 'title', 'url' } remove_empty_feeds= True feeds = [ ( u'KAPAK', u'http://www.aksiyon.com.tr/aksiyon/rss?sectionId=26'), diff --git a/recipes/weblogs_sl.recipe b/recipes/weblogs_sl.recipe index 8622cccef8..b260d2dde5 100644 --- a/recipes/weblogs_sl.recipe +++ b/recipes/weblogs_sl.recipe @@ -2,8 +2,8 @@ __license__ = 'GPL v3' __copyright__ = '4 February 2011, desUBIKado' __author__ = 'desUBIKado' -__version__ = 'v0.08' -__date__ = '30, June 2012' +__version__ = 'v0.09' +__date__ = '02, December 2012' ''' http://www.weblogssl.com/ ''' @@ -37,6 +37,7 @@ class weblogssl(BasicNewsRecipe): ,(u'Xataka Mexico', u'http://feeds.weblogssl.com/xatakamx') ,(u'Xataka M\xf3vil', u'http://feeds.weblogssl.com/xatakamovil') ,(u'Xataka Android', u'http://feeds.weblogssl.com/xatakandroid') + ,(u'Xataka Windows', u'http://feeds.weblogssl.com/xatakawindows') ,(u'Xataka Foto', u'http://feeds.weblogssl.com/xatakafoto') ,(u'Xataka ON', u'http://feeds.weblogssl.com/xatakaon') ,(u'Xataka Ciencia', u'http://feeds.weblogssl.com/xatakaciencia') @@ -80,19 +81,31 @@ class weblogssl(BasicNewsRecipe): keep_only_tags = [dict(name='div', attrs={'id':'infoblock'}), dict(name='div', attrs={'class':'post'}), - dict(name='div', attrs={'id':'blog-comments'}) + dict(name='div', attrs={'id':'blog-comments'}), + dict(name='div', attrs={'class':'container'}) #m.xataka.com ] - remove_tags = [dict(name='div', attrs={'id':'comment-nav'})] + remove_tags = [dict(name='div', attrs={'id':'comment-nav'}), + dict(name='menu', attrs={'class':'social-sharing'}), #m.xataka.com + dict(name='section' , attrs={'class':'comments'}), #m.xataka.com + dict(name='div' , attrs={'class':'article-comments'}), #m.xataka.com + dict(name='nav' , attrs={'class':'article-taxonomy'}) #m.xataka.com + ] + + remove_tags_after = dict(name='section' , attrs={'class':'comments'}) def print_version(self, url): return url.replace('http://www.', 'http://m.') preprocess_regexps = [ # Para poner una linea en blanco entre un comentario y el siguiente - (re.compile(r'
  • ', re.DOTALL|re.IGNORECASE), lambda m: ''), + (re.compile(r'', re.DOTALL|re.IGNORECASE), lambda m: '') ] + # Para sustituir el video incrustado de YouTube por una imagen def preprocess_html(self, soup): @@ -108,14 +121,16 @@ class weblogssl(BasicNewsRecipe): # Para obtener la url original del articulo a partir de la de "feedsportal" # El siguiente código es gracias al usuario "bosplans" de www.mobileread.com - # http://www.mobileread.com/forums/sho...d.php?t=130297 + # http://www.mobileread.com/forums/showthread.php?t=130297 def get_article_url(self, article): link = article.get('link', None) if link is None: return article + # if link.split('/')[-4]=="xataka2": + # return article.get('feedburner_origlink', article.get('link', article.get('guid'))) if link.split('/')[-4]=="xataka2": - return article.get('feedburner_origlink', article.get('link', article.get('guid'))) + return article.get('guid', None) if link.split('/')[-1]=="story01.htm": link=link.split('/')[-2] a=['0B','0C','0D','0E','0F','0G','0N' ,'0L0S','0A'] diff --git a/recipes/zaman.recipe b/recipes/zaman.recipe index c322febab1..50a17e2112 100644 --- a/recipes/zaman.recipe +++ b/recipes/zaman.recipe @@ -9,15 +9,15 @@ class Zaman (BasicNewsRecipe): __author__ = u'thomass' oldest_article = 2 max_articles_per_feed =50 - # no_stylesheets = True + no_stylesheets = True #delay = 1 - #use_embedded_content = False - encoding = 'ISO 8859-9' - publisher = 'Zaman' + use_embedded_content = False + encoding = 'utf-8' + publisher = 'Feza Gazetecilik' category = 'news, haberler,TR,gazete' language = 'tr' publication_type = 'newspaper ' - extra_css = '.buyukbaslik{font-weight: bold; font-size: 18px;color:#0000FF}'#body{ font-family: Verdana,Helvetica,Arial,sans-serif } .introduction{font-weight: bold} .story-feature{display: block; padding: 0; border: 1px solid; width: 40%; font-size: small} .story-feature h2{text-align: center; text-transform: uppercase} ' + extra_css = 'h1{text-transform: capitalize; font-weight: bold; font-size: 22px;color:#0000FF} p{text-align:justify} ' #.introduction{font-weight: bold} .story-feature{display: block; padding: 0; border: 1px solid; width: 40%; font-size: small} .story-feature h2{text-align: center; text-transform: uppercase} ' conversion_options = { 'tags' : category ,'language' : language @@ -26,25 +26,26 @@ class Zaman (BasicNewsRecipe): } cover_img_url = 'https://fbcdn-profile-a.akamaihd.net/hprofile-ak-snc4/188140_81722291869_2111820_n.jpg' masthead_url = 'http://medya.zaman.com.tr/extentions/zaman.com.tr/img/section/logo-section.png' + ignore_duplicate_articles = { 'title', 'url' } + auto_cleanup = False + remove_empty_feeds= True - #keep_only_tags = [dict(name='div', attrs={'id':[ 'news-detail-content']}), dict(name='td', attrs={'class':['columnist-detail','columnist_head']}) ] - remove_tags = [ dict(name='img', attrs={'src':['http://medya.zaman.com.tr/zamantryeni/pics/zamanonline.gif']})]#,dict(name='div', attrs={'class':['radioEmbedBg','radyoProgramAdi']}),dict(name='a', attrs={'class':['webkit-html-attribute-value webkit-html-external-link']}),dict(name='table', attrs={'id':['yaziYorumTablosu']}),dict(name='img', attrs={'src':['http://medya.zaman.com.tr/pics/paylas.gif','http://medya.zaman.com.tr/extentions/zaman.com.tr/img/columnist/ma-16.png']}) + #keep_only_tags = [dict(name='div', attrs={'id':[ 'contentposition19']})]#,dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'news-detail-content']}), dict(name='td', attrs={'class':['columnist-detail','columnist_head']}), ] + remove_tags = [ dict(name='img', attrs={'src':['http://cmsmedya.zaman.com.tr/images/logo/logo.bmp']}),dict(name='hr', attrs={'class':['interactive-hr']})]# remove_tags = [ dict(name='div', attrs={'class':[ 'detayUyari']}),dict(name='div', attrs={'class':[ 'detayYorum']}),dict(name='div', attrs={'class':[ 'addthis_toolbox addthis_default_style ']}),dict(name='div', attrs={'id':[ 'tumYazi']})]#,dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'xxx']}),dict(name='div', attrs={'id':[ 'xxx']}),dict(name='img', attrs={'src':['http://medya.zaman.com.tr/zamantryeni/pics/zamanonline.gif']}),dict(name='div', attrs={'class':['radioEmbedBg','radyoProgramAdi']}),dict(name='a', attrs={'class':['webkit-html-attribute-value webkit-html-external-link']}),dict(name='table', attrs={'id':['yaziYorumTablosu']}),dict(name='img', attrs={'src':['http://medya.zaman.com.tr/pics/paylas.gif','http://medya.zaman.com.tr/extentions/zaman.com.tr/img/columnist/ma-16.png']}),dict(name='div', attrs={'id':[ 'news-detail-gallery']}),dict(name='div', attrs={'id':[ 'news-detail-title-bottom-part']}),dict(name='div', attrs={'id':[ 'news-detail-news-paging-main']})]# #remove_attributes = ['width','height'] remove_empty_feeds= True feeds = [ - ( u'Anasayfa', u'http://www.zaman.com.tr/anasayfa.rss'), - ( u'Son Dakika', u'http://www.zaman.com.tr/sondakika.rss'), - #( u'En çok Okunanlar', u'http://www.zaman.com.tr/max_all.rss'), - #( u'Manşet', u'http://www.zaman.com.tr/manset.rss'), - ( u'Gündem', u'http://www.zaman.com.tr/gundem.rss'), + ( u'Manşet', u'http://www.zaman.com.tr/manset.rss'), ( u'Yazarlar', u'http://www.zaman.com.tr/yazarlar.rss'), ( u'Politika', u'http://www.zaman.com.tr/politika.rss'), ( u'Ekonomi', u'http://www.zaman.com.tr/ekonomi.rss'), ( u'Dış Haberler', u'http://www.zaman.com.tr/dishaberler.rss'), + ( u'Son Dakika', u'http://www.zaman.com.tr/sondakika.rss'), + ( u'Gündem', u'http://www.zaman.com.tr/gundem.rss'), ( u'Yorumlar', u'http://www.zaman.com.tr/yorumlar.rss'), ( u'Röportaj', u'http://www.zaman.com.tr/roportaj.rss'), ( u'Dizi Yazı', u'http://www.zaman.com.tr/dizi.rss'), @@ -59,8 +60,9 @@ class Zaman (BasicNewsRecipe): ( u'Cuma Eki', u'http://www.zaman.com.tr/cuma.rss'), ( u'Cumaertesi Eki', u'http://www.zaman.com.tr/cumaertesi.rss'), ( u'Pazar Eki', u'http://www.zaman.com.tr/pazar.rss'), + ( u'En çok Okunanlar', u'http://www.zaman.com.tr/max_all.rss'), + ( u'Anasayfa', u'http://www.zaman.com.tr/anasayfa.rss'), ] def print_version(self, url): - return url.replace('http://www.zaman.com.tr/haber.do?haberno=', 'http://www.zaman.com.tr/yazdir.do?haberno=') - + return url.replace('http://www.zaman.com.tr/newsDetail_getNewsById.action?newsId=', 'http://www.zaman.com.tr/newsDetail_openPrintPage.action?newsId=') diff --git a/setup/installer/windows/__init__.py b/setup/installer/windows/__init__.py index eea0f9b487..5e1ad4a7c0 100644 --- a/setup/installer/windows/__init__.py +++ b/setup/installer/windows/__init__.py @@ -39,18 +39,6 @@ class Win32(WinBase): def msi64(self): return installer_name('msi', is64bit=True) - def sign_msi(self): - import xattr - print ('Signing installers ...') - sign64 = False - msi64 = self.msi64 - if os.path.exists(msi64) and 'user.signed' not in xattr.list(msi64): - subprocess.check_call(['scp', msi64, self.VM_NAME + - ':build/%s/%s'%(__appname__, msi64)]) - sign64 = True - subprocess.check_call(['ssh', self.VM_NAME, '~/sign.sh'], shell=False) - return sign64 - def do_dl(self, installer, errmsg): subprocess.check_call(('scp', '%s:build/%s/%s'%(self.VM_NAME, __appname__, installer), 'dist')) @@ -62,14 +50,8 @@ class Win32(WinBase): installer = self.installer() if os.path.exists('build/winfrozen'): shutil.rmtree('build/winfrozen') - sign64 = self.sign_msi() - if sign64: - self.do_dl(self.msi64, 'Failed to d/l signed 64 bit installer') - import xattr - xattr.set(self.msi64, 'user.signed', 'true') self.do_dl(installer, 'Failed to freeze') - installer = 'dist/%s-portable-installer-%s.exe'%(__appname__, __version__) self.do_dl(installer, 'Failed to get portable installer') diff --git a/setup/installer/windows/freeze.py b/setup/installer/windows/freeze.py index bd05fb06c9..4008317341 100644 --- a/setup/installer/windows/freeze.py +++ b/setup/installer/windows/freeze.py @@ -91,6 +91,7 @@ class Win32Freeze(Command, WixMixIn): if not is64bit: self.build_portable() self.build_portable_installer() + self.sign_installers() def remove_CRT_from_manifests(self): ''' @@ -488,6 +489,17 @@ class Win32Freeze(Command, WixMixIn): subprocess.check_call([LZMA + r'\bin\elzma.exe', '-9', '--lzip', name]) + def sign_installers(self): + self.info('Signing installers...') + files = glob.glob(self.j('dist', '*.msi')) + glob.glob(self.j('dist', + '*.exe')) + if not files: + raise ValueError('No installers found') + subprocess.check_call(['signtool.exe', 'sign', '/a', '/d', + 'calibre - E-book management', '/du', + 'http://calibre-ebook.com', '/t', + 'http://timestamp.verisign.com/scripts/timstamp.dll'] + files) + def add_dir_to_zip(self, zf, path, prefix=''): ''' Add a directory recursively to the zip file with an optional prefix. diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index e4e073e383..14c2863d6b 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -__license__ = 'GPL v3' +__license__ = 'GPL v3' __copyright__ = '2010, Gregory Riker' __docformat__ = 'restructuredtext en' @@ -20,6 +20,7 @@ from calibre.utils.config import config_dir, dynamic, prefs from calibre.utils.date import now, parse_date from calibre.utils.zipfile import ZipFile + def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None): if not hasattr(dt, 'timetuple'): @@ -38,6 +39,7 @@ def logger(): _log = ThreadSafeLog() return _log + class AppleOpenFeedback(OpenFeedback): def __init__(self, plugin): @@ -102,6 +104,7 @@ class AppleOpenFeedback(OpenFeedback): return Dialog(parent, self) + class DriverBase(DeviceConfig, DevicePlugin): # Needed for config_widget to work FORMATS = ['epub', 'pdf'] @@ -116,12 +119,12 @@ class DriverBase(DeviceConfig, DevicePlugin): EXTRA_CUSTOMIZATION_MESSAGE = [ _('Use Series as Category in iTunes/iBooks') + - ':::'+_('Enable to use the series name as the iTunes Genre, ' + ':::' + _('Enable to use the series name as the iTunes Genre, ' 'iBooks Category'), _('Cache covers from iTunes/iBooks') + ':::' + _('Enable to cache and display covers from iTunes/iBooks'), - _(u'"Copy files to iTunes Media folder %s" is enabled in iTunes Preferences|Advanced')%u'\u2026' + + _(u'"Copy files to iTunes Media folder %s" is enabled in iTunes Preferences|Advanced') % u'\u2026' + ':::' + _("

    This setting should match your iTunes Preferences|Advanced setting.

    " "

    Disabling will store copies of books transferred to iTunes in your calibre configuration directory.

    " @@ -133,11 +136,11 @@ class DriverBase(DeviceConfig, DevicePlugin): False, ] - @classmethod def _config_base_name(cls): return 'iTunes' + class ITUNES(DriverBase): ''' Calling sequences: @@ -148,6 +151,8 @@ class ITUNES(DriverBase): open() card_prefix() can_handle() + _launch_iTunes() + _discover_manual_sync_mode() set_progress_reporter() get_device_information() card_prefix() @@ -156,6 +161,7 @@ class ITUNES(DriverBase): can_handle() set_progress_reporter() books() (once for each storage point) + (create self.cached_books) settings() settings() can_handle() (~1x per second OSX while idle) @@ -186,14 +192,14 @@ class ITUNES(DriverBase): free_space() ''' - name = 'Apple device interface' + name = 'Apple iTunes interface' gui_name = _('Apple device') icon = I('devices/ipad.png') - description = _('Communicate with iTunes/iBooks.') - supported_platforms = ['osx','windows'] + description = _('Communicate with iTunes/iBooks.') + supported_platforms = ['osx', 'windows'] author = 'GRiker' #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (1,1,0) + version = (1, 1, 1) DISPLAY_DISABLE_DIALOG = "display_disable_apple_driver_dialog" @@ -203,7 +209,7 @@ class ITUNES(DriverBase): USE_ITUNES_STORAGE = 2 OPEN_FEEDBACK_MESSAGE = _( - 'Apple device detected, launching iTunes, please wait ...') + 'Apple iDevice detected, launching iTunes, please wait ...') BACKLOADING_ERROR_MESSAGE = _( "Cannot copy books directly from iDevice. " "Drag from iTunes Library to desktop, then add to calibre's Library window.") @@ -218,22 +224,9 @@ class ITUNES(DriverBase): 'for more information.

    ' '

    ') - # Product IDs: - # 0x1291 iPod Touch - # 0x1293 iPod Touch 2G - # 0x1299 iPod Touch 3G - # 0x1292 iPhone 3G - # 0x1294 iPhone 3GS - # 0x1297 iPhone 4 - # 0x129a iPad - # 0x129f iPad2 (WiFi) - # 0x12a0 iPhone 4S (GSM) - # 0x12a2 iPad2 (GSM) - # 0x12a3 iPad2 (CDMA) - # 0x12a6 iPad3 (GSM) - VENDOR_ID = [0x05ac] - PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a,0x129f,0x12a2,0x12a3,0x12a6] - BCD = [0x01] + VENDOR_ID = [] + PRODUCT_ID = [] + BCD = [] # Plugboard ID DEVICE_PLUGBOARD_NAME = 'APPLE' @@ -301,7 +294,7 @@ class ITUNES(DriverBase): archive_path = os.path.join(cache_dir, "thumbs.zip") description_prefix = "added by calibre" ejected = False - iTunes= None + iTunes = None iTunes_local_storage = None library_orphans = None manual_sync_mode = False @@ -329,28 +322,28 @@ class ITUNES(DriverBase): L{books}(oncard='cardb')). ''' if DEBUG: - logger().info("ITUNES.add_books_to_metadata()") + logger().info("%s.add_books_to_metadata()" % self.__class__.__name__) task_count = float(len(self.update_list)) # Delete any obsolete copies of the book from the booklist if self.update_list: if False: - self._dump_booklist(booklists[0], header='before',indent=2) - self._dump_update_list(header='before',indent=2) - self._dump_cached_books(header='before',indent=2) + self._dump_booklist(booklists[0], header='before', indent=2) + self._dump_update_list(header='before', indent=2) + self._dump_cached_books(header='before', indent=2) - for (j,p_book) in enumerate(self.update_list): + for (j, p_book) in enumerate(self.update_list): if False: if isosx: logger().info(" looking for '%s' by %s uuid:%s" % - (p_book['title'],p_book['author'], p_book['uuid'])) + (p_book['title'], p_book['author'], p_book['uuid'])) elif iswindows: logger().info(" looking for '%s' by %s (%s)" % - (p_book['title'],p_book['author'], p_book['uuid'])) + (p_book['title'], p_book['author'], p_book['uuid'])) # Purge the booklist, self.cached_books - for i,bl_book in enumerate(booklists[0]): + for i, bl_book in enumerate(booklists[0]): if bl_book.uuid == p_book['uuid']: # Remove from booklists[0] booklists[0].pop(i) @@ -374,12 +367,12 @@ class ITUNES(DriverBase): if self.cached_books[cb]['title'] == p_book['title'] and \ self.cached_books[cb]['author'] == p_book['author']: if DEBUG: - self._dump_cached_book(self.cached_books[cb],header="removing from self.cached_books:", indent=2) + self._dump_cached_book(self.cached_books[cb], header="removing from self.cached_books:", indent=2) self.cached_books.pop(cb) break break if self.report_progress is not None: - self.report_progress((j+1)/task_count, _('Updating device metadata listing...')) + self.report_progress((j + 1) / task_count, _('Updating device metadata listing...')) if self.report_progress is not None: self.report_progress(1.0, _('Updating device metadata listing...')) @@ -394,8 +387,8 @@ class ITUNES(DriverBase): booklists[0].append(new_book) if False: - self._dump_booklist(booklists[0],header='after',indent=2) - self._dump_cached_books(header='after',indent=2) + self._dump_booklist(booklists[0], header='after', indent=2) + self._dump_cached_books(header='after', indent=2) def books(self, oncard=None, end_session=True): """ @@ -414,13 +407,13 @@ class ITUNES(DriverBase): """ if not oncard: if DEBUG: - logger().info("ITUNES:books():") + logger().info("%s.books():" % self.__class__.__name__) if self.settings().extra_customization[self.CACHE_COVERS]: logger().info(" Cover fetching/caching enabled") else: logger().info(" Cover fetching/caching disabled") - # Fetch a list of books from iPod device connected to iTunes + # Fetch a list of books from iDevice connected to iTunes if 'iPod' in self.sources: booklist = BookList(logger()) cached_books = {} @@ -429,10 +422,10 @@ class ITUNES(DriverBase): library_books = self._get_library_books() device_books = self._get_device_books() book_count = float(len(device_books)) - for (i,book) in enumerate(device_books): + for (i, book) in enumerate(device_books): this_book = Book(book.name(), book.artist()) format = 'pdf' if book.kind().startswith('PDF') else 'epub' - this_book.path = self.path_template % (book.name(), book.artist(),format) + this_book.path = self.path_template % (book.name(), book.artist(), format) try: this_book.datetime = parse_date(str(book.date_added())).timetuple() except: @@ -450,16 +443,17 @@ class ITUNES(DriverBase): booklist.add_book(this_book, False) cached_books[this_book.path] = { - 'title':book.name(), - 'author':book.artist().split(' & '), - 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, - 'dev_book':book, + 'title': book.name(), + 'author': book.artist(), + 'authors': book.artist().split(' & '), + 'lib_book': library_books[this_book.path] if this_book.path in library_books else None, + 'dev_book': book, 'uuid': book.composer() } if self.report_progress is not None: - self.report_progress((i+1)/book_count, - _('%(num)d of %(tot)d') % dict(num=i+1, tot=book_count)) + self.report_progress((i + 1) / book_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=book_count)) self._purge_orphans(library_books, cached_books) elif iswindows: @@ -470,10 +464,10 @@ class ITUNES(DriverBase): library_books = self._get_library_books() device_books = self._get_device_books() book_count = float(len(device_books)) - for (i,book) in enumerate(device_books): + for (i, book) in enumerate(device_books): this_book = Book(book.Name, book.Artist) format = 'pdf' if book.KindAsString.startswith('PDF') else 'epub' - this_book.path = self.path_template % (book.Name, book.Artist,format) + this_book.path = self.path_template % (book.Name, book.Artist, format) try: this_book.datetime = parse_date(str(book.DateAdded)).timetuple() except: @@ -490,16 +484,17 @@ class ITUNES(DriverBase): booklist.add_book(this_book, False) cached_books[this_book.path] = { - 'title':book.Name, - 'author':book.Artist.split(' & '), - 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, + 'title': book.Name, + 'author': book.Artist, + 'authors': book.Artist.split(' & '), + 'lib_book': library_books[this_book.path] if this_book.path in library_books else None, 'uuid': book.Composer, 'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' } if self.report_progress is not None: - self.report_progress((i+1)/book_count, - _('%(num)d of %(tot)d') % dict(num=i+1, + self.report_progress((i + 1) / book_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=book_count)) self._purge_orphans(library_books, cached_books) @@ -511,7 +506,7 @@ class ITUNES(DriverBase): self.cached_books = cached_books if DEBUG: self._dump_booklist(booklist, 'returning from books()', indent=2) - self._dump_cached_books('returning from books()',indent=2) + self._dump_cached_books('returning from books()', indent=2) return booklist else: return BookList(logger()) @@ -556,7 +551,7 @@ class ITUNES(DriverBase): # We need to know if iTunes sees the iPad # It may have been ejected if DEBUG: - logger().info("ITUNES.can_handle()") + logger().info("%s.can_handle()" % self.__class__.__name__) self._launch_iTunes() self.sources = self._get_sources() @@ -567,12 +562,12 @@ class ITUNES(DriverBase): self.sources = self._get_sources() if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''): attempts -= 1 - time.sleep(0.5) + time.sleep(1.0) if DEBUG: - logger().warning(" waiting for connected iPad, attempt #%d" % (10 - attempts)) + logger().warning(" waiting for connected iDevice, attempt #%d" % (10 - attempts)) else: if DEBUG: - logger().info(' found connected iPad') + logger().info(' found connected iDevice') break else: # iTunes running, but not connected iPad @@ -581,7 +576,7 @@ class ITUNES(DriverBase): self.ejected = True return False - self._discover_manual_sync_mode(wait = 2 if self.initial_status == 'launched' else 0) + self._discover_manual_sync_mode(wait=2 if self.initial_status == 'launched' else 0) return True def can_handle_windows(self, device_id, debug=False): @@ -613,26 +608,26 @@ class ITUNES(DriverBase): sys.stdout.write('.') sys.stdout.flush() if DEBUG: - logger().info('ITUNES.can_handle_windows:\n confirming connected iPad') + logger().info("%s.can_handle_windows:\n confirming connected iPad" % self.__class__.__name__) self.ejected = False self._discover_manual_sync_mode() return True else: if DEBUG: - logger().info("ITUNES.can_handle_windows():\n device ejected") + logger().info("%s.can_handle_windows():\n device ejected" % self.__class__.__name__) self.ejected = True return False except: # iTunes connection failed, probably not running anymore - logger().error("ITUNES.can_handle_windows():\n lost connection to iTunes") + logger().error("%s.can_handle_windows():\n lost connection to iTunes" % self.__class__.__name__) return False finally: pythoncom.CoUninitialize() else: if DEBUG: - logger().info("ITUNES:can_handle_windows():\n Launching iTunes") + logger().info("%s.can_handle_windows():\n Launching iTunes" % self.__class__.__name__) try: pythoncom.CoInitialize() @@ -645,9 +640,9 @@ class ITUNES(DriverBase): self.sources = self._get_sources() if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''): attempts -= 1 - time.sleep(0.5) + time.sleep(1.0) if DEBUG: - logger().warning(" waiting for connected iPad, attempt #%d" % (10 - attempts)) + logger().warning(" waiting for connected iDevice, attempt #%d" % (10 - attempts)) else: if DEBUG: logger().info(' found connected iPad in iTunes') @@ -677,7 +672,7 @@ class ITUNES(DriverBase): ('place', None) (None, None) ''' - return (None,None) + return (None, None) @classmethod def config_widget(cls): @@ -702,7 +697,7 @@ class ITUNES(DriverBase): self.problem_msg = _("Some books not found in iTunes database.\n" "Delete using the iBooks app.\n" "Click 'Show Details' for a list.") - logger().info("ITUNES:delete_books()") + logger().info("%s.delete_books()" % self.__class__.__name__) for path in paths: if self.cached_books[path]['lib_book']: if DEBUG: @@ -731,22 +726,25 @@ class ITUNES(DriverBase): else: if self.manual_sync_mode: metadata = MetaInformation(self.cached_books[path]['title'], - [self.cached_books[path]['author']]) + self.cached_books[path]['authors']) + metadata.author = self.cached_books[path]['author'] metadata.uuid = self.cached_books[path]['uuid'] + if not metadata.uuid: + metadata.uuid = "unknown" if isosx: - self._remove_existing_copy(self.cached_books[path],metadata) + self._remove_existing_copy(self.cached_books[path], metadata) elif iswindows: try: pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") - self._remove_existing_copy(self.cached_books[path],metadata) + self._remove_existing_copy(self.cached_books[path], metadata) finally: pythoncom.CoUninitialize() else: self.problem_titles.append("'%s' by %s" % - (self.cached_books[path]['title'],self.cached_books[path]['author'])) + (self.cached_books[path]['title'], self.cached_books[path]['author'])) def eject(self): ''' @@ -754,7 +752,7 @@ class ITUNES(DriverBase): are pending GUI jobs that need to communicate with the device. ''' if DEBUG: - logger().info("ITUNES:eject(): ejecting '%s'" % self.sources['iPod']) + logger().info("%s:eject(): ejecting '%s'" % (self.__class__.__name__, self.sources['iPod'])) if isosx: self.iTunes.eject(self.sources['iPod']) elif iswindows: @@ -785,7 +783,7 @@ class ITUNES(DriverBase): In Windows, a sync-in-progress blocks this call until sync is complete """ if DEBUG: - logger().info("ITUNES:free_space()") + logger().info("%s.free_space()" % self.__class__.__name__) free_space = 0 if isosx: @@ -810,7 +808,7 @@ class ITUNES(DriverBase): except: logger().error(' waiting for free_space() call to go through') - return (free_space,-1,-1) + return (free_space, -1, -1) def get_device_information(self, end_session=True): """ @@ -818,9 +816,9 @@ class ITUNES(DriverBase): @return: (device name, device version, software version on device, mime type) """ if DEBUG: - logger().info("ITUNES:get_device_information()") + logger().info("%s.get_device_information()" % self.__class__.__name__) - return (self.sources['iPod'],'hw v1.0','sw v1.0', 'mime type normally goes here') + return (self.sources['iPod'], 'hw v1.0', 'sw v1.0', 'mime type normally goes here') def get_file(self, path, outfile, end_session=True): ''' @@ -828,7 +826,7 @@ class ITUNES(DriverBase): @param outfile: file object like C{sys.stdout} or the result of an C{open} call ''' if DEBUG: - logger().info("ITUNES.get_file(): exporting '%s'" % path) + logger().info("%s.get_file(): exporting '%s'" % (self.__class__.__name__, path)) try: outfile.write(open(self.cached_books[path]['lib_book'].location().path).read()) @@ -859,15 +857,31 @@ class ITUNES(DriverBase): raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE) if DEBUG: - logger().info("ITUNES.open(connected_device: %s)" % repr(connected_device)) + vendor_id = "0x%x" % connected_device[0] + product_id = "0x%x" % connected_device[1] + bcd = "0x%x" % connected_device[2] + mfg = connected_device[3] + model = connected_device[4] + logger().info("%s.open(MFG: %s, VENDOR_ID: %s, MODEL: %s, BCD: %s, PRODUCT_ID: %s)" % + (self.__class__.__name__, + mfg, + vendor_id, + model, + bcd, + product_id + )) # Display a dialog recommending using 'Connect to iTunes' if user hasn't # previously disabled the dialog - if dynamic.get(confirm_config_name(self.DISPLAY_DISABLE_DIALOG),True): + if dynamic.get(confirm_config_name(self.DISPLAY_DISABLE_DIALOG), True): raise AppleOpenFeedback(self) else: if DEBUG: - logger().warning(" %s" % self.UNSUPPORTED_DIRECT_CONNECT_MODE_MESSAGE) + logger().error(" %s" % self.UNSUPPORTED_DIRECT_CONNECT_MODE_MESSAGE) + + # Log supported DEVICE_IDs and BCDs + logger().info(" BCD: %s" % ['0x%x' % x for x in sorted(self.BCD)]) + logger().info(" PRODUCT_ID: %s" % ['0x%x' % x for x in sorted(self.PRODUCT_ID)]) # Confirm/create thumbs archive if not os.path.exists(self.cache_dir): @@ -878,7 +892,7 @@ class ITUNES(DriverBase): if not os.path.exists(self.archive_path): logger().info(" creating zip archive") zfw = ZipFile(self.archive_path, mode='w') - zfw.writestr("iTunes Thumbs Archive",'') + zfw.writestr("iTunes Thumbs Archive", '') zfw.close() else: if DEBUG: @@ -886,7 +900,7 @@ class ITUNES(DriverBase): # If enabled in config options, create/confirm an iTunes storage folder if not self.settings().extra_customization[self.USE_ITUNES_STORAGE]: - self.iTunes_local_storage = os.path.join(config_dir,'iTunes storage') + self.iTunes_local_storage = os.path.join(config_dir, 'iTunes storage') if not os.path.exists(self.iTunes_local_storage): if DEBUG: logger()(" creating iTunes_local_storage at '%s'" % self.iTunes_local_storage) @@ -908,44 +922,48 @@ class ITUNES(DriverBase): as uuids are different ''' if DEBUG: - logger().info("ITUNES.remove_books_from_metadata()") + logger().info("%s.remove_books_from_metadata()" % self.__class__.__name__) for path in paths: if DEBUG: self._dump_cached_book(self.cached_books[path], indent=2) logger().info(" looking for '%s' by '%s' uuid:%s" % (self.cached_books[path]['title'], self.cached_books[path]['author'], - self.cached_books[path]['uuid'])) + repr(self.cached_books[path]['uuid']))) # Purge the booklist, self.cached_books, thumb cache - for i,bl_book in enumerate(booklists[0]): + for i, bl_book in enumerate(booklists[0]): if False: - logger().info(" evaluating '%s' by '%s' uuid:%s" % - (bl_book.title, bl_book.author,bl_book.uuid)) + logger().info(" evaluating '%s' by '%s' uuid:%s" % + (bl_book.title, bl_book.author, bl_book.uuid)) found = False - if bl_book.uuid == self.cached_books[path]['uuid']: - if False: - logger().info(" matched with uuid") + if bl_book.uuid and bl_book.uuid == self.cached_books[path]['uuid']: + if True: + logger().info(" --matched uuid") booklists[0].pop(i) found = True elif bl_book.title == self.cached_books[path]['title'] and \ - bl_book.author[0] == self.cached_books[path]['author']: - if False: - logger().info(" matched with title + author") + bl_book.author == self.cached_books[path]['author']: + if True: + logger().info(" --matched title + author") booklists[0].pop(i) found = True if found: # Remove from self.cached_books for cb in self.cached_books: - if self.cached_books[cb]['uuid'] == self.cached_books[path]['uuid']: + if (self.cached_books[cb]['uuid'] == self.cached_books[path]['uuid'] and + self.cached_books[cb]['author'] == self.cached_books[path]['author'] and + self.cached_books[cb]['title'] == self.cached_books[path]['title']): self.cached_books.pop(cb) break + else: + logger().error(" '%s' not found in self.cached_books" % self.cached_books[path]['title']) # Remove from thumb from thumb cache thumb_path = path.rpartition('.')[0] + '.jpg' - zf = ZipFile(self.archive_path,'a') + zf = ZipFile(self.archive_path, 'a') fnames = zf.namelist() try: thumb = [x for x in fnames if thumb_path in x][0] @@ -964,14 +982,16 @@ class ITUNES(DriverBase): else: if DEBUG: logger().error(" unable to find '%s' by '%s' (%s)" % - (bl_book.title, bl_book.author,bl_book.uuid)) + (self.cached_books[path]['title'], + self.cached_books[path]['author'], + self.cached_books[path]['uuid'])) if False: - self._dump_booklist(booklists[0], indent = 2) + self._dump_booklist(booklists[0], indent=2) self._dump_cached_books(indent=2) def reset(self, key='-1', log_packets=False, report_progress=None, - detected_device=None) : + detected_device=None): """ :key: The key to unlock the device :log_packets: If true the packet stream to/from the device is logged @@ -982,7 +1002,7 @@ class ITUNES(DriverBase): :detected_device: Device information from the device scanner """ if DEBUG: - logger().info("ITUNES.reset()") + logger().info("%s.reset()" % self.__class__.__name__) if report_progress: self.set_progress_reporter(report_progress) @@ -994,7 +1014,7 @@ class ITUNES(DriverBase): task does not have any progress information ''' if DEBUG: - logger().info("ITUNES.set_progress_reporter()") + logger().info("%s.set_progress_reporter()" % self.__class__.__name__) self.report_progress = report_progress @@ -1002,7 +1022,7 @@ class ITUNES(DriverBase): # This method is called with the plugboard that matches the format # declared in use_plugboard_ext and a device name of ITUNES if DEBUG: - logger().info("ITUNES.set_plugboard()") + logger().info("%s.set_plugboard()" % self.__class__.__name__) #logger().info(' plugboard: %s' % plugboards) self.plugboards = plugboards self.plugboard_func = pb_func @@ -1016,7 +1036,7 @@ class ITUNES(DriverBase): ''' if DEBUG: - logger().info("ITUNES.sync_booklists()") + logger().info("%s.sync_booklists()" % self.__class__.__name__) if self.update_needed: if DEBUG: @@ -1043,14 +1063,14 @@ class ITUNES(DriverBase): particular device doesn't have any of these locations it should return 0. """ if DEBUG: - logger().info("ITUNES:total_space()") + logger().info("%s.total_space()" % self.__class__.__name__) capacity = 0 if isosx: if 'iPod' in self.sources: connected_device = self.sources['iPod'] capacity = self.iTunes.sources[connected_device].capacity() - return (capacity,-1,-1) + return (capacity, -1, -1) def upload_books(self, files, names, on_card=None, end_session=True, metadata=None): @@ -1081,10 +1101,10 @@ class ITUNES(DriverBase): "Click 'Show Details' for a list.") if DEBUG: - logger().info("ITUNES.upload_books()") + logger().info("%s.upload_books()" % self.__class__.__name__) if isosx: - for (i,fpath) in enumerate(files): + for (i, fpath) in enumerate(files): format = fpath.rpartition('.')[2].lower() path = self.path_template % (metadata[i].title, authors_to_string(metadata[i].authors), @@ -1098,7 +1118,7 @@ class ITUNES(DriverBase): # Add new_book to self.cached_books if DEBUG: - logger().info("ITUNES.upload_books()") + logger().info("%s.upload_books()" % self.__class__.__name__) logger().info(" adding '%s' by '%s' uuid:%s to self.cached_books" % (metadata[i].title, authors_to_string(metadata[i].authors), @@ -1109,12 +1129,12 @@ class ITUNES(DriverBase): 'format': format, 'lib_book': lb_added, 'title': metadata[i].title, - 'uuid': metadata[i].uuid } + 'uuid': metadata[i].uuid} # Report progress if self.report_progress is not None: - self.report_progress((i+1)/file_count, - _('%(num)d of %(tot)d') % dict(num=i+1, tot=file_count)) + self.report_progress((i + 1) / file_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=file_count)) elif iswindows: import pythoncom, win32com.client @@ -1123,7 +1143,7 @@ class ITUNES(DriverBase): pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") - for (i,fpath) in enumerate(files): + for (i, fpath) in enumerate(files): format = fpath.rpartition('.')[2].lower() path = self.path_template % (metadata[i].title, authors_to_string(metadata[i].authors), @@ -1144,7 +1164,7 @@ class ITUNES(DriverBase): # Add new_book to self.cached_books if DEBUG: - logger().info("ITUNES.upload_books()") + logger().info("%s.upload_books()" % self.__class__.__name__) logger().info(" adding '%s' by '%s' uuid:%s to self.cached_books" % (metadata[i].title, authors_to_string(metadata[i].authors), @@ -1159,8 +1179,8 @@ class ITUNES(DriverBase): # Report progress if self.report_progress is not None: - self.report_progress((i+1)/file_count, - _('%(num)d of %(tot)d') % dict(num=i+1, tot=file_count)) + self.report_progress((i + 1) / file_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=file_count)) finally: pythoncom.CoUninitialize() @@ -1173,16 +1193,16 @@ class ITUNES(DriverBase): self.update_msg = "Added books to device" if False: - self._dump_booklist(new_booklist,header="after upload_books()",indent=2) - self._dump_cached_books(header="after upload_books()",indent=2) + self._dump_booklist(new_booklist, header="after upload_books()", indent=2) + self._dump_cached_books(header="after upload_books()", indent=2) return (new_booklist, [], []) # Private methods - def _add_device_book(self,fpath, metadata): + def _add_device_book(self, fpath, metadata): ''' assumes pythoncom wrapper for windows ''' - logger().info(" ITUNES._add_device_book()") + logger().info(" %s._add_device_book()" % self.__class__.__name__) if isosx: import appscript if 'iPod' in self.sources: @@ -1200,7 +1220,7 @@ class ITUNES(DriverBase): delay = 1.0 while attempts: try: - added = pl.add(appscript.mactypes.File(fpath),to=pl) + added = pl.add(appscript.mactypes.File(fpath), to=pl) if False: logger().info(" '%s' added to Device|Books" % metadata.title) break @@ -1281,18 +1301,18 @@ class ITUNES(DriverBase): base_fn = fpath.rpartition(os.sep)[2] base_fn = base_fn.rpartition('.')[0] db_added = self._find_device_book( - { 'title': base_fn if format == 'pdf' else metadata.title, + {'title': base_fn if format == 'pdf' else metadata.title, 'author': authors_to_string(metadata.authors), 'uuid': metadata.uuid, 'format': format}) return db_added - def _add_library_book(self,file, metadata): + def _add_library_book(self, file, metadata): ''' windows assumes pythoncom wrapper ''' if DEBUG: - logger().info(" ITUNES._add_library_book()") + logger().info(" %s._add_library_book()" % self.__class__.__name__) if isosx: import appscript added = self.iTunes.add(appscript.mactypes.File(file)) @@ -1348,7 +1368,7 @@ class ITUNES(DriverBase): base_fn = file.rpartition(os.sep)[2] base_fn = base_fn.rpartition('.')[0] added = self._find_library_book( - { 'title': base_fn if format == 'pdf' else metadata.title, + {'title': base_fn if format == 'pdf' else metadata.title, 'author': authors_to_string(metadata.authors), 'uuid': metadata.uuid, 'format': format}) @@ -1360,7 +1380,7 @@ class ITUNES(DriverBase): fp = cached_book['lib_book'].Location ''' if DEBUG: - logger().info(" ITUNES._add_new_copy()") + logger().info(" %s._add_new_copy()" % self.__class__.__name__) if fpath.rpartition('.')[2].lower() == 'epub': self._update_epub_metadata(fpath, metadata) @@ -1371,7 +1391,7 @@ class ITUNES(DriverBase): # If using iTunes_local_storage, copy the file, redirect iTunes to use local copy if not self.settings().extra_customization[self.USE_ITUNES_STORAGE]: local_copy = os.path.join(self.iTunes_local_storage, str(metadata.uuid) + os.path.splitext(fpath)[1]) - shutil.copyfile(fpath,local_copy) + shutil.copyfile(fpath, local_copy) fpath = local_copy if self.manual_sync_mode: @@ -1399,7 +1419,7 @@ class ITUNES(DriverBase): from PIL import Image as PILImage if DEBUG: - logger().info(" ITUNES._cover_to_thumb()") + logger().info(" %s._cover_to_thumb()" % self.__class__.__name__) thumb = None if metadata.cover: @@ -1417,18 +1437,18 @@ class ITUNES(DriverBase): if scaled: if DEBUG: logger().info(" cover scaled from %sx%s to %sx%s" % - (width,height,nwidth,nheight)) + (width, height, nwidth, nheight)) img = img.resize((nwidth, nheight), PILImage.ANTIALIAS) cd = cStringIO.StringIO() img.convert('RGB').save(cd, 'JPEG') cover_data = cd.getvalue() cd.close() else: - with open(metadata.cover,'r+b') as cd: + with open(metadata.cover, 'r+b') as cd: cover_data = cd.read() except: self.problem_titles.append("'%s' by %s" % (metadata.title, authors_to_string(metadata.authors))) - logger().error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title)) + logger().error(" error scaling '%s' for '%s'" % (metadata.cover, metadata.title)) import traceback traceback.print_exc() @@ -1467,7 +1487,7 @@ class ITUNES(DriverBase): elif iswindows: ''' 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: + with open(tc, 'wb') as tmp_cover: tmp_cover.write(cover_data) if lb_added: @@ -1505,13 +1525,13 @@ class ITUNES(DriverBase): # Refresh the thumbnail cache if DEBUG: - logger().info( " refreshing cached thumb for '%s'" % metadata.title) + logger().info(" refreshing cached thumb for '%s'" % metadata.title) zfw = ZipFile(self.archive_path, mode='a') thumb_path = path.rpartition('.')[0] + '.jpg' zfw.writestr(thumb_path, thumb) except: self.problem_titles.append("'%s' by %s" % (metadata.title, authors_to_string(metadata.authors))) - logger().error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title)) + logger().error(" error converting '%s' to thumb for '%s'" % (metadata.cover, metadata.title)) finally: try: zfw.close() @@ -1522,11 +1542,11 @@ class ITUNES(DriverBase): logger().info(" no cover defined in metadata for '%s'" % metadata.title) return thumb - def _create_new_book(self,fpath, metadata, path, db_added, lb_added, thumb, format): + def _create_new_book(self, fpath, metadata, path, db_added, lb_added, thumb, format): ''' ''' if DEBUG: - logger().info(" ITUNES._create_new_book()") + logger().info(" %s._create_new_book()" % self.__class__.__name__) this_book = Book(metadata.title, authors_to_string(metadata.authors)) this_book.datetime = time.gmtime() @@ -1575,7 +1595,7 @@ class ITUNES(DriverBase): wait is passed when launching iTunes, as it seems to need a moment to come to its senses ''' if DEBUG: - logger().info(" ITUNES._discover_manual_sync_mode()") + logger().info(" %s._discover_manual_sync_mode()" % self.__class__.__name__) if wait: time.sleep(wait) if isosx: @@ -1593,7 +1613,7 @@ class ITUNES(DriverBase): if dev_books is not None and len(dev_books): first_book = dev_books[0] if False: - logger().info(" determing manual mode by modifying '%s' by %s" % (first_book.name(), first_book.artist())) + logger().info(" determining manual mode by modifying '%s' by %s" % (first_book.name(), first_book.artist())) try: first_book.bpm.set(0) self.manual_sync_mode = True @@ -1603,7 +1623,7 @@ class ITUNES(DriverBase): if DEBUG: logger().info(" adding tracer to empty Books|Playlist") try: - added = pl.add(appscript.mactypes.File(P('tracer.epub')),to=pl) + added = pl.add(appscript.mactypes.File(P('tracer.epub')), to=pl) time.sleep(0.5) added.delete() self.manual_sync_mode = True @@ -1634,9 +1654,9 @@ class ITUNES(DriverBase): if DEBUG: logger().info(" sending tracer to empty Books|Playlist") fpath = P('tracer.epub') - mi = MetaInformation('Tracer',['calibre']) + mi = MetaInformation('Tracer', ['calibre']) try: - added = self._add_device_book(fpath,mi) + added = self._add_device_book(fpath, mi) time.sleep(0.5) added.Delete() self.manual_sync_mode = True @@ -1645,40 +1665,40 @@ class ITUNES(DriverBase): logger().info(" iTunes.manual_sync_mode: %s" % self.manual_sync_mode) - def _dump_booklist(self, booklist, header=None,indent=0): + def _dump_booklist(self, booklist, header=None, indent=0): ''' ''' if header: - msg = '\n%sbooklist %s:' % (' '*indent,header) + msg = '\n%sbooklist %s:' % (' ' * indent, header) logger().info(msg) - logger().info('%s%s' % (' '*indent,'-' * len(msg))) + logger().info('%s%s' % (' ' * indent, '-' * len(msg))) for book in booklist: if isosx: - logger().info("%s%-40.40s %-30.30s %-10.10s %s" % - (' '*indent,book.title, book.author, str(book.library_id)[-9:], book.uuid)) + logger().info("%s%-40.40s %-30.30s %-40.40s %-10.10s" % + (' ' * indent, book.title, book.author, book.uuid, str(book.library_id)[-9:])) elif iswindows: logger().info("%s%-40.40s %-30.30s" % - (' '*indent,book.title, book.author)) + (' ' * indent, book.title, book.author)) logger().info() - def _dump_cached_book(self, cached_book, header=None,indent=0): + def _dump_cached_book(self, cached_book, header=None, indent=0): ''' ''' if isosx: if header: - msg = '%s%s' % (' '*indent,header) + msg = '%s%s' % (' ' * indent, header) logger().info(msg) - logger().info( "%s%s" % (' '*indent, '-' * len(msg))) + logger().info("%s%s" % (' ' * indent, '-' * len(msg))) logger().info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" % - (' '*indent, + (' ' * indent, 'title', 'author', 'lib_book', 'dev_book', 'uuid')) logger().info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" % - (' '*indent, + (' ' * indent, cached_book['title'], cached_book['author'], str(cached_book['lib_book'])[-9:], @@ -1686,12 +1706,12 @@ class ITUNES(DriverBase): cached_book['uuid'])) elif iswindows: if header: - msg = '%s%s' % (' '*indent,header) + msg = '%s%s' % (' ' * indent, header) logger().info(msg) - logger().info( "%s%s" % (' '*indent, '-' * len(msg))) + logger().info("%s%s" % (' ' * indent, '-' * len(msg))) logger().info("%s%-40.40s %-30.30s %s" % - (' '*indent, + (' ' * indent, cached_book['title'], cached_book['author'], cached_book['uuid'])) @@ -1700,22 +1720,23 @@ class ITUNES(DriverBase): ''' ''' if header: - msg = '\n%sself.cached_books %s:' % (' '*indent,header) + msg = '\n%sself.cached_books %s:' % (' ' * indent, header) logger().info(msg) - logger().info( "%s%s" % (' '*indent,'-' * len(msg))) + logger().info("%s%s" % (' ' * indent, '-' * len(msg))) if isosx: for cb in self.cached_books.keys(): - logger().info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" % - (' '*indent, + logger().info("%s%-40.40s %-30.30s %-40.40s %-10.10s %-10.10s" % + (' ' * indent, self.cached_books[cb]['title'], self.cached_books[cb]['author'], + self.cached_books[cb]['uuid'], str(self.cached_books[cb]['lib_book'])[-9:], str(self.cached_books[cb]['dev_book'])[-9:], - self.cached_books[cb]['uuid'])) + )) elif iswindows: for cb in self.cached_books.keys(): logger().info("%s%-40.40s %-30.30s %-4.4s %s" % - (' '*indent, + (' ' * indent, self.cached_books[cb]['title'], self.cached_books[cb]['author'], self.cached_books[cb]['format'], @@ -1728,11 +1749,11 @@ class ITUNES(DriverBase): ''' from calibre.ebooks.BeautifulSoup import BeautifulSoup - logger().info(" ITUNES.__get_epub_metadata()") + logger().info(" %s.__get_epub_metadata()" % self.__class__.__name__) title = None author = None timestamp = None - zf = ZipFile(fpath,'r') + zf = ZipFile(fpath, 'r') fnames = zf.namelist() opf = [x for x in fnames if '.opf' in x][0] if opf: @@ -1741,14 +1762,14 @@ class ITUNES(DriverBase): opf_raw.close() title = soup.find('dc:title').renderContents() author = soup.find('dc:creator').renderContents() - ts = soup.find('meta',attrs={'name':'calibre:timestamp'}) + ts = soup.find('meta', attrs={'name': 'calibre:timestamp'}) if ts: # Touch existing calibre timestamp timestamp = ts['content'] if not title or not author: if DEBUG: - logger().error(" couldn't extract title/author from %s in %s" % (opf,fpath)) + logger().error(" couldn't extract title/author from %s in %s" % (opf, fpath)) logger().error(" title: %s author: %s timestamp: %s" % (title, author, timestamp)) else: if DEBUG: @@ -1759,14 +1780,15 @@ class ITUNES(DriverBase): def _dump_hex(self, src, length=16): ''' ''' - FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) - N=0; result='' + FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) + N = 0 + result = '' while src: - s,src = src[:length],src[length:] - hexa = ' '.join(["%02X"%ord(x) for x in s]) - s = s.translate(FILTER) - result += "%04X %-*s %s\n" % (N, length*3, hexa, s) - N+=length + s, src = src[:length], src[length:] + hexa = ' '.join(["%02X" % ord(x) for x in s]) + s = s.translate(FILTER) + result += "%04X %-*s %s\n" % (N, length * 3, hexa, s) + N += length print result def _dump_library_books(self, library_books): @@ -1778,16 +1800,16 @@ class ITUNES(DriverBase): logger().info(" %s" % book) logger().info() - def _dump_update_list(self,header=None,indent=0): + def _dump_update_list(self, header=None, indent=0): if header and self.update_list: - msg = '\n%sself.update_list %s' % (' '*indent,header) + msg = '\n%sself.update_list %s' % (' ' * indent, header) logger().info(msg) - logger().info( "%s%s" % (' '*indent,'-' * len(msg))) + logger().info("%s%s" % (' ' * indent, '-' * len(msg))) if isosx: for ub in self.update_list: logger().info("%s%-40.40s %-30.30s %-10.10s %s" % - (' '*indent, + (' ' * indent, ub['title'], ub['author'], str(ub['lib_book'])[-9:], @@ -1795,7 +1817,7 @@ class ITUNES(DriverBase): elif iswindows: for ub in self.update_list: logger().info("%s%-40.40s %-30.30s" % - (' '*indent, + (' ' * indent, ub['title'], ub['author'])) @@ -1806,16 +1828,16 @@ class ITUNES(DriverBase): if iswindows: dev_books = self._get_device_books_playlist() if DEBUG: - logger().info(" ITUNES._find_device_book()") + logger().info(" %s._find_device_book()" % self.__class__.__name__) logger().info(" searching for '%s' by '%s' (%s)" % - (search['title'], search['author'],search['uuid'])) + (search['title'], search['author'], search['uuid'])) attempts = 9 while attempts: # Try by uuid - only one hit if 'uuid' in search and search['uuid']: if DEBUG: logger().info(" searching by uuid '%s' ..." % search['uuid']) - hits = dev_books.Search(search['uuid'],self.SearchField.index('All')) + hits = dev_books.Search(search['uuid'], self.SearchField.index('All')) if hits: hit = hits[0] logger().info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) @@ -1825,7 +1847,7 @@ class ITUNES(DriverBase): if search['author']: if DEBUG: logger().info(" searching by author '%s' ..." % search['author']) - hits = dev_books.Search(search['author'],self.SearchField.index('Artists')) + hits = dev_books.Search(search['author'], self.SearchField.index('Artists')) if hits: for hit in hits: if hit.Name == search['title']: @@ -1836,7 +1858,7 @@ class ITUNES(DriverBase): # Search by title if no author available if DEBUG: logger().info(" searching by title '%s' ..." % search['title']) - hits = dev_books.Search(search['title'],self.SearchField.index('All')) + hits = dev_books.Search(search['title'], self.SearchField.index('All')) if hits: for hit in hits: if hit.Name == search['title']: @@ -1850,8 +1872,8 @@ class ITUNES(DriverBase): title = re.sub(r'[^0-9a-zA-Z ]', '_', search['title']) author = re.sub(r'[^0-9a-zA-Z ]', '_', search['author']) if DEBUG: - logger().info(" searching by name: '%s - %s'" % (title,author)) - hits = dev_books.Search('%s - %s' % (title,author), + logger().info(" searching by name: '%s - %s'" % (title, author)) + hits = dev_books.Search('%s - %s' % (title, author), self.SearchField.index('All')) if hits: hit = hits[0] @@ -1876,7 +1898,7 @@ class ITUNES(DriverBase): ''' if iswindows: if DEBUG: - logger().info(" ITUNES._find_library_book()") + logger().info(" %s._find_library_book()" % self.__class__.__name__) ''' if 'uuid' in search: logger().info(" looking for '%s' by %s (%s)" % @@ -1909,14 +1931,13 @@ class ITUNES(DriverBase): if DEBUG: logger().error(" no Books playlist found") - attempts = 9 while attempts: # Find book whose Album field = search['uuid'] if 'uuid' in search and search['uuid']: if DEBUG: logger().info(" searching by uuid '%s' ..." % search['uuid']) - hits = lib_books.Search(search['uuid'],self.SearchField.index('All')) + hits = lib_books.Search(search['uuid'], self.SearchField.index('All')) if hits: hit = hits[0] if DEBUG: @@ -1927,7 +1948,7 @@ class ITUNES(DriverBase): if search['author']: if DEBUG: logger().info(" searching by author '%s' ..." % search['author']) - hits = lib_books.Search(search['author'],self.SearchField.index('Artists')) + hits = lib_books.Search(search['author'], self.SearchField.index('Artists')) if hits: for hit in hits: if hit.Name == search['title']: @@ -1938,7 +1959,7 @@ class ITUNES(DriverBase): # Search by title if no author available if DEBUG: logger().info(" searching by title '%s' ..." % search['title']) - hits = lib_books.Search(search['title'],self.SearchField.index('All')) + hits = lib_books.Search(search['title'], self.SearchField.index('All')) if hits: for hit in hits: if hit.Name == search['title']: @@ -1952,8 +1973,8 @@ class ITUNES(DriverBase): title = re.sub(r'[^0-9a-zA-Z ]', '_', search['title']) author = re.sub(r'[^0-9a-zA-Z ]', '_', search['author']) if DEBUG: - logger().info(" searching by name: %s - %s" % (title,author)) - hits = lib_books.Search('%s - %s' % (title,author), + logger().info(" searching by name: %s - %s" % (title, author)) + hits = lib_books.Search('%s - %s' % (title, author), self.SearchField.index('All')) if hits: hit = hits[0] @@ -1996,7 +2017,8 @@ class ITUNES(DriverBase): thumb_data = zfr.read(thumb_path) if thumb_data == 'None': if False: - logger().info(" ITUNES._generate_thumbnail()\n returning None from cover cache for '%s'" % title) + logger().info(" %s._generate_thumbnail()\n returning None from cover cache for '%s'" % + (self.__class__.__name__, title)) zfr.close() return None except: @@ -2007,7 +2029,7 @@ class ITUNES(DriverBase): return thumb_data if DEBUG: - logger().info(" ITUNES._generate_thumbnail('%s'):" % title) + logger().info(" %s._generate_thumbnail('%s'):" % (self.__class__.__name__, title)) if isosx: # Fetch the artwork from iTunes @@ -2025,11 +2047,11 @@ class ITUNES(DriverBase): try: img_data = cStringIO.StringIO(data) im = PILImage.open(img_data) - scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80) - im = im.resize((int(width),int(height)), PILImage.ANTIALIAS) + scaled, width, height = fit_image(im.size[0], im.size[1], 60, 80) + im = im.resize((int(width), int(height)), PILImage.ANTIALIAS) thumb = cStringIO.StringIO() - im.convert('RGB').save(thumb,'JPEG') + im.convert('RGB').save(thumb, 'JPEG') thumb_data = thumb.getvalue() thumb.close() if False: @@ -2049,7 +2071,6 @@ class ITUNES(DriverBase): return thumb_data - elif iswindows: if not book.Artwork.Count: if DEBUG: @@ -2065,10 +2086,10 @@ class ITUNES(DriverBase): book.Artwork.Item(1).SaveArtworkToFile(tmp_thumb) # Resize the cover im = PILImage.open(tmp_thumb) - scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80) - im = im.resize((int(width),int(height)), PILImage.ANTIALIAS) + scaled, width, height = fit_image(im.size[0], im.size[1], 60, 80) + im = im.resize((int(width), int(height)), PILImage.ANTIALIAS) thumb = cStringIO.StringIO() - im.convert('RGB').save(thumb,'JPEG') + im.convert('RGB').save(thumb, 'JPEG') thumb_data = thumb.getvalue() os.remove(tmp_thumb) thumb.close() @@ -2081,7 +2102,7 @@ class ITUNES(DriverBase): logger().error(" error generating thumb for '%s', caching empty marker" % book.Name) thumb_data = None # Cache the empty cover - zfw.writestr(thumb_path,'None') + zfw.writestr(thumb_path, 'None') finally: zfw.close() @@ -2095,13 +2116,13 @@ class ITUNES(DriverBase): exploded_file_size = compressed_size format = file.rpartition('.')[2].lower() if format == 'epub': - myZip = ZipFile(file,'r') + myZip = ZipFile(file, 'r') myZipList = myZip.infolist() exploded_file_size = 0 for file in myZipList: exploded_file_size += file.file_size if False: - logger().info(" ITUNES._get_device_book_size()") + logger().info(" %s._get_device_book_size()" % self.__class__.__name__) logger().info(" %d items in archive" % len(myZipList)) logger().info(" compressed: %d exploded: %d" % (compressed_size, exploded_file_size)) myZip.close() @@ -2112,7 +2133,7 @@ class ITUNES(DriverBase): Assumes pythoncom wrapper for Windows ''' if DEBUG: - logger().info("\n ITUNES._get_device_books()") + logger().info("\n %s._get_device_books()" % self.__class__.__name__) device_books = [] if isosx: @@ -2131,14 +2152,13 @@ class ITUNES(DriverBase): logger().error(" book_playlist not found") for book in dev_books: - # This may need additional entries for international iTunes users if book.kind() in self.Audiobooks: if DEBUG: logger().info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) else: if DEBUG: - logger().info(" %-30.30s %-30.30s %-40.40s [%s]" % - (book.name(), book.artist(), book.album(), book.kind())) + logger().info(" %-40.40s %-30.30s %-40.40s [%s]" % + (book.name(), book.artist(), book.composer(), book.kind())) device_books.append(book) if DEBUG: logger().info() @@ -2165,13 +2185,12 @@ class ITUNES(DriverBase): logger().info(" no Books playlist found") for book in dev_books: - # This may need additional entries for international iTunes users if book.KindAsString in self.Audiobooks: if DEBUG: logger().info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString)) else: if DEBUG: - logger().info(" %-30.30s %-30.30s %-40.40s [%s]" % (book.Name, book.Artist, book.Album, book.KindAsString)) + logger().info(" %-40.40s %-30.30s %-40.40s [%s]" % (book.Name, book.Artist, book.Composer, book.KindAsString)) device_books.append(book) if DEBUG: logger().info() @@ -2206,7 +2225,7 @@ class ITUNES(DriverBase): Windows assumes pythoncom wrapper ''' if DEBUG: - logger().info("\n ITUNES._get_library_books()") + logger().info("\n %s._get_library_books()" % self.__class__.__name__) library_books = {} library_orphans = {} @@ -2245,7 +2264,7 @@ class ITUNES(DriverBase): else: # Collect calibre orphans - remnants of recipe uploads format = 'pdf' if book.kind().startswith('PDF') else 'epub' - path = self.path_template % (book.name(), book.artist(),format) + path = self.path_template % (book.name(), book.artist(), format) if str(book.description()).startswith(self.description_prefix): try: if book.location() == appscript.k.missing_value: @@ -2302,7 +2321,7 @@ class ITUNES(DriverBase): logger().info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString)) else: format = 'pdf' if book.KindAsString.startswith('PDF') else 'epub' - path = self.path_template % (book.Name, book.Artist,format) + path = self.path_template % (book.Name, book.Artist, format) # Collect calibre orphans if book.Description.startswith(self.description_prefix): @@ -2354,7 +2373,7 @@ class ITUNES(DriverBase): return {} elif iswindows: # Assumes a pythoncom wrapper - it_sources = ['Unknown','Library','iPod','AudioCD','MP3CD','Device','RadioTuner','SharedLibrary'] + it_sources = ['Unknown', 'Library', 'iPod', 'AudioCD', 'MP3CD', 'Device', 'RadioTuner', 'SharedLibrary'] names = [s.name for s in self.iTunes.sources] kinds = [it_sources[s.kind] for s in self.iTunes.sources] @@ -2367,12 +2386,12 @@ class ITUNES(DriverBase): kinds.pop(index) names.pop(index) - return dict(zip(kinds,names)) + return dict(zip(kinds, names)) - def _is_alpha(self,char): + def _is_alpha(self, char): ''' ''' - if not re.search('[a-zA-Z]',char): + if not re.search('[a-zA-Z]', char): return False else: return True @@ -2381,7 +2400,7 @@ class ITUNES(DriverBase): ''' ''' if DEBUG: - logger().info(" ITUNES:_launch_iTunes():\n Instantiating iTunes") + logger().info(" %s._launch_iTunes():\n Instantiating iTunes" % self.__class__.__name__) if isosx: import appscript @@ -2394,12 +2413,13 @@ class ITUNES(DriverBase): running_apps = appscript.app('System Events') if not 'iTunes' in running_apps.processes.name(): if DEBUG: - logger().info( "ITUNES:_launch_iTunes(): Launching iTunes" ) + logger().info("%s:_launch_iTunes(): Launching iTunes" % self.__class__.__name__) try: self.iTunes = iTunes = appscript.app('iTunes', hide=True) except: self.iTunes = None - raise UserFeedback(' ITUNES._launch_iTunes(): unable to find installed iTunes', details=None, level=UserFeedback.WARN) + raise UserFeedback(' %s._launch_iTunes(): unable to find installed iTunes' % + self.__class__.__name__, details=None, level=UserFeedback.WARN) iTunes.run() self.initial_status = 'launched' @@ -2419,7 +2439,7 @@ class ITUNES(DriverBase): except: # Try static binding import itunes - self.iTunes = appscript.app('iTunes',terms=itunes) + self.iTunes = appscript.app('iTunes', terms=itunes) try: foo = self.iTunes.name() as_binding = "static" @@ -2444,10 +2464,10 @@ class ITUNES(DriverBase): if DEBUG: logger().info(" %s %s" % (__appname__, __version__)) - logger().info(" [OSX %s, %s %s (%s), driver version %d.%d.%d]" % + logger().info(" [OSX %s, %s %s (%s), %s driver version %d.%d.%d]" % (platform.mac_ver()[0], self.iTunes.name(), self.iTunes.version(), self.initial_status, - self.version[0],self.version[1],self.version[2])) + self.__class__.__name__, self.version[0], self.version[1], self.version[2])) logger().info(" communicating with iTunes via %s %s using %s binding" % (as_name, as_version, as_binding)) logger().info(" calibre_library_path: %s" % self.calibre_library_path) @@ -2474,7 +2494,8 @@ class ITUNES(DriverBase): self.iTunes = win32com.client.Dispatch("iTunes.Application") except: self.iTunes = None - raise UserFeedback(' ITUNES._launch_iTunes(): unable to find installed iTunes', details=None, level=UserFeedback.WARN) + raise UserFeedback(' %s._launch_iTunes(): unable to find installed iTunes' + % self.__class__.__name__, details=None, level=UserFeedback.WARN) if not DEBUG: self.iTunes.Windows[0].Minimized = True @@ -2515,63 +2536,63 @@ class ITUNES(DriverBase): logger().info(" %s %s" % (__appname__, __version__)) logger().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.version[0], self.version[1], self.version[2])) logger().info(" calibre_library_path: %s" % self.calibre_library_path) - def _purge_orphans(self,library_books, cached_books): + def _purge_orphans(self, library_books, cached_books): ''' Scan library_books for any paths not on device Remove any iTunes orphans originally added by calibre This occurs when the user deletes a book in iBooks while disconnected ''' - if DEBUG: - logger().info(" ITUNES._purge_orphans()") - #self._dump_library_books(library_books) - #logger().info(" cached_books:\n %s" % "\n ".join(cached_books.keys())) + PURGE_ORPHANS = False - for book in library_books: - if isosx: - if book not in cached_books and \ - str(library_books[book].description()).startswith(self.description_prefix): - if DEBUG: - logger().info(" '%s' not found on iDevice, removing from iTunes" % book) - btr = { 'title':library_books[book].name(), - 'author':library_books[book].artist(), - 'lib_book':library_books[book]} - self._remove_from_iTunes(btr) - elif iswindows: - if book not in cached_books and \ - library_books[book].Description.startswith(self.description_prefix): - if DEBUG: - logger().info(" '%s' not found on iDevice, removing from iTunes" % book) - btr = { 'title':library_books[book].Name, - 'author':library_books[book].Artist, - 'lib_book':library_books[book]} - self._remove_from_iTunes(btr) - if DEBUG: - logger().info() + if PURGE_ORPHANS: + if DEBUG: + logger().info(" %s._purge_orphans()" % self.__class__.__name__) + #self._dump_library_books(library_books) + #logger().info(" cached_books:\n %s" % "\n ".join(cached_books.keys())) + + for book in library_books: + if isosx: + if book not in cached_books and \ + str(library_books[book].description()).startswith(self.description_prefix): + if DEBUG: + logger().info(" '%s' not found on iDevice, removing from iTunes" % book) + btr = { + 'title': library_books[book].name(), + 'author': library_books[book].artist(), + 'lib_book': library_books[book]} + self._remove_from_iTunes(btr) + elif iswindows: + if book not in cached_books and \ + library_books[book].Description.startswith(self.description_prefix): + if DEBUG: + logger().info(" '%s' not found on iDevice, removing from iTunes" % book) + btr = { + 'title': library_books[book].Name, + 'author': library_books[book].Artist, + 'lib_book': library_books[book]} + self._remove_from_iTunes(btr) + else: + if DEBUG: + logger().info(" %s._purge_orphans(disabled)" % self.__class__.__name__) def _remove_existing_copy(self, path, metadata): ''' ''' if DEBUG: - logger().info(" ITUNES._remove_existing_copy()") + logger().info(" %s._remove_existing_copy()" % self.__class__.__name__) if self.manual_sync_mode: # Delete existing from Device|Books, add to self.update_list # for deletion from booklist[0] during add_books_to_metadata for book in self.cached_books: - if self.cached_books[book]['uuid'] == metadata.uuid or \ - (self.cached_books[book]['title'] == metadata.title and \ - self.cached_books[book]['author'] == authors_to_string(metadata.authors)): + if (self.cached_books[book]['uuid'] == metadata.uuid or + (self.cached_books[book]['title'] == metadata.title and + self.cached_books[book]['author'] == metadata.author)): self.update_list.append(self.cached_books[book]) - - if DEBUG: - logger().info( " deleting device book '%s'" % (metadata.title)) self._remove_from_device(self.cached_books[book]) - - if DEBUG: - logger().info(" deleting library book '%s'" % metadata.title) self._remove_from_iTunes(self.cached_books[book]) break else: @@ -2581,12 +2602,12 @@ class ITUNES(DriverBase): # Delete existing from Library|Books, add to self.update_list # for deletion from booklist[0] during add_books_to_metadata for book in self.cached_books: - if self.cached_books[book]['uuid'] == metadata.uuid or \ - (self.cached_books[book]['title'] == metadata.title and \ - self.cached_books[book]['author'] == authors_to_string(metadata.authors)): + if (self.cached_books[book]['uuid'] == metadata.uuid or + (self.cached_books[book]['title'] == metadata.title and \ + self.cached_books[book]['author'] == metadata.author)): self.update_list.append(self.cached_books[book]) if DEBUG: - logger().info( " deleting library book '%s'" % metadata.title) + logger().info(" deleting library book '%s'" % metadata.title) self._remove_from_iTunes(self.cached_books[book]) break else: @@ -2598,7 +2619,7 @@ class ITUNES(DriverBase): Windows assumes pythoncom wrapper ''' if DEBUG: - logger().info(" ITUNES._remove_from_device()") + logger().info(" %s._remove_from_device()" % self.__class__.__name__) if isosx: if DEBUG: logger().info(" deleting '%s' from iDevice" % cached_book['title']) @@ -2615,14 +2636,14 @@ class ITUNES(DriverBase): else: if DEBUG: logger().warning(" unable to remove '%s' by '%s' (%s) from device" % - (cached_book['title'],cached_book['author'],cached_book['uuid'])) + (cached_book['title'], cached_book['author'], cached_book['uuid'])) def _remove_from_iTunes(self, cached_book): ''' iTunes does not delete books from storage when removing from database via automation ''' if DEBUG: - logger().info(" ITUNES._remove_from_iTunes():") + logger().info(" %s._remove_from_iTunes():" % self.__class__.__name__) if isosx: ''' Manually remove the book from iTunes storage ''' @@ -2664,7 +2685,8 @@ class ITUNES(DriverBase): except: # We get here if there was an error with .location().path if DEBUG: - logger().info(" '%s' not found in iTunes storage" % cached_book['title']) + logger().info(" '%s' by %s not found in iTunes storage" % + (cached_book['title'], cached_book['author'])) # Delete the book from the iTunes database try: @@ -2739,16 +2761,16 @@ class ITUNES(DriverBase): from lxml import etree if DEBUG: - logger().info(" ITUNES._update_epub_metadata()") + logger().info(" %s._update_epub_metadata()" % self.__class__.__name__) # Fetch plugboard updates metadata_x = self._xform_metadata_via_plugboard(metadata, 'epub') # Refresh epub metadata - with open(fpath,'r+b') as zfo: + with open(fpath, 'r+b') as zfo: if False: try: - zf_opf = ZipFile(fpath,'r') + zf_opf = ZipFile(fpath, 'r') fnames = zf_opf.namelist() opf = [x for x in fnames if '.opf' in x][0] except: @@ -2765,7 +2787,7 @@ class ITUNES(DriverBase): timestamp = ts.get('content') old_ts = parse_date(timestamp) metadata.timestamp = datetime.datetime(old_ts.year, old_ts.month, old_ts.day, old_ts.hour, - old_ts.minute, old_ts.second, old_ts.microsecond+1, old_ts.tzinfo) + old_ts.minute, old_ts.second, old_ts.microsecond + 1, old_ts.tzinfo) if DEBUG: logger().info(" existing timestamp: %s" % metadata.timestamp) else: @@ -2785,10 +2807,10 @@ class ITUNES(DriverBase): if _('News') in metadata_x.tags or \ _('Catalog') in metadata_x.tags: if metadata_x.title.find('[') > 0: - metadata_x.title = metadata_x.title[:metadata_x.title.find('[')-1] + metadata_x.title = metadata_x.title[:metadata_x.title.find('[') - 1] date_as_author = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) metadata_x.author = metadata_x.authors = [date_as_author] - sort_author = re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', metadata_x.title).rstrip() + sort_author = re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', metadata_x.title).rstrip() metadata_x.author_sort = '%s %s' % (sort_author, strftime('%Y-%m-%d')) # Remove any non-alpha category tags @@ -2807,7 +2829,7 @@ class ITUNES(DriverBase): Trigger a sync, wait for completion ''' if DEBUG: - logger().info(" ITUNES:_update_device():\n %s" % msg) + logger().info(" %s:_update_device():\n %s" % (self.__class__.__name__, msg)) if isosx: self.iTunes.update() @@ -2855,7 +2877,7 @@ class ITUNES(DriverBase): ''' ''' if DEBUG: - logger().info(" ITUNES._update_iTunes_metadata()") + logger().info(" %s._update_iTunes_metadata()" % self.__class__.__name__) STRIP_TAGS = re.compile(r'<[^<]*?/?>') @@ -2869,7 +2891,7 @@ class ITUNES(DriverBase): lb_added.album.set(metadata_x.title) lb_added.artist.set(authors_to_string(metadata_x.authors)) lb_added.composer.set(metadata_x.uuid) - lb_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) + lb_added.description.set("%s %s" % (self.description_prefix, strftime('%Y-%m-%d %H:%M:%S'))) lb_added.enabled.set(True) lb_added.sort_artist.set(icu_title(metadata_x.author_sort)) lb_added.sort_name.set(metadata_x.title_sort) @@ -2880,7 +2902,7 @@ class ITUNES(DriverBase): db_added.album.set(metadata_x.title) db_added.artist.set(authors_to_string(metadata_x.authors)) db_added.composer.set(metadata_x.uuid) - db_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) + db_added.description.set("%s %s" % (self.description_prefix, strftime('%Y-%m-%d %H:%M:%S'))) db_added.enabled.set(True) db_added.sort_artist.set(icu_title(metadata_x.author_sort)) db_added.sort_name.set(metadata_x.title_sort) @@ -2888,17 +2910,17 @@ class ITUNES(DriverBase): if metadata_x.comments: if lb_added: - lb_added.comment.set(STRIP_TAGS.sub('',metadata_x.comments)) + lb_added.comment.set(STRIP_TAGS.sub('', metadata_x.comments)) if db_added: - db_added.comment.set(STRIP_TAGS.sub('',metadata_x.comments)) + db_added.comment.set(STRIP_TAGS.sub('', metadata_x.comments)) if metadata_x.rating: if lb_added: - lb_added.rating.set(metadata_x.rating*10) + lb_added.rating.set(metadata_x.rating * 10) # iBooks currently doesn't allow setting rating ... ? try: if db_added: - db_added.rating.set(metadata_x.rating*10) + db_added.rating.set(metadata_x.rating * 10) except: pass @@ -2907,13 +2929,13 @@ class ITUNES(DriverBase): # If title_sort applied in plugboard, that overrides using series/index as title_sort if metadata_x.series and self.settings().extra_customization[self.USE_SERIES_AS_CATEGORY]: if DEBUG: - logger().info(" ITUNES._update_iTunes_metadata()") + logger().info(" %s._update_iTunes_metadata()" % self.__class__.__name__) logger().info(" using Series name '%s' as Genre" % metadata_x.series) # Format the index as a sort key index = metadata_x.series_index integer = int(index) - fraction = index-integer + fraction = index - integer series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0')) if lb_added: # If no title_sort plugboard tweak, create sort_name from series/index @@ -2949,7 +2971,6 @@ class ITUNES(DriverBase): db_added.genre.set(tag) break - elif metadata_x.tags is not None: if DEBUG: logger().info(" %susing Tag as Genre" % @@ -2968,7 +2989,7 @@ class ITUNES(DriverBase): lb_added.Album = metadata_x.title lb_added.Artist = authors_to_string(metadata_x.authors) lb_added.Composer = metadata_x.uuid - lb_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) + lb_added.Description = ("%s %s" % (self.description_prefix, strftime('%Y-%m-%d %H:%M:%S'))) lb_added.Enabled = True lb_added.SortArtist = icu_title(metadata_x.author_sort) lb_added.SortName = metadata_x.title_sort @@ -2981,7 +3002,7 @@ class ITUNES(DriverBase): db_added.Album = metadata_x.title db_added.Artist = authors_to_string(metadata_x.authors) db_added.Composer = metadata_x.uuid - db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) + db_added.Description = ("%s %s" % (self.description_prefix, strftime('%Y-%m-%d %H:%M:%S'))) db_added.Enabled = True db_added.SortArtist = icu_title(metadata_x.author_sort) db_added.SortName = metadata_x.title_sort @@ -2989,17 +3010,17 @@ class ITUNES(DriverBase): if metadata_x.comments: if lb_added: - lb_added.Comment = (STRIP_TAGS.sub('',metadata_x.comments)) + lb_added.Comment = (STRIP_TAGS.sub('', metadata_x.comments)) if db_added: - db_added.Comment = (STRIP_TAGS.sub('',metadata_x.comments)) + db_added.Comment = (STRIP_TAGS.sub('', metadata_x.comments)) if metadata_x.rating: if lb_added: - lb_added.AlbumRating = (metadata_x.rating*10) + lb_added.AlbumRating = (metadata_x.rating * 10) # iBooks currently doesn't allow setting rating ... ? try: if db_added: - db_added.AlbumRating = (metadata_x.rating*10) + db_added.AlbumRating = (metadata_x.rating * 10) except: if DEBUG: logger().warning(" iTunes automation interface reported an error" @@ -3015,7 +3036,7 @@ class ITUNES(DriverBase): # Format the index as a sort key index = metadata_x.series_index integer = int(index) - fraction = index-integer + fraction = index - integer series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0')) if lb_added: # If no title_sort plugboard tweak, create sort_name from series/index @@ -3089,7 +3110,7 @@ class ITUNES(DriverBase): Ensure iDevice metadata is writable. Direct connect mode only ''' if DEBUG: - logger().info(" ITUNES._wait_for_writable_metadata()") + logger().info(" %s._wait_for_writable_metadata()" % self.__class__.__name__) logger().warning(" %s" % self.UNSUPPORTED_DIRECT_CONNECT_MODE_MESSAGE) attempts = 9 @@ -3113,7 +3134,7 @@ class ITUNES(DriverBase): def _xform_metadata_via_plugboard(self, book, format): ''' Transform book metadata from plugboard templates ''' if DEBUG: - logger().info(" ITUNES._xform_metadata_via_plugboard()") + logger().info(" %s._xform_metadata_via_plugboard()" % self.__class__.__name__) if self.plugboard_func: pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards) @@ -3143,6 +3164,7 @@ class ITUNES(DriverBase): newmi = book return newmi + class ITUNES_ASYNC(ITUNES): ''' This subclass allows the user to interact directly with iTunes via a menu option @@ -3151,16 +3173,16 @@ class ITUNES_ASYNC(ITUNES): name = 'iTunes interface' gui_name = 'Apple iTunes' icon = I('devices/itunes.png') - description = _('Communicate with iTunes.') + description = _('Communicate with iTunes.') # Plugboard ID DEVICE_PLUGBOARD_NAME = 'APPLE' connected = False - def __init__(self,path): + def __init__(self, path): if DEBUG: - logger().info("ITUNES_ASYNC:__init__()") + logger().info("%s.__init__()" % self.__class__.__name__) try: import appscript @@ -3210,7 +3232,7 @@ class ITUNES_ASYNC(ITUNES): """ if not oncard: if DEBUG: - logger().info("ITUNES_ASYNC:books()") + logger().info("%s.books()" % self.__class__.__name__) if self.settings().extra_customization[self.CACHE_COVERS]: logger().info(" Cover fetching/caching enabled") else: @@ -3224,7 +3246,7 @@ class ITUNES_ASYNC(ITUNES): if isosx: library_books = self._get_library_books() book_count = float(len(library_books)) - for (i,book) in enumerate(library_books): + for (i, book) in enumerate(library_books): format = 'pdf' if library_books[book].kind().startswith('PDF') else 'epub' this_book = Book(library_books[book].name(), library_books[book].artist()) #this_book.path = library_books[book].location().path @@ -3249,17 +3271,17 @@ class ITUNES_ASYNC(ITUNES): booklist.add_book(this_book, False) cached_books[this_book.path] = { - 'title':library_books[book].name(), - 'author':library_books[book].artist().split(' & '), - 'lib_book':library_books[book], - 'dev_book':None, + 'title': library_books[book].name(), + 'author': library_books[book].artist().split(' & '), + 'lib_book': library_books[book], + 'dev_book': None, 'uuid': library_books[book].composer(), 'format': format } if self.report_progress is not None: - self.report_progress((i+1)/book_count, - _('%(num)d of %(tot)d') % dict(num=i+1, tot=book_count)) + self.report_progress((i + 1) / book_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=book_count)) elif iswindows: import pythoncom, win32com.client @@ -3269,7 +3291,7 @@ class ITUNES_ASYNC(ITUNES): self.iTunes = win32com.client.Dispatch("iTunes.Application") library_books = self._get_library_books() book_count = float(len(library_books)) - for (i,book) in enumerate(library_books): + for (i, book) in enumerate(library_books): this_book = Book(library_books[book].Name, library_books[book].Artist) format = 'pdf' if library_books[book].KindAsString.startswith('PDF') else 'epub' this_book.path = self.path_template % (library_books[book].Name, @@ -3292,16 +3314,16 @@ class ITUNES_ASYNC(ITUNES): booklist.add_book(this_book, False) cached_books[this_book.path] = { - 'title':library_books[book].Name, - 'author':library_books[book].Artist.split(' & '), - 'lib_book':library_books[book], + 'title': library_books[book].Name, + 'author': library_books[book].Artist.split(' & '), + 'lib_book': library_books[book], 'uuid': library_books[book].Composer, 'format': format } if self.report_progress is not None: - self.report_progress((i+1)/book_count, - _('%(num)d of %(tot)d') % dict(num=i+1, + self.report_progress((i + 1) / book_count, + _('%(num)d of %(tot)d') % dict(num=i + 1, tot=book_count)) finally: @@ -3312,7 +3334,7 @@ class ITUNES_ASYNC(ITUNES): self.cached_books = cached_books if DEBUG: self._dump_booklist(booklist, 'returning from books()', indent=2) - self._dump_cached_books('returning from books()',indent=2) + self._dump_cached_books('returning from books()', indent=2) return booklist else: @@ -3324,7 +3346,7 @@ class ITUNES_ASYNC(ITUNES): are pending GUI jobs that need to communicate with the device. ''' if DEBUG: - logger().info("ITUNES_ASYNC:eject()") + logger().info("%s.eject()" % self.__class__.__name__) self.iTunes = None self.connected = False @@ -3339,7 +3361,7 @@ class ITUNES_ASYNC(ITUNES): particular device doesn't have any of these locations it should return -1. """ if DEBUG: - logger().info("ITUNES_ASYNC:free_space()") + logger().info("%s.free_space()" % self.__class__.__name__) free_space = 0 if isosx: s = os.statvfs(os.sep) @@ -3348,7 +3370,7 @@ class ITUNES_ASYNC(ITUNES): free_bytes = ctypes.c_ulonglong(0) ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(os.sep), None, None, ctypes.pointer(free_bytes)) free_space = free_bytes.value - return (free_space,-1,-1) + return (free_space, -1, -1) def get_device_information(self, end_session=True): """ @@ -3356,9 +3378,9 @@ class ITUNES_ASYNC(ITUNES): @return: (device name, device version, software version on device, mime type) """ if DEBUG: - logger().info("ITUNES_ASYNC:get_device_information()") + logger().info("%s.get_device_information()" % self.__class__.__name__) - return ('iTunes','hw v1.0','sw v1.0', 'mime type normally goes here') + return ('iTunes', 'hw v1.0', 'sw v1.0', 'mime type normally goes here') def is_usb_connected(self, devices_on_system, debug=False, only_presence=False): @@ -3382,7 +3404,8 @@ class ITUNES_ASYNC(ITUNES): raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE) if DEBUG: - logger().info("ITUNES_ASYNC.open(connected_device: %s)" % repr(connected_device)) + logger().info("%s.open(connected_device: %s)" % + (self.__class__.__name__, repr(connected_device))) # Confirm/create thumbs archive if not os.path.exists(self.cache_dir): @@ -3393,7 +3416,7 @@ class ITUNES_ASYNC(ITUNES): if not os.path.exists(self.archive_path): logger().info(" creating zip archive") zfw = ZipFile(self.archive_path, mode='w') - zfw.writestr("iTunes Thumbs Archive",'') + zfw.writestr("iTunes Thumbs Archive", '') zfw.close() else: if DEBUG: @@ -3401,7 +3424,7 @@ class ITUNES_ASYNC(ITUNES): # If enabled in config options, create/confirm an iTunes storage folder if not self.settings().extra_customization[self.USE_ITUNES_STORAGE]: - self.iTunes_local_storage = os.path.join(config_dir,'iTunes storage') + self.iTunes_local_storage = os.path.join(config_dir, 'iTunes storage') if not os.path.exists(self.iTunes_local_storage): if DEBUG: logger()(" creating iTunes_local_storage at '%s'" % self.iTunes_local_storage) @@ -3419,7 +3442,7 @@ class ITUNES_ASYNC(ITUNES): ''' if DEBUG: - logger().info("ITUNES_ASYNC.sync_booklists()") + logger().info("%s.sync_booklists()" % self.__class__.__name__) # Inform user of any problem books if self.problem_titles: @@ -3433,9 +3456,10 @@ class ITUNES_ASYNC(ITUNES): ''' ''' if DEBUG: - logger().info("ITUNES_ASYNC:unmount_device()") + logger().info("%s.unmount_device()" % self.__class__.__name__) self.connected = False + class BookList(list): ''' A list of books. Each Book object must have the fields: @@ -3488,16 +3512,17 @@ class BookList(list): ''' return {} + class Book(Metadata): ''' A simple class describing a book in the iTunes Books Library. See ebooks.metadata.book.base ''' - def __init__(self,title,author): + def __init__(self, title, author): Metadata.__init__(self, title, authors=author.split(' & ')) + self.author = author self.author_sort = author_to_author_sort(author) @property def title_sorter(self): return title_sort(self.title) - diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 60672d7167..0aa946c848 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -2357,6 +2357,8 @@ class KOBOTOUCH(KOBO): update_query = 'UPDATE content SET Series=?, SeriesNumber==? where BookID is Null and ContentID = ?' if book.series is None: update_values = (None, None, book.contentID, ) + elif book.series_index is None: # This should never happen, but... + update_values = (book.series, None, book.contentID, ) else: update_values = (book.series, "%g"%book.series_index, book.contentID, ) diff --git a/src/calibre/ebooks/mobi/reader/markup.py b/src/calibre/ebooks/mobi/reader/markup.py index 9940af4fa4..3330c65a0a 100644 --- a/src/calibre/ebooks/mobi/reader/markup.py +++ b/src/calibre/ebooks/mobi/reader/markup.py @@ -74,11 +74,12 @@ def remove_kindlegen_markup(parts): part = "".join(srcpieces) parts[i] = part - # we can safely remove all of the Kindlegen generated data-AmznPageBreak tags + # we can safely remove all of the Kindlegen generated data-AmznPageBreak + # attributes find_tag_with_AmznPageBreak_pattern = re.compile( r'''(<[^>]*\sdata-AmznPageBreak=[^>]*>)''', re.IGNORECASE) within_tag_AmznPageBreak_position_pattern = re.compile( - r'''\sdata-AmznPageBreak=['"][^'"]*['"]''') + r'''\sdata-AmznPageBreak=['"]([^'"]*)['"]''') for i in xrange(len(parts)): part = parts[i] @@ -86,10 +87,8 @@ def remove_kindlegen_markup(parts): for j in range(len(srcpieces)): tag = srcpieces[j] if tag.startswith('<'): - for m in within_tag_AmznPageBreak_position_pattern.finditer(tag): - replacement = '' - tag = within_tag_AmznPageBreak_position_pattern.sub(replacement, tag, 1) - srcpieces[j] = tag + srcpieces[j] = within_tag_AmznPageBreak_position_pattern.sub( + lambda m:' style="page-break-after:%s"'%m.group(1), tag) part = "".join(srcpieces) parts[i] = part diff --git a/src/calibre/ebooks/mobi/reader/mobi8.py b/src/calibre/ebooks/mobi/reader/mobi8.py index cdbea325fa..b6fe7d9aa0 100644 --- a/src/calibre/ebooks/mobi/reader/mobi8.py +++ b/src/calibre/ebooks/mobi/reader/mobi8.py @@ -44,6 +44,18 @@ def locate_beg_end_of_tag(ml, aid): return plt, pgt return 0, 0 +def reverse_tag_iter(block): + ''' Iterate over all tags in block in reverse order, i.e. last tag + to first tag. ''' + end = len(block) + while True: + pgt = block.rfind(b'>', 0, end) + if pgt == -1: break + plt = block.rfind(b'<', 0, pgt) + if plt == -1: break + yield block[plt:pgt+1] + end = plt + class Mobi8Reader(object): def __init__(self, mobi6_reader, log): @@ -275,13 +287,12 @@ class Mobi8Reader(object): return '%s/%s'%(fi.type, fi.filename), idtext def get_id_tag(self, pos): - # find the correct tag by actually searching in the destination - # textblock at position + # Find the first tag with a named anchor (name or id attribute) before + # pos fi = self.get_file_info(pos) if fi.num is None and fi.start is None: raise ValueError('No file contains pos: %d'%pos) textblock = self.parts[fi.num] - id_map = [] npos = pos - fi.start pgt = textblock.find(b'>', npos) plt = textblock.find(b'<', npos) @@ -290,28 +301,15 @@ class Mobi8Reader(object): if plt == npos or pgt < plt: npos = pgt + 1 textblock = textblock[0:npos] - # find id links only inside of tags - # inside any < > pair find all "id=' and return whatever is inside - # the quotes - id_pattern = re.compile(br'''<[^>]*\sid\s*=\s*['"]([^'"]*)['"][^>]*>''', - re.IGNORECASE) - for m in re.finditer(id_pattern, textblock): - id_map.append((m.start(), m.group(1))) + id_re = re.compile(br'''<[^>]+\sid\s*=\s*['"]([^'"]+)['"]''') + name_re = re.compile(br'''<\s*a\s*\sname\s*=\s*['"]([^'"]+)['"]''') + for tag in reverse_tag_iter(textblock): + m = id_re.match(tag) or name_re.match(tag) + if m is not None: + return m.group(1) - if not id_map: - # Found no id in the textblock, link must be to top of file - return b'' - # if npos is before first id= inside a tag, return the first - if npos < id_map[0][0]: - return id_map[0][1] - # if npos is after the last id= inside a tag, return the last - if npos > id_map[-1][0]: - return id_map[-1][1] - # otherwise find last id before npos - for i, item in enumerate(id_map): - if npos < item[0]: - return id_map[i-1][1] - return id_map[0][1] + # No tag found, link to start of file + return b'' def create_guide(self): guide = Guide() diff --git a/src/calibre/ebooks/oeb/reader.py b/src/calibre/ebooks/oeb/reader.py index 8124d49c6a..0461491d2f 100644 --- a/src/calibre/ebooks/oeb/reader.py +++ b/src/calibre/ebooks/oeb/reader.py @@ -320,13 +320,11 @@ class OEBReader(object): self.logger.warn(u'Spine item %r not found' % idref) continue item = manifest.ids[idref] - spine.add(item, elem.get('linear')) - for item in spine: - if item.media_type.lower() not in OEB_DOCS: - if not hasattr(item.data, 'xpath'): - self.oeb.log.warn('The item %s is not a XML document.' - ' Removing it from spine.'%item.href) - spine.remove(item) + if item.media_type.lower() in OEB_DOCS and hasattr(item.data, 'xpath'): + spine.add(item, elem.get('linear')) + else: + self.oeb.log.warn('The item %s is not a XML document.' + ' Removing it from spine.'%item.href) if len(spine) == 0: raise OEBError("Spine is empty") self._spine_add_extra() diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index a9cb951e35..76ab6b9096 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -27,10 +27,10 @@ def get_custom_size(opts): custom_size = None if opts.custom_size != None: width, sep, height = opts.custom_size.partition('x') - if height != '': + if height: try: - width = int(width) - height = int(height) + width = float(width) + height = float(height) custom_size = (width, height) except: custom_size = None diff --git a/src/calibre/library/catalogs/csv_xml.py b/src/calibre/library/catalogs/csv_xml.py index 49df903320..fd2bb5113b 100644 --- a/src/calibre/library/catalogs/csv_xml.py +++ b/src/calibre/library/catalogs/csv_xml.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai -__license__ = 'GPL v3' +__license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' @@ -12,6 +12,7 @@ from calibre.customize import CatalogPlugin from calibre.library.catalogs import FIELDS from calibre.customize.conversion import DummyReporter + class CSV_XML(CatalogPlugin): 'CSV/XML catalog generator' @@ -22,27 +23,27 @@ class CSV_XML(CatalogPlugin): supported_platforms = ['windows', 'osx', 'linux'] author = 'Greg Riker' version = (1, 0, 0) - file_types = set(['csv','xml']) + file_types = set(['csv', 'xml']) cli_options = [ Option('--fields', - default = 'all', - dest = 'fields', - action = None, - help = _('The fields to output when cataloging books in the ' + default='all', + dest='fields', + action=None, + help=_('The fields to output when cataloging books in the ' 'database. Should be a comma-separated list of fields.\n' 'Available fields: %(fields)s,\n' 'plus user-created custom fields.\n' 'Example: %(opt)s=title,authors,tags\n' "Default: '%%default'\n" - "Applies to: CSV, XML output formats")%dict( + "Applies to: CSV, XML output formats") % dict( fields=', '.join(FIELDS), opt='--fields')), Option('--sort-by', - default = 'id', - dest = 'sort_by', - action = None, - help = _('Output field to sort on.\n' + default='id', + dest='sort_by', + action=None, + help=_('Output field to sort on.\n' 'Available fields: author_sort, id, rating, size, timestamp, title_sort\n' "Default: '%default'\n" "Applies to: CSV, XML output formats"))] @@ -97,7 +98,7 @@ class CSV_XML(CatalogPlugin): for entry in data: entry['ondevice'] = db.catalog_plugin_on_device_temp_mapping[entry['id']]['ondevice'] - fm = {x:db.field_metadata.get(x, {}) for x in fields} + fm = {x: db.field_metadata.get(x, {}) for x in fields} if self.fmt == 'csv': outfile = codecs.open(path_to_output, 'w', 'utf8') @@ -113,7 +114,7 @@ class CSV_XML(CatalogPlugin): outstr = [] for field in fields: if field.startswith('#'): - item = db.get_field(entry['id'],field,index_is_id=True) + item = db.get_field(entry['id'], field, index_is_id=True) elif field == 'library_name': item = current_library elif field == 'title_sort': @@ -129,7 +130,7 @@ class CSV_XML(CatalogPlugin): for format in item: fmt_list.append(format.rpartition('.')[2].lower()) item = ', '.join(fmt_list) - elif field in ['authors','tags']: + elif field in ['authors', 'tags']: item = ', '.join(item) elif field == 'isbn': # Could be 9, 10 or 13 digits @@ -137,20 +138,20 @@ class CSV_XML(CatalogPlugin): elif field in ['pubdate', 'timestamp']: item = isoformat(item) elif field == 'comments': - item = item.replace(u'\r\n',u' ') - item = item.replace(u'\n',u' ') + item = item.replace(u'\r\n', u' ') + item = item.replace(u'\n', u' ') elif fm.get(field, {}).get('datatype', None) == 'rating' and item: - item = u'%.2g'%(item/2.0) + item = u'%.2g' % (item / 2.0) # Convert HTML to markdown text if type(item) is unicode: - opening_tag = re.search('<(\w+)(\x20|>)',item) + opening_tag = re.search('<(\w+)(\x20|>)', item) if opening_tag: closing_tag = re.search('<\/%s>$' % opening_tag.group(1), item) if closing_tag: item = html2text(item) - outstr.append(u'"%s"' % unicode(item).replace('"','""')) + outstr.append(u'"%s"' % unicode(item).replace('"', '""')) outfile.write(u','.join(outstr) + u'\n') outfile.close() @@ -165,14 +166,14 @@ class CSV_XML(CatalogPlugin): for field in fields: if field.startswith('#'): - val = db.get_field(r['id'],field,index_is_id=True) + val = db.get_field(r['id'], field, index_is_id=True) if not isinstance(val, (str, unicode)): val = unicode(val) - item = getattr(E, field.replace('#','_'))(val) + item = getattr(E, field.replace('#', '_'))(val) record.append(item) for field in ('id', 'uuid', 'publisher', 'rating', 'size', - 'isbn','ondevice', 'identifiers'): + 'isbn', 'ondevice', 'identifiers'): if field in fields: val = r[field] if not val: @@ -180,7 +181,7 @@ class CSV_XML(CatalogPlugin): if not isinstance(val, (str, unicode)): if (fm.get(field, {}).get('datatype', None) == 'rating' and val): - val = u'%.2g'%(val/2.0) + val = u'%.2g' % (val / 2.0) val = unicode(val) item = getattr(E, field)(val) record.append(item) @@ -227,4 +228,3 @@ class CSV_XML(CatalogPlugin): with open(path_to_output, 'w') as f: f.write(etree.tostring(root, encoding='utf-8', xml_declaration=True, pretty_print=True)) - diff --git a/src/calibre/library/catalogs/epub_mobi.py b/src/calibre/library/catalogs/epub_mobi.py index a50c7ba861..9a7e728220 100644 --- a/src/calibre/library/catalogs/epub_mobi.py +++ b/src/calibre/library/catalogs/epub_mobi.py @@ -3,7 +3,7 @@ from __future__ import (unicode_literals, division, absolute_import, print_function) -__license__ = 'GPL v3' +__license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' __docformat__ = 'restructuredtext en' @@ -21,6 +21,7 @@ from calibre.utils.localization import get_lang Option = namedtuple('Option', 'option, default, dest, action, help') + class EPUB_MOBI(CatalogPlugin): 'ePub catalog generator' @@ -30,29 +31,29 @@ class EPUB_MOBI(CatalogPlugin): minimum_calibre_version = (0, 7, 40) author = 'Greg Riker' version = (1, 0, 0) - file_types = set(['azw3','epub','mobi']) + file_types = set(['azw3', 'epub', 'mobi']) THUMB_SMALLEST = "1.0" THUMB_LARGEST = "2.0" - cli_options = [Option('--catalog-title', # {{{ - default = 'My Books', - dest = 'catalog_title', - action = None, - help = _('Title of generated catalog used as title in metadata.\n' + cli_options = [Option('--catalog-title', # {{{ + default='My Books', + dest='catalog_title', + action=None, + help=_('Title of generated catalog used as title in metadata.\n' "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--cross-reference-authors', default=False, dest='cross_reference_authors', - action = 'store_true', + action='store_true', help=_("Create cross-references in Authors section for books with multiple authors.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--debug-pipeline', default=None, dest='debug_pipeline', - action = None, + action=None, help=_("Save the output from different stages of the conversion " "pipeline to the specified " "directory. Useful if you are unsure at which stage " @@ -62,7 +63,7 @@ class EPUB_MOBI(CatalogPlugin): Option('--exclude-genre', default='\[.+\]|^\+$', dest='exclude_genre', - action = None, + action=None, help=_("Regex describing tags to exclude as genres.\n" "Default: '%default' excludes bracketed tags, e.g. '[Project Gutenberg]', and '+', the default tag for read books.\n" "Applies to: AZW3, ePub, MOBI output formats")), @@ -82,63 +83,63 @@ class EPUB_MOBI(CatalogPlugin): Option('--generate-authors', default=False, dest='generate_authors', - action = 'store_true', + action='store_true', help=_("Include 'Authors' section in catalog.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--generate-descriptions', default=False, dest='generate_descriptions', - action = 'store_true', + action='store_true', help=_("Include 'Descriptions' section in catalog.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--generate-genres', default=False, dest='generate_genres', - action = 'store_true', + action='store_true', help=_("Include 'Genres' section in catalog.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--generate-titles', default=False, dest='generate_titles', - action = 'store_true', + action='store_true', help=_("Include 'Titles' section in catalog.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--generate-series', default=False, dest='generate_series', - action = 'store_true', + action='store_true', help=_("Include 'Series' section in catalog.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--generate-recently-added', default=False, dest='generate_recently_added', - action = 'store_true', + action='store_true', help=_("Include 'Recently Added' section in catalog.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--genre-source-field', default='Tags', dest='genre_source_field', - action = None, + action=None, help=_("Source field for Genres section.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--header-note-source-field', default='', dest='header_note_source_field', - action = None, + action=None, help=_("Custom field containing note text to insert in Description header.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--merge-comments-rule', default='::', dest='merge_comments_rule', - action = None, + action=None, help=_("#:[before|after]:[True|False] specifying:\n" " Custom field containing notes to merge with Comments\n" " [before|after] Placement of notes with respect to Comments\n" @@ -148,7 +149,7 @@ class EPUB_MOBI(CatalogPlugin): Option('--output-profile', default=None, dest='output_profile', - action = None, + action=None, help=_("Specifies the output profile. In some cases, an output profile is required to optimize the catalog for the device. For example, 'kindle' or 'kindle_dx' creates a structured Table of Contents with Sections and Articles.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), @@ -164,14 +165,14 @@ class EPUB_MOBI(CatalogPlugin): Option('--use-existing-cover', default=False, dest='use_existing_cover', - action = 'store_true', + action='store_true', help=_("Replace existing cover when generating the catalog.\n" "Default: '%default'\n" "Applies to: AZW3, ePub, MOBI output formats")), Option('--thumb-width', default='1.0', dest='thumb_width', - action = None, + action=None, help=_("Size hint (in inches) for book covers in catalog.\n" "Range: 1.0 - 2.0\n" "Default: '%default'\n" @@ -199,7 +200,7 @@ class EPUB_MOBI(CatalogPlugin): if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower(): opts.connected_kindle = True if opts.connected_device['serial'] and \ - opts.connected_device['serial'][:4] in ['B004','B005']: + opts.connected_device['serial'][:4] in ['B004', 'B005']: op = "kindle_dx" else: op = "kindle" @@ -209,7 +210,7 @@ class EPUB_MOBI(CatalogPlugin): opts.output_profile = op opts.basename = "Catalog" - opts.cli_environment = not hasattr(opts,'sync') + opts.cli_environment = not hasattr(opts, 'sync') # Hard-wired to always sort descriptions by author, with series after non-series opts.sort_descriptions_by_author = True @@ -278,14 +279,14 @@ class EPUB_MOBI(CatalogPlugin): opts.generate_genres = True opts.generate_recently_added = True opts.generate_descriptions = True - sections_list = ['Authors','Titles','Series','Genres','Recently Added','Descriptions'] + sections_list = ['Authors', 'Titles', 'Series', 'Genres', 'Recently Added', 'Descriptions'] else: opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***') - return ["No Included Sections","No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"] + return ["No Included Sections", "No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"] if opts.fmt == 'mobi' and sections_list == ['Descriptions']: warning = _("\n*** Adding 'By Authors' Section required for MOBI output ***") opts.log.warn(warning) - sections_list.insert(0,'Authors') + sections_list.insert(0, 'Authors') opts.generate_authors = True opts.log(u" Sections: %s" % ', '.join(sections_list)) @@ -294,14 +295,14 @@ class EPUB_MOBI(CatalogPlugin): # Limit thumb_width to 1.0" - 2.0" try: if float(opts.thumb_width) < float(self.THUMB_SMALLEST): - log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_SMALLEST)) + log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = self.THUMB_SMALLEST if float(opts.thumb_width) > float(self.THUMB_LARGEST): - log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_LARGEST)) + log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_LARGEST)) opts.thumb_width = self.THUMB_LARGEST opts.thumb_width = "%.2f" % float(opts.thumb_width) except: - log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_SMALLEST)) + log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width, self.THUMB_SMALLEST)) opts.thumb_width = "1.0" # eval prefix_rules if passed from command line @@ -331,13 +332,13 @@ class EPUB_MOBI(CatalogPlugin): keys.sort() build_log.append(" opts:") for key in keys: - if key in ['catalog_title','author_clip','connected_kindle','creator', - 'cross_reference_authors','description_clip','exclude_book_marker', - 'exclude_genre','exclude_tags','exclusion_rules', 'fmt', - 'genre_source_field', 'header_note_source_field','merge_comments_rule', - 'output_profile','prefix_rules','read_book_marker', - 'search_text','sort_by','sort_descriptions_by_author','sync', - 'thumb_width','use_existing_cover','wishlist_tag']: + if key in ['catalog_title', 'author_clip', 'connected_kindle', 'creator', + 'cross_reference_authors', 'description_clip', 'exclude_book_marker', + 'exclude_genre', 'exclude_tags', 'exclusion_rules', 'fmt', + 'genre_source_field', 'header_note_source_field', 'merge_comments_rule', + 'output_profile', 'prefix_rules', 'read_book_marker', + 'search_text', 'sort_by', 'sort_descriptions_by_author', 'sync', + 'thumb_width', 'use_existing_cover', 'wishlist_tag']: build_log.append(" %s: %s" % (key, repr(opts_dict[key]))) if opts.verbose: log('\n'.join(line for line in build_log)) @@ -370,8 +371,8 @@ class EPUB_MOBI(CatalogPlugin): """ GENERATE_DEBUG_EPUB = False if GENERATE_DEBUG_EPUB: - catalog_debug_path = os.path.join(os.path.expanduser('~'),'Desktop','Catalog debug') - setattr(opts,'debug_pipeline',os.path.expanduser(catalog_debug_path)) + catalog_debug_path = os.path.join(os.path.expanduser('~'), 'Desktop', 'Catalog debug') + setattr(opts, 'debug_pipeline', os.path.expanduser(catalog_debug_path)) dp = getattr(opts, 'debug_pipeline', None) if dp is not None: @@ -381,11 +382,13 @@ class EPUB_MOBI(CatalogPlugin): if opts.output_profile and opts.output_profile.startswith("kindle"): recommendations.append(('output_profile', opts.output_profile, OptionRecommendation.HIGH)) - recommendations.append(('book_producer',opts.output_profile, + recommendations.append(('book_producer', opts.output_profile, OptionRecommendation.HIGH)) if opts.fmt == 'mobi': recommendations.append(('no_inline_toc', True, OptionRecommendation.HIGH)) + recommendations.append(('verbose', 2, + OptionRecommendation.HIGH)) # Use existing cover or generate new cover cpath = None @@ -432,14 +435,13 @@ class EPUB_MOBI(CatalogPlugin): from calibre.ebooks.epub import initialize_container from calibre.ebooks.tweak import zip_rebuilder from calibre.utils.zipfile import ZipFile - input_path = os.path.join(catalog_debug_path,'input') - epub_shell = os.path.join(catalog_debug_path,'epub_shell.zip') + input_path = os.path.join(catalog_debug_path, 'input') + epub_shell = os.path.join(catalog_debug_path, 'epub_shell.zip') initialize_container(epub_shell, opf_name='content.opf') with ZipFile(epub_shell, 'r') as zf: zf.extractall(path=input_path) os.remove(epub_shell) - zip_rebuilder(input_path, os.path.join(catalog_debug_path,'input.epub')) + zip_rebuilder(input_path, os.path.join(catalog_debug_path, 'input.epub')) # returns to gui2.actions.catalog:catalog_generated() return catalog.error - diff --git a/src/calibre/library/catalogs/epub_mobi_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py index fa38b2ba83..65ed89da5c 100644 --- a/src/calibre/library/catalogs/epub_mobi_builder.py +++ b/src/calibre/library/catalogs/epub_mobi_builder.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -__license__ = 'GPL v3' +__license__ = 'GPL v3' __copyright__ = '2010, Greg Riker' import datetime, htmlentitydefs, os, platform, re, shutil, unicodedata, zlib @@ -25,6 +25,7 @@ from calibre.utils.icu import capitalize, collation_order, sort_key from calibre.utils.magick.draw import thumbnail from calibre.utils.zipfile import ZipFile + class CatalogBuilder(object): ''' Generates catalog source files from calibre database @@ -50,7 +51,7 @@ class CatalogBuilder(object): # Multiple numbers create 'Last x days', 'x to y days ago' ... # e.g, [7,15,30,60] or [30] # [] = No date ranges added - DATE_RANGE=[30] + DATE_RANGE = [30] # Text used in generated catalog for title section with other-than-ASCII leading letter SYMBOLS = _('Symbols') @@ -98,7 +99,6 @@ class CatalogBuilder(object): else: return ' ' - def __init__(self, db, _opts, plugin, report_progress=DummyReporter(), stylesheet="content/stylesheet.css", @@ -120,11 +120,13 @@ class CatalogBuilder(object): _opts.output_profile and _opts.output_profile.startswith("kindle")) else False + self.all_series = set() self.authors = None self.bookmarked_books = None self.bookmarked_books_by_date_read = None self.books_by_author = None self.books_by_date_range = None + self.books_by_description = [] self.books_by_month = None self.books_by_series = None self.books_by_title = None @@ -135,11 +137,12 @@ class CatalogBuilder(object): self.generate_recently_read = False self.genres = [] self.genre_tags_dict = \ - self.filter_genre_tags(max_len = 245 - len("%s/Genre_.html" % self.content_dir)) \ + self.filter_genre_tags(max_len=245 - len("%s/Genre_.html" % self.content_dir)) \ if self.opts.generate_genres else None self.html_filelist_1 = [] self.html_filelist_2 = [] - self.merge_comments_rule = dict(zip(['field','position','hr'], + self.individual_authors = None + self.merge_comments_rule = dict(zip(['field', 'position', 'hr'], _opts.merge_comments_rule.split(':'))) self.ncx_soup = None self.output_profile = self.get_output_profile(_opts) @@ -154,6 +157,7 @@ class CatalogBuilder(object): self.total_steps = 6.0 self.use_series_prefix_in_titles_section = False + self.dump_custom_fields() self.books_to_catalog = self.fetch_books_to_catalog() self.compute_total_steps() self.calculate_thumbnail_dimensions() @@ -202,7 +206,7 @@ class CatalogBuilder(object): else: index = book['series_index'] integer = int(index) - fraction = index-integer + fraction = index - integer series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0')) key = '%s ~%s %s' % (self._kf_author_to_author_sort(book['author']), self.generate_sort_title(book['series']), @@ -228,7 +232,7 @@ class CatalogBuilder(object): else: index = book['series_index'] integer = int(index) - fraction = index-integer + fraction = index - integer series_index = u'%04d%s' % (integer, str(u'%0.4f' % fraction).lstrip(u'0')) fs = u'{:<%d}~{!s}{!s}' % longest_author_sort key = fs.format(capitalize(book['author_sort']), @@ -239,7 +243,7 @@ class CatalogBuilder(object): def _kf_books_by_series_sorter(self, book): index = book['series_index'] integer = int(index) - fraction = index-integer + fraction = index - integer series_index = '%04d%s' % (integer, str('%0.4f' % fraction).lstrip('0')) key = '%s %s' % (self.generate_sort_title(book['series']), series_index) @@ -335,8 +339,8 @@ class CatalogBuilder(object): self.thumb_height = self.thumb_width * 1.33 if 'kindle' in x.short_name and self.opts.fmt == 'mobi': # Kindle DPI appears to be off by a factor of 2 - self.thumb_width = self.thumb_width/2 - self.thumb_height = self.thumb_height/2 + self.thumb_width = self.thumb_width / 2 + self.thumb_height = self.thumb_height / 2 break if self.opts.verbose: self.opts.log(" Thumbnails:") @@ -395,7 +399,7 @@ class CatalogBuilder(object): self.opts.log.info(' creating thumbnail archive, thumb_width: %1.2f"' % float(self.opts.thumb_width)) with ZipFile(self.thumbs_path, mode='w') as zfw: - zfw.writestr("Catalog Thumbs Archive",'') + zfw.writestr("Catalog Thumbs Archive", '') else: try: with ZipFile(self.thumbs_path, mode='r') as zfr: @@ -410,9 +414,9 @@ class CatalogBuilder(object): if float(cached_thumb_width) != float(self.opts.thumb_width): self.opts.log.warning(" invalidating cache at '%s'" % self.thumbs_path) self.opts.log.warning(' thumb_width changed: %1.2f" => %1.2f"' % - (float(cached_thumb_width),float(self.opts.thumb_width))) + (float(cached_thumb_width), float(self.opts.thumb_width))) with ZipFile(self.thumbs_path, mode='w') as zfw: - zfw.writestr("Catalog Thumbs Archive",'') + zfw.writestr("Catalog Thumbs Archive", '') else: self.opts.log.info(' existing thumb cache at %s, cached_thumb_width: %1.2f"' % (self.thumbs_path, float(cached_thumb_width))) @@ -447,7 +451,7 @@ class CatalogBuilder(object): hits.remove(amp) for hit in hits: name = hit[1:-1] - if htmlentitydefs.name2codepoint.has_key(name): + if htmlentitydefs.name2codepoint in name: s = s.replace(hit, unichr(htmlentitydefs.name2codepoint[name])) s = s.replace(amp, "&") return s @@ -468,17 +472,17 @@ class CatalogBuilder(object): self.create_catalog_directory_structure() catalog_resources = P("catalog") - files_to_copy = [('','DefaultCover.jpg'), - ('content','stylesheet.css')] + files_to_copy = [('', 'DefaultCover.jpg'), + ('content', 'stylesheet.css')] if self.generate_for_kindle_mobi: - files_to_copy.extend([('images','mastheadImage.gif')]) + files_to_copy.extend([('images', 'mastheadImage.gif')]) for file in files_to_copy: if file[0] == '': - shutil.copy(os.path.join(catalog_resources,file[1]), + shutil.copy(os.path.join(catalog_resources, file[1]), self.catalog_path) else: - shutil.copy(os.path.join(catalog_resources,file[1]), + shutil.copy(os.path.join(catalog_resources, file[1]), os.path.join(self.catalog_path, file[0])) if self.generate_for_kindle_mobi: @@ -530,14 +534,14 @@ class CatalogBuilder(object): authors = [(record['author'], record['author_sort']) for record in books_by_author] current_author = authors[0] - for (i,author) in enumerate(authors): + for (i, author) in enumerate(authors): if author != current_author and i: if author[0] == current_author[0]: if self.opts.fmt == 'mobi': # Exit if building MOBI error_msg = _("

    Inconsistent Author Sort values for Author
    " + "'{!s}':

    ".format(author[0]) + - "

    {!s} != {!s}

    ".format(author[1],current_author[1]) + + "

    {!s} != {!s}

    ".format(author[1], current_author[1]) + "

    Unable to build MOBI catalog.
    " + "Select all books by '{!s}', apply correct Author Sort value in Edit Metadata dialog, then rebuild the catalog.\n

    ".format(author[0])) @@ -553,7 +557,7 @@ class CatalogBuilder(object): self.error.append('Author Sort mismatch') error_msg = _("Warning: Inconsistent Author Sort values for Author '{!s}':\n".format(author[0]) + - " {!s} != {!s}\n".format(author[1],current_author[1])) + " {!s} != {!s}\n".format(author[1], current_author[1])) self.opts.log.warn('\n*** Metadata warning ***') self.opts.log.warn(error_msg) self.error.append(error_msg) @@ -576,7 +580,7 @@ class CatalogBuilder(object): """ def _log_prefix_rule_match_info(rule, record, matched): self.opts.log.info(" %s '%s' by %s (%s: '%s' contains '%s')" % - (rule['prefix'],record['title'], + (rule['prefix'], record['title'], record['authors'][0], rule['name'], self.db.metadata_for_field(rule['field'])['name'], matched)) @@ -585,10 +589,10 @@ class CatalogBuilder(object): for rule in self.prefix_rules: # Literal comparison for Tags field if rule['field'].lower() == 'tags': - if rule['pattern'].lower() in map(unicode.lower,record['tags']): - if self.opts.verbose: + if rule['pattern'].lower() in map(unicode.lower, record['tags']): + if self.DEBUG and self.opts.verbose: self.opts.log.info(" %s '%s' by %s (%s: Tags includes '%s')" % - (rule['prefix'],record['title'], + (rule['prefix'], record['title'], record['authors'][0], rule['name'], rule['pattern'])) return rule['prefix'] @@ -602,7 +606,7 @@ class CatalogBuilder(object): if field_contents == '': field_contents = None - if (self.db.metadata_for_field(rule['field'])['datatype'] == 'bool' and + if (self.db.metadata_for_field(rule['field'])['datatype'] == 'bool' and field_contents is None): # Handle condition where field is a bool and contents is None, # which is displayed as No @@ -616,7 +620,7 @@ class CatalogBuilder(object): try: if re.search(rule['pattern'], unicode(field_contents), re.IGNORECASE) is not None: - if self.opts.verbose: + if self.DEBUG: _log_prefix_rule_match_info(rule, record, field_contents) return rule['prefix'] except: @@ -624,12 +628,24 @@ class CatalogBuilder(object): self.opts.log.error("pattern failed to compile: %s" % rule['pattern']) pass elif field_contents is None and rule['pattern'] == 'None': - if self.opts.verbose: + if self.DEBUG: _log_prefix_rule_match_info(rule, record, field_contents) return rule['prefix'] return None + def dump_custom_fields(self): + """ + Dump custom field mappings for debugging + """ + if self.opts.verbose: + self.opts.log.info(" Custom fields:") + all_custom_fields = self.db.custom_field_keys() + for cf in all_custom_fields: + self.opts.log.info(" %-20s %-20s %s" % + (cf, "'%s'" % self.db.metadata_for_field(cf)['name'], + self.db.metadata_for_field(cf)['datatype'])) + def establish_equivalencies(self, item_list, key=None): """ Return icu equivalent sort letter. @@ -647,9 +663,9 @@ class CatalogBuilder(object): # Hack to force the cataloged leading letter to be # an unadorned character if the accented version sorts before the unaccented exceptions = { - u'Ä':u'A', - u'Ö':u'O', - u'Ü':u'U' + u'Ä': u'A', + u'Ö': u'O', + u'Ü': u'U' } if key is not None: @@ -697,7 +713,7 @@ class CatalogBuilder(object): print(" establish_equivalencies():") if key: for idx, item in enumerate(item_list): - print(" %s %s" % (cl_list[idx],item[sort_field])) + print(" %s %s" % (cl_list[idx], item[sort_field])) else: print(" %s %s" % (cl_list[idx], item)) @@ -716,7 +732,8 @@ class CatalogBuilder(object): Outputs: books_by_author: database, sorted by author - authors: list of unique authors + authors: list of book authors. Two credited authors are considered an + individual entity error: author_sort mismatches Return: @@ -728,6 +745,13 @@ class CatalogBuilder(object): books_by_author = list(self.books_to_catalog) self.detect_author_sort_mismatches(books_by_author) + + # Assumes books_by_title already populated + # init books_by_description before relisting multiple authors + if self.opts.generate_descriptions: + books_by_description = list(books_by_author) if self.opts.sort_descriptions_by_author \ + else list(self.books_by_title) + if self.opts.cross_reference_authors: books_by_author = self.relist_multiple_authors(books_by_author) @@ -737,16 +761,20 @@ class CatalogBuilder(object): asl = [i['author_sort'] for i in books_by_author] las = max(asl, key=len) + if self.opts.generate_descriptions: + self.books_by_description = sorted(books_by_description, + key=lambda x: sort_key(self._kf_books_by_author_sorter_author_sort(x, len(las)))) + books_by_author = sorted(books_by_author, key=lambda x: sort_key(self._kf_books_by_author_sorter_author_sort(x, len(las)))) if self.DEBUG and self.opts.verbose: tl = [i['title'] for i in books_by_author] lt = max(tl, key=len) - fs = '{:<6}{:<%d} {:<%d} {!s}' % (len(lt),len(las)) - print(fs.format('','Title','Author','Series')) + fs = '{:<6}{:<%d} {:<%d} {!s}' % (len(lt), len(las)) + print(fs.format('', 'Title', 'Author', 'Series')) for i in books_by_author: - print(fs.format('', i['title'],i['author_sort'],i['series'])) + print(fs.format('', i['title'], i['author_sort'], i['series'])) # Build the unique_authors set from existing data authors = [(record['author'], capitalize(record['author_sort'])) for record in books_by_author] @@ -758,7 +786,8 @@ class CatalogBuilder(object): current_author = authors[0] multiple_authors = False unique_authors = [] - for (i,author) in enumerate(authors): + individual_authors = set() + for (i, author) in enumerate(authors): if author != current_author: # Note that current_author and author are tuples: (friendly, sort) multiple_authors = True @@ -768,7 +797,7 @@ class CatalogBuilder(object): books_by_current_author)) current_author = author books_by_current_author = 1 - elif i==0 and len(authors) == 1: + elif i == 0 and len(authors) == 1: # Allow for single-book lists unique_authors.append((current_author[0], icu_title(current_author[1]), books_by_current_author)) @@ -780,14 +809,23 @@ class CatalogBuilder(object): unique_authors.append((current_author[0], icu_title(current_author[1]), books_by_current_author)) + self.authors = list(unique_authors) + self.books_by_author = books_by_author + + for ua in unique_authors: + for ia in ua[0].replace(' & ', ' & ').split(' & '): + individual_authors.add(ia) + self.individual_authors = list(individual_authors) + if self.DEBUG and self.opts.verbose: self.opts.log.info("\nfetch_books_by_author(): %d unique authors" % len(unique_authors)) for author in unique_authors: self.opts.log.info((u" %-50s %-25s %2d" % (author[0][0:45], author[1][0:20], author[2])).encode('utf-8')) + self.opts.log.info("\nfetch_books_by_author(): %d individual authors" % len(individual_authors)) + for author in sorted(individual_authors): + self.opts.log.info("%s" % author) - self.authors = unique_authors - self.books_by_author = books_by_author return True def fetch_books_by_title(self): @@ -869,6 +907,7 @@ class CatalogBuilder(object): this_title['title'] = self.convert_html_entities(record['title']) if record['series']: this_title['series'] = record['series'] + self.all_series.add(this_title['series']) this_title['series_index'] = record['series_index'] else: this_title['series'] = None @@ -969,11 +1008,11 @@ class CatalogBuilder(object): index_is_id=True) if notes: if field_md['datatype'] == 'text': - if isinstance(notes,list): + if isinstance(notes, list): notes = ' · '.join(notes) elif field_md['datatype'] == 'datetime': - notes = format_date(notes,'dd MMM yyyy') - this_title['notes'] = {'source':field_md['name'],'content':notes} + notes = format_date(notes, 'dd MMM yyyy') + this_title['notes'] = {'source': field_md['name'], 'content': notes} return this_title @@ -1000,7 +1039,7 @@ class CatalogBuilder(object): data = self.plugin.search_sort_db(self.db, self.opts) data = self.process_exclusions(data) - if self.opts.verbose and self.prefix_rules: + if self.prefix_rules and self.DEBUG: self.opts.log.info(" Added prefixes:") # Populate this_title{} from data[{},{}] @@ -1042,6 +1081,7 @@ class CatalogBuilder(object): def initialize(self, save_template): self._save_template = save_template self.SUPPORTS_SUB_DIRS = True + def save_template(self): return self._save_template @@ -1069,8 +1109,8 @@ class CatalogBuilder(object): if bookmark_extension: for vol in storage: - bkmk_path = path_map[id]['path'].replace(os.path.abspath('/'),vol) - bkmk_path = bkmk_path.replace('bookmark',bookmark_extension) + bkmk_path = path_map[id]['path'].replace(os.path.abspath('/'), vol) + bkmk_path = bkmk_path.replace('bookmark', bookmark_extension) if os.path.exists(bkmk_path): path_map[id] = bkmk_path book_ext[id] = book_extension @@ -1109,14 +1149,14 @@ class CatalogBuilder(object): bookmark_ext = path_map[id].rpartition('.')[2] myBookmark = Bookmark(path_map[id], id, book_ext[id], bookmark_ext) try: - book['percent_read'] = min(float(100*myBookmark.last_read / myBookmark.book_length),100) + book['percent_read'] = min(float(100 * myBookmark.last_read / myBookmark.book_length), 100) except: book['percent_read'] = 0 - dots = int((book['percent_read'] + 5)/10) + dots = int((book['percent_read'] + 5) / 10) dot_string = self.SYMBOL_PROGRESS_READ * dots empty_dots = self.SYMBOL_PROGRESS_UNREAD * (10 - dots) - book['reading_progress'] = '%s%s' % (dot_string,empty_dots) - bookmarks[id] = ((myBookmark,book)) + book['reading_progress'] = '%s%s' % (dot_string, empty_dots) + bookmarks[id] = ((myBookmark, book)) self.bookmarked_books = bookmarks @@ -1142,7 +1182,7 @@ class CatalogBuilder(object): else: yield tag - ans = '%s%d %s:\n' % (' ' * indent, len(tags), header) + ans = '%s%d %s:\n' % (' ' * indent, len(tags), header) ans += ' ' * (indent + 1) out_str = '' sorted_tags = sorted(tags, key=sort_key) @@ -1167,11 +1207,11 @@ class CatalogBuilder(object): clipped to max_len """ - normalized = massaged = re.sub('\s','',ascii_text(tag).lower()) - if re.search('\W',normalized): + normalized = massaged = re.sub('\s', '', ascii_text(tag).lower()) + if re.search('\W', normalized): normalized = '' for c in massaged: - if re.search('\W',c): + if re.search('\W', c): normalized += self.generate_unicode_name(c) else: normalized += c @@ -1190,11 +1230,11 @@ class CatalogBuilder(object): else: # Validate custom field is usable as a genre source field_md = self.db.metadata_for_field(self.opts.genre_source_field) - if not field_md['datatype'] in ['enumeration','text']: + if not field_md['datatype'] in ['enumeration', 'text']: all_custom_fields = self.db.custom_field_keys() eligible_custom_fields = [] for cf in all_custom_fields: - if self.db.metadata_for_field(cf)['datatype'] in ['enumeration','text']: + if self.db.metadata_for_field(cf)['datatype'] in ['enumeration', 'text']: eligible_custom_fields.append(cf) self.opts.log.error("Custom genre_source_field must be either:\n" " 'Comma separated text, like tags, shown in the browser',\n" @@ -1224,7 +1264,7 @@ class CatalogBuilder(object): normalized_tags.append(_normalize_tag(tag, max_len)) friendly_tags.append(tag) - genre_tags_dict = dict(zip(friendly_tags,normalized_tags)) + genre_tags_dict = dict(zip(friendly_tags, normalized_tags)) # Test for multiple genres resolving to same normalized form normalized_set = set(normalized_tags) @@ -1286,7 +1326,7 @@ class CatalogBuilder(object): massaged = unicode(BeautifulStoneSoup(description, convertEntities=BeautifulStoneSoup.HTML_ENTITIES)) # Replace '&' with '&' - massaged = re.sub("&","&", massaged) + massaged = re.sub("&", "&", massaged) if massaged.strip() and dest: #print traceback.print_stack(limit=3) @@ -1311,16 +1351,16 @@ class CatalogBuilder(object): if self.opts.fmt == 'mobi': codeTag = Tag(soup, "code") if prefix_char is None: - codeTag.insert(0,NavigableString(' ')) + codeTag.insert(0, NavigableString(' ')) else: - codeTag.insert(0,NavigableString(prefix_char)) + codeTag.insert(0, NavigableString(prefix_char)) return codeTag else: spanTag = Tag(soup, "span") spanTag['class'] = "prefix" if prefix_char is None: prefix_char = " " - spanTag.insert(0,NavigableString(prefix_char)) + spanTag.insert(0, NavigableString(prefix_char)) return spanTag def generate_author_anchor(self, author): @@ -1335,7 +1375,7 @@ class CatalogBuilder(object): Return: (str): asciized version of author """ - return re.sub("\W","", ascii_text(author)) + return re.sub("\W", "", ascii_text(author)) def generate_format_args(self, book): """ Generate the format args for template substitution. @@ -1399,11 +1439,11 @@ class CatalogBuilder(object): current_letter = '' current_series = None # Establish initial letter equivalencies - sort_equivalents = self.establish_equivalencies(self.books_by_author,key='author_sort') + sort_equivalents = self.establish_equivalencies(self.books_by_author, key='author_sort') for idx, book in enumerate(self.books_by_author): book_count += 1 - if self.letter_or_symbol(sort_equivalents[idx]) != current_letter : + if self.letter_or_symbol(sort_equivalents[idx]) != current_letter: # Start a new letter with Index letter if divOpeningTag is not None: divTag.insert(dtc, divOpeningTag) @@ -1427,13 +1467,13 @@ class CatalogBuilder(object): current_letter = self.letter_or_symbol(sort_equivalents[idx]) if current_letter == self.SYMBOLS: aTag['id'] = self.SYMBOLS + '_authors' - pIndexTag.insert(0,aTag) - pIndexTag.insert(1,NavigableString(self.SYMBOLS)) + pIndexTag.insert(0, aTag) + pIndexTag.insert(1, NavigableString(self.SYMBOLS)) else: aTag['id'] = self.generate_unicode_name(current_letter) + '_authors' - pIndexTag.insert(0,aTag) - pIndexTag.insert(1,NavigableString(sort_equivalents[idx])) - divOpeningTag.insert(dotc,pIndexTag) + pIndexTag.insert(0, aTag) + pIndexTag.insert(1, NavigableString(sort_equivalents[idx])) + divOpeningTag.insert(dotc, pIndexTag) dotc += 1 if book['author'] != current_author: @@ -1463,36 +1503,36 @@ class CatalogBuilder(object): pAuthorTag['class'] = "author_index" aTag = Tag(soup, "a") aTag['id'] = "%s" % self.generate_author_anchor(current_author) - aTag.insert(0,NavigableString(current_author)) - pAuthorTag.insert(0,aTag) + aTag.insert(0, NavigableString(current_author)) + pAuthorTag.insert(0, aTag) if author_count == 1: divOpeningTag.insert(dotc, pAuthorTag) dotc += 1 else: - divRunningTag.insert(drtc,pAuthorTag) + divRunningTag.insert(drtc, pAuthorTag) drtc += 1 # Check for series if book['series'] and book['series'] != current_series: # Start a new series current_series = book['series'] - pSeriesTag = Tag(soup,'p') + pSeriesTag = Tag(soup, 'p') pSeriesTag['class'] = "series" if self.opts.fmt == 'mobi': pSeriesTag['class'] = "series_mobi" if self.opts.generate_series: - aTag = Tag(soup,'a') - aTag['href'] = "%s.html#%s" % ('BySeries',self.generate_series_anchor(book['series'])) + aTag = Tag(soup, 'a') + aTag['href'] = "%s.html#%s" % ('BySeries', self.generate_series_anchor(book['series'])) aTag.insert(0, book['series']) pSeriesTag.insert(0, aTag) else: - pSeriesTag.insert(0,NavigableString('%s' % book['series'])) + pSeriesTag.insert(0, NavigableString('%s' % book['series'])) if author_count == 1: divOpeningTag.insert(dotc, pSeriesTag) dotc += 1 elif divRunningTag is not None: - divRunningTag.insert(drtc,pSeriesTag) + divRunningTag.insert(drtc, pSeriesTag) drtc += 1 if current_series and not book['series']: current_series = None @@ -1522,7 +1562,7 @@ class CatalogBuilder(object): #aTag.insert(0,'%s%s' % (escape(book['title']), pubyear)) formatted_title = self.by_authors_normal_title_template.format(**args).rstrip() non_series_books += 1 - aTag.insert(0,NavigableString(escape(formatted_title))) + aTag.insert(0, NavigableString(escape(formatted_title))) spanTag.insert(ptc, aTag) stc += 1 @@ -1533,7 +1573,7 @@ class CatalogBuilder(object): divOpeningTag.insert(dotc, pBookTag) dotc += 1 elif divRunningTag: - divRunningTag.insert(drtc,pBookTag) + divRunningTag.insert(drtc, pBookTag) drtc += 1 # loop ends here @@ -1541,7 +1581,7 @@ class CatalogBuilder(object): pTag = Tag(soup, "p") pTag['class'] = 'title' ptc = 0 - aTag = Tag(soup,'a') + aTag = Tag(soup, 'a') aTag['id'] = 'section_start' pTag.insert(ptc, aTag) ptc += 1 @@ -1550,12 +1590,12 @@ class CatalogBuilder(object): # Kindle don't need this because it shows section titles in Periodical format aTag = Tag(soup, "a") anchor_name = friendly_name.lower() - aTag['id'] = anchor_name.replace(" ","") - pTag.insert(ptc,aTag) + aTag['id'] = anchor_name.replace(" ", "") + pTag.insert(ptc, aTag) ptc += 1 - pTag.insert(ptc,NavigableString('%s' % (friendly_name))) + pTag.insert(ptc, NavigableString('%s' % (friendly_name))) - body.insert(btc,pTag) + body.insert(btc, pTag) btc += 1 if author_count == 1: @@ -1601,9 +1641,9 @@ class CatalogBuilder(object): pIndexTag['class'] = "date_index" aTag = Tag(soup, "a") aTag['id'] = "bda_%s-%s" % (current_date.year, current_date.month) - pIndexTag.insert(0,aTag) - pIndexTag.insert(1,NavigableString(date_string)) - divTag.insert(dtc,pIndexTag) + pIndexTag.insert(0, aTag) + pIndexTag.insert(1, NavigableString(date_string)) + divTag.insert(dtc, pIndexTag) dtc += 1 current_author = None current_series = None @@ -1619,27 +1659,27 @@ class CatalogBuilder(object): aTag = Tag(soup, "a") if self.opts.generate_authors: aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generate_author_anchor(current_author)) - aTag.insert(0,NavigableString(current_author)) - pAuthorTag.insert(0,aTag) - divTag.insert(dtc,pAuthorTag) + aTag.insert(0, NavigableString(current_author)) + pAuthorTag.insert(0, aTag) + divTag.insert(dtc, pAuthorTag) dtc += 1 # Check for series if new_entry['series'] and new_entry['series'] != current_series: # Start a new series current_series = new_entry['series'] - pSeriesTag = Tag(soup,'p') + pSeriesTag = Tag(soup, 'p') pSeriesTag['class'] = "series" if self.opts.fmt == 'mobi': pSeriesTag['class'] = "series_mobi" if self.opts.generate_series: - aTag = Tag(soup,'a') - aTag['href'] = "%s.html#%s" % ('BySeries',self.generate_series_anchor(new_entry['series'])) + aTag = Tag(soup, 'a') + aTag['href'] = "%s.html#%s" % ('BySeries', self.generate_series_anchor(new_entry['series'])) aTag.insert(0, new_entry['series']) pSeriesTag.insert(0, aTag) else: - pSeriesTag.insert(0,NavigableString('%s' % new_entry['series'])) - divTag.insert(dtc,pSeriesTag) + pSeriesTag.insert(0, NavigableString('%s' % new_entry['series'])) + divTag.insert(dtc, pSeriesTag) dtc += 1 if current_series and not new_entry['series']: current_series = None @@ -1667,7 +1707,7 @@ class CatalogBuilder(object): else: formatted_title = self.by_month_added_normal_title_template.format(**args).rstrip() non_series_books += 1 - aTag.insert(0,NavigableString(escape(formatted_title))) + aTag.insert(0, NavigableString(escape(formatted_title))) spanTag.insert(stc, aTag) stc += 1 @@ -1683,10 +1723,10 @@ class CatalogBuilder(object): pIndexTag = Tag(soup, "p") pIndexTag['class'] = "date_index" aTag = Tag(soup, "a") - aTag['id'] = "bda_%s" % date_range.replace(' ','') - pIndexTag.insert(0,aTag) - pIndexTag.insert(1,NavigableString(date_range)) - divTag.insert(dtc,pIndexTag) + aTag['id'] = "bda_%s" % date_range.replace(' ', '') + pIndexTag.insert(0, aTag) + pIndexTag.insert(1, NavigableString(date_range)) + divTag.insert(dtc, pIndexTag) dtc += 1 for new_entry in date_range_list: @@ -1712,7 +1752,7 @@ class CatalogBuilder(object): formatted_title = self.by_recently_added_series_title_template.format(**args).rstrip() else: formatted_title = self.by_recently_added_normal_title_template.format(**args).rstrip() - aTag.insert(0,NavigableString(escape(formatted_title))) + aTag.insert(0, NavigableString(escape(formatted_title))) spanTag.insert(stc, aTag) stc += 1 @@ -1726,7 +1766,7 @@ class CatalogBuilder(object): if self.opts.generate_authors: aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generate_author_anchor(new_entry['author'])) aTag.insert(0, NavigableString(new_entry['author'])) - emTag.insert(0,aTag) + emTag.insert(0, aTag) spanTag.insert(stc, emTag) stc += 1 @@ -1749,7 +1789,7 @@ class CatalogBuilder(object): pTag['class'] = 'title' ptc = 0 - aTag = Tag(soup,'a') + aTag = Tag(soup, 'a') aTag['id'] = 'section_start' pTag.insert(ptc, aTag) ptc += 1 @@ -1758,13 +1798,13 @@ class CatalogBuilder(object): # Kindle don't need this because it shows section titles in Periodical format aTag = Tag(soup, "a") anchor_name = friendly_name.lower() - aTag['id'] = anchor_name.replace(" ","") + aTag['id'] = anchor_name.replace(" ", "") - pTag.insert(ptc,aTag) + pTag.insert(ptc, aTag) ptc += 1 pTag.insert(ptc, NavigableString('%s' % friendly_name)) - body.insert(btc,pTag) + body.insert(btc, pTag) btc += 1 divTag = Tag(soup, "div") @@ -1773,23 +1813,23 @@ class CatalogBuilder(object): # >>> Books by date range <<< if self.use_series_prefix_in_titles_section: self.books_by_date_range = sorted(self.books_to_catalog, - key=lambda x:(x['timestamp'], x['timestamp']),reverse=True) + key=lambda x: (x['timestamp'], x['timestamp']), reverse=True) else: nspt = deepcopy(self.books_to_catalog) - self.books_by_date_range = sorted(nspt, key=lambda x:(x['timestamp'], x['timestamp']),reverse=True) + self.books_by_date_range = sorted(nspt, key=lambda x: (x['timestamp'], x['timestamp']), reverse=True) date_range_list = [] today_time = nowf().replace(hour=23, minute=59, second=59) for (i, date) in enumerate(self.DATE_RANGE): date_range_limit = self.DATE_RANGE[i] if i: - date_range = '%d to %d days ago' % (self.DATE_RANGE[i-1], self.DATE_RANGE[i]) + date_range = '%d to %d days ago' % (self.DATE_RANGE[i - 1], self.DATE_RANGE[i]) else: date_range = 'Last %d days' % (self.DATE_RANGE[i]) for book in self.books_by_date_range: book_time = book['timestamp'] - delta = today_time-book_time + delta = today_time - book_time if delta.days <= date_range_limit: date_range_list.append(book) else: @@ -1801,7 +1841,7 @@ class CatalogBuilder(object): # >>>> Books by month <<<< # Sort titles case-insensitive for by month using series prefix self.books_by_month = sorted(self.books_to_catalog, - key=lambda x:(x['timestamp'], x['timestamp']),reverse=True) + key=lambda x: (x['timestamp'], x['timestamp']), reverse=True) # Loop through books by date current_date = datetime.date.fromordinal(1) @@ -1848,9 +1888,9 @@ class CatalogBuilder(object): pIndexTag['class'] = "date_index" aTag = Tag(soup, "a") aTag['name'] = "bdr_%s-%s-%s" % (current_date.year, current_date.month, current_date.day) - pIndexTag.insert(0,aTag) - pIndexTag.insert(1,NavigableString(date_string)) - divTag.insert(dtc,pIndexTag) + pIndexTag.insert(0, aTag) + pIndexTag.insert(1, NavigableString(date_string)) + divTag.insert(dtc, pIndexTag) dtc += 1 for new_entry in todays_list: @@ -1865,7 +1905,7 @@ class CatalogBuilder(object): aTag = Tag(soup, "a") if self.opts.generate_descriptions: aTag['href'] = "book_%d.html" % (int(float(new_entry['id']))) - aTag.insert(0,escape(new_entry['title'])) + aTag.insert(0, escape(new_entry['title'])) pBookTag.insert(ptc, aTag) ptc += 1 @@ -1879,7 +1919,7 @@ class CatalogBuilder(object): if self.opts.generate_authors: aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generate_author_anchor(new_entry['author'])) aTag.insert(0, NavigableString(new_entry['author'])) - emTag.insert(0,aTag) + emTag.insert(0, aTag) pBookTag.insert(ptc, emTag) ptc += 1 @@ -1892,10 +1932,10 @@ class CatalogBuilder(object): pIndexTag = Tag(soup, "p") pIndexTag['class'] = "date_index" aTag = Tag(soup, "a") - aTag['name'] = "bdr_%s" % date_range.replace(' ','') - pIndexTag.insert(0,aTag) - pIndexTag.insert(1,NavigableString(date_range)) - divTag.insert(dtc,pIndexTag) + aTag['name'] = "bdr_%s" % date_range.replace(' ', '') + pIndexTag.insert(0, aTag) + pIndexTag.insert(1, NavigableString(date_range)) + divTag.insert(dtc, pIndexTag) dtc += 1 for new_entry in date_range_list: @@ -1905,16 +1945,16 @@ class CatalogBuilder(object): ptc = 0 # Percent read - dots = int((new_entry['percent_read'] + 5)/10) + dots = int((new_entry['percent_read'] + 5) / 10) dot_string = self.SYMBOL_PROGRESS_READ * dots empty_dots = self.SYMBOL_PROGRESS_UNREAD * (10 - dots) - pBookTag.insert(ptc, NavigableString('%s%s' % (dot_string,empty_dots))) + pBookTag.insert(ptc, NavigableString('%s%s' % (dot_string, empty_dots))) ptc += 1 aTag = Tag(soup, "a") if self.opts.generate_descriptions: aTag['href'] = "book_%d.html" % (int(float(new_entry['id']))) - aTag.insert(0,escape(new_entry['title'])) + aTag.insert(0, escape(new_entry['title'])) pBookTag.insert(ptc, aTag) ptc += 1 @@ -1928,7 +1968,7 @@ class CatalogBuilder(object): if self.opts.generate_authors: aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generate_author_anchor(new_entry['author'])) aTag.insert(0, NavigableString(new_entry['author'])) - emTag.insert(0,aTag) + emTag.insert(0, aTag) pBookTag.insert(ptc, emTag) ptc += 1 @@ -1948,7 +1988,7 @@ class CatalogBuilder(object): btc = 0 # Insert section tag - aTag = Tag(soup,'a') + aTag = Tag(soup, 'a') aTag['name'] = 'section_start' body.insert(btc, aTag) btc += 1 @@ -1956,7 +1996,7 @@ class CatalogBuilder(object): # Insert the anchor aTag = Tag(soup, "a") anchor_name = friendly_name.lower() - aTag['name'] = anchor_name.replace(" ","") + aTag['name'] = anchor_name.replace(" ", "") body.insert(btc, aTag) btc += 1 @@ -1970,13 +2010,13 @@ class CatalogBuilder(object): #print "bm_book: %s" % bm_book book[1]['bookmark_timestamp'] = book[0].timestamp try: - book[1]['percent_read'] = min(float(100*book[0].last_read / book[0].book_length),100) + book[1]['percent_read'] = min(float(100 * book[0].last_read / book[0].book_length), 100) except: book[1]['percent_read'] = 0 bookmarked_books.append(book[1]) self.bookmarked_books_by_date_read = sorted(bookmarked_books, - key=lambda x:(x['bookmark_timestamp'], x['bookmark_timestamp']),reverse=True) + key=lambda x: (x['bookmark_timestamp'], x['bookmark_timestamp']), reverse=True) # >>>> Recently read by day <<<< current_date = datetime.date.fromordinal(1) @@ -2070,7 +2110,6 @@ class CatalogBuilder(object): len(genre[key]), 'titles' if len(genre[key]) > 1 else 'title')) - # Write the results # genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...] master_genre_list = [] @@ -2081,19 +2120,19 @@ class CatalogBuilder(object): # Create sorted_authors[0] = friendly, [1] = author_sort for NCX creation authors = [] for book in genre_tag_set[genre]: - authors.append((book['author'],book['author_sort'])) + authors.append((book['author'], book['author_sort'])) # authors[] contains a list of all book authors, with multiple entries for multiple books by author # Create unique_authors with a count of books per author as the third tuple element books_by_current_author = 1 current_author = authors[0] unique_authors = [] - for (i,author) in enumerate(authors): + for (i, author) in enumerate(authors): if author != current_author and i: unique_authors.append((current_author[0], current_author[1], books_by_current_author)) current_author = author books_by_current_author = 1 - elif i==0 and len(authors) == 1: + elif i == 0 and len(authors) == 1: # Allow for single-book lists unique_authors.append((current_author[0], current_author[1], books_by_current_author)) else: @@ -2102,16 +2141,17 @@ class CatalogBuilder(object): # Write the genre book list as an article outfile = "%s/Genre_%s.html" % (self.content_dir, genre) titles_spanned = self.generate_html_by_genre(genre, - True if index==0 else False, + True if index == 0 else False, genre_tag_set[genre], outfile) tag_file = "content/Genre_%s.html" % genre - master_genre_list.append({'tag':genre, - 'file':tag_file, - 'authors':unique_authors, - 'books':genre_tag_set[genre], - 'titles_spanned':titles_spanned}) + master_genre_list.append({ + 'tag': genre, + 'file': tag_file, + 'authors': unique_authors, + 'books': genre_tag_set[genre], + 'titles_spanned': titles_spanned}) self.genres = master_genre_list @@ -2142,7 +2182,7 @@ class CatalogBuilder(object): # Insert section tag if this is the section start - first article only if section_head: - aTag = Tag(soup,'a') + aTag = Tag(soup, 'a') aTag['id'] = 'section_start' divTag.insert(dtc, aTag) dtc += 1 @@ -2153,14 +2193,14 @@ class CatalogBuilder(object): aTag = Tag(soup, 'a') aTag['id'] = "Genre_%s" % genre divTag.insert(dtc, aTag) - body.insert(btc,divTag) + body.insert(btc, divTag) btc += 1 - titleTag = body.find(attrs={'class':'title'}) - titleTag.insert(0,NavigableString('%s' % escape(self.get_friendly_genre_tag(genre)))) + titleTag = body.find(attrs={'class': 'title'}) + titleTag.insert(0, NavigableString('%s' % escape(self.get_friendly_genre_tag(genre)))) # Insert the books by author list - divTag = body.find(attrs={'class':'authors'}) + divTag = body.find(attrs={'class': 'authors'}) dtc = 0 current_author = '' @@ -2177,26 +2217,26 @@ class CatalogBuilder(object): if self.opts.generate_authors: aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generate_author_anchor(book['author'])) aTag.insert(0, book['author']) - pAuthorTag.insert(0,aTag) - divTag.insert(dtc,pAuthorTag) + pAuthorTag.insert(0, aTag) + divTag.insert(dtc, pAuthorTag) dtc += 1 # Check for series if book['series'] and book['series'] != current_series: # Start a new series current_series = book['series'] - pSeriesTag = Tag(soup,'p') + pSeriesTag = Tag(soup, 'p') pSeriesTag['class'] = "series" if self.opts.fmt == 'mobi': pSeriesTag['class'] = "series_mobi" if self.opts.generate_series: - aTag = Tag(soup,'a') + aTag = Tag(soup, 'a') aTag['href'] = "%s.html#%s" % ('BySeries', self.generate_series_anchor(book['series'])) aTag.insert(0, book['series']) pSeriesTag.insert(0, aTag) else: - pSeriesTag.insert(0,NavigableString('%s' % book['series'])) - divTag.insert(dtc,pSeriesTag) + pSeriesTag.insert(0, NavigableString('%s' % book['series'])) + divTag.insert(dtc, pSeriesTag) dtc += 1 if current_series and not book['series']: @@ -2228,7 +2268,7 @@ class CatalogBuilder(object): #aTag.insert(0,escape(book['title'])) formatted_title = self.by_genres_normal_title_template.format(**args).rstrip() non_series_books += 1 - aTag.insert(0,NavigableString(escape(formatted_title))) + aTag.insert(0, NavigableString(escape(formatted_title))) spanTag.insert(stc, aTag) stc += 1 @@ -2245,9 +2285,9 @@ class CatalogBuilder(object): outfile.close() if len(books) > 1: - titles_spanned = [(books[0]['author'],books[0]['title']), (books[-1]['author'],books[-1]['title'])] + titles_spanned = [(books[0]['author'], books[0]['title']), (books[-1]['author'], books[-1]['title'])] else: - titles_spanned = [(books[0]['author'],books[0]['title'])] + titles_spanned = [(books[0]['author'], books[0]['title'])] return titles_spanned @@ -2297,7 +2337,7 @@ class CatalogBuilder(object): series_count = 0 for idx, book in enumerate(self.books_by_series): # Check for initial letter change - if self.letter_or_symbol(sort_equivalents[idx]) != current_letter : + if self.letter_or_symbol(sort_equivalents[idx]) != current_letter: # Start a new letter with Index letter current_letter = self.letter_or_symbol(sort_equivalents[idx]) pIndexTag = Tag(soup, "p") @@ -2305,28 +2345,28 @@ class CatalogBuilder(object): aTag = Tag(soup, "a") if current_letter == self.SYMBOLS: aTag['id'] = self.SYMBOLS + "_series" - pIndexTag.insert(0,aTag) - pIndexTag.insert(1,NavigableString(self.SYMBOLS)) + pIndexTag.insert(0, aTag) + pIndexTag.insert(1, NavigableString(self.SYMBOLS)) else: aTag['id'] = self.generate_unicode_name(current_letter) + "_series" - pIndexTag.insert(0,aTag) - pIndexTag.insert(1,NavigableString(sort_equivalents[idx])) - divTag.insert(dtc,pIndexTag) + pIndexTag.insert(0, aTag) + pIndexTag.insert(1, NavigableString(sort_equivalents[idx])) + divTag.insert(dtc, pIndexTag) dtc += 1 # Check for series change if book['series'] != current_series: # Start a new series series_count += 1 current_series = book['series'] - pSeriesTag = Tag(soup,'p') + pSeriesTag = Tag(soup, 'p') pSeriesTag['class'] = "series" if self.opts.fmt == 'mobi': pSeriesTag['class'] = "series_mobi" aTag = Tag(soup, 'a') aTag['id'] = self.generate_series_anchor(book['series']) - pSeriesTag.insert(0,aTag) - pSeriesTag.insert(1,NavigableString('%s' % book['series'])) - divTag.insert(dtc,pSeriesTag) + pSeriesTag.insert(0, aTag) + pSeriesTag.insert(1, NavigableString('%s' % book['series'])) + divTag.insert(dtc, pSeriesTag) dtc += 1 # Add books @@ -2350,7 +2390,7 @@ class CatalogBuilder(object): args = self.generate_format_args(book) formatted_title = self.by_series_title_template.format(**args).rstrip() - aTag.insert(0,NavigableString(escape(formatted_title))) + aTag.insert(0, NavigableString(escape(formatted_title))) spanTag.insert(stc, aTag) stc += 1 @@ -2377,7 +2417,7 @@ class CatalogBuilder(object): pTag = Tag(soup, "p") pTag['class'] = 'title' ptc = 0 - aTag = Tag(soup,'a') + aTag = Tag(soup, 'a') aTag['id'] = 'section_start' pTag.insert(ptc, aTag) ptc += 1 @@ -2386,10 +2426,10 @@ class CatalogBuilder(object): # Insert the

    tag with book_count at the head aTag = Tag(soup, "a") anchor_name = friendly_name.lower() - aTag['id'] = anchor_name.replace(" ","") - pTag.insert(0,aTag) - pTag.insert(1,NavigableString('%s' % friendly_name)) - body.insert(btc,pTag) + aTag['id'] = anchor_name.replace(" ", "") + pTag.insert(0, aTag) + pTag.insert(1, NavigableString('%s' % friendly_name)) + body.insert(btc, pTag) btc += 1 # Add the divTag to the body @@ -2423,7 +2463,7 @@ class CatalogBuilder(object): pTag = Tag(soup, "p") pTag['class'] = 'title' ptc = 0 - aTag = Tag(soup,'a') + aTag = Tag(soup, 'a') aTag['id'] = 'section_start' pTag.insert(ptc, aTag) ptc += 1 @@ -2432,11 +2472,11 @@ class CatalogBuilder(object): # Kindle don't need this because it shows section titles in Periodical format aTag = Tag(soup, "a") aTag['id'] = "bytitle" - pTag.insert(ptc,aTag) + pTag.insert(ptc, aTag) ptc += 1 - pTag.insert(ptc,NavigableString(_('Titles'))) + pTag.insert(ptc, NavigableString(_('Titles'))) - body.insert(btc,pTag) + body.insert(btc, pTag) btc += 1 divTag = Tag(soup, "div") @@ -2478,13 +2518,13 @@ class CatalogBuilder(object): current_letter = self.letter_or_symbol(sort_equivalents[idx]) if current_letter == self.SYMBOLS: aTag['id'] = self.SYMBOLS + "_titles" - pIndexTag.insert(0,aTag) - pIndexTag.insert(1,NavigableString(self.SYMBOLS)) + pIndexTag.insert(0, aTag) + pIndexTag.insert(1, NavigableString(self.SYMBOLS)) else: aTag['id'] = self.generate_unicode_name(current_letter) + "_titles" - pIndexTag.insert(0,aTag) - pIndexTag.insert(1,NavigableString(sort_equivalents[idx])) - divRunningTag.insert(dtc,pIndexTag) + pIndexTag.insert(0, aTag) + pIndexTag.insert(1, NavigableString(sort_equivalents[idx])) + divRunningTag.insert(dtc, pIndexTag) drtc += 1 # Add books @@ -2510,7 +2550,7 @@ class CatalogBuilder(object): formatted_title = self.by_titles_series_title_template.format(**args).rstrip() else: formatted_title = self.by_titles_normal_title_template.format(**args).rstrip() - aTag.insert(0,NavigableString(escape(formatted_title))) + aTag.insert(0, NavigableString(escape(formatted_title))) spanTag.insert(stc, aTag) stc += 1 @@ -2524,7 +2564,7 @@ class CatalogBuilder(object): if self.opts.generate_authors: aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generate_author_anchor(book['author'])) aTag.insert(0, NavigableString(book['author'])) - emTag.insert(0,aTag) + emTag.insert(0, aTag) spanTag.insert(stc, emTag) stc += 1 @@ -2611,7 +2651,7 @@ class CatalogBuilder(object): author = book['author'] if book['prefix']: - author_prefix = book['prefix'] + ' ' + _("by ") + author_prefix = book['prefix'] + ' ' + _("by ") elif self.opts.connected_kindle and book['id'] in self.bookmarked_books: author_prefix = self.SYMBOL_READING + ' ' + _("by ") else: @@ -2621,16 +2661,16 @@ class CatalogBuilder(object): genres = '' if 'genres' in book: _soup = BeautifulSoup('') - genresTag = Tag(_soup,'p') + genresTag = Tag(_soup, 'p') gtc = 0 for (i, tag) in enumerate(sorted(book.get('genres', []))): - aTag = Tag(_soup,'a') + aTag = Tag(_soup, 'a') if self.opts.generate_genres: aTag['href'] = "Genre_%s.html" % self.genre_tags_dict[tag] - aTag.insert(0,escape(NavigableString(tag))) + aTag.insert(0, escape(NavigableString(tag))) genresTag.insert(gtc, aTag) gtc += 1 - if i < len(book['genres'])-1: + if i < len(book['genres']) - 1: genresTag.insert(gtc, NavigableString(' · ')) gtc += 1 genres = genresTag.renderContents() @@ -2650,12 +2690,12 @@ class CatalogBuilder(object): pubdate = pubyear = pubmonth = '' # Thumb - _soup = BeautifulSoup('',selfClosingTags=['img']) - thumb = Tag(_soup,"img") + _soup = BeautifulSoup('', selfClosingTags=['img']) + thumb = Tag(_soup, "img") if 'cover' in book and book['cover']: - thumb['src'] = "../images/thumbnail_%d.jpg" % int(book['id']) + thumb['src'] = "../images/thumbnail_%d.jpg" % int(book['id']) else: - thumb['src'] = "../images/thumbnail_default.jpg" + thumb['src'] = "../images/thumbnail_default.jpg" thumb['alt'] = "cover thumbnail" # Publisher @@ -2669,7 +2709,7 @@ class CatalogBuilder(object): if stars: star_string = self.SYMBOL_FULL_RATING * stars empty_stars = self.SYMBOL_EMPTY_RATING * (5 - stars) - rating = '%s%s
    ' % (star_string,empty_stars) + rating = '%s%s
    ' % (star_string, empty_stars) # Notes note_source = '' @@ -2698,44 +2738,44 @@ class CatalogBuilder(object): btc += 1 # Insert the link to the series or remove - aTag = body.find('a', attrs={'class':'series_id'}) + aTag = body.find('a', attrs={'class': 'series_id'}) if aTag: if book['series']: if self.opts.generate_series: - aTag['href'] = "%s.html#%s" % ('BySeries',self.generate_series_anchor(book['series'])) + aTag['href'] = "%s.html#%s" % ('BySeries', self.generate_series_anchor(book['series'])) else: aTag.extract() # Insert the author link - aTag = body.find('a', attrs={'class':'author'}) + aTag = body.find('a', attrs={'class': 'author'}) if self.opts.generate_authors and aTag: aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generate_author_anchor(book['author'])) if publisher == ' ': - publisherTag = body.find('td', attrs={'class':'publisher'}) + publisherTag = body.find('td', attrs={'class': 'publisher'}) if publisherTag: publisherTag.contents[0].replaceWith(' ') if not genres: - genresTag = body.find('p',attrs={'class':'genres'}) + genresTag = body.find('p', attrs={'class': 'genres'}) if genresTag: genresTag.extract() if not formats: - formatsTag = body.find('p',attrs={'class':'formats'}) + formatsTag = body.find('p', attrs={'class': 'formats'}) if formatsTag: formatsTag.extract() if note_content == '': - tdTag = body.find('td', attrs={'class':'notes'}) + tdTag = body.find('td', attrs={'class': 'notes'}) if tdTag: tdTag.contents[0].replaceWith(' ') - emptyTags = body.findAll('td', attrs={'class':'empty'}) + emptyTags = body.findAll('td', attrs={'class': 'empty'}) for mt in emptyTags: - newEmptyTag = Tag(BeautifulSoup(),'td') - newEmptyTag.insert(0,NavigableString(' ')) + newEmptyTag = Tag(BeautifulSoup(), 'td') + newEmptyTag.insert(0, NavigableString(' ')) mt.replaceWith(newEmptyTag) return soup @@ -2758,7 +2798,7 @@ class CatalogBuilder(object): self.update_progress_micro_step("%s %d of %d" % (_("Description HTML"), title_num, len(self.books_by_title)), - float(title_num*100/len(self.books_by_title))/100) + float(title_num * 100 / len(self.books_by_title)) / 100) # Generate the header from user-customizable template soup = self.generate_html_description_header(title) @@ -2795,7 +2835,7 @@ class CatalogBuilder(object): # Insert the supplied title soup = BeautifulSoup(header) titleTag = soup.find('title') - titleTag.insert(0,NavigableString(title)) + titleTag.insert(0, NavigableString(title)) return soup def generate_html_genre_header(self, title): @@ -2814,10 +2854,10 @@ class CatalogBuilder(object): bodyTag = soup.find('body') pTag = Tag(soup, 'p') pTag['class'] = 'title' - bodyTag.insert(0,pTag) + bodyTag.insert(0, pTag) divTag = Tag(soup, 'div') divTag['class'] = 'authors' - bodyTag.insert(1,divTag) + bodyTag.insert(1, divTag) return soup def generate_masthead_image(self, out_path): @@ -2869,9 +2909,9 @@ class CatalogBuilder(object): font = ImageFont.truetype(default_font, 48) text = self.opts.catalog_title.encode('utf-8') width, height = draw.textsize(text, font=font) - left = max(int((MI_WIDTH - width)/2.), 0) - top = max(int((MI_HEIGHT - height)/2.), 0) - draw.text((left, top), text, fill=(0,0,0), font=font) + left = max(int((MI_WIDTH - width) / 2.), 0) + top = max(int((MI_HEIGHT - height) / 2.), 0) + draw.text((left, top), text, fill=(0, 0, 0), font=font) img.save(open(out_path, 'wb'), 'GIF') def generate_ncx_header(self): @@ -2896,7 +2936,7 @@ class CatalogBuilder(object): ''' - soup = BeautifulStoneSoup(header, selfClosingTags=['content','calibre:meta-img']) + soup = BeautifulStoneSoup(header, selfClosingTags=['content', 'calibre:meta-img']) ncx = soup.find('ncx') navMapTag = Tag(soup, 'navMap') @@ -2935,22 +2975,20 @@ class CatalogBuilder(object): contentTag = Tag(soup, 'content') contentTag['src'] = "content/ByDateAdded.html" navPointTag.insert(1, contentTag) - else: + elif self.opts.generate_descriptions: # Descriptions only - sort_descriptions_by = self.books_by_author if self.opts.sort_descriptions_by_author \ - else self.books_by_title contentTag = Tag(soup, 'content') - contentTag['src'] = "content/book_%d.html" % int(sort_descriptions_by[0]['id']) + contentTag['src'] = "content/book_%d.html" % int(self.books_by_description[0]['id']) navPointTag.insert(1, contentTag) if self.generate_for_kindle_mobi: cmiTag = Tag(soup, '%s' % 'calibre:meta-img') cmiTag['id'] = "mastheadImage" cmiTag['src'] = "images/mastheadImage.gif" - navPointTag.insert(2,cmiTag) - navMapTag.insert(0,navPointTag) + navPointTag.insert(2, cmiTag) + navMapTag.insert(0, navPointTag) - ncx.insert(0,navMapTag) + ncx.insert(0, navMapTag) self.ncx_soup = soup def generate_ncx_descriptions(self, tocTitle): @@ -2970,9 +3008,6 @@ class CatalogBuilder(object): self.update_progress_full_step(_("NCX for Descriptions")) - sort_descriptions_by = self.books_by_author if self.opts.sort_descriptions_by_author \ - else self.books_by_title - # --- Construct the 'Descriptions' section --- ncx_soup = self.ncx_soup if self.generate_for_kindle_mobi: @@ -2990,19 +3025,22 @@ class CatalogBuilder(object): self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') - textTag.insert(0, NavigableString(tocTitle)) + section_header = '%s [%d]' % (tocTitle, len(self.books_by_description)) + if self.generate_for_kindle_mobi: + section_header = tocTitle + textTag.insert(0, NavigableString(section_header)) navLabelTag.insert(0, textTag) nptc = 0 navPointTag.insert(nptc, navLabelTag) nptc += 1 - contentTag = Tag(ncx_soup,"content") - contentTag['src'] = "content/book_%d.html" % int(sort_descriptions_by[0]['id']) + contentTag = Tag(ncx_soup, "content") + contentTag['src'] = "content/book_%d.html" % int(self.books_by_description[0]['id']) navPointTag.insert(nptc, contentTag) nptc += 1 # Loop over the titles - for book in sort_descriptions_by: + for book in self.books_by_description: navPointVolumeTag = Tag(ncx_soup, 'navPoint') if self.generate_for_kindle_mobi: navPointVolumeTag['class'] = "article" @@ -3040,8 +3078,8 @@ class CatalogBuilder(object): # Include Author for non-Kindle textTag.insert(0, NavigableString(self.format_ncx_text('%s · %s' % \ (book['title'], book['author']), dest='title'))) - navLabelTag.insert(0,textTag) - navPointVolumeTag.insert(0,navLabelTag) + navLabelTag.insert(0, textTag) + navPointVolumeTag.insert(0, navLabelTag) contentTag = Tag(ncx_soup, "content") contentTag['src'] = "content/book_%d.html#book%d" % (int(book['id']), int(book['id'])) @@ -3119,12 +3157,15 @@ class CatalogBuilder(object): self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') - textTag.insert(0, NavigableString(tocTitle)) + section_header = '%s [%d]' % (tocTitle, len(self.all_series)) + if self.generate_for_kindle_mobi: + section_header = tocTitle + textTag.insert(0, NavigableString(section_header)) navLabelTag.insert(0, textTag) nptc = 0 navPointTag.insert(nptc, navLabelTag) nptc += 1 - contentTag = Tag(ncx_soup,"content") + contentTag = Tag(ncx_soup, "content") contentTag['src'] = "content/%s.html#section_start" % (output) navPointTag.insert(nptc, contentTag) nptc += 1 @@ -3159,7 +3200,7 @@ class CatalogBuilder(object): current_series_list = [book['series']] else: if len(current_series_list) < self.opts.description_clip and \ - book['series'] != current_series : + book['series'] != current_series: current_series = book['series'] current_series_list.append(book['series']) @@ -3167,7 +3208,7 @@ class CatalogBuilder(object): _add_to_series_by_letter(current_series_list) # Add *article* entries for each populated series title letter - for (i,books) in enumerate(series_by_letter): + for (i, books) in enumerate(series_by_letter): navPointByLetterTag = Tag(ncx_soup, 'navPoint') if self.generate_for_kindle_mobi: navPointByLetterTag['class'] = "article" @@ -3176,14 +3217,14 @@ class CatalogBuilder(object): self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') - if len(title_letters[i])>1: + if len(title_letters[i]) > 1: fmt_string = _(u"Series beginning with %s") else: fmt_string = _(u"Series beginning with '%s'") textTag.insert(0, NavigableString(fmt_string % - (title_letters[i] if len(title_letters[i])>1 else title_letters[i]))) + (title_letters[i] if len(title_letters[i]) > 1 else title_letters[i]))) navLabelTag.insert(0, textTag) - navPointByLetterTag.insert(0,navLabelTag) + navPointByLetterTag.insert(0, navLabelTag) contentTag = Tag(ncx_soup, 'content') #contentTag['src'] = "content/%s.html#%s_series" % (output, title_letters[i]) if title_letters[i] == self.SYMBOLS: @@ -3191,7 +3232,7 @@ class CatalogBuilder(object): else: contentTag['src'] = "content/%s.html#%s_series" % (output, self.generate_unicode_name(title_letters[i])) - navPointByLetterTag.insert(1,contentTag) + navPointByLetterTag.insert(1, contentTag) if self.generate_for_kindle_mobi: cmTag = Tag(ncx_soup, '%s' % 'calibre:meta') @@ -3247,12 +3288,15 @@ class CatalogBuilder(object): self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') - textTag.insert(0, NavigableString(tocTitle)) + section_header = '%s [%d]' % (tocTitle, len(self.books_by_title)) + if self.generate_for_kindle_mobi: + section_header = tocTitle + textTag.insert(0, NavigableString(section_header)) navLabelTag.insert(0, textTag) nptc = 0 navPointTag.insert(nptc, navLabelTag) nptc += 1 - contentTag = Tag(ncx_soup,"content") + contentTag = Tag(ncx_soup, "content") contentTag['src'] = "content/%s.html#section_start" % (output) navPointTag.insert(nptc, contentTag) nptc += 1 @@ -3289,7 +3333,7 @@ class CatalogBuilder(object): current_book_list = [book['title']] else: if len(current_book_list) < self.opts.description_clip and \ - book['title'] != current_book : + book['title'] != current_book: current_book = book['title'] current_book_list.append(book['title']) @@ -3297,7 +3341,7 @@ class CatalogBuilder(object): _add_to_books_by_letter(current_book_list) # Add *article* entries for each populated title letter - for (i,books) in enumerate(books_by_letter): + for (i, books) in enumerate(books_by_letter): navPointByLetterTag = Tag(ncx_soup, 'navPoint') if self.generate_for_kindle_mobi: navPointByLetterTag['class'] = "article" @@ -3306,20 +3350,20 @@ class CatalogBuilder(object): self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') - if len(title_letters[i])>1: + if len(title_letters[i]) > 1: fmt_string = _(u"Titles beginning with %s") else: fmt_string = _(u"Titles beginning with '%s'") textTag.insert(0, NavigableString(fmt_string % - (title_letters[i] if len(title_letters[i])>1 else title_letters[i]))) + (title_letters[i] if len(title_letters[i]) > 1 else title_letters[i]))) navLabelTag.insert(0, textTag) - navPointByLetterTag.insert(0,navLabelTag) + navPointByLetterTag.insert(0, navLabelTag) contentTag = Tag(ncx_soup, 'content') if title_letters[i] == self.SYMBOLS: contentTag['src'] = "content/%s.html#%s_titles" % (output, self.SYMBOLS) else: contentTag['src'] = "content/%s.html#%s_titles" % (output, self.generate_unicode_name(title_letters[i])) - navPointByLetterTag.insert(1,contentTag) + navPointByLetterTag.insert(1, contentTag) if self.generate_for_kindle_mobi: cmTag = Tag(ncx_soup, '%s' % 'calibre:meta') @@ -3371,18 +3415,21 @@ class CatalogBuilder(object): if self.generate_for_kindle_mobi: navPointTag['class'] = "section" file_ID = "%s" % tocTitle.lower() - file_ID = file_ID.replace(" ","") + file_ID = file_ID.replace(" ", "") navPointTag['id'] = "%s-ID" % file_ID navPointTag['playOrder'] = self.play_order self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') - textTag.insert(0, NavigableString('%s' % tocTitle)) + section_header = '%s [%d]' % (tocTitle, len(self.individual_authors)) + if self.generate_for_kindle_mobi: + section_header = tocTitle + textTag.insert(0, NavigableString(section_header)) navLabelTag.insert(0, textTag) nptc = 0 navPointTag.insert(nptc, navLabelTag) nptc += 1 - contentTag = Tag(ncx_soup,"content") + contentTag = Tag(ncx_soup, "content") contentTag['src'] = "%s#section_start" % HTML_file navPointTag.insert(nptc, contentTag) nptc += 1 @@ -3426,19 +3473,19 @@ class CatalogBuilder(object): self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') - if len(authors_by_letter[1])>1: + if len(authors_by_letter[1]) > 1: fmt_string = _(u"Authors beginning with %s") else: fmt_string = _(u"Authors beginning with '%s'") - textTag.insert(0, NavigableString(fmt_string % (authors_by_letter[1]))) + textTag.insert(0, NavigableString(fmt_string % authors_by_letter[1])) navLabelTag.insert(0, textTag) - navPointByLetterTag.insert(0,navLabelTag) + navPointByLetterTag.insert(0, navLabelTag) contentTag = Tag(ncx_soup, 'content') if authors_by_letter[1] == self.SYMBOLS: contentTag['src'] = "%s#%s_authors" % (HTML_file, authors_by_letter[1]) else: contentTag['src'] = "%s#%s_authors" % (HTML_file, self.generate_unicode_name(authors_by_letter[1])) - navPointByLetterTag.insert(1,contentTag) + navPointByLetterTag.insert(1, contentTag) if self.generate_for_kindle_mobi: cmTag = Tag(ncx_soup, '%s' % 'calibre:meta') @@ -3497,7 +3544,7 @@ class CatalogBuilder(object): if self.generate_for_kindle_mobi: navPointTag['class'] = "section" file_ID = "%s" % tocTitle.lower() - file_ID = file_ID.replace(" ","") + file_ID = file_ID.replace(" ", "") navPointTag['id'] = "%s-ID" % file_ID navPointTag['playOrder'] = self.play_order self.play_order += 1 @@ -3508,7 +3555,7 @@ class CatalogBuilder(object): nptc = 0 navPointTag.insert(nptc, navLabelTag) nptc += 1 - contentTag = Tag(ncx_soup,"content") + contentTag = Tag(ncx_soup, "content") contentTag['src'] = "%s#section_start" % HTML_file navPointTag.insert(nptc, contentTag) nptc += 1 @@ -3518,15 +3565,15 @@ class CatalogBuilder(object): master_date_range_list = [] today = datetime.datetime.now() today_time = datetime.datetime(today.year, today.month, today.day) - for (i,date) in enumerate(self.DATE_RANGE): + for (i, date) in enumerate(self.DATE_RANGE): if i: - date_range = '%d to %d days ago' % (self.DATE_RANGE[i-1], self.DATE_RANGE[i]) + date_range = '%d to %d days ago' % (self.DATE_RANGE[i - 1], self.DATE_RANGE[i]) else: date_range = 'Last %d days' % (self.DATE_RANGE[i]) date_range_limit = self.DATE_RANGE[i] for book in self.books_by_date_range: book_time = datetime.datetime(book['timestamp'].year, book['timestamp'].month, book['timestamp'].day) - if (today_time-book_time).days <= date_range_limit: + if (today_time - book_time).days <= date_range_limit: #print "generate_ncx_by_date_added: %s added %d days ago" % (book['title'], (today_time-book_time).days) current_titles_list.append(book['title']) else: @@ -3541,19 +3588,19 @@ class CatalogBuilder(object): navPointByDateRangeTag = Tag(ncx_soup, 'navPoint') if self.generate_for_kindle_mobi: navPointByDateRangeTag['class'] = "article" - navPointByDateRangeTag['id'] = "%s-ID" % books_by_date_range[1].replace(' ','') + navPointByDateRangeTag['id'] = "%s-ID" % books_by_date_range[1].replace(' ', '') navPointTag['playOrder'] = self.play_order self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') textTag.insert(0, NavigableString(books_by_date_range[1])) navLabelTag.insert(0, textTag) - navPointByDateRangeTag.insert(0,navLabelTag) + navPointByDateRangeTag.insert(0, navLabelTag) contentTag = Tag(ncx_soup, 'content') contentTag['src'] = "%s#bda_%s" % (HTML_file, - books_by_date_range[1].replace(' ','')) + books_by_date_range[1].replace(' ', '')) - navPointByDateRangeTag.insert(1,contentTag) + navPointByDateRangeTag.insert(1, contentTag) if self.generate_for_kindle_mobi: cmTag = Tag(ncx_soup, '%s' % 'calibre:meta') @@ -3601,19 +3648,19 @@ class CatalogBuilder(object): navPointByMonthTag = Tag(ncx_soup, 'navPoint') if self.generate_for_kindle_mobi: navPointByMonthTag['class'] = "article" - navPointByMonthTag['id'] = "bda_%s-%s-ID" % (books_by_month[1].year,books_by_month[1].month ) + navPointByMonthTag['id'] = "bda_%s-%s-ID" % (books_by_month[1].year, books_by_month[1].month) navPointTag['playOrder'] = self.play_order self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') textTag.insert(0, NavigableString(datestr)) navLabelTag.insert(0, textTag) - navPointByMonthTag.insert(0,navLabelTag) + navPointByMonthTag.insert(0, navLabelTag) contentTag = Tag(ncx_soup, 'content') contentTag['src'] = "%s#bda_%s-%s" % (HTML_file, - books_by_month[1].year,books_by_month[1].month) + books_by_month[1].year, books_by_month[1].month) - navPointByMonthTag.insert(1,contentTag) + navPointByMonthTag.insert(1, contentTag) if self.generate_for_kindle_mobi: cmTag = Tag(ncx_soup, '%s' % 'calibre:meta') @@ -3681,7 +3728,7 @@ class CatalogBuilder(object): if self.generate_for_kindle_mobi: navPointTag['class'] = "section" file_ID = "%s" % tocTitle.lower() - file_ID = file_ID.replace(" ","") + file_ID = file_ID.replace(" ", "") navPointTag['id'] = "%s-ID" % file_ID navPointTag['playOrder'] = self.play_order self.play_order += 1 @@ -3692,7 +3739,7 @@ class CatalogBuilder(object): nptc = 0 navPointTag.insert(nptc, navLabelTag) nptc += 1 - contentTag = Tag(ncx_soup,"content") + contentTag = Tag(ncx_soup, "content") contentTag['src'] = "%s#section_start" % HTML_file navPointTag.insert(nptc, contentTag) nptc += 1 @@ -3702,15 +3749,15 @@ class CatalogBuilder(object): master_date_range_list = [] today = datetime.datetime.now() today_time = datetime.datetime(today.year, today.month, today.day) - for (i,date) in enumerate(self.DATE_RANGE): + for (i, date) in enumerate(self.DATE_RANGE): if i: - date_range = '%d to %d days ago' % (self.DATE_RANGE[i-1], self.DATE_RANGE[i]) + date_range = '%d to %d days ago' % (self.DATE_RANGE[i - 1], self.DATE_RANGE[i]) else: date_range = 'Last %d days' % (self.DATE_RANGE[i]) date_range_limit = self.DATE_RANGE[i] for book in self.bookmarked_books_by_date_read: bookmark_time = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp']) - if (today_time-bookmark_time).days <= date_range_limit: + if (today_time - bookmark_time).days <= date_range_limit: #print "generate_ncx_by_date_added: %s added %d days ago" % (book['title'], (today_time-book_time).days) current_titles_list.append(book['title']) else: @@ -3753,21 +3800,21 @@ class CatalogBuilder(object): navPointByDayTag['class'] = "article" navPointByDayTag['id'] = "bdr_%s-%s-%sID" % (books_by_day[1].year, books_by_day[1].month, - books_by_day[1].day ) + books_by_day[1].day) navPointTag['playOrder'] = self.play_order self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') textTag.insert(0, NavigableString(datestr)) navLabelTag.insert(0, textTag) - navPointByDayTag.insert(0,navLabelTag) + navPointByDayTag.insert(0, navLabelTag) contentTag = Tag(ncx_soup, 'content') contentTag['src'] = "%s#bdr_%s-%s-%s" % (HTML_file, books_by_day[1].year, books_by_day[1].month, books_by_day[1].day) - navPointByDayTag.insert(1,contentTag) + navPointByDayTag.insert(1, contentTag) if self.generate_for_kindle_mobi: cmTag = Tag(ncx_soup, '%s' % 'calibre:meta') @@ -3808,7 +3855,7 @@ class CatalogBuilder(object): self.update_progress_full_step(_("NCX for Genres")) if not len(self.genres): - self.opts.log.warn(" No genres found in tags.\n" + self.opts.log.warn(" No genres found\n" " No Genre section added to Catalog") return @@ -3824,19 +3871,21 @@ class CatalogBuilder(object): if self.generate_for_kindle_mobi: navPointTag['class'] = "section" file_ID = "%s" % tocTitle.lower() - file_ID = file_ID.replace(" ","") + file_ID = file_ID.replace(" ", "") navPointTag['id'] = "%s-ID" % file_ID navPointTag['playOrder'] = self.play_order self.play_order += 1 navLabelTag = Tag(ncx_soup, 'navLabel') textTag = Tag(ncx_soup, 'text') - # textTag.insert(0, NavigableString('%s (%d)' % (section_title, len(genre_list)))) - textTag.insert(0, NavigableString('%s' % tocTitle)) + section_header = '%s [%d]' % (tocTitle, len(self.genres)) + if self.generate_for_kindle_mobi: + section_header = tocTitle + textTag.insert(0, NavigableString(section_header)) navLabelTag.insert(0, textTag) nptc = 0 navPointTag.insert(nptc, navLabelTag) nptc += 1 - contentTag = Tag(ncx_soup,"content") + contentTag = Tag(ncx_soup, "content") contentTag['src'] = "content/Genre_%s.html#section_start" % self.genres[0]['tag'] navPointTag.insert(nptc, contentTag) nptc += 1 @@ -3859,8 +3908,8 @@ class CatalogBuilder(object): normalized_tag = self.genre_tags_dict[friendly_tag] break textTag.insert(0, self.format_ncx_text(NavigableString(friendly_tag), dest='description')) - navLabelTag.insert(0,textTag) - navPointVolumeTag.insert(0,navLabelTag) + navLabelTag.insert(0, textTag) + navPointVolumeTag.insert(0, navLabelTag) contentTag = Tag(ncx_soup, "content") contentTag['src'] = "content/Genre_%s.html#Genre_%s" % (normalized_tag, normalized_tag) navPointVolumeTag.insert(1, contentTag) @@ -3871,9 +3920,9 @@ class CatalogBuilder(object): cmTag['name'] = "author" # First - Last author - if len(genre['titles_spanned']) > 1 : + if len(genre['titles_spanned']) > 1: author_range = "%s - %s" % (genre['titles_spanned'][0][0], genre['titles_spanned'][1][0]) - else : + else: author_range = "%s" % (genre['titles_spanned'][0][0]) cmTag.insert(0, NavigableString(author_range)) @@ -3895,7 +3944,7 @@ class CatalogBuilder(object): titles = [] for title in genre['books']: titles.append(title['title']) - titles = sorted(titles, key=lambda x:(self.generate_sort_title(x),self.generate_sort_title(x))) + titles = sorted(titles, key=lambda x: (self.generate_sort_title(x), self.generate_sort_title(x))) titles_list = self.generate_short_description(u" • ".join(titles), dest="description") cmTag.insert(0, NavigableString(self.format_ncx_text(titles_list, dest='description'))) @@ -3942,12 +3991,12 @@ class CatalogBuilder(object): ''' # Add the supplied metadata tags - soup = BeautifulStoneSoup(header, selfClosingTags=['item','itemref', 'meta', 'reference']) + soup = BeautifulStoneSoup(header, selfClosingTags=['item', 'itemref', 'meta', 'reference']) metadata = soup.find('metadata') mtc = 0 titleTag = Tag(soup, "dc:title") - titleTag.insert(0,escape(self.opts.catalog_title)) + titleTag.insert(0, escape(self.opts.catalog_title)) metadata.insert(mtc, titleTag) mtc += 1 @@ -3993,7 +4042,6 @@ class CatalogBuilder(object): mtc += 1 # Write the thumbnail images, descriptions to the manifest - sort_descriptions_by = [] if self.opts.generate_descriptions: for thumb in self.thumbs: itemTag = Tag(soup, "item") @@ -4004,9 +4052,6 @@ class CatalogBuilder(object): manifest.insert(mtc, itemTag) mtc += 1 - # HTML files - add descriptions to manifest and spine - sort_descriptions_by = self.books_by_author if self.opts.sort_descriptions_by_author \ - else self.books_by_title # Add html_files to manifest and spine for file in self.html_filelist_1: @@ -4060,7 +4105,7 @@ class CatalogBuilder(object): spine.insert(stc, itemrefTag) stc += 1 - for book in sort_descriptions_by: + for book in self.books_by_description: # manifest itemTag = Tag(soup, "item") itemTag['href'] = "content/book_%d.html" % int(book['id']) @@ -4081,7 +4126,7 @@ class CatalogBuilder(object): referenceTag['type'] = 'masthead' referenceTag['title'] = 'mastheadimage-image' referenceTag['href'] = 'images/mastheadImage.gif' - guide.insert(0,referenceTag) + guide.insert(0, referenceTag) # Write the OPF file outfile = open("%s/%s.opf" % (self.catalog_path, self.opts.basename), 'w') @@ -4107,7 +4152,7 @@ class CatalogBuilder(object): if stars: star_string = self.SYMBOL_FULL_RATING * stars empty_stars = self.SYMBOL_EMPTY_RATING * (5 - stars) - rating = '%s%s' % (star_string,empty_stars) + rating = '%s%s' % (star_string, empty_stars) except: # Rating could be None pass @@ -4127,9 +4172,9 @@ class CatalogBuilder(object): # Generate a legal XHTML id/href string if self.letter_or_symbol(series) == self.SYMBOLS: - return "symbol_%s_series" % re.sub('\W','',series).lower() + return "symbol_%s_series" % re.sub('\W', '', series).lower() else: - return "%s_series" % re.sub('\W','',ascii_text(series)).lower() + return "%s_series" % re.sub('\W', '', ascii_text(series)).lower() def generate_short_description(self, description, dest=None): """ Generate a truncated version of the supplied string. @@ -4199,36 +4244,36 @@ class CatalogBuilder(object): title_words = title_sort(title).split() translated = [] - for (i,word) in enumerate(title_words): + for (i, word) in enumerate(title_words): # Leading numbers optionally translated to text equivalent # Capitalize leading sort word - if i==0: + if i == 0: # *** Keep this code in case we need to restore numbers_as_text *** if False: #if self.opts.numbers_as_text and re.match('[0-9]+',word[0]): translated.append(NumberToText(word).text.capitalize()) else: - if re.match('[0-9]+',word[0]): - word = word.replace(',','') + if re.match('[0-9]+', word[0]): + word = word.replace(',', '') suffix = re.search('[\D]', word) if suffix: - word = '%10.0f%s' % (float(word[:suffix.start()]),word[suffix.start():]) + word = '%10.0f%s' % (float(word[:suffix.start()]), word[suffix.start():]) else: word = '%10.0f' % (float(word)) # If leading char > 'A', insert symbol as leading forcing lower sort # '/' sorts below numbers, g if self.letter_or_symbol(word[0]) != word[0]: - if word[0] > 'A' or (ord('9') < ord(word[0]) < ord('A')) : + if word[0] > 'A' or (ord('9') < ord(word[0]) < ord('A')): translated.append('/') translated.append(capitalize(word)) else: - if re.search('[0-9]+',word[0]): - word = word.replace(',','') + if re.search('[0-9]+', word[0]): + word = word.replace(',', '') suffix = re.search('[\D]', word) if suffix: - word = '%10.0f%s' % (float(word[:suffix.start()]),word[suffix.start():]) + word = '%10.0f%s' % (float(word[:suffix.start()]), word[suffix.start():]) else: word = '%10.0f' % (float(word)) translated.append(word) @@ -4269,12 +4314,12 @@ class CatalogBuilder(object): if zf is not None: with zf: try: - zf.getinfo(title['uuid']+cover_crc) + zf.getinfo(title['uuid'] + cover_crc) except: pass else: # uuid found in cache with matching crc - thumb_data = zf.read(title['uuid']+cover_crc) + thumb_data = zf.read(title['uuid'] + cover_crc) with open(os.path.join(image_dir, thumb_file), 'wb') as f: f.write(thumb_data) return @@ -4286,13 +4331,14 @@ class CatalogBuilder(object): f.write(thumb_data) # Save thumb to archive - if zf is not None: # Ensure that the read succeeded + if zf is not None: + # Ensure that the read succeeded # If we failed to open the zip file for reading, # we dont know if it contained the thumb or not zf = _open_archive('a') if zf is not None: with zf: - zf.writestr(title['uuid']+cover_crc, thumb_data) + zf.writestr(title['uuid'] + cover_crc, thumb_data) def generate_thumbnails(self): """ Generate a thumbnail cover for each book. @@ -4311,11 +4357,11 @@ class CatalogBuilder(object): self.update_progress_full_step(_("Thumbnails")) thumbs = ['thumbnail_default.jpg'] image_dir = "%s/images" % self.catalog_path - for (i,title) in enumerate(self.books_by_title): + for (i, title) in enumerate(self.books_by_title): # Update status self.update_progress_micro_step("%s %d of %d" % (_("Thumbnail"), i, len(self.books_by_title)), - i/float(len(self.books_by_title))) + i / float(len(self.books_by_title))) thumb_file = 'thumbnail_%d.jpg' % int(title['id']) thumb_generated = True @@ -4337,7 +4383,7 @@ class CatalogBuilder(object): if not thumb_generated: self.opts.log.warn(" using default cover for '%s' (%d)" % (title['title'], title['id'])) # Confirm thumb exists, default is current - default_thumb_fp = os.path.join(image_dir,"thumbnail_default.jpg") + default_thumb_fp = os.path.join(image_dir, "thumbnail_default.jpg") cover = os.path.join(self.catalog_path, "DefaultCover.png") title['cover'] = cover @@ -4363,7 +4409,6 @@ class CatalogBuilder(object): # Clear the book's cover property title['cover'] = None - # Write thumb_width to the file, validating cache contents # Allows detection of aborted catalog builds with ZipFile(self.thumbs_path, mode='a') as zfw: @@ -4477,7 +4522,7 @@ class CatalogBuilder(object): raise return pr - def letter_or_symbol(self,char): + def letter_or_symbol(self, char): """ Test asciized char for A-z. Convert char to ascii, test for A-z. @@ -4551,7 +4596,7 @@ class CatalogBuilder(object): # confusion with decimal points. # Explode lost CRs to \n\n - for lost_cr in re.finditer('([a-z])([\.\?!])([A-Z])',comments): + for lost_cr in re.finditer('([a-z])([\.\?!])([A-Z])', comments): comments = comments.replace(lost_cr.group(), '%s%s\n\n%s' % (lost_cr.group(1), lost_cr.group(2), @@ -4573,17 +4618,17 @@ class CatalogBuilder(object): split_ps = comments.split(u'\n\n') tsc = 0 for p in split_ps: - pTag = Tag(soup,'p') - pTag.insert(0,p) - soup.insert(tsc,pTag) + pTag = Tag(soup, 'p') + pTag.insert(0, p) + soup.insert(tsc, pTag) tsc += 1 comments = soup.renderContents(None) # Convert solo returns to
    - comments = re.sub('[\r\n]','
    ', comments) + comments = re.sub('[\r\n]', '
    ', comments) # Convert two hypens to emdash - comments = re.sub('--','—',comments) + comments = re.sub('--', '—', comments) soup = BeautifulSoup(comments) result = BeautifulSoup() rtc = 0 @@ -4593,15 +4638,15 @@ class CatalogBuilder(object): for token in all_tokens: if type(token) is NavigableString: if not open_pTag: - pTag = Tag(result,'p') + pTag = Tag(result, 'p') open_pTag = True ptc = 0 - pTag.insert(ptc,prepare_string_for_xml(token)) + pTag.insert(ptc, prepare_string_for_xml(token)) ptc += 1 - elif token.name in ['br','b','i','em']: + elif token.name in ['br', 'b', 'i', 'em']: if not open_pTag: - pTag = Tag(result,'p') + pTag = Tag(result, 'p') open_pTag = True ptc = 0 pTag.insert(ptc, token) @@ -4631,7 +4676,7 @@ class CatalogBuilder(object): # Add back
    elems initially removed for elem in elems: - result.insert(rtc,elem) + result.insert(rtc, elem) rtc += 1 return result.renderContents(encoding=None) @@ -4704,7 +4749,7 @@ class CatalogBuilder(object): if rule[1].startswith('#') and rule[2] != '': field = rule[1] pat = rule[2] - exclusion_pairs.append((field,pat)) + exclusion_pairs.append((field, pat)) else: continue if exclusion_pairs: @@ -4713,12 +4758,12 @@ class CatalogBuilder(object): for record in data_set: for exclusion_pair in exclusion_pairs: - field,pat = exclusion_pair + field, pat = exclusion_pair field_contents = self.db.get_field(record['id'], field, index_is_id=True) - if (self.db.metadata_for_field(field)['datatype'] == 'bool' and + if (self.db.metadata_for_field(field)['datatype'] == 'bool' and field_contents is None): # Handle condition where field is a bool and contents is None, # which is displayed as No @@ -4787,7 +4832,7 @@ class CatalogBuilder(object): new_book = deepcopy(book) new_book['author'] = ' & '.join(cloned_authors) new_book['authors'] = list(cloned_authors) - asl = [author_to_author_sort(auth) for auth in cloned_authors] + asl = [author_to_author_sort(auth) for auth in cloned_authors] new_book['author_sort'] = ' & '.join(asl) books_by_author.append(new_book) @@ -4807,12 +4852,12 @@ class CatalogBuilder(object): self.current_step += 1 self.progress_string = description - self.progress_int = float((self.current_step-1)/self.total_steps) + self.progress_int = float((self.current_step - 1) / self.total_steps) if not self.progress_int: self.progress_int = 0.01 self.reporter(self.progress_int, self.progress_string) if self.opts.cli_environment: - self.opts.log(u"%3.0f%% %s" % (self.progress_int*100, self.progress_string)) + self.opts.log(u"%3.0f%% %s" % (self.progress_int * 100, self.progress_string)) def update_progress_micro_step(self, description, micro_step_pct): """ Update calibre's job status UI. @@ -4829,10 +4874,10 @@ class CatalogBuilder(object): (UI): Jobs UI updated """ - step_range = 100/self.total_steps + step_range = 100 / self.total_steps self.progress_string = description - coarse_progress = float((self.current_step-1)/self.total_steps) - fine_progress = float((micro_step_pct*step_range)/100) + coarse_progress = float((self.current_step - 1) / self.total_steps) + fine_progress = float((micro_step_pct * step_range) / 100) self.progress_int = coarse_progress + fine_progress self.reporter(self.progress_int, self.progress_string) @@ -4853,5 +4898,3 @@ class CatalogBuilder(object): outfile = open("%s/%s.ncx" % (self.catalog_path, self.opts.basename), 'w') outfile.write(self.ncx_soup.prettify()) - -