Merge from trunk

This commit is contained in:
Charles Haley 2011-05-27 13:29:06 +01:00
commit 5fdf11466f
16 changed files with 438 additions and 162 deletions

View File

@ -0,0 +1,42 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe(BasicNewsRecipe):
title = u'Aachener Nachrichten'
__author__ = 'schuster'
oldest_article = 1
max_articles_per_feed = 100
use_embedded_content = False
language = 'de'
remove_javascript = True
cover_url = 'http://www.an-online.de/einwaage/images/an_logo.png'
masthead_url = 'http://www.an-online.de/einwaage/images/an_logo.png'
extra_css = '''
.fliesstext_detail:{margin-bottom:10%;}
.headline_1:{margin-bottom:25%;}
b{font-family:Arial,Helvetica,sans-serif; font-weight:200;font-size:large;}
a{font-family:Arial,Helvetica,sans-serif; font-weight:400;font-size:large;}
ll{font-family:Arial,Helvetica,sans-serif; font-weight:100;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}
dd{font-family:Arial,Helvetica,sans-serif;font-size:large;}
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''
keep_only_tags = [
dict(name='span', attrs={'class':['fliesstext_detail', 'headline_1', 'autor_detail']}),
dict(id=['header-logo'])
]
feeds = [(u'Euregio', u'http://www.an-online.de/an/rss/Euregio.xml'),
(u'Aachen', u'http://www.an-online.de/an/rss/Aachen.xml'),
(u'Nordkreis', u'http://www.an-online.de/an/rss/Nordkreis.xml'),
(u'Düren', u'http://www.an-online.de/an/rss/Dueren.xml'),
(u'Eiffel', u'http://www.an-online.de/an/rss/Eifel.xml'),
(u'Eschweiler', u'http://www.an-online.de/an/rss/Eschweiler.xml'),
(u'Geilenkirchen', u'http://www.an-online.de/an/rss/Geilenkirchen.xml'),
(u'Heinsberg', u'http://www.an-online.de/an/rss/Heinsberg.xml'),
(u'Jülich', u'http://www.an-online.de/an/rss/Juelich.xml'),
(u'Stolberg', u'http://www.an-online.de/an/rss/Stolberg.xml'),
(u'Ratgebenr', u'http://www.an-online.de/an/rss/Ratgeber.xml')]

View File

@ -3,10 +3,7 @@ class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'Faz.net' title = u'Faz.net'
__author__ = 'schuster' __author__ = 'schuster'
remove_tags = [dict(attrs={'class':['right', 'ArrowLinkRight', 'ModulVerlagsInfo', 'left', 'Head']}), oldest_article = 1
dict(id=['BreadCrumbs', 'tstag', 'FazFooterPrint']),
dict(name=['script', 'noscript', 'style'])]
oldest_article = 2
description = 'Frankfurter Allgemeine Zeitung' description = 'Frankfurter Allgemeine Zeitung'
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
@ -15,9 +12,9 @@ class AdvancedUserRecipe1303841067(BasicNewsRecipe):
remove_javascript = True remove_javascript = True
cover_url = 'http://www.faz.net/f30/Images/Logos/logo.gif' cover_url = 'http://www.faz.net/f30/Images/Logos/logo.gif'
def print_version(self, url): remove_tags = [dict(attrs={'class':['LinkBoxModulSmall', 'ModulLesermeinungenFooter', 'ModulArtikelServices', 'SocialMediaUnten', 'ArrowLinkRight', 'ModulVerlagsInfo', 'AdData', 'FazFooter', 'Date']}),
return url.replace('.html', '~Afor~Eprint.html') dict(id=['FAZNavHeader', 'FAZNavMain', 'RightColumn', 'FazFooter', 'BreadCrumbs', 'FAZNavSubMain', 'FAZImgEvent']),
dict(name=['jksrdt'])]
feeds = [(u'Politik', u'http://www.faz.net/s/RubA24ECD630CAE40E483841DB7D16F4211/Tpl~Epartner~SRss_.xml'), feeds = [(u'Politik', u'http://www.faz.net/s/RubA24ECD630CAE40E483841DB7D16F4211/Tpl~Epartner~SRss_.xml'),

View File

@ -0,0 +1,35 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe(BasicNewsRecipe):
title = u'Frankfurter Rundschau'
__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.fr-online.de/image/view/-/1474018/data/823538/-/logo.png'
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;}
'''
feeds = [(u'Startseite', u'http://www.fr-online.de/home/-/1472778/1472778/-/view/asFeed/-/index.xml'),
(u'Politik', u'http://www.fr-online.de/politik/-/1472596/1472596/-/view/asFeed/-/index.xml'),
(u'Meinungen', u'http://www.fr-online.de/politik/meinung/-/1472602/1472602/-/view/asFeed/-/index.xml'),
(u'Wirtschaft', u'http://www.fr-online.de/wirtschaft/-/1472780/1472780/-/view/asFeed/-/index.xml'),
(u'Sport', u'http://www.fr-online.de/sport/-/1472784/1472784/-/view/asFeed/-/index.xml'),
(u'Kultur', u'http://www.fr-online.de/kultur/-/1472786/1472786/-/view/asFeed/-/index.xml'),
(u'Panorama', u'http://www.fr-online.de/panorama/-/1472782/1472782/-/view/asFeed/-/index.xml'),
(u'Digital', u'http://www.fr-online.de/digital/-/1472406/1472406/-/view/asFeed/-/index.xml'),
(u'Wissenschaft', u'http://www.fr-online.de/wissenschaft/-/1472788/1472788/-/view/asFeed/-/index.xml')
]
def print_version(self, url):
return url.replace('index.html', 'view/printVersion/-/index.html')

