sync with Kovid's branch

This commit is contained in:
Tomasz Długosz 2012-11-24 11:39:54 +01:00
commit c36f7b5e4d
200 changed files with 99066 additions and 85797 deletions

View File

@ -19,6 +19,89 @@
# new recipes:
# - title:
- version: 0.9.7
date: 2012-11-23
new features:
- title: "Edit metadata dialog: Show the size of the current book cover in the edit metadata dialog."
tickets: [1079781]
- title: "Get Books: Allow easy searching by title and author in addition to any keyword, to prevent large numbers of spurious matches."
- title: "An option to automatically convert any added book to the current output format, found under Preferences->Adding books"
- title: "E-book viewer: Allow viewing tables in a separate popup window by right clicking on the table and selecting 'View table'. Useful for reference books that have lots of large tables."
tickets: [1080710]
- title: "Catalogs: Add the current library name as an available field when generating catalogs in csv/xml format."
tickets: [1078422]
- title: "Enable colored text in the output from the command line tools on windows"
- title: "E-book viewer: Add an option to hide the help message when entering full screen mode"
- title: "E-book viewer: Add an option to always start the viewer in full screen mode"
- title: "E-book viewer: Add many more controls to the context menu, particularly useful in full screen mode"
- title: "E-book viewer: Allow easy searching of the selected word or phrase in google via the context menu"
- title: "Add a new type of FileType plugin, postimport, that runs after a book has been added to the database."
- title: "Get Books: Remove Gandalf store, add Publio store. Update the Legimi store plugin for website changes"
bug fixes:
- title: "Conversion: Correctly handle values of left and right for the deprecated align attribute of images, mapping them to the CSS float property instead of to text-align."
tickets: [1081094]
- title: "MOBI Output: When generating joint MOBI6/KF8 files do not set incorrect display CSS values for tables in the KF8 part"
- title: "Connect to iTunes: Ignore AAC audio files."
tickets: [1081096]
- title: "E-book viewer: Fix restoring from fullscreen not respecting maximized window state"
- title: "Fix rows in the device books view sometimes being too high"
- title: "Catalogs: Fixed a problem occurring when merging comments with a custom field whose type is a list."
- title: "Linux binary: Use exec in the wrapper shell scripts that are used to set env vars and launch calibre utilities."
tickets: [1077884]
- title: "E-book viewer: Fix blank pages after every page when viewing some comic files in paged mode"
- title: "E-book viewer: When printing, respect the specified page range."
tickets: [1074220]
- title: "Font subsetting: Parse the GSUB table for glyph substitution rules and do not remove any glyphs that could act as substitutes. Keep zero length glyphs like the glyphs for non printable characters when subsetting TrueType outlines."
- title: "Smarten punctuation: Fix self closing script tags causing smarten punctuation to fail"
improved recipes:
- Arguments and facts
- Business Standard
- The New Yorker
new recipes:
- title: Various Czech and Hungarian news sources
author: bubak
- title: Various Polish recipes
author: Artur Stachecki
- title: Buchreport
author: a.peter
- title: Red Voltaire
author: atordo
- title: Autosport
author: Mr Stefan
- title: House News
author: Eddie Lau
- version: 0.9.6
date: 2012-11-10

View File

@ -649,20 +649,24 @@ If it still wont launch, start a command prompt (press the windows key and R; th
Post any output you see in a help message on the `Forum <http://www.mobileread.com/forums/forumdisplay.php?f=166>`_.
|app| freezes when I click on anything?
|app| freezes/crashes occasionally?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are three possible things I know of, that can cause this:
* You recently connected an external monitor or TV to your computer. In this case, whenever |app| opens a new window like the edit metadata window or the conversion dialog, it appears on the second monitor where you dont notice it and so you think |app| has frozen. Disconnect your second monitor and restart calibre.
* You recently connected an external monitor or TV to your computer. In
this case, whenever |app| opens a new window like the edit metadata
window or the conversion dialog, it appears on the second monitor where
you dont notice it and so you think |app| has frozen. Disconnect your
second monitor and restart calibre.
* You are using a Wacom branded mouse. There is an incompatibility between Wacom mice and the graphics toolkit |app| uses. Try using a non-Wacom mouse.
* You are using a Wacom branded mouse. There is an incompatibility between
Wacom mice and the graphics toolkit |app| uses. Try using a non-Wacom
mouse.
* If you use RoboForm, it is known to cause |app| to crash. Add |app| to
the blacklist of programs inside RoboForm to fix this.
* Sometimes if some software has installed lots of new files in your fonts folder, |app| can crash until it finishes indexing them. Just start |app|, then leave it alone for about 20 minutes, without clicking on anything. After that you should be able to use |app| as normal.
the blacklist of programs inside RoboForm to fix this. Or uninstall
RoboForm.
|app| is not starting on OS X?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1,5 +1,5 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
__copyright__ = '2010 - 2012, Darko Miletic <darko.miletic at gmail.com>'
'''
www.aif.ru
'''
@ -19,12 +19,19 @@ class AIF_ru(BasicNewsRecipe):
encoding = 'cp1251'
language = 'ru'
publication_type = 'magazine'
extra_css = ' @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: Verdana,Arial,Helvetica,sans1,sans-serif} '
keep_only_tags = [dict(name='div',attrs={'id':'inner'})]
masthead_url = 'http://static3.aif.ru/glossy/index/i/logo.png'
extra_css = """
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
body{font-family: Verdana,Arial,Helvetica,sans1,sans-serif}
img{display: block}
"""
keep_only_tags = [
dict(name='div',attrs={'class':['content-header', 'zoom']})
,dict(name='div',attrs={'id':'article-text'})
]
remove_tags = [
dict(name=['iframe','object','link','base','input','img'])
,dict(name='div',attrs={'class':'photo'})
,dict(name='p',attrs={'class':'resizefont'})
dict(name=['iframe','object','link','base','input','meta'])
,dict(name='div',attrs={'class':'in-topic'})
]
feeds = [(u'News', u'http://www.aif.ru/rss/all.php')]

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')

View File

@ -1,4 +1,3 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe

View File

@ -8,7 +8,6 @@ bankier.pl
'''
from calibre.web.feeds.news import BasicNewsRecipe
import re
class bankier(BasicNewsRecipe):
title = u'Bankier.pl'
@ -35,13 +34,13 @@ class bankier(BasicNewsRecipe):
#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'),
]
(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]

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'
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
__copyright__ = '2009-2012, Darko Miletic <darko.miletic at gmail.com>'
'''
www.business-standard.com
'''
@ -14,10 +14,12 @@ class BusinessStandard(BasicNewsRecipe):
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
auto_cleanup = False
encoding = 'cp1252'
publisher = 'Business Standard Limited'
category = 'news, business, money, india, world'
language = 'en_IN'
masthead_url = 'http://feeds.business-standard.com/images/logo_08.jpg'
conversion_options = {
'comments' : description
@ -26,7 +28,7 @@ class BusinessStandard(BasicNewsRecipe):
,'publisher' : publisher
,'linearize_tables': True
}
keep_only_tags=[dict(attrs={'class':'TableClas'})]
#keep_only_tags=[dict(name='td', attrs={'class':'TableClas'})]
remove_tags = [
dict(name=['object','link','script','iframe','base','meta'])
,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'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'
__author__ = 'faber1971'
description = 'Leading articles on Italy by the best Italian editorials'
language = 'it'
oldest_article = 1
max_articles_per_feed = 100

View File

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

View File

@ -8,7 +8,6 @@ krakow.gazeta.pl
'''
from calibre.web.feeds.news import BasicNewsRecipe
import re
class gw_krakow(BasicNewsRecipe):
title = u'Gazeta.pl Kraków'

View File

@ -8,7 +8,6 @@ warszawa.gazeta.pl
'''
from calibre.web.feeds.news import BasicNewsRecipe
import re
class gw_wawa(BasicNewsRecipe):
title = u'Gazeta.pl Warszawa'

30
recipes/house_news.recipe Normal file
View File

@ -0,0 +1,30 @@
__license__ = 'GPL v3'
__copyright__ = '2012, Eddie Lau'
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipeHouseNews(BasicNewsRecipe):
title = u'House News \u4e3b\u5834\u65b0\u805e'
__author__ = 'Eddie Lau'
publisher = 'House News'
oldest_article = 1
max_articles_per_feed = 100
auto_cleanup = False
language = 'zh'
encoding = 'utf-8'
description = 'http://thehousenews.com'
category = 'Chinese, Blogs, Opinion, News, Hong Kong'
masthead_url = 'http://thehousenews.com/static/images/housebeta.jpg'
extra_css = 'img {display: block; margin-left: auto; margin-right: auto; margin-top: 10px; margin-bottom: 10px; max-height:90%;} p[class=date] {font-size:50%;} div[class=author] {font-size:75%;} p[class=caption] {font-size:50%;}'
feeds = [(u'Latest', u'http://thehousenews.com/rss/')]
keep_only_tags = [dict(name='h1'),
dict(name='div', attrs={'class':['photo']}),
dict(name='p', attrs={'class':'caption'}),
dict(name='div', attrs={'class':'articleTextWrap'}),
dict(name='div', attrs={'class':['author']}),
dict(name='p', attrs={'class':'date'})]
def populate_article_metadata(self, article, soup, first):
if first and hasattr(self, 'add_toc_thumbnail'):
picdiv = soup.find('img')
if picdiv is not None:
self.add_toc_thumbnail(article,picdiv['src'])

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

After

Width:  |  Height:  |  Size: 786 B

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']})]

View File

@ -1,4 +1,3 @@
import re
from calibre.web.feeds.news import BasicNewsRecipe

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'})

View File

@ -63,11 +63,11 @@ class NewYorker(BasicNewsRecipe):
return url.strip()
def get_cover_url(self):
cover_url = None
soup = self.index_to_soup('http://www.newyorker.com/magazine/toc/')
cover_item = soup.find('img',attrs={'id':'inThisIssuePhoto'})
cover_url = "http://www.newyorker.com/images/covers/1925/1925_02_21_p233.jpg"
soup = self.index_to_soup('http://www.newyorker.com/magazine?intcid=magazine')
cover_item = soup.find('div',attrs={'id':'media-count-1'})
if cover_item:
cover_url = 'http://www.newyorker.com' + cover_item['src'].strip()
cover_url = 'http://www.newyorker.com' + cover_item.div.img['src'].strip()
return cover_url
def preprocess_html(self, soup):

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'
__author__ = 'laca'
oldest_article = 7
language = 'en_HUN'
language = 'en_HU'
masthead_url = 'http://www.portfolio.hu/img/sit/angolfejlec2010.jpg'
use_embedded_content = False
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)

View File

@ -8,7 +8,6 @@ http://prawica.net
'''
from calibre.web.feeds.news import BasicNewsRecipe
import re
class prawica_recipe(BasicNewsRecipe):
title = u'prawica.net'

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

@ -6,6 +6,7 @@ __copyright__ = u'2012, Tomasz Dlugosz <tomek3d@gmail.com>'
rybinski.eu
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Rybinski(BasicNewsRecipe):
title = u'Rybinski.eu - economy of the XXI century'

View File

@ -17,6 +17,7 @@ class Sciencenews(BasicNewsRecipe):
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
auto_cleanup = True
timefmt = ' [%A, %d %B, %Y]'
extra_css = '''
@ -31,14 +32,14 @@ class Sciencenews(BasicNewsRecipe):
.credit{color:#A6A6A6;font-family:helvetica,arial ;font-size: xx-small ;}
'''
keep_only_tags = [ dict(name='div', attrs={'id':'column_action'}) ]
remove_tags_after = dict(name='ul', attrs={'id':'content_functions_bottom'})
remove_tags = [
dict(name='ul', attrs={'id':'content_functions_bottom'})
,dict(name='div', attrs={'id':['content_functions_top','breadcrumb_content']})
,dict(name='img', attrs={'class':'icon'})
,dict(name='div', attrs={'class': 'embiggen'})
]
#keep_only_tags = [ dict(name='div', attrs={'id':'column_action'}) ]
#remove_tags_after = dict(name='ul', attrs={'id':'content_functions_bottom'})
#remove_tags = [
#dict(name='ul', attrs={'id':'content_functions_bottom'})
#,dict(name='div', attrs={'id':['content_functions_top','breadcrumb_content']})
#,dict(name='img', attrs={'class':'icon'})
#,dict(name='div', attrs={'class': 'embiggen'})
#]
feeds = [(u"Science News / News Items", u'http://sciencenews.org/index.php/feed/type/news/name/news.rss/view/feed/name/all.rss')]
@ -53,9 +54,9 @@ class Sciencenews(BasicNewsRecipe):
return cover_url
def preprocess_html(self, soup):
#def preprocess_html(self, soup):
for tag in soup.findAll(name=['span']):
tag.name = 'div'
#for tag in soup.findAll(name=['span']):
#tag.name = 'div'
return soup
#return soup

View File

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

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'
__author__ = 'laca'
oldest_article = 7
language = 'en_HUN'
language = 'en_HU'
auto_cleanup = True
masthead_url = 'http://www.xpatloop.com/images/cms/xs_logo.gif'
use_embedded_content = False

View File

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

Binary file not shown.

View File

@ -6,7 +6,7 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, socket, struct, subprocess
import os, socket, struct, subprocess, sys, glob
from distutils.spawn import find_executable
from PyQt4 import pyqtconfig
@ -16,6 +16,7 @@ from setup import isosx, iswindows, islinux
OSX_SDK = '/Developer/SDKs/MacOSX10.5.sdk'
os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.5'
is64bit = sys.maxsize > 2**32
NMAKE = RC = msvc = MT = win_inc = win_lib = win_ddk = win_ddk_lib_dirs = None
if iswindows:
@ -35,7 +36,7 @@ if iswindows:
MT = os.path.join(os.path.dirname(p), 'bin', 'mt.exe')
MT = os.path.join(SDK, 'bin', 'mt.exe')
os.environ['QMAKESPEC'] = 'win32-msvc'
ICU = r'Q:\icu'
ICU = os.environ.get('ICU_DIR', r'Q:\icu')
QMAKE = '/Volumes/sw/qt/bin/qmake' if isosx else 'qmake'
if find_executable('qmake-qt4'):
@ -121,7 +122,8 @@ if iswindows:
zlib_lib_dirs = [sw_lib_dir]
zlib_libs = ['zlib']
magick_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.7.6')]
md = glob.glob(os.path.join(prefix, 'build', 'ImageMagick-*'))[-1]
magick_inc_dirs = [md]
magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')]
magick_libs = ['CORE_RL_wand_', 'CORE_RL_magick_']
podofo_inc = os.path.join(sw_inc_dir, 'podofo')

View File

@ -18,7 +18,7 @@ from setup.build_environment import (chmlib_inc_dirs,
msvc, MT, win_inc, win_lib, win_ddk, magick_inc_dirs, magick_lib_dirs,
magick_libs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs,
icu_lib_dirs, win_ddk_lib_dirs, ft_libs, ft_lib_dirs, ft_inc_dirs,
zlib_libs, zlib_lib_dirs, zlib_inc_dirs)
zlib_libs, zlib_lib_dirs, zlib_inc_dirs, is64bit)
MT
isunix = islinux or isosx or isbsd
@ -278,6 +278,8 @@ if iswindows:
ldflags = '/DLL /nologo /INCREMENTAL:NO /NODEFAULTLIB:libcmt.lib'.split()
#cflags = '/c /nologo /Ox /MD /W3 /EHsc /Zi'.split()
#ldflags = '/DLL /nologo /INCREMENTAL:NO /DEBUG'.split()
if is64bit:
cflags.append('/GS-')
for p in win_inc:
cflags.append('-I'+p)

