mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
merge from trunk
This commit is contained in:
commit
5bb54dc7c4
83
recipes/der_spiegel.recipe
Normal file
83
recipes/der_spiegel.recipe
Normal file
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Nikolas Mangold <nmangold at gmail.com>'
|
||||
'''
|
||||
spiegel.de
|
||||
'''
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre import strftime
|
||||
from calibre import re
|
||||
|
||||
class DerSpiegel(BasicNewsRecipe):
|
||||
title = 'Der Spiegel'
|
||||
__author__ = 'Nikolas Mangold'
|
||||
description = 'Der Spiegel, Printed Edition. Access to paid content.'
|
||||
publisher = 'SPIEGEL-VERLAG RUDOLF AUGSTEIN GMBH & CO. KG'
|
||||
category = 'news, politics, Germany'
|
||||
no_stylesheets = True
|
||||
encoding = 'cp1252'
|
||||
needs_subscription = True
|
||||
remove_empty_feeds = True
|
||||
delay = 1
|
||||
PREFIX = 'http://m.spiegel.de'
|
||||
INDEX = PREFIX + '/spiegel/print/epaper/index-heftaktuell.html'
|
||||
use_embedded_content = False
|
||||
masthead_url = 'http://upload.wikimedia.org/wikipedia/en/thumb/1/17/Der_Spiegel_logo.svg/200px-Der_Spiegel_logo.svg.png'
|
||||
language = 'de'
|
||||
publication_type = 'magazine'
|
||||
extra_css = ' body{font-family: Arial,Helvetica,sans-serif} '
|
||||
timefmt = '[%W/%Y]'
|
||||
empty_articles = ['Titelbild']
|
||||
preprocess_regexps = [
|
||||
(re.compile(r'<p>◆</p>', re.DOTALL|re.IGNORECASE), lambda match: '<hr>'),
|
||||
]
|
||||
|
||||
def get_browser(self):
|
||||
def has_login_name(form):
|
||||
try:
|
||||
form.find_control(name="f.loginName")
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
if self.username is not None and self.password is not None:
|
||||
br.open(self.PREFIX + '/meinspiegel/login.html')
|
||||
br.select_form(predicate=has_login_name)
|
||||
br['f.loginName' ] = self.username
|
||||
br['f.password'] = self.password
|
||||
br.submit()
|
||||
return br
|
||||
|
||||
remove_tags_before = dict(attrs={'class':'spArticleContent'})
|
||||
remove_tags_after = dict(attrs={'class':'spArticleCredit'})
|
||||
|
||||
def parse_index(self):
|
||||
soup = self.index_to_soup(self.INDEX)
|
||||
|
||||
cover = soup.find('img', width=248)
|
||||
if cover is not None:
|
||||
self.cover_url = cover['src']
|
||||
|
||||
index = soup.find('dl')
|
||||
|
||||
feeds = []
|
||||
for section in index.findAll('dt'):
|
||||
section_title = self.tag_to_string(section).strip()
|
||||
self.log('Found section ', section_title)
|
||||
|
||||
articles = []
|
||||
for article in section.findNextSiblings(['dd','dt']):
|
||||
if article.name == 'dt':
|
||||
break
|
||||
link = article.find('a')
|
||||
title = self.tag_to_string(link).strip()
|
||||
if title in self.empty_articles:
|
||||
continue
|
||||
self.log('Found article ', title)
|
||||
url = self.PREFIX + link['href']
|
||||
articles.append({'title' : title, 'date' : strftime(self.timefmt), 'url' : url})
|
||||
feeds.append((section_title,articles))
|
||||
return feeds;
|
@ -1,4 +1,3 @@
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Handelsblatt(BasicNewsRecipe):
|
||||
@ -7,14 +6,11 @@ class Handelsblatt(BasicNewsRecipe):
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
cover_url = 'http://www.handelsblatt.com/images/logo/logo_handelsblatt.com.png'
|
||||
# cover_url = 'http://www.handelsblatt.com/images/logo/logo_handelsblatt.com.png'
|
||||
language = 'de'
|
||||
# keep_only_tags = []
|
||||
keep_only_tags = (dict(name = 'div', attrs = {'class': ['hcf-detail-abstract hcf-teaser ajaxify','hcf-detail','hcf-author-wrapper']}))
|
||||
# keep_only_tags.append(dict(name = 'div', attrs = {'id': 'fullText'}))
|
||||
remove_tags = [dict(name='img', attrs = {'src': 'http://www.handelsblatt.com/images/icon/loading.gif'})
|
||||
,dict(name='ul' , attrs={'class':['hcf-detail-tools']})
|
||||
]
|
||||
|
||||
remove_tags_before = dict(attrs={'class':'hcf-overline'})
|
||||
remove_tags_after = dict(attrs={'class':'hcf-footer'})
|
||||
|
||||
feeds = [
|
||||
(u'Handelsblatt Exklusiv',u'http://www.handelsblatt.com/rss/exklusiv'),
|
||||
@ -28,17 +24,16 @@ class Handelsblatt(BasicNewsRecipe):
|
||||
(u'Handelsblatt Magazin',u'http://www.handelsblatt.com/rss/magazin/'),
|
||||
(u'Handelsblatt Weblogs',u'http://www.handelsblatt.com/rss/blogs')
|
||||
]
|
||||
|
||||
extra_css = '''
|
||||
.hcf-headline {font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:x-large;}
|
||||
.hcf-overline {font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:x-large;}
|
||||
.hcf-exclusive {font-family:Arial,Helvetica,sans-serif; font-style:italic;font-weight:bold; margin-right:5pt;}
|
||||
p{font-family:Arial,Helvetica,sans-serif;}
|
||||
.hcf-location-mark{font-weight:bold; margin-right:5pt;}
|
||||
.MsoNormal{font-family:Helvetica,Arial,sans-serif;}
|
||||
.hcf-author-wrapper{font-style:italic;}
|
||||
.hcf-article-date{font-size:x-small;}
|
||||
.hcf-caption {font-style:italic;font-size:small;}
|
||||
img {align:left;}
|
||||
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
|
||||
|
||||
def print_version(self, url):
|
||||
url = url.split('/')
|
||||
url[-1] = 'v_detail_tab_print,'+url[-1]
|
||||
url = '/'.join(url)
|
||||
return url
|
||||
|
@ -1,71 +1,65 @@
|
||||
#!/usr/bin/env python
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'Lorenzo Vigentini & Edwin van Maastrigt'
|
||||
__copyright__ = '2009, Lorenzo Vigentini <l.vigentini at gmail.com> and Edwin van Maastrigt <evanmaastrigt at gmail.com>'
|
||||
__description__ = 'Financial news daily paper - v1.02 (30, January 2010)'
|
||||
__author__ = 'Marco Saraceno'
|
||||
__copyright__ = '2010, Marco Saraceno <marcosaraceno at gmail.com>'
|
||||
description = 'Italian daily newspaper - v 1.1 (Mar14,2011)'
|
||||
|
||||
'''
|
||||
http://www.ilsole24ore.com/
|
||||
http://www.ilsole24ore.com
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class IlSole24Ore(BasicNewsRecipe):
|
||||
__author__ = 'Marco Saraceno'
|
||||
description = 'Italian financial daily newspaper'
|
||||
|
||||
class ilsole24Ore(BasicNewsRecipe):
|
||||
author = 'Lorenzo Vigentini & Edwin van Maastrigt'
|
||||
description = 'Financial news daily paper'
|
||||
|
||||
cover_url = 'http://www.ilsole24ore.com/img2007/print_header.gif'
|
||||
|
||||
title = u'il Sole 24 Ore New'
|
||||
publisher = 'italiaNews'
|
||||
category = 'News, finance, economy, politics'
|
||||
cover_url = 'http://www.shopping24.ilsole24ore.com/ProductRelated/rds/img/logo_sole.gif'
|
||||
title = u'Il Sole 24 Ore'
|
||||
publisher = 'Gruppo editoriale GRUPPO 24ORE'
|
||||
category = 'News, politics, culture, economy, financial, Italian'
|
||||
|
||||
language = 'it'
|
||||
timefmt = '[%a, %d %b, %Y]'
|
||||
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 50
|
||||
max_articles_per_feed = 100
|
||||
use_embedded_content = False
|
||||
extra_css = '.headline {font-size: x-large;} \n .fact { padding-top: 10pt }'
|
||||
|
||||
|
||||
remove_tags = [
|
||||
dict(name='div', attrs={'class':['header','titolo']}),
|
||||
dict(name='table', attrs={'class':['footer1024','footerdown']}),
|
||||
]
|
||||
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
def get_article_url(self, article):
|
||||
return article.get('id', article.get('guid', None))
|
||||
|
||||
def print_version(self, url):
|
||||
link, sep, params = url.rpartition('?')
|
||||
link = article.get('link', None)
|
||||
if link is None:
|
||||
return link.replace('_1.php', '_php')
|
||||
return link.replace('.shtml', '_PRN.shtml')
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='div', attrs={'class':'txt'})
|
||||
]
|
||||
# remove_tags = [dict(name='br')]
|
||||
return article
|
||||
if link.split('/')[-1]=="story01.htm":
|
||||
link=link.split('/')[-2]
|
||||
a=['0B','0C','0D','0E','0F','0G','0N' ,'0L0S','0A']
|
||||
b=['.' ,'/' ,'?' ,'-' ,'=' ,'&' ,'.com','www.','0']
|
||||
for i in range(0,len(a)):
|
||||
link=link.replace(a[i],b[i])
|
||||
link="http://"+link
|
||||
return link
|
||||
|
||||
feeds = [
|
||||
(u'Prima pagina', u'http://www.ilsole24ore.com/rss/primapagina.xml'),
|
||||
(u'Norme e tributi', u'http://www.ilsole24ore.com/rss/norme-tributi.xml'),
|
||||
(u'Finanza e mercati', u'http://www.ilsole24ore.com/rss/finanza-mercati.xml'),
|
||||
(u'Economia e lavoro', u'http://www.ilsole24ore.com/rss/economia-lavoro.xml'),
|
||||
(u'Italia', u'http://www.ilsole24ore.com/rss/italia.xml'),
|
||||
(u'Mondo', u'http://www.ilsole24ore.com/rss/mondo.xml'),
|
||||
(u'Tecnologia e business', u'http://www.ilsole24ore.com/rss/tecnologia-business.xml'),
|
||||
(u'Cultura e tempo libero', u'http://www.ilsole24ore.com/rss/tempolibero-cultura.xml'),
|
||||
(u'Sport', u'http://www.ilsole24ore.com/rss/sport.xml'),
|
||||
(u'Professionisti 24', u'http://www.ilsole24ore.com/rss/prof_home.xml'),
|
||||
(u'Ambiente e Sicurezza',u'http://www.ilsole24ore.com/rss/prof_as.xml')
|
||||
(u'Notizie Italia', u'http://www.ilsole24ore.com/rss/notizie/italia.xml'),
|
||||
(u'Notizie Europa', u'http://www.ilsole24ore.com/rss/notizie/europa.xml'),
|
||||
(u'Notizie USA', u'http://www.ilsole24ore.com/rss/notizie/usa.xml'),
|
||||
(u'Notizie Americhe', u'http://www.ilsole24ore.com/rss/notizie/americhe.xml'),
|
||||
(u'Notizie Medio Oriente e Africa', u'http://www.ilsole24ore.com/rss/notizie/medio-oriente-e-africa.xml'),
|
||||
(u'Notizie Asia e Oceania', u'http://www.ilsole24ore.com/rss/notizie/asia-e-oceania.xml'),
|
||||
(u'Commenti', u'http://www.ilsole24ore.com/rss/commenti-e-idee.xml'),
|
||||
(u'Norme e tributi', u'http://www.ilsole24ore.com/rss/norme-e-tributi.xml'),
|
||||
(u'Finanza', u'http://www.ilsole24ore.com/rss/finanza-e-mercati.xml'),
|
||||
(u'Economia', u'http://www.ilsole24ore.com/rss/economia.xml'),
|
||||
(u'Tecnologia', u'http://www.ilsole24ore.com/rss/tecnologie.xml'),
|
||||
(u'Cultura', u'http://www.ilsole24ore.com/rss/cultura.xml'),
|
||||
]
|
||||
|
||||
extra_css = '''
|
||||
html, body, table, tr, td, h1, h2, h3, h4, h5, h6, p, a, span, br, img {margin:0;padding:0;border:0;font-size:12px;font-family:"Georgia","Times New Roman";}
|
||||
.linkHighlight {color:#0292c6;}
|
||||
.txt {border-bottom:1px solid #7c7c7c;padding-bottom:20px};text-align:justify;font-family:"serif"}
|
||||
.txt p {line-height:18px;}
|
||||
.txt span {line-height:22px;}
|
||||
.title h3 {color:#7b7b7b;}
|
||||
.title h4 {color:#08526e;font-size:26px;font-family:"Times New Roman";font-weight:normal;}
|
||||
'''
|
||||
def print_version(self, url):
|
||||
return url.replace('.shtml', '_PRN.shtml')
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import string
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Newsweek(BasicNewsRecipe):
|
||||
@ -11,7 +10,6 @@ class Newsweek(BasicNewsRecipe):
|
||||
no_stylesheets = True
|
||||
|
||||
BASE_URL = 'http://www.newsweek.com'
|
||||
INDEX = BASE_URL+'/topics.html'
|
||||
|
||||
keep_only_tags = dict(name='article', attrs={'class':'article-text'})
|
||||
remove_tags = [dict(attrs={'data-dartad':True})]
|
||||
@ -23,11 +21,14 @@ class Newsweek(BasicNewsRecipe):
|
||||
return soup
|
||||
|
||||
def newsweek_sections(self):
|
||||
soup = self.index_to_soup(self.INDEX)
|
||||
for a in soup.findAll('a', title='Primary tag', href=True):
|
||||
yield (string.capitalize(self.tag_to_string(a)),
|
||||
self.BASE_URL+a['href'])
|
||||
|
||||
return [
|
||||
('Nation', 'http://www.newsweek.com/tag/nation.html'),
|
||||
('Society', 'http://www.newsweek.com/tag/society.html'),
|
||||
('Culture', 'http://www.newsweek.com/tag/culture.html'),
|
||||
('World', 'http://www.newsweek.com/tag/world.html'),
|
||||
('Politics', 'http://www.newsweek.com/tag/politics.html'),
|
||||
('Business', 'http://www.newsweek.com/tag/business.html'),
|
||||
]
|
||||
|
||||
def newsweek_parse_section_page(self, soup):
|
||||
for article in soup.findAll('article', about=True,
|
||||
|
26
recipes/the_journal.recipe
Normal file
26
recipes/the_journal.recipe
Normal file
@ -0,0 +1,26 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011 Phil Burns'
|
||||
'''
|
||||
TheJournal.ie
|
||||
'''
|
||||
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class TheJournal(BasicNewsRecipe):
|
||||
|
||||
__author_ = 'Phil Burns'
|
||||
title = u'TheJournal.ie'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
encoding = 'utf8'
|
||||
language = 'en_IE'
|
||||
timefmt = ' (%A, %B %d, %Y)'
|
||||
|
||||
no_stylesheets = True
|
||||
remove_tags = [dict(name='div', attrs={'class':'footer'}),
|
||||
dict(name=['script', 'noscript'])]
|
||||
|
||||
extra_css = 'p, div { margin: 0pt; border: 0pt; text-indent: 0.5em }'
|
||||
|
||||
feeds = [
|
||||
(u'Latest News', u'http://www.thejournal.ie/feed/')]
|
@ -5,6 +5,7 @@
|
||||
"strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n res = ''\n for i in range(0, len(args)):\n res += args[i]\n return res\n",
|
||||
"substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n",
|
||||
"ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n if val:\n return val\n else:\n return value_if_empty\n",
|
||||
"booksize": "def evaluate(self, formatter, kwargs, mi, locals):\n if mi.book_size is not None:\n try:\n return str(mi.book_size)\n except:\n pass\n return ''\n",
|
||||
"select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n if not val:\n return ''\n vals = [v.strip() for v in val.split(',')]\n for v in vals:\n if v.startswith(key+':'):\n return v[len(key)+1:]\n return ''\n",
|
||||
"field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n",
|
||||
"subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x - y)\n",
|
||||
@ -25,9 +26,9 @@
|
||||
"capitalize": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return capitalize(val)\n",
|
||||
"count": "def evaluate(self, formatter, kwargs, mi, locals, val, sep):\n return unicode(len(val.split(sep)))\n",
|
||||
"lowercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.lower()\n",
|
||||
"assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n",
|
||||
"switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
|
||||
"strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n v = strcmp(x, y)\n if v < 0:\n return lt\n if v == 0:\n return eq\n return gt\n",
|
||||
"switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
|
||||
"assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n",
|
||||
"raw_field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return unicode(getattr(mi, name, None))\n",
|
||||
"cmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n x = float(x if x else 0)\n y = float(y if y else 0)\n if x < y:\n return lt\n if x == y:\n return eq\n return gt\n"
|
||||
}
|
@ -24,8 +24,10 @@ def initialize_constants():
|
||||
global __version__, __appname__, modules, functions, basenames, scripts
|
||||
|
||||
src = open('src/calibre/constants.py', 'rb').read()
|
||||
__version__ = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
||||
__appname__ = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', src).group(1)
|
||||
nv = re.search(r'numeric_version\s+=\s+\((\d+), (\d+), (\d+)\)', src)
|
||||
__version__ = '%s.%s.%s'%(nv.group(1), nv.group(2), nv.group(3))
|
||||
__appname__ = re.search(r'__appname__\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]',
|
||||
src).group(2)
|
||||
epsrc = re.compile(r'entry_points = (\{.*?\})', re.DOTALL).\
|
||||
search(open('src/calibre/linux.py', 'rb').read()).group(1)
|
||||
entry_points = eval(epsrc, {'__appname__': __appname__})
|
||||
|
@ -13,7 +13,8 @@ from setup import Command, modules, functions, basenames, __version__, \
|
||||
from setup.build_environment import msvc, MT, RC
|
||||
from setup.installer.windows.wix import WixMixIn
|
||||
|
||||
QT_DIR = 'Q:\\Qt\\4.7.1'
|
||||
OPENSSL_DIR = r'Q:\openssl'
|
||||
QT_DIR = 'Q:\\Qt\\4.7.2'
|
||||
QT_DLLS = ['Core', 'Gui', 'Network', 'Svg', 'WebKit', 'Xml', 'XmlPatterns']
|
||||
LIBUSB_DIR = 'C:\\libusb'
|
||||
LIBUNRAR = 'C:\\Program Files\\UnrarDLL\\unrar.dll'
|
||||
@ -108,6 +109,8 @@ class Win32Freeze(Command, WixMixIn):
|
||||
self.dll_dir = self.j(self.base, 'DLLs')
|
||||
shutil.copytree(r'C:\Python%s\DLLs'%self.py_ver, self.dll_dir,
|
||||
ignore=shutil.ignore_patterns('msvc*.dll', 'Microsoft.*'))
|
||||
for x in glob.glob(self.j(OPENSSL_DIR, 'bin', '*.dll')):
|
||||
shutil.copy2(x, self.dll_dir)
|
||||
for x in QT_DLLS:
|
||||
x += '4.dll'
|
||||
if not x.startswith('phonon'): x = 'Qt'+x
|
||||
|
@ -53,12 +53,25 @@ SQLite
|
||||
|
||||
Put sqlite3*.h from the sqlite windows amlgamation in ~/sw/include
|
||||
|
||||
OpenSSL
|
||||
--------
|
||||
|
||||
First install ActiveState Perl if you dont already have perl in windows
|
||||
Download and untar the openssl tarball, follow the instructions in INSTALL.W32 (use no-asm)
|
||||
to install use prefix q:\openssl
|
||||
|
||||
perl Configure VC-WIN32 no-asm enable-static-engine --prefix=Q:/openssl
|
||||
ms\do_ms.bat
|
||||
nmake -f ms\ntdll.mak
|
||||
nmake -f ms\ntdll.mak test
|
||||
nmake -f ms\ntdll.mak install
|
||||
|
||||
Qt
|
||||
--------
|
||||
|
||||
Extract Qt sourcecode to C:\Qt\4.x.x. Run configure and make::
|
||||
|
||||
configure -opensource -release -qt-zlib -qt-gif -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs && nmake
|
||||
configure -opensource -release -qt-zlib -qt-gif -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -release -platform win32-msvc2008 -no-qt3support -webkit -xmlpatterns -no-phonon -no-style-plastique -no-style-cleanlooks -no-style-motif -no-style-cde -no-declarative -no-scripttools -no-audio-backend -no-multimedia -no-dbus -no-openvg -no-opengl -no-qt3support -confirm-license -nomake examples -nomake demos -nomake docs -openssl -I Q:\openssl\include -L Q:\openssl\lib && nmake
|
||||
|
||||
SIP
|
||||
-----
|
||||
|
@ -3,11 +3,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import uuid, sys, os, re, logging, time, random, \
|
||||
__builtin__, warnings, multiprocessing
|
||||
from contextlib import closing
|
||||
from urllib import getproxies
|
||||
from urllib2 import unquote as urllib2_unquote
|
||||
import sys, os, re, time, random, __builtin__, warnings
|
||||
__builtin__.__dict__['dynamic_property'] = lambda(func): func(None)
|
||||
from htmlentitydefs import name2codepoint
|
||||
from math import floor
|
||||
@ -16,25 +12,51 @@ from functools import partial
|
||||
warnings.simplefilter('ignore', DeprecationWarning)
|
||||
|
||||
|
||||
from calibre.constants import iswindows, isosx, islinux, isfreebsd, isfrozen, \
|
||||
terminal_controller, preferred_encoding, \
|
||||
__appname__, __version__, __author__, \
|
||||
win32event, win32api, winerror, fcntl, \
|
||||
filesystem_encoding, plugins, config_dir
|
||||
from calibre.startup import winutil, winutilerror, guess_type
|
||||
from calibre.constants import (iswindows, isosx, islinux, isfreebsd, isfrozen,
|
||||
preferred_encoding, __appname__, __version__, __author__,
|
||||
win32event, win32api, winerror, fcntl,
|
||||
filesystem_encoding, plugins, config_dir)
|
||||
from calibre.startup import winutil, winutilerror
|
||||
|
||||
if islinux and not getattr(sys, 'frozen', False):
|
||||
# Imported before PyQt4 to workaround PyQt4 util-linux conflict on gentoo
|
||||
if False and islinux and not getattr(sys, 'frozen', False):
|
||||
# Imported before PyQt4 to workaround PyQt4 util-linux conflict discovered on gentoo
|
||||
# See http://bugs.gentoo.org/show_bug.cgi?id=317557
|
||||
# Importing uuid is slow so get rid of this at some point, maybe in a few
|
||||
# years when even Debian has caught up
|
||||
# Also remember to remove it from site.py in the binary builds
|
||||
import uuid
|
||||
uuid.uuid4()
|
||||
|
||||
if False:
|
||||
# Prevent pyflakes from complaining
|
||||
winutil, winutilerror, __appname__, islinux, __version__
|
||||
fcntl, win32event, isfrozen, __author__, terminal_controller
|
||||
winerror, win32api, isfreebsd, guess_type
|
||||
fcntl, win32event, isfrozen, __author__
|
||||
winerror, win32api, isfreebsd
|
||||
|
||||
import cssutils
|
||||
cssutils.log.setLevel(logging.WARN)
|
||||
_mt_inited = False
|
||||
def _init_mimetypes():
|
||||
global _mt_inited
|
||||
import mimetypes
|
||||
mimetypes.init([P('mime.types')])
|
||||
_mt_inited = True
|
||||
|
||||
def guess_type(*args, **kwargs):
|
||||
import mimetypes
|
||||
if not _mt_inited:
|
||||
_init_mimetypes()
|
||||
return mimetypes.guess_type(*args, **kwargs)
|
||||
|
||||
def guess_all_extensions(*args, **kwargs):
|
||||
import mimetypes
|
||||
if not _mt_inited:
|
||||
_init_mimetypes()
|
||||
return mimetypes.guess_all_extensions(*args, **kwargs)
|
||||
|
||||
def get_types_map():
|
||||
import mimetypes
|
||||
if not _mt_inited:
|
||||
_init_mimetypes()
|
||||
return mimetypes.types_map
|
||||
|
||||
def to_unicode(raw, encoding='utf-8', errors='strict'):
|
||||
if isinstance(raw, unicode):
|
||||
@ -182,6 +204,7 @@ class CommandLineError(Exception):
|
||||
pass
|
||||
|
||||
def setup_cli_handlers(logger, level):
|
||||
import logging
|
||||
if os.environ.get('CALIBRE_WORKER', None) is not None and logger.handlers:
|
||||
return
|
||||
logger.setLevel(level)
|
||||
@ -243,6 +266,7 @@ def extract(path, dir):
|
||||
extractor(path, dir)
|
||||
|
||||
def get_proxies(debug=True):
|
||||
from urllib import getproxies
|
||||
proxies = getproxies()
|
||||
for key, proxy in list(proxies.items()):
|
||||
if not proxy or '..' in proxy:
|
||||
@ -386,6 +410,7 @@ class StreamReadWrapper(object):
|
||||
|
||||
def detect_ncpus():
|
||||
"""Detects the number of effective CPUs in the system"""
|
||||
import multiprocessing
|
||||
ans = -1
|
||||
try:
|
||||
ans = multiprocessing.cpu_count()
|
||||
@ -550,6 +575,9 @@ def get_download_filename(url, cookie_file=None):
|
||||
'''
|
||||
Get a local filename for a URL using the content disposition header
|
||||
'''
|
||||
from contextlib import closing
|
||||
from urllib2 import unquote as urllib2_unquote
|
||||
|
||||
filename = ''
|
||||
|
||||
br = browser()
|
||||
@ -679,4 +707,3 @@ main()
|
||||
ipshell()
|
||||
sys.argv = old_argv
|
||||
|
||||
|
||||
|
@ -1,28 +1,32 @@
|
||||
from future_builtins import map
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.7.56'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
import re, importlib
|
||||
_ver = __version__.split('.')
|
||||
_ver = [int(re.search(r'(\d+)', x).group(1)) for x in _ver]
|
||||
numeric_version = tuple(_ver)
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 7, 56)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
'''
|
||||
Various run time constants.
|
||||
'''
|
||||
|
||||
import sys, locale, codecs, os
|
||||
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
|
||||
|
||||
terminal_controller = TerminalController(sys.stdout)
|
||||
|
||||
iswindows = 'win32' in sys.platform.lower() or 'win64' in sys.platform.lower()
|
||||
isosx = 'darwin' in sys.platform.lower()
|
||||
_plat = sys.platform.lower()
|
||||
iswindows = 'win32' in _plat or 'win64' in _plat
|
||||
isosx = 'darwin' in _plat
|
||||
isnewosx = isosx and getattr(sys, 'new_app_bundle', False)
|
||||
isfreebsd = 'freebsd' in sys.platform.lower()
|
||||
isfreebsd = 'freebsd' in _plat
|
||||
islinux = not(iswindows or isosx or isfreebsd)
|
||||
isfrozen = hasattr(sys, 'frozen')
|
||||
isunix = isosx or islinux
|
||||
@ -48,15 +52,12 @@ def debug():
|
||||
DEBUG = True
|
||||
|
||||
# plugins {{{
|
||||
plugins = None
|
||||
if plugins is None:
|
||||
# Load plugins
|
||||
def load_plugins():
|
||||
plugins = {}
|
||||
plugin_path = sys.extensions_location
|
||||
sys.path.insert(0, plugin_path)
|
||||
|
||||
for plugin in [
|
||||
class Plugins(collections.Mapping):
|
||||
|
||||
def __init__(self):
|
||||
self._plugins = {}
|
||||
plugins = [
|
||||
'pictureflow',
|
||||
'lzx',
|
||||
'msdes',
|
||||
@ -70,19 +71,44 @@ if plugins is None:
|
||||
'chm_extra',
|
||||
'icu',
|
||||
'speedup',
|
||||
] + \
|
||||
(['winutil'] if iswindows else []) + \
|
||||
(['usbobserver'] if isosx else []):
|
||||
]
|
||||
if iswindows:
|
||||
plugins.append('winutil')
|
||||
if isosx:
|
||||
plugins.append('usbobserver')
|
||||
self.plugins = frozenset(plugins)
|
||||
|
||||
def load_plugin(self, name):
|
||||
if name in self._plugins:
|
||||
return
|
||||
sys.path.insert(0, sys.extensions_location)
|
||||
try:
|
||||
p, err = importlib.import_module(plugin), ''
|
||||
p, err = importlib.import_module(name), ''
|
||||
except Exception as err:
|
||||
p = None
|
||||
err = str(err)
|
||||
plugins[plugin] = (p, err)
|
||||
sys.path.remove(plugin_path)
|
||||
return plugins
|
||||
self._plugins[name] = (p, err)
|
||||
sys.path.remove(sys.extensions_location)
|
||||
|
||||
plugins = load_plugins()
|
||||
def __iter__(self):
|
||||
return iter(self.plugins)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.plugins)
|
||||
|
||||
def __contains__(self, name):
|
||||
return name in self.plugins
|
||||
|
||||
def __getitem__(self, name):
|
||||
if name not in self.plugins:
|
||||
raise KeyError('No plugin named %r'%name)
|
||||
self.load_plugin(name)
|
||||
return self._plugins[name]
|
||||
|
||||
|
||||
plugins = None
|
||||
if plugins is None:
|
||||
plugins = Plugins()
|
||||
# }}}
|
||||
|
||||
# config_dir {{{
|
||||
|
@ -9,7 +9,6 @@ from calibre.customize import FileTypePlugin, MetadataReaderPlugin, \
|
||||
from calibre.constants import numeric_version
|
||||
from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata
|
||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||
from calibre.ebooks.oeb.base import OEB_IMAGES
|
||||
from calibre.utils.config import test_eight_code
|
||||
|
||||
# To archive plugins {{{
|
||||
@ -98,6 +97,8 @@ class TXT2TXTZ(FileTypePlugin):
|
||||
on_import = True
|
||||
|
||||
def _get_image_references(self, txt, base_dir):
|
||||
from calibre.ebooks.oeb.base import OEB_IMAGES
|
||||
|
||||
images = []
|
||||
|
||||
# Textile
|
||||
|
@ -106,7 +106,7 @@ def migrate(old, new):
|
||||
from calibre.library.database import LibraryDatabase
|
||||
from calibre.library.database2 import LibraryDatabase2
|
||||
from calibre.utils.terminfo import ProgressBar
|
||||
from calibre import terminal_controller
|
||||
from calibre.constants import terminal_controller
|
||||
class Dummy(ProgressBar):
|
||||
def setLabelText(self, x): pass
|
||||
def setAutoReset(self, y): pass
|
||||
@ -119,7 +119,7 @@ def migrate(old, new):
|
||||
|
||||
db = LibraryDatabase(old)
|
||||
db2 = LibraryDatabase2(new)
|
||||
db2.migrate_old(db, Dummy(terminal_controller, 'Migrating database...'))
|
||||
db2.migrate_old(db, Dummy(terminal_controller(), 'Migrating database...'))
|
||||
prefs['library_path'] = os.path.abspath(new)
|
||||
print 'Database migrated to', os.path.abspath(new)
|
||||
|
||||
|
@ -8,7 +8,7 @@ manner.
|
||||
import sys, os, re
|
||||
from threading import RLock
|
||||
|
||||
from calibre import iswindows, isosx, plugins, islinux
|
||||
from calibre.constants import iswindows, isosx, plugins, islinux
|
||||
|
||||
osx_scanner = win_scanner = linux_scanner = None
|
||||
|
||||
|
@ -5,8 +5,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>,' \
|
||||
' and Alex Bramley <a.bramley at gmail.com>.'
|
||||
|
||||
import os, re
|
||||
from mimetypes import guess_type as guess_mimetype
|
||||
|
||||
from calibre import guess_type as guess_mimetype
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, NavigableString
|
||||
from calibre.constants import iswindows, filesystem_encoding
|
||||
from calibre.utils.chm.chm import CHMFile
|
||||
|
@ -14,7 +14,8 @@ from calibre.ebooks.conversion.preprocess import HTMLPreProcessor
|
||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||
from calibre.utils.date import parse_date
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
from calibre import extract, walk, isbytestring, filesystem_encoding
|
||||
from calibre import (extract, walk, isbytestring, filesystem_encoding,
|
||||
get_types_map)
|
||||
from calibre.constants import __version__
|
||||
|
||||
DEBUG_README=u'''
|
||||
@ -875,6 +876,9 @@ OptionRecommendation(name='sr3_replace',
|
||||
if self.opts.verbose:
|
||||
self.log.filter_level = self.log.DEBUG
|
||||
self.flush()
|
||||
import cssutils, logging
|
||||
cssutils.log.setLevel(logging.WARN)
|
||||
get_types_map() # Ensure the mimetypes module is intialized
|
||||
|
||||
if self.opts.debug_pipeline is not None:
|
||||
self.opts.verbose = max(self.opts.verbose, 4)
|
||||
|
@ -10,7 +10,6 @@ Transform OEB content into FB2 markup
|
||||
|
||||
from base64 import b64encode
|
||||
from datetime import datetime
|
||||
from mimetypes import types_map
|
||||
import re
|
||||
import uuid
|
||||
|
||||
@ -18,9 +17,6 @@ from lxml import etree
|
||||
|
||||
from calibre import prepare_string_for_xml
|
||||
from calibre.constants import __appname__, __version__
|
||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES, OPF
|
||||
from calibre.utils.magick import Image
|
||||
|
||||
class FB2MLizer(object):
|
||||
@ -100,6 +96,7 @@ class FB2MLizer(object):
|
||||
return text
|
||||
|
||||
def fb2_header(self):
|
||||
from calibre.ebooks.oeb.base import OPF
|
||||
metadata = {}
|
||||
metadata['title'] = self.oeb_book.metadata.title[0].value
|
||||
metadata['appname'] = __appname__
|
||||
@ -180,6 +177,8 @@ class FB2MLizer(object):
|
||||
return u'</FictionBook>'
|
||||
|
||||
def get_cover(self):
|
||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||
|
||||
cover_href = None
|
||||
|
||||
# Get the raster cover if it's available.
|
||||
@ -213,6 +212,8 @@ class FB2MLizer(object):
|
||||
return u''
|
||||
|
||||
def get_text(self):
|
||||
from calibre.ebooks.oeb.base import XHTML
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
text = ['<body>']
|
||||
|
||||
# Create main section if there are no others to create
|
||||
@ -248,6 +249,8 @@ class FB2MLizer(object):
|
||||
'''
|
||||
This function uses the self.image_hrefs dictionary mapping. It is populated by the dump_text function.
|
||||
'''
|
||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||
|
||||
images = []
|
||||
for item in self.oeb_book.manifest:
|
||||
# Don't write the image if it's not referenced in the document's text.
|
||||
@ -255,7 +258,7 @@ class FB2MLizer(object):
|
||||
continue
|
||||
if item.media_type in OEB_RASTER_IMAGES:
|
||||
try:
|
||||
if not item.media_type == types_map['.jpeg'] or not item.media_type == types_map['.jpg']:
|
||||
if item.media_type != 'image/jpeg':
|
||||
im = Image()
|
||||
im.load(item.data)
|
||||
im.set_compression_quality(70)
|
||||
@ -344,6 +347,8 @@ class FB2MLizer(object):
|
||||
|
||||
@return: List of string representing the XHTML converted to FB2 markup.
|
||||
'''
|
||||
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
|
||||
|
||||
# Ensure what we are converting is not a string and that the fist tag is part of the XHTML namespace.
|
||||
if not isinstance(elem_tree.tag, basestring) or namespace(elem_tree.tag) != XHTML_NS:
|
||||
return []
|
||||
|
@ -315,7 +315,8 @@ class HTMLInput(InputFormatPlugin):
|
||||
from calibre import guess_type
|
||||
from calibre.ebooks.oeb.transforms.metadata import \
|
||||
meta_info_to_oeb_metadata
|
||||
import cssutils
|
||||
import cssutils, logging
|
||||
cssutils.log.setLevel(logging.WARN)
|
||||
self.OEB_STYLES = OEB_STYLES
|
||||
oeb = create_oebbook(log, None, opts, self,
|
||||
encoding=opts.input_encoding, populate=False)
|
||||
|
@ -4,7 +4,6 @@ __copyright__ = '2010, Fabian Grassl <fg@jusmeum.de>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from calibre.ebooks.oeb.base import namespace, barename, DC11_NS
|
||||
|
||||
class EasyMeta(object):
|
||||
|
||||
@ -12,6 +11,7 @@ class EasyMeta(object):
|
||||
self.meta = meta
|
||||
|
||||
def __iter__(self):
|
||||
from calibre.ebooks.oeb.base import namespace, barename, DC11_NS
|
||||
meta = self.meta
|
||||
for item_name in meta.items:
|
||||
for item in meta[item_name]:
|
||||
|
@ -12,7 +12,6 @@ from os.path import dirname, abspath, relpath, exists, basename
|
||||
from lxml import etree
|
||||
from templite import Templite
|
||||
|
||||
from calibre.ebooks.oeb.base import element
|
||||
from calibre.customize.conversion import OutputFormatPlugin, OptionRecommendation
|
||||
from calibre import CurrentDir
|
||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||
@ -51,6 +50,7 @@ class HTMLOutput(OutputFormatPlugin):
|
||||
'''
|
||||
Generate table of contents
|
||||
'''
|
||||
from calibre.ebooks.oeb.base import element
|
||||
with CurrentDir(output_dir):
|
||||
def build_node(current_node, parent=None):
|
||||
if parent is None:
|
||||
|
@ -12,7 +12,6 @@ from lxml import etree
|
||||
|
||||
from calibre.customize.conversion import OutputFormatPlugin, \
|
||||
OptionRecommendation
|
||||
from calibre.ebooks.oeb.base import OEB_IMAGES, SVG_MIME
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
|
||||
@ -42,6 +41,8 @@ class HTMLZOutput(OutputFormatPlugin):
|
||||
])
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
from calibre.ebooks.oeb.base import OEB_IMAGES, SVG_MIME
|
||||
|
||||
# HTML
|
||||
if opts.htmlz_css_type == 'inline':
|
||||
from calibre.ebooks.htmlz.oeb2html import OEB2HTMLInlineCSSizer
|
||||
|
@ -6,11 +6,11 @@ __docformat__ = 'restructuredtext en'
|
||||
"""
|
||||
Provides abstraction for metadata reading.writing from a variety of ebook formats.
|
||||
"""
|
||||
import os, mimetypes, sys, re
|
||||
import os, sys, re
|
||||
from urllib import unquote, quote
|
||||
from urlparse import urlparse
|
||||
|
||||
from calibre import relpath
|
||||
from calibre import relpath, guess_type
|
||||
|
||||
from calibre.utils.config import tweaks
|
||||
|
||||
@ -118,7 +118,7 @@ class Resource(object):
|
||||
self.path = None
|
||||
self.fragment = ''
|
||||
try:
|
||||
self.mime_type = mimetypes.guess_type(href_or_path)[0]
|
||||
self.mime_type = guess_type(href_or_path)[0]
|
||||
except:
|
||||
self.mime_type = None
|
||||
if self.mime_type is None:
|
||||
|
@ -592,7 +592,7 @@ class Metadata(object):
|
||||
elif datatype == 'bool':
|
||||
res = _('Yes') if res else _('No')
|
||||
elif datatype == 'rating':
|
||||
res = res/2
|
||||
res = res/2.0
|
||||
return (name, unicode(res), orig_res, cmeta)
|
||||
|
||||
# convert top-level ids into their value
|
||||
@ -625,6 +625,8 @@ class Metadata(object):
|
||||
res = res + ' [%s]'%self.format_series_index()
|
||||
elif datatype == 'datetime':
|
||||
res = format_date(res, fmeta['display'].get('date_format','dd MMM yyyy'))
|
||||
elif datatype == 'rating':
|
||||
res = res/2.0
|
||||
return (name, unicode(res), orig_res, fmeta)
|
||||
|
||||
return (None, None, None, None)
|
||||
|
@ -5,11 +5,12 @@ __copyright__ = '2008, Anatoly Shipitsin <norguhtar at gmail.com>'
|
||||
|
||||
'''Read meta information from fb2 files'''
|
||||
|
||||
import mimetypes, os
|
||||
import os
|
||||
from base64 import b64decode
|
||||
from lxml import etree
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre import guess_all_extensions
|
||||
|
||||
XLINK_NS = 'http://www.w3.org/1999/xlink'
|
||||
def XLINK(name):
|
||||
@ -71,7 +72,7 @@ def get_metadata(stream):
|
||||
binary = XPath('//fb2:binary[@id="%s"]'%id)(root)
|
||||
if binary:
|
||||
mt = binary[0].get('content-type', 'image/jpeg')
|
||||
exts = mimetypes.guess_all_extensions(mt)
|
||||
exts = guess_all_extensions(mt)
|
||||
if not exts:
|
||||
exts = ['.jpg']
|
||||
cdata = (exts[0][1:], b64decode(tostring(binary[0])))
|
||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
||||
lxml based OPF parser.
|
||||
'''
|
||||
|
||||
import re, sys, unittest, functools, os, mimetypes, uuid, glob, cStringIO, json
|
||||
import re, sys, unittest, functools, os, uuid, glob, cStringIO, json
|
||||
from urllib import unquote
|
||||
from urlparse import urlparse
|
||||
|
||||
@ -20,7 +20,7 @@ from calibre.ebooks.metadata import string_to_authors, MetaInformation, check_is
|
||||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
from calibre.utils.date import parse_date, isoformat
|
||||
from calibre.utils.localization import get_lang
|
||||
from calibre import prints
|
||||
from calibre import prints, guess_type
|
||||
from calibre.utils.cleantext import clean_ascii_chars
|
||||
|
||||
class Resource(object): # {{{
|
||||
@ -42,7 +42,7 @@ class Resource(object): # {{{
|
||||
self.path = None
|
||||
self.fragment = ''
|
||||
try:
|
||||
self.mime_type = mimetypes.guess_type(href_or_path)[0]
|
||||
self.mime_type = guess_type(href_or_path)[0]
|
||||
except:
|
||||
self.mime_type = None
|
||||
if self.mime_type is None:
|
||||
@ -1000,7 +1000,7 @@ class OPF(object): # {{{
|
||||
for t in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
|
||||
for item in self.guide:
|
||||
if item.type.lower() == t:
|
||||
self.create_manifest_item(item.href(), mimetypes.guess_type(path)[0])
|
||||
self.create_manifest_item(item.href(), guess_type(path)[0])
|
||||
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
|
@ -382,7 +382,11 @@ def identify(log, abort, # {{{
|
||||
log(plog)
|
||||
log('\n'+'*'*80)
|
||||
|
||||
dummy = Metadata(_('Unknown'))
|
||||
for i, result in enumerate(presults):
|
||||
for f in plugin.prefs['ignore_fields']:
|
||||
if ':' not in f:
|
||||
setattr(result, f, getattr(dummy, f))
|
||||
result.relevance_in_source = i
|
||||
result.has_cached_cover_url = (plugin.cached_cover_url_is_reliable
|
||||
and plugin.get_cached_cover_url(result.identifiers) is not
|
||||
|
@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
|
||||
'''
|
||||
Fetch metadata using Overdrive Content Reserve
|
||||
'''
|
||||
import re, random, mechanize, copy
|
||||
import re, random, mechanize, copy, json
|
||||
from threading import RLock
|
||||
from Queue import Queue, Empty
|
||||
|
||||
@ -40,10 +40,6 @@ class OverDrive(Source):
|
||||
supports_gzip_transfer_encoding = False
|
||||
cached_cover_url_is_reliable = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
Source.__init__(self, *args, **kwargs)
|
||||
self.prefs.defaults['ignore_fields'] =['tags', 'pubdate', 'comments', 'identifier:isbn', 'language']
|
||||
|
||||
def identify(self, log, result_queue, abort, title=None, authors=None, # {{{
|
||||
identifiers={}, timeout=30):
|
||||
ovrdrv_id = identifiers.get('overdrive', None)
|
||||
@ -231,7 +227,7 @@ class OverDrive(Source):
|
||||
def sort_ovrdrv_results(self, raw, title=None, title_tokens=None, author=None, author_tokens=None, ovrdrv_id=None):
|
||||
close_matches = []
|
||||
raw = re.sub('.*?\[\[(?P<content>.*?)\]\].*', '[[\g<content>]]', raw)
|
||||
results = eval(raw)
|
||||
results = json.loads(raw)
|
||||
#print results
|
||||
# The search results are either from a keyword search or a multi-format list from a single ID,
|
||||
# sort through the results for closest match/format
|
||||
@ -393,9 +389,14 @@ class OverDrive(Source):
|
||||
|
||||
if pub_date:
|
||||
from calibre.utils.date import parse_date
|
||||
try:
|
||||
mi.pubdate = parse_date(pub_date[0].strip())
|
||||
except:
|
||||
pass
|
||||
if lang:
|
||||
mi.language = lang[0].strip()
|
||||
lang = lang[0].strip().lower()
|
||||
mi.language = {'english':'en', 'french':'fr', 'german':'de',
|
||||
'spanish':'es'}.get(lang, None)
|
||||
|
||||
if ebook_isbn:
|
||||
#print "ebook isbn is "+str(ebook_isbn[0])
|
||||
|
@ -8,23 +8,18 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, re, uuid, logging
|
||||
from mimetypes import types_map
|
||||
from collections import defaultdict
|
||||
from itertools import count
|
||||
from urlparse import urldefrag, urlparse, urlunparse, urljoin
|
||||
from urllib import unquote as urlunquote
|
||||
|
||||
from lxml import etree, html
|
||||
from cssutils import CSSParser, parseString, parseStyle, replaceUrls
|
||||
from cssutils.css import CSSRule
|
||||
|
||||
import calibre
|
||||
from calibre.constants import filesystem_encoding
|
||||
from calibre.constants import filesystem_encoding, __version__
|
||||
from calibre.translations.dynamic import translate
|
||||
from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
|
||||
from calibre.ebooks.conversion.preprocess import CSSPreProcessor
|
||||
from calibre import isbytestring, as_unicode
|
||||
from calibre import isbytestring, as_unicode, get_types_map
|
||||
|
||||
RECOVER_PARSER = etree.XMLParser(recover=True, no_network=True)
|
||||
|
||||
@ -179,6 +174,9 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False):
|
||||
If the ``link_repl_func`` returns None, the attribute or
|
||||
tag text will be removed completely.
|
||||
'''
|
||||
from cssutils import parseString, parseStyle, replaceUrls, log
|
||||
log.setLevel(logging.WARN)
|
||||
|
||||
if resolve_base_href:
|
||||
resolve_base_href(root)
|
||||
for el, attrib, link, pos in iterlinks(root, find_links_in_css=False):
|
||||
@ -248,7 +246,7 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False):
|
||||
el.attrib['style'] = repl
|
||||
|
||||
|
||||
|
||||
types_map = get_types_map()
|
||||
EPUB_MIME = types_map['.epub']
|
||||
XHTML_MIME = types_map['.xhtml']
|
||||
CSS_MIME = types_map['.css']
|
||||
@ -1075,7 +1073,9 @@ class Manifest(object):
|
||||
|
||||
|
||||
def _parse_css(self, data):
|
||||
|
||||
from cssutils.css import CSSRule
|
||||
from cssutils import CSSParser, log
|
||||
log.setLevel(logging.WARN)
|
||||
def get_style_rules_from_import(import_rule):
|
||||
ans = []
|
||||
if not import_rule.styleSheet:
|
||||
@ -2011,7 +2011,7 @@ class OEBBook(object):
|
||||
name='dtb:uid', content=unicode(self.uid))
|
||||
etree.SubElement(head, NCX('meta'),
|
||||
name='dtb:depth', content=str(self.toc.depth()))
|
||||
generator = ''.join(['calibre (', calibre.__version__, ')'])
|
||||
generator = ''.join(['calibre (', __version__, ')'])
|
||||
etree.SubElement(head, NCX('meta'),
|
||||
name='dtb:generator', content=generator)
|
||||
etree.SubElement(head, NCX('meta'),
|
||||
|
@ -10,11 +10,9 @@ import sys, os, uuid, copy, re, cStringIO
|
||||
from itertools import izip
|
||||
from urlparse import urldefrag, urlparse
|
||||
from urllib import unquote as urlunquote
|
||||
from mimetypes import guess_type
|
||||
from collections import defaultdict
|
||||
|
||||
from lxml import etree
|
||||
import cssutils
|
||||
|
||||
from calibre.ebooks.oeb.base import OPF1_NS, OPF2_NS, OPF2_NSMAP, DC11_NS, \
|
||||
DC_NSES, OPF, xml2text
|
||||
@ -30,6 +28,7 @@ from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
|
||||
from calibre.utils.localization import get_lang
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.constants import __appname__, __version__
|
||||
from calibre import guess_type
|
||||
|
||||
__all__ = ['OEBReader']
|
||||
|
||||
@ -172,6 +171,7 @@ class OEBReader(object):
|
||||
return bad
|
||||
|
||||
def _manifest_add_missing(self, invalid):
|
||||
import cssutils
|
||||
manifest = self.oeb.manifest
|
||||
known = set(manifest.hrefs)
|
||||
unchecked = set(manifest.values())
|
||||
|
@ -12,17 +12,18 @@ import os, itertools, re, logging, copy, unicodedata
|
||||
from weakref import WeakKeyDictionary
|
||||
from xml.dom import SyntaxErr as CSSSyntaxError
|
||||
import cssutils
|
||||
from cssutils.css import CSSStyleRule, CSSPageRule, CSSStyleDeclaration, \
|
||||
CSSValueList, CSSFontFaceRule, cssproperties
|
||||
from cssutils.css import (CSSStyleRule, CSSPageRule, CSSStyleDeclaration,
|
||||
CSSValueList, CSSFontFaceRule, cssproperties)
|
||||
from cssutils import profile as cssprofiles
|
||||
from lxml import etree
|
||||
from lxml.cssselect import css_to_xpath, ExpressionError, SelectorSyntaxError
|
||||
|
||||
from calibre import force_unicode
|
||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES
|
||||
from calibre.ebooks.oeb.base import XPNSMAP, xpath, urlnormalize
|
||||
from calibre.ebooks.oeb.profile import PROFILES
|
||||
|
||||
cssutils.log.setLevel(logging.WARN)
|
||||
|
||||
_html_css_stylesheet = None
|
||||
|
||||
def html_css_stylesheet():
|
||||
|
@ -9,7 +9,6 @@ import posixpath
|
||||
from urlparse import urldefrag, urlparse
|
||||
|
||||
from lxml import etree
|
||||
import cssutils
|
||||
|
||||
from calibre.ebooks.oeb.base import rewrite_links, urlnormalize
|
||||
|
||||
@ -25,6 +24,7 @@ class RenameFiles(object): # {{{
|
||||
self.renamed_items_map = renamed_items_map
|
||||
|
||||
def __call__(self, oeb, opts):
|
||||
import cssutils
|
||||
self.log = oeb.logger
|
||||
self.opts = opts
|
||||
self.oeb = oeb
|
||||
|
@ -8,8 +8,6 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
|
||||
|
||||
from urlparse import urldefrag
|
||||
|
||||
import cssutils
|
||||
|
||||
from calibre.ebooks.oeb.base import CSS_MIME, OEB_DOCS
|
||||
from calibre.ebooks.oeb.base import urlnormalize, iterlinks
|
||||
|
||||
@ -23,6 +21,7 @@ class ManifestTrimmer(object):
|
||||
return cls()
|
||||
|
||||
def __call__(self, oeb, context):
|
||||
import cssutils
|
||||
oeb.logger.info('Trimming unused files from manifest...')
|
||||
self.opts = context
|
||||
used = set()
|
||||
|
@ -21,7 +21,6 @@ except ImportError:
|
||||
import cStringIO
|
||||
|
||||
from calibre.ebooks.pdb.formatwriter import FormatWriter
|
||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||
from calibre.ebooks.pdb.header import PdbHeaderBuilder
|
||||
from calibre.ebooks.pml.pmlml import PMLMLizer
|
||||
|
||||
@ -135,6 +134,7 @@ class Writer(FormatWriter):
|
||||
62-...: Raw image data in 8 bit PNG format.
|
||||
'''
|
||||
images = []
|
||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||
|
||||
for item in manifest:
|
||||
if item.media_type in OEB_RASTER_IMAGES and item.href in image_hrefs.keys():
|
||||
|
@ -18,7 +18,6 @@ from calibre.customize.conversion import OutputFormatPlugin
|
||||
from calibre.customize.conversion import OptionRecommendation
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.utils.zipfile import ZipFile
|
||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||
from calibre.ebooks.pml.pmlml import PMLMLizer
|
||||
|
||||
class PMLOutput(OutputFormatPlugin):
|
||||
@ -60,6 +59,7 @@ class PMLOutput(OutputFormatPlugin):
|
||||
pmlz.add_dir(tdir)
|
||||
|
||||
def write_images(self, manifest, image_hrefs, out_dir, opts):
|
||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||
for item in manifest:
|
||||
if item.media_type in OEB_RASTER_IMAGES and item.href in image_hrefs.keys():
|
||||
if opts.full_image_depth:
|
||||
|
@ -12,8 +12,6 @@ import re
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
from calibre.ebooks.pdb.ereader import image_name
|
||||
from calibre.ebooks.pml import unipmlcode
|
||||
|
||||
@ -110,6 +108,9 @@ class PMLMLizer(object):
|
||||
return output
|
||||
|
||||
def get_cover_page(self):
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
from calibre.ebooks.oeb.base import XHTML
|
||||
|
||||
output = u''
|
||||
if 'cover' in self.oeb_book.guide:
|
||||
output += '\\m="cover.png"\n'
|
||||
@ -125,6 +126,9 @@ class PMLMLizer(object):
|
||||
return output
|
||||
|
||||
def get_text(self):
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
from calibre.ebooks.oeb.base import XHTML
|
||||
|
||||
text = [u'']
|
||||
for item in self.oeb_book.spine:
|
||||
self.log.debug('Converting %s to PML markup...' % item.href)
|
||||
@ -214,6 +218,8 @@ class PMLMLizer(object):
|
||||
return text
|
||||
|
||||
def dump_text(self, elem, stylizer, page, tag_stack=[]):
|
||||
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
|
||||
|
||||
if not isinstance(elem.tag, basestring) \
|
||||
or namespace(elem.tag) != XHTML_NS:
|
||||
return []
|
||||
|
@ -11,8 +11,6 @@ Transform OEB content into RB compatible markup.
|
||||
import re
|
||||
|
||||
from calibre import prepare_string_for_xml
|
||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
from calibre.ebooks.rb import unique_name
|
||||
|
||||
TAGS = [
|
||||
@ -81,6 +79,8 @@ class RBMLizer(object):
|
||||
return output
|
||||
|
||||
def get_cover_page(self):
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
from calibre.ebooks.oeb.base import XHTML
|
||||
output = u''
|
||||
if 'cover' in self.oeb_book.guide:
|
||||
if self.name_map.get(self.oeb_book.guide['cover'].href, None):
|
||||
@ -109,6 +109,9 @@ class RBMLizer(object):
|
||||
return ''.join(toc)
|
||||
|
||||
def get_text(self):
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
from calibre.ebooks.oeb.base import XHTML
|
||||
|
||||
output = [u'']
|
||||
for item in self.oeb_book.spine:
|
||||
self.log.debug('Converting %s to RocketBook HTML...' % item.href)
|
||||
@ -137,6 +140,8 @@ class RBMLizer(object):
|
||||
return text
|
||||
|
||||
def dump_text(self, elem, stylizer, page, tag_stack=[]):
|
||||
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
|
||||
|
||||
if not isinstance(elem.tag, basestring) \
|
||||
or namespace(elem.tag) != XHTML_NS:
|
||||
return [u'']
|
||||
|
@ -18,7 +18,6 @@ import cStringIO
|
||||
from calibre.ebooks.rb.rbml import RBMLizer
|
||||
from calibre.ebooks.rb import HEADER
|
||||
from calibre.ebooks.rb import unique_name
|
||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||
from calibre.constants import __appname__, __version__
|
||||
|
||||
TEXT_RECORD_SIZE = 4096
|
||||
@ -111,6 +110,7 @@ class RBWriter(object):
|
||||
return (size, pages)
|
||||
|
||||
def _images(self, manifest):
|
||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||
images = []
|
||||
used_names = []
|
||||
|
||||
|
@ -14,9 +14,6 @@ import cStringIO
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace, \
|
||||
OEB_RASTER_IMAGES
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
from calibre.ebooks.metadata import authors_to_string
|
||||
from calibre.utils.filenames import ascii_text
|
||||
from calibre.utils.magick.draw import save_cover_data_to, identify_data
|
||||
@ -100,6 +97,8 @@ class RTFMLizer(object):
|
||||
return self.mlize_spine()
|
||||
|
||||
def mlize_spine(self):
|
||||
from calibre.ebooks.oeb.base import XHTML
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
output = self.header()
|
||||
if 'titlepage' in self.oeb_book.guide:
|
||||
href = self.oeb_book.guide['titlepage'].href
|
||||
@ -154,6 +153,8 @@ class RTFMLizer(object):
|
||||
return ' }'
|
||||
|
||||
def insert_images(self, text):
|
||||
from calibre.ebooks.oeb.base import OEB_RASTER_IMAGES
|
||||
|
||||
for item in self.oeb_book.manifest:
|
||||
if item.media_type in OEB_RASTER_IMAGES:
|
||||
src = os.path.basename(item.href)
|
||||
@ -201,6 +202,8 @@ class RTFMLizer(object):
|
||||
return text
|
||||
|
||||
def dump_text(self, elem, stylizer, tag_stack=[]):
|
||||
from calibre.ebooks.oeb.base import XHTML_NS, namespace, barename
|
||||
|
||||
if not isinstance(elem.tag, basestring) \
|
||||
or namespace(elem.tag) != XHTML_NS:
|
||||
p = elem.getparent()
|
||||
|
@ -7,7 +7,6 @@ __docformat__ = 'restructuredtext en'
|
||||
import os, uuid
|
||||
|
||||
from calibre.customize.conversion import InputFormatPlugin
|
||||
from calibre.ebooks.oeb.base import DirContainer
|
||||
from calibre.ebooks.snb.snbfile import SNBFile
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
@ -30,6 +29,7 @@ class SNBInput(InputFormatPlugin):
|
||||
|
||||
def convert(self, stream, options, file_ext, log,
|
||||
accelerators):
|
||||
from calibre.ebooks.oeb.base import DirContainer
|
||||
log.debug("Parsing SNB file...")
|
||||
snbFile = SNBFile()
|
||||
try:
|
||||
|
@ -5,7 +5,8 @@ __copyright__ = '2010, Li Fanxi <lifanxi@freemindworld.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys, struct, zlib, bz2, os
|
||||
from mimetypes import types_map
|
||||
|
||||
from calibre import guess_type
|
||||
|
||||
class FileStream:
|
||||
def IsBinary(self):
|
||||
@ -180,7 +181,7 @@ class SNBFile:
|
||||
file = open(os.path.join(path, fname), 'wb')
|
||||
file.write(f.fileBody)
|
||||
file.close()
|
||||
fileNames.append((fname, types_map[ext]))
|
||||
fileNames.append((fname, guess_type('a'+ext)[0]))
|
||||
return fileNames
|
||||
|
||||
def Output(self, outputFile):
|
||||
|
@ -13,8 +13,6 @@ import re
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
|
||||
def ProcessFileName(fileName):
|
||||
# Flat the path
|
||||
@ -81,6 +79,8 @@ class SNBMLizer(object):
|
||||
body.append(entity)
|
||||
|
||||
def mlize(self):
|
||||
from calibre.ebooks.oeb.base import XHTML
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
output = [ u'' ]
|
||||
stylizer = Stylizer(self.item.data, self.item.href, self.oeb_book, self.opts, self.opts.output_profile)
|
||||
content = unicode(etree.tostring(self.item.data.find(XHTML('body')), encoding=unicode))
|
||||
@ -208,6 +208,7 @@ class SNBMLizer(object):
|
||||
return text
|
||||
|
||||
def dump_text(self, subitems, elem, stylizer, end='', pre=False, li = ''):
|
||||
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
|
||||
|
||||
if not isinstance(elem.tag, basestring) \
|
||||
or namespace(elem.tag) != XHTML_NS:
|
||||
|
@ -11,7 +11,6 @@ from lxml import etree
|
||||
|
||||
from calibre.customize.conversion import OutputFormatPlugin, \
|
||||
OptionRecommendation
|
||||
from calibre.ebooks.oeb.base import OEB_IMAGES
|
||||
from calibre.ebooks.txt.txtml import TXTMLizer
|
||||
from calibre.ebooks.txt.newlines import TxtNewlines, specified_newlines
|
||||
from calibre.ptempfile import TemporaryDirectory, TemporaryFile
|
||||
@ -109,6 +108,7 @@ class TXTZOutput(TXTOutput):
|
||||
file_type = 'txtz'
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
from calibre.ebooks.oeb.base import OEB_IMAGES
|
||||
with TemporaryDirectory('_txtz_output') as tdir:
|
||||
# TXT
|
||||
with TemporaryFile('index.txt') as tf:
|
||||
|
@ -12,8 +12,6 @@ import re
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, barename, namespace
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
|
||||
BLOCK_TAGS = [
|
||||
'div',
|
||||
@ -64,6 +62,8 @@ class TXTMLizer(object):
|
||||
return self.mlize_spine()
|
||||
|
||||
def mlize_spine(self):
|
||||
from calibre.ebooks.oeb.base import XHTML
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
output = [u'']
|
||||
output.append(self.get_toc())
|
||||
for item in self.oeb_book.spine:
|
||||
@ -185,6 +185,7 @@ class TXTMLizer(object):
|
||||
@stylizer: The style information attached to the element.
|
||||
@page: OEB page used to determine absolute urls.
|
||||
'''
|
||||
from calibre.ebooks.oeb.base import XHTML_NS, barename, namespace
|
||||
|
||||
if not isinstance(elem.tag, basestring) \
|
||||
or namespace(elem.tag) != XHTML_NS:
|
||||
|
@ -4,19 +4,17 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import os, sys, Queue, threading
|
||||
from threading import RLock
|
||||
from urllib import unquote
|
||||
|
||||
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
|
||||
QByteArray, QTranslator, QCoreApplication, QThread, \
|
||||
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, \
|
||||
QFileDialog, QFileIconProvider, \
|
||||
QIcon, QApplication, QDialog, QUrl, QFont
|
||||
from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt,
|
||||
QByteArray, QTranslator, QCoreApplication, QThread,
|
||||
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices,
|
||||
QFileDialog, QFileIconProvider,
|
||||
QIcon, QApplication, QDialog, QUrl, QFont)
|
||||
|
||||
ORG_NAME = 'KovidsBrain'
|
||||
APP_UID = 'libprs500'
|
||||
from calibre.constants import islinux, iswindows, isfreebsd, isfrozen, isosx
|
||||
from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig
|
||||
from calibre.utils.localization import set_qt_translator
|
||||
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.utils.date import UNDEFINED_DATE
|
||||
|
||||
@ -156,7 +154,9 @@ def _config():
|
||||
c.add_opt('plugin_search_history', default=[],
|
||||
help='Search history for the recipe scheduler')
|
||||
c.add_opt('worker_limit', default=6,
|
||||
help=_('Maximum number of waiting worker processes'))
|
||||
help=_(
|
||||
'Maximum number of simultaneous conversion/news download jobs. '
|
||||
'This number is twice the actual value for historical reasons.'))
|
||||
c.add_opt('get_social_metadata', default=True,
|
||||
help=_('Download social metadata (tags/rating/etc.)'))
|
||||
c.add_opt('overwrite_author_title_metadata', default=True,
|
||||
@ -330,6 +330,7 @@ class GetMetadata(QObject):
|
||||
id, args, kwargs)
|
||||
|
||||
def _from_formats(self, id, args, kwargs):
|
||||
from calibre.ebooks.metadata.meta import metadata_from_formats
|
||||
try:
|
||||
mi = metadata_from_formats(*args, **kwargs)
|
||||
except:
|
||||
@ -337,6 +338,7 @@ class GetMetadata(QObject):
|
||||
self.emit(SIGNAL('metadataf(PyQt_PyObject, PyQt_PyObject)'), id, mi)
|
||||
|
||||
def _get_metadata(self, id, args, kwargs):
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
try:
|
||||
mi = get_metadata(*args, **kwargs)
|
||||
except:
|
||||
@ -738,3 +740,4 @@ def build_forms(srcdir, info=None):
|
||||
_df = os.environ.get('CALIBRE_DEVELOP_FROM', None)
|
||||
if _df and os.path.exists(_df):
|
||||
build_forms(_df)
|
||||
|
||||
|
@ -22,7 +22,7 @@ class FetchAnnotationsAction(InterfaceAction):
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
pass
|
||||
self.qaction.triggered.connect(self.fetch_annotations)
|
||||
|
||||
def fetch_annotations(self, *args):
|
||||
# Generate a path_map from selected ids
|
||||
@ -52,6 +52,10 @@ class FetchAnnotationsAction(InterfaceAction):
|
||||
return path_map
|
||||
|
||||
device = self.gui.device_manager.device
|
||||
if not getattr(device, 'SUPPORTS_ANNOTATIONS', False):
|
||||
return error_dialog(self.gui, _('Not supported'),
|
||||
_('Fetching annotations is not supported for this device'),
|
||||
show=True)
|
||||
|
||||
if self.gui.current_view() is not self.gui.library_view:
|
||||
return error_dialog(self.gui, _('Use library only'),
|
||||
|
@ -7,7 +7,7 @@ import os, traceback, Queue, time, cStringIO, re, sys
|
||||
from threading import Thread
|
||||
|
||||
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, \
|
||||
Qt, pyqtSignal, QDialog
|
||||
Qt, pyqtSignal, QDialog, QObject
|
||||
|
||||
from calibre.customize.ui import available_input_formats, available_output_formats, \
|
||||
device_plugins
|
||||
@ -25,7 +25,6 @@ from calibre.devices.errors import FreeSpaceError
|
||||
from calibre.devices.apple.driver import ITUNES_ASYNC
|
||||
from calibre.devices.folder_device.driver import FOLDER_DEVICE
|
||||
from calibre.devices.bambook.driver import BAMBOOK, BAMBOOKWifi
|
||||
from calibre.ebooks.metadata.meta import set_metadata
|
||||
from calibre.constants import DEBUG
|
||||
from calibre.utils.config import prefs, tweaks
|
||||
from calibre.utils.magick.draw import thumbnail
|
||||
@ -334,6 +333,7 @@ class DeviceManager(Thread): # {{{
|
||||
|
||||
def _upload_books(self, files, names, on_card=None, metadata=None, plugboards=None):
|
||||
'''Upload books to device: '''
|
||||
from calibre.ebooks.metadata.meta import set_metadata
|
||||
if hasattr(self.connected_device, 'set_plugboards') and \
|
||||
callable(self.connected_device.set_plugboards):
|
||||
self.connected_device.set_plugboards(plugboards, find_plugboard)
|
||||
@ -587,18 +587,26 @@ class DeviceMenu(QMenu): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class DeviceMixin(object): # {{{
|
||||
|
||||
class DeviceSignals(QObject):
|
||||
#: This signal is emitted once, after metadata is downloaded from the
|
||||
#: connected device.
|
||||
#: The sequence: gui.device_manager.is_device_connected will become True,
|
||||
#: and the device_connection_changed signal will be emitted,
|
||||
#: then sometime later gui.device_metadata_available will be signaled.
|
||||
#: This does not mean that there are no more jobs running. Automatic metadata
|
||||
#: management might have kicked off a sync_booklists to write new metadata onto
|
||||
#: the device, and that job might still be running when the signal is emitted.
|
||||
device_metadata_available = pyqtSignal()
|
||||
|
||||
#: This signal is emitted once when the device is detected and once when
|
||||
#: it is disconnected. If the parameter is True, then it is a connection,
|
||||
#: otherwise a disconnection.
|
||||
device_connection_changed = pyqtSignal(object)
|
||||
|
||||
device_signals = DeviceSignals()
|
||||
|
||||
class DeviceMixin(object): # {{{
|
||||
|
||||
def __init__(self):
|
||||
self.device_error_dialog = error_dialog(self, _('Error'),
|
||||
_('Error communicating with device'), ' ')
|
||||
@ -745,7 +753,7 @@ class DeviceMixin(object): # {{{
|
||||
self.location_manager.update_devices()
|
||||
self.library_view.set_device_connected(self.device_connected)
|
||||
self.refresh_ondevice()
|
||||
self.device_connection_changed.emit(connected)
|
||||
device_signals.device_connection_changed.emit(connected)
|
||||
|
||||
def info_read(self, job):
|
||||
'''
|
||||
@ -784,7 +792,7 @@ class DeviceMixin(object): # {{{
|
||||
self.sync_news()
|
||||
self.sync_catalogs()
|
||||
self.refresh_ondevice()
|
||||
self.device_metadata_available.emit()
|
||||
device_signals.device_metadata_available.emit()
|
||||
|
||||
def refresh_ondevice(self, reset_only = False):
|
||||
'''
|
||||
|
@ -13,7 +13,6 @@ from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
|
||||
from calibre.gui2.dialogs.tag_editor import TagEditor
|
||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string, title_sort
|
||||
from calibre.ebooks.metadata.book.base import composite_formatter
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
||||
from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATE, \
|
||||
gprefs, question_dialog
|
||||
@ -26,6 +25,7 @@ from calibre.utils.magick.draw import identify_data
|
||||
from calibre.utils.date import qt_to_dt
|
||||
|
||||
def get_cover_data(path): # {{{
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
old = prefs['read_file_metadata']
|
||||
if not old:
|
||||
prefs['read_file_metadata'] = True
|
||||
|
@ -25,7 +25,6 @@ from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.ebooks.metadata import string_to_authors, \
|
||||
authors_to_string, check_isbn, title_sort
|
||||
from calibre.ebooks.metadata.covers import download_cover
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
from calibre.utils.config import prefs, tweaks
|
||||
from calibre.utils.date import qt_to_dt, local_tz, utcfromtimestamp
|
||||
@ -353,6 +352,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
|
||||
self.formats_changed = True
|
||||
|
||||
def get_selected_format_metadata(self):
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
old = prefs['read_file_metadata']
|
||||
if not old:
|
||||
prefs['read_file_metadata'] = True
|
||||
|
@ -156,8 +156,6 @@ class SearchBar(QWidget): # {{{
|
||||
x = ComboBoxWithHelp(self)
|
||||
x.setMaximumSize(QSize(150, 16777215))
|
||||
x.setObjectName("search_restriction")
|
||||
x.setToolTip(_('Books display will be restricted to those matching the '
|
||||
'selected saved search'))
|
||||
l.addWidget(x)
|
||||
parent.search_restriction = x
|
||||
|
||||
|
@ -18,7 +18,6 @@ from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.utils.config import tweaks, prefs
|
||||
from calibre.utils.date import dt_factory, qt_to_dt, isoformat
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
||||
from calibre.utils.search_query_parser import SearchQueryParser
|
||||
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, \
|
||||
REGEXP_MATCH, MetadataBackup, force_to_bool
|
||||
@ -478,6 +477,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
def get_preferred_formats_from_ids(self, ids, formats,
|
||||
set_metadata=False, specific_format=None,
|
||||
exclude_auto=False, mode='r+b'):
|
||||
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
||||
ans = []
|
||||
need_auto = []
|
||||
if specific_format is not None:
|
||||
@ -526,6 +526,7 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
def get_preferred_formats(self, rows, formats, paths=False,
|
||||
set_metadata=False, specific_format=None,
|
||||
exclude_auto=False):
|
||||
from calibre.ebooks.metadata.meta import set_metadata as _set_metadata
|
||||
ans = []
|
||||
need_auto = []
|
||||
if specific_format is not None:
|
||||
|
@ -19,6 +19,9 @@ from calibre.utils.config import prefs, dynamic
|
||||
from calibre.library.database2 import LibraryDatabase2
|
||||
from calibre.library.sqlite import sqlite, DatabaseException
|
||||
|
||||
if iswindows:
|
||||
winutil = plugins['winutil'][0]
|
||||
|
||||
def option_parser():
|
||||
parser = _option_parser('''\
|
||||
%prog [opts] [path_to_ebook]
|
||||
@ -80,8 +83,7 @@ def get_library_path(parent=None):
|
||||
if library_path is None: # Need to migrate to new database layout
|
||||
base = os.path.expanduser('~')
|
||||
if iswindows:
|
||||
base = plugins['winutil'][0].special_folder_path(
|
||||
plugins['winutil'][0].CSIDL_PERSONAL)
|
||||
base = winutil.special_folder_path(winutil.CSIDL_PERSONAL)
|
||||
if not base or not os.path.exists(base):
|
||||
from PyQt4.Qt import QDir
|
||||
base = unicode(QDir.homePath()).replace('/', os.sep)
|
||||
|
@ -242,7 +242,7 @@ def apply_metadata(job, gui, q, result):
|
||||
q.finished.disconnect()
|
||||
if result != q.Accepted:
|
||||
return
|
||||
id_map, failed_ids, failed_covers, title_map = job.result
|
||||
id_map, failed_ids, failed_covers, title_map, all_failed = job.result
|
||||
id_map = dict([(k, v) for k, v in id_map.iteritems() if k not in
|
||||
failed_ids])
|
||||
if not id_map:
|
||||
@ -279,11 +279,7 @@ def apply_metadata(job, gui, q, result):
|
||||
|
||||
def proceed(gui, job):
|
||||
gui.status_bar.show_message(_('Metadata download completed'), 3000)
|
||||
id_map, failed_ids, failed_covers, title_map = job.result
|
||||
fmsg = det_msg = ''
|
||||
if failed_ids or failed_covers:
|
||||
fmsg = '<p>'+_('Could not download metadata and/or covers for %d of the books. Click'
|
||||
' "Show details" to see which books.')%len(failed_ids)
|
||||
id_map, failed_ids, failed_covers, title_map, all_failed = job.result
|
||||
det_msg = []
|
||||
for i in failed_ids | failed_covers:
|
||||
title = title_map[i]
|
||||
@ -292,24 +288,37 @@ def proceed(gui, job):
|
||||
if i in failed_covers:
|
||||
title += (' ' + _('(Failed cover)'))
|
||||
det_msg.append(title)
|
||||
det_msg = '\n'.join(det_msg)
|
||||
|
||||
if all_failed:
|
||||
q = error_dialog(gui, _('Download failed'),
|
||||
_('Failed to download metadata or covers for any of the %d'
|
||||
' book(s).') % len(id_map), det_msg=det_msg)
|
||||
else:
|
||||
fmsg = det_msg = ''
|
||||
if failed_ids or failed_covers:
|
||||
fmsg = '<p>'+_('Could not download metadata and/or covers for %d of the books. Click'
|
||||
' "Show details" to see which books.')%len(failed_ids)
|
||||
msg = '<p>' + _('Finished downloading metadata for <b>%d book(s)</b>. '
|
||||
'Proceed with updating the metadata in your library?')%len(id_map)
|
||||
q = MessageBox(MessageBox.QUESTION, _('Download complete'),
|
||||
msg + fmsg, det_msg='\n'.join(det_msg), show_copy_button=bool(failed_ids),
|
||||
msg + fmsg, det_msg=det_msg, show_copy_button=bool(failed_ids),
|
||||
parent=gui)
|
||||
q.finished.connect(partial(apply_metadata, job, gui, q))
|
||||
|
||||
q.vlb = q.bb.addButton(_('View log'), q.bb.ActionRole)
|
||||
q.vlb.setIcon(QIcon(I('debug.png')))
|
||||
q.vlb.clicked.connect(partial(view_log, job, q))
|
||||
q.det_msg_toggle.setVisible(bool(failed_ids | failed_covers))
|
||||
q.setModal(False)
|
||||
q.show()
|
||||
q.finished.connect(partial(apply_metadata, job, gui, q))
|
||||
|
||||
# }}}
|
||||
|
||||
def merge_result(oldmi, newmi):
|
||||
dummy = Metadata(_('Unknown'))
|
||||
for f in msprefs['ignore_fields']:
|
||||
if ':' not in f:
|
||||
setattr(newmi, f, getattr(dummy, f))
|
||||
fields = set()
|
||||
for plugin in metadata_plugins(['identify']):
|
||||
@ -335,6 +344,7 @@ def download(ids, db, do_identify, covers,
|
||||
title_map = {}
|
||||
ans = {}
|
||||
count = 0
|
||||
all_failed = True
|
||||
for i, mi in izip(ids, metadata):
|
||||
if abort.is_set():
|
||||
log.error('Aborting...')
|
||||
@ -349,6 +359,7 @@ def download(ids, db, do_identify, covers,
|
||||
except:
|
||||
pass
|
||||
if results:
|
||||
all_failed = False
|
||||
mi = merge_result(mi, results[0])
|
||||
identifiers = mi.identifiers
|
||||
if not mi.is_null('rating'):
|
||||
@ -366,6 +377,7 @@ def download(ids, db, do_identify, covers,
|
||||
with PersistentTemporaryFile('.jpg', 'downloaded-cover-') as f:
|
||||
f.write(cdata[-1])
|
||||
mi.cover = f.name
|
||||
all_failed = False
|
||||
else:
|
||||
failed_covers.add(i)
|
||||
ans[i] = mi
|
||||
@ -373,7 +385,7 @@ def download(ids, db, do_identify, covers,
|
||||
notifications.put((count/len(ids),
|
||||
_('Downloaded %d of %d')%(count, len(ids))))
|
||||
log('Download complete, with %d failures'%len(failed_ids))
|
||||
return (ans, failed_ids, failed_covers, title_map)
|
||||
return (ans, failed_ids, failed_covers, title_map, all_failed)
|
||||
|
||||
|
||||
|
||||
|
@ -326,6 +326,7 @@ class MetadataSingleDialogBase(ResizableDialog):
|
||||
mi = d.book
|
||||
dummy = Metadata(_('Unknown'))
|
||||
for f in msprefs['ignore_fields']:
|
||||
if ':' not in f:
|
||||
setattr(mi, f, getattr(dummy, f))
|
||||
if mi is not None:
|
||||
self.update_from_mi(mi)
|
||||
|
@ -6,19 +6,27 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
|
||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
|
||||
from calibre.gui2.preferences import ConfigWidgetBase, test_widget, Setting
|
||||
from calibre.gui2.preferences.misc_ui import Ui_Form
|
||||
from calibre.gui2 import error_dialog, config, open_local_file, info_dialog
|
||||
from calibre.constants import isosx
|
||||
|
||||
# Check Integrity {{{
|
||||
class WorkersSetting(Setting):
|
||||
|
||||
def set_gui_val(self, val):
|
||||
val = val//2
|
||||
Setting.set_gui_val(self, val)
|
||||
|
||||
def get_gui_val(self):
|
||||
val = Setting.get_gui_val(self)
|
||||
return val * 2
|
||||
|
||||
class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
|
||||
def genesis(self, gui):
|
||||
self.gui = gui
|
||||
r = self.register
|
||||
r('worker_limit', config, restart_required=True)
|
||||
r('worker_limit', config, restart_required=True, setting=WorkersSetting)
|
||||
r('enforce_cpu_limit', config, restart_required=True)
|
||||
self.device_detection_button.clicked.connect(self.debug_device_detection)
|
||||
self.button_open_config_dir.clicked.connect(self.open_config_dir)
|
||||
|
@ -17,7 +17,7 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>&Maximum number of waiting worker processes (needs restart):</string>
|
||||
<string>Max. simultaneous conversion/news download jobs:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_worker_limit</cstring>
|
||||
@ -27,13 +27,7 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="opt_worker_limit">
|
||||
<property name="minimum">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>2</number>
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -17,6 +17,10 @@ class SearchRestrictionMixin(object):
|
||||
self.search_restriction.setMinimumContentsLength(10)
|
||||
self.search_restriction.setStatusTip(self.search_restriction.toolTip())
|
||||
self.search_count.setText(_("(all books)"))
|
||||
self.search_restriction_tooltip = \
|
||||
_('Books display will be restricted to those matching a '
|
||||
'selected saved search')
|
||||
self.search_restriction.setToolTip(self.search_restriction_tooltip)
|
||||
|
||||
def apply_named_search_restriction(self, name):
|
||||
if not name:
|
||||
@ -30,22 +34,31 @@ class SearchRestrictionMixin(object):
|
||||
self.apply_search_restriction(r)
|
||||
|
||||
def apply_text_search_restriction(self, search):
|
||||
search = unicode(search)
|
||||
if not search:
|
||||
self.search_restriction.setItemText(1, _('*Current search'))
|
||||
self.search_restriction.setCurrentIndex(0)
|
||||
else:
|
||||
self.search_restriction.setCurrentIndex(1)
|
||||
self.search_restriction.setItemText(1, search)
|
||||
s = '*' + search
|
||||
if self.search_restriction.count() > 1:
|
||||
txt = unicode(self.search_restriction.itemText(2))
|
||||
if txt.startswith('*'):
|
||||
self.search_restriction.setItemText(2, s)
|
||||
else:
|
||||
self.search_restriction.insertItem(2, s)
|
||||
else:
|
||||
self.search_restriction.insertItem(2, s)
|
||||
self.search_restriction.setCurrentIndex(2)
|
||||
self.search_restriction.setToolTip('<p>' +
|
||||
self.search_restriction_tooltip +
|
||||
_(' or the search ') + "'" + search + "'</p>")
|
||||
self._apply_search_restriction(search)
|
||||
|
||||
def apply_search_restriction(self, i):
|
||||
self.search_restriction.setItemText(1, _('*Current search'))
|
||||
if i == 1:
|
||||
restriction = unicode(self.search.currentText())
|
||||
if not restriction:
|
||||
self.search_restriction.setCurrentIndex(0)
|
||||
else:
|
||||
self.search_restriction.setItemText(1, restriction)
|
||||
self.apply_text_search_restriction(unicode(self.search.currentText()))
|
||||
elif i == 2 and unicode(self.search_restriction.currentText()).startswith('*'):
|
||||
self.apply_text_search_restriction(
|
||||
unicode(self.search_restriction.currentText())[1:])
|
||||
else:
|
||||
r = unicode(self.search_restriction.currentText())
|
||||
if r is not None and r != '':
|
||||
|
@ -19,7 +19,6 @@ from calibre.gui2 import NONE, error_dialog, pixmap_to_data, gprefs
|
||||
from calibre.gui2.filename_pattern_ui import Ui_Form
|
||||
from calibre import fit_image
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||
from calibre.utils.config import prefs, XMLConfig, tweaks
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator as _ProgressIndicator
|
||||
from calibre.gui2.dnd import dnd_has_image, dnd_get_image, dnd_get_files, \
|
||||
@ -95,6 +94,7 @@ class FilenamePattern(QWidget, Ui_Form):
|
||||
self.re.setCurrentIndex(0)
|
||||
|
||||
def do_test(self):
|
||||
from calibre.ebooks.metadata.meta import metadata_from_filename
|
||||
try:
|
||||
pat = self.pattern()
|
||||
except Exception as err:
|
||||
|
@ -707,6 +707,9 @@ class ResultCache(SearchQueryParser): # {{{
|
||||
for loc in location: # location is now an array of field indices
|
||||
if loc == db_col['authors']:
|
||||
### DB stores authors with commas changed to bars, so change query
|
||||
if matchkind == REGEXP_MATCH:
|
||||
q = query.replace(',', r'\|');
|
||||
else:
|
||||
q = query.replace(',', '|');
|
||||
else:
|
||||
q = query
|
||||
|
@ -15,7 +15,6 @@ from calibre.customize import CatalogPlugin
|
||||
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
|
||||
from calibre.ebooks.chardet import substitute_entites
|
||||
from calibre.ebooks.oeb.base import XHTML_NS
|
||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||
from calibre.utils.config import config_dir
|
||||
from calibre.utils.date import format_date, isoformat, is_date_undefined, now as nowf
|
||||
@ -4322,6 +4321,8 @@ Author '{0}':
|
||||
'''
|
||||
Generate description header from template
|
||||
'''
|
||||
from calibre.ebooks.oeb.base import XHTML_NS
|
||||
|
||||
def generate_html():
|
||||
args = dict(
|
||||
author=author,
|
||||
|
@ -10,8 +10,7 @@ Command line interface to the calibre database.
|
||||
import sys, os, cStringIO, re
|
||||
from textwrap import TextWrapper
|
||||
|
||||
from calibre import terminal_controller, preferred_encoding, prints, \
|
||||
isbytestring
|
||||
from calibre import preferred_encoding, prints, isbytestring
|
||||
from calibre.utils.config import OptionParser, prefs, tweaks
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
from calibre.library.database2 import LibraryDatabase2
|
||||
@ -53,6 +52,8 @@ 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()
|
||||
if sort_by:
|
||||
db.sort(sort_by, ascending)
|
||||
if search_text:
|
||||
@ -1087,6 +1088,9 @@ 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()
|
||||
|
||||
separator = ' '
|
||||
widths = list(map(lambda x : 0, fields))
|
||||
for i in data:
|
||||
|
@ -15,7 +15,8 @@ from math import ceil
|
||||
from PyQt4.QtGui import QImage
|
||||
|
||||
from calibre import prints
|
||||
from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
||||
from calibre.ebooks.metadata import (title_sort, author_to_author_sort,
|
||||
string_to_authors, authors_to_string)
|
||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||
from calibre.library.database import LibraryDatabase
|
||||
from calibre.library.field_metadata import FieldMetadata, TagsIcons
|
||||
@ -24,9 +25,7 @@ from calibre.library.caches import ResultCache
|
||||
from calibre.library.custom_columns import CustomColumns
|
||||
from calibre.library.sqlite import connect, IntegrityError
|
||||
from calibre.library.prefs import DBPrefs
|
||||
from calibre.ebooks.metadata import string_to_authors, authors_to_string
|
||||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats
|
||||
from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre.customize.ui import run_plugins_on_import
|
||||
@ -853,6 +852,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
mi.pubdate = row[fm['pubdate']]
|
||||
mi.uuid = row[fm['uuid']]
|
||||
mi.title_sort = row[fm['sort']]
|
||||
mi.book_size = row[fm['size']]
|
||||
mi.last_modified = row[fm['last_modified']]
|
||||
formats = row[fm['formats']]
|
||||
if not formats:
|
||||
@ -1378,13 +1378,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
for (cat, dex, mult, is_comp) in md:
|
||||
if not book[dex]:
|
||||
continue
|
||||
tid_cat = tids[cat]
|
||||
tcats_cat = tcategories[cat]
|
||||
if not mult:
|
||||
val = book[dex]
|
||||
if is_comp:
|
||||
item = tcategories[cat].get(val, None)
|
||||
item = tcats_cat.get(val, None)
|
||||
if not item:
|
||||
item = tag_class(val, val)
|
||||
tcategories[cat][val] = item
|
||||
tcats_cat[val] = item
|
||||
item.c += 1
|
||||
item.id = val
|
||||
if rating > 0:
|
||||
@ -1392,11 +1394,11 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
item.rc += 1
|
||||
continue
|
||||
try:
|
||||
(item_id, sort_val) = tids[cat][val] # let exceptions fly
|
||||
item = tcategories[cat].get(val, None)
|
||||
(item_id, sort_val) = tid_cat[val] # let exceptions fly
|
||||
item = tcats_cat.get(val, None)
|
||||
if not item:
|
||||
item = tag_class(val, sort_val)
|
||||
tcategories[cat][val] = item
|
||||
tcats_cat[val] = item
|
||||
item.c += 1
|
||||
item.id_set.add(book[0])
|
||||
item.id = item_id
|
||||
@ -1410,21 +1412,15 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
if is_comp:
|
||||
vals = [v.strip() for v in vals if v.strip()]
|
||||
for val in vals:
|
||||
if val not in tids:
|
||||
tids[cat][val] = (val, val)
|
||||
item = tcategories[cat].get(val, None)
|
||||
if not item:
|
||||
item = tag_class(val, val)
|
||||
tcategories[cat][val] = item
|
||||
item.c += 1
|
||||
item.id = val
|
||||
if val not in tid_cat:
|
||||
tid_cat[val] = (val, val)
|
||||
for val in vals:
|
||||
try:
|
||||
(item_id, sort_val) = tids[cat][val] # let exceptions fly
|
||||
item = tcategories[cat].get(val, None)
|
||||
(item_id, sort_val) = tid_cat[val] # let exceptions fly
|
||||
item = tcats_cat.get(val, None)
|
||||
if not item:
|
||||
item = tag_class(val, sort_val)
|
||||
tcategories[cat][val] = item
|
||||
tcats_cat[val] = item
|
||||
item.c += 1
|
||||
item.id_set.add(book[0])
|
||||
item.id = item_id
|
||||
@ -2732,6 +2728,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
self.set_identifier(id_, 'isbn', isbn, notify=notify, commit=commit)
|
||||
|
||||
def add_catalog(self, path, title):
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
|
||||
format = os.path.splitext(path)[1][1:].lower()
|
||||
with lopen(path, 'rb') as stream:
|
||||
matches = self.data.get_matches('title', '='+title)
|
||||
@ -2767,6 +2765,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
|
||||
|
||||
def add_news(self, path, arg):
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
|
||||
format = os.path.splitext(path)[1][1:].lower()
|
||||
stream = path if hasattr(path, 'read') else lopen(path, 'rb')
|
||||
stream.seek(0)
|
||||
@ -3160,6 +3160,8 @@ books_series_link feeds
|
||||
yield formats
|
||||
|
||||
def import_book_directory_multiple(self, dirpath, callback=None):
|
||||
from calibre.ebooks.metadata.meta import metadata_from_formats
|
||||
|
||||
duplicates = []
|
||||
for formats in self.find_books_in_directory(dirpath, False):
|
||||
mi = metadata_from_formats(formats)
|
||||
@ -3175,6 +3177,7 @@ books_series_link feeds
|
||||
return duplicates
|
||||
|
||||
def import_book_directory(self, dirpath, callback=None):
|
||||
from calibre.ebooks.metadata.meta import metadata_from_formats
|
||||
dirpath = os.path.abspath(dirpath)
|
||||
formats = self.find_books_in_directory(dirpath, True)
|
||||
formats = list(formats)[0]
|
||||
|
@ -14,7 +14,6 @@ from calibre.utils.formatter import TemplateFormatter
|
||||
from calibre.utils.filenames import shorten_components_to, supports_long_names, \
|
||||
ascii_filename
|
||||
from calibre.ebooks.metadata.opf2 import metadata_to_opf
|
||||
from calibre.ebooks.metadata.meta import set_metadata
|
||||
from calibre.constants import preferred_encoding
|
||||
from calibre.ebooks.metadata import fmt_sidx
|
||||
from calibre.ebooks.metadata import title_sort
|
||||
@ -198,7 +197,6 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250,
|
||||
for key in custom_metadata:
|
||||
if key in format_args:
|
||||
cm = custom_metadata[key]
|
||||
## TODO: NEWMETA: should ratings be divided by 2? The standard rating isn't...
|
||||
if cm['datatype'] == 'series':
|
||||
format_args[key] = title_sort(format_args[key], order=tsorder)
|
||||
if key+'_index' in format_args:
|
||||
@ -252,6 +250,7 @@ def save_book_to_disk(id_, db, root, opts, length):
|
||||
|
||||
def do_save_book_to_disk(id_, mi, cover, plugboards,
|
||||
format_map, root, opts, length):
|
||||
from calibre.ebooks.metadata.meta import set_metadata
|
||||
available_formats = [x.lower().strip() for x in format_map.keys()]
|
||||
if opts.formats == 'all':
|
||||
asked_formats = available_formats
|
||||
|
@ -549,17 +549,6 @@ How do I run calibre from my USB stick?
|
||||
|
||||
A portable version of calibre is available at: `portableapps.com <http://portableapps.com/node/20518>`_. However, this is usually out of date. You can also setup your own portable calibre install by following :ref:`these instructions <portablecalibre>`.
|
||||
|
||||
Why are there so many calibre-parallel processes on my system?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|app| maintains two separate worker process pools. One is used for adding books/saving to disk and the other for conversions. You can control the number of worker processes via :guilabel:`Preferences->Advanced->Miscellaneous`. So if you set it to 6 that means a maximum of 3 conversions will run simultaneously. And that is why you will see the number of worker processes changes by two when you use the up and down arrows. On windows, you can set the priority that these processes run with. This can be useful on older, single CPU machines, if you find them slowing down to a crawl when conversions are running.
|
||||
|
||||
In addition to this some conversion plugins run tasks in their own pool of processes, so for example if you bulk convert comics, each comic conversion will use three separate processes to render the images. The job manager knows this so it will run only a single comic conversion simultaneously.
|
||||
|
||||
And since I'm sure someone will ask: The reason adding/saving books are in separate processes is because of PDF. PDF processing libraries can crash on reading PDFs and I dont want the crash to take down all of calibre. Also when adding EPUB books, in order to extract the cover you have to sometimes render the HTML of the first page, which means that it either has to run in the GUI thread of the main process or in a separate process.
|
||||
|
||||
Finally, the reason calibre keep workers alive and idle instead of launching on demand is to workaround the slow startup time of python processes.
|
||||
|
||||
How do I run parts of |app| like news download and the content server on my own linux server?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -230,6 +230,7 @@ The following functions are available in addition to those described in single-f
|
||||
|
||||
* ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers.
|
||||
* ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression
|
||||
* ``booksize()`` -- returns the value of the |app| 'size' field. Returns '' if there are no formats.
|
||||
* ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``.
|
||||
* ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers.
|
||||
* ``field(name)`` -- returns the metadata field named by ``name``.
|
||||
|
@ -163,10 +163,6 @@ if not _run_once:
|
||||
__builtin__.__dict__['icu_upper'] = icu_upper
|
||||
__builtin__.__dict__['icu_title'] = title_case
|
||||
|
||||
import mimetypes
|
||||
mimetypes.init([P('mime.types')])
|
||||
guess_type = mimetypes.guess_type
|
||||
|
||||
def test_lopen():
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre import CurrentDir
|
||||
|
@ -6,22 +6,25 @@ __docformat__ = 'restructuredtext en'
|
||||
'''
|
||||
Manage application-wide preferences.
|
||||
'''
|
||||
import os, re, cPickle, textwrap, traceback, plistlib, json, base64, datetime
|
||||
import os, re, cPickle, base64, datetime, json, plistlib
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
from optparse import OptionParser as _OptionParser
|
||||
from optparse import IndentedHelpFormatter
|
||||
from collections import defaultdict
|
||||
|
||||
from calibre.constants import terminal_controller, config_dir, CONFIG_DIR_MODE, \
|
||||
__appname__, __version__, __author__
|
||||
from calibre.utils.lock import LockError, ExclusiveFile
|
||||
from calibre.constants import (config_dir, CONFIG_DIR_MODE, __appname__,
|
||||
__version__, __author__, terminal_controller)
|
||||
from calibre.utils.lock import ExclusiveFile
|
||||
from calibre.utils.config_base import (make_config_dir, Option, OptionValues,
|
||||
OptionSet, ConfigInterface, Config, prefs, StringConfig, ConfigProxy,
|
||||
read_raw_tweaks, read_tweaks, write_tweaks, tweaks, plugin_dir)
|
||||
|
||||
plugin_dir = os.path.join(config_dir, 'plugins')
|
||||
if False:
|
||||
# Make pyflakes happy
|
||||
Config, ConfigProxy, Option, OptionValues, StringConfig
|
||||
OptionSet, ConfigInterface, read_tweaks, write_tweaks
|
||||
read_raw_tweaks, tweaks, plugin_dir
|
||||
|
||||
def make_config_dir():
|
||||
if not os.path.exists(plugin_dir):
|
||||
os.makedirs(plugin_dir, mode=CONFIG_DIR_MODE)
|
||||
test_eight_code = tweaks.get('test_eight_code', False)
|
||||
|
||||
def check_config_write_access():
|
||||
return os.access(config_dir, os.W_OK) and os.access(config_dir, os.X_OK)
|
||||
@ -29,23 +32,28 @@ def check_config_write_access():
|
||||
class CustomHelpFormatter(IndentedHelpFormatter):
|
||||
|
||||
def format_usage(self, usage):
|
||||
return _("%sUsage%s: %s\n") % (terminal_controller.BLUE, terminal_controller.NORMAL, usage)
|
||||
tc = terminal_controller()
|
||||
return _("%sUsage%s: %s\n") % (tc.BLUE, tc.NORMAL, usage)
|
||||
|
||||
def format_heading(self, heading):
|
||||
return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE,
|
||||
"", heading, terminal_controller.NORMAL)
|
||||
tc = terminal_controller()
|
||||
return "%*s%s%s%s:\n" % (self.current_indent, tc.BLUE,
|
||||
"", heading, tc.NORMAL)
|
||||
|
||||
def format_option(self, option):
|
||||
import textwrap
|
||||
tc = terminal_controller()
|
||||
|
||||
result = []
|
||||
opts = self.option_strings[option]
|
||||
opt_width = self.help_position - self.current_indent - 2
|
||||
if len(opts) > opt_width:
|
||||
opts = "%*s%s\n" % (self.current_indent, "",
|
||||
terminal_controller.GREEN+opts+terminal_controller.NORMAL)
|
||||
tc.GREEN+opts+tc.NORMAL)
|
||||
indent_first = self.help_position
|
||||
else: # start help on same line as opts
|
||||
opts = "%*s%-*s " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL),
|
||||
terminal_controller.GREEN + opts + terminal_controller.NORMAL)
|
||||
opts = "%*s%-*s " % (self.current_indent, "", opt_width +
|
||||
len(tc.GREEN + tc.NORMAL), tc.GREEN + opts + tc.NORMAL)
|
||||
indent_first = 0
|
||||
result.append(opts)
|
||||
if option.help:
|
||||
@ -71,9 +79,12 @@ class OptionParser(_OptionParser):
|
||||
gui_mode=False,
|
||||
conflict_handler='resolve',
|
||||
**kwds):
|
||||
import textwrap
|
||||
tc = terminal_controller()
|
||||
|
||||
usage = textwrap.dedent(usage)
|
||||
if epilog is None:
|
||||
epilog = _('Created by ')+terminal_controller.RED+__author__+terminal_controller.NORMAL
|
||||
epilog = _('Created by ')+tc.RED+__author__+tc.NORMAL
|
||||
usage += '\n\n'+_('''Whenever you pass arguments to %prog that have spaces in them, '''
|
||||
'''enclose the arguments in quotation marks.''')
|
||||
_OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
|
||||
@ -146,353 +157,6 @@ class OptionParser(_OptionParser):
|
||||
upper.__dict__[dest] = lower.__dict__[dest]
|
||||
|
||||
|
||||
|
||||
class Option(object):
|
||||
|
||||
def __init__(self, name, switches=[], help='', type=None, choices=None,
|
||||
check=None, group=None, default=None, action=None, metavar=None):
|
||||
if choices:
|
||||
type = 'choice'
|
||||
|
||||
self.name = name
|
||||
self.switches = switches
|
||||
self.help = help.replace('%default', repr(default)) if help else None
|
||||
self.type = type
|
||||
if self.type is None and action is None and choices is None:
|
||||
if isinstance(default, float):
|
||||
self.type = 'float'
|
||||
elif isinstance(default, int) and not isinstance(default, bool):
|
||||
self.type = 'int'
|
||||
|
||||
self.choices = choices
|
||||
self.check = check
|
||||
self.group = group
|
||||
self.default = default
|
||||
self.action = action
|
||||
self.metavar = metavar
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == getattr(other, 'name', other)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Option: '+self.name
|
||||
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
|
||||
class OptionValues(object):
|
||||
|
||||
def copy(self):
|
||||
return deepcopy(self)
|
||||
|
||||
class OptionSet(object):
|
||||
|
||||
OVERRIDE_PAT = re.compile(r'#{3,100} Override Options #{15}(.*?)#{3,100} End Override #{3,100}',
|
||||
re.DOTALL|re.IGNORECASE)
|
||||
|
||||
def __init__(self, description=''):
|
||||
self.description = description
|
||||
self.defaults = {}
|
||||
self.preferences = []
|
||||
self.group_list = []
|
||||
self.groups = {}
|
||||
self.set_buffer = {}
|
||||
|
||||
def has_option(self, name_or_option_object):
|
||||
if name_or_option_object in self.preferences:
|
||||
return True
|
||||
for p in self.preferences:
|
||||
if p.name == name_or_option_object:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_option(self, name_or_option_object):
|
||||
idx = self.preferences.index(name_or_option_object)
|
||||
if idx > -1:
|
||||
return self.preferences[idx]
|
||||
for p in self.preferences:
|
||||
if p.name == name_or_option_object:
|
||||
return p
|
||||
|
||||
def add_group(self, name, description=''):
|
||||
if name in self.group_list:
|
||||
raise ValueError('A group by the name %s already exists in this set'%name)
|
||||
self.groups[name] = description
|
||||
self.group_list.append(name)
|
||||
return partial(self.add_opt, group=name)
|
||||
|
||||
def update(self, other):
|
||||
for name in other.groups.keys():
|
||||
self.groups[name] = other.groups[name]
|
||||
if name not in self.group_list:
|
||||
self.group_list.append(name)
|
||||
for pref in other.preferences:
|
||||
if pref in self.preferences:
|
||||
self.preferences.remove(pref)
|
||||
self.preferences.append(pref)
|
||||
|
||||
def smart_update(self, opts1, opts2):
|
||||
'''
|
||||
Updates the preference values in opts1 using only the non-default preference values in opts2.
|
||||
'''
|
||||
for pref in self.preferences:
|
||||
new = getattr(opts2, pref.name, pref.default)
|
||||
if new != pref.default:
|
||||
setattr(opts1, pref.name, new)
|
||||
|
||||
def remove_opt(self, name):
|
||||
if name in self.preferences:
|
||||
self.preferences.remove(name)
|
||||
|
||||
|
||||
def add_opt(self, name, switches=[], help=None, type=None, choices=None,
|
||||
group=None, default=None, action=None, metavar=None):
|
||||
'''
|
||||
Add an option to this section.
|
||||
|
||||
:param name: The name of this option. Must be a valid Python identifier.
|
||||
Must also be unique in this OptionSet and all its subsets.
|
||||
:param switches: List of command line switches for this option
|
||||
(as supplied to :module:`optparse`). If empty, this
|
||||
option will not be added to the command line parser.
|
||||
:param help: Help text.
|
||||
:param type: Type checking of option values. Supported types are:
|
||||
`None, 'choice', 'complex', 'float', 'int', 'string'`.
|
||||
:param choices: List of strings or `None`.
|
||||
:param group: Group this option belongs to. You must previously
|
||||
have created this group with a call to :method:`add_group`.
|
||||
:param default: The default value for this option.
|
||||
:param action: The action to pass to optparse. Supported values are:
|
||||
`None, 'count'`. For choices and boolean options,
|
||||
action is automatically set correctly.
|
||||
'''
|
||||
pref = Option(name, switches=switches, help=help, type=type, choices=choices,
|
||||
group=group, default=default, action=action, metavar=None)
|
||||
if group is not None and group not in self.groups.keys():
|
||||
raise ValueError('Group %s has not been added to this section'%group)
|
||||
if pref in self.preferences:
|
||||
raise ValueError('An option with the name %s already exists in this set.'%name)
|
||||
self.preferences.append(pref)
|
||||
self.defaults[name] = default
|
||||
|
||||
def option_parser(self, user_defaults=None, usage='', gui_mode=False):
|
||||
parser = OptionParser(usage, gui_mode=gui_mode)
|
||||
groups = defaultdict(lambda : parser)
|
||||
for group, desc in self.groups.items():
|
||||
groups[group] = parser.add_option_group(group.upper(), desc)
|
||||
|
||||
for pref in self.preferences:
|
||||
if not pref.switches:
|
||||
continue
|
||||
g = groups[pref.group]
|
||||
action = pref.action
|
||||
if action is None:
|
||||
action = 'store'
|
||||
if pref.default is True or pref.default is False:
|
||||
action = 'store_' + ('false' if pref.default else 'true')
|
||||
args = dict(
|
||||
dest=pref.name,
|
||||
help=pref.help,
|
||||
metavar=pref.metavar,
|
||||
type=pref.type,
|
||||
choices=pref.choices,
|
||||
default=getattr(user_defaults, pref.name, pref.default),
|
||||
action=action,
|
||||
)
|
||||
g.add_option(*pref.switches, **args)
|
||||
|
||||
|
||||
return parser
|
||||
|
||||
def get_override_section(self, src):
|
||||
match = self.OVERRIDE_PAT.search(src)
|
||||
if match:
|
||||
return match.group()
|
||||
return ''
|
||||
|
||||
def parse_string(self, src):
|
||||
options = {'cPickle':cPickle}
|
||||
if src is not None:
|
||||
try:
|
||||
if not isinstance(src, unicode):
|
||||
src = src.decode('utf-8')
|
||||
exec src in options
|
||||
except:
|
||||
print 'Failed to parse options string:'
|
||||
print repr(src)
|
||||
traceback.print_exc()
|
||||
opts = OptionValues()
|
||||
for pref in self.preferences:
|
||||
val = options.get(pref.name, pref.default)
|
||||
formatter = __builtins__.get(pref.type, None)
|
||||
if callable(formatter):
|
||||
val = formatter(val)
|
||||
setattr(opts, pref.name, val)
|
||||
|
||||
return opts
|
||||
|
||||
def render_group(self, name, desc, opts):
|
||||
prefs = [pref for pref in self.preferences if pref.group == name]
|
||||
lines = ['### Begin group: %s'%(name if name else 'DEFAULT')]
|
||||
if desc:
|
||||
lines += map(lambda x: '# '+x, desc.split('\n'))
|
||||
lines.append(' ')
|
||||
for pref in prefs:
|
||||
lines.append('# '+pref.name.replace('_', ' '))
|
||||
if pref.help:
|
||||
lines += map(lambda x: '# ' + x, pref.help.split('\n'))
|
||||
lines.append('%s = %s'%(pref.name,
|
||||
self.serialize_opt(getattr(opts, pref.name, pref.default))))
|
||||
lines.append(' ')
|
||||
return '\n'.join(lines)
|
||||
|
||||
def serialize_opt(self, val):
|
||||
if val is val is True or val is False or val is None or \
|
||||
isinstance(val, (int, float, long, basestring)):
|
||||
return repr(val)
|
||||
from PyQt4.QtCore import QString
|
||||
if isinstance(val, QString):
|
||||
return repr(unicode(val))
|
||||
pickle = cPickle.dumps(val, -1)
|
||||
return 'cPickle.loads(%s)'%repr(pickle)
|
||||
|
||||
def serialize(self, opts):
|
||||
src = '# %s\n\n'%(self.description.replace('\n', '\n# '))
|
||||
groups = [self.render_group(name, self.groups.get(name, ''), opts) \
|
||||
for name in [None] + self.group_list]
|
||||
return src + '\n\n'.join(groups)
|
||||
|
||||
class ConfigInterface(object):
|
||||
|
||||
def __init__(self, description):
|
||||
self.option_set = OptionSet(description=description)
|
||||
self.add_opt = self.option_set.add_opt
|
||||
self.add_group = self.option_set.add_group
|
||||
self.remove_opt = self.remove = self.option_set.remove_opt
|
||||
self.parse_string = self.option_set.parse_string
|
||||
self.get_option = self.option_set.get_option
|
||||
self.preferences = self.option_set.preferences
|
||||
|
||||
def update(self, other):
|
||||
self.option_set.update(other.option_set)
|
||||
|
||||
def option_parser(self, usage='', gui_mode=False):
|
||||
return self.option_set.option_parser(user_defaults=self.parse(),
|
||||
usage=usage, gui_mode=gui_mode)
|
||||
|
||||
def smart_update(self, opts1, opts2):
|
||||
self.option_set.smart_update(opts1, opts2)
|
||||
|
||||
|
||||
class Config(ConfigInterface):
|
||||
'''
|
||||
A file based configuration.
|
||||
'''
|
||||
|
||||
def __init__(self, basename, description=''):
|
||||
ConfigInterface.__init__(self, description)
|
||||
self.config_file_path = os.path.join(config_dir, basename+'.py')
|
||||
|
||||
|
||||
def parse(self):
|
||||
src = ''
|
||||
if os.path.exists(self.config_file_path):
|
||||
try:
|
||||
with ExclusiveFile(self.config_file_path) as f:
|
||||
try:
|
||||
src = f.read().decode('utf-8')
|
||||
except ValueError:
|
||||
print "Failed to parse", self.config_file_path
|
||||
traceback.print_exc()
|
||||
except LockError:
|
||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||
return self.option_set.parse_string(src)
|
||||
|
||||
def as_string(self):
|
||||
if not os.path.exists(self.config_file_path):
|
||||
return ''
|
||||
try:
|
||||
with ExclusiveFile(self.config_file_path) as f:
|
||||
return f.read().decode('utf-8')
|
||||
except LockError:
|
||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||
|
||||
def set(self, name, val):
|
||||
if not self.option_set.has_option(name):
|
||||
raise ValueError('The option %s is not defined.'%name)
|
||||
try:
|
||||
if not os.path.exists(config_dir):
|
||||
make_config_dir()
|
||||
with ExclusiveFile(self.config_file_path) as f:
|
||||
src = f.read()
|
||||
opts = self.option_set.parse_string(src)
|
||||
setattr(opts, name, val)
|
||||
footer = self.option_set.get_override_section(src)
|
||||
src = self.option_set.serialize(opts)+ '\n\n' + footer + '\n'
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
if isinstance(src, unicode):
|
||||
src = src.encode('utf-8')
|
||||
f.write(src)
|
||||
except LockError:
|
||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||
|
||||
class StringConfig(ConfigInterface):
|
||||
'''
|
||||
A string based configuration
|
||||
'''
|
||||
|
||||
def __init__(self, src, description=''):
|
||||
ConfigInterface.__init__(self, description)
|
||||
self.src = src
|
||||
|
||||
def parse(self):
|
||||
return self.option_set.parse_string(self.src)
|
||||
|
||||
def set(self, name, val):
|
||||
if not self.option_set.has_option(name):
|
||||
raise ValueError('The option %s is not defined.'%name)
|
||||
opts = self.option_set.parse_string(self.src)
|
||||
setattr(opts, name, val)
|
||||
footer = self.option_set.get_override_section(self.src)
|
||||
self.src = self.option_set.serialize(opts)+ '\n\n' + footer + '\n'
|
||||
|
||||
class ConfigProxy(object):
|
||||
'''
|
||||
A Proxy to minimize file reads for widely used config settings
|
||||
'''
|
||||
|
||||
def __init__(self, config):
|
||||
self.__config = config
|
||||
self.__opts = None
|
||||
|
||||
@property
|
||||
def defaults(self):
|
||||
return self.__config.option_set.defaults
|
||||
|
||||
def refresh(self):
|
||||
self.__opts = self.__config.parse()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.get(key)
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
return self.set(key, val)
|
||||
|
||||
def get(self, key):
|
||||
if self.__opts is None:
|
||||
self.refresh()
|
||||
return getattr(self.__opts, key)
|
||||
|
||||
def set(self, key, val):
|
||||
if self.__opts is None:
|
||||
self.refresh()
|
||||
setattr(self.__opts, key, val)
|
||||
return self.__config.set(key, val)
|
||||
|
||||
def help(self, key):
|
||||
return self.__config.get_option(key).help
|
||||
|
||||
class DynamicConfig(dict):
|
||||
'''
|
||||
A replacement for QSettings that supports dynamic config keys.
|
||||
@ -690,101 +354,6 @@ class JSONConfig(XMLConfig):
|
||||
|
||||
|
||||
|
||||
def _prefs():
|
||||
c = Config('global', 'calibre wide preferences')
|
||||
c.add_opt('database_path',
|
||||
default=os.path.expanduser('~/library1.db'),
|
||||
help=_('Path to the database in which books are stored'))
|
||||
c.add_opt('filename_pattern', default=ur'(?P<title>.+) - (?P<author>[^_]+)',
|
||||
help=_('Pattern to guess metadata from filenames'))
|
||||
c.add_opt('isbndb_com_key', default='',
|
||||
help=_('Access key for isbndb.com'))
|
||||
c.add_opt('network_timeout', default=5,
|
||||
help=_('Default timeout for network operations (seconds)'))
|
||||
c.add_opt('library_path', default=None,
|
||||
help=_('Path to directory in which your library of books is stored'))
|
||||
c.add_opt('language', default=None,
|
||||
help=_('The language in which to display the user interface'))
|
||||
c.add_opt('output_format', default='EPUB',
|
||||
help=_('The default output format for ebook conversions.'))
|
||||
c.add_opt('input_format_order', default=['EPUB', 'MOBI', 'LIT', 'PRC',
|
||||
'FB2', 'HTML', 'HTM', 'XHTM', 'SHTML', 'XHTML', 'ZIP', 'ODT', 'RTF', 'PDF',
|
||||
'TXT'],
|
||||
help=_('Ordered list of formats to prefer for input.'))
|
||||
c.add_opt('read_file_metadata', default=True,
|
||||
help=_('Read metadata from files'))
|
||||
c.add_opt('worker_process_priority', default='normal',
|
||||
help=_('The priority of worker processes. A higher priority '
|
||||
'means they run faster and consume more resources. '
|
||||
'Most tasks like conversion/news download/adding books/etc. '
|
||||
'are affected by this setting.'))
|
||||
c.add_opt('swap_author_names', default=False,
|
||||
help=_('Swap author first and last names when reading metadata'))
|
||||
c.add_opt('add_formats_to_existing', default=False,
|
||||
help=_('Add new formats to existing book records'))
|
||||
c.add_opt('installation_uuid', default=None, help='Installation UUID')
|
||||
c.add_opt('new_book_tags', default=[], help=_('Tags to apply to books added to the library'))
|
||||
|
||||
# these are here instead of the gui preferences because calibredb and
|
||||
# calibre server can execute searches
|
||||
c.add_opt('saved_searches', default={}, help=_('List of named saved searches'))
|
||||
c.add_opt('user_categories', default={}, help=_('User-created tag browser categories'))
|
||||
c.add_opt('manage_device_metadata', default='manual',
|
||||
help=_('How and when calibre updates metadata on the device.'))
|
||||
c.add_opt('limit_search_columns', default=False,
|
||||
help=_('When searching for text without using lookup '
|
||||
'prefixes, as for example, Red instead of title:Red, '
|
||||
'limit the columns searched to those named below.'))
|
||||
c.add_opt('limit_search_columns_to',
|
||||
default=['title', 'authors', 'tags', 'series', 'publisher'],
|
||||
help=_('Choose columns to be searched when not using prefixes, '
|
||||
'as for example, when searching for Redd instead of '
|
||||
'title:Red. Enter a list of search/lookup names '
|
||||
'separated by commas. Only takes effect if you set the option '
|
||||
'to limit search columns above.'))
|
||||
|
||||
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
|
||||
return c
|
||||
|
||||
prefs = ConfigProxy(_prefs())
|
||||
if prefs['installation_uuid'] is None:
|
||||
import uuid
|
||||
prefs['installation_uuid'] = str(uuid.uuid4())
|
||||
|
||||
# Read tweaks
|
||||
def read_raw_tweaks():
|
||||
make_config_dir()
|
||||
default_tweaks = P('default_tweaks.py', data=True,
|
||||
allow_user_override=False)
|
||||
tweaks_file = os.path.join(config_dir, 'tweaks.py')
|
||||
if not os.path.exists(tweaks_file):
|
||||
with open(tweaks_file, 'wb') as f:
|
||||
f.write(default_tweaks)
|
||||
with open(tweaks_file, 'rb') as f:
|
||||
return default_tweaks, f.read()
|
||||
|
||||
def read_tweaks():
|
||||
default_tweaks, tweaks = read_raw_tweaks()
|
||||
l, g = {}, {}
|
||||
try:
|
||||
exec tweaks in g, l
|
||||
except:
|
||||
print 'Failed to load custom tweaks file'
|
||||
traceback.print_exc()
|
||||
dl, dg = {}, {}
|
||||
exec default_tweaks in dg, dl
|
||||
dl.update(l)
|
||||
return dl
|
||||
|
||||
def write_tweaks(raw):
|
||||
make_config_dir()
|
||||
tweaks_file = os.path.join(config_dir, 'tweaks.py')
|
||||
with open(tweaks_file, 'wb') as f:
|
||||
f.write(raw)
|
||||
|
||||
|
||||
tweaks = read_tweaks()
|
||||
test_eight_code = tweaks.get('test_eight_code', False)
|
||||
|
||||
def migrate():
|
||||
if hasattr(os, 'geteuid') and os.geteuid() == 0:
|
||||
|
467
src/calibre/utils/config_base.py
Normal file
467
src/calibre/utils/config_base.py
Normal file
@ -0,0 +1,467 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, re, cPickle, traceback
|
||||
from functools import partial
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
|
||||
from calibre.utils.lock import LockError, ExclusiveFile
|
||||
from calibre.constants import config_dir, CONFIG_DIR_MODE
|
||||
|
||||
plugin_dir = os.path.join(config_dir, 'plugins')
|
||||
|
||||
def make_config_dir():
|
||||
if not os.path.exists(plugin_dir):
|
||||
os.makedirs(plugin_dir, mode=CONFIG_DIR_MODE)
|
||||
|
||||
class Option(object):
|
||||
|
||||
def __init__(self, name, switches=[], help='', type=None, choices=None,
|
||||
check=None, group=None, default=None, action=None, metavar=None):
|
||||
if choices:
|
||||
type = 'choice'
|
||||
|
||||
self.name = name
|
||||
self.switches = switches
|
||||
self.help = help.replace('%default', repr(default)) if help else None
|
||||
self.type = type
|
||||
if self.type is None and action is None and choices is None:
|
||||
if isinstance(default, float):
|
||||
self.type = 'float'
|
||||
elif isinstance(default, int) and not isinstance(default, bool):
|
||||
self.type = 'int'
|
||||
|
||||
self.choices = choices
|
||||
self.check = check
|
||||
self.group = group
|
||||
self.default = default
|
||||
self.action = action
|
||||
self.metavar = metavar
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == getattr(other, 'name', other)
|
||||
|
||||
def __repr__(self):
|
||||
return 'Option: '+self.name
|
||||
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
|
||||
class OptionValues(object):
|
||||
|
||||
def copy(self):
|
||||
return deepcopy(self)
|
||||
|
||||
class OptionSet(object):
|
||||
|
||||
OVERRIDE_PAT = re.compile(r'#{3,100} Override Options #{15}(.*?)#{3,100} End Override #{3,100}',
|
||||
re.DOTALL|re.IGNORECASE)
|
||||
|
||||
def __init__(self, description=''):
|
||||
self.description = description
|
||||
self.defaults = {}
|
||||
self.preferences = []
|
||||
self.group_list = []
|
||||
self.groups = {}
|
||||
self.set_buffer = {}
|
||||
|
||||
def has_option(self, name_or_option_object):
|
||||
if name_or_option_object in self.preferences:
|
||||
return True
|
||||
for p in self.preferences:
|
||||
if p.name == name_or_option_object:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_option(self, name_or_option_object):
|
||||
idx = self.preferences.index(name_or_option_object)
|
||||
if idx > -1:
|
||||
return self.preferences[idx]
|
||||
for p in self.preferences:
|
||||
if p.name == name_or_option_object:
|
||||
return p
|
||||
|
||||
def add_group(self, name, description=''):
|
||||
if name in self.group_list:
|
||||
raise ValueError('A group by the name %s already exists in this set'%name)
|
||||
self.groups[name] = description
|
||||
self.group_list.append(name)
|
||||
return partial(self.add_opt, group=name)
|
||||
|
||||
def update(self, other):
|
||||
for name in other.groups.keys():
|
||||
self.groups[name] = other.groups[name]
|
||||
if name not in self.group_list:
|
||||
self.group_list.append(name)
|
||||
for pref in other.preferences:
|
||||
if pref in self.preferences:
|
||||
self.preferences.remove(pref)
|
||||
self.preferences.append(pref)
|
||||
|
||||
def smart_update(self, opts1, opts2):
|
||||
'''
|
||||
Updates the preference values in opts1 using only the non-default preference values in opts2.
|
||||
'''
|
||||
for pref in self.preferences:
|
||||
new = getattr(opts2, pref.name, pref.default)
|
||||
if new != pref.default:
|
||||
setattr(opts1, pref.name, new)
|
||||
|
||||
def remove_opt(self, name):
|
||||
if name in self.preferences:
|
||||
self.preferences.remove(name)
|
||||
|
||||
|
||||
def add_opt(self, name, switches=[], help=None, type=None, choices=None,
|
||||
group=None, default=None, action=None, metavar=None):
|
||||
'''
|
||||
Add an option to this section.
|
||||
|
||||
:param name: The name of this option. Must be a valid Python identifier.
|
||||
Must also be unique in this OptionSet and all its subsets.
|
||||
:param switches: List of command line switches for this option
|
||||
(as supplied to :module:`optparse`). If empty, this
|
||||
option will not be added to the command line parser.
|
||||
:param help: Help text.
|
||||
:param type: Type checking of option values. Supported types are:
|
||||
`None, 'choice', 'complex', 'float', 'int', 'string'`.
|
||||
:param choices: List of strings or `None`.
|
||||
:param group: Group this option belongs to. You must previously
|
||||
have created this group with a call to :method:`add_group`.
|
||||
:param default: The default value for this option.
|
||||
:param action: The action to pass to optparse. Supported values are:
|
||||
`None, 'count'`. For choices and boolean options,
|
||||
action is automatically set correctly.
|
||||
'''
|
||||
pref = Option(name, switches=switches, help=help, type=type, choices=choices,
|
||||
group=group, default=default, action=action, metavar=None)
|
||||
if group is not None and group not in self.groups.keys():
|
||||
raise ValueError('Group %s has not been added to this section'%group)
|
||||
if pref in self.preferences:
|
||||
raise ValueError('An option with the name %s already exists in this set.'%name)
|
||||
self.preferences.append(pref)
|
||||
self.defaults[name] = default
|
||||
|
||||
def option_parser(self, user_defaults=None, usage='', gui_mode=False):
|
||||
from calibre.utils.config import OptionParser
|
||||
parser = OptionParser(usage, gui_mode=gui_mode)
|
||||
groups = defaultdict(lambda : parser)
|
||||
for group, desc in self.groups.items():
|
||||
groups[group] = parser.add_option_group(group.upper(), desc)
|
||||
|
||||
for pref in self.preferences:
|
||||
if not pref.switches:
|
||||
continue
|
||||
g = groups[pref.group]
|
||||
action = pref.action
|
||||
if action is None:
|
||||
action = 'store'
|
||||
if pref.default is True or pref.default is False:
|
||||
action = 'store_' + ('false' if pref.default else 'true')
|
||||
args = dict(
|
||||
dest=pref.name,
|
||||
help=pref.help,
|
||||
metavar=pref.metavar,
|
||||
type=pref.type,
|
||||
choices=pref.choices,
|
||||
default=getattr(user_defaults, pref.name, pref.default),
|
||||
action=action,
|
||||
)
|
||||
g.add_option(*pref.switches, **args)
|
||||
|
||||
|
||||
return parser
|
||||
|
||||
def get_override_section(self, src):
|
||||
match = self.OVERRIDE_PAT.search(src)
|
||||
if match:
|
||||
return match.group()
|
||||
return ''
|
||||
|
||||
def parse_string(self, src):
|
||||
options = {'cPickle':cPickle}
|
||||
if src is not None:
|
||||
try:
|
||||
if not isinstance(src, unicode):
|
||||
src = src.decode('utf-8')
|
||||
exec src in options
|
||||
except:
|
||||
print 'Failed to parse options string:'
|
||||
print repr(src)
|
||||
traceback.print_exc()
|
||||
opts = OptionValues()
|
||||
for pref in self.preferences:
|
||||
val = options.get(pref.name, pref.default)
|
||||
formatter = __builtins__.get(pref.type, None)
|
||||
if callable(formatter):
|
||||
val = formatter(val)
|
||||
setattr(opts, pref.name, val)
|
||||
|
||||
return opts
|
||||
|
||||
def render_group(self, name, desc, opts):
|
||||
prefs = [pref for pref in self.preferences if pref.group == name]
|
||||
lines = ['### Begin group: %s'%(name if name else 'DEFAULT')]
|
||||
if desc:
|
||||
lines += map(lambda x: '# '+x, desc.split('\n'))
|
||||
lines.append(' ')
|
||||
for pref in prefs:
|
||||
lines.append('# '+pref.name.replace('_', ' '))
|
||||
if pref.help:
|
||||
lines += map(lambda x: '# ' + x, pref.help.split('\n'))
|
||||
lines.append('%s = %s'%(pref.name,
|
||||
self.serialize_opt(getattr(opts, pref.name, pref.default))))
|
||||
lines.append(' ')
|
||||
return '\n'.join(lines)
|
||||
|
||||
def serialize_opt(self, val):
|
||||
if val is val is True or val is False or val is None or \
|
||||
isinstance(val, (int, float, long, basestring)):
|
||||
return repr(val)
|
||||
from PyQt4.QtCore import QString
|
||||
if isinstance(val, QString):
|
||||
return repr(unicode(val))
|
||||
pickle = cPickle.dumps(val, -1)
|
||||
return 'cPickle.loads(%s)'%repr(pickle)
|
||||
|
||||
def serialize(self, opts):
|
||||
src = '# %s\n\n'%(self.description.replace('\n', '\n# '))
|
||||
groups = [self.render_group(name, self.groups.get(name, ''), opts) \
|
||||
for name in [None] + self.group_list]
|
||||
return src + '\n\n'.join(groups)
|
||||
|
||||
class ConfigInterface(object):
|
||||
|
||||
def __init__(self, description):
|
||||
self.option_set = OptionSet(description=description)
|
||||
self.add_opt = self.option_set.add_opt
|
||||
self.add_group = self.option_set.add_group
|
||||
self.remove_opt = self.remove = self.option_set.remove_opt
|
||||
self.parse_string = self.option_set.parse_string
|
||||
self.get_option = self.option_set.get_option
|
||||
self.preferences = self.option_set.preferences
|
||||
|
||||
def update(self, other):
|
||||
self.option_set.update(other.option_set)
|
||||
|
||||
def option_parser(self, usage='', gui_mode=False):
|
||||
return self.option_set.option_parser(user_defaults=self.parse(),
|
||||
usage=usage, gui_mode=gui_mode)
|
||||
|
||||
def smart_update(self, opts1, opts2):
|
||||
self.option_set.smart_update(opts1, opts2)
|
||||
|
||||
|
||||
class Config(ConfigInterface):
|
||||
'''
|
||||
A file based configuration.
|
||||
'''
|
||||
|
||||
def __init__(self, basename, description=''):
|
||||
ConfigInterface.__init__(self, description)
|
||||
self.config_file_path = os.path.join(config_dir, basename+'.py')
|
||||
|
||||
|
||||
def parse(self):
|
||||
src = ''
|
||||
if os.path.exists(self.config_file_path):
|
||||
try:
|
||||
with ExclusiveFile(self.config_file_path) as f:
|
||||
try:
|
||||
src = f.read().decode('utf-8')
|
||||
except ValueError:
|
||||
print "Failed to parse", self.config_file_path
|
||||
traceback.print_exc()
|
||||
except LockError:
|
||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||
return self.option_set.parse_string(src)
|
||||
|
||||
def as_string(self):
|
||||
if not os.path.exists(self.config_file_path):
|
||||
return ''
|
||||
try:
|
||||
with ExclusiveFile(self.config_file_path) as f:
|
||||
return f.read().decode('utf-8')
|
||||
except LockError:
|
||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||
|
||||
def set(self, name, val):
|
||||
if not self.option_set.has_option(name):
|
||||
raise ValueError('The option %s is not defined.'%name)
|
||||
try:
|
||||
if not os.path.exists(config_dir):
|
||||
make_config_dir()
|
||||
with ExclusiveFile(self.config_file_path) as f:
|
||||
src = f.read()
|
||||
opts = self.option_set.parse_string(src)
|
||||
setattr(opts, name, val)
|
||||
footer = self.option_set.get_override_section(src)
|
||||
src = self.option_set.serialize(opts)+ '\n\n' + footer + '\n'
|
||||
f.seek(0)
|
||||
f.truncate()
|
||||
if isinstance(src, unicode):
|
||||
src = src.encode('utf-8')
|
||||
f.write(src)
|
||||
except LockError:
|
||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||
|
||||
class StringConfig(ConfigInterface):
|
||||
'''
|
||||
A string based configuration
|
||||
'''
|
||||
|
||||
def __init__(self, src, description=''):
|
||||
ConfigInterface.__init__(self, description)
|
||||
self.src = src
|
||||
|
||||
def parse(self):
|
||||
return self.option_set.parse_string(self.src)
|
||||
|
||||
def set(self, name, val):
|
||||
if not self.option_set.has_option(name):
|
||||
raise ValueError('The option %s is not defined.'%name)
|
||||
opts = self.option_set.parse_string(self.src)
|
||||
setattr(opts, name, val)
|
||||
footer = self.option_set.get_override_section(self.src)
|
||||
self.src = self.option_set.serialize(opts)+ '\n\n' + footer + '\n'
|
||||
|
||||
class ConfigProxy(object):
|
||||
'''
|
||||
A Proxy to minimize file reads for widely used config settings
|
||||
'''
|
||||
|
||||
def __init__(self, config):
|
||||
self.__config = config
|
||||
self.__opts = None
|
||||
|
||||
@property
|
||||
def defaults(self):
|
||||
return self.__config.option_set.defaults
|
||||
|
||||
def refresh(self):
|
||||
self.__opts = self.__config.parse()
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.get(key)
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
return self.set(key, val)
|
||||
|
||||
def get(self, key):
|
||||
if self.__opts is None:
|
||||
self.refresh()
|
||||
return getattr(self.__opts, key)
|
||||
|
||||
def set(self, key, val):
|
||||
if self.__opts is None:
|
||||
self.refresh()
|
||||
setattr(self.__opts, key, val)
|
||||
return self.__config.set(key, val)
|
||||
|
||||
def help(self, key):
|
||||
return self.__config.get_option(key).help
|
||||
|
||||
|
||||
|
||||
def _prefs():
|
||||
c = Config('global', 'calibre wide preferences')
|
||||
c.add_opt('database_path',
|
||||
default=os.path.expanduser('~/library1.db'),
|
||||
help=_('Path to the database in which books are stored'))
|
||||
c.add_opt('filename_pattern', default=ur'(?P<title>.+) - (?P<author>[^_]+)',
|
||||
help=_('Pattern to guess metadata from filenames'))
|
||||
c.add_opt('isbndb_com_key', default='',
|
||||
help=_('Access key for isbndb.com'))
|
||||
c.add_opt('network_timeout', default=5,
|
||||
help=_('Default timeout for network operations (seconds)'))
|
||||
c.add_opt('library_path', default=None,
|
||||
help=_('Path to directory in which your library of books is stored'))
|
||||
c.add_opt('language', default=None,
|
||||
help=_('The language in which to display the user interface'))
|
||||
c.add_opt('output_format', default='EPUB',
|
||||
help=_('The default output format for ebook conversions.'))
|
||||
c.add_opt('input_format_order', default=['EPUB', 'MOBI', 'LIT', 'PRC',
|
||||
'FB2', 'HTML', 'HTM', 'XHTM', 'SHTML', 'XHTML', 'ZIP', 'ODT', 'RTF', 'PDF',
|
||||
'TXT'],
|
||||
help=_('Ordered list of formats to prefer for input.'))
|
||||
c.add_opt('read_file_metadata', default=True,
|
||||
help=_('Read metadata from files'))
|
||||
c.add_opt('worker_process_priority', default='normal',
|
||||
help=_('The priority of worker processes. A higher priority '
|
||||
'means they run faster and consume more resources. '
|
||||
'Most tasks like conversion/news download/adding books/etc. '
|
||||
'are affected by this setting.'))
|
||||
c.add_opt('swap_author_names', default=False,
|
||||
help=_('Swap author first and last names when reading metadata'))
|
||||
c.add_opt('add_formats_to_existing', default=False,
|
||||
help=_('Add new formats to existing book records'))
|
||||
c.add_opt('installation_uuid', default=None, help='Installation UUID')
|
||||
c.add_opt('new_book_tags', default=[], help=_('Tags to apply to books added to the library'))
|
||||
|
||||
# these are here instead of the gui preferences because calibredb and
|
||||
# calibre server can execute searches
|
||||
c.add_opt('saved_searches', default={}, help=_('List of named saved searches'))
|
||||
c.add_opt('user_categories', default={}, help=_('User-created tag browser categories'))
|
||||
c.add_opt('manage_device_metadata', default='manual',
|
||||
help=_('How and when calibre updates metadata on the device.'))
|
||||
c.add_opt('limit_search_columns', default=False,
|
||||
help=_('When searching for text without using lookup '
|
||||
'prefixes, as for example, Red instead of title:Red, '
|
||||
'limit the columns searched to those named below.'))
|
||||
c.add_opt('limit_search_columns_to',
|
||||
default=['title', 'authors', 'tags', 'series', 'publisher'],
|
||||
help=_('Choose columns to be searched when not using prefixes, '
|
||||
'as for example, when searching for Redd instead of '
|
||||
'title:Red. Enter a list of search/lookup names '
|
||||
'separated by commas. Only takes effect if you set the option '
|
||||
'to limit search columns above.'))
|
||||
|
||||
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
|
||||
return c
|
||||
|
||||
prefs = ConfigProxy(_prefs())
|
||||
if prefs['installation_uuid'] is None:
|
||||
import uuid
|
||||
prefs['installation_uuid'] = str(uuid.uuid4())
|
||||
|
||||
# Read tweaks
|
||||
def read_raw_tweaks():
|
||||
make_config_dir()
|
||||
default_tweaks = P('default_tweaks.py', data=True,
|
||||
allow_user_override=False)
|
||||
tweaks_file = os.path.join(config_dir, 'tweaks.py')
|
||||
if not os.path.exists(tweaks_file):
|
||||
with open(tweaks_file, 'wb') as f:
|
||||
f.write(default_tweaks)
|
||||
with open(tweaks_file, 'rb') as f:
|
||||
return default_tweaks, f.read()
|
||||
|
||||
def read_tweaks():
|
||||
default_tweaks, tweaks = read_raw_tweaks()
|
||||
l, g = {}, {}
|
||||
try:
|
||||
exec tweaks in g, l
|
||||
except:
|
||||
import traceback
|
||||
print 'Failed to load custom tweaks file'
|
||||
traceback.print_exc()
|
||||
dl, dg = {}, {}
|
||||
exec default_tweaks in dg, dl
|
||||
dl.update(l)
|
||||
return dl
|
||||
|
||||
def write_tweaks(raw):
|
||||
make_config_dir()
|
||||
tweaks_file = os.path.join(config_dir, 'tweaks.py')
|
||||
with open(tweaks_file, 'wb') as f:
|
||||
f.write(raw)
|
||||
|
||||
|
||||
tweaks = read_tweaks()
|
||||
|
||||
|
@ -549,8 +549,22 @@ class BuiltinCapitalize(BuiltinFormatterFunction):
|
||||
def evaluate(self, formatter, kwargs, mi, locals, val):
|
||||
return capitalize(val)
|
||||
|
||||
class BuiltinBooksize(BuiltinFormatterFunction):
|
||||
name = 'booksize'
|
||||
arg_count = 0
|
||||
doc = _('booksize() -- return value of the field capitalized')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals):
|
||||
if mi.book_size is not None:
|
||||
try:
|
||||
return str(mi.book_size)
|
||||
except:
|
||||
pass
|
||||
return ''
|
||||
|
||||
builtin_add = BuiltinAdd()
|
||||
builtin_assign = BuiltinAssign()
|
||||
builtin_booksize = BuiltinBooksize()
|
||||
builtin_capitalize = BuiltinCapitalize()
|
||||
builtin_cmp = BuiltinCmp()
|
||||
builtin_contains = BuiltinContains()
|
||||
|
@ -10,7 +10,7 @@ import sys
|
||||
from functools import partial
|
||||
|
||||
from calibre.constants import plugins
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre.utils.config_base import tweaks
|
||||
|
||||
_icu = _collator = None
|
||||
_locale = None
|
||||
|
@ -17,7 +17,7 @@ from binascii import hexlify
|
||||
from calibre.utils.ipc.launch import Worker
|
||||
from calibre.utils.ipc.worker import PARALLEL_FUNCS
|
||||
from calibre import detect_ncpus as cpu_count
|
||||
from calibre.constants import iswindows
|
||||
from calibre.constants import iswindows, DEBUG
|
||||
from calibre.ptempfile import base_dir
|
||||
|
||||
_counter = 0
|
||||
@ -106,13 +106,14 @@ class Server(Thread):
|
||||
self.add_jobs_queue, self.changed_jobs_queue = Queue(), Queue()
|
||||
self.kill_queue = Queue()
|
||||
self.waiting_jobs = []
|
||||
self.pool, self.workers = deque(), deque()
|
||||
self.workers = deque()
|
||||
self.launched_worker_count = 0
|
||||
self._worker_launch_lock = RLock()
|
||||
|
||||
self.start()
|
||||
|
||||
def launch_worker(self, gui=False, redirect_output=None):
|
||||
start = time.time()
|
||||
with self._worker_launch_lock:
|
||||
self.launched_worker_count += 1
|
||||
id = self.launched_worker_count
|
||||
@ -136,6 +137,8 @@ class Server(Thread):
|
||||
break
|
||||
if isinstance(cw, basestring):
|
||||
raise CriticalError('Failed to launch worker process:\n'+cw)
|
||||
if DEBUG:
|
||||
print 'Worker Launch took:', time.time() - start
|
||||
return cw
|
||||
|
||||
def do_launch(self, env, gui, redirect_output, rfile):
|
||||
@ -204,13 +207,6 @@ class Server(Thread):
|
||||
job.duration = time.time() - job.start_time
|
||||
self.changed_jobs_queue.put(job)
|
||||
|
||||
# Start new workers
|
||||
if len(self.pool) + len(self.workers) < self.pool_size:
|
||||
try:
|
||||
self.pool.append(self.launch_worker())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Start waiting jobs
|
||||
sj = self.suitable_waiting_job()
|
||||
if sj is not None:
|
||||
@ -222,7 +218,7 @@ class Server(Thread):
|
||||
job.killed = job.failed = True
|
||||
job.result = None
|
||||
else:
|
||||
worker = self.pool.pop()
|
||||
worker = self.launch_worker()
|
||||
worker.start_job(job)
|
||||
self.workers.append(worker)
|
||||
job.log_path = worker.log_path
|
||||
@ -236,7 +232,7 @@ class Server(Thread):
|
||||
break
|
||||
|
||||
def suitable_waiting_job(self):
|
||||
available_workers = len(self.pool)
|
||||
available_workers = self.pool_size - len(self.workers)
|
||||
for worker in self.workers:
|
||||
job = worker.job
|
||||
if job.core_usage == -1:
|
||||
@ -302,11 +298,6 @@ class Server(Thread):
|
||||
worker.kill()
|
||||
except:
|
||||
pass
|
||||
for worker in list(self.pool):
|
||||
try:
|
||||
worker.kill()
|
||||
except:
|
||||
pass
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
@ -24,7 +24,7 @@ def available_translations():
|
||||
|
||||
def get_lang():
|
||||
'Try to figure out what language to display the interface in'
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.utils.config_base import prefs
|
||||
lang = prefs['language']
|
||||
lang = os.environ.get('CALIBRE_OVERRIDE_LANG', lang)
|
||||
if lang is not None:
|
||||
|
Loading…
x
Reference in New Issue
Block a user