sync with Kovid's branch

This commit is contained in:
Tomasz Długosz 2012-11-19 21:23:50 +01:00
commit 1235459cd4
72 changed files with 1793 additions and 178 deletions

View File

@ -0,0 +1,69 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
import re
class aktualneRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'aktualne.cz'
publisher = u'Centrum holdings'
description = 'aktuálně.cz'
oldest_article = 1
max_articles_per_feed = 20
feeds = [
(u'Domácí', u'http://aktualne.centrum.cz/feeds/rss/domaci/?photo=0'),
(u'Zprávy', u'http://aktualne.centrum.cz/feeds/rss/zpravy/?photo=0'),
(u'Praha', u'http://aktualne.centrum.cz/feeds/rss/domaci/regiony/praha/?photo=0'),
(u'Ekonomika', u'http://aktualne.centrum.cz/feeds/rss/ekonomika/?photo=0'),
(u'Finance', u'http://aktualne.centrum.cz/feeds/rss/finance/?photo=0'),
(u'Blogy a názory', u'http://blog.aktualne.centrum.cz/export-all.php')
]
language = 'cs'
cover_url = 'http://img.aktualne.centrum.cz/design/akt4/o/l/logo-akt-ciste.png'
remove_javascript = True
no_stylesheets = True
remove_attributes = []
remove_tags_before = dict(name='h1', attrs={'class':['titulek-clanku']})
filter_regexps = [r'img.aktualne.centrum.cz']
remove_tags = [dict(name='div', attrs={'id':['social-bookmark']}),
dict(name='div', attrs={'class':['box1', 'svazane-tagy']}),
dict(name='div', attrs={'class':'itemcomment id0'}),
dict(name='div', attrs={'class':'hlavicka'}),
dict(name='div', attrs={'class':'hlavni-menu'}),
dict(name='div', attrs={'class':'top-standard-brand-obal'}),
dict(name='div', attrs={'class':'breadcrumb'}),
dict(name='div', attrs={'id':'start-standard'}),
dict(name='div', attrs={'id':'forum'}),
dict(name='span', attrs={'class':'akce'}),
dict(name='span', attrs={'class':'odrazka vetsi'}),
dict(name='div', attrs={'class':'boxP'}),
dict(name='div', attrs={'class':'box2'})]
preprocess_regexps = [
(re.compile(r'<div class="(contenttitle"|socialni-site|wiki|facebook-promo|facebook-like-button"|meta-akce).*', re.DOTALL|re.IGNORECASE), lambda match: '</body>'),
(re.compile(r'<div class="[^"]*poutak-clanek-trojka".*', re.DOTALL|re.IGNORECASE), lambda match: '</body>')]
keep_only_tags = []
visited_urls = {}
def get_article_url(self, article):
url = BasicNewsRecipe.get_article_url(self, article)
if url in self.visited_urls:
self.log.debug('Ignoring duplicate: ' + url)
return None
else:
self.visited_urls[url] = True
self.log.debug('Accepting: ' + url)
return url
def encoding(self, source):
if source.newurl.find('blog.aktualne') >= 0:
enc = 'utf-8'
else:
enc = 'iso-8859-2'
self.log.debug('Called encoding ' + enc + " " + str(source.newurl))
return source.decode(enc, 'replace')

48
recipes/antyweb.recipe Normal file
View File

@ -0,0 +1,48 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AntywebRecipe(BasicNewsRecipe):
encoding = 'utf-8'
__license__ = 'GPL v3'
__author__ = u'Artur Stachecki <artur.stachecki@gmail.com>'
language = 'pl'
version = 1
title = u'Antyweb'
category = u'News'
description = u'Blog o internecie i nowych technologiach'
cover_url=''
remove_empty_feeds= True
auto_cleanup = False
no_stylesheets=True
use_embedded_content = False
oldest_article = 1
max_articles_per_feed = 100
remove_javascript = True
simultaneous_downloads = 3
keep_only_tags =[]
keep_only_tags.append(dict(name = 'h1', attrs = { 'class' : 'mm-article-title'}))
keep_only_tags.append(dict(name = 'div', attrs = {'class' : 'mm-article-content'}))
remove_tags =[]
remove_tags.append(dict(name = 'h2', attrs = {'class' : 'widgettitle'}))
remove_tags.append(dict(name = 'img', attrs = {'class' : 'alignleft'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'float: right;margin-left:1em;margin-bottom: 0.5em;padding-bottom: 3px; width: 72px;'}))
remove_tags.append(dict(name = 'img', attrs = {'src' : 'http://antyweb.pl/wp-content/uploads/2011/09/HOSTERSI_testy_pasek600x30.gif'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'podwpisowe'}))
extra_css = '''
body {font-family: verdana, arial, helvetica, geneva, sans-serif ;}
'''
feeds = [
(u'Artykuly', u'feed://feeds.feedburner.com/Antyweb?format=xml'),
]
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

50
recipes/bankier_pl.recipe Normal file
View File

@ -0,0 +1,50 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__author__ = 'teepel <teepel44@gmail.com>'
'''
bankier.pl
'''
from calibre.web.feeds.news import BasicNewsRecipe
class bankier(BasicNewsRecipe):
title = u'Bankier.pl'
__author__ = 'teepel <teepel44@gmail.com>'
language = 'pl'
description ='Polski portal finansowy. Informacje o: gospodarka, inwestowanie, finanse osobiste, prowadzenie firmy, kursy walut, notowania akcji, fundusze.'
masthead_url='http://www.bankier.pl/gfx/hd-mid-02.gif'
INDEX='http://bankier.pl/'
remove_empty_feeds= True
oldest_article = 1
max_articles_per_feed = 100
remove_javascript=True
no_stylesheets=True
simultaneous_downloads = 5
keep_only_tags =[]
keep_only_tags.append(dict(name = 'div', attrs = {'align' : 'left'}))
remove_tags =[]
remove_tags.append(dict(name = 'table', attrs = {'cellspacing' : '2'}))
remove_tags.append(dict(name = 'div', attrs = {'align' : 'center'}))
remove_tags.append(dict(name = 'img', attrs = {'src' : '/gfx/hd-mid-02.gif'}))
#remove_tags.append(dict(name = 'a', attrs = {'target' : '_blank'}))
#remove_tags.append(dict(name = 'br', attrs = {'clear' : 'all'}))
feeds = [
(u'Wiadomości dnia', u'http://feeds.feedburner.com/bankier-wiadomosci-dnia'),
(u'Finanse osobiste', u'http://feeds.feedburner.com/bankier-finanse-osobiste'),
(u'Firma', u'http://feeds.feedburner.com/bankier-firma'),
(u'Giełda', u'http://feeds.feedburner.com/bankier-gielda'),
(u'Rynek walutowy', u'http://feeds.feedburner.com/bankier-rynek-walutowy'),
(u'Komunikaty ze spółek', u'http://feeds.feedburner.com/bankier-espi'),
]
def print_version(self, url):
segment = url.split('.')
urlPart = segment[2]
segments = urlPart.split('-')
urlPart2 = segments[-1]
return 'http://www.bankier.pl/wiadomosci/print.html?article_id=' + urlPart2

55
recipes/blesk.recipe Normal file
View File

@ -0,0 +1,55 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
import re
class bleskRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Blesk'
publisher = u''
description = 'blesk.cz'
oldest_article = 1
max_articles_per_feed = 20
use_embedded_content = False
feeds = [
(u'Zprávy', u'http://www.blesk.cz/rss/7'),
(u'Blesk', u'http://www.blesk.cz/rss/1'),
(u'Sex a tabu', u'http://www.blesk.cz/rss/2'),
(u'Celebrity', u'http://www.blesk.cz/rss/5'),
(u'Cestování', u'http://www.blesk.cz/rss/12')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://img.blesk.cz/images/blesk/blesk-logo.png'
remove_javascript = True
no_stylesheets = True
extra_css = """
"""
remove_attributes = []
remove_tags_before = dict(name='div', attrs={'id':['boxContent']})
remove_tags_after = dict(name='div', attrs={'class':['artAuthors']})
remove_tags = [dict(name='div', attrs={'class':['link_clanek']}),
dict(name='div', attrs={'id':['partHeader']}),
dict(name='div', attrs={'id':['top_bottom_box', 'lista_top']})]
preprocess_regexps = [(re.compile(r'<div class="(textovytip|related)".*', re.DOTALL|re.IGNORECASE), lambda match: '</body>')]
keep_only_tags = [dict(name='div', attrs={'class':'articleContent'})]
visited_urls = {}
def get_article_url(self, article):
url = BasicNewsRecipe.get_article_url(self, article)
if url in self.visited_urls:
self.log.debug('Ignoring duplicate: ' + url)
return None
else:
self.visited_urls[url] = True
self.log.debug('Accepting: ' + url)
return url

45
recipes/buchreport.recipe Normal file
View File

@ -0,0 +1,45 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
'''Calibre recipe to convert the RSS feeds of the Buchreport to an ebook.'''
class Buchreport(BasicNewsRecipe) :
__author__ = 'a.peter'
__copyright__ = 'a.peter'
__license__ = 'GPL v3'
description = 'Buchreport'
version = 4
title = u'Buchreport'
timefmt = ' [%d.%m.%Y]'
encoding = 'cp1252'
language = 'de'
extra_css = 'body { margin-left: 0.00em; margin-right: 0.00em; } \
article, articledate, articledescription { text-align: left; } \
h1 { text-align: left; font-size: 140%; font-weight: bold; } \
h2 { text-align: left; font-size: 100%; font-weight: bold; font-style: italic; } \
h3 { text-align: left; font-size: 100%; font-weight: regular; font-style: italic; } \
h4, h5, h6 { text-align: left; font-size: 100%; font-weight: bold; }'
oldest_article = 7.0
no_stylesheets = True
remove_javascript = True
use_embedded_content = False
publication_type = 'newspaper'
remove_tags_before = dict(name='h2')
remove_tags_after = [
dict(name='div', attrs={'style':["padding-top:10px;clear:both"]})
]
remove_tags = [
dict(name='div', attrs={'style':["padding-top:10px;clear:both"]}),
dict(name='iframe'),
dict(name='img')
]
feeds = [
(u'Buchreport', u'http://www.buchreport.de/index.php?id=5&type=100')
]
def get_masthead_url(self):
return 'http://www.buchreport.de/fileadmin/template/img/buchreport_logo.jpg'

View File

@ -1,5 +1,5 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009-2012, Darko Miletic <darko.miletic at gmail.com>'
''' '''
www.business-standard.com www.business-standard.com
''' '''
@ -14,10 +14,12 @@ class BusinessStandard(BasicNewsRecipe):
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
auto_cleanup = False
encoding = 'cp1252' encoding = 'cp1252'
publisher = 'Business Standard Limited' publisher = 'Business Standard Limited'
category = 'news, business, money, india, world' category = 'news, business, money, india, world'
language = 'en_IN' language = 'en_IN'
masthead_url = 'http://feeds.business-standard.com/images/logo_08.jpg'
conversion_options = { conversion_options = {
'comments' : description 'comments' : description
@ -26,7 +28,7 @@ class BusinessStandard(BasicNewsRecipe):
,'publisher' : publisher ,'publisher' : publisher
,'linearize_tables': True ,'linearize_tables': True
} }
keep_only_tags=[dict(attrs={'class':'TableClas'})] #keep_only_tags=[dict(name='td', attrs={'class':'TableClas'})]
remove_tags = [ remove_tags = [
dict(name=['object','link','script','iframe','base','meta']) dict(name=['object','link','script','iframe','base','meta'])
,dict(attrs={'class':'rightDiv2'}) ,dict(attrs={'class':'rightDiv2'})
@ -45,3 +47,8 @@ class BusinessStandard(BasicNewsRecipe):
,(u'Management & Mktg' , u'http://feeds.business-standard.com/rss/7_0.xml' ) ,(u'Management & Mktg' , u'http://feeds.business-standard.com/rss/7_0.xml' )
,(u'Opinion' , u'http://feeds.business-standard.com/rss/5_0.xml' ) ,(u'Opinion' , u'http://feeds.business-standard.com/rss/5_0.xml' )
] ]
def print_version(self, url):
l, s, tp = url.rpartition('/')
t, k, autono = l.rpartition('/')
return 'http://www.business-standard.com/india/printpage.php?autono=' + autono + '&tp=' + tp

View File

@ -0,0 +1,68 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class ceskaPoziceRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Česká pozice'
description = 'Česká pozice'
oldest_article = 2
max_articles_per_feed = 20
feeds = [
(u'Všechny články', u'http://www.ceskapozice.cz/rss.xml'),
(u'Domov', u'http://www.ceskapozice.cz/taxonomy/term/16/feed'),
(u'Chrono', u'http://www.ceskapozice.cz/chrono/feed'),
(u'Evropa', u'http://www.ceskapozice.cz/taxonomy/term/17/feed')
]
language = 'cs'
cover_url = 'http://www.ceskapozice.cz/sites/default/files/cpozice_logo.png'
remove_javascript = True
no_stylesheets = True
domain = u'http://www.ceskapozice.cz'
use_embedded_content = False
remove_tags = [dict(name='div', attrs={'class':['block-ad', 'region region-content-ad']}),
dict(name='ul', attrs={'class':'links'}),
dict(name='div', attrs={'id':['comments', 'back-to-top']}),
dict(name='div', attrs={'class':['next-page', 'region region-content-ad']}),
dict(name='cite')]
keep_only_tags = [dict(name='div', attrs={'id':'content'})]
visited_urls = {}
def get_article_url(self, article):
url = BasicNewsRecipe.get_article_url(self, article)
if url in self.visited_urls:
self.log.debug('Ignoring duplicate: ' + url)
return None
else:
self.visited_urls[url] = True
self.log.debug('Accepting: ' + url)
return url
def preprocess_html(self, soup):
self.append_page(soup, soup.body, 3)
return soup
def append_page(self, soup, appendtag, position):
pager = soup.find('div', attrs={'class':'paging-bottom'})
if pager:
nextbutton = pager.find('li', attrs={'class':'pager-next'})
if nextbutton:
nexturl = self.domain + nextbutton.a['href']
soup2 = self.index_to_soup(nexturl)
texttag = soup2.find('div', attrs={'class':'main-body'})
for it in texttag.findAll('div', attrs={'class':'region region-content-ad'}):
it.extract()
for it in texttag.findAll('cite'):
it.extract()
newpos = len(texttag.contents)
self.append_page(soup2, texttag, newpos)
texttag.extract()
appendtag.insert(position, texttag)
pager.extract()

View File

@ -0,0 +1,30 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class ceskenovinyRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'České Noviny'
description = 'ceskenoviny.cz'
oldest_article = 1
max_articles_per_feed = 20
feeds = [
(u'Domácí', u'http://www.ceskenoviny.cz/sluzby/rss/domov.php')
#,(u'Hlavní události', u'http://www.ceskenoviny.cz/sluzby/rss/index.php')
#,(u'Přehled zpráv', u'http://www.ceskenoviny.cz/sluzby/rss/zpravy.php')
#,(u'Ze světa', u'http://www.ceskenoviny.cz/sluzby/rss/svet.php')
#,(u'Kultura', u'http://www.ceskenoviny.cz/sluzby/rss/kultura.php')
#,(u'IT', u'http://www.ceskenoviny.cz/sluzby/rss/pocitace.php')
]
language = 'cs'
cover_url = 'http://i4.cn.cz/grafika/cn_logo-print.gif'
remove_javascript = True
no_stylesheets = True
remove_attributes = []
filter_regexps = [r'img.aktualne.centrum.cz']
keep_only_tags = [dict(name='div', attrs={'id':'clnk'})]

View File

@ -0,0 +1,26 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class cro6Recipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Český rozhlas 6'
description = 'Český rozhlas 6'
oldest_article = 1
max_articles_per_feed = 20
feeds = [
(u'Český rozhlas 6', u'http://www.rozhlas.cz/export/cro6/')
]
language = 'cs'
cover_url = 'http://www.rozhlas.cz/img/e5/logo/cro6.png'
remove_javascript = True
no_stylesheets = True
remove_attributes = []
remove_tags = [dict(name='div', attrs={'class':['audio-play-all', 'poradHeaders', 'actions']}),
dict(name='p', attrs={'class':['para-last']})]
keep_only_tags = [dict(name='div', attrs={'id':'article'})]

39
recipes/demagog.cz.recipe Normal file
View File

@ -0,0 +1,39 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
import re
class demagogRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Demagog.cz'
publisher = u''
description = 'demagog.cz'
oldest_article = 6
max_articles_per_feed = 20
use_embedded_content = False
remove_empty_feeds = True
feeds = [
(u'Aktuality', u'http://demagog.cz/rss')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://demagog.cz/content/images/demagog.cz.png'
remove_javascript = True
no_stylesheets = True
extra_css = """
.vyrok_suhrn{margin-top:50px; }
.vyrok{margin-bottom:30px; }
"""
remove_tags = [dict(name='a', attrs={'class':'vyrok_odovodnenie_tgl'}),
dict(name='img', attrs={'class':'vyrok_fotografia'})]
remove_tags_before = dict(name='h1')
remove_tags_after = dict(name='div', attrs={'class':'vyrok_text_after'})
preprocess_regexps = [(re.compile(r'(<div class="vyrok_suhrn">)', re.DOTALL|re.IGNORECASE), lambda match: '\1<hr>')]

36
recipes/denik.cz.recipe Normal file
View File

@ -0,0 +1,36 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class ceskyDenikRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'denik.cz'
publisher = u''
description = u'Český deník'
oldest_article = 1
max_articles_per_feed = 20
use_embedded_content = False
remove_empty_feeds = True
feeds = [
(u'Z domova', u'http://www.denik.cz/rss/z_domova.html')
,(u'Pražský deník - Moje Praha', u'http://prazsky.denik.cz/rss/zpravy_region.html')
#,(u'Zahraničí', u'http://www.denik.cz/rss/ze_sveta.html')
#,(u'Kultura', u'http://www.denik.cz/rss/kultura.html')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://g.denik.cz/images/loga/denik.png'
remove_javascript = True
no_stylesheets = True
extra_css = """
"""
remove_tags = []
keep_only_tags = [dict(name='div', attrs={'class':'content'})]
#remove_tags_before = dict(name='h1')
remove_tags_after = dict(name='p', attrs={'class':'clanek-autor'})

View File

@ -0,0 +1,28 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class denikReferendumRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Den\u00edk Referendum'
publisher = u''
description = ''
oldest_article = 1
max_articles_per_feed = 20
feeds = [
(u'Deník Referendum', u'http://feeds.feedburner.com/DenikReferendum')
]
#encoding = 'iso-8859-2'
language = 'cs'
remove_javascript = True
no_stylesheets = True
use_embedded_content = False
remove_attributes = []
remove_tags_after = dict(name='div', attrs={'class':['text']})
remove_tags = [dict(name='div', attrs={'class':['box boxLine', 'box noprint', 'box']}),
dict(name='h3', attrs={'class':'head alt'})]
keep_only_tags = [dict(name='div', attrs={'id':['content']})]

View File

@ -7,6 +7,7 @@ class AdvancedUserRecipe1332847053(BasicNewsRecipe):
title = u'Editoriali' title = u'Editoriali'
__author__ = 'faber1971' __author__ = 'faber1971'
description = 'Leading articles on Italy by the best Italian editorials' description = 'Leading articles on Italy by the best Italian editorials'
language = 'it'
oldest_article = 1 oldest_article = 1
max_articles_per_feed = 100 max_articles_per_feed = 100

35
recipes/f1_ultra.recipe Normal file
View File

@ -0,0 +1,35 @@
from calibre.web.feeds.news import BasicNewsRecipe
import re
class f1ultra(BasicNewsRecipe):
title = u'Formuła 1 - F1 ultra'
__license__ = 'GPL v3'
__author__ = 'MrStefan <mrstefaan@gmail.com>, Artur Stachecki <artur.stachecki@gmail.com>'
language = 'pl'
description =u'Formuła 1, Robert Kubica, F3, GP2 oraz inne serie wyścigowe.'
masthead_url='http://www.f1ultra.pl/templates/f1ultra/images/logo.gif'
remove_empty_feeds= True
oldest_article = 1
max_articles_per_feed = 100
remove_javascript=True
no_stylesheets=True
keep_only_tags =[(dict(name = 'div', attrs = {'id' : 'main'}))]
remove_tags_after =[dict(attrs = {'style' : 'margin-top:5px;margin-bottom:5px;display: inline;'})]
remove_tags =[(dict(attrs = {'class' : ['buttonheading', 'avPlayerContainer', 'createdate']}))]
remove_tags.append(dict(attrs = {'title' : ['PDF', 'Drukuj', 'Email']}))
remove_tags.append(dict(name = 'form', attrs = {'method' : 'post'}))
remove_tags.append(dict(name = 'hr', attrs = {'size' : '2'}))
preprocess_regexps = [(re.compile(r'align="left"'), lambda match: ''),
(re.compile(r'align="right"'), lambda match: ''),
(re.compile(r'width=\"*\"'), lambda match: ''),
(re.compile(r'\<table .*?\>'), lambda match: '')]
extra_css = '''.contentheading { font-size: 1.4em; font-weight: bold; }
img { display: block; clear: both;}
'''
remove_attributes = ['width','height','position','float','padding-left','padding-right','padding','text-align']
feeds = [(u'F1 Ultra', u'http://www.f1ultra.pl/index.php?option=com_rd_rss&id=1&Itemid=245')]

View File

@ -8,6 +8,7 @@ from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1349086293(BasicNewsRecipe): class AdvancedUserRecipe1349086293(BasicNewsRecipe):
title = u'Foreign Policy' title = u'Foreign Policy'
language = 'en'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'International News' description = 'International News'
publisher = 'Washingtonpost.Newsweek Interactive, LLC' publisher = 'Washingtonpost.Newsweek Interactive, LLC'

View File

@ -8,7 +8,6 @@ krakow.gazeta.pl
''' '''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
import re
class gw_krakow(BasicNewsRecipe): class gw_krakow(BasicNewsRecipe):
title = u'Gazeta.pl Kraków' title = u'Gazeta.pl Kraków'
@ -46,7 +45,7 @@ class gw_krakow(BasicNewsRecipe):
remove_tags.append(dict(name = 'div', attrs = {'id' : 'gazeta_article_buttons'})) remove_tags.append(dict(name = 'div', attrs = {'id' : 'gazeta_article_buttons'}))
remove_tags_after = [dict(name = 'div', attrs = {'id' : 'gazeta_article_share'})] remove_tags_after = [dict(name = 'div', attrs = {'id' : 'gazeta_article_share'})]
feeds = [(u'Wiadomości', u'http://rss.gazeta.pl/pub/rss/krakow.xml')] feeds = [(u'Wiadomości', u'http://rss.gazeta.pl/pub/rss/krakow.xml')]
def skip_ad_pages(self, soup): def skip_ad_pages(self, soup):

View File

@ -8,7 +8,6 @@ warszawa.gazeta.pl
''' '''
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
import re
class gw_wawa(BasicNewsRecipe): class gw_wawa(BasicNewsRecipe):
title = u'Gazeta.pl Warszawa' title = u'Gazeta.pl Warszawa'
@ -43,7 +42,7 @@ class gw_wawa(BasicNewsRecipe):
remove_tags.append(dict(name = 'div', attrs = {'class' : 'gazeta_article_related_new'})) remove_tags.append(dict(name = 'div', attrs = {'class' : 'gazeta_article_related_new'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'gazetaVideoPlayer'})) remove_tags.append(dict(name = 'div', attrs = {'class' : 'gazetaVideoPlayer'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'gazeta_article_miniatures'})) remove_tags.append(dict(name = 'div', attrs = {'id' : 'gazeta_article_miniatures'}))
feeds = [(u'Wiadomości', u'http://rss.gazeta.pl/pub/rss/warszawa.xml')] feeds = [(u'Wiadomości', u'http://rss.gazeta.pl/pub/rss/warszawa.xml')]
def skip_ad_pages(self, soup): def skip_ad_pages(self, soup):

BIN
recipes/icons/antyweb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 668 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

After

Width:  |  Height:  |  Size: 786 B

BIN
recipes/icons/f1_ultra.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

36
recipes/ihned.cz.recipe Normal file
View File

@ -0,0 +1,36 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class ihnedRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'iHNed.cz'
publisher = u''
description = 'ihned.cz'
oldest_article = 1
max_articles_per_feed = 20
use_embedded_content = False
feeds = [
(u'Zprávy', u'http://zpravy.ihned.cz/?m=rss'),
(u'Hospodářské noviny', u'http://hn.ihned.cz/?p=500000_rss'),
(u'Byznys', u'http://byznys.ihned.cz/?m=rss'),
(u'Life', u'http://life.ihned.cz/?m=rss'),
(u'Dialog', u'http://dialog.ihned.cz/?m=rss')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://rss.ihned.cz/img/0/0_hp09/ihned.cz.gif'
remove_javascript = True
no_stylesheets = True
extra_css = """
"""
remove_attributes = []
remove_tags_before = dict(name='div', attrs={'id':['heading']})
remove_tags_after = dict(name='div', attrs={'id':['next-authors']})
remove_tags = [dict(name='ul', attrs={'id':['comm']}),
dict(name='div', attrs={'id':['r-big']}),
dict(name='div', attrs={'class':['tools tools-top']})]

59
recipes/insider.recipe Normal file
View File

@ -0,0 +1,59 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
import re
from calibre.web.feeds.news import BasicNewsRecipe
class insider(BasicNewsRecipe):
__author__ = 'bubak'
title = 'Insider'
language = 'cs'
remove_tags = [dict(name='div', attrs={'class':'article-related-content'})
,dict(name='div', attrs={'class':'calendar'})
,dict(name='span', attrs={'id':'labelHolder'})
]
no_stylesheets = True
keep_only_tags = [dict(name='div', attrs={'class':['doubleBlock textContentFormat']})]
preprocess_regexps = [(re.compile(r'T.mata:.*', re.DOTALL|re.IGNORECASE), lambda m: '</body>')]
needs_subscription = True
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open('http://www.denikinsider.cz/')
br.select_form(nr=0)
br['login-name'] = self.username
br['login-password'] = self.password
res = br.submit()
raw = res.read()
if u'Odhlásit se' not in raw:
raise ValueError('Failed to login to insider.cz'
'Check your username and password.')
return br
def parse_index(self):
articles = []
soup = self.index_to_soup('http://www.denikinsider.cz')
titles = soup.findAll('span', attrs={'class':'homepageArticleTitle'})
if titles is None:
raise ValueError('Could not find category content')
articles = []
seen_titles = set([])
for title in titles:
if title.string in seen_titles:
continue
article = title.parent
seen_titles.add(title.string)
url = article['href']
if url.startswith('/'):
url = 'http://www.denikinsider.cz/'+url
self.log('\tFound article:', title, 'at', url)
articles.append({'title':title.string, 'url':url, 'description':'',
'date':''})
return [(self.title, articles)]

View File

@ -0,0 +1,32 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class kudyznudyRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Kudy z nudy'
publisher = u''
description = 'kudyznudy.cz'
oldest_article = 3
max_articles_per_feed = 20
use_embedded_content = False
feeds = [
(u'Praha nejnovější', u'http://www.kudyznudy.cz/RSS/Charts.aspx?Type=Newest&Lang=cs-CZ&RegionId=1')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://www.kudyznudy.cz/App_Themes/KzN/Images/Containers/Header/HeaderLogoKZN.png'
remove_javascript = True
no_stylesheets = True
extra_css = """
"""
remove_attributes = []
remove_tags_before = dict(name='div', attrs={'class':['C_WholeContentPadding']})
remove_tags_after = dict(name='div', attrs={'class':['SurroundingsContainer']})
remove_tags = [dict(name='div', attrs={'class':['Details', 'buttons', 'SurroundingsContainer', 'breadcrumb']})]
keep_only_tags = []

40
recipes/lidovky.recipe Normal file
View File

@ -0,0 +1,40 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
import re
class lnRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'lidovky'
publisher = u''
description = 'lidovky.cz'
oldest_article = 1
max_articles_per_feed = 20
feeds = [
(u'Události', u'http://www.lidovky.cz/export/rss.asp?r=ln_domov'),
(u'Svět', u'http://www.lidovky.cz/export/rss.asp?r=ln_zahranici'),
(u'Byznys', u'http://www.lidovky.cz/export/rss.asp?c=ln_byznys'),
(u'Věda', u'http://www.lidovky.cz/export/rss.asp?r=ln_veda'),
(u'Názory', u'http://www.lidovky.cz/export/rss.asp?r=ln_nazory'),
(u'Relax', u'http://www.lidovky.cz/export/rss.asp?c=ln_relax')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://g.lidovky.cz/o/lidovky_ln3b/lidovky-logo.png'
remove_javascript = True
no_stylesheets = True
use_embedded_content = False
remove_attributes = []
remove_tags_before = dict(name='div', attrs={'id':['content']})
remove_tags_after = dict(name='div', attrs={'class':['authors']})
preprocess_regexps = [(re.compile(r'<div id="(fb-root)".*', re.DOTALL|re.IGNORECASE), lambda match: '</body>')]
keep_only_tags = []

View File

@ -0,0 +1,29 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class metropolRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Metropol TV'
publisher = u''
description = 'metropol.cz'
oldest_article = 1
max_articles_per_feed = 20
use_embedded_content = False
feeds = [
(u'Metropolcv.cz', u'http://www.metropol.cz/rss/')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://www.metropol.cz/public/css/../images/logo/metropoltv.png'
remove_javascript = True
no_stylesheets = True
extra_css = """
"""
remove_attributes = []
keep_only_tags = [dict(name='div', attrs={'id':['art-full']})]

49
recipes/myapple_pl.recipe Normal file
View File

@ -0,0 +1,49 @@
from calibre.web.feeds.news import BasicNewsRecipe
class MyAppleRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
__author__ = u'Artur Stachecki <artur.stachecki@gmail.com>'
language = 'pl'
version = 1
title = u'MyApple.pl'
category = u'News'
description = u' Największy w Polsce serwis zajmujący się tematyką związaną z Apple i wszelkimi produktami tej firmy.'
cover_url=''
remove_empty_feeds= True
no_stylesheets=True
oldest_article = 7
max_articles_per_feed = 100000
recursions = 0
no_stylesheets = True
remove_javascript = True
simultaneous_downloads = 3
keep_only_tags =[]
keep_only_tags.append(dict(name = 'div', attrs = {'id' : 'article_content'}))
remove_tags =[]
remove_tags.append(dict(name = 'div', attrs = {'class' : 'article_author_date_comment_container'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'fullwidth'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'cmslinks'}))
remove_tags.append(dict(name = 'div', attrs = {'class' : 'googleads-468'}))
remove_tags.append(dict(name = 'div', attrs = {'id' : 'comments'}))
extra_css = '''
body {font-family: verdana, arial, helvetica, geneva, sans-serif ;}
td.contentheading{font-size: large; font-weight: bold;}
'''
feeds = [
('News', 'feed://myapple.pl/external.php?do=rss&type=newcontent&sectionid=1&days=120&count=10'),
]
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

View File

@ -0,0 +1,30 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class nfpkRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Nadační fond proti korupci'
publisher = u''
description = 'nfpk.cz'
oldest_article = 7
max_articles_per_feed = 20
use_embedded_content = False
remove_empty_feeds = True
feeds = [
(u'Aktuality', u'http://feeds.feedburner.com/nfpk')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://www.nfpk.cz/_templates/nfpk/_images/logo.gif'
remove_javascript = True
no_stylesheets = True
extra_css = """
"""
remove_attributes = []
keep_only_tags = [dict(name='div', attrs={'id':'content'})]

View File

@ -0,0 +1,56 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
'''
Fetch Népszabadság
'''
from calibre.web.feeds.news import BasicNewsRecipe
class nepszabadsag(BasicNewsRecipe):
title = u'N\u00e9pszabads\u00e1g'
description = ''
__author__ = 'bubak'
use_embedded_content = False
timefmt = ' [%d %b %Y]'
oldest_article = 2
max_articles_per_feed = 20
no_stylesheets = True
language = 'hu'
#delay = 1
#timeout = 10
simultaneous_downloads = 5
#encoding = 'utf-8'
remove_javascript = True
cover_url = 'http://nol.hu/_design/image/logo_nol_live.jpg'
feeds = [
(u'Belföld', u'http://nol.hu/feed/belfold.rss')
#,(u'Külföld', u'http://nol.hu/feed/kulfold.rss')
#,(u'Gazdaság', u'http://nol.hu/feed/gazdasag.rss')
#,(u'Kultúra', u'http://nol.hu/feed/kult.rss')
]
extra_css = '''
'''
remove_attributes = []
remove_tags_before = dict(name='div', attrs={'class':['d-source']})
remove_tags_after = dict(name='div', attrs={'class':['tags']})
remove_tags = [dict(name='div', attrs={'class':['h']}),
dict(name='tfoot')]
keep_only_tags = [dict(name='table', attrs={'class':'article-box'})]
# NS sends an ad page sometimes but not frequently enough, TBD
def AAskip_ad_pages(self, soup):
if ('advertisement' in soup.find('title').string.lower()):
href = soup.find('a').get('href')
self.log.debug('Skipping to: ' + href)
new = self.browser.open(href).read().decode('utf-8', 'ignore')
#ipython(locals())
self.log.debug('Finished: ' + href)
return new
else:
return None

View File

@ -0,0 +1,32 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class pesRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Neviditelný pes'
publisher = u''
description = u'Neviditelný pes'
oldest_article = 1
max_articles_per_feed = 20
use_embedded_content = False
remove_empty_feeds = True
feeds = [
(u'Neviditelný pes', u'http://neviditelnypes.lidovky.cz/export/rss.asp?c=pes_neviditelny')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://g.zpravy.cz/o/pes/logo_pes.jpg'
remove_javascript = True
no_stylesheets = True
extra_css = """
"""
remove_tags = []
remove_tags_before = dict(name='div', attrs={'id':'art-full'})
remove_tags_after = dict(name='div', attrs={'id':'authors'})

50
recipes/novinky.cz.recipe Normal file
View File

@ -0,0 +1,50 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class novinkyRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'novinky.cz'
publisher = u'seznam.cz'
description = 'novinky.cz'
oldest_article = 1
max_articles_per_feed = 20
feeds = [
(u'Domácí', u'http://www.novinky.cz/rss2/domaci/'),
(u'Praha', u'http://www.novinky.cz/rss2/vase-zpravy/praha/'),
(u'Ekonomika', u'http://www.novinky.cz/rss2/ekonomika/'),
(u'Finance', u'http://www.novinky.cz/rss2/finance/'),
]
#encoding = 'utf-8'
language = 'cs'
cover_url = 'http://www.novinky.cz/static/images/logo.gif'
remove_javascript = True
no_stylesheets = True
remove_tags = [dict(name='div', attrs={'id':['pictureInnerBox']}),
dict(name='div', attrs={'id':['discussionEntry']}),
dict(name='span', attrs={'id':['mynews-hits', 'mynews-author']}),
dict(name='div', attrs={'class':['related']}),
dict(name='div', attrs={'id':['multimediaInfo']})]
remove_tags_before = dict(name='div',attrs={'class':['articleHeader']})
remove_tags_after = dict(name='div',attrs={'class':'related'})
keep_only_tags = []
# This source has identical articles under different links
# which are redirected to the common url. I've found
# just this API method that has the real URL
visited_urls = {}
def encoding(self, source):
url = source.newurl
if url in self.visited_urls:
self.log.debug('Ignoring duplicate: ' + url)
return None
else:
self.visited_urls[url] = True
self.log.debug('Accepting: ' + url)
return source.decode('utf-8', 'replace')

View File

@ -0,0 +1,38 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
import re
class plRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Parlamentn\u00ed Listy'
publisher = u''
description = ''
oldest_article = 1
max_articles_per_feed = 20
feeds = [
(u'Parlamentní listy.cz', u'http://www.parlamentnilisty.cz/export/rss.aspx')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://www.parlamentnilisty.cz/design/listy-logo2.png'
remove_javascript = True
no_stylesheets = True
use_embedded_content = False
remove_attributes = []
remove_tags = [dict(name='div', attrs={'class':['articledetailboxin','crumbs', 'relatedarticles articledetailbox']}),
dict(name='div', attrs={'class':['socialshare-1 noprint', 'socialshare-2 noprint']}),
dict(name='div', attrs={'id':'widget'}),
dict(name='div', attrs={'class':'article-discussion-box noprint'})]
preprocess_regexps = [(re.compile(r'<(span|strong)[^>]*>\s*Ptejte se politik.*', re.DOTALL|re.IGNORECASE), lambda match: '</body>')]
keep_only_tags = [dict(name='div', attrs={'class':['article-detail']})]

View File

@ -0,0 +1,40 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class cpsRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Piratská strana'
publisher = u''
description = ''
oldest_article = 3
max_articles_per_feed = 20
use_embedded_content = False
remove_empty_feeds = True
feeds = [
(u'Články', u'http://www.pirati.cz/rss.xml')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://www.pirati.cz/sites/all/themes/addari-cps/images/headbg.jpg'
remove_javascript = True
no_stylesheets = True
extra_css = """
"""
remove_attributes = []
keep_only_tags = [dict(name='div', attrs={'id':'postarea'})]
remove_tags = [dict(name='div', attrs={'class':['breadcrumb', 'submitted', 'links-readmore']}),
dict(name='div', attrs={'id':['comments']})]
remove_tags_before = dict(name='font', attrs={'size':'+3'})
remove_tags_after = [dict(name='iframe')]
conversion_options = {'linearize_tables' : True}

View File

@ -0,0 +1,34 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class nfpkRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Piratské noviny'
publisher = u''
description = 'nfpk.cz'
oldest_article = 2
max_articles_per_feed = 20
use_embedded_content = False
remove_empty_feeds = True
feeds = [
(u'Aktuality', u'http://www.piratskenoviny.cz/run/rss.php')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://www.piratskenoviny.cz/imgs/piratske-noviny.gif'
remove_javascript = True
no_stylesheets = True
extra_css = """
"""
remove_attributes = []
remove_tags_before = dict(name='font', attrs={'size':'+3'})
remove_tags_after = [dict(name='iframe')]
conversion_options = {'linearize_tables' : True}

View File

@ -4,7 +4,7 @@ class AdvancedUserRecipe1348063712(BasicNewsRecipe):
title = u'Portfolio.hu - English Edition' title = u'Portfolio.hu - English Edition'
__author__ = 'laca' __author__ = 'laca'
oldest_article = 7 oldest_article = 7
language = 'en_HUN' language = 'en_HU'
masthead_url = 'http://www.portfolio.hu/img/sit/angolfejlec2010.jpg' masthead_url = 'http://www.portfolio.hu/img/sit/angolfejlec2010.jpg'
use_embedded_content = False use_embedded_content = False
auto_cleanup = True auto_cleanup = True

64
recipes/pravo.recipe Normal file
View File

@ -0,0 +1,64 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.news import BasicNewsRecipe
class pravo(BasicNewsRecipe):
__author__ = 'bubak'
title = 'Právo'
language = 'cs'
remove_tags_before = dict(name='div', attrs={'class':'rubrika-ostat'})
remove_tags_after = dict(name='td', attrs={'class':'rubrika'})
remove_tags = [dict(name='td', attrs={'width':'273'})
,dict(name='td', attrs={'class':'rubrika'})
,dict(name='div', attrs={'class':'rubrika-ostat'})
]
extra_css = '.nadpis {font-weight: bold; font-size: 130%;} .medium {text-align: justify;}'
cover_url = 'http://pravo.novinky.cz/images/horni_6_logo.gif'
cover_margins = (0, 100, '#ffffff')
conversion_options = {'linearize_tables' : True}
no_stylesheets = True
# our variables
seen_titles = set([])
# only yesterday's articles are online
parent_url = 'http://pravo.novinky.cz/minule/'
feeds = [
('Hlavní stránka', 'http://pravo.novinky.cz/minule/index.php'),
('Zpravodajství', 'http://pravo.novinky.cz/minule/zpravodajstvi.php'),
('Komentáře', 'http://pravo.novinky.cz/minule/komentare.php'),
('Praha a střední Čechy', 'http://pravo.novinky.cz/minule/praha_stredni_cechy.php')
]
def parse_index(self):
articles = []
for feed in self.feeds:
articles.append(self.parse_page(feed))
return articles
def parse_page(self, (feed_title, url)):
articles = []
soup = self.index_to_soup(url)
titles = soup.findAll('a', attrs={'class':'nadpis'})
if titles is None:
raise ValueError('Could not find any articles on page ' + url)
articles = []
for article in titles:
title = article.string
if title in self.seen_titles:
continue
self.seen_titles.add(title)
url = article['href']
if not url.startswith('http'):
url = self.parent_url + url
self.log('\tFound article:', title, 'at', url)
articles.append({'title':title.string, 'url':url, 'description':'',
'date':''})
return (feed_title, articles)

37
recipes/respekt.recipe Normal file
View File

@ -0,0 +1,37 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
import re
class respektRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Respekt'
publisher = u'Respekt'
description = 'Respekt'
oldest_article = 1
max_articles_per_feed = 20
feeds = [
(u'Všechny články', u'http://respekt.ihned.cz/index.php?p=R00000_rss')
,(u'Blogy', u'http://blog.respekt.ihned.cz/?p=Rb00VR_rss')
#,(u'Respekt DJ', u'http://respekt.ihned.cz/index.php?p=R00RDJ_rss')
]
encoding = 'cp1250'
language = 'cs'
cover_url = 'http://respekt.ihned.cz/img/R/respekt_logo.png'
remove_javascript = True
no_stylesheets = True
remove_tags = [dict(name='div', attrs={'class':['d-tools', 'actions']})]
remove_tags_before = dict(name='div',attrs={'id':['detail']})
remove_tags_after = dict(name='div',attrs={'class':'d-tools'})
preprocess_regexps = [(re.compile(r'<div class="paid-zone".*', re.DOTALL|re.IGNORECASE), lambda match: 'Za zbytek článku je nutno platit. </body>'),
(re.compile(r'.*<div class="mm-ow">', re.DOTALL|re.IGNORECASE), lambda match: '<body>'),
(re.compile(r'<div class="col3">.*', re.DOTALL|re.IGNORECASE), lambda match: '</body>')]
keep_only_tags = []

View File

@ -16,7 +16,7 @@ class AdvancedUserRecipe1303841067(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
remove_javascript = True remove_javascript = True
remove_empty_feeds = True remove_empty_feeds = True
language = 'de_DE' language = 'de'
#conversion_options = {'base_font_size': 20} #conversion_options = {'base_font_size': 20}

View File

@ -0,0 +1,67 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
from calibre.web.feeds.news import BasicNewsRecipe
import re
class telepolis(BasicNewsRecipe):
title = u'Telepolis.pl'
__author__ = 'Artur Stachecki <artur.stachecki@gmail.com>'
language = 'pl'
description = u'Twój telekomunikacyjny serwis informacyjny.\
Codzienne informacje, testy i artykuły,\
promocje, baza telefonów oraz centrum rozrywki'
oldest_article = 7
masthead_url = 'http://telepolis.pl/i/telepolis-logo2.gif'
max_articles_per_feed = 100
simultaneous_downloads = 5
remove_javascript = True
no_stylesheets = True
use_embedded_content = False
remove_tags = []
remove_tags.append(dict(attrs={'alt': 'TELEPOLIS.pl'}))
preprocess_regexps = [(re.compile(r'<: .*? :>'),
lambda match: ''),
(re.compile(r'<b>Zobacz:</b>.*?</a>', re.DOTALL),
lambda match: ''),
(re.compile(r'<-ankieta.*?>'),
lambda match: ''),
(re.compile(r'\(Q\!\)'),
lambda match: ''),
(re.compile(r'\(plik.*?\)'),
lambda match: ''),
(re.compile(r'<br.*?><br.*?>', re.DOTALL),
lambda match: '')
]
extra_css = '''.tb { font-weight: bold; font-size: 20px;}'''
feeds = [
(u'Wiadomości', u'http://www.telepolis.pl/rss/news.php'),
(u'Artykuły', u'http://www.telepolis.pl/rss/artykuly.php')
]
def print_version(self, url):
if 'news.php' in url:
print_url = url.replace('news.php', 'news_print.php')
else:
print_url = url.replace('artykuly.php', 'art_print.php')
return print_url
def preprocess_html(self, soup):
for image in soup.findAll('img'):
if 'm.jpg' in image['src']:
image_big = image['src']
image_big = image_big.replace('m.jpg', '.jpg')
image['src'] = image_big
logo = soup.find('tr')
logo.extract()
for tag in soup.findAll('tr'):
for strings in ['Wiadomość wydrukowana', 'copyright']:
if strings in self.tag_to_string(tag):
tag.extract()
return self.adeify_images(soup)

44
recipes/tyden.cz.recipe Normal file
View File

@ -0,0 +1,44 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import unicode_literals
from calibre.web.feeds.recipes import BasicNewsRecipe
class tydenRecipe(BasicNewsRecipe):
__author__ = 'bubak'
title = u'Tyden.cz'
publisher = u''
description = ''
oldest_article = 1
max_articles_per_feed = 20
feeds = [
(u'Domácí', u'http://www.tyden.cz/rss/rss.php?rubrika_id=6'),
(u'Politika', u'http://www.tyden.cz/rss/rss.php?rubrika_id=173'),
(u'Kauzy', u'http://www.tyden.cz/rss/rss.php?rubrika_id=340')
]
#encoding = 'iso-8859-2'
language = 'cs'
cover_url = 'http://www.tyden.cz/img/tyden-logo.png'
remove_javascript = True
no_stylesheets = True
remove_attributes = []
remove_tags_before = dict(name='p', attrs={'id':['breadcrumbs']})
remove_tags_after = dict(name='p', attrs={'class':['author']})
visited_urls = {}
def get_article_url(self, article):
url = BasicNewsRecipe.get_article_url(self, article)
if url in self.visited_urls:
self.log.debug('Ignoring duplicate: ' + url)
return None
else:
self.visited_urls[url] = True
self.log.debug('Accepting: ' + url)
return url

View File

@ -4,7 +4,7 @@ class AdvancedUserRecipe1347997197(BasicNewsRecipe):
title = u'XpatLoop.com' title = u'XpatLoop.com'
__author__ = 'laca' __author__ = 'laca'
oldest_article = 7 oldest_article = 7
language = 'en_HUN' language = 'en_HU'
auto_cleanup = True auto_cleanup = True
masthead_url = 'http://www.xpatloop.com/images/cms/xs_logo.gif' masthead_url = 'http://www.xpatloop.com/images/cms/xs_logo.gif'
use_embedded_content = False use_embedded_content = False

View File

@ -16,7 +16,7 @@ class ZeitDe(BasicNewsRecipe):
category = 'news, Germany' category = 'news, Germany'
timefmt = ' [%a, %d %b %Y]' timefmt = ' [%a, %d %b %Y]'
publication_type = 'newspaper' publication_type = 'newspaper'
language = 'de_DE' language = 'de'
encoding = 'UTF-8' encoding = 'UTF-8'
oldest_article = 7 oldest_article = 7

View File

@ -308,6 +308,10 @@ class FileTypePlugin(Plugin): # {{{
#: to the database #: to the database
on_import = False on_import = False
#: If True, this plugin is run after books are added
#: to the database
on_postimport = False
#: If True, this plugin is run just before a conversion #: If True, this plugin is run just before a conversion
on_preprocess = False on_preprocess = False
@ -337,6 +341,16 @@ class FileTypePlugin(Plugin): # {{{
# Default implementation does nothing # Default implementation does nothing
return path_to_ebook return path_to_ebook
def postimport(self, book_id, book_format, db):
'''
Called post import, i.e., after the book file has been added to the database.
:param book_id: Database id of the added book.
:param book_format: The file type of the book that was added.
:param db: Library database.
'''
pass # Default implementation does nothing
# }}} # }}}
class MetadataReaderPlugin(Plugin): # {{{ class MetadataReaderPlugin(Plugin): # {{{
@ -447,8 +461,8 @@ class CatalogPlugin(Plugin): # {{{
# Return a list of requested fields, with opts.sort_by first # Return a list of requested fields, with opts.sort_by first
all_std_fields = set( all_std_fields = set(
['author_sort','authors','comments','cover','formats', ['author_sort','authors','comments','cover','formats',
'id','isbn','ondevice','pubdate','publisher','rating', 'id','isbn','library_name','ondevice','pubdate','publisher',
'series_index','series','size','tags','timestamp', 'rating','series_index','series','size','tags','timestamp',
'title_sort','title','uuid','languages','identifiers']) 'title_sort','title','uuid','languages','identifiers'])
all_custom_fields = set(db.custom_field_keys()) all_custom_fields = set(db.custom_field_keys())
for field in list(all_custom_fields): for field in list(all_custom_fields):
@ -460,6 +474,16 @@ class CatalogPlugin(Plugin): # {{{
if opts.fields != 'all': if opts.fields != 'all':
# Make a list from opts.fields # Make a list from opts.fields
requested_fields = set(opts.fields.split(',')) requested_fields = set(opts.fields.split(','))
# Validate requested_fields
if requested_fields - all_fields:
from calibre.library import current_library_name
invalid_fields = sorted(list(requested_fields - all_fields))
print("invalid --fields specified: %s" % ', '.join(invalid_fields))
print("available fields in '%s': %s" %
(current_library_name(), ', '.join(sorted(list(all_fields)))))
raise ValueError("unable to generate catalog with specified fields")
fields = list(all_fields & requested_fields) fields = list(all_fields & requested_fields)
else: else:
fields = list(all_fields) fields = list(all_fields)

View File

@ -104,14 +104,17 @@ def is_disabled(plugin):
# File type plugins {{{ # File type plugins {{{
_on_import = {} _on_import = {}
_on_postimport = {}
_on_preprocess = {} _on_preprocess = {}
_on_postprocess = {} _on_postprocess = {}
def reread_filetype_plugins(): def reread_filetype_plugins():
global _on_import global _on_import
global _on_postimport
global _on_preprocess global _on_preprocess
global _on_postprocess global _on_postprocess
_on_import = {} _on_import = {}
_on_postimport = {}
_on_preprocess = {} _on_preprocess = {}
_on_postprocess = {} _on_postprocess = {}
@ -122,6 +125,10 @@ def reread_filetype_plugins():
if not _on_import.has_key(ft): if not _on_import.has_key(ft):
_on_import[ft] = [] _on_import[ft] = []
_on_import[ft].append(plugin) _on_import[ft].append(plugin)
if plugin.on_postimport:
if not _on_postimport.has_key(ft):
_on_postimport[ft] = []
_on_postimport[ft].append(plugin)
if plugin.on_preprocess: if plugin.on_preprocess:
if not _on_preprocess.has_key(ft): if not _on_preprocess.has_key(ft):
_on_preprocess[ft] = [] _on_preprocess[ft] = []
@ -163,6 +170,22 @@ run_plugins_on_preprocess = functools.partial(_run_filetype_plugins,
occasion='preprocess') occasion='preprocess')
run_plugins_on_postprocess = functools.partial(_run_filetype_plugins, run_plugins_on_postprocess = functools.partial(_run_filetype_plugins,
occasion='postprocess') occasion='postprocess')
def run_plugins_on_postimport(db, book_id, fmt):
customization = config['plugin_customization']
fmt = fmt.lower()
for plugin in _on_postimport.get(fmt, []):
if is_disabled(plugin):
continue
plugin.site_customization = customization.get(plugin.name, '')
with plugin:
try:
plugin.postimport(book_id, fmt, db)
except:
print ('Running file type plugin %s failed with traceback:'%
plugin.name)
traceback.print_exc()
# }}} # }}}
# Plugin customization {{{ # Plugin customization {{{

View File

@ -19,9 +19,9 @@ class TECLAST_K3(USBMS):
PRODUCT_ID = [0x3203] PRODUCT_ID = [0x3203]
BCD = [0x0000, 0x0100] BCD = [0x0000, 0x0100]
VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX', 'PER3274B'] VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX', 'PER3274B', 'BEBOOK']
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5', WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5',
'EREADER', 'USB-MSC', 'PER3274B'] 'EREADER', 'USB-MSC', 'PER3274B', 'BEBOOK']
MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory' MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory'
STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card' STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card'

View File

@ -263,8 +263,11 @@ def generate_masthead(title, output_path=None, width=600, height=60):
masthead_font_family = recs.get('masthead_font', 'Default') masthead_font_family = recs.get('masthead_font', 'Default')
if masthead_font_family != 'Default': if masthead_font_family != 'Default':
from calibre.utils.fonts.scanner import font_scanner from calibre.utils.fonts.scanner import font_scanner, NoFonts
faces = font_scanner.fonts_for_family(masthead_font_family) try:
faces = font_scanner.fonts_for_family(masthead_font_family)
except NoFonts:
faces = []
if faces: if faces:
font_path = faces[0]['path'] font_path = faces[0]['path']

View File

@ -1103,10 +1103,14 @@ OptionRecommendation(name='search_replace',
from calibre.ebooks.oeb.transforms.unsmarten import UnsmartenPunctuation from calibre.ebooks.oeb.transforms.unsmarten import UnsmartenPunctuation
UnsmartenPunctuation()(self.oeb, self.opts) UnsmartenPunctuation()(self.oeb, self.opts)
mobi_file_type = getattr(self.opts, 'mobi_file_type', 'old')
needs_old_markup = (self.output_plugin.file_type == 'lit' or
(self.output_plugin.file_type == 'mobi' and mobi_file_type
== 'old'))
flattener = CSSFlattener(fbase=fbase, fkey=fkey, flattener = CSSFlattener(fbase=fbase, fkey=fkey,
lineh=line_height, lineh=line_height,
untable=self.output_plugin.file_type in ('mobi','lit'), untable=needs_old_markup,
unfloat=self.output_plugin.file_type in ('mobi', 'lit'), unfloat=needs_old_markup,
page_break_on_body=self.output_plugin.file_type in ('mobi', page_break_on_body=self.output_plugin.file_type in ('mobi',
'lit'), 'lit'),
specializer=partial(self.output_plugin.specialize_css_for_output, specializer=partial(self.output_plugin.specialize_css_for_output,

View File

@ -322,6 +322,10 @@ class MobiMLizer(object):
istates.append(istate) istates.append(istate)
left = 0 left = 0
display = style['display'] display = style['display']
if display == 'table-cell':
display = 'inline'
elif display.startswith('table'):
display = 'block'
isblock = (not display.startswith('inline') and style['display'] != isblock = (not display.startswith('inline') and style['display'] !=
'none') 'none')
isblock = isblock and style['float'] == 'none' isblock = isblock and style['float'] == 'none'

View File

@ -143,7 +143,7 @@ class EbookIterator(BookmarksMixin):
cover = self.opf.cover cover = self.opf.cover
if cover and self.ebook_ext in {'lit', 'mobi', 'prc', 'opf', 'fb2', if cover and self.ebook_ext in {'lit', 'mobi', 'prc', 'opf', 'fb2',
'azw', 'azw3'}: 'azw', 'azw3'}:
cfile = os.path.join(self.base, 'calibre_iterator_cover.html') cfile = os.path.join(self.base, 'calibre_iterator_cover.html')
rcpath = os.path.relpath(cover, self.base).replace(os.sep, '/') rcpath = os.path.relpath(cover, self.base).replace(os.sep, '/')
chtml = (TITLEPAGE%prepare_string_for_xml(rcpath, True)).encode('utf-8') chtml = (TITLEPAGE%prepare_string_for_xml(rcpath, True)).encode('utf-8')

View File

@ -93,6 +93,7 @@ gprefs.defaults['tag_browser_dont_collapse'] = []
gprefs.defaults['edit_metadata_single_layout'] = 'default' gprefs.defaults['edit_metadata_single_layout'] = 'default'
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}' gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
gprefs.defaults['preserve_date_on_ctl'] = True gprefs.defaults['preserve_date_on_ctl'] = True
gprefs.defaults['manual_add_auto_convert'] = False
gprefs.defaults['cb_fullscreen'] = False gprefs.defaults['cb_fullscreen'] = False
gprefs.defaults['worker_max_time'] = 0 gprefs.defaults['worker_max_time'] = 0
gprefs.defaults['show_files_after_save'] = True gprefs.defaults['show_files_after_save'] = True

View File

@ -320,15 +320,23 @@ class ChooseLibraryAction(InterfaceAction):
_('Path to library too long. Must be less than' _('Path to library too long. Must be less than'
' %d characters.')%LibraryDatabase2.WINDOWS_LIBRARY_PATH_LIMIT, ' %d characters.')%LibraryDatabase2.WINDOWS_LIBRARY_PATH_LIMIT,
show=True) show=True)
if not os.path.exists(loc):
error_dialog(self.gui, _('Not found'),
_('Cannot rename as no library was found at %s. '
'Try switching to this library first, then switch back '
'and retry the renaming.')%loc, show=True)
return
try: try:
os.rename(loc, newloc) os.rename(loc, newloc)
except: except:
import traceback import traceback
det_msg = 'Location: %r New Location: %r\n%s'%(loc, newloc,
traceback.format_exc())
error_dialog(self.gui, _('Rename failed'), error_dialog(self.gui, _('Rename failed'),
_('Failed to rename the library at %s. ' _('Failed to rename the library at %s. '
'The most common cause for this is if one of the files' 'The most common cause for this is if one of the files'
' in the library is open in another program.') % loc, ' in the library is open in another program.') % loc,
det_msg=traceback.format_exc(), show=True) det_msg=det_msg, show=True)
return return
self.stats.rename(location, newloc) self.stats.rename(location, newloc)
self.build_menus() self.build_menus()

View File

@ -267,8 +267,8 @@ class ViewAction(InterfaceAction):
def _view_books(self, rows): def _view_books(self, rows):
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
self._launch_viewer() return error_dialog(self.gui, _('Cannot view'),
return _('No books selected'), show=True)
if not self._view_check(len(rows)): if not self._view_check(len(rows)):
return return

View File

@ -42,6 +42,7 @@ class DuplicatesAdder(QObject): # {{{
# here we add all the formats for dupe book record created above # here we add all the formats for dupe book record created above
self.db_adder.add_formats(id, formats) self.db_adder.add_formats(id, formats)
self.db_adder.number_of_books_added += 1 self.db_adder.number_of_books_added += 1
self.db_adder.auto_convert_books.add(id)
self.count += 1 self.count += 1
self.added.emit(self.count) self.added.emit(self.count)
single_shot(self.add_one) single_shot(self.add_one)
@ -107,8 +108,16 @@ class DBAdder(QObject): # {{{
self.input_queue = Queue() self.input_queue = Queue()
self.output_queue = Queue() self.output_queue = Queue()
self.merged_books = set([]) self.merged_books = set([])
self.auto_convert_books = set()
def end(self): def end(self):
if (gprefs['manual_add_auto_convert'] and
self.auto_convert_books):
from calibre.gui2.ui import get_gui
gui = get_gui()
gui.iactions['Convert Books'].auto_convert_auto_add(
self.auto_convert_books)
self.input_queue.put((None, None, None)) self.input_queue.put((None, None, None))
def start(self): def start(self):
@ -152,7 +161,6 @@ class DBAdder(QObject): # {{{
fmts[-1] = fmt fmts[-1] = fmt
return fmts return fmts
def add(self, id, opf, cover, name): def add(self, id, opf, cover, name):
formats = self.ids.pop(id) formats = self.ids.pop(id)
if opf.endswith('.error'): if opf.endswith('.error'):
@ -219,6 +227,7 @@ class DBAdder(QObject): # {{{
self.duplicates.append((mi, cover, orig_formats)) self.duplicates.append((mi, cover, orig_formats))
else: else:
self.add_formats(id_, formats) self.add_formats(id_, formats)
self.auto_convert_books.add(id_)
self.number_of_books_added += 1 self.number_of_books_added += 1
else: else:
self.names.append(name) self.names.append(name)

View File

@ -1057,6 +1057,7 @@ class DeviceBooksModel(BooksModel): # {{{
booklist_dirtied = pyqtSignal() booklist_dirtied = pyqtSignal()
upload_collections = pyqtSignal(object) upload_collections = pyqtSignal(object)
resize_rows = pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
BooksModel.__init__(self, parent) BooksModel.__init__(self, parent)
@ -1163,6 +1164,11 @@ class DeviceBooksModel(BooksModel): # {{{
return flags return flags
def search(self, text, reset=True): def search(self, text, reset=True):
# This should not be here, but since the DeviceBooksModel does not
# implement count_changed and I am too lazy to fix that, this kludge
# will have to do
self.resize_rows.emit()
if not text or not text.strip(): if not text or not text.strip():
self.map = list(range(len(self.db))) self.map = list(range(len(self.db)))
else: else:

View File

@ -90,6 +90,7 @@ class BooksView(QTableView): # {{{
def __init__(self, parent, modelcls=BooksModel, use_edit_metadata_dialog=True): def __init__(self, parent, modelcls=BooksModel, use_edit_metadata_dialog=True):
QTableView.__init__(self, parent) QTableView.__init__(self, parent)
self.row_sizing_done = False
if not tweaks['horizontal_scrolling_per_column']: if not tweaks['horizontal_scrolling_per_column']:
self.setHorizontalScrollMode(self.ScrollPerPixel) self.setHorizontalScrollMode(self.ScrollPerPixel)
@ -141,6 +142,8 @@ class BooksView(QTableView): # {{{
self.display_parent = parent self.display_parent = parent
self._model = modelcls(self) self._model = modelcls(self)
self.setModel(self._model) self.setModel(self._model)
self._model.count_changed_signal.connect(self.do_row_sizing,
type=Qt.QueuedConnection)
self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.selectionModel().currentRowChanged.connect(self._model.current_changed) self.selectionModel().currentRowChanged.connect(self._model.current_changed)
@ -521,13 +524,17 @@ class BooksView(QTableView): # {{{
self.apply_state(old_state, max_sort_levels=max_levels) self.apply_state(old_state, max_sort_levels=max_levels)
self.column_header.blockSignals(False) self.column_header.blockSignals(False)
# Resize all rows to have the correct height self.do_row_sizing()
if self.model().rowCount(QModelIndex()) > 0:
self.resizeRowToContents(0)
self.verticalHeader().setDefaultSectionSize(self.rowHeight(0))
self.was_restored = True self.was_restored = True
def do_row_sizing(self):
# Resize all rows to have the correct height
if not self.row_sizing_done and self.model().rowCount(QModelIndex()) > 0:
self.resizeRowToContents(0)
self.verticalHeader().setDefaultSectionSize(self.rowHeight(0))
self.row_sizing_done = True
def resize_column_to_fit(self, column): def resize_column_to_fit(self, column):
col = self.column_map.index(column) col = self.column_map.index(column)
self.column_resized(col, self.columnWidth(col), self.columnWidth(col)) self.column_resized(col, self.columnWidth(col), self.columnWidth(col))
@ -943,6 +950,8 @@ class DeviceBooksView(BooksView): # {{{
def __init__(self, parent): def __init__(self, parent):
BooksView.__init__(self, parent, DeviceBooksModel, BooksView.__init__(self, parent, DeviceBooksModel,
use_edit_metadata_dialog=False) use_edit_metadata_dialog=False)
self._model.resize_rows.connect(self.do_row_sizing,
type=Qt.QueuedConnection)
self.can_add_columns = False self.can_add_columns = False
self.columns_resized = False self.columns_resized = False
self.resize_on_select = False self.resize_on_select = False

View File

@ -867,6 +867,7 @@ class Cover(ImageView): # {{{
def __init__(self, parent): def __init__(self, parent):
ImageView.__init__(self, parent) ImageView.__init__(self, parent)
self.show_size = True
self.dialog = parent self.dialog = parent
self._cdata = None self._cdata = None
self.cover_changed.connect(self.set_pixmap_from_data) self.cover_changed.connect(self.set_pixmap_from_data)

View File

@ -28,6 +28,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('swap_author_names', prefs) r('swap_author_names', prefs)
r('add_formats_to_existing', prefs) r('add_formats_to_existing', prefs)
r('preserve_date_on_ctl', gprefs) r('preserve_date_on_ctl', gprefs)
r('manual_add_auto_convert', gprefs)
choices = [ choices = [
(_('Ignore duplicate incoming formats'), 'ignore'), (_('Ignore duplicate incoming formats'), 'ignore'),
(_('Overwrite existing duplicate formats'), 'overwrite'), (_('Overwrite existing duplicate formats'), 'overwrite'),

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>753</width> <width>1035</width>
<height>547</height> <height>547</height>
</rect> </rect>
</property> </property>
@ -24,6 +24,36 @@
<string>The Add &amp;Process</string> <string>The Add &amp;Process</string>
</attribute> </attribute>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_add_formats_to_existing">
<property name="toolTip">
<string>Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
existing book records. The box to the right controls what happens when an existing record already has
the incoming format. Note that this option also affects the Copy to library action.
Title match ignores leading indefinite articles (&quot;the&quot;, &quot;a&quot;, &quot;an&quot;), punctuation, case, etc. Author match is exact.</string>
</property>
<property name="text">
<string>&amp;Automerge added books if they already exist in the calibre library:</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QComboBox" name="opt_automerge">
<property name="toolTip">
<string>Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
existing book records. This box controls what happens when an existing record already has
the incoming format:
Ignore duplicate incoming files - means that existing files in your calibre library will not be replaced
Overwrite existing duplicate files - means that existing files in your calibre library will be replaced
Create new record for each duplicate file - means that a new book entry will be created for each duplicate file
Title matching ignores leading indefinite articles (&quot;the&quot;, &quot;a&quot;, &quot;an&quot;), punctuation, case, etc.
Author matching is exact.</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3"> <item row="0" column="0" colspan="3">
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
<property name="text"> <property name="text">
@ -68,44 +98,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="2" column="0" colspan="3"> <item row="5" column="0">
<widget class="QCheckBox" name="opt_preserve_date_on_ctl">
<property name="text">
<string>When using the &quot;&amp;Copy to library&quot; action to copy books between libraries, preserve the date</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_add_formats_to_existing">
<property name="toolTip">
<string>Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
existing book records. The box to the right controls what happens when an existing record already has
the incoming format. Note that this option also affects the Copy to library action.
Title match ignores leading indefinite articles (&quot;the&quot;, &quot;a&quot;, &quot;an&quot;), punctuation, case, etc. Author match is exact.</string>
</property>
<property name="text">
<string>&amp;Automerge added books if they already exist in the calibre library:</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QComboBox" name="opt_automerge">
<property name="toolTip">
<string>Automerge: If books with similar titles and authors found, merge the incoming formats automatically into
existing book records. This box controls what happens when an existing record already has
the incoming format:
Ignore duplicate incoming files - means that existing files in your calibre library will not be replaced
Overwrite existing duplicate files - means that existing files in your calibre library will be replaced
Create new record for each duplicate file - means that a new book entry will be created for each duplicate file
Title matching ignores leading indefinite articles (&quot;the&quot;, &quot;a&quot;, &quot;an&quot;), punctuation, case, etc.
Author matching is exact.</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_230"> <widget class="QLabel" name="label_230">
<property name="text"> <property name="text">
<string>&amp;Tags to apply when adding a book:</string> <string>&amp;Tags to apply when adding a book:</string>
@ -115,14 +108,14 @@ Author matching is exact.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="2"> <item row="5" column="2">
<widget class="QLineEdit" name="opt_new_book_tags"> <widget class="QLineEdit" name="opt_new_book_tags">
<property name="toolTip"> <property name="toolTip">
<string>A comma-separated list of tags that will be applied to books added to the library</string> <string>A comma-separated list of tags that will be applied to books added to the library</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0" colspan="3"> <item row="6" column="0" colspan="3">
<widget class="QGroupBox" name="metadata_box"> <widget class="QGroupBox" name="metadata_box">
<property name="title"> <property name="title">
<string>&amp;Configure metadata from file name</string> <string>&amp;Configure metadata from file name</string>
@ -144,6 +137,20 @@ Author matching is exact.</string>
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="3">
<widget class="QCheckBox" name="opt_preserve_date_on_ctl">
<property name="text">
<string>When using the &quot;&amp;Copy to library&quot; action to copy books between libraries, preserve the date</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_manual_add_auto_convert">
<property name="text">
<string>Automatically &amp;convert added books to the current output format</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_4"> <widget class="QWidget" name="tab_4">

View File

@ -16,8 +16,6 @@ from calibre.gui2 import NONE, FunctionDispatcher
from calibre.gui2.store.search_result import SearchResult from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.search.download_thread import DetailsThreadPool, \ from calibre.gui2.store.search.download_thread import DetailsThreadPool, \
CoverThreadPool CoverThreadPool
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
REGEXP_MATCH
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
@ -153,7 +151,7 @@ class Matches(QAbstractItemModel):
mod_query = query mod_query = query
# Remove filter identifiers # Remove filter identifiers
# Remove the prefix. # Remove the prefix.
for loc in ('all', 'author', 'authors', 'title'): for loc in ('all', 'author', 'author2', 'authors', 'title', 'title2'):
query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query) query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query)
query = query.replace('%s:' % loc, '') query = query.replace('%s:' % loc, '')
# Remove the prefix and search text. # Remove the prefix and search text.
@ -301,11 +299,16 @@ class Matches(QAbstractItemModel):
class SearchFilter(SearchQueryParser): class SearchFilter(SearchQueryParser):
CONTAINS_MATCH = 0
EQUALS_MATCH = 1
REGEXP_MATCH = 2
IN_MATCH = 3
USABLE_LOCATIONS = [ USABLE_LOCATIONS = [
'all', 'all',
'affiliate', 'affiliate',
'author', 'author',
'author2',
'authors', 'authors',
'cover', 'cover',
'download', 'download',
@ -315,6 +318,7 @@ class SearchFilter(SearchQueryParser):
'formats', 'formats',
'price', 'price',
'title', 'title',
'title2',
'store', 'store',
] ]
@ -331,6 +335,26 @@ class SearchFilter(SearchQueryParser):
def universal_set(self): def universal_set(self):
return self.srs return self.srs
def _match(self, query, value, matchkind):
for t in value:
try: ### ignore regexp exceptions, required because search-ahead tries before typing is finished
t = icu_lower(t)
if matchkind == self.EQUALS_MATCH:
if query == t:
return True
elif matchkind == self.REGEXP_MATCH:
if re.search(query, t, re.I|re.UNICODE):
return True
elif matchkind == self.CONTAINS_MATCH:
if query in t:
return True
elif matchkind == self.IN_MATCH:
if t in query:
return True
except re.error:
pass
return False
def get_matches(self, location, query): def get_matches(self, location, query):
query = query.strip() query = query.strip()
location = location.lower().strip() location = location.lower().strip()
@ -341,17 +365,17 @@ class SearchFilter(SearchQueryParser):
elif location == 'formats': elif location == 'formats':
location = 'format' location = 'format'
matchkind = CONTAINS_MATCH matchkind = self.CONTAINS_MATCH
if len(query) > 1: if len(query) > 1:
if query.startswith('\\'): if query.startswith('\\'):
query = query[1:] query = query[1:]
elif query.startswith('='): elif query.startswith('='):
matchkind = EQUALS_MATCH matchkind = self.EQUALS_MATCH
query = query[1:] query = query[1:]
elif query.startswith('~'): elif query.startswith('~'):
matchkind = REGEXP_MATCH matchkind = self.REGEXP_MATCH
query = query[1:] query = query[1:]
if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D if matchkind != self.REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D
query = query.lower() query = query.lower()
if location not in self.USABLE_LOCATIONS: if location not in self.USABLE_LOCATIONS:
@ -372,6 +396,8 @@ class SearchFilter(SearchQueryParser):
} }
for x in ('author', 'download', 'format'): for x in ('author', 'download', 'format'):
q[x+'s'] = q[x] q[x+'s'] = q[x]
q['author2'] = q['author']
q['title2'] = q['title']
# make the price in query the same format as result # make the price in query the same format as result
if location == 'price': if location == 'price':
@ -415,16 +441,19 @@ class SearchFilter(SearchQueryParser):
### Can't separate authors because comma is used for name sep and author sep ### Can't separate authors because comma is used for name sep and author sep
### Exact match might not get what you want. For that reason, turn author ### Exact match might not get what you want. For that reason, turn author
### exactmatch searches into contains searches. ### exactmatch searches into contains searches.
if locvalue == 'author' and matchkind == EQUALS_MATCH: if locvalue == 'author' and matchkind == self.EQUALS_MATCH:
m = CONTAINS_MATCH m = self.CONTAINS_MATCH
else: else:
m = matchkind m = matchkind
if locvalue == 'format': if locvalue == 'format':
vals = accessor(sr).split(',') vals = accessor(sr).split(',')
elif locvalue in ('author2', 'title2'):
m = self.IN_MATCH
vals = re.sub(r'(^|\s)(and|not|or|a|the|is|of|,)(\s|$)', ' ', accessor(sr)).split(' ')
else: else:
vals = [accessor(sr)] vals = [accessor(sr)]
if _match(query, vals, m): if self._match(query, vals, m):
matches.add(sr) matches.add(sr)
break break
except ValueError: # Unicode errors except ValueError: # Unicode errors

View File

@ -13,7 +13,7 @@ from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox, QLabel,
QVBoxLayout, QIcon, QWidget, QTabWidget, QGridLayout, QVBoxLayout, QIcon, QWidget, QTabWidget, QGridLayout,
QComboBox) QComboBox)
from calibre.gui2 import JSONConfig, info_dialog from calibre.gui2 import JSONConfig, info_dialog, error_dialog
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
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.chooser.chooser_widget import StoreChooserWidget
@ -31,6 +31,8 @@ class SearchDialog(QDialog, Ui_Dialog):
self.setupUi(self) self.setupUi(self)
self.config = JSONConfig('store/search') self.config = JSONConfig('store/search')
self.search_title.initialize('store_search_search_title')
self.search_author.initialize('store_search_search_author')
self.search_edit.initialize('store_search_search') self.search_edit.initialize('store_search_search')
# Loads variables that store various settings. # Loads variables that store various settings.
@ -60,13 +62,24 @@ class SearchDialog(QDialog, Ui_Dialog):
self.setup_store_checks() self.setup_store_checks()
# Set the search query # Set the search query
# Title
self.search_title.setText(query)
self.search_title.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
self.search_title.setMinimumContentsLength(25)
# Author
self.search_author.setText(query)
self.search_author.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
self.search_author.setMinimumContentsLength(25)
# Keyword
self.search_edit.setText(query) self.search_edit.setText(query)
self.search_edit.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon) self.search_edit.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
self.search_edit.setMinimumContentsLength(25) self.search_edit.setMinimumContentsLength(25)
# Create and add the progress indicator # Create and add the progress indicator
self.pi = ProgressIndicator(self, 24) self.pi = ProgressIndicator(self, 24)
self.top_layout.addWidget(self.pi) self.button_layout.takeAt(0)
self.button_layout.setAlignment(Qt.AlignCenter)
self.button_layout.insertWidget(0, self.pi, 0, Qt.AlignCenter)
self.adv_search_button.setIcon(QIcon(I('search.png'))) self.adv_search_button.setIcon(QIcon(I('search.png')))
self.configure.setIcon(QIcon(I('config.png'))) self.configure.setIcon(QIcon(I('config.png')))
@ -152,8 +165,18 @@ class SearchDialog(QDialog, Ui_Dialog):
self.results_view.model().clear_results() self.results_view.model().clear_results()
# Don't start a search if there is nothing to search for. # Don't start a search if there is nothing to search for.
query = unicode(self.search_edit.text()) query = []
if self.search_title.text():
query.append(u'title2:"~%s"' % unicode(self.search_title.text()).replace('"', ' '))
if self.search_author.text():
query.append(u'author2:"%s"' % unicode(self.search_author.text()).replace('"', ' '))
if self.search_edit.text():
query.append(unicode(self.search_edit.text()))
query = " ".join(query)
if not query.strip(): if not query.strip():
error_dialog(self, _('No query'),
_('You must enter a title, author or keyword to'
' search for.'), show=True)
return return
# Give the query to the results model so it can do # Give the query to the results model so it can do
# futher filtering. # futher filtering.
@ -188,7 +211,7 @@ class SearchDialog(QDialog, Ui_Dialog):
query = query.replace('>', '') query = query.replace('>', '')
query = query.replace('<', '') query = query.replace('<', '')
# Remove the prefix. # Remove the prefix.
for loc in ('all', 'author', 'authors', 'title'): for loc in ('all', 'author', 'author2', 'authors', 'title', 'title2'):
query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query) query = re.sub(r'%s:"(?P<a>[^\s"]+)"' % loc, '\g<a>', query)
query = query.replace('%s:' % loc, '') query = query.replace('%s:' % loc, '')
# Remove the prefix and search text. # Remove the prefix and search text.

View File

@ -14,49 +14,74 @@
<string>Get Books</string> <string>Get Books</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset> <iconset resource="../../../../../resources/images.qrc">
<normaloff>:/images/store.png</normaloff>:/images/store.png</iconset> <normaloff>:/images/store.png</normaloff>:/images/store.png</iconset>
</property> </property>
<property name="sizeGripEnabled"> <property name="sizeGripEnabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QGridLayout" name="gridLayout">
<item> <item row="0" column="2">
<layout class="QHBoxLayout" name="top_layout"> <widget class="HistoryLineEdit" name="search_title">
<item> <property name="placeholderText">
<widget class="QLabel" name="label"> <string>Search by title</string>
<property name="text"> </property>
<string>Query:</string> </widget>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="adv_search_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<widget class="HistoryLineEdit" name="search_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="search">
<property name="text">
<string>Search</string>
</property>
</widget>
</item>
</layout>
</item> </item>
<item> <item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>&amp;Author:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>search_author</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="HistoryLineEdit" name="search_author">
<property name="placeholderText">
<string>Search by author</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QToolButton" name="adv_search_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>&amp;Keyword:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>search_edit</cstring>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="HistoryLineEdit" name="search_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="placeholderText">
<string>Search by any keyword</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="4">
<widget class="QSplitter" name="store_splitter"> <widget class="QSplitter" name="store_splitter">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
@ -82,8 +107,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>193</width> <width>204</width>
<height>127</height> <height>141</height>
</rect> </rect>
</property> </property>
</widget> </widget>
@ -202,7 +227,7 @@
</widget> </widget>
</widget> </widget>
</item> </item>
<item> <item row="4" column="0" colspan="3">
<layout class="QHBoxLayout" name="bottom_layout"> <layout class="QHBoxLayout" name="bottom_layout">
<item> <item>
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
@ -234,7 +259,47 @@
<item> <item>
<widget class="QPushButton" name="close"> <widget class="QPushButton" name="close">
<property name="text"> <property name="text">
<string>Close</string> <string>&amp;Close</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&amp;Title:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>search_title</cstring>
</property>
</widget>
</item>
<item row="0" column="3" rowspan="3">
<layout class="QVBoxLayout" name="button_layout">
<item>
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="baseSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="search">
<property name="text">
<string>&amp;Search</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -255,17 +320,19 @@
</customwidget> </customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>search_title</tabstop>
<tabstop>search_author</tabstop>
<tabstop>adv_search_button</tabstop>
<tabstop>search_edit</tabstop> <tabstop>search_edit</tabstop>
<tabstop>search</tabstop> <tabstop>search</tabstop>
<tabstop>results_view</tabstop>
<tabstop>store_list</tabstop> <tabstop>store_list</tabstop>
<tabstop>select_all_stores</tabstop> <tabstop>select_all_stores</tabstop>
<tabstop>select_invert_stores</tabstop> <tabstop>select_invert_stores</tabstop>
<tabstop>select_none_stores</tabstop> <tabstop>select_none_stores</tabstop>
<tabstop>results_view</tabstop>
<tabstop>configure</tabstop> <tabstop>configure</tabstop>
<tabstop>open_external</tabstop> <tabstop>open_external</tabstop>
<tabstop>close</tabstop> <tabstop>close</tabstop>
<tabstop>adv_search_button</tabstop>
</tabstops> </tabstops>
<resources> <resources>
<include location="../../../../../resources/images.qrc"/> <include location="../../../../../resources/images.qrc"/>

View File

@ -59,6 +59,10 @@ def config(defaults=None):
help=_('Show reading position in fullscreen mode.')) help=_('Show reading position in fullscreen mode.'))
c.add_opt('fullscreen_scrollbar', default=True, action='store_false', c.add_opt('fullscreen_scrollbar', default=True, action='store_false',
help=_('Show the scrollbar in fullscreen mode.')) help=_('Show the scrollbar in fullscreen mode.'))
c.add_opt('start_in_fullscreen', default=False, action='store_true',
help=_('Start viewer in full screen mode'))
c.add_opt('show_fullscreen_help', default=True, action='store_false',
help=_('Show full screen usage help'))
c.add_opt('cols_per_screen', default=1) c.add_opt('cols_per_screen', default=1)
c.add_opt('use_book_margins', default=False, action='store_true') c.add_opt('use_book_margins', default=False, action='store_true')
c.add_opt('top_margin', default=20) c.add_opt('top_margin', default=20)
@ -206,6 +210,8 @@ class ConfigDialog(QDialog, Ui_Dialog):
self.opt_fit_images.setChecked(opts.fit_images) self.opt_fit_images.setChecked(opts.fit_images)
self.opt_fullscreen_clock.setChecked(opts.fullscreen_clock) self.opt_fullscreen_clock.setChecked(opts.fullscreen_clock)
self.opt_fullscreen_scrollbar.setChecked(opts.fullscreen_scrollbar) self.opt_fullscreen_scrollbar.setChecked(opts.fullscreen_scrollbar)
self.opt_start_in_fullscreen.setChecked(opts.start_in_fullscreen)
self.opt_show_fullscreen_help.setChecked(opts.show_fullscreen_help)
self.opt_fullscreen_pos.setChecked(opts.fullscreen_pos) self.opt_fullscreen_pos.setChecked(opts.fullscreen_pos)
self.opt_cols_per_screen.setValue(opts.cols_per_screen) self.opt_cols_per_screen.setValue(opts.cols_per_screen)
self.opt_override_book_margins.setChecked(not opts.use_book_margins) self.opt_override_book_margins.setChecked(not opts.use_book_margins)
@ -279,6 +285,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
c.set('fullscreen_clock', self.opt_fullscreen_clock.isChecked()) c.set('fullscreen_clock', self.opt_fullscreen_clock.isChecked())
c.set('fullscreen_pos', self.opt_fullscreen_pos.isChecked()) c.set('fullscreen_pos', self.opt_fullscreen_pos.isChecked())
c.set('fullscreen_scrollbar', self.opt_fullscreen_scrollbar.isChecked()) c.set('fullscreen_scrollbar', self.opt_fullscreen_scrollbar.isChecked())
c.set('show_fullscreen_help', self.opt_show_fullscreen_help.isChecked())
c.set('cols_per_screen', int(self.opt_cols_per_screen.value())) c.set('cols_per_screen', int(self.opt_cols_per_screen.value()))
c.set('use_book_margins', not c.set('use_book_margins', not
self.opt_override_book_margins.isChecked()) self.opt_override_book_margins.isChecked())

View File

@ -388,20 +388,34 @@ QToolBox::tab:hover {
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2"> <item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="opt_fullscreen_pos"> <widget class="QCheckBox" name="opt_fullscreen_pos">
<property name="text"> <property name="text">
<string>Show reading &amp;position in full screen mode</string> <string>Show reading &amp;position in full screen mode</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0" colspan="2"> <item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_fullscreen_scrollbar"> <widget class="QCheckBox" name="opt_fullscreen_scrollbar">
<property name="text"> <property name="text">
<string>Show &amp;scrollbar in full screen mode</string> <string>Show &amp;scrollbar in full screen mode</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_start_in_fullscreen">
<property name="text">
<string>&amp;Start viewer in full screen mode</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_show_fullscreen_help">
<property name="text">
<string>Show &amp;help message when starting full screen mode</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="page_6"> <widget class="QWidget" name="page_6">

View File

@ -16,6 +16,7 @@ from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
from calibre.gui2.viewer.flip import SlideFlip from calibre.gui2.viewer.flip import SlideFlip
from calibre.gui2.shortcuts import Shortcuts from calibre.gui2.shortcuts import Shortcuts
from calibre.gui2 import open_url
from calibre import prints from calibre import prints
from calibre.customize.ui import all_viewer_plugins from calibre.customize.ui import all_viewer_plugins
from calibre.gui2.viewer.keys import SHORTCUTS from calibre.gui2.viewer.keys import SHORTCUTS
@ -144,6 +145,8 @@ class Document(QWebPage): # {{{
self.fullscreen_clock = opts.fullscreen_clock self.fullscreen_clock = opts.fullscreen_clock
self.fullscreen_scrollbar = opts.fullscreen_scrollbar self.fullscreen_scrollbar = opts.fullscreen_scrollbar
self.fullscreen_pos = opts.fullscreen_pos self.fullscreen_pos = opts.fullscreen_pos
self.start_in_fullscreen = opts.start_in_fullscreen
self.show_fullscreen_help = opts.show_fullscreen_help
self.use_book_margins = opts.use_book_margins self.use_book_margins = opts.use_book_margins
self.cols_per_screen = opts.cols_per_screen self.cols_per_screen = opts.cols_per_screen
self.side_margin = opts.side_margin self.side_margin = opts.side_margin
@ -481,7 +484,12 @@ class DocumentView(QWebView): # {{{
d = self.document d = self.document
self.unimplemented_actions = list(map(self.pageAction, self.unimplemented_actions = list(map(self.pageAction,
[d.DownloadImageToDisk, d.OpenLinkInNewWindow, d.DownloadLinkToDisk, [d.DownloadImageToDisk, d.OpenLinkInNewWindow, d.DownloadLinkToDisk,
d.OpenImageInNewWindow, d.OpenLink, d.Reload])) d.OpenImageInNewWindow, d.OpenLink, d.Reload, d.InspectElement]))
self.search_online_action = QAction(QIcon(I('search.png')), '', self)
self.search_online_action.setShortcut(Qt.CTRL+Qt.Key_E)
self.search_online_action.triggered.connect(self.search_online)
self.addAction(self.search_online_action)
self.dictionary_action = QAction(QIcon(I('dictionary.png')), self.dictionary_action = QAction(QIcon(I('dictionary.png')),
_('&Lookup in dictionary'), self) _('&Lookup in dictionary'), self)
self.dictionary_action.setShortcut(Qt.CTRL+Qt.Key_L) self.dictionary_action.setShortcut(Qt.CTRL+Qt.Key_L)
@ -526,6 +534,10 @@ class DocumentView(QWebView): # {{{
self.goto_location_action.setMenu(self.goto_location_menu) self.goto_location_action.setMenu(self.goto_location_menu)
self.grabGesture(Qt.SwipeGesture) self.grabGesture(Qt.SwipeGesture)
self.restore_fonts_action = QAction(_('Normal font size'), self)
self.restore_fonts_action.setCheckable(True)
self.restore_fonts_action.triggered.connect(self.restore_font_size)
def goto_next_section(self, *args): def goto_next_section(self, *args):
if self.manager is not None: if self.manager is not None:
self.manager.goto_next_section() self.manager.goto_next_section()
@ -582,6 +594,15 @@ class DocumentView(QWebView): # {{{
if self.manager is not None: if self.manager is not None:
self.manager.selection_changed(unicode(self.document.selectedText())) self.manager.selection_changed(unicode(self.document.selectedText()))
def _selectedText(self):
t = unicode(self.selectedText()).strip()
if not t:
return u''
if len(t) > 40:
t = t[:40] + u'...'
t = t.replace(u'&', u'&&')
return _("S&earch Google for '%s'")%t
def contextMenuEvent(self, ev): def contextMenuEvent(self, ev):
mf = self.document.mainFrame() mf = self.document.mainFrame()
r = mf.hitTestContent(ev.pos()) r = mf.hitTestContent(ev.pos())
@ -591,17 +612,48 @@ class DocumentView(QWebView): # {{{
menu = self.document.createStandardContextMenu() menu = self.document.createStandardContextMenu()
for action in self.unimplemented_actions: for action in self.unimplemented_actions:
menu.removeAction(action) menu.removeAction(action)
text = unicode(self.selectedText())
if text:
menu.insertAction(list(menu.actions())[0], self.dictionary_action)
menu.insertAction(list(menu.actions())[0], self.search_action)
if not img.isNull(): if not img.isNull():
menu.addAction(self.view_image_action) menu.addAction(self.view_image_action)
menu.addSeparator()
menu.addAction(self.goto_location_action) text = self._selectedText()
if self.document.in_fullscreen_mode and self.manager is not None: if text and img.isNull():
self.search_online_action.setText(text)
menu.addAction(self.search_online_action)
menu.addAction(self.dictionary_action)
menu.addAction(self.search_action)
if not text and img.isNull():
menu.addSeparator() menu.addSeparator()
menu.addAction(self.manager.toggle_toolbar_action) if self.manager.action_back.isEnabled():
menu.addAction(self.manager.action_back)
if self.manager.action_forward.isEnabled():
menu.addAction(self.manager.action_forward)
menu.addAction(self.goto_location_action)
if self.manager is not None:
menu.addSeparator()
menu.addAction(self.manager.action_table_of_contents)
menu.addSeparator()
menu.addAction(self.manager.action_font_size_larger)
self.restore_fonts_action.setChecked(self.multiplier == 1)
menu.addAction(self.restore_fonts_action)
menu.addAction(self.manager.action_font_size_smaller)
menu.addSeparator()
inspectAction = self.pageAction(self.document.InspectElement)
menu.addAction(inspectAction)
if not text and img.isNull() and self.manager is not None:
menu.addSeparator()
if self.document.in_fullscreen_mode and self.manager is not None:
menu.addAction(self.manager.toggle_toolbar_action)
menu.addAction(self.manager.action_full_screen)
menu.addSeparator()
menu.addAction(self.manager.action_quit)
menu.exec_(ev.globalPos()) menu.exec_(ev.globalPos())
def lookup(self, *args): def lookup(self, *args):
@ -616,6 +668,12 @@ class DocumentView(QWebView): # {{{
if t: if t:
self.manager.search.set_search_string(t) self.manager.search.set_search_string(t)
def search_online(self):
t = unicode(self.selectedText()).strip()
if t:
url = 'https://www.google.com/search?q=' + QUrl().toPercentEncoding(t)
open_url(QUrl.fromEncoded(url))
def set_manager(self, manager): def set_manager(self, manager):
self.manager = manager self.manager = manager
self.scrollbar = manager.horizontal_scrollbar self.scrollbar = manager.horizontal_scrollbar
@ -993,6 +1051,11 @@ class DocumentView(QWebView): # {{{
self.multiplier -= amount self.multiplier -= amount
return self.document.scroll_fraction return self.document.scroll_fraction
def restore_font_size(self):
with self.document.page_position:
self.multiplier = 1
return self.document.scroll_fraction
def changeEvent(self, event): def changeEvent(self, event):
if event.type() == event.EnabledChange: if event.type() == event.EnabledChange:
self.update() self.update()

View File

@ -21,7 +21,7 @@ from calibre.gui2 import (Application, ORG_NAME, APP_UID, choose_files,
info_dialog, error_dialog, open_url, available_height) info_dialog, error_dialog, open_url, available_height)
from calibre.ebooks.oeb.iterator.book import EbookIterator from calibre.ebooks.oeb.iterator.book import EbookIterator
from calibre.ebooks import DRMError from calibre.ebooks import DRMError
from calibre.constants import islinux, isbsd, filesystem_encoding from calibre.constants import islinux, filesystem_encoding
from calibre.utils.config import Config, StringConfig, JSONConfig from calibre.utils.config import Config, StringConfig, JSONConfig
from calibre.gui2.search_box import SearchBox2 from calibre.gui2.search_box import SearchBox2
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
@ -160,7 +160,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
PAGED_MODE_TT = _('Switch to flow mode - where the text is not broken up ' PAGED_MODE_TT = _('Switch to flow mode - where the text is not broken up '
'into pages') 'into pages')
def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None): def __init__(self, pathtoebook=None, debug_javascript=False, open_at=None,
start_in_fullscreen=False):
MainWindow.__init__(self, None) MainWindow.__init__(self, None)
self.setupUi(self) self.setupUi(self)
self.view.initialize_view(debug_javascript) self.view.initialize_view(debug_javascript)
@ -178,6 +179,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.pending_restore = False self.pending_restore = False
self.existing_bookmarks= [] self.existing_bookmarks= []
self.selected_text = None self.selected_text = None
self.was_maximized = False
self.read_settings() self.read_settings()
self.dictionary_box.hide() self.dictionary_box.hide()
self.close_dictionary_view.clicked.connect(lambda self.close_dictionary_view.clicked.connect(lambda
@ -207,7 +209,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.view.set_manager(self) self.view.set_manager(self)
self.pi = ProgressIndicator(self) self.pi = ProgressIndicator(self)
self.toc.setVisible(False) self.toc.setVisible(False)
self.action_quit = QAction(self) self.action_quit = QAction(_('&Quit'), self)
self.addAction(self.action_quit) self.addAction(self.action_quit)
self.view_resized_timer = QTimer(self) self.view_resized_timer = QTimer(self)
self.view_resized_timer.timeout.connect(self.viewport_resize_finished) self.view_resized_timer.timeout.connect(self.viewport_resize_finished)
@ -299,6 +301,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
''') ''')
self.window_mode_changed = None self.window_mode_changed = None
self.toggle_toolbar_action = QAction(_('Show/hide controls'), self) self.toggle_toolbar_action = QAction(_('Show/hide controls'), self)
self.toggle_toolbar_action.setCheckable(True)
self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars) self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars)
self.addAction(self.toggle_toolbar_action) self.addAction(self.toggle_toolbar_action)
self.full_screen_label_anim = QPropertyAnimation( self.full_screen_label_anim = QPropertyAnimation(
@ -358,6 +361,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.restore_state() self.restore_state()
self.action_toggle_paged_mode.toggled[bool].connect(self.toggle_paged_mode) self.action_toggle_paged_mode.toggled[bool].connect(self.toggle_paged_mode)
if (start_in_fullscreen or self.view.document.start_in_fullscreen):
self.action_full_screen.trigger()
def toggle_paged_mode(self, checked, at_start=False): def toggle_paged_mode(self, checked, at_start=False):
in_paged_mode = not self.action_toggle_paged_mode.isChecked() in_paged_mode = not self.action_toggle_paged_mode.isChecked()
@ -397,7 +402,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
count += 1 count += 1
def shutdown(self): def shutdown(self):
if self.isFullScreen(): if self.isFullScreen() and not self.view.document.start_in_fullscreen:
self.action_full_screen.trigger() self.action_full_screen.trigger()
return False return False
self.save_state() self.save_state()
@ -421,7 +426,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def save_state(self): def save_state(self):
state = bytearray(self.saveState(self.STATE_VERSION)) state = bytearray(self.saveState(self.STATE_VERSION))
vprefs['viewer_toolbar_state'] = state vprefs['viewer_toolbar_state'] = state
vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry())) if not self.isFullScreen():
vprefs.set('viewer_window_geometry', bytearray(self.saveGeometry()))
if self.current_book_has_toc: if self.current_book_has_toc:
vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible())) vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible()))
if self.toc.isVisible(): if self.toc.isVisible():
@ -488,6 +494,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.window_mode_changed = 'fullscreen' self.window_mode_changed = 'fullscreen'
self.tool_bar.setVisible(False) self.tool_bar.setVisible(False)
self.tool_bar2.setVisible(False) self.tool_bar2.setVisible(False)
self.was_maximized = self.isMaximized()
if not self.view.document.fullscreen_scrollbar: if not self.view.document.fullscreen_scrollbar:
self.vertical_scrollbar.setVisible(False) self.vertical_scrollbar.setVisible(False)
self.frame.layout().setSpacing(0) self.frame.layout().setSpacing(0)
@ -574,14 +581,18 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
om = self._original_frame_margins om = self._original_frame_margins
self.centralwidget.layout().setContentsMargins(om[0]) self.centralwidget.layout().setContentsMargins(om[0])
self.frame.layout().setContentsMargins(om[1]) self.frame.layout().setContentsMargins(om[1])
super(EbookViewer, self).showNormal() if self.was_maximized:
super(EbookViewer, self).showMaximized()
else:
super(EbookViewer, self).showNormal()
def handle_window_mode_toggle(self): def handle_window_mode_toggle(self):
if self.window_mode_changed: if self.window_mode_changed:
fs = self.window_mode_changed == 'fullscreen' fs = self.window_mode_changed == 'fullscreen'
self.window_mode_changed = None self.window_mode_changed = None
if fs: if fs:
self.show_full_screen_label() if self.view.document.show_fullscreen_help:
self.show_full_screen_label()
else: else:
self.view.document.switch_to_window_mode() self.view.document.switch_to_window_mode()
self.view.document.page_position.restore() self.view.document.page_position.restore()
@ -681,13 +692,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def font_size_larger(self): def font_size_larger(self):
self.view.magnify_fonts() self.view.magnify_fonts()
self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
def font_size_smaller(self): def font_size_smaller(self):
self.view.shrink_fonts() self.view.shrink_fonts()
self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
def magnification_changed(self, val): def magnification_changed(self, val):
tt = _('Make font size %(which)s\nCurrent magnification: %(mag).1f') tt = _('Make font size %(which)s\nCurrent magnification: %(mag).1f')
@ -695,6 +702,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
tt %dict(which=_('larger'), mag=val)) tt %dict(which=_('larger'), mag=val))
self.action_font_size_smaller.setToolTip( self.action_font_size_smaller.setToolTip(
tt %dict(which=_('smaller'), mag=val)) tt %dict(which=_('smaller'), mag=val))
self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
def find(self, text, repeat=False, backwards=False): def find(self, text, repeat=False, backwards=False):
if not text: if not text:
@ -1129,32 +1138,29 @@ def main(args=sys.argv):
parser = option_parser() parser = option_parser()
opts, args = parser.parse_args(args) opts, args = parser.parse_args(args)
pid = os.fork() if False and (islinux or isbsd) else -1
try: try:
open_at = float(opts.open_at) open_at = float(opts.open_at)
except: except:
open_at = None open_at = None
if pid <= 0: override = 'calibre-ebook-viewer' if islinux else None
override = 'calibre-ebook-viewer' if islinux else None app = Application(args, override_program_name=override)
app = Application(args, override_program_name=override) app.load_builtin_fonts()
app.load_builtin_fonts() app.setWindowIcon(QIcon(I('viewer.png')))
app.setWindowIcon(QIcon(I('viewer.png'))) QApplication.setOrganizationName(ORG_NAME)
QApplication.setOrganizationName(ORG_NAME) QApplication.setApplicationName(APP_UID)
QApplication.setApplicationName(APP_UID) main = EbookViewer(args[1] if len(args) > 1 else None,
main = EbookViewer(args[1] if len(args) > 1 else None, debug_javascript=opts.debug_javascript, open_at=open_at,
debug_javascript=opts.debug_javascript, open_at=open_at) start_in_fullscreen=opts.full_screen)
# This is needed for paged mode. Without it, the first document that is # This is needed for paged mode. Without it, the first document that is
# loaded will have extra blank space at the bottom, as # loaded will have extra blank space at the bottom, as
# turn_off_internal_scrollbars does not take effect for the first # turn_off_internal_scrollbars does not take effect for the first
# rendered document # rendered document
main.view.load_path(P('viewer/blank.html', allow_user_override=False)) main.view.load_path(P('viewer/blank.html', allow_user_override=False))
sys.excepthook = main.unhandled_exception sys.excepthook = main.unhandled_exception
main.show() main.show()
if opts.raise_window: if opts.raise_window:
main.raise_() main.raise_()
if opts.full_screen:
main.action_full_screen.trigger()
with main: with main:
return app.exec_() return app.exec_()
return 0 return 0

View File

@ -283,6 +283,7 @@ class ImageView(QWidget, ImageDropMixin): # {{{
self.setMinimumSize(QSize(150, 200)) self.setMinimumSize(QSize(150, 200))
ImageDropMixin.__init__(self) ImageDropMixin.__init__(self)
self.draw_border = True self.draw_border = True
self.show_size = False
def setPixmap(self, pixmap): def setPixmap(self, pixmap):
if not isinstance(pixmap, QPixmap): if not isinstance(pixmap, QPixmap):
@ -305,6 +306,7 @@ class ImageView(QWidget, ImageDropMixin): # {{{
if pmap.isNull(): if pmap.isNull():
return return
w, h = pmap.width(), pmap.height() w, h = pmap.width(), pmap.height()
ow, oh = w, h
cw, ch = self.rect().width(), self.rect().height() cw, ch = self.rect().width(), self.rect().height()
scaled, nw, nh = fit_image(w, h, cw, ch) scaled, nw, nh = fit_image(w, h, cw, ch)
if scaled: if scaled:
@ -317,12 +319,22 @@ class ImageView(QWidget, ImageDropMixin): # {{{
p = QPainter(self) p = QPainter(self)
p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform) p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
p.drawPixmap(target, pmap) p.drawPixmap(target, pmap)
pen = QPen()
pen.setWidth(self.BORDER_WIDTH)
p.setPen(pen)
if self.draw_border: if self.draw_border:
pen = QPen()
pen.setWidth(self.BORDER_WIDTH)
p.setPen(pen)
p.drawRect(target) p.drawRect(target)
#p.drawRect(self.rect()) if self.show_size:
sztgt = target.adjusted(0, 0, 0, -4)
f = p.font()
f.setBold(True)
p.setFont(f)
sz = u'\u00a0%d x %d\u00a0'%(ow, oh)
flags = Qt.AlignBottom|Qt.AlignRight|Qt.TextSingleLine
szrect = p.boundingRect(sztgt, flags, sz)
p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
p.setPen(QPen(QColor(255,255,255)))
p.drawText(sztgt, flags, sz)
p.end() p.end()
# }}} # }}}
@ -582,6 +594,9 @@ class HistoryLineEdit(QComboBox): # {{{
self.setInsertPolicy(self.NoInsert) self.setInsertPolicy(self.NoInsert)
self.setMaxCount(10) self.setMaxCount(10)
def setPlaceholderText(self, txt):
return self.lineEdit().setPlaceholderText(txt)
@property @property
def store_name(self): def store_name(self):
return 'lineedit_history_'+self._name return 'lineedit_history_'+self._name

View File

@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
FIELDS = ['all', 'title', 'title_sort', 'author_sort', 'authors', 'comments', FIELDS = ['all', 'title', 'title_sort', 'author_sort', 'authors', 'comments',
'cover', 'formats','id', 'isbn', 'ondevice', 'pubdate', 'publisher', 'cover', 'formats','id', 'isbn', 'library_name','ondevice', 'pubdate', 'publisher',
'rating', 'series_index', 'series', 'size', 'tags', 'timestamp', 'rating', 'series_index', 'series', 'size', 'tags', 'timestamp',
'uuid', 'languages', 'identifiers'] 'uuid', 'languages', 'identifiers']

View File

@ -48,17 +48,21 @@ class CSV_XML(CatalogPlugin):
"Applies to: CSV, XML output formats"))] "Applies to: CSV, XML output formats"))]
def run(self, path_to_output, opts, db, notification=DummyReporter()): def run(self, path_to_output, opts, db, notification=DummyReporter()):
from calibre.library import current_library_name
from calibre.utils.date import isoformat from calibre.utils.date import isoformat
from calibre.utils.html2text import html2text from calibre.utils.html2text import html2text
from lxml import etree
from calibre.utils.logging import default_log as log from calibre.utils.logging import default_log as log
from lxml import etree
self.fmt = path_to_output.rpartition('.')[2] self.fmt = path_to_output.rpartition('.')[2]
self.notification = notification self.notification = notification
current_library = current_library_name()
if getattr(opts, 'library_path', None):
current_library = os.path.basename(opts.library_path)
if opts.verbose: if opts.verbose:
opts_dict = vars(opts) opts_dict = vars(opts)
log("%s(): Generating %s" % (self.name,self.fmt.upper())) log("%s('%s'): Generating %s" % (self.name, current_library, self.fmt.upper()))
if opts.connected_device['is_device_connected']: if opts.connected_device['is_device_connected']:
log(" connected_device: %s" % opts.connected_device['name']) log(" connected_device: %s" % opts.connected_device['name'])
if opts_dict['search_text']: if opts_dict['search_text']:
@ -110,6 +114,8 @@ class CSV_XML(CatalogPlugin):
for field in fields: for field in fields:
if field.startswith('#'): if field.startswith('#'):
item = db.get_field(entry['id'],field,index_is_id=True) item = db.get_field(entry['id'],field,index_is_id=True)
elif field == 'library_name':
item = current_library
elif field == 'title_sort': elif field == 'title_sort':
item = entry['sort'] item = entry['sort']
else: else:
@ -215,6 +221,9 @@ class CSV_XML(CatalogPlugin):
fmt.append(E.format(f.replace(os.sep, '/'))) fmt.append(E.format(f.replace(os.sep, '/')))
record.append(fmt) record.append(fmt)
if 'library_name' in fields:
record.append(E.library_name(current_library))
with open(path_to_output, 'w') as f: with open(path_to_output, 'w') as f:
f.write(etree.tostring(root, encoding='utf-8', f.write(etree.tostring(root, encoding='utf-8',
xml_declaration=True, pretty_print=True)) xml_declaration=True, pretty_print=True))

View File

@ -14,6 +14,7 @@ from calibre import strftime
from calibre.customize import CatalogPlugin from calibre.customize import CatalogPlugin
from calibre.customize.conversion import OptionRecommendation, DummyReporter from calibre.customize.conversion import OptionRecommendation, DummyReporter
from calibre.ebooks import calibre_cover from calibre.ebooks import calibre_cover
from calibre.library import current_library_name
from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.localization import get_lang from calibre.utils.localization import get_lang
@ -208,8 +209,9 @@ class EPUB_MOBI(CatalogPlugin):
build_log = [] build_log = []
build_log.append(u"%s(): Generating %s %sin %s environment, locale: '%s'" % build_log.append(u"%s('%s'): Generating %s %sin %s environment, locale: '%s'" %
(self.name, (self.name,
current_library_name(),
self.fmt, self.fmt,
'for %s ' % opts.output_profile if opts.output_profile else '', 'for %s ' % opts.output_profile if opts.output_profile else '',
'CLI' if opts.cli_environment else 'GUI', 'CLI' if opts.cli_environment else 'GUI',

View File

@ -28,7 +28,8 @@ from calibre.ebooks.metadata.book.base import Metadata
from calibre.constants import preferred_encoding, iswindows, filesystem_encoding from calibre.constants import preferred_encoding, iswindows, filesystem_encoding
from calibre.ptempfile import (PersistentTemporaryFile, from calibre.ptempfile import (PersistentTemporaryFile,
base_dir, SpooledTemporaryFile) base_dir, SpooledTemporaryFile)
from calibre.customize.ui import run_plugins_on_import from calibre.customize.ui import (run_plugins_on_import,
run_plugins_on_postimport)
from calibre import isbytestring from calibre import isbytestring
from calibre.utils.filenames import (ascii_filename, samefile, from calibre.utils.filenames import (ascii_filename, samefile,
WindowsAtomicFolderMove, hardlink_file) WindowsAtomicFolderMove, hardlink_file)
@ -1495,8 +1496,10 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
format = os.path.splitext(npath)[-1].lower().replace('.', '').upper() format = os.path.splitext(npath)[-1].lower().replace('.', '').upper()
stream = lopen(npath, 'rb') stream = lopen(npath, 'rb')
format = check_ebook_format(stream, format) format = check_ebook_format(stream, format)
return self.add_format(index, format, stream, retval = self.add_format(index, format, stream,
index_is_id=index_is_id, path=path, notify=notify) index_is_id=index_is_id, path=path, notify=notify)
run_plugins_on_postimport(self, id, format)
return retval
def add_format(self, index, format, stream, index_is_id=False, path=None, def add_format(self, index, format, stream, index_is_id=False, path=None,
notify=True, replace=True, copy_function=None): notify=True, replace=True, copy_function=None):
@ -3475,6 +3478,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
formats, metadata = iter(formats), iter(metadata) formats, metadata = iter(formats), iter(metadata)
duplicates = [] duplicates = []
ids = [] ids = []
postimport = []
for path in paths: for path in paths:
mi = metadata.next() mi = metadata.next()
self._add_newbook_tag(mi) self._add_newbook_tag(mi)
@ -3506,8 +3510,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
format = check_ebook_format(stream, format) format = check_ebook_format(stream, format)
self.add_format(id, format, stream, index_is_id=True) self.add_format(id, format, stream, index_is_id=True)
stream.close() stream.close()
postimport.append((id, format))
self.conn.commit() self.conn.commit()
self.data.refresh_ids(self, ids) # Needed to update format list and size self.data.refresh_ids(self, ids) # Needed to update format list and size
for book_id, fmt in postimport:
run_plugins_on_postimport(self, book_id, fmt)
if duplicates: if duplicates:
paths = list(duplicate[0] for duplicate in duplicates) paths = list(duplicate[0] for duplicate in duplicates)
formats = list(duplicate[1] for duplicate in duplicates) formats = list(duplicate[1] for duplicate in duplicates)