GwR wip, KG updates

This commit is contained in:
GRiker 2010-06-16 06:32:16 -06:00
commit 4231c14659
2 changed files with 269 additions and 215 deletions

View File

@ -14,6 +14,7 @@ from calibre.devices.errors import UserFeedback
from calibre.devices.interface import DevicePlugin from calibre.devices.interface import DevicePlugin
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata.epub import set_metadata
from calibre.library.server.utils import strftime from calibre.library.server.utils import strftime
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import Config, config_dir from calibre.utils.config import Config, config_dir
@ -179,43 +180,52 @@ class ITUNES(DevicePlugin):
# Delete any obsolete copies of the book from the booklist # Delete any obsolete copies of the book from the booklist
if self.update_list: if self.update_list:
if isosx: if DEBUG:
self.log.info("ITUNES.add_books_to_metadata()")
self._dump_booklist(booklists[0], header='before',indent=2)
self._dump_update_list(header='before',indent=2)
self._dump_cached_books(header='before',indent=2)
for (j,p_book) in enumerate(self.update_list):
if DEBUG: if DEBUG:
self.log.info( "ITUNES.add_books_to_metadata()") if isosx:
self._dump_update_list('add_books_to_metadata()') self.log.info(" looking for %s" %
for (j,p_book) in enumerate(self.update_list): str(p_book['lib_book'])[-9:])
self.log.info("ITUNES.add_books_to_metadata():\n looking for %s" % elif iswindows:
str(p_book['lib_book'])[-9:]) self.log.info(" looking for '%s' by %s (%s)" %
for i,bl_book in enumerate(booklists[0]): (p_book['title'],p_book['author'], p_book['uuid']))
if bl_book.library_id == p_book['lib_book']:
booklists[0].pop(i) # Purge the booklist, self.cached_books
self.log.info("ITUNES.add_books_to_metadata():\n removing %s %s" % for i,bl_book in enumerate(booklists[0]):
(p_book['title'], str(p_book['lib_book'])[-9:])) if bl_book.uuid == p_book['uuid']:
# Remove from booklists[0]
booklists[0].pop(i)
if DEBUG:
if isosx:
self.log.info(" removing %s %s from booklists[0]" %
(p_book['title'], str(p_book['lib_book'])[-9:]))
elif iswindows:
self.log.info(" removing '%s' from booklists[0]" %
(p_book['title']))
# If >1 matching uuid, remove old title
matching_uuids = 0
for cb in self.cached_books:
if self.cached_books[cb]['uuid'] == p_book['uuid']:
matching_uuids += 1
if matching_uuids > 1:
for cb in self.cached_books:
if self.cached_books[cb]['uuid'] == p_book['uuid']:
if self.cached_books[cb]['title'] == p_book['title'] and \
self.cached_books[cb]['author'] == p_book['author']:
if DEBUG:
self._dump_cached_book(self.cached_books[cb],header="removing from self.cached_books:", indent=2)
self.cached_books.pop(cb)
break
break break
else: if self.report_progress is not None:
self.log.error(" update_list item '%s' by %s %s not found in booklists[0]" % self.report_progress(j+1/task_count, _('Updating device metadata listing...'))
(p_book['title'], p_book['author'],str(p_book['lib_book'])[-9:]))
if self.report_progress is not None:
self.report_progress(j+1/task_count, _('Updating device metadata listing...'))
elif iswindows:
if DEBUG:
self.log.info("ITUNES.add_books_to_metadata()")
for (j,p_book) in enumerate(self.update_list):
#self.log.info(" looking for '%s' by %s" % (p_book['title'],p_book['author']))
for i,bl_book in enumerate(booklists[0]):
#self.log.info(" evaluating '%s' by %s" % (bl_book.title,bl_book.author[0]))
if bl_book.title == p_book['title'] and \
bl_book.author[0] == p_book['author']:
booklists[0].pop(i)
self.log.info(" removing outdated version of '%s'" % p_book['title'])
break
else:
self.log.error(" update_list item '%s' not found in booklists[0]" % p_book['title'])
if self.report_progress is not None:
self.report_progress(j+1/task_count, _('Updating device metadata listing...'))
if self.report_progress is not None: if self.report_progress is not None:
self.report_progress(1.0, _('Updating device metadata listing...')) self.report_progress(1.0, _('Updating device metadata listing...'))
@ -223,10 +233,16 @@ class ITUNES(DevicePlugin):
# Add new books to booklists[0] # Add new books to booklists[0]
for new_book in locations[0]: for new_book in locations[0]:
if DEBUG: if DEBUG:
self.log.info(" adding '%s' by '%s' to booklists[0]" % self.log.info(" adding '%s' by '%s' to booklists[0]" %
(new_book.title, new_book.author)) (new_book.title, new_book.author))
booklists[0].append(new_book) booklists[0].append(new_book)
self.update_list = []
if DEBUG:
self._dump_booklist(booklists[0],header='after',indent=2)
self._dump_cached_books(header='after',indent=2)
def books(self, oncard=None, end_session=True): def books(self, oncard=None, end_session=True):
""" """
Return a list of ebooks on the device. Return a list of ebooks on the device.
@ -268,6 +284,7 @@ class ITUNES(DevicePlugin):
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.album()
# 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)
@ -279,7 +296,8 @@ class ITUNES(DevicePlugin):
'title':book.name(), 'title':book.name(),
'author':[book.artist()], 'author':[book.artist()],
'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.album()
} }
if self.report_progress is not None: if self.report_progress is not None:
@ -314,7 +332,8 @@ class ITUNES(DevicePlugin):
cached_books[this_book.path] = { cached_books[this_book.path] = {
'title':book.Name, 'title':book.Name,
'author':book.Artist, 'author':book.Artist,
'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.Album
} }
if self.report_progress is not None: if self.report_progress is not None:
@ -656,12 +675,13 @@ class ITUNES(DevicePlugin):
if DEBUG: if DEBUG:
self.log.info("ITUNES.remove_books_from_metadata()") self.log.info("ITUNES.remove_books_from_metadata()")
for path in paths: for path in paths:
self._dump_cached_book(self.cached_books[path]) self._dump_cached_book(self.cached_books[path], indent=2)
if self.cached_books[path]['lib_book']: if self.cached_books[path]['lib_book']:
# Remove from the booklist # Remove from the booklist
for i,book in enumerate(booklists[0]): for i,book in enumerate(booklists[0]):
if book.path == path: print "book.uuid: %s" % book.uuid
self.log.info(" removing '%s' from calibre booklist, index: %d" % (path, i)) print "self.cached_books[path]['uuid']: %s" % self.cached_books[path]['uuid']
if book.uuid == self.cached_books[path]['uuid']:
booklists[0].pop(i) booklists[0].pop(i)
break break
else: else:
@ -671,7 +691,8 @@ class ITUNES(DevicePlugin):
self.cached_books.pop(path) self.cached_books.pop(path)
if DEBUG: if DEBUG:
self.log.info(" removing '%s' from self.cached_books" % path) self.log.info(" removing '%s' from self.cached_books" % path)
# self._dump_cached_books('remove_books_from_metadata()') self._dump_cached_books(header='remove_books_from_metadata()',indent=2)
self._dump_booklist(booklists[0], header='remove_books_from_metadata()',indent=2)
else: else:
self.log.warning(" skipping purchased book, can't remove via automation interface") self.log.warning(" skipping purchased book, can't remove via automation interface")
@ -716,13 +737,10 @@ class ITUNES(DevicePlugin):
(L{books}(oncard=None), L{books}(oncard='carda'), (L{books}(oncard=None), L{books}(oncard='carda'),
L{books}(oncard='cardb')). L{books}(oncard='cardb')).
''' '''
if DEBUG:
self.log.info("ITUNES:sync_booklists()")
if self.update_needed: if self.update_needed:
if DEBUG: if DEBUG:
self.log.info(' calling _update_device') self.log.info(' calling _update_device')
self._update_device(msg=self.update_msg, wait=False) self._update_device(msg=self.update_msg, wait=False)
self.update_list = []
self.update_needed = False self.update_needed = False
# Inform user of any problem books # Inform user of any problem books
@ -782,28 +800,27 @@ class ITUNES(DevicePlugin):
if DEBUG: if DEBUG:
self.log.info("ITUNES.upload_books()") self.log.info("ITUNES.upload_books()")
self._dump_files(files, header='upload_books()') self._dump_files(files, header='upload_books()',indent=2)
# self._dump_cached_books('upload_books()') self._dump_update_list(header='upload_books()',indent=2)
self._dump_update_list('upload_books()')
if isosx: if isosx:
for (i,file) in enumerate(files): for (i,file) in enumerate(files):
path = self.path_template % (metadata[i].title, metadata[i].author[0]) path = self.path_template % (metadata[i].title, metadata[i].author[0])
self._remove_existing_copies(path,file,metadata[i]) self._remove_existing_copies(path, metadata[i])
fpath = self._get_fpath(file, touch=True) fpath = self._get_fpath(file, metadata[i], 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], lb_added, db_added) thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added)
this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb) this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb)
new_booklist.append(this_book) new_booklist.append(this_book)
self._update_iTunes_metadata(metadata[i], db_added, lb_added, this_book) self._update_iTunes_metadata(metadata[i], db_added, lb_added, this_book)
# Add new_book to self.cached_paths # Add new_book to self.cached_paths
self.cached_books[this_book.path] = { self.cached_books[this_book.path] = {
'title': metadata[i].title, 'title': metadata[i].title,
'author': metadata[i].author[0], 'author': metadata[i].author,
'lib_book': lb_added, 'lib_book': lb_added,
'dev_book': db_added } 'dev_book': db_added,
self._dump_cached_books(header="after upload_books()") 'uuid': metadata[i].uuid}
# Report progress # Report progress
if self.report_progress is not None: if self.report_progress is not None:
@ -816,14 +833,13 @@ class ITUNES(DevicePlugin):
for (i,file) in enumerate(files): for (i,file) in enumerate(files):
path = self.path_template % (metadata[i].title, metadata[i].author[0]) path = self.path_template % (metadata[i].title, metadata[i].author[0])
self._remove_existing_copies(path,file,metadata[i]) self._remove_existing_copies(path, metadata[i])
fpath = self._get_fpath(file, touch=True) fpath = self._get_fpath(file, metadata[i], 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:
# Problem finding added book, probably title/author change needing to be written to metadata # Problem finding added book, probably title/author change needing to be written to metadata
self.problem_msg = ("Title and/or author metadata mismatch with uploaded books.\n" self.problem_msg = ("Title and/or author metadata mismatch with uploaded books.\n"
"Convert epub - epub to update edited metadata before uploading.\n"
"Click 'Show Details...' for affected books.") "Click 'Show Details...' for affected books.")
self.problem_titles.append("'%s' by %s" % (metadata[i].title, metadata[i].author[0])) self.problem_titles.append("'%s' by %s" % (metadata[i].title, metadata[i].author[0]))
@ -834,10 +850,11 @@ class ITUNES(DevicePlugin):
# Add new_book to self.cached_paths # Add new_book to self.cached_paths
self.cached_books[this_book.path] = { self.cached_books[this_book.path] = {
'title': metadata[i].title, 'title': metadata[i].title,
'author': metadata[i].author[0], 'author': metadata[i].author[0],
'lib_book': lb_added, 'lib_book': lb_added,
'dev_book': db_added } 'dev_book': db_added,
'uuid': metadata[i].uuid}
# Report progress # Report progress
if self.report_progress is not None: if self.report_progress is not None:
@ -853,6 +870,9 @@ class ITUNES(DevicePlugin):
self.update_needed = True self.update_needed = True
self.update_msg = "Added books to device" self.update_msg = "Added books to device"
if DEBUG:
self._dump_booklist(new_booklist,header="after upload_books()",indent=2)
self._dump_cached_books(header="after upload_books()",indent=2)
return (new_booklist, [], []) return (new_booklist, [], [])
@ -871,12 +891,12 @@ class ITUNES(DevicePlugin):
break break
else: else:
if DEBUG: if DEBUG:
self.log.error(" Device|Books playlist not found") self.log.error(" Device|Books playlist not found")
# Add the passed book to the Device|Books playlist # Add the passed book to the Device|Books playlist
added = pl.add(appscript.mactypes.File(fpath),to=pl) added = pl.add(appscript.mactypes.File(fpath),to=pl)
if DEBUG: if DEBUG:
self.log.info(" adding '%s' to device" % fpath) self.log.info(" adding '%s' to device" % fpath)
return added return added
elif iswindows: elif iswindows:
@ -913,7 +933,7 @@ class ITUNES(DevicePlugin):
sys.stdout.write("\n") sys.stdout.write("\n")
sys.stdout.flush() sys.stdout.flush()
# This doesn't seem to work with device, just Library # This doesn't seem to work with Device, just Library
if False: if False:
if DEBUG: if DEBUG:
sys.stdout.write(" waiting for handle to added '%s' ..." % metadata.title) sys.stdout.write(" waiting for handle to added '%s' ..." % metadata.title)
@ -932,17 +952,10 @@ class ITUNES(DevicePlugin):
# Try the calibre metadata first # Try the calibre metadata first
db_added = self._find_device_book( db_added = self._find_device_book(
{'title': metadata.title, {'title': metadata.title,
'author': metadata.authors[0], 'author': metadata.authors[0],
'source': 'calibre'}) 'uuid': metadata.uuid})
# If that fails, try the epub metadata
if not db_added:
title, author = self._get_epub_metadata(fpath)
db_added = self._find_device_book(
{'title': title,
'author': author,
'source': 'epub'})
return db_added return db_added
def _add_library_book(self,file, metadata): def _add_library_book(self,file, metadata):
@ -1020,7 +1033,7 @@ class ITUNES(DevicePlugin):
return db_added, lb_added return db_added, lb_added
def _cover_to_thumb(self, path, metadata, lb_added, db_added): def _cover_to_thumb(self, path, metadata, db_added, lb_added):
''' '''
assumes pythoncom wrapper for db_added assumes pythoncom wrapper for db_added
''' '''
@ -1099,6 +1112,7 @@ class ITUNES(DevicePlugin):
this_book.path = path this_book.path = path
this_book.thumbnail = thumb this_book.thumbnail = thumb
this_book.iTunes_id = lb_added this_book.iTunes_id = lb_added
this_book.uuid = metadata.uuid
if isosx: if isosx:
if lb_added: if lb_added:
@ -1191,8 +1205,6 @@ class ITUNES(DevicePlugin):
self.manual_sync_mode = True self.manual_sync_mode = True
except: except:
self.manual_sync_mode = False self.manual_sync_mode = False
if DEBUG:
self.log.info(" iTunes.manual_sync_mode: %s" % self.manual_sync_mode)
else: else:
if DEBUG: if DEBUG:
self.log.info(" sending tracer to empty Books|Playlist") self.log.info(" sending tracer to empty Books|Playlist")
@ -1206,74 +1218,79 @@ class ITUNES(DevicePlugin):
except: except:
self.manual_sync_mode = False self.manual_sync_mode = False
self.log.info(" iTunes.manual_sync_mode: %s" % self.manual_sync_mode) self.log.info(" iTunes.manual_sync_mode: %s" % self.manual_sync_mode)
def _dump_booklist(self, booklist, header=None): def _dump_booklist(self, booklist, header=None,indent=0):
''' '''
''' '''
if header: if header:
msg = '\nbooklist, %s' % header msg = '\n%sbooklist %s:' % (' '*indent,header)
self.log.info(msg) self.log.info(msg)
self.log.info('%s' % ('-' * len(msg))) self.log.info('%s%s' % (' '*indent,'-' * len(msg)))
for book in booklist: for book in booklist:
if isosx: if isosx:
self.log.info("%-40.40s %-30.30s %-10.10s" % self.log.info("%s%-40.40s %-30.30s %-10.10s" %
(book.title, book.author, str(book.library_id)[-9:])) (' '*indent,book.title, book.author, str(book.library_id)[-9:]))
elif iswindows: elif iswindows:
self.log.info("%-40.40s %-30.30s" % self.log.info("%s%-40.40s %-30.30s" %
(book.title, book.author)) (' '*indent,book.title, book.author))
self.log.info()
def _dump_cached_book(self, cached_book, header=None): def _dump_cached_book(self, cached_book, header=None,indent=0):
''' '''
''' '''
if header: if header:
msg = '%s' % header msg = '%s%s' % (' '*indent,header)
self.log.info(msg) self.log.info(msg)
self.log.info( "%s" % ('-' * len(msg))) self.log.info( "%s%s" % (' '*indent, '-' * len(msg)))
if isosx: if isosx:
self.log.info("%-40.40s %-30.30s %-10.10s %-10.10s" % self.log.info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" %
('title', (' '*indent,
'title',
'author', 'author',
'lib_book', 'lib_book',
'dev_book')) 'dev_book',
self.log.info("%-40.40s %-30.30s %-10.10s %-10.10s" % 'uuid'))
(cached_book['title'], self.log.info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" %
(' '*indent,
cached_book['title'],
cached_book['author'], cached_book['author'],
str(cached_book['lib_book'])[-9:], str(cached_book['lib_book'])[-9:],
str(cached_book['dev_book'])[-9:])) str(cached_book['dev_book'])[-9:],
cached_book['uuid']))
elif iswindows: elif iswindows:
self.log.info("%-40.40s %-30.30s" % self.log.info("%s%-40.40s %-30.30s %s" %
(cached_book['title'], (' '*indent,
cached_book['author'])) cached_book['title'],
cached_book['author'],
cached_book['uuid']))
self.log.info() self.log.info()
def _dump_cached_books(self, header=None): def _dump_cached_books(self, header=None, indent=0):
''' '''
''' '''
if header: if header:
msg = '\nself.cached_books, %s' % header msg = '\n%sself.cached_books %s:' % (' '*indent,header)
self.log.info(msg) self.log.info(msg)
self.log.info( "%s" % ('-' * len(msg))) self.log.info( "%s%s" % (' '*indent,'-' * len(msg)))
if isosx: if isosx:
self.log.info("%-40.40s %-30.30s %-10.10s %-10.10s" %
('title',
'author',
'lib_book',
'dev_book'))
for cb in self.cached_books.keys(): for cb in self.cached_books.keys():
self.log.info("%-40.40s %-30.30s %-10.10s %-10.10s" % self.log.info("%s%-40.40s %-30.30s %-10.10s %-10.10s %s" %
(self.cached_books[cb]['title'], (' '*indent,
self.cached_books[cb]['title'],
self.cached_books[cb]['author'], self.cached_books[cb]['author'],
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():
self.log.info("%-40.40s %-30.30s" % self.log.info("%s%-40.40s %-30.30s %s" %
(self.cached_books[cb]['title'], (' '*indent,
self.cached_books[cb]['author'])) self.cached_books[cb]['title'],
self.cached_books[cb]['author'],
self.cached_books[cb]['uuid']))
self.log.info() self.log.info()
@ -1290,16 +1307,16 @@ class ITUNES(DevicePlugin):
N+=length N+=length
print result print result
def _dump_files(self, files, header=None): def _dump_files(self, files, header=None,indent=0):
if header: if header:
msg = '\nfiles passed to %s:' % header msg = '\n%sfiles passed to %s:' % (' '*indent,header)
self.log.info(msg) self.log.info(msg)
self.log.info( "%s" % ('-' * len(msg))) self.log.info( "%s%s" % (' '*indent,'-' * len(msg)))
for file in files: for file in files:
if getattr(file, 'orig_file_path', None) is not None: if getattr(file, 'orig_file_path', None) is not None:
self.log.info(" %s" % file.orig_file_path) self.log.info(" %s%s" % (' '*indent,file.orig_file_path))
elif getattr(file, 'name', None) is not None: elif getattr(file, 'name', None) is not None:
self.log.info(" %s" % file.name) self.log.info(" %s%s" % (' '*indent,file.name))
self.log.info() self.log.info()
def _dump_library_books(self, library_books): def _dump_library_books(self, library_books):
@ -1311,22 +1328,24 @@ class ITUNES(DevicePlugin):
self.log.info(" %s" % book) self.log.info(" %s" % book)
self.log.info() self.log.info()
def _dump_update_list(self,header=None): def _dump_update_list(self,header=None,indent=0):
if header: if header:
msg = '\nself.update_list called from %s' % header msg = '\n%sself.update_list %s' % (' '*indent,header)
self.log.info(msg) self.log.info(msg)
self.log.info( "%s" % ('-' * len(msg))) self.log.info( "%s%s" % (' '*indent,'-' * len(msg)))
if isosx: if isosx:
for ub in self.update_list: for ub in self.update_list:
self.log.info("%-40.40s %-30.30s %-10.10s" % self.log.info("%s%-40.40s %-30.30s %-10.10s" %
(ub['title'], (' '*indent,
ub['title'],
ub['author'], ub['author'],
str(ub['lib_book'])[-9:])) str(ub['lib_book'])[-9:]))
elif iswindows: elif iswindows:
for ub in self.update_list: for ub in self.update_list:
self.log.info("%-40.40s %-30.30s" % self.log.info("%s%-40.40s %-30.30s" %
(ub['title'], (' '*indent,
ub['title'],
ub['author'])) ub['author']))
self.log.info() self.log.info()
@ -1335,29 +1354,42 @@ class ITUNES(DevicePlugin):
Windows-only method to get a handle to device book in the current pythoncom session Windows-only method to get a handle to device book in the current pythoncom session
''' '''
if iswindows: if iswindows:
if DEBUG:
self.log.info(" ITUNES._find_device_book()")
self.log.info(" searching for '%s' by %s (%s metadata)" %
(search['title'], search['author'], search['source']))
dev_books = self._get_device_books_playlist() dev_books = self._get_device_books_playlist()
if DEBUG:
self.log.info(" ITUNES._find_device_book(uuid)")
self.log.info(" searching for %s ('%s' by %s)" %
(search['uuid'], search['title'], search['author']))
attempts = 9
while attempts:
hits = dev_books.Search(search['uuid'],self.SearchField.index('Albums'))
if hits:
hit = hits[0]
self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Album))
return hit
attempts -= 1
time.sleep(0.5)
if DEBUG:
self.log.warning(" attempt #%d" % (10 - attempts))
# Try again with title
if DEBUG:
self.log.info(" ITUNES._find_device_book(author)")
self.log.info(" searching for '%s' by %s" %
(search['title'], search['author']))
attempts = 9 attempts = 9
while attempts: while attempts:
hits = dev_books.Search(search['author'],self.SearchField.index('Artists')) hits = dev_books.Search(search['author'],self.SearchField.index('Artists'))
if hits: if hits:
for hit in hits: hit = hits[0]
self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) self.log.info(" found '%s' by %s" % (hit.Name, hit.Artist))
if hit.Name == search['title']: return hit
self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist))
return hit
attempts -= 1 attempts -= 1
time.sleep(0.5) time.sleep(0.5)
if DEBUG: if DEBUG:
self.log.warning(" attempt #%d" % (10 - attempts)) self.log.warning(" attempt #%d" % (10 - attempts))
if DEBUG: if DEBUG:
self.log.error(" search for '%s' using %s metadata yielded no hits" % self.log.error(" no hits")
(search['title'], search['source']))
return None return None
def _find_library_book(self, cached_book): def _find_library_book(self, cached_book):
@ -1367,7 +1399,8 @@ class ITUNES(DevicePlugin):
if iswindows: if iswindows:
if DEBUG: if DEBUG:
self.log.info(" ITUNES._find_library_book()") self.log.info(" ITUNES._find_library_book()")
self.log.info(" looking for '%s' by %s" % (cached_book['title'], cached_book['author'])) self.log.info(" looking for '%s' by %s (%s)" %
(cached_book['title'], cached_book['author'], cached_book['uuid']))
for source in self.iTunes.sources: for source in self.iTunes.sources:
if source.Kind == self.Sources.index('Library'): if source.Kind == self.Sources.index('Library'):
@ -1394,14 +1427,13 @@ class ITUNES(DevicePlugin):
attempts = 9 attempts = 9
while attempts: while attempts:
# Find book whose Artist field = cached_book['author'] # Find book whose Album field = cached_book['uuid']
hits = lib_books.Search(cached_book['author'],self.SearchField.index('Artists')) hits = lib_books.Search(cached_book['uuid'],self.SearchField.index('Albums'))
if hits: if hits:
for hit in hits: hit = hits[0]
self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Album))
if hit.Name == cached_book['title']: return hit
self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist))
return hit
attempts -= 1 attempts -= 1
time.sleep(0.5) time.sleep(0.5)
if DEBUG: if DEBUG:
@ -1460,9 +1492,9 @@ class ITUNES(DevicePlugin):
return None return None
# Save the cover from iTunes # Save the cover from iTunes
tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % self.ArtworkFormat[book.Artwork.Item(1).Format])
book.Artwork.Item(1).SaveArtworkToFile(tmp_thumb)
try: try:
tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % self.ArtworkFormat[book.Artwork.Item(1).Format])
book.Artwork.Item(1).SaveArtworkToFile(tmp_thumb)
# Resize the cover # Resize the cover
im = PILImage.open(tmp_thumb) im = PILImage.open(tmp_thumb)
scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80) scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80)
@ -1524,7 +1556,8 @@ class ITUNES(DevicePlugin):
self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind()))
else: else:
if DEBUG: if DEBUG:
self.log.info(" adding %-30.30s %-30.30s [%s]" % (book.name(), book.artist(), book.kind())) self.log.info(" adding %-30.30s %-30.30s [%s] %s" %
(book.name(), book.artist(), book.kind(), book.album()))
device_books.append(book) device_books.append(book)
elif iswindows: elif iswindows:
@ -1565,8 +1598,8 @@ class ITUNES(DevicePlugin):
''' '''
assumes pythoncom wrapper assumes pythoncom wrapper
''' '''
if DEBUG: # if DEBUG:
self.log.info(" ITUNES._get_device_books_playlist()") # self.log.info(" ITUNES._get_device_books_playlist()")
if iswindows: if iswindows:
if 'iPod' in self.sources: if 'iPod' in self.sources:
pl = None pl = None
@ -1606,7 +1639,7 @@ class ITUNES(DevicePlugin):
self.log.error(" can't find .opf in %s" % fpath) self.log.error(" can't find .opf in %s" % fpath)
return title, author return title, author
def _get_fpath(self,file, touch=False): def _get_fpath(self,file, metadata, update_md=False):
''' '''
If the database copy will be deleted after upload, we have to If the database copy will be deleted after upload, we have to
use file (the PersistentTemporaryFile), which will be around until use file (the PersistentTemporaryFile), which will be around until
@ -1619,8 +1652,8 @@ class ITUNES(DevicePlugin):
if not getattr(fpath, 'deleted_after_upload', False): if not getattr(fpath, 'deleted_after_upload', False):
if getattr(file, 'orig_file_path', None) is not None: if getattr(file, 'orig_file_path', None) is not None:
fpath = file.orig_file_path fpath = file.orig_file_path
if touch: if update_md:
self._touch_epub(fpath) self._update_epub_metadata(fpath, metadata)
elif getattr(file, 'name', None) is not None: elif getattr(file, 'name', None) is not None:
fpath = file.name fpath = file.name
else: else:
@ -1867,7 +1900,7 @@ class ITUNES(DevicePlugin):
This occurs when the user deletes a book in iBooks while disconnected This occurs when the user deletes a book in iBooks while disconnected
''' '''
if DEBUG: if DEBUG:
self.log.info("\n ITUNES._purge_orphans") self.log.info("\n ITUNES._purge_orphans()")
#self._dump_library_books(library_books) #self._dump_library_books(library_books)
#self.log.info(" cached_books:\n %s" % "\n ".join(cached_books.keys())) #self.log.info(" cached_books:\n %s" % "\n ".join(cached_books.keys()))
@ -1893,7 +1926,7 @@ class ITUNES(DevicePlugin):
if DEBUG: if DEBUG:
self.log.info() self.log.info()
def _remove_existing_copies(self,path,file,metadata): def _remove_existing_copies(self, path, metadata):
''' '''
''' '''
if DEBUG: if DEBUG:
@ -1902,29 +1935,33 @@ class ITUNES(DevicePlugin):
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
if path in self.cached_books: for book in self.cached_books:
self.update_list.append(self.cached_books[path]) if self.cached_books[book]['uuid'] == metadata.uuid:
self._remove_from_device(self.cached_books[path]) self.update_list.append(self.cached_books[book])
if DEBUG: self._remove_from_device(self.cached_books[book])
self.log.info( " deleting device book '%s'" % (path))
if not getattr(file, 'deleted_after_upload', False):
self._remove_from_iTunes(self.cached_books[path])
if DEBUG: if DEBUG:
self.log.info(" deleting library book '%s'" % path) self.log.info( " deleting device book '%s'" % (metadata.title))
if not getattr(file, 'deleted_after_upload', False):
self._remove_from_iTunes(self.cached_books[book])
if DEBUG:
self.log.info(" deleting library book '%s'" % metadata.title)
break
else: else:
if DEBUG: if DEBUG:
self.log.info(" '%s' not in cached_books" % metadata.title) self.log.info(" '%s' not in cached_books" % metadata.title)
else: else:
# 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
if path in self.cached_books: for book in self.cached_books:
self.update_list.append(self.cached_books[path]) if self.cached_books[book]['uuid'] == metadata.uuid:
self._remove_from_iTunes(self.cached_books[path]) self.update_list.append(self.cached_books[book])
if DEBUG: self._remove_from_iTunes(self.cached_books[book])
self.log.info( " deleting library book '%s'" % path) if DEBUG:
else: self.log.info( " deleting library book '%s'" % metadata.title)
if DEBUG: break
self.log.info(" '%s' not in cached_books" % metadata.title) else:
if DEBUG:
self.log.info(" '%s' not in cached_books" % metadata.title)
def _remove_from_device(self, cached_book): def _remove_from_device(self, cached_book):
''' '''
@ -1938,16 +1975,12 @@ class ITUNES(DevicePlugin):
elif iswindows: elif iswindows:
dev_pl = self._get_device_books_playlist() dev_pl = self._get_device_books_playlist()
hits = dev_pl.Search(cached_book['author'],self.SearchField.index('Artists')) hits = dev_pl.Search(cached_book['uuid'],self.SearchField.index('Albums'))
if hits: if hits:
for hit in hits: hit = hits[0]
if DEBUG: if DEBUG:
self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) self.log.info(" deleting '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Album))
if hit.Name == cached_book['title']: hit.Delete()
if DEBUG:
self.log.info(" deleting '%s' by %s" % (hit.Name, hit.Artist))
hit.Delete()
break
def _remove_from_iTunes(self, cached_book): def _remove_from_iTunes(self, cached_book):
''' '''
@ -2026,41 +2059,62 @@ class ITUNES(DevicePlugin):
book.Delete() book.Delete()
def _touch_epub(self, fpath): def _update_epub_metadata(self, fpath, metadata):
''' '''
Touch calibre:timestamp in OPF to force iBooks to recache Refresh metadata in database epub to force iBooks to recache
''' '''
self.log.info(" ITUNES._touch_epub()") self.log.info(" ITUNES._update_epub_metadata()")
title = None # Refresh epub metadata
author = None with open(fpath,'r+b') as zfo:
zf = ZipFile(fpath,'r') if metadata.timestamp:
fnames = zf.namelist() #old_ts = strptime(metadata.timestamp,"%Y-%m-%dT%H:%M:%S.%f+00:00")
opf = [x for x in fnames if '.opf' in x][0] old_ts = metadata.timestamp
if opf: metadata.timestamp = datetime.datetime(old_ts.year, old_ts.month, old_ts.day, old_ts.hour,
opf_raw = cStringIO.StringIO(zf.read(opf)).getvalue()
soup = BeautifulSoup(opf_raw)
md = soup.find('metadata')
ts = md.find('meta',attrs={'name':'calibre:timestamp'})
if ts:
# Touch existing calibre timestamp
timestamp = ts['content']
old_ts = strptime(timestamp,"%Y-%m-%dT%H:%M:%S.%f+00:00")
new_ts = datetime.datetime(old_ts.year, old_ts.month, old_ts.day, old_ts.hour,
old_ts.minute, old_ts.second, old_ts.microsecond+1) old_ts.minute, old_ts.second, old_ts.microsecond+1)
ts['content'] = new_ts.strftime("%Y-%m-%dT%H:%M:%S.%f+00:00")
else: else:
# Create new calibre timestamp metadata.timestamp = isoformat(now())
ts = Tag(soup,'meta')
ts['name'] = 'calibre:timestamp' if iswindows:
ts['content'] = isoformat(now()) # This hack compensates for the Windows automation interface not allowing
md.insert(len(md),ts) # us to update Category after the fact. By removing the tags, we should be
zfo = open(fpath,'r+b') # able to set the Category after the upload.
safe_replace(zfo, opf, cStringIO.StringIO(soup.renderContents())) if metadata.series:
metadata.tags = None
else:
if DEBUG: if DEBUG:
self.log.error(" can't find .opf in %s" % fpath) self.log.info(" updating to '%s' by %s" % (metadata.title, metadata.authors[0]))
set_metadata(zfo,metadata)
if False:
# This code operates directly on the OPF file, obsolete since we're rewriting md
zf = ZipFile(fpath,'r')
fnames = zf.namelist()
opf = [x for x in fnames if '.opf' in x][0]
if opf:
opf_raw = cStringIO.StringIO(zf.read(opf)).getvalue()
soup = BeautifulSoup(opf_raw)
md = soup.find('metadata')
ts = md.find('meta',attrs={'name':'calibre:timestamp'})
if ts:
# Touch existing calibre timestamp
timestamp = ts['content']
old_ts = strptime(timestamp,"%Y-%m-%dT%H:%M:%S.%f+00:00")
new_ts = datetime.datetime(old_ts.year, old_ts.month, old_ts.day, old_ts.hour,
old_ts.minute, old_ts.second, old_ts.microsecond+1)
ts['content'] = new_ts.strftime("%Y-%m-%dT%H:%M:%S.%f+00:00")
else:
# Create new calibre timestamp
ts = Tag(soup,'meta')
ts['name'] = 'calibre:timestamp'
ts['content'] = isoformat(now())
md.insert(len(md),ts)
zfo = open(fpath,'r+b')
safe_replace(zfo, opf, cStringIO.StringIO(soup.renderContents()))
else:
if DEBUG:
self.log.error(" can't find .opf in %s" % fpath)
def _update_device(self, msg='', wait=True): def _update_device(self, msg='', wait=True):
''' '''
@ -2119,8 +2173,8 @@ class ITUNES(DevicePlugin):
if isosx: if isosx:
if lb_added: if lb_added:
lb_added.album.set(metadata.title) lb_added.album.set(metadata.uuid)
lb_added.artist.set(metadata.authors[0]) #lb_added.artist.set(metadata.authors[0])
lb_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) lb_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
lb_added.enabled.set(True) lb_added.enabled.set(True)
lb_added.name.set(metadata.title) lb_added.name.set(metadata.title)
@ -2128,8 +2182,8 @@ class ITUNES(DevicePlugin):
lb_added.sort_name.set(this_book.title_sorter) lb_added.sort_name.set(this_book.title_sorter)
if db_added: if db_added:
db_added.album.set(metadata.title) db_added.album.set(metadata.uuid)
db_added.artist.set(metadata.authors[0]) #db_added.artist.set(metadata.authors[0])
db_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) db_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
db_added.enabled.set(True) db_added.enabled.set(True)
db_added.name.set(metadata.title) db_added.name.set(metadata.title)
@ -2174,8 +2228,8 @@ class ITUNES(DevicePlugin):
elif iswindows: elif iswindows:
if lb_added: if lb_added:
lb_added.Album = metadata.title lb_added.Album = metadata.uuid
lb_added.Artist = metadata.authors[0] #lb_added.Artist = metadata.authors[0]
lb_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) lb_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
lb_added.Enabled = True lb_added.Enabled = True
lb_added.Name = metadata.title lb_added.Name = metadata.title
@ -2184,8 +2238,8 @@ class ITUNES(DevicePlugin):
if db_added: if db_added:
# Album, Artist and Name are changed in _add_device_book() # Album, Artist and Name are changed in _add_device_book()
db_added.Album = metadata.title db_added.Album = metadata.uuid
db_added.Artist = metadata.authors[0] #db_added.Artist = metadata.authors[0]
db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S')))
db_added.Enabled = True db_added.Enabled = True
db_added.Name = metadata.title db_added.Name = metadata.title

View File

@ -1409,7 +1409,7 @@ class DeviceMixin(object): # {{{
# Set author_sort if it isn't already # Set author_sort if it isn't already
asort = getattr(book, 'author_sort', None) asort = getattr(book, 'author_sort', None)
if not asort and book.authors: if not asort and book.authors:
book.author_sort = self.db.author_sort_from_authors(book.authors) book.author_sort = self.library_view.model().db.author_sort_from_authors(book.authors)
resend_metadata = True resend_metadata = True
if resend_metadata: if resend_metadata: