Merge from trunk

This commit is contained in:
Charles Haley 2012-12-05 18:47:28 +01:00
commit 4130aab9da
14 changed files with 879 additions and 800 deletions

View File

@ -20,6 +20,7 @@ class Aksiyon (BasicNewsRecipe):
auto_cleanup = True auto_cleanup = True
cover_img_url = 'http://www.aksiyon.com.tr/aksiyon/images/aksiyon/top-page/aksiyon_top_r2_c1.jpg' 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' 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 remove_empty_feeds= True
feeds = [ feeds = [
( u'KAPAK', u'http://www.aksiyon.com.tr/aksiyon/rss?sectionId=26'), ( u'KAPAK', u'http://www.aksiyon.com.tr/aksiyon/rss?sectionId=26'),

View File

@ -2,8 +2,8 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '4 February 2011, desUBIKado' __copyright__ = '4 February 2011, desUBIKado'
__author__ = 'desUBIKado' __author__ = 'desUBIKado'
__version__ = 'v0.08' __version__ = 'v0.09'
__date__ = '30, June 2012' __date__ = '02, December 2012'
''' '''
http://www.weblogssl.com/ http://www.weblogssl.com/
''' '''
@ -37,6 +37,7 @@ class weblogssl(BasicNewsRecipe):
,(u'Xataka Mexico', u'http://feeds.weblogssl.com/xatakamx') ,(u'Xataka Mexico', u'http://feeds.weblogssl.com/xatakamx')
,(u'Xataka M\xf3vil', u'http://feeds.weblogssl.com/xatakamovil') ,(u'Xataka M\xf3vil', u'http://feeds.weblogssl.com/xatakamovil')
,(u'Xataka Android', u'http://feeds.weblogssl.com/xatakandroid') ,(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 Foto', u'http://feeds.weblogssl.com/xatakafoto')
,(u'Xataka ON', u'http://feeds.weblogssl.com/xatakaon') ,(u'Xataka ON', u'http://feeds.weblogssl.com/xatakaon')
,(u'Xataka Ciencia', u'http://feeds.weblogssl.com/xatakaciencia') ,(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'}), keep_only_tags = [dict(name='div', attrs={'id':'infoblock'}),
dict(name='div', attrs={'class':'post'}), 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): def print_version(self, url):
return url.replace('http://www.', 'http://m.') return url.replace('http://www.', 'http://m.')
preprocess_regexps = [ preprocess_regexps = [
# Para poner una linea en blanco entre un comentario y el siguiente # Para poner una linea en blanco entre un comentario y el siguiente
(re.compile(r'<li id="c', re.DOTALL|re.IGNORECASE), lambda match: '<br><br><li id="c') (re.compile(r'<li id="c', re.DOTALL|re.IGNORECASE), lambda match: '<br><br><li id="c'),
# Para ver las imágenes en las noticias de m.xataka.com
(re.compile(r'<noscript>', re.DOTALL|re.IGNORECASE), lambda m: ''),
(re.compile(r'</noscript>', re.DOTALL|re.IGNORECASE), lambda m: '')
] ]
# Para sustituir el video incrustado de YouTube por una imagen # Para sustituir el video incrustado de YouTube por una imagen
def preprocess_html(self, soup): 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" # 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 # 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): def get_article_url(self, article):
link = article.get('link', None) link = article.get('link', None)
if link is None: if link is None:
return article return article
# if link.split('/')[-4]=="xataka2":
# return article.get('feedburner_origlink', article.get('link', article.get('guid')))
if link.split('/')[-4]=="xataka2": 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": if link.split('/')[-1]=="story01.htm":
link=link.split('/')[-2] link=link.split('/')[-2]
a=['0B','0C','0D','0E','0F','0G','0N' ,'0L0S','0A'] a=['0B','0C','0D','0E','0F','0G','0N' ,'0L0S','0A']

View File

@ -9,15 +9,15 @@ class Zaman (BasicNewsRecipe):
__author__ = u'thomass' __author__ = u'thomass'
oldest_article = 2 oldest_article = 2
max_articles_per_feed =50 max_articles_per_feed =50
# no_stylesheets = True no_stylesheets = True
#delay = 1 #delay = 1
#use_embedded_content = False use_embedded_content = False
encoding = 'ISO 8859-9' encoding = 'utf-8'
publisher = 'Zaman' publisher = 'Feza Gazetecilik'
category = 'news, haberler,TR,gazete' category = 'news, haberler,TR,gazete'
language = 'tr' language = 'tr'
publication_type = 'newspaper ' 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 = { conversion_options = {
'tags' : category 'tags' : category
,'language' : language ,'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' 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' 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']}) ] #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://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']}) 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_attributes = ['width','height']
remove_empty_feeds= True remove_empty_feeds= True
feeds = [ feeds = [
( u'Anasayfa', u'http://www.zaman.com.tr/anasayfa.rss'), ( u'Manşet', u'http://www.zaman.com.tr/manset.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'Yazarlar', u'http://www.zaman.com.tr/yazarlar.rss'), ( u'Yazarlar', u'http://www.zaman.com.tr/yazarlar.rss'),
( u'Politika', u'http://www.zaman.com.tr/politika.rss'), ( u'Politika', u'http://www.zaman.com.tr/politika.rss'),
( u'Ekonomi', u'http://www.zaman.com.tr/ekonomi.rss'), ( u'Ekonomi', u'http://www.zaman.com.tr/ekonomi.rss'),
( u'Dış Haberler', u'http://www.zaman.com.tr/dishaberler.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'Yorumlar', u'http://www.zaman.com.tr/yorumlar.rss'),
( u'Röportaj', u'http://www.zaman.com.tr/roportaj.rss'), ( u'Röportaj', u'http://www.zaman.com.tr/roportaj.rss'),
( u'Dizi Yazı', u'http://www.zaman.com.tr/dizi.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'Cuma Eki', u'http://www.zaman.com.tr/cuma.rss'),
( u'Cumaertesi Eki', u'http://www.zaman.com.tr/cumaertesi.rss'), ( u'Cumaertesi Eki', u'http://www.zaman.com.tr/cumaertesi.rss'),
( u'Pazar Eki', u'http://www.zaman.com.tr/pazar.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): 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=')

View File

@ -39,18 +39,6 @@ class Win32(WinBase):
def msi64(self): def msi64(self):
return installer_name('msi', is64bit=True) 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): def do_dl(self, installer, errmsg):
subprocess.check_call(('scp', subprocess.check_call(('scp',
'%s:build/%s/%s'%(self.VM_NAME, __appname__, installer), 'dist')) '%s:build/%s/%s'%(self.VM_NAME, __appname__, installer), 'dist'))
@ -62,14 +50,8 @@ class Win32(WinBase):
installer = self.installer() installer = self.installer()
if os.path.exists('build/winfrozen'): if os.path.exists('build/winfrozen'):
shutil.rmtree('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') self.do_dl(installer, 'Failed to freeze')
installer = 'dist/%s-portable-installer-%s.exe'%(__appname__, __version__) installer = 'dist/%s-portable-installer-%s.exe'%(__appname__, __version__)
self.do_dl(installer, 'Failed to get portable installer') self.do_dl(installer, 'Failed to get portable installer')

View File

@ -91,6 +91,7 @@ class Win32Freeze(Command, WixMixIn):
if not is64bit: if not is64bit:
self.build_portable() self.build_portable()
self.build_portable_installer() self.build_portable_installer()
self.sign_installers()
def remove_CRT_from_manifests(self): 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]) 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=''): def add_dir_to_zip(self, zf, path, prefix=''):
''' '''
Add a directory recursively to the zip file with an optional prefix. Add a directory recursively to the zip file with an optional prefix.

View File

@ -20,6 +20,7 @@ from calibre.utils.config import config_dir, dynamic, prefs
from calibre.utils.date import now, parse_date from calibre.utils.date import now, parse_date
from calibre.utils.zipfile import ZipFile from calibre.utils.zipfile import ZipFile
def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None): def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
if not hasattr(dt, 'timetuple'): if not hasattr(dt, 'timetuple'):
@ -38,6 +39,7 @@ def logger():
_log = ThreadSafeLog() _log = ThreadSafeLog()
return _log return _log
class AppleOpenFeedback(OpenFeedback): class AppleOpenFeedback(OpenFeedback):
def __init__(self, plugin): def __init__(self, plugin):
@ -102,6 +104,7 @@ class AppleOpenFeedback(OpenFeedback):
return Dialog(parent, self) return Dialog(parent, self)
class DriverBase(DeviceConfig, DevicePlugin): class DriverBase(DeviceConfig, DevicePlugin):
# Needed for config_widget to work # Needed for config_widget to work
FORMATS = ['epub', 'pdf'] FORMATS = ['epub', 'pdf']
@ -133,11 +136,11 @@ class DriverBase(DeviceConfig, DevicePlugin):
False, False,
] ]
@classmethod @classmethod
def _config_base_name(cls): def _config_base_name(cls):
return 'iTunes' return 'iTunes'
class ITUNES(DriverBase): class ITUNES(DriverBase):
''' '''
Calling sequences: Calling sequences:
@ -148,6 +151,8 @@ class ITUNES(DriverBase):
open() open()
card_prefix() card_prefix()
can_handle() can_handle()
_launch_iTunes()
_discover_manual_sync_mode()
set_progress_reporter() set_progress_reporter()
get_device_information() get_device_information()
card_prefix() card_prefix()
@ -156,6 +161,7 @@ class ITUNES(DriverBase):
can_handle() can_handle()
set_progress_reporter() set_progress_reporter()
books() (once for each storage point) books() (once for each storage point)
(create self.cached_books)
settings() settings()
settings() settings()
can_handle() (~1x per second OSX while idle) can_handle() (~1x per second OSX while idle)
@ -186,14 +192,14 @@ class ITUNES(DriverBase):
free_space() free_space()
''' '''
name = 'Apple device interface' name = 'Apple iTunes interface'
gui_name = _('Apple device') gui_name = _('Apple device')
icon = I('devices/ipad.png') icon = I('devices/ipad.png')
description = _('Communicate with iTunes/iBooks.') description = _('Communicate with iTunes/iBooks.')
supported_platforms = ['osx', 'windows'] supported_platforms = ['osx', 'windows']
author = 'GRiker' author = 'GRiker'
#: The version of this plugin as a 3-tuple (major, minor, revision) #: 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" DISPLAY_DISABLE_DIALOG = "display_disable_apple_driver_dialog"
@ -203,7 +209,7 @@ class ITUNES(DriverBase):
USE_ITUNES_STORAGE = 2 USE_ITUNES_STORAGE = 2
OPEN_FEEDBACK_MESSAGE = _( OPEN_FEEDBACK_MESSAGE = _(
'Apple device detected, launching iTunes, please wait ...') 'Apple iDevice detected, launching iTunes, please wait ...')
BACKLOADING_ERROR_MESSAGE = _( BACKLOADING_ERROR_MESSAGE = _(
"Cannot copy books directly from iDevice. " "Cannot copy books directly from iDevice. "
"Drag from iTunes Library to desktop, then add to calibre's Library window.") "Drag from iTunes Library to desktop, then add to calibre's Library window.")
@ -218,22 +224,9 @@ class ITUNES(DriverBase):
'for more information.</p>' 'for more information.</p>'
'<p></p>') '<p></p>')
# Product IDs: VENDOR_ID = []
# 0x1291 iPod Touch PRODUCT_ID = []
# 0x1293 iPod Touch 2G BCD = []
# 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]
# Plugboard ID # Plugboard ID
DEVICE_PLUGBOARD_NAME = 'APPLE' DEVICE_PLUGBOARD_NAME = 'APPLE'
@ -329,7 +322,7 @@ class ITUNES(DriverBase):
L{books}(oncard='cardb')). L{books}(oncard='cardb')).
''' '''
if DEBUG: 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)) task_count = float(len(self.update_list))
@ -414,13 +407,13 @@ class ITUNES(DriverBase):
""" """
if not oncard: if not oncard:
if DEBUG: if DEBUG:
logger().info("ITUNES:books():") logger().info("%s.books():" % self.__class__.__name__)
if self.settings().extra_customization[self.CACHE_COVERS]: if self.settings().extra_customization[self.CACHE_COVERS]:
logger().info(" Cover fetching/caching enabled") logger().info(" Cover fetching/caching enabled")
else: else:
logger().info(" Cover fetching/caching disabled") 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: if 'iPod' in self.sources:
booklist = BookList(logger()) booklist = BookList(logger())
cached_books = {} cached_books = {}
@ -451,7 +444,8 @@ class ITUNES(DriverBase):
cached_books[this_book.path] = { cached_books[this_book.path] = {
'title': book.name(), 'title': book.name(),
'author':book.artist().split(' & '), 'author': book.artist(),
'authors': book.artist().split(' & '),
'lib_book': library_books[this_book.path] if this_book.path in library_books else None, 'lib_book': library_books[this_book.path] if this_book.path in library_books else None,
'dev_book': book, 'dev_book': book,
'uuid': book.composer() 'uuid': book.composer()
@ -491,7 +485,8 @@ class ITUNES(DriverBase):
cached_books[this_book.path] = { cached_books[this_book.path] = {
'title': book.Name, 'title': book.Name,
'author':book.Artist.split(' & '), 'author': book.Artist,
'authors': book.Artist.split(' & '),
'lib_book': library_books[this_book.path] if this_book.path in library_books else None, 'lib_book': library_books[this_book.path] if this_book.path in library_books else None,
'uuid': book.Composer, 'uuid': book.Composer,
'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' 'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub'
@ -556,7 +551,7 @@ class ITUNES(DriverBase):
# We need to know if iTunes sees the iPad # We need to know if iTunes sees the iPad
# It may have been ejected # It may have been ejected
if DEBUG: if DEBUG:
logger().info("ITUNES.can_handle()") logger().info("%s.can_handle()" % self.__class__.__name__)
self._launch_iTunes() self._launch_iTunes()
self.sources = self._get_sources() self.sources = self._get_sources()
@ -567,12 +562,12 @@ class ITUNES(DriverBase):
self.sources = self._get_sources() self.sources = self._get_sources()
if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''): if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''):
attempts -= 1 attempts -= 1
time.sleep(0.5) time.sleep(1.0)
if DEBUG: if DEBUG:
logger().warning(" waiting for connected iPad, attempt #%d" % (10 - attempts)) logger().warning(" waiting for connected iDevice, attempt #%d" % (10 - attempts))
else: else:
if DEBUG: if DEBUG:
logger().info(' found connected iPad') logger().info(' found connected iDevice')
break break
else: else:
# iTunes running, but not connected iPad # iTunes running, but not connected iPad
@ -613,26 +608,26 @@ class ITUNES(DriverBase):
sys.stdout.write('.') sys.stdout.write('.')
sys.stdout.flush() sys.stdout.flush()
if DEBUG: 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.ejected = False
self._discover_manual_sync_mode() self._discover_manual_sync_mode()
return True return True
else: else:
if DEBUG: 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 self.ejected = True
return False return False
except: except:
# iTunes connection failed, probably not running anymore # 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 return False
finally: finally:
pythoncom.CoUninitialize() pythoncom.CoUninitialize()
else: else:
if DEBUG: if DEBUG:
logger().info("ITUNES:can_handle_windows():\n Launching iTunes") logger().info("%s.can_handle_windows():\n Launching iTunes" % self.__class__.__name__)
try: try:
pythoncom.CoInitialize() pythoncom.CoInitialize()
@ -645,9 +640,9 @@ class ITUNES(DriverBase):
self.sources = self._get_sources() self.sources = self._get_sources()
if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''): if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''):
attempts -= 1 attempts -= 1
time.sleep(0.5) time.sleep(1.0)
if DEBUG: if DEBUG:
logger().warning(" waiting for connected iPad, attempt #%d" % (10 - attempts)) logger().warning(" waiting for connected iDevice, attempt #%d" % (10 - attempts))
else: else:
if DEBUG: if DEBUG:
logger().info(' found connected iPad in iTunes') logger().info(' found connected iPad in iTunes')
@ -702,7 +697,7 @@ class ITUNES(DriverBase):
self.problem_msg = _("Some books not found in iTunes database.\n" self.problem_msg = _("Some books not found in iTunes database.\n"
"Delete using the iBooks app.\n" "Delete using the iBooks app.\n"
"Click 'Show Details' for a list.") "Click 'Show Details' for a list.")
logger().info("ITUNES:delete_books()") logger().info("%s.delete_books()" % self.__class__.__name__)
for path in paths: for path in paths:
if self.cached_books[path]['lib_book']: if self.cached_books[path]['lib_book']:
if DEBUG: if DEBUG:
@ -731,8 +726,11 @@ class ITUNES(DriverBase):
else: else:
if self.manual_sync_mode: if self.manual_sync_mode:
metadata = MetaInformation(self.cached_books[path]['title'], 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'] metadata.uuid = self.cached_books[path]['uuid']
if not metadata.uuid:
metadata.uuid = "unknown"
if isosx: if isosx:
self._remove_existing_copy(self.cached_books[path], metadata) self._remove_existing_copy(self.cached_books[path], metadata)
@ -754,7 +752,7 @@ class ITUNES(DriverBase):
are pending GUI jobs that need to communicate with the device. are pending GUI jobs that need to communicate with the device.
''' '''
if DEBUG: 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: if isosx:
self.iTunes.eject(self.sources['iPod']) self.iTunes.eject(self.sources['iPod'])
elif iswindows: elif iswindows:
@ -785,7 +783,7 @@ class ITUNES(DriverBase):
In Windows, a sync-in-progress blocks this call until sync is complete In Windows, a sync-in-progress blocks this call until sync is complete
""" """
if DEBUG: if DEBUG:
logger().info("ITUNES:free_space()") logger().info("%s.free_space()" % self.__class__.__name__)
free_space = 0 free_space = 0
if isosx: if isosx:
@ -818,7 +816,7 @@ class ITUNES(DriverBase):
@return: (device name, device version, software version on device, mime type) @return: (device name, device version, software version on device, mime type)
""" """
if DEBUG: 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')
@ -828,7 +826,7 @@ class ITUNES(DriverBase):
@param outfile: file object like C{sys.stdout} or the result of an C{open} call @param outfile: file object like C{sys.stdout} or the result of an C{open} call
''' '''
if DEBUG: if DEBUG:
logger().info("ITUNES.get_file(): exporting '%s'" % path) logger().info("%s.get_file(): exporting '%s'" % (self.__class__.__name__, path))
try: try:
outfile.write(open(self.cached_books[path]['lib_book'].location().path).read()) outfile.write(open(self.cached_books[path]['lib_book'].location().path).read())
@ -859,7 +857,19 @@ class ITUNES(DriverBase):
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE) raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)
if DEBUG: 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 # Display a dialog recommending using 'Connect to iTunes' if user hasn't
# previously disabled the dialog # previously disabled the dialog
@ -867,7 +877,11 @@ class ITUNES(DriverBase):
raise AppleOpenFeedback(self) raise AppleOpenFeedback(self)
else: else:
if DEBUG: 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 # Confirm/create thumbs archive
if not os.path.exists(self.cache_dir): if not os.path.exists(self.cache_dir):
@ -908,14 +922,14 @@ class ITUNES(DriverBase):
as uuids are different as uuids are different
''' '''
if DEBUG: if DEBUG:
logger().info("ITUNES.remove_books_from_metadata()") logger().info("%s.remove_books_from_metadata()" % self.__class__.__name__)
for path in paths: for path in paths:
if DEBUG: if DEBUG:
self._dump_cached_book(self.cached_books[path], indent=2) self._dump_cached_book(self.cached_books[path], indent=2)
logger().info(" looking for '%s' by '%s' uuid:%s" % logger().info(" looking for '%s' by '%s' uuid:%s" %
(self.cached_books[path]['title'], (self.cached_books[path]['title'],
self.cached_books[path]['author'], self.cached_books[path]['author'],
self.cached_books[path]['uuid'])) repr(self.cached_books[path]['uuid'])))
# Purge the booklist, self.cached_books, thumb cache # Purge the booklist, self.cached_books, thumb cache
for i, bl_book in enumerate(booklists[0]): for i, bl_book in enumerate(booklists[0]):
@ -924,24 +938,28 @@ class ITUNES(DriverBase):
(bl_book.title, bl_book.author, bl_book.uuid)) (bl_book.title, bl_book.author, bl_book.uuid))
found = False found = False
if bl_book.uuid == self.cached_books[path]['uuid']: if bl_book.uuid and bl_book.uuid == self.cached_books[path]['uuid']:
if False: if True:
logger().info(" matched with uuid") logger().info(" --matched uuid")
booklists[0].pop(i) booklists[0].pop(i)
found = True found = True
elif bl_book.title == self.cached_books[path]['title'] and \ elif bl_book.title == self.cached_books[path]['title'] and \
bl_book.author[0] == self.cached_books[path]['author']: bl_book.author == self.cached_books[path]['author']:
if False: if True:
logger().info(" matched with title + author") logger().info(" --matched title + author")
booklists[0].pop(i) booklists[0].pop(i)
found = True found = True
if found: if found:
# Remove from self.cached_books # Remove from self.cached_books
for cb in 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) self.cached_books.pop(cb)
break break
else:
logger().error(" '%s' not found in self.cached_books" % self.cached_books[path]['title'])
# Remove from thumb from thumb cache # Remove from thumb from thumb cache
thumb_path = path.rpartition('.')[0] + '.jpg' thumb_path = path.rpartition('.')[0] + '.jpg'
@ -964,7 +982,9 @@ class ITUNES(DriverBase):
else: else:
if DEBUG: if DEBUG:
logger().error(" unable to find '%s' by '%s' (%s)" % 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: if False:
self._dump_booklist(booklists[0], indent=2) self._dump_booklist(booklists[0], indent=2)
@ -982,7 +1002,7 @@ class ITUNES(DriverBase):
:detected_device: Device information from the device scanner :detected_device: Device information from the device scanner
""" """
if DEBUG: if DEBUG:
logger().info("ITUNES.reset()") logger().info("%s.reset()" % self.__class__.__name__)
if report_progress: if report_progress:
self.set_progress_reporter(report_progress) self.set_progress_reporter(report_progress)
@ -994,7 +1014,7 @@ class ITUNES(DriverBase):
task does not have any progress information task does not have any progress information
''' '''
if DEBUG: if DEBUG:
logger().info("ITUNES.set_progress_reporter()") logger().info("%s.set_progress_reporter()" % self.__class__.__name__)
self.report_progress = report_progress self.report_progress = report_progress
@ -1002,7 +1022,7 @@ class ITUNES(DriverBase):
# This method is called with the plugboard that matches the format # This method is called with the plugboard that matches the format
# declared in use_plugboard_ext and a device name of ITUNES # declared in use_plugboard_ext and a device name of ITUNES
if DEBUG: if DEBUG:
logger().info("ITUNES.set_plugboard()") logger().info("%s.set_plugboard()" % self.__class__.__name__)
#logger().info(' plugboard: %s' % plugboards) #logger().info(' plugboard: %s' % plugboards)
self.plugboards = plugboards self.plugboards = plugboards
self.plugboard_func = pb_func self.plugboard_func = pb_func
@ -1016,7 +1036,7 @@ class ITUNES(DriverBase):
''' '''
if DEBUG: if DEBUG:
logger().info("ITUNES.sync_booklists()") logger().info("%s.sync_booklists()" % self.__class__.__name__)
if self.update_needed: if self.update_needed:
if DEBUG: if DEBUG:
@ -1043,7 +1063,7 @@ class ITUNES(DriverBase):
particular device doesn't have any of these locations it should return 0. particular device doesn't have any of these locations it should return 0.
""" """
if DEBUG: if DEBUG:
logger().info("ITUNES:total_space()") logger().info("%s.total_space()" % self.__class__.__name__)
capacity = 0 capacity = 0
if isosx: if isosx:
if 'iPod' in self.sources: if 'iPod' in self.sources:
@ -1081,7 +1101,7 @@ class ITUNES(DriverBase):
"Click 'Show Details' for a list.") "Click 'Show Details' for a list.")
if DEBUG: if DEBUG:
logger().info("ITUNES.upload_books()") logger().info("%s.upload_books()" % self.__class__.__name__)
if isosx: if isosx:
for (i, fpath) in enumerate(files): for (i, fpath) in enumerate(files):
@ -1098,7 +1118,7 @@ class ITUNES(DriverBase):
# Add new_book to self.cached_books # Add new_book to self.cached_books
if DEBUG: 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" % logger().info(" adding '%s' by '%s' uuid:%s to self.cached_books" %
(metadata[i].title, (metadata[i].title,
authors_to_string(metadata[i].authors), authors_to_string(metadata[i].authors),
@ -1144,7 +1164,7 @@ class ITUNES(DriverBase):
# Add new_book to self.cached_books # Add new_book to self.cached_books
if DEBUG: 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" % logger().info(" adding '%s' by '%s' uuid:%s to self.cached_books" %
(metadata[i].title, (metadata[i].title,
authors_to_string(metadata[i].authors), authors_to_string(metadata[i].authors),
@ -1182,7 +1202,7 @@ class ITUNES(DriverBase):
''' '''
assumes pythoncom wrapper for windows assumes pythoncom wrapper for windows
''' '''
logger().info(" ITUNES._add_device_book()") logger().info(" %s._add_device_book()" % self.__class__.__name__)
if isosx: if isosx:
import appscript import appscript
if 'iPod' in self.sources: if 'iPod' in self.sources:
@ -1292,7 +1312,7 @@ class ITUNES(DriverBase):
windows assumes pythoncom wrapper windows assumes pythoncom wrapper
''' '''
if DEBUG: if DEBUG:
logger().info(" ITUNES._add_library_book()") logger().info(" %s._add_library_book()" % self.__class__.__name__)
if isosx: if isosx:
import appscript import appscript
added = self.iTunes.add(appscript.mactypes.File(file)) added = self.iTunes.add(appscript.mactypes.File(file))
@ -1360,7 +1380,7 @@ class ITUNES(DriverBase):
fp = cached_book['lib_book'].Location fp = cached_book['lib_book'].Location
''' '''
if DEBUG: if DEBUG:
logger().info(" ITUNES._add_new_copy()") logger().info(" %s._add_new_copy()" % self.__class__.__name__)
if fpath.rpartition('.')[2].lower() == 'epub': if fpath.rpartition('.')[2].lower() == 'epub':
self._update_epub_metadata(fpath, metadata) self._update_epub_metadata(fpath, metadata)
@ -1399,7 +1419,7 @@ class ITUNES(DriverBase):
from PIL import Image as PILImage from PIL import Image as PILImage
if DEBUG: if DEBUG:
logger().info(" ITUNES._cover_to_thumb()") logger().info(" %s._cover_to_thumb()" % self.__class__.__name__)
thumb = None thumb = None
if metadata.cover: if metadata.cover:
@ -1526,7 +1546,7 @@ class ITUNES(DriverBase):
''' '''
''' '''
if DEBUG: 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 = Book(metadata.title, authors_to_string(metadata.authors))
this_book.datetime = time.gmtime() 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 wait is passed when launching iTunes, as it seems to need a moment to come to its senses
''' '''
if DEBUG: if DEBUG:
logger().info(" ITUNES._discover_manual_sync_mode()") logger().info(" %s._discover_manual_sync_mode()" % self.__class__.__name__)
if wait: if wait:
time.sleep(wait) time.sleep(wait)
if isosx: if isosx:
@ -1593,7 +1613,7 @@ class ITUNES(DriverBase):
if dev_books is not None and len(dev_books): if dev_books is not None and len(dev_books):
first_book = dev_books[0] first_book = dev_books[0]
if False: 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: try:
first_book.bpm.set(0) first_book.bpm.set(0)
self.manual_sync_mode = True self.manual_sync_mode = True
@ -1655,8 +1675,8 @@ class ITUNES(DriverBase):
for book in booklist: for book in booklist:
if isosx: if isosx:
logger().info("%s%-40.40s %-30.30s %-10.10s %s" % logger().info("%s%-40.40s %-30.30s %-40.40s %-10.10s" %
(' '*indent,book.title, book.author, str(book.library_id)[-9:], book.uuid)) (' ' * indent, book.title, book.author, book.uuid, str(book.library_id)[-9:]))
elif iswindows: elif iswindows:
logger().info("%s%-40.40s %-30.30s" % logger().info("%s%-40.40s %-30.30s" %
(' ' * indent, book.title, book.author)) (' ' * indent, book.title, book.author))
@ -1705,13 +1725,14 @@ class ITUNES(DriverBase):
logger().info("%s%s" % (' ' * indent, '-' * len(msg))) logger().info("%s%s" % (' ' * indent, '-' * len(msg)))
if isosx: if isosx:
for cb in self.cached_books.keys(): for cb in self.cached_books.keys():
logger().info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" % logger().info("%s%-40.40s %-30.30s %-40.40s %-10.10s %-10.10s" %
(' ' * indent, (' ' * indent,
self.cached_books[cb]['title'], self.cached_books[cb]['title'],
self.cached_books[cb]['author'], self.cached_books[cb]['author'],
self.cached_books[cb]['uuid'],
str(self.cached_books[cb]['lib_book'])[-9:], str(self.cached_books[cb]['lib_book'])[-9:],
str(self.cached_books[cb]['dev_book'])[-9:], str(self.cached_books[cb]['dev_book'])[-9:],
self.cached_books[cb]['uuid'])) ))
elif iswindows: elif iswindows:
for cb in self.cached_books.keys(): for cb in self.cached_books.keys():
logger().info("%s%-40.40s %-30.30s %-4.4s %s" % logger().info("%s%-40.40s %-30.30s %-4.4s %s" %
@ -1728,7 +1749,7 @@ class ITUNES(DriverBase):
''' '''
from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulSoup
logger().info(" ITUNES.__get_epub_metadata()") logger().info(" %s.__get_epub_metadata()" % self.__class__.__name__)
title = None title = None
author = None author = None
timestamp = None timestamp = None
@ -1760,7 +1781,8 @@ class ITUNES(DriverBase):
''' '''
''' '''
FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)])
N=0; result='' N = 0
result = ''
while src: while src:
s, src = src[:length], src[length:] s, src = src[:length], src[length:]
hexa = ' '.join(["%02X" % ord(x) for x in s]) hexa = ' '.join(["%02X" % ord(x) for x in s])
@ -1806,7 +1828,7 @@ class ITUNES(DriverBase):
if iswindows: if iswindows:
dev_books = self._get_device_books_playlist() dev_books = self._get_device_books_playlist()
if DEBUG: 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)" % logger().info(" searching for '%s' by '%s' (%s)" %
(search['title'], search['author'], search['uuid'])) (search['title'], search['author'], search['uuid']))
attempts = 9 attempts = 9
@ -1876,7 +1898,7 @@ class ITUNES(DriverBase):
''' '''
if iswindows: if iswindows:
if DEBUG: if DEBUG:
logger().info(" ITUNES._find_library_book()") logger().info(" %s._find_library_book()" % self.__class__.__name__)
''' '''
if 'uuid' in search: if 'uuid' in search:
logger().info(" looking for '%s' by %s (%s)" % logger().info(" looking for '%s' by %s (%s)" %
@ -1909,7 +1931,6 @@ class ITUNES(DriverBase):
if DEBUG: if DEBUG:
logger().error(" no Books playlist found") logger().error(" no Books playlist found")
attempts = 9 attempts = 9
while attempts: while attempts:
# Find book whose Album field = search['uuid'] # Find book whose Album field = search['uuid']
@ -1996,7 +2017,8 @@ class ITUNES(DriverBase):
thumb_data = zfr.read(thumb_path) thumb_data = zfr.read(thumb_path)
if thumb_data == 'None': if thumb_data == 'None':
if False: 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() zfr.close()
return None return None
except: except:
@ -2007,7 +2029,7 @@ class ITUNES(DriverBase):
return thumb_data return thumb_data
if DEBUG: if DEBUG:
logger().info(" ITUNES._generate_thumbnail('%s'):" % title) logger().info(" %s._generate_thumbnail('%s'):" % (self.__class__.__name__, title))
if isosx: if isosx:
# Fetch the artwork from iTunes # Fetch the artwork from iTunes
@ -2049,7 +2071,6 @@ class ITUNES(DriverBase):
return thumb_data return thumb_data
elif iswindows: elif iswindows:
if not book.Artwork.Count: if not book.Artwork.Count:
if DEBUG: if DEBUG:
@ -2101,7 +2122,7 @@ class ITUNES(DriverBase):
for file in myZipList: for file in myZipList:
exploded_file_size += file.file_size exploded_file_size += file.file_size
if False: 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(" %d items in archive" % len(myZipList))
logger().info(" compressed: %d exploded: %d" % (compressed_size, exploded_file_size)) logger().info(" compressed: %d exploded: %d" % (compressed_size, exploded_file_size))
myZip.close() myZip.close()
@ -2112,7 +2133,7 @@ class ITUNES(DriverBase):
Assumes pythoncom wrapper for Windows Assumes pythoncom wrapper for Windows
''' '''
if DEBUG: if DEBUG:
logger().info("\n ITUNES._get_device_books()") logger().info("\n %s._get_device_books()" % self.__class__.__name__)
device_books = [] device_books = []
if isosx: if isosx:
@ -2131,14 +2152,13 @@ class ITUNES(DriverBase):
logger().error(" book_playlist not found") logger().error(" book_playlist not found")
for book in dev_books: for book in dev_books:
# This may need additional entries for international iTunes users
if book.kind() in self.Audiobooks: if book.kind() in self.Audiobooks:
if DEBUG: if DEBUG:
logger().info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) logger().info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
else: else:
if DEBUG: if DEBUG:
logger().info(" %-30.30s %-30.30s %-40.40s [%s]" % logger().info(" %-40.40s %-30.30s %-40.40s [%s]" %
(book.name(), book.artist(), book.album(), book.kind())) (book.name(), book.artist(), book.composer(), book.kind()))
device_books.append(book) device_books.append(book)
if DEBUG: if DEBUG:
logger().info() logger().info()
@ -2165,13 +2185,12 @@ class ITUNES(DriverBase):
logger().info(" no Books playlist found") logger().info(" no Books playlist found")
for book in dev_books: for book in dev_books:
# This may need additional entries for international iTunes users
if book.KindAsString in self.Audiobooks: if book.KindAsString in self.Audiobooks:
if DEBUG: if DEBUG:
logger().info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString)) logger().info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString))
else: else:
if DEBUG: 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) device_books.append(book)
if DEBUG: if DEBUG:
logger().info() logger().info()
@ -2206,7 +2225,7 @@ class ITUNES(DriverBase):
Windows assumes pythoncom wrapper Windows assumes pythoncom wrapper
''' '''
if DEBUG: if DEBUG:
logger().info("\n ITUNES._get_library_books()") logger().info("\n %s._get_library_books()" % self.__class__.__name__)
library_books = {} library_books = {}
library_orphans = {} library_orphans = {}
@ -2381,7 +2400,7 @@ class ITUNES(DriverBase):
''' '''
''' '''
if DEBUG: if DEBUG:
logger().info(" ITUNES:_launch_iTunes():\n Instantiating iTunes") logger().info(" %s._launch_iTunes():\n Instantiating iTunes" % self.__class__.__name__)
if isosx: if isosx:
import appscript import appscript
@ -2394,12 +2413,13 @@ class ITUNES(DriverBase):
running_apps = appscript.app('System Events') running_apps = appscript.app('System Events')
if not 'iTunes' in running_apps.processes.name(): if not 'iTunes' in running_apps.processes.name():
if DEBUG: if DEBUG:
logger().info( "ITUNES:_launch_iTunes(): Launching iTunes" ) logger().info("%s:_launch_iTunes(): Launching iTunes" % self.__class__.__name__)
try: try:
self.iTunes = iTunes = appscript.app('iTunes', hide=True) self.iTunes = iTunes = appscript.app('iTunes', hide=True)
except: except:
self.iTunes = None 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() iTunes.run()
self.initial_status = 'launched' self.initial_status = 'launched'
@ -2444,10 +2464,10 @@ class ITUNES(DriverBase):
if DEBUG: if DEBUG:
logger().info(" %s %s" % (__appname__, __version__)) 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], (platform.mac_ver()[0],
self.iTunes.name(), self.iTunes.version(), self.initial_status, 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(" 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) logger().info(" calibre_library_path: %s" % self.calibre_library_path)
@ -2474,7 +2494,8 @@ class ITUNES(DriverBase):
self.iTunes = win32com.client.Dispatch("iTunes.Application") self.iTunes = win32com.client.Dispatch("iTunes.Application")
except: except:
self.iTunes = None 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: if not DEBUG:
self.iTunes.Windows[0].Minimized = True self.iTunes.Windows[0].Minimized = True
@ -2524,8 +2545,11 @@ class ITUNES(DriverBase):
Remove any iTunes orphans originally added by calibre Remove any iTunes orphans originally added by calibre
This occurs when the user deletes a book in iBooks while disconnected This occurs when the user deletes a book in iBooks while disconnected
''' '''
PURGE_ORPHANS = False
if PURGE_ORPHANS:
if DEBUG: if DEBUG:
logger().info(" ITUNES._purge_orphans()") logger().info(" %s._purge_orphans()" % self.__class__.__name__)
#self._dump_library_books(library_books) #self._dump_library_books(library_books)
#logger().info(" cached_books:\n %s" % "\n ".join(cached_books.keys())) #logger().info(" cached_books:\n %s" % "\n ".join(cached_books.keys()))
@ -2535,7 +2559,8 @@ class ITUNES(DriverBase):
str(library_books[book].description()).startswith(self.description_prefix): str(library_books[book].description()).startswith(self.description_prefix):
if DEBUG: if DEBUG:
logger().info(" '%s' not found on iDevice, removing from iTunes" % book) logger().info(" '%s' not found on iDevice, removing from iTunes" % book)
btr = { 'title':library_books[book].name(), btr = {
'title': library_books[book].name(),
'author': library_books[book].artist(), 'author': library_books[book].artist(),
'lib_book': library_books[book]} 'lib_book': library_books[book]}
self._remove_from_iTunes(btr) self._remove_from_iTunes(btr)
@ -2544,34 +2569,30 @@ class ITUNES(DriverBase):
library_books[book].Description.startswith(self.description_prefix): library_books[book].Description.startswith(self.description_prefix):
if DEBUG: if DEBUG:
logger().info(" '%s' not found on iDevice, removing from iTunes" % book) logger().info(" '%s' not found on iDevice, removing from iTunes" % book)
btr = { 'title':library_books[book].Name, btr = {
'title': library_books[book].Name,
'author': library_books[book].Artist, 'author': library_books[book].Artist,
'lib_book': library_books[book]} 'lib_book': library_books[book]}
self._remove_from_iTunes(btr) self._remove_from_iTunes(btr)
else:
if DEBUG: if DEBUG:
logger().info() logger().info(" %s._purge_orphans(disabled)" % self.__class__.__name__)
def _remove_existing_copy(self, path, metadata): def _remove_existing_copy(self, path, metadata):
''' '''
''' '''
if DEBUG: if DEBUG:
logger().info(" ITUNES._remove_existing_copy()") logger().info(" %s._remove_existing_copy()" % self.__class__.__name__)
if self.manual_sync_mode: if self.manual_sync_mode:
# Delete existing from Device|Books, add to self.update_list # Delete existing from Device|Books, add to self.update_list
# for deletion from booklist[0] during add_books_to_metadata # for deletion from booklist[0] during add_books_to_metadata
for book in self.cached_books: for book in self.cached_books:
if self.cached_books[book]['uuid'] == metadata.uuid or \ if (self.cached_books[book]['uuid'] == metadata.uuid or
(self.cached_books[book]['title'] == metadata.title and \ (self.cached_books[book]['title'] == metadata.title and
self.cached_books[book]['author'] == authors_to_string(metadata.authors)): self.cached_books[book]['author'] == metadata.author)):
self.update_list.append(self.cached_books[book]) 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]) 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]) self._remove_from_iTunes(self.cached_books[book])
break break
else: else:
@ -2581,9 +2602,9 @@ class ITUNES(DriverBase):
# Delete existing from Library|Books, add to self.update_list # Delete existing from Library|Books, add to self.update_list
# for deletion from booklist[0] during add_books_to_metadata # for deletion from booklist[0] during add_books_to_metadata
for book in self.cached_books: for book in self.cached_books:
if self.cached_books[book]['uuid'] == metadata.uuid or \ if (self.cached_books[book]['uuid'] == metadata.uuid or
(self.cached_books[book]['title'] == metadata.title and \ (self.cached_books[book]['title'] == metadata.title and \
self.cached_books[book]['author'] == authors_to_string(metadata.authors)): self.cached_books[book]['author'] == metadata.author)):
self.update_list.append(self.cached_books[book]) self.update_list.append(self.cached_books[book])
if DEBUG: if DEBUG:
logger().info(" deleting library book '%s'" % metadata.title) logger().info(" deleting library book '%s'" % metadata.title)
@ -2598,7 +2619,7 @@ class ITUNES(DriverBase):
Windows assumes pythoncom wrapper Windows assumes pythoncom wrapper
''' '''
if DEBUG: if DEBUG:
logger().info(" ITUNES._remove_from_device()") logger().info(" %s._remove_from_device()" % self.__class__.__name__)
if isosx: if isosx:
if DEBUG: if DEBUG:
logger().info(" deleting '%s' from iDevice" % cached_book['title']) logger().info(" deleting '%s' from iDevice" % cached_book['title'])
@ -2622,7 +2643,7 @@ class ITUNES(DriverBase):
iTunes does not delete books from storage when removing from database via automation iTunes does not delete books from storage when removing from database via automation
''' '''
if DEBUG: if DEBUG:
logger().info(" ITUNES._remove_from_iTunes():") logger().info(" %s._remove_from_iTunes():" % self.__class__.__name__)
if isosx: if isosx:
''' Manually remove the book from iTunes storage ''' ''' Manually remove the book from iTunes storage '''
@ -2664,7 +2685,8 @@ class ITUNES(DriverBase):
except: except:
# We get here if there was an error with .location().path # We get here if there was an error with .location().path
if DEBUG: 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 # Delete the book from the iTunes database
try: try:
@ -2739,7 +2761,7 @@ class ITUNES(DriverBase):
from lxml import etree from lxml import etree
if DEBUG: if DEBUG:
logger().info(" ITUNES._update_epub_metadata()") logger().info(" %s._update_epub_metadata()" % self.__class__.__name__)
# Fetch plugboard updates # Fetch plugboard updates
metadata_x = self._xform_metadata_via_plugboard(metadata, 'epub') metadata_x = self._xform_metadata_via_plugboard(metadata, 'epub')
@ -2807,7 +2829,7 @@ class ITUNES(DriverBase):
Trigger a sync, wait for completion Trigger a sync, wait for completion
''' '''
if DEBUG: if DEBUG:
logger().info(" ITUNES:_update_device():\n %s" % msg) logger().info(" %s:_update_device():\n %s" % (self.__class__.__name__, msg))
if isosx: if isosx:
self.iTunes.update() self.iTunes.update()
@ -2855,7 +2877,7 @@ class ITUNES(DriverBase):
''' '''
''' '''
if DEBUG: if DEBUG:
logger().info(" ITUNES._update_iTunes_metadata()") logger().info(" %s._update_iTunes_metadata()" % self.__class__.__name__)
STRIP_TAGS = re.compile(r'<[^<]*?/?>') STRIP_TAGS = re.compile(r'<[^<]*?/?>')
@ -2907,7 +2929,7 @@ class ITUNES(DriverBase):
# If title_sort applied in plugboard, that overrides using series/index as title_sort # 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 metadata_x.series and self.settings().extra_customization[self.USE_SERIES_AS_CATEGORY]:
if DEBUG: 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) logger().info(" using Series name '%s' as Genre" % metadata_x.series)
# Format the index as a sort key # Format the index as a sort key
@ -2949,7 +2971,6 @@ class ITUNES(DriverBase):
db_added.genre.set(tag) db_added.genre.set(tag)
break break
elif metadata_x.tags is not None: elif metadata_x.tags is not None:
if DEBUG: if DEBUG:
logger().info(" %susing Tag as Genre" % logger().info(" %susing Tag as Genre" %
@ -3089,7 +3110,7 @@ class ITUNES(DriverBase):
Ensure iDevice metadata is writable. Direct connect mode only Ensure iDevice metadata is writable. Direct connect mode only
''' '''
if DEBUG: 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) logger().warning(" %s" % self.UNSUPPORTED_DIRECT_CONNECT_MODE_MESSAGE)
attempts = 9 attempts = 9
@ -3113,7 +3134,7 @@ class ITUNES(DriverBase):
def _xform_metadata_via_plugboard(self, book, format): def _xform_metadata_via_plugboard(self, book, format):
''' Transform book metadata from plugboard templates ''' ''' Transform book metadata from plugboard templates '''
if DEBUG: if DEBUG:
logger().info(" ITUNES._xform_metadata_via_plugboard()") logger().info(" %s._xform_metadata_via_plugboard()" % self.__class__.__name__)
if self.plugboard_func: if self.plugboard_func:
pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards) pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards)
@ -3143,6 +3164,7 @@ class ITUNES(DriverBase):
newmi = book newmi = book
return newmi return newmi
class ITUNES_ASYNC(ITUNES): class ITUNES_ASYNC(ITUNES):
''' '''
This subclass allows the user to interact directly with iTunes via a menu option This subclass allows the user to interact directly with iTunes via a menu option
@ -3160,7 +3182,7 @@ class ITUNES_ASYNC(ITUNES):
def __init__(self, path): def __init__(self, path):
if DEBUG: if DEBUG:
logger().info("ITUNES_ASYNC:__init__()") logger().info("%s.__init__()" % self.__class__.__name__)
try: try:
import appscript import appscript
@ -3210,7 +3232,7 @@ class ITUNES_ASYNC(ITUNES):
""" """
if not oncard: if not oncard:
if DEBUG: if DEBUG:
logger().info("ITUNES_ASYNC:books()") logger().info("%s.books()" % self.__class__.__name__)
if self.settings().extra_customization[self.CACHE_COVERS]: if self.settings().extra_customization[self.CACHE_COVERS]:
logger().info(" Cover fetching/caching enabled") logger().info(" Cover fetching/caching enabled")
else: else:
@ -3324,7 +3346,7 @@ class ITUNES_ASYNC(ITUNES):
are pending GUI jobs that need to communicate with the device. are pending GUI jobs that need to communicate with the device.
''' '''
if DEBUG: if DEBUG:
logger().info("ITUNES_ASYNC:eject()") logger().info("%s.eject()" % self.__class__.__name__)
self.iTunes = None self.iTunes = None
self.connected = False self.connected = False
@ -3339,7 +3361,7 @@ class ITUNES_ASYNC(ITUNES):
particular device doesn't have any of these locations it should return -1. particular device doesn't have any of these locations it should return -1.
""" """
if DEBUG: if DEBUG:
logger().info("ITUNES_ASYNC:free_space()") logger().info("%s.free_space()" % self.__class__.__name__)
free_space = 0 free_space = 0
if isosx: if isosx:
s = os.statvfs(os.sep) s = os.statvfs(os.sep)
@ -3356,7 +3378,7 @@ class ITUNES_ASYNC(ITUNES):
@return: (device name, device version, software version on device, mime type) @return: (device name, device version, software version on device, mime type)
""" """
if DEBUG: 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')
@ -3382,7 +3404,8 @@ class ITUNES_ASYNC(ITUNES):
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE) raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)
if DEBUG: 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 # Confirm/create thumbs archive
if not os.path.exists(self.cache_dir): if not os.path.exists(self.cache_dir):
@ -3419,7 +3442,7 @@ class ITUNES_ASYNC(ITUNES):
''' '''
if DEBUG: if DEBUG:
logger().info("ITUNES_ASYNC.sync_booklists()") logger().info("%s.sync_booklists()" % self.__class__.__name__)
# Inform user of any problem books # Inform user of any problem books
if self.problem_titles: if self.problem_titles:
@ -3433,9 +3456,10 @@ class ITUNES_ASYNC(ITUNES):
''' '''
''' '''
if DEBUG: if DEBUG:
logger().info("ITUNES_ASYNC:unmount_device()") logger().info("%s.unmount_device()" % self.__class__.__name__)
self.connected = False self.connected = False
class BookList(list): class BookList(list):
''' '''
A list of books. Each Book object must have the fields: A list of books. Each Book object must have the fields:
@ -3488,6 +3512,7 @@ class BookList(list):
''' '''
return {} return {}
class Book(Metadata): class Book(Metadata):
''' '''
A simple class describing a book in the iTunes Books Library. A simple class describing a book in the iTunes Books Library.
@ -3495,9 +3520,9 @@ class Book(Metadata):
''' '''
def __init__(self, title, author): def __init__(self, title, author):
Metadata.__init__(self, title, authors=author.split(' & ')) Metadata.__init__(self, title, authors=author.split(' & '))
self.author = author
self.author_sort = author_to_author_sort(author) self.author_sort = author_to_author_sort(author)
@property @property
def title_sorter(self): def title_sorter(self):
return title_sort(self.title) return title_sort(self.title)

