This commit is contained in:
GRiker 2013-01-11 04:38:14 -07:00
commit 01df610d1a
130 changed files with 21703 additions and 17043 deletions

View File

@ -19,6 +19,68 @@
# new recipes:
# - title:
- version: 0.9.14
date: 2012-01-11
new features:
- title: "When adding multiple books and duplicates are found, allow the user to select which of the duplicate books will be added anyway."
tickets: [1095256]
- title: "Device drivers for Kobo Arc on linux, Polaroid Abdroid tablet"
tickets: [1098049]
- title: "When sorting by series, use the language of the book to decide what leading articles to remove, just as is done for sorting by title"
bug fixes:
- title: "PDF Output: Do not error out when the input document contains links with anchors not present in the document."
tickets: [1096428]
- title: "Add support for upgraded db on newest Kobo firmware"
tickets: [1095617]
- title: "PDF Output: Fix typo that broke use of custom paper sizes."
tickets: [1097563]
- title: "PDF Output: Handle empty anchors present at the end of a page"
- title: "PDF Output: Fix side margins of last page in a flow being incorrect when large side margins are used."
tickets: [1096290]
- title: "Edit metadata dialog: Allow setting the series number for custom series type columns to zero"
- title: "When bulk editing custom series-type columns and not provding a series number use 1 as the default, instead of None"
- title: "Catalogs: Fix issue with catalog generation using Hungarian UI and author_sort beginning with multiple letter groups."
tickets: [1091581]
- title: "PDF Output: Dont error out on files that have invalid font-family declarations."
tickets: [1096279]
- title: "Do not load QRawFont at global level, to allow calibre installation on systems with missing dependencies"
tickets: [1096170]
- title: "PDF Output: Fix cover not present in generated PDF files"
tickets: [1096098]
improved recipes:
- Sueddeutsche Zeitung mobil
- Boerse Online
- TidBits
- New York Review of Books
- Fleshbot
- Il Messaggero
- Libero
new recipes:
- title: Spectator Magazine, Oxford Mail and Outside Magazine
author: Krittika Goyal
- title: Libartes
author: Darko Miletic
- title: El Diplo
author: Tomas De Domenico
- version: 0.9.13
date: 2013-01-04

View File