View File

@ -0,0 +1,55 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe(BasicNewsRecipe):
title = u'RP-online'
__author__ = 'schuster'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
language = 'de'
remove_javascript = True
masthead_url = 'http://www.die-zeitungen.de/uploads/pics/LOGO_RP_ONLINE_01.jpg'
cover_url = 'http://www.manroland.com/com/pressinfo_images/com/RheinischePost_Logo_300dpi.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_before = dict(id='article_content')
remove_tags_after = dict(id='article_content')
remove_tags = [dict(attrs={'class':['goodies', 'left', 'right', 'clear-all', 'teaser anzeigenwerbung', 'lesermeinung', 'goodiebox', 'goodiebox 1', 'goodiebox 2', 'goodiebox 3', 'boxframe', 'link']}),
dict(id=['click_Fotos_link']),
dict(name=['script', 'noscript', 'style', '_top', 'click_Fotos_link'])]
feeds = [ (u'Top-News', u'http://www.ngz-online.de/app/feed/rss/topnews'),
(u'Politik', u'http://www.ngz-online.de/app/feed/rss/politik'),
(u'Wirtschaft', u'http://www.ngz-online.de/app/feed/rss/wirtschaft'),
(u'Panorama', u'http://www.ngz-online.de/app/feed/rss/panorama'),
(u'Sport', u'http://www.ngz-online.de/app/feed/rss/sport'),
(u'Tour de France', u'http://www.ngz-online.de/app/feed/rss/tourdefrance'),
(u'Fußball', u'http://www.ngz-online.de/app/feed/rss/fussball'),
(u'Fußball BuLi', u'http://www.ngz-online.de/app/feed/rss/bundesliga'),
(u'Formel 1', u'http://www.ngz-online.de/app/feed/rss/formel1'),
(u'US-Sport', u'http://www.ngz-online.de/app/feed/rss/us-sports'),
(u'Boxen', u'http://www.ngz-online.de/app/feed/rss/boxen'),
(u'Eishockey', u'http://www.ngz-online.de/app/feed/rss/eishockey'),
(u'Basketball', u'http://www.ngz-online.de/app/feed/rss/basketball'),
(u'Handball', u'http://www.ngz-online.de/app/feed/rss/handball'),
(u'Motorsport', u'http://www.ngz-online.de/app/feed/rss/motorsport'),
(u'Tennis', u'http://www.ngz-online.de/app/feed/rss/tennis'),
(u'Radsport', u'http://www.ngz-online.de/app/feed/rss/radsport'),
(u'Kultur', u'http://www.ngz-online.de/app/feed/rss/kultur'),
(u'Gesellschaft', u'http://www.ngz-online.de/app/feed/rss/gesellschaft'),
(u'Wissenschaft', u'http://www.ngz-online.de/app/feed/rss/wissen'),
(u'Gesundheit', u'http://www.ngz-online.de/app/feed/rss/gesundheit'),
(u'Digitale Welt', u'http://www.ngz-online.de/app/feed/rss/digitale'),
(u'Auto & Mobil', u'http://www.ngz-online.de/app/feed/rss/auto'),
(u'Reise & Welt', u'http://www.ngz-online.de/app/feed/rss/reise'),
(u'Beruf & Karriere', u'http://www.ngz-online.de/app/feed/rss/beruf'),
(u'Herzrasen', u'http://www.ngz-online.de/app/feed/rss/herzrasen'),
(u'About a Boy', u'http://www.ngz-online.de/app/feed/rss/about_a_boy'),
]

View File