View File

@ -2357,6 +2357,8 @@ class KOBOTOUCH(KOBO):
update_query = 'UPDATE content SET Series=?, SeriesNumber==? where BookID is Null and ContentID = ?' update_query = 'UPDATE content SET Series=?, SeriesNumber==? where BookID is Null and ContentID = ?'
if book.series is None: if book.series is None:
update_values = (None, None, book.contentID, ) 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: else:
update_values = (book.series, "%g"%book.series_index, book.contentID, ) update_values = (book.series, "%g"%book.series_index, book.contentID, )

View File

@ -74,11 +74,12 @@ def remove_kindlegen_markup(parts):
part = "".join(srcpieces) part = "".join(srcpieces)
parts[i] = part 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( find_tag_with_AmznPageBreak_pattern = re.compile(
r'''(<[^>]*\sdata-AmznPageBreak=[^>]*>)''', re.IGNORECASE) r'''(<[^>]*\sdata-AmznPageBreak=[^>]*>)''', re.IGNORECASE)
within_tag_AmznPageBreak_position_pattern = re.compile( within_tag_AmznPageBreak_position_pattern = re.compile(
r'''\sdata-AmznPageBreak=['"][^'"]*['"]''') r'''\sdata-AmznPageBreak=['"]([^'"]*)['"]''')
for i in xrange(len(parts)): for i in xrange(len(parts)):
part = parts[i] part = parts[i]
@ -86,10 +87,8 @@ def remove_kindlegen_markup(parts):
for j in range(len(srcpieces)): for j in range(len(srcpieces)):
tag = srcpieces[j] tag = srcpieces[j]
if tag.startswith('<'): if tag.startswith('<'):
for m in within_tag_AmznPageBreak_position_pattern.finditer(tag): srcpieces[j] = within_tag_AmznPageBreak_position_pattern.sub(
replacement = '' lambda m:' style="page-break-after:%s"'%m.group(1), tag)
tag = within_tag_AmznPageBreak_position_pattern.sub(replacement, tag, 1)
srcpieces[j] = tag
part = "".join(srcpieces) part = "".join(srcpieces)
parts[i] = part parts[i] = part

