From 0b24cb8674672b4386d8ba8fd764d0a54fff3127 Mon Sep 17 00:00:00 2001 From: GRiker Date: Sun, 13 Jan 2013 03:55:42 -0700 Subject: [PATCH 01/13] undid changes adding uuid as dc:identifier in anticipation of Kovid's solution. --- src/calibre/ebooks/metadata/epub.py | 3 +-- src/calibre/ebooks/metadata/opf2.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index 5b8f75c3b1..bc81df5a79 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -289,9 +289,8 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False): langs.append(lc) mi.languages = langs - reader.opf.smart_update(mi) - reader.opf.add_uuid_identifier(mi.uuid) + reader.opf.smart_update(mi) if apply_null: if not getattr(mi, 'series', None): reader.opf.series = None diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 5d853288de..3e5d95f1ce 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -1163,9 +1163,6 @@ class OPF(object): # {{{ temp.smart_update(mi, replace_metadata=replace_metadata) self._user_metadata_ = temp.get_all_user_metadata(True) - def add_uuid_identifier(self,uuid): - setattr(self,'uuid',uuid) - # }}} class OPFCreator(Metadata): From 01acc48b14423549e4827008fffed45680bab702 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 13 Jan 2013 22:38:33 +0530 Subject: [PATCH 02/13] Update Smithsonian Magazine --- recipes/smith.recipe | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/recipes/smith.recipe b/recipes/smith.recipe index 3d6a95c494..cd0c94ab35 100644 --- a/recipes/smith.recipe +++ b/recipes/smith.recipe @@ -48,10 +48,14 @@ class Smithsonian(BasicNewsRecipe): link=post.find('a',href=True) url=link['href']+'?c=y&story=fullstory' if subsection is not None: - subsection_title = self.tag_to_string(subsection) + subsection_title = self.tag_to_string(subsection).strip() prefix = (subsection_title+': ') description=self.tag_to_string(post('p', limit=2)[1]).strip() else: + if post.find('img') is not None: + subsection_title = self.tag_to_string(post.findPrevious('div', attrs={'class':'departments plainModule'}).find('p', attrs={'class':'article-cat'})).strip() + prefix = (subsection_title+': ') + description=self.tag_to_string(post.find('p')).strip() desc=re.sub('\sBy\s.*', '', description, re.DOTALL) author=re.sub('.*By\s', '', description, re.DOTALL) @@ -64,4 +68,3 @@ class Smithsonian(BasicNewsRecipe): feeds[section_title] += articles ans = [(key, val) for key, val in feeds.iteritems()] return ans - From 792d4284b37fe34b1d74e18246213b5d4fc12641 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 13 Jan 2013 23:38:04 +0530 Subject: [PATCH 03/13] Fix #1099157 (Typo in Quick Start Guide) --- resources/quick_start.epub | Bin 130580 -> 130579 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/resources/quick_start.epub b/resources/quick_start.epub index a3f74213a6a374ebb271a570214af6962283c8c4..23728547cde1d2f2f159cf71e8a335540e28abbf 100644 GIT binary patch delta 269 zcmbR8hkf!N_6?Igc&F~sb?P{#>mSF;z|gmOfyWz``m_Z9f#Yvq}Au#`jKgxmJ%aEZ*>qI}M>^g-eC z=%z~XvfI9%Z=6;5R=qmN`&sWvTK*&d-TiBST)$rR^Q+2CUt_K({^tKuxaSvqk!&{b z+iu{;n6-vcWcuu1jPi^O+jswBa{9u*jLwXI Rra%45sLA%^J0k-F0{{rXdno__ delta 270 zcmbRIhkeQ)_6?IgcsI^|Y`5r_x>GzW14IAj1s-o$>X{to)V{Vod|EMK_wK5DTyIbP zD7#gD)8apqxzX|J@BOyDi?rf6ZGV>CZ~OFj7w@q>kGEGplkv;rweqUF9hzTa{_5k+ zxn?q#ZrXk*Y-gMGOVW4ty?wRvhfAERSVS#%dk0r+H(8hM7$Dqcr^_WWXUpPqmZc90 zpC>m}ikIE)^?al1%(v;)L0)G4Cu#Xl{CBTk`{Vlcs-Is~X8Ia)J;^u!m&Sd+;EQat zzTb9zKgO&zjB?Xw{bH17WZb^%7b8Cts)wdu`@@*aWa2lukW+Jd-d{#T##hr9{AF}z S{5$>0Uq(%~r{5VF7#INPW_}?6 From d2e27f55b830bbd87a69f12d8cfeaaf57d54ec3e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 14 Jan 2013 11:50:23 +0530 Subject: [PATCH 04/13] version numbers to support dynamic loading of get books plugins --- .../gui2/store/stores/amazon_de_plugin.py | 1 + .../gui2/store/stores/amazon_es_plugin.py | 3 +- .../gui2/store/stores/amazon_fr_plugin.py | 1 + .../gui2/store/stores/amazon_it_plugin.py | 3 +- .../gui2/store/stores/amazon_plugin.py | 13 ++++---- .../gui2/store/stores/amazon_uk_plugin.py | 1 + .../gui2/store/stores/archive_org_plugin.py | 1 + .../store/stores/baen_webscription_plugin.py | 23 ++++++------- .../gui2/store/stores/bewrite_plugin.py | 7 ++-- .../gui2/store/stores/biblio_plugin.py | 7 ++-- src/calibre/gui2/store/stores/bn_plugin.py | 1 + .../gui2/store/stores/bookoteka_plugin.py | 1 + .../gui2/store/stores/chitanka_plugin.py | 1 + .../gui2/store/stores/diesel_ebooks_plugin.py | 1 + .../gui2/store/stores/ebook_nl_plugin.py | 1 + .../gui2/store/stores/ebookpoint_plugin.py | 1 + .../gui2/store/stores/ebooks_com_plugin.py | 1 + .../store/stores/ebooksgratuits_plugin.py | 1 + .../store/stores/ebookshoppe_uk_plugin.py | 1 + .../gui2/store/stores/eharlequin_plugin.py | 15 +++++---- .../gui2/store/stores/eknigi_plugin.py | 1 + src/calibre/gui2/store/stores/empik_plugin.py | 3 +- .../store/stores/escapemagazine_plugin.py | 1 + .../gui2/store/stores/feedbooks_plugin.py | 5 +-- .../gui2/store/stores/foyles_uk_plugin.py | 1 + .../gui2/store/stores/google_books_plugin.py | 21 ++++++------ .../gui2/store/stores/gutenberg_plugin.py | 1 + src/calibre/gui2/store/stores/kobo_plugin.py | 1 + .../gui2/store/stores/legimi_plugin.py | 13 ++++---- .../gui2/store/stores/libri_de_plugin.py | 1 + .../gui2/store/stores/litres_plugin.py | 1 + .../gui2/store/stores/manybooks_plugin.py | 17 +++++----- .../gui2/store/stores/mills_boon_uk_plugin.py | 1 + src/calibre/gui2/store/stores/nexto_plugin.py | 1 + .../gui2/store/stores/nook_uk_plugin.py | 1 + .../gui2/store/stores/open_books_plugin.py | 1 + .../gui2/store/stores/ozon_ru_plugin.py | 33 ++++++++++--------- .../stores/pragmatic_bookshelf_plugin.py | 3 +- .../gui2/store/stores/publio_plugin.py | 1 + .../gui2/store/stores/rw2010_plugin.py | 3 +- .../gui2/store/stores/smashwords_plugin.py | 19 ++++++----- src/calibre/gui2/store/stores/sony_plugin.py | 1 + .../gui2/store/stores/virtualo_plugin.py | 1 + .../store/stores/waterstones_uk_plugin.py | 1 + .../store/stores/weightless_books_plugin.py | 15 +++++---- .../gui2/store/stores/whsmith_uk_plugin.py | 1 + .../gui2/store/stores/woblink_plugin.py | 1 + .../gui2/store/stores/xinxii_plugin.py | 27 +++++++-------- src/calibre/gui2/store/stores/zixo_plugin.py | 1 + 49 files changed, 155 insertions(+), 106 deletions(-) diff --git a/src/calibre/gui2/store/stores/amazon_de_plugin.py b/src/calibre/gui2/store/stores/amazon_de_plugin.py index 3ccbef0b6e..58c67122e1 100644 --- a/src/calibre/gui2/store/stores/amazon_de_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_de_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/amazon_es_plugin.py b/src/calibre/gui2/store/stores/amazon_es_plugin.py index 131f77c7e9..427927a5a6 100644 --- a/src/calibre/gui2/store/stores/amazon_es_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_es_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -21,4 +22,4 @@ class AmazonESKindleStore(AmazonUKKindleStore): '&linkCode=ur2&camp=3626&creative=24790') search_url = 'http://www.amazon.es/s/?url=search-alias%3Ddigital-text&field-keywords=' - author_article = 'de ' \ No newline at end of file + author_article = 'de ' diff --git a/src/calibre/gui2/store/stores/amazon_fr_plugin.py b/src/calibre/gui2/store/stores/amazon_fr_plugin.py index cd59be0313..e3eff50450 100644 --- a/src/calibre/gui2/store/stores/amazon_fr_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_fr_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/amazon_it_plugin.py b/src/calibre/gui2/store/stores/amazon_it_plugin.py index ad028bf963..669831f89d 100644 --- a/src/calibre/gui2/store/stores/amazon_it_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_it_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -21,4 +22,4 @@ class AmazonITKindleStore(AmazonUKKindleStore): 'linkCode=ur2&camp=3370&creative=23322') search_url = 'http://www.amazon.it/s/?url=search-alias%3Ddigital-text&field-keywords=' - author_article = 'di ' \ No newline at end of file + author_article = 'di ' diff --git a/src/calibre/gui2/store/stores/amazon_plugin.py b/src/calibre/gui2/store/stores/amazon_plugin.py index 4bd1a42c9d..c2b4a05cca 100644 --- a/src/calibre/gui2/store/stores/amazon_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -135,11 +136,11 @@ class AmazonKindleStore(StorePlugin): title_xpath = './/h3[@class="newaps"]/a//text()' author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()' price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()' - + for data in doc.xpath(data_xpath): if counter <= 0: break - + # Even though we are searching digital-text only Amazon will still # put in results for non Kindle books (author pages). Se we need # to explicitly check if the item is a Kindle book and ignore it @@ -147,7 +148,7 @@ class AmazonKindleStore(StorePlugin): format = ''.join(data.xpath(format_xpath)) if 'kindle' not in format.lower(): continue - + # We must have an asin otherwise we can't easily reference the # book later. asin_href = None @@ -161,7 +162,7 @@ class AmazonKindleStore(StorePlugin): continue else: continue - + cover_url = ''.join(data.xpath(cover_xpath)) title = ''.join(data.xpath(title_xpath)) @@ -172,9 +173,9 @@ class AmazonKindleStore(StorePlugin): pass price = ''.join(data.xpath(price_xpath)) - + counter -= 1 - + s = SearchResult() s.cover_url = cover_url.strip() s.title = title.strip() diff --git a/src/calibre/gui2/store/stores/amazon_uk_plugin.py b/src/calibre/gui2/store/stores/amazon_uk_plugin.py index 0f9caf8f3e..43f50fe6b0 100644 --- a/src/calibre/gui2/store/stores/amazon_uk_plugin.py +++ b/src/calibre/gui2/store/stores/amazon_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/archive_org_plugin.py b/src/calibre/gui2/store/stores/archive_org_plugin.py index 7439056baa..ed83b1c433 100644 --- a/src/calibre/gui2/store/stores/archive_org_plugin.py +++ b/src/calibre/gui2/store/stores/archive_org_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/baen_webscription_plugin.py b/src/calibre/gui2/store/stores/baen_webscription_plugin.py index a2a4e63d74..63203693a5 100644 --- a/src/calibre/gui2/store/stores/baen_webscription_plugin.py +++ b/src/calibre/gui2/store/stores/baen_webscription_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -22,7 +23,7 @@ from calibre.gui2.store.search_result import SearchResult from calibre.gui2.store.web_store_dialog import WebStoreDialog class BaenWebScriptionStore(BasicStoreConfig, StorePlugin): - + def open(self, parent=None, detail_item=None, external=False): url = 'http://www.baenebooks.com/' @@ -41,26 +42,26 @@ class BaenWebScriptionStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://www.baenebooks.com/searchadv.aspx?IsSubmit=true&SearchTerm=' + urllib2.quote(query) - + br = browser() - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) for data in doc.xpath('//table//table//table//table//tr'): if counter <= 0: break - + id = ''.join(data.xpath('./td[1]/a/@href')) if not id or not id.startswith('p-'): continue - + title = ''.join(data.xpath('./td[1]/a/text()')) - + author = '' cover_url = '' price = '' - + with closing(br.open('http://www.baenebooks.com/' + id.strip(), timeout=timeout/4)) as nf: idata = html.fromstring(nf.read()) author = ''.join(idata.xpath('//span[@class="ProductNameText"]/../b/text()')) @@ -68,16 +69,16 @@ class BaenWebScriptionStore(BasicStoreConfig, StorePlugin): price = ''.join(idata.xpath('//span[@class="variantprice"]/text()')) a, b, price = price.partition('$') price = b + price - + pnum = '' mo = re.search(r'p-(?P\d+)-', id.strip()) if mo: pnum = mo.group('num') if pnum: cover_url = 'http://www.baenebooks.com/' + ''.join(idata.xpath('//img[@id="ProductPic%s"]/@src' % pnum)) - + counter -= 1 - + s = SearchResult() s.cover_url = cover_url s.title = title.strip() @@ -86,5 +87,5 @@ class BaenWebScriptionStore(BasicStoreConfig, StorePlugin): s.detail_item = id.strip() s.drm = SearchResult.DRM_UNLOCKED s.formats = 'RB, MOBI, EPUB, LIT, LRF, RTF, HTML' - + yield s diff --git a/src/calibre/gui2/store/stores/bewrite_plugin.py b/src/calibre/gui2/store/stores/bewrite_plugin.py index b702f15623..3ccd28d976 100644 --- a/src/calibre/gui2/store/stores/bewrite_plugin.py +++ b/src/calibre/gui2/store/stores/bewrite_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -71,7 +72,7 @@ class BeWriteStore(BasicStoreConfig, StorePlugin): with closing(br.open(search_result.detail_item, timeout=timeout)) as nf: idata = html.fromstring(nf.read()) - + price = ''.join(idata.xpath('//div[@id="content"]//td[contains(text(), "ePub")]/text()')) if not price: price = ''.join(idata.xpath('//div[@id="content"]//td[contains(text(), "MOBI")]/text()')) @@ -79,7 +80,7 @@ class BeWriteStore(BasicStoreConfig, StorePlugin): price = ''.join(idata.xpath('//div[@id="content"]//td[contains(text(), "PDF")]/text()')) price = '$' + price.split('$')[-1] search_result.price = price.strip() - + cover_img = idata.xpath('//div[@id="content"]//img/@src') if cover_img: for i in cover_img: @@ -87,7 +88,7 @@ class BeWriteStore(BasicStoreConfig, StorePlugin): cover_url = 'http://www.bewrite.net/mm5/' + i search_result.cover_url = cover_url.strip() break - + formats = set([]) if idata.xpath('boolean(//div[@id="content"]//td[contains(text(), "ePub")])'): formats.add('EPUB') diff --git a/src/calibre/gui2/store/stores/biblio_plugin.py b/src/calibre/gui2/store/stores/biblio_plugin.py index 5a40ec57cc..db7d909b3b 100644 --- a/src/calibre/gui2/store/stores/biblio_plugin.py +++ b/src/calibre/gui2/store/stores/biblio_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2012, Alex Stanev ' @@ -26,7 +27,7 @@ class BiblioStore(BasicStoreConfig, OpenSearchOPDSStore): for s in OpenSearchOPDSStore.search(self, query, max_results, timeout): yield s - + def get_details(self, search_result, timeout): # get format and DRM status from calibre import browser @@ -39,13 +40,13 @@ class BiblioStore(BasicStoreConfig, OpenSearchOPDSStore): search_result.formats = '' if idata.xpath('.//span[@class="format epub"]'): search_result.formats = 'EPUB' - + if idata.xpath('.//span[@class="format pdf"]'): if search_result.formats == '': search_result.formats = 'PDF' else: search_result.formats.join(', PDF') - + if idata.xpath('.//span[@class="format nodrm-icon"]'): search_result.drm = SearchResult.DRM_UNLOCKED else: diff --git a/src/calibre/gui2/store/stores/bn_plugin.py b/src/calibre/gui2/store/stores/bn_plugin.py index 8f2f988974..6138181fde 100644 --- a/src/calibre/gui2/store/stores/bn_plugin.py +++ b/src/calibre/gui2/store/stores/bn_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/bookoteka_plugin.py b/src/calibre/gui2/store/stores/bookoteka_plugin.py index 4df22060ed..7c3b2e8242 100644 --- a/src/calibre/gui2/store/stores/bookoteka_plugin.py +++ b/src/calibre/gui2/store/stores/bookoteka_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/chitanka_plugin.py b/src/calibre/gui2/store/stores/chitanka_plugin.py index 58ef109dba..30fc543849 100644 --- a/src/calibre/gui2/store/stores/chitanka_plugin.py +++ b/src/calibre/gui2/store/stores/chitanka_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Alex Stanev ' diff --git a/src/calibre/gui2/store/stores/diesel_ebooks_plugin.py b/src/calibre/gui2/store/stores/diesel_ebooks_plugin.py index 309ee98e4c..eebd1376ba 100644 --- a/src/calibre/gui2/store/stores/diesel_ebooks_plugin.py +++ b/src/calibre/gui2/store/stores/diesel_ebooks_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/ebook_nl_plugin.py b/src/calibre/gui2/store/stores/ebook_nl_plugin.py index 0a79026dbb..6f895f1325 100644 --- a/src/calibre/gui2/store/stores/ebook_nl_plugin.py +++ b/src/calibre/gui2/store/stores/ebook_nl_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/ebookpoint_plugin.py b/src/calibre/gui2/store/stores/ebookpoint_plugin.py index 94e6cc73ca..d0306a45ee 100644 --- a/src/calibre/gui2/store/stores/ebookpoint_plugin.py +++ b/src/calibre/gui2/store/stores/ebookpoint_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011-2012, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/ebooks_com_plugin.py b/src/calibre/gui2/store/stores/ebooks_com_plugin.py index 826b59d41d..5c901bd65e 100644 --- a/src/calibre/gui2/store/stores/ebooks_com_plugin.py +++ b/src/calibre/gui2/store/stores/ebooks_com_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/ebooksgratuits_plugin.py b/src/calibre/gui2/store/stores/ebooksgratuits_plugin.py index add4bb2d40..2f13e0be86 100644 --- a/src/calibre/gui2/store/stores/ebooksgratuits_plugin.py +++ b/src/calibre/gui2/store/stores/ebooksgratuits_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2012, Florent FAYOLLE ' diff --git a/src/calibre/gui2/store/stores/ebookshoppe_uk_plugin.py b/src/calibre/gui2/store/stores/ebookshoppe_uk_plugin.py index 804279d3fd..77801d8584 100644 --- a/src/calibre/gui2/store/stores/ebookshoppe_uk_plugin.py +++ b/src/calibre/gui2/store/stores/ebookshoppe_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/eharlequin_plugin.py b/src/calibre/gui2/store/stores/eharlequin_plugin.py index ec85ccf1d3..5c863af856 100644 --- a/src/calibre/gui2/store/stores/eharlequin_plugin.py +++ b/src/calibre/gui2/store/stores/eharlequin_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -36,9 +37,9 @@ class EHarlequinStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://ebooks.eharlequin.com/BANGSearch.dll?Type=FullText&FullTextField=All&FullTextCriteria=' + urllib2.quote(query) - + br = browser() - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) @@ -64,19 +65,19 @@ class EHarlequinStore(BasicStoreConfig, StorePlugin): s.price = price.strip() s.detail_item = 'http://ebooks.eharlequin.com/' + id.strip() s.formats = 'EPUB' - + yield s - + def get_details(self, search_result, timeout): url = 'http://ebooks.eharlequin.com/en/ContentDetails.htm?ID=' - + mo = re.search(r'\?ID=(?P.+)', search_result.detail_item) if mo: id = mo.group('id') if not id: return - - + + br = browser() with closing(br.open(url + id, timeout=timeout)) as nf: idata = html.fromstring(nf.read()) diff --git a/src/calibre/gui2/store/stores/eknigi_plugin.py b/src/calibre/gui2/store/stores/eknigi_plugin.py index 7d88465f62..7cafba421e 100644 --- a/src/calibre/gui2/store/stores/eknigi_plugin.py +++ b/src/calibre/gui2/store/stores/eknigi_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Alex Stanev ' diff --git a/src/calibre/gui2/store/stores/empik_plugin.py b/src/calibre/gui2/store/stores/empik_plugin.py index 16a7ee13e3..08b1cdcb64 100644 --- a/src/calibre/gui2/store/stores/empik_plugin.py +++ b/src/calibre/gui2/store/stores/empik_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011-2012, Tomasz Długosz ' @@ -68,7 +69,7 @@ class EmpikStore(BasicStoreConfig, StorePlugin): counter -= 1 s = SearchResult() - s.cover_url = cover_url + s.cover_url = cover_url s.title = title.strip() + ' ' + formats s.author = author.strip() s.price = price diff --git a/src/calibre/gui2/store/stores/escapemagazine_plugin.py b/src/calibre/gui2/store/stores/escapemagazine_plugin.py index 7f3f24e7d6..e3b1ef335a 100644 --- a/src/calibre/gui2/store/stores/escapemagazine_plugin.py +++ b/src/calibre/gui2/store/stores/escapemagazine_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/feedbooks_plugin.py b/src/calibre/gui2/store/stores/feedbooks_plugin.py index cac44fd8df..36521406bb 100644 --- a/src/calibre/gui2/store/stores/feedbooks_plugin.py +++ b/src/calibre/gui2/store/stores/feedbooks_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -11,10 +12,10 @@ from calibre.gui2.store.opensearch_store import OpenSearchOPDSStore from calibre.gui2.store.search_result import SearchResult class FeedbooksStore(BasicStoreConfig, OpenSearchOPDSStore): - + open_search_url = 'http://assets0.feedbooks.net/opensearch.xml?t=1253087147' web_url = 'http://feedbooks.com/' - + # http://www.feedbooks.com/catalog def search(self, query, max_results=10, timeout=60): diff --git a/src/calibre/gui2/store/stores/foyles_uk_plugin.py b/src/calibre/gui2/store/stores/foyles_uk_plugin.py index 819c412758..7c224f4f70 100644 --- a/src/calibre/gui2/store/stores/foyles_uk_plugin.py +++ b/src/calibre/gui2/store/stores/foyles_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/google_books_plugin.py b/src/calibre/gui2/store/stores/google_books_plugin.py index 6ffeab517c..f6f91fd81d 100644 --- a/src/calibre/gui2/store/stores/google_books_plugin.py +++ b/src/calibre/gui2/store/stores/google_books_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -38,7 +39,7 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin): 'ganpub': 'k352583', 'ganclk': 'GOOG_1335335464', } - + url = 'http://gan.doubleclick.net/gan_click?lid=%(lid)s&pubid=%(pubid)s' % aff_id if detail_item: detail_item += '&ganpub=%(ganpub)s&ganclk=%(ganclk)s' % aff_id @@ -53,9 +54,9 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://www.google.com/search?tbm=bks&q=' + urllib.quote_plus(query) - + br = browser() - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) @@ -76,22 +77,22 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin): author = ', '.join(authors) counter -= 1 - + s = SearchResult() s.title = title.strip() s.author = author.strip() s.detail_item = id.strip() s.drm = SearchResult.DRM_UNKNOWN - + yield s - + def get_details(self, search_result, timeout): br = browser() with closing(br.open(search_result.detail_item, timeout=timeout)) as nf: doc = html.fromstring(nf.read()) - + search_result.cover_url = ''.join(doc.xpath('//div[@class="sidebarcover"]//img/@src')) - + # Try to get the set price. price = ''.join(doc.xpath('//div[@id="gb-get-book-container"]//a/text()')) if 'read' in price.lower(): @@ -101,10 +102,10 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin): elif '-' in price: a, b, price = price.partition(' - ') search_result.price = price.strip() - + search_result.formats = ', '.join(doc.xpath('//div[contains(@class, "download-panel-div")]//a/text()')).upper() if not search_result.formats: search_result.formats = _('Unknown') - + return True diff --git a/src/calibre/gui2/store/stores/gutenberg_plugin.py b/src/calibre/gui2/store/stores/gutenberg_plugin.py index cbf3a2f565..422165f263 100644 --- a/src/calibre/gui2/store/stores/gutenberg_plugin.py +++ b/src/calibre/gui2/store/stores/gutenberg_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/kobo_plugin.py b/src/calibre/gui2/store/stores/kobo_plugin.py index 5a8b5618d5..44f4f4001c 100644 --- a/src/calibre/gui2/store/stores/kobo_plugin.py +++ b/src/calibre/gui2/store/stores/kobo_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/legimi_plugin.py b/src/calibre/gui2/store/stores/legimi_plugin.py index 509ca88104..85561c63f3 100644 --- a/src/calibre/gui2/store/stores/legimi_plugin.py +++ b/src/calibre/gui2/store/stores/legimi_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' @@ -24,7 +25,7 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog class LegimiStore(BasicStoreConfig, StorePlugin): def open(self, parent=None, detail_item=None, external=False): - + plain_url = 'http://www.legimi.com/pl/ebooki/' url = 'https://ssl.afiliant.com/affskrypt,,2f9de2,,11483,,,?u=(' + plain_url + ')' detail_url = None @@ -42,17 +43,17 @@ class LegimiStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://www.legimi.com/pl/ebooki/?szukaj=' + urllib.quote_plus(query) - + br = browser() drm_pattern = re.compile("zabezpieczona DRM") - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) for data in doc.xpath('//div[@id="listBooks"]/div'): if counter <= 0: break - + id = ''.join(data.xpath('.//a[@class="plainLink"]/@href')) if not id: continue @@ -73,7 +74,7 @@ class LegimiStore(BasicStoreConfig, StorePlugin): drm = drm_pattern.search(''.join(idata.xpath('.//div[@id="fullBookFormats"]/p/text()'))) counter -= 1 - + s = SearchResult() s.cover_url = 'http://www.legimi.com/' + cover_url s.title = title.strip() @@ -82,5 +83,5 @@ class LegimiStore(BasicStoreConfig, StorePlugin): s.detail_item = 'http://www.legimi.com/' + id.strip() s.formats = ', '.join(formats) s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED - + yield s diff --git a/src/calibre/gui2/store/stores/libri_de_plugin.py b/src/calibre/gui2/store/stores/libri_de_plugin.py index 60f7471272..d7d0807a99 100644 --- a/src/calibre/gui2/store/stores/libri_de_plugin.py +++ b/src/calibre/gui2/store/stores/libri_de_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/litres_plugin.py b/src/calibre/gui2/store/stores/litres_plugin.py index 6f4c386dda..5a1d2271fe 100644 --- a/src/calibre/gui2/store/stores/litres_plugin.py +++ b/src/calibre/gui2/store/stores/litres_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Roman Mukhin ' diff --git a/src/calibre/gui2/store/stores/manybooks_plugin.py b/src/calibre/gui2/store/stores/manybooks_plugin.py index 2b06798630..2344193b47 100644 --- a/src/calibre/gui2/store/stores/manybooks_plugin.py +++ b/src/calibre/gui2/store/stores/manybooks_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -26,7 +27,7 @@ class ManyBooksStore(BasicStoreConfig, OpenSearchOPDSStore): def search(self, query, max_results=10, timeout=60): ''' Manybooks uses a very strange opds feed. The opds - main feed is structured like a stanza feed. The + main feed is structured like a stanza feed. The search result entries give very little information and requires you to go to a detail link. The detail link has the wrong type specified (text/html instead @@ -45,7 +46,7 @@ class ManyBooksStore(BasicStoreConfig, OpenSearchOPDSStore): oquery.searchTerms = query oquery.count = max_results url = oquery.url() - + counter = max_results br = browser() with closing(br.open(url, timeout=timeout)) as f: @@ -55,11 +56,11 @@ class ManyBooksStore(BasicStoreConfig, OpenSearchOPDSStore): for data in doc.xpath('//*[local-name() = "entry"]'): if counter <= 0: break - + counter -= 1 - + s = SearchResult() - + detail_links = data.xpath('./*[local-name() = "link" and @type = "text/html"]') if not detail_links: continue @@ -73,7 +74,7 @@ class ManyBooksStore(BasicStoreConfig, OpenSearchOPDSStore): # just in case. s.title = ''.join(data.xpath('./*[local-name() = "title"]//text()')).strip() s.author = ', '.join(data.xpath('./*[local-name() = "author"]//text()')).strip() - + # Follow the detail link to get the rest of the info. with closing(br.open(detail_href, timeout=timeout/4)) as df: ddoc = etree.fromstring(df.read()) @@ -89,9 +90,9 @@ class ManyBooksStore(BasicStoreConfig, OpenSearchOPDSStore): s.author = s.author[1:] if s.author.endswith(','): s.author = s.author[:-1] - + s.cover_url = ''.join(ddata.xpath('./*[local-name() = "link" and @rel = "http://opds-spec.org/thumbnail"][1]/@href')).strip() - + for link in ddata.xpath('./*[local-name() = "link" and @rel = "http://opds-spec.org/acquisition"]'): type = link.get('type') href = link.get('href') diff --git a/src/calibre/gui2/store/stores/mills_boon_uk_plugin.py b/src/calibre/gui2/store/stores/mills_boon_uk_plugin.py index 6aa4b4b0b7..b8969beaed 100644 --- a/src/calibre/gui2/store/stores/mills_boon_uk_plugin.py +++ b/src/calibre/gui2/store/stores/mills_boon_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/nexto_plugin.py b/src/calibre/gui2/store/stores/nexto_plugin.py index 79cb1be2f1..e5f9e31980 100644 --- a/src/calibre/gui2/store/stores/nexto_plugin.py +++ b/src/calibre/gui2/store/stores/nexto_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011-2012, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/nook_uk_plugin.py b/src/calibre/gui2/store/stores/nook_uk_plugin.py index 1ff8b688bb..cc97d5cf93 100644 --- a/src/calibre/gui2/store/stores/nook_uk_plugin.py +++ b/src/calibre/gui2/store/stores/nook_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2012, John Schember ' diff --git a/src/calibre/gui2/store/stores/open_books_plugin.py b/src/calibre/gui2/store/stores/open_books_plugin.py index 99b68656e9..66642ab679 100644 --- a/src/calibre/gui2/store/stores/open_books_plugin.py +++ b/src/calibre/gui2/store/stores/open_books_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/ozon_ru_plugin.py b/src/calibre/gui2/store/stores/ozon_ru_plugin.py index b54bf01daf..9a3c2dabaa 100644 --- a/src/calibre/gui2/store/stores/ozon_ru_plugin.py +++ b/src/calibre/gui2/store/stores/ozon_ru_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Roman Mukhin ' @@ -24,33 +25,33 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog class OzonRUStore(BasicStoreConfig, StorePlugin): shop_url = 'http://www.ozon.ru' - + def open(self, parent=None, detail_item=None, external=False): - + aff_id = '?partner=romuk' # Use Kovid's affiliate id 30% of the time. if random.randint(1, 10) in (1, 2, 3): aff_id = '?partner=kovidgoyal' - + url = self.shop_url + aff_id detail_url = None if detail_item: # http://www.ozon.ru/context/detail/id/3037277/ detail_url = self.shop_url + '/context/detail/id/' + urllib2.quote(detail_item) + aff_id - + if external or self.config.get('open_external', False): open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url))) else: d = WebStoreDialog(self.gui, url, parent, detail_url) d.setWindowTitle(self.name) d.set_tags(self.config.get('tags', '')) - d.exec_() - + d.exec_() + def search(self, query, max_results=15, timeout=60): search_url = self.shop_url + '/webservice/webservice.asmx/SearchWebService?'\ 'searchText=%s&searchContext=ebook' % urllib2.quote(query) search_urls = [ search_url ] - + ## add this as the fist try if it looks like ozon ID if re.match("^\d{6,9}$", query): ozon_detail = self.shop_url + '/webservices/OzonWebSvc.asmx/ItemDetail?ID=%s' % query @@ -59,7 +60,7 @@ class OzonRUStore(BasicStoreConfig, StorePlugin): xp_template = 'normalize-space(./*[local-name() = "{0}"]/text())' counter = max_results br = browser() - + for url in search_urls: with closing(br.open(url, timeout=timeout)) as f: raw = xml_to_unicode(f.read(), strip_encoding_pats=True, assume_utf8=True)[0] @@ -86,10 +87,10 @@ class OzonRUStore(BasicStoreConfig, StorePlugin): with closing(br.open(url, timeout=timeout)) as f: raw = xml_to_unicode(f.read(), verbose=True)[0] doc = html.fromstring(raw) - + # example where we are going to find formats #
- #

