Update itunes driver for new storage model

This commit is contained in:
Kovid Goyal 2011-06-23 10:38:52 -06:00
commit 1b0f9aacc2

View File

@ -5,7 +5,7 @@ __copyright__ = '2010, Gregory Riker'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import cStringIO, ctypes, datetime, os, re, shutil, subprocess, sys, tempfile, time import cStringIO, ctypes, datetime, os, re, sys, tempfile, time
from calibre.constants import __appname__, __version__, DEBUG from calibre.constants import __appname__, __version__, DEBUG
from calibre import fit_image, confirm_config_name from calibre import fit_image, confirm_config_name
from calibre.constants import isosx, iswindows from calibre.constants import isosx, iswindows
@ -13,8 +13,7 @@ from calibre.devices.errors import OpenFeedback, UserFeedback
from calibre.devices.usbms.deviceconfig import DeviceConfig from calibre.devices.usbms.deviceconfig import DeviceConfig
from calibre.devices.interface import DevicePlugin from calibre.devices.interface import DevicePlugin
from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.ebooks.metadata import authors_to_string, MetaInformation, \ from calibre.ebooks.metadata import authors_to_string, MetaInformation, title_sort
title_sort
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.epub import set_metadata from calibre.ebooks.metadata.epub import set_metadata
from calibre.library.server.utils import strftime from calibre.library.server.utils import strftime
@ -165,8 +164,12 @@ class ITUNES(DriverBase):
settings() settings()
set_progress_reporter() set_progress_reporter()
upload_books() upload_books()
_get_fpath() _remove_existing_copy()
_update_epub_metadata() _remove_from_device()
_remove_from_iTunes()
_add_new_copy()
_add_library_book()
_update_iTunes_metadata()
add_books_to_metadata() add_books_to_metadata()
use_plugboard_ext() use_plugboard_ext()
set_plugboard() set_plugboard()
@ -183,7 +186,7 @@ class ITUNES(DriverBase):
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,0,0) version = (1,1,0)
DISPLAY_DISABLE_DIALOG = "display_disable_apple_driver_dialog" DISPLAY_DISABLE_DIALOG = "display_disable_apple_driver_dialog"
@ -278,7 +281,6 @@ class ITUNES(DriverBase):
description_prefix = "added by calibre" description_prefix = "added by calibre"
ejected = False ejected = False
iTunes= None iTunes= None
iTunes_media = None
library_orphans = None library_orphans = None
log = Log() log = Log()
manual_sync_mode = False manual_sync_mode = False
@ -414,11 +416,11 @@ class ITUNES(DriverBase):
this_book.datetime = parse_date(str(book.date_added())).timetuple() this_book.datetime = parse_date(str(book.date_added())).timetuple()
except: except:
this_book.datetime = time.gmtime() this_book.datetime = time.gmtime()
this_book.db_id = None
this_book.device_collections = [] this_book.device_collections = []
this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None
this_book.size = book.size() this_book.size = book.size()
this_book.uuid = book.composer() this_book.uuid = book.composer()
this_book.cid = None
# Hack to discover if we're running in GUI environment # Hack to discover if we're running in GUI environment
if self.report_progress is not None: if self.report_progress is not None:
this_book.thumbnail = self._generate_thumbnail(this_book.path, book) this_book.thumbnail = self._generate_thumbnail(this_book.path, book)
@ -453,10 +455,10 @@ class ITUNES(DriverBase):
this_book.datetime = parse_date(str(book.DateAdded)).timetuple() this_book.datetime = parse_date(str(book.DateAdded)).timetuple()
except: except:
this_book.datetime = time.gmtime() this_book.datetime = time.gmtime()
this_book.db_id = None
this_book.device_collections = [] this_book.device_collections = []
this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None
this_book.size = book.Size this_book.size = book.Size
this_book.cid = None
# Hack to discover if we're running in GUI environment # Hack to discover if we're running in GUI environment
if self.report_progress is not None: if self.report_progress is not None:
this_book.thumbnail = self._generate_thumbnail(this_book.path, book) this_book.thumbnail = self._generate_thumbnail(this_book.path, book)
@ -492,7 +494,7 @@ class ITUNES(DriverBase):
def can_handle(self, device_info, debug=False): def can_handle(self, device_info, debug=False):
''' '''
Unix version of :method:`can_handle_windows` OSX version of :method:`can_handle_windows`
:param device_info: Is a tupe of (vid, pid, bcd, manufacturer, product, :param device_info: Is a tupe of (vid, pid, bcd, manufacturer, product,
serial number) serial number)
@ -1022,17 +1024,14 @@ class ITUNES(DriverBase):
if DEBUG: if DEBUG:
self.log.info("ITUNES.upload_books()") self.log.info("ITUNES.upload_books()")
self._dump_files(files, header='upload_books()',indent=2)
self._dump_update_list(header='upload_books()',indent=2)
if isosx: if isosx:
for (i,file) in enumerate(files): for (i,fpath) in enumerate(files):
format = file.rpartition('.')[2].lower() format = fpath.rpartition('.')[2].lower()
path = self.path_template % (metadata[i].title, path = self.path_template % (metadata[i].title,
authors_to_string(metadata[i].authors), authors_to_string(metadata[i].authors),
format) format)
self._remove_existing_copy(path, metadata[i]) self._remove_existing_copy(path, metadata[i])
fpath = self._get_fpath(file, metadata[i], format, update_md=True)
db_added, lb_added = self._add_new_copy(fpath, metadata[i]) db_added, lb_added = self._add_new_copy(fpath, metadata[i])
thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added, format) thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added, format)
this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb, format) this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb, format)
@ -1063,13 +1062,12 @@ class ITUNES(DriverBase):
pythoncom.CoInitialize() pythoncom.CoInitialize()
self.iTunes = win32com.client.Dispatch("iTunes.Application") self.iTunes = win32com.client.Dispatch("iTunes.Application")
for (i,file) in enumerate(files): for (i,fpath) in enumerate(files):
format = file.rpartition('.')[2].lower() format = fpath.rpartition('.')[2].lower()
path = self.path_template % (metadata[i].title, path = self.path_template % (metadata[i].title,
authors_to_string(metadata[i].authors), authors_to_string(metadata[i].authors),
format) format)
self._remove_existing_copy(path, metadata[i]) self._remove_existing_copy(path, metadata[i])
fpath = self._get_fpath(file, metadata[i],format, update_md=True)
db_added, lb_added = self._add_new_copy(fpath, metadata[i]) db_added, lb_added = self._add_new_copy(fpath, metadata[i])
if self.manual_sync_mode and not db_added: if self.manual_sync_mode and not db_added:
@ -1276,24 +1274,59 @@ class ITUNES(DriverBase):
def _add_new_copy(self, fpath, metadata): def _add_new_copy(self, fpath, metadata):
''' '''
fp = cached_book['lib_book'].location().path
fp = cached_book['lib_book'].Location
''' '''
if DEBUG: if DEBUG:
self.log.info(" ITUNES._add_new_copy()") self.log.info(" ITUNES._add_new_copy()")
def _save_last_known_iTunes_storage(lb_added):
if isosx:
fp = lb_added.location().path
index = fp.rfind('/Books') + len('/Books')
last_known_iTunes_storage = fp[:index]
elif iswindows:
fp = lb_added.Location
index = fp.rfind('\Books') + len('\Books')
last_known_iTunes_storage = fp[:index]
dynamic['last_known_iTunes_storage'] = last_known_iTunes_storage
self.log.warning(" last_known_iTunes_storage: %s" % last_known_iTunes_storage)
db_added = None db_added = None
lb_added = None lb_added = None
if self.manual_sync_mode: if self.manual_sync_mode:
'''
This is the unsupported direct-connect mode.
In an attempt to avoid resetting the iTunes library Media folder, don't try to
add the book to iTunes if the last_known_iTunes_storage path is inaccessible.
This means that the path has to be set at least once, probably by using
'Connect to iTunes' and doing a transfer.
'''
self.log.warning(" unsupported direct connect mode")
db_added = self._add_device_book(fpath, metadata) db_added = self._add_device_book(fpath, metadata)
if not getattr(fpath, 'deleted_after_upload', False): last_known_iTunes_storage = dynamic.get('last_known_iTunes_storage', None)
lb_added = self._add_library_book(fpath, metadata) if last_known_iTunes_storage is not None:
if lb_added: if os.path.exists(last_known_iTunes_storage):
if DEBUG: if DEBUG:
self.log.info(" file added to Library|Books for iTunes<->iBooks tracking") self.log.warning(" iTunes storage online, adding to library")
lb_added = self._add_library_book(fpath, metadata)
else:
if DEBUG:
self.log.warning(" iTunes storage not online, can't add to library")
if lb_added:
_save_last_known_iTunes_storage(lb_added)
if not lb_added and DEBUG:
self.log.warn(" failed to add '%s' to iTunes, iTunes Media folder inaccessible" % metadata.title)
else: else:
lb_added = self._add_library_book(fpath, metadata) lb_added = self._add_library_book(fpath, metadata)
if DEBUG: if lb_added:
self.log.info(" file added to Library|Books for pending sync") _save_last_known_iTunes_storage(lb_added)
else:
raise UserFeedback("iTunes Media folder inaccessible",
details="Failed to add '%s' to iTunes" % metadata.title,
level=UserFeedback.WARN)
return db_added, lb_added return db_added, lb_added
@ -1308,8 +1341,10 @@ class ITUNES(DriverBase):
if metadata.cover: if metadata.cover:
if format == 'epub': if format == 'epub':
# Pre-shrink cover '''
# self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT Pre-shrink cover
self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT
'''
try: try:
img = PILImage.open(metadata.cover) img = PILImage.open(metadata.cover)
width = img.size[0] width = img.size[0]
@ -1317,8 +1352,8 @@ class ITUNES(DriverBase):
scaled, nwidth, nheight = fit_image(width, height, self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT) scaled, nwidth, nheight = fit_image(width, height, self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT)
if scaled: if scaled:
if DEBUG: if DEBUG:
self.log.info(" '%s' scaled from %sx%s to %sx%s" % self.log.info(" cover scaled from %sx%s to %sx%s" %
(metadata.cover,width,height,nwidth,nheight)) (width,height,nwidth,nheight))
img = img.resize((nwidth, nheight), PILImage.ANTIALIAS) img = img.resize((nwidth, nheight), PILImage.ANTIALIAS)
cd = cStringIO.StringIO() cd = cStringIO.StringIO()
img.convert('RGB').save(cd, 'JPEG') img.convert('RGB').save(cd, 'JPEG')
@ -1337,9 +1372,11 @@ class ITUNES(DriverBase):
return thumb return thumb
if isosx: if isosx:
# The following commands generate an error, but the artwork does in fact '''
# get sent to the device. Seems like a bug in Apple's automation interface? The following commands generate an error, but the artwork does in fact
# Could also be a problem with the integrity of the cover data? get sent to the device. Seems like a bug in Apple's automation interface?
Could also be a problem with the integrity of the cover data?
'''
if lb_added: if lb_added:
try: try:
lb_added.artworks[1].data_.set(cover_data) lb_added.artworks[1].data_.set(cover_data)
@ -1362,9 +1399,8 @@ class ITUNES(DriverBase):
#ipython(user_ns=locals()) #ipython(user_ns=locals())
pass pass
elif iswindows: elif iswindows:
# Write the data to a real file for Windows iTunes ''' Write the data to a real file for Windows iTunes '''
tc = os.path.join(tempfile.gettempdir(), "cover.jpg") 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) tmp_cover.write(cover_data)
@ -1423,7 +1459,8 @@ class ITUNES(DriverBase):
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()
this_book.db_id = None #this_book.cid = metadata.id
this_book.cid = None
this_book.device_collections = [] this_book.device_collections = []
this_book.format = format this_book.format = format
this_book.library_id = lb_added # ??? GR this_book.library_id = lb_added # ??? GR
@ -1431,7 +1468,6 @@ class ITUNES(DriverBase):
this_book.thumbnail = thumb this_book.thumbnail = thumb
this_book.iTunes_id = lb_added # ??? GR this_book.iTunes_id = lb_added # ??? GR
this_book.uuid = metadata.uuid this_book.uuid = metadata.uuid
if isosx: if isosx:
if lb_added: if lb_added:
this_book.size = self._get_device_book_size(fpath, lb_added.size()) this_book.size = self._get_device_book_size(fpath, lb_added.size())
@ -1462,24 +1498,6 @@ class ITUNES(DriverBase):
return this_book return this_book
def _delete_iTunesMetadata_plist(self,fpath):
'''
Delete the plist file from the file to force recache
'''
zf = ZipFile(fpath,'a')
fnames = zf.namelist()
pl_name = 'iTunesMetadata.plist'
try:
plist = [x for x in fnames if pl_name in x][0]
except:
plist = None
if plist:
if DEBUG:
self.log.info(" _delete_iTunesMetadata_plist():")
self.log.info(" deleting '%s'\n from '%s'" % (pl_name,fpath))
zf.delete(pl_name)
zf.close()
def _discover_manual_sync_mode(self, wait=0): def _discover_manual_sync_mode(self, wait=0):
''' '''
Assumes pythoncom for windows Assumes pythoncom for windows
@ -1664,18 +1682,6 @@ class ITUNES(DriverBase):
zf.close() zf.close()
return (title, author, timestamp) return (title, author, timestamp)
def _dump_files(self, files, header=None,indent=0):
if header:
msg = '\n%sfiles passed to %s:' % (' '*indent,header)
self.log.info(msg)
self.log.info( "%s%s" % (' '*indent,'-' * len(msg)))
for file in files:
if getattr(file, 'orig_file_path', None) is not None:
self.log.info(" %s%s" % (' '*indent,file.orig_file_path))
elif getattr(file, 'name', None) is not None:
self.log.info(" %s%s" % (' '*indent,file.name))
self.log.info()
def _dump_hex(self, src, length=16): def _dump_hex(self, src, length=16):
''' '''
''' '''
@ -1699,7 +1705,7 @@ class ITUNES(DriverBase):
self.log.info() self.log.info()
def _dump_update_list(self,header=None,indent=0): def _dump_update_list(self,header=None,indent=0):
if header: if header and self.update_list:
msg = '\n%sself.update_list %s' % (' '*indent,header) msg = '\n%sself.update_list %s' % (' '*indent,header)
self.log.info(msg) self.log.info(msg)
self.log.info( "%s%s" % (' '*indent,'-' * len(msg))) self.log.info( "%s%s" % (' '*indent,'-' * len(msg)))
@ -1718,7 +1724,6 @@ class ITUNES(DriverBase):
(' '*indent, (' '*indent,
ub['title'], ub['title'],
ub['author'])) ub['author']))
self.log.info()
def _find_device_book(self, search): def _find_device_book(self, search):
''' '''
@ -2117,35 +2122,6 @@ class ITUNES(DriverBase):
self.log.error(" no iPad|Books playlist found") self.log.error(" no iPad|Books playlist found")
return pl return pl
def _get_fpath(self,file, metadata, format, update_md=False):
'''
If the database copy will be deleted after upload, we have to
use file (the PersistentTemporaryFile), which will be around until
calibre exits.
If we're using the database copy, delete the plist
'''
if DEBUG:
self.log.info(" ITUNES._get_fpath()")
fpath = file
if not getattr(fpath, 'deleted_after_upload', False):
if getattr(file, 'orig_file_path', None) is not None:
# Database copy
fpath = file.orig_file_path
self._delete_iTunesMetadata_plist(fpath)
elif getattr(file, 'name', None) is not None:
# PTF
fpath = file.name
else:
# Recipe - PTF
if DEBUG:
self.log.info(" file will be deleted after upload")
if format == 'epub' and update_md:
self._update_epub_metadata(fpath, metadata)
return fpath
def _get_library_books(self): def _get_library_books(self):
''' '''
Populate a dict of paths from iTunes Library|Books Populate a dict of paths from iTunes Library|Books
@ -2349,6 +2325,7 @@ class ITUNES(DriverBase):
self.iTunes = appscript.app('iTunes') self.iTunes = appscript.app('iTunes')
self.initial_status = 'already running' self.initial_status = 'already running'
'''
# Read the current storage path for iTunes media # Read the current storage path for iTunes media
cmd = "defaults read com.apple.itunes NSNavLastRootDirectory" cmd = "defaults read com.apple.itunes NSNavLastRootDirectory"
proc = subprocess.Popen( cmd, shell=True, cwd=os.curdir, stdout=subprocess.PIPE) proc = subprocess.Popen( cmd, shell=True, cwd=os.curdir, stdout=subprocess.PIPE)
@ -2359,12 +2336,13 @@ class ITUNES(DriverBase):
else: else:
self.log.error(" could not confirm valid iTunes.media_dir from %s" % 'com.apple.itunes') self.log.error(" could not confirm valid iTunes.media_dir from %s" % 'com.apple.itunes')
self.log.error(" media_dir: %s" % media_dir) self.log.error(" media_dir: %s" % media_dir)
'''
if DEBUG: if DEBUG:
self.log.info(" %s %s" % (__appname__, __version__)) self.log.info(" %s %s" % (__appname__, __version__))
self.log.info(" [OSX %s - %s (%s), driver version %d.%d.%d]" % self.log.info(" [OSX %s - %s (%s), driver version %d.%d.%d]" %
(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.version[0],self.version[1],self.version[2]))
self.log.info(" iTunes_media: %s" % self.iTunes_media)
self.log.info(" calibre_library_path: %s" % self.calibre_library_path) self.log.info(" calibre_library_path: %s" % self.calibre_library_path)
if iswindows: if iswindows:
@ -2404,6 +2382,7 @@ class ITUNES(DriverBase):
' iTunes automation interface non-responsive, ' + ' iTunes automation interface non-responsive, ' +
'recommend reinstalling iTunes') 'recommend reinstalling iTunes')
'''
# Read the current storage path for iTunes media from the XML file # Read the current storage path for iTunes media from the XML file
media_dir = '' media_dir = ''
string = None string = None
@ -2422,13 +2401,13 @@ class ITUNES(DriverBase):
self.log.error(" '%s' not found" % media_dir) self.log.error(" '%s' not found" % media_dir)
else: else:
self.log.error(" no media dir found: string: %s" % string) self.log.error(" no media dir found: string: %s" % string)
'''
if DEBUG: if DEBUG:
self.log.info(" %s %s" % (__appname__, __version__)) self.log.info(" %s %s" % (__appname__, __version__))
self.log.info(" [Windows %s - %s (%s), driver version %d.%d.%d]" % self.log.info(" [Windows %s - %s (%s), driver version %d.%d.%d]" %
(self.iTunes.Windows[0].name, self.iTunes.Version, self.initial_status, (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]))
self.log.info(" iTunes_media: %s" % self.iTunes_media)
self.log.info(" calibre_library_path: %s" % self.calibre_library_path) self.log.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):
@ -2478,13 +2457,14 @@ class ITUNES(DriverBase):
(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'] == authors_to_string(metadata.authors)):
self.update_list.append(self.cached_books[book]) self.update_list.append(self.cached_books[book])
self._remove_from_device(self.cached_books[book])
if DEBUG: if DEBUG:
self.log.info( " deleting device book '%s'" % (metadata.title)) self.log.info( " deleting device book '%s'" % (metadata.title))
if not getattr(file, 'deleted_after_upload', False): self._remove_from_device(self.cached_books[book])
self._remove_from_iTunes(self.cached_books[book])
if DEBUG: if DEBUG:
self.log.info(" deleting library book '%s'" % metadata.title) self.log.info(" deleting library book '%s'" % metadata.title)
self._remove_from_iTunes(self.cached_books[book])
break break
else: else:
if DEBUG: if DEBUG:
@ -2497,9 +2477,9 @@ class ITUNES(DriverBase):
(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'] == authors_to_string(metadata.authors)):
self.update_list.append(self.cached_books[book]) self.update_list.append(self.cached_books[book])
self._remove_from_iTunes(self.cached_books[book])
if DEBUG: if DEBUG:
self.log.info( " deleting library book '%s'" % metadata.title) self.log.info( " deleting library book '%s'" % metadata.title)
self._remove_from_iTunes(self.cached_books[book])
break break
else: else:
if DEBUG: if DEBUG:
@ -2530,96 +2510,105 @@ class ITUNES(DriverBase):
def _remove_from_iTunes(self, cached_book): def _remove_from_iTunes(self, cached_book):
''' '''
iTunes does not delete books from storage when removing from database iTunes does not delete books from storage when removing from database via automation
We only want to delete stored copies if the file is stored in iTunes
We don't want to delete files stored outside of iTunes.
Also confirm that storage_path does not point into calibre's storage.
''' '''
if DEBUG: if DEBUG:
self.log.info(" ITUNES._remove_from_iTunes():") self.log.info(" ITUNES._remove_from_iTunes():")
if isosx: if isosx:
''' Manually remove the book from iTunes storage '''
try: try:
storage_path = os.path.split(cached_book['lib_book'].location().path) fp = cached_book['lib_book'].location().path
if cached_book['lib_book'].location().path.startswith(self.iTunes_media) and \ if DEBUG:
not storage_path[0].startswith(prefs['library_path']): self.log.info(" processing %s" % fp)
title_storage_path = storage_path[0] if fp.startswith(prefs['library_path']):
if DEBUG: self.log.info(" '%s' stored in calibre database, not removed" % cached_book['title'])
self.log.info(" removing title_storage_path: %s" % title_storage_path)
try:
shutil.rmtree(title_storage_path)
except:
self.log.info(" '%s' not empty" % title_storage_path)
# Clean up title/author directories
author_storage_path = os.path.split(title_storage_path)[0]
self.log.info(" author_storage_path: %s" % author_storage_path)
author_files = os.listdir(author_storage_path)
if '.DS_Store' in author_files:
author_files.pop(author_files.index('.DS_Store'))
if not author_files:
shutil.rmtree(author_storage_path)
if DEBUG:
self.log.info(" removing empty author_storage_path")
else:
if DEBUG:
self.log.info(" author_storage_path not empty (%d objects):" % len(author_files))
self.log.info(" %s" % '\n'.join(author_files))
else: else:
self.log.info(" '%s' (stored external to iTunes, no files deleted)" % cached_book['title']) if os.path.exists(fp):
os.remove(fp)
if DEBUG:
self.log.info(" deleting from iTunes storage")
author_storage_path = os.path.split(fp)[0]
try:
os.rmdir(author_storage_path)
if DEBUG:
self.log.info(" removing empty author directory")
except:
author_files = os.listdir(author_storage_path)
if '.DS_Store' in author_files:
author_files.pop(author_files.index('.DS_Store'))
if not author_files:
os.rmdir(author_storage_path)
if DEBUG:
self.log.info(" removing empty author directory")
'''
else:
if DEBUG:
self.log.info(" author_storage_path not empty:")
self.log.info(" %s" % '\n'.join(author_files))
'''
else:
self.log.info(" '%s' does not exist at storage location" % cached_book['title'])
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:
self.log.info(" '%s' not in iTunes storage" % cached_book['title']) self.log.info(" '%s' not found in iTunes storage" % cached_book['title'])
# Delete the book from the iTunes database
try: try:
self.iTunes.delete(cached_book['lib_book']) self.iTunes.delete(cached_book['lib_book'])
if DEBUG:
self.log.info(" removing from iTunes database")
except: except:
if DEBUG: if DEBUG:
self.log.info(" unable to remove '%s' from iTunes" % cached_book['title']) self.log.info(" unable to remove from iTunes database")
elif iswindows: elif iswindows:
''' '''
Assume we're wrapped in a pythoncom Assume we're wrapped in a pythoncom
Windows stores the book under a common author directory, so we just delete the .epub Windows stores the book under a common author directory, so we just delete the .epub
''' '''
fp = None
try: try:
book = cached_book['lib_book'] book = cached_book['lib_book']
path = book.Location fp = book.Location
except: except:
book = self._find_library_book(cached_book) book = self._find_library_book(cached_book)
if book: if book:
path = book.Location fp = book.Location
if book: if book:
if self.iTunes_media and path.startswith(self.iTunes_media) and \ if DEBUG:
not path.startswith(prefs['library_path']): self.log.info(" processing %s" % fp)
storage_path = os.path.split(path) if fp.startswith(prefs['library_path']):
if DEBUG: self.log.info(" '%s' stored in calibre database, not removed" % cached_book['title'])
self.log.info(" removing '%s' at %s" %
(cached_book['title'], path))
try:
os.remove(path)
except:
self.log.warning(" '%s' not in iTunes storage" % path)
try:
os.rmdir(storage_path[0])
self.log.info(" removed folder '%s'" % storage_path[0])
except:
self.log.info(" folder '%s' not found or not empty" % storage_path[0])
# Delete from iTunes database
else: else:
self.log.info(" '%s' (stored external to iTunes, no files deleted)" % cached_book['title']) if os.path.exists(fp):
os.remove(fp)
if DEBUG:
self.log.info(" deleting from iTunes storage")
author_storage_path = os.path.split(fp)[0]
try:
os.rmdir(author_storage_path)
if DEBUG:
self.log.info(" removing empty author directory")
except:
pass
else:
self.log.info(" '%s' does not exist at storage location" % cached_book['title'])
else: else:
if DEBUG: if DEBUG:
self.log.info(" '%s' not found in iTunes" % cached_book['title']) self.log.info(" '%s' not found in iTunes storage" % cached_book['title'])
# Delete the book from the iTunes database
try: try:
book.Delete() book.Delete()
if DEBUG:
self.log.info(" removing from iTunes database")
except: except:
if DEBUG: if DEBUG:
self.log.info(" unable to remove '%s' from iTunes" % cached_book['title']) self.log.info(" unable to remove from iTunes database")
def title_sorter(self, title): def title_sorter(self, title):
return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', title).rstrip() return re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', title).rstrip()
@ -2798,7 +2787,7 @@ class ITUNES(DriverBase):
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:
self.log.info(" ITUNES._update_iTunes_metadata()") self.log.info(" ITUNES._update_iTunes_metadata()")
self.log.info(" using Series name as Genre") self.log.info(" using Series name '%s' as Genre" % metadata_x.series)
# Format the index as a sort key # Format the index as a sort key
index = metadata_x.series_index index = metadata_x.series_index
@ -2978,8 +2967,8 @@ class ITUNES(DriverBase):
newmi = book.deepcopy_metadata() newmi = book.deepcopy_metadata()
newmi.template_to_attribute(book, pb) newmi.template_to_attribute(book, pb)
if pb is not None and DEBUG: if pb is not None and DEBUG:
self.log.info(" transforming %s using %s:" % (format, pb)) #self.log.info(" transforming %s using %s:" % (format, pb))
self.log.info(" title: %s %s" % (book.title, ">>> %s" % self.log.info(" title: '%s' %s" % (book.title, ">>> '%s'" %
newmi.title if book.title != newmi.title else '')) newmi.title if book.title != newmi.title else ''))
self.log.info(" title_sort: %s %s" % (book.title_sort, ">>> %s" % self.log.info(" title_sort: %s %s" % (book.title_sort, ">>> %s" %
newmi.title_sort if book.title_sort != newmi.title_sort else '')) newmi.title_sort if book.title_sort != newmi.title_sort else ''))
@ -3083,12 +3072,12 @@ class ITUNES_ASYNC(ITUNES):
this_book.datetime = parse_date(str(library_books[book].date_added())).timetuple() this_book.datetime = parse_date(str(library_books[book].date_added())).timetuple()
except: except:
this_book.datetime = time.gmtime() this_book.datetime = time.gmtime()
this_book.db_id = None
this_book.device_collections = [] this_book.device_collections = []
#this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None #this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None
this_book.library_id = library_books[book] this_book.library_id = library_books[book]
this_book.size = library_books[book].size() this_book.size = library_books[book].size()
this_book.uuid = library_books[book].composer() this_book.uuid = library_books[book].composer()
this_book.cid = None
# Hack to discover if we're running in GUI environment # Hack to discover if we're running in GUI environment
if self.report_progress is not None: if self.report_progress is not None:
this_book.thumbnail = self._generate_thumbnail(this_book.path, library_books[book]) this_book.thumbnail = self._generate_thumbnail(this_book.path, library_books[book])
@ -3124,11 +3113,11 @@ class ITUNES_ASYNC(ITUNES):
this_book.datetime = parse_date(str(library_books[book].DateAdded)).timetuple() this_book.datetime = parse_date(str(library_books[book].DateAdded)).timetuple()
except: except:
this_book.datetime = time.gmtime() this_book.datetime = time.gmtime()
this_book.db_id = None
this_book.device_collections = [] this_book.device_collections = []
this_book.library_id = library_books[book] this_book.library_id = library_books[book]
this_book.size = library_books[book].Size this_book.size = library_books[book].Size
this_book.uuid = library_books[book].Composer this_book.uuid = library_books[book].Composer
this_book.cid = None
# Hack to discover if we're running in GUI environment # Hack to discover if we're running in GUI environment
if self.report_progress is not None: if self.report_progress is not None:
this_book.thumbnail = self._generate_thumbnail(this_book.path, library_books[book]) this_book.thumbnail = self._generate_thumbnail(this_book.path, library_books[book])