View File

@ -44,6 +44,18 @@ def locate_beg_end_of_tag(ml, aid):
return plt, pgt return plt, pgt
return 0, 0 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): class Mobi8Reader(object):
def __init__(self, mobi6_reader, log): def __init__(self, mobi6_reader, log):
@ -275,13 +287,12 @@ class Mobi8Reader(object):
return '%s/%s'%(fi.type, fi.filename), idtext return '%s/%s'%(fi.type, fi.filename), idtext
def get_id_tag(self, pos): def get_id_tag(self, pos):
# find the correct tag by actually searching in the destination # Find the first tag with a named anchor (name or id attribute) before
# textblock at position # pos
fi = self.get_file_info(pos) fi = self.get_file_info(pos)
if fi.num is None and fi.start is None: if fi.num is None and fi.start is None:
raise ValueError('No file contains pos: %d'%pos) raise ValueError('No file contains pos: %d'%pos)
textblock = self.parts[fi.num] textblock = self.parts[fi.num]
id_map = []
npos = pos - fi.start npos = pos - fi.start
pgt = textblock.find(b'>', npos) pgt = textblock.find(b'>', npos)
plt = textblock.find(b'<', npos) plt = textblock.find(b'<', npos)
@ -290,28 +301,15 @@ class Mobi8Reader(object):
if plt == npos or pgt < plt: if plt == npos or pgt < plt:
npos = pgt + 1 npos = pgt + 1
textblock = textblock[0:npos] textblock = textblock[0:npos]
# find id links only inside of tags id_re = re.compile(br'''<[^>]+\sid\s*=\s*['"]([^'"]+)['"]''')
# inside any < > pair find all "id=' and return whatever is inside name_re = re.compile(br'''<\s*a\s*\sname\s*=\s*['"]([^'"]+)['"]''')
# the quotes for tag in reverse_tag_iter(textblock):
id_pattern = re.compile(br'''<[^>]*\sid\s*=\s*['"]([^'"]*)['"][^>]*>''', m = id_re.match(tag) or name_re.match(tag)
re.IGNORECASE) if m is not None:
for m in re.finditer(id_pattern, textblock): return m.group(1)
id_map.append((m.start(), m.group(1)))
if not id_map: # No tag found, link to start of file
# Found no id in the textblock, link must be to top of file
return b'' 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]
def create_guide(self): def create_guide(self):
guide = Guide() guide = Guide()

View File

@ -320,13 +320,11 @@ class OEBReader(object):
self.logger.warn(u'Spine item %r not found' % idref) self.logger.warn(u'Spine item %r not found' % idref)
continue continue
item = manifest.ids[idref] item = manifest.ids[idref]
if item.media_type.lower() in OEB_DOCS and hasattr(item.data, 'xpath'):
spine.add(item, elem.get('linear')) spine.add(item, elem.get('linear'))
for item in spine: else:
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.' self.oeb.log.warn('The item %s is not a XML document.'
' Removing it from spine.'%item.href) ' Removing it from spine.'%item.href)
spine.remove(item)
if len(spine) == 0: if len(spine) == 0:
raise OEBError("Spine is empty") raise OEBError("Spine is empty")
self._spine_add_extra() self._spine_add_extra()

View File

@ -27,10 +27,10 @@ def get_custom_size(opts):
custom_size = None custom_size = None
if opts.custom_size != None: if opts.custom_size != None:
width, sep, height = opts.custom_size.partition('x') width, sep, height = opts.custom_size.partition('x')
if height != '': if height:
try: try:
width = int(width) width = float(width)
height = int(height) height = float(height)
custom_size = (width, height) custom_size = (width, height)
except: except:
custom_size = None custom_size = None

View File