@ -672,6 +672,19 @@ There are three possible things I know of, that can cause this:
* The Logitech SetPoint Settings application causes random crashes in
|app| when it is open. Close it before starting |app|.
If none of the above apply to you, then there is some other program on your
computer that is interfering with |app|. First reboot your computer is safe
mode, to have as few running programs as possible, and see if the crashes still
happen. If they do not, then you know it is some program causing the problem.
The most likely such culprit is a program that modifies other programs'
behavior, such as an antivirus, a device driver, something like RoboForm (an
automatic form filling app) or an assistive technology like Voice Control or a
Screen Reader.
The only way to find the culprit is to eliminate the programs one by one and
see which one is causing the issue. Basically, stop a program, run calibre,
check for crashes. If they still happen, stop another program and repeat.
|app| is not starting on OS X?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -2,32 +2,35 @@ from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
title = u'Börse-online'
__author__ = 'schuster'
__author__ = 'schuster, Armin Geller'
oldest_article = 1
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
language = 'de'
remove_javascript = True
cover_url = 'http://www.dpv.de/images/1995/source.gif'
masthead_url = 'http://www.zeitschriften-cover.de/cover/boerse-online-cover-januar-2010-x1387.jpg'
extra_css = '''
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''
remove_tags_bevor = [dict(name='h3')]
remove_tags_after = [dict(name='div', attrs={'class':'artikelfuss'})]
remove_tags = [dict(attrs={'class':['moduleTopNav', 'moduleHeaderNav', 'text', 'blau', 'poll1150']}),
dict(id=['newsletterlayer', 'newsletterlayerClose', 'newsletterlayer_body', 'newsletterarray_error', 'newsletterlayer_emailadress', 'newsletterlayer_submit', 'kommentar']),
dict(name=['h2', 'Gesamtranking', 'h3',''])]
encoding = 'iso-8859-1'
timefmt = ' [%a, %d %b %Y]'
cover_url = 'http://www.wirtschaftsmedien-shop.de/s/media/coverimages/7576_2013107.jpg'
masthead_url = 'http://upload.wikimedia.org/wikipedia/de/5/56/B%C3%B6rse_Online_Logo.svg'
remove_tags_after = [dict(name='div', attrs={'class':['artikelfuss', 'rahmen600']})]
remove_tags = [
dict(name='div', attrs={'id':['breadcrumb', 'rightCol', 'clearall']}),
dict(name='div', attrs={'class':['footer', 'artikelfuss']}),
]
keep_only_tags = [
dict(name='div', attrs={'id':['contentWrapper']})
]
feeds = [(u'Börsennachrichten', u'http://www.boerse-online.de/rss/')]
def print_version(self, url):
return url.replace('.html#nv=rss', '.html?mode=print')
feeds = [(u'Börsennachrichten', u'http://www.boerse-online.de/rss/')]

View File

@ -13,14 +13,13 @@ class BusinessWeekMagazine(BasicNewsRecipe):
keep_only_tags = [
dict(name='div', attrs={'id':'article_body_container'}),
]
remove_tags = [dict(name='ui'),dict(name='li')]
remove_tags = [dict(name='ui'),dict(name='li'),dict(name='div', attrs={'id':['share-email']})]
no_javascript = True
no_stylesheets = True
cover_url = 'http://images.businessweek.com/mz/covers/current_120x160.jpg'
def parse_index(self):
#Go to the issue
soup = self.index_to_soup('http://www.businessweek.com/magazine/news/articles/business_news.htm')
@ -47,7 +46,6 @@ class BusinessWeekMagazine(BasicNewsRecipe):
if section_title not in feeds:
feeds[section_title] = []
feeds[section_title] += articles
div1 = soup.find ('div', attrs={'class':'column center'})
section_title = ''
for div in div1.findAll('h5'):

118
recipes/el_diplo.recipe Normal file
View File

@ -0,0 +1,118 @@
# Copyright 2013 Tomás Di Domenico
#
# This is a news fetching recipe for the Calibre ebook software, for
# fetching the Cono Sur edition of Le Monde Diplomatique (www.eldiplo.org).
#
# This recipe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this recipe. If not, see <http://www.gnu.org/licenses/>.
import re
from contextlib import closing
from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.magick import Image
class ElDiplo_Recipe(BasicNewsRecipe):
title = u'El Diplo'
__author__ = 'Tomas Di Domenico'
description = 'Publicacion mensual de Le Monde Diplomatique, edicion Argentina'
langauge = 'es_AR'
needs_subscription = True
auto_cleanup = True
def get_cover(self,url):
tmp_cover = PersistentTemporaryFile(suffix = ".jpg", prefix = "eldiplo_")
self.cover_url = tmp_cover.name
with closing(self.browser.open(url)) as r:
imgdata = r.read()
img = Image()
img.load(imgdata)
img.crop(img.size[0],img.size[1]/2,0,0)
img.save(tmp_cover.name)
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open('http://www.eldiplo.org/index.php/login/-/do_login/index.html')
br.select_form(nr=3)
br['uName'] = self.username
br['uPassword'] = self.password
br.submit()
self.browser = br
return br
def parse_index(self):
default_sect = 'General'
articles = {default_sect:[]}
ans = [default_sect]
sectionsmarker = 'DOSSIER_TITLE: '
sectionsre = re.compile('^'+sectionsmarker)
soup = self.index_to_soup('http://www.eldiplo.org/index.php')
coverdivs = soup.findAll(True,attrs={'id':['lmd-foto']})
a = coverdivs[0].find('a', href=True)
coverurl = a['href'].split("?imagen=")[1]
self.get_cover(coverurl)
thedivs = soup.findAll(True,attrs={'class':['lmd-leermas']})
for div in thedivs:
a = div.find('a', href=True)
if 'Sumario completo' in self.tag_to_string(a, use_alt=True):
summaryurl = re.sub(r'\?.*', '', a['href'])
summaryurl = 'http://www.eldiplo.org' + summaryurl
for pagenum in xrange(1,10):
soup = self.index_to_soup('{0}/?cms1_paging_p_b32={1}'.format(summaryurl,pagenum))
thedivs = soup.findAll(True,attrs={'class':['interna']})
if len(thedivs) == 0:
break
for div in thedivs:
section = div.find(True,text=sectionsre).replace(sectionsmarker,'')
if section == '':
section = default_sect
if section not in articles.keys():
articles[section] = []
ans.append(section)
nota = div.find(True,attrs={'class':['lmd-pl-titulo-nota-dossier']})
a = nota.find('a', href=True)
if not a:
continue
url = re.sub(r'\?.*', '', a['href'])
url = 'http://www.eldiplo.org' + url
title = self.tag_to_string(a, use_alt=True).strip()
summary = div.find(True, attrs={'class':'lmd-sumario-descript'}).find('p')
if summary:
description = self.tag_to_string(summary, use_alt=False)
aut = div.find(True, attrs={'class':'lmd-autor-sumario'})
if aut:
auth = self.tag_to_string(aut, use_alt=False).strip()
if not articles.has_key(section):
articles[section] = []
articles[section].append(dict(title=title,author=auth,url=url,date=None,description=description,content=''))
#ans = self.sort_index_by(ans, {'The Front Page':-1, 'Dining In, Dining Out':1, 'Obituaries':2})
ans = [(section, articles[section]) for section in ans if articles.has_key(section)]
return ans

View File

@ -18,7 +18,7 @@ class Fleshbot(BasicNewsRecipe):
encoding = 'utf-8'
use_embedded_content = True
language = 'en'
masthead_url = 'http://cache.gawkerassets.com/assets/kotaku.com/img/logo.png'
masthead_url = 'http://fbassets.s3.amazonaws.com/images/uploads/2012/01/fleshbot-logo.png'
extra_css = '''
body{font-family: "Lucida Grande",Helvetica,Arial,sans-serif}
img{margin-bottom: 1em}
@ -31,7 +31,7 @@ class Fleshbot(BasicNewsRecipe):
, 'language' : language
}
feeds = [(u'Articles', u'http://feeds.gawker.com/fleshbot/vip?format=xml')]
feeds = [(u'Articles', u'http://www.fleshbot.com/feed')]
remove_tags = [
{'class': 'feedflare'},

BIN
recipes/icons/libartes.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

View File

@ -28,12 +28,15 @@ class IlMessaggero(BasicNewsRecipe):
recursion = 10
remove_javascript = True
extra_css = ' .bianco31lucida{color: black} '
keep_only_tags = [dict(name='h1', attrs={'class':'titoloLettura2'}),
dict(name='h2', attrs={'class':'sottotitLettura'}),
dict(name='span', attrs={'class':'testoArticoloG'})
keep_only_tags = [dict(name='h1', attrs={'class':['titoloLettura2','titoloart','bianco31lucida']}),
dict(name='h2', attrs={'class':['sottotitLettura','grigio16']}),
dict(name='span', attrs={'class':'testoArticoloG'}),
dict(name='div', attrs={'id':'testodim'})
]
def get_cover_url(self):
cover = None
st = time.localtime()
@ -55,17 +58,16 @@ class IlMessaggero(BasicNewsRecipe):
feeds = [
(u'HomePage', u'http://www.ilmessaggero.it/rss/home.xml'),
(u'Primo Piano', u'http://www.ilmessaggero.it/rss/initalia_primopiano.xml'),
(u'Cronaca Bianca', u'http://www.ilmessaggero.it/rss/initalia_cronacabianca.xml'),
(u'Cronaca Nera', u'http://www.ilmessaggero.it/rss/initalia_cronacanera.xml'),
(u'Economia e Finanza', u'http://www.ilmessaggero.it/rss/economia.xml'),
(u'Politica', u'http://www.ilmessaggero.it/rss/initalia_politica.xml'),
(u'Scienza e Tecnologia', u'http://www.ilmessaggero.it/rss/scienza.xml'),
(u'Cinema', u'http://www.ilmessaggero.it/rss.php?refresh_ce#'),
(u'Viaggi', u'http://www.ilmessaggero.it/rss.php?refresh_ce#'),
(u'Cultura', u'http://www.ilmessaggero.it/rss/cultura.xml'),
(u'Tecnologia', u'http://www.ilmessaggero.it/rss/tecnologia.xml'),
(u'Spettacoli', u'http://www.ilmessaggero.it/rss/spettacoli.xml'),
(u'Edizioni Locali', u'http://www.ilmessaggero.it/rss/edlocali.xml'),
(u'Roma', u'http://www.ilmessaggero.it/rss/roma.xml'),
(u'Cultura e Tendenze', u'http://www.ilmessaggero.it/rss/roma_culturaspet.xml'),
(u'Benessere', u'http://www.ilmessaggero.it/rss/benessere.xml'),
(u'Sport', u'http://www.ilmessaggero.it/rss/sport.xml'),
(u'Calcio', u'http://www.ilmessaggero.it/rss/sport_calcio.xml'),
(u'Motori', u'http://www.ilmessaggero.it/rss/sport_motori.xml')
(u'Moda', u'http://www.ilmessaggero.it/rss/moda.xml')
]

69
recipes/libartes.recipe Normal file
View File

@ -0,0 +1,69 @@
__license__ = 'GPL v3'
__copyright__ = '2013, Darko Miletic <darko.miletic at gmail.com>'
'''
libartes.com
'''
import re
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class Libartes(BasicNewsRecipe):
title = 'Libartes'
__author__ = 'Darko Miletic'
description = 'Elektronski časopis Libartes delo je kulturnih entuzijasta, umetnika i teoretičara umetnosti i književnosti. Časopis Libartes izlazi tromesečno i bavi se različitim granama umetnosti - književnošću, muzikom, filmom, likovnim umetnostima, dizajnom i arhitekturom.'
publisher = 'Libartes'
category = 'literatura, knjizevnost, film, dizajn, arhitektura, muzika'
no_stylesheets = True
INDEX = 'http://libartes.com/'
use_embedded_content = False
encoding = 'utf-8'
language = 'sr'
publication_type = 'magazine'
masthead_url = 'http://libartes.com/index_files/logo.gif'
extra_css = """
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
body{font-family: "Times New Roman",Times,serif1, serif}
img{display:block}
.naslov{font-size: xx-large; font-weight: bold}
.nag{font-size: large; font-weight: bold}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
remove_tags_before = dict(attrs={'id':'nav'})
remove_tags_after = dict(attrs={'id':'fb' })
keep_only_tags = [dict(name='div', attrs={'id':'center_content'})]
remove_tags = [
dict(name=['object','link','iframe','embed','meta'])
,dict(attrs={'id':'nav'})
]
def parse_index(self):
articles = []
soup = self.index_to_soup(self.INDEX)
for item in soup.findAll(name='a', attrs={'class':'belad'}, href=True):
feed_link = item
if feed_link['href'].startswith(self.INDEX):
url = feed_link['href']
else:
url = self.INDEX + feed_link['href']
title = self.tag_to_string(feed_link)
date = strftime(self.timefmt)
articles.append({
'title' :title
,'date' :date
,'url' :url
,'description':''
})
return [('Casopis Libartes', articles)]

View File

@ -14,7 +14,8 @@ class LiberoNews(BasicNewsRecipe):
__author__ = 'Marini Gabriele'
description = 'Italian daily newspaper'
cover_url = 'http://www.libero-news.it/images/logo.png'
#cover_url = 'http://www.liberoquotidiano.it/images/Libero%20Quotidiano.jpg'
cover_url = 'http://www.edicola.liberoquotidiano.it/vnlibero/fpcut.jsp?testata=milano'
title = u'Libero '
publisher = 'EDITORIALE LIBERO s.r.l 2006'
category = 'News, politics, culture, economy, general interest'
@ -32,10 +33,11 @@ class LiberoNews(BasicNewsRecipe):
remove_javascript = True
keep_only_tags = [
dict(name='div', attrs={'class':'Articolo'})
dict(name='div', attrs={'class':'Articolo'}),
dict(name='article')
]
remove_tags = [
dict(name='div', attrs={'class':['CommentaFoto','Priva2']}),
dict(name='div', attrs={'class':['CommentaFoto','Priva2','login_commenti','box_16']}),
dict(name='div', attrs={'id':['commentigenerale']})
]
feeds = [

View File

@ -66,8 +66,9 @@ class NewYorkReviewOfBooks(BasicNewsRecipe):
self.log('Issue date:', date)
# Find TOC
toc = soup.find('ul', attrs={'class':'issue-article-list'})
tocs = soup.findAll('ul', attrs={'class':'issue-article-list'})
articles = []
for toc in tocs:
for li in toc.findAll('li'):
h3 = li.find('h3')
title = self.tag_to_string(h3)

View File

@ -0,0 +1,65 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class NYTimes(BasicNewsRecipe):
title = 'Outside Magazine'
__author__ = 'Krittika Goyal'
description = 'Outside Magazine - Free 1 Month Old Issue'
timefmt = ' [%d %b, %Y]'
needs_subscription = False
language = 'en'
no_stylesheets = True
#auto_cleanup = True
#auto_cleanup_keep = '//div[@class="thumbnail"]'
keep_only_tags = dict(name='div', attrs={'class':'masonry-box width-four'})
remove_tags = [
dict(name='div', attrs={'id':['share-bar', 'outbrain_widget_0', 'outbrain_widget_1', 'livefyre']}),
#dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}),
#dict(name='form', attrs={'onsubmit':''}),
dict(name='section', attrs={'id':['article-quote', 'article-navigation']}),
]
#TO GET ARTICLE TOC
def out_get_index(self):
super_url = 'http://www.outsideonline.com/magazine/'
super_soup = self.index_to_soup(super_url)
div = super_soup.find(attrs={'class':'masonry-box width-four'})
issue = div.findAll(name='article')[1]
super_a = issue.find('a', href=True)
return super_a.get('href')
# To parse artice toc
def parse_index(self):
parse_soup = self.index_to_soup(self.out_get_index())
feeds = []
feed_title = 'Articles'
articles = []
self.log('Found section:', feed_title)
div = parse_soup.find(attrs={'class':'print clearfix'})
for art in div.findAll(name='p'):
art_info = art.find(name = 'a')
if art_info is None:
continue
art_title = self.tag_to_string(art_info)
url = art_info.get('href') + '?page=all'
self.log.info('\tFound article:', art_title, 'at', url)
article = {'title':art_title, 'url':url, 'date':''}
#au = art.find(attrs={'class':'articleAuthors'})
#if au is not None:
#article['author'] = self.tag_to_string(au)
#desc = art.find(attrs={'class':'hover_text'})
#if desc is not None:
#desc = self.tag_to_string(desc)
#if 'author' in article:
#desc = ' by ' + article['author'] + ' ' +desc
#article['description'] = desc
articles.append(article)
if articles:
feeds.append((feed_title, articles))
return feeds

View File

@ -0,0 +1,22 @@
from calibre.web.feeds.news import BasicNewsRecipe
class HindustanTimes(BasicNewsRecipe):
title = u'Oxford Mail'
language = 'en_GB'
__author__ = 'Krittika Goyal'
oldest_article = 1 #days
max_articles_per_feed = 25
#encoding = 'cp1252'
use_embedded_content = False
no_stylesheets = True
auto_cleanup = True
feeds = [
('News',
'http://www.oxfordmail.co.uk/news/rss/'),
('Sports',
'http://www.oxfordmail.co.uk/sport/rss/'),
]

View File

@ -6,7 +6,6 @@ class PhilosophyNow(BasicNewsRecipe):
title = 'Philosophy Now'
__author__ = 'Rick Shang'
description = '''Philosophy Now is a lively magazine for everyone
interested in ideas. It isn't afraid to tackle all the major questions of
life, the universe and everything. Published every two months, it tries to
@ -27,7 +26,7 @@ class PhilosophyNow(BasicNewsRecipe):
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.open('https://philosophynow.org/auth/login')
br.select_form(nr = 1)
br.select_form(name="loginForm")
br['username'] = self.username
br['password'] = self.password
br.submit()
@ -50,19 +49,20 @@ class PhilosophyNow(BasicNewsRecipe):
#Go to the main body
current_issue_url = 'http://philosophynow.org/issues/' + issuenum
soup = self.index_to_soup(current_issue_url)
div = soup.find ('div', attrs={'class':'articlesColumn'})
div = soup.find ('div', attrs={'class':'contentsColumn'})
feeds = OrderedDict()
for post in div.findAll('h3'):
for post in div.findAll('h1'):
articles = []
a=post.find('a',href=True)
if a is not None:
url="http://philosophynow.org" + a['href']
title=self.tag_to_string(a).strip()
s=post.findPrevious('h4')
s=post.findPrevious('h3')
section_title = self.tag_to_string(s).strip()
d=post.findNext('p')
d=post.findNext('h2')
desc = self.tag_to_string(d).strip()
articles.append({'title':title, 'url':url, 'description':desc, 'date':''})
@ -73,3 +73,5 @@ class PhilosophyNow(BasicNewsRecipe):
ans = [(key, val) for key, val in feeds.iteritems()]
return ans
def cleanup(self):
self.browser.open('http://philosophynow.org/auth/logout')

View File

@ -0,0 +1,60 @@
from calibre.web.feeds.recipes import BasicNewsRecipe
class NYTimes(BasicNewsRecipe):
title = 'Spectator Magazine'
__author__ = 'Krittika Goyal'
description = 'Magazine'
timefmt = ' [%d %b, %Y]'
needs_subscription = False
language = 'en'
no_stylesheets = True
#auto_cleanup = True
#auto_cleanup_keep = '//div[@class="thumbnail"]'
keep_only_tags = dict(name='div', attrs={'id':'content'})
remove_tags = [
dict(name='div', attrs={'id':['disqus_thread']}),
##dict(name='div', attrs={'id':['qrformdiv', 'inSection', 'alpha-inner']}),
##dict(name='form', attrs={'onsubmit':''}),
#dict(name='section', attrs={'id':['article-quote', 'article-navigation']}),
]
#TO GET ARTICLE TOC
def spec_get_index(self):
return self.index_to_soup('http://www.spectator.co.uk/')
# To parse artice toc
def parse_index(self):
parse_soup = self.index_to_soup('http://www.spectator.co.uk/')
feeds = []
feed_title = 'Spectator Magazine Articles'
articles = []
self.log('Found section:', feed_title)
div = parse_soup.find(attrs={'class':'one-col-tax-widget magazine-list columns-1 post-8 taxonomy-category full-width widget section-widget icit-taxonomical-listings'})
for art in div.findAll(name='h2'):
art_info = art.find(name = 'a')
if art_info is None:
continue
art_title = self.tag_to_string(art_info)
url = art_info.get('href')
self.log.info('\tFound article:', art_title, 'at', url)
article = {'title':art_title, 'url':url, 'date':''}
#au = art.find(attrs={'class':'articleAuthors'})
#if au is not None:
#article['author'] = self.tag_to_string(au)
#desc = art.find(attrs={'class':'hover_text'})
#if desc is not None:
#desc = self.tag_to_string(desc)
#if 'author' in article:
#desc = ' by ' + article['author'] + ' ' +desc
#article['description'] = desc
articles.append(article)
if articles:
feeds.append((feed_title, articles))
return feeds

View File

@ -1,9 +1,12 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2012, Andreas Zeiser <andreas.zeiser@web.de>'
__copyright__ = '2012, 2013 Andreas Zeiser <andreas.zeiser@web.de>'
'''
szmobil.sueddeutsche.de/
'''
# History
# 2013.01.09 Fixed bugs in article titles containing "strong" and
# other small changes
# 2012.08.04 Initial release
from calibre import strftime
from calibre.web.feeds.recipes import BasicNewsRecipe
@ -26,6 +29,8 @@ class SZmobil(BasicNewsRecipe):
delay = 1
cover_source = 'http://www.sueddeutsche.de/verlag'
# if you want to get rid of the date on the title page use
# timefmt = ''
timefmt = ' [%a, %d %b, %Y]'
root_url ='http://szmobil.sueddeutsche.de/'
@ -76,10 +81,10 @@ class SZmobil(BasicNewsRecipe):
# first check if link is a special article in section "Meinungsseite"
if itt.find('strong')!= None:
article_name = itt.strong.string
article_shorttitle = itt.contents[1]
if len(itt.contents)>1:
shorttitles[article_id] = itt.contents[1]
articles.append( (article_name, article_url, article_id) )
shorttitles[article_id] = article_shorttitle
continue
@ -89,7 +94,7 @@ class SZmobil(BasicNewsRecipe):
else:
article_name = itt.string
if (article_name[0:10] == "&nbsp;mehr"):
if (article_name.find("&nbsp;mehr") == 0):
# just another link ("mehr") to an article
continue
@ -102,7 +107,9 @@ class SZmobil(BasicNewsRecipe):
for article_name, article_url, article_id in articles:
url = self.root_url + article_url
title = article_name
pubdate = strftime('%a, %d %b')
# if you want to get rid of date for each article use
# pubdate = strftime('')
pubdate = strftime('[%a, %d %b]')
description = ''
if shorttitles.has_key(article_id):
description = shorttitles[article_id]
@ -115,3 +122,4 @@ class SZmobil(BasicNewsRecipe):
return all_articles

View File

@ -16,8 +16,9 @@ class TidBITS(BasicNewsRecipe):
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
#auto_cleanup = True
encoding = 'utf-8'
use_embedded_content = True
use_embedded_content = False
language = 'en'
remove_empty_feeds = True
masthead_url = 'http://db.tidbits.com/images/tblogo9.gif'
@ -30,9 +31,11 @@ class TidBITS(BasicNewsRecipe):
, 'language' : language
}
remove_attributes = ['width','height']
remove_tags = [dict(name='small')]
remove_tags_after = dict(name='small')
#remove_attributes = ['width','height']
#remove_tags = [dict(name='small')]
#remove_tags_after = dict(name='small')
keep_only_tags = [dict(name='div', attrs={'id':'center_ajax_sub'})]
remove_tags = [dict(name='div', attrs={'id':'social-media'})]
feeds = [
(u'Business Apps' , u'http://db.tidbits.com/feeds/business.rss' )

Binary file not shown.

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, 13)
numeric_version = (0, 9, 14)
__version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -214,7 +214,7 @@ class ANDROID(USBMS):
'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD',
'PMP5097C', 'MASS', 'NOVO7', 'ZEKI', 'COBY', 'SXZ', 'USB_2.0',
'COBY_MID', 'VS', 'AINOL', 'TOPWISE', 'PAD703', 'NEXT8D12',
'MEDIATEK']
'MEDIATEK', 'KEENHI']
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID',
@ -234,7 +234,8 @@ class ANDROID(USBMS):
'THINKPAD_TABLET', 'SGH-T989', 'YP-G70', 'STORAGE_DEVICE',
'ADVANCED', 'SGH-I727', 'USB_FLASH_DRIVER', 'ANDROID',
'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E',
'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS']
'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS',
'ICS']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',

View File

@ -37,7 +37,7 @@ class KOBO(USBMS):
dbversion = 0
fwversion = 0
supported_dbversion = 65
supported_dbversion = 75
has_kepubs = False
supported_platforms = ['windows', 'osx', 'linux']

View File

@ -20,6 +20,9 @@ const calibre_device_entry_t calibre_mtp_device_table[] = {
, { "Google", 0x18d1, "Nexus 10", 0x4ee2, DEVICE_FLAGS_ANDROID_BUGS}
, { "Google", 0x18d1, "Nexus 10", 0x4ee1, DEVICE_FLAGS_ANDROID_BUGS}
// Kobo Arc
, { "Kobo", 0x2237, "Arc", 0xd108, DEVICE_FLAGS_ANDROID_BUGS}
, { NULL, 0xffff, NULL, 0xffff, DEVICE_FLAG_NONE }
};

View File

@ -13,7 +13,7 @@ from collections import namedtuple
from functools import partial
from calibre import prints, as_unicode
from calibre.constants import plugins
from calibre.constants import plugins, islinux
from calibre.ptempfile import SpooledTemporaryFile
from calibre.devices.errors import OpenFailed, DeviceError, BlacklistedDevice
from calibre.devices.mtp.base import MTPDeviceBase, synchronous, debug
@ -44,6 +44,17 @@ class MTP_DEVICE(MTPDeviceBase):
self.blacklisted_devices = set()
self.ejected_devices = set()
self.currently_connected_dev = None
self._is_device_mtp = None
if islinux:
from calibre.devices.mtp.unix.sysfs import MTPDetect
self._is_device_mtp = MTPDetect()
def is_device_mtp(self, d, debug=None):
''' Returns True iff the _is_device_mtp check returns True and libmtp
is able to probe the device successfully. '''
if self._is_device_mtp is None: return False
return (self._is_device_mtp(d, debug=debug) and
self.libmtp.is_mtp_device(d.busnum, d.devnum))
def set_debug_level(self, lvl):
self.libmtp.set_debug_level(lvl)
@ -77,7 +88,9 @@ class MTP_DEVICE(MTPDeviceBase):
for d in devs:
ans = cache.get(d, None)
if ans is None:
ans = (d.vendor_id, d.product_id) in self.known_devices
ans = (
(d.vendor_id, d.product_id) in self.known_devices or
self.is_device_mtp(d))
cache[d] = ans
if ans:
return d
@ -95,12 +108,13 @@ class MTP_DEVICE(MTPDeviceBase):
err = 'startup() not called on this device driver'
p(err)
return False
devs = [d for d in devices_on_system if (d.vendor_id, d.product_id)
in self.known_devices and d.vendor_id != APPLE]
devs = [d for d in devices_on_system if
( (d.vendor_id, d.product_id) in self.known_devices or
self.is_device_mtp(d, debug=p)) and d.vendor_id != APPLE]
if not devs:
p('No known MTP devices connected to system')
p('No MTP devices connected to system')
return False
p('Known MTP devices connected:')
p('MTP devices connected:')
for d in devs: p(d)
for d in devs:

View File

@ -662,13 +662,6 @@ is_mtp_device(PyObject *self, PyObject *args) {
if (!PyArg_ParseTuple(args, "ii", &busnum, &devnum)) return NULL;
/*
* LIBMTP_Check_Specific_Device does not seem to work at least on my linux
* system. Need to investigate why later. Most devices are in the device
* table so this is not terribly important.
*/
/* LIBMTP_Set_Debug(LIBMTP_DEBUG_ALL); */
/* printf("Calling check: %d %d\n", busnum, devnum); */
Py_BEGIN_ALLOW_THREADS;
ans = LIBMTP_Check_Specific_Device(busnum, devnum);
Py_END_ALLOW_THREADS;

View File

@ -0,0 +1,53 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, glob
class MTPDetect(object):
SYSFS_PATH = os.environ.get('SYSFS_PATH', '/sys')
def __init__(self):
self.base = os.path.join(self.SYSFS_PATH, 'subsystem', 'usb', 'devices')
if not os.path.exists(self.base):
self.base = os.path.join(self.SYSFS_PATH, 'bus', 'usb', 'devices')
self.ok = os.path.exists(self.base)
def __call__(self, dev, debug=None):
'''
Check if the device has an interface named "MTP" using sysfs, which
avoids probing the device.
'''
if not self.ok: return False
def read(x):
try:
with open(x, 'rb') as f:
return f.read()
except EnvironmentError:
pass
ipath = os.path.join(self.base, '{0}-*/{0}-*/interface'.format(dev.busnum))
for x in glob.glob(ipath):
raw = read(x)
if not raw or raw.strip() != b'MTP': continue
raw = read(os.path.join(os.path.dirname(os.path.dirname(x)),
'devnum'))
try:
if raw and int(raw) == dev.devnum:
if debug is not None:
debug('Unknown device {0} claims to be an MTP device'
.format(dev))
return True
except (ValueError, TypeError):
continue
return False

View File

@ -10,9 +10,7 @@ Convert OEB ebook format to PDF.
import glob, os
from PyQt4.Qt import QRawFont, QFont
from calibre.constants import iswindows
from calibre.constants import iswindows, islinux
from calibre.customize.conversion import (OutputFormatPlugin,
OptionRecommendation)
from calibre.ptempfile import TemporaryDirectory
@ -75,13 +73,13 @@ class PDFOutput(OutputFormatPlugin):
' of stretching it to fill the full first page of the'
' generated pdf.')),
OptionRecommendation(name='pdf_serif_family',
recommended_value='Times New Roman', help=_(
recommended_value='Liberation Serif' if islinux else 'Times New Roman', help=_(
'The font family used to render serif fonts')),
OptionRecommendation(name='pdf_sans_family',
recommended_value='Helvetica', help=_(
recommended_value='Liberation Sans' if islinux else 'Helvetica', help=_(
'The font family used to render sans-serif fonts')),
OptionRecommendation(name='pdf_mono_family',
recommended_value='Courier New', help=_(
recommended_value='Liberation Mono' if islinux else 'Courier New', help=_(
'The font family used to render monospaced fonts')),
OptionRecommendation(name='pdf_standard_font', choices=['serif',
'sans', 'mono'],
@ -104,6 +102,10 @@ class PDFOutput(OutputFormatPlugin):
])
def convert(self, oeb_book, output_path, input_plugin, opts, log):
from calibre.gui2 import must_use_qt, load_builtin_fonts
must_use_qt()
load_builtin_fonts()
self.oeb = oeb_book
self.input_plugin, self.opts, self.log = input_plugin, opts, log
self.output_path = output_path
@ -137,9 +139,8 @@ class PDFOutput(OutputFormatPlugin):
If you ever move to Qt WebKit 2.3+ then this will be unnecessary.
'''
from calibre.ebooks.oeb.base import urlnormalize
from calibre.gui2 import must_use_qt
from calibre.utils.fonts.utils import remove_embed_restriction
from PyQt4.Qt import QFontDatabase, QByteArray
from PyQt4.Qt import QFontDatabase, QByteArray, QRawFont, QFont
# First find all @font-face rules and remove them, adding the embedded
# fonts to Qt
@ -167,7 +168,6 @@ class PDFOutput(OutputFormatPlugin):
raw = remove_embed_restriction(raw)
except:
continue
must_use_qt()
fid = QFontDatabase.addApplicationFontFromData(QByteArray(raw))
family_name = None
if fid > -1:
@ -192,7 +192,10 @@ class PDFOutput(OutputFormatPlugin):
if ff is None: continue
val = ff.propertyValue
for i in xrange(val.length):
try:
k = icu_lower(val[i].value)
except (AttributeError, TypeError):
val[i].value = k = 'times'
if k in family_map:
val[i].value = family_map[k]
if iswindows:

View File

@ -36,7 +36,15 @@ class SubsetFonts(object):
self.oeb.manifest.remove(font['item'])
font['rule'].parentStyleSheet.deleteRule(font['rule'])
fonts = {}
for font in self.embedded_fonts:
item, chars = font['item'], font['chars']
if item.href in fonts:
fonts[item.href]['chars'] |= chars
else:
fonts[item.href] = font
for font in fonts.itervalues():
if not font['chars']:
self.log('The font %s is unused. Removing it.'%font['src'])
remove(font)

View File

@ -9,6 +9,7 @@ __docformat__ = 'restructuredtext en'
import codecs, zlib
from io import BytesIO
from datetime import datetime
from calibre.constants import plugins, ispy3
@ -65,14 +66,20 @@ def fmtnum(o):
def serialize(o, stream):
if isinstance(o, float):
stream.write_raw(pdf_float(o).encode('ascii'))
elif isinstance(o, bool):
# Must check bool before int as bools are subclasses of int
stream.write_raw(b'true' if o else b'false')
elif isinstance(o, (int, long)):
stream.write_raw(icb(o))
elif hasattr(o, 'pdf_serialize'):
o.pdf_serialize(stream)
elif o is None:
stream.write_raw(b'null')
elif isinstance(o, bool):
stream.write_raw(b'true' if o else b'false')
elif isinstance(o, datetime):
val = o.strftime("D:%Y%m%d%H%M%%02d%z")%min(59, o.second)
if datetime.tzinfo is not None:
val = "(%s'%s')"%(val[:-2], val[-2:])
stream.write(val.encode('ascii'))
else:
raise ValueError('Unknown object: %r'%o)

View File

@ -52,7 +52,6 @@ class PdfEngine(QPaintEngine):
FEATURES = QPaintEngine.AllFeatures & ~(
QPaintEngine.PorterDuff | QPaintEngine.PerspectiveTransform
| QPaintEngine.ObjectBoundingModeGradients
| QPaintEngine.LinearGradientFill
| QPaintEngine.RadialGradientFill
| QPaintEngine.ConicalGradientFill
)
@ -82,7 +81,7 @@ class PdfEngine(QPaintEngine):
self.bottom_margin) / self.pixel_height
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
self.graphics = Graphics()
self.graphics = Graphics(self.pixel_width, self.pixel_height)
self.errors_occurred = False
self.errors, self.debug = errors, debug
self.fonts = {}
@ -239,7 +238,7 @@ class PdfEngine(QPaintEngine):
@store_error
def drawTextItem(self, point, text_item):
# super(PdfEngine, self).drawTextItem(point, text_item)
# return super(PdfEngine, self).drawTextItem(point, text_item)
self.apply_graphics_state()
gi = self.qt_hack.get_glyphs(point, text_item)
if not gi.indices:
@ -247,7 +246,10 @@ class PdfEngine(QPaintEngine):
return
name = hash(bytes(gi.name))
if name not in self.fonts:
try:
self.fonts[name] = self.create_sfnt(text_item)
except UnsupportedFont:
return super(PdfEngine, self).drawTextItem(point, text_item)
metrics = self.fonts[name]
for glyph_id in gi.indices:
try:
@ -342,8 +344,8 @@ class PdfDevice(QPaintDevice): # {{{
return int(round(self.body_height * self.ydpi / 72.0))
return 0
def end_page(self):
self.engine.end_page()
def end_page(self, *args, **kwargs):
self.engine.end_page(*args, **kwargs)
def init_page(self):
self.engine.init_page()

View File

@ -47,7 +47,7 @@ def get_page_size(opts, for_comic=False): # {{{
if opts.unit == 'devicepixel':
factor = 72.0 / opts.output_profile.dpi
else:
{'point':1.0, 'inch':inch, 'cicero':cicero,
factor = {'point':1.0, 'inch':inch, 'cicero':cicero,
'didot':didot, 'pica':pica, 'millimeter':mm,
'centimeter':cm}[opts.unit]
page_size = (factor*width, factor*height)
@ -147,9 +147,10 @@ class PDFWriter(QObject):
opts = self.opts
page_size = get_page_size(self.opts)
xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY()
# We cannot set the side margins in the webview as there is no right
# margin for the last page (the margins are implemented with
# -webkit-column-gap)
ml, mr = opts.margin_left, opts.margin_right
margin_side = min(ml, mr)
ml, mr = ml - margin_side, mr - margin_side
self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml,
top_margin=0, right_margin=mr, bottom_margin=0,
xdpi=xdpi, ydpi=ydpi, errors=self.log.error,
@ -162,9 +163,7 @@ class PDFWriter(QObject):
self.total_items = len(items)
mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom))
ms = self.doc.to_px(margin_side, vertical=False)
self.margin_top, self.margin_size, self.margin_bottom = map(
lambda x:int(floor(x)), (mt, ms, mb))
self.margin_top, self.margin_bottom = map(lambda x:int(floor(x)), (mt, mb))
self.painter = QPainter(self.doc)
self.doc.set_metadata(title=pdf_metadata.title,
@ -176,6 +175,7 @@ class PDFWriter(QObject):
p = QPixmap()
p.loadFromData(self.cover_data)
if not p.isNull():
self.doc.init_page()
draw_image_page(QRect(0, 0, self.doc.width(), self.doc.height()),
self.painter, p,
preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio)
@ -184,7 +184,8 @@ class PDFWriter(QObject):
self.painter.restore()
QTimer.singleShot(0, self.render_book)
self.loop.exec_()
if self.loop.exec_() == 1:
raise Exception('PDF Output failed, see log for details')
if self.toc is not None and len(self.toc) > 0:
self.doc.add_outline(self.toc)
@ -257,7 +258,7 @@ class PDFWriter(QObject):
paged_display.layout();
paged_display.fit_images();
py_bridge.value = book_indexing.all_links_and_anchors();
'''%(self.margin_top, self.margin_size, self.margin_bottom))
'''%(self.margin_top, 0, self.margin_bottom))
amap = self.bridge_value
if not isinstance(amap, dict):
@ -278,6 +279,7 @@ class PDFWriter(QObject):
if self.doc.errors_occurred:
break
if not self.doc.errors_occurred:
self.doc.add_links(self.current_item, start_page, amap['links'],
amap['anchors'])

View File

@ -0,0 +1,125 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import sys
from future_builtins import map
from collections import namedtuple
import sip
from PyQt4.Qt import QLinearGradient, QPointF
from calibre.ebooks.pdf.render.common import Name, Array, Dictionary
Stop = namedtuple('Stop', 't color')
class LinearGradientPattern(Dictionary):
def __init__(self, brush, matrix, pdf, pixel_page_width, pixel_page_height):
self.matrix = (matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(),
matrix.dx(), matrix.dy())
gradient = sip.cast(brush.gradient(), QLinearGradient)
start, stop, stops = self.spread_gradient(gradient, pixel_page_width,
pixel_page_height, matrix)
# TODO: Handle colors with different opacities
self.const_opacity = stops[0].color[-1]
funcs = Array()
bounds = Array()
encode = Array()
for i, current_stop in enumerate(stops):
if i < len(stops) - 1:
next_stop = stops[i+1]
func = Dictionary({
'FunctionType': 2,
'Domain': Array([0, 1]),
'C0': Array(current_stop.color[:3]),
'C1': Array(next_stop.color[:3]),
'N': 1,
})
funcs.append(func)
encode.extend((0, 1))
if i+1 < len(stops) - 1:
bounds.append(next_stop.t)
func = Dictionary({
'FunctionType': 3,
'Domain': Array([stops[0].t, stops[-1].t]),
'Functions': funcs,
'Bounds': bounds,
'Encode': encode,
})
shader = Dictionary({
'ShadingType': 2,
'ColorSpace': Name('DeviceRGB'),
'AntiAlias': True,
'Coords': Array([start.x(), start.y(), stop.x(), stop.y()]),
'Function': func,
'Extend': Array([True, True]),
})
Dictionary.__init__(self, {
'Type': Name('Pattern'),
'PatternType': 2,
'Shading': shader,
'Matrix': Array(self.matrix),
})
self.cache_key = (self.__class__.__name__, self.matrix,
tuple(shader['Coords']), stops)
def spread_gradient(self, gradient, pixel_page_width, pixel_page_height,
matrix):
start = gradient.start()
stop = gradient.finalStop()
stops = list(map(lambda x: [x[0], x[1].getRgbF()], gradient.stops()))
spread = gradient.spread()
if False and spread != gradient.PadSpread:
# TODO: Finish this implementation
inv = matrix.inverted()[0]
page_rect = tuple(map(inv.map, (
QPointF(0, 0), QPointF(pixel_page_width, 0), QPointF(0, pixel_page_height),
QPointF(pixel_page_width, pixel_page_height))))
maxx = maxy = -sys.maxint-1
minx = miny = sys.maxint
for p in page_rect:
minx, maxx = min(minx, p.x()), max(maxx, p.x())
miny, maxy = min(miny, p.y()), max(maxy, p.y())
def in_page(point):
return (minx <= point.x() <= maxx and miny <= point.y() <= maxy)
offset = stop - start
llimit, rlimit = start, stop
reflect = False
base_stops = list(stops)
reversed_stops = list(reversed(stops))
do_reflect = spread == gradient.ReflectSpread
# totl = abs(stops[-1][0] - stops[0][0])
# intervals = [abs(stops[i+1] - stops[i])/totl for i in xrange(len(stops)-1)]
while in_page(llimit):
reflect ^= True
llimit -= offset
estops = reversed_stops if (reflect and do_reflect) else base_stops
stops = estops + stops
while in_page(rlimit):
reflect ^= True
rlimit += offset
estops = reversed_stops if (reflect and do_reflect) else base_stops
stops = stops + estops
return start, stop, tuple(Stop(s[0], s[1]) for s in stops)

View File

@ -11,11 +11,12 @@ from math import sqrt
from collections import namedtuple
from PyQt4.Qt import (
QBrush, QPen, Qt, QPointF, QTransform, QPainterPath, QPaintEngine, QImage)
QBrush, QPen, Qt, QPointF, QTransform, QPaintEngine, QImage)
from calibre.ebooks.pdf.render.common import (
Name, Array, fmtnum, Stream, Dictionary)
from calibre.ebooks.pdf.render.serialize import Path
from calibre.ebooks.pdf.render.gradients import LinearGradientPattern
def convert_path(path): # {{{
p = Path()
@ -248,7 +249,7 @@ class TexturePattern(TilingPattern):
class GraphicsState(object):
FIELDS = ('fill', 'stroke', 'opacity', 'transform', 'brush_origin',
'clip', 'do_fill', 'do_stroke')
'clip_updated', 'do_fill', 'do_stroke')
def __init__(self):
self.fill = QBrush()
@ -256,7 +257,7 @@ class GraphicsState(object):
self.opacity = 1.0
self.transform = QTransform()
self.brush_origin = QPointF()
self.clip = QPainterPath()
self.clip_updated = False
self.do_fill = False
self.do_stroke = True
self.qt_pattern_cache = {}
@ -274,16 +275,17 @@ class GraphicsState(object):
ans.opacity = self.opacity
ans.transform = self.transform * QTransform()
ans.brush_origin = QPointF(self.brush_origin)
ans.clip = self.clip
ans.clip_updated = self.clip_updated
ans.do_fill, ans.do_stroke = self.do_fill, self.do_stroke
return ans
class Graphics(object):
def __init__(self):
def __init__(self, page_width_px, page_height_px):
self.base_state = GraphicsState()
self.current_state = GraphicsState()
self.pending_state = None
self.page_width_px, self.page_height_px = (page_width_px, page_height_px)
def begin(self, pdf):
self.pdf = pdf
@ -311,7 +313,7 @@ class Graphics(object):
s.opacity = state.opacity()
if flags & QPaintEngine.DirtyClipPath or flags & QPaintEngine.DirtyClipRegion:
s.clip = painter.clipPath()
s.clip_updated = True
def reset(self):
self.current_state = GraphicsState()
@ -326,7 +328,7 @@ class Graphics(object):
ps = self.pending_state
pdf = self.pdf
if (ps.transform != pdf_state.transform or ps.clip != pdf_state.clip):
if ps.transform != pdf_state.transform or ps.clip_updated:
pdf.restore_stack()
pdf.save_stack()
pdf_state = self.base_state
@ -341,10 +343,13 @@ class Graphics(object):
pdf_state.brush_origin != ps.brush_origin):
self.apply_fill(ps, pdf_system, painter)
if (pdf_state.clip != ps.clip):
p = convert_path(ps.clip)
if ps.clip_updated:
ps.clip_updated = False
path = painter.clipPath()
if not path.isEmpty():
p = convert_path(path)
fill_rule = {Qt.OddEvenFill:'evenodd',
Qt.WindingFill:'winding'}[ps.clip.fillRule()]
Qt.WindingFill:'winding'}[path.fillRule()]
pdf.add_clip(p, fill_rule=fill_rule)
self.current_state = self.pending_state
@ -357,7 +362,7 @@ class Graphics(object):
pdf = self.pdf
pattern = color = pat = None
opacity = 1.0
opacity = global_opacity
do_fill = True
matrix = (QTransform.fromTranslate(brush_origin.x(), brush_origin.y())
@ -366,29 +371,30 @@ class Graphics(object):
self.brushobj = None
if style <= Qt.DiagCrossPattern:
opacity = global_opacity * vals[-1]
opacity *= vals[-1]
color = vals[:3]
if style > Qt.SolidPattern:
pat = QtPattern(style, matrix)
pattern = pdf.add_pattern(pat)
if opacity < 1e-4 or style == Qt.NoBrush:
do_fill = False
elif style == Qt.TexturePattern:
pat = TexturePattern(brush.texture(), matrix, pdf)
opacity = global_opacity
if pat.paint_type == 2:
opacity *= vals[-1]
color = vals[:3]
pattern = pdf.add_pattern(pat)
elif style == Qt.LinearGradientPattern:
pat = LinearGradientPattern(brush, matrix, pdf, self.page_width_px,
self.page_height_px)
opacity *= pat.const_opacity
# TODO: Add support for radial/conical gradient fills
if opacity < 1e-4 or style == Qt.NoBrush:
do_fill = False
self.brushobj = Brush(brush_origin, pat, color)
# TODO: Add support for gradient fills
if pat is not None:
pattern = pdf.add_pattern(pat)
return color, opacity, pattern, do_fill
def apply_stroke(self, state, pdf_system, painter):
@ -450,7 +456,10 @@ class Graphics(object):
TexturePatterns and it also uses TexturePatterns to emulate gradients,
leading to brokenness. So this method allows the paint engine to update
the brush origin before painting an object. While not perfect, this is
better than nothing.
better than nothing. The problem is that if the rect being filled has a
border, then QtWebKit generates an image of the rect size - border but
fills the full rect, and there's no way for the paint engine to know
that and adjust the brush origin.
'''
if not hasattr(self, 'last_fill') or not self.current_state.do_fill:
return

View File

@ -17,10 +17,14 @@ from calibre.ebooks.pdf.render.common import Array, Name, Dictionary, String
class Destination(Array):
def __init__(self, start_page, pos, get_pageref):
super(Destination, self).__init__(
[get_pageref(start_page + pos['column']), Name('XYZ'), pos['left'],
pos['top'], None]
)
pnum = start_page + pos['column']
try:
pref = get_pageref(pnum)
except IndexError:
pref = get_pageref(pnum-1)
super(Destination, self).__init__([
pref, Name('XYZ'), pos['left'], pos['top'], None
])
class Links(object):
@ -58,7 +62,13 @@ class Links(object):
0])})
if is_local:
path = combined_path if href else path
try:
annot['Dest'] = self.anchors[path][frag]
except KeyError:
try:
annot['Dest'] = self.anchors[path][None]
except KeyError:
pass
else:
url = href + (('#'+frag) if frag else '')
purl = urlparse(url)

View File

@ -18,6 +18,7 @@ from calibre.ebooks.pdf.render.common import (
fmtnum)
from calibre.ebooks.pdf.render.fonts import FontManager
from calibre.ebooks.pdf.render.links import Links
from calibre.utils.date import utcnow
PDFVER = b'%PDF-1.3'
@ -259,12 +260,15 @@ class PDFStream(object):
self.objects.add(PageTree(page_size))
self.objects.add(Catalog(self.page_tree))
self.current_page = Page(self.page_tree, compress=self.compress)
self.info = Dictionary({'Creator':String(creator),
'Producer':String(creator)})
self.info = Dictionary({
'Creator':String(creator),
'Producer':String(creator),
'CreationDate': utcnow(),
})
self.stroke_opacities, self.fill_opacities = {}, {}
self.font_manager = FontManager(self.objects, self.compress)
self.image_cache = {}
self.pattern_cache = {}
self.pattern_cache, self.shader_cache = {}, {}
self.debug = debug
self.links = Links(self, mark_links, page_size)
i = QImage(1, 1, QImage.Format_ARGB32)
@ -447,6 +451,11 @@ class PDFStream(object):
self.pattern_cache[pattern.cache_key] = self.objects.add(pattern)
return self.current_page.add_pattern(self.pattern_cache[pattern.cache_key])
def add_shader(self, shader):
if shader.cache_key not in self.shader_cache:
self.shader_cache[shader.cache_key] = self.objects.add(shader)
return self.shader_cache[shader.cache_key]
def draw_image(self, x, y, width, height, imgref):
name = self.current_page.add_image(imgref)
self.current_page.write('q %s 0 0 %s %s %s cm '%(fmtnum(width),

View File

@ -83,13 +83,15 @@ def run(dev, func):
raise SystemExit(1)
def brush(p, xmax, ymax):
x = xmax/3
x = 0
y = 0
w = xmax/2
pix = QPixmap(I('console.png'))
p.fillRect(x, y, w, w, QBrush(pix))
p.fillRect(0, y+xmax/1.9, w, w, QBrush(pix))
g = QLinearGradient(QPointF(x, y), QPointF(x, y+w))
g.setColorAt(0, QColor('#f00'))
g.setColorAt(0.5, QColor('#fff'))
g.setColorAt(1, QColor('#00f'))
p.fillRect(x, y, w, w, QBrush(g))
p.drawRect(x, y, w, w)
def pen(p, xmax, ymax):
pix = QPixmap(I('console.png'))

View File

@ -766,6 +766,26 @@ class Translator(QTranslator):
gui_thread = None
qt_app = None
def load_builtin_fonts():
global _rating_font
# Load the builtin fonts and any fonts added to calibre by the user to
# Qt
for ff in glob.glob(P('fonts/liberation/*.?tf')) + \
[P('fonts/calibreSymbols.otf')] + \
glob.glob(os.path.join(config_dir, 'fonts', '*.?tf')):
if ff.rpartition('.')[-1].lower() in {'ttf', 'otf'}:
with open(ff, 'rb') as s:
# Windows requires font files to be executable for them to be
# loaded successfully, so we use the in memory loader
fid = QFontDatabase.addApplicationFontFromData(s.read())
if fid > -1:
fam = QFontDatabase.applicationFontFamilies(fid)
fam = set(map(unicode, fam))
if u'calibre Symbols' in fam:
_rating_font = u'calibre Symbols'
class Application(QApplication):
def __init__(self, args, force_calibre_style=False,
@ -798,27 +818,12 @@ class Application(QApplication):
return ret
def load_builtin_fonts(self, scan_for_fonts=False):
global _rating_font
if scan_for_fonts:
from calibre.utils.fonts.scanner import font_scanner
# Start scanning the users computer for fonts
font_scanner
# Load the builtin fonts and any fonts added to calibre by the user to
# Qt
for ff in glob.glob(P('fonts/liberation/*.?tf')) + \
[P('fonts/calibreSymbols.otf')] + \
glob.glob(os.path.join(config_dir, 'fonts', '*.?tf')):
if ff.rpartition('.')[-1].lower() in {'ttf', 'otf'}:
with open(ff, 'rb') as s:
# Windows requires font files to be executable for them to be
# loaded successfully, so we use the in memory loader
fid = QFontDatabase.addApplicationFontFromData(s.read())
if fid > -1:
fam = QFontDatabase.applicationFontFamilies(fid)
fam = set(map(unicode, fam))
if u'calibre Symbols' in fam:
_rating_font = u'calibre Symbols'
load_builtin_fonts()
def load_calibre_style(self):
# On OS X QtCurve resets the palette, so we preserve it explicitly

View File

@ -169,6 +169,10 @@ class ChooseLibraryAction(InterfaceAction):
self.choose_menu = self.qaction.menu()
ac = self.create_action(spec=(_('Pick a random book'), 'random.png',
None, None), attr='action_pick_random')
ac.triggered.connect(self.pick_random)
if not os.environ.get('CALIBRE_OVERRIDE_DATABASE_PATH', None):
self.choose_menu.addAction(self.action_choose)
@ -176,12 +180,10 @@ class ChooseLibraryAction(InterfaceAction):
self.quick_menu_action = self.choose_menu.addMenu(self.quick_menu)
self.rename_menu = QMenu(_('Rename library'))
self.rename_menu_action = self.choose_menu.addMenu(self.rename_menu)
self.choose_menu.addAction(ac)
self.delete_menu = QMenu(_('Remove library'))
self.delete_menu_action = self.choose_menu.addMenu(self.delete_menu)
ac = self.create_action(spec=(_('Pick a random book'), 'random.png',
None, None), attr='action_pick_random')
ac.triggered.connect(self.pick_random)
else:
self.choose_menu.addAction(ac)
self.rename_separator = self.choose_menu.addSeparator()

View File

@ -8,10 +8,10 @@ from functools import partial
from PyQt4.Qt import QThread, QObject, Qt, QProgressDialog, pyqtSignal, QTimer
from calibre.gui2.dialogs.progress import ProgressDialog
from calibre.gui2 import (question_dialog, error_dialog, info_dialog, gprefs,
from calibre.gui2 import (error_dialog, info_dialog, gprefs,
warning_dialog, available_width)
from calibre.ebooks.metadata.opf2 import OPF
from calibre.ebooks.metadata import MetaInformation, authors_to_string
from calibre.ebooks.metadata import MetaInformation
from calibre.constants import preferred_encoding, filesystem_encoding, DEBUG
from calibre.utils.config import prefs
from calibre import prints, force_unicode, as_unicode
@ -391,25 +391,10 @@ class Adder(QObject): # {{{
if not duplicates:
return self.duplicates_processed()
self.pd.hide()
duplicate_message = []
for x in duplicates:
duplicate_message.append(_('Already in calibre:'))
matching_books = self.db.books_with_same_title(x[0])
for book_id in matching_books:
aut = [a.replace('|', ',') for a in (self.db.authors(book_id,
index_is_id=True) or '').split(',')]
duplicate_message.append('\t'+ _('%(title)s by %(author)s')%
dict(title=self.db.title(book_id, index_is_id=True),
author=authors_to_string(aut)))
duplicate_message.append(_('You are trying to add:'))
duplicate_message.append('\t'+_('%(title)s by %(author)s')%
dict(title=x[0].title,
author=x[0].format_field('authors')[1]))
duplicate_message.append('')
if question_dialog(self._parent, _('Duplicates found!'),
_('Books with the same title as the following already '
'exist in calibre. Add them anyway?'),
'\n'.join(duplicate_message)):
from calibre.gui2.dialogs.duplicates import DuplicatesQuestion
d = DuplicatesQuestion(self.db, duplicates, self._parent)
duplicates = tuple(d.duplicates)
if duplicates:
pd = QProgressDialog(_('Adding duplicates...'), '', 0, len(duplicates),
self._parent)
pd.setCancelButton(None)

View File

@ -369,6 +369,7 @@ class Series(Base):
w.setMinimumContentsLength(25)
self.name_widget = w
self.widgets = [QLabel('&'+self.col_metadata['name']+':', parent), w]
w.editTextChanged.connect(self.series_changed)
self.widgets.append(QLabel('&'+self.col_metadata['name']+_(' index:'), parent))
w = QDoubleSpinBox(parent)
@ -389,26 +390,33 @@ class Series(Base):
self.initial_index = s_index
self.initial_val = val
val = self.normalize_db_val(val)
self.name_widget.blockSignals(True)
self.name_widget.update_items_cache(values)
self.name_widget.show_initial_value(val)
self.name_widget.blockSignals(False)
def getter(self):
n = unicode(self.name_widget.currentText()).strip()
i = self.idx_widget.value()
return n, i
def series_changed(self, val):
val, s_index = self.gui_val
if tweaks['series_index_auto_increment'] == 'no_change':
pass
elif tweaks['series_index_auto_increment'] == 'const':
s_index = 1.0
else:
s_index = self.db.get_next_cc_series_num_for(val,
num=self.col_id)
self.idx_widget.setValue(s_index)
def commit(self, book_id, notify=False):
val, s_index = self.gui_val
val = self.normalize_ui_val(val)
if val != self.initial_val or s_index != self.initial_index:
if val == '':
val = s_index = None
elif s_index == 0.0:
if tweaks['series_index_auto_increment'] != 'const':
s_index = self.db.get_next_cc_series_num_for(val,
num=self.col_id)
else:
s_index = None
return self.db.set_custom(book_id, val, extra=s_index, num=self.col_id,
notify=notify, commit=False, allow_case_change=True)
else:

View File

@ -0,0 +1,118 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import (QDialog, QGridLayout, QIcon, QLabel, QTreeWidget,
QTreeWidgetItem, Qt, QFont, QDialogButtonBox)
from calibre.ebooks.metadata import authors_to_string
class DuplicatesQuestion(QDialog):
def __init__(self, db, duplicates, parent=None):
QDialog.__init__(self, parent)
self.l = l = QGridLayout()
self.setLayout(l)
self.setWindowTitle(_('Duplicates found!'))
self.i = i = QIcon(I('dialog_question.png'))
self.setWindowIcon(i)
self.l1 = l1 = QLabel()
self.l2 = l2 = QLabel(_(
'Books with the same titles as the following already '
'exist in calibre. Select which books you want added anyway.'))
l2.setWordWrap(True)
l1.setPixmap(i.pixmap(128, 128))
l.addWidget(l1, 0, 0)
l.addWidget(l2, 0, 1)
self.dup_list = dl = QTreeWidget(self)
l.addWidget(dl, 1, 0, 1, 2)
dl.setHeaderHidden(True)
dl.addTopLevelItems(list(self.process_duplicates(db, duplicates)))
dl.expandAll()
dl.setIndentation(30)
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject)
l.addWidget(bb, 2, 0, 1, 2)
self.ab = ab = bb.addButton(_('Select &all'), bb.ActionRole)
ab.clicked.connect(self.select_all)
self.nb = ab = bb.addButton(_('Select &none'), bb.ActionRole)
ab.clicked.connect(self.select_none)
self.resize(self.sizeHint())
self.exec_()
def select_all(self):
for i in xrange(self.dup_list.topLevelItemCount()):
x = self.dup_list.topLevelItem(i)
x.setCheckState(0, Qt.Checked)
def select_none(self):
for i in xrange(self.dup_list.topLevelItemCount()):
x = self.dup_list.topLevelItem(i)
x.setCheckState(0, Qt.Unchecked)
def reject(self):
self.select_none()
QDialog.reject(self)
def process_duplicates(self, db, duplicates):
ta = _('%(title)s by %(author)s')
bf = QFont(self.dup_list.font())
bf.setBold(True)
itf = QFont(self.dup_list.font())
itf.setItalic(True)
for mi, cover, formats in duplicates:
item = QTreeWidgetItem([ta%dict(
title=mi.title, author=mi.format_field('authors')[1])] , 0)
item.setCheckState(0, Qt.Checked)
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable)
item.setData(0, Qt.FontRole, bf)
item.setData(0, Qt.UserRole, (mi, cover, formats))
matching_books = db.books_with_same_title(mi)
def add_child(text):
c = QTreeWidgetItem([text], 1)
c.setFlags(Qt.ItemIsEnabled)
item.addChild(c)
return c
add_child(_('Already in calibre:')).setData(0, Qt.FontRole, itf)
for book_id in matching_books:
aut = [a.replace('|', ',') for a in (db.authors(book_id,
index_is_id=True) or '').split(',')]
add_child(ta%dict(
title=db.title(book_id, index_is_id=True),
author=authors_to_string(aut)))
add_child('')
yield item
@property
def duplicates(self):
for i in xrange(self.dup_list.topLevelItemCount()):
x = self.dup_list.topLevelItem(i)
if x.checkState(0) == Qt.Checked:
yield x.data(0, Qt.UserRole).toPyObject()
if __name__ == '__main__':
from PyQt4.Qt import QApplication
from calibre.ebooks.metadata.book.base import Metadata as M
from calibre.library import db
app = QApplication([])
db = db()
d = DuplicatesQuestion(db, [(M('Life of Pi', ['Yann Martel']), None, None),
(M('Heirs of the blade', ['Adrian Tchaikovsky']), None, None)])
print (tuple(d.duplicates))

View File

@ -1109,8 +1109,8 @@ not multiple and the destination field is multiple</string>
<rect>
<x>0</x>
<y>0</y>
<width>205</width>
<height>66</height>
<width>934</width>
<height>213</height>
</rect>
</property>
<layout class="QGridLayout" name="testgrid">
@ -1269,8 +1269,8 @@ not multiple and the destination field is multiple</string>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>252</x>
<y>382</y>
<x>258</x>
<y>638</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
@ -1285,8 +1285,8 @@ not multiple and the destination field is multiple</string>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>320</x>
<y>382</y>
<x>326</x>
<y>638</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
@ -1294,5 +1294,37 @@ not multiple and the destination field is multiple</string>
</hint>
</hints>
</connection>
<connection>
<sender>remove_all_tags</sender>
<signal>toggled(bool)</signal>
<receiver>remove_tags</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>888</x>
<y>266</y>
</hint>
<hint type="destinationlabel">
<x>814</x>
<y>268</y>
</hint>
</hints>
</connection>
<connection>
<sender>clear_languages</sender>
<signal>toggled(bool)</signal>
<receiver>languages</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>874</x>
<y>418</y>
</hint>
<hint type="destinationlabel">
<x>817</x>
<y>420</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1,10 +0,0 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

View File

@ -201,6 +201,7 @@ class SearchBar(QWidget): # {{{
x.setObjectName("search")
x.setToolTip(_("<p>Search the list of books by title, author, publisher, "
"tags, comments, etc.<br><br>Words separated by spaces are ANDed"))
x.setMinimumContentsLength(10)
l.addWidget(x)
self.search_button = QToolButton()
@ -225,7 +226,7 @@ class SearchBar(QWidget): # {{{
x = parent.saved_search = SavedSearchBox(self)
x.setMaximumSize(QSize(150, 16777215))
x.setMinimumContentsLength(15)
x.setMinimumContentsLength(10)
x.setObjectName("saved_search")
l.addWidget(x)

View File

@ -1106,6 +1106,7 @@ class SortKeyGenerator(object):
self.library_order = tweaks['title_series_sorting'] == 'library_order'
self.data = data
self.string_sort_key = sort_key
self.lang_idx = field_metadata['languages']['rec_index']
def __call__(self, record):
values = tuple(self.itervals(self.data[record]))
@ -1159,7 +1160,12 @@ class SortKeyGenerator(object):
val = ('', 1)
else:
if self.library_order:
val = title_sort(val)
try:
lang = record[self.lang_idx].partition(u',')[0]
except (AttributeError, ValueError, KeyError,
IndexError, TypeError):
lang = None
val = title_sort(val, order='library_order', lang=lang)
sidx_fm = self.field_metadata[name + '_index']
sidx = record[sidx_fm['rec_index']]
val = (self.string_sort_key(val), sidx)

View File

@ -532,6 +532,8 @@ class CustomColumns(object):
if data['datatype'] == 'series' and extra is None:
(val, extra) = self._get_series_values(val)
if extra is None:
extra = 1.0
books_to_refresh = set([])
if data['normalized']:

View File

@ -236,7 +236,7 @@ class ContentServer(object):
newmi = mi.deepcopy_metadata()
newmi.template_to_attribute(mi, cpb)
if format in ('MOBI', 'EPUB'):
if format in {'MOBI', 'EPUB', 'AZW3'}:
# Write the updated file
from calibre.ebooks.metadata.meta import set_metadata
set_metadata(fmt, newmi, format.lower())

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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