+ #

# Доступно: #

#
@@ -104,16 +105,16 @@ class OzonRUStore(BasicStoreConfig, StorePlugin): search_result.formats = ', '.join(_parse_ebook_formats(formats)) # unfortunately no direct links to download books (only buy link) # search_result.downloads['BF2'] = self.shop_url + '/order/digitalorder.aspx?id=' + + urllib2.quote(search_result.detail_item) - + #

21500 руб.

# # - + # if the price not in the search result (the ID search case) if not search_result.price: price = doc.xpath(u'normalize-space(//*[@itemprop="price"]/text())') search_result.price = format_price_in_RUR(price) - + return result def format_price_in_RUR(price): @@ -134,12 +135,12 @@ def format_price_in_RUR(price): def _parse_ebook_formats(formatsStr): ''' Creates a list with displayable names of the formats - - :param formatsStr: string with comma separated book formats + + :param formatsStr: string with comma separated book formats as it provided by ozon.ru :return: a list with displayable book formats ''' - + formatsUnstruct = formatsStr.lower() formats = [] if 'epub' in formatsUnstruct: diff --git a/src/calibre/gui2/store/stores/pragmatic_bookshelf_plugin.py b/src/calibre/gui2/store/stores/pragmatic_bookshelf_plugin.py index 99b94778bf..544fa06fe8 100644 --- a/src/calibre/gui2/store/stores/pragmatic_bookshelf_plugin.py +++ b/src/calibre/gui2/store/stores/pragmatic_bookshelf_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -14,7 +15,7 @@ class PragmaticBookshelfStore(BasicStoreConfig, OpenSearchOPDSStore): open_search_url = 'http://pragprog.com/catalog/search-description' web_url = 'http://pragprog.com/' - + # http://pragprog.com/catalog.opds def search(self, query, max_results=10, timeout=60): diff --git a/src/calibre/gui2/store/stores/publio_plugin.py b/src/calibre/gui2/store/stores/publio_plugin.py index eb00f231ea..44f3267b35 100644 --- a/src/calibre/gui2/store/stores/publio_plugin.py +++ b/src/calibre/gui2/store/stores/publio_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2012, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/rw2010_plugin.py b/src/calibre/gui2/store/stores/rw2010_plugin.py index ed4d5a53f7..fc86ae4967 100644 --- a/src/calibre/gui2/store/stores/rw2010_plugin.py +++ b/src/calibre/gui2/store/stores/rw2010_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' @@ -73,5 +74,5 @@ class RW2010Store(BasicStoreConfig, StorePlugin): s.detail_item = re.sub(r'%3D', '=', id) s.drm = SearchResult.DRM_UNLOCKED s.formats = formats[0:-2].upper() - + yield s diff --git a/src/calibre/gui2/store/stores/smashwords_plugin.py b/src/calibre/gui2/store/stores/smashwords_plugin.py index 983067ab51..580e3c2907 100644 --- a/src/calibre/gui2/store/stores/smashwords_plugin.py +++ b/src/calibre/gui2/store/stores/smashwords_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -36,7 +37,7 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): if detail_item: detail_url = url + detail_item + aff_id url = url + aff_id - + if external or self.config.get('open_external', False): open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url))) else: @@ -47,9 +48,9 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://www.smashwords.com/books/search?query=' + urllib2.quote(query) - + br = browser() - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) @@ -57,7 +58,7 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): if counter <= 0: break data = html.fromstring(html.tostring(data)) - + id = None id_a = data.xpath('//a[@class="bookTitle"]') if id_a: @@ -66,14 +67,14 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): id = id.split('/')[-1] if not id: continue - + cover_url = '' c_url = data.get('style', None) if c_url: mo = re.search(r'http://[^\'"]+', c_url) if mo: cover_url = mo.group() - + title = ''.join(data.xpath('//a[@class="bookTitle"]/text()')) subnote = ''.join(data.xpath('//span[@class="subnote"]/text()')) author = ''.join(data.xpath('//span[@class="subnote"]//a[1]//text()')) @@ -85,7 +86,7 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): price = '$0.00' counter -= 1 - + s = SearchResult() s.cover_url = cover_url s.title = title.strip() @@ -93,12 +94,12 @@ class SmashwordsStore(BasicStoreConfig, StorePlugin): s.price = price.strip() s.detail_item = '/books/view/' + id.strip() s.drm = SearchResult.DRM_UNLOCKED - + yield s def get_details(self, search_result, timeout): url = 'http://www.smashwords.com/' - + br = browser() with closing(br.open(url + search_result.detail_item, timeout=timeout)) as nf: idata = html.fromstring(nf.read()) diff --git a/src/calibre/gui2/store/stores/sony_plugin.py b/src/calibre/gui2/store/stores/sony_plugin.py index aa0c65bcde..030919c925 100644 --- a/src/calibre/gui2/store/stores/sony_plugin.py +++ b/src/calibre/gui2/store/stores/sony_plugin.py @@ -2,6 +2,7 @@ # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL v3' __copyright__ = '2012, Kovid Goyal ' diff --git a/src/calibre/gui2/store/stores/virtualo_plugin.py b/src/calibre/gui2/store/stores/virtualo_plugin.py index e6b60fbe91..02396b7f19 100644 --- a/src/calibre/gui2/store/stores/virtualo_plugin.py +++ b/src/calibre/gui2/store/stores/virtualo_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/waterstones_uk_plugin.py b/src/calibre/gui2/store/stores/waterstones_uk_plugin.py index df17372d0a..397b8ee53f 100644 --- a/src/calibre/gui2/store/stores/waterstones_uk_plugin.py +++ b/src/calibre/gui2/store/stores/waterstones_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/weightless_books_plugin.py b/src/calibre/gui2/store/stores/weightless_books_plugin.py index 330f3fdf0f..cc18cf5807 100644 --- a/src/calibre/gui2/store/stores/weightless_books_plugin.py +++ b/src/calibre/gui2/store/stores/weightless_books_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -35,9 +36,9 @@ class WeightlessBooksStore(BasicStoreConfig, StorePlugin): def search(self, query, max_results=10, timeout=60): url = 'http://weightlessbooks.com/?s=' + urllib.quote_plus(query) - + br = browser() - + counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) @@ -50,20 +51,20 @@ class WeightlessBooksStore(BasicStoreConfig, StorePlugin): continue cover_url = ''.join(data.xpath('.//div[@class="cover"]/a/img/@src')) - + price = ''.join(data.xpath('.//div[@class="buy_buttons"]/b[1]/text()')) if not price: continue - + formats = ', '.join(data.xpath('.//select[@class="eStore_variation"]//option//text()')) formats = formats.upper() - + title = ''.join(data.xpath('.//h3/a/text()')) author = ''.join(data.xpath('.//h3//text()')) author = author.replace(title, '') counter -= 1 - + s = SearchResult() s.cover_url = cover_url s.title = title.strip() @@ -72,5 +73,5 @@ class WeightlessBooksStore(BasicStoreConfig, StorePlugin): s.detail_item = id.strip() s.drm = SearchResult.DRM_UNLOCKED s.formats = formats - + yield s diff --git a/src/calibre/gui2/store/stores/whsmith_uk_plugin.py b/src/calibre/gui2/store/stores/whsmith_uk_plugin.py index 5d78340517..6f2de93523 100644 --- a/src/calibre/gui2/store/stores/whsmith_uk_plugin.py +++ b/src/calibre/gui2/store/stores/whsmith_uk_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' diff --git a/src/calibre/gui2/store/stores/woblink_plugin.py b/src/calibre/gui2/store/stores/woblink_plugin.py index 37861766f7..63ec259dbf 100644 --- a/src/calibre/gui2/store/stores/woblink_plugin.py +++ b/src/calibre/gui2/store/stores/woblink_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011-2012, Tomasz Długosz ' diff --git a/src/calibre/gui2/store/stores/xinxii_plugin.py b/src/calibre/gui2/store/stores/xinxii_plugin.py index e8721a79b8..4be0a410be 100644 --- a/src/calibre/gui2/store/stores/xinxii_plugin.py +++ b/src/calibre/gui2/store/stores/xinxii_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, John Schember ' @@ -20,25 +21,25 @@ class XinXiiStore(BasicStoreConfig, OpenSearchOPDSStore): open_search_url = 'http://www.xinxii.com/catalog-search/' web_url = 'http://xinxii.com/' - + # http://www.xinxii.com/catalog/ def search(self, query, max_results=10, timeout=60): ''' XinXii's open search url is: http://www.xinxii.com/catalog-search/query/?keywords={searchTerms}&pw={startPage?}&doc_lang={docLang}&ff={docFormat},{docFormat},{docFormat} - + This url requires the docLang and docFormat. However, the search itself sent to XinXii does not require them. They can be ignored. We cannot push this into the stanard OpenSearchOPDSStore search because of the required attributes. - + XinXii doesn't return all info supported by OpenSearchOPDSStore search function so this one is modified to remove parts that are used. ''' - + url = 'http://www.xinxii.com/catalog-search/query/?keywords=' + urllib.quote_plus(query) - + counter = max_results br = browser() with closing(br.open(url, timeout=timeout)) as f: @@ -46,29 +47,29 @@ class XinXiiStore(BasicStoreConfig, OpenSearchOPDSStore): for data in doc.xpath('//*[local-name() = "entry"]'): if counter <= 0: break - + counter -= 1 - + s = SearchResult() - + s.detail_item = ''.join(data.xpath('./*[local-name() = "id"]/text()')).strip() for link in data.xpath('./*[local-name() = "link"]'): rel = link.get('rel') href = link.get('href') type = link.get('type') - + if rel and href and type: if rel in ('http://opds-spec.org/thumbnail', 'http://opds-spec.org/image/thumbnail'): s.cover_url = href if rel == 'alternate': s.detail_item = href - + s.formats = 'EPUB, PDF' - + s.title = ' '.join(data.xpath('./*[local-name() = "title"]//text()')).strip() s.author = ', '.join(data.xpath('./*[local-name() = "author"]//*[local-name() = "name"]//text()')).strip() - + price_e = data.xpath('.//*[local-name() = "price"][1]') if price_e: price_e = price_e[0] @@ -76,6 +77,6 @@ class XinXiiStore(BasicStoreConfig, OpenSearchOPDSStore): price = ''.join(price_e.xpath('.//text()')).strip() s.price = currency_code + ' ' + price s.price = s.price.strip() - + yield s diff --git a/src/calibre/gui2/store/stores/zixo_plugin.py b/src/calibre/gui2/store/stores/zixo_plugin.py index b4e54736c0..98bbdf3155 100644 --- a/src/calibre/gui2/store/stores/zixo_plugin.py +++ b/src/calibre/gui2/store/stores/zixo_plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import (unicode_literals, division, absolute_import, print_function) +store_version = 1 # Needed for dynamic plugin loading __license__ = 'GPL 3' __copyright__ = '2011, Tomasz Długosz ' From ec9f72615b96980638949c169ea638c98fccd1f7 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 14 Jan 2013 15:29:24 +0100 Subject: [PATCH 05/13] More fixes for the custom series index is None problem, this time not to blow up in edit metadata single. --- src/calibre/gui2/custom_column_widgets.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index f4c92529e3..438a38a75e 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -383,12 +383,12 @@ class Series(Base): values = list(self.db.all_custom(num=self.col_id)) values.sort(key=sort_key) val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) - s_index = self.db.get_custom_extra(book_id, num=self.col_id, index_is_id=True) - if s_index is None: - s_index = 0.0 - self.idx_widget.setValue(s_index) - self.initial_index = s_index self.initial_val = val + s_index = self.db.get_custom_extra(book_id, num=self.col_id, index_is_id=True) + self.initial_index = s_index + if s_index is None or not isinstance(s_index, float): + s_index = 1.0 + self.idx_widget.setValue(s_index) val = self.normalize_db_val(val) self.name_widget.blockSignals(True) self.name_widget.update_items_cache(values) From bf34daf4006e1f1342e5ecffc8df5944445e15c8 Mon Sep 17 00:00:00 2001 From: GRiker Date: Mon, 14 Jan 2013 08:25:29 -0700 Subject: [PATCH 06/13] Added calibre:uuid to EXTH:112 (source) --- src/calibre/ebooks/mobi/writer8/exth.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/calibre/ebooks/mobi/writer8/exth.py b/src/calibre/ebooks/mobi/writer8/exth.py index a060e338d1..31792d2156 100644 --- a/src/calibre/ebooks/mobi/writer8/exth.py +++ b/src/calibre/ebooks/mobi/writer8/exth.py @@ -110,6 +110,12 @@ def build_exth(metadata, prefer_author_sort=False, is_periodical=False, exth.write(uuid) nrecs += 1 + # Write UUID as SOURCE + c_uuid = b'calibre:%s' % uuid + exth.write(pack(b'>II', 112, len(c_uuid) + 8)) + exth.write(c_uuid) + nrecs += 1 + # Write cdetype if not is_periodical: if not share_not_sync: From d25aa48213843dece646546dd648c2ed6e7ce2ee Mon Sep 17 00:00:00 2001 From: GRiker Date: Mon, 14 Jan 2013 13:41:21 -0700 Subject: [PATCH 07/13] Write EXTH 112 when exporting MOBI --- src/calibre/ebooks/metadata/mobi.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/calibre/ebooks/metadata/mobi.py b/src/calibre/ebooks/metadata/mobi.py index e701946c01..8797cc67b1 100644 --- a/src/calibre/ebooks/metadata/mobi.py +++ b/src/calibre/ebooks/metadata/mobi.py @@ -390,6 +390,8 @@ class MetadataUpdater(object): not added_501 and not share_not_sync): from uuid import uuid4 update_exth_record((113, str(uuid4()))) + # Add a 112 record with actual UUID + update_exth_record((112, str("calibre:%s" % mi.uuid))) if 503 in self.original_exth_records: update_exth_record((503, mi.title.encode(self.codec, 'replace'))) From 4e2b9b895d83b4d201f056c0ce666fa4e599e879 Mon Sep 17 00:00:00 2001 From: Daniele Pizzolli Date: Mon, 14 Jan 2013 21:45:45 +0100 Subject: [PATCH 08/13] Add support for Pocket Book Pro 912 --- src/calibre/customize/profiles.py | 16 ++++++++++++++-- src/calibre/devices/eb600/driver.py | 4 ++-- src/calibre/gui2/wizard/__init__.py | 7 +++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/calibre/customize/profiles.py b/src/calibre/customize/profiles.py index 00ec6294fb..7eba099bd2 100644 --- a/src/calibre/customize/profiles.py +++ b/src/calibre/customize/profiles.py @@ -770,13 +770,25 @@ class PocketBook900Output(OutputProfile): dpi = 150.0 comic_screen_size = screen_size +class PocketBookPro912Output(OutputProfile): + + author = 'Daniele Pizzolli' + name = 'PocketBook Pro 912' + short_name = 'pocketbook_pro_912' + description = _('This profile is intended for the PocketBook Pro 912 series of devices.') + + # According to http://download.pocketbook-int.com/user-guides/E_Ink/912/User_Guide_PocketBook_912(EN).pdf + screen_size = (825, 1200) + dpi = 155.0 + comic_screen_size = screen_size + output_profiles = [OutputProfile, SonyReaderOutput, SonyReader300Output, SonyReader900Output, MSReaderOutput, MobipocketOutput, HanlinV3Output, HanlinV5Output, CybookG3Output, CybookOpusOutput, KindleOutput, iPadOutput, iPad3Output, KoboReaderOutput, TabletOutput, SamsungGalaxy, SonyReaderLandscapeOutput, KindleDXOutput, IlliadOutput, IRexDR1000Output, IRexDR800Output, JetBook5Output, NookOutput, - BambookOutput, NookColorOutput, PocketBook900Output, GenericEink, - GenericEinkLarge, KindleFireOutput, KindlePaperWhiteOutput] + BambookOutput, NookColorOutput, PocketBook900Output, PocketBookPro912Output, + GenericEink, GenericEinkLarge, KindleFireOutput, KindlePaperWhiteOutput] output_profiles.sort(cmp=lambda x,y:cmp(x.name.lower(), y.name.lower())) diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py index 04501d193a..045eb2b4b7 100644 --- a/src/calibre/devices/eb600/driver.py +++ b/src/calibre/devices/eb600/driver.py @@ -234,7 +234,7 @@ class POCKETBOOK301(USBMS): class POCKETBOOK602(USBMS): name = 'PocketBook Pro 602/902 Device Interface' - description = _('Communicate with the PocketBook 602/603/902/903 reader.') + description = _('Communicate with the PocketBook 602/603/902/903/Pro 912 reader.') author = 'Kovid Goyal' supported_platforms = ['windows', 'osx', 'linux'] FORMATS = ['epub', 'fb2', 'prc', 'mobi', 'pdf', 'djvu', 'rtf', 'chm', @@ -249,7 +249,7 @@ class POCKETBOOK602(USBMS): VENDOR_NAME = '' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['PB602', 'PB603', 'PB902', - 'PB903', 'PB'] + 'PB903', 'Pocket912', 'PB'] class POCKETBOOK622(POCKETBOOK602): diff --git a/src/calibre/gui2/wizard/__init__.py b/src/calibre/gui2/wizard/__init__.py index 208a986888..1cdcb85d4c 100644 --- a/src/calibre/gui2/wizard/__init__.py +++ b/src/calibre/gui2/wizard/__init__.py @@ -245,6 +245,13 @@ class PocketBook900(PocketBook): id = 'pocketbook900' output_profile = 'pocketbook_900' +class PocketBookPro912(PocketBook): + + name = 'PocketBook Pro 912' + id = 'pocketbookpro912' + output_profile = 'pocketbook_pro_912' + + class iPhone(Device): name = 'iPhone/iTouch' From 7db6519471b60d269981f7a028a81c984311fb64 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 15 Jan 2013 08:40:18 +0530 Subject: [PATCH 09/13] Update The Chronicle of Higher Education --- recipes/chronicle_higher_ed.recipe | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/recipes/chronicle_higher_ed.recipe b/recipes/chronicle_higher_ed.recipe index 15b284cd7a..66b17cafcf 100644 --- a/recipes/chronicle_higher_ed.recipe +++ b/recipes/chronicle_higher_ed.recipe @@ -12,10 +12,10 @@ class Chronicle(BasicNewsRecipe): category = 'news' encoding = 'UTF-8' keep_only_tags = [ - dict(name='div', attrs={'class':'article'}), + dict(name='div', attrs={'class':['article','blog-mod']}), ] - remove_tags = [dict(name='div',attrs={'class':['related module1','maintitle']}), - dict(name='div', attrs={'id':['section-nav','icon-row', 'enlarge-popup']}), + remove_tags = [dict(name='div',attrs={'class':['related module1','maintitle','entry-utility','object-meta']}), + dict(name='div', attrs={'id':['section-nav','icon-row', 'enlarge-popup','confirm-popup']}), dict(name='a', attrs={'class':'show-enlarge enlarge'})] no_javascript = True no_stylesheets = True From c1c0099354fdf8a0746e13c2deeca84eee334120 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 15 Jan 2013 09:16:09 +0530 Subject: [PATCH 10/13] LIT Input: Handle lit files that set an incorrect XML mimetype for their text. Fixes #1099621 (problem converting .lit files) --- src/calibre/ebooks/lit/reader.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/calibre/ebooks/lit/reader.py b/src/calibre/ebooks/lit/reader.py index a673de87d7..98b230e5bb 100644 --- a/src/calibre/ebooks/lit/reader.py +++ b/src/calibre/ebooks/lit/reader.py @@ -11,13 +11,17 @@ import struct, os, functools, re from urlparse import urldefrag from cStringIO import StringIO from urllib import unquote as urlunquote + +from lxml import etree + from calibre.ebooks.lit import LitError from calibre.ebooks.lit.maps import OPF_MAP, HTML_MAP import calibre.ebooks.lit.mssha1 as mssha1 -from calibre.ebooks.oeb.base import urlnormalize +from calibre.ebooks.oeb.base import urlnormalize, xpath from calibre.ebooks.oeb.reader import OEBReader from calibre.ebooks import DRMError from calibre import plugins + lzx, lxzerror = plugins['lzx'] msdes, msdeserror = plugins['msdes'] @@ -907,3 +911,16 @@ class LitReader(OEBReader): Container = LitContainer DEFAULT_PROFILE = 'MSReader' + def _spine_from_opf(self, opf): + manifest = self.oeb.manifest + for elem in xpath(opf, '/o2:package/o2:spine/o2:itemref'): + idref = elem.get('idref') + if idref not in manifest.ids: + continue + item = manifest.ids[idref] + if (item.media_type.lower() == 'application/xml' and + hasattr(item.data, 'xpath') and item.data.xpath('/html')): + item.media_type = 'application/xhtml+xml' + item.data = item._parse_xhtml(etree.tostring(item.data)) + super(LitReader, self)._spine_from_opf(opf) + From 1d5e7897db744b1eac645bdc9628de13b2db1dd6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 15 Jan 2013 10:01:21 +0530 Subject: [PATCH 11/13] Dynamic loading for get books plugins --- src/calibre/gui2/actions/store.py | 14 +- src/calibre/gui2/store/__init__.py | 11 +- src/calibre/gui2/store/loader.py | 196 ++++++++++++++++++++++++ src/calibre/gui2/store/search/search.py | 1 + src/calibre/gui2/ui.py | 5 +- 5 files changed, 218 insertions(+), 9 deletions(-) create mode 100644 src/calibre/gui2/store/loader.py diff --git a/src/calibre/gui2/actions/store.py b/src/calibre/gui2/actions/store.py index adc66edea4..b84836c465 100644 --- a/src/calibre/gui2/actions/store.py +++ b/src/calibre/gui2/actions/store.py @@ -43,14 +43,16 @@ class StoreAction(InterfaceAction): icon.addFile(I('donate.png'), QSize(16, 16)) for n, p in sorted(self.gui.istores.items(), key=lambda x: x[0].lower()): if p.base_plugin.affiliate: - self.store_list_menu.addAction(icon, n, partial(self.open_store, p)) + self.store_list_menu.addAction(icon, n, + partial(self.open_store, n)) else: - self.store_list_menu.addAction(n, partial(self.open_store, p)) + self.store_list_menu.addAction(n, partial(self.open_store, n)) def do_search(self): return self.search() def search(self, query=''): + self.gui.istores.check_for_updates() self.show_disclaimer() from calibre.gui2.store.search.search import SearchDialog sd = SearchDialog(self.gui, self.gui, query) @@ -125,9 +127,13 @@ class StoreAction(InterfaceAction): self.gui.load_store_plugins() self.load_menu() - def open_store(self, store_plugin): + def open_store(self, store_plugin_name): + self.gui.istores.check_for_updates() self.show_disclaimer() - store_plugin.open(self.gui) + # It's not too important that the updated plugin have finished loading + # at this point + self.gui.istores.join(1.0) + self.gui.istores[store_plugin_name].open(self.gui) def show_disclaimer(self): confirm(('

