From d268786eeb2055ba8bc15088fadbd1b329d076ec Mon Sep 17 00:00:00 2001 From: GRiker Date: Tue, 8 Jun 2010 03:32:47 -0600 Subject: [PATCH 01/12] GwR revisions --- src/calibre/devices/apple/driver.py | 277 ++++++++++++++++------------ 1 file changed, 161 insertions(+), 116 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 6a240c7040..56d2565ce9 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -48,7 +48,7 @@ class ITUNES(DevicePlugin): supported_platforms = ['osx','windows'] author = 'GRiker' #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (0, 4, 0) + version = (0, 4, 11) OPEN_FEEDBACK_MESSAGE = _( 'Apple device detected, launching iTunes, please wait ...') @@ -221,7 +221,10 @@ class ITUNES(DevicePlugin): for (i,book) in enumerate(device_books): this_book = Book(book.name(), book.artist()) this_book.path = self.path_template % (book.name(), book.artist()) - this_book.datetime = parse_date(str(book.date_added())).timetuple() + try: + this_book.datetime = parse_date(str(book.date_added())).timetuple() + except: + pass this_book.db_id = None this_book.device_collections = [] this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None @@ -251,7 +254,10 @@ class ITUNES(DevicePlugin): for (i,book) in enumerate(device_books): this_book = Book(book.Name, book.Artist) this_book.path = self.path_template % (book.Name, book.Artist) - this_book.datetime = parse_date(str(book.DateAdded)).timetuple() + try: + this_book.datetime = parse_date(str(book.DateAdded)).timetuple() + except: + pass this_book.db_id = None this_book.device_collections = [] this_book.library_id = library_books[this_book.path] if this_book.path in library_books else None @@ -630,7 +636,7 @@ class ITUNES(DevicePlugin): :detected_device: Device information from the device scanner """ if DEBUG: - self.log.info("ITUNE.reset()") + self.log.info("ITUNES.reset()") def set_progress_reporter(self, report_progress): ''' @@ -810,7 +816,10 @@ class ITUNES(DevicePlugin): # Create a new Book this_book = Book(metadata[i].title, metadata[i].author[0]) - this_book.datetime = parse_date(str(added.date_added())).timetuple() + try: + this_book.datetime = parse_date(str(added.date_added())).timetuple() + except: + pass this_book.db_id = None this_book.device_collections = [] this_book.library_id = added @@ -868,9 +877,10 @@ class ITUNES(DevicePlugin): if lib is not None: lib_books = None for pl in lib.Playlists: - if self.PlaylistKind[pl.Kind] == 'User' and self.PlaylistSpecialKind[pl.SpecialKind] == 'Books': + if pl.Kind == self.PlaylistKind.index('User') and \ + pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): if DEBUG: - self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.Name, self.PlaylistSpecialKind[pl.SpecialKind])) + self.log.info(" Books playlist: '%s'" % (pl.Name)) lib_books = pl break else: @@ -924,14 +934,17 @@ class ITUNES(DevicePlugin): # Add to iTunes Library|Books if isinstance(file,PersistentTemporaryFile): op_status = lib_books.AddFile(file._name) - self.log.info("ITUNES.upload_books():\n iTunes adding '%s'" % file._name) + if DEBUG: + self.log.info("ITUNES.upload_books():\n iTunes adding '%s'" % file._name) else: op_status = lib_books.AddFile(file) - self.log.info(" iTunes adding '%s'" % file) + if DEBUG: + self.log.info(" iTunes adding '%s'" % file) if DEBUG: sys.stdout.write(" iTunes copying '%s' ..." % metadata[i].title) sys.stdout.flush() + while op_status.InProgress: time.sleep(0.5) if DEBUG: @@ -947,7 +960,7 @@ class ITUNES(DevicePlugin): if DEBUG: sys.stdout.write(" waiting for handle to '%s' ..." % metadata[i].title) sys.stdout.flush() - while not op_status.Tracks: + while op_status.Tracks is None: time.sleep(0.5) if DEBUG: sys.stdout.write('.') @@ -960,80 +973,83 @@ class ITUNES(DevicePlugin): added = self._find_library_book( {'title': metadata[i].title,'author': metadata[i].author[0]}) - if not added: - self.log.error("ITUNES.upload_books():\n could not find added book in iTunes") + if added: + thumb = None + # Use cover data as artwork + if metadata[i].cover: + if added.Artwork.Count: + added.Artwork.Item(1).SetArtworkFromFile(metadata[i].cover) + else: + added.AddArtworkFromFile(metadata[i].cover) - thumb = None - # Use cover data as artwork - if metadata[i].cover: - if added.Artwork.Count: - added.Artwork.Item(1).SetArtworkFromFile(metadata[i].cover) - else: - added.AddArtworkFromFile(metadata[i].cover) + try: + # Resize for thumb + width = metadata[i].thumbnail[0] + height = metadata[i].thumbnail[1] + im = PILImage.open(metadata[i].cover) + im = im.resize((width, height), PILImage.ANTIALIAS) + of = cStringIO.StringIO() + im.convert('RGB').save(of, 'JPEG') + thumb = of.getvalue() + # Refresh the thumbnail cache + if DEBUG: + self.log.info( " refreshing cached thumb for '%s'" % metadata[i].title) + archive_path = os.path.join(self.cache_dir, "thumbs.zip") + zfw = zipfile.ZipFile(archive_path, mode='a') + thumb_path = path.rpartition('.')[0] + '.jpg' + zfw.writestr(thumb_path, thumb) + zfw.close() + except: + self.problem_titles.append("'%s' by %s" % (metadata[i].title, metadata[i].author[0])) + self.log.error("ITUNES.upload_books():\n error converting '%s' to thumb for '%s'" % (metadata[i].cover,metadata[i].title)) + + # Create a new Book + this_book = Book(metadata[i].title, metadata[i].author[0]) try: - # Resize for thumb - width = metadata[i].thumbnail[0] - height = metadata[i].thumbnail[1] - im = PILImage.open(metadata[i].cover) - im = im.resize((width, height), PILImage.ANTIALIAS) - of = cStringIO.StringIO() - im.convert('RGB').save(of, 'JPEG') - thumb = of.getvalue() - - # Refresh the thumbnail cache - if DEBUG: - self.log.info( " refreshing cached thumb for '%s'" % metadata[i].title) - archive_path = os.path.join(self.cache_dir, "thumbs.zip") - zfw = zipfile.ZipFile(archive_path, mode='a') - thumb_path = path.rpartition('.')[0] + '.jpg' - zfw.writestr(thumb_path, thumb) - zfw.close() + this_book.datetime = parse_date(str(added.DateAdded)).timetuple() except: - self.problem_titles.append("'%s' by %s" % (metadata[i].title, metadata[i].author[0])) - self.log.error("ITUNES.upload_books():\n error converting '%s' to thumb for '%s'" % (metadata[i].cover,metadata[i].title)) + pass + this_book.db_id = None + this_book.device_collections = [] + this_book.library_id = added + this_book.path = path + this_book.size = added.Size # Updated later from actual storage size + this_book.thumbnail = thumb + this_book.iTunes_id = added - # Create a new Book - this_book = Book(metadata[i].title, metadata[i].author[0]) - this_book.datetime = parse_date(str(added.DateAdded)).timetuple() - this_book.db_id = None - this_book.device_collections = [] - this_book.library_id = added - this_book.path = path - this_book.size = added.Size # Updated later from actual storage size - this_book.thumbnail = thumb - this_book.iTunes_id = added + new_booklist.append(this_book) - new_booklist.append(this_book) + # Flesh out the iTunes metadata + if metadata[i].comments: + added.Comment = (strip_tags.sub('',metadata[i].comments)) + added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) + added.Enabled = True + if metadata[i].rating: + added.AlbumRating = (metadata[i].rating*10) + added.SortArtist = (metadata[i].author_sort.title()) + added.SortName = (this_book.title_sorter) - # Flesh out the iTunes metadata - if metadata[i].comments: - added.Comment = (strip_tags.sub('',metadata[i].comments)) - added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) - added.Enabled = True - if metadata[i].rating: - added.AlbumRating = (metadata[i].rating*10) - added.SortArtist = (metadata[i].author_sort.title()) - added.SortName = (this_book.title_sorter) + # Set genre from metadata + # iTunes grabs the first dc:subject from the opf metadata, + # But we can manually override with first tag starting with alpha + for tag in metadata[i].tags: + if self._is_alpha(tag[0]): + added.Category = (tag) + break - # Set genre from metadata - # iTunes grabs the first dc:subject from the opf metadata, - # But we can manually override with first tag starting with alpha - for tag in metadata[i].tags: - if self._is_alpha(tag[0]): - added.Category = (tag) - break + # Add new_book to self.cached_paths + self.cached_books[this_book.path] = { + 'title': metadata[i].title, + 'author': metadata[i].author[0], + 'lib_book': added + } - # Add new_book to self.cached_paths - self.cached_books[this_book.path] = { - 'title': metadata[i].title, - 'author': metadata[i].author[0], - 'lib_book': added - } - - # Report progress - if self.report_progress is not None: - self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count)) + # Report progress + if self.report_progress is not None: + self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count)) + else: + self.log.error("ITUNES.upload_books():\n could not find added book in iTunes") finally: pythoncom.CoUninitialize() @@ -1148,9 +1164,10 @@ class ITUNES(DevicePlugin): if lib is not None: lib_books = None for pl in lib.Playlists: - if self.PlaylistKind[pl.Kind] == 'User' and self.PlaylistSpecialKind[pl.SpecialKind] == 'Books': + if pl.Kind == self.PlaylistKind.index('User') and \ + pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): if DEBUG: - self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.Name, self.PlaylistSpecialKind[pl.SpecialKind])) + self.log.info(" Books playlist: '%s'" % (pl.Name)) lib_books = pl break else: @@ -1163,7 +1180,7 @@ class ITUNES(DevicePlugin): hits = lib_books.Search(cached_book['author'],SearchField.index('Artists')) if hits: for hit in hits: - #self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) + self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) if hit.Name == cached_book['title']: self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist)) return hit @@ -1173,7 +1190,7 @@ class ITUNES(DevicePlugin): self.log.warning(" attempt #%d" % (10 - attempts)) if DEBUG: - self.log.error(" search yielded no hits") + self.log.error(" search for '%s' yielded no hits" % cached_book['title']) return None def _generate_thumbnail(self, book_path, book): @@ -1277,18 +1294,21 @@ class ITUNES(DevicePlugin): for pl in device.playlists(): if pl.special_kind() == appscript.k.Books: if DEBUG: - self.log.info(" Book playlist: '%s' special_kind: '%s'" % (pl.name(), pl.special_kind())) + self.log.info(" Book playlist: '%s'" % (pl.name())) books = pl.file_tracks() break else: self.log.error(" book_playlist not found") for book in books: - if book.kind() in ['Book','Protected book']: - device_books.append(book) + # This may need additional entries for international iTunes users + if book.kind() in ['MPEG audio file']: + if DEBUG: + self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) else: if DEBUG: - self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) + self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind())) + device_books.append(book) elif iswindows: if 'iPod' in self.sources: @@ -1299,9 +1319,10 @@ class ITUNES(DevicePlugin): dev_books = None for pl in device.Playlists: - if self.PlaylistKind[pl.Kind] == 'User' and self.PlaylistSpecialKind[pl.SpecialKind] == 'Books': + if pl.Kind == self.PlaylistKind.index('User') and \ + pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): if DEBUG: - self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.Name, self.PlaylistSpecialKind[pl.SpecialKind])) + self.log.info(" Books playlist: '%s'" % (pl.Name)) dev_books = pl.Tracks break else: @@ -1309,10 +1330,14 @@ class ITUNES(DevicePlugin): self.log.info(" no Books playlist found") for book in dev_books: - if book.KindAsString in ['Book','Protected book']: - device_books.append(book) + # This may need additional entries for international iTunes users + if book.KindAsString in ['MPEG audio file']: + if DEBUG: + self.log.info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString)) else: - self.log.info(" ignoring '%s' of type %s" % (book.Name, book.KindAsString)) + if DEBUG: + self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString)) + device_books.append(book) finally: pythoncom.CoUninitialize() @@ -1334,7 +1359,7 @@ class ITUNES(DevicePlugin): if source.kind() == appscript.k.library: lib = source if DEBUG: - self.log.info(" Library source: '%s' kind: %s" % (lib.name(), lib.kind())) + self.log.info(" Library source: '%s'" % (lib.name())) break else: if DEBUG: @@ -1342,22 +1367,33 @@ class ITUNES(DevicePlugin): if lib is not None: lib_books = None - for pl in lib.playlists(): - if pl.special_kind() == appscript.k.Books: - if DEBUG: - self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.name(), pl.special_kind())) - break - lib_books = pl.file_tracks() - for book in lib_books: - if book.kind() in ['Book','Protected book']: - path = self.path_template % (book.name(), book.artist()) - library_books[path] = book + if lib.playlists(): + for pl in lib.playlists(): + if pl.special_kind() == appscript.k.Books: + if DEBUG: + self.log.info(" Books playlist: '%s'" % (pl.name())) + break else: if DEBUG: - self.log.info(" ignoring library book of type '%s'" % book.kind()) + self.log.info(" no Library|Books playlist found") + + lib_books = pl.file_tracks() + for book in lib_books: + # This may need additional entries for international iTunes users + if book.kind() in ['MPEG audio file']: + if DEBUG: + self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) + else: + if DEBUG: + self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind())) + path = self.path_template % (book.name(), book.artist()) + library_books[path] = book + else: + if DEBUG: + self.log.info('No Library playlists') else: if DEBUG: - self.log.info('ITUNES._get_library_books():\n No Books playlist') + self.log.info(' no Library found') elif iswindows: lib = None @@ -1374,23 +1410,32 @@ class ITUNES(DevicePlugin): if lib is not None: lib_books = None - for pl in lib.Playlists: - if self.PlaylistKind[pl.Kind] == 'User' and self.PlaylistSpecialKind[pl.SpecialKind] == 'Books': - if DEBUG: - self.log.info(" Books playlist: '%s' special_kind: '%s'" % (pl.Name, self.PlaylistSpecialKind[pl.SpecialKind])) - lib_books = pl.Tracks - break - else: - if DEBUG: - self.log.error(" no Books playlist found") - - for book in lib_books: - if book.KindAsString in ['Book','Protected book']: - path = self.path_template % (book.Name, book.Artist) - library_books[path] = book + if lib.Playlists is not None: + for pl in lib.Playlists: + if pl.Kind == self.PlaylistKind.index('User') and \ + pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): + if DEBUG: + self.log.info(" Books playlist: '%s'" % (pl.Name)) + lib_books = pl.Tracks + break else: if DEBUG: - self.log.info(" ignoring '%s' of type %s" % (book.Name, book.KindAsString)) + self.log.error(" no Library|Books playlist found") + else: + if DEBUG: + self.log.error(" no Library playlists found") + + for book in lib_books: + # This may need additional entries for international iTunes users + if book.KindAsString in ['MPEG audio file']: + if DEBUG: + self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString)) + else: + if DEBUG: + self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString)) + path = self.path_template % (book.Name, book.Artist) + library_books[path] = book + finally: pythoncom.CoUninitialize() From 5c316d4d612823dffec58b407f6af3f9e391948f Mon Sep 17 00:00:00 2001 From: GRiker Date: Wed, 9 Jun 2010 12:44:17 -0600 Subject: [PATCH 02/12] GwR wip 0.5.0 --- src/calibre/devices/apple/driver.py | 436 ++++++++++++++++++---------- 1 file changed, 289 insertions(+), 147 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 335b4abf8a..1b4bb8e408 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -5,7 +5,7 @@ __copyright__ = '2010, Gregory Riker' __docformat__ = 'restructuredtext en' -import cStringIO, os, re, shutil, subprocess, sys, tempfile, time, zipfile +import cStringIO, ctypes, os, re, shutil, subprocess, sys, tempfile, time, zipfile from calibre.constants import DEBUG from calibre import fit_image @@ -14,6 +14,7 @@ from calibre.devices.interface import DevicePlugin from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.metadata import MetaInformation from calibre.library.server.utils import strftime +from calibre.ptempfile import PersistentTemporaryFile from calibre.utils.config import Config, config_dir from calibre.utils.date import parse_date from calibre.utils.logging import Log @@ -47,7 +48,7 @@ class ITUNES(DevicePlugin): supported_platforms = ['osx','windows'] author = 'GRiker' #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (0, 4, 11) + version = (0, 5, 0) OPEN_FEEDBACK_MESSAGE = _( 'Apple device detected, launching iTunes, please wait ...') @@ -101,6 +102,15 @@ class ITUNES(DevicePlugin): 'Books', ] + SearchField = [ + 'All', + 'Visible', + 'Artists', + 'Albums', + 'Composers', + 'SongNames', + ] + # Properties cached_books = {} cache_dir = os.path.join(config_dir, 'caches', 'itunes') @@ -108,6 +118,7 @@ class ITUNES(DevicePlugin): iTunes= None iTunes_media = None log = Log() + manual_sync_mode = False path_template = 'iTunes/%s - %s.epub' problem_titles = [] problem_msg = None @@ -182,9 +193,6 @@ class ITUNES(DevicePlugin): (new_book.title, new_book.author)) booklists[0].append(new_book) -# if DEBUG: -# self._dump_booklist(booklists[0],'after add_books_to_metadata()') - def books(self, oncard=None, end_session=True): """ Return a list of ebooks on the device. @@ -210,7 +218,6 @@ class ITUNES(DevicePlugin): library_books = self._get_library_books() if 'iPod' in self.sources: - #device = self.sources['iPod'] booklist = BookList(self.log) cached_books = {} @@ -238,7 +245,8 @@ class ITUNES(DevicePlugin): cached_books[this_book.path] = { 'title':book.name(), '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 } if self.report_progress is not None: @@ -342,7 +350,7 @@ class ITUNES(DevicePlugin): self.log.warning(" waiting for identified iPad, attempt #%d" % (10 - attempts)) else: if DEBUG: - self.log.info(' found connected iPad in iTunes') + self.log.info(' found connected iPad') break else: # iTunes running, but not connected iPad @@ -350,9 +358,8 @@ class ITUNES(DevicePlugin): self.log.info(' self.ejected = True') self.ejected = True return False - else: - self.log.info(' found connected iPad in sources') + self._discover_manual_sync_mode() return True def can_handle_windows(self, device_id, debug=False): @@ -384,6 +391,7 @@ class ITUNES(DevicePlugin): if DEBUG: self.log.info('ITUNES.can_handle_windows:\n confirming connected iPad') self.ejected = False + self._discover_manual_sync_mode() return True else: if DEBUG: @@ -399,9 +407,6 @@ class ITUNES(DevicePlugin): pythoncom.CoUninitialize() else: - # This is called at entry - # We need to know if iTunes sees the iPad - # It may have been ejected if DEBUG: self.log.info("ITUNES:can_handle_windows():\n Launching iTunes") @@ -429,8 +434,10 @@ class ITUNES(DevicePlugin): self.log.info(' self.ejected = True') self.ejected = True return False - else: - self.log.info(' found connected iPad in sources') + + self.log.info(' found connected iPad in sources') + self._discover_manual_sync_mode(wait=1.0) + finally: pythoncom.CoUninitialize() @@ -460,23 +467,31 @@ class ITUNES(DevicePlugin): self.problem_msg = _("Some books not found in iTunes database.\n" "Delete using the iBooks app.\n" "Click 'Show Details' for a list.") + self.log.info("ITUNES:delete_books()") for path in paths: if self.cached_books[path]['lib_book']: if DEBUG: - self.log.info("ITUNES:delete_books(): Deleting '%s' from iTunes library" % (path)) + self.log.info(" Deleting '%s' from iTunes library" % (path)) if isosx: self._remove_from_iTunes(self.cached_books[path]) + if self.manual_sync_mode: + self._remove_device_book(self.cached_books[path]) elif iswindows: try: pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") self._remove_from_iTunes(self.cached_books[path]) + if self.manual_sync_mode: + self._remove_device_book(self.cached_books[path]) finally: pythoncom.CoUninitialize() - self.update_needed = True - self.update_msg = "Deleted books from device" + if not self.manual_sync_mode: + self.update_needed = True + self.update_msg = "Deleted books from device" + else: + self.log.info(" skipping sync phase, manual_sync_mode: True") else: self.problem_titles.append("'%s' by %s" % (self.cached_books[path]['title'],self.cached_books[path]['author'])) @@ -618,7 +633,7 @@ class ITUNES(DevicePlugin): # Remove from cached_books self.cached_books.pop(path) 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()') else: self.log.warning(" skipping purchased book, can't remove via automation interface") @@ -740,34 +755,6 @@ class ITUNES(DevicePlugin): # Delete existing from Library|Books # Add to self.update_list for deletion from booklist[0] during add_books_to_metadata - ''' - # --------------------------- - # PROVISIONAL - # Use the cover to find the database storage point of the epub - # Pass database copy to iTunes instead of the temporary file - - if False: - if DEBUG: - self.log.info(" processing '%s'" % metadata[i].title) - self.log.info(" file: %s" % (file._name if isinstance(file,PersistentTemporaryFile) else file)) - self.log.info(" cover: %s" % metadata[i].cover) - - calibre_database_item = False - if metadata[i].cover: - passed_file = file - storage_path = os.path.split(metadata[i].cover)[0] - try: - database_epub = filter(lambda x: x.endswith('.epub'), os.listdir(storage_path))[0] - file = os.path.join(storage_path,database_epub) - calibre_database_item = True - self.log.info(" using database file: %s" % file) - except: - self.log.info(" could not find epub in %s" % storage_path) - else: - self.log.info(" no cover available, using temp file") - # --------------------------- - ''' - path = self.path_template % (metadata[i].title, metadata[i].author[0]) if path in self.cached_books: if DEBUG: @@ -778,6 +765,21 @@ class ITUNES(DevicePlugin): if DEBUG: self.log.info( " deleting existing '%s'" % (path)) self._remove_from_iTunes(self.cached_books[path]) + if self.manual_sync_mode: + dev_book_added = self._remove_device_book(self.cached_books[path]) + + ''' + Old code testing for PTO + Use this with manuals_sync_mode to decide whether to add to Library|Books + if DEBUG: + self.log.info(" file: %s" % (file._name if isinstance(file,PersistentTemporaryFile) else file)) + # Add to iTunes Library|Books + if isinstance(file,PersistentTemporaryFile): + added = self.iTunes.add(appscript.mactypes.File(file._name)) + else: + added = self.iTunes.add(appscript.mactypes.File(file)) + + ''' # Add to iTunes Library|Books fpath = file @@ -785,7 +787,18 @@ class ITUNES(DevicePlugin): fpath = file.orig_file_path elif getattr(file, 'name', None) is not None: fpath = file.name - added = self.iTunes.add(appscript.mactypes.File(fpath)) + + if isinstance(file,PersistentTemporaryFile) and self.manual_sync_mode: + if DEBUG: + self.log.info(" PTF not added to Library|Books") + else: + added = self.iTunes.add(appscript.mactypes.File(fpath)) + if DEBUG: + self.log.info(" file added to Library|Books") + + dev_book_added = None + if self.manual_sync_mode: + dev_book_added = self._add_device_book(fpath) thumb = None if metadata[i].cover: @@ -853,7 +866,8 @@ class ITUNES(DevicePlugin): self.cached_books[this_book.path] = { 'title': this_book.title, 'author': this_book.author, - 'lib_book': added + 'lib_book': added, + 'dev_book': dev_book_added } # Report progress @@ -864,62 +878,12 @@ class ITUNES(DevicePlugin): try: pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") - - for source in self.iTunes.sources: - if source.Kind == self.Sources.index('Library'): - lib = source - if DEBUG: - self.log.info(" Library source: '%s' kind: %s" % (lib.Name, self.Sources[lib.Kind])) - break - else: - if DEBUG: - self.log.info(" Library source not found") - - if lib is not None: - lib_books = None - for pl in lib.Playlists: - if pl.Kind == self.PlaylistKind.index('User') and \ - pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): - if DEBUG: - self.log.info(" Books playlist: '%s'" % (pl.Name)) - lib_books = pl - break - else: - if DEBUG: - self.log.error(" no Books playlist found") + lib = self.iTunes.LibraryPlaylist for (i,file) in enumerate(files): # Delete existing from Library|Books, add to self.update_list # for deletion from booklist[0] during add_books_to_metadata - ''' - # --------------------------- - # PROVISIONAL - # Use the cover to find the database storage point of the epub - # Pass database copy to iTunes instead of the temporary file - - if False: - if DEBUG: - self.log.info(" processing '%s'" % metadata[i].title) - self.log.info(" file: %s" % (file._name if isinstance(file,PersistentTemporaryFile) else file)) - self.log.info(" cover: %s" % metadata[i].cover) - - calibre_database_item = False - if metadata[i].cover: - passed_file = file - storage_path = os.path.split(metadata[i].cover)[0] - try: - database_epub = filter(lambda x: x.endswith('.epub'), os.listdir(storage_path))[0] - file = os.path.join(storage_path,database_epub) - calibre_database_item = True - self.log.info(" using database file: %s" % file) - except: - self.log.info(" could not find epub in %s" % storage_path) - else: - self.log.info(" no cover available, using temp file") - # --------------------------- - ''' - path = self.path_template % (metadata[i].title, metadata[i].author[0]) if path in self.cached_books: self.update_list.append(self.cached_books[path]) @@ -928,6 +892,9 @@ class ITUNES(DevicePlugin): self.log.info("ITUNES.upload_books():") self.log.info( " deleting existing '%s'" % (path)) self._remove_from_iTunes(self.cached_books[path]) + if self.manual_sync_mode: + dev_book_added = self._remove_device_book(self.cached_books[path]) + else: if DEBUG: self.log.info(" '%s' not in cached_books" % metadata[i].title) @@ -939,41 +906,58 @@ class ITUNES(DevicePlugin): elif getattr(file, 'name', None) is not None: fpath = file.name - op_status = lib_books.AddFile(fpath) - self.log.info("ITUNES.upload_books():\n iTunes adding '%s'" - % fpath) - - if DEBUG: - sys.stdout.write(" iTunes copying '%s' ..." % metadata[i].title) - sys.stdout.flush() - - while op_status.InProgress: - time.sleep(0.5) + # If this file is to be deleted after xfer to device, don't add it to the + # iTunes database, as the file path will be invalid when calibre exits. + # Only possible in manual_sync_mode + if getattr(file, 'deleted_after_upload', False) and self.manual_sync_mode: if DEBUG: - sys.stdout.write('.') - sys.stdout.flush() - if DEBUG: - sys.stdout.write("\n") - sys.stdout.flush() - - if False: - # According to the Apple API, .Tracks should be populated once the xfer - # is complete, but I can't seem to make that work. + self.log.info(" PTF not added to Library|Books") + else: + # Add fpath to Library|Books + file_s = ctypes.c_char_p(fpath) + FileArray = ctypes.c_char_p * 1 + fa = FileArray(file_s) + op_status = lib.AddFiles(fa) if DEBUG: - sys.stdout.write(" waiting for handle to '%s' ..." % metadata[i].title) + self.log.info(" file added to Library|Books") + + self.log.info("ITUNES.upload_books():\n iTunes adding '%s'" + % fpath) + + if DEBUG: + sys.stdout.write(" iTunes copying '%s' ..." % metadata[i].title) sys.stdout.flush() - while op_status.Tracks is None: + + while op_status.InProgress: time.sleep(0.5) if DEBUG: sys.stdout.write('.') sys.stdout.flush() if DEBUG: - print - added = op_status.Tracks.Item[1] - else: - # This approach simply scans Library|Books for the book we just added - added = self._find_library_book( - {'title': metadata[i].title,'author': metadata[i].author[0]}) + sys.stdout.write("\n") + sys.stdout.flush() + + if True: + if DEBUG: + sys.stdout.write(" waiting for handle to added '%s' ..." % metadata[i].title) + sys.stdout.flush() + while op_status.Tracks is None: + time.sleep(0.5) + if DEBUG: + sys.stdout.write('.') + sys.stdout.flush() + if DEBUG: + print + added = op_status.Tracks[0] + else: + # This approach simply scans Library|Books for the book we just added + added = self._find_library_book( + {'title': metadata[i].title, + 'author': metadata[i].author[0]}) + + dev_book_added = None + if self.manual_sync_mode: + dev_book_added = self._add_device_book(fpath) if added: thumb = None @@ -1044,7 +1028,8 @@ class ITUNES(DevicePlugin): self.cached_books[this_book.path] = { 'title': metadata[i].title, 'author': metadata[i].author[0], - 'lib_book': added + 'lib_book': added, + 'dev_book': dev_book_added } # Report progress @@ -1059,12 +1044,115 @@ class ITUNES(DevicePlugin): self.report_progress(1.0, _('finished')) # Tell sync_booklists we need a re-sync - self.update_needed = True - self.update_msg = "Added books to device" + if not self.manual_sync_mode: + self.update_needed = True + self.update_msg = "Added books to device" return (new_booklist, [], []) # Private methods + def _add_device_book(self,fpath): + ''' + ''' + self.log.info("ITUNES._add_device_book()") + if isosx: + if 'iPod' in self.sources: + connected_device = self.sources['iPod'] + device = self.iTunes.sources[connected_device] + for pl in device.playlists(): + if pl.special_kind() == appscript.k.Books: + break + else: + if DEBUG: + self.log.error(" Device|Books playlist not found") + + # Add the passed book to the Device|Books playlist + added = pl.add(appscript.mactypes.File(fpath),to=pl) + if DEBUG: + self.log.info(" adding '%s' to device" % fpath) + return added + + elif iswindows: + if 'iPod' in self.sources: + try: + pythoncom.CoInitialize() + connected_device = self.sources['iPod'] + device = self.iTunes.sources.ItemByName(connected_device) + + dev_books = None + added = None + for pl in device.Playlists: + if pl.Kind == self.PlaylistKind.index('User') and \ + pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): + break + else: + if DEBUG: + self.log.info(" no Books playlist found") + + # Add the passed book to the Device|Books playlist + if pl: + added = pl.AddFile(fpath) + if DEBUG: + self.log.info(" adding '%s' to device" % fpath) + finally: + pythoncom.CoUninitialize() + + return added + + def _discover_manual_sync_mode(self, wait=0): + ''' + Assumes pythoncom for windows + wait is passed when launching iTunes, as it seems to need a moment to come to its senses + + ''' + if DEBUG: + self.log.info("ITUNES._discover_manual_sync_mode()") + if isosx: + connected_device = self.sources['iPod'] + dev_books = None + device = self.iTunes.sources[connected_device] + for pl in device.playlists(): + if pl.special_kind() == appscript.k.Books: + dev_books = pl.file_tracks() + break + else: + self.log.error(" book_playlist not found") + + if len(dev_books): + first_book = dev_books[0] + #if DEBUG: + #self.log.info(" determing manual mode by modifying '%s' by %s" % (first_book.name(), first_book.artist())) + try: + first_book.bpm.set(0) + self.manual_sync_mode = True + except: + self.manual_sync_mode = False + self.log.info(" iTunes.manual_sync_mode: %s" % self.manual_sync_mode) + + elif iswindows: + if wait: + time.sleep(wait) + connected_device = self.sources['iPod'] + device = self.iTunes.sources.ItemByName(connected_device) + + dev_books = None + for pl in device.Playlists: + if pl.Kind == self.PlaylistKind.index('User') and \ + pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): + dev_books = pl.Tracks + break + + if dev_books.Count: + first_book = dev_books.Item(1) + #if DEBUG: + #self.log.info(" determing manual mode by modifying '%s' by %s" % (first_book.Name, first_book.Artist)) + try: + first_book.BPM = 0 + self.manual_sync_mode = True + except: + self.manual_sync_mode = False + self.log.info(" iTunes.manual_sync_mode: %s" % self.manual_sync_mode) + def _dump_booklist(self, booklist, header=None): ''' ''' @@ -1090,10 +1178,11 @@ class ITUNES(DevicePlugin): self.log.info( "%s" % ('-' * len(msg))) if isosx: for cb in self.cached_books.keys(): - self.log.info("%-40.40s %-30.30s %-10.10s" % + self.log.info("%-40.40s %-30.30s %-10.10s %-10.10s" % (self.cached_books[cb]['title'], 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:])) elif iswindows: for cb in self.cached_books.keys(): self.log.info("%-40.40s %-30.30s" % @@ -1121,7 +1210,10 @@ class ITUNES(DevicePlugin): self.log.info(msg) self.log.info( "%s" % ('-' * len(msg))) for file in files: - self.log.info(file) + if getattr(file, 'orig_file_path', None) is not None: + self.log.info(" %s" % file.orig_file_path) + elif getattr(file, 'name', None) is not None: + self.log.info(" %s" % file.name) self.log.info() def _dump_update_list(self,header=None): @@ -1147,7 +1239,6 @@ class ITUNES(DevicePlugin): ''' Windows-only method to get a handle to a library book in the current pythoncom session ''' - SearchField = ['All','Visible','Artists','Titles','Composers','SongNames'] if iswindows: if DEBUG: self.log.info("ITUNES._find_library_book()") @@ -1178,8 +1269,8 @@ class ITUNES(DevicePlugin): attempts = 9 while attempts: - # Find all books by this author, then match title - hits = lib_books.Search(cached_book['author'],SearchField.index('Artists')) + # Find book whose Artist field = cached_book['author'] + hits = lib_books.Search(cached_book['author'],self.SearchField.index('Artists')) if hits: for hit in hits: self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) @@ -1346,6 +1437,30 @@ class ITUNES(DevicePlugin): return device_books + def _get_device_playlist(self): + ''' + + ''' + if iswindows: + if 'iPod' in self.sources: + pl = None + try: + pythoncom.CoInitialize() + connected_device = self.sources['iPod'] + device = self.iTunes.sources.ItemByName(connected_device) + + dev_books = None + for pl in device.Playlists: + if pl.Kind == self.PlaylistKind.index('User') and \ + pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): + break + else: + if DEBUG: + self.log.error(" no iPad|Books playlist found") + finally: + pythoncom.CoUninitialize() + return pl + def _get_library_books(self): ''' Populate a dict of paths from iTunes Library|Books @@ -1427,17 +1542,20 @@ class ITUNES(DevicePlugin): if DEBUG: self.log.error(" no Library playlists found") - for book in lib_books: - # This may need additional entries for international iTunes users - if book.KindAsString in ['MPEG audio file']: - if DEBUG: - self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString)) - else: - if DEBUG: - self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString)) - path = self.path_template % (book.Name, book.Artist) - library_books[path] = book - + try: + for book in lib_books: + # This may need additional entries for international iTunes users + if book.KindAsString in ['MPEG audio file']: + if DEBUG: + self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString)) + else: + if DEBUG: + self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString)) + path = self.path_template % (book.Name, book.Artist) + library_books[path] = book + except: + if DEBUG: + self.log.info(" no books in library") finally: pythoncom.CoUninitialize() @@ -1553,6 +1671,30 @@ class ITUNES(DevicePlugin): self.version[0],self.version[1],self.version[2])) self.log.info(" iTunes_media: %s" % self.iTunes_media) + def _remove_device_book(self, cached_book): + ''' + Windows assumes pythoncom wrapper + ''' + self.log.info("ITUNES._remove_device_book()") + if isosx: + if DEBUG: + self.log.info(" deleting %s" % cached_book['dev_book']) + result = cached_book['dev_book'].delete() + print "result: %s" % result + + elif iswindows: + dev_pl = self._get_device_playlist() + hits = dev_pl.Search(cached_book['author'],self.SearchField.index('Artists')) + if hits: + for hit in hits: + if DEBUG: + self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) + if hit.Name == cached_book['title']: + if DEBUG: + self.log.info(" deleting '%s' by %s" % (hit.Name, hit.Artist)) + results = hit.Delete() + break + def _remove_from_iTunes(self, cached_book): ''' iTunes does not delete books from storage when removing from database From 6a3f7e533b717b0fb6292a521a8b0390618be395 Mon Sep 17 00:00:00 2001 From: GRiker Date: Wed, 9 Jun 2010 12:55:30 -0600 Subject: [PATCH 03/12] GwR wip --- src/calibre/devices/apple/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 1b4bb8e408..67656908e2 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -957,7 +957,7 @@ class ITUNES(DevicePlugin): dev_book_added = None if self.manual_sync_mode: - dev_book_added = self._add_device_book(fpath) + added = dev_book_added = self._add_device_book(fpath) if added: thumb = None From 8b5833db167858820ddcb35c5de0d2926623f83a Mon Sep 17 00:00:00 2001 From: GRiker Date: Wed, 9 Jun 2010 17:06:53 -0600 Subject: [PATCH 04/12] GwR wip --- src/calibre/devices/apple/driver.py | 481 ++++++++++++++++++---------- src/calibre/gui2/cover_flow.py | 3 +- 2 files changed, 313 insertions(+), 171 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 67656908e2..8ad006e361 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -476,14 +476,14 @@ class ITUNES(DevicePlugin): if isosx: self._remove_from_iTunes(self.cached_books[path]) if self.manual_sync_mode: - self._remove_device_book(self.cached_books[path]) + self._remove_from_device(self.cached_books[path]) elif iswindows: try: pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") self._remove_from_iTunes(self.cached_books[path]) if self.manual_sync_mode: - self._remove_device_book(self.cached_books[path]) + self._remove_from_device(self.cached_books[path]) finally: pythoncom.CoUninitialize() @@ -766,7 +766,7 @@ class ITUNES(DevicePlugin): self.log.info( " deleting existing '%s'" % (path)) self._remove_from_iTunes(self.cached_books[path]) if self.manual_sync_mode: - dev_book_added = self._remove_device_book(self.cached_books[path]) + dev_book_added = self._remove_from_device(self.cached_books[path]) ''' Old code testing for PTO @@ -881,162 +881,144 @@ class ITUNES(DevicePlugin): lib = self.iTunes.LibraryPlaylist for (i,file) in enumerate(files): - # Delete existing from Library|Books, add to self.update_list - # for deletion from booklist[0] during add_books_to_metadata - path = self.path_template % (metadata[i].title, metadata[i].author[0]) - if path in self.cached_books: - self.update_list.append(self.cached_books[path]) - if DEBUG: - self.log.info("ITUNES.upload_books():") - self.log.info( " deleting existing '%s'" % (path)) - self._remove_from_iTunes(self.cached_books[path]) - if self.manual_sync_mode: - dev_book_added = self._remove_device_book(self.cached_books[path]) - - else: - if DEBUG: - self.log.info(" '%s' not in cached_books" % metadata[i].title) - - # Add to iTunes Library|Books - fpath = file - if getattr(file, 'orig_file_path', None) is not None: - fpath = file.orig_file_path - elif getattr(file, 'name', None) is not None: - fpath = file.name - - # If this file is to be deleted after xfer to device, don't add it to the - # iTunes database, as the file path will be invalid when calibre exits. - # Only possible in manual_sync_mode - if getattr(file, 'deleted_after_upload', False) and self.manual_sync_mode: - if DEBUG: - self.log.info(" PTF not added to Library|Books") - else: - # Add fpath to Library|Books - file_s = ctypes.c_char_p(fpath) - FileArray = ctypes.c_char_p * 1 - fa = FileArray(file_s) - op_status = lib.AddFiles(fa) - if DEBUG: - self.log.info(" file added to Library|Books") - - self.log.info("ITUNES.upload_books():\n iTunes adding '%s'" - % fpath) - - if DEBUG: - sys.stdout.write(" iTunes copying '%s' ..." % metadata[i].title) - sys.stdout.flush() - - while op_status.InProgress: - time.sleep(0.5) - if DEBUG: - sys.stdout.write('.') - sys.stdout.flush() - if DEBUG: - sys.stdout.write("\n") - sys.stdout.flush() - - if True: - if DEBUG: - sys.stdout.write(" waiting for handle to added '%s' ..." % metadata[i].title) - sys.stdout.flush() - while op_status.Tracks is None: - time.sleep(0.5) - if DEBUG: - sys.stdout.write('.') - sys.stdout.flush() - if DEBUG: - print - added = op_status.Tracks[0] - else: - # This approach simply scans Library|Books for the book we just added - added = self._find_library_book( - {'title': metadata[i].title, - 'author': metadata[i].author[0]}) - - dev_book_added = None if self.manual_sync_mode: - added = dev_book_added = self._add_device_book(fpath) - - if added: - thumb = None - # Use cover data as artwork - if metadata[i].cover: - if added.Artwork.Count: - added.Artwork.Item(1).SetArtworkFromFile(metadata[i].cover) - else: - added.AddArtworkFromFile(metadata[i].cover) - - try: - # Resize for thumb - width = metadata[i].thumbnail[0] - height = metadata[i].thumbnail[1] - im = PILImage.open(metadata[i].cover) - im = im.resize((width, height), PILImage.ANTIALIAS) - of = cStringIO.StringIO() - im.convert('RGB').save(of, 'JPEG') - thumb = of.getvalue() - - # Refresh the thumbnail cache + # Delete existing from Device|Books, add to self.update_list + # for deletion from booklist[0] during add_books_to_metadata + if path in self.cached_books: + self.update_list.append(self.cached_books[path]) + self._remove_from_device(self.cached_books[path]) + if DEBUG: + self.log.info( " deleting device book '%s'" % (path)) + if not getattr(fpath, 'deleted_after_upload', False): + self._remove_from_iTunes(self.cached_books[path]) if DEBUG: - self.log.info( " refreshing cached thumb for '%s'" % metadata[i].title) - archive_path = os.path.join(self.cache_dir, "thumbs.zip") - zfw = zipfile.ZipFile(archive_path, mode='a') - thumb_path = path.rpartition('.')[0] + '.jpg' - zfw.writestr(thumb_path, thumb) - zfw.close() - except: - self.problem_titles.append("'%s' by %s" % (metadata[i].title, metadata[i].author[0])) - self.log.error("ITUNES.upload_books():\n error converting '%s' to thumb for '%s'" % (metadata[i].cover,metadata[i].title)) + self.log.info(" deleting library book '%s'" % path) + else: + if DEBUG: + self.log.info(" '%s' not in cached_books" % metadata[i].title) + else: + # Delete existing from Library|Books, add to self.update_list + # for deletion from booklist[0] during add_books_to_metadata + if path in self.cached_books: + self.update_list.append(self.cached_books[path]) + self._remove_from_iTunes(self.cached_books[path]) + if DEBUG: + self.log.info("ITUNES.upload_books():") + self.log.info( " deleting library book '%s'" % path) + else: + if DEBUG: + self.log.info(" '%s' not in cached_books" % metadata[i].title) - # Create a new Book - this_book = Book(metadata[i].title, metadata[i].author[0]) + # If the database copy will be deleted after upload, we have to + # use file (the PersistentTemporaryFile), which will be around until + # calibre exits. + fpath = file + lb_added = None + db_added = None + if not getattr(fpath, 'deleted_after_upload', False): + if getattr(file, 'orig_file_path', None) is not None: + fpath = file.orig_file_path + elif getattr(file, 'name', None) is not None: + fpath = file.name + else: + if DEBUG: + self.log.info(" file will be deleted after upload") + + if self.manual_sync_mode: + db_added = self._add_device_book(fpath, metadata[i]) + if DEBUG: + self.log.info(" file uploaded to Device|Books") + if not getattr(fpath, 'deleted_after_upload', False): + lb_added = self._add_library_book(fpath, metadata[i]) + if DEBUG: + self.log.info(" file added to Library|Books for iTunes:iBooks tracking") + else: + lb_added = self._add_library_book(fpath, metadata[i]) + if DEBUG: + self.log.info(" file added to Library|Books for pending sync") + + # Use calibre cover data as artwork if available + thumb = self._cover_to_thumb(path, metadata[i], lb_added, db_added) + + # Create a new Book + this_book = Book(metadata[i].title, metadata[i].author[0]) + if lb_added: try: - this_book.datetime = parse_date(str(added.DateAdded)).timetuple() + this_book.datetime = parse_date(str(lb_added.DateAdded)).timetuple() except: pass - this_book.db_id = None - this_book.device_collections = [] - this_book.library_id = added - this_book.path = path - this_book.size = added.Size # Updated later from actual storage size - this_book.thumbnail = thumb - this_book.iTunes_id = added - - new_booklist.append(this_book) - - # Flesh out the iTunes metadata - if metadata[i].comments: - added.Comment = (strip_tags.sub('',metadata[i].comments)) - added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) - added.Enabled = True - if metadata[i].rating: - added.AlbumRating = (metadata[i].rating*10) - added.SortArtist = (metadata[i].author_sort.title()) - added.SortName = (this_book.title_sorter) - - # Set genre from metadata - # iTunes grabs the first dc:subject from the opf metadata, - # But we can manually override with first tag starting with alpha - for tag in metadata[i].tags: - if self._is_alpha(tag[0]): - added.Category = (tag) - break - - # Add new_book to self.cached_paths - self.cached_books[this_book.path] = { - 'title': metadata[i].title, - 'author': metadata[i].author[0], - 'lib_book': added, - 'dev_book': dev_book_added - } - - # Report progress - if self.report_progress is not None: - self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count)) + elif db_added: + try: + this_book.datetime = parse_date(str(db_added.DateAdded)).timetuple() + except: + pass + this_book.db_id = None + this_book.device_collections = [] + this_book.library_id = lb_added + this_book.path = path + if lb_added: + this_book.size = self._get_device_book_size(fpath, lb_added.Size) else: - self.log.error("ITUNES.upload_books():\n could not find added book in iTunes") + this_book.size = self._get_device_book_size(fpath, db_added.Size) + this_book.thumbnail = thumb + if lb_added: + this_book.iTunes_id = lb_added + new_booklist.append(this_book) + + # Flesh out the iTunes metadata + if metadata[i].comments: + if lb_added: + lb_added.Comment = (strip_tags.sub('',metadata[i].comments)) + if db_added: + db_added.Comment = (strip_tags.sub('',metadata[i].comments)) + + if metadata[i].rating: + if lb_added: + lb_added.AlbumRating = (metadata[i].rating*10) + # iBooks currently doesn't allow setting rating ... ? + try: + if db_added: + db_added.AlbumRating = (metadata[i].rating*10) + except: + pass + + if lb_added: + lb_added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) + lb_added.Enabled = True + lb_added.SortArtist = (metadata[i].author_sort.title()) + lb_added.SortName = (this_book.title_sorter) + + if db_added: + db_added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) + db_added.Enabled = True + db_added.SortArtist = (metadata[i].author_sort.title()) + db_added.SortName = (this_book.title_sorter) + + # Set genre from metadata + # iTunes grabs the first dc:subject from the opf metadata, + # But we can manually override with first tag starting with alpha + for tag in metadata[i].tags: + if self._is_alpha(tag[0]): + if lb_added: + lb_added.Category = (tag) + if db_added: + db_added.Category = (tag) + break + + # Add new_book to self.cached_paths + self.cached_books[this_book.path] = { + 'title': metadata[i].title, + 'author': metadata[i].author[0], + 'lib_book': lb_added, + 'dev_book': db_added + } + + # Report progress + if self.report_progress is not None: + self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count)) + finally: pythoncom.CoUninitialize() @@ -1051,7 +1033,7 @@ class ITUNES(DevicePlugin): return (new_booklist, [], []) # Private methods - def _add_device_book(self,fpath): + def _add_device_book(self,fpath, metadata): ''' ''' self.log.info("ITUNES._add_device_book()") @@ -1091,14 +1073,148 @@ class ITUNES(DevicePlugin): # Add the passed book to the Device|Books playlist if pl: + ''' added = pl.AddFile(fpath) if DEBUG: self.log.info(" adding '%s' to device" % fpath) + ''' + file_s = ctypes.c_char_p(fpath) + FileArray = ctypes.c_char_p * 1 + fa = FileArray(file_s) + op_status = pl.AddFiles(fa) + + if DEBUG: + sys.stdout.write(" uploading '%s' to device ..." % metadata.title) + sys.stdout.flush() + + while op_status.InProgress: + time.sleep(0.5) + if DEBUG: + sys.stdout.write('.') + sys.stdout.flush() + if DEBUG: + sys.stdout.write("\n") + sys.stdout.flush() + + # This doesn't seem to work with device, just Library + if False: + if DEBUG: + sys.stdout.write(" waiting for handle to added '%s' ..." % metadata.title) + sys.stdout.flush() + while op_status.Tracks is None: + time.sleep(0.5) + if DEBUG: + sys.stdout.write('.') + sys.stdout.flush() + if DEBUG: + print + added = op_status.Tracks[0] + else: + # This approach simply scans Library|Books for the book we just added + added = self._find_device_book( + {'title': metadata.title, + 'author': metadata.author[0]}) + return added + finally: pythoncom.CoUninitialize() return added + def _add_library_book(self,file, metadata): + ''' + assumes pythoncom wrapper + ''' + self.log.info("ITUNES._add_library_book()") + if isosx: + print "to be implemented" + + elif iswindows: + lib = self.iTunes.LibraryPlaylist + file_s = ctypes.c_char_p(file) + FileArray = ctypes.c_char_p * 1 + fa = FileArray(file_s) + op_status = lib.AddFiles(fa) + if DEBUG: + self.log.info(" file added to Library|Books") + + self.log.info(" iTunes adding '%s'" % file) + + if DEBUG: + sys.stdout.write(" iTunes copying '%s' ..." % metadata.title) + sys.stdout.flush() + + while op_status.InProgress: + time.sleep(0.5) + if DEBUG: + sys.stdout.write('.') + sys.stdout.flush() + if DEBUG: + sys.stdout.write("\n") + sys.stdout.flush() + + if True: + if DEBUG: + sys.stdout.write(" waiting for handle to added '%s' ..." % metadata.title) + sys.stdout.flush() + while op_status.Tracks is None: + time.sleep(0.5) + if DEBUG: + sys.stdout.write('.') + sys.stdout.flush() + if DEBUG: + print + added = op_status.Tracks[0] + else: + # This approach simply scans Library|Books for the book we just added + added = self._find_library_book( + {'title': metadata.title, + 'author': metadata.author[0]}) + return added + + def _cover_to_thumb(self, path, metadata, lb_added, db_added): + ''' + assumes pythoncom wrapper for db_added + ''' + self.log.info("ITUNES._cover_to_thumb()") + thumb = None + if metadata.cover: + if lb_added: + if lb_added.Artwork.Count: + lb_added.Artwork.Item(1).SetArtworkFromFile(metadata.cover) + else: + lb_added.AddArtworkFromFile(metadata.cover) + + if db_added: + if db_added.Artwork.Count: + db_added.Artwork.Item(1).SetArtworkFromFile(metadata.cover) + else: + db_added.AddArtworkFromFile(metadata.cover) + + try: + # Resize for thumb + width = metadata.thumbnail[0] + height = metadata.thumbnail[1] + im = PILImage.open(metadata.cover) + im = im.resize((width, height), PILImage.ANTIALIAS) + of = cStringIO.StringIO() + im.convert('RGB').save(of, 'JPEG') + thumb = of.getvalue() + + # Refresh the thumbnail cache + if DEBUG: + self.log.info( " refreshing cached thumb for '%s'" % metadata.title) + archive_path = os.path.join(self.cache_dir, "thumbs.zip") + zfw = zipfile.ZipFile(archive_path, mode='a') + thumb_path = path.rpartition('.')[0] + '.jpg' + zfw.writestr(thumb_path, thumb) + zfw.close() + except: + self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) + self.log.error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title)) + + return thumb + def _discover_manual_sync_mode(self, wait=0): ''' Assumes pythoncom for windows @@ -1235,6 +1351,35 @@ class ITUNES(DevicePlugin): ub['author'])) self.log.info() + def _find_device_book(self, cached_book): + ''' + Windows-only method to get a handle to device book in the current pythoncom session + ''' + if iswindows: + if DEBUG: + self.log.info("ITUNES._find_device_book()") + self.log.info(" looking for '%s' by %s" % (cached_book['title'], cached_book['author'])) + + dev_books = self._get_device_books_playlist() + attempts = 9 + while attempts: + # Find book whose Artist field = cached_book['author'] + hits = dev_books.Search(cached_book['author'],self.SearchField.index('Artists')) + if hits: + for hit in hits: + self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) + if hit.Name == cached_book['title']: + self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist)) + return hit + attempts -= 1 + time.sleep(0.5) + if DEBUG: + self.log.warning(" attempt #%d" % (10 - attempts)) + + if DEBUG: + self.log.error(" search for '%s' yielded no hits" % cached_book['title']) + return None + def _find_library_book(self, cached_book): ''' Windows-only method to get a handle to a library book in the current pythoncom session @@ -1437,28 +1582,24 @@ class ITUNES(DevicePlugin): return device_books - def _get_device_playlist(self): + def _get_device_books_playlist(self): ''' - + assumes pythoncom wrapper ''' if iswindows: if 'iPod' in self.sources: pl = None - try: - pythoncom.CoInitialize() - connected_device = self.sources['iPod'] - device = self.iTunes.sources.ItemByName(connected_device) + connected_device = self.sources['iPod'] + device = self.iTunes.sources.ItemByName(connected_device) - dev_books = None - for pl in device.Playlists: - if pl.Kind == self.PlaylistKind.index('User') and \ - pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): - break - else: - if DEBUG: - self.log.error(" no iPad|Books playlist found") - finally: - pythoncom.CoUninitialize() + dev_books = None + for pl in device.Playlists: + if pl.Kind == self.PlaylistKind.index('User') and \ + pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): + break + else: + if DEBUG: + self.log.error(" no iPad|Books playlist found") return pl def _get_library_books(self): @@ -1671,11 +1812,11 @@ class ITUNES(DevicePlugin): self.version[0],self.version[1],self.version[2])) self.log.info(" iTunes_media: %s" % self.iTunes_media) - def _remove_device_book(self, cached_book): + def _remove_from_device(self, cached_book): ''' Windows assumes pythoncom wrapper ''' - self.log.info("ITUNES._remove_device_book()") + self.log.info("ITUNES._remove_from_device()") if isosx: if DEBUG: self.log.info(" deleting %s" % cached_book['dev_book']) @@ -1683,7 +1824,7 @@ class ITUNES(DevicePlugin): print "result: %s" % result elif iswindows: - dev_pl = self._get_device_playlist() + dev_pl = self._get_device_books_playlist() hits = dev_pl.Search(cached_book['author'],self.SearchField.index('Artists')) if hits: for hit in hits: diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index 3bd554e891..98a36532da 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -67,7 +67,8 @@ if pictureflow is not None: return ans def reset(self): - self.dataChanged.emit() + #self.dataChanged.emit() + pass def image(self, index): return self.model.cover(index) From 3f1e6645e2fb3c3da736b24971b0a5a7acc3c3d4 Mon Sep 17 00:00:00 2001 From: GRiker Date: Thu, 10 Jun 2010 04:56:36 -0600 Subject: [PATCH 05/12] GwR wip --- src/calibre/devices/apple/driver.py | 405 +++++++++++++++++----------- 1 file changed, 254 insertions(+), 151 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 8ad006e361..fc7d56e496 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -738,7 +738,6 @@ class ITUNES(DevicePlugin): new_booklist = [] self.update_list = [] - strip_tags = re.compile(r'<[^<]*?/?>') file_count = float(len(files)) self.problem_titles = [] self.problem_msg = _("Some cover art could not be converted.\n" @@ -750,36 +749,27 @@ class ITUNES(DevicePlugin): # self._dump_cached_books('upload_books()') self._dump_update_list('upload_books()') + ''' if isosx: + for (i,file) in enumerate(files): - # Delete existing from Library|Books - # Add to self.update_list for deletion from booklist[0] during add_books_to_metadata - path = self.path_template % (metadata[i].title, metadata[i].author[0]) - if path in self.cached_books: - if DEBUG: - self.log.info(" adding '%s' by %s to self.update_list" % - (self.cached_books[path]['title'],self.cached_books[path]['author'])) - self.update_list.append(self.cached_books[path]) - if DEBUG: - self.log.info( " deleting existing '%s'" % (path)) - self._remove_from_iTunes(self.cached_books[path]) - if self.manual_sync_mode: - dev_book_added = self._remove_from_device(self.cached_books[path]) + if self.manual_sync_mode: + # Delete existing from Device|Books, add to self.update_list + # for deletion from booklist[0] during add_books_to_metadata + if path in self.cached_books: + self.update_list.append(self.cached_books[path]) + if DEBUG: + self.log.info(" adding '%s' by %s to self.update_list" % + (self.cached_books[path]['title'],self.cached_books[path]['author'])) - ''' - Old code testing for PTO - Use this with manuals_sync_mode to decide whether to add to Library|Books - if DEBUG: - self.log.info(" file: %s" % (file._name if isinstance(file,PersistentTemporaryFile) else file)) - # Add to iTunes Library|Books - if isinstance(file,PersistentTemporaryFile): - added = self.iTunes.add(appscript.mactypes.File(file._name)) - else: - added = self.iTunes.add(appscript.mactypes.File(file)) + if DEBUG: + self.log.info( " deleting existing '%s'" % (path)) + self._remove_from_iTunes(self.cached_books[path]) + if self.manual_sync_mode: + dev_book_added = self._remove_from_device(self.cached_books[path]) - ''' # Add to iTunes Library|Books fpath = file @@ -873,152 +863,54 @@ class ITUNES(DevicePlugin): # Report progress if self.report_progress is not None: self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count)) + ''' + if isosx: + for (i,file) in enumerate(files): + path = self.path_template % (metadata[i].title, metadata[i].author[0]) + self._remove_existing_copies(path,file,metadata[i]) + fpath = self._get_fpath(file) + db_added, lb_added = self._add_new_copy(fpath, metadata[i]) + thumb = self._cover_to_thumb(path, metadata[i], lb_added, db_added) + this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb) + new_booklist.append(this_book) + self._update_iTunes_metadata(metadata[i], db_added, lb_added, this_book) + + # Add new_book to self.cached_paths + self.cached_books[this_book.path] = { + 'title': metadata[i].title, + 'author': metadata[i].author[0], + 'lib_book': lb_added, + 'dev_book': db_added } + + # Report progress + if self.report_progress is not None: + self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count)) elif iswindows: try: pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") - lib = self.iTunes.LibraryPlaylist for (i,file) in enumerate(files): path = self.path_template % (metadata[i].title, metadata[i].author[0]) - - if self.manual_sync_mode: - # Delete existing from Device|Books, add to self.update_list - # for deletion from booklist[0] during add_books_to_metadata - if path in self.cached_books: - self.update_list.append(self.cached_books[path]) - self._remove_from_device(self.cached_books[path]) - if DEBUG: - self.log.info( " deleting device book '%s'" % (path)) - if not getattr(fpath, 'deleted_after_upload', False): - self._remove_from_iTunes(self.cached_books[path]) - if DEBUG: - self.log.info(" deleting library book '%s'" % path) - else: - if DEBUG: - self.log.info(" '%s' not in cached_books" % metadata[i].title) - else: - # Delete existing from Library|Books, add to self.update_list - # for deletion from booklist[0] during add_books_to_metadata - if path in self.cached_books: - self.update_list.append(self.cached_books[path]) - self._remove_from_iTunes(self.cached_books[path]) - if DEBUG: - self.log.info("ITUNES.upload_books():") - self.log.info( " deleting library book '%s'" % path) - else: - if DEBUG: - self.log.info(" '%s' not in cached_books" % metadata[i].title) - - # If the database copy will be deleted after upload, we have to - # use file (the PersistentTemporaryFile), which will be around until - # calibre exits. - fpath = file - lb_added = None - db_added = None - if not getattr(fpath, 'deleted_after_upload', False): - if getattr(file, 'orig_file_path', None) is not None: - fpath = file.orig_file_path - elif getattr(file, 'name', None) is not None: - fpath = file.name - else: - if DEBUG: - self.log.info(" file will be deleted after upload") - - if self.manual_sync_mode: - db_added = self._add_device_book(fpath, metadata[i]) - if DEBUG: - self.log.info(" file uploaded to Device|Books") - if not getattr(fpath, 'deleted_after_upload', False): - lb_added = self._add_library_book(fpath, metadata[i]) - if DEBUG: - self.log.info(" file added to Library|Books for iTunes:iBooks tracking") - else: - lb_added = self._add_library_book(fpath, metadata[i]) - if DEBUG: - self.log.info(" file added to Library|Books for pending sync") - - # Use calibre cover data as artwork if available + self._remove_existing_copies(path,file,metadata[i]) + fpath = self._get_fpath(file) + db_added, lb_added = self._add_new_copy(fpath, metadata[i]) thumb = self._cover_to_thumb(path, metadata[i], lb_added, db_added) - - # Create a new Book - this_book = Book(metadata[i].title, metadata[i].author[0]) - if lb_added: - try: - this_book.datetime = parse_date(str(lb_added.DateAdded)).timetuple() - except: - pass - elif db_added: - try: - this_book.datetime = parse_date(str(db_added.DateAdded)).timetuple() - except: - pass - this_book.db_id = None - this_book.device_collections = [] - this_book.library_id = lb_added - this_book.path = path - if lb_added: - this_book.size = self._get_device_book_size(fpath, lb_added.Size) - else: - this_book.size = self._get_device_book_size(fpath, db_added.Size) - this_book.thumbnail = thumb - if lb_added: - this_book.iTunes_id = lb_added + this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb) new_booklist.append(this_book) - - # Flesh out the iTunes metadata - if metadata[i].comments: - if lb_added: - lb_added.Comment = (strip_tags.sub('',metadata[i].comments)) - if db_added: - db_added.Comment = (strip_tags.sub('',metadata[i].comments)) - - if metadata[i].rating: - if lb_added: - lb_added.AlbumRating = (metadata[i].rating*10) - # iBooks currently doesn't allow setting rating ... ? - try: - if db_added: - db_added.AlbumRating = (metadata[i].rating*10) - except: - pass - - if lb_added: - lb_added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) - lb_added.Enabled = True - lb_added.SortArtist = (metadata[i].author_sort.title()) - lb_added.SortName = (this_book.title_sorter) - - if db_added: - db_added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) - db_added.Enabled = True - db_added.SortArtist = (metadata[i].author_sort.title()) - db_added.SortName = (this_book.title_sorter) - - # Set genre from metadata - # iTunes grabs the first dc:subject from the opf metadata, - # But we can manually override with first tag starting with alpha - for tag in metadata[i].tags: - if self._is_alpha(tag[0]): - if lb_added: - lb_added.Category = (tag) - if db_added: - db_added.Category = (tag) - break + self._update_iTunes_metadata(metadata[i], db_added, lb_added, this_book) # Add new_book to self.cached_paths self.cached_books[this_book.path] = { 'title': metadata[i].title, 'author': metadata[i].author[0], 'lib_book': lb_added, - 'dev_book': db_added - } + 'dev_book': db_added } # Report progress if self.report_progress is not None: self.report_progress(i+1/file_count, _('%d of %d') % (i+1, file_count)) - finally: pythoncom.CoUninitialize() @@ -1172,6 +1064,30 @@ class ITUNES(DevicePlugin): 'author': metadata.author[0]}) return added + def _add_new_copy(self, fpath, metadata): + ''' + ''' + if DEBUG: + self.log.info("ITUNES._add_new_copy()") + + db_added = None + lb_added = None + + if self.manual_sync_mode: + db_added = self._add_device_book(fpath, metadata) + if DEBUG: + self.log.info(" file uploaded to Device|Books") + if not getattr(fpath, 'deleted_after_upload', False): + lb_added = self._add_library_book(fpath, metadata) + if DEBUG: + self.log.info(" file added to Library|Books for iTunes:iBooks tracking") + else: + lb_added = self._add_library_book(fpath, metadata) + if DEBUG: + self.log.info(" file added to Library|Books for pending sync") + + return db_added, lb_added + def _cover_to_thumb(self, path, metadata, lb_added, db_added): ''' assumes pythoncom wrapper for db_added @@ -1215,6 +1131,51 @@ class ITUNES(DevicePlugin): return thumb + def _create_new_book(self,fpath, metadata, path, db_added, lb_added, thumb): + ''' + ''' + if DEBUG: + self.log.info("ITUNES._create_new_book()") + + this_book = Book(metadata.title, metadata.author[0]) + + this_book.db_id = None + this_book.device_collections = [] + this_book.library_id = lb_added + this_book.path = path + this_book.thumbnail = thumb + this_book.iTunes_id = lb_added + + if isosx: + if lb_added: + this_book.size = self._get_device_book_size(fpath, lb_added.size()) + try: + this_book.datetime = parse_date(str(lb_added.date_added())).timetuple() + except: + pass + elif db_added: + this_book.size = self._get_device_book_size(fpath, db_added.size()) + try: + this_book.datetime = parse_date(str(db_added.date_added())).timetuple() + except: + pass + + elif iswindows: + if lb_added: + this_book.size = self._get_device_book_size(fpath, lb_added.Size) + try: + this_book.datetime = parse_date(str(lb_added.DateAdded)).timetuple() + except: + pass + elif db_added: + this_book.size = self._get_device_book_size(fpath, db_added.Size) + try: + this_book.datetime = parse_date(str(db_added.DateAdded)).timetuple() + except: + pass + + return this_book + def _discover_manual_sync_mode(self, wait=0): ''' Assumes pythoncom for windows @@ -1602,6 +1563,26 @@ class ITUNES(DevicePlugin): self.log.error(" no iPad|Books playlist found") return pl + def _get_fpath(self,file): + ''' + If the database copy will be deleted after upload, we have to + use file (the PersistentTemporaryFile), which will be around until + calibre exits. + ''' + 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: + fpath = file.orig_file_path + elif getattr(file, 'name', None) is not None: + fpath = file.name + else: + if DEBUG: + self.log.info(" file will be deleted after upload") + return fpath + def _get_library_books(self): ''' Populate a dict of paths from iTunes Library|Books @@ -1812,6 +1793,39 @@ class ITUNES(DevicePlugin): self.version[0],self.version[1],self.version[2])) self.log.info(" iTunes_media: %s" % self.iTunes_media) + def _remove_existing_copies(self,path,file,metadata): + ''' + ''' + if DEBUG: + self.log.info("ITUNES._remove_existing_copies()") + + if self.manual_sync_mode: + # Delete existing from Device|Books, add to self.update_list + # for deletion from booklist[0] during add_books_to_metadata + if path in self.cached_books: + self.update_list.append(self.cached_books[path]) + self._remove_from_device(self.cached_books[path]) + if DEBUG: + 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: + self.log.info(" deleting library book '%s'" % path) + else: + if DEBUG: + self.log.info(" '%s' not in cached_books" % metadata.title) + else: + # Delete existing from Library|Books, add to self.update_list + # for deletion from booklist[0] during add_books_to_metadata + if path in self.cached_books: + self.update_list.append(self.cached_books[path]) + self._remove_from_iTunes(self.cached_books[path]) + if DEBUG: + self.log.info( " deleting library book '%s'" % path) + else: + if DEBUG: + self.log.info(" '%s' not in cached_books" % metadata.title) + def _remove_from_device(self, cached_book): ''' Windows assumes pythoncom wrapper @@ -1955,6 +1969,95 @@ class ITUNES(DevicePlugin): finally: pythoncom.CoUninitialize() + def _update_iTunes_metadata(self, metadata, db_added, lb_added, this_book): + ''' + ''' + if DEBUG: + self.log.info("ITUNES._update_iTunes_metadata()") + + strip_tags = re.compile(r'<[^<]*?/?>') + + if isosx: + if metadata.comments: + if lb_added: + lb_added.comment.set(strip_tags.sub('',metadata.comments)) + if db_added: + db_added.comment.set(strip_tags.sub('',metadata.comments)) + + if metadata.rating: + if lb_added: + lb_added.rating.set(metadata.rating*10) + # iBooks currently doesn't allow setting rating ... ? + try: + if db_added: + db_added.rating.set(metadata.rating*10) + except: + pass + + if lb_added: + lb_added.description.set("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) + lb_added.enabled.set(True) + lb_added.sort_artist.set(metadata.author_sort.title()) + lb_added.sort_name.set(this_book.title_sorter) + + if db_added: + db_added.description.set("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) + db_added.enabled.set(True) + db_added.sort_artist.set(metadata.author_sort.title()) + db_added.sort_name.set(this_book.title_sorter) + + # Set genre from metadata + # iTunes grabs the first dc:subject from the opf metadata, + # But we can manually override with first tag starting with alpha + for tag in metadata.tags: + if self._is_alpha(tag[0]): + if lb_added: + lb_added.genre.set(tag) + if db_added: + db_added.genre.set(tag) + break + + + elif iswindows: + if metadata.comments: + if lb_added: + lb_added.Comment = (strip_tags.sub('',metadata.comments)) + if db_added: + db_added.Comment = (strip_tags.sub('',metadata.comments)) + + if metadata.rating: + if lb_added: + lb_added.AlbumRating = (metadata.rating*10) + # iBooks currently doesn't allow setting rating ... ? + try: + if db_added: + db_added.AlbumRating = (metadata.rating*10) + except: + pass + + if lb_added: + lb_added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) + lb_added.Enabled = True + lb_added.SortArtist = (metadata.author_sort.title()) + lb_added.SortName = (this_book.title_sorter) + + if db_added: + db_added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) + db_added.Enabled = True + db_added.SortArtist = (metadata.author_sort.title()) + db_added.SortName = (this_book.title_sorter) + + # Set genre from metadata + # iTunes grabs the first dc:subject from the opf metadata, + # But we can manually override with first tag starting with alpha + for tag in metadata.tags: + if self._is_alpha(tag[0]): + if lb_added: + lb_added.Category = (tag) + if db_added: + db_added.Category = (tag) + break + class BookList(list): ''' A list of books. Each Book object must have the fields: From 7a67294ae7fd0f3b77e6a7b3a918fea7bff22e4b Mon Sep 17 00:00:00 2001 From: GRiker Date: Thu, 10 Jun 2010 09:03:23 -0600 Subject: [PATCH 06/12] GwR revisions apple driver 0.5 --- src/calibre/devices/apple/driver.py | 332 +++++++++++++++++----------- 1 file changed, 201 insertions(+), 131 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index fc7d56e496..1bff7b9779 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -114,6 +114,7 @@ class ITUNES(DevicePlugin): # Properties cached_books = {} cache_dir = os.path.join(config_dir, 'caches', 'itunes') + description_prefix = "added by calibre" ejected = False iTunes= None iTunes_media = None @@ -620,6 +621,7 @@ class ITUNES(DevicePlugin): if DEBUG: self.log.info("ITUNES.remove_books_from_metadata()") for path in paths: + self._dump_cached_book(self.cached_books[path]) if self.cached_books[path]['lib_book']: # Remove from the booklist for i,book in enumerate(booklists[0]): @@ -881,6 +883,7 @@ class ITUNES(DevicePlugin): 'author': metadata[i].author[0], 'lib_book': lb_added, 'dev_book': db_added } + self._dump_cached_books(header="after upload_books()") # Report progress if self.report_progress is not None: @@ -928,7 +931,7 @@ class ITUNES(DevicePlugin): def _add_device_book(self,fpath, metadata): ''' ''' - self.log.info("ITUNES._add_device_book()") + self.log.info(" ITUNES._add_device_book()") if isosx: if 'iPod' in self.sources: connected_device = self.sources['iPod'] @@ -961,7 +964,7 @@ class ITUNES(DevicePlugin): break else: if DEBUG: - self.log.info(" no Books playlist found") + self.log.info(" no Books playlist found") # Add the passed book to the Device|Books playlist if pl: @@ -976,7 +979,7 @@ class ITUNES(DevicePlugin): op_status = pl.AddFiles(fa) if DEBUG: - sys.stdout.write(" uploading '%s' to device ..." % metadata.title) + sys.stdout.write(" uploading '%s' to device ..." % metadata.title) sys.stdout.flush() while op_status.InProgress: @@ -991,7 +994,7 @@ class ITUNES(DevicePlugin): # This doesn't seem to work with device, just Library if False: if DEBUG: - sys.stdout.write(" waiting for handle to added '%s' ..." % metadata.title) + sys.stdout.write(" waiting for handle to added '%s' ..." % metadata.title) sys.stdout.flush() while op_status.Tracks is None: time.sleep(0.5) @@ -1015,11 +1018,11 @@ class ITUNES(DevicePlugin): def _add_library_book(self,file, metadata): ''' - assumes pythoncom wrapper + windows assumes pythoncom wrapper ''' - self.log.info("ITUNES._add_library_book()") + self.log.info(" ITUNES._add_library_book()") if isosx: - print "to be implemented" + added = self.iTunes.add(appscript.mactypes.File(file)) elif iswindows: lib = self.iTunes.LibraryPlaylist @@ -1028,12 +1031,12 @@ class ITUNES(DevicePlugin): fa = FileArray(file_s) op_status = lib.AddFiles(fa) if DEBUG: - self.log.info(" file added to Library|Books") + self.log.info(" file added to Library|Books") - self.log.info(" iTunes adding '%s'" % file) + self.log.info(" iTunes adding '%s'" % file) if DEBUG: - sys.stdout.write(" iTunes copying '%s' ..." % metadata.title) + sys.stdout.write(" iTunes copying '%s' ..." % metadata.title) sys.stdout.flush() while op_status.InProgress: @@ -1047,7 +1050,7 @@ class ITUNES(DevicePlugin): if True: if DEBUG: - sys.stdout.write(" waiting for handle to added '%s' ..." % metadata.title) + sys.stdout.write(" waiting for handle to added '%s' ..." % metadata.title) sys.stdout.flush() while op_status.Tracks is None: time.sleep(0.5) @@ -1062,13 +1065,13 @@ class ITUNES(DevicePlugin): added = self._find_library_book( {'title': metadata.title, 'author': metadata.author[0]}) - return added + return added def _add_new_copy(self, fpath, metadata): ''' ''' if DEBUG: - self.log.info("ITUNES._add_new_copy()") + self.log.info(" ITUNES._add_new_copy()") db_added = None lb_added = None @@ -1076,15 +1079,15 @@ class ITUNES(DevicePlugin): if self.manual_sync_mode: db_added = self._add_device_book(fpath, metadata) if DEBUG: - self.log.info(" file uploaded to Device|Books") + self.log.info(" file uploaded to Device|Books") if not getattr(fpath, 'deleted_after_upload', False): lb_added = self._add_library_book(fpath, metadata) if DEBUG: - self.log.info(" file added to Library|Books for iTunes:iBooks tracking") + self.log.info(" file added to Library|Books for iTunes:iBooks tracking") else: lb_added = self._add_library_book(fpath, metadata) if DEBUG: - self.log.info(" file added to Library|Books for pending sync") + self.log.info(" file added to Library|Books for pending sync") return db_added, lb_added @@ -1092,20 +1095,42 @@ class ITUNES(DevicePlugin): ''' assumes pythoncom wrapper for db_added ''' - self.log.info("ITUNES._cover_to_thumb()") + self.log.info(" ITUNES._cover_to_thumb()") thumb = None if metadata.cover: - if lb_added: - if lb_added.Artwork.Count: - lb_added.Artwork.Item(1).SetArtworkFromFile(metadata.cover) - else: - lb_added.AddArtworkFromFile(metadata.cover) + if isosx: + cover_data = open(metadata.cover,'rb') + if lb_added: + lb_added.artworks[1].data_.set(cover_data.read()) - if db_added: - if db_added.Artwork.Count: - db_added.Artwork.Item(1).SetArtworkFromFile(metadata.cover) - else: - db_added.AddArtworkFromFile(metadata.cover) + if db_added: + # The following command generates an error, but the artwork does in fact + # get sent to the device. Seems like a bug in Apple's automation interface + try: + db_added.artworks[1].data_.set(cover_data.read()) + except: + if DEBUG: + self.log.warning(" iTunes automation interface generated an error" + " when adding artwork to '%s'" % metadata.title) + #import traceback + #traceback.print_exc() + #from calibre import ipython + #ipython(user_ns=locals()) + pass + + + elif iswindows: + if lb_added: + if lb_added.Artwork.Count: + lb_added.Artwork.Item(1).SetArtworkFromFile(metadata.cover) + else: + lb_added.AddArtworkFromFile(metadata.cover) + + if db_added: + if db_added.Artwork.Count: + db_added.Artwork.Item(1).SetArtworkFromFile(metadata.cover) + else: + db_added.AddArtworkFromFile(metadata.cover) try: # Resize for thumb @@ -1119,7 +1144,7 @@ class ITUNES(DevicePlugin): # Refresh the thumbnail cache if DEBUG: - self.log.info( " refreshing cached thumb for '%s'" % metadata.title) + self.log.info( " refreshing cached thumb for '%s'" % metadata.title) archive_path = os.path.join(self.cache_dir, "thumbs.zip") zfw = zipfile.ZipFile(archive_path, mode='a') thumb_path = path.rpartition('.')[0] + '.jpg' @@ -1127,7 +1152,7 @@ class ITUNES(DevicePlugin): zfw.close() except: self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) - self.log.error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title)) + self.log.error(" error converting '%s' to thumb for '%s'" % (metadata.cover,metadata.title)) return thumb @@ -1135,7 +1160,7 @@ class ITUNES(DevicePlugin): ''' ''' if DEBUG: - self.log.info("ITUNES._create_new_book()") + self.log.info(" ITUNES._create_new_book()") this_book = Book(metadata.title, metadata.author[0]) @@ -1183,7 +1208,7 @@ class ITUNES(DevicePlugin): ''' if DEBUG: - self.log.info("ITUNES._discover_manual_sync_mode()") + self.log.info(" ITUNES._discover_manual_sync_mode()") if isosx: connected_device = self.sources['iPod'] dev_books = None @@ -1193,18 +1218,18 @@ class ITUNES(DevicePlugin): dev_books = pl.file_tracks() break else: - self.log.error(" book_playlist not found") + self.log.error(" book_playlist not found") if len(dev_books): first_book = dev_books[0] #if DEBUG: - #self.log.info(" determing manual mode by modifying '%s' by %s" % (first_book.name(), first_book.artist())) + #self.log.info(" determing manual mode by modifying '%s' by %s" % (first_book.name(), first_book.artist())) try: first_book.bpm.set(0) self.manual_sync_mode = True except: 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) elif iswindows: if wait: @@ -1228,7 +1253,7 @@ class ITUNES(DevicePlugin): self.manual_sync_mode = True except: 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): ''' @@ -1246,6 +1271,31 @@ class ITUNES(DevicePlugin): self.log.info("%-40.40s %-30.30s" % (book.title, book.author)) + def _dump_cached_book(self, cached_book, header=None): + ''' + ''' + if header: + msg = '%s' % header + self.log.info(msg) + self.log.info( "%s" % ('-' * len(msg))) + if isosx: + self.log.info("%-40.40s %-30.30s %-10.10s %-10.10s" % + ('title', + 'author', + 'lib_book', + 'dev_book')) + self.log.info("%-40.40s %-30.30s %-10.10s %-10.10s" % + (cached_book['title'], + cached_book['author'], + str(cached_book['lib_book'])[-9:], + str(cached_book['dev_book'])[-9:])) + elif iswindows: + self.log.info("%-40.40s %-30.30s" % + (cached_book['title'], + cached_book['author'])) + + self.log.info() + def _dump_cached_books(self, header=None): ''' ''' @@ -1254,6 +1304,11 @@ class ITUNES(DevicePlugin): self.log.info(msg) self.log.info( "%s" % ('-' * len(msg))) 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(): self.log.info("%-40.40s %-30.30s %-10.10s %-10.10s" % (self.cached_books[cb]['title'], @@ -1318,8 +1373,8 @@ class ITUNES(DevicePlugin): ''' if iswindows: if DEBUG: - self.log.info("ITUNES._find_device_book()") - self.log.info(" looking for '%s' by %s" % (cached_book['title'], cached_book['author'])) + self.log.info(" ITUNES._find_device_book()") + self.log.info(" looking for '%s' by %s" % (cached_book['title'], cached_book['author'])) dev_books = self._get_device_books_playlist() attempts = 9 @@ -1328,17 +1383,17 @@ class ITUNES(DevicePlugin): hits = dev_books.Search(cached_book['author'],self.SearchField.index('Artists')) if hits: for hit in hits: - self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) + self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) if hit.Name == cached_book['title']: - self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist)) + self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist)) return hit attempts -= 1 time.sleep(0.5) if DEBUG: - self.log.warning(" attempt #%d" % (10 - attempts)) + self.log.warning(" attempt #%d" % (10 - attempts)) if DEBUG: - self.log.error(" search for '%s' yielded no hits" % cached_book['title']) + self.log.error(" search for '%s' yielded no hits" % cached_book['title']) return None def _find_library_book(self, cached_book): @@ -1347,18 +1402,18 @@ class ITUNES(DevicePlugin): ''' if iswindows: if DEBUG: - self.log.info("ITUNES._find_library_book()") - self.log.info(" looking for '%s' by %s" % (cached_book['title'], cached_book['author'])) + self.log.info(" ITUNES._find_library_book()") + self.log.info(" looking for '%s' by %s" % (cached_book['title'], cached_book['author'])) for source in self.iTunes.sources: if source.Kind == self.Sources.index('Library'): lib = source if DEBUG: - self.log.info(" Library source: '%s' kind: %s" % (lib.Name, self.Sources[lib.Kind])) + self.log.info(" Library source: '%s' kind: %s" % (lib.Name, self.Sources[lib.Kind])) break else: if DEBUG: - self.log.info(" Library source not found") + self.log.info(" Library source not found") if lib is not None: lib_books = None @@ -1366,12 +1421,12 @@ class ITUNES(DevicePlugin): if pl.Kind == self.PlaylistKind.index('User') and \ pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): if DEBUG: - self.log.info(" Books playlist: '%s'" % (pl.Name)) + self.log.info(" Books playlist: '%s'" % (pl.Name)) lib_books = pl break else: if DEBUG: - self.log.error(" no Books playlist found") + self.log.error(" no Books playlist found") attempts = 9 while attempts: @@ -1379,17 +1434,17 @@ class ITUNES(DevicePlugin): hits = lib_books.Search(cached_book['author'],self.SearchField.index('Artists')) if hits: for hit in hits: - self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) + self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) if hit.Name == cached_book['title']: - self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist)) + self.log.info(" matched '%s' by %s" % (hit.Name, hit.Artist)) return hit attempts -= 1 time.sleep(0.5) if DEBUG: - self.log.warning(" attempt #%d" % (10 - attempts)) + self.log.warning(" attempt #%d" % (10 - attempts)) if DEBUG: - self.log.error(" search for '%s' yielded no hits" % cached_book['title']) + self.log.error(" search for '%s' yielded no hits" % cached_book['title']) return None def _generate_thumbnail(self, book_path, book): @@ -1411,6 +1466,7 @@ class ITUNES(DevicePlugin): else: return thumb_data + self.log.info(" ITUNES._generate_thumbnail()") if isosx: try: # Resize the cover @@ -1424,21 +1480,19 @@ class ITUNES(DevicePlugin): # Cache the tagged thumb if DEBUG: - self.log.info("ITUNES._generate_thumbnail(): generated thumb for '%s', caching" % book.name()) + self.log.info(" generated thumb for '%s', caching" % book.name()) zfw.writestr(thumb_path, thumb.getvalue()) zfw.close() return thumb.getvalue() except: - self.log.error("ITUNES._generate_thumbnail(): error generating thumb for '%s'" % book.name()) + self.log.error(" error generating thumb for '%s'" % book.name()) return None elif iswindows: - if DEBUG: - self.log.info("ITUNES._generate_thumbnail()") if not book.Artwork.Count: if DEBUG: - self.log.info(" no artwork available") + self.log.info(" no artwork available") return None # Save the cover from iTunes @@ -1455,12 +1509,12 @@ class ITUNES(DevicePlugin): # Cache the tagged thumb if DEBUG: - self.log.info(" generated thumb for '%s', caching" % book.Name) + self.log.info(" generated thumb for '%s', caching" % book.Name) zfw.writestr(thumb_path, thumb.getvalue()) zfw.close() return thumb.getvalue() except: - self.log.error(" error generating thumb for '%s'" % book.Name) + self.log.error(" error generating thumb for '%s'" % book.Name) return None def _get_device_book_size(self, file, compressed_size): @@ -1473,9 +1527,9 @@ class ITUNES(DevicePlugin): for file in myZipList: exploded_file_size += file.file_size if DEBUG: - self.log.info("ITUNES._get_device_book_size()") - self.log.info(" %d items in archive" % len(myZipList)) - self.log.info(" compressed: %d exploded: %d" % (compressed_size, exploded_file_size)) + self.log.info(" ITUNES._get_device_book_size()") + self.log.info(" %d items in archive" % len(myZipList)) + self.log.info(" compressed: %d exploded: %d" % (compressed_size, exploded_file_size)) return exploded_file_size def _get_device_books(self): @@ -1483,7 +1537,7 @@ class ITUNES(DevicePlugin): Assumes pythoncom wrapper for Windows ''' if DEBUG: - self.log.info("\nITUNES._get_device_books()") + self.log.info("\n ITUNES._get_device_books()") device_books = [] if isosx: @@ -1493,20 +1547,20 @@ class ITUNES(DevicePlugin): for pl in device.playlists(): if pl.special_kind() == appscript.k.Books: if DEBUG: - self.log.info(" Book playlist: '%s'" % (pl.name())) + self.log.info(" Book playlist: '%s'" % (pl.name())) books = pl.file_tracks() break else: - self.log.error(" book_playlist not found") + self.log.error(" book_playlist not found") for book in books: # This may need additional entries for international iTunes users if book.kind() in ['MPEG audio file']: if DEBUG: - 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: if DEBUG: - self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind())) + self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind())) device_books.append(book) elif iswindows: @@ -1521,21 +1575,21 @@ class ITUNES(DevicePlugin): if pl.Kind == self.PlaylistKind.index('User') and \ pl.SpecialKind == self.PlaylistSpecialKind.index('Books'): if DEBUG: - self.log.info(" Books playlist: '%s'" % (pl.Name)) + self.log.info(" Books playlist: '%s'" % (pl.Name)) dev_books = pl.Tracks break else: if DEBUG: - self.log.info(" no Books playlist found") + self.log.info(" no Books playlist found") for book in dev_books: # This may need additional entries for international iTunes users if book.KindAsString in ['MPEG audio file']: if DEBUG: - self.log.info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString)) + self.log.info(" ignoring '%s' of type '%s'" % (book.Name, book.KindAsString)) else: if DEBUG: - self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString)) + self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString)) device_books.append(book) finally: @@ -1547,6 +1601,8 @@ class ITUNES(DevicePlugin): ''' assumes pythoncom wrapper ''' + if DEBUG: + self.log.info(" ITUNES._get_device_books_playlist()") if iswindows: if 'iPod' in self.sources: pl = None @@ -1560,7 +1616,7 @@ class ITUNES(DevicePlugin): break else: if DEBUG: - self.log.error(" no iPad|Books playlist found") + self.log.error(" no iPad|Books playlist found") return pl def _get_fpath(self,file): @@ -1570,7 +1626,7 @@ class ITUNES(DevicePlugin): calibre exits. ''' if DEBUG: - self.log.info("ITUNES._get_fpath()") + self.log.info(" ITUNES._get_fpath()") fpath = file if not getattr(fpath, 'deleted_after_upload', False): @@ -1580,7 +1636,7 @@ class ITUNES(DevicePlugin): fpath = file.name else: if DEBUG: - self.log.info(" file will be deleted after upload") + self.log.info(" file will be deleted after upload") return fpath def _get_library_books(self): @@ -1588,7 +1644,7 @@ class ITUNES(DevicePlugin): Populate a dict of paths from iTunes Library|Books ''' if DEBUG: - self.log.info("\nITUNES._get_library_books()") + self.log.info("\n ITUNES._get_library_books()") library_books = {} lib = None @@ -1598,11 +1654,11 @@ class ITUNES(DevicePlugin): if source.kind() == appscript.k.library: lib = source if DEBUG: - self.log.info(" Library source: '%s'" % (lib.name())) + self.log.info(" Library source: '%s'" % (lib.name())) break else: if DEBUG: - self.log.error(' Library source not found') + self.log.error(' Library source not found') if lib is not None: lib_books = None @@ -1610,29 +1666,37 @@ class ITUNES(DevicePlugin): for pl in lib.playlists(): if pl.special_kind() == appscript.k.Books: if DEBUG: - self.log.info(" Books playlist: '%s'" % (pl.name())) + self.log.info(" Books playlist: '%s'" % (pl.name())) break else: if DEBUG: - self.log.info(" no Library|Books playlist found") + self.log.info(" no Library|Books playlist found") lib_books = pl.file_tracks() for book in lib_books: # This may need additional entries for international iTunes users if book.kind() in ['MPEG audio file']: if DEBUG: - 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: - if DEBUG: - self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind())) + # Remove calibre orphans + if str(book.description()).startswith(self.description_prefix): + if book.location() == appscript.k.missing_value: + if DEBUG: + self.log.info(" deleting calibre orphan '%s' from Library|Books" % book.name()) + book.delete() + continue + path = self.path_template % (book.name(), book.artist()) library_books[path] = book + if DEBUG: + self.log.info(" adding %-30.30s [%s]" % (book.name(), book.kind())) else: if DEBUG: - self.log.info('No Library playlists') + self.log.info(' no Library playlists') else: if DEBUG: - self.log.info(' no Library found') + self.log.info(' no Library found') elif iswindows: lib = None @@ -1671,10 +1735,18 @@ class ITUNES(DevicePlugin): if DEBUG: self.log.info(" ignoring %-30.30s of type '%s'" % (book.Name, book.KindAsString)) else: - if DEBUG: - self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString)) + # Remove calibre orphans + if book.Description.startswith(self.description_prefix): + if not book.Location: + if DEBUG: + self.log.info(" deleting calibre orphan '%s' from Library|Books" % book.Name) + book.Delete() + continue + path = self.path_template % (book.Name, book.Artist) library_books[path] = book + if DEBUG: + self.log.info(" adding %-30.30s [%s]" % (book.Name, book.KindAsString)) except: if DEBUG: self.log.info(" no books in library") @@ -1730,7 +1802,7 @@ class ITUNES(DevicePlugin): ''' ''' if DEBUG: - self.log.info("ITUNES:_launch_iTunes():\n Instantiating iTunes") + self.log.info(" ITUNES:_launch_iTunes():\n Instantiating iTunes") if isosx: ''' @@ -1756,13 +1828,13 @@ class ITUNES(DevicePlugin): if os.path.exists(media_dir): self.iTunes_media = media_dir 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') if DEBUG: - self.log.info(" [%s - %s (%s), driver version %d.%d.%d]" % + self.log.info(" [%s - %s (%s), driver version %d.%d.%d]" % (self.iTunes.name(), self.iTunes.version(), initial_status, self.version[0],self.version[1],self.version[2])) - self.log.info(" iTunes_media: %s" % self.iTunes_media) + self.log.info(" iTunes_media: %s" % self.iTunes_media) if iswindows: ''' Launch iTunes if not already running @@ -1783,21 +1855,21 @@ class ITUNES(DevicePlugin): if os.path.exists(media_dir): self.iTunes_media = media_dir else: - self.log.error(" could not extract valid iTunes.media_dir from %s" % self.iTunes.LibraryXMLPath) - self.log.error(" %s" % string.parent.prettify()) - self.log.error(" '%s' not found" % media_dir) + self.log.error(" could not extract valid iTunes.media_dir from %s" % self.iTunes.LibraryXMLPath) + self.log.error(" %s" % string.parent.prettify()) + self.log.error(" '%s' not found" % media_dir) if DEBUG: - self.log.info( " [%s - %s (%s), driver version %d.%d.%d]" % + self.log.info(" [%s - %s (%s), driver version %d.%d.%d]" % (self.iTunes.Windows[0].name, self.iTunes.Version, initial_status, self.version[0],self.version[1],self.version[2])) - self.log.info(" iTunes_media: %s" % self.iTunes_media) + self.log.info(" iTunes_media: %s" % self.iTunes_media) def _remove_existing_copies(self,path,file,metadata): ''' ''' if DEBUG: - self.log.info("ITUNES._remove_existing_copies()") + self.log.info(" ITUNES._remove_existing_copies()") if self.manual_sync_mode: # Delete existing from Device|Books, add to self.update_list @@ -1806,14 +1878,14 @@ class ITUNES(DevicePlugin): self.update_list.append(self.cached_books[path]) self._remove_from_device(self.cached_books[path]) if DEBUG: - self.log.info( " deleting device book '%s'" % (path)) + 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: - self.log.info(" deleting library book '%s'" % path) + self.log.info(" deleting library book '%s'" % path) else: if DEBUG: - self.log.info(" '%s' not in cached_books" % metadata.title) + self.log.info(" '%s' not in cached_books" % metadata.title) else: # Delete existing from Library|Books, add to self.update_list # for deletion from booklist[0] during add_books_to_metadata @@ -1821,21 +1893,20 @@ class ITUNES(DevicePlugin): self.update_list.append(self.cached_books[path]) self._remove_from_iTunes(self.cached_books[path]) if DEBUG: - self.log.info( " deleting library book '%s'" % path) + self.log.info( " deleting library book '%s'" % path) else: if DEBUG: - self.log.info(" '%s' not in cached_books" % metadata.title) + self.log.info(" '%s' not in cached_books" % metadata.title) def _remove_from_device(self, cached_book): ''' Windows assumes pythoncom wrapper ''' - self.log.info("ITUNES._remove_from_device()") + self.log.info(" ITUNES._remove_from_device()") if isosx: if DEBUG: - self.log.info(" deleting %s" % cached_book['dev_book']) + self.log.info(" deleting %s" % cached_book['dev_book']) result = cached_book['dev_book'].delete() - print "result: %s" % result elif iswindows: dev_pl = self._get_device_books_playlist() @@ -1843,10 +1914,10 @@ class ITUNES(DevicePlugin): if hits: for hit in hits: if DEBUG: - self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) + self.log.info(" evaluating '%s' by %s" % (hit.Name, hit.Artist)) if hit.Name == cached_book['title']: if DEBUG: - self.log.info(" deleting '%s' by %s" % (hit.Name, hit.Artist)) + self.log.info(" deleting '%s' by %s" % (hit.Name, hit.Artist)) results = hit.Delete() break @@ -1856,34 +1927,36 @@ class ITUNES(DevicePlugin): 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 ''' + if DEBUG: + self.log.info(" ITUNES._remove_from_iTunes():") + if isosx: storage_path = os.path.split(cached_book['lib_book'].location().path) if cached_book['lib_book'].location().path.startswith(self.iTunes_media): title_storage_path = storage_path[0] if DEBUG: - self.log.info("ITUNES._remove_from_iTunes():") - self.log.info(" removing title_storage_path: %s" % title_storage_path) + 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) + 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) + 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") + 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)) + self.log.info(" author_storage_path not empty (%d objects):" % len(author_files)) + self.log.info(" %s" % '\n'.join(author_files)) else: - self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title']) + self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title']) self.iTunes.delete(cached_book['lib_book']) @@ -1892,42 +1965,40 @@ class ITUNES(DevicePlugin): Assume we're wrapped in a pythoncom Windows stores the book under a common author directory, so we just delete the .epub ''' - if DEBUG: - self.log.info("ITUNES._remove_from_iTunes():\n '%s'" % cached_book['title']) + book = self._find_library_book(cached_book) if book: path = book.Location storage_path = os.path.split(book.Location) if book.Location.startswith(self.iTunes_media): if DEBUG: - self.log.info("ITUNES._remove_from_iTunes():") - self.log.info(" removing '%s' at %s" % + self.log.info(" removing '%s' at %s" % (cached_book['title'], path)) try: os.remove(path) except: - self.log.warning(" could not find '%s' in iTunes storage" % path) + self.log.warning(" could not find '%s' in iTunes storage" % path) try: os.rmdir(storage_path[0]) - self.log.info(" removed folder '%s'" % 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]) + self.log.info(" folder '%s' not found or not empty" % storage_path[0]) # Delete from iTunes database else: - self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title']) + self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title']) book.Delete() else: - self.log.warning(" could not find '%s' in iTunes database" % cached_book['title']) + self.log.warning(" could not find '%s' in iTunes database" % cached_book['title']) def _update_device(self, msg='', wait=True): ''' Trigger a sync, wait for completion ''' if DEBUG: - self.log.info("ITUNES:_update_device():\n %s" % msg) + self.log.info(" ITUNES:_update_device():\n %s" % msg) if isosx: self.iTunes.update() @@ -1935,7 +2006,7 @@ class ITUNES(DevicePlugin): if wait: # This works if iTunes has books not yet synced to iPad. if DEBUG: - sys.stdout.write(" waiting for iPad sync to complete ...") + sys.stdout.write(" waiting for iPad sync to complete ...") sys.stdout.flush() while len(self._get_device_books()) != (len(self._get_library_books()) + len(self._get_purchased_book_ids())): if DEBUG: @@ -1950,7 +2021,7 @@ class ITUNES(DevicePlugin): self.iTunes.UpdateIPod() if wait: if DEBUG: - sys.stdout.write(" waiting for iPad sync to complete ...") + sys.stdout.write(" waiting for iPad sync to complete ...") sys.stdout.flush() while True: db_count = len(self._get_device_books()) @@ -1973,7 +2044,7 @@ class ITUNES(DevicePlugin): ''' ''' if DEBUG: - self.log.info("ITUNES._update_iTunes_metadata()") + self.log.info(" ITUNES._update_iTunes_metadata()") strip_tags = re.compile(r'<[^<]*?/?>') @@ -1995,13 +2066,13 @@ class ITUNES(DevicePlugin): pass if lb_added: - lb_added.description.set("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) + lb_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) lb_added.enabled.set(True) lb_added.sort_artist.set(metadata.author_sort.title()) lb_added.sort_name.set(this_book.title_sorter) if db_added: - db_added.description.set("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) + db_added.description.set("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) db_added.enabled.set(True) db_added.sort_artist.set(metadata.author_sort.title()) db_added.sort_name.set(this_book.title_sorter) @@ -2036,14 +2107,13 @@ class ITUNES(DevicePlugin): pass if lb_added: - lb_added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) + lb_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) lb_added.Enabled = True lb_added.SortArtist = (metadata.author_sort.title()) lb_added.SortName = (this_book.title_sorter) if db_added: - db_added.Description = ("added by calibre %s" % strftime('%Y-%m-%d %H:%M:%S')) - db_added.Enabled = True + db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) db_added.SortArtist = (metadata.author_sort.title()) db_added.SortName = (this_book.title_sorter) From 5cf08b6b526d5c2d0d483d5b61ea96ec4b75dc9b Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 11 Jun 2010 04:26:57 -0600 Subject: [PATCH 07/12] GwR revisions wip --- src/calibre/devices/apple/driver.py | 104 +++++++++++++++++++--------- 1 file changed, 73 insertions(+), 31 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 1bff7b9779..c97549043d 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -35,10 +35,39 @@ if iswindows: class ITUNES(DevicePlugin): ''' - try: - pythoncom.CoInitialize() - finally: - pythoncom.CoUninitialize() + Calling sequences: + Initialization: + can_handle() or can_handle_windows() + reset() + open() + card_prefix() + can_handle() + set_progress_reporter() + get_device_information() + card_prefix() + free_space() + (Job 1 Get device information finishes) + can_handle() + set_progress_reporter() + books() (once for each storage point) + settings() + settings() + can_handle() (~1x per second OSX while idle) + Delete: + delete_books() + remove_books_from_metadata() + sync_booklists() + card_prefix() + free_space() + Upload: + settings() + set_progress_reporter() + upload_books() + add_books_to_metadata() + set_progress_reporter() + sync_booklists() + card_prefix() + free_space() ''' name = 'Apple device interface' @@ -1778,17 +1807,27 @@ class ITUNES(DevicePlugin): def _get_sources(self): ''' Return a dict of sources + Check for >1 iPod device connected to iTunes ''' if isosx: names = [s.name() for s in self.iTunes.sources()] kinds = [str(s.kind()).rpartition('.')[2] for s in self.iTunes.sources()] - return dict(zip(kinds,names)) elif iswindows: # Assumes a pythoncom wrapper it_sources = ['Unknown','Library','iPod','AudioCD','MP3CD','Device','RadioTuner','SharedLibrary'] names = [s.name for s in self.iTunes.sources] kinds = [it_sources[s.kind] for s in self.iTunes.sources] - return dict(zip(kinds,names)) + + # If more than one connected iDevice, remove all from list to prevent driver initialization + if kinds.count('iPod') > 1: + if DEBUG: + self.log.error(" %d connected iPod devices detected, calibre supports a single connected iDevice" % kinds.count('iPod')) + while kinds.count('iPod'): + index = kinds.index('iPod') + kinds.pop(index) + names.pop(index) + + return dict(zip(kinds,names)) def _is_alpha(self,char): ''' @@ -1931,34 +1970,37 @@ class ITUNES(DevicePlugin): self.log.info(" ITUNES._remove_from_iTunes():") if isosx: - storage_path = os.path.split(cached_book['lib_book'].location().path) - if cached_book['lib_book'].location().path.startswith(self.iTunes_media): - title_storage_path = storage_path[0] - if DEBUG: - 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) + try: + storage_path = os.path.split(cached_book['lib_book'].location().path) + if cached_book['lib_book'].location().path.startswith(self.iTunes_media): + title_storage_path = storage_path[0] if DEBUG: - self.log.info(" removing empty author_storage_path") + 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: - if DEBUG: - self.log.info(" author_storage_path not empty (%d objects):" % len(author_files)) - self.log.info(" %s" % '\n'.join(author_files)) - else: - self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title']) + self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title']) - self.iTunes.delete(cached_book['lib_book']) + self.iTunes.delete(cached_book['lib_book']) + except: + self.log.warning(" error removing %s from iTunes" % cached_book['title']) elif iswindows: ''' From 781858ca150993a0606135b91f00b69dab6b23e6 Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 11 Jun 2010 06:24:59 -0600 Subject: [PATCH 08/12] GwR fix to output empty catalogs when no search results --- src/calibre/library/catalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index 53a93deac4..21aa863031 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -83,7 +83,7 @@ class CSV_XML(CatalogPlugin): if not len(data): log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text) - raise SystemExit(1) + #raise SystemExit(1) # Get the requested output fields as a list fields = self.get_output_fields(opts) From 531b17171f05a535e1d83755a5164535a5c66f51 Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 11 Jun 2010 07:10:30 -0600 Subject: [PATCH 09/12] GwR revisions iDevice driver --- src/calibre/devices/apple/driver.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index c97549043d..721c2dda05 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -1712,9 +1712,9 @@ class ITUNES(DevicePlugin): if str(book.description()).startswith(self.description_prefix): if book.location() == appscript.k.missing_value: if DEBUG: - self.log.info(" deleting calibre orphan '%s' from Library|Books" % book.name()) - book.delete() - continue + self.log.info(" found calibre orphan '%s' in Library|Books" % book.name()) + #book.delete() + #continue path = self.path_template % (book.name(), book.artist()) library_books[path] = book @@ -1768,9 +1768,9 @@ class ITUNES(DevicePlugin): if book.Description.startswith(self.description_prefix): if not book.Location: if DEBUG: - self.log.info(" deleting calibre orphan '%s' from Library|Books" % book.Name) - book.Delete() - continue + self.log.info(" found calibre orphan '%s' in Library|Books" % book.Name) + #book.Delete() + #continue path = self.path_template % (book.Name, book.Artist) library_books[path] = book @@ -1998,9 +1998,11 @@ class ITUNES(DevicePlugin): else: self.log.info(" '%s' stored external to iTunes, no files deleted" % cached_book['title']) - self.iTunes.delete(cached_book['lib_book']) except: - self.log.warning(" error removing %s from iTunes" % cached_book['title']) + # We get here if there was an error with .location().path + self.log.info(" removing orphan '%s' from iTunes" % cached_book['title']) + + self.iTunes.delete(cached_book['lib_book']) elif iswindows: ''' From d1a47c22811e960a6bed7544593b0fcff3ee3e61 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 11 Jun 2010 10:06:52 -0600 Subject: [PATCH 10/12] ... --- src/calibre/gui2/viewer/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index 0964ed47c3..fca3586f9d 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -229,9 +229,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.connect(self.action_previous_page, SIGNAL('triggered(bool)'), lambda x:self.view.previous_page()) self.connect(self.action_find_next, SIGNAL('triggered(bool)'), - lambda x:self.find(self.search.smart_text, True, repeat=True)) + lambda x:self.find(self.search.smart_text, repeat=True)) self.connect(self.action_find_previous, SIGNAL('triggered(bool)'), - lambda x:self.find(self.search.smart_text, True, + lambda x:self.find(self.search.smart_text, repeat=True, backwards=True)) self.connect(self.action_full_screen, SIGNAL('triggered(bool)'), From ab9acc2b95e2feb240346d27fd07d2380dd0c06b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 11 Jun 2010 10:39:20 -0600 Subject: [PATCH 11/12] Fix another regression in the search box --- src/calibre/gui2/search_box.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py index 17a815c4ce..e4de18a132 100644 --- a/src/calibre/gui2/search_box.py +++ b/src/calibre/gui2/search_box.py @@ -100,7 +100,6 @@ class SearchBox2(QComboBox): self.help_state = False def clear_to_help(self): - self.search.emit('') self._in_a_search = False self.setEditText(self.help_text) if self.timer is not None: # Turn off any timers that got started in setEditText @@ -112,6 +111,7 @@ class SearchBox2(QComboBox): 'QLineEdit { color: gray; background-color: %s; }' % self.normal_background) self.emit(SIGNAL('cleared()')) + self.search.emit('') def text(self): return self.currentText() From 6b12237e7fb58cbae05d3d2bb1698d9069cb02d0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 11 Jun 2010 10:53:29 -0600 Subject: [PATCH 12/12] Fix widescreen detection --- src/calibre/gui2/__init__.py | 14 ++++++++++---- src/calibre/gui2/init.py | 5 +++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index cbe9449f1f..3063ef252d 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -127,10 +127,16 @@ def available_width(): desktop = QCoreApplication.instance().desktop() return desktop.availableGeometry().width() -try: - is_widescreen = float(available_width())/available_height() > 1.4 -except: - is_widescreen = True +_is_widescreen = None + +def is_widescreen(): + global _is_widescreen + if _is_widescreen is None: + try: + _is_widescreen = float(available_width())/available_height() > 1.4 + except: + _is_widescreen = False + return _is_widescreen def extension(path): return os.path.splitext(path)[1][1:].lower() diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 93f3a7c623..5a039e7bc1 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -290,14 +290,15 @@ class LibraryWidget(Splitter): # {{{ def __init__(self, parent): orientation = Qt.Vertical if config['gui_layout'] == 'narrow' and \ - not is_widescreen else Qt.Horizontal + not is_widescreen() else Qt.Horizontal #orientation = Qt.Vertical idx = 0 if orientation == Qt.Vertical else 1 + size = 300 if orientation == Qt.Vertical else 550 Splitter.__init__(self, 'cover_browser_splitter', _('Cover Browser'), I('cover_flow.svg'), orientation=orientation, parent=parent, connect_button=not config['separate_cover_flow'], - side_index=idx, initial_side_size=400, initial_show=False) + side_index=idx, initial_side_size=size, initial_show=False) parent.library_view = BooksView(parent) parent.library_view.setObjectName('library_view') self.addWidget(parent.library_view)