@ -12,6 +12,7 @@ from calibre.customize import CatalogPlugin
from calibre.library.catalogs import FIELDS from calibre.library.catalogs import FIELDS
from calibre.customize.conversion import DummyReporter from calibre.customize.conversion import DummyReporter
class CSV_XML(CatalogPlugin): class CSV_XML(CatalogPlugin):
'CSV/XML catalog generator' 'CSV/XML catalog generator'
@ -227,4 +228,3 @@ class CSV_XML(CatalogPlugin):
with open(path_to_output, 'w') as f: with open(path_to_output, 'w') as f:
f.write(etree.tostring(root, encoding='utf-8', f.write(etree.tostring(root, encoding='utf-8',
xml_declaration=True, pretty_print=True)) xml_declaration=True, pretty_print=True))

View File

@ -21,6 +21,7 @@ from calibre.utils.localization import get_lang
Option = namedtuple('Option', 'option, default, dest, action, help') Option = namedtuple('Option', 'option, default, dest, action, help')
class EPUB_MOBI(CatalogPlugin): class EPUB_MOBI(CatalogPlugin):
'ePub catalog generator' 'ePub catalog generator'
@ -386,6 +387,8 @@ class EPUB_MOBI(CatalogPlugin):
if opts.fmt == 'mobi': if opts.fmt == 'mobi':
recommendations.append(('no_inline_toc', True, recommendations.append(('no_inline_toc', True,
OptionRecommendation.HIGH)) OptionRecommendation.HIGH))
recommendations.append(('verbose', 2,
OptionRecommendation.HIGH))
# Use existing cover or generate new cover # Use existing cover or generate new cover
cpath = None cpath = None
@ -442,4 +445,3 @@ class EPUB_MOBI(CatalogPlugin):
# returns to gui2.actions.catalog:catalog_generated() # returns to gui2.actions.catalog:catalog_generated()
return catalog.error return catalog.error

