mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
c2b317bab4
@ -20,6 +20,80 @@
|
||||
# - title:
|
||||
|
||||
|
||||
- version: 0.8.57
|
||||
date: 2012-06-22
|
||||
|
||||
new features:
|
||||
- title: "PDF Output: Full pagination support. No more cutoff bottom line."
|
||||
type: major
|
||||
description: "Fixes a long standing bug in calibre's PDF Output that caused the bottom line of some pages to be partially cut off and prevented top and bottom margins from working."
|
||||
|
||||
- title: "calibredb add now prints out the ids of added books"
|
||||
tickets: [1014303]
|
||||
|
||||
- title: "Kobo Vox driver: Add support for new Google Play firmware"
|
||||
tickets: [1014129]
|
||||
|
||||
- title: "Driver for Prestigio PMP5097PRO"
|
||||
tickets: [1013864]
|
||||
|
||||
- title: "Add option to disable tooltips in the book list under Preferences->Look & Feel"
|
||||
|
||||
- title: "When customizing builtin recipes download the latest version of the recipe to customize instead of using the possibly out of date bundled version"
|
||||
|
||||
bug fixes:
|
||||
- title: "PDF Output: Use the cover from the input document when no cover is specified during a conversion"
|
||||
|
||||
- title: "E-book Viewer: Printing now has proper pagination with top and bottom margins no lines partially cut-off at the bottom and full style retention"
|
||||
|
||||
- title: "KF8 Input: Handle files with incorrectly encoded guide type entries."
|
||||
tickets: [1015020]
|
||||
|
||||
- title: "E-book viewer: Disable hyphenation on windows xp as Qt WebKit barfs on soft hyphens on windows XP"
|
||||
|
||||
- title: "Handle OS X systems with invalid palette colors."
|
||||
tickets: [1014900]
|
||||
|
||||
- title: "Tag Browser: Fix regression that broke partitioning of hierarchical categories."
|
||||
tickets: [1014065]
|
||||
|
||||
- title: "LRF Output: Handle negative page margins"
|
||||
tickets: [1014103]
|
||||
|
||||
- title: "Template language: Fix arithmetic functions to tolerate the value 'None' as returned by raw_field()"
|
||||
|
||||
- title: "Fix custom title sort set in the edit metadata dialog getting reset by the conversion dialog"
|
||||
|
||||
improved recipes:
|
||||
- The Economist
|
||||
- Akter
|
||||
- 24 Sata sr
|
||||
- Novi List
|
||||
- Metro Montreal
|
||||
- Mode Durable
|
||||
- CanardPC
|
||||
- The Economic Collapse
|
||||
- Our Daily Bread
|
||||
|
||||
new recipes:
|
||||
- title: Akter Daily
|
||||
author: Darko MIletic
|
||||
|
||||
- title: BBC Brasil
|
||||
author: Claviola
|
||||
|
||||
- title: Homopedia.pl
|
||||
author: rainbowwarrior
|
||||
|
||||
- title: National Geographic Magazine
|
||||
author: Terminal Veracity
|
||||
|
||||
- title: Something Awful
|
||||
author: atordo
|
||||
|
||||
- title: Huffington Post UK
|
||||
author: Krittika Goyal
|
||||
|
||||
- version: 0.8.56
|
||||
date: 2012-06-15
|
||||
|
||||
|
@ -195,7 +195,7 @@ It can get tiresome to keep re-adding a plugin to calibre to test small changes.
|
||||
|
||||
Once you've located the zip file of your plugin you can then directly update it with your changes instead of re-adding it each time. To do so from the command line, in the directory that contains your plugin source code, use::
|
||||
|
||||
calibre -s; sleep 4s; zip -R /path/to/plugin/zip/file.zip *; calibre
|
||||
calibre -s; zip -R /path/to/plugin/zip/file.zip *; calibre
|
||||
|
||||
This will shutdown a running calibre. Wait for the shutdown to complete, then update your plugin files and relaunch calibre.
|
||||
It relies on the freely available zip command line tool.
|
||||
|
@ -1,6 +1,7 @@
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2009-2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
|
||||
'''
|
||||
24sata.rs
|
||||
@ -21,26 +22,29 @@ class Ser24Sata(BasicNewsRecipe):
|
||||
encoding = 'utf-8'
|
||||
use_embedded_content = False
|
||||
language = 'sr'
|
||||
publication_type = 'newspaper'
|
||||
extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} body{font-family: serif1, serif} .article_description{font-family: serif1, serif}'
|
||||
publication_type = 'newsportal'
|
||||
extra_css = """
|
||||
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
|
||||
body{font-family: serif1, serif}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
, 'linearize_tables' : True
|
||||
}
|
||||
|
||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||
|
||||
feeds = [(u'Vesti Dana', u'http://www.24sata.rs/rss.php')]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
return self.adeify_images(soup)
|
||||
feeds = [
|
||||
(u'Vesti' , u'http://www.24sata.rs/rss/vesti.xml' ),
|
||||
(u'Sport' , u'http://www.24sata.rs/rss/sport.xml' ),
|
||||
(u'Šou' , u'http://www.24sata.rs/rss/sou.xml' ),
|
||||
(u'Specijal', u'http://www.24sata.rs/rss/specijal.xml'),
|
||||
(u'Novi Sad', u'http://www.24sata.rs/rss/ns.xml' )
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
article = url.partition('#')[0]
|
||||
article_id = article.partition('id=')[2]
|
||||
return 'http://www.24sata.rs/_print.php?id=' + article_id
|
||||
|
||||
dpart, spart, apart = url.rpartition('/')
|
||||
return dpart + '/print/' + apart
|
||||
|
@ -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>'
|
||||
'''
|
||||
abc.com.py
|
||||
'''
|
||||
@ -7,7 +7,7 @@ abc.com.py
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class ABC_py(BasicNewsRecipe):
|
||||
title = 'ABC digital'
|
||||
title = 'ABC Color'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Noticias de Paraguay y el resto del mundo'
|
||||
publisher = 'ABC'
|
||||
@ -15,12 +15,16 @@ class ABC_py(BasicNewsRecipe):
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'cp1252'
|
||||
encoding = 'utf8'
|
||||
use_embedded_content = False
|
||||
language = 'es_PY'
|
||||
remove_empty_feeds = True
|
||||
masthead_url = 'http://www.abc.com.py/plantillas/img/abc-logo.png'
|
||||
publication_type = 'newspaper'
|
||||
extra_css = ' body{font-family: Arial,Helvetica,sans-serif } img{margin-bottom: 0.4em} '
|
||||
extra_css = """
|
||||
body{font-family: UnitSlabProMedium,"Times New Roman",serif }
|
||||
img{margin-bottom: 0.4em; display: block;}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
@ -29,21 +33,19 @@ class ABC_py(BasicNewsRecipe):
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
remove_tags = [dict(name=['form','iframe','embed','object','link','base','table']),dict(attrs={'class':'toolbox'})]
|
||||
remove_tags_after = dict(attrs={'class':'date'})
|
||||
keep_only_tags = [dict(attrs={'class':'zcontent'})]
|
||||
remove_tags = [
|
||||
dict(name=['form','iframe','embed','object','link','base','table']),
|
||||
dict(attrs={'class':['es-carousel-wrapper']}),
|
||||
dict(attrs={'id':['tools','article-banner-1']})
|
||||
]
|
||||
keep_only_tags = [dict(attrs={'id':'article'})]
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Ultimo momento' , u'http://www.abc.com.py/ultimo-momento.xml' )
|
||||
,(u'Nacionales' , u'http://www.abc.com.py/nacionales.xml' )
|
||||
,(u'Internacionales' , u'http://www.abc.com.py/internacionales.xml' )
|
||||
,(u'Deportes' , u'http://www.abc.com.py/deportes.xml' )
|
||||
,(u'Espectaculos' , u'http://www.abc.com.py/espectaculos.xml' )
|
||||
,(u'Ciencia y Tecnologia', u'http://www.abc.com.py/ciencia-y-tecnologia.xml')
|
||||
(u'Ultimo momento', u'http://www.abc.com.py/rss.xml' )
|
||||
,(u'Nacionales' , u'http://www.abc.com.py/nacionales/rss.xml' )
|
||||
,(u'Mundo' , u'http://www.abc.com.py/internacionales/rss.xml')
|
||||
,(u'Deportes' , u'http://www.abc.com.py/deportes/rss.xml' )
|
||||
,(u'Espectaculos' , u'http://www.abc.com.py/espectaculos/rss.xml' )
|
||||
,(u'TecnoCiencia' , u'http://www.abc.com.py/ciencia/rss.xml' )
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
@ -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>'
|
||||
'''
|
||||
akter.co.rs
|
||||
'''
|
||||
@ -8,7 +8,7 @@ import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Akter(BasicNewsRecipe):
|
||||
title = 'AKTER'
|
||||
title = 'AKTER - Nedeljnik'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'AKTER - nedeljni politicki magazin savremene Srbije'
|
||||
publisher = 'Akter Media Group d.o.o.'
|
||||
@ -18,61 +18,37 @@ class Akter(BasicNewsRecipe):
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf-8'
|
||||
masthead_url = 'http://www.akter.co.rs/templates/gk_thenews2/images/style2/logo.png'
|
||||
masthead_url = 'http://www.akter.co.rs/gfx/logoneover.png'
|
||||
language = 'sr'
|
||||
publication_type = 'magazine'
|
||||
remove_empty_feeds = True
|
||||
PREFIX = 'http://www.akter.co.rs'
|
||||
extra_css = """
|
||||
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||
.article_description,body{font-family: Arial,Helvetica,sans1,sans-serif}
|
||||
.color-2{display:block; margin-bottom: 10px; padding: 5px, 10px;
|
||||
border-left: 1px solid #D00000; color: #D00000}
|
||||
img{margin-bottom: 0.8em} """
|
||||
body{font-family: Tahoma,Geneva,sans1,sans-serif}
|
||||
img{margin-bottom: 0.8em; display: block;}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
, 'linearize_tables' : True
|
||||
}
|
||||
|
||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||
|
||||
feeds = [
|
||||
(u'Politika' , u'http://www.akter.co.rs/index.php/politikaprint.html' )
|
||||
,(u'Ekonomija' , u'http://www.akter.co.rs/index.php/ekonomijaprint.html')
|
||||
,(u'Life&Style' , u'http://www.akter.co.rs/index.php/lsprint.html' )
|
||||
,(u'Sport' , u'http://www.akter.co.rs/index.php/sportprint.html' )
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return self.adeify_images(soup)
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'section_to_print'})]
|
||||
feeds = [(u'Nedeljnik', u'http://akter.co.rs/rss/nedeljnik')]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?tmpl=component&print=1&page='
|
||||
dpart, spart, apart = url.rpartition('/')
|
||||
return dpart + '/print-' + apart
|
||||
|
||||
def parse_index(self):
|
||||
totalfeeds = []
|
||||
lfeeds = self.get_feeds()
|
||||
for feedobj in lfeeds:
|
||||
feedtitle, feedurl = feedobj
|
||||
self.report_progress(0, _('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl))
|
||||
articles = []
|
||||
soup = self.index_to_soup(feedurl)
|
||||
for item in soup.findAll(attrs={'class':['sectiontableentry1','sectiontableentry2']}):
|
||||
link = item.find('a')
|
||||
url = self.PREFIX + link['href']
|
||||
title = self.tag_to_string(link)
|
||||
articles.append({
|
||||
'title' :title
|
||||
,'date' :''
|
||||
,'url' :url
|
||||
,'description':''
|
||||
})
|
||||
totalfeeds.append((feedtitle, articles))
|
||||
return totalfeeds
|
||||
def get_cover_url(self):
|
||||
soup = self.index_to_soup('http://www.akter.co.rs/weekly.html')
|
||||
divt = soup.find('div', attrs={'class':'lastissue'})
|
||||
if divt:
|
||||
imgt = divt.find('img')
|
||||
if imgt:
|
||||
return 'http://www.akter.co.rs' + imgt['src']
|
||||
return None
|
||||
|
||||
|
44
recipes/akter_dnevnik.recipe
Normal file
44
recipes/akter_dnevnik.recipe
Normal file
@ -0,0 +1,44 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
akter.co.rs
|
||||
'''
|
||||
|
||||
import re
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Akter(BasicNewsRecipe):
|
||||
title = 'AKTER - Dnevnik'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'AKTER - Najnovije vesti iz Srbije'
|
||||
publisher = 'Akter Media Group d.o.o.'
|
||||
category = 'vesti, online vesti, najnovije vesti, politika, sport, ekonomija, biznis, finansije, berza, kultura, zivot, putovanja, auto, automobili, tehnologija, politicki magazin, dogadjaji, desavanja, lifestyle, zdravlje, zdravstvo, vest, novine, nedeljnik, srbija, novi sad, vojvodina, svet, drustvo, zabava, republika srpska, beograd, intervju, komentar, reportaza, arhiva vesti, news, serbia, politics'
|
||||
oldest_article = 8
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
encoding = 'utf-8'
|
||||
masthead_url = 'http://www.akter.co.rs/gfx/logodnover.png'
|
||||
language = 'sr'
|
||||
publication_type = 'magazine'
|
||||
remove_empty_feeds = True
|
||||
extra_css = """
|
||||
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||
body{font-family: Tahoma,Geneva,sans1,sans-serif}
|
||||
img{margin-bottom: 0.8em; display: block;}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher': publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'section_to_print'})]
|
||||
feeds = [(u'Vesti', u'http://akter.co.rs/rss/dnevni')]
|
||||
|
||||
def print_version(self, url):
|
||||
dpart, spart, apart = url.rpartition('/')
|
||||
return dpart + '/print-' + apart
|
@ -147,10 +147,9 @@ class BBCBrasilRecipe(BasicNewsRecipe):
|
||||
|
||||
|
||||
# Author of this recipe.
|
||||
__author__ = 'claviola'
|
||||
__author__ = 'Carlos Laviola'
|
||||
|
||||
# Specify English as the language of the RSS feeds (ISO-639 code).
|
||||
language = 'en_GB'
|
||||
language = 'pt_BR'
|
||||
|
||||
# Set tags.
|
||||
tags = 'news, sport, blog'
|
||||
|
@ -20,7 +20,23 @@ class Economist(BasicNewsRecipe):
|
||||
INDEX = 'http://www.economist.com/printedition'
|
||||
description = ('Global news and current affairs from a European'
|
||||
' perspective. Best downloaded on Friday mornings (GMT)')
|
||||
extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }'
|
||||
extra_css = '''
|
||||
.headline {font-size: x-large;}
|
||||
h2 { font-size: small; }
|
||||
h1 { font-size: medium; }
|
||||
.pullquote {
|
||||
float: right;
|
||||
font-size: larger;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
page-break-inside:avoid;
|
||||
border-bottom: 3px solid black;
|
||||
border-top: 3px solid black;
|
||||
width: 228px;
|
||||
margin: 0px 0px 10px 15px;
|
||||
padding: 7px 0px 9px;
|
||||
}
|
||||
'''
|
||||
oldest_article = 7.0
|
||||
remove_tags = [
|
||||
dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
|
||||
|
@ -20,7 +20,24 @@ class Economist(BasicNewsRecipe):
|
||||
INDEX = 'http://www.economist.com/printedition'
|
||||
description = ('Global news and current affairs from a European'
|
||||
' perspective. Best downloaded on Friday mornings (GMT)')
|
||||
extra_css = '.headline {font-size: x-large;} \n h2 { font-size: small; } \n h1 { font-size: medium; }'
|
||||
extra_css = '''
|
||||
.headline {font-size: x-large;}
|
||||
h2 { font-size: small; }
|
||||
h1 { font-size: medium; }
|
||||
.pullquote {
|
||||
float: right;
|
||||
font-size: larger;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
page-break-inside:avoid;
|
||||
border-bottom: 3px solid black;
|
||||
border-top: 3px solid black;
|
||||
width: 228px;
|
||||
margin: 0px 0px 10px 15px;
|
||||
padding: 7px 0px 9px;
|
||||
}
|
||||
'''
|
||||
|
||||
oldest_article = 7.0
|
||||
remove_tags = [
|
||||
dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
|
||||
|
56
recipes/marketing_sensoriale.recipe
Normal file
56
recipes/marketing_sensoriale.recipe
Normal file
@ -0,0 +1,56 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.utils.ipc.simple_worker import fork_job
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
|
||||
js_fetcher = '''
|
||||
|
||||
import calibre.web.jsbrowser.browser as jsbrowser
|
||||
|
||||
def grab(url):
|
||||
browser = jsbrowser.Browser()
|
||||
#10 second timeout
|
||||
browser.visit(url, 10)
|
||||
browser.run_for_a_time(10)
|
||||
html = browser.html
|
||||
browser.close()
|
||||
return html
|
||||
|
||||
'''
|
||||
class MarketingSensoriale(BasicNewsRecipe):
|
||||
|
||||
title = u'Marketing sensoriale'
|
||||
__author__ = 'NotTaken'
|
||||
description = 'Marketing Sensoriale, il Blog'
|
||||
category = 'Blog'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 200
|
||||
no_stylesheets = True
|
||||
encoding = 'utf8'
|
||||
use_embedded_content = False
|
||||
language = 'it'
|
||||
remove_empty_feeds = True
|
||||
recursions = 0
|
||||
requires_version = (0, 8, 58)
|
||||
auto_cleanup = False
|
||||
simultaneous_downloads = 1
|
||||
articles_are_obfuscated = True
|
||||
|
||||
remove_tags_after = [dict(name='div', attrs={'class':['article-footer']})]
|
||||
|
||||
|
||||
def get_article_url(self, article):
|
||||
return article.get('feedburner_origlink', None)
|
||||
|
||||
def get_obfuscated_article(self, url):
|
||||
result = fork_job(js_fetcher, 'grab', (url,), module_is_source_code=True)
|
||||
|
||||
html = result['result']
|
||||
if isinstance(html, type(u'')):
|
||||
html = html.encode('utf-8')
|
||||
pt = PersistentTemporaryFile('.html')
|
||||
pt.write(html)
|
||||
pt.close()
|
||||
return pt.name
|
||||
|
||||
feeds = [(u'Marketing sensoriale', u'http://feeds.feedburner.com/MarketingSensoriale?format=xml')]
|
||||
|
@ -10,11 +10,11 @@ from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class OGlobo(BasicNewsRecipe):
|
||||
title = 'O Globo'
|
||||
__author__ = 'Darko Miletic and Sujata Raman'
|
||||
__author__ = 'Darko Miletic and Carlos Laviola'
|
||||
description = 'News from Brasil'
|
||||
publisher = 'O Globo'
|
||||
category = 'news, politics, Brasil'
|
||||
oldest_article = 2
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
@ -39,43 +39,35 @@ class OGlobo(BasicNewsRecipe):
|
||||
.commentario p{color:#007BB5; font-style:italic;}
|
||||
'''
|
||||
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'id':'ltintb'}),
|
||||
dict(name='a', attrs={'class':['img imgLoader','img ftr imgLoader']}),]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='script')
|
||||
,dict(name='object')
|
||||
,dict(name='form')
|
||||
,dict(name='div', attrs={'id':['linksPatGoogle','rdpm','cor','com','env','rcm_st','coment',]})
|
||||
,dict(name='div', attrs={'class':'box-zap-anu2'})
|
||||
,dict(name='a', attrs={'class':'assine'})
|
||||
,dict(name='link')
|
||||
,dict(name='div', attrs={'id':'header'})
|
||||
,dict(name='p', attrs={'id':'info-date-press'})
|
||||
]
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'Todos os canais', u'http://oglobo.globo.com/rss/plantao.xml')
|
||||
,(u'Ciencia', u'http://oglobo.globo.com/rss/plantaociencia.xml')
|
||||
,(u'Educacao', u'http://oglobo.globo.com/rss/plantaoeducacao.xml')
|
||||
,(u'Opiniao', u'http://oglobo.globo.com/rss/plantaoopiniao.xml')
|
||||
,(u'Sao Paulo', u'http://oglobo.globo.com/rss/plantaosaopaulo.xml')
|
||||
,(u'Viagem', u'http://oglobo.globo.com/rss/plantaoviagem.xml')
|
||||
,(u'Cultura', u'http://oglobo.globo.com/rss/plantaocultura.xml')
|
||||
,(u'Esportes', u'http://oglobo.globo.com/rss/plantaoesportes.xml')
|
||||
,(u'Mundo', u'http://oglobo.globo.com/rss/plantaomundo.xml')
|
||||
,(u'Pais', u'http://oglobo.globo.com/rss/plantaopais.xml')
|
||||
,(u'Rio', u'http://oglobo.globo.com/rss/plantaorio.xml')
|
||||
,(u'Saude', u'http://oglobo.globo.com/rss/plantaosaude.xml')
|
||||
,(u'Viver Melhor', u'http://oglobo.globo.com/rss/plantaovivermelhor.xml')
|
||||
,(u'Economia', u'http://oglobo.globo.com/rss/plantaoeconomia.xml')
|
||||
,(u'Tecnologia', u'http://oglobo.globo.com/rss/plantaotecnologia.xml')
|
||||
(u'Todos os canais', u'http://oglobo.globo.com/rss.xml?completo=true')
|
||||
,(u'Ciencia', u'http://oglobo.globo.com/rss.xml?secao=ciencia&completo=true')
|
||||
,(u'Educacao', u'http://oglobo.globo.com/rss.xml?secao=educacao&completo=true')
|
||||
,(u'Opiniao', u'http://oglobo.globo.com/rss.xml?secao=opiniao&completo=true')
|
||||
,(u'Cultura', u'http://oglobo.globo.com/rss.xml?secao=cultura&completo=true')
|
||||
,(u'Esportes', u'http://oglobo.globo.com/rss.xml?secao=esportes&completo=true')
|
||||
,(u'Mundo', u'http://oglobo.globo.com/rss.xml?secao=mundo&completo=true')
|
||||
,(u'Pais', u'http://oglobo.globo.com/rss.xml?secao=pais&completo=true')
|
||||
,(u'Rio', u'http://oglobo.globo.com/rss.xml?secao=rio&completo=true')
|
||||
,(u'Saude', u'http://oglobo.globo.com/rss.xml?secao=saude&completo=true')
|
||||
,(u'Economia', u'http://oglobo.globo.com/rss.xml?secao=economia&completo=true')
|
||||
,(u'Tecnologia', u'http://oglobo.globo.com/rss.xml?secao=tecnologia&completo=true')
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?service=print'
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
||||
language = 'pt'
|
||||
|
||||
language = 'pt_BR'
|
||||
|
Binary file not shown.
@ -60,7 +60,12 @@ function goto_reference(ref) {
|
||||
if (num < 0) {alert("Invalid reference: "+ref); return;}
|
||||
var p = $("p");
|
||||
if (num >= p.length) {alert("Reference not found: "+ref); return;}
|
||||
$.scrollTo($(p[num]), 1000,
|
||||
var dest = $(p[num]);
|
||||
if (window.paged_display.in_paged_mode) {
|
||||
var xpos = dest.offset().left;
|
||||
window.paged_display.scroll_to_xpos(xpos, true, true, 1000);
|
||||
} else
|
||||
$.scrollTo(dest, 1000,
|
||||
{onAfter:function(){window.py_bridge.animated_scroll_done()}});
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,15 @@ def get_rsync_pw():
|
||||
return open('/home/kovid/work/kde/conf/buildbot').read().partition(
|
||||
':')[-1].strip()
|
||||
|
||||
def is_vm_running(name):
|
||||
pat = '/%s/'%name
|
||||
pids= [pid for pid in os.listdir('/proc') if pid.isdigit()]
|
||||
for pid in pids:
|
||||
cmdline = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read()
|
||||
if 'vmware-vmx' in cmdline and pat in cmdline:
|
||||
return True
|
||||
return False
|
||||
|
||||
class Rsync(Command):
|
||||
|
||||
description = 'Sync source tree from development machine'
|
||||
@ -46,14 +55,16 @@ class Push(Command):
|
||||
def run(self, opts):
|
||||
from threading import Thread
|
||||
threads = []
|
||||
for host in (
|
||||
r'Owner@winxp:/cygdrive/c/Documents\ and\ Settings/Owner/calibre',
|
||||
'kovid@ox:calibre',
|
||||
r'kovid@win7:/cygdrive/c/Users/kovid/calibre',
|
||||
):
|
||||
for host, vmname in {
|
||||
r'Owner@winxp:/cygdrive/c/Documents\ and\ Settings/Owner/calibre':'winxp',
|
||||
'kovid@ox:calibre':None,
|
||||
r'kovid@win7:/cygdrive/c/Users/kovid/calibre':'Windows 7',
|
||||
}.iteritems():
|
||||
if vmname is None or is_vm_running(vmname):
|
||||
rcmd = BASE_RSYNC + EXCLUDES + ['.', host]
|
||||
print '\n\nPushing to:', host, '\n'
|
||||
threads.append(Thread(target=subprocess.check_call, args=(rcmd,)))
|
||||
print '\n\nPushing to:', vmname or host, '\n'
|
||||
threads.append(Thread(target=subprocess.check_call, args=(rcmd,),
|
||||
kwargs={'stdout':open(os.devnull, 'wb')}))
|
||||
threads[-1].start()
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
@ -118,13 +129,7 @@ class VMInstaller(Command):
|
||||
|
||||
|
||||
def run_vm(self):
|
||||
pat = '/%s/'%(self.VM_CHECK or self.VM_NAME)
|
||||
pids= [pid for pid in os.listdir('/proc') if pid.isdigit()]
|
||||
for pid in pids:
|
||||
cmdline = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read()
|
||||
if 'vmware-vmx' in cmdline and pat in cmdline:
|
||||
return
|
||||
|
||||
if is_vm_running(self.VM_CHECK or self.VM_NAME): return
|
||||
self.__p = subprocess.Popen([self.vm])
|
||||
|
||||
def start_vm(self, sleep=75):
|
||||
|
@ -97,7 +97,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 -opensource -release -ltcg -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 -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
|
||||
|
||||
Add the path to the bin folder inside the Qt dir to your system PATH.
|
||||
|
||||
@ -115,7 +115,7 @@ PyQt4
|
||||
|
||||
Compiling instructions::
|
||||
|
||||
python configure.py -c -j5 -e QtCore -e QtGui -e QtSvg -e QtNetwork -e QtWebKit -e QtXmlPatterns --verbose
|
||||
python configure.py -c -j5 -e QtCore -e QtGui -e QtSvg -e QtNetwork -e QtWebKit -e QtXmlPatterns --verbose --confirm-license
|
||||
nmake
|
||||
nmake install
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -13,31 +13,31 @@ 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: 2011-09-27 18:14+0000\n"
|
||||
"Last-Translator: Kovid Goyal <Unknown>\n"
|
||||
"PO-Revision-Date: 2012-06-14 09:06+0000\n"
|
||||
"Last-Translator: Eugene Marshal <Unknown>\n"
|
||||
"Language-Team: Russian <debian-l10n-russian@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: 2011-11-26 05:35+0000\n"
|
||||
"X-Generator: Launchpad (build 14381)\n"
|
||||
"X-Launchpad-Export-Date: 2012-06-15 04:42+0000\n"
|
||||
"X-Generator: Launchpad (build 15414)\n"
|
||||
"Language: ru\n"
|
||||
|
||||
#. name for aaa
|
||||
msgid "Ghotuo"
|
||||
msgstr ""
|
||||
msgstr "Гхотуо"
|
||||
|
||||
#. name for aab
|
||||
msgid "Alumu-Tesu"
|
||||
msgstr ""
|
||||
msgstr "Алуму-тесу"
|
||||
|
||||
#. name for aac
|
||||
msgid "Ari"
|
||||
msgstr ""
|
||||
msgstr "Ари"
|
||||
|
||||
#. name for aad
|
||||
msgid "Amal"
|
||||
msgstr ""
|
||||
msgstr "Амал"
|
||||
|
||||
#. name for aae
|
||||
msgid "Albanian; Arbëreshë"
|
||||
@ -45,11 +45,11 @@ msgstr ""
|
||||
|
||||
#. name for aaf
|
||||
msgid "Aranadan"
|
||||
msgstr ""
|
||||
msgstr "Аранадан"
|
||||
|
||||
#. name for aag
|
||||
msgid "Ambrak"
|
||||
msgstr ""
|
||||
msgstr "Амбрак"
|
||||
|
||||
#. name for aah
|
||||
msgid "Arapesh; Abu'"
|
||||
@ -57,23 +57,23 @@ msgstr ""
|
||||
|
||||
#. name for aai
|
||||
msgid "Arifama-Miniafia"
|
||||
msgstr ""
|
||||
msgstr "Арифама-Миниафиа"
|
||||
|
||||
#. name for aak
|
||||
msgid "Ankave"
|
||||
msgstr ""
|
||||
msgstr "Анкаве"
|
||||
|
||||
#. name for aal
|
||||
msgid "Afade"
|
||||
msgstr ""
|
||||
msgstr "Афаде"
|
||||
|
||||
#. name for aam
|
||||
msgid "Aramanik"
|
||||
msgstr ""
|
||||
msgstr "Араманик"
|
||||
|
||||
#. name for aan
|
||||
msgid "Anambé"
|
||||
msgstr ""
|
||||
msgstr "Анамбе"
|
||||
|
||||
#. name for aao
|
||||
msgid "Arabic; Algerian Saharan"
|
||||
@ -93,7 +93,7 @@ msgstr "Афар"
|
||||
|
||||
#. name for aas
|
||||
msgid "Aasáx"
|
||||
msgstr ""
|
||||
msgstr "Асакс"
|
||||
|
||||
#. name for aat
|
||||
msgid "Albanian; Arvanitika"
|
||||
@ -101,27 +101,27 @@ msgstr ""
|
||||
|
||||
#. name for aau
|
||||
msgid "Abau"
|
||||
msgstr ""
|
||||
msgstr "Абау"
|
||||
|
||||
#. name for aaw
|
||||
msgid "Solong"
|
||||
msgstr ""
|
||||
msgstr "Солонг"
|
||||
|
||||
#. name for aax
|
||||
msgid "Mandobo Atas"
|
||||
msgstr ""
|
||||
msgstr "Мандобо Атас"
|
||||
|
||||
#. name for aaz
|
||||
msgid "Amarasi"
|
||||
msgstr ""
|
||||
msgstr "Амараси"
|
||||
|
||||
#. name for aba
|
||||
msgid "Abé"
|
||||
msgstr ""
|
||||
msgstr "Абе"
|
||||
|
||||
#. name for abb
|
||||
msgid "Bankon"
|
||||
msgstr ""
|
||||
msgstr "Банкон"
|
||||
|
||||
#. name for abc
|
||||
msgid "Ayta; Ambala"
|
||||
@ -129,7 +129,7 @@ msgstr ""
|
||||
|
||||
#. name for abd
|
||||
msgid "Manide"
|
||||
msgstr ""
|
||||
msgstr "Мэнайд"
|
||||
|
||||
#. name for abe
|
||||
msgid "Abnaki; Western"
|
||||
@ -137,11 +137,11 @@ msgstr ""
|
||||
|
||||
#. name for abf
|
||||
msgid "Abai Sungai"
|
||||
msgstr ""
|
||||
msgstr "Абаи Сунгаи"
|
||||
|
||||
#. name for abg
|
||||
msgid "Abaga"
|
||||
msgstr ""
|
||||
msgstr "Абага"
|
||||
|
||||
#. name for abh
|
||||
msgid "Arabic; Tajiki"
|
||||
@ -149,11 +149,11 @@ msgstr ""
|
||||
|
||||
#. name for abi
|
||||
msgid "Abidji"
|
||||
msgstr ""
|
||||
msgstr "Абиджи"
|
||||
|
||||
#. name for abj
|
||||
msgid "Aka-Bea"
|
||||
msgstr ""
|
||||
msgstr "Ака-Беа"
|
||||
|
||||
#. name for abk
|
||||
msgid "Abkhazian"
|
||||
@ -161,19 +161,19 @@ msgstr "Абхазский"
|
||||
|
||||
#. name for abl
|
||||
msgid "Lampung Nyo"
|
||||
msgstr ""
|
||||
msgstr "Лампунг Ньё"
|
||||
|
||||
#. name for abm
|
||||
msgid "Abanyom"
|
||||
msgstr ""
|
||||
msgstr "Абанйом"
|
||||
|
||||
#. name for abn
|
||||
msgid "Abua"
|
||||
msgstr ""
|
||||
msgstr "Абуа"
|
||||
|
||||
#. name for abo
|
||||
msgid "Abon"
|
||||
msgstr ""
|
||||
msgstr "Абон"
|
||||
|
||||
#. name for abp
|
||||
msgid "Ayta; Abellen"
|
||||
@ -185,7 +185,7 @@ msgstr ""
|
||||
|
||||
#. name for abr
|
||||
msgid "Abron"
|
||||
msgstr ""
|
||||
msgstr "Аброн"
|
||||
|
||||
#. name for abs
|
||||
msgid "Malay; Ambonese"
|
||||
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 8, 56)
|
||||
numeric_version = (0, 8, 57)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
|
@ -90,6 +90,7 @@ class ANDROID(USBMS):
|
||||
0x4e22 : [0x0100, 0x226, 0x227, 0x231],
|
||||
0xb058 : [0x0222, 0x226, 0x227],
|
||||
0x0ff9 : [0x0226],
|
||||
0xc91 : HTC_BCDS,
|
||||
0xdddd : [0x216],
|
||||
},
|
||||
|
||||
@ -165,7 +166,10 @@ class ANDROID(USBMS):
|
||||
0x2237: { 0x2208 : [0x0226] },
|
||||
|
||||
# Lenovo
|
||||
0x17ef : { 0x7421 : [0x0216] },
|
||||
0x17ef : {
|
||||
0x7421 : [0x0216],
|
||||
0x741b : [0x9999],
|
||||
},
|
||||
|
||||
# Pantech
|
||||
0x10a9 : { 0x6050 : [0x227] },
|
||||
@ -203,7 +207,8 @@ class ANDROID(USBMS):
|
||||
'GT-I9003_CARD', 'XT912', 'FILE-CD_GADGET', 'RK29_SDK', 'MB855',
|
||||
'XT910', 'BOOK_A10', 'USB_2.0_DRIVER', 'I9100T', 'P999DW',
|
||||
'KTABLET_PC', 'INGENIC', 'GT-I9001_CARD', 'USB_2.0_DRIVER',
|
||||
'GT-S5830L_CARD', 'UNIVERSE', 'XT875', 'PRO', '.KOBO_VOX']
|
||||
'GT-S5830L_CARD', 'UNIVERSE', 'XT875', 'PRO', '.KOBO_VOX',
|
||||
'THINKPAD_TABLET']
|
||||
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',
|
||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, dbus
|
||||
import os, dbus, re
|
||||
|
||||
def node_mountpoint(node):
|
||||
|
||||
@ -19,13 +19,20 @@ def node_mountpoint(node):
|
||||
return de_mangle(line[1])
|
||||
return None
|
||||
|
||||
class NoUDisks1(Exception):
|
||||
pass
|
||||
|
||||
class UDisks(object):
|
||||
|
||||
def __init__(self):
|
||||
self.bus = dbus.SystemBus()
|
||||
try:
|
||||
self.main = dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
|
||||
'/org/freedesktop/UDisks'), 'org.freedesktop.UDisks')
|
||||
except dbus.exceptions.DBusException as e:
|
||||
if getattr(e, '_dbus_error_name', None) == 'org.freedesktop.DBus.Error.ServiceUnknown':
|
||||
raise NoUDisks1()
|
||||
raise
|
||||
|
||||
def device(self, device_node_path):
|
||||
devpath = self.main.FindDeviceByDeviceFile(device_node_path)
|
||||
@ -56,6 +63,102 @@ class UDisks(object):
|
||||
d = self.device(parent)
|
||||
d.DriveEject([])
|
||||
|
||||
class NoUDisks2(Exception):
|
||||
pass
|
||||
|
||||
class UDisks2(object):
|
||||
|
||||
BLOCK = 'org.freedesktop.UDisks2.Block'
|
||||
FILESYSTEM = 'org.freedesktop.UDisks2.Filesystem'
|
||||
DRIVE = 'org.freedesktop.UDisks2.Drive'
|
||||
|
||||
def __init__(self):
|
||||
self.bus = dbus.SystemBus()
|
||||
try:
|
||||
self.bus.get_object('org.freedesktop.UDisks2',
|
||||
'/org/freedesktop/UDisks2')
|
||||
except dbus.exceptions.DBusException as e:
|
||||
if getattr(e, '_dbus_error_name', None) == 'org.freedesktop.DBus.Error.ServiceUnknown':
|
||||
raise NoUDisks2()
|
||||
raise
|
||||
|
||||
def device(self, device_node_path):
|
||||
device_node_path = os.path.realpath(device_node_path)
|
||||
devname = device_node_path.split('/')[-1]
|
||||
|
||||
# First we try a direct object path
|
||||
bd = self.bus.get_object('org.freedesktop.UDisks2',
|
||||
'/org/freedesktop/UDisks2/block_devices/%s'%devname)
|
||||
try:
|
||||
device = bd.Get(self.BLOCK, 'Device',
|
||||
dbus_interface='org.freedesktop.DBus.Properties')
|
||||
device = bytearray(device).replace(b'\x00', b'').decode('utf-8')
|
||||
except:
|
||||
device = None
|
||||
|
||||
if device == device_node_path:
|
||||
return bd
|
||||
|
||||
# Enumerate all devices known to UDisks
|
||||
devs = self.bus.get_object('org.freedesktop.UDisks2',
|
||||
'/org/freedesktop/UDisks2/block_devices')
|
||||
xml = devs.Introspect(dbus_interface='org.freedesktop.DBus.Introspectable')
|
||||
for dev in re.finditer(r'name=[\'"](.+?)[\'"]', type(u'')(xml)):
|
||||
bd = self.bus.get_object('org.freedesktop.UDisks2',
|
||||
'/org/freedesktop/UDisks2/block_devices/%s2'%dev.group(1))
|
||||
try:
|
||||
device = bd.Get(self.BLOCK, 'Device',
|
||||
dbus_interface='org.freedesktop.DBus.Properties')
|
||||
device = bytearray(device).replace(b'\x00', b'').decode('utf-8')
|
||||
except:
|
||||
device = None
|
||||
if device == device_node_path:
|
||||
return bd
|
||||
|
||||
raise ValueError('%r not known to UDisks2'%device_node_path)
|
||||
|
||||
def mount(self, device_node_path):
|
||||
d = self.device(device_node_path)
|
||||
mount_options = ['rw', 'noexec', 'nosuid',
|
||||
'sync', 'nodev', 'uid=%d'%os.geteuid(), 'gid=%d'%os.getegid()]
|
||||
try:
|
||||
return unicode(d.Mount(
|
||||
{
|
||||
'auth.no_user_interaction':True,
|
||||
'options':','.join(mount_options)
|
||||
},
|
||||
dbus_interface=self.FILESYSTEM))
|
||||
except:
|
||||
# May be already mounted, check
|
||||
mp = node_mountpoint(str(device_node_path))
|
||||
if mp is None:
|
||||
raise
|
||||
return mp
|
||||
|
||||
def unmount(self, device_node_path):
|
||||
d = self.device(device_node_path)
|
||||
d.Unmount({'force':True, 'auth.no_user_interaction':True},
|
||||
dbus_interface=self.FILESYSTEM)
|
||||
|
||||
def drive_for_device(self, device):
|
||||
drive = device.Get(self.BLOCK, 'Drive',
|
||||
dbus_interface='org.freedesktop.DBus.Properties')
|
||||
return self.bus.get_object('org.freedesktop.UDisks2', drive)
|
||||
|
||||
def eject(self, device_node_path):
|
||||
drive = self.drive_for_device(self.device(device_node_path))
|
||||
drive.Eject({'auth.no_user_interaction':True},
|
||||
dbus_interface=self.DRIVE)
|
||||
|
||||
def get_udisks(ver=None):
|
||||
if ver is None:
|
||||
try:
|
||||
u = UDisks2()
|
||||
except NoUDisks2:
|
||||
u = UDisks()
|
||||
return u
|
||||
return UDisks2() if ver == 2 else UDisks()
|
||||
|
||||
def mount(node_path):
|
||||
u = UDisks()
|
||||
u.mount(node_path)
|
||||
@ -68,15 +171,19 @@ def umount(node_path):
|
||||
u = UDisks()
|
||||
u.unmount(node_path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
def test_udisks(ver=None):
|
||||
import sys
|
||||
dev = sys.argv[1]
|
||||
print 'Testing with node', dev
|
||||
u = UDisks()
|
||||
u = get_udisks(ver=ver)
|
||||
print 'Using Udisks:', u.__class__.__name__
|
||||
print 'Mounted at:', u.mount(dev)
|
||||
print 'Unmounting'
|
||||
u.unmount(dev)
|
||||
print 'Ejecting:'
|
||||
u.eject(dev)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_udisks()
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__license__ = 'GPL 3'
|
||||
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
@ -61,7 +61,7 @@ ORIENTATIONS = ['portrait', 'landscape']
|
||||
class PDFOutput(OutputFormatPlugin):
|
||||
|
||||
name = 'PDF Output'
|
||||
author = 'John Schember and Kovid Goyal'
|
||||
author = 'Kovid Goyal'
|
||||
file_type = 'pdf'
|
||||
|
||||
options = set([
|
||||
@ -97,24 +97,6 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.metadata = oeb_book.metadata
|
||||
self.cover_data = None
|
||||
|
||||
# Remove page-break-before on <body> element as it causes
|
||||
# blank pages in PDF Output
|
||||
from calibre.ebooks.oeb.base import XPath
|
||||
stylesheet = self.oeb.manifest.main_stylesheet
|
||||
if stylesheet is not None:
|
||||
from cssutils.css import CSSRule
|
||||
classes = set(['.calibre'])
|
||||
for x in self.oeb.spine:
|
||||
root = x.data
|
||||
body = XPath('//h:body[@class]')(root)
|
||||
if body:
|
||||
classes.add('.'+body[0].get('class'))
|
||||
|
||||
for rule in stylesheet.data.cssRules.rulesOfType(CSSRule.STYLE_RULE):
|
||||
if rule.selectorList.selectorText in classes:
|
||||
rule.style.removeProperty('page-break-before')
|
||||
rule.style.removeProperty('page-break-after')
|
||||
|
||||
|
||||
if input_plugin.is_image_collection:
|
||||
log.debug('Converting input as an image collection...')
|
||||
@ -128,16 +110,12 @@ class PDFOutput(OutputFormatPlugin):
|
||||
self.write(ImagePDFWriter, images)
|
||||
|
||||
def get_cover_data(self):
|
||||
g, m = self.oeb.guide, self.oeb.manifest
|
||||
if 'titlepage' not in g:
|
||||
if 'cover' in g:
|
||||
href = g['cover'].href
|
||||
from calibre.ebooks.oeb.base import urlnormalize
|
||||
for item in m:
|
||||
if item.href == urlnormalize(href):
|
||||
oeb = self.oeb
|
||||
if (oeb.metadata.cover and
|
||||
unicode(oeb.metadata.cover[0]) in oeb.manifest.ids):
|
||||
cover_id = unicode(oeb.metadata.cover[0])
|
||||
item = oeb.manifest.ids[cover_id]
|
||||
self.cover_data = item.data
|
||||
if not isinstance(self.cover_data, basestring):
|
||||
self.cover_data = None
|
||||
|
||||
def convert_text(self, oeb_book):
|
||||
from calibre.ebooks.pdf.writer import PDFWriter
|
||||
|
@ -687,7 +687,11 @@ class Amazon(Source):
|
||||
return True
|
||||
|
||||
for div in root.xpath(r'//div[starts-with(@id, "result_")]'):
|
||||
for a in div.xpath(r'descendant::a[@class="title" and @href]'):
|
||||
links = div.xpath(r'descendant::a[@class="title" and @href]')
|
||||
if not links:
|
||||
# New amazon markup
|
||||
links = div.xpath('descendant::h3/a[@href]')
|
||||
for a in links:
|
||||
title = tostring(a, method='text', encoding=unicode)
|
||||
if title_ok(title):
|
||||
matches.append(a.get('href'))
|
||||
|
@ -167,7 +167,8 @@ def test_identify(tests): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
||||
def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None,
|
||||
fail_missing_meta=True): # {{{
|
||||
'''
|
||||
:param name: Plugin name
|
||||
:param tests: List of 2-tuples. Each two tuple is of the form (args,
|
||||
@ -246,6 +247,7 @@ def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
||||
None]
|
||||
if not good:
|
||||
prints('Failed to find', plugin.test_fields(possibles[0]))
|
||||
if fail_missing_meta:
|
||||
raise SystemExit(1)
|
||||
|
||||
if results[0] is not possibles[0]:
|
||||
@ -263,9 +265,10 @@ def test_identify_plugin(name, tests, modify_plugin=lambda plugin:None): # {{{
|
||||
results.append(rq.get_nowait())
|
||||
except Empty:
|
||||
break
|
||||
if not results:
|
||||
if not results and fail_missing_meta:
|
||||
prints('Cover download failed')
|
||||
raise SystemExit(1)
|
||||
elif results:
|
||||
cdata = results[0]
|
||||
cover = os.path.join(tdir, plugin.name.replace(' ',
|
||||
'')+'-%s-cover.jpg'%sanitize_file_name2(mi.title.replace(' ',
|
||||
|
@ -111,7 +111,7 @@ class Skeleton(object):
|
||||
self.chunks = chunks
|
||||
|
||||
self.skeleton = self.render(root)
|
||||
self.body_offset = self.skeleton.find('<body')
|
||||
self.body_offset = self.skeleton.find(b'<body')
|
||||
self.calculate_metrics(root)
|
||||
|
||||
self.calculate_insert_positions()
|
||||
@ -127,7 +127,7 @@ class Skeleton(object):
|
||||
self.metrics = {}
|
||||
for tag in root.xpath('//*[@aid]'):
|
||||
text = (tag.text or '').encode('utf-8')
|
||||
raw = tostring(tag, with_tail=True)
|
||||
raw = close_self_closing_tags(tostring(tag, with_tail=True))
|
||||
start_length = len(raw.partition(b'>')[0]) + len(text) + 1
|
||||
end_length = len(raw.rpartition(b'<')[-1]) + 1
|
||||
self.metrics[tag.get('aid')] = Metric(start_length, end_length)
|
||||
|
@ -15,15 +15,6 @@ log = (args...) -> # {{{
|
||||
process.stdout.write(msg + '\n')
|
||||
# }}}
|
||||
|
||||
body_height = () -> # {{{
|
||||
db = document.body
|
||||
dde = document.documentElement
|
||||
if db? and dde?
|
||||
return Math.max(db.scrollHeight, dde.scrollHeight, db.offsetHeight,
|
||||
dde.offsetHeight, db.clientHeight, dde.clientHeight)
|
||||
return 0
|
||||
# }}}
|
||||
|
||||
window_scroll_pos = (win=window) -> # {{{
|
||||
if typeof(win.pageXOffset) == 'number'
|
||||
x = win.pageXOffset
|
||||
@ -59,12 +50,12 @@ absleft = (elem) -> # {{{
|
||||
# }}}
|
||||
|
||||
class PagedDisplay
|
||||
###
|
||||
This class is a namespace to expose functions via the
|
||||
window.paged_display object. The most important functions are:
|
||||
|
||||
layout(): causes the currently loaded document to be laid out in columns.
|
||||
###
|
||||
# This class is a namespace to expose functions via the
|
||||
# window.paged_display object. The most important functions are:
|
||||
#
|
||||
# set_geometry(): sets the parameters used to layout text in paged mode
|
||||
#
|
||||
# layout(): causes the currently loaded document to be laid out in columns.
|
||||
|
||||
constructor: () ->
|
||||
if not this instanceof arguments.callee
|
||||
@ -74,35 +65,42 @@ class PagedDisplay
|
||||
this.screen_width = 0
|
||||
this.in_paged_mode = false
|
||||
this.current_margin_side = 0
|
||||
this.is_full_screen_layout = false
|
||||
|
||||
set_geometry: (cols_per_screen=2, margin_top=20, margin_side=40, margin_bottom=20) ->
|
||||
set_geometry: (cols_per_screen=1, margin_top=20, margin_side=40, margin_bottom=20) ->
|
||||
this.margin_top = margin_top
|
||||
this.margin_side = margin_side
|
||||
this.margin_bottom = margin_bottom
|
||||
this.cols_per_screen = cols_per_screen
|
||||
|
||||
layout: () ->
|
||||
# Remove the top margin from the first child of body as that gets added
|
||||
# to the top margin of body. This is done here just in case
|
||||
# getComputedStyle() causes a relayout. The re-layout will be much
|
||||
# faster before we implement the multi-column layout.
|
||||
for node in document.body.childNodes
|
||||
if node.nodeType == 1 # Element node
|
||||
style = window.getComputedStyle(node)
|
||||
if style.display in ['block', 'table']
|
||||
node.style.setProperty('margin-top', '0px')
|
||||
break
|
||||
body_style = window.getComputedStyle(document.body)
|
||||
# When laying body out in columns, webkit bleeds the top margin of the
|
||||
# first block element out above the columns, leading to an extra top
|
||||
# margin for the page. We compensate for that here. Computing the
|
||||
# boundingrect of body is very expensive with column layout, so we do
|
||||
# it before the column layout is applied.
|
||||
first_layout = false
|
||||
if not this.in_paged_mode
|
||||
document.body.style.marginTop = '0px'
|
||||
extra_margin = document.body.getBoundingClientRect().top
|
||||
margin_top = (this.margin_top - extra_margin) + 'px'
|
||||
# Check if the current document is a full screen layout like
|
||||
# cover, if so we treat it specially.
|
||||
single_screen = (document.body.scrollWidth < window.innerWidth + 25 and document.body.scrollHeight < window.innerHeight + 25)
|
||||
first_layout = true
|
||||
else
|
||||
# resize event
|
||||
margin_top = body_style.marginTop
|
||||
|
||||
ww = window.innerWidth
|
||||
wh = window.innerHeight
|
||||
body_height = wh - this.margin_bottom = this.margin_top
|
||||
n = this.cols_per_screen
|
||||
|
||||
# Calculate the column width so that cols_per_screen columns fit in the
|
||||
# window in such a way the right margin of the last column is <=
|
||||
# side_margin (it may be less if the window width is not a
|
||||
# multiple of n*(col_width+2*side_margin).
|
||||
|
||||
n = this.cols_per_screen
|
||||
adjust = ww - Math.floor(ww/n)*n
|
||||
# Ensure that the margins are large enough that the adjustment does not
|
||||
# cause them to become negative semidefinite
|
||||
@ -113,7 +111,6 @@ class PagedDisplay
|
||||
this.page_width = col_width + 2*sm
|
||||
this.screen_width = this.page_width * this.cols_per_screen
|
||||
|
||||
body_style = window.getComputedStyle(document.body)
|
||||
fgcolor = body_style.getPropertyValue('color')
|
||||
bs = document.body.style
|
||||
|
||||
@ -121,9 +118,9 @@ class PagedDisplay
|
||||
bs.setProperty('-webkit-column-width', col_width+'px')
|
||||
bs.setProperty('-webkit-column-rule-color', fgcolor)
|
||||
bs.setProperty('overflow', 'visible')
|
||||
bs.setProperty('height', 'auto')
|
||||
bs.setProperty('width', 'auto')
|
||||
bs.setProperty('margin-top', this.margin_top+'px')
|
||||
bs.setProperty('height', (window.innerHeight - this.margin_top - this.margin_bottom) + 'px')
|
||||
bs.setProperty('width', (window.innerWidth - 2*sm)+'px')
|
||||
bs.setProperty('margin-top', margin_top)
|
||||
bs.setProperty('margin-bottom', this.margin_bottom+'px')
|
||||
bs.setProperty('margin-left', sm+'px')
|
||||
bs.setProperty('margin-right', sm+'px')
|
||||
@ -146,6 +143,15 @@ class PagedDisplay
|
||||
priority = rule.style.getPropertyPriority(prop)
|
||||
rule.style.setProperty(cprop, val, priority)
|
||||
|
||||
if first_layout
|
||||
# Because of a bug in webkit column mode, svg elements defined with
|
||||
# width 100% are wider than body and lead to a blank page after the
|
||||
# current page (when cols_per_screen == 1). Similarly img elements
|
||||
# with height=100% overflow the first column
|
||||
has_svg = document.getElementsByTagName('svg').length > 0
|
||||
only_img = document.getElementsByTagName('img').length == 1 and document.getElementsByTagName('div').length < 2 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
|
||||
|
||||
this.in_paged_mode = true
|
||||
this.current_margin_side = sm
|
||||
return sm
|
||||
@ -155,19 +161,49 @@ class PagedDisplay
|
||||
xpos = Math.floor(document.body.scrollWidth * frac)
|
||||
this.scroll_to_xpos(xpos)
|
||||
|
||||
scroll_to_xpos: (xpos) ->
|
||||
scroll_to_xpos: (xpos, animated=false, notify=false, duration=1000) ->
|
||||
# Scroll so that the column containing xpos is the left most column in
|
||||
# the viewport
|
||||
if typeof(xpos) != 'number'
|
||||
log(xpos, 'is not a number, cannot scroll to it!')
|
||||
return
|
||||
if this.is_full_screen_layout
|
||||
window.scrollTo(0, 0)
|
||||
return
|
||||
pos = 0
|
||||
until (pos <= xpos < pos + this.page_width)
|
||||
pos += this.page_width
|
||||
limit = document.body.scrollWidth - this.screen_width
|
||||
pos = limit if pos > limit
|
||||
if animated
|
||||
this.animated_scroll(pos, duration=1000, notify=notify)
|
||||
else
|
||||
window.scrollTo(pos, 0)
|
||||
|
||||
animated_scroll: (pos, duration=1000, notify=true) ->
|
||||
# Scroll the window to X-position pos in an animated fashion over
|
||||
# duration milliseconds. If notify is true, py_bridge.animated_scroll_done is
|
||||
# called.
|
||||
delta = pos - window.pageXOffset
|
||||
interval = 50
|
||||
steps = Math.floor(duration/interval)
|
||||
step_size = Math.floor(delta/steps)
|
||||
this.current_scroll_animation = {target:pos, step_size:step_size, interval:interval, notify:notify, fn: () =>
|
||||
a = this.current_scroll_animation
|
||||
npos = window.pageXOffset + a.step_size
|
||||
completed = false
|
||||
if Math.abs(npos - a.target) < Math.abs(a.step_size)
|
||||
completed = true
|
||||
npos = a.target
|
||||
window.scrollTo(npos, 0)
|
||||
if completed
|
||||
if notify
|
||||
window.py_bridge.animated_scroll_done()
|
||||
else
|
||||
setTimeout(a.fn, a.interval)
|
||||
}
|
||||
this.current_scroll_animation.fn()
|
||||
|
||||
current_pos: (frac) ->
|
||||
# The current scroll position as a fraction between 0 and 1
|
||||
limit = document.body.scrollWidth - window.innerWidth
|
||||
@ -178,6 +214,8 @@ class PagedDisplay
|
||||
current_column_location: () ->
|
||||
# The location of the left edge of the left most column currently
|
||||
# visible in the viewport
|
||||
if this.is_full_screen_layout
|
||||
return 0
|
||||
x = window.pageXOffset + Math.max(10, this.current_margin_side)
|
||||
edge = Math.floor(x/this.page_width) * this.page_width
|
||||
while edge < x
|
||||
@ -187,6 +225,8 @@ class PagedDisplay
|
||||
next_screen_location: () ->
|
||||
# The position to scroll to for the next screen (which could contain
|
||||
# more than one pages). Returns -1 if no further scrolling is possible.
|
||||
if this.is_full_screen_layout
|
||||
return -1
|
||||
cc = this.current_column_location()
|
||||
ans = cc + this.screen_width
|
||||
limit = document.body.scrollWidth - window.innerWidth
|
||||
@ -197,6 +237,8 @@ class PagedDisplay
|
||||
previous_screen_location: () ->
|
||||
# The position to scroll to for the previous screen (which could contain
|
||||
# more than one pages). Returns -1 if no further scrolling is possible.
|
||||
if this.is_full_screen_layout
|
||||
return -1
|
||||
cc = this.current_column_location()
|
||||
ans = cc - this.screen_width
|
||||
if ans < 0
|
||||
@ -209,6 +251,8 @@ class PagedDisplay
|
||||
# The position to scroll to for the next column (same as
|
||||
# next_screen_location() if columns per screen == 1). Returns -1 if no
|
||||
# further scrolling is possible.
|
||||
if this.is_full_screen_layout
|
||||
return -1
|
||||
cc = this.current_column_location()
|
||||
ans = cc + this.page_width
|
||||
limit = document.body.scrollWidth - window.innerWidth
|
||||
@ -220,6 +264,8 @@ class PagedDisplay
|
||||
# The position to scroll to for the previous column (same as
|
||||
# previous_screen_location() if columns per screen == 1). Returns -1 if
|
||||
# no further scrolling is possible.
|
||||
if this.is_full_screen_layout
|
||||
return -1
|
||||
cc = this.current_column_location()
|
||||
ans = cc - this.page_width
|
||||
if ans < 0
|
||||
@ -308,8 +354,7 @@ if window?
|
||||
window.paged_display = new PagedDisplay()
|
||||
|
||||
# TODO:
|
||||
# Go to reference positions
|
||||
# Indexing
|
||||
# Resizing of images
|
||||
# Special handling for identifiable covers (colspan)?
|
||||
# Full screen mode
|
||||
# Highlight on jump_to_anchor
|
||||
|
@ -105,14 +105,14 @@ class UniqueFilenames(object): # {{{
|
||||
base, ext = posixpath.splitext(item.href)
|
||||
nhref = base + suffix + ext
|
||||
nhref = oeb.manifest.generate(href=nhref)[1]
|
||||
spine_pos = item.spine_position
|
||||
oeb.manifest.remove(item)
|
||||
nitem = oeb.manifest.add(item.id, nhref, item.media_type, data=data,
|
||||
fallback=item.fallback)
|
||||
self.seen_filenames.add(posixpath.basename(nhref))
|
||||
self.rename_map[item.href] = nhref
|
||||
if item.spine_position is not None:
|
||||
oeb.spine.insert(item.spine_position, nitem, item.linear)
|
||||
oeb.spine.remove(item)
|
||||
oeb.manifest.remove(item)
|
||||
if spine_pos is not None:
|
||||
oeb.spine.insert(spine_pos, nitem, item.linear)
|
||||
else:
|
||||
self.seen_filenames.add(fname)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
'''
|
||||
@ -87,11 +87,6 @@ def get_pdf_printer(opts, for_comic=False, output_file_name=None):
|
||||
|
||||
return printer
|
||||
|
||||
def get_printer_page_size(opts, for_comic=False):
|
||||
printer = get_pdf_printer(opts, for_comic=for_comic)
|
||||
size = printer.paperSize(QPrinter.Millimeter)
|
||||
return size.width() / 10., size.height() / 10.
|
||||
|
||||
def draw_image_page(printer, painter, p, preserve_aspect_ratio=True):
|
||||
page_rect = printer.pageRect()
|
||||
if preserve_aspect_ratio:
|
||||
@ -138,13 +133,16 @@ class PDFWriter(QObject): # {{{
|
||||
self.view.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform)
|
||||
self.view.loadFinished.connect(self._render_html,
|
||||
type=Qt.QueuedConnection)
|
||||
for x in (Qt.Horizontal, Qt.Vertical):
|
||||
self.view.page().mainFrame().setScrollBarPolicy(x,
|
||||
Qt.ScrollBarAlwaysOff)
|
||||
self.render_queue = []
|
||||
self.combine_queue = []
|
||||
self.tmp_path = PersistentTemporaryDirectory(u'_pdf_output_parts')
|
||||
|
||||
self.opts = opts
|
||||
self.size = get_printer_page_size(opts)
|
||||
self.cover_data = cover_data
|
||||
self.paged_js = None
|
||||
|
||||
def dump(self, items, out_stream, pdf_metadata):
|
||||
self.metadata = pdf_metadata
|
||||
@ -176,19 +174,46 @@ class PDFWriter(QObject): # {{{
|
||||
if ok:
|
||||
item_path = os.path.join(self.tmp_path, '%i.pdf' % len(self.combine_queue))
|
||||
self.logger.debug('\tRendering item %s as %i.pdf' % (os.path.basename(str(self.view.url().toLocalFile())), len(self.combine_queue)))
|
||||
printer = get_pdf_printer(self.opts, output_file_name=item_path)
|
||||
self.view.page().mainFrame().evaluateJavaScript('''
|
||||
document.body.style.backgroundColor = "white";
|
||||
|
||||
''')
|
||||
self.view.print_(printer)
|
||||
printer.abort()
|
||||
self.do_paged_render(item_path)
|
||||
else:
|
||||
# The document is so corrupt that we can't render the page.
|
||||
self.loop.exit(0)
|
||||
raise Exception('Document cannot be rendered.')
|
||||
self._render_book()
|
||||
|
||||
def do_paged_render(self, outpath):
|
||||
from PyQt4.Qt import QSize, QPainter
|
||||
if self.paged_js is None:
|
||||
from calibre.utils.resources import compiled_coffeescript
|
||||
self.paged_js = compiled_coffeescript('ebooks.oeb.display.paged',
|
||||
dynamic=False)
|
||||
printer = get_pdf_printer(self.opts, output_file_name=outpath)
|
||||
painter = QPainter(printer)
|
||||
zoomx = printer.logicalDpiX()/self.view.logicalDpiX()
|
||||
zoomy = printer.logicalDpiY()/self.view.logicalDpiY()
|
||||
painter.scale(zoomx, zoomy)
|
||||
|
||||
pr = printer.pageRect()
|
||||
evaljs = self.view.page().mainFrame().evaluateJavaScript
|
||||
evaljs(self.paged_js)
|
||||
self.view.page().setViewportSize(QSize(pr.width()/zoomx,
|
||||
pr.height()/zoomy))
|
||||
evaljs('''
|
||||
document.body.style.backgroundColor = "white";
|
||||
paged_display.set_geometry(1, 0, 0, 0);
|
||||
paged_display.layout();
|
||||
''')
|
||||
mf = self.view.page().mainFrame()
|
||||
while True:
|
||||
mf.render(painter)
|
||||
nsl = evaljs('paged_display.next_screen_location()').toInt()
|
||||
if not nsl[1] or nsl[0] <= 0: break
|
||||
evaljs('window.scrollTo(%d, 0)'%nsl[0])
|
||||
printer.newPage()
|
||||
|
||||
painter.end()
|
||||
printer.abort()
|
||||
|
||||
def _delete_tmpdir(self):
|
||||
if os.path.exists(self.tmp_path):
|
||||
shutil.rmtree(self.tmp_path, True)
|
||||
@ -237,7 +262,6 @@ class ImagePDFWriter(object):
|
||||
def __init__(self, opts, log, cover_data=None):
|
||||
self.opts = opts
|
||||
self.log = log
|
||||
self.size = get_printer_page_size(opts, for_comic=True)
|
||||
|
||||
def dump(self, items, out_stream, pdf_metadata):
|
||||
f = PersistentTemporaryFile('_comic2pdf.pdf')
|
||||
|
@ -329,10 +329,11 @@ class AddAction(InterfaceAction):
|
||||
x.decode(preferred_encoding, 'replace') for x in
|
||||
self._adder.merged_books])
|
||||
info_dialog(self.gui, _('Merged some books'),
|
||||
_('The following duplicate books were found and incoming '
|
||||
_('The following %d duplicate books were found and incoming '
|
||||
'book formats were processed and merged into your '
|
||||
'Calibre database according to your automerge '
|
||||
'settings:'), det_msg=books, show=True)
|
||||
'settings:')%len(self._adder.merged_books),
|
||||
det_msg=books, show=True)
|
||||
|
||||
if getattr(self._adder, 'number_of_books_added', 0) > 0 or \
|
||||
getattr(self._adder, 'merged_books', False):
|
||||
|
@ -116,6 +116,9 @@ class EditorWidget(QWebView): # {{{
|
||||
ss = extra_shortcuts.get(wac, None)
|
||||
if ss:
|
||||
ac.setShortcut(QKeySequence(getattr(QKeySequence, ss)))
|
||||
if wac == 'RemoveFormat':
|
||||
ac.triggered.connect(self.remove_format_cleanup,
|
||||
type=Qt.QueuedConnection)
|
||||
|
||||
self.action_color = QAction(QIcon(I('format-text-color')), _('Foreground color'),
|
||||
self)
|
||||
@ -227,6 +230,9 @@ class EditorWidget(QWebView): # {{{
|
||||
js = 'document.execCommand("%s", false, null);' % cmd
|
||||
frame.evaluateJavaScript(js)
|
||||
|
||||
def remove_format_cleanup(self):
|
||||
self.html = self.html
|
||||
|
||||
@dynamic_property
|
||||
def html(self):
|
||||
|
||||
|
@ -323,6 +323,10 @@ def communicate(opts, args):
|
||||
|
||||
if opts.shutdown_running_calibre:
|
||||
t.conn.send('shutdown:')
|
||||
from calibre.utils.lock import singleinstance
|
||||
prints(_('Shutdown command sent, waiting for shutdown...'))
|
||||
while not singleinstance('calibre GUI'):
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
if len(args) > 1:
|
||||
args[1] = os.path.abspath(args[1])
|
||||
|
@ -205,6 +205,8 @@ class Document(QWebPage): # {{{
|
||||
return self.anchor_positions
|
||||
|
||||
def switch_to_paged_mode(self, onresize=False):
|
||||
if onresize and not self.loaded_javascript:
|
||||
return
|
||||
side_margin = self.javascript('window.paged_display.layout()', 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
|
||||
@ -294,6 +296,7 @@ class Document(QWebPage): # {{{
|
||||
self.mainFrame().setScrollPosition(QPoint(x, y))
|
||||
|
||||
def jump_to_anchor(self, anchor):
|
||||
if not self.loaded_javascript: return
|
||||
self.javascript('window.paged_display.jump_to_anchor("%s")'%anchor)
|
||||
|
||||
def element_ypos(self, elem):
|
||||
@ -352,7 +355,7 @@ class Document(QWebPage): # {{{
|
||||
except ZeroDivisionError:
|
||||
return 0.
|
||||
def fset(self, val):
|
||||
if self.in_paged_mode:
|
||||
if self.in_paged_mode and self.loaded_javascript:
|
||||
self.javascript('paged_display.scroll_to_pos(%f)'%val)
|
||||
else:
|
||||
npos = val * (self.height - self.window_height)
|
||||
|
@ -138,7 +138,9 @@ class Reference(QLineEdit):
|
||||
self.editingFinished.connect(self.editing_finished)
|
||||
|
||||
def editing_finished(self):
|
||||
self.goto.emit(unicode(self.text()))
|
||||
text = unicode(self.text())
|
||||
self.setText('')
|
||||
self.goto.emit(text)
|
||||
|
||||
class RecentAction(QAction):
|
||||
|
||||
@ -411,10 +413,12 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
return c.remember_current_page
|
||||
|
||||
def print_book(self):
|
||||
Printing(self.iterator.spine, False)
|
||||
p = Printing(self.iterator, self)
|
||||
p.start_print()
|
||||
|
||||
def print_preview(self):
|
||||
Printing(self.iterator.spine, True)
|
||||
p = Printing(self.iterator, self)
|
||||
p.start_preview()
|
||||
|
||||
def toggle_fullscreen(self, x):
|
||||
if self.isFullScreen():
|
||||
|
@ -1,127 +1,104 @@
|
||||
#!/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__ = '2009, John Schember <john@nachtimwald.com>'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
import os, sys, urlparse
|
||||
|
||||
from BeautifulSoup import BeautifulSoup, Tag
|
||||
|
||||
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, Qt, \
|
||||
QPrinter, QPrintPreviewDialog, QPrintDialog, QDialog, QMetaObject, Q_ARG
|
||||
from PyQt4.Qt import (QObject, QEventLoop, Qt, QPrintDialog, QPainter, QSize,
|
||||
QPrintPreviewDialog)
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
PRINTCSS = 'body{width:100%;margin:0;padding:0;font-family:Arial;color:#000;background:none;font-size:12pt;text-align:left;}h1,h2,h3,h4,h5,h6{font-family:Helvetica;}h1{font-size:19pt;}h2{font-size:17pt;}h3{font-size:15pt;}h4,h5,h6{font-size:12pt;}pre,code,samp{font:10ptCourier,monospace;white-space:pre-wrap;page-break-inside:avoid;}blockquote{margin:1.3em;padding:1em;font-size:10pt;}hr{background-color:#ccc;}aimg{border:none;}a:link,a:visited{background:transparent;font-weight:700;text-decoration:underline;color:#333;}a:link:after,a{color:#000;}table{margin:1px;text-align:left;}th{border-bottom:1pxsolid#333;font-weight:bold;}td{border-bottom:1pxsolid#333;}th,td{padding:4px10px4px0;}tfoot{font-style:italic;}caption{background:#fff;margin-bottom:2em;text-align:left;}thead{display:table-header-group;}tr{page-break-inside:avoid;}#header,.header,#footer,.footer,#navbar,.navbar,#navigation,.navigation,#rightSideBar,.rightSideBar,#leftSideBar,.leftSideBar{display:none;}'
|
||||
from calibre.gui2 import error_dialog
|
||||
from calibre.ebooks.oeb.display.webview import load_html
|
||||
from calibre.utils.resources import compiled_coffeescript
|
||||
|
||||
class Printing(QObject):
|
||||
def __init__(self, spine, preview):
|
||||
from calibre.gui2 import is_ok_to_use_qt
|
||||
if not is_ok_to_use_qt():
|
||||
raise Exception('Not OK to use Qt')
|
||||
QObject.__init__(self)
|
||||
self.loop = QEventLoop()
|
||||
|
||||
self.view = QWebView()
|
||||
if preview:
|
||||
self.connect(self.view, SIGNAL('loadFinished(bool)'), self.print_preview)
|
||||
else:
|
||||
self.connect(self.view, SIGNAL('loadFinished(bool)'), self.print_book)
|
||||
def __init__(self, iterator, parent):
|
||||
QObject.__init__(self, parent)
|
||||
self.current_index = 0
|
||||
self.iterator = iterator
|
||||
self.view = QWebView(self.parent())
|
||||
self.mf = mf = self.view.page().mainFrame()
|
||||
for x in (Qt.Horizontal, Qt.Vertical):
|
||||
mf.setScrollBarPolicy(x, Qt.ScrollBarAlwaysOff)
|
||||
self.view.loadFinished.connect(self.load_finished)
|
||||
self.paged_js = compiled_coffeescript('ebooks.oeb.display.paged',
|
||||
dynamic=False)
|
||||
|
||||
self.process_content(spine)
|
||||
def load_finished(self, ok):
|
||||
self.loaded_ok = ok
|
||||
|
||||
def process_content(self, spine):
|
||||
content = ''
|
||||
def start_print(self):
|
||||
self.pd = QPrintDialog(self.parent())
|
||||
self.pd.open(self._start_print)
|
||||
|
||||
for path in spine:
|
||||
raw = self.raw_content(path)
|
||||
content += self.parsed_content(raw, path)
|
||||
def _start_print(self):
|
||||
self.do_print(self.pd.printer())
|
||||
|
||||
refined_content = self.refine_content(content)
|
||||
def start_preview(self):
|
||||
self.pd = QPrintPreviewDialog(self.parent())
|
||||
self.pd.paintRequested.connect(self.do_print)
|
||||
self.pd.exec_()
|
||||
|
||||
base = os.path.splitdrive(spine[0])[0]
|
||||
base = base if base != '' else '/'
|
||||
def do_print(self, printer):
|
||||
painter = QPainter(printer)
|
||||
zoomx = printer.logicalDpiX()/self.view.logicalDpiX()
|
||||
zoomy = printer.logicalDpiY()/self.view.logicalDpiY()
|
||||
painter.scale(zoomx, zoomy)
|
||||
pr = printer.pageRect()
|
||||
self.view.page().setViewportSize(QSize(pr.width()/zoomx,
|
||||
pr.height()/zoomy))
|
||||
evaljs = self.mf.evaluateJavaScript
|
||||
loop = QEventLoop(self)
|
||||
first = True
|
||||
|
||||
QMetaObject.invokeMethod(self, "load_content", Qt.QueuedConnection, Q_ARG('QString', refined_content), Q_ARG('QString', base))
|
||||
self.loop.exec_()
|
||||
for path in self.iterator.spine:
|
||||
self.loaded_ok = None
|
||||
load_html(path, self.view, codec=getattr(path, 'encoding', 'utf-8'),
|
||||
mime_type=getattr(path, 'mime_type', None))
|
||||
while self.loaded_ok is None:
|
||||
loop.processEvents(loop.ExcludeUserInputEvents)
|
||||
if not self.loaded_ok:
|
||||
return error_dialog(self.parent(), _('Failed to render'),
|
||||
_('Failed to render document %s')%path, show=True)
|
||||
evaljs(self.paged_js)
|
||||
evaljs('''
|
||||
document.body.style.backgroundColor = "white";
|
||||
paged_display.set_geometry(1, 0, 0, 0);
|
||||
paged_display.layout();
|
||||
''')
|
||||
|
||||
@QtCore.pyqtSignature('load_content(QString, QString)')
|
||||
def load_content(self, content, base):
|
||||
self.view.setHtml(content, QUrl(base))
|
||||
while True:
|
||||
if not first:
|
||||
printer.newPage()
|
||||
first = False
|
||||
self.mf.render(painter)
|
||||
nsl = evaljs('paged_display.next_screen_location()').toInt()
|
||||
if not nsl[1] or nsl[0] <= 0: break
|
||||
evaljs('window.scrollTo(%d, 0)'%nsl[0])
|
||||
|
||||
def raw_content(self, path):
|
||||
return open(path, 'rb').read().decode(path.encoding)
|
||||
|
||||
def parsed_content(self, raw_content, path):
|
||||
dom_tree = BeautifulSoup(raw_content).body
|
||||
|
||||
# Remove sytle information that is applied to the entire document.
|
||||
# This does not remove styles applied within a tag.
|
||||
styles = dom_tree.findAll('style')
|
||||
for s in styles:
|
||||
s.extract()
|
||||
|
||||
scripts = dom_tree.findAll('script')
|
||||
for s in scripts:
|
||||
s.extract()
|
||||
|
||||
# Convert all relative links to absolute paths.
|
||||
links = dom_tree.findAll(src=True)
|
||||
for s in links:
|
||||
if QUrl(s['src']).isRelative():
|
||||
s['src'] = urlparse.urljoin(path, s['src'])
|
||||
links = dom_tree.findAll(href=True)
|
||||
for s in links:
|
||||
if QUrl(s['href']).isRelative():
|
||||
s['href'] = urlparse.urljoin(path, s['href'])
|
||||
|
||||
return unicode(dom_tree)
|
||||
|
||||
# Adds the begenning and endings tags to the document.
|
||||
# Adds the print css.
|
||||
def refine_content(self, content):
|
||||
dom_tree = BeautifulSoup('<html><head></head><body>%s</body></html>' % content)
|
||||
|
||||
css = dom_tree.findAll('link')
|
||||
for c in css:
|
||||
c.extract()
|
||||
|
||||
print_css = Tag(BeautifulSoup(), 'style', [('type', 'text/css'), ('title', 'override_css')])
|
||||
print_css.insert(0, PRINTCSS)
|
||||
dom_tree.findAll('head')[0].insert(0, print_css)
|
||||
|
||||
return unicode(dom_tree)
|
||||
|
||||
def print_preview(self, ok):
|
||||
printer = QPrinter(QPrinter.HighResolution)
|
||||
printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
|
||||
|
||||
previewDialog = QPrintPreviewDialog(printer)
|
||||
|
||||
self.connect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.view.print_)
|
||||
previewDialog.exec_()
|
||||
self.disconnect(previewDialog, SIGNAL('paintRequested(QPrinter *)'), self.view.print_)
|
||||
|
||||
self.loop.quit()
|
||||
|
||||
def print_book(self, ok):
|
||||
printer = QPrinter(QPrinter.HighResolution)
|
||||
printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch)
|
||||
|
||||
printDialog = QPrintDialog(printer)
|
||||
printDialog.setWindowTitle(_("Print eBook"))
|
||||
|
||||
printDialog.exec_()
|
||||
if printDialog.result() == QDialog.Accepted:
|
||||
self.view.print_(printer)
|
||||
|
||||
self.loop.quit()
|
||||
|
||||
def main():
|
||||
return 0
|
||||
painter.end()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
from calibre.gui2 import Application
|
||||
from calibre.ebooks.oeb.iterator.book import EbookIterator
|
||||
from PyQt4.Qt import QPrinter, QTimer
|
||||
import sys
|
||||
app = Application([])
|
||||
|
||||
def doit():
|
||||
with EbookIterator(sys.argv[-1]) as it:
|
||||
p = Printing(it, None)
|
||||
printer = QPrinter()
|
||||
of = sys.argv[-1]+'.pdf'
|
||||
printer.setOutputFileName(of)
|
||||
p.do_print(printer)
|
||||
print ('Printed to:', of)
|
||||
app.exit()
|
||||
QTimer.singleShot(0, doit)
|
||||
app.exec_()
|
||||
|
||||
|
@ -6,14 +6,12 @@ Miscellaneous widgets used in the GUI
|
||||
import re, traceback, os
|
||||
|
||||
from PyQt4.Qt import (QIcon, QFont, QLabel, QListWidget, QAction,
|
||||
QListWidgetItem, QTextCharFormat, QApplication,
|
||||
QSyntaxHighlighter, QCursor, QColor, QWidget,
|
||||
QPixmap, QSplitterHandle, QToolButton,
|
||||
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal,
|
||||
QRegExp, QSettings, QSize, QSplitter,
|
||||
QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene,
|
||||
QMenu, QStringListModel, QCompleter, QStringList,
|
||||
QTimer, QRect, QFontDatabase, QGraphicsView)
|
||||
QListWidgetItem, QTextCharFormat, QApplication, QSyntaxHighlighter,
|
||||
QCursor, QColor, QWidget, QPixmap, QSplitterHandle, QToolButton,
|
||||
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, QRegExp, QSize,
|
||||
QSplitter, QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene, QMenu,
|
||||
QStringListModel, QCompleter, QStringList, QTimer, QRect,
|
||||
QFontDatabase, QGraphicsView, QByteArray)
|
||||
|
||||
from calibre.constants import iswindows
|
||||
from calibre.gui2 import (NONE, error_dialog, pixmap_to_data, gprefs,
|
||||
@ -803,69 +801,29 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
|
||||
@classmethod
|
||||
def loadConfig(cls):
|
||||
Config = cls.Config
|
||||
settings = QSettings()
|
||||
def setDefaultString(name, default):
|
||||
value = settings.value(name).toString()
|
||||
if value.isEmpty():
|
||||
value = default
|
||||
Config[name] = value
|
||||
|
||||
for name in ("window", "shell"):
|
||||
Config["%swidth" % name] = settings.value("%swidth" % name,
|
||||
QVariant(QApplication.desktop() \
|
||||
.availableGeometry().width() / 2)).toInt()[0]
|
||||
Config["%sheight" % name] = settings.value("%sheight" % name,
|
||||
QVariant(QApplication.desktop() \
|
||||
.availableGeometry().height() / 2)).toInt()[0]
|
||||
Config["%sy" % name] = settings.value("%sy" % name,
|
||||
QVariant(0)).toInt()[0]
|
||||
Config["toolbars"] = settings.value("toolbars").toByteArray()
|
||||
Config["splitter"] = settings.value("splitter").toByteArray()
|
||||
Config["shellx"] = settings.value("shellx", QVariant(0)).toInt()[0]
|
||||
Config["windowx"] = settings.value("windowx", QVariant(QApplication \
|
||||
.desktop().availableGeometry().width() / 2)).toInt()[0]
|
||||
Config["remembergeometry"] = settings.value("remembergeometry",
|
||||
QVariant(True)).toBool()
|
||||
Config["startwithshell"] = settings.value("startwithshell",
|
||||
QVariant(True)).toBool()
|
||||
Config["showwindowinfo"] = settings.value("showwindowinfo",
|
||||
QVariant(True)).toBool()
|
||||
setDefaultString("shellstartup", """\
|
||||
from __future__ import division
|
||||
import codecs
|
||||
import sys
|
||||
sys.stdin = codecs.getreader("UTF8")(sys.stdin)
|
||||
sys.stdout = codecs.getwriter("UTF8")(sys.stdout)""")
|
||||
setDefaultString("newfile", """\
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import division
|
||||
|
||||
import sys
|
||||
""")
|
||||
Config["backupsuffix"] = settings.value("backupsuffix",
|
||||
QVariant(".bak")).toString()
|
||||
setDefaultString("beforeinput", "#>>>")
|
||||
setDefaultString("beforeoutput", "#---")
|
||||
Config["cwd"] = settings.value("cwd", QVariant(".")).toString()
|
||||
Config["tooltipsize"] = settings.value("tooltipsize",
|
||||
QVariant(150)).toInt()[0]
|
||||
Config["maxlinestoscan"] = settings.value("maxlinestoscan",
|
||||
QVariant(5000)).toInt()[0]
|
||||
Config["pythondocpath"] = settings.value("pythondocpath",
|
||||
QVariant("http://docs.python.org")).toString()
|
||||
Config["autohidefinddialog"] = settings.value("autohidefinddialog",
|
||||
QVariant(True)).toBool()
|
||||
Config["findcasesensitive"] = settings.value("findcasesensitive",
|
||||
QVariant(False)).toBool()
|
||||
Config["findwholewords"] = settings.value("findwholewords",
|
||||
QVariant(False)).toBool()
|
||||
Config["tabwidth"] = settings.value("tabwidth",
|
||||
QVariant(4)).toInt()[0]
|
||||
Config["fontfamily"] = settings.value("fontfamily",
|
||||
QVariant("monospace")).toString()
|
||||
Config["fontsize"] = settings.value("fontsize",
|
||||
QVariant(10)).toInt()[0]
|
||||
Config["%swidth" % name] = QVariant(QApplication.desktop().availableGeometry().width() / 2).toInt()[0]
|
||||
Config["%sheight" % name] = QVariant(QApplication.desktop().availableGeometry().height() / 2).toInt()[0]
|
||||
Config["%sy" % name] = QVariant(0).toInt()[0]
|
||||
Config["toolbars"] = QByteArray(b'')
|
||||
Config["splitter"] = QByteArray(b'')
|
||||
Config["shellx"] = QVariant(0).toInt()[0]
|
||||
Config["windowx"] = QVariant(QApplication.desktop().availableGeometry().width() / 2).toInt()[0]
|
||||
Config["remembergeometry"] = QVariant(True).toBool()
|
||||
Config["startwithshell"] = QVariant(True).toBool()
|
||||
Config["showwindowinfo"] = QVariant(True).toBool()
|
||||
Config["backupsuffix"] = QVariant(".bak").toString()
|
||||
Config["cwd"] = QVariant(".").toString()
|
||||
Config["tooltipsize"] = QVariant(150).toInt()[0]
|
||||
Config["maxlinestoscan"] = QVariant(5000).toInt()[0]
|
||||
Config["pythondocpath"] = QVariant("http://docs.python.org").toString()
|
||||
Config["autohidefinddialog"] = QVariant(True).toBool()
|
||||
Config["findcasesensitive"] = QVariant(False).toBool()
|
||||
Config["findwholewords"] = QVariant(False).toBool()
|
||||
Config["tabwidth"] = QVariant(4).toInt()[0]
|
||||
Config["fontfamily"] = QVariant("monospace").toString()
|
||||
Config["fontsize"] = QVariant(10).toInt()[0]
|
||||
for name, color, bold, italic in (
|
||||
("normal", "#000000", False, False),
|
||||
("keyword", "#000080", True, False),
|
||||
@ -877,12 +835,9 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
|
||||
("number", "#924900", False, False),
|
||||
("error", "#FF0000", False, False),
|
||||
("pyqt", "#50621A", False, False)):
|
||||
Config["%sfontcolor" % name] = settings.value(
|
||||
"%sfontcolor" % name, QVariant(color)).toString()
|
||||
Config["%sfontbold" % name] = settings.value(
|
||||
"%sfontbold" % name, QVariant(bold)).toBool()
|
||||
Config["%sfontitalic" % name] = settings.value(
|
||||
"%sfontitalic" % name, QVariant(italic)).toBool()
|
||||
Config["%sfontcolor" % name] = QVariant(color).toString()
|
||||
Config["%sfontbold" % name] = QVariant(bold).toBool()
|
||||
Config["%sfontitalic" % name] = QVariant(italic).toBool()
|
||||
|
||||
|
||||
@classmethod
|
||||
|
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
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
Loading…
x
Reference in New Issue
Block a user