merge from trunk

This commit is contained in:
Charles Haley 2013-05-02 19:49:17 +02:00
commit 2ea73468b8
136 changed files with 64243 additions and 42951 deletions

View File

@ -67,8 +67,12 @@ and you will most likely get help from one of |app|'s many developers.
Getting the code
------------------
|app| uses `Bazaar <http://bazaar-vcs.org/>`_, a distributed version control system. Bazaar is available on all the platforms |app| supports.
After installing Bazaar, you can get the |app| source code with the command::
You can get the |app| source code in two ways, using a version control system or
directly downloading a `tarball <http://status.calibre-ebook.com/dist/src>`_.
|app| uses `Bazaar <http://bazaar-vcs.org/>`_, a distributed version control
system. Bazaar is available on all the platforms |app| supports. After
installing Bazaar, you can get the |app| source code with the command::
bzr branch lp:calibre
@ -124,6 +128,8 @@ discuss them in the forum or contact Kovid directly (his email address is all ov
Windows development environment
---------------------------------
.. note:: You must also get the |app| source code separately as described above.
Install |app| normally, using the Windows installer. Then open a Command Prompt and change to
the previously checked out |app| code directory. For example::
@ -153,6 +159,8 @@ near the top of the file. Now run the command :command:`calibredb`. The very fir
OS X development environment
------------------------------
.. note:: You must also get the |app| source code separately as described above.
Install |app| normally using the provided .dmg. Then open a Terminal and change to
the previously checked out |app| code directory, for example::
@ -183,6 +191,8 @@ window, indicating that you are running from source.
Linux development environment
------------------------------
.. note:: You must also get the |app| source code separately as described above.
|app| is primarily developed on Linux. You have two choices in setting up the development environment. You can install the
|app| binary as normal and use that as a runtime environment to do your development. This approach is similar to that
used in Windows and OS X. Alternatively, you can install |app| from source. Instructions for setting up a development

View File

@ -1,68 +1,63 @@
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
from collections import OrderedDict
class TNR(BasicNewsRecipe):
title = 'The New Republic'
__author__ = 'Rick Shang'
description = 'The New Republic is a journal of opinion with an emphasis on politics and domestic and international affairs. It carries feature articles by staff and contributing editors. The second half of each issue is devoted to book and the arts, theater, motion pictures, music and art.'
language = 'en'
category = 'news'
encoding = 'UTF-8'
remove_tags = [dict(attrs={'class':['print-logo','print-site_name','print-hr']})]
no_javascript = True
no_stylesheets = True
def parse_index(self):
#Go to the issue
soup0 = self.index_to_soup('http://www.tnr.com/magazine-issues')
issue = soup0.find('div',attrs={'id':'current_issue'})
#Find date
date = self.tag_to_string(issue.find('div',attrs={'class':'date'})).strip()
self.timefmt = u' [%s]'%date
#Go to the main body
current_issue_url = 'http://www.tnr.com' + issue.find('a', href=True)['href']
soup = self.index_to_soup(current_issue_url)
div = soup.find ('div', attrs={'class':'article_detail_body'})
#Find cover
self.cover_url = div.find('img',src=True)['src']
feeds = OrderedDict()
section_title = ''
subsection_title = ''
for post in div.findAll('p'):
articles = []
em=post.find('em')
b=post.find('b')
a=post.find('a',href=True)
p=post.find('img', src=True)
#Find cover
if p is not None:
self.cover_url = p['src'].strip()
if em is not None:
section_title = self.tag_to_string(em).strip()
subsection_title = ''
elif b is not None:
subsection_title=self.tag_to_string(b).strip()
elif a is not None:
prefix = (subsection_title+': ') if subsection_title else ''
url=re.sub('www.tnr.com','www.tnr.com/print', a['href'])
author=re.sub('.*by\s', '', self.tag_to_string(post), re.DOTALL)
title=prefix + self.tag_to_string(a).strip()+ u' (%s)'%author
articles.append({'title':title, 'url':url, 'description':'', 'date':''})
if articles:
if section_title not in feeds:
feeds[section_title] = []
feeds[section_title] += articles
ans = [(key, val) for key, val in feeds.iteritems()]
return ans
import re
from calibre.web.feeds.recipes import BasicNewsRecipe
class TNR(BasicNewsRecipe):
title = 'The New Republic'
__author__ = 'Krittika Goyal'
description = '''The New Republic is a journal of opinion with an emphasis
on politics and domestic and international affairs. It carries feature
articles by staff and contributing editors. The second half of each issue
is devoted to book and the arts, theater, motion pictures, music and art.'''
language = 'en'
encoding = 'UTF-8'
needs_subscription = True
preprocess_regexps = [
(re.compile(r'<!--.*?-->', re.DOTALL), lambda m: ''),
(re.compile(r'<script.*?</script>', re.DOTALL), lambda m: ''),
]
def get_browser(self):
br = BasicNewsRecipe.get_browser(self)
br.open('http://www.newrepublic.com/user')
br.select_form(nr=1)
try:
br['user'] = self.username
except:
br['name'] = self.username
br['pass'] = self.password
self.log('Logging in...')
raw = br.submit().read()
if 'SIGN OUT' not in raw:
raise ValueError('Failed to log in to tnr.com, check your username and password')
self.log('Logged in successfully')
return br
def parse_index(self):
raw = self.index_to_soup('http://www.newrepublic.com/current-issue', raw=True)
# raw = self.index_to_soup(open('/t/raw.html').read().decode('utf-8'), raw=True)
for pat, sub in self.preprocess_regexps:
raw = pat.sub(sub, raw)
soup = self.index_to_soup(raw)
feed_title = 'The New Republic Magazine Articles'
articles = []
for div in soup.findAll('div', attrs={'class':lambda x: x and 'field-item' in x.split()}):
a = div.find('a', href=True, attrs={'class':lambda x: x != 'author'})
if a is not None:
art_title = self.tag_to_string(a)
url = a.get('href')
num = re.search(r'/(\d+)/', url)
if num is not None:
art = num.group(1)
url = 'http://www.newrepublic.com/node/%s/print'%art
self.log.info('\tFound article:', art_title, 'at', url)
article = {'title':art_title, 'url':url, 'description':'', 'date':''}
articles.append(article)
return [(feed_title, articles)]

View File

@ -10,13 +10,13 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2013-04-24 08:47+0000\n"
"PO-Revision-Date: 2013-04-29 11:25+0000\n"
"Last-Translator: Tadeáš Pařík <tadeas.parik@gmail.com>\n"
"Language-Team: Czech <debian-l10n-czech@lists.debian.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-04-25 05:20+0000\n"
"X-Launchpad-Export-Date: 2013-04-30 05:16+0000\n"
"X-Generator: Launchpad (build 16580)\n"
"Language: cs\n"
@ -4354,171 +4354,171 @@ msgstr "pirlatapa"
#. name for bxj
msgid "Bayungu"
msgstr ""
msgstr "bayungu"
#. name for bxk
msgid "Bukusu"
msgstr ""
msgstr "bukusu"
#. name for bxl
msgid "Jalkunan"
msgstr ""
msgstr "jalkunan"
#. name for bxm
msgid "Buriat; Mongolia"
msgstr ""
msgstr "buriat; Mongolsko"
#. name for bxn
msgid "Burduna"
msgstr ""
msgstr "burduna"
#. name for bxo
msgid "Barikanchi"
msgstr ""
msgstr "barikanchi"
#. name for bxp
msgid "Bebil"
msgstr ""
msgstr "bebil"
#. name for bxq
msgid "Beele"
msgstr ""
msgstr "beele"
#. name for bxr
msgid "Buriat; Russia"
msgstr ""
msgstr "buriat; Rusko"
#. name for bxs
msgid "Busam"
msgstr ""
msgstr "busam"
#. name for bxu
msgid "Buriat; China"
msgstr ""
msgstr "buriat; Čína"
#. name for bxv
msgid "Berakou"
msgstr ""
msgstr "berakou"
#. name for bxw
msgid "Bankagooma"
msgstr ""
msgstr "bankagooma"
#. name for bxx
msgid "Borna (Democratic Republic of Congo)"
msgstr ""
msgstr "borna (Demokratická republika Kongo)"
#. name for bxz
msgid "Binahari"
msgstr ""
msgstr "binahari"
#. name for bya
msgid "Batak"
msgstr ""
msgstr "batak"
#. name for byb
msgid "Bikya"
msgstr ""
msgstr "bikya"
#. name for byc
msgid "Ubaghara"
msgstr ""
msgstr "ubaghara"
#. name for byd
msgid "Benyadu'"
msgstr ""
msgstr "benyadu'"
#. name for bye
msgid "Pouye"
msgstr ""
msgstr "pouye"
#. name for byf
msgid "Bete"
msgstr ""
msgstr "bete"
#. name for byg
msgid "Baygo"
msgstr ""
msgstr "baygo"
#. name for byh
msgid "Bhujel"
msgstr ""
msgstr "bhujel"
#. name for byi
msgid "Buyu"
msgstr ""
msgstr "buyu"
#. name for byj
msgid "Bina (Nigeria)"
msgstr ""
msgstr "bina (Nigérie)"
#. name for byk
msgid "Biao"
msgstr ""
msgstr "biao"
#. name for byl
msgid "Bayono"
msgstr ""
msgstr "bayono"
#. name for bym
msgid "Bidyara"
msgstr ""
msgstr "bidyara"
#. name for byn
msgid "Bilin"
msgstr ""
msgstr "bilin"
#. name for byo
msgid "Biyo"
msgstr ""
msgstr "biyo"
#. name for byp
msgid "Bumaji"
msgstr ""
msgstr "bumaji"
#. name for byq
msgid "Basay"
msgstr ""
msgstr "basay"
#. name for byr
msgid "Baruya"
msgstr ""
msgstr "baruya"
#. name for bys
msgid "Burak"
msgstr ""
msgstr "burak"
#. name for byt
msgid "Berti"
msgstr ""
msgstr "berti"
#. name for byv
msgid "Medumba"
msgstr ""
msgstr "medumba"
#. name for byw
msgid "Belhariya"
msgstr ""
msgstr "belhariya"
#. name for byx
msgid "Qaqet"
msgstr ""
msgstr "qaqet"
#. name for byy
msgid "Buya"
msgstr ""
msgstr "buya"
#. name for byz
msgid "Banaro"
msgstr ""
msgstr "banaro"
#. name for bza
msgid "Bandi"
msgstr ""
msgstr "bandi"
#. name for bzb
msgid "Andio"
msgstr ""
msgstr "andio"
#. name for bzc
msgid "Malagasy; Southern Betsimisaraka"
@ -4526,27 +4526,27 @@ msgstr ""
#. name for bzd
msgid "Bribri"
msgstr ""
msgstr "bribri"
#. name for bze
msgid "Bozo; Jenaama"
msgstr ""
msgstr "bozo; Jenaama"
#. name for bzf
msgid "Boikin"
msgstr ""
msgstr "Boikin"
#. name for bzg
msgid "Babuza"
msgstr ""
msgstr "babuza"
#. name for bzh
msgid "Buang; Mapos"
msgstr ""
msgstr "buang; mapos"
#. name for bzi
msgid "Bisu"
msgstr ""
msgstr "bisu"
#. name for bzj
msgid "Kriol English; Belize"
@ -4554,7 +4554,7 @@ msgstr ""
#. name for bzk
msgid "Creole English; Nicaragua"
msgstr ""
msgstr "kreolská angličtina; Nikaragua"
#. name for bzl
msgid "Boano (Sulawesi)"
@ -4562,27 +4562,27 @@ msgstr ""
#. name for bzm
msgid "Bolondo"
msgstr ""
msgstr "bolondo"
#. name for bzn
msgid "Boano (Maluku)"
msgstr ""
msgstr "boano (Maluku)"
#. name for bzo
msgid "Bozaba"
msgstr ""
msgstr "bozaba"
#. name for bzp
msgid "Kemberano"
msgstr ""
msgstr "kemberano"
#. name for bzq
msgid "Buli (Indonesia)"
msgstr ""
msgstr "buli (Indonésie)"
#. name for bzr
msgid "Biri"
msgstr ""
msgstr "biri"
#. name for bzs
msgid "Brazilian Sign Language"
@ -4590,43 +4590,43 @@ msgstr "brazilský znakový jazyk"
#. name for bzt
msgid "Brithenig"
msgstr ""
msgstr "brithenig"
#. name for bzu
msgid "Burmeso"
msgstr ""
msgstr "burmeso"
#. name for bzv
msgid "Bebe"
msgstr ""
msgstr "bebe"
#. name for bzw
msgid "Basa (Nigeria)"
msgstr ""
msgstr "basa (Nigérie)"
#. name for bzx
msgid "Bozo; Kɛlɛngaxo"
msgstr ""
msgstr "bozo; Kɛlɛngaxo"
#. name for bzy
msgid "Obanliku"
msgstr ""
msgstr "obanliku"
#. name for bzz
msgid "Evant"
msgstr ""
msgstr "evant"
#. name for caa
msgid "Chortí"
msgstr ""
msgstr "chortí"
#. name for cab
msgid "Garifuna"
msgstr ""
msgstr "garifuna"
#. name for cac
msgid "Chuj"
msgstr ""
msgstr "chuj"
#. name for cad
msgid "Caddo"
@ -4634,59 +4634,59 @@ msgstr "caddo"
#. name for cae
msgid "Lehar"
msgstr ""
msgstr "lehar"
#. name for caf
msgid "Carrier; Southern"
msgstr ""
msgstr "carrier; jižní"
#. name for cag
msgid "Nivaclé"
msgstr ""
msgstr "nivaclé"
#. name for cah
msgid "Cahuarano"
msgstr ""
msgstr "cahuarano"
#. name for caj
msgid "Chané"
msgstr ""
msgstr "chané"
#. name for cak
msgid "Kaqchikel"
msgstr ""
msgstr "kaqchikel"
#. name for cal
msgid "Carolinian"
msgstr ""
msgstr "carolinian"
#. name for cam
msgid "Cemuhî"
msgstr ""
msgstr "cemuhî"
#. name for can
msgid "Chambri"
msgstr ""
msgstr "chambri"
#. name for cao
msgid "Chácobo"
msgstr ""
msgstr "chácobo"
#. name for cap
msgid "Chipaya"
msgstr ""
msgstr "chipaya"
#. name for caq
msgid "Nicobarese; Car"
msgstr ""
msgstr "nicobarese; Car"
#. name for car
msgid "Carib; Galibi"
msgstr ""
msgstr "carib; Galibi"
#. name for cas
msgid "Tsimané"
msgstr ""
msgstr "tsimané"
#. name for cat
msgid "Catalan"
@ -4694,71 +4694,71 @@ msgstr "katalánština"
#. name for cav
msgid "Cavineña"
msgstr ""
msgstr "cavineña"
#. name for caw
msgid "Callawalla"
msgstr ""
msgstr "callawalla"
#. name for cax
msgid "Chiquitano"
msgstr ""
msgstr "chiquitano"
#. name for cay
msgid "Cayuga"
msgstr ""
msgstr "cayuga"
#. name for caz
msgid "Canichana"
msgstr ""
msgstr "canichana"
#. name for cbb
msgid "Cabiyarí"
msgstr ""
msgstr "cabiyarí"
#. name for cbc
msgid "Carapana"
msgstr ""
msgstr "carapana"
#. name for cbd
msgid "Carijona"
msgstr ""
msgstr "carijona"
#. name for cbe
msgid "Chipiajes"
msgstr ""
msgstr "chipiajes"
#. name for cbg
msgid "Chimila"
msgstr ""
msgstr "chimila"
#. name for cbh
msgid "Cagua"
msgstr ""
msgstr "cagua"
#. name for cbi
msgid "Chachi"
msgstr ""
msgstr "chachi"
#. name for cbj
msgid "Ede Cabe"
msgstr ""
msgstr "ede cabe"
#. name for cbk
msgid "Chavacano"
msgstr ""
msgstr "chavacano"
#. name for cbl
msgid "Chin; Bualkhaw"
msgstr ""
msgstr "chin; Bualkhaw"
#. name for cbn
msgid "Nyahkur"
msgstr ""
msgstr "nyahkur"
#. name for cbo
msgid "Izora"
msgstr ""
msgstr "izora"
#. name for cbr
msgid "Cashibo-Cacataibo"
@ -4782,15 +4782,15 @@ msgstr ""
#. name for cbw
msgid "Kinabalian"
msgstr ""
msgstr "kinabalian"
#. name for cby
msgid "Carabayo"
msgstr ""
msgstr "carabayo"
#. name for cca
msgid "Cauca"
msgstr ""
msgstr "cauca"
#. name for ccc
msgid "Chamicuro"
@ -4802,19 +4802,19 @@ msgstr ""
#. name for cce
msgid "Chopi"
msgstr ""
msgstr "chopi"
#. name for ccg
msgid "Daka; Samba"
msgstr ""
msgstr "daka; Samba"
#. name for cch
msgid "Atsam"
msgstr ""
msgstr "atsam"
#. name for ccj
msgid "Kasanga"
msgstr ""
msgstr "kasanga"
#. name for ccl
msgid "Cutchi-Swahili"
@ -4822,7 +4822,7 @@ msgstr ""
#. name for ccm
msgid "Creole Malay; Malaccan"
msgstr ""
msgstr "kreolská malajština; Malaccan"
#. name for cco
msgid "Chinantec; Comaltepec"
@ -4834,11 +4834,11 @@ msgstr ""
#. name for ccq
msgid "Chaungtha"
msgstr ""
msgstr "chaungtha"
#. name for ccr
msgid "Cacaopera"
msgstr ""
msgstr "cacaopera"
#. name for cda
msgid "Choni"
@ -30790,11 +30790,11 @@ msgstr ""
#. name for zua
msgid "Zeem"
msgstr ""
msgstr "zeem"
#. name for zuh
msgid "Tokano"
msgstr ""
msgstr "tokano"
#. name for zul
msgid "Zulu"
@ -30802,7 +30802,7 @@ msgstr "Zulu"
#. name for zum
msgid "Kumzari"
msgstr ""
msgstr "kumzari"
#. name for zun
msgid "Zuni"
@ -30810,40 +30810,40 @@ msgstr "zunijština"
#. name for zuy
msgid "Zumaya"
msgstr ""
msgstr "zumaya"
#. name for zwa
msgid "Zay"
msgstr ""
msgstr "zay"
#. name for zxx
msgid "No linguistic content"
msgstr ""
msgstr "bez lingvistického obsahu"
#. name for zyb
msgid "Zhuang; Yongbei"
msgstr ""
msgstr "zhuang; Yongbei"
#. name for zyg
msgid "Zhuang; Yang"
msgstr ""
msgstr "zhuang; Yang"
#. name for zyj
msgid "Zhuang; Youjiang"
msgstr ""
msgstr "zhuang; Youjiang"
#. name for zyn
msgid "Zhuang; Yongnan"
msgstr ""
msgstr "zhuang; Youjiang"
#. name for zyp
msgid "Zyphe"
msgstr ""
msgstr "zyphe"
#. name for zza
msgid "Zaza"
msgstr ""
msgstr "zaza"
#. name for zzj
msgid "Zhuang; Zuojiang"
msgstr ""
msgstr "zhuang; Zuojiang"

File diff suppressed because it is too large Load Diff

View File

@ -30,23 +30,23 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2011-09-27 16:53+0000\n"
"Last-Translator: Christian Rose <menthos@menthos.com>\n"
"PO-Revision-Date: 2013-04-28 21:03+0000\n"
"Last-Translator: Merarom <Unknown>\n"
"Language-Team: Swedish <sv@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-11-26 05:39+0000\n"
"X-Generator: Launchpad (build 14381)\n"
"X-Launchpad-Export-Date: 2013-04-29 04:38+0000\n"
"X-Generator: Launchpad (build 16580)\n"
"Language: sv\n"
#. name for aaa
msgid "Ghotuo"
msgstr ""
msgstr "Ghotuo"
#. name for aab
msgid "Alumu-Tesu"
msgstr ""
msgstr "Alumu-Tesu"
#. name for aac
msgid "Ari"
@ -58,7 +58,7 @@ msgstr ""
#. name for aae
msgid "Albanian; Arbëreshë"
msgstr ""
msgstr "Albanska; Arbëreshë"
#. name for aaf
msgid "Aranadan"
@ -78,7 +78,7 @@ msgstr ""
#. name for aak
msgid "Ankave"
msgstr ""
msgstr "Ankave"
#. name for aal
msgid "Afade"
@ -94,7 +94,7 @@ msgstr ""
#. name for aao
msgid "Arabic; Algerian Saharan"
msgstr ""
msgstr "Arabiska;algeriska Sahara"
#. name for aap
msgid "Arára; Pará"
@ -114,7 +114,7 @@ msgstr ""
#. name for aat
msgid "Albanian; Arvanitika"
msgstr ""
msgstr "Albanska; Arvanitika"
#. name for aau
msgid "Abau"
@ -218,7 +218,7 @@ msgstr ""
#. name for abv
msgid "Arabic; Baharna"
msgstr ""
msgstr "Arabiska; Baharna"
#. name for abw
msgid "Pal"
@ -311,7 +311,7 @@ msgstr ""
#. name for acw
msgid "Arabic; Hijazi"
msgstr ""
msgstr "Arabiska; Hijazi"
#. name for acx
msgid "Arabic; Omani"
@ -319,7 +319,7 @@ msgstr ""
#. name for acy
msgid "Arabic; Cypriot"
msgstr ""
msgstr "Arabiska; Cypriotiska"
#. name for acz
msgid "Acheron"
@ -343,7 +343,7 @@ msgstr ""
#. name for adf
msgid "Arabic; Dhofari"
msgstr ""
msgstr "Arabiska; Dhofari"
#. name for adg
msgid "Andegerebinha"
@ -419,11 +419,11 @@ msgstr ""
#. name for aeb
msgid "Arabic; Tunisian"
msgstr ""
msgstr "Arabiska; Tunisiska"
#. name for aec
msgid "Arabic; Saidi"
msgstr ""
msgstr "Arabiska,; Saidi"
#. name for aed
msgid "Argentine Sign Language"
@ -479,7 +479,7 @@ msgstr ""
#. name for afb
msgid "Arabic; Gulf"
msgstr ""
msgstr "Arabiska,; Gulf"
#. name for afd
msgid "Andai"
@ -803,7 +803,7 @@ msgstr ""
#. name for ajt
msgid "Arabic; Judeo-Tunisian"
msgstr ""
msgstr "Arabiska; judisk-tunisiska"
#. name for aju
msgid "Arabic; Judeo-Moroccan"
@ -963,7 +963,7 @@ msgstr ""
#. name for aln
msgid "Albanian; Gheg"
msgstr ""
msgstr "Albanska; Gheg"
#. name for alo
msgid "Larike-Wakasihu"
@ -9431,7 +9431,7 @@ msgstr ""
#. name for hlb
msgid "Halbi"
msgstr ""
msgstr "Halbi"
#. name for hld
msgid "Halang Doan"

View File

@ -68,7 +68,7 @@ class TXT2TXTZ(FileTypePlugin):
images.append(path)
# Markdown inline
for m in re.finditer(ur'(?mu)\!\[([^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*)\]\s*\((?P<path>[^\)]*)\)', txt):
for m in re.finditer(ur'(?mu)\!\[([^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*)\]\s*\((?P<path>[^\)]*)\)', txt): # noqa
path = m.group('path')
if path and not os.path.isabs(path) and guess_type(path)[0] in OEB_IMAGES and os.path.exists(os.path.join(base_dir, path)):
images.append(path)
@ -78,7 +78,7 @@ class TXT2TXTZ(FileTypePlugin):
for m in re.finditer(ur'(?mu)^(\ ?\ ?\ ?)\[(?P<id>[^\]]*)\]:\s*(?P<path>[^\s]*)$', txt):
if m.group('id') and m.group('path'):
refs[m.group('id')] = m.group('path')
for m in re.finditer(ur'(?mu)\!\[([^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*)\]\s*\[(?P<id>[^\]]*)\]', txt):
for m in re.finditer(ur'(?mu)\!\[([^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*(\[[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*\])*[^\]\[]*)\]\s*\[(?P<id>[^\]]*)\]', txt): # noqa
path = refs.get(m.group('id'), None)
if path and not os.path.isabs(path) and guess_type(path)[0] in OEB_IMAGES and os.path.exists(os.path.join(base_dir, path)):
images.append(path)
@ -414,7 +414,7 @@ class ZipMetadataReader(MetadataReaderPlugin):
from calibre.ebooks.metadata.zip import get_metadata
return get_metadata(stream)
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
plugins += [x for x in list(locals().values()) if isinstance(x, type) and
x.__name__.endswith('MetadataReader')]
# }}}
@ -527,7 +527,7 @@ class TXTZMetadataWriter(MetadataWriterPlugin):
from calibre.ebooks.metadata.extz import set_metadata
set_metadata(stream, mi)
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
plugins += [x for x in list(locals().values()) if isinstance(x, type) and
x.__name__.endswith('MetadataWriter')]
# }}}
@ -630,7 +630,6 @@ plugins += input_profiles + output_profiles
# }}}
# Device driver plugins {{{
from calibre.devices.apple.driver import ITUNES
from calibre.devices.hanlin.driver import HANLINV3, HANLINV5, BOOX, SPECTRA
from calibre.devices.blackberry.driver import BLACKBERRY, PLAYBOOK
from calibre.devices.cybook.driver import CYBOOK, ORIZON
@ -644,6 +643,7 @@ from calibre.devices.jetbook.driver import (JETBOOK, MIBUK, JETBOOK_MINI,
JETBOOK_COLOR)
from calibre.devices.kindle.driver import (KINDLE, KINDLE2, KINDLE_DX,
KINDLE_FIRE)
from calibre.devices.apple.driver import ITUNES
from calibre.devices.nook.driver import NOOK, NOOK_COLOR
from calibre.devices.prs505.driver import PRS505
from calibre.devices.prst1.driver import PRST1
@ -1263,7 +1263,7 @@ class StoreAmazonUKKindleStore(StoreBase):
class StoreArchiveOrgStore(StoreBase):
name = 'Archive.org'
description = u'An Internet library offering permanent access for researchers, historians, scholars, people with disabilities, and the general public to historical collections that exist in digital format.'
description = u'An Internet library offering permanent access for researchers, historians, scholars, people with disabilities, and the general public to historical collections that exist in digital format.' # noqa
actual_plugin = 'calibre.gui2.store.stores.archive_org_plugin:ArchiveOrgStore'
drm_free_only = True
@ -1290,7 +1290,7 @@ class StoreBNStore(StoreBase):
class StoreBeamEBooksDEStore(StoreBase):
name = 'Beam EBooks DE'
author = 'Charles Haley'
description = u'Bei uns finden Sie: Tausende deutschsprachige eBooks; Alle eBooks ohne hartes DRM; PDF, ePub und Mobipocket Format; Sofortige Verfügbarkeit - 24 Stunden am Tag; Günstige Preise; eBooks für viele Lesegeräte, PC,Mac und Smartphones; Viele Gratis eBooks'
description = u'Bei uns finden Sie: Tausende deutschsprachige eBooks; Alle eBooks ohne hartes DRM; PDF, ePub und Mobipocket Format; Sofortige Verfügbarkeit - 24 Stunden am Tag; Günstige Preise; eBooks für viele Lesegeräte, PC,Mac und Smartphones; Viele Gratis eBooks' # noqa
actual_plugin = 'calibre.gui2.store.stores.beam_ebooks_de_plugin:BeamEBooksDEStore'
drm_free_only = True
@ -1310,7 +1310,7 @@ class StoreBiblioStore(StoreBase):
class StoreBookotekaStore(StoreBase):
name = 'Bookoteka'
author = u'Tomasz Długosz'
description = u'E-booki w Bookotece dostępne są w formacie EPUB oraz PDF. Publikacje sprzedawane w Bookotece są objęte prawami autorskimi. Zobowiązaliśmy się chronić te prawa, ale bez ograniczania dostępu do książki użytkownikowi, który nabył ją w legalny sposób. Dlatego też Bookoteka stosuje tak zwany „watermarking transakcyjny” czyli swego rodzaju znaki wodne.'
description = u'E-booki w Bookotece dostępne są w formacie EPUB oraz PDF. Publikacje sprzedawane w Bookotece są objęte prawami autorskimi. Zobowiązaliśmy się chronić te prawa, ale bez ograniczania dostępu do książki użytkownikowi, który nabył ją w legalny sposób. Dlatego też Bookoteka stosuje tak zwany „watermarking transakcyjny” czyli swego rodzaju znaki wodne.' # noqa
actual_plugin = 'calibre.gui2.store.stores.bookoteka_plugin:BookotekaStore'
drm_free_only = True
@ -1329,7 +1329,7 @@ class StoreChitankaStore(StoreBase):
class StoreDieselEbooksStore(StoreBase):
name = 'Diesel eBooks'
description = u'Instant access to over 2.4 million titles from hundreds of publishers including Harlequin, HarperCollins, John Wiley & Sons, McGraw-Hill, Simon & Schuster and Random House.'
description = u'Instant access to over 2.4 million titles from hundreds of publishers including Harlequin, HarperCollins, John Wiley & Sons, McGraw-Hill, Simon & Schuster and Random House.' # noqa
actual_plugin = 'calibre.gui2.store.stores.diesel_ebooks_plugin:DieselEbooksStore'
headquarters = 'US'
@ -1358,7 +1358,7 @@ class StoreEbookpointStore(StoreBase):
class StoreEbookscomStore(StoreBase):
name = 'eBooks.com'
description = u'Sells books in multiple electronic formats in all categories. Technical infrastructure is cutting edge, robust and scalable, with servers in the US and Europe.'
description = u'Sells books in multiple electronic formats in all categories. Technical infrastructure is cutting edge, robust and scalable, with servers in the US and Europe.' # noqa
actual_plugin = 'calibre.gui2.store.stores.ebooks_com_plugin:EbookscomStore'
headquarters = 'US'
@ -1386,7 +1386,7 @@ class StoreEbooksGratuitsStore(StoreBase):
class StoreEHarlequinStore(StoreBase):
name = 'eHarlequin'
description = u'A global leader in series romance and one of the world\'s leading publishers of books for women. Offers women a broad range of reading from romance to bestseller fiction, from young adult novels to erotic literature, from nonfiction to fantasy, from African-American novels to inspirational romance, and more.'
description = u'A global leader in series romance and one of the world\'s leading publishers of books for women. Offers women a broad range of reading from romance to bestseller fiction, from young adult novels to erotic literature, from nonfiction to fantasy, from African-American novels to inspirational romance, and more.' # noqa
actual_plugin = 'calibre.gui2.store.stores.eharlequin_plugin:EHarlequinStore'
headquarters = 'CA'
@ -1406,7 +1406,7 @@ class StoreEKnigiStore(StoreBase):
class StoreEmpikStore(StoreBase):
name = 'Empik'
author = u'Tomasz Długosz'
description = u'Empik to marka o unikalnym dziedzictwie i legendarne miejsce, dawne “okno na świat”. Jest obecna w polskim krajobrazie kulturalnym od 60 lat (wcześniej jako Kluby Międzynarodowej Prasy i Książki).'
description = u'Empik to marka o unikalnym dziedzictwie i legendarne miejsce, dawne “okno na świat”. Jest obecna w polskim krajobrazie kulturalnym od 60 lat (wcześniej jako Kluby Międzynarodowej Prasy i Książki).' # noqa
actual_plugin = 'calibre.gui2.store.stores.empik_plugin:EmpikStore'
headquarters = 'PL'
@ -1425,7 +1425,7 @@ class StoreEscapeMagazineStore(StoreBase):
class StoreFeedbooksStore(StoreBase):
name = 'Feedbooks'
description = u'Feedbooks is a cloud publishing and distribution service, connected to a large ecosystem of reading systems and social networks. Provides a variety of genres from independent and classic books.'
description = u'Feedbooks is a cloud publishing and distribution service, connected to a large ecosystem of reading systems and social networks. Provides a variety of genres from independent and classic books.' # noqa
actual_plugin = 'calibre.gui2.store.stores.feedbooks_plugin:FeedbooksStore'
headquarters = 'FR'
@ -1451,7 +1451,7 @@ class StoreGoogleBooksStore(StoreBase):
class StoreGutenbergStore(StoreBase):
name = 'Project Gutenberg'
description = u'The first producer of free ebooks. Free in the United States because their copyright has expired. They may not be free of copyright in other countries. Readers outside of the United States must check the copyright laws of their countries before downloading or redistributing our ebooks.'
description = u'The first producer of free ebooks. Free in the United States because their copyright has expired. They may not be free of copyright in other countries. Readers outside of the United States must check the copyright laws of their countries before downloading or redistributing our ebooks.' # noqa
actual_plugin = 'calibre.gui2.store.stores.gutenberg_plugin:GutenbergStore'
drm_free_only = True
@ -1460,7 +1460,7 @@ class StoreGutenbergStore(StoreBase):
class StoreKoboStore(StoreBase):
name = 'Kobo'
description = u'With over 2.3 million eBooks to browse we have engaged readers in over 200 countries in Kobo eReading. Our eBook listings include New York Times Bestsellers, award winners, classics and more!'
description = u'With over 2.3 million eBooks to browse we have engaged readers in over 200 countries in Kobo eReading. Our eBook listings include New York Times Bestsellers, award winners, classics and more!' # noqa
actual_plugin = 'calibre.gui2.store.stores.kobo_plugin:KoboStore'
headquarters = 'CA'
@ -1550,7 +1550,7 @@ class StoreNextoStore(StoreBase):
class StoreNookUKStore(StoreBase):
name = 'Nook UK'
author = 'John Schember'
description = u'Barnes & Noble S.à r.l, a subsidiary of Barnes & Noble, Inc., a leading retailer of content, digital media and educational products, is proud to bring the award-winning NOOK® reading experience and a leading digital bookstore to the UK.'
description = u'Barnes & Noble S.à r.l, a subsidiary of Barnes & Noble, Inc., a leading retailer of content, digital media and educational products, is proud to bring the award-winning NOOK® reading experience and a leading digital bookstore to the UK.' # noqa
actual_plugin = 'calibre.gui2.store.stores.nook_uk_plugin:NookUKStore'
headquarters = 'UK'
@ -1627,7 +1627,7 @@ class StoreVirtualoStore(StoreBase):
class StoreWaterstonesUKStore(StoreBase):
name = 'Waterstones UK'
author = 'Charles Haley'
description = u'Waterstone\'s mission is to be the leading Bookseller on the High Street and online providing customers the widest choice, great value and expert advice from a team passionate about Bookselling.'
description = u'Waterstone\'s mission is to be the leading Bookseller on the High Street and online providing customers the widest choice, great value and expert advice from a team passionate about Bookselling.' # noqa
actual_plugin = 'calibre.gui2.store.stores.waterstones_uk_plugin:WaterstonesUKStore'
headquarters = 'UK'
@ -1727,6 +1727,28 @@ plugins += [
if __name__ == '__main__':
# Test load speed
import subprocess, textwrap
try:
subprocess.check_call(['python', '-c', textwrap.dedent(
'''
import init_calibre # noqa
def doit():
import calibre.customize.builtins as b # noqa
def show_stats():
from pstats import Stats
s = Stats('/tmp/calibre_stats')
s.sort_stats('cumulative')
s.print_stats(30)
import cProfile
cProfile.run('doit()', '/tmp/calibre_stats')
show_stats()
'''
)])
except subprocess.CalledProcessError:
raise SystemExit(1)
try:
subprocess.check_call(['python', '-c', textwrap.dedent(
'''
@ -1739,7 +1761,10 @@ if __name__ == '__main__':
for x in ('lxml', 'calibre.ebooks.BeautifulSoup', 'uuid',
'calibre.utils.terminal', 'calibre.utils.magick', 'PIL', 'Image',
'sqlite3', 'mechanize', 'httplib', 'xml'):
'sqlite3', 'mechanize', 'httplib', 'xml', 'inspect', 'urllib',
'calibre.utils.date', 'calibre.utils.config', 'platform',
'calibre.utils.zipfile',
):
if x in sys.modules:
ret = 1
print (x, 'has been loaded by a plugin')

View File

@ -9,6 +9,50 @@ __docformat__ = 'restructuredtext en'
SPOOL_SIZE = 30*1024*1024
def _get_next_series_num_for_list(series_indices):
from calibre.utils.config_base import tweaks
from math import ceil, floor
if not series_indices:
if isinstance(tweaks['series_index_auto_increment'], (int, float)):
return float(tweaks['series_index_auto_increment'])
return 1.0
series_indices = [x[0] for x in series_indices]
if tweaks['series_index_auto_increment'] == 'next':
return floor(series_indices[-1]) + 1
if tweaks['series_index_auto_increment'] == 'first_free':
for i in xrange(1, 10000):
if i not in series_indices:
return i
# really shouldn't get here.
if tweaks['series_index_auto_increment'] == 'next_free':
for i in xrange(int(ceil(series_indices[0])), 10000):
if i not in series_indices:
return i
# really shouldn't get here.
if tweaks['series_index_auto_increment'] == 'last_free':
for i in xrange(int(ceil(series_indices[-1])), 0, -1):
if i not in series_indices:
return i
return series_indices[-1] + 1
if isinstance(tweaks['series_index_auto_increment'], (int, float)):
return float(tweaks['series_index_auto_increment'])
return 1.0
def _get_series_values(val):
import re
series_index_pat = re.compile(r'(.*)\s+\[([.0-9]+)\]$')
if not val:
return (val, None)
match = series_index_pat.match(val.strip())
if match is not None:
idx = match.group(2)
try:
idx = float(idx)
return (match.group(1).strip(), idx)
except:
pass
return (val, None)
'''
Rewrite of the calibre database backend.

View File

@ -25,6 +25,7 @@ from calibre.utils.config import to_json, from_json, prefs, tweaks
from calibre.utils.date import utcfromtimestamp, parse_date
from calibre.utils.filenames import (is_case_sensitive, samefile, hardlink_file, ascii_filename,
WindowsAtomicFolderMove)
from calibre.utils.magick.draw import save_cover_data_to
from calibre.utils.recycle_bin import delete_tree
from calibre.db.tables import (OneToOneTable, ManyToOneTable, ManyToManyTable,
SizeTable, FormatsTable, AuthorsTable, IdentifiersTable, PathTable,
@ -973,6 +974,23 @@ class DB(object):
return True
return False
def set_cover(self, book_id, path, data):
path = os.path.abspath(os.path.join(self.library_path, path))
if not os.path.exists(path):
os.makedirs(path)
path = os.path.join(path, 'cover.jpg')
if callable(getattr(data, 'save', None)):
from calibre.gui2 import pixmap_to_data
data = pixmap_to_data(data)
else:
if callable(getattr(data, 'read', None)):
data = data.read()
try:
save_cover_data_to(data, path)
except (IOError, OSError):
time.sleep(0.2)
save_cover_data_to(data, path)
def copy_format_to(self, book_id, fmt, fname, path, dest,
windows_atomic_move=None, use_hardlink=False):
path = self.format_abspath(book_id, fmt, fname, path)

View File

@ -22,6 +22,7 @@ from calibre.db.search import Search
from calibre.db.tables import VirtualTable
from calibre.db.write import get_series_values
from calibre.db.lazy import FormatMetadata, FormatsList
from calibre.ebooks.metadata import string_to_authors
from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.opf2 import metadata_to_opf
from calibre.ptempfile import (base_dir, PersistentTemporaryFile,
@ -669,7 +670,7 @@ class Cache(object):
self.dirtied_cache.update(new_dirtied)
@write_api
def set_field(self, name, book_id_to_val_map, allow_case_change=True):
def set_field(self, name, book_id_to_val_map, allow_case_change=True, do_path_update=True):
f = self.fields[name]
is_series = f.metadata['datatype'] == 'series'
update_path = name in {'title', 'authors'}
@ -702,7 +703,7 @@ class Cache(object):
for name in self.composites:
self.fields[name].pop_cache(dirtied)
if dirtied and update_path:
if dirtied and update_path and do_path_update:
self._update_path(dirtied, mark_as_dirtied=False)
self._mark_as_dirty(dirtied)
@ -822,6 +823,102 @@ class Cache(object):
if callback is not None:
callback(book_id, mi, True)
@write_api
def set_cover(self, book_id_data_map):
''' Set the cover for this book. data can be either a QImage,
QPixmap, file object or bytestring '''
for book_id, data in book_id_data_map.iteritems():
try:
path = self._field_for('path', book_id).replace('/', os.sep)
except AttributeError:
self._update_path((book_id,))
path = self._field_for('path', book_id).replace('/', os.sep)
self.backend.set_cover(book_id, path, data)
self._set_field('cover', {book_id:1 for book_id in book_id_data_map})
@write_api
def set_metadata(self, book_id, mi, ignore_errors=False, force_changes=False,
set_title=True, set_authors=True):
if callable(getattr(mi, 'to_book_metadata', None)):
# Handle code passing in an OPF object instead of a Metadata object
mi = mi.to_book_metadata()
def set_field(name, val, **kwargs):
self._set_field(name, {book_id:val}, **kwargs)
path_changed = False
if set_title and mi.title:
path_changed = True
set_field('title', mi.title, do_path_update=False)
if set_authors:
path_changed = True
if not mi.authors:
mi.authors = [_('Unknown')]
authors = []
for a in mi.authors:
authors += string_to_authors(a)
set_field('authors', authors, do_path_update=False)
if path_changed:
self._update_path((book_id,))
def protected_set_field(name, val, **kwargs):
try:
set_field(name, val, **kwargs)
except:
if ignore_errors:
traceback.print_exc()
else:
raise
for field in ('rating', 'series_index', 'timestamp'):
val = getattr(mi, field)
if val is not None:
protected_set_field(field, val)
# force_changes has no effect on cover manipulation
cdata = mi.cover_data[1]
if cdata is None and isinstance(mi.cover, basestring) and mi.cover and os.access(mi.cover, os.R_OK):
with lopen(mi.cover, 'rb') as f:
raw = f.read()
if raw:
cdata = raw
if cdata is not None:
self._set_cover({book_id: cdata})
for field in ('title_sort', 'author_sort', 'publisher', 'series',
'tags', 'comments', 'languages', 'pubdate'):
val = mi.get(field, None)
if (force_changes and val is not None) or not mi.is_null(field):
protected_set_field(field, val)
# identifiers will always be replaced if force_changes is True
mi_idents = mi.get_identifiers()
if force_changes:
protected_set_field('identifiers', mi_idents)
elif mi_idents:
identifiers = self._field_for('identifiers', book_id, default_value={})
for key, val in mi_idents.iteritems():
if val and val.strip(): # Don't delete an existing identifier
identifiers[icu_lower(key)] = val
protected_set_field('identifiers', identifiers)
user_mi = mi.get_all_user_metadata(make_copy=False)
fm = self.field_metadata
for key in user_mi.iterkeys():
if (key in fm and
user_mi[key]['datatype'] == fm[key]['datatype'] and
(user_mi[key]['datatype'] != 'text' or
user_mi[key]['is_multiple'] == fm[key]['is_multiple'])):
val = mi.get(key, None)
if force_changes or val is not None:
protected_set_field(key, val)
extra = mi.get_extra(key)
if extra is not None:
protected_set_field(key+'_index', extra)
# }}}
class SortKey(object): # {{{

View File

@ -9,8 +9,10 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import os, traceback
from functools import partial
from calibre.db import _get_next_series_num_for_list, _get_series_values
from calibre.db.backend import DB
from calibre.db.cache import Cache
from calibre.db.categories import CATEGORY_SORTS
from calibre.db.view import View
from calibre.utils.date import utcnow
@ -20,6 +22,10 @@ class LibraryDatabase(object):
PATH_LIMIT = DB.PATH_LIMIT
WINDOWS_LIBRARY_PATH_LIMIT = DB.WINDOWS_LIBRARY_PATH_LIMIT
CATEGORY_SORTS = CATEGORY_SORTS
MATCH_TYPE = ('any', 'all')
CUSTOM_DATA_TYPES = frozenset(['rating', 'text', 'comments', 'datetime',
'int', 'float', 'bool', 'series', 'composite', 'enumeration'])
@classmethod
def exists_at(cls, path):
@ -148,3 +154,17 @@ class LibraryDatabase(object):
os.makedirs(path)
return path
# Private interface {{{
def __iter__(self):
for row in self.data.iterall():
yield row
def _get_next_series_num_for_list(self, series_indices):
return _get_next_series_num_for_list(series_indices)
def _get_series_values(self, val):
return _get_series_values(val)
# }}}

View File

@ -6,6 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import inspect
from calibre.db.tests.base import BaseTest
class LegacyTest(BaseTest):
@ -82,6 +83,7 @@ class LegacyTest(BaseTest):
# }}}
def test_legacy_getters(self): # {{{
' Test various functions to get individual bits of metadata '
old = self.init_old()
getters = ('path', 'abspath', 'title', 'authors', 'series',
'publisher', 'author_sort', 'authors', 'comments',
@ -89,11 +91,43 @@ class LegacyTest(BaseTest):
'timestamp', 'uuid', 'pubdate', 'ondevice',
'metadata_last_modified', 'languages')
oldvals = {g:tuple(getattr(old, g)(x) for x in xrange(3)) + tuple(getattr(old, g)(x, True) for x in (1,2,3)) for g in getters}
old_rows = {tuple(r)[:5] for r in old}
old.close()
db = self.init_legacy()
newvals = {g:tuple(getattr(db, g)(x) for x in xrange(3)) + tuple(getattr(db, g)(x, True) for x in (1,2,3)) for g in getters}
new_rows = {tuple(r)[:5] for r in db}
for x in (oldvals, newvals):
x['tags'] = tuple(set(y.split(',')) if y else y for y in x['tags'])
self.assertEqual(oldvals, newvals)
self.assertEqual(old_rows, new_rows)
# }}}
def test_legacy_coverage(self): # {{{
' Check that the emulation of the legacy interface is (almost) total '
cl = self.cloned_library
db = self.init_old(cl)
ndb = self.init_legacy()
SKIP_ATTRS = {
'TCat_Tag', '_add_newbook_tag', '_clean_identifier', '_library_id_', '_set_authors',
'_set_title', '_set_custom', '_update_author_in_cache',
}
SKIP_ARGSPEC = {
'__init__',
}
for attr in dir(db):
if attr in SKIP_ATTRS:
continue
self.assertTrue(hasattr(ndb, attr), 'The attribute %s is missing' % attr)
obj, nobj = getattr(db, attr), getattr(ndb, attr)
if attr not in SKIP_ARGSPEC:
try:
argspec = inspect.getargspec(obj)
except TypeError:
pass
else:
self.assertEqual(argspec, inspect.getargspec(nobj), 'argspec for %s not the same' % attr)
# }}}

View File

@ -355,4 +355,7 @@ class WritingTest(BaseTest):
ae(opf.authors, ['author1', 'author2'])
# }}}
def test_set_cover(self):
' Test setting of cover '
self.assertTrue(False, 'TODO: test set_cover() and set_metadata()')

View File

@ -29,11 +29,12 @@ class MarkedVirtualField(object):
for book_id in candidates:
yield self.marked_ids.get(book_id, default_value), {book_id}
class TableRow(list):
class TableRow(object):
def __init__(self, book_id, view):
self.book_id = book_id
self.view = weakref.ref(view)
self.column_count = view.column_count
def __getitem__(self, obj):
view = self.view()
@ -43,6 +44,13 @@ class TableRow(list):
else:
return view._field_getters[obj](self.book_id)
def __len__(self):
return self.column_count
def __iter__(self):
for i in xrange(self.column_count):
yield self[i]
def format_is_multiple(x, sep=',', repl=None):
if not x:
return None
@ -67,6 +75,7 @@ class View(object):
self.search_restriction = self.base_restriction = ''
self.search_restriction_name = self.base_restriction_name = ''
self._field_getters = {}
self.column_count = len(cache.backend.FIELD_MAP)
for col, idx in cache.backend.FIELD_MAP.iteritems():
label, fmt = col, lambda x:x
func = {
@ -107,7 +116,7 @@ class View(object):
fmt = partial(format_is_multiple, sep=sep)
self._field_getters[idx] = partial(func, label, fmt=fmt) if func == self._get else func
self._map = tuple(self.cache.all_book_ids())
self._map = tuple(sorted(self.cache.all_book_ids()))
self._map_filtered = tuple(self._map)
def get_property(self, id_or_index, index_is_id=False, loc=-1):
@ -124,21 +133,21 @@ class View(object):
return idx if index_is_id else self.index_to_id(idx)
def __getitem__(self, row):
return TableRow(self._map_filtered[row], self.cache)
return TableRow(self._map_filtered[row], self)
def __len__(self):
return len(self._map_filtered)
def __iter__(self):
for book_id in self._map_filtered:
yield self._data[book_id]
yield TableRow(book_id, self)
def iterall(self):
for book_id in self._map:
yield self[book_id]
for book_id in self.iterallids():
yield TableRow(book_id, self)
def iterallids(self):
for book_id in self._map:
for book_id in sorted(self._map):
yield book_id
def get_field_map_field(self, row, col, index_is_id=True):

View File

@ -5,7 +5,7 @@ __copyright__ = '2010, Gregory Riker'
__docformat__ = 'restructuredtext en'
import cStringIO, ctypes, datetime, os, platform, re, shutil, sys, tempfile, time
import cStringIO, ctypes, datetime, os, re, shutil, sys, tempfile, time
from calibre import fit_image, confirm_config_name, strftime as _strftime
from calibre.constants import (
@ -17,13 +17,12 @@ from calibre.devices.interface import DevicePlugin
from calibre.ebooks.metadata import (author_to_author_sort, authors_to_string,
MetaInformation, title_sort)
from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.config import config_dir, dynamic, prefs
from calibre.utils.date import now, parse_date
from calibre.utils.zipfile import ZipFile
from calibre.utils.config_base import config_dir, prefs
DEBUG = CALIBRE_DEBUG
def strftime(fmt='%Y/%m/%d %H:%M:%S', dt=None):
from calibre.utils.date import now
if not hasattr(dt, 'timetuple'):
dt = now()
@ -96,6 +95,7 @@ class AppleOpenFeedback(OpenFeedback):
def do_it(self, return_code):
from calibre.utils.logging import default_log
from calibre.utils.config import dynamic
if return_code == self.Accepted:
default_log.info(" Apple driver ENABLED")
dynamic[confirm_config_name(self.cd.plugin.DISPLAY_DISABLE_DIALOG)] = False
@ -413,6 +413,7 @@ class ITUNES(DriverBase):
list of device books.
"""
from calibre.utils.date import parse_date
if not oncard:
if DEBUG:
logger().info("%s.books():" % self.__class__.__name__)
@ -860,6 +861,7 @@ class ITUNES(DriverBase):
Note that most of the initialization is necessarily performed in can_handle(), as
we need to talk to iTunes to discover if there's a connected iPod
'''
from calibre.utils.zipfile import ZipFile
if self.iTunes is None:
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)
@ -881,6 +883,7 @@ class ITUNES(DriverBase):
if False:
# Display a dialog recommending using 'Connect to iTunes' if user hasn't
# previously disabled the dialog
from calibre.utils.config import dynamic
if dynamic.get(confirm_config_name(self.DISPLAY_DISABLE_DIALOG), True):
raise AppleOpenFeedback(self)
else:
@ -930,6 +933,7 @@ class ITUNES(DriverBase):
NB: This will not find books that were added by a different installation of calibre
as uuids are different
'''
from calibre.utils.zipfile import ZipFile
if DEBUG:
logger().info("%s.remove_books_from_metadata()" % self.__class__.__name__)
for path in paths:
@ -1429,6 +1433,7 @@ class ITUNES(DriverBase):
as of iTunes 9.2, iBooks 1.1, can't set artwork for PDF files via automation
'''
from PIL import Image as PILImage
from calibre.utils.zipfile import ZipFile
if DEBUG:
logger().info(" %s._cover_to_thumb()" % self.__class__.__name__)
@ -1557,6 +1562,7 @@ class ITUNES(DriverBase):
def _create_new_book(self, fpath, metadata, path, db_added, lb_added, thumb, format):
'''
'''
from calibre.utils.date import parse_date
if DEBUG:
logger().info(" %s._create_new_book()" % self.__class__.__name__)
@ -1761,6 +1767,7 @@ class ITUNES(DriverBase):
'''
'''
from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.utils.zipfile import ZipFile
logger().info(" %s.__get_epub_metadata()" % self.__class__.__name__)
title = None
@ -2014,6 +2021,7 @@ class ITUNES(DriverBase):
as of iTunes 9.2, iBooks 1.1, can't set artwork for PDF files via automation
'''
from PIL import Image as PILImage
from calibre.utils.zipfile import ZipFile
if not self.settings().extra_customization[self.CACHE_COVERS]:
thumb_data = None
@ -2126,6 +2134,7 @@ class ITUNES(DriverBase):
'''
Calculate the exploded size of file
'''
from calibre.utils.zipfile import ZipFile
exploded_file_size = compressed_size
format = file.rpartition('.')[2].lower()
if format == 'epub':
@ -2478,6 +2487,7 @@ class ITUNES(DriverBase):
'''
if DEBUG:
import platform
logger().info(" %s %s" % (__appname__, __version__))
logger().info(" [OSX %s, %s %s (%s), %s driver version %d.%d.%d]" %
(platform.mac_ver()[0],
@ -2622,7 +2632,7 @@ class ITUNES(DriverBase):
# for deletion from booklist[0] during add_books_to_metadata
for book in self.cached_books:
if (self.cached_books[book]['uuid'] == metadata.uuid or
(self.cached_books[book]['title'] == metadata.title and \
(self.cached_books[book]['title'] == metadata.title and
self.cached_books[book]['author'] == metadata.author)):
self.update_list.append(self.cached_books[book])
if DEBUG:
@ -2776,8 +2786,10 @@ class ITUNES(DriverBase):
def _update_epub_metadata(self, fpath, metadata):
'''
'''
from calibre.utils.date import parse_date, now
from calibre.ebooks.metadata.epub import set_metadata
from lxml import etree
from calibre.utils.zipfile import ZipFile
if DEBUG:
logger().info(" %s._update_epub_metadata()" % self.__class__.__name__)
@ -3248,6 +3260,7 @@ class ITUNES_ASYNC(ITUNES):
list of device books.
"""
from calibre.utils.date import parse_date
if not oncard:
if DEBUG:
logger().info("%s.books()" % self.__class__.__name__)
@ -3418,6 +3431,7 @@ class ITUNES_ASYNC(ITUNES):
Note that most of the initialization is necessarily performed in can_handle(), as
we need to talk to iTunes to discover if there's a connected iPod
'''
from calibre.utils.zipfile import ZipFile
if self.iTunes is None:
raise OpenFeedback(self.ITUNES_SANDBOX_LOCKOUT_MESSAGE)

View File

@ -10,8 +10,7 @@ from calibre.utils.icu import sort_key
from calibre.devices.usbms.books import Book as Book_
from calibre.devices.usbms.books import CollectionsBookList
from calibre.utils.config import prefs
from calibre.utils.date import parse_date
from calibre.utils.config_base import prefs
from calibre.devices.usbms.driver import debug_print
from calibre.ebooks.metadata import author_to_author_sort
@ -19,6 +18,7 @@ class Book(Book_):
def __init__(self, prefix, lpath, title=None, authors=None, mime=None, date=None, ContentType=None,
thumbnail_name=None, size=None, other=None):
from calibre.utils.date import parse_date
# debug_print('Book::__init__ - title=', title)
show_debug = title is not None and title.lower().find("xxxxx") >= 0
if show_debug:

View File

@ -26,7 +26,7 @@ from calibre.devices.usbms.driver import USBMS, debug_print
from calibre import prints
from calibre.ptempfile import PersistentTemporaryFile
from calibre.constants import DEBUG
from calibre.utils.config import prefs
from calibre.utils.config_base import prefs
class KOBO(USBMS):
@ -1193,7 +1193,7 @@ class KOBO(USBMS):
db.set_comment(db_id, mi.comments)
# Add bookmark file to db_id
# NOTE: As it is, this copied the book from the device back to the library. That meant it replaced the
# NOTE: As it is, this copied the book from the device back to the library. That meant it replaced the
# existing file. Taking this out for that reason, but some books have a ANNOT file that could be
# copied.
# db.add_format_with_hooks(db_id, bm.value.bookmark_extension,
@ -1212,10 +1212,10 @@ class KOBOTOUCH(KOBO):
min_dbversion_series = 65
min_dbversion_archive = 71
min_dbversion_images_on_sdcard = 77
max_supported_fwversion = (2,5,1)
min_fwversion_images_on_sdcard = (2,4,1)
has_kepubs = True
booklist_class = KTCollectionsBookList

View File

@ -17,8 +17,6 @@ from calibre.devices.errors import PathError
from calibre.devices.mtp.base import debug
from calibre.devices.mtp.defaults import DeviceDefaults
from calibre.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory
from calibre.utils.config import from_json, to_json, JSONConfig
from calibre.utils.date import now, isoformat, utcnow
from calibre.utils.filenames import shorten_components_to
BASE = importlib.import_module('calibre.devices.mtp.%s.driver'%(
@ -57,6 +55,7 @@ class MTP_DEVICE(BASE):
@property
def prefs(self):
from calibre.utils.config import JSONConfig
if self._prefs is None:
self._prefs = p = JSONConfig('mtp_devices')
p.defaults['format_map'] = self.FORMATS
@ -103,6 +102,7 @@ class MTP_DEVICE(BASE):
del self.prefs[x]
def open(self, device, library_uuid):
from calibre.utils.date import isoformat, utcnow
self.current_library_uuid = library_uuid
self.location_paths = None
self.driveinfo = {}
@ -128,6 +128,8 @@ class MTP_DEVICE(BASE):
# Device information {{{
def _update_drive_info(self, storage, location_code, name=None):
from calibre.utils.date import isoformat, now
from calibre.utils.config import from_json, to_json
import uuid
f = storage.find_path((self.DRIVEINFO,))
dinfo = {}

View File

@ -11,7 +11,6 @@ import os, time, re
from calibre.devices.usbms.driver import USBMS, debug_print
from calibre.devices.prs505 import MEDIA_XML, MEDIA_EXT, CACHE_XML, CACHE_EXT, \
MEDIA_THUMBNAIL, CACHE_THUMBNAIL
from calibre.devices.prs505.sony_cache import XMLCache
from calibre import __appname__, prints
from calibre.devices.usbms.books import CollectionsBookList
@ -178,6 +177,7 @@ class PRS505(USBMS):
return fname
def initialize_XML_cache(self):
from calibre.devices.prs505.sony_cache import XMLCache
paths, prefixes, ext_paths = {}, {}, {}
for prefix, path, ext_path, source_id in [
('main', MEDIA_XML, MEDIA_EXT, 0),

View File

@ -7,7 +7,7 @@ Created on 29 Jun 2012
@author: charles
'''
import socket, select, json, inspect, os, traceback, time, sys, random
import socket, select, json, os, traceback, time, sys, random
import posixpath
import hashlib, threading
import Queue
@ -34,8 +34,7 @@ from calibre.library import current_library_name
from calibre.library.server import server_config as content_server_config
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.ipc import eintr_retry_call
from calibre.utils.config import from_json, tweaks
from calibre.utils.date import isoformat, now
from calibre.utils.config_base import tweaks
from calibre.utils.filenames import ascii_filename as sanitize, shorten_components_to
from calibre.utils.mdns import (publish as publish_zeroconf, unpublish as
unpublish_zeroconf, get_all_ips)
@ -345,6 +344,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
def _debug(self, *args):
# manual synchronization so we don't lose the calling method name
import inspect
with self.sync_lock:
if not DEBUG:
return
@ -373,6 +373,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
# copied from USBMS. Perhaps this could be a classmethod in usbms?
def _update_driveinfo_record(self, dinfo, prefix, location_code, name=None):
from calibre.utils.date import isoformat, now
import uuid
if not isinstance(dinfo, dict):
dinfo = {}
@ -593,6 +594,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
raise ControlError(desc='Device responded with incorrect information')
def _receive_from_client(self, print_debug_info=True):
from calibre.utils.config import from_json
extra_debug = self.settings().extra_customization[self.OPT_EXTRA_DEBUG]
try:
v = self._read_string_from_net()
@ -816,6 +818,7 @@ class SMART_DEVICE_APP(DeviceConfig, DevicePlugin):
@synchronous('sync_lock')
def open(self, connected_device, library_uuid):
from calibre.utils.date import isoformat, now
self._debug()
if not self.is_connected:
# We have been called to retry the connection. Give up immediately

View File

@ -12,9 +12,8 @@ from calibre.devices.mime import mime_type_ext
from calibre.devices.interface import BookList as _BookList
from calibre.constants import preferred_encoding
from calibre import isbytestring, force_unicode
from calibre.utils.config import device_prefs, tweaks
from calibre.utils.config_base import tweaks
from calibre.utils.icu import sort_key
from calibre.utils.formatter import EvalFormatter
class Book(Metadata):
def __init__(self, prefix, lpath, size=None, other=None):
@ -109,6 +108,7 @@ class CollectionsBookList(BookList):
return None
def compute_category_name(self, field_key, field_value, field_meta):
from calibre.utils.formatter import EvalFormatter
renames = tweaks['sony_collection_renaming_rules']
field_name = renames.get(field_key, None)
if field_name is None:
@ -124,6 +124,7 @@ class CollectionsBookList(BookList):
def get_collections(self, collection_attributes):
from calibre.devices.usbms.driver import debug_print
from calibre.utils.config import device_prefs
debug_print('Starting get_collections:', device_prefs['manage_device_metadata'])
debug_print('Renaming rules:', tweaks['sony_collection_renaming_rules'])
debug_print('Formatting template:', tweaks['sony_collection_name_template'])

View File

@ -4,7 +4,7 @@ __license__ = 'GPL 3'
__copyright__ = '2009, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
from calibre.utils.config import Config, ConfigProxy
from calibre.utils.config_base import Config, ConfigProxy
class DeviceConfig(object):

View File

@ -20,8 +20,6 @@ from calibre.devices.usbms.cli import CLI
from calibre.devices.usbms.device import Device
from calibre.devices.usbms.books import BookList, Book
from calibre.ebooks.metadata.book.json_codec import JsonCodec
from calibre.utils.config import from_json, to_json
from calibre.utils.date import now, isoformat
BASE_TIME = None
def debug_print(*args):
@ -58,6 +56,7 @@ class USBMS(CLI, Device):
SCAN_FROM_ROOT = False
def _update_driveinfo_record(self, dinfo, prefix, location_code, name=None):
from calibre.utils.date import now, isoformat
import uuid
if not isinstance(dinfo, dict):
dinfo = {}
@ -75,6 +74,7 @@ class USBMS(CLI, Device):
return dinfo
def _update_driveinfo_file(self, prefix, location_code, name=None):
from calibre.utils.config import from_json, to_json
if os.path.exists(os.path.join(prefix, self.DRIVEINFO)):
with open(os.path.join(prefix, self.DRIVEINFO), 'rb') as f:
try:

View File

@ -206,9 +206,11 @@ class EPUBInput(InputFormatPlugin):
not_for_spine = set()
for y in opf.itermanifest():
id_ = y.get('id', None)
if id_ and y.get('media-type', None) in \
('application/vnd.adobe-page-template+xml','application/text'):
not_for_spine.add(id_)
if id_ and y.get('media-type', None) in {
'application/vnd.adobe-page-template+xml', 'application/vnd.adobe.page-template+xml',
'application/adobe-page-template+xml', 'application/adobe.page-template+xml',
'application/text'}:
not_for_spine.add(id_)
seen = set()
for x in list(opf.iterspine()):

View File

@ -10,7 +10,6 @@ __docformat__ = 'restructuredtext en'
import re, tempfile, os
from functools import partial
from itertools import izip
from urllib import quote
from calibre.constants import islinux, isbsd
from calibre.customize.conversion import (InputFormatPlugin,
@ -223,6 +222,7 @@ class HTMLInput(InputFormatPlugin):
return link, frag
def resource_adder(self, link_, base=None):
from urllib import quote
link, frag = self.link_to_local_path(link_, base=base)
if link is None:
return link_

View File

@ -10,7 +10,6 @@ import shutil
from calibre.customize.conversion import InputFormatPlugin
from calibre.ptempfile import TemporaryDirectory
from calibre.utils.zipfile import ZipFile
class PMLInput(InputFormatPlugin):
@ -86,6 +85,7 @@ class PMLInput(InputFormatPlugin):
accelerators):
from calibre.ebooks.metadata.toc import TOC
from calibre.ebooks.metadata.opf2 import OPFCreator
from calibre.utils.zipfile import ZipFile
self.options = options
self.log = log

View File

@ -7,12 +7,11 @@ __docformat__ = 'restructuredtext en'
Provides abstraction for metadata reading.writing from a variety of ebook formats.
"""
import os, sys, re
from urllib import unquote, quote
from urlparse import urlparse
from calibre import relpath, guess_type, remove_bracketed_text, prints
from calibre.utils.config import tweaks
from calibre.utils.config_base import tweaks
try:
_author_pat = re.compile(tweaks['authors_split_regex'])
@ -188,6 +187,7 @@ class Resource(object):
'''
def __init__(self, href_or_path, basedir=os.getcwdu(), is_path=True):
from urllib import unquote
self._href = None
self._basedir = basedir
self.path = None
@ -226,6 +226,7 @@ class Resource(object):
`basedir`: If None, the basedir of this resource is used (see :method:`set_basedir`).
If this resource has no basedir, then the current working directory is used as the basedir.
'''
from urllib import quote
if basedir is None:
if self._basedir:
basedir = self._basedir

View File

@ -10,7 +10,6 @@ import os
from contextlib import closing
from calibre.customize import FileTypePlugin
from calibre.utils.zipfile import ZipFile, stringFileHeader
def is_comic(list_of_names):
extensions = set([x.rpartition('.')[-1].lower() for x in list_of_names
@ -19,6 +18,7 @@ def is_comic(list_of_names):
return len(extensions - comic_extensions) == 0
def archive_type(stream):
from calibre.utils.zipfile import stringFileHeader
try:
pos = stream.tell()
except:
@ -47,6 +47,7 @@ class ArchiveExtract(FileTypePlugin):
on_import = True
def run(self, archive):
from calibre.utils.zipfile import ZipFile
is_rar = archive.lower().endswith('.rar')
if is_rar:
from calibre.utils.unrar import extract_member, names

View File

@ -13,9 +13,7 @@ from calibre.ebooks.metadata.book import (SC_COPYABLE_FIELDS,
SC_FIELDS_COPY_NOT_NULL, STANDARD_METADATA_FIELDS,
TOP_LEVEL_IDENTIFIERS, ALL_METADATA_FIELDS)
from calibre.library.field_metadata import FieldMetadata
from calibre.utils.date import isoformat, format_date, parse_only_date
from calibre.utils.icu import sort_key
from calibre.utils.formatter import TemplateFormatter
# Special sets used to optimize the performance of getting and setting
# attributes on Metadata objects
@ -44,38 +42,6 @@ NULL_VALUES = {
field_metadata = FieldMetadata()
class SafeFormat(TemplateFormatter):
def get_value(self, orig_key, args, kwargs):
if not orig_key:
return ''
key = orig_key = orig_key.lower()
if key != 'title_sort' and key not in TOP_LEVEL_IDENTIFIERS and \
key not in ALL_METADATA_FIELDS:
key = field_metadata.search_term_to_field_key(key)
if key is None or (self.book and
key not in self.book.all_field_keys()):
if hasattr(self.book, orig_key):
key = orig_key
else:
raise ValueError(_('Value: unknown field ') + orig_key)
try:
b = self.book.get_user_metadata(key, False)
except:
b = None
if b and ((b['datatype'] == 'int' and self.book.get(key, 0) == 0) or
(b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0)):
v = ''
else:
v = self.book.format_field(key, series_with_index=False)[1]
if v is None:
return ''
if v == '':
return ''
return v
# DEPRECATED. This is not thread safe. Do not use.
composite_formatter = SafeFormat()
class Metadata(object):
@ -116,6 +82,7 @@ class Metadata(object):
# List of strings or []
self.author = list(authors) if authors else []# Needed for backward compatibility
self.authors = list(authors) if authors else []
from calibre.ebooks.metadata.book.formatter import SafeFormat
self.formatter = SafeFormat()
self.template_cache = template_cache
@ -454,6 +421,7 @@ class Metadata(object):
'''
if not ops:
return
from calibre.ebooks.metadata.book.formatter import SafeFormat
formatter = SafeFormat()
for op in ops:
try:
@ -633,6 +601,7 @@ class Metadata(object):
returns the tuple (display_name, formatted_value, original_value,
field_metadata)
'''
from calibre.utils.date import format_date
# Handle custom series index
if key.startswith('#') and key.endswith('_index'):
@ -717,6 +686,7 @@ class Metadata(object):
A string representation of this object, suitable for printing to
console
'''
from calibre.utils.date import isoformat
from calibre.ebooks.metadata import authors_to_string
ans = []
def fmt(x, y):
@ -809,6 +779,7 @@ def field_from_string(field, raw, field_metadata):
elif dt == 'rating':
val = float(raw) * 2
elif dt == 'datetime':
from calibre.utils.date import parse_only_date
val = parse_only_date(raw)
elif dt == 'bool':
if raw.lower() in {'true', 'yes', 'y'}:

View File

@ -0,0 +1,48 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
from calibre.ebooks.metadata.book import TOP_LEVEL_IDENTIFIERS, ALL_METADATA_FIELDS
from calibre.utils.formatter import TemplateFormatter
class SafeFormat(TemplateFormatter):
def __init__(self):
TemplateFormatter.__init__(self)
from calibre.ebooks.metadata.book.base import field_metadata
self.field_metadata = field_metadata
def get_value(self, orig_key, args, kwargs):
if not orig_key:
return ''
key = orig_key = orig_key.lower()
if key != 'title_sort' and key not in TOP_LEVEL_IDENTIFIERS and \
key not in ALL_METADATA_FIELDS:
key = self.field_metadata.search_term_to_field_key(key)
if key is None or (self.book and
key not in self.book.all_field_keys()):
if hasattr(self.book, orig_key):
key = orig_key
else:
raise ValueError(_('Value: unknown field ') + orig_key)
try:
b = self.book.get_user_metadata(key, False)
except:
b = None
if b and ((b['datatype'] == 'int' and self.book.get(key, 0) == 0) or
(b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0)):
v = ''
else:
v = self.book.format_field(key, series_with_index=False)[1]
if v is None:
return ''
if v == '':
return ''
return v

View File

@ -11,17 +11,18 @@ from datetime import datetime, time
from calibre.ebooks.metadata.book import SERIALIZABLE_FIELDS
from calibre.constants import filesystem_encoding, preferred_encoding
from calibre.library.field_metadata import FieldMetadata
from calibre.utils.date import parse_date, isoformat, UNDEFINED_DATE, local_tz
from calibre import isbytestring
# Translate datetimes to and from strings. The string form is the datetime in
# UTC. The returned date is also UTC
def string_to_datetime(src):
from calibre.utils.date import parse_date
if src == "None":
return None
return parse_date(src)
def datetime_to_string(dateval):
from calibre.utils.date import isoformat, UNDEFINED_DATE, local_tz
if dateval is None:
return "None"
if not isinstance(dateval, datetime):

View File

@ -554,6 +554,10 @@ class OPF(object): # {{{
resolve_entities=True, assume_utf8=True)
raw = raw[raw.find('<'):]
self.root = etree.fromstring(raw, self.PARSER)
try:
self.package_version = float(self.root.get('version', None))
except (AttributeError, TypeError, ValueError):
self.package_version = 0
self.metadata = self.metadata_path(self.root)
if not self.metadata:
raise ValueError('Malformed OPF file: No <metadata> element')
@ -1116,7 +1120,10 @@ class OPF(object): # {{{
def get_metadata_element(self, name):
matches = self.metadata_elem_path(self.metadata, name=name)
if matches:
return matches[-1]
num = -1
if self.package_version >= 3 and name == 'title':
num = 0
return matches[num]
def create_metadata_element(self, name, attrib=None, is_dc=True):
if is_dc:

View File

@ -8,7 +8,6 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import socket, time, re
from urllib import urlencode
from threading import Thread
from Queue import Queue, Empty
@ -18,7 +17,6 @@ from calibre.ebooks.metadata import check_isbn
from calibre.ebooks.metadata.sources.base import (Source, Option, fixcase,
fixauthors)
from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.date import parse_only_date
from calibre.utils.localization import canonicalize_lang
class Worker(Thread): # Get details {{{
@ -495,6 +493,7 @@ class Worker(Thread): # Get details {{{
def parse_pubdate(self, pd):
for x in reversed(pd.xpath(self.publisher_xpath)):
if x.tail:
from calibre.utils.date import parse_only_date
ans = x.tail
date = ans.rpartition('(')[-1].replace(')', '').strip()
date = self.delocalize_datestr(date)
@ -637,6 +636,7 @@ class Amazon(Source):
def create_query(self, log, title=None, authors=None, identifiers={}, # {{{
domain=None):
from urllib import urlencode
if domain is None:
domain = self.domain

View File

@ -12,27 +12,9 @@ from future_builtins import map
from calibre import browser, random_user_agent
from calibre.customize import Plugin
from calibre.utils.config import JSONConfig
from calibre.utils.titlecase import titlecase
from calibre.utils.icu import capitalize, lower, upper
from calibre.ebooks.metadata import check_isbn
msprefs = JSONConfig('metadata_sources/global.json')
msprefs.defaults['txt_comments'] = False
msprefs.defaults['ignore_fields'] = []
msprefs.defaults['user_default_ignore_fields'] = []
msprefs.defaults['max_tags'] = 20
msprefs.defaults['wait_after_first_identify_result'] = 30 # seconds
msprefs.defaults['wait_after_first_cover_result'] = 60 # seconds
msprefs.defaults['swap_author_names'] = False
msprefs.defaults['fewer_tags'] = True
msprefs.defaults['find_first_edition_date'] = False
# Google covers are often poor quality (scans/errors) but they have high
# resolution, so they trump covers from better sources. So make sure they
# are only used if no other covers are found.
msprefs.defaults['cover_priorities'] = {'Google':2, 'Google Images':2, 'Big Book Search':2}
def create_log(ostream=None):
from calibre.utils.logging import ThreadSafeLog, FileStream
log = ThreadSafeLog(level=ThreadSafeLog.DEBUG)
@ -162,6 +144,7 @@ def fixauthors(authors):
def fixcase(x):
if x:
from calibre.utils.titlecase import titlecase
x = titlecase(x)
return x
@ -263,6 +246,7 @@ class Source(Plugin):
@property
def prefs(self):
if self._config_obj is None:
from calibre.utils.config import JSONConfig
self._config_obj = JSONConfig('metadata_sources/%s.json'%self.name)
return self._config_obj
# }}}

View File

@ -13,7 +13,8 @@ from threading import Thread, Event
from io import BytesIO
from calibre.customize.ui import metadata_plugins
from calibre.ebooks.metadata.sources.base import msprefs, create_log
from calibre.ebooks.metadata.sources.base import create_log
from calibre.ebooks.metadata.sources.prefs import msprefs
from calibre.utils.magick.draw import Image, save_cover_data_to
class Worker(Thread):

View File

@ -8,7 +8,6 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>; 2011, Li Fanxi <lifan
__docformat__ = 'restructuredtext en'
import time
from urllib import urlencode
from functools import partial
from Queue import Queue, Empty
@ -172,6 +171,7 @@ class Douban(Source):
# }}}
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
from urllib import urlencode
SEARCH_URL = 'http://api.douban.com/book/subjects?'
ISBN_URL = 'http://api.douban.com/book/subject/isbn/'
SUBJECT_URL = 'http://api.douban.com/book/subject/'

View File

@ -8,7 +8,6 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import time, hashlib
from urllib import urlencode
from functools import partial
from Queue import Queue, Empty
@ -16,7 +15,6 @@ from calibre.ebooks.metadata import check_isbn
from calibre.ebooks.metadata.sources.base import Source
from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.chardet import xml_to_unicode
from calibre.utils.date import parse_date, utcnow
from calibre.utils.cleantext import clean_ascii_chars
from calibre.utils.localization import canonicalize_lang
from calibre import as_unicode
@ -131,6 +129,7 @@ def to_metadata(browser, log, entry_, timeout): # {{{
# pubdate
pubdate = get_text(extra, date)
if pubdate:
from calibre.utils.date import parse_date, utcnow
try:
default = utcnow().replace(day=15)
mi.pubdate = parse_date(pubdate, assume_utc=True, default=default)
@ -179,6 +178,7 @@ class GoogleBooks(Source):
# }}}
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
from urllib import urlencode
BASE_URL = 'http://books.google.com/books/feeds/volumes?'
isbn = check_isbn(identifiers.get('isbn', None))
q = ''

View File

@ -16,7 +16,8 @@ from operator import attrgetter
from urlparse import urlparse
from calibre.customize.ui import metadata_plugins, all_metadata_plugins
from calibre.ebooks.metadata.sources.base import create_log, msprefs
from calibre.ebooks.metadata.sources.base import create_log
from calibre.ebooks.metadata.sources.prefs import msprefs
from calibre.ebooks.metadata.xisbn import xisbn
from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.date import utc_tz, as_utc

View File

@ -7,7 +7,6 @@ __license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from urllib import quote
from calibre.ebooks.metadata import check_isbn
@ -66,6 +65,7 @@ class ISBNDB(Source):
return self.isbndb_key is not None
def create_query(self, title=None, authors=None, identifiers={}): # {{{
from urllib import quote
base_url = BASE_URL%self.isbndb_key
isbn = check_isbn(identifiers.get('isbn', None))
q = ''

View File

@ -6,14 +6,12 @@ __copyright__ = '2011, Roman Mukhin <ramses_ru at hotmail.com>'
__docformat__ = 'restructuredtext en'
import re
from urllib import quote_plus
from Queue import Queue, Empty
from calibre import as_unicode
from calibre.ebooks.metadata import check_isbn
from calibre.ebooks.metadata.sources.base import Source
from calibre.ebooks.metadata.book.base import Metadata
from calibre.utils.date import parse_only_date
class Ozon(Source):
name = 'OZON.ru'
@ -49,6 +47,7 @@ class Ozon(Source):
# }}}
def create_query(self, log, title=None, authors=None, identifiers={}): # {{{
from urllib import quote_plus
# div_book -> search only books, ebooks and audio books
search_url = self.ozon_url + '/webservice/webservice.asmx/SearchWebService?searchContext=div_book&searchText='
@ -390,33 +389,34 @@ def _get_affiliateId(): # {{{
return aff_id
# }}}
# for now only RUS ISBN are supported
#http://ru.wikipedia.org/wiki/ISBN_российских_издательств
isbn_pat = re.compile(r"""
^
(\d{3})? # match GS1 Prefix for ISBN13
(5) # group identifier for rRussian-speaking countries
( # begin variable length for Publisher
[01]\d{1}| # 2x
[2-6]\d{2}| # 3x
7\d{3}| # 4x (starting with 7)
8[0-4]\d{2}| # 4x (starting with 8)
9[2567]\d{2}| # 4x (starting with 9)
99[26]\d{1}| # 4x (starting with 99)
8[5-9]\d{3}| # 5x (starting with 8)
9[348]\d{3}| # 5x (starting with 9)
900\d{2}| # 5x (starting with 900)
91[0-8]\d{2}| # 5x (starting with 91)
90[1-9]\d{3}| # 6x (starting with 90)
919\d{3}| # 6x (starting with 919)
99[^26]\d{4} # 7x (starting with 99)
) # end variable length for Publisher
(\d+) # Title
([\dX]) # Check digit
$
""", re.VERBOSE)
def _format_isbn(log, isbn): # {{{
# for now only RUS ISBN are supported
#http://ru.wikipedia.org/wiki/ISBN_российских_издательств
isbn_pat = re.compile(r"""
^
(\d{3})? # match GS1 Prefix for ISBN13
(5) # group identifier for rRussian-speaking countries
( # begin variable length for Publisher
[01]\d{1}| # 2x
[2-6]\d{2}| # 3x
7\d{3}| # 4x (starting with 7)
8[0-4]\d{2}| # 4x (starting with 8)
9[2567]\d{2}| # 4x (starting with 9)
99[26]\d{1}| # 4x (starting with 99)
8[5-9]\d{3}| # 5x (starting with 8)
9[348]\d{3}| # 5x (starting with 9)
900\d{2}| # 5x (starting with 900)
91[0-8]\d{2}| # 5x (starting with 91)
90[1-9]\d{3}| # 6x (starting with 90)
919\d{3}| # 6x (starting with 919)
99[^26]\d{4} # 7x (starting with 99)
) # end variable length for Publisher
(\d+) # Title
([\dX]) # Check digit
$
""", re.VERBOSE)
res = check_isbn(isbn)
if res:
m = isbn_pat.match(res)
@ -460,6 +460,7 @@ def _normalizeAuthorNameWithInitials(name): # {{{
# }}}
def toPubdate(log, yearAsString): # {{{
from calibre.utils.date import parse_only_date
res = None
if yearAsString:
try:

View File

@ -0,0 +1,28 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
from calibre.utils.config import JSONConfig
msprefs = JSONConfig('metadata_sources/global.json')
msprefs.defaults['txt_comments'] = False
msprefs.defaults['ignore_fields'] = []
msprefs.defaults['user_default_ignore_fields'] = []
msprefs.defaults['max_tags'] = 20
msprefs.defaults['wait_after_first_identify_result'] = 30 # seconds
msprefs.defaults['wait_after_first_cover_result'] = 60 # seconds
msprefs.defaults['swap_author_names'] = False
msprefs.defaults['fewer_tags'] = True
msprefs.defaults['find_first_edition_date'] = False
# Google covers are often poor quality (scans/errors) but they have high
# resolution, so they trump covers from better sources. So make sure they
# are only used if no other covers are found.
msprefs.defaults['cover_priorities'] = {'Google':2, 'Google Images':2, 'Big Book Search':2}

View File

@ -14,8 +14,8 @@ from threading import Event
from calibre.customize.ui import all_metadata_plugins
from calibre import prints, sanitize_file_name2
from calibre.ebooks.metadata import check_isbn
from calibre.ebooks.metadata.sources.base import (create_log,
get_cached_cover_urls, msprefs)
from calibre.ebooks.metadata.sources.base import create_log, get_cached_cover_urls
from calibre.ebooks.metadata.sources.prefs import msprefs
def isbn_test(isbn):
isbn_ = check_isbn(isbn)

View File

@ -20,9 +20,10 @@ class RemoveAdobeMargins(object):
self.oeb, self.opts, self.log = oeb, opts, log
for item in self.oeb.manifest:
if (item.media_type in ('application/vnd.adobe-page-template+xml',
'application/vnd.adobe.page-template+xml') and
hasattr(item.data, 'xpath')):
if item.media_type in {
'application/vnd.adobe-page-template+xml', 'application/vnd.adobe.page-template+xml',
'application/adobe-page-template+xml', 'application/adobe.page-template+xml',
} and hasattr(item.data, 'xpath'):
self.log('Removing page margins specified in the'
' Adobe page template')
for elem in item.data.xpath(
@ -84,13 +85,12 @@ class RemoveFakeMargins(object):
except:
pass
else:
if ( (hasattr(ti, 'startswith') and ti.startswith('-')) or
if ((hasattr(ti, 'startswith') and ti.startswith('-')) or
isinstance(ti, (int, float)) and ti < 0):
raise NegativeTextIndent()
return style.marginLeft, style.marginRight, style
return '', '', None
def process_level(self, level):
elems = self.levels[level]
self.stats[level+'_left'] = Counter()
@ -107,7 +107,6 @@ class RemoveFakeMargins(object):
remove_left = self.analyze_stats(self.stats[level+'_left'])
remove_right = self.analyze_stats(self.stats[level+'_right'])
if remove_left:
mcl = self.stats[level+'_left'].most_common(1)[0][0]
self.log('Removing level %s left margin of:'%level, mcl)

View File

@ -5,10 +5,10 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, shutil
import os, shutil, copy
from functools import partial
from PyQt4.Qt import QMenu, QModelIndex, QTimer
from PyQt4.Qt import QMenu, QModelIndex, QTimer, QIcon
from calibre.gui2 import error_dialog, Dispatcher, question_dialog
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
@ -16,7 +16,8 @@ from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.dialogs.device_category_editor import DeviceCategoryEditor
from calibre.gui2.actions import InterfaceAction
from calibre.ebooks.metadata import authors_to_string
from calibre.ebooks.metadata.opf2 import OPF
from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.opf2 import OPF, metadata_to_opf
from calibre.utils.icu import sort_key
from calibre.db.errors import NoSuchFormat
@ -147,14 +148,18 @@ class EditMetadataAction(InterfaceAction):
payload = (id_map, tdir, log_file, lm_map,
failed_ids.union(failed_covers))
self.gui.proceed_question(self.apply_downloaded_metadata, payload,
review_apply = partial(self.apply_downloaded_metadata, True)
normal_apply = partial(self.apply_downloaded_metadata, False)
self.gui.proceed_question(normal_apply, payload,
log_file, _('Download log'), _('Download complete'), msg,
det_msg=det_msg, show_copy_button=show_copy_button,
cancel_callback=partial(self.cleanup_bulk_download, tdir),
log_is_file=True, checkbox_msg=checkbox_msg,
checkbox_checked=False)
checkbox_checked=False, action_callback=review_apply,
action_label=_('Review downloaded metadata'),
action_icon=QIcon(I('auto_author_sort.png')))
def apply_downloaded_metadata(self, payload, *args):
def apply_downloaded_metadata(self, review, payload, *args):
good_ids, tdir, log_file, lm_map, failed_ids = payload
if not good_ids:
return
@ -194,6 +199,57 @@ class EditMetadataAction(InterfaceAction):
cov = None
id_map[bid] = (opf, cov)
if review:
def get_metadata(book_id):
oldmi = db.get_metadata(book_id, index_is_id=True, get_cover=True, cover_as_data=True)
opf, cov = id_map[book_id]
if opf is None:
newmi = Metadata(oldmi.title, authors=tuple(oldmi.authors))
else:
with open(opf, 'rb') as f:
newmi = OPF(f, basedir=os.path.dirname(opf), populate_spine=False).to_book_metadata()
newmi.cover, newmi.cover_data = None, (None, None)
for x in ('title', 'authors'):
if newmi.is_null(x):
# Title and author are set to null if they are
# the same as the originals as an optimization,
# we undo that, as it is confusing.
newmi.set(x, copy.copy(oldmi.get(x)))
if cov:
with open(cov, 'rb') as f:
newmi.cover_data = ('jpg', f.read())
return oldmi, newmi
from calibre.gui2.metadata.diff import CompareMany
d = CompareMany(
set(id_map), get_metadata, db.field_metadata, parent=self.gui,
window_title=_('Review downloaded metadata'),
reject_button_tooltip=_('Discard downloaded metadata for this book'),
accept_all_tooltip=_('Use the downloaded metadata for all remaining books'),
reject_all_tooltip=_('Discard downloaded metadata for all remaining books'),
revert_tooltip=_('Discard the downloaded value for: %s'),
intro_msg=_('The downloaded metadata is on the left and the original metadata'
' is on the right. If a downloaded value is blank or unknown,'
' the original value is used.')
)
if d.exec_() == d.Accepted:
nid_map = {}
for book_id, (changed, mi) in d.accepted.iteritems():
if mi is None: # discarded
continue
if changed:
opf, cov = id_map[book_id]
cfile = mi.cover
mi.cover, mi.cover_data = None, (None, None)
with open(opf, 'wb') as f:
f.write(metadata_to_opf(mi))
if cfile:
shutil.copyfile(cfile, cov)
os.remove(cfile)
nid_map[book_id] = id_map[book_id]
id_map = nid_map
else:
id_map = {}
restrict_to_failed = bool(args and args[0])
if restrict_to_failed:
db.data.set_marked_ids(failed_ids)

View File

@ -12,7 +12,7 @@ from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
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 SafeFormat
from calibre.ebooks.metadata.book.formatter import SafeFormat
from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATETIME, \
gprefs, question_dialog

View File

@ -11,7 +11,8 @@ from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QSyntaxHighlighter, QFont,
from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.template_dialog_ui import Ui_TemplateDialog
from calibre.utils.formatter_functions import formatter_functions
from calibre.ebooks.metadata.book.base import SafeFormat, Metadata
from calibre.ebooks.metadata.book.base import Metadata
from calibre.ebooks.metadata.book.formatter import SafeFormat
from calibre.library.coloring import (displayable_columns)
@ -178,7 +179,7 @@ class TemplateHighlighter(QSyntaxHighlighter):
list = reversed(self.paren_positions[0:found_pp])
for pp in list:
if pp.paren == chr:
stack += 1;
stack += 1
elif stack:
stack -= 1
else:

View File

@ -14,7 +14,7 @@ from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage,
from calibre.gui2 import NONE, UNDEFINED_QDATETIME, error_dialog
from calibre.utils.search_query_parser import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ebooks.metadata.book.base import SafeFormat
from calibre.ebooks.metadata.book.formatter import SafeFormat
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import tweaks, device_prefs, prefs
from calibre.utils.date import dt_factory, qt_to_dt, as_local_time

View File

@ -78,7 +78,13 @@ class LineEdit(QLineEdit):
@property
def is_blank(self):
return not self.current_val.strip()
val = self.current_val.strip()
if self.field in {'title', 'authors'}:
return val in {'', _('Unknown')}
return not val
def same_as(self, other):
return self.current_val == other.current_val
class LanguagesEdit(LE):
@ -111,6 +117,9 @@ class LanguagesEdit(LE):
def is_blank(self):
return not self.current_val
def same_as(self, other):
return self.current_val == other.current_val
class RatingsEdit(RatingEdit):
changed = pyqtSignal()
@ -135,6 +144,9 @@ class RatingsEdit(RatingEdit):
def is_blank(self):
return self.value() == 0
def same_as(self, other):
return self.current_val == other.current_val
class DateEdit(PubdateEdit):
changed = pyqtSignal()
@ -157,7 +169,10 @@ class DateEdit(PubdateEdit):
@property
def is_blank(self):
return self.current_val == UNDEFINED_DATE
return self.current_val.year <= UNDEFINED_DATE.year
def same_as(self, other):
return self.text() == other.text()
class SeriesEdit(LineEdit):
@ -229,6 +244,9 @@ class CommentsEdit(Editor):
def is_blank(self):
return not self.current_val.strip()
def same_as(self, other):
return self.current_val == other.current_val
class CoverView(QWidget):
changed = pyqtSignal()
@ -288,6 +306,9 @@ class CoverView(QWidget):
pt.write(pixmap_to_data(self.pixmap))
mi.cover = pt.name
def same_as(self, other):
return self.current_val == other.current_val
def sizeHint(self):
return QSize(225, 300)
@ -383,7 +404,7 @@ class CompareSingle(QWidget):
def changed(self, field):
w = self.widgets[field]
if w.new.current_val != w.old.current_val and (not self.blank_as_equal or not w.new.is_blank):
if not w.new.same_as(w.old) and (not self.blank_as_equal or not w.new.is_blank):
w.label.setFont(self.changed_font)
else:
w.label.setFont(QApplication.font())
@ -412,8 +433,14 @@ class CompareSingle(QWidget):
class CompareMany(QDialog):
def __init__(self, ids, get_metadata, field_metadata, parent=None, window_title=None, skip_button_tooltip=None,
accept_all_tooltip=None, reject_all_tooltip=None, **kwargs):
def __init__(self, ids, get_metadata, field_metadata, parent=None,
window_title=None,
reject_button_tooltip=None,
accept_all_tooltip=None,
reject_all_tooltip=None,
revert_tooltip=None,
intro_msg=None,
**kwargs):
QDialog.__init__(self, parent)
self.l = l = QVBoxLayout()
self.setLayout(l)
@ -424,7 +451,12 @@ class CompareMany(QDialog):
self.accepted = OrderedDict()
self.window_title = window_title or _('Compare metadata')
self.compare_widget = CompareSingle(field_metadata, parent=parent, **kwargs)
if intro_msg:
self.la = la = QLabel(intro_msg)
la.setWordWrap(True)
l.addWidget(la)
self.compare_widget = CompareSingle(field_metadata, parent=parent, revert_tooltip=revert_tooltip, **kwargs)
self.sa = sa = QScrollArea()
l.addWidget(sa)
sa.setWidget(self.compare_widget)
@ -432,20 +464,24 @@ class CompareMany(QDialog):
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Cancel)
bb.rejected.connect(self.reject)
self.aarb = b = bb.addButton(_('&Accept all remaining'), bb.YesRole)
if accept_all_tooltip:
b.setToolTip(accept_all_tooltip)
b.clicked.connect(self.accept_all_remaining)
self.rarb = b = bb.addButton(_('Re&ject all remaining'), bb.NoRole)
if reject_all_tooltip:
b.setToolTip(reject_all_tooltip)
b.clicked.connect(self.reject_all_remaining)
self.sb = b = bb.addButton(_('&Reject'), bb.ActionRole)
b.clicked.connect(partial(self.next_item, False))
if skip_button_tooltip:
b.setToolTip(skip_button_tooltip)
self.nb = b = bb.addButton(_('&Next'), bb.ActionRole)
b.setIcon(QIcon(I('forward.png')))
if self.total > 1:
self.aarb = b = bb.addButton(_('&Accept all remaining'), bb.YesRole)
b.setIcon(QIcon(I('ok.png')))
if accept_all_tooltip:
b.setToolTip(accept_all_tooltip)
b.clicked.connect(self.accept_all_remaining)
self.rarb = b = bb.addButton(_('Re&ject all remaining'), bb.NoRole)
b.setIcon(QIcon(I('minus.png')))
if reject_all_tooltip:
b.setToolTip(reject_all_tooltip)
b.clicked.connect(self.reject_all_remaining)
self.sb = b = bb.addButton(_('&Reject'), bb.ActionRole)
b.clicked.connect(partial(self.next_item, False))
b.setIcon(QIcon(I('minus.png')))
if reject_button_tooltip:
b.setToolTip(reject_button_tooltip)
self.nb = b = bb.addButton(_('&Next') if self.total > 1 else _('&OK'), bb.ActionRole)
b.setIcon(QIcon(I('forward.png' if self.total > 1 else 'ok.png')))
b.clicked.connect(partial(self.next_item, True))
b.setDefault(True)
l.addWidget(bb)
@ -460,6 +496,7 @@ class CompareMany(QDialog):
geom = gprefs.get('diff_dialog_geom', None)
if geom is not None:
self.restoreGeometry(geom)
b.setFocus(Qt.OtherFocusReason)
def accept(self):
gprefs.set('diff_dialog_geom', bytearray(self.saveGeometry()))
@ -478,12 +515,14 @@ class CompareMany(QDialog):
return self.accept()
if self.current_mi is not None:
changed = self.compare_widget.apply_changes()
if self.current_mi is not None:
old_id = self.ids.pop(0)
self.accepted[old_id] = (changed, self.current_mi) if accept else (False, None)
if not self.ids:
return self.accept()
self.setWindowTitle(self.window_title + _(' [%(num)d of %(tot)d]') % dict(
num=(self.total - len(self.ids) + 1), tot=self.total))
oldmi, newmi = self.get_metadata(self.ids[0])
old_id = self.ids.pop(0)
if self.current_mi is not None:
self.accepted[old_id] = (changed, self.current_mi) if accept else (False, None)
self.compare_widget(oldmi, newmi)
def accept_all_remaining(self):

View File

@ -421,7 +421,7 @@ class MetadataSingleDialogBase(ResizableDialog):
ret = d.start(title=self.title.current_val, authors=self.authors.current_val,
identifiers=self.identifiers.current_val)
if ret == d.Accepted:
from calibre.ebooks.metadata.sources.base import msprefs
from calibre.ebooks.metadata.sources.prefs import msprefs
mi = d.book
dummy = Metadata(_('Unknown'))
for f in msprefs['ignore_fields']:

View File

@ -14,7 +14,7 @@ from PyQt4.Qt import (QAbstractTableModel, Qt, QAbstractListModel, QWidget,
from calibre.gui2.preferences import ConfigWidgetBase, test_widget
from calibre.gui2.preferences.metadata_sources_ui import Ui_Form
from calibre.ebooks.metadata.sources.base import msprefs
from calibre.ebooks.metadata.sources.prefs import msprefs
from calibre.customize.ui import (all_metadata_plugins, is_disabled,
enable_plugin, disable_plugin, default_disabled_plugins)
from calibre.gui2 import NONE, error_dialog, question_dialog

View File

@ -606,6 +606,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
if restrictions:
restrictions = ' :: ' + restrictions
font.setBold(True)
font.setItalic(True)
self.virtual_library.setFont(font)
title = u'{0} - || {1}{2} ||'.format(
__appname__, self.iactions['Choose Library'].library_name(), restrictions)

View File

@ -17,7 +17,6 @@ from calibre.ebooks import calibre_cover
from calibre.library import current_library_name
from calibre.library.catalogs import AuthorSortMismatchException, EmptyCatalogException
from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import JSONConfig
from calibre.utils.localization import calibre_langcode_to_name, canonicalize_lang, get_lang
Option = namedtuple('Option', 'option, default, dest, action, help')
@ -191,6 +190,7 @@ class EPUB_MOBI(CatalogPlugin):
def run(self, path_to_output, opts, db, notification=DummyReporter()):
from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder
from calibre.utils.logging import default_log as log
from calibre.utils.config import JSONConfig
# If preset specified from the cli, insert stored options from JSON file
if hasattr(opts, 'preset') and opts.preset:

View File

@ -11,7 +11,6 @@ import os, sys, shutil, cStringIO, glob, time, functools, traceback, re, \
from collections import defaultdict
import threading, random
from itertools import repeat
from math import ceil, floor
from calibre import prints, force_unicode
from calibre.ebooks.metadata import (title_sort, author_to_author_sort,
@ -42,6 +41,7 @@ from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format
from calibre.utils.magick.draw import save_cover_data_to
from calibre.utils.recycle_bin import delete_file, delete_tree
from calibre.utils.formatter_functions import load_user_template_functions
from calibre.db import _get_next_series_num_for_list, _get_series_values
from calibre.db.errors import NoSuchFormat
from calibre.db.lazy import FormatMetadata, FormatsList
from calibre.db.categories import Tag, CATEGORY_SORTS
@ -2194,31 +2194,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return self._get_next_series_num_for_list(series_indices)
def _get_next_series_num_for_list(self, series_indices):
if not series_indices:
if isinstance(tweaks['series_index_auto_increment'], (int, float)):
return float(tweaks['series_index_auto_increment'])
return 1.0
series_indices = [x[0] for x in series_indices]
if tweaks['series_index_auto_increment'] == 'next':
return floor(series_indices[-1]) + 1
if tweaks['series_index_auto_increment'] == 'first_free':
for i in range(1, 10000):
if i not in series_indices:
return i
# really shouldn't get here.
if tweaks['series_index_auto_increment'] == 'next_free':
for i in range(int(ceil(series_indices[0])), 10000):
if i not in series_indices:
return i
# really shouldn't get here.
if tweaks['series_index_auto_increment'] == 'last_free':
for i in range(int(ceil(series_indices[-1])), 0, -1):
if i not in series_indices:
return i
return series_indices[-1] + 1
if isinstance(tweaks['series_index_auto_increment'], (int, float)):
return float(tweaks['series_index_auto_increment'])
return 1.0
return _get_next_series_num_for_list(series_indices)
def set(self, row, column, val, allow_case_change=False):
'''
@ -3156,17 +3132,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
series_index_pat = re.compile(r'(.*)\s+\[([.0-9]+)\]$')
def _get_series_values(self, val):
if not val:
return (val, None)
match = self.series_index_pat.match(val.strip())
if match is not None:
idx = match.group(2)
try:
idx = float(idx)
return (match.group(1).strip(), idx)
except:
pass
return (val, None)
return _get_series_values(val)
def set_series(self, id, series, notify=True, commit=True, allow_case_change=True):
self.conn.execute('DELETE FROM books_series_link WHERE book=?',(id,))

View File

@ -6,7 +6,7 @@ Created on 25 May 2010
import copy, traceback
from collections import OrderedDict
from calibre.utils.config import tweaks
from calibre.utils.config_base import tweaks
class TagsIcons(dict):
'''

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
import os
from calibre.utils.config import Config, StringConfig, config_dir, tweaks
from calibre.utils.config_base import Config, StringConfig, config_dir, tweaks
listen_on = tweaks['server_listen_on']

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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