@ -1,19 +1,21 @@
{ {
"and": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if not args[i]:\n return ''\n i += 1\n return '1'\n",
"contains": "def evaluate(self, formatter, kwargs, mi, locals,\n val, test, value_if_present, value_if_not):\n if re.search(test, val):\n return value_if_present\n else:\n return value_if_not\n", "contains": "def evaluate(self, formatter, kwargs, mi, locals,\n val, test, value_if_present, value_if_not):\n if re.search(test, val):\n return value_if_present\n else:\n return value_if_not\n",
"divide": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x / y)\n", "divide": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x / y)\n",
"uppercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.upper()\n", "uppercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.upper()\n",
"strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n res = ''\n for i in range(0, len(args)):\n res += args[i]\n return res\n", "strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n res = ''\n for i in range(0, len(args)):\n res += args[i]\n return res\n",
"in_list": "def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv):\n l = [v.strip() for v in val.split(sep) if v.strip()]\n for v in l:\n if re.search(pat, v):\n return fv\n return nfv\n", "in_list": "def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv):\n l = [v.strip() for v in val.split(sep) if v.strip()]\n for v in l:\n if re.search(pat, v):\n return fv\n return nfv\n",
"substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n", "multiply": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x * y)\n",
"ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n if val:\n return val\n else:\n return value_if_empty\n", "ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n if val:\n return val\n else:\n return value_if_empty\n",
"booksize": "def evaluate(self, formatter, kwargs, mi, locals):\n if mi.book_size is not None:\n try:\n return str(mi.book_size)\n except:\n pass\n return ''\n", "booksize": "def evaluate(self, formatter, kwargs, mi, locals):\n if mi.book_size is not None:\n try:\n return str(mi.book_size)\n except:\n pass\n return ''\n",
"select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n if not val:\n return ''\n vals = [v.strip() for v in val.split(',')]\n for v in vals:\n if v.startswith(key+':'):\n return v[len(key)+1:]\n return ''\n", "select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n if not val:\n return ''\n vals = [v.strip() for v in val.split(',')]\n for v in vals:\n if v.startswith(key+':'):\n return v[len(key)+1:]\n return ''\n",
"strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n v = strcmp(x, y)\n if v < 0:\n return lt\n if v == 0:\n return eq\n return gt\n",
"first_non_empty": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return args[i]\n i += 1\n return ''\n", "first_non_empty": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return args[i]\n i += 1\n return ''\n",
"field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n", "re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val)\n",
"subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x - y)\n", "subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x - y)\n",
"list_item": "def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):\n if not val:\n return ''\n index = int(index)\n val = val.split(sep)\n try:\n return val[index]\n except:\n return ''\n", "list_item": "def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):\n if not val:\n return ''\n index = int(index)\n val = val.split(sep)\n try:\n return val[index]\n except:\n return ''\n",
"shorten": "def evaluate(self, formatter, kwargs, mi, locals,\n val, leading, center_string, trailing):\n l = max(0, int(leading))\n t = max(0, int(trailing))\n if len(val) > l + len(center_string) + t:\n return val[0:l] + center_string + ('' if t == 0 else val[-t:])\n else:\n return val\n", "shorten": "def evaluate(self, formatter, kwargs, mi, locals,\n val, leading, center_string, trailing):\n l = max(0, int(leading))\n t = max(0, int(trailing))\n if len(val) > l + len(center_string) + t:\n return val[0:l] + center_string + ('' if t == 0 else val[-t:])\n else:\n return val\n",
"re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val)\n", "field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n",
"add": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x + y)\n", "add": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x + y)\n",
"lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if len(args) == 2: # here for backwards compatibility\n if val:\n return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n else:\n return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n if (len(args) % 2) != 1:\n raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n if re.search(args[i], val):\n return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n i += 2\n", "lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if len(args) == 2: # here for backwards compatibility\n if val:\n return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n else:\n return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n if (len(args) % 2) != 1:\n raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n if re.search(args[i], val):\n return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n i += 2\n",
"template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n template = template.replace('[[', '{').replace(']]', '}')\n return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)\n", "template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n template = template.replace('[[', '{').replace(']]', '}')\n return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)\n",
@ -23,14 +25,15 @@
"sublist": "def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index, sep):\n if not val:\n return ''\n si = int(start_index)\n ei = int(end_index)\n val = val.split(sep)\n try:\n if ei == 0:\n return sep.join(val[si:])\n else:\n return sep.join(val[si:ei])\n except:\n return ''\n", "sublist": "def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index, sep):\n if not val:\n return ''\n si = int(start_index)\n ei = int(end_index)\n val = val.split(sep)\n try:\n if ei == 0:\n return sep.join(val[si:])\n else:\n return sep.join(val[si:ei])\n except:\n return ''\n",
"test": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):\n if val:\n return value_if_set\n else:\n return value_not_set\n", "test": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):\n if val:\n return value_if_set\n else:\n return value_not_set\n",
"eval": "def evaluate(self, formatter, kwargs, mi, locals, template):\n from formatter import eval_formatter\n template = template.replace('[[', '{').replace(']]', '}')\n return eval_formatter.safe_format(template, locals, 'EVAL', None)\n", "eval": "def evaluate(self, formatter, kwargs, mi, locals, template):\n from formatter import eval_formatter\n template = template.replace('[[', '{').replace(']]', '}')\n return eval_formatter.safe_format(template, locals, 'EVAL', None)\n",
"multiply": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x * y)\n", "not": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return '1'\n i += 1\n return ''\n",
"format_date": "def evaluate(self, formatter, kwargs, mi, locals, val, format_string):\n if not val:\n return ''\n try:\n dt = parse_date(val)\n s = format_date(dt, format_string)\n except:\n s = 'BAD DATE'\n return s\n", "format_date": "def evaluate(self, formatter, kwargs, mi, locals, val, format_string):\n if not val:\n return ''\n try:\n dt = parse_date(val)\n s = format_date(dt, format_string)\n except:\n s = 'BAD DATE'\n return s\n",
"capitalize": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return capitalize(val)\n", "capitalize": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return capitalize(val)\n",
"count": "def evaluate(self, formatter, kwargs, mi, locals, val, sep):\n return unicode(len(val.split(sep)))\n", "count": "def evaluate(self, formatter, kwargs, mi, locals, val, sep):\n return unicode(len(val.split(sep)))\n",
"lowercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.lower()\n", "lowercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.lower()\n",
"strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n v = strcmp(x, y)\n if v < 0:\n return lt\n if v == 0:\n return eq\n return gt\n", "substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n",
"switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
"assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n", "assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n",
"switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
"or": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return '1'\n i += 1\n return ''\n",
"raw_field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return unicode(getattr(mi, name, None))\n", "raw_field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return unicode(getattr(mi, name, None))\n",
"cmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n x = float(x if x else 0)\n y = float(y if y else 0)\n if x < y:\n return lt\n if x == y:\n return eq\n return gt\n" "cmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n x = float(x if x else 0)\n y = float(y if y else 0)\n if x < y:\n return lt\n if x == y:\n return eq\n return gt\n"
} }

View File

@ -594,7 +594,7 @@ from calibre.devices.iliad.driver import ILIAD
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800 from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI
from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
from calibre.devices.nook.driver import NOOK, NOOK_COLOR from calibre.devices.nook.driver import NOOK, NOOK_COLOR, NOOK_TSR
from calibre.devices.prs505.driver import PRS505 from calibre.devices.prs505.driver import PRS505
from calibre.devices.user_defined.driver import USER_DEFINED from calibre.devices.user_defined.driver import USER_DEFINED
from calibre.devices.android.driver import ANDROID, S60 from calibre.devices.android.driver import ANDROID, S60
@ -693,8 +693,7 @@ plugins += [
KINDLE, KINDLE,
KINDLE2, KINDLE2,
KINDLE_DX, KINDLE_DX,
NOOK, NOOK, NOOK_COLOR, NOOK_TSR,
NOOK_COLOR,
PRS505, PRS505,
ANDROID, ANDROID,
S60, S60,
@ -1346,6 +1345,16 @@ class StoreSmashwordsStore(StoreBase):
headquarters = 'US' headquarters = 'US'
formats = ['EPUB', 'HTML', 'LRF', 'MOBI', 'PDB', 'RTF', 'TXT'] formats = ['EPUB', 'HTML', 'LRF', 'MOBI', 'PDB', 'RTF', 'TXT']
class StoreVirtualoStore(StoreBase):
name = 'Virtualo'
author = u'Tomasz Długosz'
description = u'Księgarnia internetowa, która oferuje bezpieczny i szeroki dostęp do książek w formie cyfrowej.'
actual_plugin = 'calibre.gui2.store.virtualo_plugin:VirtualoStore'
drm_free_only = False
headquarters = 'PL'
formats = ['EPUB', 'PDF']
class StoreWaterstonesUKStore(StoreBase): class StoreWaterstonesUKStore(StoreBase):
name = 'Waterstones UK' name = 'Waterstones UK'
author = 'Charles Haley' author = 'Charles Haley'
@ -1376,7 +1385,7 @@ class StoreWizardsTowerBooksStore(StoreBase):
class StoreWoblinkStore(StoreBase): class StoreWoblinkStore(StoreBase):
name = 'Woblink' name = 'Woblink'
author = 'Tomasz Długosz' author = u'Tomasz Długosz'
description = u'Czytanie zdarza się wszędzie!' description = u'Czytanie zdarza się wszędzie!'
actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore' actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore'
@ -1411,6 +1420,7 @@ plugins += [
StoreOReillyStore, StoreOReillyStore,
StorePragmaticBookshelfStore, StorePragmaticBookshelfStore,
StoreSmashwordsStore, StoreSmashwordsStore,
StoreVirtualoStore,
StoreWaterstonesUKStore, StoreWaterstonesUKStore,
StoreWeightlessBooksStore, StoreWeightlessBooksStore,
StoreWizardsTowerBooksStore, StoreWizardsTowerBooksStore,

View File

@ -107,3 +107,13 @@ class NOOK_COLOR(NOOK):
return filepath return filepath
class NOOK_TSR(NOOK):
gui_name = _('Nook Simple')
description = _('Communicate with the Nook TSR eBook reader.')
PRODUCT_ID = [0x003]
BCD = [0x216]
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'My Files/Books'

View File

@ -297,9 +297,11 @@ class MobiMLizer(object):
if id_: if id_:
# Keep anchors so people can use display:none # Keep anchors so people can use display:none
# to generate hidden TOCs # to generate hidden TOCs
tail = elem.tail
elem.clear() elem.clear()
elem.text = None elem.text = None
elem.set('id', id_) elem.set('id', id_)
elem.tail = tail
else: else:
return return
tag = barename(elem.tag) tag = barename(elem.tag)
@ -309,7 +311,8 @@ class MobiMLizer(object):
istates.append(istate) istates.append(istate)
left = 0 left = 0
display = style['display'] display = style['display']
isblock = not display.startswith('inline') isblock = (not display.startswith('inline') and style['display'] !=
'none')
isblock = isblock and style['float'] == 'none' isblock = isblock and style['float'] == 'none'
isblock = isblock and tag != 'br' isblock = isblock and tag != 'br'
if isblock: if isblock:

View File

@ -44,7 +44,7 @@ class StoreAction(InterfaceAction):
def search(self, query=''): def search(self, query=''):
self.show_disclaimer() self.show_disclaimer()
from calibre.gui2.store.search.search import SearchDialog from calibre.gui2.store.search.search import SearchDialog
sd = SearchDialog(self.gui.istores, self.gui, query) sd = SearchDialog(self.gui, self.gui, query)
sd.exec_() sd.exec_()
def _get_selected_row(self): def _get_selected_row(self):

View File

@ -207,8 +207,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.recipe_model.searched.connect(self.search.search_done, self.recipe_model.searched.connect(self.search.search_done,
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
self.recipe_model.searched.connect(self.search_done) self.recipe_model.searched.connect(self.search_done)
self.search.setFocus(Qt.OtherFocusReason) self.recipes.setFocus(Qt.OtherFocusReason)
self.commit_on_change = True self.commit_on_change = True
self.previous_urn = None
self.recipes.setModel(self.recipe_model) self.recipes.setModel(self.recipe_model)
self.detail_box.setVisible(False) self.detail_box.setVisible(False)
@ -228,6 +229,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.old_news.setValue(gconf['oldest_news']) self.old_news.setValue(gconf['oldest_news'])
self.go_button.clicked.connect(self.search.do_search)
self.clear_search_button.clicked.connect(self.search.clear_clicked)
def set_pw_echo_mode(self, state): def set_pw_echo_mode(self, state):
self.password.setEchoMode(self.password.Normal self.password.setEchoMode(self.password.Normal
if state == Qt.Checked else self.password.Password) if state == Qt.Checked else self.password.Password)
@ -265,14 +269,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
self.last_downloaded.setVisible(enabled) self.last_downloaded.setVisible(enabled)
def current_changed(self, current, previous): def current_changed(self, current, previous):
if self.commit_on_change: if self.previous_urn is not None:
if previous.isValid(): self.commit(urn=self.previous_urn)
if not self.commit(urn=getattr(previous.internalPointer(), self.previous_urn = None
'urn', None)):
self.commit_on_change = False
self.recipes.setCurrentIndex(previous)
else:
self.commit_on_change = True
urn = self.current_urn urn = self.current_urn
if urn is not None: if urn is not None:
@ -332,6 +331,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
return True return True
def initialize_detail_box(self, urn): def initialize_detail_box(self, urn):
self.previous_urn = urn
self.detail_box.setVisible(True) self.detail_box.setVisible(True)
self.download_button.setVisible(True) self.download_button.setVisible(True)
self.detail_box.setCurrentIndex(0) self.detail_box.setCurrentIndex(0)

View File

@ -17,21 +17,30 @@
<iconset resource="../../../../resources/images.qrc"> <iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/scheduler.png</normaloff>:/images/scheduler.png</iconset> <normaloff>:/images/scheduler.png</normaloff>:/images/scheduler.png</iconset>
</property> </property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,2"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_8"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="SearchBox2" name="search"/>
</item>
<item>
<widget class="QToolButton" name="go_button">
<property name="text"> <property name="text">
<string>&amp;Search:</string> <string>Go</string>
</property>
<property name="buddy">
<cstring>search</cstring>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item>
<widget class="SearchBox2" name="search"/> <widget class="QToolButton" name="clear_search_button">
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/clear_left.png</normaloff>:/images/clear_left.png</iconset>
</property>
</widget>
</item> </item>
<item row="0" column="2" rowspan="3"> </layout>
</item>
<item row="0" column="1" rowspan="2">
<widget class="QScrollArea" name="scrollArea"> <widget class="QScrollArea" name="scrollArea">
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::NoFrame</enum> <enum>QFrame::NoFrame</enum>
@ -44,7 +53,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>486</width> <width>524</width>
<height>504</height> <height>504</height>
</rect> </rect>
</property> </property>
@ -320,7 +329,7 @@
</widget> </widget>
</widget> </widget>
</item> </item>
<item row="1" column="0" colspan="2"> <item row="1" column="0">
<widget class="QTreeView" name="recipes"> <widget class="QTreeView" name="recipes">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Expanding"> <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
@ -345,7 +354,17 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="2"> <item row="2" column="0">
<widget class="QLabel" name="count_label">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">
<item> <item>
<widget class="QLabel" name="label_7"> <widget class="QLabel" name="label_7">
@ -376,17 +395,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="4" column="2"> <item row="3" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Save</set>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QPushButton" name="download_all_button"> <widget class="QPushButton" name="download_all_button">
<property name="toolTip"> <property name="toolTip">
<string>Download all scheduled news sources at once</string> <string>Download all scheduled news sources at once</string>
@ -394,15 +403,19 @@
<property name="text"> <property name="text">
<string>Download &amp;all scheduled</string> <string>Download &amp;all scheduled</string>
</property> </property>
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/news.png</normaloff>:/images/news.png</iconset>
</property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2"> <item row="3" column="1">
<widget class="QLabel" name="count_label"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="text"> <property name="orientation">
<string/> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="alignment"> <property name="standardButtons">
<set>Qt::AlignCenter</set> <set>QDialogButtonBox::Save</set>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -103,7 +103,22 @@ class Matches(QAbstractItemModel):
return Qt.Unchecked return Qt.Unchecked
return Qt.Checked return Qt.Checked
elif role == Qt.ToolTipRole: elif role == Qt.ToolTipRole:
if col == 0:
if is_disabled(result):
return QVariant(_('<p>This store is currently diabled and cannot be used in other parts of calibre.</p>'))
else:
return QVariant(_('<p>This store is currently enabled and can be used in other parts of calibre.</p>'))
elif col == 1:
return QVariant('<p>%s</p>' % result.description) return QVariant('<p>%s</p>' % result.description)
elif col == 2:
if result.drm_free_only:
return QVariant(_('<p>This store only distributes ebooks with DRM.</p>'))
else:
return QVariant(_('<p>This store distributes ebooks with DRM. It may have some titles without DRM, but you will need to check on a per title basis.</p>'))
elif col == 3:
return QVariant(_('<p>This store is headquartered in %s. This is a good indication of what market the store caters to. However, this does not necessarily mean that the store is limited to that market only.</p>') % result.headquarters)
elif col == 4:
return QVariant(_('<p>This store distributes ebooks in the following formats: %s</p>') % ', '.join(result.formats))
return NONE return NONE
def setData(self, index, data, role): def setData(self, index, data, role):
@ -130,7 +145,7 @@ class Matches(QAbstractItemModel):
elif col == 1: elif col == 1:
text = match.name text = match.name
elif col == 2: elif col == 2:
text = 'b' if getattr(match, 'drm', True) else 'a' text = 'a' if getattr(match, 'drm_free_only', True) else 'b'
elif col == 3: elif col == 3:
text = getattr(match, 'headquarters', '') text = getattr(match, 'headquarters', '')
return text return text
@ -240,5 +255,3 @@ class SearchFilter(SearchQueryParser):
import traceback import traceback
traceback.print_exc() traceback.print_exc()
return matches return matches

View File

@ -10,10 +10,11 @@ import re
from random import shuffle from random import shuffle
from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox,
QVBoxLayout, QIcon, QWidget) QVBoxLayout, QIcon, QWidget, QTabWidget)
from calibre.gui2 import JSONConfig, info_dialog from calibre.gui2 import JSONConfig, info_dialog
from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
from calibre.gui2.store.config.search.search_widget import StoreConfigWidget from calibre.gui2.store.config.search.search_widget import StoreConfigWidget
from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog
from calibre.gui2.store.search.download_thread import SearchThreadPool, \ from calibre.gui2.store.search.download_thread import SearchThreadPool, \
@ -22,7 +23,7 @@ from calibre.gui2.store.search.search_ui import Ui_Dialog
class SearchDialog(QDialog, Ui_Dialog): class SearchDialog(QDialog, Ui_Dialog):
def __init__(self, istores, parent=None, query=''): def __init__(self, gui, parent=None, query=''):
QDialog.__init__(self, parent) QDialog.__init__(self, parent)
self.setupUi(self) self.setupUi(self)
@ -34,8 +35,7 @@ class SearchDialog(QDialog, Ui_Dialog):
# the variables it sets up are used later. # the variables it sets up are used later.
self.load_settings() self.load_settings()
# We keep a cache of store plugins and reference them by name. self.gui = gui
self.store_plugins = istores
# Setup our worker threads. # Setup our worker threads.
self.search_pool = SearchThreadPool(self.search_thread_count) self.search_pool = SearchThreadPool(self.search_thread_count)
@ -49,22 +49,11 @@ class SearchDialog(QDialog, Ui_Dialog):
self.hang_check = 0 self.hang_check = 0
# Update store caches silently. # Update store caches silently.
for p in self.store_plugins.values(): for p in self.gui.istores.values():
self.cache_pool.add_task(p, self.timeout) self.cache_pool.add_task(p, self.timeout)
# Add check boxes for each store so the user self.store_checks = {}
# can disable searching specific stores on a self.setup_store_checks()
# per search basis.
stores_check_widget = QWidget()
store_list_layout = QVBoxLayout()
stores_check_widget.setLayout(store_list_layout)
for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()):
cbox = QCheckBox(x)
cbox.setChecked(False)
store_list_layout.addWidget(cbox)
setattr(self, 'store_check_' + x, cbox)
store_list_layout.addStretch()
self.store_list.setWidget(stores_check_widget)
# Set the search query # Set the search query
self.search_edit.setText(query) self.search_edit.setText(query)
@ -92,6 +81,27 @@ class SearchDialog(QDialog, Ui_Dialog):
self.restore_state() self.restore_state()
def setup_store_checks(self):
# Add check boxes for each store so the user
# can disable searching specific stores on a
# per search basis.
existing = {}
for n in self.store_checks:
existing[n] = self.store_checks[n].isChecked()
self.store_checks = {}
stores_check_widget = QWidget()
store_list_layout = QVBoxLayout()
stores_check_widget.setLayout(store_list_layout)
for x in sorted(self.gui.istores.keys(), key=lambda x: x.lower()):
cbox = QCheckBox(x)
cbox.setChecked(existing.get(x, False))
store_list_layout.addWidget(cbox)
self.store_checks[x] = cbox
store_list_layout.addStretch()
self.store_list.setWidget(stores_check_widget)
def build_adv_search(self): def build_adv_search(self):
adv = AdvSearchBuilderDialog(self) adv = AdvSearchBuilderDialog(self)
if adv.exec_() == QDialog.Accepted: if adv.exec_() == QDialog.Accepted:
@ -126,11 +136,12 @@ class SearchDialog(QDialog, Ui_Dialog):
# futher filtering. # futher filtering.
self.results_view.model().set_query(query) self.results_view.model().set_query(query)
# Plugins are in alphebetic order. Randomize the # Plugins are in random order that does not change.
# order of plugin names. This way plugins closer # Randomize the ord of the plugin names every time
# there is a search. This way plugins closer
# to a don't have an unfair advantage over # to a don't have an unfair advantage over
# plugins further from a. # plugins further from a.
store_names = self.store_plugins.keys() store_names = self.store_checks.keys()
if not store_names: if not store_names:
return return
# Remove all of our internal filtering logic from the query. # Remove all of our internal filtering logic from the query.
@ -138,8 +149,8 @@ class SearchDialog(QDialog, Ui_Dialog):
shuffle(store_names) shuffle(store_names)
# Add plugins that the user has checked to the search pool's work queue. # Add plugins that the user has checked to the search pool's work queue.
for n in store_names: for n in store_names:
if getattr(self, 'store_check_' + n).isChecked(): if self.store_checks[n].isChecked():
self.search_pool.add_task(query, n, self.store_plugins[n], self.max_results, self.timeout) self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout)
self.hang_check = 0 self.hang_check = 0
self.checker.start(100) self.checker.start(100)
self.pi.startAnimation() self.pi.startAnimation()
@ -179,8 +190,8 @@ class SearchDialog(QDialog, Ui_Dialog):
self.config['open_external'] = self.open_external.isChecked() self.config['open_external'] = self.open_external.isChecked()
store_check = {} store_check = {}
for n in self.store_plugins: for k, v in self.store_checks.items():
store_check[n] = getattr(self, 'store_check_' + n).isChecked() store_check[k] = v.isChecked()
self.config['store_checked'] = store_check self.config['store_checked'] = store_check
def restore_state(self): def restore_state(self):
@ -206,8 +217,8 @@ class SearchDialog(QDialog, Ui_Dialog):
store_check = self.config.get('store_checked', None) store_check = self.config.get('store_checked', None)
if store_check: if store_check:
for n in store_check: for n in store_check:
if hasattr(self, 'store_check_' + n): if n in self.store_checks:
getattr(self, 'store_check_' + n).setChecked(store_check[n]) self.store_checks[n].setChecked(store_check[n])
self.results_view.model().sort_col = self.config.get('sort_col', 2) self.results_view.model().sort_col = self.config.get('sort_col', 2)
self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder) self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder)
@ -234,20 +245,27 @@ class SearchDialog(QDialog, Ui_Dialog):
self.config['open_external'] = self.open_external.isChecked() self.config['open_external'] = self.open_external.isChecked()
d = QDialog(self) d = QDialog(self)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box = QDialogButtonBox(QDialogButtonBox.Close)
v = QVBoxLayout(d) v = QVBoxLayout(d)
button_box.accepted.connect(d.accept) button_box.accepted.connect(d.accept)
button_box.rejected.connect(d.reject) button_box.rejected.connect(d.reject)
d.setWindowTitle(_('Customize get books search')) d.setWindowTitle(_('Customize get books search'))
config_widget = StoreConfigWidget(self.config)
v.addWidget(config_widget) tab_widget = QTabWidget(d)
v.addWidget(tab_widget)
v.addWidget(button_box) v.addWidget(button_box)
d.exec_() chooser_config_widget = StoreChooserWidget()
search_config_widget = StoreConfigWidget(self.config)
if d.result() == QDialog.Accepted: tab_widget.addTab(chooser_config_widget, _('Choose stores'))
config_widget.save_settings() tab_widget.addTab(search_config_widget, _('Configure search'))
d.exec_()
search_config_widget.save_settings()
self.config_changed() self.config_changed()
self.gui.load_store_plugins()
self.setup_store_checks()
def config_changed(self): def config_changed(self):
self.load_settings() self.load_settings()
@ -283,7 +301,7 @@ class SearchDialog(QDialog, Ui_Dialog):
def open_store(self, index): def open_store(self, index):
result = self.results_view.model().get_result(index) result = self.results_view.model().get_result(index)
self.store_plugins[result.store_name].open(self, result.detail_item, self.open_external.isChecked()) self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked())
def check_progress(self): def check_progress(self):
if not self.search_pool.threads_running() and not self.results_view.model().cover_pool.threads_running() and not self.results_view.model().details_pool.threads_running(): if not self.search_pool.threads_running() and not self.results_view.model().cover_pool.threads_running() and not self.results_view.model().details_pool.threads_running():
@ -292,27 +310,16 @@ class SearchDialog(QDialog, Ui_Dialog):
if not self.pi.isAnimated(): if not self.pi.isAnimated():
self.pi.startAnimation() self.pi.startAnimation()
def get_store_checks(self):
'''
Returns a list of QCheckBox's for each store.
'''
checks = []
for x in self.store_plugins:
check = getattr(self, 'store_check_' + x, None)
if check:
checks.append(check)
return checks
def stores_select_all(self): def stores_select_all(self):
for check in self.get_store_checks(): for check in self.store_checks.values():
check.setChecked(True) check.setChecked(True)
def stores_select_invert(self): def stores_select_invert(self):
for check in self.get_store_checks(): for check in self.store_checks.values():
check.setChecked(not check.isChecked()) check.setChecked(not check.isChecked())
def stores_select_none(self): def stores_select_none(self):
for check in self.get_store_checks(): for check in self.store_checks.values():
check.setChecked(False) check.setChecked(False)
def dialog_closed(self, result): def dialog_closed(self, result):

View File

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>'
__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 VirtualoStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://virtualo.pl/ebook/c2/'
detail_url = None
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_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://virtualo.pl/c2/?q=' + urllib.quote(query.encode('utf-8'))
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//div[@id="product_list"]/div/div[@class="column"]'):
if counter <= 0:
break
id = ''.join(data.xpath('.//table/tr[2]/td[1]/a/@href'))
if not id:
continue
price = ''.join(data.xpath('.//span[@class="price"]/text() | .//span[@class="price abbr"]/text()'))
cover_url = ''.join(data.xpath('.//table/tr[2]/td[1]/a/img/@src'))
title = ''.join(data.xpath('.//div[@class="title"]/a/text()'))
author = ', '.join(data.xpath('.//div[@class="authors"]/a/text()'))
formats = ', '.join(data.xpath('.//span[@class="format"]/a/text()'))
formats = re.sub(r'(, )?ONLINE(, )?', '', formats)
counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.title = title.strip() + ' ' + formats
s.author = author.strip()
s.price = price + ''
s.detail_item = 'http://virtualo.pl' + id.strip()
s.formats = formats.upper().strip()
s.drm = SearchResult.DRM_UNKNOWN
yield s

View File

@ -46,6 +46,64 @@ class TestEmail(QDialog, TE_Dialog):
finally: finally:
self.test_button.setEnabled(True) self.test_button.setEnabled(True)
class RelaySetup(QDialog):
def __init__(self, service, parent):
QDialog.__init__(self, parent)
self.l = l = QGridLayout()
self.setLayout(l)
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject)
self.tl = QLabel(('<p>'+_('Setup sending email using') +
' <b>{name}</b><p>' +
_('If you don\'t have an account, you can sign up for a free {name} email '
'account at <a href="http://{url}">http://{url}</a>. {extra}')).format(
**service))
l.addWidget(self.tl, 0, 0, 3, 0)
self.tl.setWordWrap(True)
self.tl.setOpenExternalLinks(True)
for name, label in (
['from_', _('Your %s &email address:')],
['username', _('Your %s &username:')],
['password', _('Your %s &password:')],
):
la = QLabel(label%service['name'])
le = QLineEdit(self)
setattr(self, name, le)
setattr(self, name+'_label', la)
r = l.rowCount()
l.addWidget(la, r, 0)
l.addWidget(le, r, 1)
la.setBuddy(le)
if name == 'password':
self.ptoggle = QCheckBox(_('&Show password'), self)
l.addWidget(self.ptoggle, r, 2)
self.ptoggle.stateChanged.connect(
lambda s: self.password.setEchoMode(self.password.Normal if s
== Qt.Checked else self.password.Password))
self.username.setText(service['username'])
self.password.setEchoMode(self.password.Password)
self.bl = QLabel('<p>' + _(
'If you plan to use email to send books to your Kindle, remember to'
' add the your %s email address to the allowed email addresses in your '
'Amazon.com Kindle management page.')%service['name'])
self.bl.setWordWrap(True)
l.addWidget(self.bl, l.rowCount(), 0, 3, 0)
l.addWidget(bb, l.rowCount(), 0, 3, 0)
self.setWindowTitle(_('Setup') + ' ' + service['name'])
self.resize(self.sizeHint())
self.service = service
def accept(self):
un = unicode(self.username.text())
if self.service.get('at_in_username', False) and '@' not in un:
return error_dialog(self, _('Incorrect username'),
_('%s needs the full email address as your username') %
self.service['name'], show=True)
QDialog.accept(self)
class SendEmail(QWidget, Ui_Form): class SendEmail(QWidget, Ui_Form):
@ -129,7 +187,8 @@ class SendEmail(QWidget, Ui_Form):
'port': 587, 'port': 587,
'username': '@gmail.com', 'username': '@gmail.com',
'url': 'www.gmail.com', 'url': 'www.gmail.com',
'extra': '' 'extra': '',
'at_in_username': True,
}, },
'hotmail': { 'hotmail': {
'name': 'Hotmail', 'name': 'Hotmail',
@ -143,53 +202,10 @@ class SendEmail(QWidget, Ui_Form):
' will let calibre send email. In this case, I' ' will let calibre send email. In this case, I'
' strongly suggest you setup a free gmail account' ' strongly suggest you setup a free gmail account'
' instead.'), ' instead.'),
'at_in_username': True,
} }
}[service] }[service]
d = QDialog(self) d = RelaySetup(service, self)
l = QGridLayout()
d.setLayout(l)
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
bb.accepted.connect(d.accept)
bb.rejected.connect(d.reject)
d.tl = QLabel(('<p>'+_('Setup sending email using') +
' <b>{name}</b><p>' +
_('If you don\'t have an account, you can sign up for a free {name} email '
'account at <a href="http://{url}">http://{url}</a>. {extra}')).format(
**service))
l.addWidget(d.tl, 0, 0, 3, 0)
d.tl.setWordWrap(True)
d.tl.setOpenExternalLinks(True)
for name, label in (
['from_', _('Your %s &email address:')],
['username', _('Your %s &username:')],
['password', _('Your %s &password:')],
):
la = QLabel(label%service['name'])
le = QLineEdit(d)
setattr(d, name, le)
setattr(d, name+'_label', la)
r = l.rowCount()
l.addWidget(la, r, 0)
l.addWidget(le, r, 1)
la.setBuddy(le)
if name == 'password':
d.ptoggle = QCheckBox(_('&Show password'), d)
l.addWidget(d.ptoggle, r, 2)
d.ptoggle.stateChanged.connect(
lambda s: d.password.setEchoMode(d.password.Normal if s
== Qt.Checked else d.password.Password))
d.username.setText(service['username'])
d.password.setEchoMode(d.password.Password)
d.bl = QLabel('<p>' + _(
'If you plan to use email to send books to your Kindle, remember to'
' add the your %s email address to the allowed email addresses in your '
'Amazon.com Kindle management page.')%service['name'])
d.bl.setWordWrap(True)
l.addWidget(d.bl, l.rowCount(), 0, 3, 0)
l.addWidget(bb, l.rowCount(), 0, 3, 0)
d.setWindowTitle(_('Setup') + ' ' + service['name'])
d.resize(d.sizeHint())
bb.setVisible(True)
if d.exec_() != d.Accepted: if d.exec_() != d.Accepted:
return return
self.relay_username.setText(d.username.text()) self.relay_username.setText(d.username.text())

View File

@ -587,7 +587,7 @@ You can download news and convert it into an ebook with the command::
/opt/calibre/ebook-convert "Title of news source.recipe" outputfile.epub /opt/calibre/ebook-convert "Title of news source.recipe" outputfile.epub
If you want to generate MOBI, use outputfile.mobi instead. If you want to generate MOBI, use outputfile.mobi instead and use ``--output-profile kindle``.
You can email downloaded news with the command:: You can email downloaded news with the command::