mirror of
git://github.com/kovidgoyal/calibre.git
synced 2026-05-09 01:14:36 +02:00
Merge from trunk
This commit is contained in:
commit
9ed81718dd
93 changed files with 58912 additions and 32015 deletions
|
|
@ -19,6 +19,67 @@
|
|||
# new recipes:
|
||||
# - title:
|
||||
|
||||
- version: 0.8.5
|
||||
date: 2011-06-10
|
||||
|
||||
new features:
|
||||
- title: "A new 'portable' calibre build, useful if you like to carry around calibre and its library on a USB key"
|
||||
type: major
|
||||
description: "For details, see: http://calibre-ebook.com/download_portable"
|
||||
|
||||
- title: "E-book viewer: Remember the last used font size multiplier."
|
||||
tickets: [774343]
|
||||
|
||||
- title: "Preliminary support for the Kobo Touch. Drivers for the ZTE v9 tablet, Samsung S2, Notion Ink Adam and PocketBook 360+"
|
||||
|
||||
- title: "When downloading metadata merge rather than replace tags"
|
||||
|
||||
- title: "Edit metadata dialog: When pasting in an ISBN, if not valid ISBN if present on the clipboard popup a box for the user to enter the ISBN"
|
||||
|
||||
- title: "Windows build: Add code to load .pyd python extensions from a zip file. This allows many more files in the calibre installation to be zipped up, speeding up the installer."
|
||||
- title: "Add an action to remove all formats from the selected books to the remove books button"
|
||||
|
||||
|
||||
bug fixes:
|
||||
- title: "Various minor bug fixes to the column coloring code"
|
||||
|
||||
- title: "Fix the not() template function"
|
||||
|
||||
- title: "Nook Color/TSR: When sending books to the storage card place them in the My Files/Books subdirectory. Also do not upload cover thumbnails as users report that the NC/TSR don't use them."
|
||||
tickets: [792842]
|
||||
|
||||
- title: "Get Books: Update plugins for Amazon and B&N stores to handle website changes. Enable some stores by default on first run. Add Zixo store"
|
||||
tickets: [792762]
|
||||
|
||||
- title: "Comic Input: Replace the # character in filenames as it can cause problem with conversion/vieweing."
|
||||
tickets: [792723]
|
||||
|
||||
- title: "When writing files to zipfile, reset timestamp if it doesn't fit in 1980's vintage storage structures"
|
||||
|
||||
- title: "Amazon metadata plugin: Fix parsing of published date from amazon.de when it has februar in it"
|
||||
|
||||
improved recipes:
|
||||
- Ambito
|
||||
- GoComics
|
||||
- Le Monde Diplomatique
|
||||
- Max Planck
|
||||
- express.de
|
||||
|
||||
new recipes:
|
||||
- title: Ambito Financiero
|
||||
author: Darko Miletic
|
||||
|
||||
- title: Stiin Tas Technica
|
||||
author: Silviu Cotoara
|
||||
|
||||
- title: "Metro News NL"
|
||||
author: DrMerry
|
||||
|
||||
- title: "Brigitte.de, Polizeipresse DE and Heise Online"
|
||||
author: schuster
|
||||
|
||||
|
||||
|
||||
- version: 0.8.4
|
||||
date: 2011-06-03
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2008-2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
ambito.com
|
||||
'''
|
||||
|
|
@ -11,51 +9,56 @@
|
|||
class Ambito(BasicNewsRecipe):
|
||||
title = 'Ambito.com'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Informacion Libre las 24 horas'
|
||||
publisher = 'Ambito.com'
|
||||
category = 'news, politics, Argentina'
|
||||
description = 'Ambito.com con noticias del Diario Ambito Financiero de Buenos Aires'
|
||||
publisher = 'Editorial Nefir S.A.'
|
||||
category = 'news, politics, economy, finances, Argentina'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
encoding = 'iso-8859-1'
|
||||
cover_url = 'http://www.ambito.com/img/logo_.jpg'
|
||||
remove_javascript = True
|
||||
encoding = 'cp1252'
|
||||
masthead_url = 'http://www.ambito.com/img/logo_.jpg'
|
||||
use_embedded_content = False
|
||||
language = 'es_AR'
|
||||
publication_type = 'newsportal'
|
||||
extra_css = """
|
||||
body{font-family: "Trebuchet MS",Verdana,sans-serif}
|
||||
.volanta{font-size: small}
|
||||
.t2_portada{font-size: xx-large; font-family: Georgia,serif; color: #026698}
|
||||
"""
|
||||
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'align':'justify'})]
|
||||
|
||||
remove_tags = [dict(name=['object','link'])]
|
||||
remove_tags = [dict(name=['object','link','embed','iframe','meta','link','table','img'])]
|
||||
remove_attributes = ['align']
|
||||
|
||||
feeds = [
|
||||
(u'Principales Noticias', u'http://www.ambito.com/rss/noticiasp.asp' )
|
||||
,(u'Economia' , u'http://www.ambito.com/rss/noticias.asp?S=Econom%EDa' )
|
||||
,(u'Politica' , u'http://www.ambito.com/rss/noticias.asp?S=Pol%EDtica' )
|
||||
,(u'Informacion General' , u'http://www.ambito.com/rss/noticias.asp?S=Informaci%F3n%20General')
|
||||
,(u'Agro' , u'http://www.ambito.com/rss/noticias.asp?S=Agro' )
|
||||
,(u'Campo' , u'http://www.ambito.com/rss/noticias.asp?S=Agro' )
|
||||
,(u'Internacionales' , u'http://www.ambito.com/rss/noticias.asp?S=Internacionales' )
|
||||
,(u'Deportes' , u'http://www.ambito.com/rss/noticias.asp?S=Deportes' )
|
||||
,(u'Espectaculos' , u'http://www.ambito.com/rss/noticias.asp?S=Espect%E1culos' )
|
||||
,(u'Tecnologia' , u'http://www.ambito.com/rss/noticias.asp?S=Tecnologia' )
|
||||
,(u'Salud' , u'http://www.ambito.com/rss/noticias.asp?S=Salud' )
|
||||
,(u'Tecnologia' , u'http://www.ambito.com/rss/noticias.asp?S=Tecnolog%EDa' )
|
||||
,(u'Ambito Nacional' , u'http://www.ambito.com/rss/noticias.asp?S=Ambito%20Nacional' )
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('http://www.ambito.com/noticia.asp?','http://www.ambito.com/noticias/imprimir.asp?')
|
||||
return url.replace('/noticia.asp?','/noticias/imprimir.asp?')
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
mtag = '<meta http-equiv="Content-Language" content="es-AR"/>'
|
||||
soup.head.insert(0,mtag)
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
for item in soup.findAll('a'):
|
||||
str = item.string
|
||||
if str is None:
|
||||
str = self.tag_to_string(item)
|
||||
item.replaceWith(str)
|
||||
return soup
|
||||
|
||||
language = 'es_AR'
|
||||
|
|
|
|||
87
recipes/ambito_financiero.recipe
Normal file
87
recipes/ambito_financiero.recipe
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
ambito.com/diario
|
||||
'''
|
||||
|
||||
import time
|
||||
from calibre import strftime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Ambito_Financiero(BasicNewsRecipe):
|
||||
title = 'Ambito Financiero'
|
||||
__author__ = 'Darko Miletic'
|
||||
description = 'Informacion Libre las 24 horas'
|
||||
publisher = 'Editorial Nefir S.A.'
|
||||
category = 'news, politics, economy, Argentina'
|
||||
no_stylesheets = True
|
||||
encoding = 'cp1252'
|
||||
masthead_url = 'http://www.ambito.com/diario/img/logo_af.gif'
|
||||
publication_type = 'newspaper'
|
||||
needs_subscription = 'optional'
|
||||
use_embedded_content = False
|
||||
language = 'es_AR'
|
||||
PREFIX = 'http://www.ambito.com'
|
||||
INDEX = PREFIX + '/diario/index.asp'
|
||||
LOGIN = PREFIX + '/diario/login/entrada.asp'
|
||||
extra_css = """
|
||||
body{font-family: "Trebuchet MS",Verdana,sans-serif}
|
||||
.volanta{font-size: small}
|
||||
.t2_portada{font-size: xx-large; font-family: Georgia,serif; color: #026698}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'align':'justify'})]
|
||||
remove_tags = [dict(name=['object','link','embed','iframe','meta','link','table','img'])]
|
||||
remove_attributes = ['align']
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
br.open(self.INDEX)
|
||||
if self.username is not None and self.password is not None:
|
||||
br.open(self.LOGIN)
|
||||
br.select_form(name='frmlogin')
|
||||
br['USER_NAME'] = self.username
|
||||
br['USER_PASS'] = self.password
|
||||
br.submit()
|
||||
return br
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('/diario/noticia.asp?','/noticias/imprimir.asp?')
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
for item in soup.findAll('a'):
|
||||
str = item.string
|
||||
if str is None:
|
||||
str = self.tag_to_string(item)
|
||||
item.replaceWith(str)
|
||||
return soup
|
||||
|
||||
def parse_index(self):
|
||||
soup = self.index_to_soup(self.INDEX)
|
||||
cover_item = soup.find('img',attrs={'class':'fotodespliegue'})
|
||||
if cover_item:
|
||||
self.cover_url = self.PREFIX + cover_item['src']
|
||||
articles = []
|
||||
checker = []
|
||||
for feed_link in soup.findAll('a', attrs={'class':['t0_portada','t2_portada','bajada']}):
|
||||
url = self.PREFIX + feed_link['href']
|
||||
title = self.tag_to_string(feed_link)
|
||||
date = strftime("%a, %d %b %Y %H:%M:%S +0000",time.gmtime())
|
||||
if url not in checker:
|
||||
checker.append(url)
|
||||
articles.append({
|
||||
'title' :title
|
||||
,'date' :date
|
||||
,'url' :url
|
||||
,'description':u''
|
||||
})
|
||||
return [(self.title, articles)]
|
||||
52
recipes/daily_mirror.recipe
Normal file
52
recipes/daily_mirror.recipe
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
title = u'The Daily Mirror'
|
||||
description = 'News as provide by The Daily Mirror -UK'
|
||||
|
||||
__author__ = 'Dave Asbury'
|
||||
language = 'en_GB'
|
||||
|
||||
cover_url = 'http://yookeo.com/screens/m/i/mirror.co.uk.jpg'
|
||||
|
||||
masthead_url = 'http://www.nmauk.co.uk/nma/images/daily_mirror.gif'
|
||||
|
||||
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
remove_empty_feeds = True
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='h1'),
|
||||
dict(attrs={'class':['article-attr']}),
|
||||
dict(name='div', attrs={'class' : [ 'article-body', 'crosshead']})
|
||||
|
||||
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class' : ['caption', 'article-resize']}),
|
||||
dict( attrs={'class':'append-html'})
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
feeds = [
|
||||
|
||||
(u'News', u'http://www.mirror.co.uk/news/rss.xml')
|
||||
,(u'Tech News', u'http://www.mirror.co.uk/news/technology/rss.xml')
|
||||
,(u'Weird World','http://www.mirror.co.uk/news/weird-world/rss.xml')
|
||||
,(u'Film Gossip','http://www.mirror.co.uk/celebs/film/rss.xml')
|
||||
,(u'Music News','http://www.mirror.co.uk/celebs/music/rss.xml')
|
||||
,(u'Celebs and Tv Gossip','http://www.mirror.co.uk/celebs/tv/rss.xml')
|
||||
,(u'Sport','http://www.mirror.co.uk/sport/rss.xml')
|
||||
,(u'Life Style','http://www.mirror.co.uk/life-style/rss.xml')
|
||||
,(u'Advice','http://www.mirror.co.uk/advice/rss.xml')
|
||||
,(u'Travel','http://www.mirror.co.uk/advice/travel/rss.xml')
|
||||
|
||||
# example of commented out feed not needed ,(u'Travel','http://www.mirror.co.uk/advice/travel/rss.xml')
|
||||
]
|
||||
|
||||
BIN
recipes/icons/ambito_financiero.png
Normal file
BIN
recipes/icons/ambito_financiero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 508 B |
BIN
recipes/icons/observatorul_cultural.png
Normal file
BIN
recipes/icons/observatorul_cultural.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
recipes/icons/stiintasitehnica.png
Normal file
BIN
recipes/icons/stiintasitehnica.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 703 B |
42
recipes/nme.recipe
Normal file
42
recipes/nme.recipe
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
title = u'New Musical Express Magazine'
|
||||
__author__ = "scissors"
|
||||
language = 'en'
|
||||
remove_empty_feeds = True
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
cover_url = 'http://tawanda3000.files.wordpress.com/2011/02/nme-logo.jpg'
|
||||
|
||||
remove_tags = [
|
||||
dict( attrs={'class':'clear_icons'}),
|
||||
dict( attrs={'class':'share_links'}),
|
||||
dict( attrs={'id':'right_panel'}),
|
||||
dict( attrs={'class':'today box'})
|
||||
|
||||
]
|
||||
|
||||
keep_only_tags = [
|
||||
|
||||
dict(name='h1'),
|
||||
#dict(name='h3'),
|
||||
dict(attrs={'class' : 'BText'}),
|
||||
dict(attrs={'class' : 'Bmore'}),
|
||||
dict(attrs={'class' : 'bPosts'}),
|
||||
dict(attrs={'class' : 'text'}),
|
||||
dict(attrs={'id' : 'article_gallery'}),
|
||||
dict(attrs={'class' : 'article_text'})
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
feeds = [
|
||||
(u'NME News', u'http://feeds2.feedburner.com/nmecom/rss/newsxml'),
|
||||
(u'Reviews', u'http://feeds2.feedburner.com/nme/SdML'),
|
||||
(u'Blogs', u'http://www.nme.com/blog/index.php?blog=140&tempskin=_rss2'),
|
||||
|
||||
]
|
||||
56
recipes/stiintasitehnica.recipe
Normal file
56
recipes/stiintasitehnica.recipe
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = u'2011, Silviu Cotoar\u0103'
|
||||
'''
|
||||
stiintasitehnica.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Stiintasitehnica(BasicNewsRecipe):
|
||||
title = u'\u0218tiin\u021b\u0103 \u015fi Tehnic\u0103'
|
||||
__author__ = u'Silviu Cotoar\u0103'
|
||||
description = u'\u0218tiin\u021b\u0103 \u015fi Tehnic\u0103'
|
||||
publisher = u'\u0218tiin\u021b\u0103 \u015fi Tehnic\u0103'
|
||||
oldest_article = 50
|
||||
language = 'ro'
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
category = u'Ziare,Reviste,Stiinta,Tehnica'
|
||||
encoding = 'utf-8'
|
||||
cover_url = 'http://www.stiintasitehnica.com/images/logo.jpg'
|
||||
|
||||
conversion_options = {
|
||||
'comments' : description
|
||||
,'tags' : category
|
||||
,'language' : language
|
||||
,'publisher' : publisher
|
||||
}
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'id':'mainColumn2'})
|
||||
]
|
||||
|
||||
remove_tags = [
|
||||
dict(name='span', attrs={'class':['redEar']})
|
||||
, dict(name='table', attrs={'class':['connect_widget_interactive_area']})
|
||||
, dict(name='div', attrs={'class':['panel-overlay']})
|
||||
, dict(name='div', attrs={'id':['pointer']})
|
||||
, dict(name='img', attrs={'class':['nav-next', 'nav-prev']})
|
||||
, dict(name='table', attrs={'class':['connect_widget_interactive_area']})
|
||||
, dict(name='hr', attrs={'class':['dotted']})
|
||||
]
|
||||
|
||||
remove_tags_after = [
|
||||
dict(name='hr', attrs={'class':['dotted']})
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'Feeds', u'http://www.stiintasitehnica.com/rss/stiri.xml')
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
return self.adeify_images(soup)
|
||||
|
|
@ -32,16 +32,11 @@
|
|||
<xsl:value-of select="fb:description/fb:title-info/fb:book-title"/>
|
||||
</title>
|
||||
<style type="text/css">
|
||||
a { color : #0002CC }
|
||||
|
||||
a:hover { color : #BF0000 }
|
||||
|
||||
body { background-color : #FEFEFE; color : #000000; font-family : Verdana, Geneva, Arial, Helvetica, sans-serif; text-align : justify }
|
||||
body { text-align : justify }
|
||||
|
||||
h1{ font-size : 160%; font-style : normal; font-weight : bold; text-align : left; border : 1px solid Black; background-color : #E7E7E7; margin-left : 0px; page-break-before : always; }
|
||||
|
||||
h2{ font-size : 130%; font-style : normal; font-weight : bold; text-align : left; background-color : #EEEEEE; border : 1px solid Gray; page-break-before : always; }
|
||||
|
||||
h3{ font-size : 110%; font-style : normal; font-weight : bold; text-align : left; background-color : #F1F1F1; border : 1px solid Silver;}
|
||||
|
||||
h4{ font-size : 100%; font-style : normal; font-weight : bold; text-align : left; border : 1px solid Gray; background-color : #F4F4F4;}
|
||||
|
|
@ -56,13 +51,11 @@
|
|||
|
||||
hr { color : Black }
|
||||
|
||||
div {font-family : "Times New Roman", Times, serif; text-align : justify}
|
||||
|
||||
ul {margin-left: 0}
|
||||
|
||||
.epigraph{width:50%; margin-left : 35%;}
|
||||
|
||||
div.paragraph { text-align: justify; text-indent: 2em; }
|
||||
div.paragraph { text-indent: 2em; }
|
||||
</style>
|
||||
<link rel="stylesheet" type="text/css" href="inline-styles.css" />
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -179,6 +179,24 @@ def ignore_lib(root, items):
|
|||
shutil.copytree(self.j(comext, 'shell'), self.j(sp_dir, 'win32com', 'shell'))
|
||||
shutil.rmtree(comext)
|
||||
|
||||
# Fix PyCrypto, removing the bootstrap .py modules that load the .pyd
|
||||
# modules, since they do not work when in a zip file
|
||||
for crypto_dir in glob.glob(self.j(sp_dir, 'pycrypto-*', 'Crypto')):
|
||||
for dirpath, dirnames, filenames in os.walk(crypto_dir):
|
||||
for f in filenames:
|
||||
name, ext = os.path.splitext(f)
|
||||
if ext == '.pyd':
|
||||
with open(self.j(dirpath, name+'.py')) as f:
|
||||
raw = f.read().strip()
|
||||
if (not raw.startswith('def __bootstrap__') or not
|
||||
raw.endswith('__bootstrap__()')):
|
||||
raise Exception('The PyCrypto file %r has non'
|
||||
' bootstrap code'%self.j(dirpath, f))
|
||||
for ext in ('.py', '.pyc', '.pyo'):
|
||||
x = self.j(dirpath, name+ext)
|
||||
if os.path.exists(x):
|
||||
os.remove(x)
|
||||
|
||||
for pat in (r'PyQt4\uic\port_v3', ):
|
||||
x = glob.glob(self.j(self.lib_dir, 'site-packages', pat))[0]
|
||||
shutil.rmtree(x)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 8, 4)
|
||||
numeric_version = (0, 8, 5)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
|
|
|
|||
|
|
@ -355,11 +355,17 @@ def remove_plugin(plugin_or_name):
|
|||
name = getattr(plugin_or_name, 'name', plugin_or_name)
|
||||
plugins = config['plugins']
|
||||
removed = False
|
||||
if name in plugins.keys():
|
||||
if name in plugins:
|
||||
removed = True
|
||||
zfp = plugins[name]
|
||||
if os.path.exists(zfp):
|
||||
os.remove(zfp)
|
||||
try:
|
||||
zfp = os.path.join(plugin_dir, name+'.zip')
|
||||
if os.path.exists(zfp):
|
||||
os.remove(zfp)
|
||||
zfp = plugins[name]
|
||||
if os.path.exists(zfp):
|
||||
os.remove(zfp)
|
||||
except:
|
||||
pass
|
||||
plugins.pop(name)
|
||||
config['plugins'] = plugins
|
||||
initialize_plugins()
|
||||
|
|
@ -495,8 +501,15 @@ def initialize_plugins():
|
|||
builtin_names]
|
||||
for p in conflicts:
|
||||
remove_plugin(p)
|
||||
for zfp in list(config['plugins'].itervalues()) + builtin_plugins:
|
||||
external_plugins = config['plugins']
|
||||
for zfp in list(external_plugins) + builtin_plugins:
|
||||
try:
|
||||
if not isinstance(zfp, type):
|
||||
# We have a plugin name
|
||||
pname = zfp
|
||||
zfp = os.path.join(plugin_dir, zfp+'.zip')
|
||||
if not os.path.exists(zfp):
|
||||
zfp = external_plugins[pname]
|
||||
try:
|
||||
plugin = load_plugin(zfp) if not isinstance(zfp, type) else zfp
|
||||
except PluginNotFound:
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class KOBO(USBMS):
|
|||
name = 'Kobo Reader Device Interface'
|
||||
gui_name = 'Kobo Reader'
|
||||
description = _('Communicate with the Kobo Reader')
|
||||
author = 'Timothy Legge and Kovid Goyal'
|
||||
author = 'Timothy Legge'
|
||||
version = (1, 0, 9)
|
||||
|
||||
dbversion = 0
|
||||
|
|
@ -37,8 +37,8 @@ class KOBO(USBMS):
|
|||
CAN_SET_METADATA = ['collections']
|
||||
|
||||
VENDOR_ID = [0x2237]
|
||||
PRODUCT_ID = [0x4161]
|
||||
BCD = [0x0110, 0x0323]
|
||||
PRODUCT_ID = [0x4161, 0x4163]
|
||||
BCD = [0x0110, 0x0323, 0x0326]
|
||||
|
||||
VENDOR_NAME = ['KOBO_INC', 'KOBO']
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['.KOBOEREADER', 'EREADER']
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ def upload_cover(self, path, filename, metadata, filepath):
|
|||
with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile:
|
||||
coverfile.write(coverdata)
|
||||
|
||||
|
||||
def sanitize_path_components(self, components):
|
||||
return [x.replace('#', '_') for x in components]
|
||||
|
||||
|
|
@ -110,6 +109,11 @@ def create_upload_path(self, path, mdata, fname, create_dirs=True):
|
|||
def upload_cover(self, path, filename, metadata, filepath):
|
||||
pass
|
||||
|
||||
def get_carda_ebook_dir(self, for_upload=False):
|
||||
if for_upload:
|
||||
return 'My Files/Books'
|
||||
return ''
|
||||
|
||||
class NOOK_TSR(NOOK):
|
||||
gui_name = _('Nook Simple')
|
||||
description = _('Communicate with the Nook TSR eBook reader.')
|
||||
|
|
@ -117,9 +121,15 @@ class NOOK_TSR(NOOK):
|
|||
PRODUCT_ID = [0x003]
|
||||
BCD = [0x216]
|
||||
|
||||
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'My Files/Books'
|
||||
EBOOK_DIR_MAIN = 'My Files/Books'
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'EBOOK_DISK'
|
||||
|
||||
def upload_cover(self, path, filename, metadata, filepath):
|
||||
pass
|
||||
|
||||
def get_carda_ebook_dir(self, for_upload=False):
|
||||
if for_upload:
|
||||
return 'My Files/Books'
|
||||
return ''
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -837,6 +837,9 @@ def post_yank_cleanup(self):
|
|||
def get_main_ebook_dir(self, for_upload=False):
|
||||
return self.EBOOK_DIR_MAIN
|
||||
|
||||
def get_carda_ebook_dir(self, for_upload=False):
|
||||
return self.EBOOK_DIR_CARD_A
|
||||
|
||||
def _sanity_check(self, on_card, files):
|
||||
if on_card == 'carda' and not self._card_a_prefix:
|
||||
raise ValueError(_('The reader has no storage card in this slot.'))
|
||||
|
|
@ -847,7 +850,7 @@ def _sanity_check(self, on_card, files):
|
|||
|
||||
if on_card == 'carda':
|
||||
path = os.path.join(self._card_a_prefix,
|
||||
*(self.EBOOK_DIR_CARD_A.split('/')))
|
||||
*(self.get_carda_ebook_dir(for_upload=True).split('/')))
|
||||
elif on_card == 'cardb':
|
||||
path = os.path.join(self._card_b_prefix,
|
||||
*(self.EBOOK_DIR_CARD_B.split('/')))
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ def books(self, oncard=None, end_session=True):
|
|||
self._card_b_prefix if oncard == 'cardb' \
|
||||
else self._main_prefix
|
||||
|
||||
ebook_dirs = self.EBOOK_DIR_CARD_A if oncard == 'carda' else \
|
||||
ebook_dirs = self.get_carda_ebook_dir() if oncard == 'carda' else \
|
||||
self.EBOOK_DIR_CARD_B if oncard == 'cardb' else \
|
||||
self.get_main_ebook_dir()
|
||||
|
||||
|
|
|
|||
|
|
@ -439,7 +439,8 @@ def edit_device_collections(self, view, oncard=None):
|
|||
view.reset()
|
||||
|
||||
# Apply bulk metadata changes {{{
|
||||
def apply_metadata_changes(self, id_map, title=None, msg='', callback=None):
|
||||
def apply_metadata_changes(self, id_map, title=None, msg='', callback=None,
|
||||
merge_tags=True):
|
||||
'''
|
||||
Apply the metadata changes in id_map to the database synchronously
|
||||
id_map must be a mapping of ids to Metadata objects. Set any fields you
|
||||
|
|
@ -466,9 +467,9 @@ def apply_metadata_changes(self, id_map, title=None, msg='', callback=None):
|
|||
cancelable=False)
|
||||
self.apply_pd.setModal(True)
|
||||
self.apply_pd.show()
|
||||
self._am_merge_tags = True
|
||||
self.do_one_apply()
|
||||
|
||||
|
||||
def do_one_apply(self):
|
||||
if self.apply_current_idx >= len(self.apply_id_map):
|
||||
return self.finalize_apply()
|
||||
|
|
@ -484,6 +485,12 @@ def do_one_apply(self):
|
|||
mi.identifiers = idents
|
||||
if mi.is_null('series'):
|
||||
mi.series_index = None
|
||||
if self._am_merge_tags:
|
||||
old_tags = db.tags(i, index_is_id=True)
|
||||
if old_tags:
|
||||
tags = [x.strip() for x in old_tags.split(',')] + (
|
||||
mi.tags if mi.tags else [])
|
||||
mi.tags = list(set(tags))
|
||||
db.set_metadata(i, mi, commit=False, set_title=set_title,
|
||||
set_authors=set_authors, notify=False)
|
||||
self.applied_ids.append(i)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
<string> KB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>100</number>
|
||||
<number>25</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000</number>
|
||||
|
|
|
|||
|
|
@ -1074,16 +1074,16 @@ def paste_isbn(self):
|
|||
|
||||
# }}}
|
||||
|
||||
class ISBNDialog(QDialog) :
|
||||
class ISBNDialog(QDialog) : # {{{
|
||||
|
||||
def __init__(self, parent, txt):
|
||||
QDialog.__init__(self, parent)
|
||||
l = QGridLayout()
|
||||
self.setLayout(l)
|
||||
self.setWindowTitle(_('Empty or Invalid ISBN'))
|
||||
w = QLabel(_('Enter a different ISBN value if desired'))
|
||||
self.setWindowTitle(_('Invalid ISBN'))
|
||||
w = QLabel(_('Enter an ISBN'))
|
||||
l.addWidget(w, 0, 0, 1, 2)
|
||||
w = QLabel(_('Value:'))
|
||||
w = QLabel(_('ISBN:'))
|
||||
l.addWidget(w, 1, 0, 1, 1)
|
||||
self.line_edit = w = QLineEdit();
|
||||
w.setText(txt)
|
||||
|
|
@ -1095,6 +1095,17 @@ def __init__(self, parent, txt):
|
|||
w.accepted.connect(self.accept)
|
||||
w.rejected.connect(self.reject)
|
||||
self.checkText(self.text())
|
||||
sz = self.sizeHint()
|
||||
sz.setWidth(sz.width()+50)
|
||||
self.resize(sz)
|
||||
|
||||
def accept(self):
|
||||
isbn = unicode(self.line_edit.text())
|
||||
if not check_isbn(isbn):
|
||||
return error_dialog(self, _('Invalid ISBN'),
|
||||
_('The ISBN you entered is not valid. Try again.'),
|
||||
show=True)
|
||||
QDialog.accept(self)
|
||||
|
||||
def checkText(self, txt):
|
||||
isbn = unicode(txt)
|
||||
|
|
@ -1113,6 +1124,8 @@ def checkText(self, txt):
|
|||
def text(self):
|
||||
return unicode(self.line_edit.text())
|
||||
|
||||
# }}}
|
||||
|
||||
class PublisherEdit(MultiCompleteComboBox): # {{{
|
||||
LABEL = _('&Publisher:')
|
||||
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ def cover_from_format(self, *args):
|
|||
show=True)
|
||||
return
|
||||
|
||||
def update_from_mi(self, mi, update_sorts=True):
|
||||
def update_from_mi(self, mi, update_sorts=True, merge_tags=True):
|
||||
if not mi.is_null('title'):
|
||||
self.title.current_val = mi.title
|
||||
if update_sorts:
|
||||
|
|
@ -334,7 +334,11 @@ def update_from_mi(self, mi, update_sorts=True):
|
|||
if not mi.is_null('publisher'):
|
||||
self.publisher.current_val = mi.publisher
|
||||
if not mi.is_null('tags'):
|
||||
self.tags.current_val = mi.tags
|
||||
old_tags = self.tags.current_val
|
||||
tags = mi.tags if mi.tags else []
|
||||
if old_tags and merge_tags:
|
||||
tags += old_tags
|
||||
self.tags.current_val = tags
|
||||
if not mi.is_null('identifiers'):
|
||||
current = self.identifiers.current_val
|
||||
current.update(mi.identifiers)
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@
|
|||
from base64 import b64encode
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import QSize, QSizePolicy, QUrl, SIGNAL, Qt, QTimer, \
|
||||
QPainter, QPalette, QBrush, QFontDatabase, QDialog, \
|
||||
QColor, QPoint, QImage, QRegion, QVariant, QIcon, \
|
||||
QFont, pyqtSignature, QAction, QByteArray, QMenu
|
||||
from PyQt4.Qt import (QSize, QSizePolicy, QUrl, SIGNAL, Qt, QTimer,
|
||||
QPainter, QPalette, QBrush, QFontDatabase, QDialog,
|
||||
QColor, QPoint, QImage, QRegion, QVariant, QIcon,
|
||||
QFont, pyqtSignature, QAction, QByteArray, QMenu,
|
||||
pyqtSignal)
|
||||
from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
|
||||
|
||||
from calibre.utils.config import Config, StringConfig
|
||||
|
|
@ -496,6 +497,7 @@ def __init__(self, html):
|
|||
|
||||
class DocumentView(QWebView): # {{{
|
||||
|
||||
magnification_changed = pyqtSignal(object)
|
||||
DISABLED_BRUSH = QBrush(Qt.lightGray, Qt.Dense5Pattern)
|
||||
|
||||
def __init__(self, *args):
|
||||
|
|
@ -908,15 +910,22 @@ def scroll_to(self, pos, notify=True):
|
|||
if notify and self.manager is not None and self.document.ypos != old_pos:
|
||||
self.manager.scrolled(self.scroll_fraction)
|
||||
|
||||
@dynamic_property
|
||||
def multiplier(self):
|
||||
return self.document.mainFrame().textSizeMultiplier()
|
||||
def fget(self):
|
||||
return self.document.mainFrame().textSizeMultiplier()
|
||||
def fset(self, val):
|
||||
self.document.mainFrame().setTextSizeMultiplier(val)
|
||||
self.magnification_changed.emit(val)
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
def magnify_fonts(self):
|
||||
self.document.mainFrame().setTextSizeMultiplier(self.multiplier()+0.2)
|
||||
self.multiplier += 0.2
|
||||
return self.document.scroll_fraction
|
||||
|
||||
def shrink_fonts(self):
|
||||
self.document.mainFrame().setTextSizeMultiplier(max(self.multiplier()-0.2, 0))
|
||||
if self.multiplier >= 0.2:
|
||||
self.multiplier -= 0.2
|
||||
return self.document.scroll_fraction
|
||||
|
||||
def changeEvent(self, event):
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||
def __init__(self, pathtoebook=None, debug_javascript=False):
|
||||
MainWindow.__init__(self, None)
|
||||
self.setupUi(self)
|
||||
self.view.magnification_changed.connect(self.magnification_changed)
|
||||
self.show_toc_on_open = False
|
||||
self.current_book_has_toc = False
|
||||
self.base_window_title = unicode(self.windowTitle())
|
||||
|
|
@ -345,6 +346,7 @@ def save_state(self):
|
|||
if self.toc.isVisible():
|
||||
vprefs.set('viewer_splitter_state',
|
||||
bytearray(self.splitter.saveState()))
|
||||
vprefs['multiplier'] = self.view.multiplier
|
||||
|
||||
def restore_state(self):
|
||||
state = vprefs.get('viewer_toolbar_state', None)
|
||||
|
|
@ -354,6 +356,9 @@ def restore_state(self):
|
|||
self.restoreState(state, self.STATE_VERSION)
|
||||
except:
|
||||
pass
|
||||
mult = vprefs.get('multiplier', None)
|
||||
if mult:
|
||||
self.view.multiplier = mult
|
||||
|
||||
|
||||
def lookup(self, word):
|
||||
|
|
@ -476,16 +481,22 @@ def open_recent(self, action):
|
|||
|
||||
def font_size_larger(self, checked):
|
||||
frac = self.view.magnify_fonts()
|
||||
self.action_font_size_larger.setEnabled(self.view.multiplier() < 3)
|
||||
self.action_font_size_smaller.setEnabled(self.view.multiplier() > 0.2)
|
||||
self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
|
||||
self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
|
||||
self.set_page_number(frac)
|
||||
|
||||
def font_size_smaller(self, checked):
|
||||
frac = self.view.shrink_fonts()
|
||||
self.action_font_size_larger.setEnabled(self.view.multiplier() < 3)
|
||||
self.action_font_size_smaller.setEnabled(self.view.multiplier() > 0.2)
|
||||
self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
|
||||
self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
|
||||
self.set_page_number(frac)
|
||||
|
||||
def magnification_changed(self, val):
|
||||
tt = _('Make font size %s\nCurrent magnification: %.1f')
|
||||
self.action_font_size_larger.setToolTip(
|
||||
tt %(_('larger'), val))
|
||||
self.action_font_size_smaller.setToolTip(
|
||||
tt %(_('smaller'), val))
|
||||
|
||||
def find(self, text, repeat=False, backwards=False):
|
||||
if not text:
|
||||
|
|
|
|||
|
|
@ -319,7 +319,7 @@ but it requires that the Kindle be rebooted *every time* it is disconnected from
|
|||
changes to the collections to be recognized. As such, it is unlikely that
|
||||
any |app| developers will ever feel motivated enough to support it. There is however, a |app| plugin
|
||||
that allows you to create collections on your Kindle from the |app| metadata. It is available
|
||||
`here <http://www.mobileread.com/forums/showthread.php?t=118635>`_.
|
||||
`from here <http://www.mobileread.com/forums/showthread.php?t=118635>`_.
|
||||
|
||||
Library Management
|
||||
------------------
|
||||
|
|
@ -570,7 +570,7 @@ For many reasons:
|
|||
* *There is no need to update every week*. If you are happy with how |app| works turn off the update notification and be on your merry way. Check back to see if you want to update once a year or so.
|
||||
* Pre downloading the updates for all users in the background would mean require about 80TB of bandwidth *every week*. That costs thousands of dollars a month. And |app| is currently growing at 300,000 new users every month.
|
||||
* If I implement a dialog that downloads the update and launches it, instead of going to the website as it does now, that would save the most ardent |app| updater, *at most five clicks a week*. There are far higher priority things to do in |app| development.
|
||||
* If you really, really hate downloading |app| every week but still want to be upto the latest, I encourage you to run from source, which makes updating trivial. Instructions are :ref:`here <develop>`.
|
||||
* If you really, really hate downloading |app| every week but still want to be up to the latest, I encourage you to run from source, which makes updating trivial. Instructions are :ref:`available here <develop>`.
|
||||
|
||||
How is |app| licensed?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -99,12 +99,12 @@ def test():
|
|||
test_lxml()
|
||||
test_fontconfig()
|
||||
test_sqlite()
|
||||
if iswindows:
|
||||
test_winutil()
|
||||
test_win32()
|
||||
test_qt()
|
||||
test_imaging()
|
||||
test_unrar()
|
||||
if iswindows:
|
||||
test_win32()
|
||||
test_winutil()
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
|
|
|
|||
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
17024
src/calibre/translations/az.po
Normal file
17024
src/calibre/translations/az.po
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -202,6 +202,10 @@ def main(args=sys.argv):
|
|||
|
||||
|
||||
if len(args) > 1:
|
||||
if len(args) < 4:
|
||||
print ('You must specify the from address, to address and body text'
|
||||
' on the command line')
|
||||
return 1
|
||||
msg = compose_mail(args[1], args[2], args[3], subject=opts.subject,
|
||||
attachment=opts.attachment)
|
||||
from_, to = args[1:3]
|
||||
|
|
|
|||
Loading…
Reference in a new issue