From ee6d94aa405afdd4cb3b2f04ad0bb9c956871445 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 09:17:02 -0600 Subject: [PATCH 01/13] Fix #5695 (AttributeError: 'LibraryServer' object has no attribute 'stanza') --- src/calibre/library/server/content.py | 5 +---- src/calibre/manual/faq.rst | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/calibre/library/server/content.py b/src/calibre/library/server/content.py index 12bd786322..4dd32eb284 100644 --- a/src/calibre/library/server/content.py +++ b/src/calibre/library/server/content.py @@ -127,10 +127,7 @@ class ContentServer(object): cherrypy.log('User agent: '+ua) if want_opds: - return self.stanza(search=kwargs.get('search', None), sortby=kwargs.get('sortby',None), authorid=kwargs.get('authorid',None), - tagid=kwargs.get('tagid',None), - seriesid=kwargs.get('seriesid',None), - offset=kwargs.get('offset', 0)) + return self.opds(version=0) if want_mobile: return self.mobile() diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 99afc32b18..833d99f28e 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -157,7 +157,9 @@ If you get timeout errors while browsing the calibre catalog in Stanza, try incr Alternative for the iPad ^^^^^^^^^^^^^^^^^^^^^^^^^^^ -As of |app| version 0.7.0, on windows and OS X you can plugin your iPad into the computer using its charging cable, and |app| will detect it and show you a list of books on the iPad. You can then use the Send to device button to send books directly to iBooks on the iPad. +As of |app| version 0.7.0, you can plugin your iPad into the computer using its charging cable, and |app| will detect it and show you a list of books on the iPad. You can then use the Send to device button to send books directly to iBooks on the iPad. + +This method only works on Windows XP and higher and OS X 10.5 and higher. Linux is not supported (iTunes is not available in linux) and OS X 10.4 is not supported. How do I use |app| with my Android phone? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From a22c3339cb6a9a7864770bacc63d1c4273488bce Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 09:21:40 -0600 Subject: [PATCH 02/13] Fix #5700 (Calibre 0.7.0 Splash Screen) --- src/calibre/gui2/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 29ae1875c8..ba40c0c92b 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -226,7 +226,7 @@ class GuiRunner(QObject): self.splash_pixmap = QPixmap() self.splash_pixmap.load(I('library.png')) self.splash_screen = QSplashScreen(self.splash_pixmap, - Qt.SplashScreen|Qt.WindowStaysOnTopHint) + Qt.SplashScreen) self.splash_screen.showMessage(_('Starting %s: Loading books...') % __appname__) self.splash_screen.show() From 15e4cd48bc4971b8abdb20f4ec838e99599713a4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 09:27:06 -0600 Subject: [PATCH 03/13] Fix #5698 (Fixed recipe Vreme) --- resources/recipes/vreme.recipe | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/resources/recipes/vreme.recipe b/resources/recipes/vreme.recipe index a54353b78c..197c20f108 100644 --- a/resources/recipes/vreme.recipe +++ b/resources/recipes/vreme.recipe @@ -52,10 +52,12 @@ class Vreme(BasicNewsRecipe): def parse_index(self): articles = [] soup = self.index_to_soup(self.INDEX) - + cover_item = soup.find('div',attrs={'id':'najava'}) + if cover_item: + self.cover_url = self.INDEX + cover_item.img['src'] for item in soup.findAll(['h3','h4']): - description = '' - title_prefix = '' + description = u'' + title_prefix = u'' feed_link = item.find('a') if feed_link and feed_link.has_key('href') and feed_link['href'].startswith('/cms/view.php'): url = self.INDEX + feed_link['href'] @@ -67,7 +69,7 @@ class Vreme(BasicNewsRecipe): ,'url' :url ,'description':description }) - return [(soup.head.title.string, articles)] + return [('Nedeljnik Vreme', articles)] remove_tags = [ dict(name=['object','link']) @@ -76,11 +78,3 @@ class Vreme(BasicNewsRecipe): def print_version(self, url): return url + '&print=yes' - - def get_cover_url(self): - cover_url = None - soup = self.index_to_soup(self.INDEX) - cover_item = soup.find('div',attrs={'id':'najava'}) - if cover_item: - cover_url = self.INDEX + cover_item.img['src'] - return cover_url From 59d2b5fb68a48b50f7ac3aa053478fe6c59d8980 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 09:51:58 -0600 Subject: [PATCH 04/13] SONY driver: Fix problem caused by null titles --- src/calibre/devices/prs505/sony_cache.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/prs505/sony_cache.py b/src/calibre/devices/prs505/sony_cache.py index 177a741e0d..0292a275d7 100644 --- a/src/calibre/devices/prs505/sony_cache.py +++ b/src/calibre/devices/prs505/sony_cache.py @@ -415,10 +415,11 @@ class XMLCache(object): prints('\tmtime', strftime(os.path.getmtime(path))) record.set('date', date) record.set('size', str(os.stat(path).st_size)) - record.set('title', book.title) + title = book.title if book.title else _('Unknown') + record.set('title', title) ts = book.title_sort if not ts: - ts = title_sort(book.title) + ts = title_sort(title) record.set('titleSorter', ts) record.set('author', authors_to_string(book.authors)) ext = os.path.splitext(path)[1] From b935eb5e466c2a2dc0433bbb1497201f87cdd5f3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 10:02:33 -0600 Subject: [PATCH 05/13] Fix news downloading breaking on windows systems with local encoding other than UTF-8. Fixes #5699 (Problem with Clarin recipe) --- src/calibre/web/feeds/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/web/feeds/__init__.py b/src/calibre/web/feeds/__init__.py index bcb8c2d74f..70b5557ad4 100644 --- a/src/calibre/web/feeds/__init__.py +++ b/src/calibre/web/feeds/__init__.py @@ -11,7 +11,7 @@ from lxml import html from calibre.web.feeds.feedparser import parse from calibre.utils.logging import default_log -from calibre import entity_to_unicode +from calibre import entity_to_unicode, strftime from calibre.utils.date import dt_factory, utcnow, local_tz class Article(object): @@ -55,7 +55,8 @@ class Article(object): def formatted_date(self): def fget(self): if self._formatted_date is None: - self._formatted_date = self.localtime.strftime(" [%a, %d %b %H:%M]") + self._formatted_date = strftime(" [%a, %d %b %H:%M]", + t=self.localtime.timetuple()) return self._formatted_date def fset(self, val): self._formatted_date = val From 1fc5cd26c853df5838d9c4fa850945394b504512 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 12:19:24 -0600 Subject: [PATCH 06/13] Fix #5708 (Updated recipe for Gizmodo) --- resources/recipes/gizmodo.recipe | 10 ++++++---- src/calibre/web/feeds/__init__.py | 6 +++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/resources/recipes/gizmodo.recipe b/resources/recipes/gizmodo.recipe index 6f6e6ae0cf..4233ef66b7 100644 --- a/resources/recipes/gizmodo.recipe +++ b/resources/recipes/gizmodo.recipe @@ -17,7 +17,7 @@ class Gizmodo(BasicNewsRecipe): max_articles_per_feed = 100 no_stylesheets = True encoding = 'utf-8' - use_embedded_content = True + use_embedded_content = False language = 'en' masthead_url = 'http://cache.gawkerassets.com/assets/gizmodo.com/img/logo.png' extra_css = ' body{font-family: "Lucida Grande",Helvetica,Arial,sans-serif} img{margin-bottom: 1em} ' @@ -29,9 +29,11 @@ class Gizmodo(BasicNewsRecipe): , 'language' : language } - remove_attributes = ['width','height'] - remove_tags = [dict(name='div',attrs={'class':'feedflare'})] - remove_tags_after = dict(name='div',attrs={'class':'feedflare'}) + remove_attributes = ['width','height'] + keep_only_tags = [dict(attrs={'class':'content permalink'})] + remove_tags_before = dict(name='h1') + remove_tags = [dict(attrs={'class':'contactinfo'})] + remove_tags_after = dict(attrs={'class':'contactinfo'}) feeds = [(u'Articles', u'http://feeds.gawker.com/gizmodo/full')] diff --git a/src/calibre/web/feeds/__init__.py b/src/calibre/web/feeds/__init__.py index 70b5557ad4..da7122c491 100644 --- a/src/calibre/web/feeds/__init__.py +++ b/src/calibre/web/feeds/__init__.py @@ -53,13 +53,17 @@ class Article(object): @dynamic_property def formatted_date(self): + def fget(self): if self._formatted_date is None: self._formatted_date = strftime(" [%a, %d %b %H:%M]", t=self.localtime.timetuple()) return self._formatted_date + def fset(self, val): - self._formatted_date = val + if isinstance(val, unicode): + self._formatted_date = val + return property(fget=fget, fset=fset) @dynamic_property From a76b689d5d02957f9933b12a84705ba1d093bf99 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 14:51:09 -0600 Subject: [PATCH 07/13] When setting an image with transparent pixels as the book cover, overlay it on a white background first. Fixes transparent covers getting random backgrounds. --- src/calibre/library/database2.py | 16 ++++------------ src/calibre/utils/magick_draw.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 29e1901ce2..f1eeb23643 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -9,12 +9,6 @@ The database used to store ebook metadata import os, sys, shutil, cStringIO, glob,functools, traceback from itertools import repeat from math import floor -try: - from PIL import Image as PILImage - PILImage -except ImportError: - import Image as PILImage - from PyQt4.QtGui import QImage @@ -37,7 +31,7 @@ from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp from calibre.utils.config import prefs from calibre.utils.search_query_parser import saved_searches from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format - +from calibre.utils.magick_draw import save_cover_data_to if iswindows: import calibre.utils.winshell as winshell @@ -475,11 +469,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): if callable(getattr(data, 'save', None)): data.save(path) else: - f = data - if not callable(getattr(data, 'read', None)): - f = cStringIO.StringIO(data) - im = PILImage.open(f) - im.convert('RGB').save(path, 'JPEG') + if callable(getattr(data, 'read', None)): + data = data.read() + save_cover_data_to(data, path) def book_on_device(self, id): if callable(self.book_on_device_func): diff --git a/src/calibre/utils/magick_draw.py b/src/calibre/utils/magick_draw.py index aeb5bb2268..c4a6c1d76e 100644 --- a/src/calibre/utils/magick_draw.py +++ b/src/calibre/utils/magick_draw.py @@ -212,6 +212,23 @@ def create_cover_page(top_lines, logo_path, width=590, height=750, p.DestroyMagickWand(canvas) return ans +def save_cover_data_to(data, path, bgcolor='white'): + ''' + Saves image in data to path, in the format specified by the path + extension. Composes the image onto a blank cancas so as to + properly convert transparent images. + ''' + with open(path, 'wb') as f: + f.write(data) + with p.ImageMagick(): + img = load_image(path) + canvas = create_canvas(p.MagickGetImageWidth(img), + p.MagickGetImageHeight(img), bgcolor) + compose_image(canvas, img, 0, 0) + p.MagickWriteImage(canvas, path) + p.DestroyMagickWand(img) + p.DestroyMagickWand(canvas) + def test(): import subprocess with TemporaryFile('.png') as f: From 6a11da64cc73edd3d2261513ffe6e3fd4d9443a5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 15:19:13 -0600 Subject: [PATCH 08/13] Make the book details pabe occupy the full lower part of the window --- src/calibre/gui2/main.ui | 410 ++++++++++++++++++++------------------- 1 file changed, 209 insertions(+), 201 deletions(-) diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui index 8c1d1bf787..1abaf53d3c 100644 --- a/src/calibre/gui2/main.ui +++ b/src/calibre/gui2/main.ui @@ -28,7 +28,7 @@ :/images/library.png:/images/library.png - + @@ -305,78 +305,79 @@ - - - - - - 0 - 100 - - - - Qt::Vertical - - - - - 100 - 100 - - - - 0 - - - - - - - Qt::Horizontal - - - - - - - true - - - true - - - true - - - true - - - - + + + + 0 + 100 + + + + Qt::Vertical + + + + + + + + 100 + 100 + + + + 0 + + + + + + + Qt::Horizontal + + + + + + + true + + + true + + + true + + + true + + + + Sort by &popularity - - - - - 0 - - - - Match any + + + + + 0 - - - - Match all - - - - - + + + Match any + + + + + Match all + + + + + Create, edit, and delete user categories @@ -385,10 +386,49 @@ Manage &user categories - - + + + + + + + 100 + 10 + + + + true + + + true + + + false + + + QAbstractItemView::DragDrop + + + true + + + QAbstractItemView::SelectRows + + + false + + + false + + - + + + + + + + 100 @@ -420,139 +460,107 @@ false - - - + + + + + + + + + + 10 + 10 + + + + true + + + true + + + false + + + QAbstractItemView::DragDrop + + + true + + + QAbstractItemView::SelectRows + + + false + + + false + + + + + + + + + + + + 10 + 10 + + + + true + + + true + + + false + + + QAbstractItemView::DragDrop + + + true + + + QAbstractItemView::SelectRows + + + false + + + false + + + + + - - - - - - - 100 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - + + + + + + 0 + 0 + + + + + 30 + 0 + + - - - - - - - 10 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - 10 - 10 - - - - true - - - true - - - false - - - QAbstractItemView::DragDrop - - - true - - - QAbstractItemView::SelectRows - - - false - - - false - - - - - - - - - - - - - - 0 - 0 - - - - - + + + + + From 85f21f629c918764ceb5635e39adbc2250c10fab Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 16:27:31 -0600 Subject: [PATCH 09/13] Fix SONY driver in linux --- src/calibre/devices/prs505/driver.py | 37 ++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index bd06d2d7e1..6f8f9de6df 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -6,8 +6,7 @@ __docformat__ = 'restructuredtext en' Device driver for the SONY devices ''' -import os -import re +import os, time, re from calibre.devices.usbms.driver import USBMS from calibre.devices.prs505 import MEDIA_XML @@ -66,6 +65,40 @@ class PRS505(USBMS): def windows_filter_pnp_id(self, pnp_id): return '_LAUNCHER' in pnp_id + def open(self): + self.report_progress = lambda x, y: x + USBMS.open(self) + + def write_cache(prefix): + try: + cachep = os.path.join(prefix, *(self.CACHE_XML.split('/'))) + if not os.path.exists(cachep): + dname = os.path.dirname(cachep) + if not os.path.exists(dname): + try: + os.makedirs(dname, mode=0777) + except: + time.sleep(5) + os.makedirs(dname, mode=0777) + with open(cachep, 'wb') as f: + f.write(u''' + + + '''.encode('utf8')) + return True + except: + import traceback + traceback.print_exc() + return False + + if self._card_a_prefix is not None: + if not write_cache(self._card_a_prefix): + self._card_a_prefix = None + if self._card_b_prefix is not None: + if not write_cache(self._card_b_prefix): + self._card_b_prefix = None + + def get_device_information(self, end_session=True): return (self.gui_name, '', '', '') From cf8bb5540994e2dd343df12b7426488d155ef8d6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 16:30:16 -0600 Subject: [PATCH 10/13] ... --- src/calibre/devices/prs505/driver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 6f8f9de6df..fc72469c71 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -66,7 +66,6 @@ class PRS505(USBMS): return '_LAUNCHER' in pnp_id def open(self): - self.report_progress = lambda x, y: x USBMS.open(self) def write_cache(prefix): From 3a7d5c431f048436f56f3f00e4900eefbcb913d2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 16:32:25 -0600 Subject: [PATCH 11/13] cleanup sony fix --- src/calibre/devices/prs505/driver.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index fc72469c71..671c239dd5 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -65,8 +65,7 @@ class PRS505(USBMS): def windows_filter_pnp_id(self, pnp_id): return '_LAUNCHER' in pnp_id - def open(self): - USBMS.open(self) + def post_open_callback(self): def write_cache(prefix): try: @@ -90,6 +89,9 @@ class PRS505(USBMS): traceback.print_exc() return False + # Make sure we don't have the launcher partition + # as one of the cards + if self._card_a_prefix is not None: if not write_cache(self._card_a_prefix): self._card_a_prefix = None From 462b2e6c44751baac6b573b9a8450f4512d05312 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 17:15:58 -0600 Subject: [PATCH 12/13] Internationalization fixes in apple driver --- src/calibre/devices/apple/driver.py | 324 +++++++++++++++++++--------- 1 file changed, 227 insertions(+), 97 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index e17d8e980c..daa42892db 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -31,6 +31,37 @@ if isosx: if iswindows: import pythoncom, win32com.client + +class ITUNES(DevicePlugin): + ''' + try: + pythoncom.CoInitialize() + finally: + pythoncom.CoUninitialize() + ''' + + name = 'Apple device interface' + gui_name = 'Apple device' + icon = I('devices/ipad.png') + description = _('Communicate with iBooks through iTunes.') + supported_platforms = ['osx','windows'] + author = 'GRiker' + #: The version of this plugin as a 3-tuple (major, minor, revision) + version = (0, 4, 0) + + OPEN_FEEDBACK_MESSAGE = _( + 'Apple device detected, launching iTunes, please wait ...') + + FORMATS = ['epub'] + + # Product IDs: + # 0x1292:iPhone 3G + # 0x129a:iPad + VENDOR_ID = [0x05ac] + PRODUCT_ID = [0x129a] + BCD = [0x01] + + # iTunes enumerations Sources = [ 'Unknown', 'Library', @@ -48,34 +79,27 @@ if iswindows: 'BMP' ] -class ITUNES(DevicePlugin): - ''' - try: - pythoncom.CoInitialize() - finally: - pythoncom.CoUninitialize() - ''' + PlaylistKind = [ + 'Unknown', + 'Library', + 'User', + 'CD', + 'Device', + 'Radio Tuner' + ] - name = 'Apple device interface' - gui_name = 'Apple device' - icon = I('devices/ipad.png') - description = _('Communicate with iBooks through iTunes.') - supported_platforms = ['osx','windows'] - author = 'GRiker' - #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (1, 0, 0) - - OPEN_FEEDBACK_MESSAGE = _( - 'Apple device detected, launching iTunes, please wait ...') - - FORMATS = ['epub'] - - # Product IDs: - # 0x1292:iPhone 3G - # 0x129a:iPad - VENDOR_ID = [0x05ac] - PRODUCT_ID = [0x129a] - BCD = [0x01] + PlaylistSpecialKind = [ + 'Unknown', + 'Purchased Music', + 'Party Shuffle', + 'Podcasts', + 'Folder', + 'Video', + 'Music', + 'Movies', + 'TV Shows', + 'Books', + ] # Properties cached_books = {} @@ -459,12 +483,14 @@ class ITUNES(DevicePlugin): if isosx: self.iTunes.eject(self.sources['iPod']) elif iswindows: - try: - pythoncom.CoInitialize() - self.iTunes = win32com.client.Dispatch("iTunes.Application") - self.iTunes.sources.ItemByName(self.sources['iPod']).EjectIPod() - finally: - pythoncom.CoUninitialize() + if 'iPod' in self.sources: + try: + pythoncom.CoInitialize() + self.iTunes = win32com.client.Dispatch("iTunes.Application") + self.iTunes.sources.ItemByName(self.sources['iPod']).EjectIPod() + + finally: + pythoncom.CoUninitialize() self.iTunes = None self.sources = None @@ -635,7 +661,7 @@ class ITUNES(DevicePlugin): if self.update_needed: if DEBUG: self.log.info(' calling _update_device') - self._update_device(msg=self.update_msg) + self._update_device(msg=self.update_msg, wait=False) self.update_needed = False # Get actual size of updated books on device @@ -736,12 +762,13 @@ class ITUNES(DevicePlugin): self.problem_msg = _("Some cover art could not be converted.\n" "Click 'Show Details' for a list.") + 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()') + 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 @@ -836,11 +863,36 @@ class ITUNES(DevicePlugin): try: pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") - lib = self.iTunes.sources.ItemByName('Library') - lib_playlists = [pl.Name for pl in lib.Playlists] - if not 'Books' in lib_playlists: - self.log.error(" no 'Books' playlist in Library") - library_books = lib.Playlists.ItemByName('Books') + + 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 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 + break + else: + if DEBUG: + self.log.error(" no Books playlist found") + + # +# lib = self.iTunes.sources.ItemByName('Library') +# lib_playlists = [pl.Name for pl in lib.Playlists] +# if not 'Books' in lib_playlists: +# self.log.error(" no 'Books' playlist in Library") +# library_books = lib.Playlists.ItemByName('Books') + # for (i,file) in enumerate(files): path = self.path_template % (metadata[i].title, metadata[i].author[0]) @@ -859,10 +911,10 @@ class ITUNES(DevicePlugin): # Add to iTunes Library|Books if isinstance(file,PersistentTemporaryFile): - op_status = library_books.AddFile(file._name) + op_status = lib_books.AddFile(file._name) self.log.info("ITUNES.upload_books():\n iTunes adding '%s'" % file._name) else: - op_status = library_books.AddFile(file) + op_status = lib_books.AddFile(file) self.log.info(" iTunes adding '%s'" % file) if DEBUG: @@ -1060,20 +1112,6 @@ class ITUNES(DevicePlugin): 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 - ''' - SearchField = ['All','Visible','Artists','Titles','Composers','SongNames'] - if iswindows: - dev_books = self.iTunes.sources.ItemByName(self.sources['iPod']).Playlists.ItemByName('Books') - hits = dev_books.Search(cached_book['title'],SearchField.index('Titles')) - if hits: - for hit in hits: - if hit.Artist == cached_book['author']: - return hit - 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 @@ -1083,7 +1121,28 @@ class ITUNES(DevicePlugin): if DEBUG: self.log.info("ITUNES._find_library_book()") self.log.info(" looking for '%s' by %s" % (cached_book['title'], cached_book['author'])) - lib_books = self.iTunes.sources.ItemByName('Library').Playlists.ItemByName('Books') + + 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 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 + break + else: + if DEBUG: + self.log.error(" no Books playlist found") attempts = 9 while attempts: @@ -1121,12 +1180,6 @@ 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) - return thumb_data if isosx: @@ -1160,7 +1213,7 @@ class ITUNES(DevicePlugin): return None # Save the cover from iTunes - tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % ArtworkFormat[book.Artwork.Item(1).Format]) + tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % self.ArtworkFormat[book.Artwork.Item(1).Format]) book.Artwork.Item(1).SaveArtworkToFile(tmp_thumb) try: # Resize the cover @@ -1184,8 +1237,6 @@ 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'" % @@ -1214,53 +1265,134 @@ class ITUNES(DevicePlugin): def _get_device_books(self): ''' - Assumes pythoncom wrapper + Assumes pythoncom wrapper for Windows ''' + if DEBUG: + self.log.info("\nITUNES._get_device_books()") + + device_books = [] if isosx: if 'iPod' in self.sources: connected_device = self.sources['iPod'] - if 'Books' in self.iTunes.sources[connected_device].playlists.name(): - return self.iTunes.sources[connected_device].playlists['Books'].file_tracks() - return [] + device = self.iTunes.sources[connected_device] + 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())) + 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) + else: + if DEBUG: + self.log.info(" ignoring '%s' of type '%s'" % (book.name(), book.kind())) elif iswindows: if 'iPod' in self.sources: - connected_device = self.sources['iPod'] - dev = self.iTunes.sources.ItemByName(connected_device) - 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 [] + try: + pythoncom.CoInitialize() + connected_device = self.sources['iPod'] + device = self.iTunes.sources.ItemByName(connected_device) + + dev_books = None + for pl in device.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])) + dev_books = pl.Tracks + break + else: + if DEBUG: + self.log.info(" no Books playlist found") + + for book in dev_books: + if book.KindAsString in ['Book','Protected book']: + device_books.append(book) + else: + self.log.info(" ignoring '%s' of type %s" % (book.Name, book.KindAsString)) + + finally: + pythoncom.CoUninitialize() + + return device_books def _get_library_books(self): ''' Populate a dict of paths from iTunes Library|Books ''' + if DEBUG: + self.log.info("\nITUNES._get_library_books()") + library_books = {} + lib = None if isosx: - lib = self.iTunes.sources['library'] - if 'Books' in lib.playlists.name(): - lib_books = lib.playlists['Books'].file_tracks() + for source in self.iTunes.sources(): + if source.kind() == appscript.k.library: + lib = source + if DEBUG: + self.log.info(" Library source: '%s' kind: %s" % (lib.name(), lib.kind())) + break + else: + if DEBUG: + self.log.error(' Library source not found') + + 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: - path = self.path_template % (book.name(), book.artist()) - library_books[path] = book + if book.kind() in ['Book','Protected book']: + path = self.path_template % (book.name(), book.artist()) + library_books[path] = book + else: + if DEBUG: + self.log.info(" ignoring library book of type '%s'" % book.kind()) + else: + if DEBUG: + self.log.info('ITUNES._get_library_books():\n No Books playlist') + elif iswindows: + lib = None try: pythoncom.CoInitialize() self.iTunes = win32com.client.Dispatch("iTunes.Application") - lib = self.iTunes.sources.ItemByName('Library') - lib_playlists = [pl.Name for pl in lib.Playlists] - if 'Books' in lib_playlists: - lib_books = lib.Playlists.ItemByName('Books').Tracks + for source in self.iTunes.sources: + if source.Kind == self.Sources.index('Library'): + lib = source + self.log.info(" Library source: '%s' kind: %s" % (lib.Name, self.Sources[lib.Kind])) + break + else: + self.log.error(" Library source not found") + + 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: - path = self.path_template % (book.Name, book.Artist) - library_books[path] = book + if book.KindAsString in ['Book','Protected book']: + path = self.path_template % (book.Name, book.Artist) + library_books[path] = book + else: + if DEBUG: + self.log.info(" ignoring '%s' of type %s" % (book.Name, book.KindAsString)) finally: pythoncom.CoUninitialize() @@ -1455,11 +1587,9 @@ class ITUNES(DevicePlugin): sys.stdout.write('\n') sys.stdout.flush() break - finally: pythoncom.CoUninitialize() - class BookList(list): ''' A list of books. Each Book object must have the fields: From 15eb98b203073c5ec0192b2765875c0f3c5ff28a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 5 Jun 2010 17:30:34 -0600 Subject: [PATCH 13/13] ... --- src/calibre/devices/prs505/driver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py index 671c239dd5..5a820b3ed8 100644 --- a/src/calibre/devices/prs505/driver.py +++ b/src/calibre/devices/prs505/driver.py @@ -69,7 +69,7 @@ class PRS505(USBMS): def write_cache(prefix): try: - cachep = os.path.join(prefix, *(self.CACHE_XML.split('/'))) + cachep = os.path.join(prefix, *(CACHE_XML.split('/'))) if not os.path.exists(cachep): dname = os.path.dirname(cachep) if not os.path.exists(dname):