View File

@ -301,7 +301,7 @@ class LinuxFreeze(Command):
export MAGICK_CONFIGURE_PATH=$lib/{1}/config
export MAGICK_CODER_MODULE_PATH=$lib/{1}/modules-Q16/coders
export MAGICK_CODER_FILTER_PATH=$lib/{1}/modules-Q16/filters
$base/bin/{0} "$@"
exec $base/bin/{0} "$@"
''')
dest = self.j(self.obj_dir, bname+'.o')

View File

@ -6,13 +6,11 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, shutil, subprocess, re
import os, shutil, subprocess
from setup import Command, __appname__, __version__
from setup.installer import VMInstaller
SIGNTOOL = r'C:\cygwin\home\kovid\sign.bat'
class Win(Command):
description = 'Build windows binary installers'
@ -38,11 +36,7 @@ class Win32(VMInstaller):
def sign_msi(self):
print ('Signing installers ...')
raw = open(self.VM).read()
vmx = re.search(r'''launch_vmware\(['"](.+?)['"]''', raw).group(1)
subprocess.check_call(['vmrun', '-T', 'ws', '-gu', 'kovid', '-gp',
"et tu brutus", 'runProgramInGuest', vmx, 'cmd.exe', '/C',
r'C:\cygwin\home\kovid\sign.bat'])
subprocess.check_call(['ssh', self.VM_NAME, '~/sign.sh'], shell=False)
def download_installer(self):
installer = self.installer()

View File

@ -10,18 +10,17 @@ import sys, os, shutil, glob, py_compile, subprocess, re, zipfile, time, textwra
from setup import (Command, modules, functions, basenames, __version__,
__appname__)
from setup.build_environment import msvc, MT, RC
from setup.build_environment import msvc, MT, RC, is64bit
from setup.installer.windows.wix import WixMixIn
ICU_DIR = r'Q:\icu'
OPENSSL_DIR = r'Q:\openssl'
QT_DIR = 'Q:\\Qt\\4.8.2'
ICU_DIR = os.environ.get('ICU_DIR', r'Q:\icu')
OPENSSL_DIR = os.environ.get('OPENSSL_DIR', r'Q:\openssl')
QT_DIR = os.environ.get('QT_DIR', 'Q:\\Qt\\4.8.2')
QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns']
QTCURVE = r'C:\plugins\styles'
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
LIBUNRAR = os.environ.get('UNRARDLL', 'C:\\Program Files\\UnrarDLL\\unrar.dll')
SW = r'C:\cygwin\home\kovid\sw'
IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.7.6',
'VisualMagick', 'bin')
IMAGEMAGICK = os.path.join(SW, 'build',
'ImageMagick-*\\VisualMagick\\bin')
CRT = r'C:\Microsoft.VC90.CRT'
LZMA = r'Q:\easylzma\build\easylzma-0.0.8'
@ -89,8 +88,9 @@ class Win32Freeze(Command, WixMixIn):
self.archive_lib_dir()
self.remove_CRT_from_manifests()
self.create_installer()
self.build_portable()
self.build_portable_installer()
if not is64bit:
self.build_portable()
self.build_portable_installer()
def remove_CRT_from_manifests(self):
'''
@ -262,8 +262,8 @@ class Win32Freeze(Command, WixMixIn):
print
print 'Adding third party dependencies'
print '\tAdding unrar'
shutil.copyfile(LIBUNRAR,
os.path.join(self.dll_dir, os.path.basename(LIBUNRAR)))
shutil.copyfile(LIBUNRAR, os.path.join(self.dll_dir,
os.path.basename(LIBUNRAR).replace('64', '')))
print '\tAdding misc binary deps'
bindir = os.path.join(SW, 'bin')
@ -278,12 +278,15 @@ class Win32Freeze(Command, WixMixIn):
if not ok: continue
dest = self.dll_dir
shutil.copy2(f, dest)
for x in ('zlib1.dll', 'libxml2.dll'):
shutil.copy2(self.j(bindir, x+'.manifest'), self.dll_dir)
for x in ('zlib1.dll', 'libxml2.dll', 'libxslt.dll', 'libexslt.dll'):
msrc = self.j(bindir, x+'.manifest')
if os.path.exists(msrc):
shutil.copy2(msrc, self.dll_dir)
# Copy ImageMagick
impath = glob.glob(IMAGEMAGICK)[-1]
for pat in ('*.dll', '*.xml'):
for f in glob.glob(self.j(IMAGEMAGICK, pat)):
for f in glob.glob(self.j(impath, pat)):
ok = True
for ex in ('magick++', 'x11.dll', 'xext.dll'):
if ex in f.lower(): ok = False

View File

@ -4,16 +4,98 @@ Notes on setting up the windows development environment
Overview
----------
calibre and all its dependencies are compiled using Visual Studio 2008 express edition (free from MS). All the following instructions must be run in a visual studio command prompt unless otherwise noted.
calibre and all its dependencies are compiled using Visual Studio 2008. All the
following instructions must be run in a visual studio command prompt (the
various commands use unix notation, so if you want to use them directly, you
have to setup cygwin).
calibre contains build script to automate the building of the calibre installer. These scripts make certain assumptions about where dependencies are installed. Your best best is to setup a VM and replicate the paths mentioned below exactly.
calibre contains build script to automate the building of the calibre
installer. These scripts make certain assumptions about where dependencies are
installed. Your best best is to setup a VM and replicate the paths mentioned
below exactly.
Microsoft Visual Studio and Windows SDK
----------------------------------------
You have to use Visual Studio 2008 as that is the version Python 2.x works
with.
You need Visual Studio 2008 Express Edition for 32-bit and Professional for 64
bit.
1) Install Visual Studio
2) Install Visual Studio SP1 from http://www.microsoft.com/en-us/download/details.aspx?id=10986
(First check if the version of VS 2008 you have is not already SP1)
3) Install The Windows SDK. You need to install a version that is built for VS
2008. Get it from here: http://www.microsoft.com/en-us/download/details.aspx?id=3138
4) If you are building 64bit, edit the properties of the Visual Studio command
prompt shortcut to pass "amd64" instead of "x86" to the vsvars.bat file so that
it uses the 64 bit tools.
I've read that it is possible to use the 64-bit compiler that comes with the
Windows SDK With VS 2008 Express Edition, but I can't be bothered figuring it
out. Just use the Professional Edition.
Cygwin
------------
This is needed for automation of the build process, and the ease of use of the
unix shell (bash).
Install, vim, rsync, openssh, unzip, wget, make at a minimum.
After installing python run::
python setup/vcvars.py && echo 'source ~/.vcvars' >> ~/.bash_profile
To allow you to use the visual studio tools in the cygwin shell.
The following is only needed for automation (setting up ssh access to the
windows machine).
In order to build debug builds (.pdb files and sign files), you have to be able
to login as the normal user account with ssh. To do this, follow these steps:
* Setup a password for your user account
* Follow the steps here:
http://pcsupport.about.com/od/windows7/ht/auto-logon-windows-7.htm or
http://pcsupport.about.com/od/windowsxp/ht/auto-logon-xp.htm to allow the
machine to bootup without having to enter the password
* First clean out any existing cygwin ssh setup with::
net stop sshd
cygrunsrv -R sshd
net user sshd /DELETE
net user cyg_server /DELETE (delete any other cygwin users account you
can list them with net user)
rm -R /etc/ssh*
mkpasswd -cl > /etc/passwd
mkgroup --local > /etc/group
* Assign the necessary rights to the normal user account::
editrights.exe -a SeAssignPrimaryTokenPrivilege -u kovid
editrights.exe -a SeCreateTokenPrivilege -u kovid
editrights.exe -a SeTcbPrivilege -u kovid
editrights.exe -a SeServiceLogonRight -u kovid
* Run::
ssh-host-config
And answer (yes) to all questions. If it asks do you want to use a
different user name, specify the name of your user account and enter
username and password (it asks on Win 7 not on Win XP)
* On Windows XP, I also had to run::
passwd -R
to allow sshd to use my normal user account even with public key
authentication. See http://cygwin.com/cygwin-ug-net/ntsec.html for
details. On Windows 7 this wasn't necessary for some reason.
* Start sshd with::
net start sshd
* See http://www.kgx.net.nz/2010/03/cygwin-sshd-and-windows-7/ for details
Pass port 22 through Windows firewall. Create ~/.ssh/authorized_keys
Basic dependencies
--------------------
Install cygwin and setup sshd (optional). Used to enable automation of the calibre build VM from linux, not needed if you are building manually.
Install cmake, python, WiX (WiX is used to generate the .msi installer)
Install MS Visual Studio 2008, cmake, python and WiX.
You have to
Set CMAKE_PREFIX_PATH environment variable to C:\cygwin\home\kovid\sw
@ -21,10 +103,16 @@ This is where all dependencies will be installed.
Add C:\Python27\Scripts and C:\Python27 to PATH
Edit mimetypes.py in C:\Python27\Lib and set _winreg = None to prevent reading of mimetypes from the windows registry
Edit mimetypes.py in C:\Python27\Lib and set _winreg = None to prevent reading
of mimetypes from the windows registry
Install setuptools from http://pypi.python.org/pypi/setuptools
If there are no windows binaries already compiled for the version of python you are using then download the source and run the following command in the folder where the source has been unpacked::
Python packages
------------------
Install setuptools from http://pypi.python.org/pypi/setuptools If there are no
windows binaries already compiled for the version of python you are using then
download the source and run the following command in the folder where the
source has been unpacked::
python setup.py install
@ -32,10 +120,9 @@ Run the following command to install python dependencies::
easy_install --always-unzip -U mechanize pyreadline python-dateutil dnspython cssutils clientform pycrypto cssselect
Install BeautifulSoup 3.0.x manually into site-packages (3.1.x parses broken HTML very poorly)
Install pywin32 and edit win32com\__init__.py setting _frozen = True and
__gen_path__ to a temp dir (otherwise it tries to set it to a dir in the install tree which leads to permission errors)
__gen_path__ to a temp dir (otherwise it tries to set it to a dir in the
install tree which leads to permission errors)
Note that you should use::
import tempfile
@ -43,42 +130,58 @@ Note that you should use::
tempfile.gettempdir(), "gen_py",
"%d.%d" % (sys.version_info[0], sys.version_info[1]))
Use gettempdir instead of the win32 api method as gettempdir returns a temp dir that is guaranteed to actually work.
Use gettempdir instead of the win32 api method as gettempdir returns a temp dir
that is guaranteed to actually work.
Also edit win32com\client\gencache.py and change the except IOError on line 57 to catch all exceptions.
Also edit win32com\client\gencache.py and change the except IOError on line 57
to catch all exceptions.
SQLite
---------
Put sqlite3*.h from the sqlite windows amlgamation in ~/sw/include
Put sqlite3*.h from the sqlite windows amalgamation in ~/sw/include
APSW
-----
Download source from http://code.google.com/p/apsw/downloads/list and run in visual studio prompt
python setup.py fetch --all build --missing-checksum-ok --enable-all-extensions install test
python setup.py fetch --all --missing-checksum-ok build --enable-all-extensions install test
OpenSSL
--------
First install ActiveState Perl if you dont already have perl in windows
Download and untar the openssl tarball, follow the instructions in INSTALL.W32 (use no-asm)
Then, get nasm.exe from
http://www.nasm.us/pub/nasm/releasebuilds/2.05/nasm-2.05-win32.zip and put it
somewhere on your PATH (I chose ~/sw/bin)
Download and untar the openssl tarball, follow the instructions in INSTALL.(W32|W64)
to install use prefix q:\openssl
perl Configure VC-WIN32 no-asm enable-static-engine --prefix=Q:/openssl
ms\do_ms.bat
nmake -f ms\ntdll.mak
nmake -f ms\ntdll.mak test
nmake -f ms\ntdll.mak install
For 32-bit::
perl Configure VC-WIN32 no-asm enable-static-engine --prefix=Q:/openssl
ms\do_ms.bat
nmake -f ms\ntdll.mak
nmake -f ms\ntdll.mak test
nmake -f ms\ntdll.mak install
For 64-bit::
perl Configure VC-WIN64A no-asm enable-static-engine --prefix=C:/cygwin/home/kovid/sw/private/openssl
ms\do_win64a
nmake -f ms\ntdll.mak
nmake -f ms\ntdll.mak test
nmake -f ms\ntdll.mak install
Qt
--------
Download Qt sourcecode (.zip) from: http://qt-project.org/downloads
Extract Qt sourcecode to C:\Qt\current
Extract Qt sourcecode to C:\Qt\4.x.x.
Qt uses its own routine to locate and load "system libraries" including the openssl libraries needed for "Get Books". This means that we have to apply the following patch to have Qt load the openssl libraries bundled with calibre:
Qt uses its own routine to locate and load "system libraries" including the
openssl libraries needed for "Get Books". This means that we have to apply the
following patch to have Qt load the openssl libraries bundled with calibre:
--- src/corelib/plugin/qsystemlibrary.cpp 2011-02-22 05:04:00.000000000 -0700
@ -97,7 +200,7 @@ Now, run configure and make::
-no-plugin-manifests is needed so that loading the plugins does not fail looking for the CRT assembly
configure -ltcg -opensource -release -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -no-plugin-manifests -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake
./configure.exe -ltcg -opensource -release -qt-zlib -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -nomake tools -no-plugin-manifests -openssl -I $OPENSSL_DIR/include -L $OPENSSL_DIR/lib && nmake
Add the path to the bin folder inside the Qt dir to your system PATH.
@ -106,9 +209,7 @@ SIP
Available from: http://www.riverbankcomputing.co.uk/software/sip/download ::
python configure.py -p win32-msvc2008
nmake
nmake install
python configure.py -p win32-msvc2008 && nmake && nmake install
PyQt4
----------
@ -119,15 +220,6 @@ Compiling instructions::
nmake
nmake install
Python Imaging Library
------------------------
Install as normal using installer at http://www.lfd.uci.edu/~gohlke/pythonlibs/
Test it on the target system with
calibre-debug -c "import _imaging, _imagingmath, _imagingft, _imagingcms"
ICU
-------
@ -151,71 +243,63 @@ Optionally run make check
Libunrar
----------
http://www.rarlab.com/rar/UnRARDLL.exe install and add C:\Program Files\UnrarDLL to PATH
Get the source from http://www.rarlab.com/rar_add.htm
lxml
------
Open UnrarDll.vcproj, change build type to release.
If building 64 bit change Win32 to x64.
http://pypi.python.org/pypi/lxml
Build the Solution, find the dll in the build subdir. As best as I can tell,
the vcproj already defines the SILENT preprocessor directive, but you should
test this.
jpeg-7
-------
.. http://www.rarlab.com/rar/UnRARDLL.exe install and add C:\Program Files\UnrarDLL to PATH
Copy::
jconfig.vc to jconfig.h, makejsln.vc9 to jpeg.sln,
makeasln.vc9 to apps.sln, makejvcp.vc9 to jpeg.vcproj,
makecvcp.vc9 to cjpeg.vcproj, makedvcp.vc9 to djpeg.vcproj,
maketvcp.vc9 to jpegtran.vcproj, makervcp.vc9 to rdjpgcom.vcproj, and
makewvcp.vc9 to wrjpgcom.vcproj. (Note that the renaming is critical!)
Load jpeg.sln in Visual Studio
Goto Project->Properties->General Properties and change Configuration Type to dll
Add
#define USE_WINDOWS_MESSAGEBOX
to jconfig.h (this will cause error messages to show up in a box)
Change the definitions of GLOBAL and EXTERN in jmorecfg.h to
#define GLOBAL(type) __declspec(dllexport) type
#define EXTERN(type) extern __declspec(dllexport) type
cp build/jpeg-7/Release/jpeg.dll bin/
cp build/jpeg-7/Release/jpeg.lib build/jpeg-7/Release/jpeg.exp
cp build/jpeg-7/jerror.h build/jpeg-7/jpeglib.h build/jpeg-7/jconfig.h build/jpeg-7/jmorecfg.h include/
TODO: 64-bit check that SILENT is defined and that the ctypes bindings actuall
work
zlib
------
nmake -f win32/Makefile.msc
nmake -f win32/Makefile.msc test
Build with::
nmake -f win32/Makefile.msc
nmake -f win32/Makefile.msc test
cp zlib1.dll* ../../bin
cp zlib.lib zdll.* ../../lib
cp zconf.h zlib.h ../../include
cp zlib1.dll* ../../bin
cp zlib.lib zdll.* ../../lib
cp zconf.h zlib.h ../../include
jpeg-8
-------
Get the source code from: http://sourceforge.net/projects/libjpeg-turbo/files/
Run::
chmod +x cmakescripts/* && cd build
cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DWITH_JPEG8=1 ..
nmake
cp sharedlib/jpeg8.dll* ~/sw/bin/
cp sharedlib/jpeg.lib ~/sw/lib/
cp jconfig.h ../jerror.h ../jpeglib.h ../jmorecfg.h ~/sw/include
libpng
---------
cp scripts/CMakelists.txt .
mkdir build
Run cmake-gui.exe with source directory . and build directory build
You will have to point to sw/lib/zdll.lib and sw/include for zlib
Also disable PNG_NO_STDIO and PNG_NO_CONSOLE_IO
Download the libpng .zip source file from:
http://www.libpng.org/pub/png/libpng.html
Now open PNG.sln in VS2008
Set Build type to Release
cp build/libpng-1.2.40/build/Release/libpng12.dll bin/
cp build/libpng-1.2.40/build/Release/png12.* lib/
cp build/libpng-1.2.40/png.h build/libpng-1.2.40/pngconf.h include/
Run::
mkdir build && cd build
cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DZLIB_INCLUDE_DIR=C:/cygwin/home/kovid/sw/include -DZLIB_LIBRARY=C:/cygwin/home/kovid/sw/lib/zdll.lib ..
nmake
cp libpng*.dll ~/sw/bin/
cp libpng*.lib ~/sw/lib/
cp pnglibconf.h ../png.h ../pngconf.h ~/sw/include/
freetype
-----------
Get the .zip source from: http://download.savannah.gnu.org/releases/freetype/
Edit *all copies* of the file ftoption.h and add to generate a .lib
and a correct dll
@ -225,42 +309,143 @@ and a correct dll
VS 2008 .sln file is present, open it
Change active build type to release mutithreaded
* If you are doing x64 build, click the Win32 dropdown, select
Configuration manager->Active solution platform -> New -> x64
Project->Properties->Configuration Properties
change configuration type to dll
* Change active build type to release mutithreaded
cp build/freetype-2.3.9/objs/release_mt/freetype.dll bin/
* Project->Properties->Configuration Properties change configuration type
to dll and build solution
cp "`find . -name *.dll`" ~/sw/bin/
cp "`find . -name freetype.lib`" ~/sw/lib/
Now change configuration back to static for .lib and build solution
cp "`find . -name freetype*MT.lib`" ~/sw/lib/
Now change configuration back to static for .lib
cp build/freetype-2.3.9/objs/win32/vc2008/freetype239MT.lib lib/
cp -rf build/freetype-2.3.9/include/* include/
cp -rf include/* ~/sw/include/
TODO: Test if this bloody thing actually works on 64 bit (apparently freetype
assumes sizeof(long) == sizeof(ptr) which is not true in Win64. See for
example: http://forum.openscenegraph.org/viewtopic.php?t=2880
expat
--------
Has a VC 6 project file expat.dsw
Get from: http://sourceforge.net/projects/expat/files/expat/
Set active build to Relase and change build type to dll
Apparently expat requires stdint.h which VS 2008 does not have. So we get our
own.
cp build/expat-2.0.1/win32/bin/Release/*.lib lib/
cp build/expat-2.0.1/win32/bin/Release/*.exp lib/
cp build/expat-2.0.1/win32/bin/Release/*.dll bin/
cp build/expat-2.0.1/lib/expat.h build/expat-2.0.1/lib/expat_external.h include/
Run::
cd lib
wget http://msinttypes.googlecode.com/svn/trunk/stdint.h
mkdir build && cd build
cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release ..
nmake
cp expat.dll ~/sw/bin/ && cp expat.lib ~/sw/lib/
cp ../lib/expat.h ../lib/expat_external.h ~/sw/include
libiconv
----------
Run::
mkdir vs2008 && cd vs2008
Then follow these instructions:
http://www.codeproject.com/Articles/302012/How-to-Build-libiconv-with-Microsoft-Visual-Studio
Change the type to Release and config to x64 or Win32 and Build solution and
then::
cp "`find . -name *.dll`" ~/sw/bin/
cp "`find . -name *.dll.manifest`" ~/sw/bin/
cp "`find . -name *.lib`" ~/sw/lib/iconv.lib
cp "`find . -name iconv.h`" ~/sw/include/
Information for using a static version of libiconv is at the link above.
libxml2
-------------
cd win32
cscript configure.js include=C:\cygwin\home\kovid\sw\include lib=C:\cygwin\home\sw\lib prefix=C:\cygwin\home\kovid\sw zlib=yes iconv=no
nmake /f Makefile.msvc
nmake /f Makefile.msvc install
mv lib/libxml2.dll bin/
cp ./build/libxml2-2.7.5/win32/bin.msvc/*.manifest bin/
Get it from: ftp://xmlsoft.org/libxml2/
Run::
cd win32
cscript.exe configure.js include=C:/cygwin/home/kovid/sw/include lib=C:/cygwin/home/kovid/sw/lib prefix=C:/cygwin/home/kovid/sw zlib=yes iconv=yes
nmake /f Makefile.msvc
mkdir -p ~/sw/include/libxml2/libxml
cp include/libxml/*.h ~/sw/include/libxml2/libxml/
find . -type f \( -name "*.dll" -o -name "*.dll.manifest" \) -exec cp "{}" ~/sw/bin/ \;
find . -name libxml2.lib -exec cp "{}" ~/sw/lib/ \;
libxslt
---------
Get it from: ftp://xmlsoft.org/libxml2/
Run::
cd win32
cscript.exe configure.js include=C:/cygwin/home/kovid/sw/include include=C:/cygwin/home/kovid/sw/include/libxml2 lib=C:/cygwin/home/kovid/sw/lib prefix=C:/cygwin/home/kovid/sw zlib=yes iconv=yes
nmake /f Makefile.msvc
mkdir -p ~/sw/include/libxslt ~/sw/include/libexslt
cp libxslt/*.h ~/sw/include/libxslt/
cp libexslt/*.h ~/sw/include/libexslt/
find . -type f \( -name "*.dll" -o -name "*.dll.manifest" \) -exec cp "{}" ~/sw/bin/ \;
find . -name lib*xslt.lib -exec cp "{}" ~/sw/lib/ \;
lxml
------
Get the source from: http://pypi.python.org/pypi/lxml
Add the following to the top of setupoptions.py::
if option == 'cflags':
return ['-IC:/cygwin/home/kovid/sw/include/libxml2',
'-IC:/cygwin/home/kovid/sw/include']
else:
return ['-LC:/cygwin/home/kovid/sw/lib']
Then, edit src/lxml/includes/etree_defs.h and change the section starting with
#ifndef LIBXML2_NEW_BUFFER
to
#ifdef LIBXML2_NEW_BUFFER
# define xmlBufContent(buf) xmlBufferContent(buf)
# define xmlBufLength(buf) xmlBufferLength(buf)
#endif
Run::
python setup.py install
Python Imaging Library
------------------------
For 32-bit:
Install as normal using installer at http://www.lfd.uci.edu/~gohlke/pythonlibs/
For 64-bit:
Download from http://pypi.python.org/pypi/Pillow/
Edit setup.py setting the ROOT values, like this::
SW = r'C:\cygwin\home\kovid\sw'
JPEG_ROOT = ZLIB_ROOT = FREETYPE_ROOT = (SW+r'\lib', SW+r'\include')
Build and install with::
python setup.py build
python setup.py install
Note that the lcms module will not be built. PIL requires lcms-1.x but only
lcms-2.x can be compiled as a 64 bit library.
Test it on the target system with
calibre-debug -c "from PIL import Image; import _imaging, _imagingmath, _imagingft"
kdewin32-msvc
----------------
I dont think this is needed any more, I've left it here just in case I'm wrong.
Get it from http://www.winkde.org/pub/kde/ports/win32/repository/kdesupport/
mkdir build
Run cmake
@ -279,29 +464,34 @@ cp build/kdewin32-msvc-0.3.9/include/*.h include/
poppler
-------------
In Cmake: disable GTK, Qt, OPenjpeg, cpp, lcms, gtk_tests, qt_tests. Enable qt4, jpeg, png and zlib
mkdir build
NOTE: poppler must be built as a static library, unless you build the qt4 bindings
Run the cmake GUI which will find the various dependencies automatically.
On 64 bit cmake might not let you choose Visual Studio 2008, in whcih case
leave the source field blank, click configure choose Visual Studio 2008 and
then enter the source field.
cp build/utils/Release/*.exe ../../bin/
In Cmake: disable GTK, Qt, OPenjpeg, cpp, lcms, gtk_tests, qt_tests. Enable
jpeg, png and zlib::
cp build/utils/Release/*.exe ../../bin/
podofo
----------
Download from http://podofo.sourceforge.net/download.html
Add the following three lines near the top of CMakeLists.txt
SET(WANT_LIB64 FALSE)
SET(PODOFO_BUILD_SHARED TRUE)
SET(PODOFO_BUILD_STATIC FALSE)
cp build/podofo-*/build/src/Release/podofo.dll bin/
cp build/podofo-*/build/src/Release/podofo.lib lib/
cp build/podofo-*/build/src/Release/podofo.exp lib/
cp build/podofo-*/build/podofo_config.h include/podofo/
cp -r build/podofo-*/src/* include/podofo/
You have to use >=0.9.1
Run::
cp "`find . -name *.dll`" ~/sw/bin/
cp "`find . -name *.lib`" ~/sw/lib/
mkdir ~/sw/include/podofo
cp build/podofo_config.h ~/sw/include/podofo
cp -r src/* ~/sw/include/podofo/
ImageMagick
@ -324,7 +514,7 @@ Undefine ProvideDllMain and MAGICKCORE_X11_DELEGATE
Now open VisualMagick/VisualDynamicMT.sln set to Release
Remove the CORE_xlib, UTIL_Imdisplay and CORE_Magick++ projects.
F7 for build project, you will get one error due to the removal of xlib, ignore
F7 for build solution, you will get one error due to the removal of xlib, ignore
it.
netifaces
@ -334,10 +524,10 @@ Download the source tarball from http://alastairs-place.net/projects/netifaces/
Rename netifaces.c to netifaces.cpp and make the same change in setup.py
Run
Run::
python setup.py build
cp `find build/ -name *.pyd` /cygdrive/c/Python27/Lib/site-packages/
python setup.py build
cp build/lib.win32-2.7/netifaces.pyd /cygdrive/c/Python27/Lib/site-packages/
psutil
--------
@ -352,11 +542,23 @@ cp -r build/lib.win32-*/* /cygdrive/c/Python27/Lib/site-packages/
easylzma
----------
This is only needed to build the portable installer.
Get it from http://lloyd.github.com/easylzma/ (use the trunk version)
Run cmake and build the Visual Studio solution (generates CLI tools and dll and
static lib automatically)
chmlib
-------
Download the zip source code from: http://www.jedrea.com/chmlib/
Run::
cd src && unzip ./ChmLib-ds6.zip
Then open ChmLib.dsw in Visual Studio, change the configuration to Release
(Win32|x64) and build solution, this will generate a static library in
Release/ChmLib.lib
calibre
---------

View File

@ -217,19 +217,15 @@ wchar_t* get_app_dirw() {
void load_python_dll() {
char *app_dir, *fc_dir, *fc_file, *dll_dir, *qt_plugin_dir;
char *app_dir, *dll_dir, *qt_plugin_dir;
size_t l;
app_dir = get_app_dir();
l = strlen(app_dir)+20;
dll_dir = (char*) calloc(l, sizeof(char));
fc_dir = (char*) calloc(l, sizeof(char));
fc_file = (char*) calloc(l, sizeof(char));
qt_plugin_dir = (char*) calloc(l, sizeof(char));
if (!dll_dir || !qt_plugin_dir || !fc_dir) ExitProcess(_show_error(L"Out of memory", L"", 1));
if (!dll_dir || !qt_plugin_dir) ExitProcess(_show_error(L"Out of memory", L"", 1));
_snprintf_s(dll_dir, l, _TRUNCATE, "%sDLLs", app_dir);
_snprintf_s(fc_dir, l, _TRUNCATE, "%sfontconfig", app_dir);
_snprintf_s(fc_file, l, _TRUNCATE, "%s\\fonts.conf", fc_dir);
_snprintf_s(qt_plugin_dir, l, _TRUNCATE, "%sqt_plugins", app_dir);
free(app_dir);
@ -237,8 +233,6 @@ void load_python_dll() {
_putenv_s("MAGICK_CONFIGURE_PATH", dll_dir);
_putenv_s("MAGICK_CODER_MODULE_PATH", dll_dir);
_putenv_s("MAGICK_FILTER_MODULE_PATH", dll_dir);
_putenv_s("FC_CONFIG_DIR", fc_dir);
_putenv_s("FC_CONFIG_FILE", fc_file);
_putenv_s("QT_PLUGIN_PATH", qt_plugin_dir);
if (!SetDllDirectoryA(dll_dir)) ExitProcess(show_last_error(L"Failed to set DLL directory."));

File diff suppressed because it is too large Load Diff

View File

@ -18,14 +18,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2012-09-04 18:42+0000\n"
"Last-Translator: SimonFS <simonschuette@arcor.de>\n"
"PO-Revision-Date: 2012-11-08 15:28+0000\n"
"Last-Translator: Elmux <bla.mail@gmx.net>\n"
"Language-Team: German <debian-l10n-german@lists.debian.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-09-05 04:37+0000\n"
"X-Generator: Launchpad (build 15901)\n"
"X-Launchpad-Export-Date: 2012-11-09 04:39+0000\n"
"X-Generator: Launchpad (build 16250)\n"
"Language: de\n"
#. name for aaa
@ -58,7 +58,7 @@ msgstr "Ambrak"
#. name for aah
msgid "Arapesh; Abu'"
msgstr ""
msgstr "Arapesh;Abu' (Papua-Neuguinea)"
#. name for aai
msgid "Arifama-Miniafia"
@ -102,7 +102,7 @@ msgstr "Aasáx"
#. name for aat
msgid "Albanian; Arvanitika"
msgstr ""
msgstr "Albanisch, Arvanitikanisch"
#. name for aau
msgid "Abau"

82
setup/vcvars.py Normal file
View File

@ -0,0 +1,82 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, sys, subprocess
from distutils.msvc9compiler import find_vcvarsall, get_build_version
plat = 'amd64' if sys.maxsize > 2**32 else 'x86'
def remove_dups(variable):
old_list = variable.split(os.pathsep)
new_list = []
for i in old_list:
if i not in new_list:
new_list.append(i)
return os.pathsep.join(new_list)
def query_process(cmd):
result = {}
popen = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
try:
stdout, stderr = popen.communicate()
if popen.wait() != 0:
raise RuntimeError(stderr.decode("mbcs"))
stdout = stdout.decode("mbcs")
for line in stdout.splitlines():
if '=' not in line:
continue
line = line.strip()
key, value = line.split('=', 1)
key = key.lower()
if key == 'path':
if value.endswith(os.pathsep):
value = value[:-1]
value = remove_dups(value)
result[key] = value
finally:
popen.stdout.close()
popen.stderr.close()
return result
def query_vcvarsall():
vcvarsall = find_vcvarsall(get_build_version())
return query_process('"%s" %s & set' % (vcvarsall, plat))
env = query_vcvarsall()
paths = env['path'].split(';')
lib = env['lib']
include = env['include']
libpath = env['libpath']
def unix(paths):
up = []
for p in paths:
prefix, p = p.replace(os.sep, '/').partition('/')[0::2]
up.append('/cygdrive/%s/%s'%(prefix[0].lower(), p))
return ':'.join(up)
raw = '''\
#!/bin/sh
export PATH="%s:$PATH"
export LIB="%s"
export INCLUDE="%s"
export LIBPATH="%s"
'''%(unix(paths), lib, include, libpath)
with open(os.path.expanduser('~/.vcvars'), 'wb') as f:
f.write(raw.encode('utf-8'))

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = u'calibre'
numeric_version = (0, 9, 6)
numeric_version = (0, 9, 7)
__version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
@ -14,14 +14,6 @@ Various run time constants.
import sys, locale, codecs, os, importlib, collections
_tc = None
def terminal_controller():
global _tc
if _tc is None:
from calibre.utils.terminfo import TerminalController
_tc = TerminalController(sys.stdout)
return _tc
_plat = sys.platform.lower()
iswindows = 'win32' in _plat or 'win64' in _plat
isosx = 'darwin' in _plat
@ -37,6 +29,8 @@ isportable = os.environ.get('CALIBRE_PORTABLE_BUILD', None) is not None
ispy3 = sys.version_info.major > 2
isxp = iswindows and sys.getwindowsversion().major < 6
isworker = os.environ.has_key('CALIBRE_WORKER') or os.environ.has_key('CALIBRE_SIMPLE_WORKER')
if isworker:
os.environ.pop('CALIBRE_FORCE_ANSI', None)
try:
preferred_encoding = locale.getpreferredencoding()

View File

@ -308,6 +308,10 @@ class FileTypePlugin(Plugin): # {{{
#: to the database
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
on_preprocess = False
@ -337,6 +341,16 @@ class FileTypePlugin(Plugin): # {{{
# Default implementation does nothing
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): # {{{
@ -447,8 +461,8 @@ class CatalogPlugin(Plugin): # {{{
# Return a list of requested fields, with opts.sort_by first
all_std_fields = set(
['author_sort','authors','comments','cover','formats',
'id','isbn','ondevice','pubdate','publisher','rating',
'series_index','series','size','tags','timestamp',
'id','isbn','library_name','ondevice','pubdate','publisher',
'rating','series_index','series','size','tags','timestamp',
'title_sort','title','uuid','languages','identifiers'])
all_custom_fields = set(db.custom_field_keys())
for field in list(all_custom_fields):
@ -460,6 +474,16 @@ class CatalogPlugin(Plugin): # {{{
if opts.fields != 'all':
# Make a list from opts.fields
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)
else:
fields = list(all_fields)

View File

@ -1433,15 +1433,6 @@ class StoreFoylesUKStore(StoreBase):
formats = ['EPUB', 'PDF']
affiliate = True
class StoreGandalfStore(StoreBase):
name = 'Gandalf'
author = u'Tomasz Długosz'
description = u'Księgarnia internetowa Gandalf.'
actual_plugin = 'calibre.gui2.store.stores.gandalf_plugin:GandalfStore'
headquarters = 'PL'
formats = ['EPUB', 'PDF']
class StoreGoogleBooksStore(StoreBase):
name = 'Google Books'
description = u'Google Books'
@ -1472,7 +1463,7 @@ class StoreKoboStore(StoreBase):
class StoreLegimiStore(StoreBase):
name = 'Legimi'
author = u'Tomasz Długosz'
description = u'Tanie oraz darmowe ebooki, egazety i blogi w formacie EPUB, wprost na Twój e-czytnik, iPhone, iPad, Android i komputer'
description = u'Ebooki w formacie EPUB, MOBI i PDF'
actual_plugin = 'calibre.gui2.store.stores.legimi_plugin:LegimiStore'
headquarters = 'PL'
@ -1566,6 +1557,15 @@ class StorePragmaticBookshelfStore(StoreBase):
headquarters = 'US'
formats = ['EPUB', 'MOBI', 'PDF']
class StorePublioStore(StoreBase):
name = 'Publio'
description = u'Publio.pl to księgarnia internetowa, w której mogą Państwo nabyć e-booki i audiobooki.'
actual_plugin = 'calibre.gui2.store.stores.publio_plugin:PublioStore'
author = u'Tomasz Długosz'
headquarters = 'PL'
formats = ['EPUB', 'MOBI', 'PDF']
class StoreRW2010Store(StoreBase):
name = 'RW2010'
description = u'Polski serwis self-publishingowy. Pliki PDF, EPUB i MOBI. Maksymalna cena utworu nie przekracza u nas 10 złotych!'
@ -1675,7 +1675,6 @@ plugins += [
StoreEscapeMagazineStore,
StoreFeedbooksStore,
StoreFoylesUKStore,
StoreGandalfStore,
StoreGoogleBooksStore,
StoreGutenbergStore,
StoreKoboStore,
@ -1689,6 +1688,7 @@ plugins += [
StoreOpenBooksStore,
StoreOzonRUStore,
StorePragmaticBookshelfStore,
StorePublioStore,
StoreRW2010Store,
StoreSmashwordsStore,
StoreVirtualoStore,
@ -1716,7 +1716,7 @@ if __name__ == '__main__':
ret = 0
for x in ('lxml', 'calibre.ebooks.BeautifulSoup', 'uuid',
'calibre.utils.terminfo', 'calibre.utils.magick', 'PIL', 'Image',
'calibre.utils.terminal', 'calibre.utils.magick', 'PIL', 'Image',
'sqlite3', 'mechanize', 'httplib', 'xml'):
if x in sys.modules:
ret = 1

View File

@ -104,14 +104,17 @@ def is_disabled(plugin):
# File type plugins {{{
_on_import = {}
_on_postimport = {}
_on_preprocess = {}
_on_postprocess = {}
def reread_filetype_plugins():
global _on_import
global _on_postimport
global _on_preprocess
global _on_postprocess
_on_import = {}
_on_postimport = {}
_on_preprocess = {}
_on_postprocess = {}
@ -122,6 +125,10 @@ def reread_filetype_plugins():
if not _on_import.has_key(ft):
_on_import[ft] = []
_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 not _on_preprocess.has_key(ft):
_on_preprocess[ft] = []
@ -163,6 +170,22 @@ run_plugins_on_preprocess = functools.partial(_run_filetype_plugins,
occasion='preprocess')
run_plugins_on_postprocess = functools.partial(_run_filetype_plugins,
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 {{{

View File

@ -15,7 +15,13 @@ def option_parser():
parser = OptionParser(usage='''\
%prog [options]
Run an embedded python interpreter.
Various command line interfaces useful for debugging calibre. With no options,
this command starts an embedded python interpreter. You can also run the main
calibre GUI and the calibre viewer in debug mode.
It also contains interfaces to various bits of calibre that do not have
dedicated command line tools, such as font subsetting, tweaking ebooks and so
on.
''')
parser.add_option('-c', '--command', help='Run python code.', default=None)
parser.add_option('-e', '--exec-file', default=None, help='Run the python code in file.')
@ -37,9 +43,6 @@ Run an embedded python interpreter.
help='Run the ebook viewer',)
parser.add_option('--paths', default=False, action='store_true',
help='Output the paths necessary to setup the calibre environment')
parser.add_option('--migrate', action='store_true', default=False,
help='Migrate old database. Needs two arguments. Path '
'to library1.db and path to new library folder.')
parser.add_option('--add-simple-plugin', default=None,
help='Add a simple plugin (i.e. a plugin that consists of only a '
'.py file), by specifying the path to the py file containing the '
@ -118,28 +121,6 @@ def reinit_db(dbpath, callback=None, sql_dump=None):
os.remove(dest)
prints('Database successfully re-initialized')
def migrate(old, new):
from calibre.utils.config import prefs
from calibre.library.database import LibraryDatabase
from calibre.library.database2 import LibraryDatabase2
from calibre.utils.terminfo import ProgressBar
from calibre.constants import terminal_controller
class Dummy(ProgressBar):
def setLabelText(self, x): pass
def setAutoReset(self, y): pass
def reset(self): pass
def setRange(self, min, max):
self.min = min
self.max = max
def setValue(self, val):
self.update(float(val)/getattr(self, 'max', 1))
db = LibraryDatabase(old)
db2 = LibraryDatabase2(new)
db2.migrate_old(db, Dummy(terminal_controller(), 'Migrating database...'))
prefs['library_path'] = os.path.abspath(new)
print 'Database migrated to', os.path.abspath(new)
def debug_device_driver():
from calibre.devices import debug
debug(ioreg_to_tmp=True, buf=sys.stdout)
@ -249,11 +230,6 @@ def main(args=sys.argv):
exec opts.command
elif opts.debug_device_driver:
debug_device_driver()
elif opts.migrate:
if len(args) < 3:
print 'You must specify the path to library1.db and the path to the new library folder'
return 1
migrate(args[1], args[2])
elif opts.add_simple_plugin is not None:
add_simple_plugin(opts.add_simple_plugin)
elif opts.paths:

View File

@ -240,6 +240,7 @@ class ITUNES(DriverBase):
# iTunes enumerations
Audiobooks = [
'AAC audio file',
'Audible file',
'MPEG audio file',
'Protected AAC audio file'

View File

@ -11,7 +11,6 @@ from optparse import OptionParser
from calibre import __version__, __appname__, human_readable
from calibre.devices.errors import PathError
from calibre.utils.terminfo import TerminalController
from calibre.devices.errors import ArgumentError, DeviceError, DeviceLocked
from calibre.customize.ui import device_plugins
from calibre.devices.scanner import DeviceScanner
@ -20,8 +19,7 @@ from calibre.utils.config import device_prefs
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
class FileFormatter(object):
def __init__(self, file, term):
self.term = term
def __init__(self, file):
self.is_dir = file.is_dir
self.is_readonly = file.is_readonly
self.size = file.size
@ -94,7 +92,7 @@ def info(dev):
print "Software version:", info[2]
print "Mime type: ", info[3]
def ls(dev, path, term, recurse=False, color=False, human_readable_size=False, ll=False, cols=0):
def ls(dev, path, recurse=False, human_readable_size=False, ll=False, cols=0):
def col_split(l, cols): # split list l into columns
rows = len(l) / cols
if len(l) % cols:
@ -126,14 +124,13 @@ def ls(dev, path, term, recurse=False, color=False, human_readable_size=False, l
for file in files:
size = len(str(file.size))
if human_readable_size:
file = FileFormatter(file, term)
file = FileFormatter(file)
size = len(file.human_readable_size)
if size > maxlen: maxlen = size
for file in files:
file = FileFormatter(file, term)
file = FileFormatter(file)
name = file.name if ll else file.isdir_name
lsoutput.append(name)
if color: name = file.name_in_color
lscoloutput.append(name)
if ll:
size = str(file.size)
@ -173,10 +170,8 @@ def shutdown_plugins():
pass
def main():
term = TerminalController()
cols = term.COLS
if not cols: # On windows terminal width is unknown
cols = 80
from calibre.utils.terminal import geometry
cols = geometry()[0]
parser = OptionParser(usage="usage: %prog [options] command args\n\ncommand "+
"is one of: info, books, df, ls, cp, mkdir, touch, cat, rm, eject, test_file\n\n"+
@ -260,7 +255,6 @@ def main():
dev.mkdir(args[0])
elif command == "ls":
parser = OptionParser(usage="usage: %prog ls [options] path\nList files on the device\n\npath must begin with / or card:/")
parser.add_option("--color", help="show ls output in color", dest="color", action="store_true", default=False)
parser.add_option("-l", help="In addition to the name of each file, print the file type, permissions, and timestamp (the modification time, in the local timezone). Times are local.", dest="ll", action="store_true", default=False)
parser.add_option("-R", help="Recursively list subdirectories encountered. /dev and /proc are omitted", dest="recurse", action="store_true", default=False)
parser.remove_option("-h")
@ -269,7 +263,7 @@ def main():
if len(args) != 1:
parser.print_help()
return 1
print ls(dev, args[0], term, color=options.color, recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
print ls(dev, args[0], recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
elif command == "info":
info(dev)
elif command == "cp":

View File

@ -14,6 +14,9 @@ const calibre_device_entry_t calibre_mtp_device_table[] = {
// Amazon Kindle Fire HD
, { "Amazon", 0x1949, "Fire HD", 0x0007, DEVICE_FLAGS_ANDROID_BUGS}
// Nexus 10
, { "Google", 0x18d1, "Nexus 10", 0x4ee2, DEVICE_FLAGS_ANDROID_BUGS}
, { NULL, 0xffff, NULL, 0xffff, DEVICE_FLAG_NONE }
};

View File

@ -696,7 +696,7 @@ PyObject* wpd::put_file(IPortableDevice *device, const wchar_t *parent_id, const
PyBytes_AsStringAndSize(raw, &buf, &bytes_read);
if (bytes_read > 0) {
Py_BEGIN_ALLOW_THREADS;
hr = dest->Write(buf, bytes_read, &bytes_written);
hr = dest->Write(buf, (ULONG)bytes_read, &bytes_written);
Py_END_ALLOW_THREADS;
Py_DECREF(raw);
if (hr == STG_E_MEDIUMFULL) { PyErr_SetString(WPDError, "Cannot write to device as it is full"); break; }

View File

@ -19,9 +19,9 @@ class TECLAST_K3(USBMS):
PRODUCT_ID = [0x3203]
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',
'EREADER', 'USB-MSC', 'PER3274B']
'EREADER', 'USB-MSC', 'PER3274B', 'BEBOOK']
MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory'
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')
if masthead_font_family != 'Default':
from calibre.utils.fonts.scanner import font_scanner
faces = font_scanner.fonts_for_family(masthead_font_family)
from calibre.utils.fonts.scanner import font_scanner, NoFonts
try:
faces = font_scanner.fonts_for_family(masthead_font_family)
except NoFonts:
faces = []
if faces:
font_path = faces[0]['path']

View File

@ -101,7 +101,7 @@ cpalmdoc_rfind(Byte *data, Py_ssize_t pos, Py_ssize_t chunk_length) {
static Py_ssize_t
cpalmdoc_do_compress(buffer *b, char *output) {
Py_ssize_t i = 0, j, chunk_len, dist;
unsigned compound;
unsigned int compound;
Byte c, n;
bool found;
char *head;
@ -119,7 +119,7 @@ cpalmdoc_do_compress(buffer *b, char *output) {
dist = i - j;
if (j < i && dist <= 2047) {
found = true;
compound = (dist << 3) + chunk_len-3;
compound = (unsigned int)((dist << 3) + chunk_len-3);
*(output++) = CHAR(0x80 + (compound >> 8 ));
*(output++) = CHAR(compound & 0xFF);
i += chunk_len;
@ -148,7 +148,7 @@ cpalmdoc_do_compress(buffer *b, char *output) {
temp.data[temp.len++] = c; j++;
}
i += temp.len - 1;
*(output++) = temp.len;
*(output++) = (char)temp.len;
for (j=0; j < temp.len; j++) *(output++) = (char)temp.data[j];
}
}

View File

@ -1103,10 +1103,14 @@ OptionRecommendation(name='search_replace',
from calibre.ebooks.oeb.transforms.unsmarten import UnsmartenPunctuation
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,
lineh=line_height,
untable=self.output_plugin.file_type in ('mobi','lit'),
unfloat=self.output_plugin.file_type in ('mobi', 'lit'),
untable=needs_old_markup,
unfloat=needs_old_markup,
page_break_on_body=self.output_plugin.file_type in ('mobi',
'lit'),
specializer=partial(self.output_plugin.specialize_css_for_output,

View File

@ -214,7 +214,11 @@ class MobiMLizer(object):
if tag in CONTENT_TAGS:
bstate.inline = para
pstate = bstate.istate = None
etree.SubElement(para, XHTML(tag), attrib=istate.attrib)
try:
etree.SubElement(para, XHTML(tag), attrib=istate.attrib)
except:
print 'Invalid subelement:', para, tag, istate.attrib
raise
elif tag in TABLE_TAGS:
para.attrib['valign'] = 'top'
if istate.ids:
@ -322,6 +326,10 @@ class MobiMLizer(object):
istates.append(istate)
left = 0
display = style['display']
if display == 'table-cell':
display = 'inline'
elif display.startswith('table'):
display = 'block'
isblock = (not display.startswith('inline') and style['display'] !=
'none')
isblock = isblock and style['float'] == 'none'

View File

@ -0,0 +1,53 @@
#!/usr/bin/env coffee
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
###
Copyright 2012, Kovid Goyal <kovid at kovidgoyal.net>
Released under the GPLv3 License
###
if window?.calibre_utils
log = window.calibre_utils.log
merge = (node, cnode) ->
rules = node.ownerDocument.defaultView.getMatchedCSSRules(node, '')
if rules
for rule in rules
style = rule.style
for name in style
val = style.getPropertyValue(name)
if val and not cnode.style.getPropertyValue(name)
cnode.style.setProperty(name, val)
inline_styles = (node) ->
cnode = node.cloneNode(true)
merge(node, cnode)
nl = node.getElementsByTagName('*')
cnl = cnode.getElementsByTagName('*')
for node, i in nl
merge(node, cnl[i])
return cnode
class CalibreExtract
# This class is a namespace to expose functions via the
# window.calibre_extract object.
constructor: () ->
if not this instanceof arguments.callee
throw new Error('CalibreExtract constructor called as function')
this.marked_node = null
mark: (node) =>
this.marked_node = node
extract: (node=null) =>
if node == null
node = this.marked_node
cnode = inline_styles(node)
return cnode.outerHTML
if window?
window.calibre_extract = new CalibreExtract()

View File

@ -71,7 +71,7 @@ class PagedDisplay
this.margin_side = margin_side
this.margin_bottom = margin_bottom
layout: () ->
layout: (is_single_page=false) ->
# start_time = new Date().getTime()
body_style = window.getComputedStyle(document.body)
bs = document.body.style
@ -151,6 +151,8 @@ class PagedDisplay
has_svg = document.getElementsByTagName('svg').length > 0
only_img = document.getElementsByTagName('img').length == 1 and document.getElementsByTagName('div').length < 3 and document.getElementsByTagName('p').length < 2
this.is_full_screen_layout = (only_img or has_svg) and single_screen and document.body.scrollWidth > document.body.clientWidth
if is_single_page
this.is_full_screen_layout = true
this.in_paged_mode = true
this.current_margin_side = sm

View File

@ -126,6 +126,7 @@ class EbookIterator(BookmarksMixin):
self.spine = []
Spiny = partial(SpineItem, read_anchor_map=read_anchor_map,
run_char_count=run_char_count)
is_comic = plumber.input_fmt.lower() in {'cbc', 'cbz', 'cbr', 'cb7'}
for i in ordered:
spath = i.path
mt = None
@ -135,12 +136,14 @@ class EbookIterator(BookmarksMixin):
mt = guess_type(spath)[0]
try:
self.spine.append(Spiny(spath, mime_type=mt))
if is_comic:
self.spine[-1].is_single_page = True
except:
self.log.warn('Missing spine item:', repr(spath))
cover = self.opf.cover
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')
rcpath = os.path.relpath(cover, self.base).replace(os.sep, '/')
chtml = (TITLEPAGE%prepare_string_for_xml(rcpath, True)).encode('utf-8')

View File

@ -53,6 +53,7 @@ class SpineItem(unicode):
if mime_type is None:
mime_type = guess_type(obj)[0]
obj.mime_type = mime_type
obj.is_single_page = None
return obj
class IndexEntry(object):

View File

@ -313,7 +313,7 @@ class CSSFlattener(object):
if val in ('middle', 'bottom', 'top'):
cssdict['vertical-align'] = val
elif val in ('left', 'right'):
cssdict['text-align'] = val
cssdict['float'] = val
del node.attrib['align']
if node.tag == XHTML('font'):
tags = ['descendant::h:%s'%x for x in ('p', 'div', 'table', 'h1',

View File

@ -93,6 +93,7 @@ gprefs.defaults['tag_browser_dont_collapse'] = []
gprefs.defaults['edit_metadata_single_layout'] = 'default'
gprefs.defaults['default_author_link'] = 'http://en.wikipedia.org/w/index.php?search={author}'
gprefs.defaults['preserve_date_on_ctl'] = True
gprefs.defaults['manual_add_auto_convert'] = False
gprefs.defaults['cb_fullscreen'] = False
gprefs.defaults['worker_max_time'] = 0
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'
' %d characters.')%LibraryDatabase2.WINDOWS_LIBRARY_PATH_LIMIT,
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:
os.rename(loc, newloc)
except:
import traceback
det_msg = 'Location: %r New Location: %r\n%s'%(loc, newloc,
traceback.format_exc())
error_dialog(self.gui, _('Rename failed'),
_('Failed to rename the library at %s. '
'The most common cause for this is if one of the files'
' in the library is open in another program.') % loc,
det_msg=traceback.format_exc(), show=True)
det_msg=det_msg, show=True)
return
self.stats.rename(location, newloc)
self.build_menus()

View File

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

View File

@ -42,6 +42,7 @@ class DuplicatesAdder(QObject): # {{{
# here we add all the formats for dupe book record created above
self.db_adder.add_formats(id, formats)
self.db_adder.number_of_books_added += 1
self.db_adder.auto_convert_books.add(id)
self.count += 1
self.added.emit(self.count)
single_shot(self.add_one)
@ -107,8 +108,16 @@ class DBAdder(QObject): # {{{
self.input_queue = Queue()
self.output_queue = Queue()
self.merged_books = set([])
self.auto_convert_books = set()
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))
def start(self):
@ -152,7 +161,6 @@ class DBAdder(QObject): # {{{
fmts[-1] = fmt
return fmts
def add(self, id, opf, cover, name):
formats = self.ids.pop(id)
if opf.endswith('.error'):
@ -219,6 +227,7 @@ class DBAdder(QObject): # {{{
self.duplicates.append((mi, cover, orig_formats))
else:
self.add_formats(id_, formats)
self.auto_convert_books.add(id_)
self.number_of_books_added += 1
else:
self.names.append(name)

View File

@ -17,6 +17,7 @@ from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATETIME, \
gprefs, question_dialog
from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.gui2.metadata.basic_widgets import CalendarWidget
from calibre.utils.config import dynamic, JSONConfig
from calibre.utils.titlecase import titlecase
from calibre.utils.icu import sort_key, capitalize
@ -339,6 +340,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.tag_editor_button.clicked.connect(self.tag_editor)
self.autonumber_series.stateChanged[int].connect(self.auto_number_changed)
self.pubdate.setMinimumDateTime(UNDEFINED_QDATETIME)
self.pubdate_cw = CalendarWidget(self.pubdate)
self.pubdate.setCalendarWidget(self.pubdate_cw)
pubdate_format = tweaks['gui_pubdate_display_format']
if pubdate_format is not None:
self.pubdate.setDisplayFormat(pubdate_format)

View File

@ -1057,6 +1057,7 @@ class DeviceBooksModel(BooksModel): # {{{
booklist_dirtied = pyqtSignal()
upload_collections = pyqtSignal(object)
resize_rows = pyqtSignal()
def __init__(self, parent):
BooksModel.__init__(self, parent)
@ -1163,6 +1164,11 @@ class DeviceBooksModel(BooksModel): # {{{
return flags
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():
self.map = list(range(len(self.db)))
else:

View File

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

View File

@ -20,26 +20,8 @@ Usage: %prog [options]
Launch the Graphical User Interface
'''):
parser = OptionParser(usage)
# The b is required because of a regression in optparse.py in python 2.7.0
parser.add_option(b'--redirect-console-output', default=False, action='store_true', dest='redirect',
help=_('Redirect console output to a dialog window (both stdout and stderr). Useful on windows where GUI apps do not have a output streams.'))
return parser
class DebugWindow(ConversionErrorDialog):
def __init__(self, parent):
ConversionErrorDialog.__init__(self, parent, 'Console output', '')
self.setModal(Qt.NonModal)
font = QFont()
font.setStyleHint(QFont.TypeWriter)
self.text.setFont(font)
def write(self, msg):
self.text.setPlainText(self.text.toPlainText()+QString(msg))
def flush(self):
pass
class GarbageCollector(QObject):
'''
@ -120,10 +102,6 @@ class MainWindow(QMainWindow):
QMainWindow.__init__(self, parent)
if disable_automatic_gc:
self._gc = GarbageCollector(self, debug=False)
if getattr(opts, 'redirect', False):
self.__console_redirect = DebugWindow(self)
sys.stdout = sys.stderr = self.__console_redirect
self.__console_redirect.show()
def unhandled_exception(self, type, value, tb):
if type == KeyboardInterrupt:

View File

@ -867,6 +867,7 @@ class Cover(ImageView): # {{{
def __init__(self, parent):
ImageView.__init__(self, parent)
self.show_size = True
self.dialog = parent
self._cdata = None
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('add_formats_to_existing', prefs)
r('preserve_date_on_ctl', gprefs)
r('manual_add_auto_convert', gprefs)
choices = [
(_('Ignore duplicate incoming formats'), 'ignore'),
(_('Overwrite existing duplicate formats'), 'overwrite'),

View File

@ -6,7 +6,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>753</width>
<width>1035</width>
<height>547</height>
</rect>
</property>
@ -24,6 +24,36 @@
<string>The Add &amp;Process</string>
</attribute>
<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">
<widget class="QLabel" name="label_6">
<property name="text">
@ -68,44 +98,7 @@
</item>
</layout>
</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_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">
<item row="5" column="0">
<widget class="QLabel" name="label_230">
<property name="text">
<string>&amp;Tags to apply when adding a book:</string>
@ -115,14 +108,14 @@ Author matching is exact.</string>
</property>
</widget>
</item>
<item row="4" column="2">
<item row="5" column="2">
<widget class="QLineEdit" name="opt_new_book_tags">
<property name="toolTip">
<string>A comma-separated list of tags that will be applied to books added to the library</string>
</property>
</widget>
</item>
<item row="5" column="0" colspan="3">
<item row="6" column="0" colspan="3">
<widget class="QGroupBox" name="metadata_box">
<property name="title">
<string>&amp;Configure metadata from file name</string>
@ -144,6 +137,20 @@ Author matching is exact.</string>
</layout>
</widget>
</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>
</widget>
<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.download_thread import DetailsThreadPool, \
CoverThreadPool
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
REGEXP_MATCH
from calibre.utils.icu import sort_key
from calibre.utils.search_query_parser import SearchQueryParser
@ -153,7 +151,7 @@ class Matches(QAbstractItemModel):
mod_query = query
# Remove filter identifiers
# 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 = query.replace('%s:' % loc, '')
# Remove the prefix and search text.
@ -301,11 +299,16 @@ class Matches(QAbstractItemModel):
class SearchFilter(SearchQueryParser):
CONTAINS_MATCH = 0
EQUALS_MATCH = 1
REGEXP_MATCH = 2
IN_MATCH = 3
USABLE_LOCATIONS = [
'all',
'affiliate',
'author',
'author2',
'authors',
'cover',
'download',
@ -315,6 +318,7 @@ class SearchFilter(SearchQueryParser):
'formats',
'price',
'title',
'title2',
'store',
]
@ -331,6 +335,26 @@ class SearchFilter(SearchQueryParser):
def universal_set(self):
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):
query = query.strip()
location = location.lower().strip()
@ -341,17 +365,17 @@ class SearchFilter(SearchQueryParser):
elif location == 'formats':
location = 'format'
matchkind = CONTAINS_MATCH
matchkind = self.CONTAINS_MATCH
if len(query) > 1:
if query.startswith('\\'):
query = query[1:]
elif query.startswith('='):
matchkind = EQUALS_MATCH
matchkind = self.EQUALS_MATCH
query = query[1:]
elif query.startswith('~'):
matchkind = REGEXP_MATCH
matchkind = self.REGEXP_MATCH
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()
if location not in self.USABLE_LOCATIONS:
@ -372,6 +396,8 @@ class SearchFilter(SearchQueryParser):
}
for x in ('author', 'download', 'format'):
q[x+'s'] = q[x]
q['author2'] = q['author']
q['title2'] = q['title']
# make the price in query the same format as result
if location == 'price':
@ -415,16 +441,19 @@ class SearchFilter(SearchQueryParser):
### 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
### exactmatch searches into contains searches.
if locvalue == 'author' and matchkind == EQUALS_MATCH:
m = CONTAINS_MATCH
if locvalue == 'author' and matchkind == self.EQUALS_MATCH:
m = self.CONTAINS_MATCH
else:
m = matchkind
if locvalue == 'format':
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:
vals = [accessor(sr)]
if _match(query, vals, m):
if self._match(query, vals, m):
matches.add(sr)
break
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,
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.progress_indicator import ProgressIndicator
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
@ -31,6 +31,8 @@ class SearchDialog(QDialog, Ui_Dialog):
self.setupUi(self)
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')
# Loads variables that store various settings.
@ -60,13 +62,24 @@ class SearchDialog(QDialog, Ui_Dialog):
self.setup_store_checks()
# 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.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLengthWithIcon)
self.search_edit.setMinimumContentsLength(25)
# Create and add the progress indicator
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.configure.setIcon(QIcon(I('config.png')))
@ -152,8 +165,18 @@ class SearchDialog(QDialog, Ui_Dialog):
self.results_view.model().clear_results()
# 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():
error_dialog(self, _('No query'),
_('You must enter a title, author or keyword to'
' search for.'), show=True)
return
# Give the query to the results model so it can do
# futher filtering.
@ -188,7 +211,7 @@ class SearchDialog(QDialog, Ui_Dialog):
query = query.replace('>', '')
query = query.replace('<', '')
# 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 = query.replace('%s:' % loc, '')
# Remove the prefix and search text.

View File

@ -14,49 +14,74 @@
<string>Get Books</string>
</property>
<property name="windowIcon">
<iconset>
<iconset resource="../../../../../resources/images.qrc">
<normaloff>:/images/store.png</normaloff>:/images/store.png</iconset>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="top_layout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Query:</string>
</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>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="HistoryLineEdit" name="search_title">
<property name="placeholderText">
<string>Search by title</string>
</property>
</widget>
</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">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@ -82,8 +107,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>193</width>
<height>127</height>
<width>204</width>
<height>141</height>
</rect>
</property>
</widget>
@ -202,7 +227,7 @@
</widget>
</widget>
</item>
<item>
<item row="4" column="0" colspan="3">
<layout class="QHBoxLayout" name="bottom_layout">
<item>
<widget class="QLabel" name="label_2">
@ -234,7 +259,47 @@
<item>
<widget class="QPushButton" name="close">
<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>
</widget>
</item>
@ -255,17 +320,19 @@
</customwidget>
</customwidgets>
<tabstops>
<tabstop>search_title</tabstop>
<tabstop>search_author</tabstop>
<tabstop>adv_search_button</tabstop>
<tabstop>search_edit</tabstop>
<tabstop>search</tabstop>
<tabstop>results_view</tabstop>
<tabstop>store_list</tabstop>
<tabstop>select_all_stores</tabstop>
<tabstop>select_invert_stores</tabstop>
<tabstop>select_none_stores</tabstop>
<tabstop>results_view</tabstop>
<tabstop>configure</tabstop>
<tabstop>open_external</tabstop>
<tabstop>close</tabstop>
<tabstop>adv_search_button</tabstop>
</tabstops>
<resources>
<include location="../../../../../resources/images.qrc"/>

View File

@ -1,82 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011-2012, Tomasz Długosz <tomek3d@gmail.com>'
__docformat__ = 'restructuredtext en'
import re
import urllib
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class GandalfStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
url = 'http://www.gandalf.com.pl/ebooks/'
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
else:
d = WebStoreDialog(self.gui, url, parent, detail_item)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
counter = max_results
page = 1
url = 'http://www.gandalf.com.pl/we/' + urllib.quote_plus(query.decode('utf-8').encode('iso8859_2')) + '/bdb'
br = browser()
while counter:
with closing(br.open((url + str(page-1) + '/#s') if (page-1) else (url + '/#s'), timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//div[@class="box"]'):
if counter <= 0:
break
id = ''.join(data.xpath('.//div[@class="info"]/h3/a/@href'))
if not id:
continue
cover_url = ''.join(data.xpath('.//div[@class="info"]/h3/a/@id'))
title = ''.join(data.xpath('.//div[@class="info"]/h3/a/@title'))
formats = ''.join(data.xpath('.//div[@class="info"]/p[1]/text()'))
formats = re.findall(r'\((.*?)\)',formats)[0]
author = ''.join(data.xpath('.//div[@class="info"]/h4/text() | .//div[@class="info"]/h4/span/text()'))
price = ''.join(data.xpath('.//div[@class="options"]/h3/text()'))
price = re.sub('PLN', '', price)
price = re.sub('\.', ',', price)
drm = data.xpath('boolean(.//div[@class="info" and contains(., "Zabezpieczenie: DRM")])')
counter -= 1
s = SearchResult()
s.cover_url = 'http://imguser.gandalf.com.pl/' + re.sub('p', 'p_', cover_url) + '.jpg'
s.title = title.strip()
s.author = author.strip()
s.price = price
s.detail_item = id.strip()
if drm:
s.drm = SearchResult.DRM_LOCKED
else:
s.drm = SearchResult.DRM_UNLOCKED
s.formats = formats.upper().strip()
yield s
if not doc.xpath('boolean(//div[@class="wyszukiwanie_podstawowe_header"]//div[@class="box"])'):
break
page+=1

View File

@ -25,7 +25,7 @@ class LegimiStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
plain_url = 'http://www.legimi.com/pl/ebooks/?price=any'
plain_url = 'http://www.legimi.com/pl/ebooki/'
url = 'https://ssl.afiliant.com/affskrypt,,2f9de2,,11483,,,?u=(' + plain_url + ')'
detail_url = None
@ -41,32 +41,36 @@ class LegimiStore(BasicStoreConfig, StorePlugin):
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://www.legimi.com/pl/ebooks/?price=any&lang=pl&search=' + urllib.quote_plus(query) + '&sort=relevance'
url = 'http://www.legimi.com/pl/ebooki/?szukaj=' + urllib.quote_plus(query)
br = browser()
drm_pattern = re.compile("(DRM)")
drm_pattern = re.compile("zabezpieczona DRM")
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//div[@class="list"]/ul/li'):
for data in doc.xpath('//div[@id="listBooks"]/div'):
if counter <= 0:
break
id = ''.join(data.xpath('.//div[@class="item_cover_container"]/a[1]/@href'))
id = ''.join(data.xpath('.//a[@class="plainLink"]/@href'))
if not id:
continue
cover_url = ''.join(data.xpath('.//div[@class="item_cover_container"]/a/img/@src'))
title = ''.join(data.xpath('.//div[@class="item_entries"]/h2/a/text()'))
author = ''.join(data.xpath('.//div[@class="item_entries"]/span[1]/a/text()'))
cover_url = ''.join(data.xpath('.//img[1]/@src'))
title = ''.join(data.xpath('.//span[@class="bookListTitle ellipsis"]/text()'))
author = ''.join(data.xpath('.//span[@class="bookListAuthor ellipsis"]/text()'))
author = re.sub(',','',author)
author = re.sub(';',',',author)
price = ''.join(data.xpath('.//span[@class="ebook_price"]/text()'))
formats = ''.join(data.xpath('.//div[@class="item_entries"]/span[3]/text()'))
formats = re.sub('Format:','',formats)
drm = drm_pattern.search(formats)
formats = re.sub('\(DRM\)','',formats)
price = ''.join(data.xpath('.//div[@class="bookListPrice"]/span/text()'))
formats = []
with closing(br.open(id.strip(), timeout=timeout/4)) as nf:
idata = html.fromstring(nf.read())
formatlist = idata.xpath('.//div[@id="fullBookFormats"]//span[@class="bookFormat"]/text()')
for x in formatlist:
if x.strip() not in formats:
formats.append(x.strip())
drm = drm_pattern.search(''.join(idata.xpath('.//div[@id="fullBookFormats"]/p/text()')))
counter -= 1
@ -76,7 +80,7 @@ class LegimiStore(BasicStoreConfig, StorePlugin):
s.author = author.strip()
s.price = price
s.detail_item = 'http://www.legimi.com/' + id.strip()
s.formats = ', '.join(formats)
s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED
s.formats = formats.strip()
yield s

View File

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2012, Tomasz Długosz <tomek3d@gmail.com>'
__docformat__ = 'restructuredtext en'
import urllib
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class PublioStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
google_analytics = '?utm_source=tdcalibre&utm_medium=calibre'
url = 'http://www.publio.pl/e-booki.html' + google_analytics
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner((detail_item + google_analytics) if detail_item else url)))
else:
d = WebStoreDialog(self.gui, url, parent, detail_item)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=20, timeout=60):
br = browser()
counter = max_results
page = 1
while counter:
with closing(br.open('http://www.publio.pl/e-booki,strona' + str(page) + '.html?q=' + urllib.quote(query), timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//div[@class="item"]'):
if counter <= 0:
break
id = ''.join(data.xpath('.//div[@class="img"]/a/@href'))
if not id:
continue
cover_url = ''.join(data.xpath('.//div[@class="img"]/a/img/@data-original'))
title = ''.join(data.xpath('.//div[@class="desc"]/h4/a/text()'))
title2 = ''.join(data.xpath('.//div[@class="desc"]/h5/a/text()'))
if title2:
title = title + '. ' + title2
author = ', '.join(data.xpath('./div[@class="desc"]/div[@class="detailShortList"]/div[@class="row"]/a/text()'))
price = ''.join(data.xpath('.//div[@class="priceBoxContener "]/div/ins/text()'))
if not price:
price = ''.join(data.xpath('.//div[@class="priceBoxContener "]/div/text()'))
formats = ', '.join(data.xpath('.//div[@class="formats"]/a/img/@alt'))
counter -= 1
s = SearchResult()
s.cover_url = 'http://www.publio.pl' + cover_url
s.title = title.strip()
s.author = author.strip()
s.price = price.strip()
s.detail_item = 'http://www.publio.pl' + id.strip()
s.drm = SearchResult.DRM_LOCKED if 'DRM' in formats else SearchResult.DRM_UNLOCKED
s.formats = formats.replace(' DRM','').strip()
yield s
if not doc.xpath('boolean(//a[@class="next"])'):
break
page+=1

View File

@ -59,6 +59,10 @@ def config(defaults=None):
help=_('Show reading position in fullscreen mode.'))
c.add_opt('fullscreen_scrollbar', default=True, action='store_false',
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('use_book_margins', default=False, action='store_true')
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_fullscreen_clock.setChecked(opts.fullscreen_clock)
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_cols_per_screen.setValue(opts.cols_per_screen)
self.opt_override_book_margins.setChecked(not opts.use_book_margins)
@ -279,7 +285,9 @@ class ConfigDialog(QDialog, Ui_Dialog):
c.set('fullscreen_clock', self.opt_fullscreen_clock.isChecked())
c.set('fullscreen_pos', self.opt_fullscreen_pos.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('start_in_fullscreen', self.opt_start_in_fullscreen.isChecked())
c.set('use_book_margins', not
self.opt_override_book_margins.isChecked())
c.set('text_color', self.current_text_color)

View File

@ -388,20 +388,34 @@ QToolBox::tab:hover {
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="opt_fullscreen_pos">
<property name="text">
<string>Show reading &amp;position in full screen mode</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_fullscreen_scrollbar">
<property name="text">
<string>Show &amp;scrollbar in full screen mode</string>
</property>
</widget>
</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>
</widget>
<widget class="QWidget" name="page_6">

View File

@ -12,10 +12,11 @@ from PyQt4.Qt import (QSize, QSizePolicy, QUrl, SIGNAL, Qt, pyqtProperty,
QPainter, QPalette, QBrush, QDialog, QColor, QPoint, QImage, QRegion,
QIcon, pyqtSignature, QAction, QMenu, QString, pyqtSignal,
QSwipeGesture, QApplication, pyqtSlot)
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings, QWebElement
from calibre.gui2.viewer.flip import SlideFlip
from calibre.gui2.shortcuts import Shortcuts
from calibre.gui2 import open_url
from calibre import prints
from calibre.customize.ui import all_viewer_plugins
from calibre.gui2.viewer.keys import SHORTCUTS
@ -23,6 +24,7 @@ from calibre.gui2.viewer.javascript import JavaScriptLoader
from calibre.gui2.viewer.position import PagePosition
from calibre.gui2.viewer.config import config, ConfigDialog, load_themes
from calibre.gui2.viewer.image_popup import ImagePopup
from calibre.gui2.viewer.table_popup import TablePopup
from calibre.ebooks.oeb.display.webview import load_html
from calibre.constants import isxp, iswindows
# }}}
@ -30,6 +32,7 @@ from calibre.constants import isxp, iswindows
class Document(QWebPage): # {{{
page_turn = pyqtSignal(object)
mark_element = pyqtSignal(QWebElement)
def set_font_settings(self, opts):
settings = self.settings()
@ -144,6 +147,8 @@ class Document(QWebPage): # {{{
self.fullscreen_clock = opts.fullscreen_clock
self.fullscreen_scrollbar = opts.fullscreen_scrollbar
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.cols_per_screen = opts.cols_per_screen
self.side_margin = opts.side_margin
@ -179,6 +184,7 @@ class Document(QWebPage): # {{{
ensure_ascii=False)))
for pl in self.all_viewer_plugins:
pl.load_javascript(evaljs)
evaljs('py_bridge.mark_element.connect(window.calibre_extract.mark)')
@pyqtSignature("")
def animated_scroll_done(self):
@ -204,7 +210,7 @@ class Document(QWebPage): # {{{
_pass_json_value = pyqtProperty(QString, fget=_pass_json_value_getter,
fset=_pass_json_value_setter)
def after_load(self):
def after_load(self, last_loaded_path=None):
self.javascript('window.paged_display.read_document_margins()')
self.set_bottom_padding(0)
self.fit_images()
@ -213,7 +219,7 @@ class Document(QWebPage): # {{{
if self.in_fullscreen_mode:
self.switch_to_fullscreen_mode()
if self.in_paged_mode:
self.switch_to_paged_mode()
self.switch_to_paged_mode(last_loaded_path=last_loaded_path)
self.read_anchor_positions(use_cache=False)
evaljs = self.mainFrame().evaluateJavaScript
for pl in self.all_viewer_plugins:
@ -240,7 +246,7 @@ class Document(QWebPage): # {{{
self.anchor_positions = {}
return {k:tuple(v) for k, v in self.anchor_positions.iteritems()}
def switch_to_paged_mode(self, onresize=False):
def switch_to_paged_mode(self, onresize=False, last_loaded_path=None):
if onresize and not self.loaded_javascript:
return
self.javascript('''
@ -251,9 +257,12 @@ class Document(QWebPage): # {{{
self.cols_per_screen, self.top_margin, self.side_margin,
self.bottom_margin
))
side_margin = self.javascript('window.paged_display.layout()', typ=int)
force_fullscreen_layout = bool(getattr(last_loaded_path,
'is_single_page', False))
f = 'true' if force_fullscreen_layout else 'false'
side_margin = self.javascript('window.paged_display.layout(%s)'%f, typ=int)
# Setup the contents size to ensure that there is a right most margin.
# Without this webkit renders the final column with no margin, as the
# Without this WebKit renders the final column with no margin, as the
# columns extend beyond the boundaries (and margin) of body
mf = self.mainFrame()
sz = mf.contentsSize()
@ -442,6 +451,10 @@ class Document(QWebPage): # {{{
self.height+amount)
self.setPreferredContentsSize(s)
def extract_node(self):
return unicode(self.mainFrame().evaluateJavaScript(
'window.calibre_extract.extract()').toString())
# }}}
class DocumentView(QWebView): # {{{
@ -478,15 +491,23 @@ class DocumentView(QWebView): # {{{
d = self.document
self.unimplemented_actions = list(map(self.pageAction,
[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')),
_('&Lookup in dictionary'), self)
self.dictionary_action.setShortcut(Qt.CTRL+Qt.Key_L)
self.dictionary_action.triggered.connect(self.lookup)
self.addAction(self.dictionary_action)
self.image_popup = ImagePopup(self)
self.table_popup = TablePopup(self)
self.view_image_action = QAction(QIcon(I('view-image.png')), _('View &image...'), self)
self.view_image_action.triggered.connect(self.image_popup)
self.view_table_action = QAction(QIcon(I('view.png')), _('View &table...'), self)
self.view_table_action.triggered.connect(self.popup_table)
self.search_action = QAction(QIcon(I('dictionary.png')),
_('&Search for next occurrence'), self)
self.search_action.setShortcut(Qt.CTRL+Qt.Key_S)
@ -523,6 +544,10 @@ class DocumentView(QWebView): # {{{
self.goto_location_action.setMenu(self.goto_location_menu)
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):
if self.manager is not None:
self.manager.goto_next_section()
@ -579,26 +604,85 @@ class DocumentView(QWebView): # {{{
if self.manager is not None:
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 popup_table(self):
html = self.document.extract_node()
self.table_popup(html, QUrl.fromLocalFile(self.last_loaded_path),
self.document.font_magnification_step)
def contextMenuEvent(self, ev):
mf = self.document.mainFrame()
r = mf.hitTestContent(ev.pos())
img = r.pixmap()
elem = r.element()
if elem.isNull():
elem = r.enclosingBlockElement()
table = None
parent = elem
while not parent.isNull():
if (unicode(parent.tagName()) == u'table' or
unicode(parent.localName()) == u'table'):
table = parent
break
parent = parent.parent()
self.image_popup.current_img = img
self.image_popup.current_url = r.imageUrl()
menu = self.document.createStandardContextMenu()
for action in self.unimplemented_actions:
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():
menu.addAction(self.view_image_action)
menu.addSeparator()
menu.addAction(self.goto_location_action)
if self.document.in_fullscreen_mode and self.manager is not None:
if table is not None:
self.document.mark_element.emit(table)
menu.addAction(self.view_table_action)
text = self._selectedText()
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.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())
def lookup(self, *args):
@ -613,6 +697,12 @@ class DocumentView(QWebView): # {{{
if 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):
self.manager = manager
self.scrollbar = manager.horizontal_scrollbar
@ -730,7 +820,7 @@ class DocumentView(QWebView): # {{{
return
self.loading_url = None
self.document.load_javascript_libraries()
self.document.after_load()
self.document.after_load(self.last_loaded_path)
self._size_hint = self.document.mainFrame().contentsSize()
scrolled = False
if self.to_bottom:
@ -990,6 +1080,11 @@ class DocumentView(QWebView): # {{{
self.multiplier -= amount
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):
if event.type() == event.EnabledChange:
self.update()

