From 8292b1d71dbea20739dae39bf0321f35424cff04 Mon Sep 17 00:00:00 2001
From: GRiker
Date: Mon, 3 Dec 2012 16:49:09 -0700
Subject: [PATCH] Revisions to catalog building code, cleaning up diagnostics,
tweaks to TOC section titles. Revisions to Apple driver in anticipation of
releasing optional iDevice driver.
---
src/calibre/devices/apple/driver.py | 530 ++++++-------
src/calibre/library/catalogs/csv_xml.py | 48 +-
.../library/catalogs/epub_mobi_builder.py | 722 +++++++++---------
3 files changed, 681 insertions(+), 619 deletions(-)
diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py
index d8c6d03f55..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:
@@ -158,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)
@@ -191,11 +195,11 @@ class ITUNES(DriverBase):
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,0,0)
+ version = (1, 1, 1)
DISPLAY_DISABLE_DIALOG = "display_disable_apple_driver_dialog"
@@ -290,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
@@ -325,21 +329,21 @@ class ITUNES(DriverBase):
# 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)
@@ -363,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...'))
@@ -383,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):
"""
@@ -409,7 +413,7 @@ class ITUNES(DriverBase):
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 = {}
@@ -418,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:
@@ -439,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:
@@ -459,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:
@@ -479,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)
@@ -500,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 +562,7 @@ 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 iDevice, attempt #%d" % (10 - attempts))
else:
@@ -570,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):
@@ -634,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')
@@ -666,7 +672,7 @@ class ITUNES(DriverBase):
('place', None)
(None, None)
'''
- return (None,None)
+ return (None, None)
@classmethod
def config_widget(cls):
@@ -720,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):
'''
@@ -799,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):
"""
@@ -809,7 +818,7 @@ class ITUNES(DriverBase):
if DEBUG:
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):
'''
@@ -848,28 +857,32 @@ class ITUNES(DriverBase):
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)
if DEBUG:
- 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]
+ 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
+ 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().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):
if DEBUG:
@@ -879,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:
@@ -887,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)
@@ -916,37 +929,41 @@ class ITUNES(DriverBase):
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]
@@ -965,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
@@ -1051,7 +1070,7 @@ class ITUNES(DriverBase):
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):
@@ -1085,7 +1104,7 @@ class ITUNES(DriverBase):
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),
@@ -1110,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
@@ -1124,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),
@@ -1160,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()
@@ -1174,12 +1193,12 @@ 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
'''
@@ -1201,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
@@ -1282,13 +1301,13 @@ 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
'''
@@ -1349,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})
@@ -1372,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:
@@ -1418,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()
@@ -1468,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:
@@ -1506,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()
@@ -1523,7 +1542,7 @@ 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:
@@ -1604,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
@@ -1635,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
@@ -1646,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:],
@@ -1687,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']))
@@ -1701,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'],
@@ -1733,7 +1753,7 @@ class ITUNES(DriverBase):
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:
@@ -1742,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:
@@ -1760,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):
@@ -1779,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:],
@@ -1796,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']))
@@ -1809,14 +1830,14 @@ class ITUNES(DriverBase):
if DEBUG:
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))
@@ -1826,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']:
@@ -1837,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']:
@@ -1851,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]
@@ -1910,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:
@@ -1928,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']:
@@ -1939,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']:
@@ -1953,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]
@@ -2027,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:
@@ -2051,7 +2071,6 @@ class ITUNES(DriverBase):
return thumb_data
-
elif iswindows:
if not book.Artwork.Count:
if DEBUG:
@@ -2067,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()
@@ -2083,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()
@@ -2097,7 +2116,7 @@ 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:
@@ -2133,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()
@@ -2167,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()
@@ -2247,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:
@@ -2304,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):
@@ -2356,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]
@@ -2369,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
@@ -2396,7 +2413,7 @@ class ITUNES(DriverBase):
running_apps = appscript.app('System Events')
if not 'iTunes' in running_apps.processes.name():
if DEBUG:
- logger().info( "%s:_launch_iTunes(): Launching iTunes" % self.__class__.__name__)
+ logger().info("%s:_launch_iTunes(): Launching iTunes" % self.__class__.__name__)
try:
self.iTunes = iTunes = appscript.app('iTunes', hide=True)
except:
@@ -2422,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"
@@ -2450,7 +2467,7 @@ class ITUNES(DriverBase):
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.__class__.__name__, 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)
@@ -2519,41 +2536,47 @@ 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(" %s._purge_orphans()" % self.__class__.__name__)
- #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):
'''
@@ -2565,17 +2588,11 @@ class ITUNES(DriverBase):
# 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:
@@ -2585,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:
@@ -2619,7 +2636,7 @@ 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):
'''
@@ -2668,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:
@@ -2749,10 +2767,10 @@ class ITUNES(DriverBase):
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:
@@ -2769,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:
@@ -2789,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
@@ -2873,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)
@@ -2884,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)
@@ -2892,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
@@ -2917,7 +2935,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
@@ -2953,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" %
@@ -2972,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
@@ -2985,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
@@ -2993,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"
@@ -3019,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
@@ -3147,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
@@ -3155,14 +3173,14 @@ 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("%s.__init__()" % self.__class__.__name__)
@@ -3228,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
@@ -3253,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
@@ -3273,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,
@@ -3296,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:
@@ -3316,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:
@@ -3352,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):
"""
@@ -3362,7 +3380,7 @@ class ITUNES_ASYNC(ITUNES):
if DEBUG:
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):
@@ -3398,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:
@@ -3406,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)
@@ -3441,6 +3459,7 @@ class ITUNES_ASYNC(ITUNES):
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:
@@ -3493,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/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_builder.py b/src/calibre/library/catalogs/epub_mobi_builder.py
index fa38b2ba83..24049e83f0 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 = None
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,12 @@ 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
+ 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 +760,19 @@ class CatalogBuilder(object):
asl = [i['author_sort'] for i in books_by_author]
las = max(asl, key=len)
+ 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 +784,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 +795,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 +807,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 +905,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 +1006,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 +1037,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 +1079,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 +1107,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 +1147,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 +1180,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 +1205,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 +1228,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 +1262,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 +1324,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 +1349,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 +1373,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 +1437,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 +1465,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 +1501,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 +1560,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 +1571,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 +1579,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 +1588,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 +1639,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 +1657,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 +1705,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 +1721,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 +1750,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 +1764,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 +1787,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 +1796,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 +1811,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 +1839,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 +1886,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 +1903,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 +1917,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 +1930,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 +1943,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 +1966,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 +1986,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 +1994,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 +2008,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 +2108,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 +2118,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 +2139,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 +2180,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 +2191,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 +2215,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 +2266,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 +2283,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 +2335,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 +2343,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 +2388,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 +2415,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 +2424,10 @@ class CatalogBuilder(object):
# Insert the