From f89d0efa1f216018aeab84be2be53ab15012e41a Mon Sep 17 00:00:00 2001 From: John Schember Date: Mon, 4 Apr 2011 19:47:59 -0400 Subject: [PATCH 01/19] HTMLZ Output: Use urldefrag instead of doing it ourself. --- src/calibre/ebooks/htmlz/oeb2html.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/calibre/ebooks/htmlz/oeb2html.py b/src/calibre/ebooks/htmlz/oeb2html.py index 827e57b932..af5867356a 100644 --- a/src/calibre/ebooks/htmlz/oeb2html.py +++ b/src/calibre/ebooks/htmlz/oeb2html.py @@ -12,7 +12,7 @@ Transform OEB content into a single (more or less) HTML file. import os -from urlparse import urlparse +from urlparse import urlparse, urldefrag from calibre import prepare_string_for_xml from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace @@ -70,9 +70,7 @@ class OEB2HTML(object): if tag == 'a': href = page.abshref(attribs['href']) if self.url_is_relative(href): - id = '' - if '#' in href: - href, n, id = href.partition('#') + href, id = urldefrag(href) href = '#%s' % self.get_link_id(href, id) attribs['href'] = href return attribs From 265eabf1a613fcdc3651631fd3f9589bf7d4e7be Mon Sep 17 00:00:00 2001 From: John Schember Date: Tue, 5 Apr 2011 21:59:11 -0400 Subject: [PATCH 02/19] HTMLZ Output: Rewrite links via oeb.base.rewrite_links function. --- src/calibre/ebooks/htmlz/oeb2html.py | 112 +++++++++++++++------------ 1 file changed, 64 insertions(+), 48 deletions(-) diff --git a/src/calibre/ebooks/htmlz/oeb2html.py b/src/calibre/ebooks/htmlz/oeb2html.py index af5867356a..7d915bcfcb 100644 --- a/src/calibre/ebooks/htmlz/oeb2html.py +++ b/src/calibre/ebooks/htmlz/oeb2html.py @@ -12,10 +12,13 @@ Transform OEB content into a single (more or less) HTML file. import os -from urlparse import urlparse, urldefrag +from functools import partial +from lxml import html +from urlparse import urldefrag from calibre import prepare_string_for_xml -from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace +from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace,\ + OEB_IMAGES, XLINK, rewrite_links from calibre.ebooks.oeb.stylizer import Stylizer from calibre.utils.logging import default_log @@ -40,6 +43,8 @@ class OEB2HTML(object): self.opts = opts self.links = {} self.images = {} + self.base_hrefs = [item.href for item in oeb_book.spine] + self.map_resources(oeb_book) return self.mlize_spine(oeb_book) @@ -47,6 +52,8 @@ class OEB2HTML(object): output = [u''] for item in oeb_book.spine: self.log.debug('Converting %s to HTML...' % item.href) + self.rewrite_ids(item.data, item) + rewrite_links(item.data, partial(self.rewrite_link, page=item)) stylizer = Stylizer(item.data, item.href, oeb_book, self.opts) output += self.dump_text(item.data.find(XHTML('body')), stylizer, item) output.append('\n\n') @@ -56,41 +63,61 @@ class OEB2HTML(object): def dump_text(self, elem, stylizer, page): raise NotImplementedError - def get_link_id(self, href, aid): - aid = '%s#%s' % (href, aid) - if aid not in self.links: - self.links[aid] = 'calibre_link-%s' % len(self.links.keys()) - return self.links[aid] + def get_link_id(self, href, id=''): + if id: + href += '#%s' % id + if href not in self.links: + self.links[href] = '#calibre_link-%s' % len(self.links.keys()) + return self.links[href] - def rewrite_link(self, tag, attribs, page): - # Rewrite ids. - if 'id' in attribs: - attribs['id'] = self.get_link_id(page.href, attribs['id']) - # Rewrite links. - if tag == 'a': - href = page.abshref(attribs['href']) - if self.url_is_relative(href): - href, id = urldefrag(href) - href = '#%s' % self.get_link_id(href, id) - attribs['href'] = href - return attribs - - def rewrite_image(self, tag, attribs, page): - if tag == 'img': - src = attribs.get('src', None) - if src: - src = page.abshref(src) - if src not in self.images: - ext = os.path.splitext(src)[1] + def map_resources(self, oeb_book): + for item in oeb_book.manifest: + if item.media_type in OEB_IMAGES: + if item.href not in self.images: + ext = os.path.splitext(item.href)[1] fname = '%s%s' % (len(self.images), ext) fname = fname.zfill(10) - self.images[src] = fname - attribs['src'] = 'images/%s' % self.images[src] - return attribs - - def url_is_relative(self, url): - o = urlparse(url) - return False if o.scheme else True + self.images[item.href] = fname + if item in oeb_book.spine: + self.get_link_id(item.href) + root = item.data.find(XHTML('body')) + link_attrs = set(html.defs.link_attrs) + link_attrs.add(XLINK('href')) + for el in root.iter(): + attribs = el.attrib + try: + if not isinstance(el.tag, basestring): + continue + except UnicodeDecodeError: + continue + for attr in attribs: + if attr in link_attrs: + href = item.abshref(attribs[attr]) + href, id = urldefrag(href) + if href in self.base_hrefs: + self.get_link_id(href, id) + + def rewrite_link(self, url, page=None): + if not page: + return url + abs_url = page.abshref(url) + if abs_url in self.images: + return 'images/%s' % self.images[abs_url] + if abs_url in self.links: + return self.links[abs_url] + return url + + def rewrite_ids(self, root, page): + for el in root.iter(): + try: + tag = el.tag + except UnicodeDecodeError: + continue + if tag == XHTML('body'): + el.attrib['id'] = self.get_link_id(page.href)[1:] + continue + if 'id' in el.attrib: + el.attrib['id'] = self.get_link_id(page.href, el.attrib['id'])[1:] def get_css(self, oeb_book): css = u'' @@ -127,13 +154,9 @@ class OEB2HTMLNoCSSizer(OEB2HTML): tags = [] tag = barename(elem.tag) attribs = elem.attrib - - attribs = self.rewrite_link(tag, attribs, page) - attribs = self.rewrite_image(tag, attribs, page) - + if tag == 'body': tag = 'div' - attribs['id'] = self.get_link_id(page.href, '') tags.append(tag) # Ignore anything that is set to not be displayed. @@ -215,14 +238,10 @@ class OEB2HTMLInlineCSSizer(OEB2HTML): tags = [] tag = barename(elem.tag) attribs = elem.attrib - - attribs = self.rewrite_link(tag, attribs, page) - attribs = self.rewrite_image(tag, attribs, page) style_a = '%s' % style if tag == 'body': tag = 'div' - attribs['id'] = self.get_link_id(page.href, '') if not style['page-break-before'] == 'always': style_a = 'page-break-before: always;' + ' ' if style_a else '' + style_a tags.append(tag) @@ -277,6 +296,8 @@ class OEB2HTMLClassCSSizer(OEB2HTML): output = [] for item in oeb_book.spine: self.log.debug('Converting %s to HTML...' % item.href) + self.rewrite_ids(item.data, item) + rewrite_links(item.data, partial(self.rewrite_link, page=item)) stylizer = Stylizer(item.data, item.href, oeb_book, self.opts) output += self.dump_text(item.data.find(XHTML('body')), stylizer, item) output.append('\n\n') @@ -304,17 +325,12 @@ class OEB2HTMLClassCSSizer(OEB2HTML): # Setup our variables. text = [''] - #style = stylizer.style(elem) tags = [] tag = barename(elem.tag) attribs = elem.attrib - attribs = self.rewrite_link(tag, attribs, page) - attribs = self.rewrite_image(tag, attribs, page) - if tag == 'body': tag = 'div' - attribs['id'] = self.get_link_id(page.href, '') tags.append(tag) # Remove attributes we won't want. From 739609210ef60dc4d0bb15fa0253d0c1b7940081 Mon Sep 17 00:00:00 2001 From: John Schember Date: Tue, 5 Apr 2011 22:12:50 -0400 Subject: [PATCH 03/19] ... --- src/calibre/ebooks/htmlz/oeb2html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/ebooks/htmlz/oeb2html.py b/src/calibre/ebooks/htmlz/oeb2html.py index 7d915bcfcb..b8a6362a99 100644 --- a/src/calibre/ebooks/htmlz/oeb2html.py +++ b/src/calibre/ebooks/htmlz/oeb2html.py @@ -88,7 +88,7 @@ class OEB2HTML(object): try: if not isinstance(el.tag, basestring): continue - except UnicodeDecodeError: + except: continue for attr in attribs: if attr in link_attrs: From 049776de273cd8bb77fd81887cad0eeb008bc930 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 08:24:25 -0600 Subject: [PATCH 04/19] ... --- src/calibre/manual/faq.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 97ef32e9d4..f48fa9dc16 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -99,7 +99,8 @@ We just need some information from you: device. Once you send us the output for a particular operating system, support for the device in that operating system -will appear in the next release of |app|. +will appear in the next release of |app|. To send us the output, open a bug report and attach the output to it. +See `http://calibre-ebook.com/bugs`_. My device is not being detected by |app|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 593f3aaf0a6f08bbab384a66d0f4af9bf074d397 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 08:44:50 -0600 Subject: [PATCH 05/19] Support for Motorola Atrix --- src/calibre/devices/android/driver.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 54e4979524..7702a7caf0 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -36,7 +36,9 @@ class ANDROID(USBMS): # Motorola 0x22b8 : { 0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100], 0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216], - 0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216] }, + 0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216], + 0x7086 : [0x0226], + }, # Sony Ericsson 0xfce : { 0xd12e : [0x0100]}, @@ -101,7 +103,8 @@ class ANDROID(USBMS): 'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE', 'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H', 'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD', - '7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2'] + '7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2', + 'MB860'] WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'A70S', 'A101IT', '7'] From 504ef950568ab8fcdd0b04c7af5de78ffd4ab0a1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 10:03:46 -0600 Subject: [PATCH 06/19] When dealing with ZIP/RAR archives, use the file header rather than the file extension to detrmine the file type, when possible. This fixes the common case of CBZ files being actually cbr files and vice versa --- src/calibre/__init__.py | 21 ++++++++++++++++----- src/calibre/customize/builtins.py | 8 ++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index 1799072045..2f457bf2bc 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -217,14 +217,25 @@ def filename_to_utf8(name): return name.decode(codec, 'replace').encode('utf8') def extract(path, dir): - ext = os.path.splitext(path)[1][1:].lower() extractor = None - if ext in ['zip', 'cbz', 'epub', 'oebzip']: - from calibre.libunzip import extract as zipextract - extractor = zipextract - elif ext in ['cbr', 'rar']: + # First use the file header to identify its type + with open(path, 'rb') as f: + id_ = f.read(3) + if id_ == b'Rar': from calibre.libunrar import extract as rarextract extractor = rarextract + elif id_.startswith(b'PK'): + from calibre.libunzip import extract as zipextract + extractor = zipextract + if extractor is None: + # Fallback to file extension + ext = os.path.splitext(path)[1][1:].lower() + if ext in ['zip', 'cbz', 'epub', 'oebzip']: + from calibre.libunzip import extract as zipextract + extractor = zipextract + elif ext in ['cbr', 'rar']: + from calibre.libunrar import extract as rarextract + extractor = rarextract if extractor is None: raise Exception('Unknown archive type') extractor(path, dir) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 1e40a8e5ff..91abfacc95 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -166,6 +166,14 @@ class ComicMetadataReader(MetadataReaderPlugin): description = _('Extract cover from comic files') def get_metadata(self, stream, ftype): + if hasattr(stream, 'seek') and hasattr(stream, 'tell'): + pos = stream.tell() + id_ = stream.read(3) + stream.seek(pos) + if id_ == b'Rar': + ftype = 'cbr' + elif id.startswith(b'PK'): + ftype = 'cbz' if ftype == 'cbr': from calibre.libunrar import extract_first_alphabetically as extract_first extract_first From ddf6bd19f557a8f546422da0c10c667dd623fc18 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 10:50:55 -0600 Subject: [PATCH 07/19] Add a 'plugin tweak' test_eight_code which if set to True will cause calibre to use code intended for the 0.8.x series. Note that this code is in heavy development so only set this tweak if you are OK with having parts of calibre broken. --- src/calibre/customize/builtins.py | 26 ++++++++++++++++---------- src/calibre/utils/config.py | 1 + 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 91abfacc95..93cdfe50d9 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -10,6 +10,7 @@ from calibre.constants import numeric_version from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.oeb.base import OEB_IMAGES +from calibre.utils.config import test_eight_code # To archive plugins {{{ class HTML2ZIP(FileTypePlugin): @@ -612,20 +613,25 @@ from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG from calibre.devices.kobo.driver import KOBO from calibre.devices.bambook.driver import BAMBOOK -from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon, \ - KentDistrictLibrary -from calibre.ebooks.metadata.douban import DoubanBooks -from calibre.ebooks.metadata.nicebooks import NiceBooks, NiceBooksCovers -from calibre.ebooks.metadata.covers import OpenLibraryCovers, \ - AmazonCovers, DoubanCovers from calibre.library.catalog import CSV_XML, EPUB_MOBI, BIBTEX from calibre.ebooks.epub.fix.unmanifested import Unmanifested from calibre.ebooks.epub.fix.epubcheck import Epubcheck -plugins = [HTML2ZIP, PML2PMLZ, TXT2TXTZ, ArchiveExtract, GoogleBooks, ISBNDB, Amazon, - KentDistrictLibrary, DoubanBooks, NiceBooks, CSV_XML, EPUB_MOBI, BIBTEX, Unmanifested, - Epubcheck, OpenLibraryCovers, AmazonCovers, DoubanCovers, - NiceBooksCovers] +plugins = [HTML2ZIP, PML2PMLZ, TXT2TXTZ, ArchiveExtract, CSV_XML, EPUB_MOBI, BIBTEX, Unmanifested, + Epubcheck, ] + +if not test_eight_code: + from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon, \ + KentDistrictLibrary + from calibre.ebooks.metadata.douban import DoubanBooks + from calibre.ebooks.metadata.nicebooks import NiceBooks, NiceBooksCovers + from calibre.ebooks.metadata.covers import OpenLibraryCovers, \ + AmazonCovers, DoubanCovers + + plugins += [GoogleBooks, ISBNDB, Amazon, + OpenLibraryCovers, AmazonCovers, DoubanCovers, + NiceBooksCovers, KentDistrictLibrary, DoubanBooks, NiceBooks] + plugins += [ ComicInput, EPUBInput, diff --git a/src/calibre/utils/config.py b/src/calibre/utils/config.py index d5a489acf1..66316d051b 100644 --- a/src/calibre/utils/config.py +++ b/src/calibre/utils/config.py @@ -784,6 +784,7 @@ def write_tweaks(raw): tweaks = read_tweaks() +test_eight_code = tweaks.get('test_eight_code', False) def migrate(): if hasattr(os, 'geteuid') and os.geteuid() == 0: From 261df5b15d1bb9636f2adf6fb982708fb3c35f91 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 11:09:50 -0600 Subject: [PATCH 08/19] Use test_eight_code in fetch-ebook-metadata --- src/calibre/customize/builtins.py | 18 ++++++++++-------- src/calibre/ebooks/metadata/sources/cli.py | 9 ++++++++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 93cdfe50d9..298799daa5 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -620,7 +620,16 @@ from calibre.ebooks.epub.fix.epubcheck import Epubcheck plugins = [HTML2ZIP, PML2PMLZ, TXT2TXTZ, ArchiveExtract, CSV_XML, EPUB_MOBI, BIBTEX, Unmanifested, Epubcheck, ] -if not test_eight_code: +if test_eight_code: +# New metadata download plugins {{{ + from calibre.ebooks.metadata.sources.google import GoogleBooks + from calibre.ebooks.metadata.sources.amazon import Amazon + from calibre.ebooks.metadata.sources.openlibrary import OpenLibrary + + plugins += [GoogleBooks, Amazon, OpenLibrary] + +# }}} +else: from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon, \ KentDistrictLibrary from calibre.ebooks.metadata.douban import DoubanBooks @@ -1069,11 +1078,4 @@ plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions, #}}} -# New metadata download plugins {{{ -from calibre.ebooks.metadata.sources.google import GoogleBooks -from calibre.ebooks.metadata.sources.amazon import Amazon -from calibre.ebooks.metadata.sources.openlibrary import OpenLibrary -plugins += [GoogleBooks, Amazon, OpenLibrary] - -# }}} diff --git a/src/calibre/ebooks/metadata/sources/cli.py b/src/calibre/ebooks/metadata/sources/cli.py index 58042da2bf..cb422f939d 100644 --- a/src/calibre/ebooks/metadata/sources/cli.py +++ b/src/calibre/ebooks/metadata/sources/cli.py @@ -19,9 +19,13 @@ from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.metadata.sources.base import create_log from calibre.ebooks.metadata.sources.identify import identify from calibre.ebooks.metadata.sources.covers import download_cover - +from calibre.utils.config import test_eight_code def option_parser(): + if not test_eight_code: + from calibre.ebooks.metadata.fetch import option_parser + return option_parser() + parser = OptionParser(textwrap.dedent( '''\ %prog [options] @@ -44,6 +48,9 @@ def option_parser(): return parser def main(args=sys.argv): + if not test_eight_code: + from calibre.ebooks.metadata.fetch import main + return main(args) parser = option_parser() opts, args = parser.parse_args(args) From d63d47a9f53ffa73017351802c454cd10d010062 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 12:28:07 -0600 Subject: [PATCH 09/19] Use new edit metadata dialog when test_eight_code is True --- src/calibre/gui2/actions/edit_metadata.py | 50 +++++++++++++++++----- src/calibre/gui2/metadata/basic_widgets.py | 5 +-- src/calibre/gui2/metadata/single.py | 22 ++++++++-- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index 3f053e5223..c3ceb27e7e 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -17,6 +17,7 @@ from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.tag_list_editor import TagListEditor from calibre.gui2.actions import InterfaceAction from calibre.utils.icu import sort_key +from calibre.utils.config import test_eight_code class EditMetadataAction(InterfaceAction): @@ -133,8 +134,6 @@ class EditMetadataAction(InterfaceAction): row_list = [r.row() for r in rows] current_row = 0 - changed = set([]) - db = self.gui.library_view.model().db if len(row_list) == 1: cr = row_list[0] @@ -142,6 +141,24 @@ class EditMetadataAction(InterfaceAction): list(range(self.gui.library_view.model().rowCount(QModelIndex()))) current_row = row_list.index(cr) + if test_eight_code: + changed = self.do_edit_metadata(row_list, current_row) + else: + changed = self.do_edit_metadata_old(row_list, current_row) + + if changed: + self.gui.library_view.model().refresh_ids(list(changed)) + current = self.gui.library_view.currentIndex() + m = self.gui.library_view.model() + if self.gui.cover_flow: + self.gui.cover_flow.dataChanged() + m.current_changed(current, previous) + self.gui.tags_view.recount() + + def do_edit_metadata_old(self, row_list, current_row): + changed = set([]) + db = self.gui.library_view.model().db + while True: prev = next_ = None if current_row > 0: @@ -167,15 +184,28 @@ class EditMetadataAction(InterfaceAction): self.gui.library_view.set_current_row(current_row) self.gui.library_view.scroll_to_row(current_row) + def do_edit_metadata(self, row_list, current_row): + from calibre.gui2.metadata.single import edit_metadata + db = self.gui.library_view.model().db + changed, rows_to_refresh = edit_metadata(db, row_list, current_row, + parent=self.gui, view_slot=self.view_format_callback, + set_current_callback=self.set_current_callback) + return changed + + def set_current_callback(self, id_): + db = self.gui.library_view.model().db + current_row = db.row(id_) + self.gui.library_view.set_current_row(current_row) + self.gui.library_view.scroll_to_row(current_row) + + def view_format_callback(self, id_, fmt): + view = self.gui.iactions['View'] + if id_ is None: + view._view_file(fmt) + else: + db = self.gui.library_view.model().db + view.view_format(db.row(id_), fmt) - if changed: - self.gui.library_view.model().refresh_ids(list(changed)) - current = self.gui.library_view.currentIndex() - m = self.gui.library_view.model() - if self.gui.cover_flow: - self.gui.cover_flow.dataChanged() - m.current_changed(current, previous) - self.gui.tags_view.recount() def edit_bulk_metadata(self, checked): ''' diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index bab9073588..0b7d96c07c 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -428,7 +428,7 @@ class Format(QListWidgetItem): # {{{ if timestamp is not None: ts = timestamp.astimezone(local_tz) t = strftime('%a, %d %b %Y [%H:%M:%S]', ts.timetuple()) - text = _('Last modified: %s')%t + text = _('Last modified: %s\n\nDouble click to view')%t self.setToolTip(text) self.setStatusTip(text) @@ -577,8 +577,7 @@ class FormatsManager(QWidget): # {{{ self.changed = True def show_format(self, item, *args): - fmt = item.ext - self.dialog.view_format.emit(fmt) + self.dialog.do_view_format.emit(item.path, item.ext) def get_selected_format_metadata(self, db, id_): old = prefs['read_file_metadata'] diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index 70307eb3b1..bba8528573 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -26,7 +26,7 @@ from calibre.utils.config import tweaks class MetadataSingleDialogBase(ResizableDialog): - view_format = pyqtSignal(object) + view_format = pyqtSignal(object, object) cc_two_column = tweaks['metadata_single_use_2_cols_for_custom_fields'] one_line_comments_toolbar = False @@ -194,6 +194,13 @@ class MetadataSingleDialogBase(ResizableDialog): pass # Do something # }}} + def do_view_format(self, path, fmt): + if path: + self.view_format.emit(None, path) + else: + self.view_format.emit(self.book_id, fmt) + + def do_layout(self): raise NotImplementedError() @@ -204,6 +211,8 @@ class MetadataSingleDialogBase(ResizableDialog): widget.initialize(self.db, id_) for widget in getattr(self, 'custom_metadata_widgets', []): widget.initialize(id_) + if callable(self.set_current_callback): + self.set_current_callback(id_) # Commented out as it doesn't play nice with Next, Prev buttons #self.fetch_metadata_button.setFocus(Qt.OtherFocusReason) @@ -339,11 +348,13 @@ class MetadataSingleDialogBase(ResizableDialog): gprefs['metasingle_window_geometry3'] = bytearray(self.saveGeometry()) # Dialog use methods {{{ - def start(self, row_list, current_row, view_slot=None): + def start(self, row_list, current_row, view_slot=None, + set_current_callback=None): self.row_list = row_list self.current_row = current_row if view_slot is not None: self.view_format.connect(view_slot) + self.set_current_callback = set_current_callback self.do_one(apply_changes=False) ret = self.exec_() self.break_cycles() @@ -375,6 +386,7 @@ class MetadataSingleDialogBase(ResizableDialog): def break_cycles(self): # Break any reference cycles that could prevent python # from garbage collecting this dialog + self.set_current_callback = None def disconnect(signal): try: signal.disconnect() @@ -643,9 +655,11 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ # }}} -def edit_metadata(db, row_list, current_row, parent=None, view_slot=None): +def edit_metadata(db, row_list, current_row, parent=None, view_slot=None, + set_current_callback=None): d = MetadataSingleDialog(db, parent) - d.start(row_list, current_row, view_slot=view_slot) + d.start(row_list, current_row, view_slot=view_slot, + set_current_callback=set_current_callback) return d.changed, d.rows_to_refresh if __name__ == '__main__': From 901960ec044b8689a5cdf9062a69cdeae8306940 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 12:29:29 -0600 Subject: [PATCH 10/19] ... --- src/calibre/gui2/metadata/basic_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py index 0b7d96c07c..b0b7115ca1 100644 --- a/src/calibre/gui2/metadata/basic_widgets.py +++ b/src/calibre/gui2/metadata/basic_widgets.py @@ -577,7 +577,7 @@ class FormatsManager(QWidget): # {{{ self.changed = True def show_format(self, item, *args): - self.dialog.do_view_format.emit(item.path, item.ext) + self.dialog.do_view_format(item.path, item.ext) def get_selected_format_metadata(self, db, id_): old = prefs['read_file_metadata'] From 67a467107ea387042880d2257c8c61a063b80b4f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 12:35:01 -0600 Subject: [PATCH 11/19] ... --- src/calibre/gui2/metadata/single.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/metadata/single.py b/src/calibre/gui2/metadata/single.py index bba8528573..4f66e0d2ba 100644 --- a/src/calibre/gui2/metadata/single.py +++ b/src/calibre/gui2/metadata/single.py @@ -32,9 +32,9 @@ class MetadataSingleDialogBase(ResizableDialog): def __init__(self, db, parent=None): self.db = db - self.changed = set([]) - self.books_to_refresh = set([]) - self.rows_to_refresh = set([]) + self.changed = set() + self.books_to_refresh = set() + self.rows_to_refresh = set() ResizableDialog.__init__(self, parent) def setupUi(self, *args): # {{{ @@ -386,7 +386,7 @@ class MetadataSingleDialogBase(ResizableDialog): def break_cycles(self): # Break any reference cycles that could prevent python # from garbage collecting this dialog - self.set_current_callback = None + self.set_current_callback = self.db = None def disconnect(signal): try: signal.disconnect() From 2e08bc51712079312a96c18d0dbb0481bed56bc0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 12:41:10 -0600 Subject: [PATCH 12/19] Fix #752464 ("Kommersant" recipe is broken) --- recipes/kommersant.recipe | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/recipes/kommersant.recipe b/recipes/kommersant.recipe index f24a5da909..09fb8f8ad8 100644 --- a/recipes/kommersant.recipe +++ b/recipes/kommersant.recipe @@ -1,5 +1,5 @@ __license__ = 'GPL v3' -__copyright__ = '2010, Darko Miletic ' +__copyright__ = '2010-2011, Darko Miletic ' ''' www.kommersant.ru ''' @@ -20,7 +20,13 @@ class Kommersant_ru(BasicNewsRecipe): language = 'ru' publication_type = 'newspaper' masthead_url = 'http://www.kommersant.ru/CorpPics/logo_daily_1.gif' - extra_css = ' @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: Arial, sans1, sans-serif} span#ctl00_ContentPlaceHolderStyle_LabelSubTitle{margin-bottom: 1em; display: block} .author{margin-bottom: 1em; display: block} .paragraph{margin-bottom: 1em; display: block} .vvodka{font-weight: bold; margin-bottom: 1em} ' + extra_css = """ + @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} + body{font-family: Tahoma, Arial, Helvetica, sans1, sans-serif} + .title{font-size: x-large; font-weight: bold; margin-bottom: 1em} + .subtitle{font-size: large; margin-bottom: 1em} + .document_vvodka{font-weight: bold; margin-bottom: 1em} + """ conversion_options = { 'comment' : description @@ -29,14 +35,11 @@ class Kommersant_ru(BasicNewsRecipe): , 'language' : language } - keep_only_tags = [ - dict(attrs={'id':'ctl00_ContentPlaceHolderStyle_PanelHeader'}) - ,dict(attrs={'class':['vvodka','paragraph','author']}) - ] - remove_tags = [dict(name=['iframe','object','link','img','base'])] + keep_only_tags = [dict(attrs={'class':['document','document_vvodka','document_text','document_authors vblock']})] + remove_tags = [dict(name=['iframe','object','link','img','base','meta'])] feeds = [(u'Articles', u'http://feeds.kommersant.ru/RSS_Export/RU/daily.xml')] - def print_version(self, url): - return url.replace('doc-rss.aspx','doc.aspx') + '&print=true' - + def print_version(self, url): + return url.replace('/doc-rss/','/Doc/') + '/Print' + \ No newline at end of file From cad3b71b324ffb280b90c309bfdbe7ea376a1430 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 13:54:10 -0600 Subject: [PATCH 13/19] ... --- src/calibre/manual/gui.rst | 3 ++- src/calibre/manual/index.rst | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/calibre/manual/gui.rst b/src/calibre/manual/gui.rst index 9307ff30f6..7b6e60c93a 100644 --- a/src/calibre/manual/gui.rst +++ b/src/calibre/manual/gui.rst @@ -71,7 +71,7 @@ Edit metadata |emii| The :guilabel:`Edit metadata` action has six variations, which can be accessed by clicking the down arrow on the right side of the button. - 1. **Edit metadata individually**: This allows you to edit the metadata of books one-by-one, with the option of fetching metadata, including covers from the internet. It also allows you to add/remove particular ebook formats from a book. For more detail see :ref:`metadata`. + 1. **Edit metadata individually**: This allows you to edit the metadata of books one-by-one, with the option of fetching metadata, including covers from the internet. It also allows you to add/remove particular ebook formats from a book. 2. **Edit metadata in bulk**: This allows you to edit common metadata fields for large numbers of books simulataneously. It operates on all the books you have selected in the :ref:`Library view `. 3. **Download metadata and covers**: Downloads metadata and covers (if available), for the books that are selected in the book list. 4. **Download only metadata**: Downloads only metadata (if available), for the books that are selected in the book list. @@ -79,6 +79,7 @@ Edit metadata 6. **Download only social metadata**: Downloads only social metadata such as tags and reviews (if available), for the books that are selected in the book list. 7. **Merge Book Records**: Gives you the capability of merging the metadata and formats of two or more book records together. You can choose to either delete or keep the records that were not clicked first. +For more details see :ref:`metadata`. .. _convert_ebooks: diff --git a/src/calibre/manual/index.rst b/src/calibre/manual/index.rst index 996a1de382..e54882dda0 100644 --- a/src/calibre/manual/index.rst +++ b/src/calibre/manual/index.rst @@ -70,7 +70,7 @@ Customizing |app|'s e-book conversion .. toctree:: :maxdepth: 2 - viewer + conversion Editing e-book metadata ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -78,7 +78,7 @@ Editing e-book metadata .. toctree:: :maxdepth: 2 - viewer + metadata Frequently Asked Questions ^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 3e230ac838eab493f7125534fb024f1f01eaefb9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 15:46:51 -0600 Subject: [PATCH 14/19] ... --- src/calibre/manual/conf.py | 2 +- src/calibre/manual/faq.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/manual/conf.py b/src/calibre/manual/conf.py index fc8962bcfd..d2b3a91d8d 100644 --- a/src/calibre/manual/conf.py +++ b/src/calibre/manual/conf.py @@ -126,7 +126,7 @@ html_use_modindex = False html_use_index = False # If true, the reST sources are included in the HTML build as _sources/. -html_copy_source = False +html_copy_source = True # Output file base name for HTML help builder. htmlhelp_basename = 'calibredoc' diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index f48fa9dc16..f8b257fd75 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -100,7 +100,7 @@ We just need some information from you: Once you send us the output for a particular operating system, support for the device in that operating system will appear in the next release of |app|. To send us the output, open a bug report and attach the output to it. -See `http://calibre-ebook.com/bugs`_. +See `calibre bugs `_. My device is not being detected by |app|? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 58899e65ef4e532976f59fb6da1c1484a9a5ad4d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 16:23:18 -0600 Subject: [PATCH 15/19] ... --- src/calibre/gui2/metadata/single_download.py | 39 ++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/calibre/gui2/metadata/single_download.py diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py new file mode 100644 index 0000000000..ace4133d7a --- /dev/null +++ b/src/calibre/gui2/metadata/single_download.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2011, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, + QStyle, QApplication) + +class RichTextDelegate(QStyledItemDelegate): # {{{ + + def __init__(self, parent=None): + QStyledItemDelegate.__init__(self, parent) + + def to_doc(self, index): + doc = QTextDocument() + doc.setHtml(index.data().toString()) + return doc + + def sizeHint(self, option, index): + ans = self.to_doc(index).size().toSize() + ans.setHeight(ans.height()+10) + return ans + + def paint(self, painter, option, index): + painter.save() + painter.setClipRect(QRectF(option.rect)) + if hasattr(QStyle, 'CE_ItemViewItem'): + QApplication.style().drawControl(QStyle.CE_ItemViewItem, option, painter) + elif option.state & QStyle.State_Selected: + painter.fillRect(option.rect, option.palette.highlight()) + painter.translate(option.rect.topLeft()) + self.to_doc(index).drawContents(painter) + painter.restore() +# }}} + From 535622519776599f3ded7b62dfb9c16a6b4acf8d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 21:51:03 -0600 Subject: [PATCH 16/19] Start work on new metadata download GUIs --- src/calibre/ebooks/metadata/sources/amazon.py | 2 +- src/calibre/ebooks/metadata/sources/base.py | 7 + src/calibre/ebooks/metadata/sources/covers.py | 2 +- .../ebooks/metadata/sources/identify.py | 2 +- src/calibre/ebooks/metadata/sources/isbndb.py | 3 + src/calibre/gui2/metadata/single_download.py | 154 +++++++++++++++++- src/calibre/manual/server.rst | 4 +- 7 files changed, 167 insertions(+), 7 deletions(-) diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index d48f502c29..b070132de9 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -279,7 +279,7 @@ class Worker(Thread): # Get details {{{ class Amazon(Source): - name = 'Amazon Metadata' + name = 'Amazon Store' description = _('Downloads metadata from Amazon') capabilities = frozenset(['identify', 'cover']) diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py index faa7420081..d4e090084c 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -167,6 +167,13 @@ class Source(Plugin): # Configuration {{{ + def is_configured(self): + ''' + Return False if your plugin needs to be configured before it can be + used. For example, it might need a username/password/API key. + ''' + return True + @property def prefs(self): if self._config_obj is None: diff --git a/src/calibre/ebooks/metadata/sources/covers.py b/src/calibre/ebooks/metadata/sources/covers.py index 46b278397c..cf6ec90c54 100644 --- a/src/calibre/ebooks/metadata/sources/covers.py +++ b/src/calibre/ebooks/metadata/sources/covers.py @@ -76,7 +76,7 @@ def run_download(log, results, abort, (plugin, width, height, fmt, bytes) ''' - plugins = list(metadata_plugins(['cover'])) + plugins = [p for p in metadata_plugins(['cover']) if p.is_configured()] rq = Queue() workers = [Worker(p, abort, title, authors, identifiers, timeout, rq) for p diff --git a/src/calibre/ebooks/metadata/sources/identify.py b/src/calibre/ebooks/metadata/sources/identify.py index cbc12b6167..8c6172f0e2 100644 --- a/src/calibre/ebooks/metadata/sources/identify.py +++ b/src/calibre/ebooks/metadata/sources/identify.py @@ -250,7 +250,7 @@ def merge_identify_results(result_map, log): def identify(log, abort, # {{{ title=None, authors=None, identifiers={}, timeout=30): start_time = time.time() - plugins = list(metadata_plugins(['identify'])) + plugins = [p for p in metadata_plugins(['identify']) if p.is_configured()] kwargs = { 'title': title, diff --git a/src/calibre/ebooks/metadata/sources/isbndb.py b/src/calibre/ebooks/metadata/sources/isbndb.py index 3cd9d96c81..ab9342c6cb 100644 --- a/src/calibre/ebooks/metadata/sources/isbndb.py +++ b/src/calibre/ebooks/metadata/sources/isbndb.py @@ -37,4 +37,7 @@ class ISBNDB(Source): self.isbndb_key = prefs['isbndb_key'] + def is_configured(self): + return self.isbndb_key is not None + diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index ace4133d7a..be521b6000 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -7,8 +7,13 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' -from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, - QStyle, QApplication) +from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt, + QStyle, QApplication, QDialog, QVBoxLayout, QLabel, QDialogButtonBox, + QStackedWidget, QWidget, QTableView, QGridLayout, QFontInfo, QPalette) +from PyQt4.QtWebKit import QWebView + +from calibre.customize.ui import metadata_plugins +from calibre.ebooks.metadata import authors_to_string class RichTextDelegate(QStyledItemDelegate): # {{{ @@ -37,3 +42,148 @@ class RichTextDelegate(QStyledItemDelegate): # {{{ painter.restore() # }}} +class ResultsView(QTableView): + + def __init__(self, parent=None): + QTableView.__init__(self, parent) + +class Comments(QWebView): # {{{ + + def __init__(self, parent=None): + QWebView.__init__(self, parent) + self.setAcceptDrops(False) + self.setMaximumWidth(270) + self.setMinimumWidth(270) + + palette = self.palette() + palette.setBrush(QPalette.Base, Qt.transparent) + self.page().setPalette(palette) + self.setAttribute(Qt.WA_OpaquePaintEvent, False) + + def turnoff_scrollbar(self, *args): + self.page().mainFrame().setScrollBarPolicy(Qt.Horizontal, Qt.ScrollBarAlwaysOff) + + def show_data(self, html): + def color_to_string(col): + ans = '#000000' + if col.isValid(): + col = col.toRgb() + if col.isValid(): + ans = unicode(col.name()) + return ans + + f = QFontInfo(QApplication.font(self.parent())).pixelSize() + c = color_to_string(QApplication.palette().color(QPalette.Normal, + QPalette.WindowText)) + templ = '''\ + + + + + +
+ %%s +
+ + + '''%(f, c) + self.setHtml(templ%html) +# }}} + +class IdentifyWidget(QWidget): + + def __init__(self, parent=None): + QWidget.__init__(self, parent) + + self.l = l = QGridLayout() + self.setLayout(l) + + names = [''+p.name+'' for p in metadata_plugins(['identify']) if + p.is_configured()] + self.top = QLabel('

'+_('calibre is downloading metadata from: ') + + ', '.join(names)) + self.top.setWordWrap(True) + l.addWidget(self.top, 0, 0) + + self.results_view = ResultsView(self) + l.addWidget(self.results_view, 1, 0) + + self.comments_view = Comments(self) + l.addWidget(self.comments_view, 1, 1) + + self.query = QLabel('download starting...') + f = self.query.font() + f.setPointSize(f.pointSize()-2) + self.query.setFont(f) + self.query.setWordWrap(True) + l.addWidget(self.query, 2, 0, 1, 2) + + def start(self, title=None, authors=None, identifiers={}): + parts = [] + if title: + parts.append('title:'+title) + if authors: + parts.append('authors:'+authors_to_string(authors)) + if identifiers: + x = ', '.join('%s:%s'%(k, v) for k, v in identifiers) + parts.append(x) + self.query.setText(_('Query: ')+'; '.join(parts)) + self.comments_view.show_data('

'+_('Downloading, please wait')+ + '.

'+ + ''' + + ''') + +class FullFetch(QDialog): # {{{ + + def __init__(self, parent=None): + QDialog.__init__(self, parent) + + self.setWindowTitle(_('Downloading metadata...')) + self.setWindowIcon(QIcon(I('metadata.png'))) + + self.stack = QStackedWidget() + self.l = l = QVBoxLayout() + self.setLayout(l) + l.addWidget(self.stack) + + self.bb = QDialogButtonBox(QDialogButtonBox.Cancel) + l.addWidget(self.bb) + self.bb.rejected.connect(self.reject) + + self.identify_widget = IdentifyWidget(self) + self.stack.addWidget(self.identify_widget) + self.resize(850, 500) + + def accept(self): + # Prevent pressing Enter from closing the dialog + pass + + def start(self, title=None, authors=None, identifiers={}): + self.identify_widget.start(title=title, authors=authors, + identifiers=identifiers) + self.exec_() +# }}} + +if __name__ == '__main__': + app = QApplication([]) + d = FullFetch() + d.start(title='great gatsby', authors=['Fitzgerald']) + diff --git a/src/calibre/manual/server.rst b/src/calibre/manual/server.rst index 6d1adc88cd..82ec5c2927 100644 --- a/src/calibre/manual/server.rst +++ b/src/calibre/manual/server.rst @@ -16,7 +16,7 @@ Here, we will show you how to integrate the |app| content server into another se Using a reverse proxy ----------------------- -This is the simplest approach as it allows you to use the binary calibre install with no external dependencies/system integration requirements. +A reverse proxy is when your normal server accepts incoming requests and passes them onto the calibre server. It then reads the response from the calibre server and forwards it to the client. This means that you can simply run the calibre server as normal without trying to integrate it closely with your main server, and you can take advantage of whatever authentication systems you main server has in place. This is the simplest approach as it allows you to use the binary calibre install with no external dependencies/system integration requirements. Below, is an example of how to achieve this with Apache as your main server, but it will work with any server that supports Reverse Proxies. First start the |app| content server as shown below:: @@ -33,7 +33,7 @@ The exact technique for enabling the proxy modules will vary depending on your A RewriteRule ^/calibre/(.*) http://localhost:8080/calibre/$1 [proxy] RewriteRule ^/calibre http://localhost:8080 [proxy] -That's all, you will now be able to access the |app| Content Server under the /calibre URL in your apache server. +That's all, you will now be able to access the |app| Content Server under the /calibre URL in your apache server. The above rules pass all requests under /calibre to the calibre server running on port 8080 and thanks to the --url-prefix option above, the calibre server handles them transparently. .. note:: If you are willing to devote an entire VirtualHost to the content server, then there is no need to use --url-prefix and RewriteRule, instead just use the ProxyPass directive. From fc1e9175fcb40d95c701f4b2d8a3c1025c4c2aad Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 22:00:53 -0600 Subject: [PATCH 17/19] Fix some server settings not being applied when clicking start server in Preferences->Sharing over the net --- src/calibre/gui2/preferences/server.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/calibre/gui2/preferences/server.py b/src/calibre/gui2/preferences/server.py index 82519f17cd..421dbe737f 100644 --- a/src/calibre/gui2/preferences/server.py +++ b/src/calibre/gui2/preferences/server.py @@ -57,17 +57,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('autolaunch_server', config) - def set_server_options(self): - c = self.proxy - c.set('port', self.opt_port.value()) - c.set('username', unicode(self.opt_username.text()).strip()) - p = unicode(self.opt_password.text()).strip() - if not p: - p = None - c.set('password', p) - def start_server(self): - self.set_server_options() + ConfigWidgetBase.commit(self) self.gui.start_content_server(check_started=False) while not self.gui.content_server.is_running and self.gui.content_server.exception is None: time.sleep(1) From 2befe1eb584186f7cff24088e7a1a0edd2ee3b74 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 22:53:08 -0600 Subject: [PATCH 18/19] ... --- src/calibre/gui2/metadata/single_download.py | 91 +++++++++++++++----- src/calibre/utils/logging.py | 54 ++++++++++-- 2 files changed, 116 insertions(+), 29 deletions(-) diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index be521b6000..426d0b9e78 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -7,6 +7,8 @@ __license__ = 'GPL v3' __copyright__ = '2011, Kovid Goyal ' __docformat__ = 'restructuredtext en' +from threading import Thread, Event + from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt, QStyle, QApplication, QDialog, QVBoxLayout, QLabel, QDialogButtonBox, QStackedWidget, QWidget, QTableView, QGridLayout, QFontInfo, QPalette) @@ -14,6 +16,18 @@ from PyQt4.QtWebKit import QWebView from calibre.customize.ui import metadata_plugins from calibre.ebooks.metadata import authors_to_string +from calibre.utils.logging import ThreadSafeLog, UnicodeHTMLStream +from calibre.ebooks.metadata.sources.identify import identify + +class Log(ThreadSafeLog): # {{{ + + def __init__(self): + ThreadSafeLog.__init__(self, level=self.DEBUG) + self.outputs = [UnicodeHTMLStream()] + + def clear(self): + self.outputs[0].clear() +# }}} class RichTextDelegate(QStyledItemDelegate): # {{{ @@ -95,10 +109,35 @@ class Comments(QWebView): # {{{ self.setHtml(templ%html) # }}} +class IdentifyWorker(Thread): + + def __init__(self, log, abort, title, authors, identifiers): + Thread.__init__(self) + self.daemon = True + + self.log, self.abort = log, abort + self.title, self.authors, self.identifiers = (title, authors. + identifiers) + + self.results = [] + self.error = None + + def run(self): + try: + self.results = identify(self.log, self.abort, title=self.title, + authors=self.authors, identifiers=self.identifiers) + for i, result in enumerate(self.results): + result.gui_rank = i + except: + import traceback + self.error = traceback.format_exc() + class IdentifyWidget(QWidget): - def __init__(self, parent=None): + def __init__(self, log, parent=None): QWidget.__init__(self, parent) + self.log = log + self.abort = Event() self.l = l = QGridLayout() self.setLayout(l) @@ -123,7 +162,27 @@ class IdentifyWidget(QWidget): self.query.setWordWrap(True) l.addWidget(self.query, 2, 0, 1, 2) + self.comments_view.show_data('

'+_('Downloading')+ + '
.

'+ + ''' + + ''') + def start(self, title=None, authors=None, identifiers={}): + self.log.clear() + self.log('Starting download') parts = [] if title: parts.append('title:'+title) @@ -133,28 +192,18 @@ class IdentifyWidget(QWidget): x = ', '.join('%s:%s'%(k, v) for k, v in identifiers) parts.append(x) self.query.setText(_('Query: ')+'; '.join(parts)) - self.comments_view.show_data('

'+_('Downloading, please wait')+ - '.

'+ - ''' - - ''') + self.log(unicode(self.query.text())) + + self.worker = IdentifyWorker(self.log, self.abort, self.title, + self.authors, self.identifiers) + + # self.worker.start() class FullFetch(QDialog): # {{{ - def __init__(self, parent=None): + def __init__(self, log, parent=None): QDialog.__init__(self, parent) + self.log = log self.setWindowTitle(_('Downloading metadata...')) self.setWindowIcon(QIcon(I('metadata.png'))) @@ -168,7 +217,7 @@ class FullFetch(QDialog): # {{{ l.addWidget(self.bb) self.bb.rejected.connect(self.reject) - self.identify_widget = IdentifyWidget(self) + self.identify_widget = IdentifyWidget(log, self) self.stack.addWidget(self.identify_widget) self.resize(850, 500) @@ -184,6 +233,6 @@ class FullFetch(QDialog): # {{{ if __name__ == '__main__': app = QApplication([]) - d = FullFetch() + d = FullFetch(Log()) d.start(title='great gatsby', authors=['Fitzgerald']) diff --git a/src/calibre/utils/logging.py b/src/calibre/utils/logging.py index f4b2e6f0b6..45e21ded39 100644 --- a/src/calibre/utils/logging.py +++ b/src/calibre/utils/logging.py @@ -14,7 +14,7 @@ import sys, traceback, cStringIO from functools import partial from threading import RLock - +from calibre import isbytestring, force_unicode, as_unicode class Stream(object): @@ -63,15 +63,16 @@ class FileStream(Stream): class HTMLStream(Stream): + color = { + DEBUG: '', + INFO:'', + WARN: '', + ERROR: '' + } + normal = '' + def __init__(self, stream=sys.stdout): Stream.__init__(self, stream) - self.color = { - DEBUG: '', - INFO:'', - WARN: '', - ERROR: '' - } - self.normal = '' def prints(self, level, *args, **kwargs): self.stream.write(self.color[level]) @@ -82,6 +83,43 @@ class HTMLStream(Stream): def flush(self): self.stream.flush() +class UnicodeHTMLStream(HTMLStream): + + def __init__(self): + self.clear() + + def flush(self): + pass + + def prints(self, level, *args, **kwargs): + col = self.color[level] + if col != self.last_col: + if self.data: + self.data.append(self.normal) + self.data.append(col) + self.last_col = col + + sep = kwargs.get(u'sep', u' ') + end = kwargs.get(u'end', u'\n') + + for arg in args: + if isbytestring(arg): + arg = force_unicode(arg) + elif not isinstance(arg, unicode): + arg = as_unicode(arg) + self.data.append(arg+sep) + self.data.append(end) + + def clear(self): + self.data = [] + self.last_col = self.color[INFO] + + @property + def html(self): + end = self.normal if self.data else u'' + return u''.join(self.data) + end + + class Log(object): DEBUG = DEBUG From 011403978718034d2817e19ce0b91a20fc766f76 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Apr 2011 22:54:15 -0600 Subject: [PATCH 19/19] ... --- src/calibre/gui2/metadata/single_download.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index 426d0b9e78..049ac611c5 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -116,7 +116,7 @@ class IdentifyWorker(Thread): self.daemon = True self.log, self.abort = log, abort - self.title, self.authors, self.identifiers = (title, authors. + self.title, self.authors, self.identifiers = (title, authors, identifiers) self.results = [] @@ -194,8 +194,8 @@ class IdentifyWidget(QWidget): self.query.setText(_('Query: ')+'; '.join(parts)) self.log(unicode(self.query.text())) - self.worker = IdentifyWorker(self.log, self.abort, self.title, - self.authors, self.identifiers) + self.worker = IdentifyWorker(self.log, self.abort, title, + authors, identifiers) # self.worker.start()