View File

@ -34,11 +34,12 @@ class JavaScriptLoader(object):
'utils':'ebooks.oeb.display.utils',
'fs':'ebooks.oeb.display.full_screen',
'math': 'ebooks.oeb.display.mathjax',
'extract': 'ebooks.oeb.display.extract',
}
ORDER = ('jquery', 'jquery_scrollTo', 'bookmarks', 'referencing', 'images',
'hyphenation', 'hyphenator', 'utils', 'cfi', 'indexing', 'paged',
'fs', 'math')
'fs', 'math', 'extract')
def __init__(self, dynamic_coffeescript=False):

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)
from calibre.ebooks.oeb.iterator.book import EbookIterator
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.gui2.search_box import SearchBox2
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 '
'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)
self.setupUi(self)
self.view.initialize_view(debug_javascript)
@ -178,6 +179,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.pending_restore = False
self.existing_bookmarks= []
self.selected_text = None
self.was_maximized = False
self.read_settings()
self.dictionary_box.hide()
self.close_dictionary_view.clicked.connect(lambda
@ -207,7 +209,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.view.set_manager(self)
self.pi = ProgressIndicator(self)
self.toc.setVisible(False)
self.action_quit = QAction(self)
self.action_quit = QAction(_('&Quit'), self)
self.addAction(self.action_quit)
self.view_resized_timer = QTimer(self)
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.toggle_toolbar_action = QAction(_('Show/hide controls'), self)
self.toggle_toolbar_action.setCheckable(True)
self.toggle_toolbar_action.triggered.connect(self.toggle_toolbars)
self.addAction(self.toggle_toolbar_action)
self.full_screen_label_anim = QPropertyAnimation(
@ -358,6 +361,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.restore_state()
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):
in_paged_mode = not self.action_toggle_paged_mode.isChecked()
@ -397,7 +402,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
count += 1
def shutdown(self):
if self.isFullScreen():
if self.isFullScreen() and not self.view.document.start_in_fullscreen:
self.action_full_screen.trigger()
return False
self.save_state()
@ -421,7 +426,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def save_state(self):
state = bytearray(self.saveState(self.STATE_VERSION))
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:
vprefs.set('viewer_toc_isvisible', bool(self.toc.isVisible()))
if self.toc.isVisible():
@ -488,6 +494,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.window_mode_changed = 'fullscreen'
self.tool_bar.setVisible(False)
self.tool_bar2.setVisible(False)
self.was_maximized = self.isMaximized()
if not self.view.document.fullscreen_scrollbar:
self.vertical_scrollbar.setVisible(False)
self.frame.layout().setSpacing(0)
@ -503,17 +510,18 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def show_full_screen_label(self):
f = self.full_screen_label
self.esc_full_screen_action.setEnabled(True)
f.setVisible(True)
height = 200
width = int(0.7*self.view.width())
f.resize(width, height)
f.move((self.view.width() - width)//2, (self.view.height()-height)//2)
a = self.full_screen_label_anim
a.setDuration(500)
a.setStartValue(QSize(width, 0))
a.setEndValue(QSize(width, height))
a.start()
QTimer.singleShot(3500, self.full_screen_label.hide)
if self.view.document.show_fullscreen_help:
f.setVisible(True)
a = self.full_screen_label_anim
a.setDuration(500)
a.setStartValue(QSize(width, 0))
a.setEndValue(QSize(width, height))
a.start()
QTimer.singleShot(3500, self.full_screen_label.hide)
self.view.document.switch_to_fullscreen_mode()
if self.view.document.fullscreen_clock:
self.show_clock()
@ -574,7 +582,10 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
om = self._original_frame_margins
self.centralwidget.layout().setContentsMargins(om[0])
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):
if self.window_mode_changed:
@ -681,13 +692,9 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
def font_size_larger(self):
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):
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):
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))
self.action_font_size_smaller.setToolTip(
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):
if not text:
@ -1129,32 +1138,29 @@ def main(args=sys.argv):
parser = option_parser()
opts, args = parser.parse_args(args)
pid = os.fork() if False and (islinux or isbsd) else -1
try:
open_at = float(opts.open_at)
except:
open_at = None
if pid <= 0:
override = 'calibre-ebook-viewer' if islinux else None
app = Application(args, override_program_name=override)
app.load_builtin_fonts()
app.setWindowIcon(QIcon(I('viewer.png')))
QApplication.setOrganizationName(ORG_NAME)
QApplication.setApplicationName(APP_UID)
main = EbookViewer(args[1] if len(args) > 1 else None,
debug_javascript=opts.debug_javascript, open_at=open_at)
# This is needed for paged mode. Without it, the first document that is
# loaded will have extra blank space at the bottom, as
# turn_off_internal_scrollbars does not take effect for the first
# rendered document
main.view.load_path(P('viewer/blank.html', allow_user_override=False))
override = 'calibre-ebook-viewer' if islinux else None
app = Application(args, override_program_name=override)
app.load_builtin_fonts()
app.setWindowIcon(QIcon(I('viewer.png')))
QApplication.setOrganizationName(ORG_NAME)
QApplication.setApplicationName(APP_UID)
main = EbookViewer(args[1] if len(args) > 1 else None,
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
# loaded will have extra blank space at the bottom, as
# turn_off_internal_scrollbars does not take effect for the first
# rendered document
main.view.load_path(P('viewer/blank.html', allow_user_override=False))
sys.excepthook = main.unhandled_exception
main.show()
if opts.raise_window:
main.raise_()
if opts.full_screen:
main.action_full_screen.trigger()
sys.excepthook = main.unhandled_exception
main.show()
if opts.raise_window:
main.raise_()
with main:
return app.exec_()
return 0

View File

@ -0,0 +1,83 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import (QDialog, QDialogButtonBox, QVBoxLayout, QApplication,
QSize, QIcon, Qt)
from PyQt4.QtWebKit import QWebView
from calibre.gui2 import gprefs, error_dialog
class TableView(QDialog):
def __init__(self, parent, font_magnification_step):
QDialog.__init__(self, parent)
self.font_magnification_step = font_magnification_step
dw = QApplication.instance().desktop()
self.avail_geom = dw.availableGeometry(parent)
self.view = QWebView(self)
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Close)
bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject)
self.zi_button = zi = bb.addButton(_('Zoom &in'), bb.ActionRole)
self.zo_button = zo = bb.addButton(_('Zoom &out'), bb.ActionRole)
zi.setIcon(QIcon(I('plus.png')))
zo.setIcon(QIcon(I('minus.png')))
zi.clicked.connect(self.zoom_in)
zo.clicked.connect(self.zoom_out)
self.l = l = QVBoxLayout()
self.setLayout(l)
l.addWidget(self.view)
l.addWidget(bb)
def zoom_in(self):
self.view.setZoomFactor(self.view.zoomFactor() +
self.font_magnification_step)
def zoom_out(self):
self.view.setZoomFactor(max(0.1, self.view.zoomFactor()
-self.font_magnification_step))
def __call__(self, html, baseurl):
self.view.setHtml(
'<!DOCTYPE html><html><body bgcolor="white">%s<body></html>'%html,
baseurl)
geom = self.avail_geom
self.resize(QSize(int(geom.width()/2.5), geom.height()-50))
geom = gprefs.get('viewer_table_popup_geometry', None)
if geom is not None:
self.restoreGeometry(geom)
self.setWindowTitle(_('View Table'))
self.show()
def done(self, e):
gprefs['viewer_table_popup_geometry'] = bytearray(self.saveGeometry())
return QDialog.done(self, e)
class TablePopup(object):
def __init__(self, parent):
self.parent = parent
self.dialogs = []
def __call__(self, html, baseurl, font_magnification_step):
if not html:
return error_dialog(self.parent, _('No table found'),
_('No table was found'), show=True)
d = TableView(self.parent, font_magnification_step)
self.dialogs.append(d)
d.finished.connect(self.cleanup, type=Qt.QueuedConnection)
d(html, baseurl)
def cleanup(self):
for d in tuple(self.dialogs):
if not d.isVisible():
self.dialogs.remove(d)

View File

@ -283,6 +283,7 @@ class ImageView(QWidget, ImageDropMixin): # {{{
self.setMinimumSize(QSize(150, 200))
ImageDropMixin.__init__(self)
self.draw_border = True
self.show_size = False
def setPixmap(self, pixmap):
if not isinstance(pixmap, QPixmap):
@ -305,6 +306,7 @@ class ImageView(QWidget, ImageDropMixin): # {{{
if pmap.isNull():
return
w, h = pmap.width(), pmap.height()
ow, oh = w, h
cw, ch = self.rect().width(), self.rect().height()
scaled, nw, nh = fit_image(w, h, cw, ch)
if scaled:
@ -317,12 +319,22 @@ class ImageView(QWidget, ImageDropMixin): # {{{
p = QPainter(self)
p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
p.drawPixmap(target, pmap)
pen = QPen()
pen.setWidth(self.BORDER_WIDTH)
p.setPen(pen)
if self.draw_border:
pen = QPen()
pen.setWidth(self.BORDER_WIDTH)
p.setPen(pen)
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()
# }}}
@ -582,6 +594,9 @@ class HistoryLineEdit(QComboBox): # {{{
self.setInsertPolicy(self.NoInsert)
self.setMaxCount(10)
def setPlaceholderText(self, txt):
return self.lineEdit().setPlaceholderText(txt)
@property
def store_name(self):
return 'lineedit_history_'+self._name

View File

@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
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',
'uuid', 'languages', 'identifiers']

Some files were not shown because too many files have changed in this diff Show More