Sync with trunk.
@ -19,6 +19,89 @@
|
||||
# new recipes:
|
||||
# - title:
|
||||
|
||||
- version: 0.9.7
|
||||
date: 2012-11-23
|
||||
|
||||
new features:
|
||||
- title: "Edit metadata dialog: Show the size of the current book cover in the edit metadata dialog."
|
||||
tickets: [1079781]
|
||||
|
||||
- title: "Get Books: Allow easy searching by title and author in addition to any keyword, to prevent large numbers of spurious matches."
|
||||
|
||||
- title: "An option to automatically convert any added book to the current output format, found under Preferences->Adding books"
|
||||
|
||||
- title: "E-book viewer: Allow viewing tables in a separate popup window by right clicking on the table and selecting 'View table'. Useful for reference books that have lots of large tables."
|
||||
tickets: [1080710]
|
||||
|
||||
- title: "Catalogs: Add the current library name as an available field when generating catalogs in csv/xml format."
|
||||
tickets: [1078422]
|
||||
|
||||
- title: "Enable colored text in the output from the command line tools on windows"
|
||||
|
||||
- title: "E-book viewer: Add an option to hide the help message when entering full screen mode"
|
||||
|
||||
- title: "E-book viewer: Add an option to always start the viewer in full screen mode"
|
||||
|
||||
- title: "E-book viewer: Add many more controls to the context menu, particularly useful in full screen mode"
|
||||
|
||||
- title: "E-book viewer: Allow easy searching of the selected word or phrase in google via the context menu"
|
||||
|
||||
- title: "Add a new type of FileType plugin, postimport, that runs after a book has been added to the database."
|
||||
|
||||
- title: "Get Books: Remove Gandalf store, add Publio store. Update the Legimi store plugin for website changes"
|
||||
|
||||
bug fixes:
|
||||
- title: "Conversion: Correctly handle values of left and right for the deprecated align attribute of images, mapping them to the CSS float property instead of to text-align."
|
||||
tickets: [1081094]
|
||||
|
||||
- title: "MOBI Output: When generating joint MOBI6/KF8 files do not set incorrect display CSS values for tables in the KF8 part"
|
||||
|
||||
- title: "Connect to iTunes: Ignore AAC audio files."
|
||||
tickets: [1081096]
|
||||
|
||||
- title: "E-book viewer: Fix restoring from fullscreen not respecting maximized window state"
|
||||
|
||||
- title: "Fix rows in the device books view sometimes being too high"
|
||||
|
||||
- title: "Catalogs: Fixed a problem occurring when merging comments with a custom field whose type is a list."
|
||||
|
||||
- title: "Linux binary: Use exec in the wrapper shell scripts that are used to set env vars and launch calibre utilities."
|
||||
tickets: [1077884]
|
||||
|
||||
- title: "E-book viewer: Fix blank pages after every page when viewing some comic files in paged mode"
|
||||
|
||||
- title: "E-book viewer: When printing, respect the specified page range."
|
||||
tickets: [1074220]
|
||||
|
||||
- title: "Font subsetting: Parse the GSUB table for glyph substitution rules and do not remove any glyphs that could act as substitutes. Keep zero length glyphs like the glyphs for non printable characters when subsetting TrueType outlines."
|
||||
|
||||
- title: "Smarten punctuation: Fix self closing script tags causing smarten punctuation to fail"
|
||||
|
||||
|
||||
improved recipes:
|
||||
- Arguments and facts
|
||||
- Business Standard
|
||||
- The New Yorker
|
||||
|
||||
new recipes:
|
||||
- title: Various Czech and Hungarian news sources
|
||||
author: bubak
|
||||
|
||||
- title: Various Polish recipes
|
||||
author: Artur Stachecki
|
||||
|
||||
- title: Buchreport
|
||||
author: a.peter
|
||||
|
||||
- title: Red Voltaire
|
||||
author: atordo
|
||||
|
||||
- title: Autosport
|
||||
author: Mr Stefan
|
||||
|
||||
- title: House News
|
||||
author: Eddie Lau
|
||||
|
||||
- version: 0.9.6
|
||||
date: 2012-11-10
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2010 - 2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
www.aif.ru
|
||||
'''
|
||||
@ -19,12 +19,19 @@ class AIF_ru(BasicNewsRecipe):
|
||||
encoding = 'cp1251'
|
||||
language = 'ru'
|
||||
publication_type = 'magazine'
|
||||
extra_css = ' @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: Verdana,Arial,Helvetica,sans1,sans-serif} '
|
||||
keep_only_tags = [dict(name='div',attrs={'id':'inner'})]
|
||||
masthead_url = 'http://static3.aif.ru/glossy/index/i/logo.png'
|
||||
extra_css = """
|
||||
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
|
||||
body{font-family: Verdana,Arial,Helvetica,sans1,sans-serif}
|
||||
img{display: block}
|
||||
"""
|
||||
keep_only_tags = [
|
||||
dict(name='div',attrs={'class':['content-header', 'zoom']})
|
||||
,dict(name='div',attrs={'id':'article-text'})
|
||||
]
|
||||
remove_tags = [
|
||||
dict(name=['iframe','object','link','base','input','img'])
|
||||
,dict(name='div',attrs={'class':'photo'})
|
||||
,dict(name='p',attrs={'class':'resizefont'})
|
||||
dict(name=['iframe','object','link','base','input','meta'])
|
||||
,dict(name='div',attrs={'class':'in-topic'})
|
||||
]
|
||||
|
||||
feeds = [(u'News', u'http://www.aif.ru/rss/all.php')]
|
||||
|
27
recipes/app_funds.recipe
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'teepel <teepel44@gmail.com>'
|
||||
|
||||
'''
|
||||
appfunds.blogspot.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class app_funds(BasicNewsRecipe):
|
||||
title = u'APP Funds'
|
||||
__author__ = 'teepel <teepel44@gmail.com>'
|
||||
language = 'pl'
|
||||
description ='Blog inwestora dla inwestorów i oszczędzających'
|
||||
INDEX='http://appfunds.blogspot.com'
|
||||
remove_empty_feeds= True
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
simultaneous_downloads = 5
|
||||
remove_javascript=True
|
||||
no_stylesheets=True
|
||||
auto_cleanup = True
|
||||
|
||||
feeds = [(u'blog', u'http://feeds.feedburner.com/blogspot/etVI')]
|
||||
|
@ -1,39 +1,88 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = u'2010, Tomasz Dlugosz <tomek3d@gmail.com>'
|
||||
__copyright__ = u'2010-2012, Tomasz Dlugosz <tomek3d@gmail.com>'
|
||||
'''
|
||||
fronda.pl
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
import re
|
||||
from datetime import timedelta, date
|
||||
|
||||
class Fronda(BasicNewsRecipe):
|
||||
title = u'Fronda.pl'
|
||||
publisher = u'Fronda.pl'
|
||||
description = u'Portal po\u015bwi\u0119cony - Infformacje'
|
||||
description = u'Portal po\u015bwi\u0119cony - Informacje'
|
||||
language = 'pl'
|
||||
__author__ = u'Tomasz D\u0142ugosz'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
use_embedded_content = False
|
||||
no_stylesheets = True
|
||||
|
||||
feeds = [(u'Infformacje', u'http://fronda.pl/news/feed')]
|
||||
extra_css = '''
|
||||
h1 {font-size:150%}
|
||||
.body {text-align:left;}
|
||||
div.headline {font-weight:bold}
|
||||
'''
|
||||
|
||||
keep_only_tags = [dict(name='h2', attrs={'class':'news_title'}),
|
||||
dict(name='div', attrs={'class':'naglowek_tresc'}),
|
||||
dict(name='div', attrs={'id':'czytaj'}) ]
|
||||
earliest_date = date.today() - timedelta(days=oldest_article)
|
||||
|
||||
remove_tags = [dict(name='a', attrs={'class':'print'})]
|
||||
def date_cut(self,datestr):
|
||||
# eg. 5.11.2012, 12:07
|
||||
timestamp = datestr.split(',')[0]
|
||||
parts = timestamp.split('.')
|
||||
art_date = date(int(parts[2]),int(parts[1]),int(parts[0]))
|
||||
return True if art_date < self.earliest_date else False
|
||||
|
||||
preprocess_regexps = [
|
||||
(re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in
|
||||
[ (r'<p><a href="http://fronda.pl/sklepy">.*</a></p>', lambda match: ''),
|
||||
(r'<p><a href="http://fronda.pl/pasaz">.*</a></p>', lambda match: ''),
|
||||
(r'<h3><strong>W.* lektury.*</a></p></div>', lambda match: '</div>'),
|
||||
(r'<h3>Zobacz t.*?</div>', lambda match: '</div>'),
|
||||
(r'<p[^>]*> </p>', lambda match: ''),
|
||||
(r'<p><span style=".*?"><br /></span></p> ', lambda match: ''),
|
||||
(r'<a style=\'float:right;margin-top:3px;\' href="http://www.facebook.com/share.php?.*?</a>', lambda match: '')]
|
||||
]
|
||||
def parse_index(self):
|
||||
genres = [
|
||||
('ekonomia,4.html', 'Ekonomia'),
|
||||
('filozofia,15.html', 'Filozofia'),
|
||||
('historia,6.html', 'Historia'),
|
||||
('kosciol,8.html', 'Kościół'),
|
||||
('kultura,5.html', 'Kultura'),
|
||||
('media,10.html', 'Media'),
|
||||
('nauka,9.html', 'Nauka'),
|
||||
('polityka,11.html', 'Polityka'),
|
||||
('polska,12.html', 'Polska'),
|
||||
('prolife,3.html', 'Prolife'),
|
||||
('religia,7.html', 'Religia'),
|
||||
('rodzina,13.html', 'Rodzina'),
|
||||
('swiat,14.html', 'Świat'),
|
||||
('wydarzenie,16.html', 'Wydarzenie')
|
||||
]
|
||||
feeds = []
|
||||
articles = {}
|
||||
|
||||
for url, genName in genres:
|
||||
soup = self.index_to_soup('http://www.fronda.pl/c/'+ url)
|
||||
articles[genName] = []
|
||||
for item in soup.findAll('li'):
|
||||
article_h = item.find('h2')
|
||||
if not article_h:
|
||||
continue
|
||||
article_date = self.tag_to_string(item.find('b'))
|
||||
if self.date_cut(article_date):
|
||||
continue
|
||||
article_a = article_h.find('a')
|
||||
article_url = 'http://www.fronda.pl' + article_a['href']
|
||||
article_title = self.tag_to_string(article_a)
|
||||
articles[genName].append( { 'title' : article_title, 'url' : article_url, 'date' : article_date })
|
||||
feeds.append((genName, articles[genName]))
|
||||
return feeds
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'yui-g'})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['related-articles',
|
||||
'button right',
|
||||
'pagination']}),
|
||||
dict(name='h3', attrs={'class':'block-header article comments'}),
|
||||
dict(name='ul', attrs={'class':'comment-list'}),
|
||||
dict(name='ul', attrs={'class':'category'}),
|
||||
dict(name='p', attrs={'id':'comments-disclaimer'}),
|
||||
dict(name='div', attrs={'id':'comment-form'})
|
||||
]
|
||||
|
30
recipes/house_news.recipe
Normal file
@ -0,0 +1,30 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Eddie Lau'
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipeHouseNews(BasicNewsRecipe):
|
||||
title = u'House News \u4e3b\u5834\u65b0\u805e'
|
||||
__author__ = 'Eddie Lau'
|
||||
publisher = 'House News'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
auto_cleanup = False
|
||||
language = 'zh'
|
||||
encoding = 'utf-8'
|
||||
description = 'http://thehousenews.com'
|
||||
category = 'Chinese, Blogs, Opinion, News, Hong Kong'
|
||||
masthead_url = 'http://thehousenews.com/static/images/housebeta.jpg'
|
||||
extra_css = 'img {display: block; margin-left: auto; margin-right: auto; margin-top: 10px; margin-bottom: 10px; max-height:90%;} p[class=date] {font-size:50%;} div[class=author] {font-size:75%;} p[class=caption] {font-size:50%;}'
|
||||
feeds = [(u'Latest', u'http://thehousenews.com/rss/')]
|
||||
keep_only_tags = [dict(name='h1'),
|
||||
dict(name='div', attrs={'class':['photo']}),
|
||||
dict(name='p', attrs={'class':'caption'}),
|
||||
dict(name='div', attrs={'class':'articleTextWrap'}),
|
||||
dict(name='div', attrs={'class':['author']}),
|
||||
dict(name='p', attrs={'class':'date'})]
|
||||
|
||||
def populate_article_metadata(self, article, soup, first):
|
||||
if first and hasattr(self, 'add_toc_thumbnail'):
|
||||
picdiv = soup.find('img')
|
||||
if picdiv is not None:
|
||||
self.add_toc_thumbnail(article,picdiv['src'])
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 878 B |
BIN
recipes/icons/app_funds.png
Normal file
After Width: | Height: | Size: 471 B |
BIN
recipes/icons/ekundelek_pl.png
Normal file
After Width: | Height: | Size: 536 B |
BIN
recipes/icons/gosc_niedzielny.png
Normal file
After Width: | Height: | Size: 588 B |
BIN
recipes/icons/kp.png
Normal file
After Width: | Height: | Size: 485 B |
BIN
recipes/icons/prawica_net.png
Normal file
After Width: | Height: | Size: 609 B |
BIN
recipes/icons/samcik_blox.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
52
recipes/kp.recipe
Normal file
@ -0,0 +1,52 @@
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class KrytykaPolitycznaRecipe(BasicNewsRecipe):
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = u'intromatyk <intromatyk@gmail.com>'
|
||||
language = 'pl'
|
||||
version = 1
|
||||
|
||||
title = u'Krytyka Polityczna'
|
||||
category = u'News'
|
||||
description = u' Lewicowe pismo zaangażowane w bieg spraw publicznych w Polsce.'
|
||||
cover_url=''
|
||||
remove_empty_feeds= True
|
||||
no_stylesheets=True
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100000
|
||||
recursions = 0
|
||||
|
||||
no_stylesheets = True
|
||||
remove_javascript = True
|
||||
simultaneous_downloads = 3
|
||||
|
||||
keep_only_tags =[]
|
||||
keep_only_tags.append(dict(name = 'h1', attrs = {'class' : 'print-title'}))
|
||||
keep_only_tags.append(dict(name = 'div', attrs = {'class' : 'print-content'}))
|
||||
|
||||
remove_tags =[]
|
||||
remove_tags.append(dict(attrs = {'class' : ['field field-type-text field-field-story-switch', 'field field-type-filefield field-field-story-temp' , 'field field-type-text field-field-story-author', 'field field-type-text field-field-story-lead-switch']}))
|
||||
|
||||
extra_css = '''
|
||||
body {font-family: verdana, arial, helvetica, geneva, sans-serif ;}
|
||||
td.contentheading{font-size: large; font-weight: bold;}
|
||||
'''
|
||||
|
||||
feeds = [
|
||||
('Wszystkie', 'http://www.krytykapolityczna.pl/rss.xml')
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
soup = self.index_to_soup(url)
|
||||
print_ico = soup.find(attrs = {'class' : 'print-page'})
|
||||
print_uri = print_ico['href']
|
||||
self.log('PRINT', print_uri)
|
||||
return 'http://www.krytykapolityczna.pl/' + print_uri
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for alink in soup.findAll('a'):
|
||||
if alink.string is not None:
|
||||
tstr = alink.string
|
||||
alink.replaceWith(tstr)
|
||||
return soup
|
@ -13,7 +13,7 @@ import datetime
|
||||
class Newsweek(BasicNewsRecipe):
|
||||
|
||||
# how many issues to go back, 0 means get the most current one
|
||||
BACK_ISSUES = 1
|
||||
BACK_ISSUES = 2
|
||||
|
||||
EDITION = '0'
|
||||
DATE = None
|
||||
|
40
recipes/prawica_net.recipe
Normal file
@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'teepel <teepel44@gmail.com>'
|
||||
|
||||
'''
|
||||
http://prawica.net
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class prawica_recipe(BasicNewsRecipe):
|
||||
title = u'prawica.net'
|
||||
__author__ = 'teepel <teepel44@gmail.com>'
|
||||
language = 'pl'
|
||||
description ='Wiadomości ze strony prawica.net'
|
||||
INDEX='http://prawica.net/'
|
||||
remove_empty_feeds= True
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
remove_javascript=True
|
||||
no_stylesheets=True
|
||||
|
||||
feeds = [(u'all', u'http://prawica.net/all/feed')]
|
||||
|
||||
|
||||
keep_only_tags =[]
|
||||
#this line should show title of the article, but it doesnt work
|
||||
keep_only_tags.append(dict(name = 'h1', attrs = {'class' : 'print-title'}))
|
||||
keep_only_tags.append(dict(name = 'div', attrs = {'class' : 'content'}))
|
||||
|
||||
|
||||
remove_tags =[]
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'field field-type-viewfield field-field-autor2'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'class' : 'field field-type-viewfield field-field-publikacje-autora'}))
|
||||
remove_tags.append(dict(name = 'div', attrs = {'id' : 'rate-widget-2 rate-widget clear-block rate-average rate-widget-fivestar rate-daa7512627f21dcf15e0af47e5279f0e rate-processed'}))
|
||||
remove_tags_after =[(dict(name = 'div', attrs = {'class' : 'field-label-inline-first'}))]
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('http://prawica.net/', 'http://prawica.net/print/')
|
29
recipes/rybinski.recipe
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = u'2012, Tomasz Dlugosz <tomek3d@gmail.com>'
|
||||
'''
|
||||
rybinski.eu
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Rybinski(BasicNewsRecipe):
|
||||
title = u'Rybinski.eu - economy of the XXI century'
|
||||
description = u'Blog ekonomiczny dra hab. Krzysztofa Rybi\u0144skiego'
|
||||
language = 'pl'
|
||||
__author__ = u'Tomasz D\u0142ugosz'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
|
||||
feeds = [(u'wpisy', u'http://www.rybinski.eu/?feed=rss2&lang=pl')]
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'post'})]
|
||||
|
||||
remove_tags = [
|
||||
dict(name = 'div', attrs = {'class' : 'post-meta-1'}),
|
||||
dict(name = 'div', attrs = {'class' : 'post-meta-2'}),
|
||||
dict(name = 'div', attrs = {'class' : 'post-comments'})
|
||||
]
|
||||
|
26
recipes/samcik_blox.recipe
Normal file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'teepel <teepel44@gmail.com>'
|
||||
|
||||
'''
|
||||
samcik.blox.pl
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class samcik(BasicNewsRecipe):
|
||||
title = u'Maciej Samcik Blog'
|
||||
__author__ = 'teepel <teepel44@gmail.com>'
|
||||
language = 'pl'
|
||||
description =u'Blog Macieja Samcika, długoletniego dziennikarza ekonomicznego Gazety Wyborczej . O finansach małych i dużych. Mnóstwo ciekawostek na temat pieniędzy.'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
remove_javascript=True
|
||||
no_stylesheets=True
|
||||
simultaneous_downloads = 3
|
||||
|
||||
remove_tags =[]
|
||||
remove_tags.append(dict(name = 'table', attrs = {'border' : '0'}))
|
||||
|
||||
feeds = [(u'Wpisy', u'http://samcik.blox.pl/rss2')]
|
@ -17,6 +17,7 @@ class Sciencenews(BasicNewsRecipe):
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
auto_cleanup = True
|
||||
timefmt = ' [%A, %d %B, %Y]'
|
||||
|
||||
extra_css = '''
|
||||
@ -31,14 +32,14 @@ class Sciencenews(BasicNewsRecipe):
|
||||
.credit{color:#A6A6A6;font-family:helvetica,arial ;font-size: xx-small ;}
|
||||
'''
|
||||
|
||||
keep_only_tags = [ dict(name='div', attrs={'id':'column_action'}) ]
|
||||
remove_tags_after = dict(name='ul', attrs={'id':'content_functions_bottom'})
|
||||
remove_tags = [
|
||||
dict(name='ul', attrs={'id':'content_functions_bottom'})
|
||||
,dict(name='div', attrs={'id':['content_functions_top','breadcrumb_content']})
|
||||
,dict(name='img', attrs={'class':'icon'})
|
||||
,dict(name='div', attrs={'class': 'embiggen'})
|
||||
]
|
||||
#keep_only_tags = [ dict(name='div', attrs={'id':'column_action'}) ]
|
||||
#remove_tags_after = dict(name='ul', attrs={'id':'content_functions_bottom'})
|
||||
#remove_tags = [
|
||||
#dict(name='ul', attrs={'id':'content_functions_bottom'})
|
||||
#,dict(name='div', attrs={'id':['content_functions_top','breadcrumb_content']})
|
||||
#,dict(name='img', attrs={'class':'icon'})
|
||||
#,dict(name='div', attrs={'class': 'embiggen'})
|
||||
#]
|
||||
|
||||
feeds = [(u"Science News / News Items", u'http://sciencenews.org/index.php/feed/type/news/name/news.rss/view/feed/name/all.rss')]
|
||||
|
||||
@ -53,9 +54,9 @@ class Sciencenews(BasicNewsRecipe):
|
||||
|
||||
return cover_url
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
#def preprocess_html(self, soup):
|
||||
|
||||
for tag in soup.findAll(name=['span']):
|
||||
tag.name = 'div'
|
||||
#for tag in soup.findAll(name=['span']):
|
||||
#tag.name = 'div'
|
||||
|
||||
return soup
|
||||
#return soup
|
||||
|
BIN
resources/images/mimetypes/cbr.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
resources/images/mimetypes/cbz.png
Normal file
After Width: | Height: | Size: 10 KiB |
@ -11,6 +11,7 @@ let g:syntastic_cpp_include_dirs = [
|
||||
\'/usr/include/freetype2',
|
||||
\'/usr/include/fontconfig',
|
||||
\'src/qtcurve/common', 'src/qtcurve',
|
||||
\'src/unrar',
|
||||
\'/usr/include/ImageMagick',
|
||||
\]
|
||||
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs
|
||||
|
@ -6,7 +6,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, socket, struct, subprocess, sys
|
||||
import os, socket, struct, subprocess, sys, glob
|
||||
from distutils.spawn import find_executable
|
||||
|
||||
from PyQt4 import pyqtconfig
|
||||
@ -36,7 +36,7 @@ if iswindows:
|
||||
MT = os.path.join(os.path.dirname(p), 'bin', 'mt.exe')
|
||||
MT = os.path.join(SDK, 'bin', 'mt.exe')
|
||||
os.environ['QMAKESPEC'] = 'win32-msvc'
|
||||
ICU = r'Q:\icu'
|
||||
ICU = os.environ.get('ICU_DIR', r'Q:\icu')
|
||||
|
||||
QMAKE = '/Volumes/sw/qt/bin/qmake' if isosx else 'qmake'
|
||||
if find_executable('qmake-qt4'):
|
||||
@ -122,7 +122,8 @@ if iswindows:
|
||||
zlib_lib_dirs = [sw_lib_dir]
|
||||
zlib_libs = ['zlib']
|
||||
|
||||
magick_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.7.6')]
|
||||
md = glob.glob(os.path.join(prefix, 'build', 'ImageMagick-*'))[-1]
|
||||
magick_inc_dirs = [md]
|
||||
magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')]
|
||||
magick_libs = ['CORE_RL_wand_', 'CORE_RL_magick_']
|
||||
podofo_inc = os.path.join(sw_inc_dir, 'podofo')
|
||||
|
@ -47,6 +47,13 @@ class Extension(object):
|
||||
self.ldflags = kwargs.get('ldflags', [])
|
||||
self.optional = kwargs.get('optional', False)
|
||||
self.needs_ddk = kwargs.get('needs_ddk', False)
|
||||
of = kwargs.get('optimize_level', None)
|
||||
if of is None:
|
||||
of = '/Ox' if iswindows else '-O3'
|
||||
else:
|
||||
flag = '/O%d' if iswindows else '-O%d'
|
||||
of = flag % of
|
||||
self.cflags.insert(0, of)
|
||||
|
||||
def preflight(self, obj_dir, compiler, linker, builder, cflags, ldflags):
|
||||
pass
|
||||
@ -176,6 +183,24 @@ extensions = [
|
||||
sip_files = ['calibre/gui2/progress_indicator/QProgressIndicator.sip']
|
||||
),
|
||||
|
||||
Extension('unrar',
|
||||
['unrar/%s.cpp'%(x.partition('.')[0]) for x in '''
|
||||
rar.o strlist.o strfn.o pathfn.o savepos.o smallfn.o global.o file.o
|
||||
filefn.o filcreat.o archive.o arcread.o unicode.o system.o
|
||||
isnt.o crypt.o crc.o rawread.o encname.o resource.o match.o
|
||||
timefn.o rdwrfn.o consio.o options.o ulinks.o errhnd.o rarvm.o
|
||||
secpassword.o rijndael.o getbits.o sha1.o extinfo.o extract.o
|
||||
volume.o list.o find.o unpack.o cmddata.o filestr.o scantree.o
|
||||
'''.split()] + ['calibre/utils/unrar.cpp'],
|
||||
inc_dirs=['unrar'],
|
||||
cflags = [('/' if iswindows else '-') + x for x in (
|
||||
'DSILENT', 'DRARDLL', 'DUNRAR')] + (
|
||||
[] if iswindows else ['-D_FILE_OFFSET_BITS=64',
|
||||
'-D_LARGEFILE_SOURCE']),
|
||||
optimize_level=2,
|
||||
libraries=['User32', 'Advapi32', 'kernel32', 'Shell32'] if iswindows else []
|
||||
),
|
||||
|
||||
]
|
||||
|
||||
|
||||
@ -239,7 +264,7 @@ if isunix:
|
||||
cxx = os.environ.get('CXX', 'g++')
|
||||
cflags = os.environ.get('OVERRIDE_CFLAGS',
|
||||
# '-Wall -DNDEBUG -ggdb -fno-strict-aliasing -pipe')
|
||||
'-O3 -Wall -DNDEBUG -fno-strict-aliasing -pipe')
|
||||
'-Wall -DNDEBUG -fno-strict-aliasing -pipe')
|
||||
cflags = shlex.split(cflags) + ['-fPIC']
|
||||
ldflags = os.environ.get('OVERRIDE_LDFLAGS', '-Wall')
|
||||
ldflags = shlex.split(ldflags)
|
||||
@ -274,7 +299,7 @@ if isosx:
|
||||
|
||||
if iswindows:
|
||||
cc = cxx = msvc.cc
|
||||
cflags = '/c /nologo /Ox /MD /W3 /EHsc /DNDEBUG'.split()
|
||||
cflags = '/c /nologo /MD /W3 /EHsc /DNDEBUG'.split()
|
||||
ldflags = '/DLL /nologo /INCREMENTAL:NO /NODEFAULTLIB:libcmt.lib'.split()
|
||||
#cflags = '/c /nologo /Ox /MD /W3 /EHsc /Zi'.split()
|
||||
#ldflags = '/DLL /nologo /INCREMENTAL:NO /DEBUG'.split()
|
||||
|
@ -43,7 +43,6 @@ class LinuxFreeze(Command):
|
||||
'/usr/lib/liblcms2.so.2',
|
||||
'/usr/lib/libstlport.so.5.1',
|
||||
'/tmp/calibre-mount-helper',
|
||||
'/usr/lib/libunrar.so',
|
||||
'/usr/lib/libchm.so.0',
|
||||
'/usr/lib/libsqlite3.so.0',
|
||||
'/usr/lib/libmng.so.1',
|
||||
|
@ -32,7 +32,6 @@ binary_includes = [
|
||||
'/usr/lib/liblcms.so.1',
|
||||
'/usr/lib/liblzma.so.0',
|
||||
'/usr/lib/libexpat.so.1',
|
||||
'/usr/lib/libunrar.so',
|
||||
'/usr/lib/libsqlite3.so.0',
|
||||
'/usr/lib/libmng.so.1',
|
||||
'/usr/lib/libpodofo.so.0.9.1',
|
||||
|
@ -437,8 +437,8 @@ class Py2App(object):
|
||||
|
||||
@flush
|
||||
def add_misc_libraries(self):
|
||||
for x in ('usb-1.0.0', 'mtp.9', 'unrar', 'readline.6.1',
|
||||
'wmflite-0.2.7', 'chm.0', 'sqlite3.0'):
|
||||
for x in ('usb-1.0.0', 'mtp.9', 'readline.6.1', 'wmflite-0.2.7',
|
||||
'chm.0', 'sqlite3.0'):
|
||||
info('\nAdding', x)
|
||||
x = 'lib%s.dylib'%x
|
||||
shutil.copy2(join(SW, 'lib', x), self.frameworks_dir)
|
||||
|
@ -388,7 +388,7 @@ def main():
|
||||
'dist_dir' : 'build/py2app',
|
||||
'argv_emulation' : True,
|
||||
'iconfile' : icon,
|
||||
'frameworks': ['libusb.dylib', 'libunrar.dylib'],
|
||||
'frameworks': ['libusb.dylib'],
|
||||
'includes' : ['sip', 'pkg_resources', 'PyQt4.QtXml',
|
||||
'PyQt4.QtSvg', 'PyQt4.QtWebKit', 'commands',
|
||||
'mechanize', 'ClientForm', 'usbobserver',
|
||||
|
@ -10,17 +10,16 @@ import sys, os, shutil, glob, py_compile, subprocess, re, zipfile, time, textwra
|
||||
|
||||
from setup import (Command, modules, functions, basenames, __version__,
|
||||
__appname__)
|
||||
from setup.build_environment import msvc, MT, RC
|
||||
from setup.build_environment import msvc, MT, RC, is64bit
|
||||
from setup.installer.windows.wix import WixMixIn
|
||||
|
||||
ICU_DIR = os.environ.get('ICU_DIR', r'Q:\icu')
|
||||
OPENSSL_DIR = os.environ.get('OPENSSL_DIR', r'Q:\openssl')
|
||||
QT_DIR = os.environ.get('QT_DIR', 'Q:\\Qt\\4.8.2')
|
||||
QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns']
|
||||
LIBUNRAR = os.environ.get('UNRARDLL', 'C:\\Program Files\\UnrarDLL\\unrar.dll')
|
||||
SW = r'C:\cygwin\home\kovid\sw'
|
||||
IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-6.7.6',
|
||||
'VisualMagick', 'bin')
|
||||
IMAGEMAGICK = os.path.join(SW, 'build',
|
||||
'ImageMagick-*\\VisualMagick\\bin')
|
||||
CRT = r'C:\Microsoft.VC90.CRT'
|
||||
LZMA = r'Q:\easylzma\build\easylzma-0.0.8'
|
||||
|
||||
@ -88,8 +87,9 @@ class Win32Freeze(Command, WixMixIn):
|
||||
self.archive_lib_dir()
|
||||
self.remove_CRT_from_manifests()
|
||||
self.create_installer()
|
||||
self.build_portable()
|
||||
self.build_portable_installer()
|
||||
if not is64bit:
|
||||
self.build_portable()
|
||||
self.build_portable_installer()
|
||||
|
||||
def remove_CRT_from_manifests(self):
|
||||
'''
|
||||
@ -260,9 +260,6 @@ class Win32Freeze(Command, WixMixIn):
|
||||
|
||||
print
|
||||
print 'Adding third party dependencies'
|
||||
print '\tAdding unrar'
|
||||
shutil.copyfile(LIBUNRAR, os.path.join(self.dll_dir,
|
||||
os.path.basename(LIBUNRAR).replace('64', '')))
|
||||
|
||||
print '\tAdding misc binary deps'
|
||||
bindir = os.path.join(SW, 'bin')
|
||||
@ -283,8 +280,9 @@ class Win32Freeze(Command, WixMixIn):
|
||||
shutil.copy2(msrc, self.dll_dir)
|
||||
|
||||
# Copy ImageMagick
|
||||
impath = glob.glob(IMAGEMAGICK)[-1]
|
||||
for pat in ('*.dll', '*.xml'):
|
||||
for f in glob.glob(self.j(IMAGEMAGICK, pat)):
|
||||
for f in glob.glob(self.j(impath, pat)):
|
||||
ok = True
|
||||
for ex in ('magick++', 'x11.dll', 'xext.dll'):
|
||||
if ex in f.lower(): ok = False
|
||||
@ -565,9 +563,12 @@ class Win32Freeze(Command, WixMixIn):
|
||||
for x in (self.plugins_dir, self.dll_dir):
|
||||
for pyd in os.listdir(x):
|
||||
if pyd.endswith('.pyd') and pyd not in {
|
||||
'sqlite_custom.pyd', 'calibre_style.pyd'}:
|
||||
'unrar.pyd', 'sqlite_custom.pyd', 'calibre_style.pyd'}:
|
||||
# sqlite_custom has to be a file for
|
||||
# sqlite_load_extension to work
|
||||
# For some reason unrar.pyd crashes when processing
|
||||
# password protected RAR files if loaded from inside
|
||||
# pylib.zip
|
||||
self.add_to_zipfile(zf, pyd, x)
|
||||
os.remove(self.j(x, pyd))
|
||||
|
||||
|
@ -240,23 +240,6 @@ Run make (note that you must have GNU make installed in cygwin)
|
||||
|
||||
Optionally run make check
|
||||
|
||||
Libunrar
|
||||
----------
|
||||
|
||||
Get the source from http://www.rarlab.com/rar_add.htm
|
||||
|
||||
Open UnrarDll.vcproj, change build type to release.
|
||||
If building 64 bit change Win32 to x64.
|
||||
|
||||
Build the Solution, find the dll in the build subdir. As best as I can tell,
|
||||
the vcproj already defines the SILENT preprocessor directive, but you should
|
||||
test this.
|
||||
|
||||
.. http://www.rarlab.com/rar/UnRARDLL.exe install and add C:\Program Files\UnrarDLL to PATH
|
||||
|
||||
TODO: 64-bit check that SILENT is defined and that the ctypes bindings actuall
|
||||
work
|
||||
|
||||
zlib
|
||||
------
|
||||
|
||||
@ -420,16 +403,32 @@ Run::
|
||||
Python Imaging Library
|
||||
------------------------
|
||||
|
||||
For 32-bit:
|
||||
Install as normal using installer at http://www.lfd.uci.edu/~gohlke/pythonlibs/
|
||||
|
||||
For 64-bit:
|
||||
Download from http://pypi.python.org/pypi/Pillow/
|
||||
Edit setup.py setting the ROOT values, like this::
|
||||
|
||||
SW = r'C:\cygwin\home\kovid\sw'
|
||||
JPEG_ROOT = ZLIB_ROOT = FREETYPE_ROOT = (SW+r'\lib', SW+r'\include')
|
||||
|
||||
Build and install with::
|
||||
python setup.py build
|
||||
python setup.py install
|
||||
|
||||
Note that the lcms module will not be built. PIL requires lcms-1.x but only
|
||||
lcms-2.x can be compiled as a 64 bit library.
|
||||
|
||||
Test it on the target system with
|
||||
|
||||
calibre-debug -c "from PIL import _imaging, _imagingmath, _imagingft, _imagingcms"
|
||||
|
||||
calibre-debug -c "from PIL import Image; import _imaging, _imagingmath, _imagingft"
|
||||
|
||||
kdewin32-msvc
|
||||
----------------
|
||||
|
||||
I dont think this is needed any more, I've left it here just in case I'm wrong.
|
||||
|
||||
Get it from http://www.winkde.org/pub/kde/ports/win32/repository/kdesupport/
|
||||
mkdir build
|
||||
Run cmake
|
||||
@ -448,29 +447,34 @@ cp build/kdewin32-msvc-0.3.9/include/*.h include/
|
||||
poppler
|
||||
-------------
|
||||
|
||||
In Cmake: disable GTK, Qt, OPenjpeg, cpp, lcms, gtk_tests, qt_tests. Enable qt4, jpeg, png and zlib
|
||||
mkdir build
|
||||
|
||||
NOTE: poppler must be built as a static library, unless you build the qt4 bindings
|
||||
Run the cmake GUI which will find the various dependencies automatically.
|
||||
On 64 bit cmake might not let you choose Visual Studio 2008, in whcih case
|
||||
leave the source field blank, click configure choose Visual Studio 2008 and
|
||||
then enter the source field.
|
||||
|
||||
cp build/utils/Release/*.exe ../../bin/
|
||||
In Cmake: disable GTK, Qt, OPenjpeg, cpp, lcms, gtk_tests, qt_tests. Enable
|
||||
jpeg, png and zlib::
|
||||
|
||||
cp build/utils/Release/*.exe ../../bin/
|
||||
|
||||
podofo
|
||||
----------
|
||||
|
||||
Download from http://podofo.sourceforge.net/download.html
|
||||
|
||||
Add the following three lines near the top of CMakeLists.txt
|
||||
SET(WANT_LIB64 FALSE)
|
||||
SET(PODOFO_BUILD_SHARED TRUE)
|
||||
SET(PODOFO_BUILD_STATIC FALSE)
|
||||
|
||||
cp build/podofo-*/build/src/Release/podofo.dll bin/
|
||||
cp build/podofo-*/build/src/Release/podofo.lib lib/
|
||||
cp build/podofo-*/build/src/Release/podofo.exp lib/
|
||||
|
||||
cp build/podofo-*/build/podofo_config.h include/podofo/
|
||||
cp -r build/podofo-*/src/* include/podofo/
|
||||
|
||||
You have to use >=0.9.1
|
||||
Run::
|
||||
cp "`find . -name *.dll`" ~/sw/bin/
|
||||
cp "`find . -name *.lib`" ~/sw/lib/
|
||||
mkdir ~/sw/include/podofo
|
||||
cp build/podofo_config.h ~/sw/include/podofo
|
||||
cp -r src/* ~/sw/include/podofo/
|
||||
|
||||
|
||||
ImageMagick
|
||||
@ -493,7 +497,7 @@ Undefine ProvideDllMain and MAGICKCORE_X11_DELEGATE
|
||||
Now open VisualMagick/VisualDynamicMT.sln set to Release
|
||||
Remove the CORE_xlib, UTIL_Imdisplay and CORE_Magick++ projects.
|
||||
|
||||
F7 for build project, you will get one error due to the removal of xlib, ignore
|
||||
F7 for build solution, you will get one error due to the removal of xlib, ignore
|
||||
it.
|
||||
|
||||
netifaces
|
||||
@ -503,10 +507,10 @@ Download the source tarball from http://alastairs-place.net/projects/netifaces/
|
||||
|
||||
Rename netifaces.c to netifaces.cpp and make the same change in setup.py
|
||||
|
||||
Run
|
||||
Run::
|
||||
python setup.py build
|
||||
cp `find build/ -name *.pyd` /cygdrive/c/Python27/Lib/site-packages/
|
||||
|
||||
python setup.py build
|
||||
cp build/lib.win32-2.7/netifaces.pyd /cygdrive/c/Python27/Lib/site-packages/
|
||||
|
||||
psutil
|
||||
--------
|
||||
@ -528,6 +532,16 @@ Get it from http://lloyd.github.com/easylzma/ (use the trunk version)
|
||||
Run cmake and build the Visual Studio solution (generates CLI tools and dll and
|
||||
static lib automatically)
|
||||
|
||||
chmlib
|
||||
-------
|
||||
|
||||
Download the zip source code from: http://www.jedrea.com/chmlib/
|
||||
Run::
|
||||
cd src && unzip ./ChmLib-ds6.zip
|
||||
Then open ChmLib.dsw in Visual Studio, change the configuration to Release
|
||||
(Win32|x64) and build solution, this will generate a static library in
|
||||
Release/ChmLib.lib
|
||||
|
||||
calibre
|
||||
---------
|
||||
|
||||
|
@ -217,19 +217,15 @@ wchar_t* get_app_dirw() {
|
||||
|
||||
|
||||
void load_python_dll() {
|
||||
char *app_dir, *fc_dir, *fc_file, *dll_dir, *qt_plugin_dir;
|
||||
char *app_dir, *dll_dir, *qt_plugin_dir;
|
||||
size_t l;
|
||||
|
||||
app_dir = get_app_dir();
|
||||
l = strlen(app_dir)+20;
|
||||
dll_dir = (char*) calloc(l, sizeof(char));
|
||||
fc_dir = (char*) calloc(l, sizeof(char));
|
||||
fc_file = (char*) calloc(l, sizeof(char));
|
||||
qt_plugin_dir = (char*) calloc(l, sizeof(char));
|
||||
if (!dll_dir || !qt_plugin_dir || !fc_dir) ExitProcess(_show_error(L"Out of memory", L"", 1));
|
||||
if (!dll_dir || !qt_plugin_dir) ExitProcess(_show_error(L"Out of memory", L"", 1));
|
||||
_snprintf_s(dll_dir, l, _TRUNCATE, "%sDLLs", app_dir);
|
||||
_snprintf_s(fc_dir, l, _TRUNCATE, "%sfontconfig", app_dir);
|
||||
_snprintf_s(fc_file, l, _TRUNCATE, "%s\\fonts.conf", fc_dir);
|
||||
_snprintf_s(qt_plugin_dir, l, _TRUNCATE, "%sqt_plugins", app_dir);
|
||||
free(app_dir);
|
||||
|
||||
@ -237,8 +233,6 @@ void load_python_dll() {
|
||||
_putenv_s("MAGICK_CONFIGURE_PATH", dll_dir);
|
||||
_putenv_s("MAGICK_CODER_MODULE_PATH", dll_dir);
|
||||
_putenv_s("MAGICK_FILTER_MODULE_PATH", dll_dir);
|
||||
_putenv_s("FC_CONFIG_DIR", fc_dir);
|
||||
_putenv_s("FC_CONFIG_FILE", fc_file);
|
||||
_putenv_s("QT_PLUGIN_PATH", qt_plugin_dir);
|
||||
|
||||
if (!SetDllDirectoryA(dll_dir)) ExitProcess(show_last_error(L"Failed to set DLL directory."));
|
||||
|
@ -18,14 +18,14 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
|
||||
"devel@lists.alioth.debian.org>\n"
|
||||
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
|
||||
"PO-Revision-Date: 2012-09-04 18:42+0000\n"
|
||||
"Last-Translator: SimonFS <simonschuette@arcor.de>\n"
|
||||
"PO-Revision-Date: 2012-11-08 15:28+0000\n"
|
||||
"Last-Translator: Elmux <bla.mail@gmx.net>\n"
|
||||
"Language-Team: German <debian-l10n-german@lists.debian.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-09-05 04:37+0000\n"
|
||||
"X-Generator: Launchpad (build 15901)\n"
|
||||
"X-Launchpad-Export-Date: 2012-11-09 04:39+0000\n"
|
||||
"X-Generator: Launchpad (build 16250)\n"
|
||||
"Language: de\n"
|
||||
|
||||
#. name for aaa
|
||||
@ -58,7 +58,7 @@ msgstr "Ambrak"
|
||||
|
||||
#. name for aah
|
||||
msgid "Arapesh; Abu'"
|
||||
msgstr ""
|
||||
msgstr "Arapesh;Abu' (Papua-Neuguinea)"
|
||||
|
||||
#. name for aai
|
||||
msgid "Arifama-Miniafia"
|
||||
@ -102,7 +102,7 @@ msgstr "Aasáx"
|
||||
|
||||
#. name for aat
|
||||
msgid "Albanian; Arvanitika"
|
||||
msgstr ""
|
||||
msgstr "Albanisch, Arvanitikanisch"
|
||||
|
||||
#. name for aau
|
||||
msgid "Abau"
|
||||
|
@ -264,7 +264,7 @@ def extract(path, dir):
|
||||
with open(path, 'rb') as f:
|
||||
id_ = f.read(3)
|
||||
if id_ == b'Rar':
|
||||
from calibre.libunrar import extract as rarextract
|
||||
from calibre.utils.unrar import extract as rarextract
|
||||
extractor = rarextract
|
||||
elif id_.startswith(b'PK'):
|
||||
from calibre.libunzip import extract as zipextract
|
||||
@ -276,7 +276,7 @@ def extract(path, dir):
|
||||
from calibre.libunzip import extract as zipextract
|
||||
extractor = zipextract
|
||||
elif ext in ['cbr', 'rar']:
|
||||
from calibre.libunrar import extract as rarextract
|
||||
from calibre.utils.unrar import extract as rarextract
|
||||
extractor = rarextract
|
||||
if extractor is None:
|
||||
raise Exception('Unknown archive type')
|
||||
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 9, 6)
|
||||
numeric_version = (0, 9, 7)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
@ -14,14 +14,6 @@ Various run time constants.
|
||||
|
||||
import sys, locale, codecs, os, importlib, collections
|
||||
|
||||
_tc = None
|
||||
def terminal_controller():
|
||||
global _tc
|
||||
if _tc is None:
|
||||
from calibre.utils.terminfo import TerminalController
|
||||
_tc = TerminalController(sys.stdout)
|
||||
return _tc
|
||||
|
||||
_plat = sys.platform.lower()
|
||||
iswindows = 'win32' in _plat or 'win64' in _plat
|
||||
isosx = 'darwin' in _plat
|
||||
@ -36,7 +28,10 @@ isunix = isosx or islinux
|
||||
isportable = os.environ.get('CALIBRE_PORTABLE_BUILD', None) is not None
|
||||
ispy3 = sys.version_info.major > 2
|
||||
isxp = iswindows and sys.getwindowsversion().major < 6
|
||||
is64bit = sys.maxint > (1 << 32)
|
||||
isworker = os.environ.has_key('CALIBRE_WORKER') or os.environ.has_key('CALIBRE_SIMPLE_WORKER')
|
||||
if isworker:
|
||||
os.environ.pop('CALIBRE_FORCE_ANSI', None)
|
||||
|
||||
try:
|
||||
preferred_encoding = locale.getpreferredencoding()
|
||||
@ -49,6 +44,19 @@ winerror = importlib.import_module('winerror') if iswindows else None
|
||||
win32api = importlib.import_module('win32api') if iswindows else None
|
||||
fcntl = None if iswindows else importlib.import_module('fcntl')
|
||||
|
||||
_osx_ver = None
|
||||
def get_osx_version():
|
||||
global _osx_ver
|
||||
if _osx_ver is None:
|
||||
import platform
|
||||
from collections import namedtuple
|
||||
OSX = namedtuple('OSX', 'major minor tertiary')
|
||||
try:
|
||||
_osx_ver = OSX(*(map(int, platform.mac_ver()[0].split('.'))))
|
||||
except:
|
||||
_osx_ver = OSX(0, 0, 0)
|
||||
return _osx_ver
|
||||
|
||||
filesystem_encoding = sys.getfilesystemencoding()
|
||||
if filesystem_encoding is None: filesystem_encoding = 'utf-8'
|
||||
else:
|
||||
@ -91,6 +99,7 @@ class Plugins(collections.Mapping):
|
||||
'speedup',
|
||||
'freetype',
|
||||
'woff',
|
||||
'unrar',
|
||||
]
|
||||
if iswindows:
|
||||
plugins.extend(['winutil', 'wpd', 'winfonts'])
|
||||
@ -177,6 +186,9 @@ def get_version():
|
||||
v = __version__
|
||||
if getattr(sys, 'frozen', False) and dv and os.path.abspath(dv) in sys.path:
|
||||
v += '*'
|
||||
if iswindows and is64bit:
|
||||
v += ' [64bit]'
|
||||
|
||||
return v
|
||||
|
||||
def get_portable_base():
|
||||
|
@ -8,7 +8,7 @@ from calibre import guess_type
|
||||
from calibre.customize import (FileTypePlugin, MetadataReaderPlugin,
|
||||
MetadataWriterPlugin, PreferencesPlugin, InterfaceActionBase, StoreBase)
|
||||
from calibre.constants import numeric_version
|
||||
from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata
|
||||
from calibre.ebooks.metadata.archive import ArchiveExtract, get_comic_metadata
|
||||
from calibre.ebooks.html.to_zip import HTML2ZIP
|
||||
|
||||
plugins = []
|
||||
@ -140,7 +140,7 @@ class ComicMetadataReader(MetadataReaderPlugin):
|
||||
elif id_.startswith(b'PK'):
|
||||
ftype = 'cbz'
|
||||
if ftype == 'cbr':
|
||||
from calibre.libunrar import extract_first_alphabetically as extract_first
|
||||
from calibre.utils.unrar import extract_first_alphabetically as extract_first
|
||||
extract_first
|
||||
else:
|
||||
from calibre.libunzip import extract_member
|
||||
@ -150,9 +150,9 @@ class ComicMetadataReader(MetadataReaderPlugin):
|
||||
ret = extract_first(stream)
|
||||
mi = MetaInformation(None, None)
|
||||
stream.seek(0)
|
||||
if ftype == 'cbz':
|
||||
if ftype in {'cbr', 'cbz'}:
|
||||
try:
|
||||
mi.smart_update(get_cbz_metadata(stream))
|
||||
mi.smart_update(get_comic_metadata(stream, ftype))
|
||||
except:
|
||||
pass
|
||||
if ret is not None:
|
||||
@ -1433,15 +1433,6 @@ class StoreFoylesUKStore(StoreBase):
|
||||
formats = ['EPUB', 'PDF']
|
||||
affiliate = True
|
||||
|
||||
class StoreGandalfStore(StoreBase):
|
||||
name = 'Gandalf'
|
||||
author = u'Tomasz Długosz'
|
||||
description = u'Księgarnia internetowa Gandalf.'
|
||||
actual_plugin = 'calibre.gui2.store.stores.gandalf_plugin:GandalfStore'
|
||||
|
||||
headquarters = 'PL'
|
||||
formats = ['EPUB', 'PDF']
|
||||
|
||||
class StoreGoogleBooksStore(StoreBase):
|
||||
name = 'Google Books'
|
||||
description = u'Google Books'
|
||||
@ -1472,7 +1463,7 @@ class StoreKoboStore(StoreBase):
|
||||
class StoreLegimiStore(StoreBase):
|
||||
name = 'Legimi'
|
||||
author = u'Tomasz Długosz'
|
||||
description = u'Tanie oraz darmowe ebooki, egazety i blogi w formacie EPUB, wprost na Twój e-czytnik, iPhone, iPad, Android i komputer'
|
||||
description = u'Ebooki w formacie EPUB, MOBI i PDF'
|
||||
actual_plugin = 'calibre.gui2.store.stores.legimi_plugin:LegimiStore'
|
||||
|
||||
headquarters = 'PL'
|
||||
@ -1566,6 +1557,15 @@ class StorePragmaticBookshelfStore(StoreBase):
|
||||
headquarters = 'US'
|
||||
formats = ['EPUB', 'MOBI', 'PDF']
|
||||
|
||||
class StorePublioStore(StoreBase):
|
||||
name = 'Publio'
|
||||
description = u'Publio.pl to księgarnia internetowa, w której mogą Państwo nabyć e-booki i audiobooki.'
|
||||
actual_plugin = 'calibre.gui2.store.stores.publio_plugin:PublioStore'
|
||||
author = u'Tomasz Długosz'
|
||||
|
||||
headquarters = 'PL'
|
||||
formats = ['EPUB', 'MOBI', 'PDF']
|
||||
|
||||
class StoreRW2010Store(StoreBase):
|
||||
name = 'RW2010'
|
||||
description = u'Polski serwis self-publishingowy. Pliki PDF, EPUB i MOBI. Maksymalna cena utworu nie przekracza u nas 10 złotych!'
|
||||
@ -1675,7 +1675,6 @@ plugins += [
|
||||
StoreEscapeMagazineStore,
|
||||
StoreFeedbooksStore,
|
||||
StoreFoylesUKStore,
|
||||
StoreGandalfStore,
|
||||
StoreGoogleBooksStore,
|
||||
StoreGutenbergStore,
|
||||
StoreKoboStore,
|
||||
@ -1689,6 +1688,7 @@ plugins += [
|
||||
StoreOpenBooksStore,
|
||||
StoreOzonRUStore,
|
||||
StorePragmaticBookshelfStore,
|
||||
StorePublioStore,
|
||||
StoreRW2010Store,
|
||||
StoreSmashwordsStore,
|
||||
StoreVirtualoStore,
|
||||
@ -1716,7 +1716,7 @@ if __name__ == '__main__':
|
||||
ret = 0
|
||||
|
||||
for x in ('lxml', 'calibre.ebooks.BeautifulSoup', 'uuid',
|
||||
'calibre.utils.terminfo', 'calibre.utils.magick', 'PIL', 'Image',
|
||||
'calibre.utils.terminal', 'calibre.utils.magick', 'PIL', 'Image',
|
||||
'sqlite3', 'mechanize', 'httplib', 'xml'):
|
||||
if x in sys.modules:
|
||||
ret = 1
|
||||
|
@ -15,7 +15,13 @@ def option_parser():
|
||||
parser = OptionParser(usage='''\
|
||||
%prog [options]
|
||||
|
||||
Run an embedded python interpreter.
|
||||
Various command line interfaces useful for debugging calibre. With no options,
|
||||
this command starts an embedded python interpreter. You can also run the main
|
||||
calibre GUI and the calibre viewer in debug mode.
|
||||
|
||||
It also contains interfaces to various bits of calibre that do not have
|
||||
dedicated command line tools, such as font subsetting, tweaking ebooks and so
|
||||
on.
|
||||
''')
|
||||
parser.add_option('-c', '--command', help='Run python code.', default=None)
|
||||
parser.add_option('-e', '--exec-file', default=None, help='Run the python code in file.')
|
||||
@ -37,9 +43,6 @@ Run an embedded python interpreter.
|
||||
help='Run the ebook viewer',)
|
||||
parser.add_option('--paths', default=False, action='store_true',
|
||||
help='Output the paths necessary to setup the calibre environment')
|
||||
parser.add_option('--migrate', action='store_true', default=False,
|
||||
help='Migrate old database. Needs two arguments. Path '
|
||||
'to library1.db and path to new library folder.')
|
||||
parser.add_option('--add-simple-plugin', default=None,
|
||||
help='Add a simple plugin (i.e. a plugin that consists of only a '
|
||||
'.py file), by specifying the path to the py file containing the '
|
||||
@ -118,28 +121,6 @@ def reinit_db(dbpath, callback=None, sql_dump=None):
|
||||
os.remove(dest)
|
||||
prints('Database successfully re-initialized')
|
||||
|
||||
def migrate(old, new):
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.library.database import LibraryDatabase
|
||||
from calibre.library.database2 import LibraryDatabase2
|
||||
from calibre.utils.terminfo import ProgressBar
|
||||
from calibre.constants import terminal_controller
|
||||
class Dummy(ProgressBar):
|
||||
def setLabelText(self, x): pass
|
||||
def setAutoReset(self, y): pass
|
||||
def reset(self): pass
|
||||
def setRange(self, min, max):
|
||||
self.min = min
|
||||
self.max = max
|
||||
def setValue(self, val):
|
||||
self.update(float(val)/getattr(self, 'max', 1))
|
||||
|
||||
db = LibraryDatabase(old)
|
||||
db2 = LibraryDatabase2(new)
|
||||
db2.migrate_old(db, Dummy(terminal_controller(), 'Migrating database...'))
|
||||
prefs['library_path'] = os.path.abspath(new)
|
||||
print 'Database migrated to', os.path.abspath(new)
|
||||
|
||||
def debug_device_driver():
|
||||
from calibre.devices import debug
|
||||
debug(ioreg_to_tmp=True, buf=sys.stdout)
|
||||
@ -166,8 +147,10 @@ def print_basic_debug_info(out=None):
|
||||
if out is None: out = sys.stdout
|
||||
out = functools.partial(prints, file=out)
|
||||
import platform
|
||||
from calibre.constants import __appname__, get_version, isportable, isosx
|
||||
out(__appname__, get_version(), 'Portable' if isportable else '')
|
||||
from calibre.constants import (__appname__, get_version, isportable, isosx,
|
||||
isfrozen)
|
||||
out(__appname__, get_version(), 'Portable' if isportable else '',
|
||||
'isfrozen:', isfrozen)
|
||||
out(platform.platform(), platform.system())
|
||||
out(platform.system_alias(platform.system(), platform.release(),
|
||||
platform.version()))
|
||||
@ -249,11 +232,6 @@ def main(args=sys.argv):
|
||||
exec opts.command
|
||||
elif opts.debug_device_driver:
|
||||
debug_device_driver()
|
||||
elif opts.migrate:
|
||||
if len(args) < 3:
|
||||
print 'You must specify the path to library1.db and the path to the new library folder'
|
||||
return 1
|
||||
migrate(args[1], args[2])
|
||||
elif opts.add_simple_plugin is not None:
|
||||
add_simple_plugin(opts.add_simple_plugin)
|
||||
elif opts.paths:
|
||||
|
@ -240,6 +240,7 @@ class ITUNES(DriverBase):
|
||||
|
||||
# iTunes enumerations
|
||||
Audiobooks = [
|
||||
'AAC audio file',
|
||||
'Audible file',
|
||||
'MPEG audio file',
|
||||
'Protected AAC audio file'
|
||||
|
@ -11,7 +11,6 @@ from optparse import OptionParser
|
||||
|
||||
from calibre import __version__, __appname__, human_readable
|
||||
from calibre.devices.errors import PathError
|
||||
from calibre.utils.terminfo import TerminalController
|
||||
from calibre.devices.errors import ArgumentError, DeviceError, DeviceLocked
|
||||
from calibre.customize.ui import device_plugins
|
||||
from calibre.devices.scanner import DeviceScanner
|
||||
@ -20,8 +19,7 @@ from calibre.utils.config import device_prefs
|
||||
MINIMUM_COL_WIDTH = 12 #: Minimum width of columns in ls output
|
||||
|
||||
class FileFormatter(object):
|
||||
def __init__(self, file, term):
|
||||
self.term = term
|
||||
def __init__(self, file):
|
||||
self.is_dir = file.is_dir
|
||||
self.is_readonly = file.is_readonly
|
||||
self.size = file.size
|
||||
@ -94,7 +92,7 @@ def info(dev):
|
||||
print "Software version:", info[2]
|
||||
print "Mime type: ", info[3]
|
||||
|
||||
def ls(dev, path, term, recurse=False, color=False, human_readable_size=False, ll=False, cols=0):
|
||||
def ls(dev, path, recurse=False, human_readable_size=False, ll=False, cols=0):
|
||||
def col_split(l, cols): # split list l into columns
|
||||
rows = len(l) / cols
|
||||
if len(l) % cols:
|
||||
@ -126,14 +124,13 @@ def ls(dev, path, term, recurse=False, color=False, human_readable_size=False, l
|
||||
for file in files:
|
||||
size = len(str(file.size))
|
||||
if human_readable_size:
|
||||
file = FileFormatter(file, term)
|
||||
file = FileFormatter(file)
|
||||
size = len(file.human_readable_size)
|
||||
if size > maxlen: maxlen = size
|
||||
for file in files:
|
||||
file = FileFormatter(file, term)
|
||||
file = FileFormatter(file)
|
||||
name = file.name if ll else file.isdir_name
|
||||
lsoutput.append(name)
|
||||
if color: name = file.name_in_color
|
||||
lscoloutput.append(name)
|
||||
if ll:
|
||||
size = str(file.size)
|
||||
@ -173,10 +170,8 @@ def shutdown_plugins():
|
||||
pass
|
||||
|
||||
def main():
|
||||
term = TerminalController()
|
||||
cols = term.COLS
|
||||
if not cols: # On windows terminal width is unknown
|
||||
cols = 80
|
||||
from calibre.utils.terminal import geometry
|
||||
cols = geometry()[0]
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options] command args\n\ncommand "+
|
||||
"is one of: info, books, df, ls, cp, mkdir, touch, cat, rm, eject, test_file\n\n"+
|
||||
@ -260,7 +255,6 @@ def main():
|
||||
dev.mkdir(args[0])
|
||||
elif command == "ls":
|
||||
parser = OptionParser(usage="usage: %prog ls [options] path\nList files on the device\n\npath must begin with / or card:/")
|
||||
parser.add_option("--color", help="show ls output in color", dest="color", action="store_true", default=False)
|
||||
parser.add_option("-l", help="In addition to the name of each file, print the file type, permissions, and timestamp (the modification time, in the local timezone). Times are local.", dest="ll", action="store_true", default=False)
|
||||
parser.add_option("-R", help="Recursively list subdirectories encountered. /dev and /proc are omitted", dest="recurse", action="store_true", default=False)
|
||||
parser.remove_option("-h")
|
||||
@ -269,7 +263,7 @@ def main():
|
||||
if len(args) != 1:
|
||||
parser.print_help()
|
||||
return 1
|
||||
print ls(dev, args[0], term, color=options.color, recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
|
||||
print ls(dev, args[0], recurse=options.recurse, ll=options.ll, human_readable_size=options.hrs, cols=cols),
|
||||
elif command == "info":
|
||||
info(dev)
|
||||
elif command == "cp":
|
||||
|
@ -14,6 +14,9 @@ const calibre_device_entry_t calibre_mtp_device_table[] = {
|
||||
// Amazon Kindle Fire HD
|
||||
, { "Amazon", 0x1949, "Fire HD", 0x0007, DEVICE_FLAGS_ANDROID_BUGS}
|
||||
|
||||
// Nexus 10
|
||||
, { "Google", 0x18d1, "Nexus 10", 0x4ee2, DEVICE_FLAGS_ANDROID_BUGS}
|
||||
|
||||
, { NULL, 0xffff, NULL, 0xffff, DEVICE_FLAG_NONE }
|
||||
};
|
||||
|
||||
|
@ -696,7 +696,7 @@ PyObject* wpd::put_file(IPortableDevice *device, const wchar_t *parent_id, const
|
||||
PyBytes_AsStringAndSize(raw, &buf, &bytes_read);
|
||||
if (bytes_read > 0) {
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
hr = dest->Write(buf, bytes_read, &bytes_written);
|
||||
hr = dest->Write(buf, (ULONG)bytes_read, &bytes_written);
|
||||
Py_END_ALLOW_THREADS;
|
||||
Py_DECREF(raw);
|
||||
if (hr == STG_E_MEDIUMFULL) { PyErr_SetString(WPDError, "Cannot write to device as it is full"); break; }
|
||||
|
@ -5,7 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, dbus, re
|
||||
import os, re
|
||||
|
||||
def node_mountpoint(node):
|
||||
|
||||
@ -25,6 +25,7 @@ class NoUDisks1(Exception):
|
||||
class UDisks(object):
|
||||
|
||||
def __init__(self):
|
||||
import dbus
|
||||
self.bus = dbus.SystemBus()
|
||||
try:
|
||||
self.main = dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
|
||||
@ -35,6 +36,7 @@ class UDisks(object):
|
||||
raise
|
||||
|
||||
def device(self, device_node_path):
|
||||
import dbus
|
||||
devpath = self.main.FindDeviceByDeviceFile(device_node_path)
|
||||
return dbus.Interface(self.bus.get_object('org.freedesktop.UDisks',
|
||||
devpath), 'org.freedesktop.UDisks.Device')
|
||||
@ -73,6 +75,7 @@ class UDisks2(object):
|
||||
DRIVE = 'org.freedesktop.UDisks2.Drive'
|
||||
|
||||
def __init__(self):
|
||||
import dbus
|
||||
self.bus = dbus.SystemBus()
|
||||
try:
|
||||
self.bus.get_object('org.freedesktop.UDisks2',
|
||||
|
@ -101,7 +101,7 @@ cpalmdoc_rfind(Byte *data, Py_ssize_t pos, Py_ssize_t chunk_length) {
|
||||
static Py_ssize_t
|
||||
cpalmdoc_do_compress(buffer *b, char *output) {
|
||||
Py_ssize_t i = 0, j, chunk_len, dist;
|
||||
unsigned compound;
|
||||
unsigned int compound;
|
||||
Byte c, n;
|
||||
bool found;
|
||||
char *head;
|
||||
@ -119,7 +119,7 @@ cpalmdoc_do_compress(buffer *b, char *output) {
|
||||
dist = i - j;
|
||||
if (j < i && dist <= 2047) {
|
||||
found = true;
|
||||
compound = (dist << 3) + chunk_len-3;
|
||||
compound = (unsigned int)((dist << 3) + chunk_len-3);
|
||||
*(output++) = CHAR(0x80 + (compound >> 8 ));
|
||||
*(output++) = CHAR(compound & 0xFF);
|
||||
i += chunk_len;
|
||||
@ -148,7 +148,7 @@ cpalmdoc_do_compress(buffer *b, char *output) {
|
||||
temp.data[temp.len++] = c; j++;
|
||||
}
|
||||
i += temp.len - 1;
|
||||
*(output++) = temp.len;
|
||||
*(output++) = (char)temp.len;
|
||||
for (j=0; j < temp.len; j++) *(output++) = (char)temp.data[j];
|
||||
}
|
||||
}
|
||||
|
@ -1103,10 +1103,14 @@ OptionRecommendation(name='search_replace',
|
||||
from calibre.ebooks.oeb.transforms.unsmarten import UnsmartenPunctuation
|
||||
UnsmartenPunctuation()(self.oeb, self.opts)
|
||||
|
||||
mobi_file_type = getattr(self.opts, 'mobi_file_type', 'old')
|
||||
needs_old_markup = (self.output_plugin.file_type == 'lit' or
|
||||
(self.output_plugin.file_type == 'mobi' and mobi_file_type
|
||||
== 'old'))
|
||||
flattener = CSSFlattener(fbase=fbase, fkey=fkey,
|
||||
lineh=line_height,
|
||||
untable=self.output_plugin.file_type in ('mobi','lit'),
|
||||
unfloat=self.output_plugin.file_type in ('mobi', 'lit'),
|
||||
untable=needs_old_markup,
|
||||
unfloat=needs_old_markup,
|
||||
page_break_on_body=self.output_plugin.file_type in ('mobi',
|
||||
'lit'),
|
||||
specializer=partial(self.output_plugin.specialize_css_for_output,
|
||||
|
@ -48,12 +48,13 @@ class ArchiveExtract(FileTypePlugin):
|
||||
def run(self, archive):
|
||||
is_rar = archive.lower().endswith('.rar')
|
||||
if is_rar:
|
||||
from calibre.libunrar import extract_member, names
|
||||
from calibre.utils.unrar import extract_member, names
|
||||
else:
|
||||
zf = ZipFile(archive, 'r')
|
||||
|
||||
if is_rar:
|
||||
fnames = names(archive)
|
||||
with open(archive, 'rb') as rf:
|
||||
fnames = list(names(rf))
|
||||
else:
|
||||
fnames = zf.namelist()
|
||||
|
||||
@ -76,7 +77,8 @@ class ArchiveExtract(FileTypePlugin):
|
||||
of = self.temporary_file('_archive_extract.'+ext)
|
||||
with closing(of):
|
||||
if is_rar:
|
||||
data = extract_member(archive, match=None, name=fname)[1]
|
||||
with open(archive, 'rb') as f:
|
||||
data = extract_member(f, match=None, name=fname)[1]
|
||||
of.write(data)
|
||||
else:
|
||||
of.write(zf.read(fname))
|
||||
@ -108,21 +110,44 @@ def get_comic_book_info(d, mi):
|
||||
authors.append(x)
|
||||
if authors:
|
||||
mi.authors = authors
|
||||
comments = d.get('comments', '')
|
||||
if comments and comments.strip():
|
||||
mi.comments = comments.strip()
|
||||
pubm, puby = d.get('publicationMonth', None), d.get('publicationYear', None)
|
||||
if puby is not None:
|
||||
from calibre.utils.date import parse_only_date
|
||||
from datetime import date
|
||||
try:
|
||||
dt = date(puby, 6 if pubm is None else pubm, 15)
|
||||
dt = parse_only_date(str(dt))
|
||||
mi.pubdate = dt
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def get_cbz_metadata(stream):
|
||||
def get_comic_metadata(stream, stream_type):
|
||||
# See http://code.google.com/p/comicbookinfo/wiki/Example
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
import json
|
||||
|
||||
zf = ZipFile(stream)
|
||||
comment = None
|
||||
|
||||
mi = MetaInformation(None, None)
|
||||
if zf.comment:
|
||||
m = json.loads(zf.comment)
|
||||
if hasattr(m, 'keys'):
|
||||
for cat in m.keys():
|
||||
|
||||
if stream_type == 'cbz':
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
zf = ZipFile(stream)
|
||||
comment = zf.comment
|
||||
elif stream_type == 'cbr':
|
||||
from calibre.utils.unrar import RARFile
|
||||
f = RARFile(stream, get_comment=True)
|
||||
comment = f.comment
|
||||
|
||||
if comment:
|
||||
import json
|
||||
m = json.loads(comment)
|
||||
if hasattr(m, 'iterkeys'):
|
||||
for cat in m.iterkeys():
|
||||
if cat.startswith('ComicBookInfo'):
|
||||
get_comic_book_info(m[cat], mi)
|
||||
break
|
||||
return mi
|
||||
|
||||
|
@ -8,35 +8,27 @@ Read metadata from RAR archives
|
||||
'''
|
||||
|
||||
import os
|
||||
from io import BytesIO
|
||||
|
||||
from calibre.ptempfile import PersistentTemporaryFile, TemporaryDirectory
|
||||
from calibre.libunrar import extract_member, names
|
||||
from calibre import CurrentDir
|
||||
from calibre.utils.unrar import extract_member, names
|
||||
|
||||
def get_metadata(stream):
|
||||
from calibre.ebooks.metadata.archive import is_comic
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
|
||||
path = getattr(stream, 'name', False)
|
||||
if not path:
|
||||
pt = PersistentTemporaryFile('_rar-meta.rar')
|
||||
pt.write(stream.read())
|
||||
pt.close()
|
||||
path = pt.name
|
||||
path = os.path.abspath(path)
|
||||
file_names = list(names(path))
|
||||
file_names = list(names(stream))
|
||||
if is_comic(file_names):
|
||||
return get_metadata(stream, 'cbr')
|
||||
for f in file_names:
|
||||
stream_type = os.path.splitext(f)[1].lower()
|
||||
if stream_type:
|
||||
stream_type = stream_type[1:]
|
||||
if stream_type in ('lit', 'opf', 'prc', 'mobi', 'fb2', 'epub',
|
||||
'rb', 'imp', 'pdf', 'lrf', 'azw', 'azw1', 'azw3'):
|
||||
with TemporaryDirectory() as tdir:
|
||||
with CurrentDir(tdir):
|
||||
stream = extract_member(path, match=None, name=f,
|
||||
as_file=True)[1]
|
||||
if stream_type in {'lit', 'opf', 'prc', 'mobi', 'fb2', 'epub',
|
||||
'rb', 'imp', 'pdf', 'lrf', 'azw', 'azw1',
|
||||
'azw3'}:
|
||||
name, data = extract_member(stream, match=None, name=f)
|
||||
stream = BytesIO(data)
|
||||
stream.name = os.path.basename(name)
|
||||
return get_metadata(stream, stream_type)
|
||||
raise ValueError('No ebook found in RAR archive')
|
||||
|
||||
|
@ -214,7 +214,11 @@ class MobiMLizer(object):
|
||||
if tag in CONTENT_TAGS:
|
||||
bstate.inline = para
|
||||
pstate = bstate.istate = None
|
||||
etree.SubElement(para, XHTML(tag), attrib=istate.attrib)
|
||||
try:
|
||||
etree.SubElement(para, XHTML(tag), attrib=istate.attrib)
|
||||
except:
|
||||
print 'Invalid subelement:', para, tag, istate.attrib
|
||||
raise
|
||||
elif tag in TABLE_TAGS:
|
||||
para.attrib['valign'] = 'top'
|
||||
if istate.ids:
|
||||
@ -322,6 +326,10 @@ class MobiMLizer(object):
|
||||
istates.append(istate)
|
||||
left = 0
|
||||
display = style['display']
|
||||
if display == 'table-cell':
|
||||
display = 'inline'
|
||||
elif display.startswith('table'):
|
||||
display = 'block'
|
||||
isblock = (not display.startswith('inline') and style['display'] !=
|
||||
'none')
|
||||
isblock = isblock and style['float'] == 'none'
|
||||
|
53
src/calibre/ebooks/oeb/display/extract.coffee
Normal file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env coffee
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
###
|
||||
Copyright 2012, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
Released under the GPLv3 License
|
||||
###
|
||||
|
||||
if window?.calibre_utils
|
||||
log = window.calibre_utils.log
|
||||
|
||||
merge = (node, cnode) ->
|
||||
rules = node.ownerDocument.defaultView.getMatchedCSSRules(node, '')
|
||||
if rules
|
||||
for rule in rules
|
||||
style = rule.style
|
||||
for name in style
|
||||
val = style.getPropertyValue(name)
|
||||
if val and not cnode.style.getPropertyValue(name)
|
||||
cnode.style.setProperty(name, val)
|
||||
|
||||
inline_styles = (node) ->
|
||||
cnode = node.cloneNode(true)
|
||||
merge(node, cnode)
|
||||
nl = node.getElementsByTagName('*')
|
||||
cnl = cnode.getElementsByTagName('*')
|
||||
for node, i in nl
|
||||
merge(node, cnl[i])
|
||||
|
||||
return cnode
|
||||
|
||||
class CalibreExtract
|
||||
# This class is a namespace to expose functions via the
|
||||
# window.calibre_extract object.
|
||||
|
||||
constructor: () ->
|
||||
if not this instanceof arguments.callee
|
||||
throw new Error('CalibreExtract constructor called as function')
|
||||
this.marked_node = null
|
||||
|
||||
mark: (node) =>
|
||||
this.marked_node = node
|
||||
|
||||
extract: (node=null) =>
|
||||
if node == null
|
||||
node = this.marked_node
|
||||
cnode = inline_styles(node)
|
||||
return cnode.outerHTML
|
||||
|
||||
if window?
|
||||
window.calibre_extract = new CalibreExtract()
|
||||
|
||||
|
@ -116,6 +116,18 @@ class PagedDisplay
|
||||
# above the columns, which causes them to effectively be added to the
|
||||
# page margins (the margin collapse algorithm)
|
||||
bs.setProperty('-webkit-margin-collapse', 'separate')
|
||||
# Remove any webkit specified default margin from the first child of body
|
||||
# Otherwise, you could end up with an effective negative margin, I dont
|
||||
# understand exactly why, but see:
|
||||
# https://bugs.launchpad.net/calibre/+bug/1082640 for an example
|
||||
c = document.body.firstChild
|
||||
count = 0
|
||||
while c?.nodeType != 1 and count < 20
|
||||
c = c?.nextSibling
|
||||
count += 1
|
||||
if c?.nodeType == 1
|
||||
c.style.setProperty('-webkit-margin-before', '0')
|
||||
|
||||
|
||||
bs.setProperty('overflow', 'visible')
|
||||
bs.setProperty('height', (window.innerHeight - this.margin_top - this.margin_bottom) + 'px')
|
||||
|
@ -28,7 +28,7 @@ def self_closing_sub(match):
|
||||
tag = match.group(1)
|
||||
if tag.lower().strip() == 'br':
|
||||
return match.group()
|
||||
return '<%s %s></%s>'%(match.group(1), match.group(2), match.group(1))
|
||||
return '<%s%s></%s>'%(match.group(1), match.group(2), match.group(1))
|
||||
|
||||
def load_html(path, view, codec='utf-8', mime_type=None,
|
||||
pre_load_callback=lambda x:None, path_is_html=False):
|
||||
@ -45,12 +45,9 @@ def load_html(path, view, codec='utf-8', mime_type=None,
|
||||
|
||||
html = EntityDeclarationProcessor(html).processed_html
|
||||
has_svg = re.search(r'<[:a-zA-Z]*svg', html) is not None
|
||||
if 'xhtml' in mime_type:
|
||||
self_closing_pat = re.compile(r'<([a-z1-6]+)\s+([^>]+)/>',
|
||||
re.IGNORECASE)
|
||||
html = self_closing_pat.sub(self_closing_sub, html)
|
||||
self_closing_pat = re.compile(r'<\s*([A-Za-z1-6]+)([^>]*)/\s*>')
|
||||
html = self_closing_pat.sub(self_closing_sub, html)
|
||||
|
||||
html = re.sub(ur'<\s*title\s*/\s*>', u'', html, flags=re.IGNORECASE)
|
||||
loading_url = QUrl.fromLocalFile(path)
|
||||
pre_load_callback(loading_url)
|
||||
|
||||
|
@ -313,7 +313,7 @@ class CSSFlattener(object):
|
||||
if val in ('middle', 'bottom', 'top'):
|
||||
cssdict['vertical-align'] = val
|
||||
elif val in ('left', 'right'):
|
||||
cssdict['text-align'] = val
|
||||
cssdict['float'] = val
|
||||
del node.attrib['align']
|
||||
if node.tag == XHTML('font'):
|
||||
tags = ['descendant::h:%s'%x for x in ('p', 'div', 'table', 'h1',
|
||||
|
@ -105,6 +105,7 @@ gprefs.defaults['ui_style'] = 'calibre' if iswindows or isosx else 'system'
|
||||
gprefs.defaults['tag_browser_old_look'] = False
|
||||
gprefs.defaults['book_list_tooltips'] = True
|
||||
gprefs.defaults['bd_show_cover'] = True
|
||||
gprefs.defaults['bd_overlay_cover_size'] = False
|
||||
# }}}
|
||||
|
||||
NONE = QVariant() #: Null value to return from the data function of item models
|
||||
@ -465,6 +466,8 @@ class FileIconProvider(QFileIconProvider):
|
||||
'gif' : 'gif',
|
||||
'png' : 'png',
|
||||
'bmp' : 'bmp',
|
||||
'cbz' : 'cbz',
|
||||
'cbr' : 'cbr',
|
||||
'svg' : 'svg',
|
||||
'html' : 'html',
|
||||
'htmlz' : 'html',
|
||||
|
@ -7,7 +7,8 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import (QPixmap, QSize, QWidget, Qt, pyqtSignal, QUrl, QIcon,
|
||||
QPropertyAnimation, QEasingCurve, QApplication, QFontInfo, QAction,
|
||||
QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette, QMenu)
|
||||
QSizePolicy, QPainter, QRect, pyqtProperty, QLayout, QPalette, QMenu,
|
||||
QPen, QColor)
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
from calibre import fit_image, force_unicode, prepare_string_for_xml
|
||||
@ -324,6 +325,17 @@ class CoverView(QWidget): # {{{
|
||||
p.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform)
|
||||
p.drawPixmap(target, self.pixmap.scaled(target.size(),
|
||||
Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
if gprefs['bd_overlay_cover_size']:
|
||||
sztgt = target.adjusted(0, 0, 0, -4)
|
||||
f = p.font()
|
||||
f.setBold(True)
|
||||
p.setFont(f)
|
||||
sz = u'\u00a0%d x %d\u00a0'%(self.pixmap.width(), self.pixmap.height())
|
||||
flags = Qt.AlignBottom|Qt.AlignRight|Qt.TextSingleLine
|
||||
szrect = p.boundingRect(sztgt, flags, sz)
|
||||
p.fillRect(szrect.adjusted(0, 0, 0, 4), QColor(0, 0, 0, 200))
|
||||
p.setPen(QPen(QColor(255,255,255)))
|
||||
p.drawText(sztgt, flags, sz)
|
||||
p.end()
|
||||
|
||||
current_pixmap_size = pyqtProperty('QSize',
|
||||
|
@ -17,6 +17,7 @@ from calibre.gui2.custom_column_widgets import populate_metadata_page
|
||||
from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATETIME, \
|
||||
gprefs, question_dialog
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
from calibre.gui2.metadata.basic_widgets import CalendarWidget
|
||||
from calibre.utils.config import dynamic, JSONConfig
|
||||
from calibre.utils.titlecase import titlecase
|
||||
from calibre.utils.icu import sort_key, capitalize
|
||||
@ -339,6 +340,8 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
|
||||
self.tag_editor_button.clicked.connect(self.tag_editor)
|
||||
self.autonumber_series.stateChanged[int].connect(self.auto_number_changed)
|
||||
self.pubdate.setMinimumDateTime(UNDEFINED_QDATETIME)
|
||||
self.pubdate_cw = CalendarWidget(self.pubdate)
|
||||
self.pubdate.setCalendarWidget(self.pubdate_cw)
|
||||
pubdate_format = tweaks['gui_pubdate_display_format']
|
||||
if pubdate_format is not None:
|
||||
self.pubdate.setDisplayFormat(pubdate_format)
|
||||
|
@ -7,9 +7,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import StringIO, traceback, sys, gc
|
||||
|
||||
from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QTimer, \
|
||||
QAction, QMenu, QMenuBar, QIcon, pyqtSignal, QObject
|
||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||
from PyQt4.Qt import (QMainWindow, QTimer, QAction, QMenu, QMenuBar, QIcon,
|
||||
pyqtSignal, QObject)
|
||||
from calibre.utils.config import OptionParser
|
||||
from calibre.gui2 import error_dialog
|
||||
from calibre import prints
|
||||
@ -20,26 +19,8 @@ Usage: %prog [options]
|
||||
Launch the Graphical User Interface
|
||||
'''):
|
||||
parser = OptionParser(usage)
|
||||
# The b is required because of a regression in optparse.py in python 2.7.0
|
||||
parser.add_option(b'--redirect-console-output', default=False, action='store_true', dest='redirect',
|
||||
help=_('Redirect console output to a dialog window (both stdout and stderr). Useful on windows where GUI apps do not have a output streams.'))
|
||||
return parser
|
||||
|
||||
class DebugWindow(ConversionErrorDialog):
|
||||
|
||||
def __init__(self, parent):
|
||||
ConversionErrorDialog.__init__(self, parent, 'Console output', '')
|
||||
self.setModal(Qt.NonModal)
|
||||
font = QFont()
|
||||
font.setStyleHint(QFont.TypeWriter)
|
||||
self.text.setFont(font)
|
||||
|
||||
def write(self, msg):
|
||||
self.text.setPlainText(self.text.toPlainText()+QString(msg))
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
class GarbageCollector(QObject):
|
||||
|
||||
'''
|
||||
@ -120,10 +101,6 @@ class MainWindow(QMainWindow):
|
||||
QMainWindow.__init__(self, parent)
|
||||
if disable_automatic_gc:
|
||||
self._gc = GarbageCollector(self, debug=False)
|
||||
if getattr(opts, 'redirect', False):
|
||||
self.__console_redirect = DebugWindow(self)
|
||||
sys.stdout = sys.stderr = self.__console_redirect
|
||||
self.__console_redirect.show()
|
||||
|
||||
def unhandled_exception(self, type, value, tb):
|
||||
if type == KeyboardInterrupt:
|
||||
|
@ -7,7 +7,7 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from calibre.constants import islinux, isosx
|
||||
from calibre.constants import islinux, isosx, get_osx_version, __appname__
|
||||
|
||||
class Notifier(object):
|
||||
|
||||
@ -101,32 +101,36 @@ class QtNotifier(Notifier):
|
||||
except:
|
||||
pass
|
||||
|
||||
class GrowlNotifier(Notifier):
|
||||
|
||||
notification_type = 'All notifications'
|
||||
class AppleNotifier(Notifier):
|
||||
|
||||
def __init__(self):
|
||||
self.ok = False
|
||||
import os, sys
|
||||
try:
|
||||
import Growl
|
||||
self.icon = Growl.Image.imageFromPath(I('notify.png'))
|
||||
self.growl = Growl.GrowlNotifier(applicationName='calibre',
|
||||
applicationIcon=self.icon, notifications=[self.notification_type])
|
||||
self.growl.register()
|
||||
self.ok = True
|
||||
self.exe = os.path.join(sys.console_binaries_path, 'notifier')
|
||||
self.ok = os.access(self.exe, os.X_OK)
|
||||
import subprocess
|
||||
self.call = subprocess.check_call
|
||||
except:
|
||||
self.ok = False
|
||||
pass
|
||||
|
||||
def encode(self, msg):
|
||||
if isinstance(msg, unicode):
|
||||
msg = msg.encode('utf-8')
|
||||
return msg
|
||||
def notify(self, body, summary):
|
||||
def encode(x):
|
||||
if isinstance(x, unicode):
|
||||
x = x.encode('utf-8')
|
||||
return x
|
||||
|
||||
cmd = [self.exe, '-title', __appname__, '-activate',
|
||||
'net.kovidgoyal.calibre', '-message', encode(body)]
|
||||
if summary:
|
||||
cmd += ['-subtitle', encode(summary)]
|
||||
self.call(cmd)
|
||||
|
||||
def __call__(self, body, summary=None, replaces_id=None, timeout=0):
|
||||
timeout, body, summary = self.get_msg_parms(timeout, body, summary)
|
||||
if self.ok:
|
||||
try:
|
||||
self.growl.notify(self.notification_type, self.encode(summary),
|
||||
self.encode(body))
|
||||
self.notify(body, summary)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
@ -140,10 +144,10 @@ def get_notifier(systray=None):
|
||||
ans = FDONotifier()
|
||||
if not ans.ok:
|
||||
ans = None
|
||||
#if isosx:
|
||||
# ans = GrowlNotifier()
|
||||
# if not ans.ok:
|
||||
# ans = None
|
||||
elif False and isosx and get_osx_version() >= (10, 8, 0):
|
||||
ans = AppleNotifier()
|
||||
if not ans.ok:
|
||||
ans = None
|
||||
if ans is None:
|
||||
ans = QtNotifier(systray)
|
||||
if not ans.ok:
|
||||
|
@ -107,6 +107,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
r('book_list_tooltips', gprefs)
|
||||
r('tag_browser_old_look', gprefs, restart_required=True)
|
||||
r('bd_show_cover', gprefs)
|
||||
r('bd_overlay_cover_size', gprefs)
|
||||
|
||||
r('cover_flow_queue_length', config, restart_required=True)
|
||||
|
||||
|
@ -309,11 +309,38 @@ Manage Authors. You can use the values {author} and
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_bd_show_cover">
|
||||
<property name="text">
|
||||
<string>Show &cover in the book details panel</string>
|
||||
</property>
|
||||
</widget>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="opt_bd_show_cover">
|
||||
<property name="text">
|
||||
<string>Show &cover in the book details panel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="opt_bd_overlay_cover_size">
|
||||
<property name="toolTip">
|
||||
<string>Show the size of the book's cover in pixels</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show cover &size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_bd1">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -1,82 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
__license__ = 'GPL 3'
|
||||
__copyright__ = '2011-2012, Tomasz Długosz <tomek3d@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import re
|
||||
import urllib
|
||||
from contextlib import closing
|
||||
|
||||
from lxml import html
|
||||
|
||||
from PyQt4.Qt import QUrl
|
||||
|
||||
from calibre import browser, url_slash_cleaner
|
||||
from calibre.gui2 import open_url
|
||||
from calibre.gui2.store import StorePlugin
|
||||
from calibre.gui2.store.basic_config import BasicStoreConfig
|
||||
from calibre.gui2.store.search_result import SearchResult
|
||||
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||
|
||||
class GandalfStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
url = 'http://www.gandalf.com.pl/ebooks/'
|
||||
|
||||
if external or self.config.get('open_external', False):
|
||||
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
|
||||
else:
|
||||
d = WebStoreDialog(self.gui, url, parent, detail_item)
|
||||
d.setWindowTitle(self.name)
|
||||
d.set_tags(self.config.get('tags', ''))
|
||||
d.exec_()
|
||||
|
||||
def search(self, query, max_results=10, timeout=60):
|
||||
counter = max_results
|
||||
page = 1
|
||||
url = 'http://www.gandalf.com.pl/we/' + urllib.quote_plus(query.decode('utf-8').encode('iso8859_2')) + '/bdb'
|
||||
|
||||
br = browser()
|
||||
|
||||
while counter:
|
||||
with closing(br.open((url + str(page-1) + '/#s') if (page-1) else (url + '/#s'), timeout=timeout)) as f:
|
||||
doc = html.fromstring(f.read())
|
||||
for data in doc.xpath('//div[@class="box"]'):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
id = ''.join(data.xpath('.//div[@class="info"]/h3/a/@href'))
|
||||
if not id:
|
||||
continue
|
||||
|
||||
cover_url = ''.join(data.xpath('.//div[@class="info"]/h3/a/@id'))
|
||||
title = ''.join(data.xpath('.//div[@class="info"]/h3/a/@title'))
|
||||
formats = ''.join(data.xpath('.//div[@class="info"]/p[1]/text()'))
|
||||
formats = re.findall(r'\((.*?)\)',formats)[0]
|
||||
author = ''.join(data.xpath('.//div[@class="info"]/h4/text() | .//div[@class="info"]/h4/span/text()'))
|
||||
price = ''.join(data.xpath('.//div[@class="options"]/h3/text()'))
|
||||
price = re.sub('PLN', 'zł', price)
|
||||
price = re.sub('\.', ',', price)
|
||||
drm = data.xpath('boolean(.//div[@class="info" and contains(., "Zabezpieczenie: DRM")])')
|
||||
|
||||
counter -= 1
|
||||
|
||||
s = SearchResult()
|
||||
s.cover_url = 'http://imguser.gandalf.com.pl/' + re.sub('p', 'p_', cover_url) + '.jpg'
|
||||
s.title = title.strip()
|
||||
s.author = author.strip()
|
||||
s.price = price
|
||||
s.detail_item = id.strip()
|
||||
if drm:
|
||||
s.drm = SearchResult.DRM_LOCKED
|
||||
else:
|
||||
s.drm = SearchResult.DRM_UNLOCKED
|
||||
s.formats = formats.upper().strip()
|
||||
|
||||
yield s
|
||||
if not doc.xpath('boolean(//div[@class="wyszukiwanie_podstawowe_header"]//div[@class="box"])'):
|
||||
break
|
||||
page+=1
|
@ -25,7 +25,7 @@ class LegimiStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
|
||||
plain_url = 'http://www.legimi.com/pl/ebooks/?price=any'
|
||||
plain_url = 'http://www.legimi.com/pl/ebooki/'
|
||||
url = 'https://ssl.afiliant.com/affskrypt,,2f9de2,,11483,,,?u=(' + plain_url + ')'
|
||||
detail_url = None
|
||||
|
||||
@ -41,32 +41,36 @@ class LegimiStore(BasicStoreConfig, StorePlugin):
|
||||
d.exec_()
|
||||
|
||||
def search(self, query, max_results=10, timeout=60):
|
||||
url = 'http://www.legimi.com/pl/ebooks/?price=any&lang=pl&search=' + urllib.quote_plus(query) + '&sort=relevance'
|
||||
url = 'http://www.legimi.com/pl/ebooki/?szukaj=' + urllib.quote_plus(query)
|
||||
|
||||
br = browser()
|
||||
drm_pattern = re.compile("(DRM)")
|
||||
drm_pattern = re.compile("zabezpieczona DRM")
|
||||
|
||||
counter = max_results
|
||||
with closing(br.open(url, timeout=timeout)) as f:
|
||||
doc = html.fromstring(f.read())
|
||||
for data in doc.xpath('//div[@class="list"]/ul/li'):
|
||||
for data in doc.xpath('//div[@id="listBooks"]/div'):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
id = ''.join(data.xpath('.//div[@class="item_cover_container"]/a[1]/@href'))
|
||||
id = ''.join(data.xpath('.//a[@class="plainLink"]/@href'))
|
||||
if not id:
|
||||
continue
|
||||
|
||||
cover_url = ''.join(data.xpath('.//div[@class="item_cover_container"]/a/img/@src'))
|
||||
title = ''.join(data.xpath('.//div[@class="item_entries"]/h2/a/text()'))
|
||||
author = ''.join(data.xpath('.//div[@class="item_entries"]/span[1]/a/text()'))
|
||||
cover_url = ''.join(data.xpath('.//img[1]/@src'))
|
||||
title = ''.join(data.xpath('.//span[@class="bookListTitle ellipsis"]/text()'))
|
||||
author = ''.join(data.xpath('.//span[@class="bookListAuthor ellipsis"]/text()'))
|
||||
author = re.sub(',','',author)
|
||||
author = re.sub(';',',',author)
|
||||
price = ''.join(data.xpath('.//span[@class="ebook_price"]/text()'))
|
||||
formats = ''.join(data.xpath('.//div[@class="item_entries"]/span[3]/text()'))
|
||||
formats = re.sub('Format:','',formats)
|
||||
drm = drm_pattern.search(formats)
|
||||
formats = re.sub('\(DRM\)','',formats)
|
||||
price = ''.join(data.xpath('.//div[@class="bookListPrice"]/span/text()'))
|
||||
formats = []
|
||||
with closing(br.open(id.strip(), timeout=timeout/4)) as nf:
|
||||
idata = html.fromstring(nf.read())
|
||||
formatlist = idata.xpath('.//div[@id="fullBookFormats"]//span[@class="bookFormat"]/text()')
|
||||
for x in formatlist:
|
||||
if x.strip() not in formats:
|
||||
formats.append(x.strip())
|
||||
drm = drm_pattern.search(''.join(idata.xpath('.//div[@id="fullBookFormats"]/p/text()')))
|
||||
|
||||
counter -= 1
|
||||
|
||||
@ -76,7 +80,7 @@ class LegimiStore(BasicStoreConfig, StorePlugin):
|
||||
s.author = author.strip()
|
||||
s.price = price
|
||||
s.detail_item = 'http://www.legimi.com/' + id.strip()
|
||||
s.formats = ', '.join(formats)
|
||||
s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED
|
||||
s.formats = formats.strip()
|
||||
|
||||
yield s
|
||||
|
79
src/calibre/gui2/store/stores/publio_plugin.py
Normal file
@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
__license__ = 'GPL 3'
|
||||
__copyright__ = '2012, Tomasz Długosz <tomek3d@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import urllib
|
||||
from contextlib import closing
|
||||
|
||||
from lxml import html
|
||||
|
||||
from PyQt4.Qt import QUrl
|
||||
|
||||
from calibre import browser, url_slash_cleaner
|
||||
from calibre.gui2 import open_url
|
||||
from calibre.gui2.store import StorePlugin
|
||||
from calibre.gui2.store.basic_config import BasicStoreConfig
|
||||
from calibre.gui2.store.search_result import SearchResult
|
||||
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||
|
||||
class PublioStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
google_analytics = '?utm_source=tdcalibre&utm_medium=calibre'
|
||||
url = 'http://www.publio.pl/e-booki.html' + google_analytics
|
||||
|
||||
if external or self.config.get('open_external', False):
|
||||
open_url(QUrl(url_slash_cleaner((detail_item + google_analytics) if detail_item else url)))
|
||||
else:
|
||||
d = WebStoreDialog(self.gui, url, parent, detail_item)
|
||||
d.setWindowTitle(self.name)
|
||||
d.set_tags(self.config.get('tags', ''))
|
||||
d.exec_()
|
||||
|
||||
def search(self, query, max_results=20, timeout=60):
|
||||
|
||||
br = browser()
|
||||
|
||||
counter = max_results
|
||||
page = 1
|
||||
while counter:
|
||||
with closing(br.open('http://www.publio.pl/e-booki,strona' + str(page) + '.html?q=' + urllib.quote(query), timeout=timeout)) as f:
|
||||
doc = html.fromstring(f.read())
|
||||
for data in doc.xpath('//div[@class="item"]'):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
id = ''.join(data.xpath('.//div[@class="img"]/a/@href'))
|
||||
if not id:
|
||||
continue
|
||||
|
||||
cover_url = ''.join(data.xpath('.//div[@class="img"]/a/img/@data-original'))
|
||||
title = ''.join(data.xpath('.//div[@class="desc"]/h4/a/text()'))
|
||||
title2 = ''.join(data.xpath('.//div[@class="desc"]/h5/a/text()'))
|
||||
if title2:
|
||||
title = title + '. ' + title2
|
||||
author = ', '.join(data.xpath('./div[@class="desc"]/div[@class="detailShortList"]/div[@class="row"]/a/text()'))
|
||||
price = ''.join(data.xpath('.//div[@class="priceBoxContener "]/div/ins/text()'))
|
||||
if not price:
|
||||
price = ''.join(data.xpath('.//div[@class="priceBoxContener "]/div/text()'))
|
||||
formats = ', '.join(data.xpath('.//div[@class="formats"]/a/img/@alt'))
|
||||
|
||||
counter -= 1
|
||||
|
||||
s = SearchResult()
|
||||
s.cover_url = 'http://www.publio.pl' + cover_url
|
||||
s.title = title.strip()
|
||||
s.author = author.strip()
|
||||
s.price = price.strip()
|
||||
s.detail_item = 'http://www.publio.pl' + id.strip()
|
||||
s.drm = SearchResult.DRM_LOCKED if 'DRM' in formats else SearchResult.DRM_UNLOCKED
|
||||
s.formats = formats.replace(' DRM','').strip()
|
||||
|
||||
yield s
|
||||
if not doc.xpath('boolean(//a[@class="next"])'):
|
||||
break
|
||||
page+=1
|
@ -67,6 +67,7 @@ class UpdateNotification(QDialog):
|
||||
|
||||
def __init__(self, calibre_version, plugin_updates, parent=None):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setAttribute(Qt.WA_QuitOnClose, False)
|
||||
self.resize(400, 250)
|
||||
self.l = QGridLayout()
|
||||
self.setLayout(self.l)
|
||||
|
@ -287,6 +287,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
||||
c.set('fullscreen_scrollbar', self.opt_fullscreen_scrollbar.isChecked())
|
||||
c.set('show_fullscreen_help', self.opt_show_fullscreen_help.isChecked())
|
||||
c.set('cols_per_screen', int(self.opt_cols_per_screen.value()))
|
||||
c.set('start_in_fullscreen', self.opt_start_in_fullscreen.isChecked())
|
||||
c.set('use_book_margins', not
|
||||
self.opt_override_book_margins.isChecked())
|
||||
c.set('text_color', self.current_text_color)
|
||||
|
@ -12,7 +12,7 @@ from PyQt4.Qt import (QSize, QSizePolicy, QUrl, SIGNAL, Qt, pyqtProperty,
|
||||
QPainter, QPalette, QBrush, QDialog, QColor, QPoint, QImage, QRegion,
|
||||
QIcon, pyqtSignature, QAction, QMenu, QString, pyqtSignal,
|
||||
QSwipeGesture, QApplication, pyqtSlot)
|
||||
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
|
||||
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings, QWebElement
|
||||
|
||||
from calibre.gui2.viewer.flip import SlideFlip
|
||||
from calibre.gui2.shortcuts import Shortcuts
|
||||
@ -24,6 +24,7 @@ from calibre.gui2.viewer.javascript import JavaScriptLoader
|
||||
from calibre.gui2.viewer.position import PagePosition
|
||||
from calibre.gui2.viewer.config import config, ConfigDialog, load_themes
|
||||
from calibre.gui2.viewer.image_popup import ImagePopup
|
||||
from calibre.gui2.viewer.table_popup import TablePopup
|
||||
from calibre.ebooks.oeb.display.webview import load_html
|
||||
from calibre.constants import isxp, iswindows
|
||||
# }}}
|
||||
@ -31,6 +32,7 @@ from calibre.constants import isxp, iswindows
|
||||
class Document(QWebPage): # {{{
|
||||
|
||||
page_turn = pyqtSignal(object)
|
||||
mark_element = pyqtSignal(QWebElement)
|
||||
|
||||
def set_font_settings(self, opts):
|
||||
settings = self.settings()
|
||||
@ -182,6 +184,7 @@ class Document(QWebPage): # {{{
|
||||
ensure_ascii=False)))
|
||||
for pl in self.all_viewer_plugins:
|
||||
pl.load_javascript(evaljs)
|
||||
evaljs('py_bridge.mark_element.connect(window.calibre_extract.mark)')
|
||||
|
||||
@pyqtSignature("")
|
||||
def animated_scroll_done(self):
|
||||
@ -448,6 +451,10 @@ class Document(QWebPage): # {{{
|
||||
self.height+amount)
|
||||
self.setPreferredContentsSize(s)
|
||||
|
||||
def extract_node(self):
|
||||
return unicode(self.mainFrame().evaluateJavaScript(
|
||||
'window.calibre_extract.extract()').toString())
|
||||
|
||||
# }}}
|
||||
|
||||
class DocumentView(QWebView): # {{{
|
||||
@ -496,8 +503,11 @@ class DocumentView(QWebView): # {{{
|
||||
self.dictionary_action.triggered.connect(self.lookup)
|
||||
self.addAction(self.dictionary_action)
|
||||
self.image_popup = ImagePopup(self)
|
||||
self.table_popup = TablePopup(self)
|
||||
self.view_image_action = QAction(QIcon(I('view-image.png')), _('View &image...'), self)
|
||||
self.view_image_action.triggered.connect(self.image_popup)
|
||||
self.view_table_action = QAction(QIcon(I('view.png')), _('View &table...'), self)
|
||||
self.view_table_action.triggered.connect(self.popup_table)
|
||||
self.search_action = QAction(QIcon(I('dictionary.png')),
|
||||
_('&Search for next occurrence'), self)
|
||||
self.search_action.setShortcut(Qt.CTRL+Qt.Key_S)
|
||||
@ -603,10 +613,26 @@ class DocumentView(QWebView): # {{{
|
||||
t = t.replace(u'&', u'&&')
|
||||
return _("S&earch Google for '%s'")%t
|
||||
|
||||
def popup_table(self):
|
||||
html = self.document.extract_node()
|
||||
self.table_popup(html, QUrl.fromLocalFile(self.last_loaded_path),
|
||||
self.document.font_magnification_step)
|
||||
|
||||
def contextMenuEvent(self, ev):
|
||||
mf = self.document.mainFrame()
|
||||
r = mf.hitTestContent(ev.pos())
|
||||
img = r.pixmap()
|
||||
elem = r.element()
|
||||
if elem.isNull():
|
||||
elem = r.enclosingBlockElement()
|
||||
table = None
|
||||
parent = elem
|
||||
while not parent.isNull():
|
||||
if (unicode(parent.tagName()) == u'table' or
|
||||
unicode(parent.localName()) == u'table'):
|
||||
table = parent
|
||||
break
|
||||
parent = parent.parent()
|
||||
self.image_popup.current_img = img
|
||||
self.image_popup.current_url = r.imageUrl()
|
||||
menu = self.document.createStandardContextMenu()
|
||||
@ -615,6 +641,9 @@ class DocumentView(QWebView): # {{{
|
||||
|
||||
if not img.isNull():
|
||||
menu.addAction(self.view_image_action)
|
||||
if table is not None:
|
||||
self.document.mark_element.emit(table)
|
||||
menu.addAction(self.view_table_action)
|
||||
|
||||
text = self._selectedText()
|
||||
if text and img.isNull():
|
||||
|
@ -34,11 +34,12 @@ class JavaScriptLoader(object):
|
||||
'utils':'ebooks.oeb.display.utils',
|
||||
'fs':'ebooks.oeb.display.full_screen',
|
||||
'math': 'ebooks.oeb.display.mathjax',
|
||||
'extract': 'ebooks.oeb.display.extract',
|
||||
}
|
||||
|
||||
ORDER = ('jquery', 'jquery_scrollTo', 'bookmarks', 'referencing', 'images',
|
||||
'hyphenation', 'hyphenator', 'utils', 'cfi', 'indexing', 'paged',
|
||||
'fs', 'math')
|
||||
'fs', 'math', 'extract')
|
||||
|
||||
|
||||
def __init__(self, dynamic_coffeescript=False):
|
||||
@ -79,7 +80,7 @@ class JavaScriptLoader(object):
|
||||
evaljs(src)
|
||||
|
||||
if not lang:
|
||||
lang = 'en'
|
||||
lang = default_lang or 'en'
|
||||
|
||||
def lang_name(l):
|
||||
l = l.lower()
|
||||
|
@ -510,17 +510,18 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
def show_full_screen_label(self):
|
||||
f = self.full_screen_label
|
||||
self.esc_full_screen_action.setEnabled(True)
|
||||
f.setVisible(True)
|
||||
height = 200
|
||||
width = int(0.7*self.view.width())
|
||||
f.resize(width, height)
|
||||
f.move((self.view.width() - width)//2, (self.view.height()-height)//2)
|
||||
a = self.full_screen_label_anim
|
||||
a.setDuration(500)
|
||||
a.setStartValue(QSize(width, 0))
|
||||
a.setEndValue(QSize(width, height))
|
||||
a.start()
|
||||
QTimer.singleShot(3500, self.full_screen_label.hide)
|
||||
if self.view.document.show_fullscreen_help:
|
||||
f.setVisible(True)
|
||||
a = self.full_screen_label_anim
|
||||
a.setDuration(500)
|
||||
a.setStartValue(QSize(width, 0))
|
||||
a.setEndValue(QSize(width, height))
|
||||
a.start()
|
||||
QTimer.singleShot(3500, self.full_screen_label.hide)
|
||||
self.view.document.switch_to_fullscreen_mode()
|
||||
if self.view.document.fullscreen_clock:
|
||||
self.show_clock()
|
||||
@ -591,8 +592,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
fs = self.window_mode_changed == 'fullscreen'
|
||||
self.window_mode_changed = None
|
||||
if fs:
|
||||
if self.view.document.show_fullscreen_help:
|
||||
self.show_full_screen_label()
|
||||
self.show_full_screen_label()
|
||||
else:
|
||||
self.view.document.switch_to_window_mode()
|
||||
self.view.document.page_position.restore()
|
||||
|
83
src/calibre/gui2/viewer/table_popup.py
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from PyQt4.Qt import (QDialog, QDialogButtonBox, QVBoxLayout, QApplication,
|
||||
QSize, QIcon, Qt)
|
||||
from PyQt4.QtWebKit import QWebView
|
||||
|
||||
from calibre.gui2 import gprefs, error_dialog
|
||||
|
||||
class TableView(QDialog):
|
||||
|
||||
def __init__(self, parent, font_magnification_step):
|
||||
QDialog.__init__(self, parent)
|
||||
self.font_magnification_step = font_magnification_step
|
||||
dw = QApplication.instance().desktop()
|
||||
self.avail_geom = dw.availableGeometry(parent)
|
||||
|
||||
self.view = QWebView(self)
|
||||
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
bb.accepted.connect(self.accept)
|
||||
bb.rejected.connect(self.reject)
|
||||
self.zi_button = zi = bb.addButton(_('Zoom &in'), bb.ActionRole)
|
||||
self.zo_button = zo = bb.addButton(_('Zoom &out'), bb.ActionRole)
|
||||
zi.setIcon(QIcon(I('plus.png')))
|
||||
zo.setIcon(QIcon(I('minus.png')))
|
||||
zi.clicked.connect(self.zoom_in)
|
||||
zo.clicked.connect(self.zoom_out)
|
||||
|
||||
self.l = l = QVBoxLayout()
|
||||
self.setLayout(l)
|
||||
l.addWidget(self.view)
|
||||
l.addWidget(bb)
|
||||
|
||||
def zoom_in(self):
|
||||
self.view.setZoomFactor(self.view.zoomFactor() +
|
||||
self.font_magnification_step)
|
||||
|
||||
def zoom_out(self):
|
||||
self.view.setZoomFactor(max(0.1, self.view.zoomFactor()
|
||||
-self.font_magnification_step))
|
||||
|
||||
def __call__(self, html, baseurl):
|
||||
self.view.setHtml(
|
||||
'<!DOCTYPE html><html><body bgcolor="white">%s<body></html>'%html,
|
||||
baseurl)
|
||||
geom = self.avail_geom
|
||||
self.resize(QSize(int(geom.width()/2.5), geom.height()-50))
|
||||
geom = gprefs.get('viewer_table_popup_geometry', None)
|
||||
if geom is not None:
|
||||
self.restoreGeometry(geom)
|
||||
self.setWindowTitle(_('View Table'))
|
||||
self.show()
|
||||
|
||||
def done(self, e):
|
||||
gprefs['viewer_table_popup_geometry'] = bytearray(self.saveGeometry())
|
||||
return QDialog.done(self, e)
|
||||
|
||||
class TablePopup(object):
|
||||
|
||||
def __init__(self, parent):
|
||||
self.parent = parent
|
||||
self.dialogs = []
|
||||
|
||||
def __call__(self, html, baseurl, font_magnification_step):
|
||||
if not html:
|
||||
return error_dialog(self.parent, _('No table found'),
|
||||
_('No table was found'), show=True)
|
||||
d = TableView(self.parent, font_magnification_step)
|
||||
self.dialogs.append(d)
|
||||
d.finished.connect(self.cleanup, type=Qt.QueuedConnection)
|
||||
d(html, baseurl)
|
||||
|
||||
def cleanup(self):
|
||||
for d in tuple(self.dialogs):
|
||||
if not d.isVisible():
|
||||
self.dialogs.remove(d)
|
||||
|
@ -65,8 +65,7 @@ def get_db(dbpath, options):
|
||||
|
||||
def do_list(db, fields, afields, sort_by, ascending, search_text, line_width, separator,
|
||||
prefix, subtitle='Books in the calibre database'):
|
||||
from calibre.constants import terminal_controller as tc
|
||||
terminal_controller = tc()
|
||||
from calibre.utils.terminal import ColoredStream, geometry
|
||||
if sort_by:
|
||||
db.sort(sort_by, ascending)
|
||||
if search_text:
|
||||
@ -101,7 +100,7 @@ def do_list(db, fields, afields, sort_by, ascending, search_text, line_width, se
|
||||
for j, field in enumerate(fields):
|
||||
widths[j] = max(widths[j], len(unicode(i[field])))
|
||||
|
||||
screen_width = terminal_controller.COLS if line_width < 0 else line_width
|
||||
screen_width = geometry()[0] if line_width < 0 else line_width
|
||||
if not screen_width:
|
||||
screen_width = 80
|
||||
field_width = screen_width//len(fields)
|
||||
@ -120,7 +119,8 @@ def do_list(db, fields, afields, sort_by, ascending, search_text, line_width, se
|
||||
widths = list(base_widths)
|
||||
titles = map(lambda x, y: '%-*s%s'%(x-len(separator), y, separator),
|
||||
widths, title_fields)
|
||||
print terminal_controller.GREEN + ''.join(titles)+terminal_controller.NORMAL
|
||||
with ColoredStream(sys.stdout, fg='green'):
|
||||
print ''.join(titles)
|
||||
|
||||
wrappers = map(lambda x: TextWrapper(x-1), widths)
|
||||
o = cStringIO.StringIO()
|
||||
@ -1288,8 +1288,7 @@ def command_list_categories(args, dbpath):
|
||||
fields = ['category', 'tag_name', 'count', 'rating']
|
||||
|
||||
def do_list():
|
||||
from calibre.constants import terminal_controller as tc
|
||||
terminal_controller = tc()
|
||||
from calibre.utils.terminal import geometry, ColoredStream
|
||||
|
||||
separator = ' '
|
||||
widths = list(map(lambda x : 0, fields))
|
||||
@ -1297,7 +1296,7 @@ def command_list_categories(args, dbpath):
|
||||
for j, field in enumerate(fields):
|
||||
widths[j] = max(widths[j], max(len(field), len(unicode(i[field]))))
|
||||
|
||||
screen_width = terminal_controller.COLS if opts.width < 0 else opts.width
|
||||
screen_width = geometry()[0]
|
||||
if not screen_width:
|
||||
screen_width = 80
|
||||
field_width = screen_width//len(fields)
|
||||
@ -1316,7 +1315,8 @@ def command_list_categories(args, dbpath):
|
||||
widths = list(base_widths)
|
||||
titles = map(lambda x, y: '%-*s%s'%(x-len(separator), y, separator),
|
||||
widths, fields)
|
||||
print terminal_controller.GREEN + ''.join(titles)+terminal_controller.NORMAL
|
||||
with ColoredStream(sys.stdout, fg='green'):
|
||||
print ''.join(titles)
|
||||
|
||||
wrappers = map(lambda x: TextWrapper(x-1), widths)
|
||||
o = cStringIO.StringIO()
|
||||
|
@ -20,6 +20,7 @@ from calibre.ebooks.metadata import title_sort
|
||||
from calibre.utils.date import parse_date, as_local_time
|
||||
from calibre import strftime, prints, sanitize_file_name_unicode
|
||||
from calibre.ptempfile import SpooledTemporaryFile
|
||||
from calibre.db.lazy import FormatsList
|
||||
|
||||
plugboard_any_device_value = 'any device'
|
||||
plugboard_any_format_value = 'any format'
|
||||
@ -159,7 +160,7 @@ class Formatter(TemplateFormatter):
|
||||
return self.composite_values[key]
|
||||
if key in kwargs:
|
||||
val = kwargs[key]
|
||||
if isinstance(val, list):
|
||||
if isinstance(val, list) or isinstance(val, FormatsList):
|
||||
val = ','.join(val)
|
||||
return val.replace('/', '_').replace('\\', '_')
|
||||
return ''
|
||||
|
@ -1,292 +0,0 @@
|
||||
from __future__ import with_statement
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
"""
|
||||
This module provides a thin ctypes based wrapper around libunrar.
|
||||
|
||||
See ftp://ftp.rarlabs.com/rar/unrarsrc-3.7.5.tar.gz
|
||||
"""
|
||||
import os, ctypes, sys, re
|
||||
from ctypes import Structure as _Structure, c_char_p, c_uint, c_void_p, POINTER, \
|
||||
byref, c_wchar_p, c_int, c_char, c_wchar
|
||||
from tempfile import NamedTemporaryFile
|
||||
from StringIO import StringIO
|
||||
|
||||
from calibre import iswindows, load_library, CurrentDir
|
||||
from calibre.ptempfile import TemporaryDirectory, PersistentTemporaryFile
|
||||
|
||||
_librar_name = 'libunrar'
|
||||
cdll = ctypes.cdll
|
||||
if iswindows:
|
||||
class Structure(_Structure):
|
||||
_pack_ = 1
|
||||
_librar_name = 'unrar'
|
||||
cdll = ctypes.windll
|
||||
else:
|
||||
Structure = _Structure
|
||||
if hasattr(sys, 'frozen') and iswindows:
|
||||
lp = os.path.join(os.path.dirname(sys.executable), 'DLLs', 'unrar.dll')
|
||||
_libunrar = cdll.LoadLibrary(lp)
|
||||
elif hasattr(sys, 'frozen_path'):
|
||||
lp = os.path.join(sys.frozen_path, 'lib', 'libunrar.so')
|
||||
_libunrar = cdll.LoadLibrary(lp)
|
||||
else:
|
||||
_libunrar = load_library(_librar_name, cdll)
|
||||
|
||||
RAR_OM_LIST = 0
|
||||
RAR_OM_EXTRACT = 1
|
||||
|
||||
ERAR_END_ARCHIVE = 10
|
||||
ERAR_NO_MEMORY = 11
|
||||
ERAR_BAD_DATA = 12
|
||||
ERAR_BAD_ARCHIVE = 13
|
||||
ERAR_UNKNOWN_FORMAT = 14
|
||||
ERAR_EOPEN = 15
|
||||
ERAR_ECREATE = 16
|
||||
ERAR_ECLOSE = 17
|
||||
ERAR_EREAD = 18
|
||||
ERAR_EWRITE = 19
|
||||
ERAR_SMALL_BUF = 20
|
||||
ERAR_UNKNOWN = 21
|
||||
ERAR_MISSING_PASSWORD = 22
|
||||
|
||||
RAR_VOL_ASK = 0
|
||||
RAR_VOL_NOTIFY = 1
|
||||
|
||||
RAR_SKIP = 0
|
||||
RAR_TEST = 1
|
||||
RAR_EXTRACT = 2
|
||||
|
||||
class UnRARException(Exception):
|
||||
pass
|
||||
|
||||
class RAROpenArchiveDataEx(Structure):
|
||||
_fields_ = [
|
||||
('ArcName', c_char_p),
|
||||
('ArcNameW', c_wchar_p),
|
||||
('OpenMode', c_uint),
|
||||
('OpenResult', c_uint),
|
||||
('CmtBuf', c_char_p),
|
||||
('CmtBufSize', c_uint),
|
||||
('CmtSize', c_uint),
|
||||
('CmtState', c_uint),
|
||||
('Flags', c_uint),
|
||||
('Reserved', c_uint * 32)
|
||||
]
|
||||
|
||||
class RARHeaderDataEx(Structure):
|
||||
_fields_ = [
|
||||
('ArcName', c_char*1024),
|
||||
('ArcNameW', c_wchar*1024),
|
||||
('FileName', c_char*1024),
|
||||
('FileNameW', c_wchar*1024),
|
||||
('Flags', c_uint),
|
||||
('PackSize', c_uint),
|
||||
('PackSizeHigh', c_uint),
|
||||
('UnpSize', c_uint),
|
||||
('UnpSizeHigh', c_uint),
|
||||
('HostOS', c_uint),
|
||||
('FileCRC', c_uint),
|
||||
('FileTime', c_uint),
|
||||
('UnpVer', c_uint),
|
||||
('Method', c_uint),
|
||||
('FileAttr', c_uint),
|
||||
('CmtBuf', c_char_p),
|
||||
('CmtBufSize', c_uint),
|
||||
('CmtSize', c_uint),
|
||||
('CmtState', c_uint),
|
||||
('Reserved', c_uint*1024)
|
||||
]
|
||||
|
||||
# Define a callback function
|
||||
#CALLBACK_FUNC = CFUNCTYPE(c_int, c_uint, c_long, c_char_p, c_long)
|
||||
#def py_callback_func(msg, user_data, p1, p2):
|
||||
# return 0
|
||||
|
||||
#callback_func = CALLBACK_FUNC(py_callback_func)
|
||||
|
||||
_libunrar.RAROpenArchiveEx.argtypes = [POINTER(RAROpenArchiveDataEx)]
|
||||
_libunrar.RAROpenArchiveEx.restype = c_void_p
|
||||
_libunrar.RARReadHeaderEx.argtypes = [c_void_p, POINTER(RARHeaderDataEx)]
|
||||
_libunrar.RARReadHeaderEx.restype = c_int
|
||||
_libunrar.RARProcessFileW.argtypes = [c_void_p, c_int, c_wchar_p, c_wchar_p]
|
||||
_libunrar.RARProcessFileW.restype = c_int
|
||||
_libunrar.RARCloseArchive.argtypes = [c_void_p]
|
||||
_libunrar.RARCloseArchive.restype = c_int
|
||||
_libunrar.RARSetPassword.argtypes = [c_void_p, c_char_p]
|
||||
#_libunrar.RARSetCallback.argtypes = [c_void_p, CALLBACK_FUNC, c_long]
|
||||
|
||||
|
||||
def _interpret_open_error(code, path):
|
||||
msg = 'Unknown error.'
|
||||
if code == ERAR_NO_MEMORY:
|
||||
msg = "Not enough memory to process " + path
|
||||
elif code == ERAR_BAD_DATA:
|
||||
msg = "Archive header broken: " + path
|
||||
elif code == ERAR_BAD_ARCHIVE:
|
||||
msg = path + ' is not a RAR archive.'
|
||||
elif code == ERAR_EOPEN:
|
||||
msg = 'Cannot open ' + path
|
||||
return msg
|
||||
|
||||
def _interpret_process_file_error(code):
|
||||
msg = 'Unknown Error'
|
||||
if code == ERAR_UNKNOWN_FORMAT:
|
||||
msg = 'Unknown archive format'
|
||||
elif code == ERAR_BAD_ARCHIVE:
|
||||
msg = 'Bad volume'
|
||||
elif code == ERAR_ECREATE:
|
||||
msg = 'File create error'
|
||||
elif code == ERAR_EOPEN:
|
||||
msg = 'Volume open error'
|
||||
elif code == ERAR_ECLOSE:
|
||||
msg = 'File close error'
|
||||
elif code == ERAR_EREAD:
|
||||
msg = 'Read error'
|
||||
elif code == ERAR_EWRITE:
|
||||
msg = 'Write error'
|
||||
elif code == ERAR_BAD_DATA:
|
||||
msg = 'CRC error'
|
||||
elif code == ERAR_MISSING_PASSWORD:
|
||||
msg = 'Password is required.'
|
||||
return msg
|
||||
|
||||
def get_archive_info(flags):
|
||||
ios = StringIO()
|
||||
print >>ios, 'Volume:\t\t', 'yes' if (flags & 1) else 'no'
|
||||
print >>ios, 'Comment:\t', 'yes' if (flags & 2) else 'no'
|
||||
print >>ios, 'Locked:\t\t', 'yes' if (flags & 4) else 'no'
|
||||
print >>ios, 'Solid:\t\t', 'yes' if (flags & 8) else 'no'
|
||||
print >>ios, 'New naming:\t', 'yes' if (flags & 16) else 'no'
|
||||
print >>ios, 'Authenticity:\t', 'yes' if (flags & 32) else 'no'
|
||||
print >>ios, 'Recovery:\t', 'yes' if (flags & 64) else 'no'
|
||||
print >>ios, 'Encr.headers:\t', 'yes' if (flags & 128) else 'no'
|
||||
print >>ios, 'First Volume:\t', 'yes' if (flags & 256) else 'no or older than 3.0'
|
||||
return ios.getvalue()
|
||||
|
||||
def extract(path, dir):
|
||||
"""
|
||||
Extract archive C{filename} into directory C{dir}
|
||||
"""
|
||||
open_archive_data = RAROpenArchiveDataEx(ArcName=path, OpenMode=RAR_OM_EXTRACT, CmtBuf=None)
|
||||
arc_data = _libunrar.RAROpenArchiveEx(byref(open_archive_data))
|
||||
cwd = os.getcwdu()
|
||||
if not os.path.isdir( dir ):
|
||||
os.mkdir( dir )
|
||||
os.chdir( dir )
|
||||
try:
|
||||
if open_archive_data.OpenResult != 0:
|
||||
raise UnRARException(_interpret_open_error(open_archive_data.OpenResult, path))
|
||||
#prints('Archive:', path)
|
||||
#print get_archive_info(open_archive_data.Flags)
|
||||
header_data = RARHeaderDataEx(CmtBuf=None)
|
||||
#_libunrar.RARSetCallback(arc_data, callback_func, mode)
|
||||
while True:
|
||||
RHCode = _libunrar.RARReadHeaderEx(arc_data, byref(header_data))
|
||||
if RHCode != 0:
|
||||
break
|
||||
PFCode = _libunrar.RARProcessFileW(arc_data, RAR_EXTRACT, None, None)
|
||||
if PFCode != 0:
|
||||
raise UnRARException(_interpret_process_file_error(PFCode))
|
||||
if RHCode == ERAR_BAD_DATA:
|
||||
raise UnRARException('File header broken')
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
_libunrar.RARCloseArchive(arc_data)
|
||||
|
||||
def names(path):
|
||||
if hasattr(path, 'read'):
|
||||
data = path.read()
|
||||
f = NamedTemporaryFile(suffix='.rar')
|
||||
f.write(data)
|
||||
f.flush()
|
||||
path = f.name
|
||||
open_archive_data = RAROpenArchiveDataEx(ArcName=path, OpenMode=RAR_OM_LIST, CmtBuf=None)
|
||||
arc_data = _libunrar.RAROpenArchiveEx(byref(open_archive_data))
|
||||
try:
|
||||
if open_archive_data.OpenResult != 0:
|
||||
raise UnRARException(_interpret_open_error(open_archive_data.OpenResult, path))
|
||||
header_data = RARHeaderDataEx(CmtBuf=None)
|
||||
while True:
|
||||
if _libunrar.RARReadHeaderEx(arc_data, byref(header_data)) != 0:
|
||||
break
|
||||
PFCode = _libunrar.RARProcessFileW(arc_data, RAR_SKIP, None, None)
|
||||
if PFCode != 0:
|
||||
raise UnRARException(_interpret_process_file_error(PFCode))
|
||||
yield header_data.FileNameW
|
||||
finally:
|
||||
_libunrar.RARCloseArchive(arc_data)
|
||||
|
||||
def _extract_member(path, match, name):
|
||||
|
||||
def is_match(fname):
|
||||
return (name is not None and fname == name) or \
|
||||
(match is not None and match.search(fname) is not None)
|
||||
|
||||
open_archive_data = RAROpenArchiveDataEx(ArcName=path, OpenMode=RAR_OM_EXTRACT, CmtBuf=None)
|
||||
arc_data = _libunrar.RAROpenArchiveEx(byref(open_archive_data))
|
||||
try:
|
||||
if open_archive_data.OpenResult != 0:
|
||||
raise UnRARException(_interpret_open_error(open_archive_data.OpenResult, path))
|
||||
header_data = RARHeaderDataEx(CmtBuf=None)
|
||||
first = True
|
||||
while True:
|
||||
if _libunrar.RARReadHeaderEx(arc_data, byref(header_data)) != 0:
|
||||
raise UnRARException('%s has no files'%path if first
|
||||
else 'No match found in %s'%path)
|
||||
file_name = header_data.FileNameW
|
||||
if is_match(file_name):
|
||||
PFCode = _libunrar.RARProcessFileW(arc_data, RAR_EXTRACT, None, None)
|
||||
if PFCode != 0:
|
||||
raise UnRARException(_interpret_process_file_error(PFCode))
|
||||
abspath = os.path.abspath(os.path.join(*file_name.split('/')))
|
||||
return abspath
|
||||
else:
|
||||
PFCode = _libunrar.RARProcessFileW(arc_data, RAR_SKIP, None, None)
|
||||
if PFCode != 0:
|
||||
raise UnRARException(_interpret_process_file_error(PFCode))
|
||||
first = False
|
||||
|
||||
finally:
|
||||
_libunrar.RARCloseArchive(arc_data)
|
||||
|
||||
def extract_member(path, match=re.compile(r'\.(jpg|jpeg|gif|png)\s*$', re.I),
|
||||
name=None, as_file=False):
|
||||
if hasattr(path, 'read'):
|
||||
data = path.read()
|
||||
f = NamedTemporaryFile(suffix='.rar')
|
||||
f.write(data)
|
||||
f.flush()
|
||||
path = f.name
|
||||
|
||||
path = os.path.abspath(path)
|
||||
if as_file:
|
||||
path = _extract_member(path, match, name)
|
||||
return path, open(path, 'rb')
|
||||
else:
|
||||
with TemporaryDirectory('_libunrar') as tdir:
|
||||
with CurrentDir(tdir):
|
||||
path = _extract_member(path, match, name)
|
||||
return path, open(path, 'rb').read()
|
||||
|
||||
def extract_first_alphabetically(path):
|
||||
remove_path = False
|
||||
if hasattr(path, 'read'):
|
||||
data = path.read()
|
||||
with PersistentTemporaryFile('.rar') as f:
|
||||
f.write(data)
|
||||
path = f.name
|
||||
remove_path = True
|
||||
|
||||
names_ = [x for x in names(path) if os.path.splitext(x)[1][1:].lower() in
|
||||
('png', 'jpg', 'jpeg', 'gif')]
|
||||
names_.sort()
|
||||
ans = extract_member(path, name=names_[0], match=None)
|
||||
try:
|
||||
if remove_path:
|
||||
os.remove(path)
|
||||
except:
|
||||
pass
|
||||
return ans
|
||||
|
||||
|
@ -74,17 +74,21 @@ def test_imaging():
|
||||
print ('ImageMagick OK!')
|
||||
else:
|
||||
raise RuntimeError('ImageMagick choked!')
|
||||
from PIL import Image, _imaging, _imagingmath, _imagingft, _imagingcms
|
||||
_imaging, _imagingmath, _imagingft, _imagingcms
|
||||
from PIL import Image
|
||||
try:
|
||||
import _imaging, _imagingmath, _imagingft
|
||||
_imaging, _imagingmath, _imagingft
|
||||
except ImportError:
|
||||
from PIL import _imaging, _imagingmath, _imagingft
|
||||
_imaging, _imagingmath, _imagingft
|
||||
i = Image.open(cStringIO.StringIO(data))
|
||||
if i.size < (20, 20):
|
||||
raise RuntimeError('PIL choked!')
|
||||
print ('PIL OK!')
|
||||
|
||||
def test_unrar():
|
||||
from calibre.libunrar import _libunrar
|
||||
if not _libunrar:
|
||||
raise RuntimeError('Failed to load libunrar')
|
||||
from calibre.utils.unrar import test_basic
|
||||
test_basic()
|
||||
print ('Unrar OK!')
|
||||
|
||||
def test_icu():
|
||||
|