' + diff --git a/src/calibre/gui2/store/__init__.py b/src/calibre/gui2/store/__init__.py index ae42d82032..3af0a14cda 100644 --- a/src/calibre/gui2/store/__init__.py +++ b/src/calibre/gui2/store/__init__.py @@ -49,13 +49,16 @@ class StorePlugin(object): # {{{ See declined.txt for a list of stores that do not want to be included. ''' - def __init__(self, gui, name): - from calibre.gui2 import JSONConfig + minimum_calibre_version = (0, 9, 14) + def __init__(self, gui, name, config=None, base_plugin=None): self.gui = gui self.name = name - self.base_plugin = None - self.config = JSONConfig('store/stores/' + ascii_filename(self.name)) + self.base_plugin = base_plugin + if config is None: + from calibre.gui2 import JSONConfig + config = JSONConfig('store/stores/' + ascii_filename(self.name)) + self.config = config def open(self, gui, parent=None, detail_item=None, external=False): ''' diff --git a/src/calibre/gui2/store/loader.py b/src/calibre/gui2/store/loader.py new file mode 100644 index 0000000000..c0769991dc --- /dev/null +++ b/src/calibre/gui2/store/loader.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2013, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import sys, time, io, re +from zlib import decompressobj +from collections import OrderedDict +from threading import Thread +from urllib import urlencode + +from calibre import prints, browser +from calibre.constants import numeric_version, DEBUG +from calibre.gui2.store import StorePlugin +from calibre.utils.config import JSONConfig + +class VersionMismatch(ValueError): + def __init__(self, ver): + ValueError.__init__(self, 'calibre too old') + self.ver = ver + +def download_updates(ver_map={}, server='http://status.calibre-ebook.com'): + data = {k:type(u'')(v) for k, v in ver_map.iteritems()} + data['ver'] = '1' + url = '%s/stores?%s'%(server, urlencode(data)) + br = browser() + # We use a timeout here to ensure the non-daemonic update thread does not + # cause calibre to hang indefinitely during shutdown + raw = br.open(url, timeout=4.0).read() + + while raw: + name, raw = raw.partition(b'\0')[0::2] + name = name.decode('utf-8') + d = decompressobj() + src = d.decompress(raw) + src = src.decode('utf-8') + # Python complains if there is a coding declaration in a unicode string + src = re.sub(r'^#.*coding\s*[:=]\s*([-\w.]+)', '#', src, flags=re.MULTILINE) + # Translate newlines to \n + src = io.StringIO(src, newline=None).getvalue() + yield name, src + raw = d.unused_data + +class Stores(OrderedDict): + + CHECK_INTERVAL = 24 * 60 * 60 + + def builtins_loaded(self): + self.last_check_time = 0 + self.version_map = {} + self.cached_version_map = {} + self.name_rmap = {} + for key, val in self.iteritems(): + prefix, name = val.__module__.rpartition('.')[0::2] + if prefix == 'calibre.gui2.store.stores' and name.endswith('_plugin'): + module = sys.modules[val.__module__] + sv = getattr(module, 'store_version', None) + if sv is not None: + name = name.rpartition('_')[0] + self.version_map[name] = sv + self.name_rmap[name] = key + self.cache_file = JSONConfig('store/plugin_cache') + self.load_cache() + + def load_cache(self): + # Load plugins from on disk cache + remove = set() + pat = re.compile(r'^store_version\s*=\s*(\d+)', re.M) + for name, src in self.cache_file.iteritems(): + try: + key = self.name_rmap[name] + except KeyError: + # Plugin has been disabled + m = pat.search(src[:512]) + if m is not None: + try: + self.cached_version_map[name] = int(m.group(1)) + except (TypeError, ValueError): + pass + continue + + try: + obj, ver = self.load_object(src, key) + except VersionMismatch as e: + self.cached_version_map[name] = e.ver + continue + except: + import traceback + prints('Failed to load cached store:', name) + traceback.print_exc() + else: + if not self.replace_plugin(ver, name, obj, 'cached'): + # Builtin plugin is newer than cached + remove.add(name) + + if remove: + with self.cache_file: + for name in remove: + del self.cache_file[name] + + def check_for_updates(self): + if hasattr(self, 'update_thread') and self.update_thread.is_alive(): + return + if time.time() - self.last_check_time < self.CHECK_INTERVAL: + return + try: + self.update_thread.start() + except (RuntimeError, AttributeError): + self.update_thread = Thread(target=self.do_update) + self.update_thread.start() + + def join(self, timeout=None): + hasattr(self, 'update_thread') and self.update_thread.join(timeout) + + def download_updates(self): + ver_map = {name:max(ver, self.cached_version_map.get(name, -1)) + for name, ver in self.version_map.iteritems()} + try: + updates = download_updates(ver_map) + except: + import traceback + traceback.print_exc() + else: + for name, code in updates: + yield name, code + + def do_update(self): + replacements = {} + + for name, src in self.download_updates(): + try: + key = self.name_rmap[name] + except KeyError: + # Plugin has been disabled + replacements[name] = src + continue + try: + obj, ver = self.load_object(src, key) + except VersionMismatch as e: + self.cached_version_map[name] = e.ver + replacements[name] = src + continue + except: + import traceback + prints('Failed to load downloaded store:', name) + traceback.print_exc() + else: + if self.replace_plugin(ver, name, obj, 'downloaded'): + replacements[name] = src + + if replacements: + with self.cache_file: + for name, src in replacements.iteritems(): + self.cache_file[name] = src + + def replace_plugin(self, ver, name, obj, source): + if ver > self.version_map[name]: + if DEBUG: + prints('Loaded', source, 'store plugin for:', + self.name_rmap[name], 'at version:', ver) + self[self.name_rmap[name]] = obj + self.version_map[name] = ver + return True + return False + + def load_object(self, src, key): + namespace = {} + builtin = self[key] + exec src in namespace + ver = namespace['store_version'] + cls = None + for x in namespace.itervalues(): + if (isinstance(x, type) and issubclass(x, StorePlugin) and x is not + StorePlugin): + cls = x + break + if cls is None: + raise ValueError('No store plugin found') + if cls.minimum_calibre_version > numeric_version: + raise VersionMismatch(ver) + return cls(builtin.gui, builtin.name, config=builtin.config, + base_plugin=builtin.base_plugin), ver + +if __name__ == '__main__': + st = time.time() + for name, code in download_updates(): + print(name) + print(code) + print('\n', '_'*80, '\n', sep='') + print ('Time to download all plugins: %.2f'%( time.time() - st)) + + diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index b4ae0bc943..20c6c09a03 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -194,6 +194,7 @@ class SearchDialog(QDialog, Ui_Dialog): query = self.clean_query(query) shuffle(store_names) # Add plugins that the user has checked to the search pool's work queue. + self.gui.istores.join(4.0) # Wait for updated plugins to load for n in store_names: if self.store_checks[n].isChecked(): self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index db64969179..65993ff31c 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -155,7 +155,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ acmap[ac.name] = ac def load_store_plugins(self): - self.istores = OrderedDict() + from calibre.gui2.store.loader import Stores + self.istores = Stores() for store in available_store_plugins(): if self.opts.ignore_plugins and store.plugin_path is not None: continue @@ -169,6 +170,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if store.plugin_path is None: raise continue + self.istores.builtins_loaded() def init_istore(self, store): st = store.load_actual_plugin(self) @@ -790,6 +792,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ except KeyboardInterrupt: pass time.sleep(2) + self.istores.join() self.hide_windows() # Do not report any errors that happen after the shutdown sys.excepthook = sys.__excepthook__ From 8de9017002e0d5f57ada1958492c6ccf3a232f1b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 15 Jan 2013 10:29:14 +0530 Subject: [PATCH 12/13] ... --- src/calibre/gui2/store/loader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/gui2/store/loader.py b/src/calibre/gui2/store/loader.py index c0769991dc..45c258a915 100644 --- a/src/calibre/gui2/store/loader.py +++ b/src/calibre/gui2/store/loader.py @@ -107,6 +107,7 @@ class Stores(OrderedDict): return if time.time() - self.last_check_time < self.CHECK_INTERVAL: return + self.last_check_time = time.time() try: self.update_thread.start() except (RuntimeError, AttributeError): From d2423bfe8bff5f168fbe0ca2aa1c2fa352df9edc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 15 Jan 2013 10:31:11 +0530 Subject: [PATCH 13/13] Restore subscription for nytime subscription recipe --- recipes/nytimes_sub.recipe | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/recipes/nytimes_sub.recipe b/recipes/nytimes_sub.recipe index df44856293..35f7b46517 100644 --- a/recipes/nytimes_sub.recipe +++ b/recipes/nytimes_sub.recipe @@ -124,19 +124,19 @@ class NYTimes(BasicNewsRecipe): if headlinesOnly: title='New York Times Headlines' description = 'Headlines from the New York Times' - needs_subscription = False + needs_subscription = True elif webEdition: title='New York Times (Web)' description = 'New York Times on the Web' - needs_subscription = False + needs_subscription = True elif replaceKindleVersion: title='The New York Times' description = 'Today\'s New York Times' - needs_subscription = False + needs_subscription = True else: title='New York Times' description = 'Today\'s New York Times' - needs_subscription = False + needs_subscription = True def decode_url_date(self,url): urlitems = url.split('/') @@ -359,6 +359,14 @@ class NYTimes(BasicNewsRecipe): def get_browser(self): br = BasicNewsRecipe.get_browser() + if self.username is not None and self.password is not None: + br.open('http://www.nytimes.com/auth/login') + br.form = br.forms().next() + br['userid'] = self.username + br['password'] = self.password + raw = br.submit().read() + if 'Please try again' in raw: + raise Exception('Your username and password are incorrect') return br cover_tag = 'NY_NYT'