From 5562b12d095b15b3d80261810b2c5bd03bcde349 Mon Sep 17 00:00:00 2001 From: GRiker Date: Fri, 18 Jun 2010 15:21:12 -0600 Subject: [PATCH 1/8] GwR revisions 0.7.0 --- src/calibre/devices/apple/driver.py | 41 ++++++++++++++++------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index a646b282f2..28f7758f72 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -6,8 +6,7 @@ __docformat__ = 'restructuredtext en' import cStringIO, ctypes, datetime, os, re, shutil, subprocess, sys, tempfile, time - -from calibre.constants import DEBUG +from calibre.constants import __appname__, __version__, DEBUG from calibre import fit_image from calibre.constants import isosx, iswindows from calibre.devices.errors import UserFeedback @@ -79,7 +78,7 @@ class ITUNES(DevicePlugin): supported_platforms = ['osx','windows'] author = 'GRiker' #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (0,6,0) + version = (0,7,0) OPEN_FEEDBACK_MESSAGE = _( 'Apple device detected, launching iTunes, please wait ...') @@ -294,7 +293,7 @@ class ITUNES(DevicePlugin): 'author':[book.artist()], 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, 'dev_book':book, - 'uuid': book.album() + 'uuid': book.composer() } if self.report_progress is not None: @@ -330,7 +329,7 @@ class ITUNES(DevicePlugin): 'title':book.Name, 'author':book.Artist, 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, - 'uuid': book.Album + 'uuid': book.Composer } if self.report_progress is not None: @@ -1426,10 +1425,10 @@ class ITUNES(DevicePlugin): attempts = 9 while attempts: # Try by uuid - only one hit - hits = dev_books.Search(search['uuid'],self.SearchField.index('Albums')) + hits = dev_books.Search(search['uuid'],self.SearchField.index('All')) if hits: hit = hits[0] - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Album)) + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) return hit # Try by author - there could be multiple hits @@ -1438,7 +1437,7 @@ class ITUNES(DevicePlugin): for hit in hits: if hit.Name == search['title']: if DEBUG: - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Album)) + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) return hit attempts -= 1 @@ -1493,11 +1492,11 @@ class ITUNES(DevicePlugin): if 'uuid' in search: if DEBUG: self.log.info(" searching by uuid '%s' ..." % search['uuid']) - hits = lib_books.Search(search['uuid'],self.SearchField.index('Albums')) + hits = lib_books.Search(search['uuid'],self.SearchField.index('All')) if hits: hit = hits[0] if DEBUG: - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Album)) + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) return hit if DEBUG: @@ -1507,7 +1506,7 @@ class ITUNES(DevicePlugin): for hit in hits: if hit.Name == search['title']: if DEBUG: - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Album)) + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) return hit attempts -= 1 @@ -1925,6 +1924,7 @@ class ITUNES(DevicePlugin): self.log.error(" could not confirm valid iTunes.media_dir from %s" % 'com.apple.itunes') self.log.error(" media_dir: %s" % media_dir) if DEBUG: + self.log.info(" %s %s" % (__appname__, __version__)) self.log.info(" [OSX %s - %s (%s), driver version %d.%d.%d]" % (self.iTunes.name(), self.iTunes.version(), self.initial_status, self.version[0],self.version[1],self.version[2])) @@ -1954,6 +1954,7 @@ class ITUNES(DevicePlugin): self.log.error(" '%s' not found" % media_dir) if DEBUG: + self.log.info(" %s %s" % (__appname__, __version__)) self.log.info(" [Windows %s - %s (%s), driver version %d.%d.%d]" % (self.iTunes.Windows[0].name, self.iTunes.Version, self.initial_status, self.version[0],self.version[1],self.version[2])) @@ -2041,7 +2042,7 @@ class ITUNES(DevicePlugin): elif iswindows: dev_pl = self._get_device_books_playlist() - hits = dev_pl.Search(cached_book['uuid'],self.SearchField.index('Albums')) + hits = dev_pl.Search(cached_book['uuid'],self.SearchField.index('All')) if hits: hit = hits[0] if False: @@ -2095,7 +2096,7 @@ class ITUNES(DevicePlugin): self.iTunes.delete(cached_book['lib_book']) except: if DEBUG: - self.log.info(" '%s' not found in iTunes" % cached_book['title']) + self.log.info(" unable to remove '%s' from iTunes" % cached_book['title']) elif iswindows: ''' @@ -2134,7 +2135,7 @@ class ITUNES(DevicePlugin): book.Delete() except: if DEBUG: - self.log.info(" '%s' not found in iTunes" % cached_book['title']) + self.log.info(" unable to remove '%s' from iTunes" % cached_book['title']) def _update_epub_metadata(self, fpath, metadata): ''' @@ -2241,14 +2242,16 @@ class ITUNES(DevicePlugin): if isosx: if lb_added: - lb_added.album.set(metadata.uuid) + lb_added.album.set(metadata.title) + lb_added.composer.set(metadata.uuid) 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.album.set(metadata.uuid) + db_added.album.set(metadata.title) + db_added.composer.set(metadata.uuid) 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()) @@ -2296,14 +2299,16 @@ class ITUNES(DevicePlugin): elif iswindows: if lb_added: - lb_added.Album = metadata.uuid + lb_added.Album = metadata.title + lb_added.Composer = metadata.uuid 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.Album = metadata.uuid + db_added.Album = metadata.title + db_added.Composer = metadata.uuid db_added.Description = ("%s %s" % (self.description_prefix,strftime('%Y-%m-%d %H:%M:%S'))) db_added.Enabled = True db_added.SortArtist = (metadata.author_sort.title()) From 7a157dc3b618941ab174fb78560ecfd1e695908a Mon Sep 17 00:00:00 2001 From: GRiker Date: Sat, 19 Jun 2010 10:29:51 -0600 Subject: [PATCH 2/8] GwR wip --- resources/recipes/nytimes.recipe | 2 + resources/recipes/nytimes_sub.recipe | 51 +++------ src/calibre/web/feeds/news.py | 8 +- src/calibre/web/feeds/templates.py | 156 ++++++++++++++++++--------- 4 files changed, 127 insertions(+), 90 deletions(-) diff --git a/resources/recipes/nytimes.recipe b/resources/recipes/nytimes.recipe index 9fbcf6d3d1..527f19839c 100644 --- a/resources/recipes/nytimes.recipe +++ b/resources/recipes/nytimes.recipe @@ -79,6 +79,7 @@ class NYTimes(BasicNewsRecipe): 'doubleRule', 'dottedLine', 'entry-meta', + 'entry-response module', 'icon enlargeThis', 'leftNavTabs', 'module box nav', @@ -110,6 +111,7 @@ class NYTimes(BasicNewsRecipe): 'navigation', 'portfolioInline', 'relatedArticles', + 'respond', 'side_search', 'side_index', 'side_tool', diff --git a/resources/recipes/nytimes_sub.recipe b/resources/recipes/nytimes_sub.recipe index bcec51ce97..f63611f6e3 100644 --- a/resources/recipes/nytimes_sub.recipe +++ b/resources/recipes/nytimes_sub.recipe @@ -66,6 +66,7 @@ class NYTimes(BasicNewsRecipe): 'doubleRule', 'dottedLine', 'entry-meta', + 'entry-response module', 'icon enlargeThis', 'leftNavTabs', 'module box nav', @@ -97,6 +98,7 @@ class NYTimes(BasicNewsRecipe): 'navigation', 'portfolioInline', 'relatedArticles', + 'respond', 'side_search', 'side_index', 'side_tool', @@ -417,12 +419,11 @@ class NYTimes(BasicNewsRecipe): return soup - def postprocess_book(self, oeb, opts, log) : - print "\npostprocess_book()\n" - - def extract_byline(href) : - # :' + articlebody = soup.find('div',attrs={'class':'articlebody'}) + if not articlebody: + print 'postprocess_book.extract_description(): Did not find
:' print soup.prettify() return None - paras = articleBody.findAll('p') + paras = articlebody.findAll('p') for p in paras: if p.renderContents() > '' : return self.massageNCXText(self.tag_to_string(p,use_alt=False)) return None - # Method entry point here - # Single section toc looks different than multi-section tocs - if oeb.toc.depth() == 2 : - for article in oeb.toc : - if article.author is None : - article.author = extract_byline(article.href) - if article.description is None : - article.description = extract_description(article.href).decode('utf-8') - elif oeb.toc.depth() == 3 : - for section in oeb.toc : - for article in section : - if article.author is None : - article.author = extract_byline(article.href) - if article.description is None : - article.description = extract_description(article.href) + article.author = extract_author(soup) + article.summary = article.text_summary = extract_description(soup) def strip_anchors(self,soup): paras = soup.findAll(True) diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 73e0fae8e8..9264d3d0a3 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -752,7 +752,8 @@ class BasicNewsRecipe(Recipe): - def feed2index(self, feed): + def feed2index(self, f, feeds): + feed = feeds[f] if feed.image_url is not None: # Download feed image imgdir = os.path.join(self.output_dir, 'images') if not os.path.isdir(imgdir): @@ -808,7 +809,8 @@ class BasicNewsRecipe(Recipe): templ = templates.TouchscreenFeedTemplate() css = touchscreen_css + '\n\n' + (self.extra_css if self.extra_css else '') - return templ.generate(feed, self.description_limiter, + + return templ.generate(f, feeds, self.description_limiter, extra_css=css).render(doctype='xhtml') @@ -951,7 +953,7 @@ class BasicNewsRecipe(Recipe): #feeds.restore_duplicates() for f, feed in enumerate(feeds): - html = self.feed2index(feed) + html = self.feed2index(f,feeds) feed_dir = os.path.join(self.output_dir, 'feed_%d'%f) with open(os.path.join(feed_dir, 'index.html'), 'wb') as fi: fi.write(html) diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index 7ebf7294ae..45e713a9ac 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -107,32 +107,66 @@ class TouchscreenNavBarTemplate(Template): align = 'center' if center else 'left' navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_100', style='text-align:'+align)) - if bottom: - navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) - text = 'This article was downloaded by ' - p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') - p[0].tail = ' from ' - navbar.append(p) - navbar.append(BR()) - navbar.append(BR()) + + if False: + if bottom: + navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + text = 'This article was downloaded by ' + p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') + p[0].tail = ' from ' + navbar.append(p) + navbar.append(BR()) + navbar.append(BR()) + else: + next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ + else 'article_%d'%(art+1) + up = '../..' if art == number_of_articles_in_feed - 1 else '..' + href = '%s%s/%s/index.html'%(prefix, up, next) + navbar.text = '| ' + navbar.append(A('Next', href=href)) + + href = '%s../index.html#article_%d'%(prefix, art) + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A('Section Menu', href=href)) + href = '%s../../index.html#feed_%d'%(prefix, feed) + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A("Sections", href=href)) + if art > 0 and not bottom: + href = '%s../article_%d/index.html'%(prefix, art-1) + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A('Previous', href=href)) else: + if bottom: + navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + text = 'This article was downloaded by ' + p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') + p[0].tail = ' from ' + navbar.append(p) + navbar.append(BR()) + navbar.append(BR()) + else: + # | Previous + if art > 0 and not bottom: + href = '%s../article_%d/index.html'%(prefix, art-1) + navbar.text = '| ' + navbar.append(A('Previous', href=href)) + + # | Section | Main | + href = '%s../index.html#article_%d'%(prefix, art) + if art > 0: + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A('Articles', href=href)) + href = '%s../../index.html#feed_%d'%(prefix, feed) + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A("Sections", href=href)) + + # | Next next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ else 'article_%d'%(art+1) up = '../..' if art == number_of_articles_in_feed - 1 else '..' href = '%s%s/%s/index.html'%(prefix, up, next) - navbar.text = '| ' - navbar.append(A('Next', href=href)) - - href = '%s../index.html#article_%d'%(prefix, art) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Section Menu', href=href)) - href = '%s../../index.html#feed_%d'%(prefix, feed) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Main Menu', href=href)) - if art > 0 and not bottom: - href = '%s../article_%d/index.html'%(prefix, art-1) navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Previous', href=href)) + navbar.append(A('Next', href=href)) navbar.iterchildren(reversed=True).next().tail = ' | ' if not bottom: @@ -200,7 +234,8 @@ class TouchscreenIndexTemplate(Template): class FeedTemplate(Template): - def _generate(self, feed, cutoff, extra_css=None, style=None): + def _generate(self, f, feeds, cutoff, extra_css=None, style=None): + feed = feeds[f] head = HEAD(TITLE(feed.title)) if style: head.append(STYLE(style, type='text/css')) @@ -250,7 +285,41 @@ class FeedTemplate(Template): class TouchscreenFeedTemplate(Template): - def _generate(self, feed, cutoff, extra_css=None, style=None): + def _generate(self, f, feeds, cutoff, extra_css=None, style=None): + + def trim_title(title,clip=15): + if len(title)>clip: + tokens = title.split(' ') + new_title_tokens = [] + new_title_len = 0 + for token in tokens: + if len(token) + new_title_len < clip: + new_title_tokens.append(token) + new_title_len += len(token) + 1 + else: + new_title_tokens.append('...') + title = ' '.join(new_title_tokens) + break + return title + + feed = feeds[f] + + # Construct the navbar + navbar = DIV('| ', CLASS('calibre_navbar', 'calibre_rescale_100'),style='text-align:center') + if f > 0: + link = A(trim_title(feeds[f-1].title), href = '../feed_%d/index.html' % int(f-1)) + link.tail = ' |' + navbar.append(link) + + link = A("Sections", href="../index.html") + link.tail = ' |' + navbar.append(link) + if f < len(feeds)-1: + link = A(trim_title(feeds[f+1].title), href = '../feed_%d/index.html' % int(f+1)) + link.tail = ' |' + navbar.append(link) + + # Build the page head = HEAD(TITLE(feed.title)) if style: head.append(STYLE(style, type='text/css')) @@ -262,6 +331,7 @@ class TouchscreenFeedTemplate(Template): DIV(style="border-top:1px solid gray;border-bottom:1em solid white") ) body.append(div) + if getattr(feed, 'image', None): div.append(DIV(IMG( alt = feed.image_alt if feed.image_alt else '', @@ -280,41 +350,21 @@ class TouchscreenFeedTemplate(Template): continue tr = TR() - if True: - div_td = DIV( - A(article.title, CLASS('summary_headline','calibre_rescale_120', - href=article.url)), - style="display:inline-block") - if article.author: - div_td.append(DIV(article.author, - CLASS('summary_byline', 'calibre_rescale_100'))) - if article.summary: - div_td.append(DIV(cutoff(article.text_summary), - CLASS('summary_text', 'calibre_rescale_100'))) - tr.append(TD(div_td)) - else: - td = TD( - 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('summary_text', 'calibre_rescale_100'))) - - tr.append(td) + div_td = DIV( + A(article.title, CLASS('summary_headline','calibre_rescale_120', + href=article.url)), + style="display:inline-block") + if article.author: + div_td.append(DIV(article.author, + CLASS('summary_byline', 'calibre_rescale_100'))) + if article.summary: + div_td.append(DIV(cutoff(article.text_summary), + CLASS('summary_text', 'calibre_rescale_100'))) + tr.append(TD(div_td)) toc.append(tr) div.append(toc) - - navbar = DIV('| ', CLASS('calibre_navbar', 'calibre_rescale_100'),style='text-align:center') - link = A('Up one level', href="../index.html") - link.tail = ' |' - navbar.append(link) div.append(navbar) - self.root = HTML(head, body) class EmbeddedContent(Template): From b9cb28b88086cdbabbcedba232bc83f87b8e408e Mon Sep 17 00:00:00 2001 From: GRiker Date: Sat, 19 Jun 2010 17:22:32 -0600 Subject: [PATCH 3/8] GwR revisions --- src/calibre/devices/apple/driver.py | 15 +- src/calibre/library/custom_columns.py | 6 +- src/calibre/web/feeds/news.py | 11 + src/calibre/web/feeds/templates.py | 441 +++++++++++++++----------- 4 files changed, 279 insertions(+), 194 deletions(-) diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 28f7758f72..a994efb0f6 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -1557,6 +1557,10 @@ class ITUNES(DevicePlugin): return thumb.getvalue() except: self.log.error(" error generating thumb for '%s'" % book.name()) + try: + zfw.close() + except: + pass return None elif iswindows: @@ -1586,6 +1590,10 @@ class ITUNES(DevicePlugin): return thumb.getvalue() except: self.log.error(" error generating thumb for '%s'" % book.Name) + try: + zfw.close() + except: + pass return None def _get_device_book_size(self, file, compressed_size): @@ -2108,13 +2116,14 @@ class ITUNES(DevicePlugin): path = book.Location except: book = self._find_library_book(cached_book) + path = book.Location if book: - storage_path = os.path.split(book.Location) - if book.Location.startswith(self.iTunes_media): + storage_path = os.path.split(path) + if path.startswith(self.iTunes_media): if DEBUG: self.log.info(" removing '%s' at %s" % - (cached_book['title'], book.Location)) + (cached_book['title'], path)) try: os.remove(path) except: diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py index c0ba91e252..823a240065 100644 --- a/src/calibre/library/custom_columns.py +++ b/src/calibre/library/custom_columns.py @@ -467,7 +467,8 @@ class CustomColumns(object): books_ratings_link as bl, ratings as r WHERE {lt}.value={table}.id and bl.book={lt}.book and - r.id = bl.rating and r.rating <> 0) avg_rating + r.id = bl.rating and r.rating <> 0) avg_rating, + value AS sort FROM {table}; CREATE VIEW tag_browser_filtered_{table} AS SELECT @@ -481,7 +482,8 @@ class CustomColumns(object): ratings as r WHERE {lt}.value={table}.id AND bl.book={lt}.book AND r.id = bl.rating AND r.rating <> 0 AND - books_list_filter(bl.book)) avg_rating + books_list_filter(bl.book)) avg_rating, + value AS sort FROM {table}; '''.format(lt=lt, table=table), diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 9264d3d0a3..972617f422 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -805,6 +805,17 @@ class BasicNewsRecipe(Recipe): font-family:monospace; } + /* + .touchscreen_navbar { + -webkit-border-radius:4px; + background:#ccc; + border:#ccc 1px solid; + margin-left: 25%; + margin-right: 25%; + width: 50%; + } + */ + ''' templ = templates.TouchscreenFeedTemplate() diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index 45e713a9ac..0b99002ad6 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -14,6 +14,7 @@ def CLASS(*args, **kwargs): # class is a reserved word in Python kwargs['class'] = ' '.join(args) return kwargs +# Regular templates class Template(object): IS_HTML = True @@ -44,135 +45,30 @@ class Template(object): return etree.tostring(self.root, encoding='utf-8', xml_declaration=True, pretty_print=True) -class NavBarTemplate(Template): +class EmbeddedContent(Template): - def _generate(self, bottom, feed, art, number_of_articles_in_feed, - two_levels, url, __appname__, prefix='', center=True, - extra_css=None, style=None): - head = HEAD(TITLE('navbar')) + def _generate(self, article, style=None, extra_css=None): + content = article.content if article.content else '' + summary = article.summary if article.summary else '' + text = content if len(content) > len(summary) else summary + head = HEAD(TITLE(article.title)) if style: head.append(STYLE(style, type='text/css')) if extra_css: head.append(STYLE(extra_css, type='text/css')) - if prefix and not prefix.endswith('/'): - prefix += '/' - align = 'center' if center else 'left' - navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_70', - style='text-align:'+align)) - if bottom: - navbar.append(HR()) - text = 'This article was downloaded by ' - p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') - p[0].tail = ' from ' - navbar.append(p) - navbar.append(BR()) - navbar.append(BR()) - else: - next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ - else 'article_%d'%(art+1) - up = '../..' if art == number_of_articles_in_feed - 1 else '..' - href = '%s%s/%s/index.html'%(prefix, up, next) - navbar.text = '| ' - navbar.append(A('Next', href=href)) - href = '%s../index.html#article_%d'%(prefix, art) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Section Menu', href=href)) - href = '%s../../index.html#feed_%d'%(prefix, feed) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Main Menu', href=href)) - if art > 0 and not bottom: - href = '%s../article_%d/index.html'%(prefix, art-1) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Previous', href=href)) - navbar.iterchildren(reversed=True).next().tail = ' | ' - if not bottom: - navbar.append(HR()) - - self.root = HTML(head, BODY(navbar)) - -class TouchscreenNavBarTemplate(Template): - - def _generate(self, bottom, feed, art, number_of_articles_in_feed, - two_levels, url, __appname__, prefix='', center=True, - extra_css=None, style=None): - head = HEAD(TITLE('navbar')) - if style: - head.append(STYLE(style, type='text/css')) - if extra_css: - head.append(STYLE(extra_css, type='text/css')) - - if prefix and not prefix.endswith('/'): - prefix += '/' - align = 'center' if center else 'left' - navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_100', - style='text-align:'+align)) - - if False: - if bottom: - navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) - text = 'This article was downloaded by ' - p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') - p[0].tail = ' from ' - navbar.append(p) - navbar.append(BR()) - navbar.append(BR()) - else: - next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ - else 'article_%d'%(art+1) - up = '../..' if art == number_of_articles_in_feed - 1 else '..' - href = '%s%s/%s/index.html'%(prefix, up, next) - navbar.text = '| ' - navbar.append(A('Next', href=href)) - - href = '%s../index.html#article_%d'%(prefix, art) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Section Menu', href=href)) - href = '%s../../index.html#feed_%d'%(prefix, feed) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A("Sections", href=href)) - if art > 0 and not bottom: - href = '%s../article_%d/index.html'%(prefix, art-1) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Previous', href=href)) - else: - if bottom: - navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) - text = 'This article was downloaded by ' - p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') - p[0].tail = ' from ' - navbar.append(p) - navbar.append(BR()) - navbar.append(BR()) - else: - # | Previous - if art > 0 and not bottom: - href = '%s../article_%d/index.html'%(prefix, art-1) - navbar.text = '| ' - navbar.append(A('Previous', href=href)) - - # | Section | Main | - href = '%s../index.html#article_%d'%(prefix, art) - if art > 0: - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Articles', href=href)) - href = '%s../../index.html#feed_%d'%(prefix, feed) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A("Sections", href=href)) - - # | Next - next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ - else 'article_%d'%(art+1) - up = '../..' if art == number_of_articles_in_feed - 1 else '..' - href = '%s%s/%s/index.html'%(prefix, up, next) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Next', href=href)) - - navbar.iterchildren(reversed=True).next().tail = ' | ' - if not bottom: - navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) - - self.root = HTML(head, BODY(navbar)) + if isbytestring(text): + text = text.decode('utf-8', 'replace') + elements = html.fragments_fromstring(text) + self.root = HTML(head, + BODY(H2(article.title), DIV())) + div = self.root.find('body').find('div') + if elements and isinstance(elements[0], unicode): + div.text = elements[0] + elements = list(elements)[1:] + for elem in elements: + elem.getparent().remove(elem) + div.append(elem) class IndexTemplate(Template): @@ -198,40 +94,6 @@ class IndexTemplate(Template): CLASS('calibre_rescale_100')) self.root = HTML(head, BODY(div)) -class TouchscreenIndexTemplate(Template): - - def _generate(self, title, masthead, datefmt, feeds, extra_css=None, style=None): - if isinstance(datefmt, unicode): - datefmt = datefmt.encode(preferred_encoding) - date = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) - masthead_p = etree.Element("p") - masthead_p.set("style","text-align:center") - masthead_img = etree.Element("img") - masthead_img.set("src",masthead) - masthead_img.set("alt","masthead") - masthead_p.append(masthead_img) - - head = HEAD(TITLE(title)) - if style: - head.append(STYLE(style, type='text/css')) - if extra_css: - head.append(STYLE(extra_css, type='text/css')) - - toc = TABLE(CLASS('toc'),width="100%",border="0",cellpadding="3px") - for i, feed in enumerate(feeds): - if feed: - tr = TR() - 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'), - #DIV(style="border-color:gray;border-top-style:solid;border-width:thin"), - DIV(style="border-top:1px solid gray;border-bottom:1em solid white"), - toc) - self.root = HTML(head, BODY(div)) - class FeedTemplate(Template): def _generate(self, f, feeds, cutoff, extra_css=None, style=None): @@ -283,11 +145,95 @@ class FeedTemplate(Template): self.root = HTML(head, body) +class NavBarTemplate(Template): + + def _generate(self, bottom, feed, art, number_of_articles_in_feed, + two_levels, url, __appname__, prefix='', center=True, + extra_css=None, style=None): + head = HEAD(TITLE('navbar')) + if style: + head.append(STYLE(style, type='text/css')) + if extra_css: + head.append(STYLE(extra_css, type='text/css')) + + if prefix and not prefix.endswith('/'): + prefix += '/' + align = 'center' if center else 'left' + + navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_70', + style='text-align:'+align)) + if bottom: + navbar.append(HR()) + text = 'This article was downloaded by ' + p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') + p[0].tail = ' from ' + navbar.append(p) + navbar.append(BR()) + navbar.append(BR()) + else: + next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ + else 'article_%d'%(art+1) + up = '../..' if art == number_of_articles_in_feed - 1 else '..' + href = '%s%s/%s/index.html'%(prefix, up, next) + navbar.text = '| ' + navbar.append(A('Next', href=href)) + href = '%s../index.html#article_%d'%(prefix, art) + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A('Section Menu', href=href)) + href = '%s../../index.html#feed_%d'%(prefix, feed) + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A('Main Menu', href=href)) + if art > 0 and not bottom: + href = '%s../article_%d/index.html'%(prefix, art-1) + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A('Previous', href=href)) + navbar.iterchildren(reversed=True).next().tail = ' | ' + if not bottom: + navbar.append(HR()) + + self.root = HTML(head, BODY(navbar)) + + +# Touchscreen templates +class TouchscreenIndexTemplate(Template): + + def _generate(self, title, masthead, datefmt, feeds, extra_css=None, style=None): + if isinstance(datefmt, unicode): + datefmt = datefmt.encode(preferred_encoding) + date = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) + masthead_p = etree.Element("p") + masthead_p.set("style","text-align:center") + masthead_img = etree.Element("img") + masthead_img.set("src",masthead) + masthead_img.set("alt","masthead") + masthead_p.append(masthead_img) + + head = HEAD(TITLE(title)) + if style: + head.append(STYLE(style, type='text/css')) + if extra_css: + head.append(STYLE(extra_css, type='text/css')) + + toc = TABLE(CLASS('toc'),width="100%",border="0",cellpadding="3px") + for i, feed in enumerate(feeds): + if feed: + tr = TR() + 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'), + #DIV(style="border-color:gray;border-top-style:solid;border-width:thin"), + DIV(style="border-top:1px solid gray;border-bottom:1em solid white"), + toc) + self.root = HTML(head, BODY(div)) + class TouchscreenFeedTemplate(Template): def _generate(self, f, feeds, cutoff, extra_css=None, style=None): - def trim_title(title,clip=15): + def trim_title(title,clip=18): if len(title)>clip: tokens = title.split(' ') new_title_tokens = [] @@ -295,7 +241,7 @@ class TouchscreenFeedTemplate(Template): for token in tokens: if len(token) + new_title_len < clip: new_title_tokens.append(token) - new_title_len += len(token) + 1 + new_title_len += len(token) else: new_title_tokens.append('...') title = ' '.join(new_title_tokens) @@ -305,19 +251,42 @@ class TouchscreenFeedTemplate(Template): feed = feeds[f] # Construct the navbar - navbar = DIV('| ', CLASS('calibre_navbar', 'calibre_rescale_100'),style='text-align:center') - if f > 0: - link = A(trim_title(feeds[f-1].title), href = '../feed_%d/index.html' % int(f-1)) - link.tail = ' |' - navbar.append(link) + if False: + navbar = DIV('', CLASS('calibre_navbar', 'calibre_rescale_100'),style='text-align:center') + if f > 0: + link = A(trim_title('< ' + feeds[f-1].title + ' '), href = '../feed_%d/index.html' % int(f-1)) + #link.tail = ' |' + navbar.append(link) - link = A("Sections", href="../index.html") - link.tail = ' |' - navbar.append(link) - if f < len(feeds)-1: - link = A(trim_title(feeds[f+1].title), href = '../feed_%d/index.html' % int(f+1)) - link.tail = ' |' + link = A("| Sections |", href="../index.html") + #link.tail = ' |' navbar.append(link) + if f < len(feeds)-1: + link = A(trim_title(' '+feeds[f+1].title) + ' >', href = '../feed_%d/index.html' % int(f+1)) + #link.tail = ' |' + navbar.append(link) + else: + navbar_t = TABLE(width="100%", align="center", border="0", + cellspacing="3px", cellpadding="3px") + navbar_tr = TR() + + link = '' + if f > 0: + link = A(trim_title('<' + feeds[f-1].title), + href = '../feed_%d/index.html' % int(f-1)) + navbar_tr.append(TD(link, width="40%", align="right")) + + link = A("Sections", href="../index.html") + navbar_tr.append(TD(link,width="20%", align="center")) + + link = '' + if f < len(feeds)-1: + link = A(trim_title(feeds[f+1].title) + '>', + href = '../feed_%d/index.html' % int(f+1)) + navbar_tr.append(TD(link, width="40%", align="left")) + + navbar_t.append(navbar_tr) + navbar = navbar_t # Build the page head = HEAD(TITLE(feed.title)) @@ -361,34 +330,128 @@ class TouchscreenFeedTemplate(Template): div_td.append(DIV(cutoff(article.text_summary), CLASS('summary_text', 'calibre_rescale_100'))) tr.append(TD(div_td)) - toc.append(tr) + div.append(toc) + div.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) div.append(navbar) self.root = HTML(head, body) -class EmbeddedContent(Template): +class TouchscreenNavBarTemplate(Template): - def _generate(self, article, style=None, extra_css=None): - content = article.content if article.content else '' - summary = article.summary if article.summary else '' - text = content if len(content) > len(summary) else summary - head = HEAD(TITLE(article.title)) + def _generate(self, bottom, feed, art, number_of_articles_in_feed, + two_levels, url, __appname__, prefix='', center=True, + extra_css=None, style=None): + head = HEAD(TITLE('navbar')) if style: head.append(STYLE(style, type='text/css')) if extra_css: head.append(STYLE(extra_css, type='text/css')) - if isbytestring(text): - text = text.decode('utf-8', 'replace') - elements = html.fragments_fromstring(text) - self.root = HTML(head, - BODY(H2(article.title), DIV())) - div = self.root.find('body').find('div') - if elements and isinstance(elements[0], unicode): - div.text = elements[0] - elements = list(elements)[1:] - for elem in elements: - elem.getparent().remove(elem) - div.append(elem) + if prefix and not prefix.endswith('/'): + prefix += '/' + align = 'center' if center else 'left' + if False: + navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_100', + style='text-align:'+align)) + + if bottom and False: + navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + text = 'This article was downloaded by ' + p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') + p[0].tail = ' from ' + navbar.append(p) + navbar.append(BR()) + navbar.append(BR()) + + # | Previous + if art > 0: + href = '%s../article_%d/index.html'%(prefix, art-1) + navbar.append(A('< Previous', href=href)) + + # | Articles | Sections | + href = '%s../index.html#article_%d'%(prefix, art) + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A('Articles', href=href)) + + href = '%s../../index.html#feed_%d'%(prefix, feed) + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A("Sections", href=href)) + + # | Next + next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ + else 'article_%d'%(art+1) + up = '../..' if art == number_of_articles_in_feed - 1 else '..' + href = '%s%s/%s/index.html'%(prefix, up, next) + navbar.iterchildren(reversed=True).next().tail = ' | ' + navbar.append(A('Next >', href=href)) + + #navbar.iterchildren(reversed=True).next().tail = ' | ' + if not bottom: + navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + else: + ''' + table.touchscreen_navbar { + -webkit-border-radius:4px; + background:#ccc; + border:#ccc 1px solid; + margin-left: 25%; + margin-right: 25%; + width: 50%;} + + + + + + + + + +
< Articles Sections >
+ + ''' + + navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_120')) + + if bottom: + navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + + navbar_t = TABLE( + align="center", + border="0", + cellpadding="3px", + cellspacing="3px", + width="100%" + ) + + navbar_tr = TR() + # | Previous + if art > 0: + href = '%s../article_%d/index.html'%(prefix, art-1) + navbar_tr.append(TD(A(' ', href=href),width="32%", align="left")) + navbar_t.append(navbar_tr) + navbar.append(navbar_t) + #print "\n%s\n" % etree.tostring(navbar, pretty_print=True) + + if not bottom: + navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + + self.root = HTML(head, BODY(navbar)) From 5d91f7515a35462ed39f512d2f7312b2dc94da64 Mon Sep 17 00:00:00 2001 From: GRiker Date: Sun, 20 Jun 2010 09:50:17 -0600 Subject: [PATCH 4/8] GwR wip --- src/calibre/ebooks/oeb/stylizer.py | 11 ++ src/calibre/web/feeds/news.py | 82 +++++++------- src/calibre/web/feeds/templates.py | 176 +++++++---------------------- 3 files changed, 98 insertions(+), 171 deletions(-) diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index 0637dddfb6..d6070efade 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -126,6 +126,17 @@ class Stylizer(object): head = head[0] else: head = [] + + # GwR : Add webkit profile to cssutils before validating + if True: + wk_macros = { + 'border-width': '{length}|thin|medium|thick' + } + wk_props = { + '-webkit-border-radius': r'{border-width}(\s+{border-width}){0,3}|inherit' + } + cssutils.profile.addProfile('webkit', wk_props, wk_macros) + parser = cssutils.CSSParser(fetcher=self._fetch_css_file, log=logging.getLogger('calibre.css')) self.font_face_rules = [] diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 972617f422..29b581c361 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -280,6 +280,46 @@ class BasicNewsRecipe(Recipe): } ''' + #: The CSS that is used to style the touchscreen elements, i.e., the navigation bars and + #: the Feed summaries. + touchscreen_css = u''' + .article_navbar { + -webkit-border-radius:4px; + background-color:#eee; + border:1px solid #888; + margin-left: 5%; + margin-right: 5%; + width: 90%; + } + + .feed_navbar { + -webkit-border-radius:4px; + background-color:#eee; + border:1px solid #888; + margin-left: 5%; + margin-right: 5%; + width: 90%; + } + + .summary_headline { + font-weight:bold; text-align:left; + } + + .summary_byline { + text-align:left; + font-family:monospace; + } + + .summary_text { + text-align:left; + } + + .feed { + font-family:sans-serif; font-weight:bold; font-size:larger; + } + ''' + + #: By default, calibre will use a default image for the masthead (Kindle only). #: Override this in your recipe to provide a url to use as a masthead. masthead_url = None @@ -638,6 +678,9 @@ class BasicNewsRecipe(Recipe): if self.delay > 0: self.simultaneous_downloads = 1 + if self.touchscreen: + self.extra_css += self.touchscreen_css + self.navbar = templates.TouchscreenNavBarTemplate() if self.touchscreen else templates.NavBarTemplate() self.failed_downloads = [] self.partial_failures = [] @@ -661,8 +704,7 @@ class BasicNewsRecipe(Recipe): templ = self.navbar.generate(False, f, a, feed_len, not self.has_single_feed, url, __appname__, - center=self.center_navbar, - extra_css=self.extra_css) + center=self.center_navbar) elem = BeautifulSoup(templ.render(doctype='xhtml').decode('utf-8')).find('div') body.insert(0, elem) if self.remove_javascript: @@ -783,43 +825,7 @@ class BasicNewsRecipe(Recipe): css = self.template_css + '\n\n' +(self.extra_css if self.extra_css else '') if self.touchscreen: - touchscreen_css = u''' - .summary_headline { - font-weight:bold; text-align:left; - } - - .summary_byline { - text-align:left; - font-family:monospace; - } - - .summary_text { - text-align:left; - } - - .feed { - font-family:sans-serif; font-weight:bold; font-size:larger; - } - - .calibre_navbar { - font-family:monospace; - } - - /* - .touchscreen_navbar { - -webkit-border-radius:4px; - background:#ccc; - border:#ccc 1px solid; - margin-left: 25%; - margin-right: 25%; - width: 50%; - } - */ - - ''' - templ = templates.TouchscreenFeedTemplate() - css = touchscreen_css + '\n\n' + (self.extra_css if self.extra_css else '') return templ.generate(f, feeds, self.description_limiter, extra_css=css).render(doctype='xhtml') diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index 0b99002ad6..5b4704e766 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal ' from lxml import html, etree from lxml.html.builder import HTML, HEAD, TITLE, STYLE, DIV, BODY, \ - STRONG, BR, SPAN, A, HR, UL, LI, H2, IMG, P as PT, \ + STRONG, EM, BR, SPAN, A, HR, UL, LI, H2, IMG, P as PT, \ TABLE, TD, TR from calibre import preferred_encoding, strftime, isbytestring @@ -251,42 +251,26 @@ class TouchscreenFeedTemplate(Template): feed = feeds[f] # Construct the navbar - if False: - navbar = DIV('', CLASS('calibre_navbar', 'calibre_rescale_100'),style='text-align:center') - if f > 0: - link = A(trim_title('< ' + feeds[f-1].title + ' '), href = '../feed_%d/index.html' % int(f-1)) - #link.tail = ' |' - navbar.append(link) + navbar_t = TABLE(CLASS('feed_navbar')) + navbar_tr = TR() - link = A("| Sections |", href="../index.html") - #link.tail = ' |' - navbar.append(link) - if f < len(feeds)-1: - link = A(trim_title(' '+feeds[f+1].title) + ' >', href = '../feed_%d/index.html' % int(f+1)) - #link.tail = ' |' - navbar.append(link) - else: - navbar_t = TABLE(width="100%", align="center", border="0", - cellspacing="3px", cellpadding="3px") - navbar_tr = TR() + link = '' + if f > 0: + link = A(EM( '< ' + trim_title(feeds[f-1].title)), + href = '../feed_%d/index.html' % int(f-1)) + navbar_tr.append(TD(link, width="40%", align="center")) - link = '' - if f > 0: - link = A(trim_title('<' + feeds[f-1].title), - href = '../feed_%d/index.html' % int(f-1)) - navbar_tr.append(TD(link, width="40%", align="right")) + link = A(STRONG('Sections'), href="../index.html") + navbar_tr.append(TD(link,width="20%",align="center")) - link = A("Sections", href="../index.html") - navbar_tr.append(TD(link,width="20%", align="center")) + link = '' + if f < len(feeds)-1: + link = A(EM(trim_title(feeds[f+1].title) + ' >'), + href = '../feed_%d/index.html' % int(f+1)) + navbar_tr.append(TD(link, width="40%", align="center")) - link = '' - if f < len(feeds)-1: - link = A(trim_title(feeds[f+1].title) + '>', - href = '../feed_%d/index.html' % int(f+1)) - navbar_tr.append(TD(link, width="40%", align="left")) - - navbar_t.append(navbar_tr) - navbar = navbar_t + navbar_t.append(navbar_tr) + navbar = navbar_t # Build the page head = HEAD(TITLE(feed.title)) @@ -333,7 +317,8 @@ class TouchscreenFeedTemplate(Template): toc.append(tr) div.append(toc) - div.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + #div.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + div.append(BR()) div.append(navbar) self.root = HTML(head, body) @@ -348,110 +333,35 @@ class TouchscreenNavBarTemplate(Template): if extra_css: head.append(STYLE(extra_css, type='text/css')) - if prefix and not prefix.endswith('/'): - prefix += '/' - align = 'center' if center else 'left' + navbar = DIV() + if bottom: + navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) - if False: - navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_100', - style='text-align:'+align)) - - if bottom and False: - navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) - text = 'This article was downloaded by ' - p = PT(text, STRONG(__appname__), A(url, href=url), style='text-align:left') - p[0].tail = ' from ' - navbar.append(p) - navbar.append(BR()) - navbar.append(BR()) - - # | Previous - if art > 0: - href = '%s../article_%d/index.html'%(prefix, art-1) - navbar.append(A('< Previous', href=href)) - - # | Articles | Sections | - href = '%s../index.html#article_%d'%(prefix, art) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Articles', href=href)) - - href = '%s../../index.html#feed_%d'%(prefix, feed) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A("Sections", href=href)) - - # | Next - next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ - else 'article_%d'%(art+1) - up = '../..' if art == number_of_articles_in_feed - 1 else '..' - href = '%s%s/%s/index.html'%(prefix, up, next) - navbar.iterchildren(reversed=True).next().tail = ' | ' - navbar.append(A('Next >', href=href)) - - #navbar.iterchildren(reversed=True).next().tail = ' | ' - if not bottom: - navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + navbar_t = TABLE(CLASS('article_navbar')) + navbar_tr = TR() + # | Previous + if art > 0: + href = '%s../article_%d/index.html'%(prefix, art-1) + navbar_tr.append(TD(A(EM('< Previous'), href=href), width="32%", align="center")) else: - ''' - table.touchscreen_navbar { - -webkit-border-radius:4px; - background:#ccc; - border:#ccc 1px solid; - margin-left: 25%; - margin-right: 25%; - width: 50%;} - - - - - - - - - -
< Articles Sections >
+ navbar_tr.append(TD('', width="25%")) - ''' + # | Articles | Sections | + href = '%s../index.html#article_%d'%(prefix, art) + navbar_tr.append(TD(A(STRONG('Articles'), href=href),width="18%", align="center")) - navbar = DIV(CLASS('calibre_navbar', 'calibre_rescale_120')) + href = '%s../../index.html#feed_%d'%(prefix, feed) + navbar_tr.append(TD(A(STRONG('Sections'), href=href),width="18%", align="center")) - if bottom: - navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + # | Next + next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ + else 'article_%d'%(art+1) + up = '../..' if art == number_of_articles_in_feed - 1 else '..' + href = '%s%s/%s/index.html'%(prefix, up, next) - navbar_t = TABLE( - align="center", - border="0", - cellpadding="3px", - cellspacing="3px", - width="100%" - ) - - navbar_tr = TR() - # | Previous - if art > 0: - href = '%s../article_%d/index.html'%(prefix, art-1) - navbar_tr.append(TD(A(' ', href=href),width="32%", align="left")) - navbar_t.append(navbar_tr) - navbar.append(navbar_t) - #print "\n%s\n" % etree.tostring(navbar, pretty_print=True) - - if not bottom: - navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) + navbar_tr.append(TD(A(EM('Next >'), href=href),width="32%", align="center")) + navbar_t.append(navbar_tr) + navbar.append(navbar_t) + #print "\n%s\n" % etree.tostring(navbar, pretty_print=True) self.root = HTML(head, BODY(navbar)) From bd04312a764c6f9592cfe6cec8a000d247fbaf22 Mon Sep 17 00:00:00 2001 From: GRiker Date: Tue, 22 Jun 2010 11:41:08 -0600 Subject: [PATCH 5/8] GwR wip --- resources/default_tweaks.py | 7 +- src/calibre/customize/profiles.py | 79 +++++- src/calibre/devices/apple/driver.py | 420 +++++++++++++++++++--------- src/calibre/ebooks/oeb/stylizer.py | 15 +- src/calibre/gui2/actions.py | 3 +- src/calibre/web/feeds/news.py | 51 +--- src/calibre/web/feeds/templates.py | 54 ++-- 7 files changed, 416 insertions(+), 213 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index aaeb992151..40b3d2fa32 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -72,4 +72,9 @@ gui_pubdate_display_format = 'MMM yyyy' # without changing anything is sufficient to change the sort. title_series_sorting = 'library_order' - +# Apple iDevice +# Control whether Series name is used as Category/Genre in iTunes/iBooks +# If set to 'True', a Book's Series name will be used as the Category/Genre +# If set to 'False', the book's first tag beginning with an alpha character will +# be used as the Category/Genre +iDevice_use_series_as_category = True diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py index c872c9df38..68345ca0da 100644 --- a/src/calibre/customize/profiles.py +++ b/src/calibre/customize/profiles.py @@ -237,9 +237,6 @@ class OutputProfile(Plugin): # If True the MOBI renderer on the device supports MOBI indexing supports_mobi_indexing = False - # Device supports displaying a nested TOC - supports_nested_toc = True - # If True output should be optimized for a touchscreen interface touchscreen = False @@ -256,8 +253,82 @@ class iPadOutput(OutputProfile): screen_size = (768, 1024) comic_screen_size = (768, 1024) dpi = 132.0 - supports_nested_toc = False + timefmt = '%A, %d %b %Y' + cssutils_addProfile = { 'name':'webkit', + 'props': { + '-webkit-border-bottom-left-radius':'{length}', + '-webkit-border-bottom-right-radius':'{length}', + '-webkit-border-top-left-radius':'{length}', + '-webkit-border-top-right-radius':'{length}', + '-webkit-border-radius': r'{border-width}(\s+{border-width}){0,3}|inherit', + }, + 'macros': {'border-width': '{length}|medium|thick|thin'}} touchscreen = True + touchscreen_css = u''' + /* hr used in articles */ + .caption_divider { + border:#ccc 1px solid; + } + + .touchscreen_navbar { + background:#ccc; + border:#ccc 1px solid; + border-collapse:separate; + border-spacing:1px; + margin-left: 5%; + margin-right: 5%; + width: 90%; + -webkit-border-radius:4px; + } + .touchscreen_navbar td { + background:#fff; + font-family:Helvetica; + font-size:80%; + padding: 5px; + text-align:center; + } + .touchscreen_navbar td:first-child { + -webkit-border-top-left-radius:4px; + -webkit-border-bottom-left-radius:4px; + } + .touchscreen_navbar td:last-child { + -webkit-border-top-right-radius:4px; + -webkit-border-bottom-right-radius:4px; + } + + .feed_link { + font-style: italic; + } + + + /* Feed summary formatting */ + .feed_title { + text-align: center; + font-size: 160%; + } + + .summary_headline { + font-weight:bold; + text-align:left; + } + + .summary_byline { + text-align:left; + font-family:monospace; + } + + .summary_text { + text-align:left; + } + + .feed { + font-family:sans-serif; + font-weight:bold; + font-size:larger; + } + + ''' + class SonyReaderOutput(OutputProfile): diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index a994efb0f6..2efe2b553f 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -15,7 +15,7 @@ from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata.epub import set_metadata from calibre.library.server.utils import strftime -from calibre.utils.config import Config, config_dir +from calibre.utils.config import Config, config_dir, tweaks from calibre.utils.date import isoformat, now, parse_date from calibre.utils.logging import Log from calibre.utils.zipfile import ZipFile @@ -78,12 +78,12 @@ class ITUNES(DevicePlugin): supported_platforms = ['osx','windows'] author = 'GRiker' #: The version of this plugin as a 3-tuple (major, minor, revision) - version = (0,7,0) + version = (0,8,0) OPEN_FEEDBACK_MESSAGE = _( 'Apple device detected, launching iTunes, please wait ...') - FORMATS = ['epub'] + FORMATS = ['epub','pdf'] # Product IDs: # 0x1292:iPhone 3G @@ -141,6 +141,10 @@ class ITUNES(DevicePlugin): 'SongNames', ] + # Cover art size limits + MAX_COVER_WIDTH = 510 + MAX_COVER_HEIGHT = 680 + # Properties cached_books = {} cache_dir = os.path.join(config_dir, 'caches', 'itunes') @@ -159,7 +163,7 @@ class ITUNES(DevicePlugin): sources = None update_msg = None update_needed = False - use_series_data = True + use_series_as_category = tweaks['iDevice_use_series_as_category'] # Public methods def add_books_to_metadata(self, locations, metadata, booklists): @@ -173,16 +177,17 @@ class ITUNES(DevicePlugin): (L{books}(oncard=None), L{books}(oncard='carda'), L{books}(oncard='cardb')). ''' + if DEBUG: + self.log.info("ITUNES.add_books_to_metadata()") task_count = float(len(self.update_list)) # Delete any obsolete copies of the book from the booklist if self.update_list: - if True: - self.log.info("ITUNES.add_books_to_metadata()") - #self._dump_booklist(booklists[0], header='before',indent=2) - #self._dump_update_list(header='before',indent=2) - #self._dump_cached_books(header='before',indent=2) + if False: + self._dump_booklist(booklists[0], header='before',indent=2) + self._dump_update_list(header='before',indent=2) + self._dump_cached_books(header='before',indent=2) for (j,p_book) in enumerate(self.update_list): if False: @@ -230,12 +235,12 @@ class ITUNES(DevicePlugin): # Add new books to booklists[0] for new_book in locations[0]: - if False: + if DEBUG: self.log.info(" adding '%s' by '%s' to booklists[0]" % (new_book.title, new_book.author)) booklists[0].append(new_book) - if False: + if DEBUG: self._dump_booklist(booklists[0],header='after',indent=2) self._dump_cached_books(header='after',indent=2) @@ -329,7 +334,8 @@ class ITUNES(DevicePlugin): 'title':book.Name, 'author':book.Artist, 'lib_book':library_books[this_book.path] if this_book.path in library_books else None, - 'uuid': book.Composer + 'uuid': book.Composer, + 'format': 'pdf' if book.KindAsString.startswith('PDF') else 'epub' } if self.report_progress is not None: @@ -343,9 +349,9 @@ class ITUNES(DevicePlugin): if self.report_progress is not None: self.report_progress(1.0, _('finished')) self.cached_books = cached_books -# if DEBUG: -# self._dump_booklist(booklist, 'returning from books():') -# self._dump_cached_books('returning from books():') + if DEBUG: + self._dump_booklist(booklist, 'returning from books()', indent=2) + self._dump_cached_books('returning from books()',indent=2) return booklist else: return [] @@ -685,6 +691,9 @@ class ITUNES(DevicePlugin): @param booklists: A tuple containing the result of calls to (L{books}(oncard=None), L{books}(oncard='carda'), L{books}(oncard='cardb')). + + NB: This will not find books that were added by a different installation of calibre + as uuids are different ''' if DEBUG: self.log.info("ITUNES.remove_books_from_metadata()") @@ -750,6 +759,10 @@ class ITUNES(DevicePlugin): (L{books}(oncard=None), L{books}(oncard='carda'), L{books}(oncard='cardb')). ''' + + if DEBUG: + self.log.info("ITUNES.sync_booklists()") + if self.update_needed: if DEBUG: self.log.info(' calling _update_device') @@ -812,29 +825,32 @@ class ITUNES(DevicePlugin): self.problem_msg = _("Some cover art could not be converted.\n" "Click 'Show Details' for a list.") - if False: + if DEBUG: self.log.info("ITUNES.upload_books()") self._dump_files(files, header='upload_books()',indent=2) self._dump_update_list(header='upload_books()',indent=2) if isosx: for (i,file) in enumerate(files): + format = file.rpartition('.')[2].lower() path = self.path_template % (metadata[i].title, metadata[i].author[0]) self._remove_existing_copy(path, metadata[i]) - fpath = self._get_fpath(file, metadata[i], update_md=True) + fpath = self._get_fpath(file, metadata[i], format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) - thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added) - this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb) + thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added, format) + this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb, format) 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, - 'lib_book': lb_added, 'dev_book': db_added, - 'uuid': metadata[i].uuid} + 'format': format, + 'lib_book': lb_added, + 'title': metadata[i].title, + 'uuid': metadata[i].uuid } + # Report progress if self.report_progress is not None: @@ -846,9 +862,10 @@ class ITUNES(DevicePlugin): self.iTunes = win32com.client.Dispatch("iTunes.Application") for (i,file) in enumerate(files): + format = file.rpartition('.')[2].lower() path = self.path_template % (metadata[i].title, metadata[i].author[0]) self._remove_existing_copy(path, metadata[i]) - fpath = self._get_fpath(file, metadata[i], update_md=True) + fpath = self._get_fpath(file, metadata[i],format, update_md=True) db_added, lb_added = self._add_new_copy(fpath, metadata[i]) if self.manual_sync_mode and not db_added: @@ -857,17 +874,18 @@ class ITUNES(DevicePlugin): "Click 'Show Details...' for affected books.") self.problem_titles.append("'%s' by %s" % (metadata[i].title, metadata[i].author[0])) - 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) + thumb = self._cover_to_thumb(path, metadata[i], db_added, lb_added, format) + this_book = self._create_new_book(fpath, metadata[i], path, db_added, lb_added, thumb, format) 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, + 'format': format, + 'lib_book': lb_added, + 'title': metadata[i].title, 'uuid': metadata[i].uuid} # Report progress @@ -968,7 +986,8 @@ class ITUNES(DevicePlugin): db_added = self._find_device_book( {'title': metadata.title, 'author': metadata.authors[0], - 'uuid': metadata.uuid}) + 'uuid': metadata.uuid, + 'format': fpath.rpartition('.')[2].lower()}) return db_added @@ -1021,7 +1040,8 @@ class ITUNES(DevicePlugin): added = self._find_library_book( { 'title': metadata.title, 'author': metadata.author[0], - 'uuid': metadata.uuid}) + 'uuid': metadata.uuid, + 'format': file.rpartition('.')[2].lower()}) return added def _add_new_copy(self, fpath, metadata): @@ -1047,46 +1067,82 @@ class ITUNES(DevicePlugin): return db_added, lb_added - def _cover_to_thumb(self, path, metadata, db_added, lb_added): + def _cover_to_thumb(self, path, metadata, db_added, lb_added, format): ''' assumes pythoncom wrapper for db_added + as of iTunes 9.2, iBooks 1.1, can't set artwork for PDF files via automation ''' self.log.info(" ITUNES._cover_to_thumb()") + thumb = None if 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: - # 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 (format == 'epub'): + # Pre-shrink cover + # self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT + try: + img = PILImage.open(metadata.cover) + width = img.size[0] + height = img.size[1] + scaled, nwidth, nheight = fit_image(width, height, self.MAX_COVER_WIDTH, self.MAX_COVER_HEIGHT) + if scaled: if DEBUG: - self.log.warning(" iTunes automation interface reported an error" - " when adding artwork to '%s' on the iDevice" % 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) + self.log.info(" '%s' scaled from %sx%s to %sx%s" % + (metadata.cover,width,height,nwidth,nheight)) + img = img.resize((nwidth, nheight), PILImage.ANTIALIAS) + cd = cStringIO.StringIO() + img.convert('RGB').save(cd, 'JPEG') + cover_data = cd.getvalue() + cd.close() else: - lb_added.AddArtworkFromFile(metadata.cover) + with open(metadata.cover,'r+b') as cd: + cover_data = cd.read() + except: + self.problem_titles.append("'%s' by %s" % (metadata.title, metadata.author[0])) + self.log.error(" error scaling '%s' for '%s'" % (metadata.cover,metadata.title)) + return thumb - if db_added: - if db_added.Artwork.Count: - db_added.Artwork.Item(1).SetArtworkFromFile(metadata.cover) - else: - db_added.AddArtworkFromFile(metadata.cover) + if isosx: + if lb_added: + lb_added.artworks[1].data_.set(cover_data) + + 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) + except: + if DEBUG: + self.log.warning(" iTunes automation interface reported an error" + " when adding artwork to '%s' on the iDevice" % metadata.title) + #import traceback + #traceback.print_exc() + #from calibre import ipython + #ipython(user_ns=locals()) + pass + + + elif iswindows: + # Write the data to a real file for Windows iTunes + tc = os.path.join(tempfile.gettempdir(), "cover.jpg") + with open(tc,'wb') as tmp_cover: + tmp_cover.write(cover_data) + + if lb_added: + if lb_added.Artwork.Count: + lb_added.Artwork.Item(1).SetArtworkFromFile(tc) + else: + lb_added.AddArtworkFromFile(tc) + + if db_added: + if db_added.Artwork.Count: + db_added.Artwork.Item(1).SetArtworkFromFile(tc) + else: + db_added.AddArtworkFromFile(tc) + + elif format == 'pdf': + if DEBUG: + self.log.info(" unable to set PDF cover via automation interface") try: # Resize for thumb @@ -1097,6 +1153,7 @@ class ITUNES(DevicePlugin): of = cStringIO.StringIO() im.convert('RGB').save(of, 'JPEG') thumb = of.getvalue() + of.close() # Refresh the thumbnail cache if DEBUG: @@ -1105,14 +1162,15 @@ class ITUNES(DevicePlugin): zfw = 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)) + finally: + zfw.close() - return thumb + return thumb - def _create_new_book(self,fpath, metadata, path, db_added, lb_added, thumb): + def _create_new_book(self,fpath, metadata, path, db_added, lb_added, thumb, format): ''' ''' if DEBUG: @@ -1122,6 +1180,7 @@ class ITUNES(DevicePlugin): this_book.db_id = None this_book.device_collections = [] + this_book.format = format this_book.library_id = lb_added this_book.path = path this_book.thumbnail = thumb @@ -1319,10 +1378,11 @@ class ITUNES(DevicePlugin): self.cached_books[cb]['uuid'])) elif iswindows: for cb in self.cached_books.keys(): - self.log.info("%s%-40.40s %-30.30s %s" % + self.log.info("%s%-40.40s %-30.30s %-4.4s %s" % (' '*indent, self.cached_books[cb]['title'], self.cached_books[cb]['author'], + self.cached_books[cb]['format'], self.cached_books[cb]['uuid'])) self.log.info() @@ -1338,8 +1398,9 @@ class ITUNES(DevicePlugin): fnames = zf.namelist() opf = [x for x in fnames if '.opf' in x][0] if opf: - opf_raw = cStringIO.StringIO(zf.read(opf)).getvalue() - soup = BeautifulSoup(opf_raw) + opf_raw = cStringIO.StringIO(zf.read(opf)) + soup = BeautifulSoup(opf_raw.getvalue()) + opf_raw.close() title = soup.find('dc:title').renderContents() author = soup.find('dc:creator').renderContents() ts = soup.find('meta',attrs={'name':'calibre:timestamp'}) @@ -1428,7 +1489,7 @@ class ITUNES(DevicePlugin): hits = dev_books.Search(search['uuid'],self.SearchField.index('All')) if hits: hit = hits[0] - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) return hit # Try by author - there could be multiple hits @@ -1437,9 +1498,25 @@ class ITUNES(DevicePlugin): for hit in hits: if hit.Name == search['title']: if DEBUG: - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) return hit + # PDF metadata was rewritten at export as 'safe(title) - safe(author)' + if search['format'] == 'pdf': + title = re.sub(r'[^0-9a-zA-Z ]', '_', search['title']) + author = re.sub(r'[^0-9a-zA-Z ]', '_', search['author']) + if DEBUG: + self.log.info(" searching by name: '%s - %s'" % (title,author)) + hits = dev_books.Search('%s - %s' % (title,author), + self.SearchField.index('All')) + if hits: + hit = hits[0] + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + return hit + else: + if DEBUG: + self.log.info(" no PDF hits") + attempts -= 1 time.sleep(0.5) if DEBUG: @@ -1496,7 +1573,7 @@ class ITUNES(DevicePlugin): if hits: hit = hits[0] if DEBUG: - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) return hit if DEBUG: @@ -1506,9 +1583,25 @@ class ITUNES(DevicePlugin): for hit in hits: if hit.Name == search['title']: if DEBUG: - self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) return hit + # PDF metadata was rewritten at export as 'safe(title) - safe(author)' + if search['format'] == 'pdf': + title = re.sub(r'[^0-9a-zA-Z ]', '_', search['title']) + author = re.sub(r'[^0-9a-zA-Z ]', '_', search['author']) + if DEBUG: + self.log.info(" searching by name: %s - %s" % (title,author)) + hits = lib_books.Search('%s - %s' % (title,author), + self.SearchField.index('All')) + if hits: + hit = hits[0] + self.log.info(" found '%s' by %s (%s)" % (hit.Name, hit.Artist, hit.Composer)) + return hit + else: + if DEBUG: + self.log.info(" no PDF hits") + attempts -= 1 time.sleep(0.5) if DEBUG: @@ -1523,10 +1616,12 @@ class ITUNES(DevicePlugin): Convert iTunes artwork to thumbnail Cache generated thumbnails cache_dir = os.path.join(config_dir, 'caches', 'itunes') + as of iTunes 9.2, iBooks 1.1, can't set artwork for PDF files via automation ''' archive_path = os.path.join(self.cache_dir, "thumbs.zip") thumb_path = book_path.rpartition('.')[0] + '.jpg' + format = book_path.rpartition('.')[2].lower() try: zfr = ZipFile(archive_path) @@ -1539,77 +1634,99 @@ class ITUNES(DevicePlugin): self.log.info(" ITUNES._generate_thumbnail()") if isosx: - try: - # Resize the cover - data = book.artworks[1].raw_data().data - #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) - thumb = cStringIO.StringIO() - im.convert('RGB').save(thumb,'JPEG') - - # Cache the tagged thumb - if DEBUG: - 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()) + if format == 'epub': try: + if False: + self.log.info(" fetching artwork from %s\n %s" % (book_path,book)) + # Resize the cover + data = book.artworks[1].raw_data().data + #self._dump_hex(data[:256]) + img_data = cStringIO.StringIO(data) + im = PILImage.open(img_data) + scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80) + im = im.resize((int(width),int(height)), PILImage.ANTIALIAS) + img_data.close() + + thumb = cStringIO.StringIO() + im.convert('RGB').save(thumb,'JPEG') + thumb_data = thumb.getvalue() + thumb.close() + + # Cache the tagged thumb + if DEBUG: + self.log.info(" generated thumb for '%s', caching" % book.name()) + zfw.writestr(thumb_path, thumb_data) zfw.close() + return thumb_data except: - pass + self.log.error(" error generating thumb for '%s'" % book.name()) + try: + zfw.close() + except: + pass + return None + else: + if DEBUG: + self.log.info(" unable to generate PDF thumbs") return None elif iswindows: if not book.Artwork.Count: if DEBUG: - self.log.info(" no artwork available") + self.log.info(" no artwork available for '%s'" % book.Name) return None - # Save the cover from iTunes - try: - tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % self.ArtworkFormat[book.Artwork.Item(1).Format]) - book.Artwork.Item(1).SaveArtworkToFile(tmp_thumb) - # Resize the cover - im = PILImage.open(tmp_thumb) - scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80) - im = im.resize((int(width),int(height)), PILImage.ANTIALIAS) - thumb = cStringIO.StringIO() - im.convert('RGB').save(thumb,'JPEG') - os.remove(tmp_thumb) - - # Cache the tagged thumb - if DEBUG: - 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) + if format == 'epub': + # Save the cover from iTunes try: + tmp_thumb = os.path.join(tempfile.gettempdir(), "thumb.%s" % self.ArtworkFormat[book.Artwork.Item(1).Format]) + book.Artwork.Item(1).SaveArtworkToFile(tmp_thumb) + # Resize the cover + im = PILImage.open(tmp_thumb) + scaled, width, height = fit_image(im.size[0],im.size[1], 60, 80) + im = im.resize((int(width),int(height)), PILImage.ANTIALIAS) + thumb = cStringIO.StringIO() + im.convert('RGB').save(thumb,'JPEG') + thumb_data = thmb.getvalue() + os.remove(tmp_thumb) + thumb.close() + + # Cache the tagged thumb + if DEBUG: + self.log.info(" generated thumb for '%s', caching" % book.Name) + zfw.writestr(thumb_path, thumb_data) zfw.close() + return thumb_data except: - pass + self.log.error(" error generating thumb for '%s'" % book.Name) + try: + zfw.close() + except: + pass + return None + else: + if DEBUG: + self.log.info(" unable to generate PDF thumbs") return None def _get_device_book_size(self, file, compressed_size): ''' Calculate the exploded size of file ''' - myZip = ZipFile(file,'r') - myZipList = myZip.infolist() - exploded_file_size = 0 - for file in myZipList: - exploded_file_size += file.file_size - if False: - 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)) - myZip.close() + exploded_file_size = compressed_size + format = file.rpartition('.')[2].lower() + if format == 'epub': + myZip = ZipFile(file,'r') + myZipList = myZip.infolist() + exploded_file_size = 0 + for file in myZipList: + exploded_file_size += file.file_size + if False: + 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)) + myZip.close() return exploded_file_size def _get_device_books(self): @@ -1701,7 +1818,7 @@ class ITUNES(DevicePlugin): self.log.error(" no iPad|Books playlist found") return pl - def _get_fpath(self,file, metadata, update_md=False): + def _get_fpath(self,file, metadata, format, update_md=False): ''' If the database copy will be deleted after upload, we have to use file (the PersistentTemporaryFile), which will be around until @@ -1723,9 +1840,9 @@ class ITUNES(DevicePlugin): else: # Recipe - PTF if DEBUG: - self.log.info(" file will be deleted after upload") + self.log.info(" file will be deleted after upload") - if update_md: + if format == 'epub' and update_md: self._update_epub_metadata(fpath, metadata) return fpath @@ -1950,10 +2067,12 @@ class ITUNES(DevicePlugin): # Read the current storage path for iTunes media from the XML file with open(self.iTunes.LibraryXMLPath, 'r') as xml: - soup = BeautifulSoup(xml.read().decode('utf-8')) - mf = soup.find('key',text="Music Folder").parent - string = mf.findNext('string').renderContents() - media_dir = os.path.abspath(string[len('file://localhost/'):].replace('%20',' ')) + for line in xml: + if line.strip().startswith('Music Folder'): + soup = BeautifulSoup(line) + string = soup.find('string').renderContents() + media_dir = os.path.abspath(string[len('file://localhost/'):].replace('%20',' ')) + break if os.path.exists(media_dir): self.iTunes_media = media_dir else: @@ -2028,7 +2147,9 @@ class ITUNES(DevicePlugin): # Delete existing from Library|Books, add to self.update_list # for deletion from booklist[0] during add_books_to_metadata for book in self.cached_books: - if self.cached_books[book]['uuid'] == metadata.uuid: + if (self.cached_books[book]['uuid'] == metadata.uuid) or \ + (self.cached_books[book]['title'] == metadata.title and \ + self.cached_books[book]['author'] == metadata.authors[0]): self.update_list.append(self.cached_books[book]) self._remove_from_iTunes(self.cached_books[book]) if DEBUG: @@ -2036,7 +2157,7 @@ class ITUNES(DevicePlugin): break else: if DEBUG: - self.log.info(" '%s' not in cached_books" % metadata.title) + self.log.info(" '%s' not found in cached_books" % metadata.title) def _remove_from_device(self, cached_book): ''' @@ -2158,12 +2279,14 @@ class ITUNES(DevicePlugin): fnames = zf_opf.namelist() opf = [x for x in fnames if '.opf' in x][0] if opf: - opf_raw = cStringIO.StringIO(zf_opf.read(opf)).getvalue() - soup = BeautifulSoup(opf_raw) + opf_raw = cStringIO.StringIO(zf_opf.read(opf)) + soup = BeautifulSoup(opf_raw.getvalue()) + opf_raw.close() + + # Touch existing calibre timestamp md = soup.find('metadata') ts = md.find('meta',attrs={'name':'calibre:timestamp'}) if ts: - # Touch existing calibre timestamp timestamp = ts['content'] old_ts = parse_date(timestamp) metadata.timestamp = datetime.datetime(old_ts.year, old_ts.month, old_ts.day, old_ts.hour, @@ -2172,6 +2295,15 @@ class ITUNES(DevicePlugin): metadata.timestamp = isoformat(now()) if DEBUG: self.log.info(" add timestamp: %s" % metadata.timestamp) + + # Fix the language declaration for iBooks 1.1 + patched_language = 'en-US' + language = md.find('dc:language') + if language: + self.log.info(" changing from '%s' to '%s'" % + (language.renderContents(),patched_language)) + metadata.language = patched_language + zf_opf.close() # If 'News' in tags, tweak the title/author for friendlier display in iBooks @@ -2257,6 +2389,9 @@ class ITUNES(DevicePlugin): lb_added.enabled.set(True) lb_added.sort_artist.set(metadata.author_sort.title()) lb_added.sort_name.set(this_book.title_sorter) + if this_book.format == 'pdf': + lb_added.artist.set(metadata.authors[0]) + lb_added.name.set(metadata.title) if db_added: db_added.album.set(metadata.title) @@ -2265,6 +2400,9 @@ class ITUNES(DevicePlugin): db_added.enabled.set(True) db_added.sort_artist.set(metadata.author_sort.title()) db_added.sort_name.set(this_book.title_sorter) + if this_book.format == 'pdf': + db_added.artist.set(metadata.authors[0]) + db_added.name.set(metadata.title) if metadata.comments: if lb_added: @@ -2284,7 +2422,9 @@ class ITUNES(DevicePlugin): # Set genre from series if available, else first alpha tag # Otherwise iTunes grabs the first dc:subject from the opf metadata - if self.use_series_data and metadata.series: + if self.use_series_as_category and metadata.series: + if DEBUG: + self.log.info(" using Series name as Genre") if lb_added: lb_added.sort_name.set("%s %03d" % (metadata.series, metadata.series_index)) lb_added.genre.set(metadata.series) @@ -2298,6 +2438,8 @@ class ITUNES(DevicePlugin): db_added.episode_number.set(metadata.series_index) elif metadata.tags: + if DEBUG: + self.log.info(" using Tag as Genre") for tag in metadata.tags: if self._is_alpha(tag[0]): if lb_added: @@ -2314,6 +2456,9 @@ class ITUNES(DevicePlugin): lb_added.Enabled = True lb_added.SortArtist = (metadata.author_sort.title()) lb_added.SortName = (this_book.title_sorter) + if this_book.format == 'pdf': + lb_added.Artist = metadata.authors[0] + lb_added.Name = metadata.title if db_added: db_added.Album = metadata.title @@ -2322,6 +2467,9 @@ class ITUNES(DevicePlugin): db_added.Enabled = True db_added.SortArtist = (metadata.author_sort.title()) db_added.SortName = (this_book.title_sorter) + if this_book.format == 'pdf': + db_added.Artist = metadata.authors[0] + db_added.Name = metadata.title if metadata.comments: if lb_added: @@ -2345,7 +2493,9 @@ class ITUNES(DevicePlugin): # Otherwise iBooks uses first from opf # iTunes balks on setting EpisodeNumber, but it sticks (9.1.1.12) - if self.use_series_data and metadata.series: + if self.use_series_as_category and metadata.series: + if DEBUG: + self.log.info(" using Series name as Genre") if lb_added: lb_added.SortName = "%s %03d" % (metadata.series, metadata.series_index) lb_added.Genre = metadata.series @@ -2365,6 +2515,8 @@ class ITUNES(DevicePlugin): self.log.warning(" iTunes automation interface reported an error" " setting EpisodeNumber on iDevice") elif metadata.tags: + if DEBUG: + self.log.info(" using Tag as Genre") for tag in metadata.tags: if self._is_alpha(tag[0]): if lb_added: diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py index d6070efade..4560c608a5 100644 --- a/src/calibre/ebooks/oeb/stylizer.py +++ b/src/calibre/ebooks/oeb/stylizer.py @@ -127,15 +127,12 @@ class Stylizer(object): else: head = [] - # GwR : Add webkit profile to cssutils before validating - if True: - wk_macros = { - 'border-width': '{length}|thin|medium|thick' - } - wk_props = { - '-webkit-border-radius': r'{border-width}(\s+{border-width}){0,3}|inherit' - } - cssutils.profile.addProfile('webkit', wk_props, wk_macros) + # Add optional cssutils parsing profile from output_profile + if hasattr(self.opts.output_profile, 'cssutils_addProfile'): + profile = self.opts.output_profile.cssutils_addProfile + cssutils.profile.addProfile(profile['name'], + profile['props'], + profile['macros']) parser = cssutils.CSSParser(fetcher=self._fetch_css_file, log=logging.getLogger('calibre.css')) diff --git a/src/calibre/gui2/actions.py b/src/calibre/gui2/actions.py index f838e9c1fe..a3f8442200 100644 --- a/src/calibre/gui2/actions.py +++ b/src/calibre/gui2/actions.py @@ -176,7 +176,8 @@ class AnnotationsAction(object): # {{{ def mark_book_as_read(self,id): read_tag = gprefs.get('catalog_epub_mobi_read_tag') - self.db.set_tags(id, [read_tag], append=True) + if read_tag: + self.db.set_tags(id, [read_tag], append=True) def canceled(self): self.pd.hide() diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 29b581c361..78058a95ec 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -280,46 +280,6 @@ class BasicNewsRecipe(Recipe): } ''' - #: The CSS that is used to style the touchscreen elements, i.e., the navigation bars and - #: the Feed summaries. - touchscreen_css = u''' - .article_navbar { - -webkit-border-radius:4px; - background-color:#eee; - border:1px solid #888; - margin-left: 5%; - margin-right: 5%; - width: 90%; - } - - .feed_navbar { - -webkit-border-radius:4px; - background-color:#eee; - border:1px solid #888; - margin-left: 5%; - margin-right: 5%; - width: 90%; - } - - .summary_headline { - font-weight:bold; text-align:left; - } - - .summary_byline { - text-align:left; - font-family:monospace; - } - - .summary_text { - text-align:left; - } - - .feed { - font-family:sans-serif; font-weight:bold; font-size:larger; - } - ''' - - #: By default, calibre will use a default image for the masthead (Kindle only). #: Override this in your recipe to provide a url to use as a masthead. masthead_url = None @@ -625,6 +585,8 @@ class BasicNewsRecipe(Recipe): self.lrf = options.lrf self.output_profile = options.output_profile self.touchscreen = getattr(self.output_profile, 'touchscreen', False) + if self.touchscreen and getattr(self.output_profile, 'touchscreen_css',False): + self.extra_css += self.output_profile.touchscreen_css self.output_dir = os.path.abspath(self.output_dir) if options.test: @@ -678,10 +640,8 @@ class BasicNewsRecipe(Recipe): if self.delay > 0: self.simultaneous_downloads = 1 - if self.touchscreen: - self.extra_css += self.touchscreen_css - - self.navbar = templates.TouchscreenNavBarTemplate() if self.touchscreen else templates.NavBarTemplate() + self.navbar = templates.TouchscreenNavBarTemplate() if self.touchscreen else \ + templates.NavBarTemplate() self.failed_downloads = [] self.partial_failures = [] @@ -768,7 +728,8 @@ class BasicNewsRecipe(Recipe): timefmt = self.timefmt if self.touchscreen: templ = templates.TouchscreenIndexTemplate() - timefmt = '%A, %d %b %Y' + if getattr(self.output_profile,'timefmt',False): + timefmt = self.output_profile.timefmt return templ.generate(self.title, "mastheadImage.jpg", timefmt, feeds, extra_css=css).render(doctype='xhtml') diff --git a/src/calibre/web/feeds/templates.py b/src/calibre/web/feeds/templates.py index 5b4704e766..26d4cbdc9d 100644 --- a/src/calibre/web/feeds/templates.py +++ b/src/calibre/web/feeds/templates.py @@ -3,6 +3,9 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' + +import copy + from lxml import html, etree from lxml.html.builder import HTML, HEAD, TITLE, STYLE, DIV, BODY, \ STRONG, EM, BR, SPAN, A, HR, UL, LI, H2, IMG, P as PT, \ @@ -73,6 +76,7 @@ class EmbeddedContent(Template): class IndexTemplate(Template): def _generate(self, title, masthead, datefmt, feeds, extra_css=None, style=None): + self.IS_HTML = False if isinstance(datefmt, unicode): datefmt = datefmt.encode(preferred_encoding) date = strftime(datefmt) @@ -198,6 +202,9 @@ class NavBarTemplate(Template): class TouchscreenIndexTemplate(Template): def _generate(self, title, masthead, datefmt, feeds, extra_css=None, style=None): + + self.IS_HTML = False + if isinstance(datefmt, unicode): datefmt = datefmt.encode(preferred_encoding) date = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y')) @@ -238,6 +245,8 @@ class TouchscreenFeedTemplate(Template): tokens = title.split(' ') new_title_tokens = [] new_title_len = 0 + if len(tokens[0]) > clip: + return tokens[0][:clip] + '...' for token in tokens: if len(token) + new_title_len < clip: new_title_tokens.append(token) @@ -248,29 +257,37 @@ class TouchscreenFeedTemplate(Template): break return title + self.IS_HTML = False feed = feeds[f] # Construct the navbar - navbar_t = TABLE(CLASS('feed_navbar')) + navbar_t = TABLE(CLASS('touchscreen_navbar')) navbar_tr = TR() + # Previous Section link = '' if f > 0: - link = A(EM( '< ' + trim_title(feeds[f-1].title)), + link = A(CLASS('feed_link'), + trim_title(feeds[f-1].title), href = '../feed_%d/index.html' % int(f-1)) navbar_tr.append(TD(link, width="40%", align="center")) + # Up to Sections link = A(STRONG('Sections'), href="../index.html") navbar_tr.append(TD(link,width="20%",align="center")) + # Next Section link = '' if f < len(feeds)-1: - link = A(EM(trim_title(feeds[f+1].title) + ' >'), + link = A(CLASS('feed_link'), + trim_title(feeds[f+1].title), href = '../feed_%d/index.html' % int(f+1)) - navbar_tr.append(TD(link, width="40%", align="center")) - + navbar_tr.append(TD(link, width="40%", align="center", )) navbar_t.append(navbar_tr) - navbar = navbar_t + top_navbar = navbar_t + bottom_navbar = copy.copy(navbar_t) + #print "\n%s\n" % etree.tostring(navbar_t, pretty_print=True) + # Build the page head = HEAD(TITLE(feed.title)) @@ -280,8 +297,8 @@ class TouchscreenFeedTemplate(Template): head.append(STYLE(extra_css, type='text/css')) body = BODY(style='page-break-before:always') div = DIV( - H2(feed.title, CLASS('calibre_feed_title', 'calibre_rescale_160')), - DIV(style="border-top:1px solid gray;border-bottom:1em solid white") + top_navbar, + H2(feed.title, CLASS('feed_title')) ) body.append(div) @@ -317,9 +334,8 @@ class TouchscreenFeedTemplate(Template): toc.append(tr) div.append(toc) - #div.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) div.append(BR()) - div.append(navbar) + div.append(bottom_navbar) self.root = HTML(head, body) class TouchscreenNavBarTemplate(Template): @@ -334,24 +350,23 @@ class TouchscreenNavBarTemplate(Template): head.append(STYLE(extra_css, type='text/css')) navbar = DIV() - if bottom: - navbar.append(DIV(style="border-top:1px solid gray;border-bottom:1em solid white")) - - navbar_t = TABLE(CLASS('article_navbar')) + navbar_t = TABLE(CLASS('touchscreen_navbar')) navbar_tr = TR() + # | Previous if art > 0: href = '%s../article_%d/index.html'%(prefix, art-1) - navbar_tr.append(TD(A(EM('< Previous'), href=href), width="32%", align="center")) + navbar_tr.append(TD(A(EM('Previous'),href=href), + width="32%")) else: - navbar_tr.append(TD('', width="25%")) + navbar_tr.append(TD('', width="32%")) # | Articles | Sections | href = '%s../index.html#article_%d'%(prefix, art) - navbar_tr.append(TD(A(STRONG('Articles'), href=href),width="18%", align="center")) + navbar_tr.append(TD(A(STRONG('Articles'), href=href),width="18%")) href = '%s../../index.html#feed_%d'%(prefix, feed) - navbar_tr.append(TD(A(STRONG('Sections'), href=href),width="18%", align="center")) + navbar_tr.append(TD(A(STRONG('Sections'), href=href),width="18%")) # | Next next = 'feed_%d'%(feed+1) if art == number_of_articles_in_feed - 1 \ @@ -359,7 +374,8 @@ class TouchscreenNavBarTemplate(Template): up = '../..' if art == number_of_articles_in_feed - 1 else '..' href = '%s%s/%s/index.html'%(prefix, up, next) - navbar_tr.append(TD(A(EM('Next >'), href=href),width="32%", align="center")) + navbar_tr.append(TD(A(EM('Next'),href=href), + width="32%")) navbar_t.append(navbar_tr) navbar.append(navbar_t) #print "\n%s\n" % etree.tostring(navbar, pretty_print=True) From 1a9a74b849765d879fbde6c3d9fbe163add805e0 Mon Sep 17 00:00:00 2001 From: GRiker Date: Tue, 22 Jun 2010 11:43:49 -0600 Subject: [PATCH 6/8] GwR wip --- resources/default_tweaks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index d2f2667222..b9f58178f7 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -77,4 +77,4 @@ title_series_sorting = 'library_order' # If set to 'True', a Book's Series name will be used as the Genre # If set to 'False', the book's first tag beginning with an alpha character will # be used as the Genre -ITUNES_use_series_as_category = True +ITUNES_use_series_as_category = False From 40718a915a1aa3cb695a6401d10a0fe65ae65131 Mon Sep 17 00:00:00 2001 From: GRiker Date: Tue, 22 Jun 2010 11:44:44 -0600 Subject: [PATCH 7/8] GwR wip --- resources/default_tweaks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index b9f58178f7..ac5ba8e898 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -74,7 +74,7 @@ title_series_sorting = 'library_order' # Apple iTunes/iDevice # Control whether Series name is used as Genre in iTunes/iBooks -# If set to 'True', a Book's Series name will be used as the Genre +# If set to 'True', a Book's Series name (if one exists) will be used as the Genre # If set to 'False', the book's first tag beginning with an alpha character will # be used as the Genre ITUNES_use_series_as_category = False From 60cb1c2fbe167b4ac03b4b60144e00993ce247ac Mon Sep 17 00:00:00 2001 From: GRiker Date: Tue, 22 Jun 2010 16:15:10 -0600 Subject: [PATCH 8/8] GwR revisions wip --- resources/default_tweaks.py | 7 -- src/calibre/devices/apple/driver.py | 104 ++++++++++++++++++++++++--- src/calibre/devices/kindle/driver.py | 1 + 3 files changed, 97 insertions(+), 15 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index ac5ba8e898..bda839b28f 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -71,10 +71,3 @@ gui_pubdate_display_format = 'MMM yyyy' # order until the title is edited. Double-clicking on a title and hitting return # without changing anything is sufficient to change the sort. title_series_sorting = 'library_order' - -# Apple iTunes/iDevice -# Control whether Series name is used as Genre in iTunes/iBooks -# If set to 'True', a Book's Series name (if one exists) will be used as the Genre -# If set to 'False', the book's first tag beginning with an alpha character will -# be used as the Genre -ITUNES_use_series_as_category = False diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 91cfba98f1..3b37289dab 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -10,12 +10,13 @@ from calibre.constants import __appname__, __version__, DEBUG from calibre import fit_image from calibre.constants import isosx, iswindows from calibre.devices.errors import UserFeedback +from calibre.devices.usbms.deviceconfig import DeviceConfig from calibre.devices.interface import DevicePlugin from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata.epub import set_metadata from calibre.library.server.utils import strftime -from calibre.utils.config import Config, config_dir, tweaks +from calibre.utils.config import Config, ConfigProxy, config_dir from calibre.utils.date import isoformat, now, parse_date from calibre.utils.logging import Log from calibre.utils.zipfile import ZipFile @@ -34,7 +35,7 @@ if iswindows: import pythoncom, win32com.client -class ITUNES(DevicePlugin): +class ITUNES(DeviceConfig, DevicePlugin): ''' Calling sequences: Initialization: @@ -85,6 +86,15 @@ class ITUNES(DevicePlugin): FORMATS = ['epub','pdf'] + # Configuration + HELP_MESSAGE = _('Configure Device') + EXTRA_CUSTOMIZATION_MESSAGE = None + EXTRA_CUSTOMIZATION_DEFAULT = None + MUST_READ_METADATA = False + SAVE_TEMPLATE = '{title}' + SUPPORTS_SUB_DIRS = False + SUPPORTS_USE_AUTHOR_SORT = False + # Product IDs: # 0x1292:iPhone 3G # 0x129a:iPad @@ -163,7 +173,7 @@ class ITUNES(DevicePlugin): sources = None update_msg = None update_needed = False - use_series_as_category = tweaks['ITUNES_use_series_as_category'] + use_series_as_category = False # Public methods def add_books_to_metadata(self, locations, metadata, booklists): @@ -512,6 +522,29 @@ class ITUNES(DevicePlugin): ''' return (None,None) + def config_widget(self): + ''' + Return a QWidget with settings for the device interface + ''' + if DEBUG: + self.log.info("ITUNES.config_widget()") + from calibre.gui2.device_drivers.configwidget import ConfigWidget + cw = ConfigWidget(self.settings(), self.FORMATS, self.SUPPORTS_SUB_DIRS, + self.MUST_READ_METADATA, self.SUPPORTS_USE_AUTHOR_SORT, + self.EXTRA_CUSTOMIZATION_MESSAGE) + # Turn off the Save template + cw.opt_save_template.setVisible(False) + cw.label.setVisible(False) + + # Repurpose the checkbox + cw.opt_read_metadata.setText("Use Series as Genre in iTunes/iBooks") + return cw + + def customization_help(self,gui=False): + if DEBUG: + self.log.info("ITUNES.customization_help()") + return _('Configure Device') + def delete_books(self, paths, end_session=True): ''' Delete books at paths on device. @@ -741,16 +774,45 @@ class ITUNES(DevicePlugin): ''' self.report_progress = report_progress + def save_settings(self, settings_widget): + ''' + Should save settings to disk. Takes the widget created in config_widget + and saves all settings to disk. + ''' + if DEBUG: + self.log.info("ITUNES.save_settings()") + proxy = self._configProxy() + proxy['format_map'] = settings_widget.format_map() + if self.SUPPORTS_SUB_DIRS: + proxy['use_subdirs'] = settings_widget.use_subdirs() + if not self.MUST_READ_METADATA: + proxy['read_metadata'] = settings_widget.read_metadata() + if self.SUPPORTS_USE_AUTHOR_SORT: + proxy['use_author_sort'] = settings_widget.use_author_sort() + if self.EXTRA_CUSTOMIZATION_MESSAGE: + ec = unicode(settings_widget.opt_extra_customization.text()).strip() + if not ec: + ec = None + proxy['extra_customization'] = ec + st = unicode(settings_widget.opt_save_template.text()) + proxy['save_template'] = st + + # Snag the read_metadata check box contents on the way by + self.use_series_as_category = settings_widget.read_metadata() + def settings(self): ''' Should return an opts object. The opts object should have one attribute `format_map` which is an ordered list of formats for the device. ''' - 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, - help=_('Ordered list of formats the device will accept')) - return c.parse() + if DEBUG: + self.log.info("ITUNES.settings()") + opts = self._config().parse() + + # Repurpose the read_metadata check box + self.use_series_as_category = opts.read_metadata + + return opts def sync_booklists(self, booklists, end_session=True): ''' @@ -1067,6 +1129,27 @@ class ITUNES(DevicePlugin): return db_added, lb_added + def _config(self): + 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, + help=_('Ordered list of formats the device will accept')) + c.add_opt('use_subdirs', default=True, + help=_('Place files in sub directories if the device supports them')) + c.add_opt('read_metadata', default=True, + help=_('Use Series as Genre in iTunes/iBooks')) + c.add_opt('use_author_sort', default=False, + help=_('Use author sort instead of author')) + c.add_opt('save_template', default=self._default_save_template(), + help=_('Template to control how books are titled in iTunes/iBooks')) + c.add_opt('extra_customization', + default=self.EXTRA_CUSTOMIZATION_DEFAULT, + help=_('Extra customization')) + return c + + def _configProxy(self): + return ConfigProxy(self._config()) + def _cover_to_thumb(self, path, metadata, db_added, lb_added, format): ''' assumes pythoncom wrapper for db_added @@ -1217,6 +1300,11 @@ class ITUNES(DevicePlugin): return this_book + def _default_save_template(self): + from calibre.library.save_to_disk import config + return self.SAVE_TEMPLATE if self.SAVE_TEMPLATE else \ + config().parse().send_template + def _delete_iTunesMetadata_plist(self,fpath): ''' Delete the plist file from the file to force recache diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py index cd56d210e1..c3e7bb190d 100644 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -429,6 +429,7 @@ class Bookmark(): entries, = unpack('>I', data[9:13]) current_entry = 0 e_base = 0x0d + self.pdf_page_offset = 0 while current_entry < entries: ''' location, = unpack('>I', data[e_base+2:e_base+6])