View File

@ -25,6 +25,7 @@ from calibre.utils.icu import capitalize, collation_order, sort_key
from calibre.utils.magick.draw import thumbnail from calibre.utils.magick.draw import thumbnail
from calibre.utils.zipfile import ZipFile from calibre.utils.zipfile import ZipFile
class CatalogBuilder(object): class CatalogBuilder(object):
''' '''
Generates catalog source files from calibre database Generates catalog source files from calibre database
@ -98,7 +99,6 @@ class CatalogBuilder(object):
else: else:
return '&nbsp;' return '&nbsp;'
def __init__(self, db, _opts, plugin, def __init__(self, db, _opts, plugin,
report_progress=DummyReporter(), report_progress=DummyReporter(),
stylesheet="content/stylesheet.css", stylesheet="content/stylesheet.css",
@ -120,11 +120,13 @@ class CatalogBuilder(object):
_opts.output_profile and _opts.output_profile and
_opts.output_profile.startswith("kindle")) else False _opts.output_profile.startswith("kindle")) else False
self.all_series = set()
self.authors = None self.authors = None
self.bookmarked_books = None self.bookmarked_books = None
self.bookmarked_books_by_date_read = None self.bookmarked_books_by_date_read = None
self.books_by_author = None self.books_by_author = None
self.books_by_date_range = None self.books_by_date_range = None
self.books_by_description = []
self.books_by_month = None self.books_by_month = None
self.books_by_series = None self.books_by_series = None
self.books_by_title = None self.books_by_title = None
@ -139,6 +141,7 @@ class CatalogBuilder(object):
if self.opts.generate_genres else None if self.opts.generate_genres else None
self.html_filelist_1 = [] self.html_filelist_1 = []
self.html_filelist_2 = [] self.html_filelist_2 = []
self.individual_authors = None
self.merge_comments_rule = dict(zip(['field', 'position', 'hr'], self.merge_comments_rule = dict(zip(['field', 'position', 'hr'],
_opts.merge_comments_rule.split(':'))) _opts.merge_comments_rule.split(':')))
self.ncx_soup = None self.ncx_soup = None
@ -154,6 +157,7 @@ class CatalogBuilder(object):
self.total_steps = 6.0 self.total_steps = 6.0
self.use_series_prefix_in_titles_section = False self.use_series_prefix_in_titles_section = False
self.dump_custom_fields()
self.books_to_catalog = self.fetch_books_to_catalog() self.books_to_catalog = self.fetch_books_to_catalog()
self.compute_total_steps() self.compute_total_steps()
self.calculate_thumbnail_dimensions() self.calculate_thumbnail_dimensions()
@ -447,7 +451,7 @@ class CatalogBuilder(object):
hits.remove(amp) hits.remove(amp)
for hit in hits: for hit in hits:
name = hit[1:-1] 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(hit, unichr(htmlentitydefs.name2codepoint[name]))
s = s.replace(amp, "&") s = s.replace(amp, "&")
return s return s
@ -586,7 +590,7 @@ class CatalogBuilder(object):
# Literal comparison for Tags field # Literal comparison for Tags field
if rule['field'].lower() == 'tags': if rule['field'].lower() == 'tags':
if rule['pattern'].lower() in map(unicode.lower, record['tags']): if rule['pattern'].lower() in map(unicode.lower, record['tags']):
if self.opts.verbose: if self.DEBUG and self.opts.verbose:
self.opts.log.info(" %s '%s' by %s (%s: Tags includes '%s')" % 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'], record['authors'][0], rule['name'],
@ -616,7 +620,7 @@ class CatalogBuilder(object):
try: try:
if re.search(rule['pattern'], unicode(field_contents), if re.search(rule['pattern'], unicode(field_contents),
re.IGNORECASE) is not None: re.IGNORECASE) is not None:
if self.opts.verbose: if self.DEBUG:
_log_prefix_rule_match_info(rule, record, field_contents) _log_prefix_rule_match_info(rule, record, field_contents)
return rule['prefix'] return rule['prefix']
except: except:
@ -624,12 +628,24 @@ class CatalogBuilder(object):
self.opts.log.error("pattern failed to compile: %s" % rule['pattern']) self.opts.log.error("pattern failed to compile: %s" % rule['pattern'])
pass pass
elif field_contents is None and rule['pattern'] == 'None': 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) _log_prefix_rule_match_info(rule, record, field_contents)
return rule['prefix'] return rule['prefix']
return None 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): def establish_equivalencies(self, item_list, key=None):
""" Return icu equivalent sort letter. """ Return icu equivalent sort letter.
@ -716,7 +732,8 @@ class CatalogBuilder(object):
Outputs: Outputs:
books_by_author: database, sorted by author 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 error: author_sort mismatches
Return: Return:
@ -728,6 +745,13 @@ class CatalogBuilder(object):
books_by_author = list(self.books_to_catalog) books_by_author = list(self.books_to_catalog)
self.detect_author_sort_mismatches(books_by_author) 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: if self.opts.cross_reference_authors:
books_by_author = self.relist_multiple_authors(books_by_author) books_by_author = self.relist_multiple_authors(books_by_author)
@ -737,6 +761,10 @@ class CatalogBuilder(object):
asl = [i['author_sort'] for i in books_by_author] asl = [i['author_sort'] for i in books_by_author]
las = max(asl, key=len) 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, books_by_author = sorted(books_by_author,
key=lambda x: sort_key(self._kf_books_by_author_sorter_author_sort(x, len(las)))) key=lambda x: sort_key(self._kf_books_by_author_sorter_author_sort(x, len(las))))
@ -758,6 +786,7 @@ class CatalogBuilder(object):
current_author = authors[0] current_author = authors[0]
multiple_authors = False multiple_authors = False
unique_authors = [] unique_authors = []
individual_authors = set()
for (i, author) in enumerate(authors): for (i, author) in enumerate(authors):
if author != current_author: if author != current_author:
# Note that current_author and author are tuples: (friendly, sort) # Note that current_author and author are tuples: (friendly, sort)
@ -780,14 +809,23 @@ class CatalogBuilder(object):
unique_authors.append((current_author[0], icu_title(current_author[1]), unique_authors.append((current_author[0], icu_title(current_author[1]),
books_by_current_author)) 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(' &amp; ', ' & ').split(' & '):
individual_authors.add(ia)
self.individual_authors = list(individual_authors)
if self.DEBUG and self.opts.verbose: if self.DEBUG and self.opts.verbose:
self.opts.log.info("\nfetch_books_by_author(): %d unique authors" % len(unique_authors)) self.opts.log.info("\nfetch_books_by_author(): %d unique authors" % len(unique_authors))
for author in unique_authors: for author in unique_authors:
self.opts.log.info((u" %-50s %-25s %2d" % (author[0][0:45], author[1][0:20], self.opts.log.info((u" %-50s %-25s %2d" % (author[0][0:45], author[1][0:20],
author[2])).encode('utf-8')) 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 return True
def fetch_books_by_title(self): def fetch_books_by_title(self):
@ -869,6 +907,7 @@ class CatalogBuilder(object):
this_title['title'] = self.convert_html_entities(record['title']) this_title['title'] = self.convert_html_entities(record['title'])
if record['series']: if record['series']:
this_title['series'] = record['series'] this_title['series'] = record['series']
self.all_series.add(this_title['series'])
this_title['series_index'] = record['series_index'] this_title['series_index'] = record['series_index']
else: else:
this_title['series'] = None this_title['series'] = None
@ -1000,7 +1039,7 @@ class CatalogBuilder(object):
data = self.plugin.search_sort_db(self.db, self.opts) data = self.plugin.search_sort_db(self.db, self.opts)
data = self.process_exclusions(data) 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:") self.opts.log.info(" Added prefixes:")
# Populate this_title{} from data[{},{}] # Populate this_title{} from data[{},{}]
@ -1042,6 +1081,7 @@ class CatalogBuilder(object):
def initialize(self, save_template): def initialize(self, save_template):
self._save_template = save_template self._save_template = save_template
self.SUPPORTS_SUB_DIRS = True self.SUPPORTS_SUB_DIRS = True
def save_template(self): def save_template(self):
return self._save_template return self._save_template
@ -2070,7 +2110,6 @@ class CatalogBuilder(object):
len(genre[key]), len(genre[key]),
'titles' if len(genre[key]) > 1 else 'title')) 'titles' if len(genre[key]) > 1 else 'title'))
# Write the results # Write the results
# genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...] # genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...]
master_genre_list = [] master_genre_list = []
@ -2107,7 +2146,8 @@ class CatalogBuilder(object):
outfile) outfile)
tag_file = "content/Genre_%s.html" % genre tag_file = "content/Genre_%s.html" % genre
master_genre_list.append({'tag':genre, master_genre_list.append({
'tag': genre,
'file': tag_file, 'file': tag_file,
'authors': unique_authors, 'authors': unique_authors,
'books': genre_tag_set[genre], 'books': genre_tag_set[genre],
@ -2935,12 +2975,10 @@ class CatalogBuilder(object):
contentTag = Tag(soup, 'content') contentTag = Tag(soup, 'content')
contentTag['src'] = "content/ByDateAdded.html" contentTag['src'] = "content/ByDateAdded.html"
navPointTag.insert(1, contentTag) navPointTag.insert(1, contentTag)
else: elif self.opts.generate_descriptions:
# Descriptions only # 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 = 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) navPointTag.insert(1, contentTag)
if self.generate_for_kindle_mobi: if self.generate_for_kindle_mobi:
@ -2970,9 +3008,6 @@ class CatalogBuilder(object):
self.update_progress_full_step(_("NCX for Descriptions")) 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 --- # --- Construct the 'Descriptions' section ---
ncx_soup = self.ncx_soup ncx_soup = self.ncx_soup
if self.generate_for_kindle_mobi: if self.generate_for_kindle_mobi:
@ -2990,19 +3025,22 @@ class CatalogBuilder(object):
self.play_order += 1 self.play_order += 1
navLabelTag = Tag(ncx_soup, 'navLabel') navLabelTag = Tag(ncx_soup, 'navLabel')
textTag = Tag(ncx_soup, 'text') 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) navLabelTag.insert(0, textTag)
nptc = 0 nptc = 0
navPointTag.insert(nptc, navLabelTag) navPointTag.insert(nptc, navLabelTag)
nptc += 1 nptc += 1
contentTag = Tag(ncx_soup, "content") contentTag = Tag(ncx_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(nptc, contentTag) navPointTag.insert(nptc, contentTag)
nptc += 1 nptc += 1
# Loop over the titles # Loop over the titles
for book in sort_descriptions_by: for book in self.books_by_description:
navPointVolumeTag = Tag(ncx_soup, 'navPoint') navPointVolumeTag = Tag(ncx_soup, 'navPoint')
if self.generate_for_kindle_mobi: if self.generate_for_kindle_mobi:
navPointVolumeTag['class'] = "article" navPointVolumeTag['class'] = "article"
@ -3119,7 +3157,10 @@ class CatalogBuilder(object):
self.play_order += 1 self.play_order += 1
navLabelTag = Tag(ncx_soup, 'navLabel') navLabelTag = Tag(ncx_soup, 'navLabel')
textTag = Tag(ncx_soup, 'text') 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) navLabelTag.insert(0, textTag)
nptc = 0 nptc = 0
navPointTag.insert(nptc, navLabelTag) navPointTag.insert(nptc, navLabelTag)
@ -3247,7 +3288,10 @@ class CatalogBuilder(object):
self.play_order += 1 self.play_order += 1
navLabelTag = Tag(ncx_soup, 'navLabel') navLabelTag = Tag(ncx_soup, 'navLabel')
textTag = Tag(ncx_soup, 'text') 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) navLabelTag.insert(0, textTag)
nptc = 0 nptc = 0
navPointTag.insert(nptc, navLabelTag) navPointTag.insert(nptc, navLabelTag)
@ -3377,7 +3421,10 @@ class CatalogBuilder(object):
self.play_order += 1 self.play_order += 1
navLabelTag = Tag(ncx_soup, 'navLabel') navLabelTag = Tag(ncx_soup, 'navLabel')
textTag = Tag(ncx_soup, 'text') 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) navLabelTag.insert(0, textTag)
nptc = 0 nptc = 0
navPointTag.insert(nptc, navLabelTag) navPointTag.insert(nptc, navLabelTag)
@ -3430,7 +3477,7 @@ class CatalogBuilder(object):
fmt_string = _(u"Authors beginning with %s") fmt_string = _(u"Authors beginning with %s")
else: else:
fmt_string = _(u"Authors beginning with '%s'") 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) navLabelTag.insert(0, textTag)
navPointByLetterTag.insert(0, navLabelTag) navPointByLetterTag.insert(0, navLabelTag)
contentTag = Tag(ncx_soup, 'content') contentTag = Tag(ncx_soup, 'content')
@ -3808,7 +3855,7 @@ class CatalogBuilder(object):
self.update_progress_full_step(_("NCX for Genres")) self.update_progress_full_step(_("NCX for Genres"))
if not len(self.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") " No Genre section added to Catalog")
return return
@ -3830,8 +3877,10 @@ class CatalogBuilder(object):
self.play_order += 1 self.play_order += 1
navLabelTag = Tag(ncx_soup, 'navLabel') navLabelTag = Tag(ncx_soup, 'navLabel')
textTag = Tag(ncx_soup, 'text') textTag = Tag(ncx_soup, 'text')
# textTag.insert(0, NavigableString('%s (%d)' % (section_title, len(genre_list)))) section_header = '%s [%d]' % (tocTitle, len(self.genres))
textTag.insert(0, NavigableString('%s' % tocTitle)) if self.generate_for_kindle_mobi:
section_header = tocTitle
textTag.insert(0, NavigableString(section_header))
navLabelTag.insert(0, textTag) navLabelTag.insert(0, textTag)
nptc = 0 nptc = 0
navPointTag.insert(nptc, navLabelTag) navPointTag.insert(nptc, navLabelTag)
@ -3993,7 +4042,6 @@ class CatalogBuilder(object):
mtc += 1 mtc += 1
# Write the thumbnail images, descriptions to the manifest # Write the thumbnail images, descriptions to the manifest
sort_descriptions_by = []
if self.opts.generate_descriptions: if self.opts.generate_descriptions:
for thumb in self.thumbs: for thumb in self.thumbs:
itemTag = Tag(soup, "item") itemTag = Tag(soup, "item")
@ -4004,9 +4052,6 @@ class CatalogBuilder(object):
manifest.insert(mtc, itemTag) manifest.insert(mtc, itemTag)
mtc += 1 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 # Add html_files to manifest and spine
for file in self.html_filelist_1: for file in self.html_filelist_1:
@ -4060,7 +4105,7 @@ class CatalogBuilder(object):
spine.insert(stc, itemrefTag) spine.insert(stc, itemrefTag)
stc += 1 stc += 1
for book in sort_descriptions_by: for book in self.books_by_description:
# manifest # manifest
itemTag = Tag(soup, "item") itemTag = Tag(soup, "item")
itemTag['href'] = "content/book_%d.html" % int(book['id']) itemTag['href'] = "content/book_%d.html" % int(book['id'])
@ -4286,7 +4331,8 @@ class CatalogBuilder(object):
f.write(thumb_data) f.write(thumb_data)
# Save thumb to archive # 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, # If we failed to open the zip file for reading,
# we dont know if it contained the thumb or not # we dont know if it contained the thumb or not
zf = _open_archive('a') zf = _open_archive('a')
@ -4363,7 +4409,6 @@ class CatalogBuilder(object):
# Clear the book's cover property # Clear the book's cover property
title['cover'] = None title['cover'] = None
# Write thumb_width to the file, validating cache contents # Write thumb_width to the file, validating cache contents
# Allows detection of aborted catalog builds # Allows detection of aborted catalog builds
with ZipFile(self.thumbs_path, mode='a') as zfw: with ZipFile(self.thumbs_path, mode='a') as zfw:
@ -4853,5 +4898,3 @@ class CatalogBuilder(object):
outfile = open("%s/%s.ncx" % (self.catalog_path, self.opts.basename), 'w') outfile = open("%s/%s.ncx" % (self.catalog_path, self.opts.basename), 'w')
outfile.write(self.ncx_soup.prettify()) outfile.write(self.ncx_soup.prettify())