Sync with trunk.

This commit is contained in:
John Schember 2012-11-27 08:15:02 -05:00
commit 4f1ac31cb0
296 changed files with 125952 additions and 85880 deletions

View File

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

View File

@ -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
View 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')]

View File

@ -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[^>]*>&nbsp;</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
View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 878 B

BIN
recipes/icons/app_funds.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 B

BIN
recipes/icons/kp.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

52
recipes/kp.recipe Normal file
View 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

View File

@ -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

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

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

View File

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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -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

View File

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

View File

@ -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()

View File

@ -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',

View File

@ -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',

View File

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

View File

@ -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',

View File

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

View File

@ -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
---------

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = u'calibre'
numeric_version = (0, 9, 6)
numeric_version = (0, 9, 7)
__version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
@ -14,14 +14,6 @@ Various run time constants.
import sys, locale, codecs, os, importlib, collections
_tc = None
def terminal_controller():
global _tc
if _tc is None:
from calibre.utils.terminfo import TerminalController
_tc = TerminalController(sys.stdout)
return _tc
_plat = sys.platform.lower()
iswindows = 'win32' in _plat or 'win64' in _plat
isosx = 'darwin' in _plat
@ -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():

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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',

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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',

View File

@ -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',

View File

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

View File

@ -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:

View File

@ -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:

View File

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

View File

@ -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 &amp;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 &amp;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 &amp;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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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():

View File

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

View File

@ -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()

View File

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

View File

@ -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()

View File

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

View File

@ -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

View File

@ -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():

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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