From 25cc95386c24103ee86781218850150912ce970d Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 4 Jun 2010 07:14:54 -0600 Subject: [PATCH 1/4] GwR revisions --- src/calibre/devices/apple/driver.py | 231 ++++++++++++++++++++-------- src/calibre/web/feeds/news.py | 73 +++++++-- src/calibre/web/feeds/templates.py | 9 +- 3 files changed, 233 insertions(+), 80 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 8d1037bc44..5edb8acf02 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -51,7 +51,8 @@ class ITUNES(DevicePlugin): description = _('Communicate with iBooks through iTunes.') supported_platforms = ['osx','windows'] author = 'GRiker' - driver_version = '0.2' + #: The version of this plugin as a 3-tuple (major, minor, revision) + version = (0, 3, 0) OPEN_FEEDBACK_MESSAGE = _( 'Apple device detected, launching iTunes, please wait ...') @@ -99,16 +100,19 @@ class ITUNES(DevicePlugin): if isosx: if DEBUG: self.log.info( "ITUNES.add_books_to_metadata()") + self._dump_update_list('add_books_to_metadata()') for (j,p_book) in enumerate(self.update_list): - #self.log.info("ITUNES.add_books_to_metadata(): looking for %s" % p_book['lib_book']) + self.log.info("ITUNES.add_books_to_metadata(): looking for %s" % + str(p_book['lib_book'])[-9:]) for i,bl_book in enumerate(booklists[0]): - #self.log.info("ITUNES.add_books_to_metadata(): evaluating %s" % bl_book.library_id) if bl_book.library_id == p_book['lib_book']: booklists[0].pop(i) - #self.log.info("ITUNES.add_books_to_metadata(): removing %s" % p_book['title']) + self.log.info("ITUNES.add_books_to_metadata(): removing %s %s" % + (p_book['title'], str(p_book['lib_book'])[-9:])) break else: - self.log.error(" update_list item '%s' not found in booklists[0]" % p_book['title']) + self.log.error(" update_list item '%s' by %s %s not found in booklists[0]" % + (p_book['title'], p_book['author'],str(p_book['lib_book'])[-9:])) if self.report_progress is not None: self.report_progress(j+1/task_count, _('Updating device metadata listing...')) @@ -136,7 +140,12 @@ class ITUNES(DevicePlugin): # Add new books to booklists[0] for new_book in locations[0]: + if DEBUG: + self.log.info(" adding '%s' by '%s' to booklists[0]" % + (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): """ @@ -153,10 +162,10 @@ class ITUNES(DevicePlugin): list of device books. """ - if DEBUG: - self.log.info("ITUNES:books(oncard=%s)" % oncard) - if not oncard: + if DEBUG: + self.log.info("ITUNES:books(oncard=%s)" % oncard) + # Fetch a list of books from iPod device connected to iTunes # Fetch Library|Books @@ -187,7 +196,7 @@ class ITUNES(DevicePlugin): cached_books[this_book.path] = { 'title':book.name(), - 'author':book.artist(), + 'author':[book.artist()], 'lib_book':library_books[this_book.path] if this_book.path in library_books else None } @@ -232,7 +241,8 @@ class ITUNES(DevicePlugin): self.report_progress(1.0, _('finished')) self.cached_books = cached_books if DEBUG: - self._dump_cached_books() + self._dump_booklist(booklist, 'returning from books():') + self._dump_cached_books('returning from books():') return booklist else: return [] @@ -262,9 +272,9 @@ class ITUNES(DevicePlugin): ''' self.sources = self._get_sources() if 'iPod' in self.sources: - if DEBUG: - sys.stdout.write('.') - sys.stdout.flush() +# if DEBUG: +# sys.stdout.write('.') +# sys.stdout.flush() return True else: if DEBUG: @@ -333,8 +343,8 @@ class ITUNES(DevicePlugin): ('place', None) (None, None) ''' - if DEBUG: - self.log.info("ITUNES:card_prefix()") +# if DEBUG: +# self.log.info("ITUNES:card_prefix()") return (None,None) def delete_books(self, paths, end_session=True): @@ -399,6 +409,8 @@ class ITUNES(DevicePlugin): @return: A 3 element list with free space in bytes of (1, 2, 3). If a particular device doesn't have any of these locations it should return -1. + + In Windows, a sync-in-progress blocks this call until sync is complete """ if DEBUG: self.log.info("ITUNES:free_space()") @@ -471,7 +483,7 @@ class ITUNES(DevicePlugin): if DEBUG: self.log.info( " %s - %s (%s), driver version %s" % - (self.iTunes.name(), self.iTunes.version(), initial_status, self.driver_version)) + (self.iTunes.name(), self.iTunes.version(), initial_status, repr(self.version))) # Init the iTunes source list ''' @@ -495,8 +507,9 @@ class ITUNES(DevicePlugin): initial_status = 'launched' if DEBUG: - self.log.info( " %s - %s (%s), driver version %s" % - (self.iTunes.Windows[0].name, self.iTunes.Version, initial_status, self.driver_version)) + 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])) # Init the iTunes source list self.sources = self._get_sources() @@ -545,10 +558,10 @@ class ITUNES(DevicePlugin): self.log.error("ITUNES.remove_books_from_metadata(): '%s' not found in self.cached_book" % path) # Remove from cached_books + self.cached_books.pop(path) if DEBUG: self.log.info("ITUNES.remove_books_from_metadata(): Removing '%s' from self.cached_books" % path) - self.cached_books.pop(path) - + self._dump_cached_books('remove_books_from_metadata()') else: self.log.warning("ITUNES.remove_books_from_metadata(): skipping purchased book, can't remove via automation interface") @@ -573,8 +586,8 @@ class ITUNES(DevicePlugin): If it is called with -1 that means that the task does not have any progress information ''' - if DEBUG: - self.log.info("ITUNES:set_progress_reporter()") +# if DEBUG: +# self.log.info("ITUNES:set_progress_reporter()") self.report_progress = report_progress def settings(self): @@ -582,8 +595,8 @@ class ITUNES(DevicePlugin): Should return an opts object. The opts object should have one attribute `format_map` which is an ordered list of formats for the device. ''' - if DEBUG: - self.log.info("ITUNES.settings()") +# if DEBUG: +# self.log.info("ITUNES.settings()") klass = self if isinstance(self, type) else self.__class__ c = Config('device_drivers_%s' % klass.__name__, _('settings for device drivers')) c.add_opt('format_map', default=self.FORMATS, @@ -600,23 +613,29 @@ class ITUNES(DevicePlugin): if DEBUG: self.log.info("ITUNES:sync_booklists():") if self.update_needed: + if DEBUG: + self.log.info(' calling _update_device') self._update_device(msg=self.update_msg) self.update_needed = False # Get actual size of updated books on device if self.update_list: if DEBUG: - self.log.info("ITUNES:sync_booklists()\n update_list:") - for ub in self.update_list: - self.log.info(" '%s' by %s" % (ub['title'], ub['author'])) - + self._dump_update_list(header='sync_booklists()') if isosx: for updated_book in self.update_list: - size_on_device = self._get_device_book_size(updated_book['title'], updated_book['author']) + size_on_device = self._get_device_book_size(updated_book['title'], + updated_book['author'][0]) if size_on_device: + if DEBUG: + self._dump_booklist(booklists[0], 'sync_booklists()') + self.log.info(" looking for '%s' by %s" % + (updated_book['title'], updated_book['author'])) for book in booklists[0]: if book.title == updated_book['title'] and \ - book.author[0] == updated_book['author']: + book.author == updated_book['author']: + if DEBUG: + self.log.info(" found '%s' by %s" % (book.title, book.author[0])) book.size = size_on_device break else: @@ -705,15 +724,24 @@ class ITUNES(DevicePlugin): "Click 'Show Details' for a list.") if isosx: + if DEBUG: + self.log.info("ITUNES.upload_books():") + self._dump_files(files, header='upload_books()') + self._dump_cached_books('upload_books()') + self._dump_update_list('upload_books()') for (i,file) in enumerate(files): path = self.path_template % (metadata[i].title, metadata[i].author[0]) # 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: + if DEBUG: + self.log.info(" adding '%s' by %s to self.update_list" % + (self.cached_books[path]['title'],self.cached_books[path]['author'])) + + # *** Second time a book is updated the author is a list *** 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]) @@ -941,26 +969,44 @@ class ITUNES(DevicePlugin): return (new_booklist, [], []) # Private methods - def _dump_booklist(self,booklist, header="booklists[0]"): + def _dump_booklist(self, booklist, header=None): ''' ''' - self.log.info() - self.log.info(header) - self.log.info( "%s" % ('-' * len(header))) - for i,book in enumerate(booklist): - self.log.info( "%2d %-25.25s %s" % (i,book.title, book.library_id)) + if header: + msg = '\nbooklist, %s' % header + self.log.info(msg) + self.log.info('%s' % ('-' * len(msg))) + + for book in booklist: + if isosx: + self.log.info("%-40.40s %-30.30s %-10.10s" % + (book.title, book.author, str(book.library_id)[-9:])) + elif iswindows: + self.log.info("%-40.40s %-30.30s" % + (book.title, book.author)) + + def _dump_cached_books(self, header=None): + ''' + ''' + if header: + msg = '\nself.cached_books, %s' % header + self.log.info(msg) + 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.cached_books[cb]['title'], + self.cached_books[cb]['author'], + str(self.cached_books[cb]['lib_book'])[-9:])) + elif iswindows: + for cb in self.cached_books.keys(): + self.log.info("%-40.40s %-30.30s" % + (self.cached_books[cb]['title'], + self.cached_books[cb]['author'])) + self.log.info() - def _dump_cached_books(self): - ''' - ''' - self.log.info("\n%-40.40s %-12.12s" % ('Device Books','In Library')) - self.log.info("%-40.40s %-12.12s" % ('------------','----------')) - for cb in self.cached_books.keys(): - self.log.info("%-40.40s %6.6s" % (self.cached_books[cb]['title'], 'yes' if self.cached_books[cb]['lib_book'] else ' no')) - self.log.info("\n") - - def _hexdump(self, src, length=16): + def _dump_hex(self, src, length=16): ''' ''' FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) @@ -973,6 +1019,34 @@ class ITUNES(DevicePlugin): N+=length print result + def _dump_files(self, files, header=None): + if header: + msg = '\nfiles passed to %s:' % header + self.log.info(msg) + self.log.info( "%s" % ('-' * len(msg))) + for file in files: + self.log.info(file) + self.log.info() + + def _dump_update_list(self,header=None): + if header: + msg = '\nself.update_list called from %s' % header + self.log.info(msg) + self.log.info( "%s" % ('-' * len(msg))) + + if isosx: + for ub in self.update_list: + self.log.info("%-40.40s %-30.30s %-10.10s" % + (ub['title'], + ub['author'], + str(ub['lib_book'])[-9:])) + elif iswindows: + for ub in self.update_list: + self.log.info("%-40.40s %-30.30s" % + (ub['title'], + ub['author'])) + self.log.info() + def _find_device_book(self, cached_book): ''' Windows-only method to get a handle to a device book in the current pythoncom session @@ -1034,11 +1108,11 @@ class ITUNES(DevicePlugin): except: zfw = zipfile.ZipFile(archive_path, mode='a') else: - if DEBUG: - if isosx: - self.log.info("ITUNES._generate_thumbnail(): cached thumb found for '%s'" % book.name()) - elif iswindows: - self.log.info("ITUNES._generate_thumbnail(): cached thumb found for '%s'" % book.Name) +# if DEBUG: +# if isosx: +# self.log.info("ITUNES._generate_thumbnail(): cached thumb found for '%s'" % book.name()) +# elif iswindows: +# self.log.info("ITUNES._generate_thumbnail(): cached thumb found for '%s'" % book.Name) return thumb_data @@ -1046,7 +1120,7 @@ class ITUNES(DevicePlugin): try: # Resize the cover data = book.artworks[1].raw_data().data - #self._hexdump(data[:256]) + #self._dump_hex(data[:256]) im = PILImage.open(cStringIO.StringIO(data)) scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80) im = im.resize((int(width),int(height)), PILImage.ANTIALIAS) @@ -1097,20 +1171,27 @@ class ITUNES(DevicePlugin): def _get_device_book_size(self, title, author): ''' Fetch the size of a book stored on the device + + Windows: If sync-in-progress, this call blocked until sync completes ''' if DEBUG: - self.log.info("ITUNES._get_device_book_size():\n looking for title: '%s' author: %s" % (title,author)) + self.log.info("ITUNES._get_device_book_size():\n looking for title: '%s' author: '%s'" % + (title,author)) device_books = self._get_device_books() if isosx: for d_book in device_books: if DEBUG: - self.log.info(" evaluating title: '%s' author: '%s'" % (d_book.name(), d_book.artist())) + self.log.info(" evaluating title: '%s' author: '%s'" % + (d_book.name(), d_book.artist())) if d_book.name() == title and d_book.artist() == author: + if DEBUG: + self.log.info(' found it') return d_book.size() else: - self.log.error("ITUNES._get_device_book_size(): could not find '%s' by '%s' in device_books" % (title,author)) + self.log.error("ITUNES._get_device_book_size():" + " could not find '%s' by '%s' in device_books" % (title,author)) return None elif iswindows: for d_book in device_books: @@ -1188,9 +1269,10 @@ class ITUNES(DevicePlugin): return [] elif iswindows: dev = self.iTunes.sources.ItemByName(connected_device) - dev_playlists = [pl.Name for pl in dev.Playlists] - if 'Purchased' in dev_playlists: - return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Purchased').Tracks + if dev.Playlists is not None: + dev_playlists = [pl.Name for pl in dev.Playlists] + if 'Purchased' in dev_playlists: + return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Purchased').Tracks else: return [] @@ -1222,10 +1304,30 @@ class ITUNES(DevicePlugin): ''' if isosx: storage_path = os.path.split(cached_book['lib_book'].location().path) + title_storage_path = storage_path[0] if DEBUG: self.log.info("ITUNES._remove_from_iTunes():") - self.log.info(" removing storage_path: %s" % storage_path[0]) - shutil.rmtree(storage_path[0]) + 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)) + self.iTunes.delete(cached_book['lib_book']) elif iswindows: @@ -1280,7 +1382,7 @@ class ITUNES(DevicePlugin): try: pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") - #result = self.iTunes.UpdateIPod() + result = self.iTunes.UpdateIPod() if wait: if DEBUG: sys.stdout.write(" waiting for iPad sync to complete ...") @@ -1291,7 +1393,8 @@ class ITUNES(DevicePlugin): pb_count = len(self._get_purchased_book_ids()) if db_count != lb_count + pb_count: if DEBUG: - sys.stdout.write('.') + sys.stdout.write(' %d != %d + %d\n' % (db_count,lb_count,pb_count)) + #sys.stdout.write('.') sys.stdout.flush() time.sleep(2) else: @@ -1333,8 +1436,6 @@ class BookList(list): Add the book to the booklist. Intent is to maintain any device-internal metadata. Return True if booklists must be sync'ed ''' - if DEBUG: - self.log.info("BookList.add_book():\n%s" % book) self.append(book) def remove_book(self, book): diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index f1c3b414b1..b9335a17fa 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -267,7 +267,7 @@ class BasicNewsRecipe(Recipe): } a.article { - font-weight: bold; + font-weight: bold; text-align:left; } a.feed { @@ -283,6 +283,15 @@ class BasicNewsRecipe(Recipe): #: Override this in your recipe to provide a url to use as a masthead. masthead_url = None + #: By default, the cover image returned by get_cover_url() will be used as + #: the cover for the periodical. Overriding this in your recipe instructs + #: calibre to render the downloaded cover into a frame whose width and height + #: are expressed as a percentage of the downloaded cover. + #: For example, + #: cover_margins = (10,15) + #: would pad the downloaded cover 10px on the left and right, 15px on the top and bottom. + cover_margins = (0,0) + #: Set to a non empty string to disable this recipe #: The string will be used as the disabled message recipe_disabled = None @@ -758,15 +767,15 @@ class BasicNewsRecipe(Recipe): if self.touchscreen: touchscreen_css = u''' .summary_headline { - font-size:large; font-weight:bold; margin-top:0px; margin-bottom:0px; + font-weight:bold; text-align:left; } .summary_byline { - font-size:small; margin-top:0px; margin-bottom:0px; + font-family:monospace; } .summary_text { - margin-top:0px; margin-bottom:0px; + text-align:left; } .feed { @@ -782,9 +791,6 @@ class BasicNewsRecipe(Recipe): border-width:thin; } - table.toc { - font-size:large; - } ''' templ = templates.TouchscreenFeedTemplate() @@ -960,8 +966,50 @@ class BasicNewsRecipe(Recipe): cfile.write(open(cu, 'rb').read()) else: self.report_progress(1, _('Downloading cover from %s')%cu) - with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r): - cfile.write(r.read()) + + if self.cover_margin[0] == 0 and self.cover_margin[1] == 0: + with nested(open(cpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r): + cfile.write(r.read()) + else: + ccpath = os.path.join(self.output_dir, 'cover_contents.'+ext) + with nested(open(ccpath, 'wb'), closing(self.browser.open(cu))) as (cfile, r): + cfile.write(r.read()) + + import calibre.utils.PythonMagickWand as pw + with pw.ImageMagick(): + img = pw.NewMagickWand() + img2 = pw.NewMagickWand() + frame = pw.NewMagickWand() + p = pw.NewPixelWand() + if img < 0 or img2 < 0 or p < 0 or frame < 0: + raise RuntimeError('Out of memory') + if not pw.MagickReadImage(img, ccpath): + severity = pw.ExceptionType(0) + msg = pw.MagickGetException(img, byref(severity)) + raise IOError('Failed to read image from: %s: %s' + %(ccpath, msg)) + #pw.PixelSetColor(p, 'white') + pw.PixelSetColor(p, 'rgb(252,252,252)') + width = pw.MagickGetImageWidth(img) + self.cover_margin[0]*2 + height = pw.MagickGetImageHeight(img) + self.cover_margin[1]*2 + if not pw.MagickNewImage(img2, width, height, p): + raise RuntimeError('Out of memory') + if not pw.MagickNewImage(frame, width, height, p): + raise RuntimeError('Out of memory') + if not pw.MagickCompositeImage(img2, img, pw.OverCompositeOp, 0, 0): + raise RuntimeError('Out of memory') + left = self.cover_margin[0] + top = self.cover_margin[1] + if not pw.MagickCompositeImage(frame, img2, pw.OverCompositeOp, + left, top): + raise RuntimeError('Out of memory') + if not pw.MagickWriteImage(frame, cpath): + raise RuntimeError('Failed to save image to %s'%cpath) + pw.DestroyPixelWand(p) + for x in (img, img2, frame): + pw.DestroyMagickWand(x) + os.remove(ccpath) + if ext.lower() == 'pdf': from calibre.ebooks.metadata.pdf import get_metadata stream = open(cpath, 'rb') @@ -1118,8 +1166,10 @@ class BasicNewsRecipe(Recipe): mi.author_sort = __appname__ if self.output_profile.name == 'iPad': date_as_author = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) - mi.authors = [date_as_author] - mi.author_sort = strftime('%Y-%m-%d') + mi = MetaInformation(self.short_title(), [date_as_author]) + mi.publisher = __appname__ + sort_author = re.sub('^\s*A\s+|^\s*The\s+|^\s*An\s+', '', self.title).rstrip() + mi.author_sort = '%s %s' % (sort_author, strftime('%Y-%m-%d')) mi.publication_type = 'periodical:'+self.publication_type mi.timestamp = nowf() mi.comments = self.description @@ -1243,7 +1293,6 @@ class BasicNewsRecipe(Recipe): with nested(open(opf_path, 'wb'), open(ncx_path, 'wb')) as (opf_file, ncx_file): opf.render(opf_file, ncx_file) - def article_downloaded(self, request, result): index = os.path.join(os.path.dirname(result[0]), 'index.html') if index != result[0]: diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index 00b7fec24e..ef588f83c9 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -185,7 +185,7 @@ class TouchscreenIndexTemplate(Template): for i, feed in enumerate(feeds): if feed: tr = TR() - tr.append(TD( CLASS('toc_item'), A(feed.title, href='feed_%d/index.html'%i))) + tr.append(TD( CLASS('toc_item','calibre_rescale_120'), A(feed.title.strip(), href='feed_%d/index.html'%i))) tr.append(TD( '%s' % len(feed.articles), style="text-align:right")) toc.append(tr) @@ -279,12 +279,15 @@ class TouchscreenFeedTemplate(Template): continue tr = TR() td = TD( - A(article.title, CLASS('article calibre_rescale_100', + A(article.title, CLASS('summary_headline','calibre_rescale_120', href=article.url)) ) + if article.author: + td.append(DIV(article.author, + CLASS('summary_byline', 'calibre_rescale_100'))) if article.summary: td.append(DIV(cutoff(article.text_summary), - CLASS('article_description', 'calibre_rescale_80'))) + CLASS('summary_text', 'calibre_rescale_100'))) tr.append(td) toc.append(tr) div.append(toc) From 375393997bc17fb6911781791b68f1f8b1bef44c Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 4 Jun 2010 11:48:24 -0600 Subject: [PATCH 2/4] GwR revisions --- src/calibre/devices/apple/driver.py | 272 +++++++++++++++++----------- src/calibre/web/feeds/templates.py | 6 +- 2 files changed, 165 insertions(+), 113 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 5edb8acf02..fddb233360 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -44,6 +44,12 @@ if iswindows: ] class ITUNES(DevicePlugin): + ''' + try: + pythoncom.CoInitialize() + finally: + pythoncom.CoUninitialize() + ''' name = 'Apple device interface' gui_name = 'Apple device' @@ -69,6 +75,7 @@ class ITUNES(DevicePlugin): # Properties cached_books = {} cache_dir = os.path.join(config_dir, 'caches', 'itunes') + ejected = False iTunes= None log = Log() path_template = 'iTunes/%s - %s.epub' @@ -264,31 +271,49 @@ class ITUNES(DevicePlugin): if self.iTunes: # Check for connected book-capable device - try: - ''' - names = [s.name() for s in self.iTunes.sources()] - kinds = [str(s.kind()).rpartition('.')[2] for s in self.iTunes.sources()] - self.sources = sources = dict(zip(kinds,names)) - ''' - self.sources = self._get_sources() - if 'iPod' in self.sources: -# if DEBUG: -# sys.stdout.write('.') -# sys.stdout.flush() - return True - else: - if DEBUG: - self.log.info("ITUNES.can_handle(): device ejected") - return False - except: - # iTunes connection failed, probably not running anymore - self.log.error("ITUNES.can_handle(): lost connection to iTunes") + self.sources = self._get_sources() + if 'iPod' in self.sources: + #if DEBUG: + #sys.stdout.write('.') + #sys.stdout.flush() + return True + else: + if DEBUG: + sys.stdout.write('-') + sys.stdout.flush() return False else: - # can_handle() is called once before open(), so need to return True - # to keep things going + # 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(): iTunes not yet instantiated") + self.log.info("ITUNES.can_handle()") + + self._launch_iTunes() + self.sources = self._get_sources() + if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''): + attempts = 9 + while attempts: + # If iTunes was just launched, device may not be detected yet + self.sources = self._get_sources() + if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''): + attempts -= 1 + time.sleep(0.5) + if DEBUG: + self.log.warning(" waiting for identified iPad, attempt #%d" % (10 - attempts)) + else: + if DEBUG: + self.log.info(' found connected iPad in iTunes') + break + else: + # iTunes running, but not connected iPad + if DEBUG: + self.log.info(' self.ejected = True') + self.ejected = True + return False + else: + self.log.info(' found connected iPad in sources') + return True def can_handle_windows(self, device_id, debug=False): @@ -302,35 +327,74 @@ class ITUNES(DevicePlugin): :param device_info: On windows a device ID string. On Unix a tuple of ``(vendor_id, product_id, bcd)``. + + iPad implementation notes: + It is necessary to use this method to check for the presence of a connected + iPad, as we have to return True if we can handle device interaction, or False if not. + ''' if self.iTunes: - # Check for connected book-capable device + # We've previously run, so the user probably ejected the device try: - ''' - names = [s.name() for s in self.iTunes.sources()] - kinds = [str(s.kind()).rpartition('.')[2] for s in self.iTunes.sources()] - self.sources = sources = dict(zip(kinds,names)) - ''' + pythoncom.CoInitialize() self.sources = self._get_sources() if 'iPod' in self.sources: if DEBUG: sys.stdout.write('.') sys.stdout.flush() + if DEBUG: + self.log.info('ITUNES.can_handle_windows:\n confirming connected iPad') + self.ejected = False return True else: if DEBUG: - self.log.info("ITUNES.can_handle(): device ejected") + self.log.info("ITUNES.can_handle_windows():\n device ejected") + self.ejected = True return False except: # iTunes connection failed, probably not running anymore - self.log.error("ITUNES.can_handle(): lost connection to iTunes") + + self.log.error("ITUNES.can_handle_windows():\n lost connection to iTunes") return False + finally: + pythoncom.CoUninitialize() else: - # can_handle_windows() is called once before open(), so need to return True - # to keep things going + # 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(): iTunes not yet instantiated") + self.log.info("ITUNES:can_handle_windows():\n Launching iTunes") + + try: + pythoncom.CoInitialize() + self._launch_iTunes() + self.sources = self._get_sources() + if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''): + attempts = 9 + while attempts: + # If iTunes was just launched, device may not be detected yet + self.sources = self._get_sources() + if (not 'iPod' in self.sources) or (self.sources['iPod'] == ''): + attempts -= 1 + time.sleep(0.5) + if DEBUG: + self.log.warning(" waiting for identified iPad, attempt #%d" % (10 - attempts)) + else: + if DEBUG: + self.log.info(' found connected iPad in iTunes') + break + else: + # iTunes running, but not connected iPad + if DEBUG: + self.log.info(' self.ejected = True') + self.ejected = True + return False + else: + self.log.info(' found connected iPad in sources') + finally: + pythoncom.CoUninitialize() + return True def card_prefix(self, end_session=True): @@ -343,8 +407,6 @@ class ITUNES(DevicePlugin): ('place', None) (None, None) ''' -# if DEBUG: -# self.log.info("ITUNES:card_prefix()") return (None,None) def delete_books(self, paths, end_session=True): @@ -423,13 +485,19 @@ class ITUNES(DevicePlugin): elif iswindows: if 'iPod' in self.sources: - try: - pythoncom.CoInitialize() - self.iTunes = win32com.client.Dispatch("iTunes.Application") - connected_device = self.sources['iPod'] - free_space = self.iTunes.sources.ItemByName(connected_device).FreeSpace - finally: - pythoncom.CoUninitialize() + + while True: + try: + try: + pythoncom.CoInitialize() + self.iTunes = win32com.client.Dispatch("iTunes.Application") + connected_device = self.sources['iPod'] + free_space = self.iTunes.sources.ItemByName(connected_device).FreeSpace + finally: + pythoncom.CoUninitialize() + break + except: + self.log.error(' waiting for free_space() call to go through') return (free_space,-1,-1) @@ -462,62 +530,11 @@ class ITUNES(DevicePlugin): mounted. The base class within USBMS device.py has a implementation of this function that should serve as a good example for USB Mass storage devices. + + Note that most of the initialization is necessarily performed in can_handle(), as + we need to talk to iTunes to discover if there's a connected iPod ''' - if isosx: - # Launch iTunes if not already running - if DEBUG: - self.log.info("ITUNES:open(): Instantiating iTunes") - - # Instantiate iTunes - running_apps = appscript.app('System Events') - if not 'iTunes' in running_apps.processes.name(): - if DEBUG: - self.log.info( "ITUNES:open(): Launching iTunes" ) - self.iTunes = iTunes= appscript.app('iTunes', hide=True) - iTunes.run() - initial_status = 'launched' - else: - self.iTunes = appscript.app('iTunes') - initial_status = 'already running' - - if DEBUG: - self.log.info( " %s - %s (%s), driver version %s" % - (self.iTunes.name(), self.iTunes.version(), initial_status, repr(self.version))) - - # Init the iTunes source list - ''' - names = [s.name() for s in self.iTunes.sources()] - kinds = [str(s.kind()).rpartition('.')[2] for s in self.iTunes.sources()] - self.sources = dict(zip(kinds,names)) - ''' - self.sources = self._get_sources() - - elif iswindows: - # Launch iTunes if not already running - if DEBUG: - self.log.info("ITUNES:open(): Instantiating iTunes") - - # Instantiate iTunes - try: - pythoncom.CoInitialize() - self.iTunes = win32com.client.Dispatch("iTunes.Application") - if not DEBUG: - self.iTunes.Windows[0].Minimized = True - initial_status = 'launched' - - if DEBUG: - 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])) - - # Init the iTunes source list - self.sources = self._get_sources() - - finally: - pythoncom.CoUninitialize() - - # Confirm/create thumbs archive archive_path = os.path.join(self.cache_dir, "thumbs.zip") @@ -586,8 +603,6 @@ class ITUNES(DevicePlugin): If it is called with -1 that means that the task does not have any progress information ''' -# if DEBUG: -# self.log.info("ITUNES:set_progress_reporter()") self.report_progress = report_progress def settings(self): @@ -595,8 +610,6 @@ class ITUNES(DevicePlugin): Should return an opts object. The opts object should have one attribute `format_map` which is an ordered list of formats for the device. ''' -# if DEBUG: -# self.log.info("ITUNES.settings()") klass = self if isinstance(self, type) else self.__class__ c = Config('device_drivers_%s' % klass.__name__, _('settings for device drivers')) c.add_opt('format_map', default=self.FORMATS, @@ -627,16 +640,9 @@ class ITUNES(DevicePlugin): size_on_device = self._get_device_book_size(updated_book['title'], updated_book['author'][0]) if size_on_device: - if DEBUG: - self._dump_booklist(booklists[0], 'sync_booklists()') - self.log.info(" looking for '%s' by %s" % - (updated_book['title'], updated_book['author'])) for book in booklists[0]: if book.title == updated_book['title'] and \ book.author == updated_book['author']: - if DEBUG: - self.log.info(" found '%s' by %s" % (book.title, book.author[0])) - book.size = size_on_device break else: self.log.error("ITUNES:sync_booklists(): could not update book size for '%s'" % updated_book['title']) @@ -1224,6 +1230,10 @@ class ITUNES(DevicePlugin): dev_playlists = [pl.Name for pl in dev.Playlists] if 'Books' in dev_playlists: return self.iTunes.sources.ItemByName(connected_device).Playlists.ItemByName('Books').Tracks + else: + return [] + if DEBUG: + self.log.warning('ITUNES._get_device_book(): No iPod device connected') return [] def _get_library_books(self): @@ -1285,6 +1295,7 @@ class ITUNES(DevicePlugin): 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] @@ -1298,6 +1309,49 @@ class ITUNES(DevicePlugin): else: return True + def _launch_iTunes(self): + ''' + ''' + if DEBUG: + self.log.info("ITUNES:_launch_iTunes():\n Instantiating iTunes") + + if isosx: + ''' + Launch iTunes if not already running + ''' + # Instantiate iTunes + running_apps = appscript.app('System Events') + if not 'iTunes' in running_apps.processes.name(): + if DEBUG: + self.log.info( "ITUNES:open(): Launching iTunes" ) + self.iTunes = iTunes= appscript.app('iTunes', hide=True) + iTunes.run() + initial_status = 'launched' + else: + self.iTunes = appscript.app('iTunes') + initial_status = 'already running' + + if DEBUG: + 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])) + + if iswindows: + ''' + Launch iTunes if not already running + Assumes pythoncom wrapper + ''' + # Instantiate iTunes + self.iTunes = win32com.client.Dispatch("iTunes.Application") + if not DEBUG: + self.iTunes.Windows[0].Minimized = True + initial_status = 'launched' + + if DEBUG: + 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])) + def _remove_from_iTunes(self, cached_book): ''' iTunes does not delete books from storage when removing from database @@ -1393,8 +1447,8 @@ class ITUNES(DevicePlugin): pb_count = len(self._get_purchased_book_ids()) if db_count != lb_count + pb_count: if DEBUG: - sys.stdout.write(' %d != %d + %d\n' % (db_count,lb_count,pb_count)) - #sys.stdout.write('.') + #sys.stdout.write(' %d != %d + %d\n' % (db_count,lb_count,pb_count)) + sys.stdout.write('.') sys.stdout.flush() time.sleep(2) else: diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index 21e733ade8..e98edd0e95 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -185,15 +185,13 @@ class TouchscreenIndexTemplate(Template): for i, feed in enumerate(feeds): if feed: tr = TR() - tr.append(TD( CLASS('toc_item','calibre_rescale_120'), A(feed.title.strip(), href='feed_%d/index.html'%i))) + tr.append(TD( CLASS('calibre_rescale_120'), A(feed.title, href='feed_%d/index.html'%i))) tr.append(TD( '%s' % len(feed.articles), style="text-align:right")) toc.append(tr) - div = DIV( masthead_p, PT(date, style='text-align:center'), - toc, - CLASS('calibre_rescale_100')) + toc) self.root = HTML(head, BODY(div)) class FeedTemplate(Template): From 9041d2c275acdd3506ba42ea47b90fd55b192d4e Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 4 Jun 2010 11:55:29 -0600 Subject: [PATCH 3/4] GwR revisions --- src/calibre/devices/apple/driver.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index fddb233360..3776ca0ad4 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -1188,9 +1188,6 @@ class ITUNES(DevicePlugin): if isosx: for d_book in device_books: - if DEBUG: - self.log.info(" evaluating title: '%s' author: '%s'" % - (d_book.name(), d_book.artist())) if d_book.name() == title and d_book.artist() == author: if DEBUG: self.log.info(' found it') @@ -1201,10 +1198,6 @@ class ITUNES(DevicePlugin): return None elif iswindows: for d_book in device_books: - ''' - if DEBUG: - self.log.info(" evaluating title: '%s' author: '%s'" % (d_book.Name, d_book.Artist)) - ''' if d_book.Name == title and d_book.Artist == author: self.log.info(" found it") return d_book.Size From 6c46096016187bd15580276ae35facf40d08305c Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 4 Jun 2010 12:07:39 -0600 Subject: [PATCH 4/4] GwR revisions --- 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 3776ca0ad4..461a729d50 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -58,7 +58,7 @@ class ITUNES(DevicePlugin): supported_platforms = ['osx','windows'] author = 'GRiker' #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (0, 3, 0) + version = (1, 0, 0) OPEN_FEEDBACK_MESSAGE = _( 'Apple device detected, launching iTunes, please wait ...')