mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
...
This commit is contained in:
commit
ac174d8175
@ -20,6 +20,7 @@ from calibre.utils.config import config_dir, dynamic, prefs
|
|||||||
from calibre.utils.date import now, parse_date
|
from calibre.utils.date import now, parse_date
|
||||||
from calibre.utils.zipfile import ZipFile
|
from calibre.utils.zipfile import ZipFile
|
||||||
|
|
||||||
|
|
||||||
def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
|
def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
|
||||||
|
|
||||||
if not hasattr(dt, 'timetuple'):
|
if not hasattr(dt, 'timetuple'):
|
||||||
@ -38,6 +39,7 @@ def logger():
|
|||||||
_log = ThreadSafeLog()
|
_log = ThreadSafeLog()
|
||||||
return _log
|
return _log
|
||||||
|
|
||||||
|
|
||||||
class AppleOpenFeedback(OpenFeedback):
|
class AppleOpenFeedback(OpenFeedback):
|
||||||
|
|
||||||
def __init__(self, plugin):
|
def __init__(self, plugin):
|
||||||
@ -102,6 +104,7 @@ class AppleOpenFeedback(OpenFeedback):
|
|||||||
|
|
||||||
return Dialog(parent, self)
|
return Dialog(parent, self)
|
||||||
|
|
||||||
|
|
||||||
class DriverBase(DeviceConfig, DevicePlugin):
|
class DriverBase(DeviceConfig, DevicePlugin):
|
||||||
# Needed for config_widget to work
|
# Needed for config_widget to work
|
||||||
FORMATS = ['epub', 'pdf']
|
FORMATS = ['epub', 'pdf']
|
||||||
@ -133,11 +136,11 @@ class DriverBase(DeviceConfig, DevicePlugin):
|
|||||||
False,
|
False,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _config_base_name(cls):
|
def _config_base_name(cls):
|
||||||
return 'iTunes'
|
return 'iTunes'
|
||||||
|
|
||||||
|
|
||||||
class ITUNES(DriverBase):
|
class ITUNES(DriverBase):
|
||||||
'''
|
'''
|
||||||
Calling sequences:
|
Calling sequences:
|
||||||
@ -148,6 +151,8 @@ class ITUNES(DriverBase):
|
|||||||
open()
|
open()
|
||||||
card_prefix()
|
card_prefix()
|
||||||
can_handle()
|
can_handle()
|
||||||
|
_launch_iTunes()
|
||||||
|
_discover_manual_sync_mode()
|
||||||
set_progress_reporter()
|
set_progress_reporter()
|
||||||
get_device_information()
|
get_device_information()
|
||||||
card_prefix()
|
card_prefix()
|
||||||
@ -156,6 +161,7 @@ class ITUNES(DriverBase):
|
|||||||
can_handle()
|
can_handle()
|
||||||
set_progress_reporter()
|
set_progress_reporter()
|
||||||
books() (once for each storage point)
|
books() (once for each storage point)
|
||||||
|
(create self.cached_books)
|
||||||
settings()
|
settings()
|
||||||
settings()
|
settings()
|
||||||
can_handle() (~1x per second OSX while idle)
|
can_handle() (~1x per second OSX while idle)
|
||||||
@ -186,14 +192,14 @@ class ITUNES(DriverBase):
|
|||||||
free_space()
|
free_space()
|
||||||
'''
|
'''
|
||||||
|
|
||||||
name = 'Apple device interface'
|
name = 'Apple iTunes interface'
|
||||||
gui_name = _('Apple device')
|
gui_name = _('Apple device')
|
||||||
icon = I('devices/ipad.png')
|
icon = I('devices/ipad.png')
|
||||||
description = _('Communicate with iTunes/iBooks.')
|
description = _('Communicate with iTunes/iBooks.')
|
||||||
supported_platforms = ['osx', 'windows']
|
supported_platforms = ['osx', 'windows']
|
||||||
author = 'GRiker'
|
author = 'GRiker'
|
||||||
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
#: The version of this plugin as a 3-tuple (major, minor, revision)
|
||||||
version = (1,1,0)
|
version = (1, 1, 1)
|
||||||
|
|
||||||
DISPLAY_DISABLE_DIALOG = "display_disable_apple_driver_dialog"
|
DISPLAY_DISABLE_DIALOG = "display_disable_apple_driver_dialog"
|
||||||
|
|
||||||
@ -203,7 +209,7 @@ class ITUNES(DriverBase):
|
|||||||
USE_ITUNES_STORAGE = 2
|
USE_ITUNES_STORAGE = 2
|
||||||
|
|
||||||
OPEN_FEEDBACK_MESSAGE = _(
|
OPEN_FEEDBACK_MESSAGE = _(
|
||||||
'Apple device detected, launching iTunes, please wait ...')
|
'Apple iDevice detected, launching iTunes, please wait ...')
|
||||||
BACKLOADING_ERROR_MESSAGE = _(
|
BACKLOADING_ERROR_MESSAGE = _(
|
||||||
"Cannot copy books directly from iDevice. "
|
"Cannot copy books directly from iDevice. "
|
||||||
"Drag from iTunes Library to desktop, then add to calibre's Library window.")
|
"Drag from iTunes Library to desktop, then add to calibre's Library window.")
|
||||||
@ -218,22 +224,9 @@ class ITUNES(DriverBase):
|
|||||||
'for more information.</p>'
|
'for more information.</p>'
|
||||||
'<p></p>')
|
'<p></p>')
|
||||||
|
|
||||||
# Product IDs:
|
VENDOR_ID = []
|
||||||
# 0x1291 iPod Touch
|
PRODUCT_ID = []
|
||||||
# 0x1293 iPod Touch 2G
|
BCD = []
|
||||||
# 0x1299 iPod Touch 3G
|
|
||||||
# 0x1292 iPhone 3G
|
|
||||||
# 0x1294 iPhone 3GS
|
|
||||||
# 0x1297 iPhone 4
|
|
||||||
# 0x129a iPad
|
|
||||||
# 0x129f iPad2 (WiFi)
|
|
||||||
# 0x12a0 iPhone 4S (GSM)
|
|
||||||
# 0x12a2 iPad2 (GSM)
|
|
||||||
# 0x12a3 iPad2 (CDMA)
|
|
||||||
# 0x12a6 iPad3 (GSM)
|
|
||||||
VENDOR_ID = [0x05ac]
|
|
||||||
PRODUCT_ID = [0x1292,0x1293,0x1294,0x1297,0x1299,0x129a,0x129f,0x12a2,0x12a3,0x12a6]
|
|
||||||
BCD = [0x01]
|
|
||||||
|
|
||||||
# Plugboard ID
|
# Plugboard ID
|
||||||
DEVICE_PLUGBOARD_NAME = 'APPLE'
|
DEVICE_PLUGBOARD_NAME = 'APPLE'
|
||||||
@ -329,7 +322,7 @@ class ITUNES(DriverBase):
|
|||||||
L{books}(oncard='cardb')).
|
L{books}(oncard='cardb')).
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.add_books_to_metadata()")
|
logger().info("%s.add_books_to_metadata()" % self.__class__.__name__)
|
||||||
|
|
||||||
task_count = float(len(self.update_list))
|
task_count = float(len(self.update_list))
|
||||||
|
|
||||||
@ -414,13 +407,13 @@ class ITUNES(DriverBase):
|
|||||||
"""
|
"""
|
||||||
if not oncard:
|
if not oncard:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES:books():")
|
logger().info("%s.books():" % self.__class__.__name__)
|
||||||
if self.settings().extra_customization[self.CACHE_COVERS]:
|
if self.settings().extra_customization[self.CACHE_COVERS]:
|
||||||
logger().info(" Cover fetching/caching enabled")
|
logger().info(" Cover fetching/caching enabled")
|
||||||
else:
|
else:
|
||||||
logger().info(" Cover fetching/caching disabled")
|
logger().info(" Cover fetching/caching disabled")
|
||||||
|
|
||||||
# Fetch a list of books from iPod device connected to iTunes
|
# Fetch a list of books from iDevice connected to iTunes
|
||||||
if 'iPod' in self.sources:
|
if 'iPod' in self.sources:
|
||||||
booklist = BookList(logger())
|
booklist = BookList(logger())
|
||||||
cached_books = {}
|
cached_books = {}
|
||||||
@ -451,7 +444,8 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
cached_books[this_book.path] = {
|
cached_books[this_book.path] = {
|
||||||
'title': book.name(),
|
'title': book.name(),
|
||||||
'author':book.artist().split(' & '),
|
'author': book.artist(),
|
||||||
|
'authors': book.artist().split(' & '),
|
||||||
'lib_book': library_books[this_book.path] if this_book.path in library_books else None,
|
'lib_book': library_books[this_book.path] if this_book.path in library_books else None,
|
||||||
'dev_book': book,
|
'dev_book': book,
|
||||||
'uuid': book.composer()
|
'uuid': book.composer()
|
||||||
@ -491,7 +485,8 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
cached_books[this_book.path] = {
|
cached_books[this_book.path] = {
|
||||||
'title': book.Name,
|
'title': book.Name,
|
||||||
'author':book.Artist.split(' & '),
|
'author': book.Artist,
|
||||||
|
'authors': book.Artist.split(' & '),
|
||||||
'lib_book': library_books[this_book.path] if this_book.path in library_books else None,
|
'lib_book': library_books[this_book.path] if this_book.path in library_books else None,
|
||||||
'uuid': book.Composer,
|
'uuid': book.Composer,
|
||||||
'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub'
|
'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub'
|
||||||
@ -556,7 +551,7 @@ class ITUNES(DriverBase):
|
|||||||
# We need to know if iTunes sees the iPad
|
# We need to know if iTunes sees the iPad
|
||||||
# It may have been ejected
|
# It may have been ejected
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.can_handle()")
|
logger().info("%s.can_handle()" % self.__class__.__name__)
|
||||||
|
|
||||||
self._launch_iTunes()
|
self._launch_iTunes()
|
||||||
self.sources = self._get_sources()
|
self.sources = self._get_sources()
|
||||||
@ -567,12 +562,12 @@ class ITUNES(DriverBase):
|
|||||||
self.sources = self._get_sources()
|
self.sources = self._get_sources()
|
||||||
if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''):
|
if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''):
|
||||||
attempts -= 1
|
attempts -= 1
|
||||||
time.sleep(0.5)
|
time.sleep(1.0)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().warning(" waiting for connected iPad, attempt #%d" % (10 - attempts))
|
logger().warning(" waiting for connected iDevice, attempt #%d" % (10 - attempts))
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(' found connected iPad')
|
logger().info(' found connected iDevice')
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# iTunes running, but not connected iPad
|
# iTunes running, but not connected iPad
|
||||||
@ -613,26 +608,26 @@ class ITUNES(DriverBase):
|
|||||||
sys.stdout.write('.')
|
sys.stdout.write('.')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info('ITUNES.can_handle_windows:\n confirming connected iPad')
|
logger().info("%s.can_handle_windows:\n confirming connected iPad" % self.__class__.__name__)
|
||||||
self.ejected = False
|
self.ejected = False
|
||||||
self._discover_manual_sync_mode()
|
self._discover_manual_sync_mode()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.can_handle_windows():\n device ejected")
|
logger().info("%s.can_handle_windows():\n device ejected" % self.__class__.__name__)
|
||||||
self.ejected = True
|
self.ejected = True
|
||||||
return False
|
return False
|
||||||
except:
|
except:
|
||||||
# iTunes connection failed, probably not running anymore
|
# iTunes connection failed, probably not running anymore
|
||||||
|
|
||||||
logger().error("ITUNES.can_handle_windows():\n lost connection to iTunes")
|
logger().error("%s.can_handle_windows():\n lost connection to iTunes" % self.__class__.__name__)
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
pythoncom.CoUninitialize()
|
pythoncom.CoUninitialize()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES:can_handle_windows():\n Launching iTunes")
|
logger().info("%s.can_handle_windows():\n Launching iTunes" % self.__class__.__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pythoncom.CoInitialize()
|
pythoncom.CoInitialize()
|
||||||
@ -645,9 +640,9 @@ class ITUNES(DriverBase):
|
|||||||
self.sources = self._get_sources()
|
self.sources = self._get_sources()
|
||||||
if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''):
|
if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''):
|
||||||
attempts -= 1
|
attempts -= 1
|
||||||
time.sleep(0.5)
|
time.sleep(1.0)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().warning(" waiting for connected iPad, attempt #%d" % (10 - attempts))
|
logger().warning(" waiting for connected iDevice, attempt #%d" % (10 - attempts))
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(' found connected iPad in iTunes')
|
logger().info(' found connected iPad in iTunes')
|
||||||
@ -702,7 +697,7 @@ class ITUNES(DriverBase):
|
|||||||
self.problem_msg = _("Some books not found in iTunes database.\n"
|
self.problem_msg = _("Some books not found in iTunes database.\n"
|
||||||
"Delete using the iBooks app.\n"
|
"Delete using the iBooks app.\n"
|
||||||
"Click 'Show Details' for a list.")
|
"Click 'Show Details' for a list.")
|
||||||
logger().info("ITUNES:delete_books()")
|
logger().info("%s.delete_books()" % self.__class__.__name__)
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if self.cached_books[path]['lib_book']:
|
if self.cached_books[path]['lib_book']:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -731,8 +726,11 @@ class ITUNES(DriverBase):
|
|||||||
else:
|
else:
|
||||||
if self.manual_sync_mode:
|
if self.manual_sync_mode:
|
||||||
metadata = MetaInformation(self.cached_books[path]['title'],
|
metadata = MetaInformation(self.cached_books[path]['title'],
|
||||||
[self.cached_books[path]['author']])
|
self.cached_books[path]['authors'])
|
||||||
|
metadata.author = self.cached_books[path]['author']
|
||||||
metadata.uuid = self.cached_books[path]['uuid']
|
metadata.uuid = self.cached_books[path]['uuid']
|
||||||
|
if not metadata.uuid:
|
||||||
|
metadata.uuid = "unknown"
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
self._remove_existing_copy(self.cached_books[path], metadata)
|
self._remove_existing_copy(self.cached_books[path], metadata)
|
||||||
@ -754,7 +752,7 @@ class ITUNES(DriverBase):
|
|||||||
are pending GUI jobs that need to communicate with the device.
|
are pending GUI jobs that need to communicate with the device.
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES:eject(): ejecting '%s'" % self.sources['iPod'])
|
logger().info("%s:eject(): ejecting '%s'" % (self.__class__.__name__, self.sources['iPod']))
|
||||||
if isosx:
|
if isosx:
|
||||||
self.iTunes.eject(self.sources['iPod'])
|
self.iTunes.eject(self.sources['iPod'])
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
@ -785,7 +783,7 @@ class ITUNES(DriverBase):
|
|||||||
In Windows, a sync-in-progress blocks this call until sync is complete
|
In Windows, a sync-in-progress blocks this call until sync is complete
|
||||||
"""
|
"""
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES:free_space()")
|
logger().info("%s.free_space()" % self.__class__.__name__)
|
||||||
|
|
||||||
free_space = 0
|
free_space = 0
|
||||||
if isosx:
|
if isosx:
|
||||||
@ -818,7 +816,7 @@ class ITUNES(DriverBase):
|
|||||||
@return: (device name, device version, software version on device, mime type)
|
@return: (device name, device version, software version on device, mime type)
|
||||||
"""
|
"""
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES:get_device_information()")
|
logger().info("%s.get_device_information()" % self.__class__.__name__)
|
||||||
|
|
||||||
return (self.sources['iPod'], 'hw v1.0', 'sw v1.0', 'mime type normally goes here')
|
return (self.sources['iPod'], 'hw v1.0', 'sw v1.0', 'mime type normally goes here')
|
||||||
|
|
||||||
@ -828,7 +826,7 @@ class ITUNES(DriverBase):
|
|||||||
@param outfile: file object like C{sys.stdout} or the result of an C{open} call
|
@param outfile: file object like C{sys.stdout} or the result of an C{open} call
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.get_file(): exporting '%s'" % path)
|
logger().info("%s.get_file(): exporting '%s'" % (self.__class__.__name__, path))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
outfile.write(open(self.cached_books[path]['lib_book'].location().path).read())
|
outfile.write(open(self.cached_books[path]['lib_book'].location().path).read())
|
||||||
@ -859,7 +857,19 @@ class ITUNES(DriverBase):
|
|||||||
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)
|
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.open(connected_device: %s)" % repr(connected_device))
|
vendor_id = "0x%x" % connected_device[0]
|
||||||
|
product_id = "0x%x" % connected_device[1]
|
||||||
|
bcd = "0x%x" % connected_device[2]
|
||||||
|
mfg = connected_device[3]
|
||||||
|
model = connected_device[4]
|
||||||
|
logger().info("%s.open(MFG: %s, VENDOR_ID: %s, MODEL: %s, BCD: %s, PRODUCT_ID: %s)" %
|
||||||
|
(self.__class__.__name__,
|
||||||
|
mfg,
|
||||||
|
vendor_id,
|
||||||
|
model,
|
||||||
|
bcd,
|
||||||
|
product_id
|
||||||
|
))
|
||||||
|
|
||||||
# Display a dialog recommending using 'Connect to iTunes' if user hasn't
|
# Display a dialog recommending using 'Connect to iTunes' if user hasn't
|
||||||
# previously disabled the dialog
|
# previously disabled the dialog
|
||||||
@ -867,7 +877,11 @@ class ITUNES(DriverBase):
|
|||||||
raise AppleOpenFeedback(self)
|
raise AppleOpenFeedback(self)
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().warning(" %s" % self.UNSUPPORTED_DIRECT_CONNECT_MODE_MESSAGE)
|
logger().error(" %s" % self.UNSUPPORTED_DIRECT_CONNECT_MODE_MESSAGE)
|
||||||
|
|
||||||
|
# Log supported DEVICE_IDs and BCDs
|
||||||
|
logger().info(" BCD: %s" % ['0x%x' % x for x in sorted(self.BCD)])
|
||||||
|
logger().info(" PRODUCT_ID: %s" % ['0x%x' % x for x in sorted(self.PRODUCT_ID)])
|
||||||
|
|
||||||
# Confirm/create thumbs archive
|
# Confirm/create thumbs archive
|
||||||
if not os.path.exists(self.cache_dir):
|
if not os.path.exists(self.cache_dir):
|
||||||
@ -908,14 +922,14 @@ class ITUNES(DriverBase):
|
|||||||
as uuids are different
|
as uuids are different
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.remove_books_from_metadata()")
|
logger().info("%s.remove_books_from_metadata()" % self.__class__.__name__)
|
||||||
for path in paths:
|
for path in paths:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
self._dump_cached_book(self.cached_books[path], indent=2)
|
self._dump_cached_book(self.cached_books[path], indent=2)
|
||||||
logger().info(" looking for '%s' by '%s' uuid:%s" %
|
logger().info(" looking for '%s' by '%s' uuid:%s" %
|
||||||
(self.cached_books[path]['title'],
|
(self.cached_books[path]['title'],
|
||||||
self.cached_books[path]['author'],
|
self.cached_books[path]['author'],
|
||||||
self.cached_books[path]['uuid']))
|
repr(self.cached_books[path]['uuid'])))
|
||||||
|
|
||||||
# Purge the booklist, self.cached_books, thumb cache
|
# Purge the booklist, self.cached_books, thumb cache
|
||||||
for i, bl_book in enumerate(booklists[0]):
|
for i, bl_book in enumerate(booklists[0]):
|
||||||
@ -924,24 +938,28 @@ class ITUNES(DriverBase):
|
|||||||
(bl_book.title, bl_book.author, bl_book.uuid))
|
(bl_book.title, bl_book.author, bl_book.uuid))
|
||||||
|
|
||||||
found = False
|
found = False
|
||||||
if bl_book.uuid == self.cached_books[path]['uuid']:
|
if bl_book.uuid and bl_book.uuid == self.cached_books[path]['uuid']:
|
||||||
if False:
|
if True:
|
||||||
logger().info(" matched with uuid")
|
logger().info(" --matched uuid")
|
||||||
booklists[0].pop(i)
|
booklists[0].pop(i)
|
||||||
found = True
|
found = True
|
||||||
elif bl_book.title == self.cached_books[path]['title'] and \
|
elif bl_book.title == self.cached_books[path]['title'] and \
|
||||||
bl_book.author[0] == self.cached_books[path]['author']:
|
bl_book.author == self.cached_books[path]['author']:
|
||||||
if False:
|
if True:
|
||||||
logger().info(" matched with title + author")
|
logger().info(" --matched title + author")
|
||||||
booklists[0].pop(i)
|
booklists[0].pop(i)
|
||||||
found = True
|
found = True
|
||||||
|
|
||||||
if found:
|
if found:
|
||||||
# Remove from self.cached_books
|
# Remove from self.cached_books
|
||||||
for cb in self.cached_books:
|
for cb in self.cached_books:
|
||||||
if self.cached_books[cb]['uuid'] == self.cached_books[path]['uuid']:
|
if (self.cached_books[cb]['uuid'] == self.cached_books[path]['uuid'] and
|
||||||
|
self.cached_books[cb]['author'] == self.cached_books[path]['author'] and
|
||||||
|
self.cached_books[cb]['title'] == self.cached_books[path]['title']):
|
||||||
self.cached_books.pop(cb)
|
self.cached_books.pop(cb)
|
||||||
break
|
break
|
||||||
|
else:
|
||||||
|
logger().error(" '%s' not found in self.cached_books" % self.cached_books[path]['title'])
|
||||||
|
|
||||||
# Remove from thumb from thumb cache
|
# Remove from thumb from thumb cache
|
||||||
thumb_path = path.rpartition('.')[0] + '.jpg'
|
thumb_path = path.rpartition('.')[0] + '.jpg'
|
||||||
@ -964,7 +982,9 @@ class ITUNES(DriverBase):
|
|||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().error(" unable to find '%s' by '%s' (%s)" %
|
logger().error(" unable to find '%s' by '%s' (%s)" %
|
||||||
(bl_book.title, bl_book.author,bl_book.uuid))
|
(self.cached_books[path]['title'],
|
||||||
|
self.cached_books[path]['author'],
|
||||||
|
self.cached_books[path]['uuid']))
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
self._dump_booklist(booklists[0], indent=2)
|
self._dump_booklist(booklists[0], indent=2)
|
||||||
@ -982,7 +1002,7 @@ class ITUNES(DriverBase):
|
|||||||
:detected_device: Device information from the device scanner
|
:detected_device: Device information from the device scanner
|
||||||
"""
|
"""
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.reset()")
|
logger().info("%s.reset()" % self.__class__.__name__)
|
||||||
if report_progress:
|
if report_progress:
|
||||||
self.set_progress_reporter(report_progress)
|
self.set_progress_reporter(report_progress)
|
||||||
|
|
||||||
@ -994,7 +1014,7 @@ class ITUNES(DriverBase):
|
|||||||
task does not have any progress information
|
task does not have any progress information
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.set_progress_reporter()")
|
logger().info("%s.set_progress_reporter()" % self.__class__.__name__)
|
||||||
|
|
||||||
self.report_progress = report_progress
|
self.report_progress = report_progress
|
||||||
|
|
||||||
@ -1002,7 +1022,7 @@ class ITUNES(DriverBase):
|
|||||||
# This method is called with the plugboard that matches the format
|
# This method is called with the plugboard that matches the format
|
||||||
# declared in use_plugboard_ext and a device name of ITUNES
|
# declared in use_plugboard_ext and a device name of ITUNES
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.set_plugboard()")
|
logger().info("%s.set_plugboard()" % self.__class__.__name__)
|
||||||
#logger().info(' plugboard: %s' % plugboards)
|
#logger().info(' plugboard: %s' % plugboards)
|
||||||
self.plugboards = plugboards
|
self.plugboards = plugboards
|
||||||
self.plugboard_func = pb_func
|
self.plugboard_func = pb_func
|
||||||
@ -1016,7 +1036,7 @@ class ITUNES(DriverBase):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.sync_booklists()")
|
logger().info("%s.sync_booklists()" % self.__class__.__name__)
|
||||||
|
|
||||||
if self.update_needed:
|
if self.update_needed:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -1043,7 +1063,7 @@ class ITUNES(DriverBase):
|
|||||||
particular device doesn't have any of these locations it should return 0.
|
particular device doesn't have any of these locations it should return 0.
|
||||||
"""
|
"""
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES:total_space()")
|
logger().info("%s.total_space()" % self.__class__.__name__)
|
||||||
capacity = 0
|
capacity = 0
|
||||||
if isosx:
|
if isosx:
|
||||||
if 'iPod' in self.sources:
|
if 'iPod' in self.sources:
|
||||||
@ -1081,7 +1101,7 @@ class ITUNES(DriverBase):
|
|||||||
"Click 'Show Details' for a list.")
|
"Click 'Show Details' for a list.")
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.upload_books()")
|
logger().info("%s.upload_books()" % self.__class__.__name__)
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
for (i, fpath) in enumerate(files):
|
for (i, fpath) in enumerate(files):
|
||||||
@ -1098,7 +1118,7 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
# Add new_book to self.cached_books
|
# Add new_book to self.cached_books
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.upload_books()")
|
logger().info("%s.upload_books()" % self.__class__.__name__)
|
||||||
logger().info(" adding '%s' by '%s' uuid:%s to self.cached_books" %
|
logger().info(" adding '%s' by '%s' uuid:%s to self.cached_books" %
|
||||||
(metadata[i].title,
|
(metadata[i].title,
|
||||||
authors_to_string(metadata[i].authors),
|
authors_to_string(metadata[i].authors),
|
||||||
@ -1144,7 +1164,7 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
# Add new_book to self.cached_books
|
# Add new_book to self.cached_books
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES.upload_books()")
|
logger().info("%s.upload_books()" % self.__class__.__name__)
|
||||||
logger().info(" adding '%s' by '%s' uuid:%s to self.cached_books" %
|
logger().info(" adding '%s' by '%s' uuid:%s to self.cached_books" %
|
||||||
(metadata[i].title,
|
(metadata[i].title,
|
||||||
authors_to_string(metadata[i].authors),
|
authors_to_string(metadata[i].authors),
|
||||||
@ -1182,7 +1202,7 @@ class ITUNES(DriverBase):
|
|||||||
'''
|
'''
|
||||||
assumes pythoncom wrapper for windows
|
assumes pythoncom wrapper for windows
|
||||||
'''
|
'''
|
||||||
logger().info(" ITUNES._add_device_book()")
|
logger().info(" %s._add_device_book()" % self.__class__.__name__)
|
||||||
if isosx:
|
if isosx:
|
||||||
import appscript
|
import appscript
|
||||||
if 'iPod' in self.sources:
|
if 'iPod' in self.sources:
|
||||||
@ -1292,7 +1312,7 @@ class ITUNES(DriverBase):
|
|||||||
windows assumes pythoncom wrapper
|
windows assumes pythoncom wrapper
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._add_library_book()")
|
logger().info(" %s._add_library_book()" % self.__class__.__name__)
|
||||||
if isosx:
|
if isosx:
|
||||||
import appscript
|
import appscript
|
||||||
added = self.iTunes.add(appscript.mactypes.File(file))
|
added = self.iTunes.add(appscript.mactypes.File(file))
|
||||||
@ -1360,7 +1380,7 @@ class ITUNES(DriverBase):
|
|||||||
fp = cached_book['lib_book'].Location
|
fp = cached_book['lib_book'].Location
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._add_new_copy()")
|
logger().info(" %s._add_new_copy()" % self.__class__.__name__)
|
||||||
|
|
||||||
if fpath.rpartition('.')[2].lower() == 'epub':
|
if fpath.rpartition('.')[2].lower() == 'epub':
|
||||||
self._update_epub_metadata(fpath, metadata)
|
self._update_epub_metadata(fpath, metadata)
|
||||||
@ -1399,7 +1419,7 @@ class ITUNES(DriverBase):
|
|||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._cover_to_thumb()")
|
logger().info(" %s._cover_to_thumb()" % self.__class__.__name__)
|
||||||
|
|
||||||
thumb = None
|
thumb = None
|
||||||
if metadata.cover:
|
if metadata.cover:
|
||||||
@ -1526,7 +1546,7 @@ class ITUNES(DriverBase):
|
|||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._create_new_book()")
|
logger().info(" %s._create_new_book()" % self.__class__.__name__)
|
||||||
|
|
||||||
this_book = Book(metadata.title, authors_to_string(metadata.authors))
|
this_book = Book(metadata.title, authors_to_string(metadata.authors))
|
||||||
this_book.datetime = time.gmtime()
|
this_book.datetime = time.gmtime()
|
||||||
@ -1575,7 +1595,7 @@ class ITUNES(DriverBase):
|
|||||||
wait is passed when launching iTunes, as it seems to need a moment to come to its senses
|
wait is passed when launching iTunes, as it seems to need a moment to come to its senses
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._discover_manual_sync_mode()")
|
logger().info(" %s._discover_manual_sync_mode()" % self.__class__.__name__)
|
||||||
if wait:
|
if wait:
|
||||||
time.sleep(wait)
|
time.sleep(wait)
|
||||||
if isosx:
|
if isosx:
|
||||||
@ -1593,7 +1613,7 @@ class ITUNES(DriverBase):
|
|||||||
if dev_books is not None and len(dev_books):
|
if dev_books is not None and len(dev_books):
|
||||||
first_book = dev_books[0]
|
first_book = dev_books[0]
|
||||||
if False:
|
if False:
|
||||||
logger().info(" determing manual mode by modifying '%s' by %s" % (first_book.name(), first_book.artist()))
|
logger().info(" determining manual mode by modifying '%s' by %s" % (first_book.name(), first_book.artist()))
|
||||||
try:
|
try:
|
||||||
first_book.bpm.set(0)
|
first_book.bpm.set(0)
|
||||||
self.manual_sync_mode = True
|
self.manual_sync_mode = True
|
||||||
@ -1655,8 +1675,8 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
for book in booklist:
|
for book in booklist:
|
||||||
if isosx:
|
if isosx:
|
||||||
logger().info("%s%-40.40s %-30.30s %-10.10s %s" %
|
logger().info("%s%-40.40s %-30.30s %-40.40s %-10.10s" %
|
||||||
(' '*indent,book.title, book.author, str(book.library_id)[-9:], book.uuid))
|
(' ' * indent, book.title, book.author, book.uuid, str(book.library_id)[-9:]))
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
logger().info("%s%-40.40s %-30.30s" %
|
logger().info("%s%-40.40s %-30.30s" %
|
||||||
(' ' * indent, book.title, book.author))
|
(' ' * indent, book.title, book.author))
|
||||||
@ -1705,13 +1725,14 @@ class ITUNES(DriverBase):
|
|||||||
logger().info("%s%s" % (' ' * indent, '-' * len(msg)))
|
logger().info("%s%s" % (' ' * indent, '-' * len(msg)))
|
||||||
if isosx:
|
if isosx:
|
||||||
for cb in self.cached_books.keys():
|
for cb in self.cached_books.keys():
|
||||||
logger().info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" %
|
logger().info("%s%-40.40s %-30.30s %-40.40s %-10.10s %-10.10s" %
|
||||||
(' ' * indent,
|
(' ' * indent,
|
||||||
self.cached_books[cb]['title'],
|
self.cached_books[cb]['title'],
|
||||||
self.cached_books[cb]['author'],
|
self.cached_books[cb]['author'],
|
||||||
|
self.cached_books[cb]['uuid'],
|
||||||
str(self.cached_books[cb]['lib_book'])[-9:],
|
str(self.cached_books[cb]['lib_book'])[-9:],
|
||||||
str(self.cached_books[cb]['dev_book'])[-9:],
|
str(self.cached_books[cb]['dev_book'])[-9:],
|
||||||
self.cached_books[cb]['uuid']))
|
))
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
for cb in self.cached_books.keys():
|
for cb in self.cached_books.keys():
|
||||||
logger().info("%s%-40.40s %-30.30s %-4.4s %s" %
|
logger().info("%s%-40.40s %-30.30s %-4.4s %s" %
|
||||||
@ -1728,7 +1749,7 @@ class ITUNES(DriverBase):
|
|||||||
'''
|
'''
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||||
|
|
||||||
logger().info(" ITUNES.__get_epub_metadata()")
|
logger().info(" %s.__get_epub_metadata()" % self.__class__.__name__)
|
||||||
title = None
|
title = None
|
||||||
author = None
|
author = None
|
||||||
timestamp = None
|
timestamp = None
|
||||||
@ -1760,7 +1781,8 @@ class ITUNES(DriverBase):
|
|||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)])
|
FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)])
|
||||||
N=0; result=''
|
N = 0
|
||||||
|
result = ''
|
||||||
while src:
|
while src:
|
||||||
s, src = src[:length], src[length:]
|
s, src = src[:length], src[length:]
|
||||||
hexa = ' '.join(["%02X" % ord(x) for x in s])
|
hexa = ' '.join(["%02X" % ord(x) for x in s])
|
||||||
@ -1806,7 +1828,7 @@ class ITUNES(DriverBase):
|
|||||||
if iswindows:
|
if iswindows:
|
||||||
dev_books = self._get_device_books_playlist()
|
dev_books = self._get_device_books_playlist()
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._find_device_book()")
|
logger().info(" %s._find_device_book()" % self.__class__.__name__)
|
||||||
logger().info(" searching for '%s' by '%s' (%s)" %
|
logger().info(" searching for '%s' by '%s' (%s)" %
|
||||||
(search['title'], search['author'], search['uuid']))
|
(search['title'], search['author'], search['uuid']))
|
||||||
attempts = 9
|
attempts = 9
|
||||||
@ -1876,7 +1898,7 @@ class ITUNES(DriverBase):
|
|||||||
'''
|
'''
|
||||||
if iswindows:
|
if iswindows:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._find_library_book()")
|
logger().info(" %s._find_library_book()" % self.__class__.__name__)
|
||||||
'''
|
'''
|
||||||
if 'uuid' in search:
|
if 'uuid' in search:
|
||||||
logger().info(" looking for '%s' by %s (%s)" %
|
logger().info(" looking for '%s' by %s (%s)" %
|
||||||
@ -1909,7 +1931,6 @@ class ITUNES(DriverBase):
|
|||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().error(" no Books playlist found")
|
logger().error(" no Books playlist found")
|
||||||
|
|
||||||
|
|
||||||
attempts = 9
|
attempts = 9
|
||||||
while attempts:
|
while attempts:
|
||||||
# Find book whose Album field = search['uuid']
|
# Find book whose Album field = search['uuid']
|
||||||
@ -1996,7 +2017,8 @@ class ITUNES(DriverBase):
|
|||||||
thumb_data = zfr.read(thumb_path)
|
thumb_data = zfr.read(thumb_path)
|
||||||
if thumb_data == 'None':
|
if thumb_data == 'None':
|
||||||
if False:
|
if False:
|
||||||
logger().info(" ITUNES._generate_thumbnail()\n returning None from cover cache for '%s'" % title)
|
logger().info(" %s._generate_thumbnail()\n returning None from cover cache for '%s'" %
|
||||||
|
(self.__class__.__name__, title))
|
||||||
zfr.close()
|
zfr.close()
|
||||||
return None
|
return None
|
||||||
except:
|
except:
|
||||||
@ -2007,7 +2029,7 @@ class ITUNES(DriverBase):
|
|||||||
return thumb_data
|
return thumb_data
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._generate_thumbnail('%s'):" % title)
|
logger().info(" %s._generate_thumbnail('%s'):" % (self.__class__.__name__, title))
|
||||||
if isosx:
|
if isosx:
|
||||||
|
|
||||||
# Fetch the artwork from iTunes
|
# Fetch the artwork from iTunes
|
||||||
@ -2049,7 +2071,6 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
return thumb_data
|
return thumb_data
|
||||||
|
|
||||||
|
|
||||||
elif iswindows:
|
elif iswindows:
|
||||||
if not book.Artwork.Count:
|
if not book.Artwork.Count:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
@ -2101,7 +2122,7 @@ class ITUNES(DriverBase):
|
|||||||
for file in myZipList:
|
for file in myZipList:
|
||||||
exploded_file_size += file.file_size
|
exploded_file_size += file.file_size
|
||||||
if False:
|
if False:
|
||||||
logger().info(" ITUNES._get_device_book_size()")
|
logger().info(" %s._get_device_book_size()" % self.__class__.__name__)
|
||||||
logger().info(" %d items in archive" % len(myZipList))
|
logger().info(" %d items in archive" % len(myZipList))
|
||||||
logger().info(" compressed: %d exploded: %d" % (compressed_size, exploded_file_size))
|
logger().info(" compressed: %d exploded: %d" % (compressed_size, exploded_file_size))
|
||||||
myZip.close()
|
myZip.close()
|
||||||
@ -2112,7 +2133,7 @@ class ITUNES(DriverBase):
|
|||||||
Assumes pythoncom wrapper for Windows
|
Assumes pythoncom wrapper for Windows
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("\n ITUNES._get_device_books()")
|
logger().info("\n %s._get_device_books()" % self.__class__.__name__)
|
||||||
|
|
||||||
device_books = []
|
device_books = []
|
||||||
if isosx:
|
if isosx:
|
||||||
@ -2131,14 +2152,13 @@ class ITUNES(DriverBase):
|
|||||||
logger().error(" book_playlist not found")
|
logger().error(" book_playlist not found")
|
||||||
|
|
||||||
for book in dev_books:
|
for book in dev_books:
|
||||||
# This may need additional entries for international iTunes users
|
|
||||||
if book.kind() in self.Audiobooks:
|
if book.kind() in self.Audiobooks:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
|
logger().info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" %-30.30s %-30.30s %-40.40s [%s]" %
|
logger().info(" %-40.40s %-30.30s %-40.40s [%s]" %
|
||||||
(book.name(), book.artist(), book.album(), book.kind()))
|
(book.name(), book.artist(), book.composer(), book.kind()))
|
||||||
device_books.append(book)
|
device_books.append(book)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info()
|
logger().info()
|
||||||
@ -2165,13 +2185,12 @@ class ITUNES(DriverBase):
|
|||||||
logger().info(" no Books playlist found")
|
logger().info(" no Books playlist found")
|
||||||
|
|
||||||
for book in dev_books:
|
for book in dev_books:
|
||||||
# This may need additional entries for international iTunes users
|
|
||||||
if book.KindAsString in self.Audiobooks:
|
if book.KindAsString in self.Audiobooks:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString))
|
logger().info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString))
|
||||||
else:
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" %-30.30s %-30.30s %-40.40s [%s]" % (book.Name, book.Artist, book.Album, book.KindAsString))
|
logger().info(" %-40.40s %-30.30s %-40.40s [%s]" % (book.Name, book.Artist, book.Composer, book.KindAsString))
|
||||||
device_books.append(book)
|
device_books.append(book)
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info()
|
logger().info()
|
||||||
@ -2206,7 +2225,7 @@ class ITUNES(DriverBase):
|
|||||||
Windows assumes pythoncom wrapper
|
Windows assumes pythoncom wrapper
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("\n ITUNES._get_library_books()")
|
logger().info("\n %s._get_library_books()" % self.__class__.__name__)
|
||||||
|
|
||||||
library_books = {}
|
library_books = {}
|
||||||
library_orphans = {}
|
library_orphans = {}
|
||||||
@ -2381,7 +2400,7 @@ class ITUNES(DriverBase):
|
|||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES:_launch_iTunes():\n Instantiating iTunes")
|
logger().info(" %s._launch_iTunes():\n Instantiating iTunes" % self.__class__.__name__)
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
import appscript
|
import appscript
|
||||||
@ -2394,12 +2413,13 @@ class ITUNES(DriverBase):
|
|||||||
running_apps = appscript.app('System Events')
|
running_apps = appscript.app('System Events')
|
||||||
if not 'iTunes' in running_apps.processes.name():
|
if not 'iTunes' in running_apps.processes.name():
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info( "ITUNES:_launch_iTunes(): Launching iTunes" )
|
logger().info("%s:_launch_iTunes(): Launching iTunes" % self.__class__.__name__)
|
||||||
try:
|
try:
|
||||||
self.iTunes = iTunes = appscript.app('iTunes', hide=True)
|
self.iTunes = iTunes = appscript.app('iTunes', hide=True)
|
||||||
except:
|
except:
|
||||||
self.iTunes = None
|
self.iTunes = None
|
||||||
raise UserFeedback(' ITUNES._launch_iTunes(): unable to find installed iTunes', details=None, level=UserFeedback.WARN)
|
raise UserFeedback(' %s._launch_iTunes(): unable to find installed iTunes' %
|
||||||
|
self.__class__.__name__, details=None, level=UserFeedback.WARN)
|
||||||
|
|
||||||
iTunes.run()
|
iTunes.run()
|
||||||
self.initial_status = 'launched'
|
self.initial_status = 'launched'
|
||||||
@ -2444,10 +2464,10 @@ class ITUNES(DriverBase):
|
|||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" %s %s" % (__appname__, __version__))
|
logger().info(" %s %s" % (__appname__, __version__))
|
||||||
logger().info(" [OSX %s, %s %s (%s), driver version %d.%d.%d]" %
|
logger().info(" [OSX %s, %s %s (%s), %s driver version %d.%d.%d]" %
|
||||||
(platform.mac_ver()[0],
|
(platform.mac_ver()[0],
|
||||||
self.iTunes.name(), self.iTunes.version(), self.initial_status,
|
self.iTunes.name(), self.iTunes.version(), self.initial_status,
|
||||||
self.version[0],self.version[1],self.version[2]))
|
self.__class__.__name__, self.version[0], self.version[1], self.version[2]))
|
||||||
logger().info(" communicating with iTunes via %s %s using %s binding" % (as_name, as_version, as_binding))
|
logger().info(" communicating with iTunes via %s %s using %s binding" % (as_name, as_version, as_binding))
|
||||||
logger().info(" calibre_library_path: %s" % self.calibre_library_path)
|
logger().info(" calibre_library_path: %s" % self.calibre_library_path)
|
||||||
|
|
||||||
@ -2474,7 +2494,8 @@ class ITUNES(DriverBase):
|
|||||||
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
self.iTunes = win32com.client.Dispatch("iTunes.Application")
|
||||||
except:
|
except:
|
||||||
self.iTunes = None
|
self.iTunes = None
|
||||||
raise UserFeedback(' ITUNES._launch_iTunes(): unable to find installed iTunes', details=None, level=UserFeedback.WARN)
|
raise UserFeedback(' %s._launch_iTunes(): unable to find installed iTunes'
|
||||||
|
% self.__class__.__name__, details=None, level=UserFeedback.WARN)
|
||||||
|
|
||||||
if not DEBUG:
|
if not DEBUG:
|
||||||
self.iTunes.Windows[0].Minimized = True
|
self.iTunes.Windows[0].Minimized = True
|
||||||
@ -2524,8 +2545,11 @@ class ITUNES(DriverBase):
|
|||||||
Remove any iTunes orphans originally added by calibre
|
Remove any iTunes orphans originally added by calibre
|
||||||
This occurs when the user deletes a book in iBooks while disconnected
|
This occurs when the user deletes a book in iBooks while disconnected
|
||||||
'''
|
'''
|
||||||
|
PURGE_ORPHANS = False
|
||||||
|
|
||||||
|
if PURGE_ORPHANS:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._purge_orphans()")
|
logger().info(" %s._purge_orphans()" % self.__class__.__name__)
|
||||||
#self._dump_library_books(library_books)
|
#self._dump_library_books(library_books)
|
||||||
#logger().info(" cached_books:\n %s" % "\n ".join(cached_books.keys()))
|
#logger().info(" cached_books:\n %s" % "\n ".join(cached_books.keys()))
|
||||||
|
|
||||||
@ -2535,7 +2559,8 @@ class ITUNES(DriverBase):
|
|||||||
str(library_books[book].description()).startswith(self.description_prefix):
|
str(library_books[book].description()).startswith(self.description_prefix):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" '%s' not found on iDevice, removing from iTunes" % book)
|
logger().info(" '%s' not found on iDevice, removing from iTunes" % book)
|
||||||
btr = { 'title':library_books[book].name(),
|
btr = {
|
||||||
|
'title': library_books[book].name(),
|
||||||
'author': library_books[book].artist(),
|
'author': library_books[book].artist(),
|
||||||
'lib_book': library_books[book]}
|
'lib_book': library_books[book]}
|
||||||
self._remove_from_iTunes(btr)
|
self._remove_from_iTunes(btr)
|
||||||
@ -2544,34 +2569,30 @@ class ITUNES(DriverBase):
|
|||||||
library_books[book].Description.startswith(self.description_prefix):
|
library_books[book].Description.startswith(self.description_prefix):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" '%s' not found on iDevice, removing from iTunes" % book)
|
logger().info(" '%s' not found on iDevice, removing from iTunes" % book)
|
||||||
btr = { 'title':library_books[book].Name,
|
btr = {
|
||||||
|
'title': library_books[book].Name,
|
||||||
'author': library_books[book].Artist,
|
'author': library_books[book].Artist,
|
||||||
'lib_book': library_books[book]}
|
'lib_book': library_books[book]}
|
||||||
self._remove_from_iTunes(btr)
|
self._remove_from_iTunes(btr)
|
||||||
|
else:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info()
|
logger().info(" %s._purge_orphans(disabled)" % self.__class__.__name__)
|
||||||
|
|
||||||
def _remove_existing_copy(self, path, metadata):
|
def _remove_existing_copy(self, path, metadata):
|
||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._remove_existing_copy()")
|
logger().info(" %s._remove_existing_copy()" % self.__class__.__name__)
|
||||||
|
|
||||||
if self.manual_sync_mode:
|
if self.manual_sync_mode:
|
||||||
# Delete existing from Device|Books, add to self.update_list
|
# Delete existing from Device|Books, add to self.update_list
|
||||||
# for deletion from booklist[0] during add_books_to_metadata
|
# for deletion from booklist[0] during add_books_to_metadata
|
||||||
for book in self.cached_books:
|
for book in self.cached_books:
|
||||||
if self.cached_books[book]['uuid'] == metadata.uuid or \
|
if (self.cached_books[book]['uuid'] == metadata.uuid or
|
||||||
(self.cached_books[book]['title'] == metadata.title and \
|
(self.cached_books[book]['title'] == metadata.title and
|
||||||
self.cached_books[book]['author'] == authors_to_string(metadata.authors)):
|
self.cached_books[book]['author'] == metadata.author)):
|
||||||
self.update_list.append(self.cached_books[book])
|
self.update_list.append(self.cached_books[book])
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
logger().info( " deleting device book '%s'" % (metadata.title))
|
|
||||||
self._remove_from_device(self.cached_books[book])
|
self._remove_from_device(self.cached_books[book])
|
||||||
|
|
||||||
if DEBUG:
|
|
||||||
logger().info(" deleting library book '%s'" % metadata.title)
|
|
||||||
self._remove_from_iTunes(self.cached_books[book])
|
self._remove_from_iTunes(self.cached_books[book])
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@ -2581,9 +2602,9 @@ class ITUNES(DriverBase):
|
|||||||
# Delete existing from Library|Books, add to self.update_list
|
# Delete existing from Library|Books, add to self.update_list
|
||||||
# for deletion from booklist[0] during add_books_to_metadata
|
# for deletion from booklist[0] during add_books_to_metadata
|
||||||
for book in self.cached_books:
|
for book in self.cached_books:
|
||||||
if self.cached_books[book]['uuid'] == metadata.uuid or \
|
if (self.cached_books[book]['uuid'] == metadata.uuid or
|
||||||
(self.cached_books[book]['title'] == metadata.title and \
|
(self.cached_books[book]['title'] == metadata.title and \
|
||||||
self.cached_books[book]['author'] == authors_to_string(metadata.authors)):
|
self.cached_books[book]['author'] == metadata.author)):
|
||||||
self.update_list.append(self.cached_books[book])
|
self.update_list.append(self.cached_books[book])
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" deleting library book '%s'" % metadata.title)
|
logger().info(" deleting library book '%s'" % metadata.title)
|
||||||
@ -2598,7 +2619,7 @@ class ITUNES(DriverBase):
|
|||||||
Windows assumes pythoncom wrapper
|
Windows assumes pythoncom wrapper
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._remove_from_device()")
|
logger().info(" %s._remove_from_device()" % self.__class__.__name__)
|
||||||
if isosx:
|
if isosx:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" deleting '%s' from iDevice" % cached_book['title'])
|
logger().info(" deleting '%s' from iDevice" % cached_book['title'])
|
||||||
@ -2622,7 +2643,7 @@ class ITUNES(DriverBase):
|
|||||||
iTunes does not delete books from storage when removing from database via automation
|
iTunes does not delete books from storage when removing from database via automation
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._remove_from_iTunes():")
|
logger().info(" %s._remove_from_iTunes():" % self.__class__.__name__)
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
''' Manually remove the book from iTunes storage '''
|
''' Manually remove the book from iTunes storage '''
|
||||||
@ -2664,7 +2685,8 @@ class ITUNES(DriverBase):
|
|||||||
except:
|
except:
|
||||||
# We get here if there was an error with .location().path
|
# We get here if there was an error with .location().path
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" '%s' not found in iTunes storage" % cached_book['title'])
|
logger().info(" '%s' by %s not found in iTunes storage" %
|
||||||
|
(cached_book['title'], cached_book['author']))
|
||||||
|
|
||||||
# Delete the book from the iTunes database
|
# Delete the book from the iTunes database
|
||||||
try:
|
try:
|
||||||
@ -2739,7 +2761,7 @@ class ITUNES(DriverBase):
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._update_epub_metadata()")
|
logger().info(" %s._update_epub_metadata()" % self.__class__.__name__)
|
||||||
|
|
||||||
# Fetch plugboard updates
|
# Fetch plugboard updates
|
||||||
metadata_x = self._xform_metadata_via_plugboard(metadata, 'epub')
|
metadata_x = self._xform_metadata_via_plugboard(metadata, 'epub')
|
||||||
@ -2807,7 +2829,7 @@ class ITUNES(DriverBase):
|
|||||||
Trigger a sync, wait for completion
|
Trigger a sync, wait for completion
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES:_update_device():\n %s" % msg)
|
logger().info(" %s:_update_device():\n %s" % (self.__class__.__name__, msg))
|
||||||
|
|
||||||
if isosx:
|
if isosx:
|
||||||
self.iTunes.update()
|
self.iTunes.update()
|
||||||
@ -2855,7 +2877,7 @@ class ITUNES(DriverBase):
|
|||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._update_iTunes_metadata()")
|
logger().info(" %s._update_iTunes_metadata()" % self.__class__.__name__)
|
||||||
|
|
||||||
STRIP_TAGS = re.compile(r'<[^<]*?/?>')
|
STRIP_TAGS = re.compile(r'<[^<]*?/?>')
|
||||||
|
|
||||||
@ -2907,7 +2929,7 @@ class ITUNES(DriverBase):
|
|||||||
# If title_sort applied in plugboard, that overrides using series/index as title_sort
|
# If title_sort applied in plugboard, that overrides using series/index as title_sort
|
||||||
if metadata_x.series and self.settings().extra_customization[self.USE_SERIES_AS_CATEGORY]:
|
if metadata_x.series and self.settings().extra_customization[self.USE_SERIES_AS_CATEGORY]:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._update_iTunes_metadata()")
|
logger().info(" %s._update_iTunes_metadata()" % self.__class__.__name__)
|
||||||
logger().info(" using Series name '%s' as Genre" % metadata_x.series)
|
logger().info(" using Series name '%s' as Genre" % metadata_x.series)
|
||||||
|
|
||||||
# Format the index as a sort key
|
# Format the index as a sort key
|
||||||
@ -2949,7 +2971,6 @@ class ITUNES(DriverBase):
|
|||||||
db_added.genre.set(tag)
|
db_added.genre.set(tag)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
elif metadata_x.tags is not None:
|
elif metadata_x.tags is not None:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" %susing Tag as Genre" %
|
logger().info(" %susing Tag as Genre" %
|
||||||
@ -3089,7 +3110,7 @@ class ITUNES(DriverBase):
|
|||||||
Ensure iDevice metadata is writable. Direct connect mode only
|
Ensure iDevice metadata is writable. Direct connect mode only
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._wait_for_writable_metadata()")
|
logger().info(" %s._wait_for_writable_metadata()" % self.__class__.__name__)
|
||||||
logger().warning(" %s" % self.UNSUPPORTED_DIRECT_CONNECT_MODE_MESSAGE)
|
logger().warning(" %s" % self.UNSUPPORTED_DIRECT_CONNECT_MODE_MESSAGE)
|
||||||
|
|
||||||
attempts = 9
|
attempts = 9
|
||||||
@ -3113,7 +3134,7 @@ class ITUNES(DriverBase):
|
|||||||
def _xform_metadata_via_plugboard(self, book, format):
|
def _xform_metadata_via_plugboard(self, book, format):
|
||||||
''' Transform book metadata from plugboard templates '''
|
''' Transform book metadata from plugboard templates '''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info(" ITUNES._xform_metadata_via_plugboard()")
|
logger().info(" %s._xform_metadata_via_plugboard()" % self.__class__.__name__)
|
||||||
|
|
||||||
if self.plugboard_func:
|
if self.plugboard_func:
|
||||||
pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards)
|
pb = self.plugboard_func(self.DEVICE_PLUGBOARD_NAME, format, self.plugboards)
|
||||||
@ -3143,6 +3164,7 @@ class ITUNES(DriverBase):
|
|||||||
newmi = book
|
newmi = book
|
||||||
return newmi
|
return newmi
|
||||||
|
|
||||||
|
|
||||||
class ITUNES_ASYNC(ITUNES):
|
class ITUNES_ASYNC(ITUNES):
|
||||||
'''
|
'''
|
||||||
This subclass allows the user to interact directly with iTunes via a menu option
|
This subclass allows the user to interact directly with iTunes via a menu option
|
||||||
@ -3160,7 +3182,7 @@ class ITUNES_ASYNC(ITUNES):
|
|||||||
|
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES_ASYNC:__init__()")
|
logger().info("%s.__init__()" % self.__class__.__name__)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import appscript
|
import appscript
|
||||||
@ -3210,7 +3232,7 @@ class ITUNES_ASYNC(ITUNES):
|
|||||||
"""
|
"""
|
||||||
if not oncard:
|
if not oncard:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES_ASYNC:books()")
|
logger().info("%s.books()" % self.__class__.__name__)
|
||||||
if self.settings().extra_customization[self.CACHE_COVERS]:
|
if self.settings().extra_customization[self.CACHE_COVERS]:
|
||||||
logger().info(" Cover fetching/caching enabled")
|
logger().info(" Cover fetching/caching enabled")
|
||||||
else:
|
else:
|
||||||
@ -3324,7 +3346,7 @@ class ITUNES_ASYNC(ITUNES):
|
|||||||
are pending GUI jobs that need to communicate with the device.
|
are pending GUI jobs that need to communicate with the device.
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES_ASYNC:eject()")
|
logger().info("%s.eject()" % self.__class__.__name__)
|
||||||
self.iTunes = None
|
self.iTunes = None
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
|
||||||
@ -3339,7 +3361,7 @@ class ITUNES_ASYNC(ITUNES):
|
|||||||
particular device doesn't have any of these locations it should return -1.
|
particular device doesn't have any of these locations it should return -1.
|
||||||
"""
|
"""
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES_ASYNC:free_space()")
|
logger().info("%s.free_space()" % self.__class__.__name__)
|
||||||
free_space = 0
|
free_space = 0
|
||||||
if isosx:
|
if isosx:
|
||||||
s = os.statvfs(os.sep)
|
s = os.statvfs(os.sep)
|
||||||
@ -3356,7 +3378,7 @@ class ITUNES_ASYNC(ITUNES):
|
|||||||
@return: (device name, device version, software version on device, mime type)
|
@return: (device name, device version, software version on device, mime type)
|
||||||
"""
|
"""
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES_ASYNC:get_device_information()")
|
logger().info("%s.get_device_information()" % self.__class__.__name__)
|
||||||
|
|
||||||
return ('iTunes', 'hw v1.0', 'sw v1.0', 'mime type normally goes here')
|
return ('iTunes', 'hw v1.0', 'sw v1.0', 'mime type normally goes here')
|
||||||
|
|
||||||
@ -3382,7 +3404,8 @@ class ITUNES_ASYNC(ITUNES):
|
|||||||
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)
|
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES_ASYNC.open(connected_device: %s)" % repr(connected_device))
|
logger().info("%s.open(connected_device: %s)" %
|
||||||
|
(self.__class__.__name__, repr(connected_device)))
|
||||||
|
|
||||||
# Confirm/create thumbs archive
|
# Confirm/create thumbs archive
|
||||||
if not os.path.exists(self.cache_dir):
|
if not os.path.exists(self.cache_dir):
|
||||||
@ -3419,7 +3442,7 @@ class ITUNES_ASYNC(ITUNES):
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES_ASYNC.sync_booklists()")
|
logger().info("%s.sync_booklists()" % self.__class__.__name__)
|
||||||
|
|
||||||
# Inform user of any problem books
|
# Inform user of any problem books
|
||||||
if self.problem_titles:
|
if self.problem_titles:
|
||||||
@ -3433,9 +3456,10 @@ class ITUNES_ASYNC(ITUNES):
|
|||||||
'''
|
'''
|
||||||
'''
|
'''
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
logger().info("ITUNES_ASYNC:unmount_device()")
|
logger().info("%s.unmount_device()" % self.__class__.__name__)
|
||||||
self.connected = False
|
self.connected = False
|
||||||
|
|
||||||
|
|
||||||
class BookList(list):
|
class BookList(list):
|
||||||
'''
|
'''
|
||||||
A list of books. Each Book object must have the fields:
|
A list of books. Each Book object must have the fields:
|
||||||
@ -3488,6 +3512,7 @@ class BookList(list):
|
|||||||
'''
|
'''
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
class Book(Metadata):
|
class Book(Metadata):
|
||||||
'''
|
'''
|
||||||
A simple class describing a book in the iTunes Books Library.
|
A simple class describing a book in the iTunes Books Library.
|
||||||
@ -3495,9 +3520,9 @@ class Book(Metadata):
|
|||||||
'''
|
'''
|
||||||
def __init__(self, title, author):
|
def __init__(self, title, author):
|
||||||
Metadata.__init__(self, title, authors=author.split(' & '))
|
Metadata.__init__(self, title, authors=author.split(' & '))
|
||||||
|
self.author = author
|
||||||
self.author_sort = author_to_author_sort(author)
|
self.author_sort = author_to_author_sort(author)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def title_sorter(self):
|
def title_sorter(self):
|
||||||
return title_sort(self.title)
|
return title_sort(self.title)
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ from calibre.customize import CatalogPlugin
|
|||||||
from calibre.library.catalogs import FIELDS
|
from calibre.library.catalogs import FIELDS
|
||||||
from calibre.customize.conversion import DummyReporter
|
from calibre.customize.conversion import DummyReporter
|
||||||
|
|
||||||
|
|
||||||
class CSV_XML(CatalogPlugin):
|
class CSV_XML(CatalogPlugin):
|
||||||
'CSV/XML catalog generator'
|
'CSV/XML catalog generator'
|
||||||
|
|
||||||
@ -227,4 +228,3 @@ class CSV_XML(CatalogPlugin):
|
|||||||
with open(path_to_output, 'w') as f:
|
with open(path_to_output, 'w') as f:
|
||||||
f.write(etree.tostring(root, encoding='utf-8',
|
f.write(etree.tostring(root, encoding='utf-8',
|
||||||
xml_declaration=True, pretty_print=True))
|
xml_declaration=True, pretty_print=True))
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ from calibre.utils.icu import capitalize, collation_order, sort_key
|
|||||||
from calibre.utils.magick.draw import thumbnail
|
from calibre.utils.magick.draw import thumbnail
|
||||||
from calibre.utils.zipfile import ZipFile
|
from calibre.utils.zipfile import ZipFile
|
||||||
|
|
||||||
|
|
||||||
class CatalogBuilder(object):
|
class CatalogBuilder(object):
|
||||||
'''
|
'''
|
||||||
Generates catalog source files from calibre database
|
Generates catalog source files from calibre database
|
||||||
@ -98,7 +99,6 @@ class CatalogBuilder(object):
|
|||||||
else:
|
else:
|
||||||
return ' '
|
return ' '
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, db, _opts, plugin,
|
def __init__(self, db, _opts, plugin,
|
||||||
report_progress=DummyReporter(),
|
report_progress=DummyReporter(),
|
||||||
stylesheet="content/stylesheet.css",
|
stylesheet="content/stylesheet.css",
|
||||||
@ -120,11 +120,13 @@ class CatalogBuilder(object):
|
|||||||
_opts.output_profile and
|
_opts.output_profile and
|
||||||
_opts.output_profile.startswith("kindle")) else False
|
_opts.output_profile.startswith("kindle")) else False
|
||||||
|
|
||||||
|
self.all_series = set()
|
||||||
self.authors = None
|
self.authors = None
|
||||||
self.bookmarked_books = None
|
self.bookmarked_books = None
|
||||||
self.bookmarked_books_by_date_read = None
|
self.bookmarked_books_by_date_read = None
|
||||||
self.books_by_author = None
|
self.books_by_author = None
|
||||||
self.books_by_date_range = None
|
self.books_by_date_range = None
|
||||||
|
self.books_by_description = None
|
||||||
self.books_by_month = None
|
self.books_by_month = None
|
||||||
self.books_by_series = None
|
self.books_by_series = None
|
||||||
self.books_by_title = None
|
self.books_by_title = None
|
||||||
@ -139,6 +141,7 @@ class CatalogBuilder(object):
|
|||||||
if self.opts.generate_genres else None
|
if self.opts.generate_genres else None
|
||||||
self.html_filelist_1 = []
|
self.html_filelist_1 = []
|
||||||
self.html_filelist_2 = []
|
self.html_filelist_2 = []
|
||||||
|
self.individual_authors = None
|
||||||
self.merge_comments_rule = dict(zip(['field', 'position', 'hr'],
|
self.merge_comments_rule = dict(zip(['field', 'position', 'hr'],
|
||||||
_opts.merge_comments_rule.split(':')))
|
_opts.merge_comments_rule.split(':')))
|
||||||
self.ncx_soup = None
|
self.ncx_soup = None
|
||||||
@ -154,6 +157,7 @@ class CatalogBuilder(object):
|
|||||||
self.total_steps = 6.0
|
self.total_steps = 6.0
|
||||||
self.use_series_prefix_in_titles_section = False
|
self.use_series_prefix_in_titles_section = False
|
||||||
|
|
||||||
|
self.dump_custom_fields()
|
||||||
self.books_to_catalog = self.fetch_books_to_catalog()
|
self.books_to_catalog = self.fetch_books_to_catalog()
|
||||||
self.compute_total_steps()
|
self.compute_total_steps()
|
||||||
self.calculate_thumbnail_dimensions()
|
self.calculate_thumbnail_dimensions()
|
||||||
@ -447,7 +451,7 @@ class CatalogBuilder(object):
|
|||||||
hits.remove(amp)
|
hits.remove(amp)
|
||||||
for hit in hits:
|
for hit in hits:
|
||||||
name = hit[1:-1]
|
name = hit[1:-1]
|
||||||
if htmlentitydefs.name2codepoint.has_key(name):
|
if htmlentitydefs.name2codepoint in name:
|
||||||
s = s.replace(hit, unichr(htmlentitydefs.name2codepoint[name]))
|
s = s.replace(hit, unichr(htmlentitydefs.name2codepoint[name]))
|
||||||
s = s.replace(amp, "&")
|
s = s.replace(amp, "&")
|
||||||
return s
|
return s
|
||||||
@ -586,7 +590,7 @@ class CatalogBuilder(object):
|
|||||||
# Literal comparison for Tags field
|
# Literal comparison for Tags field
|
||||||
if rule['field'].lower() == 'tags':
|
if rule['field'].lower() == 'tags':
|
||||||
if rule['pattern'].lower() in map(unicode.lower, record['tags']):
|
if rule['pattern'].lower() in map(unicode.lower, record['tags']):
|
||||||
if self.opts.verbose:
|
if self.DEBUG and self.opts.verbose:
|
||||||
self.opts.log.info(" %s '%s' by %s (%s: Tags includes '%s')" %
|
self.opts.log.info(" %s '%s' by %s (%s: Tags includes '%s')" %
|
||||||
(rule['prefix'], record['title'],
|
(rule['prefix'], record['title'],
|
||||||
record['authors'][0], rule['name'],
|
record['authors'][0], rule['name'],
|
||||||
@ -616,7 +620,7 @@ class CatalogBuilder(object):
|
|||||||
try:
|
try:
|
||||||
if re.search(rule['pattern'], unicode(field_contents),
|
if re.search(rule['pattern'], unicode(field_contents),
|
||||||
re.IGNORECASE) is not None:
|
re.IGNORECASE) is not None:
|
||||||
if self.opts.verbose:
|
if self.DEBUG:
|
||||||
_log_prefix_rule_match_info(rule, record, field_contents)
|
_log_prefix_rule_match_info(rule, record, field_contents)
|
||||||
return rule['prefix']
|
return rule['prefix']
|
||||||
except:
|
except:
|
||||||
@ -624,12 +628,24 @@ class CatalogBuilder(object):
|
|||||||
self.opts.log.error("pattern failed to compile: %s" % rule['pattern'])
|
self.opts.log.error("pattern failed to compile: %s" % rule['pattern'])
|
||||||
pass
|
pass
|
||||||
elif field_contents is None and rule['pattern'] == 'None':
|
elif field_contents is None and rule['pattern'] == 'None':
|
||||||
if self.opts.verbose:
|
if self.DEBUG:
|
||||||
_log_prefix_rule_match_info(rule, record, field_contents)
|
_log_prefix_rule_match_info(rule, record, field_contents)
|
||||||
return rule['prefix']
|
return rule['prefix']
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def dump_custom_fields(self):
|
||||||
|
"""
|
||||||
|
Dump custom field mappings for debugging
|
||||||
|
"""
|
||||||
|
if self.opts.verbose:
|
||||||
|
self.opts.log.info(" Custom fields:")
|
||||||
|
all_custom_fields = self.db.custom_field_keys()
|
||||||
|
for cf in all_custom_fields:
|
||||||
|
self.opts.log.info(" %-20s %-20s %s" %
|
||||||
|
(cf, "'%s'" % self.db.metadata_for_field(cf)['name'],
|
||||||
|
self.db.metadata_for_field(cf)['datatype']))
|
||||||
|
|
||||||
def establish_equivalencies(self, item_list, key=None):
|
def establish_equivalencies(self, item_list, key=None):
|
||||||
""" Return icu equivalent sort letter.
|
""" Return icu equivalent sort letter.
|
||||||
|
|
||||||
@ -716,7 +732,8 @@ class CatalogBuilder(object):
|
|||||||
|
|
||||||
Outputs:
|
Outputs:
|
||||||
books_by_author: database, sorted by author
|
books_by_author: database, sorted by author
|
||||||
authors: list of unique authors
|
authors: list of book authors. Two credited authors are considered an
|
||||||
|
individual entity
|
||||||
error: author_sort mismatches
|
error: author_sort mismatches
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
@ -728,6 +745,12 @@ class CatalogBuilder(object):
|
|||||||
|
|
||||||
books_by_author = list(self.books_to_catalog)
|
books_by_author = list(self.books_to_catalog)
|
||||||
self.detect_author_sort_mismatches(books_by_author)
|
self.detect_author_sort_mismatches(books_by_author)
|
||||||
|
|
||||||
|
# Assumes books_by_title already populated
|
||||||
|
# init books_by_description before relisting multiple authors
|
||||||
|
books_by_description = list(books_by_author) if self.opts.sort_descriptions_by_author \
|
||||||
|
else list(self.books_by_title)
|
||||||
|
|
||||||
if self.opts.cross_reference_authors:
|
if self.opts.cross_reference_authors:
|
||||||
books_by_author = self.relist_multiple_authors(books_by_author)
|
books_by_author = self.relist_multiple_authors(books_by_author)
|
||||||
|
|
||||||
@ -737,6 +760,9 @@ class CatalogBuilder(object):
|
|||||||
asl = [i['author_sort'] for i in books_by_author]
|
asl = [i['author_sort'] for i in books_by_author]
|
||||||
las = max(asl, key=len)
|
las = max(asl, key=len)
|
||||||
|
|
||||||
|
self.books_by_description = sorted(books_by_description,
|
||||||
|
key=lambda x: sort_key(self._kf_books_by_author_sorter_author_sort(x, len(las))))
|
||||||
|
|
||||||
books_by_author = sorted(books_by_author,
|
books_by_author = sorted(books_by_author,
|
||||||
key=lambda x: sort_key(self._kf_books_by_author_sorter_author_sort(x, len(las))))
|
key=lambda x: sort_key(self._kf_books_by_author_sorter_author_sort(x, len(las))))
|
||||||
|
|
||||||
@ -758,6 +784,7 @@ class CatalogBuilder(object):
|
|||||||
current_author = authors[0]
|
current_author = authors[0]
|
||||||
multiple_authors = False
|
multiple_authors = False
|
||||||
unique_authors = []
|
unique_authors = []
|
||||||
|
individual_authors = set()
|
||||||
for (i, author) in enumerate(authors):
|
for (i, author) in enumerate(authors):
|
||||||
if author != current_author:
|
if author != current_author:
|
||||||
# Note that current_author and author are tuples: (friendly, sort)
|
# Note that current_author and author are tuples: (friendly, sort)
|
||||||
@ -780,14 +807,23 @@ class CatalogBuilder(object):
|
|||||||
unique_authors.append((current_author[0], icu_title(current_author[1]),
|
unique_authors.append((current_author[0], icu_title(current_author[1]),
|
||||||
books_by_current_author))
|
books_by_current_author))
|
||||||
|
|
||||||
|
self.authors = list(unique_authors)
|
||||||
|
self.books_by_author = books_by_author
|
||||||
|
|
||||||
|
for ua in unique_authors:
|
||||||
|
for ia in ua[0].replace(' & ', ' & ').split(' & '):
|
||||||
|
individual_authors.add(ia)
|
||||||
|
self.individual_authors = list(individual_authors)
|
||||||
|
|
||||||
if self.DEBUG and self.opts.verbose:
|
if self.DEBUG and self.opts.verbose:
|
||||||
self.opts.log.info("\nfetch_books_by_author(): %d unique authors" % len(unique_authors))
|
self.opts.log.info("\nfetch_books_by_author(): %d unique authors" % len(unique_authors))
|
||||||
for author in unique_authors:
|
for author in unique_authors:
|
||||||
self.opts.log.info((u" %-50s %-25s %2d" % (author[0][0:45], author[1][0:20],
|
self.opts.log.info((u" %-50s %-25s %2d" % (author[0][0:45], author[1][0:20],
|
||||||
author[2])).encode('utf-8'))
|
author[2])).encode('utf-8'))
|
||||||
|
self.opts.log.info("\nfetch_books_by_author(): %d individual authors" % len(individual_authors))
|
||||||
|
for author in sorted(individual_authors):
|
||||||
|
self.opts.log.info("%s" % author)
|
||||||
|
|
||||||
self.authors = unique_authors
|
|
||||||
self.books_by_author = books_by_author
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def fetch_books_by_title(self):
|
def fetch_books_by_title(self):
|
||||||
@ -869,6 +905,7 @@ class CatalogBuilder(object):
|
|||||||
this_title['title'] = self.convert_html_entities(record['title'])
|
this_title['title'] = self.convert_html_entities(record['title'])
|
||||||
if record['series']:
|
if record['series']:
|
||||||
this_title['series'] = record['series']
|
this_title['series'] = record['series']
|
||||||
|
self.all_series.add(this_title['series'])
|
||||||
this_title['series_index'] = record['series_index']
|
this_title['series_index'] = record['series_index']
|
||||||
else:
|
else:
|
||||||
this_title['series'] = None
|
this_title['series'] = None
|
||||||
@ -1000,7 +1037,7 @@ class CatalogBuilder(object):
|
|||||||
data = self.plugin.search_sort_db(self.db, self.opts)
|
data = self.plugin.search_sort_db(self.db, self.opts)
|
||||||
data = self.process_exclusions(data)
|
data = self.process_exclusions(data)
|
||||||
|
|
||||||
if self.opts.verbose and self.prefix_rules:
|
if self.prefix_rules and self.DEBUG:
|
||||||
self.opts.log.info(" Added prefixes:")
|
self.opts.log.info(" Added prefixes:")
|
||||||
|
|
||||||
# Populate this_title{} from data[{},{}]
|
# Populate this_title{} from data[{},{}]
|
||||||
@ -1042,6 +1079,7 @@ class CatalogBuilder(object):
|
|||||||
def initialize(self, save_template):
|
def initialize(self, save_template):
|
||||||
self._save_template = save_template
|
self._save_template = save_template
|
||||||
self.SUPPORTS_SUB_DIRS = True
|
self.SUPPORTS_SUB_DIRS = True
|
||||||
|
|
||||||
def save_template(self):
|
def save_template(self):
|
||||||
return self._save_template
|
return self._save_template
|
||||||
|
|
||||||
@ -2070,7 +2108,6 @@ class CatalogBuilder(object):
|
|||||||
len(genre[key]),
|
len(genre[key]),
|
||||||
'titles' if len(genre[key]) > 1 else 'title'))
|
'titles' if len(genre[key]) > 1 else 'title'))
|
||||||
|
|
||||||
|
|
||||||
# Write the results
|
# Write the results
|
||||||
# genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...]
|
# genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...]
|
||||||
master_genre_list = []
|
master_genre_list = []
|
||||||
@ -2107,7 +2144,8 @@ class CatalogBuilder(object):
|
|||||||
outfile)
|
outfile)
|
||||||
|
|
||||||
tag_file = "content/Genre_%s.html" % genre
|
tag_file = "content/Genre_%s.html" % genre
|
||||||
master_genre_list.append({'tag':genre,
|
master_genre_list.append({
|
||||||
|
'tag': genre,
|
||||||
'file': tag_file,
|
'file': tag_file,
|
||||||
'authors': unique_authors,
|
'authors': unique_authors,
|
||||||
'books': genre_tag_set[genre],
|
'books': genre_tag_set[genre],
|
||||||
@ -2937,10 +2975,8 @@ class CatalogBuilder(object):
|
|||||||
navPointTag.insert(1, contentTag)
|
navPointTag.insert(1, contentTag)
|
||||||
else:
|
else:
|
||||||
# Descriptions only
|
# Descriptions only
|
||||||
sort_descriptions_by = self.books_by_author if self.opts.sort_descriptions_by_author \
|
|
||||||
else self.books_by_title
|
|
||||||
contentTag = Tag(soup, 'content')
|
contentTag = Tag(soup, 'content')
|
||||||
contentTag['src'] = "content/book_%d.html" % int(sort_descriptions_by[0]['id'])
|
contentTag['src'] = "content/book_%d.html" % int(self.books_by_description[0]['id'])
|
||||||
navPointTag.insert(1, contentTag)
|
navPointTag.insert(1, contentTag)
|
||||||
|
|
||||||
if self.generate_for_kindle_mobi:
|
if self.generate_for_kindle_mobi:
|
||||||
@ -2970,9 +3006,6 @@ class CatalogBuilder(object):
|
|||||||
|
|
||||||
self.update_progress_full_step(_("NCX for Descriptions"))
|
self.update_progress_full_step(_("NCX for Descriptions"))
|
||||||
|
|
||||||
sort_descriptions_by = self.books_by_author if self.opts.sort_descriptions_by_author \
|
|
||||||
else self.books_by_title
|
|
||||||
|
|
||||||
# --- Construct the 'Descriptions' section ---
|
# --- Construct the 'Descriptions' section ---
|
||||||
ncx_soup = self.ncx_soup
|
ncx_soup = self.ncx_soup
|
||||||
if self.generate_for_kindle_mobi:
|
if self.generate_for_kindle_mobi:
|
||||||
@ -2990,19 +3023,22 @@ class CatalogBuilder(object):
|
|||||||
self.play_order += 1
|
self.play_order += 1
|
||||||
navLabelTag = Tag(ncx_soup, 'navLabel')
|
navLabelTag = Tag(ncx_soup, 'navLabel')
|
||||||
textTag = Tag(ncx_soup, 'text')
|
textTag = Tag(ncx_soup, 'text')
|
||||||
textTag.insert(0, NavigableString(tocTitle))
|
section_header = '%s [%d]' % (tocTitle, len(self.books_by_description))
|
||||||
|
if self.generate_for_kindle_mobi:
|
||||||
|
section_header = tocTitle
|
||||||
|
textTag.insert(0, NavigableString(section_header))
|
||||||
navLabelTag.insert(0, textTag)
|
navLabelTag.insert(0, textTag)
|
||||||
nptc = 0
|
nptc = 0
|
||||||
navPointTag.insert(nptc, navLabelTag)
|
navPointTag.insert(nptc, navLabelTag)
|
||||||
nptc += 1
|
nptc += 1
|
||||||
contentTag = Tag(ncx_soup, "content")
|
contentTag = Tag(ncx_soup, "content")
|
||||||
contentTag['src'] = "content/book_%d.html" % int(sort_descriptions_by[0]['id'])
|
contentTag['src'] = "content/book_%d.html" % int(self.books_by_description[0]['id'])
|
||||||
navPointTag.insert(nptc, contentTag)
|
navPointTag.insert(nptc, contentTag)
|
||||||
nptc += 1
|
nptc += 1
|
||||||
|
|
||||||
# Loop over the titles
|
# Loop over the titles
|
||||||
|
|
||||||
for book in sort_descriptions_by:
|
for book in self.books_by_description:
|
||||||
navPointVolumeTag = Tag(ncx_soup, 'navPoint')
|
navPointVolumeTag = Tag(ncx_soup, 'navPoint')
|
||||||
if self.generate_for_kindle_mobi:
|
if self.generate_for_kindle_mobi:
|
||||||
navPointVolumeTag['class'] = "article"
|
navPointVolumeTag['class'] = "article"
|
||||||
@ -3119,7 +3155,10 @@ class CatalogBuilder(object):
|
|||||||
self.play_order += 1
|
self.play_order += 1
|
||||||
navLabelTag = Tag(ncx_soup, 'navLabel')
|
navLabelTag = Tag(ncx_soup, 'navLabel')
|
||||||
textTag = Tag(ncx_soup, 'text')
|
textTag = Tag(ncx_soup, 'text')
|
||||||
textTag.insert(0, NavigableString(tocTitle))
|
section_header = '%s [%d]' % (tocTitle, len(self.all_series))
|
||||||
|
if self.generate_for_kindle_mobi:
|
||||||
|
section_header = tocTitle
|
||||||
|
textTag.insert(0, NavigableString(section_header))
|
||||||
navLabelTag.insert(0, textTag)
|
navLabelTag.insert(0, textTag)
|
||||||
nptc = 0
|
nptc = 0
|
||||||
navPointTag.insert(nptc, navLabelTag)
|
navPointTag.insert(nptc, navLabelTag)
|
||||||
@ -3247,7 +3286,10 @@ class CatalogBuilder(object):
|
|||||||
self.play_order += 1
|
self.play_order += 1
|
||||||
navLabelTag = Tag(ncx_soup, 'navLabel')
|
navLabelTag = Tag(ncx_soup, 'navLabel')
|
||||||
textTag = Tag(ncx_soup, 'text')
|
textTag = Tag(ncx_soup, 'text')
|
||||||
textTag.insert(0, NavigableString(tocTitle))
|
section_header = '%s [%d]' % (tocTitle, len(self.books_by_title))
|
||||||
|
if self.generate_for_kindle_mobi:
|
||||||
|
section_header = tocTitle
|
||||||
|
textTag.insert(0, NavigableString(section_header))
|
||||||
navLabelTag.insert(0, textTag)
|
navLabelTag.insert(0, textTag)
|
||||||
nptc = 0
|
nptc = 0
|
||||||
navPointTag.insert(nptc, navLabelTag)
|
navPointTag.insert(nptc, navLabelTag)
|
||||||
@ -3377,7 +3419,10 @@ class CatalogBuilder(object):
|
|||||||
self.play_order += 1
|
self.play_order += 1
|
||||||
navLabelTag = Tag(ncx_soup, 'navLabel')
|
navLabelTag = Tag(ncx_soup, 'navLabel')
|
||||||
textTag = Tag(ncx_soup, 'text')
|
textTag = Tag(ncx_soup, 'text')
|
||||||
textTag.insert(0, NavigableString('%s' % tocTitle))
|
section_header = '%s [%d]' % (tocTitle, len(self.individual_authors))
|
||||||
|
if self.generate_for_kindle_mobi:
|
||||||
|
section_header = tocTitle
|
||||||
|
textTag.insert(0, NavigableString(section_header))
|
||||||
navLabelTag.insert(0, textTag)
|
navLabelTag.insert(0, textTag)
|
||||||
nptc = 0
|
nptc = 0
|
||||||
navPointTag.insert(nptc, navLabelTag)
|
navPointTag.insert(nptc, navLabelTag)
|
||||||
@ -3430,7 +3475,7 @@ class CatalogBuilder(object):
|
|||||||
fmt_string = _(u"Authors beginning with %s")
|
fmt_string = _(u"Authors beginning with %s")
|
||||||
else:
|
else:
|
||||||
fmt_string = _(u"Authors beginning with '%s'")
|
fmt_string = _(u"Authors beginning with '%s'")
|
||||||
textTag.insert(0, NavigableString(fmt_string % (authors_by_letter[1])))
|
textTag.insert(0, NavigableString(fmt_string % authors_by_letter[1]))
|
||||||
navLabelTag.insert(0, textTag)
|
navLabelTag.insert(0, textTag)
|
||||||
navPointByLetterTag.insert(0, navLabelTag)
|
navPointByLetterTag.insert(0, navLabelTag)
|
||||||
contentTag = Tag(ncx_soup, 'content')
|
contentTag = Tag(ncx_soup, 'content')
|
||||||
@ -3808,7 +3853,7 @@ class CatalogBuilder(object):
|
|||||||
self.update_progress_full_step(_("NCX for Genres"))
|
self.update_progress_full_step(_("NCX for Genres"))
|
||||||
|
|
||||||
if not len(self.genres):
|
if not len(self.genres):
|
||||||
self.opts.log.warn(" No genres found in tags.\n"
|
self.opts.log.warn(" No genres found\n"
|
||||||
" No Genre section added to Catalog")
|
" No Genre section added to Catalog")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -3830,8 +3875,10 @@ class CatalogBuilder(object):
|
|||||||
self.play_order += 1
|
self.play_order += 1
|
||||||
navLabelTag = Tag(ncx_soup, 'navLabel')
|
navLabelTag = Tag(ncx_soup, 'navLabel')
|
||||||
textTag = Tag(ncx_soup, 'text')
|
textTag = Tag(ncx_soup, 'text')
|
||||||
# textTag.insert(0, NavigableString('%s (%d)' % (section_title, len(genre_list))))
|
section_header = '%s [%d]' % (tocTitle, len(self.genres))
|
||||||
textTag.insert(0, NavigableString('%s' % tocTitle))
|
if self.generate_for_kindle_mobi:
|
||||||
|
section_header = tocTitle
|
||||||
|
textTag.insert(0, NavigableString(section_header))
|
||||||
navLabelTag.insert(0, textTag)
|
navLabelTag.insert(0, textTag)
|
||||||
nptc = 0
|
nptc = 0
|
||||||
navPointTag.insert(nptc, navLabelTag)
|
navPointTag.insert(nptc, navLabelTag)
|
||||||
@ -3993,7 +4040,6 @@ class CatalogBuilder(object):
|
|||||||
mtc += 1
|
mtc += 1
|
||||||
|
|
||||||
# Write the thumbnail images, descriptions to the manifest
|
# Write the thumbnail images, descriptions to the manifest
|
||||||
sort_descriptions_by = []
|
|
||||||
if self.opts.generate_descriptions:
|
if self.opts.generate_descriptions:
|
||||||
for thumb in self.thumbs:
|
for thumb in self.thumbs:
|
||||||
itemTag = Tag(soup, "item")
|
itemTag = Tag(soup, "item")
|
||||||
@ -4004,9 +4050,6 @@ class CatalogBuilder(object):
|
|||||||
manifest.insert(mtc, itemTag)
|
manifest.insert(mtc, itemTag)
|
||||||
mtc += 1
|
mtc += 1
|
||||||
|
|
||||||
# HTML files - add descriptions to manifest and spine
|
|
||||||
sort_descriptions_by = self.books_by_author if self.opts.sort_descriptions_by_author \
|
|
||||||
else self.books_by_title
|
|
||||||
# Add html_files to manifest and spine
|
# Add html_files to manifest and spine
|
||||||
|
|
||||||
for file in self.html_filelist_1:
|
for file in self.html_filelist_1:
|
||||||
@ -4060,7 +4103,8 @@ class CatalogBuilder(object):
|
|||||||
spine.insert(stc, itemrefTag)
|
spine.insert(stc, itemrefTag)
|
||||||
stc += 1
|
stc += 1
|
||||||
|
|
||||||
for book in sort_descriptions_by:
|
if self.opts.generate_descriptions:
|
||||||
|
for book in self.books_by_description:
|
||||||
# manifest
|
# manifest
|
||||||
itemTag = Tag(soup, "item")
|
itemTag = Tag(soup, "item")
|
||||||
itemTag['href'] = "content/book_%d.html" % int(book['id'])
|
itemTag['href'] = "content/book_%d.html" % int(book['id'])
|
||||||
@ -4286,7 +4330,8 @@ class CatalogBuilder(object):
|
|||||||
f.write(thumb_data)
|
f.write(thumb_data)
|
||||||
|
|
||||||
# Save thumb to archive
|
# Save thumb to archive
|
||||||
if zf is not None: # Ensure that the read succeeded
|
if zf is not None:
|
||||||
|
# Ensure that the read succeeded
|
||||||
# If we failed to open the zip file for reading,
|
# If we failed to open the zip file for reading,
|
||||||
# we dont know if it contained the thumb or not
|
# we dont know if it contained the thumb or not
|
||||||
zf = _open_archive('a')
|
zf = _open_archive('a')
|
||||||
@ -4363,7 +4408,6 @@ class CatalogBuilder(object):
|
|||||||
# Clear the book's cover property
|
# Clear the book's cover property
|
||||||
title['cover'] = None
|
title['cover'] = None
|
||||||
|
|
||||||
|
|
||||||
# Write thumb_width to the file, validating cache contents
|
# Write thumb_width to the file, validating cache contents
|
||||||
# Allows detection of aborted catalog builds
|
# Allows detection of aborted catalog builds
|
||||||
with ZipFile(self.thumbs_path, mode='a') as zfw:
|
with ZipFile(self.thumbs_path, mode='a') as zfw:
|
||||||
@ -4853,5 +4897,3 @@ class CatalogBuilder(object):
|
|||||||
|
|
||||||
outfile = open("%s/%s.ncx" % (self.catalog_path, self.opts.basename), 'w')
|
outfile = open("%s/%s.ncx" % (self.catalog_path, self.opts.basename), 'w')
|
||||||
outfile.write(self.ncx_soup.prettify())
|
outfile.write(self.ncx_soup.prettify())
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user