From 1d423c8d018a764e638db016cf232cf1f6e8883d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 May 2011 14:03:16 -0600 Subject: [PATCH 01/46] iProfesional by DM. Fixes #782369 (New recipe for iprofessional) --- recipes/icons/iprofesional.png | Bin 0 -> 1088 bytes recipes/iprofesional.recipe | 79 +++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 recipes/icons/iprofesional.png create mode 100644 recipes/iprofesional.recipe diff --git a/recipes/icons/iprofesional.png b/recipes/icons/iprofesional.png new file mode 100644 index 0000000000000000000000000000000000000000..fa52d7dbfc62b03cd7076bf53a04843bbb6c5223 GIT binary patch literal 1088 zcmeHE`AgGr7#<2Gig{!xCZ;ARc?>HuN{wokE18>anW$8jQISWb+q@5wwoFGNdCiIf zHJMJ(oCldobBfD6@@gK;P4lG8WBVST&!?|{MZfgG`@X+CFYog_ub9ocY-#RjPNUH* znIVjDYFh7=nJLvheXJsAG*cQolp72IvD?Zc6mg<*`H6^hS%=ctdGBmgmaIc{Ls;g= z5)$(YUUD%B31fvT3BlEPQF(0_0uTWNcl2M>)ATStJxr)jj{5V`a72KK2J!3W7uE9b zQ(Ag>CRX7>anT>G51ZfIGXz&h4VA70f(4awteFcT3HN|}+5GXjMSgM3sfeuF78NhC zC_L%q!rH1_q28%-F;+Swh>-{(NKR<9V0jQhWFScFYq5G*^Lbz_v!tFdtmCQz&I6Ir zxC;;t5V^FvL21M)-F-Nz+NE{0&<$o=+xo@~vP*_4=f+Mo*wjK2#H5x&iodO+EQKc& zQeqLdx&7uCM&RQ!dW3CtsV1@|AI4{v&U1xQOG)HWQ;yv}$($y5+jcfkJZF1tyNQvs=9V6-@;EaB+p{Wa z)5=}r85b?4%mQ-X#q~%c-}dty<3#fbQ@dfAEox7;5Kb9~ZL%14fPGgIu#yv(=m zM&F8XXpYpGXRMgonb30zeoqend_E+vbN8nuJj(d=>yGo2z;AvfrFxx0DPAhGdFY*W zJWATppl2R?+*=J*A+3UW&WNb^ux8-J3CZ2DbN<&_VVT? z2T9AO)1?{{o54<(8LQN}Sbl)ax7$Hu#+(ivn5=WgT<4k6yzS!~Ifw V=ED7RsqTCEGlN--#tS#|{{p^Vq>%ss literal 0 HcmV?d00001 diff --git a/recipes/iprofesional.recipe b/recipes/iprofesional.recipe new file mode 100644 index 0000000000..e8edbbc7a3 --- /dev/null +++ b/recipes/iprofesional.recipe @@ -0,0 +1,79 @@ +__license__ = 'GPL v3' +__copyright__ = '2011, Darko Miletic ' +''' +www.iprofesional.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class iProfesional(BasicNewsRecipe): + title = 'iProfesional.com' + __author__ = 'Darko Miletic' + description = 'Las ultimas noticias sobre profesionales' + publisher = 'Emprendimientos Corporativos S.A.' + category = 'news, IT, impuestos, negocios, politics, Argentina' + oldest_article = 2 + max_articles_per_feed = 200 + no_stylesheets = True + encoding = 'utf8' + use_embedded_content = False + language = 'es_AR' + remove_empty_feeds = True + publication_type = 'nesportal' + masthead_url = 'http://www.iprofesional.com/img/logo-iprofesional.png' + extra_css = """ + body{font-family: Arial,Helvetica,sans-serif } + img{margin-bottom: 0.4em; display:block} + .titulo-interior{font-family: Georgia,"Times New Roman",Times,serif} + .autor-nota{font-size: small; font-weight: bold; font-style: italic; color: gray} + """ + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + } + + keep_only_tags = [dict(attrs={'class':['fecha','interior-nota']})] + + remove_tags = [ + dict(name=['meta','link','base','embed','object','iframe']) + ,dict(attrs={'class':['menu-imprimir','guardarNota','IN-widget','fin','permalink']}) + ] + remove_attributes=['lang','xmlns:og','xmlns:fb'] + + + feeds = [ + (u'Ultimas noticias' , u'http://feeds.feedburner.com/iprofesional-principales-noticias') + ,(u'Finanzas' , u'http://feeds.feedburner.com/iprofesional-finanzas' ) + ,(u'Impuestos' , u'http://feeds.feedburner.com/iprofesional-impuestos' ) + ,(u'Negocios' , u'http://feeds.feedburner.com/iprofesional-economia' ) + ,(u'Comercio Exterior' , u'http://feeds.feedburner.com/iprofesional-comercio-exterior' ) + ,(u'Tecnologia' , u'http://feeds.feedburner.com/iprofesional-tecnologia' ) + ,(u'Management' , u'http://feeds.feedburner.com/iprofesional-managment' ) + ,(u'Marketing' , u'http://feeds.feedburner.com/iprofesional-marketing' ) + ,(u'Legales' , u'http://feeds.feedburner.com/iprofesional-legales' ) + ,(u'Autos' , u'http://feeds.feedburner.com/iprofesional-autos' ) + ,(u'Vinos' , u'http://feeds.feedburner.com/iprofesional-vinos-bodegas' ) + ] + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + for item in soup.findAll('a'): + limg = item.find('img') + if item.string is not None: + str = item.string + item.replaceWith(str) + else: + if limg: + item.name = 'div' + item.attrs = [] + else: + str = self.tag_to_string(item) + item.replaceWith(str) + for item in soup.findAll('img'): + if not item.has_key('alt'): + item['alt'] = 'image' + return soup From e904b4d6203a48d19f194cd2d7db87f054b05edd Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 May 2011 14:04:46 -0600 Subject: [PATCH 02/46] Financial Sense by DM. Fixes #782368 (New recipe for financial sense) --- recipes/financialsense.recipe | 64 +++++++++++++++++++++++++++++++ recipes/icons/financialsense.png | Bin 0 -> 702 bytes 2 files changed, 64 insertions(+) create mode 100644 recipes/financialsense.recipe create mode 100644 recipes/icons/financialsense.png diff --git a/recipes/financialsense.recipe b/recipes/financialsense.recipe new file mode 100644 index 0000000000..4224396edf --- /dev/null +++ b/recipes/financialsense.recipe @@ -0,0 +1,64 @@ +__license__ = 'GPL v3' +__copyright__ = '2011, Darko Miletic ' +''' +www.financialsense.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class FinancialSense(BasicNewsRecipe): + title = 'Financial Sense' + __author__ = 'Darko Miletic' + description = 'Uncommon News & Views for the Wise Investor' + publisher = 'Financial Sense' + category = 'news, finances, politics, USA' + oldest_article = 2 + max_articles_per_feed = 200 + no_stylesheets = True + encoding = 'utf8' + use_embedded_content = False + language = 'en' + remove_empty_feeds = True + publication_type = 'newsportal' + masthead_url = 'http://www.financialsense.com/sites/default/files/logo.jpg' + extra_css = """ + body{font-family: Arial,"Helvetica Neue",Helvetica,sans-serif } + img{margin-bottom: 0.4em; display:block} + h2{color: gray} + .name{margin-right: 5em} + """ + + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + , 'language' : language + } + + remove_tags =[dict(name=['meta','link','base','object','embed','iframe'])] + remove_tags_after=dict(attrs={'class':'vcard'}) + keep_only_tags =[dict(attrs={'class':['title','post-meta','content','item-title','vcard']})] + remove_attributes=['lang','type'] + + + feeds = [(u'Articles', u'http://feeds.feedburner.com/fso')] + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + for item in soup.findAll('a'): + limg = item.find('img') + if item.string is not None: + str = item.string + item.replaceWith(str) + else: + if limg: + item.name = 'div' + item.attrs = [] + else: + str = self.tag_to_string(item) + item.replaceWith(str) + for item in soup.findAll('img'): + if not item.has_key('alt'): + item['alt'] = 'image' + return soup diff --git a/recipes/icons/financialsense.png b/recipes/icons/financialsense.png new file mode 100644 index 0000000000000000000000000000000000000000..5f02e7770a6449fda934c53765ccdf700afeef48 GIT binary patch literal 702 zcmV;v0zv(WP)k<3|tKn7%%Fc>DM7pW@<@4<0^JR@G!@ z=Y0D7t&yn>!iK+p|9t=U^~d*b7mlxHVd2o!HQ&Ael%l4sw35NQ&wpl2YJB$WIf@Pc z{{HD`$dOY}v~-DieDlcMIdfL6-^(l5(uY^~S@<-$S$}W~DPK9a2dEnAq&Kf$efsj_KZo?e zt&Gc{4i85|J6nqn-~PY< z_;t&ki*H^%MoAj`_U?XgaiyA}mxQdEii*yPcj+Hm#i-7+J){&HMN7UnV9d zgocOrZ}9PQv#@d=J-k;zR^->uAMf6OF*39J^Pf#nP>7L{5k2t%lL#Xt$c4Xu|7K-n k1-kRor%wU`0%&O&0LdpR Date: Fri, 13 May 2011 14:43:56 -0600 Subject: [PATCH 03/46] Linux binaries: Always use either Cleanlook or Plastique styles for the GUI if no style can be loaded from the host computer --- src/calibre/gui2/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 28504f2a31..3270fcfde5 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -625,6 +625,16 @@ class Application(QApplication): if s is not None: font.setStretch(s) QApplication.setFont(font) + st = self.style() + if st is not None: + st = unicode(st.objectName()).lower() + if (islinux or isfreebsd) and st in ('windows', 'motif', 'cde'): + from PyQt4.Qt import QStyleFactory + styles = set(map(unicode, QStyleFactory.keys())) + if 'Cleanlooks' in styles: + self.setStyle('Cleanlooks') + else: + self.setStyle('Plastique') def _send_file_open_events(self): with self._file_open_lock: From 89251d86b13bf67f56e8f18460273410750d91a2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 May 2011 15:19:21 -0600 Subject: [PATCH 04/46] Fix assert in Qt caused by incorrect use of beginInsertRows --- src/calibre/gui2/tag_view.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index a3e39aefd2..da5029bab3 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -1263,7 +1263,7 @@ class TagsModel(QAbstractItemModel): # {{{ d['last'] = data[key][cat_len-1] name = eval_formatter.safe_format(collapse_template, d, 'TAG_VIEW', None) - self.beginInsertRows(category_index, 999999, 1) #len(data[key])-1) + self.beginInsertRows(category_index, 999998, 999999) #len(data[key])-1) sub_cat = TagTreeItem(parent=category, data = name, tooltip = None, temporary=True, category_icon = category_node.icon, @@ -1296,7 +1296,7 @@ class TagsModel(QAbstractItemModel): # {{{ key in ['authors', 'publisher', 'news', 'formats', 'rating'] or key not in self.db.prefs.get('categories_using_hierarchy', []) or len(components) == 1): - self.beginInsertRows(category_index, 999999, 1) + self.beginInsertRows(category_index, 999998, 999999) n = TagTreeItem(parent=node_parent, data=tag, tooltip=tt, icon_map=self.icon_state_map) if tag.id_set is not None: @@ -1332,7 +1332,7 @@ class TagsModel(QAbstractItemModel): # {{{ t.is_hierarchical = \ '5state' if t.category != 'search' else '3state' t.name = comp - self.beginInsertRows(category_index, 999999, 1) + self.beginInsertRows(category_index, 999998, 999999) node_parent = TagTreeItem(parent=node_parent, data=t, tooltip=tt, icon_map=self.icon_state_map) child_map[(comp,tag.category)] = node_parent From 362328dc9dfe6c14a9e3299a5b3c8fa4ae8f7fd9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 May 2011 18:06:43 -0600 Subject: [PATCH 05/46] Amazon metadata download: Allow user to configure plugin to use any of the US, UK, German, French and Italian Amazon websites --- src/calibre/ebooks/metadata/sources/amazon.py | 165 ++++++++++++++++-- src/calibre/ebooks/metadata/sources/base.py | 5 +- src/calibre/gui2/metadata/config.py | 11 +- 3 files changed, 159 insertions(+), 22 deletions(-) diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index dc260c87ef..cf440fe291 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -16,7 +16,7 @@ from lxml.html import soupparser, tostring from calibre import as_unicode from calibre.ebooks.metadata import check_isbn -from calibre.ebooks.metadata.sources.base import Source +from calibre.ebooks.metadata.sources.base import Source, Option from calibre.utils.cleantext import clean_ascii_chars from calibre.ebooks.chardet import xml_to_unicode from calibre.ebooks.metadata.book.base import Metadata @@ -37,6 +37,81 @@ class Worker(Thread): # Get details {{{ self.relevance, self.plugin = relevance, plugin self.browser = browser.clone_browser() self.cover_url = self.amazon_id = self.isbn = None + self.domain = self.plugin.domain + + months = { + 'de': { + 1 : ['jän'], + 3 : ['märz'], + 5 : ['mai'], + 6 : ['juni'], + 7 : ['juli'], + 10: ['okt'], + 12: ['dez'] + }, + 'it': { + 1: ['enn'], + 2: ['febbr'], + 5: ['magg'], + 6: ['giugno'], + 7: ['luglio'], + 8: ['ag'], + 9: ['sett'], + 10: ['ott'], + 12: ['dic'], + }, + 'fr': { + 1: ['janv'], + 2: ['févr'], + 3: ['mars'], + 4: ['avril'], + 5: ['mai'], + 6: ['juin'], + 7: ['juil'], + 8: ['août'], + 9: ['sept'], + 12: ['déc'], + }, + + } + + self.english_months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + self.months = months.get(self.domain, {}) + + self.pd_xpath = ''' + //h2[text()="Product Details" or \ + text()="Produktinformation" or \ + text()="Dettagli prodotto" or \ + text()="Product details" or \ + text()="Détails sur le produit"]/../div[@class="content"] + ''' + self.publisher_xpath = ''' + descendant::*[starts-with(text(), "Publisher:") or \ + starts-with(text(), "Verlag:") or \ + starts-with(text(), "Editore:") or \ + starts-with(text(), "Editeur")] + ''' + self.language_xpath = ''' + descendant::*[ + starts-with(text(), "Language:") \ + or text() = "Language" \ + or text() = "Sprache:" \ + or text() = "Lingua:" \ + or starts-with(text(), "Langue") \ + ] + ''' + self.ratings_pat = re.compile( + r'([0-9.]+) (out of|von|su|étoiles sur) (\d+)( (stars|Sternen|stelle)){0,1}') + + def delocalize_datestr(self, raw): + if not self.months: + return raw + ans = raw.lower() + for i, vals in self.months.iteritems(): + for x in vals: + ans = ans.replace(x, self.english_months[i]) + return ans def run(self): try: @@ -132,7 +207,7 @@ class Worker(Thread): # Get details {{{ self.log.exception('Error parsing cover for url: %r'%self.url) mi.has_cover = bool(self.cover_url) - pd = root.xpath('//h2[text()="Product Details"]/../div[@class="content"]') + pd = root.xpath(self.pd_xpath) if pd: pd = pd[0] @@ -194,23 +269,29 @@ class Worker(Thread): # Get details {{{ def parse_authors(self, root): x = '//h1[@class="parseasinTitle"]/following-sibling::span/*[(name()="a" and @href) or (name()="span" and @class="contributorNameTrigger")]' aname = root.xpath(x) + if not aname: + aname = root.xpath(''' + //h1[@class="parseasinTitle"]/following-sibling::*[(name()="a" and @href) or (name()="span" and @class="contributorNameTrigger")] + ''') for x in aname: x.tail = '' authors = [tostring(x, encoding=unicode, method='text').strip() for x in aname] + authors = [a for a in authors if a] return authors def parse_rating(self, root): ratings = root.xpath('//div[@class="jumpBar"]/descendant::span[@class="asinReviewsSummary"]') if not ratings: ratings = root.xpath('//div[@class="buying"]/descendant::span[@class="asinReviewsSummary"]') - pat = re.compile(r'([0-9.]+) out of (\d+) stars') + if not ratings: + ratings = root.xpath('//span[@class="crAvgStars"]/descendant::span[@class="asinReviewsSummary"]') if ratings: for elem in ratings[0].xpath('descendant::*[@title]'): t = elem.get('title').strip() - m = pat.match(t) + m = self.ratings_pat.match(t) if m is not None: - return float(m.group(1))/float(m.group(2)) * 5 + return float(m.group(1))/float(m.group(3)) * 5 def parse_comments(self, root): desc = root.xpath('//div[@id="productDescription"]/*[@class="content"]') @@ -264,27 +345,31 @@ class Worker(Thread): # Get details {{{ return ans def parse_publisher(self, pd): - for x in reversed(pd.xpath( - 'descendant::*[starts-with(text(), "Publisher:")]')): + for x in reversed(pd.xpath(self.publisher_xpath)): if x.tail: ans = x.tail.partition(';')[0] return ans.partition('(')[0].strip() def parse_pubdate(self, pd): - for x in reversed(pd.xpath( - 'descendant::*[starts-with(text(), "Publisher:")]')): + for x in reversed(pd.xpath(self.publisher_xpath)): if x.tail: ans = x.tail date = ans.partition('(')[-1].replace(')', '').strip() + date = self.delocalize_datestr(date) return parse_date(date, assume_utc=True) def parse_language(self, pd): - for x in reversed(pd.xpath( - 'descendant::*[starts-with(text(), "Language:")]')): + for x in reversed(pd.xpath(self.language_xpath)): if x.tail: ans = x.tail.strip() - if ans == 'English': + if ans in ('English', 'Englisch'): return 'en' + elif ans in ('German', 'Deutsch'): + return 'de' + elif ans in ('Italian', 'Italiano'): + return 'it' + elif ans in ('French', 'Français',): + return 'fr' # }}} class Amazon(Source): @@ -304,8 +389,15 @@ class Amazon(Source): 'fr' : _('France'), 'de' : _('Germany'), 'uk' : _('UK'), + 'it' : _('Italy'), } + options = ( + Option('domain', 'choices', 'com', _('Amazon website to use:'), + _('Metadata from Amazon will be fetched using this ' + 'country\'s Amazon website.'), choices=AMAZON_DOMAINS), + ) + def get_book_url(self, identifiers): # {{{ asin = identifiers.get('amazon', None) if asin is None: @@ -314,8 +406,16 @@ class Amazon(Source): return ('amazon', asin, 'http://amzn.com/%s'%asin) # }}} + @property + def domain(self): + domain = self.prefs['domain'] + if domain not in self.AMAZON_DOMAINS: + domain = 'com' + + return domain + def create_query(self, log, title=None, authors=None, identifiers={}): # {{{ - domain = self.prefs.get('domain', 'com') + domain = self.domain # See the amazon detailed search page to get all options q = { 'search-alias' : 'aps', @@ -355,6 +455,8 @@ class Amazon(Source): latin1q = dict([(x.encode('latin1', 'ignore'), y.encode('latin1', 'ignore')) for x, y in q.iteritems()]) + if domain == 'uk': + domain = 'co.uk' url = 'http://www.amazon.%s/s/?'%domain + urlencode(latin1q) return url @@ -526,8 +628,7 @@ if __name__ == '__main__': # tests {{{ # src/calibre/ebooks/metadata/sources/amazon.py from calibre.ebooks.metadata.sources.test import (test_identify_plugin, title_test, authors_test) - test_identify_plugin(Amazon.name, - [ + com_tests = [ # {{{ ( # Description has links {'identifiers':{'isbn': '9780671578275'}}, @@ -575,6 +676,38 @@ if __name__ == '__main__': # tests {{{ ), - ]) + ] # }}} + + de_tests = [ # {{{ + ( + {'identifiers':{'isbn': '3548283519'}}, + [title_test('Wer Wind sät', + exact=True), authors_test(['Nele Neuhaus']) + ] + + ), + ] # }}} + + it_tests = [ # {{{ + ( + {'identifiers':{'isbn': '8838922195'}}, + [title_test('La briscola in cinque', + exact=True), authors_test(['Marco Malvaldi']) + ] + + ), + ] # }}} + + fr_tests = [ # {{{ + ( + {'identifiers':{'isbn': '2221116798'}}, + [title_test('L\'étrange voyage de Monsieur Daldry', + exact=True), authors_test(['Marc Levy']) + ] + + ), + ] # }}} + + test_identify_plugin(Amazon.name, com_tests) # }}} diff --git a/src/calibre/ebooks/metadata/sources/base.py b/src/calibre/ebooks/metadata/sources/base.py index c20cb1db83..889132a484 100644 --- a/src/calibre/ebooks/metadata/sources/base.py +++ b/src/calibre/ebooks/metadata/sources/base.py @@ -145,10 +145,13 @@ class Option(object): :param default: The default value for this option :param label: A short (few words) description of this option :param desc: A longer description of this option - :param choices: A list of possible values, used only if type='choices' + :param choices: A dict of possible values, used only if type='choices'. + dict is of the form {key:human readable label, ...} ''' self.name, self.type, self.default, self.label, self.desc = (name, type_, default, label, desc) + if choices and not isinstance(choices, dict): + choices = dict([(x, x) for x in choices]) self.choices = choices class Source(Plugin): diff --git a/src/calibre/gui2/metadata/config.py b/src/calibre/gui2/metadata/config.py index cb51324ae0..6138f27090 100644 --- a/src/calibre/gui2/metadata/config.py +++ b/src/calibre/gui2/metadata/config.py @@ -10,7 +10,7 @@ __docformat__ = 'restructuredtext en' import textwrap from PyQt4.Qt import (QWidget, QGridLayout, QGroupBox, QListView, Qt, QSpinBox, - QDoubleSpinBox, QCheckBox, QLineEdit, QComboBox, QLabel) + QDoubleSpinBox, QCheckBox, QLineEdit, QComboBox, QLabel, QVariant) from calibre.gui2.preferences.metadata_sources import FieldsModel as FM @@ -95,9 +95,9 @@ class ConfigWidget(QWidget): widget.setChecked(bool(val)) elif opt.type == 'choices': widget = QComboBox(self) - for x in opt.choices: - widget.addItem(x) - idx = opt.choices.index(val) + for key, label in opt.choices.iteritems(): + widget.addItem(label, QVariant(key)) + idx = widget.findData(QVariant(val)) widget.setCurrentIndex(idx) widget.opt = opt widget.setToolTip(textwrap.fill(opt.desc)) @@ -124,7 +124,8 @@ class ConfigWidget(QWidget): elif isinstance(w, QCheckBox): val = w.isChecked() elif isinstance(w, QComboBox): - val = unicode(w.currentText()) + idx = w.currentIndex() + val = unicode(w.itemData(idx).toString()) self.plugin.prefs[w.opt.name] = val From 3aeca93ff33765c231de2e2ab332ba94921c58af Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 13 May 2011 18:17:04 -0600 Subject: [PATCH 06/46] ... --- src/calibre/ebooks/metadata/sources/amazon.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/calibre/ebooks/metadata/sources/amazon.py b/src/calibre/ebooks/metadata/sources/amazon.py index cf440fe291..b9c27b8fac 100644 --- a/src/calibre/ebooks/metadata/sources/amazon.py +++ b/src/calibre/ebooks/metadata/sources/amazon.py @@ -104,6 +104,17 @@ class Worker(Thread): # Get details {{{ self.ratings_pat = re.compile( r'([0-9.]+) (out of|von|su|étoiles sur) (\d+)( (stars|Sternen|stelle)){0,1}') + lm = { + 'en': ('English', 'Englisch'), + 'fr': ('French', 'Français'), + 'it': ('Italian', 'Italiano'), + 'de': ('German', 'Deutsch'), + } + self.lang_map = {} + for code, names in lm.iteritems(): + for name in names: + self.lang_map[name] = code + def delocalize_datestr(self, raw): if not self.months: return raw @@ -362,14 +373,9 @@ class Worker(Thread): # Get details {{{ for x in reversed(pd.xpath(self.language_xpath)): if x.tail: ans = x.tail.strip() - if ans in ('English', 'Englisch'): - return 'en' - elif ans in ('German', 'Deutsch'): - return 'de' - elif ans in ('Italian', 'Italiano'): - return 'it' - elif ans in ('French', 'Français',): - return 'fr' + ans = self.lang_map.get(ans, None) + if ans: + return ans # }}} class Amazon(Source): From e062578f1c63fdbfbe26554272580745f75a33f9 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 14 May 2011 08:35:14 -0600 Subject: [PATCH 07/46] Fix #782454 (Calibre fails to compile with png-1.5) --- recipes/economist.recipe | 2 +- src/calibre/ebooks/pdf/images.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/recipes/economist.recipe b/recipes/economist.recipe index 894f5880b3..22e317ee68 100644 --- a/recipes/economist.recipe +++ b/recipes/economist.recipe @@ -20,7 +20,7 @@ class Economist(BasicNewsRecipe): INDEX = 'http://www.economist.com/printedition' description = ('Global news and current affairs from a European' ' perspective. Best downloaded on Friday mornings (GMT)') - + extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }' oldest_article = 7.0 cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg' remove_tags = [ diff --git a/src/calibre/ebooks/pdf/images.cpp b/src/calibre/ebooks/pdf/images.cpp index 0e7d8b0d70..8ca7448001 100644 --- a/src/calibre/ebooks/pdf/images.cpp +++ b/src/calibre/ebooks/pdf/images.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "images.h" #include "utils.h" From e20624a445f15215caa515de98957e51d663e7be Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 14 May 2011 12:10:05 -0400 Subject: [PATCH 08/46] Store: Handle | in author names for LN,FN records. --- src/calibre/gui2/actions/store.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/actions/store.py b/src/calibre/gui2/actions/store.py index 5007dad659..4e0c8bb2ba 100644 --- a/src/calibre/gui2/actions/store.py +++ b/src/calibre/gui2/actions/store.py @@ -49,14 +49,23 @@ class StoreAction(InterfaceAction): return rows[0].row() def _get_author(self, row): - author = '' + authors = [] + if self.gui.current_view() is self.gui.library_view: - author = self.gui.library_view.model().authors(row) + a = self.gui.library_view.model().authors(row) + authors = a.split(',') else: mi = self.gui.current_view().model().get_book_display_info(row) - author = ' & '.join(mi.authors) + authors = mi.authors - return author + corrected_authors = [] + for x in authors: + a = x.split('|') + a.reverse() + a = ' '.join(a) + corrected_authors.append(a) + + return ' & '.join(corrected_authors) def search_author(self): row = self._get_selected_row() From 00d7568123e9aa2a92cadde4c1fb4dd9a9aa5b70 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 14 May 2011 12:34:18 -0400 Subject: [PATCH 09/46] Store: Save search history. --- src/calibre/gui2/store/search/search.py | 2 ++ src/calibre/gui2/store/search/search.ui | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index eea1a692de..e9c9c149a7 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -28,6 +28,8 @@ class SearchDialog(QDialog, Ui_Dialog): self.setupUi(self) self.config = JSONConfig('store/search') + + self.search_edit.initialize('store_search_search') # We keep a cache of store plugins and reference them by name. self.store_plugins = istores diff --git a/src/calibre/gui2/store/search/search.ui b/src/calibre/gui2/store/search/search.ui index fe5aaceda4..8dd423baec 100644 --- a/src/calibre/gui2/store/search/search.ui +++ b/src/calibre/gui2/store/search/search.ui @@ -38,7 +38,14 @@ - + + + + 0 + 0 + + + @@ -201,6 +208,11 @@ QTreeView
results_view.h
+ + HistoryLineEdit + QLineEdit +
widgets.h
+
From 07a64546681d1e2ab610e2495d6df79aeca4b74e Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 14 May 2011 12:38:15 -0400 Subject: [PATCH 10/46] Store: MobileRead add history to search. --- .../gui2/store/mobileread/store_dialog.py | 1 + .../gui2/store/mobileread/store_dialog.ui | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/store/mobileread/store_dialog.py b/src/calibre/gui2/store/mobileread/store_dialog.py index af300565aa..7a7b27837d 100644 --- a/src/calibre/gui2/store/mobileread/store_dialog.py +++ b/src/calibre/gui2/store/mobileread/store_dialog.py @@ -20,6 +20,7 @@ class MobeReadStoreDialog(QDialog, Ui_Dialog): self.setupUi(self) self.plugin = plugin + self.search_query.initialize('store_mobileread_search') self.adv_search_button.setIcon(QIcon(I('search.png'))) diff --git a/src/calibre/gui2/store/mobileread/store_dialog.ui b/src/calibre/gui2/store/mobileread/store_dialog.ui index 6d31efab6d..b698352dfd 100644 --- a/src/calibre/gui2/store/mobileread/store_dialog.ui +++ b/src/calibre/gui2/store/mobileread/store_dialog.ui @@ -34,7 +34,14 @@
- + + + + 0 + 0 + + + @@ -107,6 +114,13 @@ + + + HistoryLineEdit + QLineEdit +
widgets.h
+
+
From 37319b8503a4ebe76f4dbc3070b7bcc1da4e9d57 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 14 May 2011 19:49:45 -0400 Subject: [PATCH 11/46] Possible fix for bug #779556: Preferences hotkey lost in Mac version. --- src/calibre/gui2/actions/preferences.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/actions/preferences.py b/src/calibre/gui2/actions/preferences.py index b65967e994..1ebd4ea6ba 100644 --- a/src/calibre/gui2/actions/preferences.py +++ b/src/calibre/gui2/actions/preferences.py @@ -19,8 +19,9 @@ class PreferencesAction(InterfaceAction): def genesis(self): pm = QMenu() - acname = _('Change calibre behavior') if isosx else _('Preferences') - pm.addAction(QIcon(I('config.png')), acname, self.do_config) + pm.addAction(QIcon(I('config.png')), _('Preferences'), self.do_config) + if isosx: + pm.addAction(QIcon(I('config.png')), _('Change calibre behavior'), self.do_config) pm.addAction(QIcon(I('wizard.png')), _('Run welcome wizard'), self.gui.run_wizard) if not DEBUG: From 3aec78057ad442985153d3f8b0b23bc1e93e99d5 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sat, 14 May 2011 20:31:49 -0400 Subject: [PATCH 12/46] Store: Fix BeWrite plugin loading detail pages. --- src/calibre/gui2/store/bewrite_plugin.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/store/bewrite_plugin.py b/src/calibre/gui2/store/bewrite_plugin.py index 80f3b2d54e..bfd543db49 100644 --- a/src/calibre/gui2/store/bewrite_plugin.py +++ b/src/calibre/gui2/store/bewrite_plugin.py @@ -26,14 +26,9 @@ class BeWriteStore(BasicStoreConfig, StorePlugin): url = 'http://www.bewrite.net/mm5/merchant.mvc?Screen=SFNT' if external or self.config.get('open_external', False): - if detail_item: - url = url + detail_item - open_url(QUrl(url_slash_cleaner(url))) + open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url))) else: - detail_url = None - if detail_item: - detail_url = url + detail_item - d = WebStoreDialog(self.gui, url, parent, detail_url) + d = WebStoreDialog(self.gui, url, parent, detail_item) d.setWindowTitle(self.name) d.set_tags(self.config.get('tags', '')) d.exec_() From 0f85127e9366abcd629aa4c73e882a7ca2338aa3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 14 May 2011 18:56:18 -0600 Subject: [PATCH 13/46] Express.de by schuster --- recipes/express_de.recipe | 74 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 recipes/express_de.recipe diff --git a/recipes/express_de.recipe b/recipes/express_de.recipe new file mode 100644 index 0000000000..255538b08e --- /dev/null +++ b/recipes/express_de.recipe @@ -0,0 +1,74 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1303841067(BasicNewsRecipe): + + title = u'Express.de' + __author__ = 'schuster' + oldest_article = 2 + max_articles_per_feed = 50 + no_stylesheets = True + use_embedded_content = False + language = 'de' + extra_css = ''' + h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;} + h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;} + + ''' + remove_javascript = True + remove_tags_befor = [dict(name='div', attrs={'class':'Datum'})] + remove_tags_after = [dict(name='div', attrs={'class':'MoreNews'})] + + remove_tags = [dict(id='kalaydo'), + dict(id='Header'), + dict(id='Searchline'), + dict(id='MainNav'), + dict(id='Logo'), + dict(id='MainLinkSpacer'), + dict(id='MainLinks'), + dict(title='Diese Seite Bookmarken'), + + dict(name='span'), + dict(name='div', attrs={'class':'spacer_leftneu'}), + dict(name='div', attrs={'class':'button kalaydologo'}), + dict(name='div', attrs={'class':'button stellenneu'}), + dict(name='div', attrs={'class':'button autoneu'}), + dict(name='div', attrs={'class':'button immobilienneu'}), + dict(name='div', attrs={'class':'button kleinanzeigen'}), + dict(name='div', attrs={'class':'button tiereneu'}), + dict(name='div', attrs={'class':'button ferienwohnungen'}), + dict(name='div', attrs={'class':'button inserierenneu'}), + dict(name='div', attrs={'class':'spacer_rightneu'}), + dict(name='div', attrs={'class':'spacer_rightcorner'}), + dict(name='div', attrs={'class':'HeaderMetaNav'}), + dict(name='div', attrs={'class':'HeaderSearchOption'}), + dict(name='div', attrs={'class':'HeaderSearch'}), + dict(name='div', attrs={'class':'sbutton'}), + dict(name='div', attrs={'class':'active'}), + +] + + + def preprocess_html(self, soup): + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup + + feeds = [(u'Top-Themen', u'http://www.express.de/home/-/2126/2126/-/view/asFeed/-/index.xml'), + (u'Regional - Köln', u'http://www.express.de/regional/koeln/-/2856/2856/-/view/asFeed/-/index.xml'), + (u'Regional - Bonn', u'http://www.express.de/regional/bonn/-/2860/2860/-/view/asFeed/-/index.xml'), + (u'Regional - Düsseldorf', u'http://www.express.de/regional/duesseldorf/-/2858/2858/-/view/asFeed/-/index.xml'), + (u'Regional - Region', u'http://www.express.de/regional/-/2178/2178/-/view/asFeed/-/index.xml'), + (u'Sport-News', u'http://www.express.de/sport/-/2176/2176/-/view/asFeed/-/index.xml'), + (u'Fussball-News', u'http://www.express.de/sport/fussball/-/3186/3186/-/view/asFeed/-/index.xml'), + (u'1.FC Köln News', u'http://www.express.de/sport/fussball/fc-koeln/-/3192/3192/-/view/asFeed/-/index.xml'), + (u'Alemannia Aachen News', u'http://www.express.de/sport/fussball/alemannia/-/3290/3290/-/view/asFeed/-/index.xml'), + (u'Borussia M~Gladbach', u'http://www.express.de/sport/fussball/gladbach/-/3286/3286/-/view/asFeed/-/index.xml'), + (u'Fortuna D~Dorf', u'http://www.express.de/sport/fussball/fortuna/-/3292/3292/-/view/asFeed/-/index.xml'), + (u'Basketball News', u'http://www.express.de/sport/basketball/-/3190/3190/-/view/asFeed/-/index.xml'), + (u'Big Brother', u'http://www.express.de/news/promi-show/big-brother/-/2402/2402/-/view/asFeed/-/index.xml'), + + + +] From e149160e9aa522c369d965a5edc85704576d3d97 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 May 2011 08:25:51 -0600 Subject: [PATCH 14/46] Various news german sources by schuster --- recipes/pro_physik.recipe | 22 ++++++++++++++++++++++ recipes/spektrum.recipe | 28 ++++++++++++++++++++++++++++ recipes/technology_review_de.recipe | 24 ++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 recipes/pro_physik.recipe create mode 100644 recipes/spektrum.recipe create mode 100644 recipes/technology_review_de.recipe diff --git a/recipes/pro_physik.recipe b/recipes/pro_physik.recipe new file mode 100644 index 0000000000..eca10e96f3 --- /dev/null +++ b/recipes/pro_physik.recipe @@ -0,0 +1,22 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe +class AdvancedUserRecipe1303841067(BasicNewsRecipe): + + title = u'Pro Physik' + __author__ = 'schuster' + oldest_article = 4 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + language = 'de' + remove_javascript = True + cover_url = 'http://www.pro-physik.de/Phy/images/site/prophysik_logo1.jpg' + + + def print_version(self, url): + return url.replace('leadArticle.do', 'print.do') + + + feeds = [(u'Hightech', u'http://www.pro-physik.de/Phy/hightechfeed.xml'), + (u'Forschung', u'http://www.pro-physik.de/Phy/forschungfeed.xml'), + (u'Magazin', u'http://www.pro-physik.de/Phy/magazinfeed.xml')] + diff --git a/recipes/spektrum.recipe b/recipes/spektrum.recipe new file mode 100644 index 0000000000..d52c44b127 --- /dev/null +++ b/recipes/spektrum.recipe @@ -0,0 +1,28 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe +class AdvancedUserRecipe1303841067(BasicNewsRecipe): + title = u'Spektrum (der Wissenschaft)' + __author__ = 'schuster' + oldest_article = 7 + max_articles_per_feed = 100 + language = 'de' + cover_url = 'http://upload.wikimedia.org/wikipedia/de/3/3b/Spektrum_der_Wissenschaft_Logo.svg' + + remove_tags = [dict(attrs={'class':['hauptnaviPkt gainlayout', 'hauptnaviButton', 'suchButton', 'suchbegriffKasten', 'loginButton', 'subnavigation', 'artikelInfoLeiste gainlayout', 'artikelTools', 'nurLetzteSeite', 'link', 'boxUnterArtikel', 'leserbriefeBlock', 'boxTitel', 'boxInhalt', 'sehrklein', 'boxabstand', 'werbeboxinhalt', 'rbabstand', 'bildlinks', 'rechtebox', 'denkmalbox', 'denkmalfrage']}), + dict(id=['pflip', 'verlagsleiste', 'bereich', 'bannerVertikal', 'headerLogoLink', 'kopf', 'topNavi', 'headerSchnellsuche', 'headerSchnellsucheWarten', 'navigation', 'navigationL', 'navigationR', 'inhalt', 'rechtespalte', 'sdwboxenshop', 'shopboxen', 'fuss']), + dict(name=['naservice'])] + + def print_version(self,url): + newurl = url.replace('artikel/', 'sixcms/detail.php?id=') + return newurl + '&_druckversion=1' + + + + feeds = [(u'Spektrum der Wissenschaft', u'http://www.spektrum.de/artikel/982623'), + (u'SpektrumDirekt', u'http://www.spektrumdirekt.de/artikel/996406'), + (u'Sterne und Weltraum', u'http://www.astronomie-heute.de/artikel/865248'), + (u'Gehirn & Geist', u'http://www.gehirn-und-geist.de/artikel/982626'), + (u'epoc', u'http://www.epoc.de/artikel/982625') + +] + + filter_regexps = [r'ads\.doubleclick\.net'] diff --git a/recipes/technology_review_de.recipe b/recipes/technology_review_de.recipe new file mode 100644 index 0000000000..b8b67d24b6 --- /dev/null +++ b/recipes/technology_review_de.recipe @@ -0,0 +1,24 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe +class AdvancedUserRecipe1303841067(BasicNewsRecipe): + + title = u'Technology Review' + __author__ = 'schuster' + remove_tags_before = dict(id='keywords') + remove_tags_after = dict(id='kommentar') + remove_tags = [dict(attrs={'class':['navi_oben_pvg', 'navi_oben_tarifr', 'navi_oben_itm', 'navi_oben_eve', 'navi_oben_whi', 'navi_oben_abo', 'navi_oben_shop', 'navi_top_logo', 'navi_top_abschnitt', 'first']}), + dict(id=['footer', 'toolsRight', 'articleInline', 'navigation', 'archive', 'side_search', 'blog_sidebar', 'side_tool', 'side_index']), + dict(name=['script', 'noscript', 'style'])] + oldest_article = 4 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + language = 'de' + remove_javascript = True + + def print_version(self, url): + return url + '?view=print' + + + feeds = [ + (u'Technik News', u'http://www.heise.de/tr/news-atom.xml') ] + From 902dc7aad606d0a5fb1a0fb394c8b1355436ed8b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 May 2011 08:34:10 -0600 Subject: [PATCH 15/46] ... --- recipes/bild_de.recipe | 46 +++++++++++++++++++++++++++++++++++++++ recipes/max_planck.recipe | 22 +++++++++++++++++++ recipes/ngz.recipe | 29 ++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 recipes/bild_de.recipe create mode 100644 recipes/max_planck.recipe create mode 100644 recipes/ngz.recipe diff --git a/recipes/bild_de.recipe b/recipes/bild_de.recipe new file mode 100644 index 0000000000..ad16b94405 --- /dev/null +++ b/recipes/bild_de.recipe @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +from calibre.web.feeds.recipes import BasicNewsRecipe +class AdvancedUserRecipe1303841067(BasicNewsRecipe): + title = u'Bild.de' + __author__ = 'schuster' + oldest_article = 1 + max_articles_per_feed = 50 + no_stylesheets = True + use_embedded_content = False + language = 'de' + remove_javascript = True + +# get cover from myspace + cover_url = 'http://a3.l3-images.myspacecdn.com/images02/56/0232f842170b4d349779f8379c27e073/l.jpg' + +# set what to fetch on the site + remove_tags_before = dict(name = 'h2', attrs={'id':'cover'}) + remove_tags_after = dict(name ='div', attrs={'class':'back'}) + +# thanx to kiklop74 for code (see sticky thread -> Recipes - Re-usable code) +# this one removes a lot of direct-link's + def preprocess_html(self, soup): + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup + +# remove the ad's + filter_regexps = [r'.\.smartadserver\.com'] + def skip_ad_pages(self, soup): + return None + +#get the real url behind .feedsportal.com and fetch the artikels + def get_article_url(self, article): + return article.get('id', article.get('guid', None)) + +#list of the rss source from www.bild.de + feeds = [(u'Überblick', u'http://rss.bild.de/bild.xml'), + (u'News', u'http://rss.bild.de/bild-news.xml'), + (u'Politik', u'http://rss.bild.de/bild-politik.xml'), + (u'Unterhaltung', u'http://rss.bild.de/bild-unterhaltung.xml'), + (u'Sport', u'http://rss.bild.de/bild-sport.xml'), + (u'Lifestyle', u'http://rss.bild.de/bild-lifestyle.xml'), + (u'Ratgeber', u'http://rss.bild.de/bild-ratgeber.xml') +] diff --git a/recipes/max_planck.recipe b/recipes/max_planck.recipe new file mode 100644 index 0000000000..e9bf62008a --- /dev/null +++ b/recipes/max_planck.recipe @@ -0,0 +1,22 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe +class AdvancedUserRecipe1303841067(BasicNewsRecipe): + + title = u'Max-Planck-Inst.' + __author__ = 'schuster' + remove_tags = [dict(attrs={'class':['clearfix', 'lens', 'col2_box_list', 'col2_box_teaser group_ext no_print', 'dotted_line', 'col2_box_teaser', 'box_image small', 'bold', 'col2_box_teaser no_print', 'print_kontakt']}), + dict(id=['ie_clearing', 'col2', 'col2_content']), + dict(name=['script', 'noscript', 'style'])] + oldest_article = 30 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + language = 'de' + remove_javascript = True + + def print_version(self, url): + split_url = url.split("/") + print_url = 'http://www.mpg.de/print/' + split_url[3] + return print_url + + feeds = [(u'Forschung', u'http://www.mpg.de/de/forschung.rss')] + diff --git a/recipes/ngz.recipe b/recipes/ngz.recipe new file mode 100644 index 0000000000..7265483e53 --- /dev/null +++ b/recipes/ngz.recipe @@ -0,0 +1,29 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe +class AdvancedUserRecipe1303841067(BasicNewsRecipe): + + title = u'NGZ-online' + __author__ = 'schuster' + remove_tags_before = dict(id='bu') + remove_tags_after = dict(id='noblock') + remove_tags = [dict(attrs={'class':['articleTools', 'post-tools', 'side_tool', 'nextArticleLink clearfix', 'liketext']}), + dict(id=['footer', 'toolsRight', 'articleInline', 'navigation', 'archive', 'side_search', 'blog_sidebar', 'side_tool', 'side_index', 'Verlinken', 'vorheriger', 'LESERKOMMENTARE', 'bei facebook', 'bei twitter', 'Schreiben Sie jetzt Ihre Meinung:', 'Thema', 'Ihr Beitrag', 'Ihr Name', 'Ich möchte über weitere Lesermeinungen zu diesem Artikel per E-Mail informiert werden.', 'banneroben', 'bannerrechts', 'inserieren', 'stellen', 'auto', 'immobilien', 'kleinanzeige', 'tiere', 'ferienwohnung', 'NGZ Card', 'Mediengruppe RP', 'Werben', 'Newsletter', 'Wetter', 'RSS', 'Abo', 'Anzeigen', 'Redaktion', 'Schulprojekte', 'Gast', 'Mein NGZ', 'Nachrichten', 'Sport', 'Wirtschaft', 'Stadt-Infos', 'Bilderserien', 'Bookmarken', 'del.icio.us', 'Mister Wong', 'YiGG', 'Webnews', 'Shortnews', 'Twitter', 'Newsider', 'Facebook', 'StudiVZ/MeinVZ', 'Versenden', 'Drucken']), + dict(name=['script', 'noscript', 'style'])] + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + language = 'de' + remove_javascript = True + cover_url = 'http://www.rhein-kreis-neuss-macht-sport.de/sport/includes/bilder/ngz_logo.jpg' + + def print_version(self, url): + return url + '?ot=de.circit.rpo.PopupPageLayout.ot' + feeds = [ +(u'Grevenbroich', u'http://www.ngz-online.de/app/feed/rss/grevenbroich'), +(u'Kreis Neuss', u'http://www.ngz-online.de/app/feed/rss/rheinkreisneuss'), +(u'Dormagen', u'http://www.ngz-online.de/app/feed/rss/dormagen'), +(u'J\xfcchen', u'http://www.ngz-online.de/app/feed/rss/juechen'), +(u'Rommerskirchen', u'http://www.ngz-online.de/app/feed/rss/rommerskirchen') + +] + From ac320e4b57d877baeb7d3ed2b0941d92fa21c8e6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 May 2011 09:33:53 -0600 Subject: [PATCH 16/46] Updated golem.de --- recipes/golem_de.recipe | 143 ++++++++++++++++++---------------------- 1 file changed, 65 insertions(+), 78 deletions(-) diff --git a/recipes/golem_de.recipe b/recipes/golem_de.recipe index 77374cb162..2a1fcca18b 100644 --- a/recipes/golem_de.recipe +++ b/recipes/golem_de.recipe @@ -1,83 +1,70 @@ -#!/usr/bin/env python +from calibre.web.feeds.recipes import BasicNewsRecipe + +class AdvancedUserRecipe1303841067(BasicNewsRecipe): -from calibre.web.feeds.news import BasicNewsRecipe -class golem_ger(BasicNewsRecipe): title = u'Golem.de' - language = 'de' - __author__ = 'Kovid Goyal' + __author__ = 'schuster' + oldest_article = 7 - max_articles_per_feed = 100 - language = 'de' - lang = 'de-DE' - no_stylesheets = True - encoding = 'iso-8859-1' - recursions = 1 - match_regexps = [r'http://www.golem.de/.*.html'] - - keep_only_tags = [ - dict(name='h1', attrs={'class':'artikelhead'}), - dict(name='p', attrs={'class':'teaser'}), - dict(name='div', attrs={'class':'artikeltext'}), - dict(name='h2', attrs={'id':'artikelhead'}), - ] - - - - remove_tags = [ - dict(name='div', attrs={'id':['similarContent','topContentWrapper','storycarousel','aboveFootPromo','comments','toolbar','breadcrumbs','commentlink','sidebar','rightColumn']}), - dict(name='div', attrs={'class':['gg_embeddedSubText','gg_embeddedIndex gg_solid','gg_toOldGallery','golemGallery']}), - dict(name='img', attrs={'class':['gg_embedded','gg_embeddedIconRight gg_embeddedIconFS gg_cursorpointer']}), - dict(name='td', attrs={'class':['xsmall']}), - ] - - - # remove_tags_after = [ - # dict(name='div', attrs={'id':['contentad2']}) - # ] - - - feeds = [ - (u'Golem.de', u'http://rss.golem.de/rss.php?feed=ATOM1.0'), - (u'Audio/Video', u'http://rss.golem.de/rss.php?tp=av&feed=RSS2.0'), - (u'Foto', u'http://rss.golem.de/rss.php?tp=foto&feed=RSS2.0'), - (u'Games', u'http://rss.golem.de/rss.php?tp=games&feed=RSS2.0'), - (u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS1.0'), - (u'Mobil', u'http://rss.golem.de/rss.php?tp=mc&feed=ATOM1.0'), - (u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS1.0'), - (u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=ATOM1.0'), - (u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'), - (u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'), - (u'Wirtschaft', u'http://rss.golem.de/rss.php?tp=wirtschaft&feed=RSS2.0'), - (u'Hardware', u'http://rss.golem.de/rss.php?r=hw&feed=RSS2.0'), - (u'Software', u'http://rss.golem.de/rss.php?r=sw&feed=RSS2.0'), - (u'Networld', u'http://rss.golem.de/rss.php?r=nw&feed=RSS2.0'), - (u'Entertainment', u'http://rss.golem.de/rss.php?r=et&feed=RSS2.0'), - (u'TK', u'http://rss.golem.de/rss.php?r=tk&feed=RSS2.0'), - (u'E-Commerce', u'http://rss.golem.de/rss.php?r=ec&feed=RSS2.0'), - (u'Unternehmen/Maerkte', u'http://rss.golem.de/rss.php?r=wi&feed=RSS2.0') - ] - - - - - feeds = [ - (u'Golem.de', u'http://rss.golem.de/rss.php?feed=ATOM1.0'), - (u'Mobil', u'http://rss.golem.de/rss.php?tp=mc&feed=feed=RSS2.0'), - (u'OSS', u'http://rss.golem.de/rss.php?tp=oss&feed=RSS2.0'), - (u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=RSS2.0'), - (u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'), - (u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'), - ] - - + max_articles_per_feed = 10 + no_stylesheets = True + use_embedded_content = False + language = 'de' + cover_url = 'http://www.e-energy.de/images/logo_golem.jpg' + masthead_url = 'http://www.golem.de/staticrl/images/logo.png' extra_css = ''' - h1 {color:#0066CC;font-family:Arial,Helvetica,sans-serif; font-size:30px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:20px;margin-bottom:2 em;} - h2 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:22px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; } - h3 {color:#4D4D4D;font-family:Arial,Helvetica,sans-serif; font-size:x-small; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal; line-height:5px;} - h4 {color:#333333; font-family:Arial,Helvetica,sans-serif;font-size:13px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:13px; } - h5 {color:#333333; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:11px; text-transform:uppercase;} - .teaser {font-style:italic;font-size:12pt;margin-bottom:15pt;} - .xsmall{font-style:italic;font-size:x-small;} - .td{font-style:italic;font-size:x-small;} - img {align:left;} + h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;} + h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;} + ''' + remove_javascript = True + remove_tags_befor = [dict(name='header', attrs={'class':'cluster-header'})] + remove_tags_after = [dict(name='p', attrs={'class':'meta'})] + remove_tags = [dict(rel='nofollow'), + dict(name='header', attrs={'id':'header'}), + dict(name='div', attrs={'class':'dh1'}), + dict(name='label', attrs={'class':'implied'}), + dict(name='section', attrs={'id':'comments'}), + dict(name='li', attrs={'class':'gg_prebackcounterItem'}), + dict(name='li', attrs={'class':'gg_prebackcounterItem gg_embeddedIndexCounter'}), + dict(name='img', attrs={'class':'gg_embeddedIconRight gg_embeddedIconFS gg_cursorpointer'}), + dict(name='div', attrs={'target':'_blank'}) +] + + def get_browser(self, *args, **kwargs): + from calibre import browser + kwargs['user_agent'] = 'mozilla' + return browser(*args, **kwargs) + + def get_article_url(self, article): + return article.get('id', article.get('guid', None)) + + def preprocess_html(self, soup): + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup + + feeds = [(u'Audio/Video', u'http://rss.golem.de/rss.php?tp=av&feed=RSS2.0'), + (u'Foto', u'http://rss.golem.de/rss.php?tp=foto&feed=RSS2.0'), + (u'Games', u'http://rss.golem.de/rss.php?tp=games&feed=RSS2.0'), + (u'Handy', u'http://rss.golem.de/rss.php?tp=handy&feed=RSS2.0'), + (u'Internet', u'http://rss.golem.de/rss.php?tp=inet&feed=RSS2.0'), + (u'Mobile', u'http://rss.golem.de/rss.php?tp=mc&feed=RSS2.0'), + (u'OSS', u'http://rss.golem.de/rss.php?tp=oss&feed=RSS2.0'), + (u'Politik/Recht', u'http://rss.golem.de/rss.php?tp=pol&feed=RSS2.0'), + (u'Security', u'http://rss.golem.de/rss.php?tp=sec&feed=RSS2.0'), + (u'Desktop-Applikationen', u'http://rss.golem.de/rss.php?tp=apps&feed=RSS2.0'), + (u'Software-Entwicklung', u'http://rss.golem.de/rss.php?tp=dev&feed=RSS2.0'), + (u'Wirtschaft', u'http://rss.golem.de/rss.php?tp=wirtschaft&feed=RSS2.0'), + (u'Hardware', u'http://rss.golem.de/rss.php?r=hw&feed=RSS2.0'), + (u'Software', u'http://rss.golem.de/rss.php?r=sw&feed=RSS2.0'), + (u'Networld', u'http://rss.golem.de/rss.php?r=nw&feed=RSS2.0'), + (u'Entertainment', u'http://rss.golem.de/rss.php?r=et&feed=RSS2.0'), + (u'TK', u'http://rss.golem.de/rss.php?r=tk&feed=RSS2.0'), + (u'Wirtschaft', u'http://rss.golem.de/rss.php?r=wi&feed=RSS2.0'), + (u'E-Commerce', u'http://rss.golem.de/rss.php?r=ec&feed=RSS2.0') + +] + From 5af35ae15ff4b7f62609907f258f076656aaa552 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 May 2011 09:35:55 -0600 Subject: [PATCH 17/46] Updated faz.net --- recipes/faznet.recipe | 79 ++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/recipes/faznet.recipe b/recipes/faznet.recipe index 13093c82af..01a46d43ba 100644 --- a/recipes/faznet.recipe +++ b/recipes/faznet.recipe @@ -1,51 +1,38 @@ -__license__ = 'GPL v3' -__copyright__ = '2008-2009, Kovid Goyal , Darko Miletic ' -''' -Profile to download FAZ.net -''' +from calibre.web.feeds.recipes import BasicNewsRecipe +class AdvancedUserRecipe1303841067(BasicNewsRecipe): -from calibre.web.feeds.news import BasicNewsRecipe - -class FazNet(BasicNewsRecipe): - title = 'FAZ NET' - __author__ = 'Kovid Goyal, Darko Miletic' + title = u'Faz.net' + __author__ = 'schuster' + remove_tags = [dict(attrs={'class':['right', 'ArrowLinkRight', 'ModulVerlagsInfo', 'left', 'Head']}), + dict(id=['BreadCrumbs', 'tstag', 'FazFooterPrint']), + dict(name=['script', 'noscript', 'style'])] + oldest_article = 2 description = 'Frankfurter Allgemeine Zeitung' - publisher = 'FAZ Electronic Media GmbH' - category = 'news, politics, Germany' - use_embedded_content = False - language = 'de' - - max_articles_per_feed = 30 - no_stylesheets = True - encoding = 'utf-8' - remove_javascript = True - - html2lrf_options = [ - '--comment', description - , '--category', category - , '--publisher', publisher - ] - - html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' - - keep_only_tags = [dict(name='div', attrs={'class':'Article'})] - - remove_tags = [ - dict(name=['object','link','embed','base']) - ,dict(name='div', attrs={'class':['LinkBoxModulSmall','ModulVerlagsInfo']}) - ] - - - feeds = [ ('FAZ.NET', 'http://www.faz.net/s/Rub/Tpl~Epartner~SRss_.xml') ] + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + language = 'de' + remove_javascript = True + cover_url = 'http://www.faz.net/f30/Images/Logos/logo.gif' def print_version(self, url): - article, sep, rest = url.partition('?') - return article.replace('.html', '~Afor~Eprint.html') + return url.replace('.html', '~Afor~Eprint.html') + + + + feeds = [(u'Politik', u'http://www.faz.net/s/RubA24ECD630CAE40E483841DB7D16F4211/Tpl~Epartner~SRss_.xml'), + (u'Wirtschaft', u'http://www.faz.net/s/RubC9401175958F4DE28E143E68888825F6/Tpl~Epartner~SRss_.xml'), + (u'Feuilleton', u'http://www.faz.net/s/RubCC21B04EE95145B3AC877C874FB1B611/Tpl~Epartner~SRss_.xml'), + (u'Sport', u'http://www.faz.net/s/Rub9F27A221597D4C39A82856B0FE79F051/Tpl~Epartner~SRss_.xml'), + (u'Gesellschaft', u'http://www.faz.net/s/Rub02DBAA63F9EB43CEB421272A670A685C/Tpl~Epartner~SRss_.xml'), + (u'Finanzen', u'http://www.faz.net/s/Rub4B891837ECD14082816D9E088A2D7CB4/Tpl~Epartner~SRss_.xml'), + (u'Wissen', u'http://www.faz.net/s/Rub7F4BEE0E0C39429A8565089709B70C44/Tpl~Epartner~SRss_.xml'), + (u'Reise', u'http://www.faz.net/s/RubE2FB5CA667054BDEA70FB3BC45F8D91C/Tpl~Epartner~SRss_.xml'), + (u'Technik & Motor', u'http://www.faz.net/s/Rub01E4D53776494844A85FDF23F5707AD8/Tpl~Epartner~SRss_.xml'), + (u'Beruf & Chance', u'http://www.faz.net/s/RubB1E10A8367E8446897468EDAA6EA0504/Tpl~Epartner~SRss_.xml'), + (u'Kunstmarkt', u'http://www.faz.net/s/RubBC09F7BF72A2405A96718ECBFB68FBFE/Tpl~Epartner~SRss_.xml'), + (u'Immobilien ', u'http://www.faz.net/s/RubFED172A9E10F46B3A5F01B02098C0C8D/Tpl~Epartner~SRss_.xml'), + (u'Rhein-Main Zeitung', u'http://www.faz.net/s/RubABE881A6669742C2A5EBCB5D50D7EBEE/Tpl~Epartner~SRss_.xml'), + (u'Atomdebatte ', u'http://www.faz.net/s/Rub469C43057F8C437CACC2DE9ED41B7950/Tpl~Epartner~SRss_.xml') + ] - def preprocess_html(self, soup): - mtag = '' - soup.head.insert(0,mtag) - del soup.body['onload'] - for item in soup.findAll(style=True): - del item['style'] - return soup From c6066ce7e3dd9441a8903996a252806319cb1653 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 May 2011 10:21:48 -0600 Subject: [PATCH 18/46] Fix #782742 (Download metadata never finds anything) --- src/calibre/utils/titlecase.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/calibre/utils/titlecase.py b/src/calibre/utils/titlecase.py index ff5b446268..12f3079a10 100755 --- a/src/calibre/utils/titlecase.py +++ b/src/calibre/utils/titlecase.py @@ -10,7 +10,6 @@ License: http://www.opensource.org/licenses/mit-license.php import re from calibre.utils.icu import capitalize -from calibre.utils.config import prefs __all__ = ['titlecase'] __version__ = '0.5' @@ -31,6 +30,17 @@ ALL_CAPS = re.compile(r'^[A-Z\s%s]+$' % PUNCT) UC_INITIALS = re.compile(r"^(?:[A-Z]{1}\.{1}|[A-Z]{1}\.{1}[A-Z]{1})+$") MAC_MC = re.compile(r"^([Mm]a?c)(.+)") + +_lang = None + +def lang(): + global _lang + if _lang is None: + from calibre.utils.localization import get_lang + _lang = get_lang().lower() + return _lang + + def titlecase(text): """ @@ -68,7 +78,7 @@ def titlecase(text): line.append(icu_lower(word)) continue - if prefs['language'].lower().startswith('en'): + if lang().startswith('en'): match = MAC_MC.match(word) if match and not match.group(2)[:3] in ('hin', 'ht'): line.append("%s%s" % (capitalize(match.group(1)), From 7912b658f5fe15c434c46362ceed9e084c31601f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 May 2011 10:46:46 -0600 Subject: [PATCH 19/46] Fix Dagens Nyheter --- recipes/dn_se.recipe | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/recipes/dn_se.recipe b/recipes/dn_se.recipe index cd0ce8a8a2..a071d78918 100644 --- a/recipes/dn_se.recipe +++ b/recipes/dn_se.recipe @@ -37,7 +37,7 @@ class DN_se(BasicNewsRecipe): ,(u'Kultur' , u'http://www.dn.se/kultur-rss' ) ] - keep_only_tags = [dict(name='div', attrs={'id':'article'})] + keep_only_tags = [dict(name='div', attrs={'id':'article-content'})] remove_tags_before = dict(name='h1') remove_tags_after = dict(name='div',attrs={'id':'byline'}) remove_tags = [ @@ -45,5 +45,5 @@ class DN_se(BasicNewsRecipe): ,dict(name='div',attrs={'id':'hook'}) ] - - + + From 997a943257482e28aef34dba543306d0478b04a0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 May 2011 10:49:29 -0600 Subject: [PATCH 20/46] Fix #782408 (.mobi import broken in 0.8.1) --- setup/installer/windows/__init__.py | 1 - setup/resources.py | 31 ++++++++----------- .../ebooks/unihandecode/pykakasi/jisyo.py | 22 +++++-------- 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/setup/installer/windows/__init__.py b/setup/installer/windows/__init__.py index 59042ac56c..b51eccc832 100644 --- a/setup/installer/windows/__init__.py +++ b/setup/installer/windows/__init__.py @@ -32,7 +32,6 @@ class Win32(VMInstaller): FREEZE_TEMPLATE = 'python -OO setup.py {freeze_command} --no-ice' INSTALLER_EXT = 'msi' SHUTDOWN_CMD = ['shutdown.exe', '-s', '-f', '-t', '0'] - BUILD_BUILD = ['python setup.py kakasi',] + VMInstaller.BUILD_BUILD def download_installer(self): installer = self.installer() diff --git a/setup/resources.py b/setup/resources.py index 8d988dbab2..15772e1830 100644 --- a/setup/resources.py +++ b/setup/resources.py @@ -6,10 +6,10 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, cPickle, re, anydbm, shutil, marshal, zipfile, glob +import os, cPickle, re, shutil, marshal, zipfile, glob from zlib import compress -from setup import Command, basenames, __appname__, iswindows +from setup import Command, basenames, __appname__ def get_opts_from_parser(parser): def do_opt(opt): @@ -34,12 +34,12 @@ class Kakasi(Command): self.records = {} src = self.j(self.KAKASI_PATH, 'kakasidict.utf8') dest = self.j(self.RESOURCES, 'localization', - 'pykakasi','kanwadict2.db') + 'pykakasi','kanwadict2.pickle') base = os.path.dirname(dest) if not os.path.exists(base): os.makedirs(base) - if self.newer(dest, src) or iswindows: + if self.newer(dest, src): self.info('\tGenerating Kanwadict') for line in open(src, "r"): @@ -50,7 +50,7 @@ class Kakasi(Command): dest = self.j(self.RESOURCES, 'localization', 'pykakasi','itaijidict2.pickle') - if self.newer(dest, src) or iswindows: + if self.newer(dest, src): self.info('\tGenerating Itaijidict') self.mkitaiji(src, dest) @@ -58,7 +58,7 @@ class Kakasi(Command): dest = self.j(self.RESOURCES, 'localization', 'pykakasi','kanadict2.pickle') - if self.newer(dest, src) or iswindows: + if self.newer(dest, src): self.info('\tGenerating kanadict') self.mkkanadict(src, dest) @@ -75,7 +75,7 @@ class Kakasi(Command): continue pair = re.sub(r'\\u([0-9a-fA-F]{4})', lambda x:unichr(int(x.group(1),16)), line) dic[pair[0]] = pair[1] - cPickle.dump(dic, open(dst, 'w'), protocol=-1) #pickle + cPickle.dump(dic, open(dst, 'wb'), protocol=-1) #pickle def mkkanadict(self, src, dst): dic = {} @@ -87,7 +87,7 @@ class Kakasi(Command): continue (alpha, kana) = line.split(' ') dic[kana] = alpha - cPickle.dump(dic, open(dst, 'w'), protocol=-1) #pickle + cPickle.dump(dic, open(dst, 'wb'), protocol=-1) #pickle def parsekdict(self, line): line = line.decode("utf-8").strip() @@ -115,16 +115,11 @@ class Kakasi(Command): self.records[key][kanji]=[(yomi, tail)] def kanwaout(self, out): - try: - # Needed as otherwise anydbm tries to create a gdbm db when the db - # created on Unix is found - os.remove(out) - except: - pass - dic = anydbm.open(out, 'n') - for (k, v) in self.records.iteritems(): - dic[k] = compress(marshal.dumps(v)) - dic.close() + with open(out, 'wb') as f: + dic = {} + for k, v in self.records.iteritems(): + dic[k] = compress(marshal.dumps(v)) + cPickle.dump(dic, f, -1) def clean(self): kakasi = self.j(self.RESOURCES, 'localization', 'pykakasi') diff --git a/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py b/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py index d56e409ee5..03b2c8024f 100644 --- a/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py +++ b/src/calibre/ebooks/unihandecode/pykakasi/jisyo.py @@ -2,12 +2,8 @@ # jisyo.py # # Copyright 2011 Hiroshi Miura -from cPickle import load -import anydbm,marshal +import cPickle, marshal from zlib import decompress -import os - -import calibre.utils.resources as resources class jisyo (object): kanwadict = None @@ -25,16 +21,14 @@ class jisyo (object): def __init__(self): if self.kanwadict is None: - dictpath = resources.get_path(os.path.join('localization','pykakasi','kanwadict2.db')) - self.kanwadict = anydbm.open(dictpath,'r') - if self.itaijidict is None: - itaijipath = resources.get_path(os.path.join('localization','pykakasi','itaijidict2.pickle')) - itaiji_pkl = open(itaijipath, 'rb') - self.itaijidict = load(itaiji_pkl) + self.kanwadict = cPickle.loads( + P('localization/pykakasi/kanwadict2.pickle', data=True)) + if self.itaijidict is None: + self.itaijidict = cPickle.loads( + P('localization/pykakasi/itaijidict2.pickle', data=True)) if self.kanadict is None: - kanadictpath = resources.get_path(os.path.join('localization','pykakasi','kanadict2.pickle')) - kanadict_pkl = open(kanadictpath, 'rb') - self.kanadict = load(kanadict_pkl) + self.kanadict = cPickle.loads( + P('localization/pykakasi/kanadict2.pickle', data=True)) def load_jisyo(self, char): try:#python2 From 010b2e808130139d5b3608f2c023fd3a518641c6 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 May 2011 12:35:54 -0600 Subject: [PATCH 21/46] MOBI Input: Don't error out when detecting TOC structure if one of the elements has an invalid margin unit --- src/calibre/ebooks/mobi/reader.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 3d858864a8..934e8476d2 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -690,6 +690,14 @@ class MobiReader(object): lm = unit_convert('2em', 12, 500, 166) lm = self.left_margins.get(tag, lm) ti = self.text_indents.get(tag, ti) + try: + lm = float(lm) + except: + lm = 0.0 + try: + ti = float(ti) + except: + ti = 0.0 return lm + ti parent = tag From efcf6efa9c083a619fce03b039c721ad8bb8f38b Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 15 May 2011 18:49:55 -0400 Subject: [PATCH 22/46] Store: Add archive.org plugin. --- src/calibre/customize/builtins.py | 10 ++- src/calibre/gui2/store/archive_org_plugin.py | 89 ++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 src/calibre/gui2/store/archive_org_plugin.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index ce8c7f71cb..2677e008ae 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1107,6 +1107,12 @@ class StoreAmazonUKKindleStore(StoreBase): description = _('Kindle books from Amazon.uk') actual_plugin = 'calibre.gui2.store.amazon_uk_plugin:AmazonUKKindleStore' +class StoreArchiveOrgStore(StoreBase): + name = 'Archive.org' + description = _('Rree Books : Download & Streaming : Ebook and Texts Archive : Internet Archive') + actual_plugin = 'calibre.gui2.store.archive_org_plugin:ArchiveOrgStore' + + class StoreBaenWebScriptionStore(StoreBase): name = 'Baen WebScription' description = _('Ebooks for readers.') @@ -1202,8 +1208,8 @@ class StoreWizardsTowerBooksStore(StoreBase): description = 'Wizard\'s Tower Press' actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore' -plugins += [StoreAmazonKindleStore, StoreAmazonDEKindleStore, StoreAmazonUKKindleStore, - StoreBaenWebScriptionStore, StoreBNStore, +plugins += [StoreArchiveOrgStore, StoreAmazonKindleStore, StoreAmazonDEKindleStore, + StoreAmazonUKKindleStore, StoreBaenWebScriptionStore, StoreBNStore, StoreBeamEBooksDEStore, StoreBeWriteStore, StoreDieselEbooksStore, StoreEbookscomStore, StoreEPubBuyDEStore, StoreEHarlequinStore, StoreFeedbooksStore, diff --git a/src/calibre/gui2/store/archive_org_plugin.py b/src/calibre/gui2/store/archive_org_plugin.py new file mode 100644 index 0000000000..e8e96b3839 --- /dev/null +++ b/src/calibre/gui2/store/archive_org_plugin.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember ' +__docformat__ = 'restructuredtext en' + +import urllib +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser, url_slash_cleaner +from calibre.gui2 import open_url +from calibre.gui2.store import StorePlugin +from calibre.gui2.store.basic_config import BasicStoreConfig +from calibre.gui2.store.search_result import SearchResult +from calibre.gui2.store.web_store_dialog import WebStoreDialog + +class ArchiveOrgStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://www.archive.org/details/texts' + + if detail_item: + detail_item = url_slash_cleaner('http://www.archive.org' + detail_item) + + if external or self.config.get('open_external', False): + open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url))) + else: + d = WebStoreDialog(self.gui, url, parent, detail_item) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + def search(self, query, max_results=10, timeout=60): + query = query + ' AND mediatype:texts' + url = 'http://www.archive.org/search.php?query=' + urllib.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('//td[@class="hitCell"]'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//a[@class="titleLink"]/@href')) + if not id: + continue + + title = ''.join(data.xpath('.//a[@class="titleLink"]//text()')) + authors = data.xpath('.//text()') + if not authors: + continue + author = None + for a in authors: + if '-' in a: + author = a.replace('-', ' ').strip() + if author: + break + if not author: + continue + + counter -= 1 + + s = SearchResult() + s.title = title.strip() + s.author = author.strip() + s.price = '$0.00' + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED + + yield s + + def get_details(self, search_result, timeout): + url = url_slash_cleaner('http://www.archive.org' + search_result.detail_item) + + br = browser() + with closing(br.open(url, timeout=timeout)) as nf: + idata = html.fromstring(nf.read()) + formats = ', '.join(idata.xpath('//p[@id="dl" and @class="content"]//a/text()')) + search_result.formats = formats.upper() + + return True From a26557414cf8734417db61775b2abe61074a262a Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 15 May 2011 19:54:46 -0400 Subject: [PATCH 23/46] Store: Cleanup descriptions. Add Nexto store plugin. --- src/calibre/customize/builtins.py | 34 +++++----- src/calibre/gui2/store/nexto_plugin.py | 86 ++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 src/calibre/gui2/store/nexto_plugin.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index 2677e008ae..761ec05402 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1,4 +1,5 @@ -import os.path +# -*- coding: utf-8 -*- + __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' @@ -1094,22 +1095,22 @@ plugins += [LookAndFeel, Behavior, Columns, Toolbar, Search, InputOptions, # Store plugins {{{ class StoreAmazonKindleStore(StoreBase): name = 'Amazon Kindle' - description = _('Kindle books from Amazon') + description = _('Kindle books from Amazon.') actual_plugin = 'calibre.gui2.store.amazon_plugin:AmazonKindleStore' class StoreAmazonDEKindleStore(StoreBase): name = 'Amazon DE Kindle' - description = _('Kindle eBooks') + description = _('Kindle books form Amazon.de.') actual_plugin = 'calibre.gui2.store.amazon_de_plugin:AmazonDEKindleStore' class StoreAmazonUKKindleStore(StoreBase): name = 'Amazon UK Kindle' - description = _('Kindle books from Amazon.uk') + description = _('Kindle books from Amazon.uk.') actual_plugin = 'calibre.gui2.store.amazon_uk_plugin:AmazonUKKindleStore' class StoreArchiveOrgStore(StoreBase): name = 'Archive.org' - description = _('Rree Books : Download & Streaming : Ebook and Texts Archive : Internet Archive') + description = _('Free Books : Download & Streaming : Ebook and Texts Archive : Internet Archive.') actual_plugin = 'calibre.gui2.store.archive_org_plugin:ArchiveOrgStore' @@ -1125,7 +1126,7 @@ class StoreBNStore(StoreBase): class StoreBeamEBooksDEStore(StoreBase): name = 'Beam EBooks DE' - description = _('der eBook Shop') + description = _('Der eBook Shop.') actual_plugin = 'calibre.gui2.store.beam_ebooks_de_plugin:BeamEBooksDEStore' class StoreBeWriteStore(StoreBase): @@ -1145,12 +1146,12 @@ class StoreEbookscomStore(StoreBase): class StoreEPubBuyDEStore(StoreBase): name = 'EPUBBuy DE' - description = _('EPUBReaders eBook Shop') + description = _('EPUBReaders eBook Shop.') actual_plugin = 'calibre.gui2.store.epubbuy_de_plugin:EPubBuyDEStore' class StoreEHarlequinStore(StoreBase): name = 'eHarlequin' - description = _('entertain, enrich, inspire.') + description = _('Entertain, enrich, inspire.') actual_plugin = 'calibre.gui2.store.eharlequin_plugin:EHarlequinStore' class StoreFeedbooksStore(StoreBase): @@ -1160,7 +1161,7 @@ class StoreFeedbooksStore(StoreBase): class StoreFoylesUKStore(StoreBase): name = 'Foyles UK' - description = _('Foyles of London, online') + description = _('Foyles of London, online.') actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore' class StoreGutenbergStore(StoreBase): @@ -1180,9 +1181,14 @@ class StoreManyBooksStore(StoreBase): class StoreMobileReadStore(StoreBase): name = 'MobileRead' - description = _('Ebooks handcrafted with the utmost care') + description = _('Ebooks handcrafted with the utmost care.') actual_plugin = 'calibre.gui2.store.mobileread.mobileread_plugin:MobileReadStore' +class StoreNextoStore(StoreBase): + name = 'Nexto' + description = _('Audiobooki mp3, ebooki, prasa - księgarnia internetowa.') + actual_plugin = 'calibre.gui2.store.nexto_plugin:NextoStore' + class StoreOpenLibraryStore(StoreBase): name = 'Open Library' description = _('One web page for every book.') @@ -1195,17 +1201,17 @@ class StoreSmashwordsStore(StoreBase): class StoreWaterstonesUKStore(StoreBase): name = 'Waterstones UK' - description = _('Feel every word') + description = _('Feel every word.') actual_plugin = 'calibre.gui2.store.waterstones_uk_plugin:WaterstonesUKStore' class StoreWeightlessBooksStore(StoreBase): name = 'Weightless Books' - description = '(e)Books That Don\'t Weigh You Down' + description = '(e)Books That Don\'t Weigh You Down.' actual_plugin = 'calibre.gui2.store.weightless_books_plugin:WeightlessBooksStore' class StoreWizardsTowerBooksStore(StoreBase): name = 'Wizards Tower Books' - description = 'Wizard\'s Tower Press' + description = 'Wizard\'s Tower Press.' actual_plugin = 'calibre.gui2.store.wizards_tower_books_plugin:WizardsTowerBooksStore' plugins += [StoreArchiveOrgStore, StoreAmazonKindleStore, StoreAmazonDEKindleStore, @@ -1214,7 +1220,7 @@ plugins += [StoreArchiveOrgStore, StoreAmazonKindleStore, StoreAmazonDEKindleSto StoreDieselEbooksStore, StoreEbookscomStore, StoreEPubBuyDEStore, StoreEHarlequinStore, StoreFeedbooksStore, StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore, - StoreMobileReadStore, StoreOpenLibraryStore, StoreSmashwordsStore, + StoreMobileReadStore, StoreNextoStore, StoreOpenLibraryStore, StoreSmashwordsStore, StoreWaterstonesUKStore, StoreWeightlessBooksStore, StoreWizardsTowerBooksStore] # }}} diff --git a/src/calibre/gui2/store/nexto_plugin.py b/src/calibre/gui2/store/nexto_plugin.py new file mode 100644 index 0000000000..154274d12a --- /dev/null +++ b/src/calibre/gui2/store/nexto_plugin.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, Tomasz Długosz ' +__docformat__ = 'restructuredtext en' + +import re +import urllib +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser, url_slash_cleaner +from calibre.gui2 import open_url +from calibre.gui2.store import StorePlugin +from calibre.gui2.store.basic_config import BasicStoreConfig +from calibre.gui2.store.search_result import SearchResult +from calibre.gui2.store.web_store_dialog import WebStoreDialog + +class NextoStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + pid = '155711' + + url = 'http://www.nexto.pl/ebooki_c1015.xml?pid=' + pid + detail_url = None + + if detail_item: + book_id = re.search(r'p[0-9]*\.xml\Z', detail_item) + book_id = book_id.group(0).replace('.xml','').replace('p','') + if book_id: + detail_url = 'http://www.nexto.pl/rf/pr?p=' + book_id + '&pid=' + pid + + 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_() + + def search(self, query, max_results=10, timeout=60): + url = 'http://www.nexto.pl/szukaj.xml?search-clause=' + urllib.quote_plus(query.encode('utf-8')) + '&scid=1015' + + br = browser() + + counter = max_results + with closing(br.open(url, timeout=timeout)) as f: + doc = html.fromstring(f.read()) + for data in doc.xpath('//ul[@class="productslist"]/li'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//div[@class="cover_container"]/a[1]/@href')) + if not id: + continue + + price = ''.join(data.xpath('.//strong[@class="nprice"]/text()')) + + cover_url = ''.join(data.xpath('.//img[@class="cover"]/@src')) + title = ''.join(data.xpath('.//a[@class="title"]/text()')) + formats = ', '.join(data.xpath('.//ul[@class="formats_available"]/li//b/text()')) + DrmFree = re.search(r'bez.DRM', formats) + formats = re.sub(r'\(.+\)', '', formats) + + author = '' + with closing(br.open('http://www.nexto.pl/' + id.strip(), timeout=timeout/4)) as nf: + idata = html.fromstring(nf.read()) + author = ''.join(idata.xpath('//div[@class="basic_data"]/p[1]/b/a/text()')) + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED if DrmFree else SearchResult.DRM_LOCKED + s.formats = formats.upper().strip() + + yield s From e4c6592dcfa8ef311c043eed8bb8af430dc94b03 Mon Sep 17 00:00:00 2001 From: John Schember Date: Sun, 15 May 2011 20:10:51 -0400 Subject: [PATCH 24/46] Store: Search: Fix scrolling of store checkboxes. --- src/calibre/gui2/store/search/search.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index e9c9c149a7..29bd6822a9 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en' import re from random import shuffle -from PyQt4.Qt import (Qt, QDialog, QTimer, QCheckBox, QVBoxLayout, QIcon) +from PyQt4.Qt import (Qt, QDialog, QTimer, QCheckBox, QVBoxLayout, QIcon, QWidget) from calibre.gui2 import JSONConfig, info_dialog from calibre.gui2.progress_indicator import ProgressIndicator @@ -47,14 +47,16 @@ class SearchDialog(QDialog, Ui_Dialog): # Add check boxes for each store so the user # can disable searching specific stores on a # per search basis. + stores_check_widget = QWidget() stores_group_layout = QVBoxLayout() - self.stores_group.setLayout(stores_group_layout) + stores_check_widget.setLayout(stores_group_layout) for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()): cbox = QCheckBox(x) cbox.setChecked(True) stores_group_layout.addWidget(cbox) setattr(self, 'store_check_' + x, cbox) stores_group_layout.addStretch() + self.stores_group.setWidget(stores_check_widget) # Set the search query self.search_edit.setText(query) From 517e75316b480570ab7848a901b99b1cd77b2bac Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 May 2011 09:28:45 -0600 Subject: [PATCH 25/46] Good House Keeping by Anonymous --- recipes/good_house_keeping.recipe | 31 +++++++++++++++++++++++++++++++ src/calibre/ebooks/mobi/reader.py | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 recipes/good_house_keeping.recipe diff --git a/recipes/good_house_keeping.recipe b/recipes/good_house_keeping.recipe new file mode 100644 index 0000000000..1dc26e88e7 --- /dev/null +++ b/recipes/good_house_keeping.recipe @@ -0,0 +1,31 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1305547242(BasicNewsRecipe): + title = u'Good House Keeping' + language = 'en' + __author__ = 'Anonymous' + oldest_article = 7 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + remove_javascript = True + + def print_version(self,url): + segments = url.split('/') + printURL = '/'.join(segments[0:3]) + '/print-this/' + '/'.join(segments[4:]) + return printURL + + def preprocess_html(self, soup): + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup + + feeds = [ (u'Recipes & Entertaining', u'http://www.goodhousekeeping.com/food/food-rss/?src=rss'), + (u'Home & House', u'http://www.goodhousekeeping.com/home/home-rss/?src=rss'), + (u'Diet & Health', u'http://www.goodhousekeeping.com/health/health-rss/?src=rss'), + (u'Beauty & Style', u'http://www.goodhousekeeping.com/beauty/beauty-rss/?src=rss'), + (u'Family & Pets', u'http://www.goodhousekeeping.com/family/family-rss/?src=rss'), + (u'Saving Money', u'http://www.goodhousekeeping.com/money/money-rss/?src=rss'), + ] diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 934e8476d2..2a31732f08 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -693,10 +693,12 @@ class MobiReader(object): try: lm = float(lm) except: + raise lm = 0.0 try: ti = float(ti) except: + raise ti = 0.0 return lm + ti From ad966140a16f3cf9da3f5a7ff12d7c8677d08c08 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 May 2011 14:31:19 -0600 Subject: [PATCH 26/46] ... --- src/calibre/ebooks/mobi/reader.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 2a31732f08..934e8476d2 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -693,12 +693,10 @@ class MobiReader(object): try: lm = float(lm) except: - raise lm = 0.0 try: ti = float(ti) except: - raise ti = 0.0 return lm + ti From 4367a52d941f3c4be40d1e6d745dbee2abc664cb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 May 2011 14:41:24 -0600 Subject: [PATCH 27/46] Cosmopolitan.de by schuster --- recipes/cosmopolitan_de.recipe | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 recipes/cosmopolitan_de.recipe diff --git a/recipes/cosmopolitan_de.recipe b/recipes/cosmopolitan_de.recipe new file mode 100644 index 0000000000..9eb35efa5e --- /dev/null +++ b/recipes/cosmopolitan_de.recipe @@ -0,0 +1,34 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1305567197(BasicNewsRecipe): + title = u'Cosmopolitan.de' + __author__ = 'schuster' + oldest_article = 7 + language = 'de' + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + remove_javascript = True + cover_url = 'http://www.cosmopolitan.com/cm/shared/site_images/print_this/cosmopolitan_logo.gif' + remove_tags_before = dict(name = 'h1', attrs={'class':'artikel'}) + remove_tags_after = dict(name ='div', attrs={'class':'morePages'}) + extra_css = ''' + h2{font-family:Arial,Helvetica,sans-serif; font-size: x-small;} + h1{ font-family:Arial,Helvetica,sans-serif; font-size:x-large; font-weight:bold;} + ''' + remove_tags = [ dict(id='strong'), + dict(title='strong'), + dict(name='span'), + dict(name='li', attrs={'class':'large'}), + dict(name='ul', attrs={'class':'articleImagesPortrait clearfix'}), + dict(name='p', attrs={'class':'external'}), + dict(name='a', attrs={'target':'_blank'}),] + feeds = [ (u'Komplett', u'http://www.cosmopolitan.de/rss/allgemein.xml'), + (u'Mode', u'http://www.cosmopolitan.de/rss/mode.xml'), + (u'Beauty', u'http://www.cosmopolitan.de/rss/beauty.xml'), + (u'Liebe&Sex', u'http://www.cosmopolitan.de/rss/liebe.xml'), + (u'Psychologie', u'http://www.cosmopolitan.de/rss/psychologie.xml'), + (u'Job&Karriere', u'http://www.cosmopolitan.de/rss/job.xml'), + (u'Lifestyle', u'http://www.cosmopolitan.de/rss/lifestyle.xml'), + (u'Shopping', u'http://www.cosmopolitan.de/rss/shopping.xml'), + (u'Bildergalerien', u'http://www.cosmopolitan.de/rss/bildgalerien.xml')] From 454b14c68f04aa8341806a2ed41824a522e91bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20D=C5=82ugosz?= Date: Mon, 16 May 2011 23:29:40 +0200 Subject: [PATCH 28/46] icons for Polish recipes --- recipes/icons/osnews_pl.png | Bin 0 -> 1006 bytes recipes/icons/rmf24_opinie.png | Bin 0 -> 722 bytes recipes/icons/swiatkindle.png | Bin 0 -> 425 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 recipes/icons/osnews_pl.png create mode 100644 recipes/icons/rmf24_opinie.png create mode 100644 recipes/icons/swiatkindle.png diff --git a/recipes/icons/osnews_pl.png b/recipes/icons/osnews_pl.png new file mode 100644 index 0000000000000000000000000000000000000000..157bbefa727e9e1fa700369009b21592d44ecc83 GIT binary patch literal 1006 zcmeAS@N?(olHy`uVBq!ia0vp^0w65F1|noMXEwRgbDb6jnWdSzjJF*K(&S!|It+mE5*9Zc$b=J~9WgAWf5-kGCf=;pFzYiYrQ%6;)#s*`7_ozBiy_frv?wQbw9 z)2T)$pLC1!%g61Wc2-Z{e6_Y|e(nE%Urds>$@UwU{VFlal$i7WZEnsKFLw6y^L0N0 zG-fcaw?+xrRE22V*!KF_ z=i1si#d@bFPtL!$X8rZg=jV7X=Vi7ifA{C-yS($a?`+XIK69R$l9H5kbkwQm?QQ$& z^DCJSAD($;W3sUD-MhQiX_)76tk+SW+}W~<$-QsIDxK?}H{YDBF3haFOJq)9Gco^$N4y?uXsZbHF_D=QbfzpguMz~jnz$e?QxhsSc$-eQMI z9sk0XYceXD{YrLS_Hx%g{+L}??$`gZxNv*6*U`rg?(OS}WQzoZ z9_MsER%CfDbZSQZ{cXD33>`^^2TWv_hcS1YN1K%6^~D;KFP4U~PMfxE zySC_pT&CBrckiCYTJvYe*3?-CHm9FHJ$tspS}~Ty8#@xhr?1oIy7=SA2@gH?JG-Wy zEGcC)HqI3i=`}`^M+z!^5~(kKMY~@c2iagq7dc zsMV~D7ovV!m6g@~Es8zxxyGI+c6%<$dqm`20NSqDlNdpQVoqT`g->FM|P#k;3^ZTi&5_i$yKYo#}aRUpMFc`SaU-9|+{%ulu`ebMg0e5>Fo`Wxov%1KPXz#ksj@GdHGj zI#oU0WD&blX-|9opR216X?1Pj=00Rs%Vkqp^mo@|lhyyFQ{z&Woj?)FK#IZ0z|c_Fz*N`JEX2sr%GA`#&_dh5$jZQ=QtgEwiiX_$l+3hBEEGJR2=mxK8t1*IFkADeVTSsYg?X>3u7 zEm$(S}zdbp1~lsA~}Hl z6${J7_p_Kd>@F=j;KsO8<6_67=M4-VOxNyftZ4SgaIV>PVd28Zrwl`$`>uIZ`s@8W zR^by3v1cz;Z98&mUfmr@VHOwTM8=M^c8vf%5%ayvYOk^+2YYKy<-fT7x8b(AOAGe# zKNAx^?>E^*dV!;fz7B&)u7GLAe}__=!xNvkpL~8y;_sBjZ;Q;g_uYGTaQD~5rzQc+ zoDyb#1jD-3R{9iViQV>-Q;*k~=we)w*6>!`f5V|Qg4gBxdy0i%lYRMlxe27{?l#Gf~o}jci%G845s7H%yIa+E@GKc6~v|xyGERH00)|WTsW(*04Hc Rayd`~gQu&X%Q~loCIBf=9ykC1 literal 0 HcmV?d00001 diff --git a/recipes/icons/swiatkindle.png b/recipes/icons/swiatkindle.png new file mode 100644 index 0000000000000000000000000000000000000000..1fc505bfbfabd98cc547b7844357266c21e1502e GIT binary patch literal 425 zcmeAS@N?(olHy`uVBq!ia0vp^0wBx*Bp9q_EZ7UAn3BBRT^JZv^(q?yd7K3vk;OpT z1B~5HX4?T7uRUEHLnJPno;&Sz*g>N0;r~y2c*NJm?AY>vvunzwlv$Hs${0<~Oq&$q ztC1+g-_ad(mn*i?{$Jo7yFE2E2U@3it>;e8jlBAm%_3Q8a^+48JR4T+`P_Z_^~;zm0Xkxq!^403=MS+Omz*-LW~TpOo7N$+rY@mz+h^CIv Date: Mon, 16 May 2011 18:48:14 -0400 Subject: [PATCH 29/46] Store: Fix Wizards Press store when it returns an exact match by loading the detail page. --- .../gui2/store/wizards_tower_books_plugin.py | 88 +++++++++++++------ 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/src/calibre/gui2/store/wizards_tower_books_plugin.py b/src/calibre/gui2/store/wizards_tower_books_plugin.py index c17ea2ca64..90966fc06a 100644 --- a/src/calibre/gui2/store/wizards_tower_books_plugin.py +++ b/src/calibre/gui2/store/wizards_tower_books_plugin.py @@ -44,40 +44,70 @@ class WizardsTowerBooksStore(BasicStoreConfig, StorePlugin): counter = max_results with closing(br.open(url, timeout=timeout)) as f: doc = html.fromstring(f.read()) - for data in doc.xpath('//table[@class="gridp"]//td'): - if counter <= 0: - break - - id = ''.join(data.xpath('.//span[@class="prti"]/a/@href')) - id = id.strip() - if not id: - continue - - cover_url = ''.join(data.xpath('.//div[@class="prim"]/a/img/@src')) - cover_url = url_slash_cleaner(self.url + cover_url.strip()) - - price = ''.join(data.xpath('.//font[@class="selling_price"]//text()')) - price = price.strip() - if not price: - continue - - title = ''.join(data.xpath('.//span[@class="prti"]/a/b/text()')) - author = ''.join(data.xpath('.//p[@class="last"]/text()')) - a, b, author = author.partition(' by ') - - counter -= 1 - + if 'search.html' in f.geturl(): + for data in doc.xpath('//table[@class="gridp"]//td'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//span[@class="prti"]/a/@href')) + id = id.strip() + if not id: + continue + + cover_url = ''.join(data.xpath('.//div[@class="prim"]/a/img/@src')) + cover_url = url_slash_cleaner(self.url + cover_url.strip()) + + price = ''.join(data.xpath('.//font[@class="selling_price"]//text()')) + price = price.strip() + if not price: + continue + + title = ''.join(data.xpath('.//span[@class="prti"]/a/b/text()')) + author = ''.join(data.xpath('.//p[@class="last"]/text()')) + a, b, author = author.partition(' by ') + + counter -= 1 + + s = SearchResult() + s.cover_url = cover_url + s.title = title.strip() + s.author = author.strip() + s.price = price.strip() + s.detail_item = id.strip() + s.drm = SearchResult.DRM_UNLOCKED + + yield s + # Exact match brought us to the books detail page. + else: s = SearchResult() - s.cover_url = cover_url - s.title = title.strip() - s.author = author.strip() - s.price = price.strip() - s.detail_item = id.strip() + + cover_url = ''.join(doc.xpath('//div[@id="image"]/a/img[@title="Zoom"]/@src')).strip() + s.cover_url = url_slash_cleaner(self.url + cover_url.strip()) + + s.title = ''.join(doc.xpath('//form[@name="details"]/h1/text()')).strip() + + authors = doc.xpath('//p[contains(., "Author:")]//text()') + author_index = None + for i, a in enumerate(authors): + if 'author' in a.lower(): + author_index = i + 1 + break + if author_index is not None and len(authors) > author_index: + a = authors[author_index] + a = a.replace(u'\xa0', '') + s.author = a.strip() + + s.price = ''.join(doc.xpath('//span[@id="price_selling"]//text()')).strip() + s.detail_item = f.geturl().replace(self.url, '').strip() + s.formats = ', '.join(doc.xpath('//select[@id="N1_"]//option//text()')) s.drm = SearchResult.DRM_UNLOCKED - + yield s def get_details(self, search_result, timeout): + if search_result.formats: + return False + br = browser() with closing(br.open(url_slash_cleaner(self.url + search_result.detail_item), timeout=timeout)) as nf: idata = html.fromstring(nf.read()) From fdaed5123e90514e60b23c3bec04393cd69cf124 Mon Sep 17 00:00:00 2001 From: John Schember Date: Tue, 17 May 2011 07:04:58 -0400 Subject: [PATCH 30/46] Store: Default new stores to unchecked in search dialog. --- src/calibre/gui2/store/search/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/store/search/search.py b/src/calibre/gui2/store/search/search.py index 29bd6822a9..e0d0251f98 100644 --- a/src/calibre/gui2/store/search/search.py +++ b/src/calibre/gui2/store/search/search.py @@ -52,7 +52,7 @@ class SearchDialog(QDialog, Ui_Dialog): stores_check_widget.setLayout(stores_group_layout) for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()): cbox = QCheckBox(x) - cbox.setChecked(True) + cbox.setChecked(False) stores_group_layout.addWidget(cbox) setattr(self, 'store_check_' + x, cbox) stores_group_layout.addStretch() From 3f0b7afd52d36cc9d8fc8376d7081070bcffaba5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 17 May 2011 08:27:27 -0600 Subject: [PATCH 31/46] ... --- src/calibre/manual/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/manual/faq.rst b/src/calibre/manual/faq.rst index 1c6b65c770..d3784eda6f 100644 --- a/src/calibre/manual/faq.rst +++ b/src/calibre/manual/faq.rst @@ -22,7 +22,7 @@ It can convert every input format in the following list, to every output format. *Input Formats:* CBZ, CBR, CBC, CHM, EPUB, FB2, HTML, HTMLZ, LIT, LRF, MOBI, ODT, PDF, PRC, PDB, PML, RB, RTF, SNB, TCR, TXT, TXTZ -*Output Formats:* EPUB, FB2, OEB, LIT, LRF, MOBI, HTMLZ, PDB, PML, RB, PDF, SNB, TCR, TXT, TXTZ +*Output Formats:* EPUB, FB2, OEB, LIT, LRF, MOBI, HTMLZ, PDB, PML, RB, PDF, RTF, SNB, TCR, TXT, TXTZ .. note :: From dc6f033b466d63269fa2c7f856b49f4e57937c62 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 17 May 2011 09:30:51 -0600 Subject: [PATCH 32/46] Updated United Daily --- recipes/united_daily.recipe | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/recipes/united_daily.recipe b/recipes/united_daily.recipe index 6954a7e725..1013b3d2b6 100644 --- a/recipes/united_daily.recipe +++ b/recipes/united_daily.recipe @@ -14,6 +14,7 @@ class UnitedDaily(BasicNewsRecipe): (u'生活', u'http://udn.com/udnrss/life.xml'), (u'綜合', u'http://udn.com/udnrss/education.xml'), (u'意見評論', u'http://udn.com/udnrss/opinion.xml'), + (u'校園博覽會', u'http://mag.udn.com/udnrss/campus_rss.xml'), (u'大台北', u'http://udn.com/udnrss/local_taipei.xml'), (u'桃竹苗', u'http://udn.com/udnrss/local_tyhcml.xml'), (u'中彰投', u'http://udn.com/udnrss/local_tcchnt.xml'), @@ -21,15 +22,21 @@ class UnitedDaily(BasicNewsRecipe): (u'高屏離島', u'http://udn.com/udnrss/local_ksptisland.xml'), (u'基宜花東', u'http://udn.com/udnrss/local_klilhltt.xml'), (u'台灣百寶鄉', u'http://udn.com/udnrss/local_oddlyenough.xml'), + (u'台灣人物', u'http://mag.udn.com/udnrss/people_rss.xml'), (u'兩岸要聞', u'http://udn.com/udnrss/mainland.xml'), (u'國際焦點', u'http://udn.com/udnrss/international.xml'), (u'台商經貿', u'http://udn.com/udnrss/financechina.xml'), (u'國際財經', u'http://udn.com/udnrss/financeworld.xml'), + (u'全球觀察', u'http://mag.udn.com/udnrss/world_rss.xml'), (u'財經焦點', u'http://udn.com/udnrss/financesfocus.xml'), (u'股市要聞', u'http://udn.com/udnrss/stock.xml'), (u'股市快訊', u'http://udn.com/udnrss/stklatest.xml'), (u'稅務法務', u'http://udn.com/udnrss/tax.xml'), (u'房市情報', u'http://udn.com/udnrss/houses.xml'), + (u'個人理財', u'http://mag.udn.com/udnrss/wealth_rss.xml'), + (u'研究報告', u'http://mag.udn.com/udnrss/report_rss.xml'), + (u'基金', u'http://mag.udn.com/udnrss/fund_rss.xml'), + (u'理財會客室', u'http://mag.udn.com/udnrss/m_forum_rss.xml'), (u'棒球', u'http://udn.com/udnrss/baseball.xml'), (u'籃球', u'http://udn.com/udnrss/basketball.xml'), (u'體壇動態', u'http://udn.com/udnrss/sportsfocus.xml'), @@ -40,19 +47,24 @@ class UnitedDaily(BasicNewsRecipe): (u'電影世界', u'http://udn.com/udnrss/movie.xml'), (u'流行音樂', u'http://udn.com/udnrss/music.xml'), (u'觀點專題', u'http://udn.com/udnrss/starssubject.xml'), + (u'消費流行', u'http://mag.udn.com/udnrss/happylife_rss.xml'), (u'食樂指南', u'http://udn.com/udnrss/food.xml'), + (u'數位資訊', u'http://mag.udn.com/udnrss/digital_rss.xml'), (u'折扣好康', u'http://udn.com/udnrss/shopping.xml'), + (u'發燒車訊', u'http://mag.udn.com/udnrss/car_rss.xml'), (u'醫藥新聞', u'http://udn.com/udnrss/health.xml'), (u'家婦繽紛', u'http://udn.com/udnrss/benfen.xml'), (u'談星論命', u'http://udn.com/udnrss/astrology.xml'), (u'文化副刊', u'http://udn.com/udnrss/reading.xml'), + (u'旅遊休閒', u'http://travel.udn.com/udnrss/travel_rss.xml'), + (u'健康醫藥', u'http://mag.udn.com/udnrss/life_rss.xml'), ] - extra_css = '''div[id='story_title'] {font-size:200%; font-weight:bold;}''' + extra_css = '''div[id='story_title'] {font-size:200%; font-weight:bold;} td[class='story_title'] {font-size:200%; font-weight:bold;} td[class='story_title'] td[class='story_title']>div {font-size:200%; font-weight:bold;}''' __author__ = 'Eddie Lau' - __version__ = '1.0' - language = 'zh' + __version__ = '1.1' + language = 'zh-TW' publisher = 'United Daily News Group' description = 'United Daily (Taiwan)' category = 'News, Chinese, Taiwan' @@ -63,5 +75,12 @@ class UnitedDaily(BasicNewsRecipe): conversion_options = {'linearize_tables':True} masthead_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif' cover_url = 'http://udn.com/NEWS/2004/images/logo_udn.gif' - keep_only_tags = [dict(name='div', attrs={'id':['story_title','story_author', 'story']})] + keep_only_tags = [dict(name='td', attrs={'class':['story_title']}), + dict(name='div', attrs={'id':['story_title']}), + dict(name='td', attrs={'class':['story_author']}), + dict(name='div', attrs={'id':['story_author']}), + dict(name='td', attrs={'class':['story']}), + dict(name='div', attrs={'id':['story']}), + ] remove_tags = [dict(name='div', attrs={'id':['mvouter']})] + From 253e49141a1739423fdf793c9098359a78c116f2 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 17 May 2011 09:35:45 -0600 Subject: [PATCH 33/46] Men's Health by Anonymous --- recipes/mens_health.recipe | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 recipes/mens_health.recipe diff --git a/recipes/mens_health.recipe b/recipes/mens_health.recipe new file mode 100644 index 0000000000..4e69db8a7c --- /dev/null +++ b/recipes/mens_health.recipe @@ -0,0 +1,10 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1305636254(BasicNewsRecipe): + title = u'Mens Health (US)' + language = 'en' + __author__ = 'Anonymous' + oldest_article = 14 + max_articles_per_feed = 100 + + feeds = [(u'News', u'http://blogs.menshealth.com/health-headlines/feed')] From a0942198acb97419569a83bd10ce4462cfaf8155 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 17 May 2011 13:06:56 -0600 Subject: [PATCH 34/46] Good to Know by Anonymous --- recipes/good_to_know.recipe | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 recipes/good_to_know.recipe diff --git a/recipes/good_to_know.recipe b/recipes/good_to_know.recipe new file mode 100644 index 0000000000..cf374128ce --- /dev/null +++ b/recipes/good_to_know.recipe @@ -0,0 +1,32 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1305547242(BasicNewsRecipe): + title = u'Good to Know (uk)' + oldest_article = 14 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + remove_javascript = True + __author__ = 'Anonymous' + language = 'en_GB' + remove_tags = [dict(name='div', attrs={'class':'articles_footer', 'class':'printoptions'})] + + def print_version(self, url): + return url + '/print/1' + + def preprocess_html(self, soup): + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup + + feeds = [ (u'Family Conception Advice', u'http://www.goodtoknow.co.uk/feeds/family.rss'), + (u'Family Health Advice', u'http://www.goodtoknow.co.uk/feeds/health.rss'), + (u'Diet Advice', u'http://www.goodtoknow.co.uk/feeds/diet.rss'), + (u'Food Advice', u'http://www.goodtoknow.co.uk/feeds/food.rss'), + (u'Sex Advice', u'http://www.goodtoknow.co.uk/feeds/sex.rss'), + (u'Easy Exercise', u'http://www.goodtoknow.co.uk/feeds/easyexercise.rss'), + (u'Recipes', u'http://www.goodtoknow.co.uk/feeds/recipes.rss'), + (u'Food Quick-tips', u'http://www.goodtoknow.co.uk/feeds/foodquicktips.rss'), + ] From 9908fc66325028ea80b958d6d8fe0cc0db990984 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 17 May 2011 14:19:06 -0600 Subject: [PATCH 35/46] Glamour by Anonymous --- recipes/glamour.recipe | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 recipes/glamour.recipe diff --git a/recipes/glamour.recipe b/recipes/glamour.recipe new file mode 100644 index 0000000000..40e6b6e88b --- /dev/null +++ b/recipes/glamour.recipe @@ -0,0 +1,38 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1305547242(BasicNewsRecipe): + title = u'Glamour (US)' + oldest_article = 21 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + language = 'en' + remove_javascript = True + __author__ = 'Anonymous' + remove_tags = [dict(name='div', attrs={'class':'articles_footer', 'class':'printoptions'})] + + def print_version(self, url): + return url + '?printable=true' + + def preprocess_html(self, soup): + for alink in soup.findAll('a'): + if alink.string is not None: + tstr = alink.string + alink.replaceWith(tstr) + return soup + + feeds = [ (u'All Fashion', u'http://feeds.glamour.com/glamour/all_fashion'), + (u'All Beauty', u'http://feeds.glamour.com/glamour/all_beauty'), + (u'All Sex, Love & Life', u'http://feeds.glamour.com/glamour/sex_love_life'), + (u'All Health & Fitness', u'http://feeds.glamour.com/glamour/health_fitness'), + (u'Shopping', u'http://feeds.glamour.com/glamour/shopping'), + (u'Slaves to Fashion blog', u'http://feeds.glamour.com/glamour/slavestofashion'), + (u'The Girls in the Beauty Department', u'http://feeds.glamour.com/glamour/thegirlsinthebeautydepartment'), + (u'Smitten blog', u'http://feeds.glamour.com/glamour/smitten'), + (u'Save the Date', u'http://feeds.feedburner.com/glamour/save-the-date'), + (u'Single-ish blog', u'http://feeds.glamour.com/glamour/glamoursingle-ish'), + (u'Save the Date', u'http://feeds.feedburner.com/glamour/save-the-date'), + (u'Vitamin G blog', u'http://feeds.glamour.com/glamour/vitamin-g'), + (u'Margarita Shapes Up blog', u'http://feeds.glamour.com/glamour/margaritashapesup'), + (u'Little Miss Fortune blog', u'http://feeds.glamour.com/glamour/little-miss-fortune'), + ] From 1f31873432f6eccaf44931dd76f4a9225f56aba1 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 17 May 2011 14:31:54 -0600 Subject: [PATCH 36/46] Add a tweak that controls what words are treated as suffixes when geenrating an author sort string from an author name. Also Fix #782551 (authorsort error on brackets) --- resources/default_tweaks.py | 7 +++- src/calibre/__init__.py | 18 +++++++++++ src/calibre/ebooks/metadata/__init__.py | 43 +++++++++++++++++-------- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index e91b4a62d5..691a82fc36 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -41,14 +41,19 @@ authors_completer_append_separator = False #: Author sort name algorithm # The algorithm used to copy author to author_sort # Possible values are: -# invert: use "fn ln" -> "ln, fn" (the default algorithm) +# invert: use "fn ln" -> "ln, fn" # copy : copy author to author_sort without modification # comma : use 'copy' if there is a ',' in the name, otherwise use 'invert' # nocomma : "fn ln" -> "ln fn" (without the comma) # When this tweak is changed, the author_sort values stored with each author # must be recomputed by right-clicking on an author in the left-hand tags pane, # selecting 'manage authors', and pressing 'Recalculate all author sort values'. +# The author name suffixes are words that are ignored when they occur at the +# end of an author name. The case of the suffix is ignored and trailing +# periods are automatically handled. author_sort_copy_method = 'comma' +author_name_suffixes = ('Jr', 'Sr', 'Inc', 'Ph.D', 'Phd', + 'MD', 'M.D', 'I', 'II', 'III', 'IV') #: Use author sort in Tag Browser # Set which author field to display in the tags pane (the list of authors, diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py index bc99947345..b82ea984ec 100644 --- a/src/calibre/__init__.py +++ b/src/calibre/__init__.py @@ -630,6 +630,24 @@ def human_readable(size): size = size[:-2] return size + " " + suffix +def remove_bracketed_text(src, + brackets={u'(':u')', u'[':u']', u'{':u'}'}): + from collections import Counter + counts = Counter() + buf = [] + src = force_unicode(src) + rmap = dict([(v, k) for k, v in brackets.iteritems()]) + for char in src: + if char in brackets: + counts[char] += 1 + elif char in rmap: + idx = rmap[char] + if counts[idx] > 0: + counts[idx] -= 1 + elif sum(counts.itervalues()) < 1: + buf.append(char) + return u''.join(buf) + if isosx: import glob, shutil fdir = os.path.expanduser('~/.fonts') diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 9c7838cb2c..2c26d011b7 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -10,7 +10,7 @@ import os, sys, re from urllib import unquote, quote from urlparse import urlparse -from calibre import relpath, guess_type +from calibre import relpath, guess_type, remove_bracketed_text from calibre.utils.config import tweaks @@ -27,20 +27,37 @@ def authors_to_string(authors): else: return '' -_bracket_pat = re.compile(r'[\[({].*?[})\]]') -def author_to_author_sort(author): +def author_to_author_sort(author, method=None): if not author: - return '' - method = tweaks['author_sort_copy_method'] - if method == 'copy' or (method == 'comma' and ',' in author): + return u'' + sauthor = remove_bracketed_text(author).strip() + tokens = sauthor.split() + if len(tokens) < 2: return author - author = _bracket_pat.sub('', author).strip() - tokens = author.split() - if tokens and tokens[-1] not in ('Inc.', 'Inc'): - tokens = tokens[-1:] + tokens[:-1] - if len(tokens) > 1 and method != 'nocomma': - tokens[0] += ',' - return ' '.join(tokens) + if method is None: + method = tweaks['author_sort_copy_method'] + if method == u'copy': + return author + suffixes = set([x.lower() for x in tweaks['author_name_suffixes']]) + suffixes |= set([x+u'.' for x in suffixes]) + + last = tokens[-1].lower() + suffix = None + if last in suffixes: + suffix = tokens[-1] + tokens = tokens[:-1] + + if method == u'comma' and u',' in u''.join(tokens): + return author + + atokens = tokens[-1:] + tokens[:-1] + if suffix: + atokens.append(suffix) + + if method != u'nocomma' and len(atokens) > 1: + atokens[0] += u',' + + return u' '.join(atokens) def authors_to_sort_string(authors): return ' & '.join(map(author_to_author_sort, authors)) From 913c27390c8763d9826558011497799414511815 Mon Sep 17 00:00:00 2001 From: John Schember Date: Tue, 17 May 2011 19:10:11 -0400 Subject: [PATCH 37/46] Store: Add Google Books plugin. --- src/calibre/customize/builtins.py | 8 +- src/calibre/gui2/store/google_books_plugin.py | 93 +++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/calibre/gui2/store/google_books_plugin.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index d3fa6e2e83..8e16d4c76c 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -1164,6 +1164,11 @@ class StoreFoylesUKStore(StoreBase): description = _('Foyles of London, online.') actual_plugin = 'calibre.gui2.store.foyles_uk_plugin:FoylesUKStore' +class StoreGoogleBooksStore(StoreBase): + name = 'Google Books' + description = _('Google Books') + actual_plugin = 'calibre.gui2.store.google_books_plugin:GoogleBooksStore' + class StoreGutenbergStore(StoreBase): name = 'Project Gutenberg' description = _('The first producer of free ebooks.') @@ -1219,7 +1224,8 @@ plugins += [StoreArchiveOrgStore, StoreAmazonKindleStore, StoreAmazonDEKindleSto StoreBeamEBooksDEStore, StoreBeWriteStore, StoreDieselEbooksStore, StoreEbookscomStore, StoreEPubBuyDEStore, StoreEHarlequinStore, StoreFeedbooksStore, - StoreFoylesUKStore, StoreGutenbergStore, StoreKoboStore, StoreManyBooksStore, + StoreFoylesUKStore, StoreGoogleBooksStore, StoreGutenbergStore, + StoreKoboStore, StoreManyBooksStore, StoreMobileReadStore, StoreNextoStore, StoreOpenLibraryStore, StoreSmashwordsStore, StoreWaterstonesUKStore, StoreWeightlessBooksStore, StoreWizardsTowerBooksStore] diff --git a/src/calibre/gui2/store/google_books_plugin.py b/src/calibre/gui2/store/google_books_plugin.py new file mode 100644 index 0000000000..6db0cc10b8 --- /dev/null +++ b/src/calibre/gui2/store/google_books_plugin.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +from __future__ import (unicode_literals, division, absolute_import, print_function) + +__license__ = 'GPL 3' +__copyright__ = '2011, John Schember ' +__docformat__ = 'restructuredtext en' + +import urllib +from contextlib import closing + +from lxml import html + +from PyQt4.Qt import QUrl + +from calibre import browser, url_slash_cleaner +from calibre.gui2 import open_url +from calibre.gui2.store import StorePlugin +from calibre.gui2.store.basic_config import BasicStoreConfig +from calibre.gui2.store.search_result import SearchResult +from calibre.gui2.store.web_store_dialog import WebStoreDialog + +class GoogleBooksStore(BasicStoreConfig, StorePlugin): + + def open(self, parent=None, detail_item=None, external=False): + url = 'http://books.google.com/' + + if external or self.config.get('open_external', False): + open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url))) + else: + d = WebStoreDialog(self.gui, url, parent, detail_item) + d.setWindowTitle(self.name) + d.set_tags(self.config.get('tags', '')) + d.exec_() + + 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()) + for data in doc.xpath('//ol[@id="rso"]/li'): + if counter <= 0: + break + + id = ''.join(data.xpath('.//h3/a/@href')) + if not id: + continue + + title = ''.join(data.xpath('.//h3/a//text()')) + authors = data.xpath('.//span[@class="gl"]//a//text()') + if authors[-1].strip().lower() == 'preview': + authors = authors[:-1] + else: + continue + 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[@class="buy-price-container"]/span[contains(@class, "buy-price")]/text()')) + # Try to get the price inside of a buy button. + if not price.strip(): + price = ''.join(doc.xpath('//div[@class="buy-container"]/a/text()')) + price = price.split('-')[-1] + # No price set for this book. + if not price.strip(): + price = '$0.00' + 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 + From 7727195777baba33d2fd9fd844a9364093d236c3 Mon Sep 17 00:00:00 2001 From: John Schember Date: Tue, 17 May 2011 19:10:51 -0400 Subject: [PATCH 38/46] Store: Modify Nexto url. --- src/calibre/gui2/store/nexto_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/store/nexto_plugin.py b/src/calibre/gui2/store/nexto_plugin.py index 154274d12a..d63cf18233 100644 --- a/src/calibre/gui2/store/nexto_plugin.py +++ b/src/calibre/gui2/store/nexto_plugin.py @@ -26,7 +26,7 @@ class NextoStore(BasicStoreConfig, StorePlugin): def open(self, parent=None, detail_item=None, external=False): pid = '155711' - url = 'http://www.nexto.pl/ebooki_c1015.xml?pid=' + pid + url = 'http://www.nexto.pl/ebooki_c1015.xml' detail_url = None if detail_item: From faf5ba7d7c80b248621bd0b0a4f7f0f41d12f27d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 17 May 2011 17:31:49 -0600 Subject: [PATCH 39/46] Add API to run arbitrary functions in worker processes --- src/calibre/utils/ipc/worker.py | 54 +++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/calibre/utils/ipc/worker.py b/src/calibre/utils/ipc/worker.py index a891d09f3d..88e571673f 100644 --- a/src/calibre/utils/ipc/worker.py +++ b/src/calibre/utils/ipc/worker.py @@ -50,6 +50,12 @@ PARALLEL_FUNCS = { 'save_book' : ('calibre.ebooks.metadata.worker', 'save_book', 'notification'), + + 'arbitrary' : + ('calibre.utils.ipc.worker', 'arbitrary', None), + + 'arbitrary_n' : + ('calibre.utils.ipc.worker', 'arbitrary', 'notification'), } class Progress(Thread): @@ -73,7 +79,55 @@ class Progress(Thread): except: break +def arbitrary(module_name, func_name, args, kwargs={}): + ''' + An entry point that allows arbitrary functions to be run in a parallel + process. useful for plugin developers that want to run jobs in a parallel + process. + To use this entry point, simply create a ParallelJob with the module and + function names for the real entry point. + + Remember that args and kwargs must be serialized so only use basic types + for them. + + To use this, you will do something like + + from calibre.gui2 import Dispatcher + gui.job_manager.run_job(Dispatcher(job_done), 'arbitrary', + args=('calibre_plugins.myplugin.worker', 'do_work', + ('arg1' 'arg2', 'arg3')), + description='Change the world') + + The function job_done will be called on completion, see the code in + gui2.actions.catalog for an example of using run_job and Dispatcher. + + :param module_name: The fully qualified name of the module that contains + the actual function to be run. For example: + calibre_plugins.myplugin.worker + :param func_name: The name of the function to be run. + :param name: A list (or tuple) of arguments that will be passed to the + function ``func_name`` + :param kwargs: A dictionary of keyword arguments to pass to func_name + ''' + module = importlib.import_module(module_name) + func = getattr(module, func_name) + return func(*args, **kwargs) + +def arbitrary_n(module_name, func_name, args, kwargs={}, + notification=lambda x, y: y): + ''' + Same as :func:`arbitrary` above, except that func_name must support a + keyword argument "notification". This will be a function that accepts two + arguments. func_name should call it periodically with progress information. + The first argument is a float between 0 and 1 that represent percent + completed and the second is a string with a message (it can be an empty + string). + ''' + module = importlib.import_module(module_name) + func = getattr(module, func_name) + kwargs['notification'] = notification + return func(*args, **kwargs) def get_func(name): module, func, notification = PARALLEL_FUNCS[name] From 033580e575ff5319f89d68c2dba1dfe822a28b7e Mon Sep 17 00:00:00 2001 From: John Schember Date: Tue, 17 May 2011 20:00:42 -0400 Subject: [PATCH 40/46] Store: remove leading and trailing spaces when searching for title and author from menu. --- src/calibre/gui2/actions/store.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/actions/store.py b/src/calibre/gui2/actions/store.py index 2493a617ac..c8507e851c 100644 --- a/src/calibre/gui2/actions/store.py +++ b/src/calibre/gui2/actions/store.py @@ -68,7 +68,7 @@ class StoreAction(InterfaceAction): a = ' '.join(a) corrected_authors.append(a) - return ' & '.join(corrected_authors) + return ' & '.join(corrected_authors).strip() def search_author(self): row = self._get_selected_row() @@ -87,7 +87,7 @@ class StoreAction(InterfaceAction): mi = self.gui.current_view().model().get_book_display_info(row) title = mi.title - return title + return title.strip() def search_title(self): row = self._get_selected_row() From 93f8e4f7c58b5da03b596be5a3f46dc7d3073e2b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 May 2011 08:36:49 -0600 Subject: [PATCH 41/46] Update Dvhn --- recipes/dvhn.recipe | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/recipes/dvhn.recipe b/recipes/dvhn.recipe index 4c093aa9d2..d0330990fc 100644 --- a/recipes/dvhn.recipe +++ b/recipes/dvhn.recipe @@ -1,19 +1,21 @@ +import re from calibre.web.feeds.news import BasicNewsRecipe class AdvancedUserRecipe1302341394(BasicNewsRecipe): title = u'DvhN' - oldest_article = 1 + __author__ = 'Reijndert' + oldest_article = 7 max_articles_per_feed = 200 - __author__ = 'Reijndert' no_stylesheets = True - cover_url = 'http://www.dvhn.nl/template/Dagblad_v2.0/gfx/logo_DvhN.gif' + cover_url = 'http://members.home.nl/apm.de.haas/calibre/DvhN.jpg' language = 'nl' country = 'NL' version = 1 publisher = u'Dagblad van het Noorden' category = u'Nieuws' description = u'Nieuws uit Noord Nederland' + timefmt = ' %Y-%m-%d (%a)' keep_only_tags = [dict(name='div', attrs={'id':'fullPicture'}) @@ -21,11 +23,26 @@ class AdvancedUserRecipe1302341394(BasicNewsRecipe): ] remove_tags = [ - dict(name=['object','link','iframe','base']) - ,dict(name='span',attrs={'class':'copyright'}) + dict(name='span',attrs={'class':'location'}) ] - feeds = [(u'Drenthe', u'http://www.dvhn.nl/nieuws/drenthe/index.jsp?service=rss'), (u'Groningen', u'http://www.dvhn.nl/nieuws/groningen/index.jsp?service=rss'), (u'Nederland', u'http://www.dvhn.nl/nieuws/nederland/index.jsp?service=rss'), (u'Wereld', u'http://www.dvhn.nl/nieuws/wereld/index.jsp?service=rss'), (u'Economie', u'http://www.dvhn.nl/nieuws/economie/index.jsp?service=rss'), (u'Sport', u'http://www.dvhn.nl/nieuws/sport/index.jsp?service=rss'), (u'Cultuur', u'http://www.dvhn.nl/nieuws/kunst/index.jsp?service=rss'), (u'24 Uur', u'http://www.dvhn.nl/nieuws/24uurdvhn/index.jsp?service=rss&selectiontype=last24hours')] + preprocess_regexps = [ + (re.compile(r''), lambda h1: '') + ,(re.compile(r''), lambda h2: '') + ,(re.compile(r'Word vriend van Dagblad van het Noorden op Facebook'), lambda h3: '') + ,(re.compile(r'Volg Dagblad van het Noorden op Twitter'), lambda h3: '') + ] + + + feeds = [(u'Drenthe', u'http://www.dvhn.nl/nieuws/drenthe/index.jsp?service=rss') + , (u'Groningen', u'http://www.dvhn.nl/nieuws/groningen/index.jsp?service=rss') + , (u'Nederland', u'http://www.dvhn.nl/nieuws/nederland/index.jsp?service=rss') + , (u'Wereld', u'http://www.dvhn.nl/nieuws/wereld/index.jsp?service=rss') + , (u'Economie', u'http://www.dvhn.nl/nieuws/economie/index.jsp?service=rss') + , (u'Sport', u'http://www.dvhn.nl/nieuws/sport/index.jsp?service=rss') + , (u'Cultuur', u'http://www.dvhn.nl/nieuws/kunst/index.jsp?service=rss') + , (u'24 Uur', u'http://www.dvhn.nl/nieuws/24uurdvhn/index.jsp?service=rss&selectiontype=last24hours') + ] extra_css = ''' body {font-family: verdana, arial, helvetica, geneva, sans-serif;} From d882c28144e28d7cbe78addb95e3c3402e1c7ada Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 May 2011 09:15:17 -0600 Subject: [PATCH 42/46] Updated Newsweek --- recipes/newsweek.recipe | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/recipes/newsweek.recipe b/recipes/newsweek.recipe index 97abd69aac..a31706e257 100644 --- a/recipes/newsweek.recipe +++ b/recipes/newsweek.recipe @@ -11,6 +11,20 @@ class Newsweek(BasicNewsRecipe): BASE_URL = 'http://www.newsweek.com' + topics = { + 'Culture' : '/tag/culture.html', + 'Business' : '/tag/business.html', + 'Society' : '/tag/society.html', + 'Science' : '/tag/science.html', + 'Education' : '/tag/education.html', + 'Politics' : '/tag/politics.html', + 'Health' : '/tag/health.html', + 'World' : '/tag/world.html', + 'Nation' : '/tag/nation.html', + 'Technology' : '/tag/technology.html', + 'Game Changers' : '/tag/game-changers.html', + } + keep_only_tags = dict(name='article', attrs={'class':'article-text'}) remove_tags = [dict(attrs={'data-dartad':True})] remove_attributes = ['property'] @@ -21,14 +35,10 @@ class Newsweek(BasicNewsRecipe): return soup def newsweek_sections(self): - return [ - ('Nation', 'http://www.newsweek.com/tag/nation.html'), - ('Society', 'http://www.newsweek.com/tag/society.html'), - ('Culture', 'http://www.newsweek.com/tag/culture.html'), - ('World', 'http://www.newsweek.com/tag/world.html'), - ('Politics', 'http://www.newsweek.com/tag/politics.html'), - ('Business', 'http://www.newsweek.com/tag/business.html'), - ] + for topic_name, topic_url in self.topics.iteritems(): + yield (topic_name, + self.BASE_URL+topic_url) + def newsweek_parse_section_page(self, soup): for article in soup.findAll('article', about=True, From 5317f8bb9c0acd80576ca577ffc02d33fb138c1e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 May 2011 14:16:41 -0600 Subject: [PATCH 43/46] Various German news sources by schuster --- recipes/borse_online.recipe | 33 ++++++++++++++++++++ recipes/capital_de.recipe | 61 +++++++++++++++++++++++++++++++++++++ recipes/impulse_de.recipe | 32 +++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 recipes/borse_online.recipe create mode 100644 recipes/capital_de.recipe create mode 100644 recipes/impulse_de.recipe diff --git a/recipes/borse_online.recipe b/recipes/borse_online.recipe new file mode 100644 index 0000000000..c192ce2b8d --- /dev/null +++ b/recipes/borse_online.recipe @@ -0,0 +1,33 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe +class AdvancedUserRecipe1303841067(BasicNewsRecipe): + + title = u'Börse-online' + __author__ = 'schuster' + oldest_article = 1 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + language = 'de' + remove_javascript = True + cover_url = 'http://www.dpv.de/images/1995/source.gif' + masthead_url = 'http://www.zeitschriften-cover.de/cover/boerse-online-cover-januar-2010-x1387.jpg' + extra_css = ''' + h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;} + img {min-width:300px; max-width:600px; min-height:300px; max-height:800px} + p{font-family:Arial,Helvetica,sans-serif;font-size:small;} + body{font-family:Helvetica,Arial,sans-serif;font-size:small;} + ''' + remove_tags_bevor = [dict(name='h3')] + remove_tags_after = [dict(name='div', attrs={'class':'artikelfuss'})] + remove_tags = [dict(attrs={'class':['moduleTopNav', 'moduleHeaderNav', 'text', 'blau', 'poll1150']}), + dict(id=['newsletterlayer', 'newsletterlayerClose', 'newsletterlayer_body', 'newsletterarray_error', 'newsletterlayer_emailadress', 'newsletterlayer_submit', 'kommentar']), + dict(name=['h2', 'Gesamtranking', 'h3',''])] + + def print_version(self, url): + return url.replace('.html#nv=rss', '.html?mode=print') + + + + feeds = [(u'Börsennachrichten', u'http://www.boerse-online.de/rss/')] + diff --git a/recipes/capital_de.recipe b/recipes/capital_de.recipe new file mode 100644 index 0000000000..6826049bc9 --- /dev/null +++ b/recipes/capital_de.recipe @@ -0,0 +1,61 @@ +from calibre.web.feeds.news import BasicNewsRecipe +class AdvancedUserRecipe1305470859(BasicNewsRecipe): + title = u'Capital.de' + language = 'de' + __author__ = 'schuster' + oldest_article =7 + max_articles_per_feed = 35 + no_stylesheets = True + remove_javascript = True + use_embedded_content = False + masthead_url = 'http://www.wirtschaftsmedien-shop.de/media/stores/wirtschaftsmedien/capital/teaser_large_abo.jpg' + cover_url = 'http://d1kb9jvg6ylufe.cloudfront.net/WebsiteCMS/de/unternehmen/linktipps/mainColumn/08/image/DE_Capital_bis20mm_SW.jpg' + + extra_css = ''' + h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;} + img {min-width:300px; max-width:600px; min-height:300px; max-height:800px} + p{font-family:Arial,Helvetica,sans-serif;font-size:small;} + body{font-family:Helvetica,Arial,sans-serif;font-size:small;} + ''' + def print_version(self, url): + return url.replace ('nv=rss#utm_source=rss2&utm_medium=rss_feed&utm_campaign=/', 'mode=print') + remove_tags_bevor = [dict(name='td', attrs={'class':'textcell'})] + remove_tags_after = [dict(name='div', attrs={'class':'artikelsplit'})] + + feeds = [ (u'Wirtschaftsmagazin', u'http://www.capital.de/rss/'), + (u'Unternehmen', u'http://www.capital.de/rss/unternehmen'), + (u'Finanz & Geldanlage', u'http://www.capital.de/rss/finanzen/geldanlage')] + + def append_page(self, soup, appendtag, position): + pager = soup.find('div',attrs={'class':'artikelsplit'}) + if pager: + nexturl = self.INDEX + pager.a['href'] + soup2 = self.index_to_soup(nexturl) + texttag = soup2.find('div', attrs={'class':'printable'}) + for it in texttag.findAll(style=True): + del it['style'] + newpos = len(texttag.contents) + self.append_page(soup2,texttag,newpos) + texttag.extract() + appendtag.insert(position,texttag) + + + def preprocess_html(self, soup): + for item in soup.findAll(style=True): + del item['style'] + for item in soup.findAll('div', attrs={'class':'artikelsplit'}): + item.extract() + self.append_page(soup, soup.body, 3) + pager = soup.find('div',attrs={'class':'artikelsplit'}) + if pager: + pager.extract() + return self.adeify_images(soup) + + + + remove_tags = [dict(attrs={'class':['navSeitenAlle', 'kommentieren', 'teaserheader', 'teasercontent', 'info', 'zwischenhead', 'artikelsplit']}), + dict(id=['topNav', 'mainNav', 'subNav', 'socialmedia', 'footerRahmen', 'gatrixx_marktinformationen', 'pager', 'weitere']), + dict(span=['ratingtext', 'Gesamtranking', 'h3','']), + dict(rel=['canonical'])] + diff --git a/recipes/impulse_de.recipe b/recipes/impulse_de.recipe new file mode 100644 index 0000000000..d38c0aa6a6 --- /dev/null +++ b/recipes/impulse_de.recipe @@ -0,0 +1,32 @@ +from calibre.web.feeds.news import BasicNewsRecipe +class AdvancedUserRecipe1305470859(BasicNewsRecipe): + title = u'Impulse.de' + language = 'de' + __author__ = 'schuster' + oldest_article =14 + max_articles_per_feed = 100 + no_stylesheets = True + remove_javascript = True + use_embedded_content = False + cover_url = 'http://www.bvk.de/files/image/bilder/Logo%20Impulse.jpg' + + extra_css = ''' + h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} + h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;} + img {min-width:300px; max-width:600px; min-height:300px; max-height:800px} + p{font-family:Arial,Helvetica,sans-serif;font-size:small;} + body{font-family:Helvetica,Arial,sans-serif;font-size:small;} + ''' + def print_version(self, url): + return url.replace ('#utm_source=rss2&utm_medium=rss_feed&utm_campaign=/', '?mode=print') + remove_tags_bevor = [dict(name='h1', attrs={'class':'h2'})] + remove_tags_after = [dict(name='div', attrs={'class':'artikelfuss'})] + + feeds = [ (u'impulstest', u'http://www.impulse.de/rss/')] + + + remove_tags = [dict(attrs={'class':['navSeitenAlle', 'kommentieren', 'teaserheader', 'teasercontent', 'info', 'zwischenhead', 'kasten_artikel']}), + dict(id=['metaNav', 'impKopf', 'impTopNav', 'impSubNav', 'footerRahmen', 'gatrixx_marktinformationen', 'pager', 'weitere', 'socialmedia', 'rating_open']), + dict(span=['ratingtext', 'Gesamtranking', 'h3','']), + dict(rel=['canonical'])] + From b0c0d40e6ff57ebd5d15e2cf344c8aa4e8541d7f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 May 2011 20:02:00 -0600 Subject: [PATCH 44/46] Fix #780484 (Intermittent crash when disconnect with dialog open) --- src/calibre/gui2/bars.py | 309 ++++++++++++++++++++++++ src/calibre/gui2/device.py | 2 + src/calibre/gui2/layout.py | 284 ++-------------------- src/calibre/gui2/preferences/main.py | 5 +- src/calibre/gui2/preferences/toolbar.py | 3 + src/calibre/gui2/ui.py | 6 +- 6 files changed, 336 insertions(+), 273 deletions(-) create mode 100644 src/calibre/gui2/bars.py diff --git a/src/calibre/gui2/bars.py b/src/calibre/gui2/bars.py new file mode 100644 index 0000000000..8bae9d65d1 --- /dev/null +++ b/src/calibre/gui2/bars.py @@ -0,0 +1,309 @@ +#!/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 (QObject, QToolBar, Qt, QSize, QToolButton, QVBoxLayout, + QLabel, QWidget, QAction, QMenuBar, QMenu) + +from calibre.constants import isosx +from calibre.gui2 import gprefs + +class ToolBar(QToolBar): # {{{ + + def __init__(self, donate, location_manager, parent): + QToolBar.__init__(self, parent) + self.setContextMenuPolicy(Qt.PreventContextMenu) + self.setMovable(False) + self.setFloatable(False) + self.setOrientation(Qt.Horizontal) + self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea) + self.setStyleSheet('QToolButton:checked { font-weight: bold }') + self.preferred_width = self.sizeHint().width() + self.gui = parent + self.donate_button = donate + self.added_actions = [] + + self.location_manager = location_manager + donate.setAutoRaise(True) + donate.setCursor(Qt.PointingHandCursor) + self.setAcceptDrops(True) + self.showing_donate = False + + def resizeEvent(self, ev): + QToolBar.resizeEvent(self, ev) + style = self.get_text_style() + self.setToolButtonStyle(style) + if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'): + self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly) + + def get_text_style(self): + style = Qt.ToolButtonTextUnderIcon + s = gprefs['toolbar_icon_size'] + if s != 'off': + p = gprefs['toolbar_text'] + if p == 'never': + style = Qt.ToolButtonIconOnly + elif p == 'auto' and self.preferred_width > self.width()+35: + style = Qt.ToolButtonIconOnly + return style + + def contextMenuEvent(self, *args): + pass + + def update_lm_actions(self): + for ac in self.added_actions: + if ac in self.location_manager.all_actions: + ac.setVisible(ac in self.location_manager.available_actions) + + def init_bar(self, actions): + self.showing_donate = False + for ac in self.added_actions: + m = ac.menu() + if m is not None: + m.setVisible(False) + + self.clear() + self.added_actions = [] + + bar = self + + for what in actions: + if what is None: + bar.addSeparator() + elif what == 'Location Manager': + for ac in self.location_manager.all_actions: + bar.addAction(ac) + bar.added_actions.append(ac) + bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup) + ac.setVisible(False) + elif what == 'Donate': + self.d_widget = QWidget() + self.d_widget.setLayout(QVBoxLayout()) + self.d_widget.layout().addWidget(self.donate_button) + if isosx: + self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }') + self.d_widget.layout().setContentsMargins(0,0,0,0) + self.d_widget.setContentsMargins(0,0,0,0) + self.d_widget.filler = QLabel(u'\u00a0') + self.d_widget.layout().addWidget(self.d_widget.filler) + bar.addWidget(self.d_widget) + self.showing_donate = True + elif what in self.gui.iactions: + action = self.gui.iactions[what] + bar.addAction(action.qaction) + self.added_actions.append(action.qaction) + self.setup_tool_button(bar, action.qaction, action.popup_type) + self.preferred_width = self.sizeHint().width() + + def setup_tool_button(self, bar, ac, menu_mode=None): + ch = bar.widgetForAction(ac) + if ch is None: + ch = self.child_bar.widgetForAction(ac) + ch.setCursor(Qt.PointingHandCursor) + ch.setAutoRaise(True) + if ac.menu() is not None and menu_mode is not None: + ch.setPopupMode(menu_mode) + return ch + + #support drag&drop from/to library from/to reader/card + def dragEnterEvent(self, event): + md = event.mimeData() + if md.hasFormat("application/calibre+from_library") or \ + md.hasFormat("application/calibre+from_device"): + event.setDropAction(Qt.CopyAction) + event.accept() + else: + event.ignore() + + def dragMoveEvent(self, event): + allowed = False + md = event.mimeData() + #Drop is only allowed in the location manager widget's different from the selected one + for ac in self.location_manager.available_actions: + w = self.widgetForAction(ac) + if w is not None: + if ( md.hasFormat("application/calibre+from_library") or \ + md.hasFormat("application/calibre+from_device") ) and \ + w.geometry().contains(event.pos()) and \ + isinstance(w, QToolButton) and not w.isChecked(): + allowed = True + break + if allowed: + event.acceptProposedAction() + else: + event.ignore() + + def dropEvent(self, event): + data = event.mimeData() + + mime = 'application/calibre+from_library' + if data.hasFormat(mime): + ids = list(map(int, str(data.data(mime)).split())) + tgt = None + for ac in self.location_manager.available_actions: + w = self.widgetForAction(ac) + if w is not None and w.geometry().contains(event.pos()): + tgt = ac.calibre_name + if tgt is not None: + if tgt == 'main': + tgt = None + self.gui.sync_to_device(tgt, False, send_ids=ids) + event.accept() + + mime = 'application/calibre+from_device' + if data.hasFormat(mime): + paths = [unicode(u.toLocalFile()) for u in data.urls()] + if paths: + self.gui.iactions['Add Books'].add_books_from_device( + self.gui.current_view(), paths=paths) + event.accept() + +# }}} + +class MenuAction(QAction): # {{{ + + def __init__(self, clone, parent): + QAction.__init__(self, clone.text(), parent) + self.clone = clone + clone.changed.connect(self.clone_changed) + + def clone_changed(self): + self.setText(self.clone.text()) +# }}} + +class MenuBar(QMenuBar): # {{{ + + def __init__(self, location_manager, parent): + QMenuBar.__init__(self, parent) + self.gui = parent + self.setNativeMenuBar(True) + + self.location_manager = location_manager + self.added_actions = [] + + self.donate_action = QAction(_('Donate'), self) + self.donate_menu = QMenu() + self.donate_menu.addAction(self.gui.donate_action) + self.donate_action.setMenu(self.donate_menu) + + def update_lm_actions(self): + for ac in self.added_actions: + if ac in self.location_manager.all_actions: + ac.setVisible(ac in self.location_manager.available_actions) + + def init_bar(self, actions): + for ac in self.added_actions: + m = ac.menu() + if m is not None: + m.setVisible(False) + + self.clear() + self.added_actions = [] + + for what in actions: + if what is None: + continue + elif what == 'Location Manager': + for ac in self.location_manager.all_actions: + ac = self.build_menu(ac) + self.addAction(ac) + self.added_actions.append(ac) + ac.setVisible(False) + elif what == 'Donate': + self.addAction(self.donate_action) + elif what in self.gui.iactions: + action = self.gui.iactions[what] + ac = self.build_menu(action.qaction) + self.addAction(ac) + self.added_actions.append(ac) + + def build_menu(self, action): + m = action.menu() + ac = MenuAction(action, self) + if m is None: + m = QMenu() + m.addAction(action) + ac.setMenu(m) + return ac + +# }}} + +class BarsManager(QObject): + + def __init__(self, donate_button, location_manager, parent): + QObject.__init__(self, parent) + self.donate_button, self.location_manager = (donate_button, + location_manager) + + bars = [ToolBar(donate_button, location_manager, parent) for i in + range(3)] + self.main_bars = tuple(bars[:2]) + self.child_bars = tuple(bars[2:]) + + + self.apply_settings() + self.init_bars() + + def database_changed(self, db): + pass + + @property + def bars(self): + for x in self.main_bars + self.child_bars: + yield x + + @property + def showing_donate(self): + for b in self.bars: + if b.isVisible() and b.showing_donate: + return True + return False + + def init_bars(self): + self.bar_actions = tuple( + [gprefs['action-layout-toolbar'+x] for x in ('', '-device')] + + [gprefs['action-layout-toolbar-child']] + + [gprefs['action-layout-menubar']] + + [gprefs['action-layout-menubar-device']] + ) + + for bar, actions in zip(self.bars, self.bar_actions[:3]): + bar.init_bar(actions) + + def update_bars(self): + showing_device = self.location_manager.has_device + main_bar = self.main_bars[1 if showing_device else 0] + child_bar = self.child_bars[0] + for bar in self.bars: + bar.setVisible(False) + bar.update_lm_actions() + if main_bar.added_actions: + main_bar.setVisible(True) + if child_bar.added_actions: + child_bar.setVisible(True) + + self.menu_bar = MenuBar(self.location_manager, self.parent()) + self.menu_bar.init_bar(self.bar_actions[4 if showing_device else 3]) + self.menu_bar.update_lm_actions() + self.menu_bar.setVisible(bool(self.menu_bar.added_actions)) + self.parent().setMenuBar(self.menu_bar) + + def apply_settings(self): + sz = gprefs['toolbar_icon_size'] + sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz] + style = Qt.ToolButtonTextUnderIcon + if sz > 0 and gprefs['toolbar_text'] == 'never': + style = Qt.ToolButtonIconOnly + + for bar in self.bars: + bar.setIconSize(QSize(sz, sz)) + bar.setToolButtonStyle(style) + self.donate_button.set_normal_icon_size(sz, sz) + + diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 49542abdc1..02bce9e701 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -751,6 +751,7 @@ class DeviceMixin(object): # {{{ if self.current_view() != self.library_view: self.book_details.reset_info() self.location_manager.update_devices() + self.bars_manager.update_bars() self.library_view.set_device_connected(self.device_connected) self.refresh_ondevice() device_signals.device_connection_changed.emit(connected) @@ -1198,6 +1199,7 @@ class DeviceMixin(object): # {{{ cp, fs = job.result self.location_manager.update_devices(cp, fs, self.device_manager.device.icon) + self.bars_manager.update_bars() # reset the views so that up-to-date info is shown. These need to be # here because some drivers update collections in sync_booklists self.memory_view.reset() diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index b3c9bd3a02..46b6356a6e 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -7,15 +7,15 @@ __docformat__ = 'restructuredtext en' from functools import partial -from PyQt4.Qt import (QIcon, Qt, QWidget, QToolBar, QSize, - pyqtSignal, QToolButton, QMenu, QMenuBar, QAction, +from PyQt4.Qt import (QIcon, Qt, QWidget, QSize, + pyqtSignal, QToolButton, QMenu, QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup) -from calibre.constants import __appname__, isosx +from calibre.constants import __appname__ from calibre.gui2.search_box import SearchBox2, SavedSearchBox from calibre.gui2.throbber import ThrobbingButton -from calibre.gui2 import gprefs +from calibre.gui2.bars import BarsManager from calibre.gui2.widgets import ComboBoxWithHelp from calibre import human_readable @@ -35,6 +35,8 @@ class LocationManager(QObject): # {{{ self._mem = [] self.tooltips = {} + self.all_actions = [] + def ac(name, text, icon, tooltip): icon = QIcon(I(icon)) ac = self.location_actions.addAction(icon, text) @@ -44,7 +46,7 @@ class LocationManager(QObject): # {{{ receiver = partial(self._location_selected, name) ac.triggered.connect(receiver) self.tooltips[name] = tooltip - + m = QMenu(parent) self._mem.append(m) a = m.addAction(icon, tooltip) @@ -59,6 +61,7 @@ class LocationManager(QObject): # {{{ ac.setMenu(m) ac.calibre_name = name + self.all_actions.append(ac) return ac self.library_action = ac('library', _('Library'), 'lt.png', @@ -77,7 +80,7 @@ class LocationManager(QObject): # {{{ self.switch_menu.addSeparator() else: self.switch_menu = QMenu() - + self.switch_menu.addAction(choose_action) self.cs_menus = [] for t, acs in [(_('Quick switch'), quick_actions), @@ -91,7 +94,7 @@ class LocationManager(QObject): # {{{ self.switch_menu.addSeparator() for ac in switch_actions: self.switch_menu.addAction(ac) - + if self.switch_menu != self.library_action.menu(): self.library_action.setMenu(self.switch_menu) @@ -234,259 +237,6 @@ class Spacer(QWidget): # {{{ self.l.addStretch(10) # }}} -class MenuAction(QAction): # {{{ - - def __init__(self, clone, parent): - QAction.__init__(self, clone.text(), parent) - self.clone = clone - clone.changed.connect(self.clone_changed) - - def clone_changed(self): - self.setText(self.clone.text()) -# }}} - -class MenuBar(QMenuBar): # {{{ - - def __init__(self, location_manager, parent): - QMenuBar.__init__(self, parent) - self.gui = parent - self.setNativeMenuBar(True) - - self.location_manager = location_manager - self.location_manager.locations_changed.connect(self.build_bar) - self.added_actions = [] - - self.donate_action = QAction(_('Donate'), self) - self.donate_menu = QMenu() - self.donate_menu.addAction(self.gui.donate_action) - self.donate_action.setMenu(self.donate_menu) - self.build_bar() - - def build_bar(self, changed_action=None): - showing_device = self.location_manager.has_device - actions = '-device' if showing_device else '' - actions = gprefs['action-layout-menubar'+actions] - - show_main = len(actions) > 0 - self.setVisible(show_main) - - for ac in self.added_actions: - m = ac.menu() - if m is not None: - m.setVisible(False) - - self.clear() - self.added_actions = [] - self.action_map = {} - - for what in actions: - if what is None: - continue - elif what == 'Location Manager': - for ac in self.location_manager.available_actions: - ac = self.build_menu(ac) - self.addAction(ac) - self.added_actions.append(ac) - elif what == 'Donate': - self.addAction(self.donate_action) - elif what in self.gui.iactions: - action = self.gui.iactions[what] - ac = self.build_menu(action.qaction) - self.addAction(ac) - self.added_actions.append(ac) - - def build_menu(self, action): - m = action.menu() - ac = MenuAction(action, self) - if m is None: - m = QMenu() - m.addAction(action) - ac.setMenu(m) - return ac - -# }}} - -class BaseToolBar(QToolBar): # {{{ - - def __init__(self, parent): - QToolBar.__init__(self, parent) - self.setContextMenuPolicy(Qt.PreventContextMenu) - self.setMovable(False) - self.setFloatable(False) - self.setOrientation(Qt.Horizontal) - self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea) - self.setStyleSheet('QToolButton:checked { font-weight: bold }') - self.preferred_width = self.sizeHint().width() - - def resizeEvent(self, ev): - QToolBar.resizeEvent(self, ev) - style = self.get_text_style() - self.setToolButtonStyle(style) - if hasattr(self, 'd_widget') and hasattr(self.d_widget, 'filler'): - self.d_widget.filler.setVisible(style != Qt.ToolButtonIconOnly) - - def get_text_style(self): - style = Qt.ToolButtonTextUnderIcon - s = gprefs['toolbar_icon_size'] - if s != 'off': - p = gprefs['toolbar_text'] - if p == 'never': - style = Qt.ToolButtonIconOnly - elif p == 'auto' and self.preferred_width > self.width()+35: - style = Qt.ToolButtonIconOnly - return style - - def contextMenuEvent(self, *args): - pass - -# }}} - -class ToolBar(BaseToolBar): # {{{ - - def __init__(self, donate, location_manager, child_bar, parent): - BaseToolBar.__init__(self, parent) - self.gui = parent - self.child_bar = child_bar - self.donate_button = donate - self.apply_settings() - - self.location_manager = location_manager - self.location_manager.locations_changed.connect(self.build_bar) - donate.setAutoRaise(True) - donate.setCursor(Qt.PointingHandCursor) - self.added_actions = [] - self.build_bar() - self.setAcceptDrops(True) - - def apply_settings(self): - sz = gprefs['toolbar_icon_size'] - sz = {'off':0, 'small':24, 'medium':48, 'large':64}[sz] - self.setIconSize(QSize(sz, sz)) - self.child_bar.setIconSize(QSize(sz, sz)) - style = Qt.ToolButtonTextUnderIcon - if sz > 0 and gprefs['toolbar_text'] == 'never': - style = Qt.ToolButtonIconOnly - self.setToolButtonStyle(style) - self.child_bar.setToolButtonStyle(style) - self.donate_button.set_normal_icon_size(sz, sz) - - def build_bar(self): - self.showing_donate = False - showing_device = self.location_manager.has_device - mactions = '-device' if showing_device else '' - mactions = gprefs['action-layout-toolbar'+mactions] - cactions = gprefs['action-layout-toolbar-child'] - - show_main = len(mactions) > 0 - self.setVisible(show_main) - show_child = len(cactions) > 0 - self.child_bar.setVisible(show_child) - - for ac in self.added_actions: - m = ac.menu() - if m is not None: - m.setVisible(False) - - self.clear() - self.child_bar.clear() - self.added_actions = [] - - for bar, actions in ((self, mactions), (self.child_bar, cactions)): - for what in actions: - if what is None: - bar.addSeparator() - elif what == 'Location Manager': - for ac in self.location_manager.available_actions: - bar.addAction(ac) - bar.added_actions.append(ac) - bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup) - elif what == 'Donate': - self.d_widget = QWidget() - self.d_widget.setLayout(QVBoxLayout()) - self.d_widget.layout().addWidget(self.donate_button) - if isosx: - self.d_widget.setStyleSheet('QWidget, QToolButton {background-color: none; border: none; }') - self.d_widget.layout().setContentsMargins(0,0,0,0) - self.d_widget.setContentsMargins(0,0,0,0) - self.d_widget.filler = QLabel(u'\u00a0') - self.d_widget.layout().addWidget(self.d_widget.filler) - bar.addWidget(self.d_widget) - self.showing_donate = True - elif what in self.gui.iactions: - action = self.gui.iactions[what] - bar.addAction(action.qaction) - self.added_actions.append(action.qaction) - self.setup_tool_button(bar, action.qaction, action.popup_type) - self.preferred_width = self.sizeHint().width() - self.child_bar.preferred_width = self.child_bar.sizeHint().width() - - def setup_tool_button(self, bar, ac, menu_mode=None): - ch = bar.widgetForAction(ac) - if ch is None: - ch = self.child_bar.widgetForAction(ac) - ch.setCursor(Qt.PointingHandCursor) - ch.setAutoRaise(True) - if ac.menu() is not None and menu_mode is not None: - ch.setPopupMode(menu_mode) - return ch - - def database_changed(self, db): - pass - - #support drag&drop from/to library from/to reader/card - def dragEnterEvent(self, event): - md = event.mimeData() - if md.hasFormat("application/calibre+from_library") or \ - md.hasFormat("application/calibre+from_device"): - event.setDropAction(Qt.CopyAction) - event.accept() - else: - event.ignore() - - def dragMoveEvent(self, event): - allowed = False - md = event.mimeData() - #Drop is only allowed in the location manager widget's different from the selected one - for ac in self.location_manager.available_actions: - w = self.widgetForAction(ac) - if w is not None: - if ( md.hasFormat("application/calibre+from_library") or \ - md.hasFormat("application/calibre+from_device") ) and \ - w.geometry().contains(event.pos()) and \ - isinstance(w, QToolButton) and not w.isChecked(): - allowed = True - break - if allowed: - event.acceptProposedAction() - else: - event.ignore() - - def dropEvent(self, event): - data = event.mimeData() - - mime = 'application/calibre+from_library' - if data.hasFormat(mime): - ids = list(map(int, str(data.data(mime)).split())) - tgt = None - for ac in self.location_manager.available_actions: - w = self.widgetForAction(ac) - if w is not None and w.geometry().contains(event.pos()): - tgt = ac.calibre_name - if tgt is not None: - if tgt == 'main': - tgt = None - self.gui.sync_to_device(tgt, False, send_ids=ids) - event.accept() - - mime = 'application/calibre+from_device' - if data.hasFormat(mime): - paths = [unicode(u.toLocalFile()) for u in data.urls()] - if paths: - self.gui.iactions['Add Books'].add_books_from_device( - self.gui.current_view(), paths=paths) - event.accept() - -# }}} class MainWindowMixin(object): # {{{ @@ -507,13 +257,13 @@ class MainWindowMixin(object): # {{{ self.iactions['Fetch News'].init_scheduler(db) self.search_bar = SearchBar(self) - self.child_bar = BaseToolBar(self) - self.tool_bar = ToolBar(self.donate_button, - self.location_manager, self.child_bar, self) - self.addToolBar(Qt.TopToolBarArea, self.tool_bar) - self.addToolBar(Qt.BottomToolBarArea, self.child_bar) - self.menu_bar = MenuBar(self.location_manager, self) - self.setMenuBar(self.menu_bar) + self.bars_manager = BarsManager(self.donate_button, + self.location_manager, self) + for bar in self.bars_manager.main_bars: + self.addToolBar(Qt.TopToolBarArea, bar) + for bar in self.bars_manager.child_bars: + self.addToolBar(Qt.BottomToolBarArea, bar) + self.bars_manager.update_bars() self.setUnifiedTitleAndToolBarOnMac(True) l = self.centralwidget.layout() diff --git a/src/calibre/gui2/preferences/main.py b/src/calibre/gui2/preferences/main.py index c5f9a11d16..85a5fc018c 100644 --- a/src/calibre/gui2/preferences/main.py +++ b/src/calibre/gui2/preferences/main.py @@ -361,10 +361,9 @@ class Preferences(QMainWindow): self.gui.tags_view.recount() self.gui.create_device_menu() self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) - self.gui.tool_bar.build_bar() - self.gui.menu_bar.build_bar() + self.gui.bars_manager.apply_settings() + self.gui.bars_manager.update_bars() self.gui.build_context_menus() - self.gui.tool_bar.apply_settings() return QMainWindow.closeEvent(self, *args) diff --git a/src/calibre/gui2/preferences/toolbar.py b/src/calibre/gui2/preferences/toolbar.py index 7f5e0c4441..04ddb29170 100644 --- a/src/calibre/gui2/preferences/toolbar.py +++ b/src/calibre/gui2/preferences/toolbar.py @@ -317,6 +317,9 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): am.restore_defaults() self.changed_signal.emit() + def refresh_gui(self, gui): + gui.bars_manager.init_bars() + if __name__ == '__main__': from PyQt4.Qt import QApplication diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 435b9ebe78..122bfc81b4 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -288,8 +288,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.db_images.reset() self.library_view.model().count_changed() - self.tool_bar.database_changed(self.library_view.model().db) - self.library_view.model().database_changed.connect(self.tool_bar.database_changed, + self.bars_manager.database_changed(self.library_view.model().db) + self.library_view.model().database_changed.connect(self.bars_manager.database_changed, type=Qt.QueuedConnection) ########################### Tags Browser ############################## @@ -324,7 +324,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ self.read_settings() self.finalize_layout() - if self.tool_bar.showing_donate: + if self.bars_manager.showing_donate: self.donate_button.start_animation() self.set_window_title() From 1e4cab886de0cc17fe9162dc6ac15bb78dfdd1bb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 May 2011 20:45:12 -0600 Subject: [PATCH 45/46] ... --- src/calibre/gui2/device.py | 2 +- src/calibre/gui2/preferences/toolbar.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index 02bce9e701..3977a6bca1 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -765,6 +765,7 @@ class DeviceMixin(object): # {{{ info, cp, fs = job.result self.location_manager.update_devices(cp, fs, self.device_manager.device.icon) + self.bars_manager.update_bars() self.status_bar.device_connected(info[0]) self.device_manager.books(Dispatcher(self.metadata_downloaded)) @@ -1199,7 +1200,6 @@ class DeviceMixin(object): # {{{ cp, fs = job.result self.location_manager.update_devices(cp, fs, self.device_manager.device.icon) - self.bars_manager.update_bars() # reset the views so that up-to-date info is shown. These need to be # here because some drivers update collections in sync_booklists self.memory_view.reset() diff --git a/src/calibre/gui2/preferences/toolbar.py b/src/calibre/gui2/preferences/toolbar.py index 04ddb29170..6ed9b32ff1 100644 --- a/src/calibre/gui2/preferences/toolbar.py +++ b/src/calibre/gui2/preferences/toolbar.py @@ -319,6 +319,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): def refresh_gui(self, gui): gui.bars_manager.init_bars() + gui.bars_manager.update_bars() if __name__ == '__main__': From 2d169def5ff5f8ae6179f1d6ce2e5fc87b71f26d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 May 2011 20:59:00 -0600 Subject: [PATCH 46/46] ... --- src/calibre/gui2/bars.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/calibre/gui2/bars.py b/src/calibre/gui2/bars.py index 8bae9d65d1..d586bfe1c3 100644 --- a/src/calibre/gui2/bars.py +++ b/src/calibre/gui2/bars.py @@ -277,6 +277,13 @@ class BarsManager(QObject): bar.init_bar(actions) def update_bars(self): + ''' + This shows the correct main toolbar and rebuilds the menubar based on + whether a device is connected or not. Note that the toolbars are + explicitly not rebuilt, this is to workaround a Qt limitation iwth + QToolButton's popup menus and modal dialogs. If you want the toolbars + rebuilt, call init_bars(). + ''' showing_device = self.location_manager.has_device main_bar = self.main_bars[1 if showing_device else 0] child_bar = self.child_bars[0]