This commit is contained in:
GRiker 2013-03-01 04:26:07 -07:00
commit 4fe4ab84ee
124 changed files with 32602 additions and 26505 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,182 @@
__license__ = 'GPL v3'
__copyright__ = '2013, Darko Miletic <darko.miletic at gmail.com>'
'''
http://www.ft.com/intl/us-edition
'''
import datetime
from calibre.ptempfile import PersistentTemporaryFile
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class FinancialTimes(BasicNewsRecipe):
title = 'Financial Times (US) printed edition'
__author__ = 'Darko Miletic'
description = "The Financial Times (FT) is one of the world's leading business news and information organisations, recognised internationally for its authority, integrity and accuracy."
publisher = 'The Financial Times Ltd.'
category = 'news, finances, politics, UK, World'
oldest_article = 2
language = 'en'
max_articles_per_feed = 250
no_stylesheets = True
use_embedded_content = False
needs_subscription = True
encoding = 'utf8'
publication_type = 'newspaper'
articles_are_obfuscated = True
temp_files = []
masthead_url = 'http://im.media.ft.com/m/img/masthead_main.jpg'
LOGIN = 'https://registration.ft.com/registration/barrier/login'
LOGIN2 = 'http://media.ft.com/h/subs3.html'
INDEX = 'http://www.ft.com/intl/us-edition'
PREFIX = 'http://www.ft.com'
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
, 'linearize_tables' : True
}
def get_browser(self):
br = BasicNewsRecipe.get_browser(self)
br.open(self.INDEX)
if self.username is not None and self.password is not None:
br.open(self.LOGIN2)
br.select_form(name='loginForm')
br['username'] = self.username
br['password'] = self.password
br.submit()
return br
keep_only_tags = [
dict(name='div' , attrs={'class':['fullstory fullstoryHeader', 'ft-story-header']})
,dict(name='div' , attrs={'class':'standfirst'})
,dict(name='div' , attrs={'id' :'storyContent'})
,dict(name='div' , attrs={'class':['ft-story-body','index-detail']})
,dict(name='h2' , attrs={'class':'entry-title'} )
,dict(name='span', attrs={'class':lambda x: x and 'posted-on' in x.split()} )
,dict(name='span', attrs={'class':'author_byline'} )
,dict(name='div' , attrs={'class':'entry-content'} )
]
remove_tags = [
dict(name='div', attrs={'id':'floating-con'})
,dict(name=['meta','iframe','base','object','embed','link'])
,dict(attrs={'class':['storyTools','story-package','screen-copy','story-package separator','expandable-image']})
]
remove_attributes = ['width','height','lang']
extra_css = """
body{font-family: Georgia,Times,"Times New Roman",serif}
h2{font-size:large}
.ft-story-header{font-size: x-small}
.container{font-size:x-small;}
h3{font-size:x-small;color:#003399;}
.copyright{font-size: x-small}
img{margin-top: 0.8em; display: block}
.lastUpdated{font-family: Arial,Helvetica,sans-serif; font-size: x-small}
.byline,.ft-story-body,.ft-story-header{font-family: Arial,Helvetica,sans-serif}
"""
def get_artlinks(self, elem):
articles = []
count = 0
for item in elem.findAll('a',href=True):
count = count + 1
if self.test and count > 2:
return articles
rawlink = item['href']
url = rawlink
if not rawlink.startswith('http://'):
url = self.PREFIX + rawlink
try:
urlverified = self.browser.open_novisit(url).geturl() # resolve redirect.
except:
continue
title = self.tag_to_string(item)
date = strftime(self.timefmt)
articles.append({
'title' :title
,'date' :date
,'url' :urlverified
,'description':''
})
return articles
def parse_index(self):
feeds = []
soup = self.index_to_soup(self.INDEX)
dates= self.tag_to_string(soup.find('div', attrs={'class':'btm-links'}).find('div'))
self.timefmt = ' [%s]'%dates
wide = soup.find('div',attrs={'class':'wide'})
if not wide:
return feeds
allsections = wide.findAll(attrs={'class':lambda x: x and 'footwell' in x.split()})
if not allsections:
return feeds
count = 0
for item in allsections:
count = count + 1
if self.test and count > 2:
return feeds
fitem = item.h3
if not fitem:
fitem = item.h4
ftitle = self.tag_to_string(fitem)
self.report_progress(0, _('Fetching feed')+' %s...'%(ftitle))
feedarts = self.get_artlinks(item.ul)
feeds.append((ftitle,feedarts))
return feeds
def preprocess_html(self, soup):
items = ['promo-box','promo-title',
'promo-headline','promo-image',
'promo-intro','promo-link','subhead']
for item in items:
for it in soup.findAll(item):
it.name = 'div'
it.attrs = []
for item in soup.findAll(style=True):
del item['style']
for item in soup.findAll('a'):
limg = item.find('img')
if item.string is not None:
str = item.string
item.replaceWith(str)
else:
if limg:
item.name = 'div'
item.attrs = []
else:
str = self.tag_to_string(item)
item.replaceWith(str)
for item in soup.findAll('img'):
if not item.has_key('alt'):
item['alt'] = 'image'
return soup
def get_cover_url(self):
cdate = datetime.date.today()
if cdate.isoweekday() == 7:
cdate -= datetime.timedelta(days=1)
return cdate.strftime('http://specials.ft.com/vtf_pdf/%d%m%y_FRONT1_USA.pdf')
def get_obfuscated_article(self, url):
count = 0
while (count < 10):
try:
response = self.browser.open(url)
html = response.read()
count = 10
except:
print "Retrying download..."
count += 1
tfile = PersistentTemporaryFile('_fa.html')
tfile.write(html)
tfile.close()
self.temp_files.append(tfile)
return tfile.name
def cleanup(self):
self.browser.open('https://registration.ft.com/registration/login/logout?location=')

View File

@ -1,5 +1,4 @@
from calibre.web.feeds.news import BasicNewsRecipe
import re
class HNonlineRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
@ -65,4 +64,4 @@ class HNonlineRecipe(BasicNewsRecipe):
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/LiberationSans.ttf)}
body {font-family: sans1, serif1;}
'''
'''

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -5,7 +5,6 @@
http://www.unperiodico.unal.edu.co/
'''
from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe
class UNPeriodico(BasicNewsRecipe):
@ -18,5 +17,5 @@ class UNPeriodico(BasicNewsRecipe):
max_articles_per_feed = 100
publication_type = 'newspaper'
feeds = [
(u'UNPeriodico', u'http://www.unperiodico.unal.edu.co/rss/type/rss2/')
]
(u'UNPeriodico', u'http://www.unperiodico.unal.edu.co/rss/type/rss2/')
]

Binary file not shown.

View File

@ -482,5 +482,10 @@ h2.library_name {
border: none
}
.details #random_button {
display:block
}
/* }}} */

View File

@ -324,9 +324,15 @@ function show_details(a_dom) {
function book() {
hidesort();
$('.details .left img').load(function() {
var rb = $('#random_button');
rb.button();
var img = $('.details .left img');
var height = $('#main').height();
height = Math.max(height, img.height() + 100);
var bh = 0;
if (rb.length > 0) {
bh = rb.height();
}
height = Math.max(height, img.height() + bh + 100);
$('#main').height(height);
});
}

View File

@ -1,6 +1,7 @@
<div id="details_{id}" class="details">
<div class="left">
<a href="{get_url}" title="Click to read {title} in the {fmt} format" class="details_thumb"><img alt="Cover of {title}" src="{prefix}/get/cover/{id}" /></a>
{random}
</div>
<div class="right">
<div class="field formats">{formats}</div>

View File

@ -524,3 +524,10 @@ preselect_first_completion = False
# that starts with numbers and is a little slower.
numeric_collation = False
#: Sort the list of libraries alphabetically
# The list of libraries in the Copy to Library and Quick Switch menus are
# normally sorted by most used. However, if there are more than a certain
# number of such libraries, the sorting becomes alphabetic. You can set that
# number here. The default is ten libraries.
many_libraries = 10

View File

@ -38,7 +38,7 @@ binary_includes = [
'/lib/libz.so.1',
'/usr/lib/libtiff.so.5',
'/lib/libbz2.so.1',
'/usr/lib/libpoppler.so.27',
'/usr/lib/libpoppler.so.28',
'/usr/lib/libxml2.so.2',
'/usr/lib/libopenjpeg.so.2',
'/usr/lib/libxslt.so.1',

View File

@ -9,14 +9,14 @@ msgstr ""
"Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2013-01-12 08:34+0000\n"
"Last-Translator: Jellby <Unknown>\n"
"PO-Revision-Date: 2013-02-26 12:21+0000\n"
"Last-Translator: Miguel Angel del Olmo <silinio45@gmail.com>\n"
"Language-Team: Español; Castellano <>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-01-13 04:37+0000\n"
"X-Generator: Launchpad (build 16420)\n"
"X-Launchpad-Export-Date: 2013-02-27 04:37+0000\n"
"X-Generator: Launchpad (build 16506)\n"
#. name for aaa
msgid "Ghotuo"
@ -9708,7 +9708,7 @@ msgstr ""
#. name for hto
msgid "Huitoto; Minica"
msgstr ""
msgstr "Huitoto; Meneca"
#. name for hts
msgid "Hadza"
@ -9736,7 +9736,7 @@ msgstr ""
#. name for hue
msgid "Huave; San Francisco Del Mar"
msgstr ""
msgstr "Huave; San Francisco Del Mar"
#. name for huf
msgid "Humene"
@ -9792,7 +9792,7 @@ msgstr ""
#. name for hus
msgid "Huastec"
msgstr ""
msgstr "Huasteco"
#. name for hut
msgid "Humla"
@ -9800,11 +9800,11 @@ msgstr ""
#. name for huu
msgid "Huitoto; Murui"
msgstr ""
msgstr "Huitoto; Murui"
#. name for huv
msgid "Huave; San Mateo Del Mar"
msgstr ""
msgstr "Huave; San Mateo Del Mar"
#. name for huw
msgid "Hukumina"
@ -9812,7 +9812,7 @@ msgstr ""
#. name for hux
msgid "Huitoto; Nüpode"
msgstr ""
msgstr "Huitoto; Nipode"
#. name for huy
msgid "Hulaulá"
@ -9828,7 +9828,7 @@ msgstr ""
#. name for hve
msgid "Huave; San Dionisio Del Mar"
msgstr ""
msgstr "Huave; San Dionisio Del Mar"
#. name for hvk
msgid "Haveke"
@ -9840,7 +9840,7 @@ msgstr ""
#. name for hvv
msgid "Huave; Santa María Del Mar"
msgstr ""
msgstr "Huave; Santa María Del Mar"
#. name for hwa
msgid "Wané"
@ -9884,7 +9884,7 @@ msgstr "Iban"
#. name for ibb
msgid "Ibibio"
msgstr ""
msgstr "Ibibio"
#. name for ibd
msgid "Iwaidja"
@ -9964,7 +9964,7 @@ msgstr ""
#. name for ide
msgid "Idere"
msgstr ""
msgstr "Idere"
#. name for idi
msgid "Idi"
@ -9976,7 +9976,7 @@ msgstr "Ido"
#. name for idr
msgid "Indri"
msgstr ""
msgstr "Indri"
#. name for ids
msgid "Idesa"
@ -9988,7 +9988,7 @@ msgstr ""
#. name for idu
msgid "Idoma"
msgstr ""
msgstr "Idoma"
#. name for ifa
msgid "Ifugao; Amganad"
@ -9996,7 +9996,7 @@ msgstr ""
#. name for ifb
msgid "Ifugao; Batad"
msgstr ""
msgstr "Ifugao; Batad"
#. name for ife
msgid "Ifè"
@ -10004,7 +10004,7 @@ msgstr ""
#. name for iff
msgid "Ifo"
msgstr ""
msgstr "Ifo"
#. name for ifk
msgid "Ifugao; Tuwali"
@ -10064,7 +10064,7 @@ msgstr ""
#. name for ihi
msgid "Ihievbe"
msgstr ""
msgstr "Ihievbe"
#. name for ihp
msgid "Iha"
@ -10288,15 +10288,15 @@ msgstr ""
#. name for iou
msgid "Tuma-Irumu"
msgstr ""
msgstr "Tuma-Irumu"
#. name for iow
msgid "Iowa-Oto"
msgstr ""
msgstr "Iowa-Oto"
#. name for ipi
msgid "Ipili"
msgstr ""
msgstr "Ipili"
#. name for ipk
msgid "Inupiaq"
@ -10304,7 +10304,7 @@ msgstr "Iñupiaq"
#. name for ipo
msgid "Ipiko"
msgstr ""
msgstr "Ipiko"
#. name for iqu
msgid "Iquito"
@ -30768,7 +30768,7 @@ msgstr ""
#. name for zts
msgid "Zapotec; Tilquiapan"
msgstr ""
msgstr "Zapoteco; Tilquiapan"
#. name for ztt
msgid "Zapotec; Tejalapan"

View File

@ -13,14 +13,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2012-04-04 19:53+0000\n"
"Last-Translator: Antoni Kudelski <antekk@linux.pl>\n"
"PO-Revision-Date: 2013-02-23 12:04+0000\n"
"Last-Translator: Marcin Ostajewski (panszpik) <Unknown>\n"
"Language-Team: Polish <translation-team-pl@lists.sourceforge.net>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-04-05 04:43+0000\n"
"X-Generator: Launchpad (build 15060)\n"
"X-Launchpad-Export-Date: 2013-02-24 04:41+0000\n"
"X-Generator: Launchpad (build 16506)\n"
"Language: pl\n"
#. name for aaa
@ -857,11 +857,11 @@ msgstr "Akurio"
#. name for akp
msgid "Siwu"
msgstr ""
msgstr "Siwu"
#. name for akq
msgid "Ak"
msgstr ""
msgstr "Ak"
#. name for akr
msgid "Araki"
@ -973,7 +973,7 @@ msgstr "ałtajski południowy"
#. name for alu
msgid "'Are'are"
msgstr ""
msgstr "'Are'are"
#. name for alw
msgid "Alaba-Kabeena"
@ -1037,7 +1037,7 @@ msgstr "War-Jaintia"
#. name for amm
msgid "Ama (Papua New Guinea)"
msgstr ""
msgstr "Ama (Papua New Guinea)"
#. name for amn
msgid "Amanab"
@ -1061,7 +1061,7 @@ msgstr "Amarakaeri"
#. name for ams
msgid "Amami-Oshima; Southern"
msgstr ""
msgstr "Południowy amami-oshima"
#. name for amt
msgid "Amto"
@ -1069,7 +1069,7 @@ msgstr "Amto"
#. name for amu
msgid "Amuzgo; Guerrero"
msgstr ""
msgstr "Amuzgo; Guerrero"
#. name for amv
msgid "Ambelau"
@ -1249,7 +1249,7 @@ msgstr "Ömie"
#. name for aon
msgid "Arapesh; Bumbita"
msgstr ""
msgstr "Arapesh; Bumbita"
#. name for aor
msgid "Aore"
@ -1289,7 +1289,7 @@ msgstr "Bukiyip"
#. name for apf
msgid "Agta; Pahanan"
msgstr ""
msgstr "Agta; Pahanan"
#. name for apg
msgid "Ampanang"
@ -1305,19 +1305,19 @@ msgstr "Apiaká"
#. name for apj
msgid "Apache; Jicarilla"
msgstr ""
msgstr "Apache; Jicarilla"
#. name for apk
msgid "Apache; Kiowa"
msgstr ""
msgstr "Apache; Kiowa"
#. name for apl
msgid "Apache; Lipan"
msgstr ""
msgstr "Apache; Lipan"
#. name for apm
msgid "Apache; Mescalero-Chiricahua"
msgstr ""
msgstr "Apache; Mescalero-Chiricahua"
#. name for apn
msgid "Apinayé"
@ -1337,11 +1337,11 @@ msgstr "a-pucikwar"
#. name for apr
msgid "Arop-Lokep"
msgstr ""
msgstr "Arop-Lokep"
#. name for aps
msgid "Arop-Sissano"
msgstr ""
msgstr "Arop-Sissano"
#. name for apt
msgid "Apatani"
@ -1357,7 +1357,7 @@ msgstr "Alapmunte"
#. name for apw
msgid "Apache; Western"
msgstr ""
msgstr "Zachodni apache"
#. name for apx
msgid "Aputai"
@ -1389,7 +1389,7 @@ msgstr "Atohwaim"
#. name for aqn
msgid "Alta; Northern"
msgstr ""
msgstr "Północny alta"
#. name for aqp
msgid "Atakapa"
@ -1409,7 +1409,7 @@ msgstr "arabski"
#. name for arb
msgid "Arabic; Standard"
msgstr ""
msgstr "Standardowy arabski"
#. name for arc
msgid "Aramaic; Official (700-300 BCE)"
@ -1465,15 +1465,15 @@ msgstr "arabski algierski"
#. name for arr
msgid "Karo (Brazil)"
msgstr ""
msgstr "Karo (Brazylia)"
#. name for ars
msgid "Arabic; Najdi"
msgstr ""
msgstr "Arabski Najdi"
#. name for aru
msgid "Aruá (Amazonas State)"
msgstr ""
msgstr "Aruá (stan Amazonas)"
#. name for arv
msgid "Arbore"
@ -1485,7 +1485,7 @@ msgstr "arawak"
#. name for arx
msgid "Aruá (Rodonia State)"
msgstr ""
msgstr "Aruá (stan Rodonia)"
#. name for ary
msgid "Arabic; Moroccan"
@ -1529,11 +1529,11 @@ msgstr "Abishira"
#. name for asi
msgid "Buruwai"
msgstr ""
msgstr "Buruwai"
#. name for asj
msgid "Nsari"
msgstr ""
msgstr "Nsari"
#. name for ask
msgid "Ashkun"
@ -1541,7 +1541,7 @@ msgstr "aszkun"
#. name for asl
msgid "Asilulu"
msgstr ""
msgstr "Asilulu"
#. name for asm
msgid "Assamese"
@ -1549,11 +1549,11 @@ msgstr "asamski"
#. name for asn
msgid "Asuriní; Xingú"
msgstr ""
msgstr "Asuriní; Xingú"
#. name for aso
msgid "Dano"
msgstr ""
msgstr "Dano"
#. name for asp
msgid "Algerian Sign Language"
@ -1565,11 +1565,11 @@ msgstr "austriacki język migowy"
#. name for asr
msgid "Asuri"
msgstr ""
msgstr "Asuri"
#. name for ass
msgid "Ipulo"
msgstr ""
msgstr "Ipulo"
#. name for ast
msgid "Asturian"
@ -1577,11 +1577,11 @@ msgstr "asturyjski"
#. name for asu
msgid "Asurini; Tocantins"
msgstr ""
msgstr "Asurini; Tocantins"
#. name for asv
msgid "Asoa"
msgstr ""
msgstr "Asoa"
#. name for asw
msgid "Australian Aborigines Sign Language"
@ -1589,43 +1589,43 @@ msgstr "język migowy Aborygenów australijskich"
#. name for asx
msgid "Muratayak"
msgstr ""
msgstr "Muratayak"
#. name for asy
msgid "Asmat; Yaosakor"
msgstr ""
msgstr "Asmat; Yaosakor"
#. name for asz
msgid "As"
msgstr ""
msgstr "As"
#. name for ata
msgid "Pele-Ata"
msgstr ""
msgstr "Pele-Ata"
#. name for atb
msgid "Zaiwa"
msgstr ""
msgstr "Zaiwa"
#. name for atc
msgid "Atsahuaca"
msgstr ""
msgstr "Atsahuaca"
#. name for atd
msgid "Manobo; Ata"
msgstr ""
msgstr "Manobo; Ata"
#. name for ate
msgid "Atemble"
msgstr ""
msgstr "Atemble"
#. name for atg
msgid "Ivbie North-Okpela-Arhe"
msgstr ""
msgstr "Ivbie North-Okpela-Arhe"
#. name for ati
msgid "Attié"
msgstr ""
msgstr "Attié"
#. name for atj
msgid "Atikamekw"
@ -1633,111 +1633,111 @@ msgstr "atikamekw"
#. name for atk
msgid "Ati"
msgstr ""
msgstr "Ati"
#. name for atl
msgid "Agta; Mt. Iraya"
msgstr ""
msgstr "Agta; Mt. Iraya"
#. name for atm
msgid "Ata"
msgstr ""
msgstr "Ata"
#. name for atn
msgid "Ashtiani"
msgstr ""
msgstr "Ashtiani"
#. name for ato
msgid "Atong"
msgstr ""
msgstr "Atong"
#. name for atp
msgid "Atta; Pudtol"
msgstr ""
msgstr "Atta; Pudtol"
#. name for atq
msgid "Aralle-Tabulahan"
msgstr ""
msgstr "Aralle-Tabulahan"
#. name for atr
msgid "Waimiri-Atroari"
msgstr ""
msgstr "Waimiri-Atroari"
#. name for ats
msgid "Gros Ventre"
msgstr ""
msgstr "Gros Ventre"
#. name for att
msgid "Atta; Pamplona"
msgstr ""
msgstr "Atta; Pamplona"
#. name for atu
msgid "Reel"
msgstr ""
msgstr "Reel"
#. name for atv
msgid "Altai; Northern"
msgstr ""
msgstr "Altai; Northern"
#. name for atw
msgid "Atsugewi"
msgstr ""
msgstr "Atsugewi"
#. name for atx
msgid "Arutani"
msgstr ""
msgstr "Arutani"
#. name for aty
msgid "Aneityum"
msgstr ""
msgstr "Aneityum"
#. name for atz
msgid "Arta"
msgstr ""
msgstr "Arta"
#. name for aua
msgid "Asumboa"
msgstr ""
msgstr "Asumboa"
#. name for aub
msgid "Alugu"
msgstr ""
msgstr "Alugu"
#. name for auc
msgid "Waorani"
msgstr ""
msgstr "Waorani"
#. name for aud
msgid "Anuta"
msgstr ""
msgstr "Anuta"
#. name for aue
msgid "=/Kx'au//'ein"
msgstr ""
msgstr "=/Kx'au//'ein"
#. name for aug
msgid "Aguna"
msgstr ""
msgstr "Aguna"
#. name for auh
msgid "Aushi"
msgstr ""
msgstr "Aushi"
#. name for aui
msgid "Anuki"
msgstr ""
msgstr "Anuki"
#. name for auj
msgid "Awjilah"
msgstr ""
msgstr "Awjilah"
#. name for auk
msgid "Heyo"
msgstr ""
msgstr "Heyo"
#. name for aul
msgid "Aulua"
msgstr ""
msgstr "Aulua"
#. name for aum
msgid "Asu (Nigeria)"
@ -1745,11 +1745,11 @@ msgstr "asu (Nigeria)"
#. name for aun
msgid "One; Molmo"
msgstr ""
msgstr "One; Molmo"
#. name for auo
msgid "Auyokawa"
msgstr ""
msgstr "Auyokawa"
#. name for aup
msgid "Makayam"
@ -1757,19 +1757,19 @@ msgstr ""
#. name for auq
msgid "Anus"
msgstr ""
msgstr "Anus"
#. name for aur
msgid "Aruek"
msgstr ""
msgstr "Aruek"
#. name for aut
msgid "Austral"
msgstr ""
msgstr "Austral"
#. name for auu
msgid "Auye"
msgstr ""
msgstr "Auye"
#. name for auw
msgid "Awyi"
@ -1781,7 +1781,7 @@ msgstr ""
#. name for auy
msgid "Awiyaana"
msgstr ""
msgstr "Awiyaana"
#. name for auz
msgid "Arabic; Uzbeki"
@ -1793,11 +1793,11 @@ msgstr "awarski"
#. name for avb
msgid "Avau"
msgstr ""
msgstr "Avau"
#. name for avd
msgid "Alviri-Vidari"
msgstr ""
msgstr "Alviri-Vidari"
#. name for ave
msgid "Avestan"
@ -1805,11 +1805,11 @@ msgstr "awestyjski"
#. name for avi
msgid "Avikam"
msgstr ""
msgstr "Avikam"
#. name for avk
msgid "Kotava"
msgstr ""
msgstr "Kotava"
#. name for avl
msgid "Arabic; Eastern Egyptian Bedawi"
@ -1817,23 +1817,23 @@ msgstr ""
#. name for avn
msgid "Avatime"
msgstr ""
msgstr "Avatime"
#. name for avo
msgid "Agavotaguerra"
msgstr ""
msgstr "Agavotaguerra"
#. name for avs
msgid "Aushiri"
msgstr ""
msgstr "Aushiri"
#. name for avt
msgid "Au"
msgstr ""
msgstr "Au"
#. name for avu
msgid "Avokaya"
msgstr ""
msgstr "Avokaya"
#. name for avv
msgid "Avá-Canoeiro"
@ -1849,7 +1849,7 @@ msgstr "awa (Papua Nowa Gwinea)"
#. name for awc
msgid "Cicipu"
msgstr ""
msgstr "Cicipu"
#. name for awe
msgid "Awetí"
@ -1857,15 +1857,15 @@ msgstr ""
#. name for awh
msgid "Awbono"
msgstr ""
msgstr "Awbono"
#. name for awi
msgid "Aekyom"
msgstr ""
msgstr "Aekyom"
#. name for awk
msgid "Awabakal"
msgstr ""
msgstr "Awabakal"
#. name for awm
msgid "Arawum"
@ -1873,31 +1873,31 @@ msgstr "arawum"
#. name for awn
msgid "Awngi"
msgstr ""
msgstr "Awngi"
#. name for awo
msgid "Awak"
msgstr ""
msgstr "Awak"
#. name for awr
msgid "Awera"
msgstr ""
msgstr "Awera"
#. name for aws
msgid "Awyu; South"
msgstr ""
msgstr "Południowy aywu"
#. name for awt
msgid "Araweté"
msgstr ""
msgstr "Araweté"
#. name for awu
msgid "Awyu; Central"
msgstr ""
msgstr "Środkowy aywu"
#. name for awv
msgid "Awyu; Jair"
msgstr ""
msgstr "Awyu; Jair"
#. name for aww
msgid "Awun"
@ -1905,7 +1905,7 @@ msgstr "awun"
#. name for awx
msgid "Awara"
msgstr ""
msgstr "Awara"
#. name for awy
msgid "Awyu; Edera"
@ -1913,15 +1913,15 @@ msgstr "ederah"
#. name for axb
msgid "Abipon"
msgstr ""
msgstr "Abipon"
#. name for axg
msgid "Arára; Mato Grosso"
msgstr ""
msgstr "Arára; Mato Grosso"
#. name for axk
msgid "Yaka (Central African Republic)"
msgstr ""
msgstr "Yaka (Central African Republic)"
#. name for axm
msgid "Armenian; Middle"
@ -1929,7 +1929,7 @@ msgstr "średnioormiański"
#. name for axx
msgid "Xaragure"
msgstr ""
msgstr "Xaragure"
#. name for aya
msgid "Awar"
@ -1937,7 +1937,7 @@ msgstr "awar"
#. name for ayb
msgid "Gbe; Ayizo"
msgstr ""
msgstr "Gbe; Ayizo"
#. name for ayc
msgid "Aymara; Southern"
@ -1945,27 +1945,27 @@ msgstr "ajmara południowy"
#. name for ayd
msgid "Ayabadhu"
msgstr ""
msgstr "Ayabadhu"
#. name for aye
msgid "Ayere"
msgstr ""
msgstr "Ayere"
#. name for ayg
msgid "Ginyanga"
msgstr ""
msgstr "Ginyanga"
#. name for ayh
msgid "Arabic; Hadrami"
msgstr ""
msgstr "Arabski Hadrami"
#. name for ayi
msgid "Leyigha"
msgstr ""
msgstr "Leyigha"
#. name for ayk
msgid "Akuku"
msgstr ""
msgstr "Akuku"
#. name for ayl
msgid "Arabic; Libyan"
@ -1977,19 +1977,19 @@ msgstr "ajmara"
#. name for ayn
msgid "Arabic; Sanaani"
msgstr ""
msgstr "Arabski Sanaani"
#. name for ayo
msgid "Ayoreo"
msgstr ""
msgstr "Ayoreo"
#. name for ayp
msgid "Arabic; North Mesopotamian"
msgstr ""
msgstr "Arabski; Mezopotamia Północna"
#. name for ayq
msgid "Ayi (Papua New Guinea)"
msgstr ""
msgstr "Ayi (Papua Nowa Gwinea)"
#. name for ayr
msgid "Aymara; Central"
@ -1997,27 +1997,27 @@ msgstr "ajmara centralny"
#. name for ays
msgid "Ayta; Sorsogon"
msgstr ""
msgstr "Ayta; Sorsogon"
#. name for ayt
msgid "Ayta; Magbukun"
msgstr ""
msgstr "Ayta; Magbukun"
#. name for ayu
msgid "Ayu"
msgstr ""
msgstr "Ayu"
#. name for ayy
msgid "Ayta; Tayabas"
msgstr ""
msgstr "Ayta; Tayabas"
#. name for ayz
msgid "Mai Brat"
msgstr ""
msgstr "Mai Brat"
#. name for aza
msgid "Azha"
msgstr ""
msgstr "Azha"
#. name for azb
msgid "Azerbaijani; South"
@ -2029,7 +2029,7 @@ msgstr "azerski"
#. name for azg
msgid "Amuzgo; San Pedro Amuzgos"
msgstr ""
msgstr "Amuzgo; San Pedro Amuzgos"
#. name for azj
msgid "Azerbaijani; North"
@ -2037,35 +2037,35 @@ msgstr "północnoazerski"
#. name for azm
msgid "Amuzgo; Ipalapa"
msgstr ""
msgstr "Amuzgo; Ipalapa"
#. name for azo
msgid "Awing"
msgstr ""
msgstr "Awing"
#. name for azt
msgid "Atta; Faire"
msgstr ""
msgstr "Atta; Faire"
#. name for azz
msgid "Nahuatl; Highland Puebla"
msgstr ""
msgstr "Nahuatl; Wyżyna Puebla"
#. name for baa
msgid "Babatana"
msgstr ""
msgstr "Babatana"
#. name for bab
msgid "Bainouk-Gunyuño"
msgstr ""
msgstr "Bainouk-Gunyuño"
#. name for bac
msgid "Badui"
msgstr ""
msgstr "Badui"
#. name for bae
msgid "Baré"
msgstr ""
msgstr "Baré"
#. name for baf
msgid "Nubaca"

File diff suppressed because it is too large Load Diff

View File

@ -13,14 +13,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
"PO-Revision-Date: 2013-01-21 14:06+0000\n"
"Last-Translator: Don Miguel <bmv@mail.ru>\n"
"PO-Revision-Date: 2013-02-21 23:51+0000\n"
"Last-Translator: Глория Хрусталёва <gloriya@hushmail.com>\n"
"Language-Team: Russian <debian-l10n-russian@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-01-22 04:46+0000\n"
"X-Generator: Launchpad (build 16430)\n"
"X-Launchpad-Export-Date: 2013-02-23 05:19+0000\n"
"X-Generator: Launchpad (build 16506)\n"
"Language: ru\n"
#. name for aaa
@ -237,7 +237,7 @@ msgstr "Ачехский"
#. name for acf
msgid "Creole French; Saint Lucian"
msgstr ""
msgstr "Креольский французский; Сент-люсийский"
#. name for ach
msgid "Acoli"
@ -257,7 +257,7 @@ msgstr ""
#. name for acm
msgid "Arabic; Mesopotamian"
msgstr ""
msgstr "Арабский; Месопатамский"
#. name for acn
msgid "Achang"
@ -273,7 +273,7 @@ msgstr ""
#. name for acr
msgid "Achi"
msgstr ""
msgstr "Ачи"
#. name for acs
msgid "Acroá"
@ -297,7 +297,7 @@ msgstr ""
#. name for acx
msgid "Arabic; Omani"
msgstr ""
msgstr "Арабский; Оманский"
#. name for acy
msgid "Arabic; Cypriot"
@ -369,7 +369,7 @@ msgstr ""
#. name for ads
msgid "Adamorobe Sign Language"
msgstr ""
msgstr "Знаковый язык Адаморобе"
#. name for adt
msgid "Adnyamathanha"
@ -389,7 +389,7 @@ msgstr ""
#. name for ady
msgid "Adyghe"
msgstr ""
msgstr "Адыгейский"
#. name for adz
msgid "Adzera"
@ -401,7 +401,7 @@ msgstr ""
#. name for aeb
msgid "Arabic; Tunisian"
msgstr ""
msgstr "Арабский; Тунисский"
#. name for aec
msgid "Arabic; Saidi"
@ -409,7 +409,7 @@ msgstr ""
#. name for aed
msgid "Argentine Sign Language"
msgstr ""
msgstr "Аргентинский язык жестов"
#. name for aee
msgid "Pashayi; Northeast"
@ -429,7 +429,7 @@ msgstr ""
#. name for aen
msgid "Armenian Sign Language"
msgstr ""
msgstr "Армянский язык жестов"
#. name for aeq
msgid "Aer"
@ -609,7 +609,7 @@ msgstr ""
#. name for agx
msgid "Aghul"
msgstr ""
msgstr "Агульский"
#. name for agy
msgid "Alta; Southern"
@ -665,7 +665,7 @@ msgstr ""
#. name for ahr
msgid "Ahirani"
msgstr ""
msgstr "Ахирани"
#. name for ahs
msgid "Ashe"
@ -701,7 +701,7 @@ msgstr ""
#. name for aig
msgid "Creole English; Antigua and Barbuda"
msgstr ""
msgstr "Креольский английский; Антигуа и Барбуда"
#. name for aih
msgid "Ai-Cham"
@ -709,7 +709,7 @@ msgstr ""
#. name for aii
msgid "Neo-Aramaic; Assyrian"
msgstr ""
msgstr "Новоарамейский; Ассирийский"
#. name for aij
msgid "Lishanid Noshan"
@ -825,7 +825,7 @@ msgstr ""
#. name for akg
msgid "Anakalangu"
msgstr ""
msgstr "Анакалангу"
#. name for akh
msgid "Angal Heneng"
@ -881,7 +881,7 @@ msgstr ""
#. name for akv
msgid "Akhvakh"
msgstr ""
msgstr "Ахвахский"
#. name for akw
msgid "Akwa"
@ -897,7 +897,7 @@ msgstr ""
#. name for akz
msgid "Alabama"
msgstr ""
msgstr "Язык племени алабама"
#. name for ala
msgid "Alago"
@ -945,7 +945,7 @@ msgstr ""
#. name for aln
msgid "Albanian; Gheg"
msgstr ""
msgstr "Албанский; Гегский"
#. name for alo
msgid "Larike-Wakasihu"
@ -953,11 +953,11 @@ msgstr ""
#. name for alp
msgid "Alune"
msgstr ""
msgstr "Алуне"
#. name for alq
msgid "Algonquin"
msgstr ""
msgstr "Алгонкинский"
#. name for alr
msgid "Alutor"
@ -965,7 +965,7 @@ msgstr ""
#. name for als
msgid "Albanian; Tosk"
msgstr ""
msgstr "Албанский; Тоскский"
#. name for alt
msgid "Altai; Southern"
@ -1037,7 +1037,7 @@ msgstr ""
#. name for amm
msgid "Ama (Papua New Guinea)"
msgstr ""
msgstr "Ама (Папуа-Новая Гвинея)"
#. name for amn
msgid "Amanab"
@ -1077,7 +1077,7 @@ msgstr ""
#. name for amw
msgid "Neo-Aramaic; Western"
msgstr ""
msgstr "Новоарамейский; Западный"
#. name for amx
msgid "Anmatyerre"
@ -1085,7 +1085,7 @@ msgstr ""
#. name for amy
msgid "Ami"
msgstr ""
msgstr "Ами"
#. name for amz
msgid "Atampaya"
@ -1281,7 +1281,7 @@ msgstr ""
#. name for apd
msgid "Arabic; Sudanese"
msgstr ""
msgstr "Арабский; Суданский"
#. name for ape
msgid "Bukiyip"
@ -1373,7 +1373,7 @@ msgstr ""
#. name for aqc
msgid "Archi"
msgstr ""
msgstr "Арчинский"
#. name for aqd
msgid "Dogon; Ampari"
@ -1409,11 +1409,11 @@ msgstr "Арабский"
#. name for arb
msgid "Arabic; Standard"
msgstr ""
msgstr "Арабский; Стандартный"
#. name for arc
msgid "Aramaic; Official (700-300 BCE)"
msgstr ""
msgstr "Арамейский; Официальный"
#. name for ard
msgid "Arabana"
@ -1461,7 +1461,7 @@ msgstr "Арапахо"
#. name for arq
msgid "Arabic; Algerian"
msgstr ""
msgstr "Арабский; Алжирский"
#. name for arr
msgid "Karo (Brazil)"
@ -1489,11 +1489,11 @@ msgstr ""
#. name for ary
msgid "Arabic; Moroccan"
msgstr ""
msgstr "Арабский; Марокканский"
#. name for arz
msgid "Arabic; Egyptian"
msgstr ""
msgstr "Арабский; Египетский"
#. name for asa
msgid "Asu (Tanzania)"
@ -1537,7 +1537,7 @@ msgstr ""
#. name for ask
msgid "Ashkun"
msgstr ""
msgstr "Ашкун"
#. name for asl
msgid "Asilulu"
@ -1573,7 +1573,7 @@ msgstr ""
#. name for ast
msgid "Asturian"
msgstr ""
msgstr "Астурийский"
#. name for asu
msgid "Asurini; Tocantins"
@ -1693,7 +1693,7 @@ msgstr ""
#. name for atz
msgid "Arta"
msgstr ""
msgstr "Арта"
#. name for aua
msgid "Asumboa"
@ -1969,7 +1969,7 @@ msgstr ""
#. name for ayl
msgid "Arabic; Libyan"
msgstr ""
msgstr "Арабский; Ливийский"
#. name for aym
msgid "Aymara"
@ -1985,7 +1985,7 @@ msgstr ""
#. name for ayp
msgid "Arabic; North Mesopotamian"
msgstr ""
msgstr "Арабский; Северомесопатамский"
#. name for ayq
msgid "Ayi (Papua New Guinea)"
@ -2021,7 +2021,7 @@ msgstr ""
#. name for azb
msgid "Azerbaijani; South"
msgstr ""
msgstr "Азербайджанский; Южный"
#. name for aze
msgid "Azerbaijani"
@ -2033,7 +2033,7 @@ msgstr ""
#. name for azj
msgid "Azerbaijani; North"
msgstr ""
msgstr "Азербайджанский; Северный"
#. name for azm
msgid "Amuzgo; Ipalapa"
@ -2077,7 +2077,7 @@ msgstr ""
#. name for bah
msgid "Creole English; Bahamas"
msgstr ""
msgstr "Креольский английский; Багамский"
#. name for baj
msgid "Barakai"
@ -2113,7 +2113,7 @@ msgstr ""
#. name for bas
msgid "Basa (Cameroon)"
msgstr ""
msgstr "Баса (Камерун)"
#. name for bau
msgid "Bada (Nigeria)"
@ -2381,7 +2381,7 @@ msgstr ""
#. name for bdj
msgid "Bai"
msgstr ""
msgstr "Бай"
#. name for bdk
msgid "Budukh"
@ -2473,7 +2473,7 @@ msgstr ""
#. name for beg
msgid "Belait"
msgstr ""
msgstr "Белайт"
#. name for beh
msgid "Biali"
@ -2497,7 +2497,7 @@ msgstr "Белорусский"
#. name for bem
msgid "Bemba (Zambia)"
msgstr ""
msgstr "Бемба (Замбия)"
#. name for ben
msgid "Bengali"
@ -2641,7 +2641,7 @@ msgstr ""
#. name for bfy
msgid "Bagheli"
msgstr ""
msgstr "Багхели"
#. name for bfz
msgid "Pahari; Mahasu"
@ -2737,7 +2737,7 @@ msgstr ""
#. name for bgx
msgid "Turkish; Balkan Gagauz"
msgstr ""
msgstr "Турецкий; Гагаузский"
#. name for bgy
msgid "Benggoi"
@ -2753,7 +2753,7 @@ msgstr ""
#. name for bhb
msgid "Bhili"
msgstr ""
msgstr "Бхили"
#. name for bhc
msgid "Biga"
@ -3113,7 +3113,7 @@ msgstr ""
#. name for bku
msgid "Buhid"
msgstr ""
msgstr "Бухид"
#. name for bkv
msgid "Bekwarra"
@ -3333,7 +3333,7 @@ msgstr ""
#. name for bmy
msgid "Bemba (Democratic Republic of Congo)"
msgstr ""
msgstr "Бемба (Демократическая Республика Конго)"
#. name for bmz
msgid "Baramu"
@ -3409,7 +3409,7 @@ msgstr ""
#. name for bns
msgid "Bundeli"
msgstr ""
msgstr "Бундели"
#. name for bnu
msgid "Bentong"
@ -3553,7 +3553,7 @@ msgstr ""
#. name for bph
msgid "Botlikh"
msgstr ""
msgstr "Ботлихский"
#. name for bpi
msgid "Bagupi"
@ -3613,7 +3613,7 @@ msgstr ""
#. name for bpw
msgid "Bo (Papua New Guinea)"
msgstr ""
msgstr "Бо (Папуа-Новая Гвинея)"
#. name for bpx
msgid "Bareli; Palya"
@ -3621,7 +3621,7 @@ msgstr ""
#. name for bpy
msgid "Bishnupriya"
msgstr ""
msgstr "Бишнуприя"
#. name for bpz
msgid "Bilba"
@ -3821,7 +3821,7 @@ msgstr ""
#. name for brx
msgid "Bodo (India)"
msgstr ""
msgstr "Бодо (Индия)"
#. name for bry
msgid "Burui"
@ -3849,7 +3849,7 @@ msgstr ""
#. name for bsf
msgid "Bauchi"
msgstr ""
msgstr "Баучи"
#. name for bsg
msgid "Bashkardi"
@ -3857,7 +3857,7 @@ msgstr ""
#. name for bsh
msgid "Kati"
msgstr ""
msgstr "Кати"
#. name for bsi
msgid "Bassossi"
@ -3869,7 +3869,7 @@ msgstr ""
#. name for bsk
msgid "Burushaski"
msgstr ""
msgstr "Бурушаски"
#. name for bsl
msgid "Basa-Gumna"
@ -4389,7 +4389,7 @@ msgstr ""
#. name for bxr
msgid "Buriat; Russia"
msgstr ""
msgstr "Бурятский; Россия"
#. name for bxs
msgid "Busam"
@ -4553,11 +4553,11 @@ msgstr ""
#. name for bzj
msgid "Kriol English; Belize"
msgstr ""
msgstr "Креольский английский; Белиз"
#. name for bzk
msgid "Creole English; Nicaragua"
msgstr ""
msgstr "Креольский английский; Никарагуа"
#. name for bzl
msgid "Boano (Sulawesi)"
@ -5001,7 +5001,7 @@ msgstr ""
#. name for chm
msgid "Mari (Russia)"
msgstr ""
msgstr "Марийский (Россия)"
#. name for chn
msgid "Chinook jargon"
@ -5285,7 +5285,7 @@ msgstr ""
#. name for cmn
msgid "Chinese; Mandarin"
msgstr ""
msgstr "Китайский; Мандарин"
#. name for cmo
msgid "Mnong; Central"
@ -7581,7 +7581,7 @@ msgstr ""
#. name for fij
msgid "Fijian"
msgstr "Фиджи"
msgstr "Фиджийский"
#. name for fil
msgid "Filipino"
@ -8037,11 +8037,11 @@ msgstr ""
#. name for gcf
msgid "Creole French; Guadeloupean"
msgstr ""
msgstr "Креольский французский; Гваделупский"
#. name for gcl
msgid "Creole English; Grenadian"
msgstr ""
msgstr "Креольский английский; Гренадский"
#. name for gcn
msgid "Gaina"
@ -8049,7 +8049,7 @@ msgstr ""
#. name for gcr
msgid "Creole French; Guianese"
msgstr ""
msgstr "Креольский французский; Гвианский"
#. name for gct
msgid "German; Colonia Tovar"
@ -9089,7 +9089,7 @@ msgstr ""
#. name for gyn
msgid "Creole English; Guyanese"
msgstr ""
msgstr "Креольский английский; Гайянский"
#. name for gyr
msgid "Guarayu"
@ -9853,7 +9853,7 @@ msgstr ""
#. name for hwc
msgid "Creole English; Hawai'i"
msgstr ""
msgstr "Креольский английский; Гавайский"
#. name for hwo
msgid "Hwana"
@ -10577,7 +10577,7 @@ msgstr ""
#. name for jam
msgid "Creole English; Jamaican"
msgstr ""
msgstr "Креольский английский; Ямайский"
#. name for jao
msgid "Yanyuwa"
@ -14245,7 +14245,7 @@ msgstr ""
#. name for lir
msgid "English; Liberian"
msgstr ""
msgstr "Креольский английский; Либерийский"
#. name for lis
msgid "Lisu"
@ -14661,7 +14661,7 @@ msgstr ""
#. name for lou
msgid "Creole French; Louisiana"
msgstr ""
msgstr "Креольский французский; Луизиана"
#. name for lov
msgid "Lopi"
@ -15021,7 +15021,7 @@ msgstr ""
#. name for lzz
msgid "Laz"
msgstr ""
msgstr "Лазский"
#. name for maa
msgid "Mazatec; San Jerónimo Tecóatl"
@ -15337,7 +15337,7 @@ msgstr ""
#. name for mdf
msgid "Moksha"
msgstr "Мокша"
msgstr "Мокшанский"
#. name for mdg
msgid "Massalat"
@ -19993,7 +19993,7 @@ msgstr ""
#. name for orv
msgid "Russian; Old"
msgstr ""
msgstr "Древнерусский"
#. name for orw
msgid "Oro Win"
@ -20109,7 +20109,7 @@ msgstr ""
#. name for oty
msgid "Tamil; Old"
msgstr ""
msgstr "Древнетамильский"
#. name for otz
msgid "Otomi; Ixtenco"
@ -21897,7 +21897,7 @@ msgstr ""
#. name for rcf
msgid "Creole French; Réunion"
msgstr ""
msgstr "Креольский французский; Реюньон"
#. name for rdb
msgid "Rudbari"
@ -23081,7 +23081,7 @@ msgstr ""
#. name for sin
msgid "Sinhala"
msgstr ""
msgstr "Сингальский"
#. name for sip
msgid "Sikkimese"
@ -24661,7 +24661,7 @@ msgstr ""
#. name for tch
msgid "Creole English; Turks And Caicos"
msgstr ""
msgstr "Креольский английский; Тёркс и Кайкос"
#. name for tci
msgid "Wára"
@ -24957,7 +24957,7 @@ msgstr ""
#. name for tgh
msgid "Creole English; Tobagonian"
msgstr ""
msgstr "Креольский английский; Тобагский"
#. name for tgi
msgid "Lawunuia"
@ -25401,7 +25401,7 @@ msgstr ""
#. name for tly
msgid "Talysh"
msgstr ""
msgstr "Талышский"
#. name for tma
msgid "Tama (Chad)"
@ -25845,7 +25845,7 @@ msgstr ""
#. name for trf
msgid "Creole English; Trinidadian"
msgstr ""
msgstr "Креольский английский; Тринидадский"
#. name for trg
msgid "Lishán Didán"
@ -27121,7 +27121,7 @@ msgstr ""
#. name for vic
msgid "Creole English; Virgin Islands"
msgstr ""
msgstr "Креольский английский; Виргинские острова"
#. name for vid
msgid "Vidunda"
@ -28209,7 +28209,7 @@ msgstr ""
#. name for wyy
msgid "Fijian; Western"
msgstr ""
msgstr "Западнофиджийский"
#. name for xaa
msgid "Arabic; Andalusian"

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = u'calibre'
numeric_version = (0, 9, 20)
numeric_version = (0, 9, 21)
__version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"

View File

@ -19,6 +19,7 @@ from calibre.db.errors import NoSuchFormat
from calibre.db.fields import create_field
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.book.base import Metadata
from calibre.ptempfile import (base_dir, PersistentTemporaryFile,
@ -618,8 +619,31 @@ class Cache(object):
def set_field(self, name, book_id_to_val_map, allow_case_change=True):
# TODO: Specialize title/authors to also update path
# TODO: Handle updating caches used by composite fields
dirtied = self.fields[name].writer.set_books(
# TODO: Ensure the sort fields are updated for title/author/series?
f = self.fields[name]
is_series = f.metadata['datatype'] == 'series'
if is_series:
bimap, simap = {}, {}
for k, v in book_id_to_val_map.iteritems():
if isinstance(v, basestring):
v, sid = get_series_values(v)
else:
v = sid = None
if name.startswith('#') and sid is None:
sid = 1.0 # The value will be set to 1.0 in the db table
bimap[k] = v
if sid is not None:
simap[k] = sid
book_id_to_val_map = bimap
dirtied = f.writer.set_books(
book_id_to_val_map, self.backend, allow_case_change=allow_case_change)
if is_series and simap:
sf = self.fields[f.name+'_index']
dirtied |= sf.writer.set_books(simap, self.backend, allow_case_change=False)
return dirtied
# }}}

View File

@ -22,6 +22,7 @@ from calibre.utils.localization import calibre_langcode_to_name
class Field(object):
is_many = False
is_many_many = False
def __init__(self, name, table):
self.name, self.table = name, table
@ -299,6 +300,7 @@ class ManyToOneField(Field):
class ManyToManyField(Field):
is_many = True
is_many_many = True
def __init__(self, *args, **kwargs):
Field.__init__(self, *args, **kwargs)

View File

@ -123,9 +123,8 @@ class ManyToOneTable(Table):
def read_id_maps(self, db):
for row in db.conn.execute('SELECT id, {0} FROM {1}'.format(
self.metadata['column'], self.metadata['table'])):
if row[1]:
self.id_map[row[0]] = self.unserialize(row[1])
self.metadata['column'], self.metadata['table'])):
self.id_map[row[0]] = self.unserialize(row[1])
def read_maps(self, db):
for row in db.conn.execute(
@ -218,3 +217,4 @@ class LanguagesTable(ManyToManyTable):
ManyToManyTable.read_id_maps(self, db)
lm = lang_map()
self.lang_name_map = {x:lm.get(x, x) for x in self.id_map.itervalues()}

View File

@ -75,7 +75,7 @@ class WritingTest(BaseTest):
test.name, old_sqlite_res, sqlite_res))
del db
def test_one_one(self):
def test_one_one(self): # {{{
'Test setting of values in one-one fields'
tests = [self.create_test('#yesno', (True, False, 'true', 'false', None))]
for name, getter, setter in (
@ -114,6 +114,96 @@ class WritingTest(BaseTest):
tests.append(self.create_test(name, tuple(vals), getter, setter))
self.run_tests(tests)
# }}}
def test_many_one_basic(self): # {{{
'Test the different code paths for writing to a many-one field'
cl = self.cloned_library
cache = self.init_cache(cl)
f = cache.fields['publisher']
item_ids = {f.ids_for_book(1)[0], f.ids_for_book(2)[0]}
val = 'Changed'
self.assertEqual(cache.set_field('publisher', {1:val, 2:val}), {1, 2})
cache2 = self.init_cache(cl)
for book_id in (1, 2):
for c in (cache, cache2):
self.assertEqual(c.field_for('publisher', book_id), val)
self.assertFalse(item_ids.intersection(set(c.fields['publisher'].table.id_map)))
del cache2
self.assertFalse(cache.set_field('publisher', {1:val, 2:val}))
val = val.lower()
self.assertFalse(cache.set_field('publisher', {1:val, 2:val},
allow_case_change=False))
self.assertEqual(cache.set_field('publisher', {1:val, 2:val}), {1, 2})
cache2 = self.init_cache(cl)
for book_id in (1, 2):
for c in (cache, cache2):
self.assertEqual(c.field_for('publisher', book_id), val)
del cache2
self.assertEqual(cache.set_field('publisher', {1:'new', 2:'New'}), {1, 2})
self.assertEqual(cache.field_for('publisher', 1).lower(), 'new')
self.assertEqual(cache.field_for('publisher', 2).lower(), 'new')
self.assertEqual(cache.set_field('publisher', {1:None, 2:'NEW'}), {1, 2})
self.assertEqual(len(f.table.id_map), 1)
self.assertEqual(cache.set_field('publisher', {2:None}), {2})
self.assertEqual(len(f.table.id_map), 0)
cache2 = self.init_cache(cl)
self.assertEqual(len(cache2.fields['publisher'].table.id_map), 0)
del cache2
self.assertEqual(cache.set_field('publisher', {1:'one', 2:'two',
3:'three'}), {1, 2, 3})
self.assertEqual(cache.set_field('publisher', {1:''}), set([1]))
self.assertEqual(cache.set_field('publisher', {1:'two'}), set([1]))
self.assertEqual(tuple(map(f.for_book, (1,2,3))), ('two', 'two', 'three'))
self.assertEqual(cache.set_field('publisher', {1:'Two'}), {1, 2})
cache2 = self.init_cache(cl)
self.assertEqual(tuple(map(f.for_book, (1,2,3))), ('Two', 'Two', 'three'))
del cache2
# Enum
self.assertFalse(cache.set_field('#enum', {1:'Not allowed'}))
self.assertEqual(cache.set_field('#enum', {1:'One', 2:'One', 3:'Three'}), {1, 3})
self.assertEqual(cache.set_field('#enum', {1:None}), set([1]))
cache2 = self.init_cache(cl)
for c in (cache, cache2):
for i, val in {1:None, 2:'One', 3:'Three'}.iteritems():
self.assertEqual(c.field_for('#enum', i), val)
del cache2
# Rating
self.assertFalse(cache.set_field('rating', {1:6, 2:4}))
self.assertEqual(cache.set_field('rating', {1:0, 3:2}), {1, 3})
self.assertEqual(cache.set_field('#rating', {1:None, 2:4, 3:8}), {1, 2, 3})
cache2 = self.init_cache(cl)
for c in (cache, cache2):
for i, val in {1:None, 2:4, 3:2}.iteritems():
self.assertEqual(c.field_for('rating', i), val)
for i, val in {1:None, 2:4, 3:8}.iteritems():
self.assertEqual(c.field_for('#rating', i), val)
del cache2
# Series
self.assertFalse(cache.set_field('series',
{1:'a series one', 2:'a series one'}, allow_case_change=False))
self.assertEqual(cache.set_field('series', {3:'Series [3]'}), set([3]))
self.assertEqual(cache.set_field('#series', {1:'Series', 3:'Series'}),
{1, 3})
self.assertEqual(cache.set_field('#series', {2:'Series [0]'}), set([2]))
cache2 = self.init_cache(cl)
for c in (cache, cache2):
for i, val in {1:'A Series One', 2:'A Series One', 3:'Series'}.iteritems():
self.assertEqual(c.field_for('series', i), val)
for i in (1, 2, 3):
self.assertEqual(c.field_for('#series', i), 'Series')
for i, val in {1:2, 2:1, 3:3}.iteritems():
self.assertEqual(c.field_for('series_index', i), val)
for i, val in {1:1, 2:0, 3:1}.iteritems():
self.assertEqual(c.field_for('#series_index', i), val)
del cache2
# }}}
def tests():
return unittest.TestLoader().loadTestsFromTestCase(WritingTest)

View File

@ -7,6 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import re
from functools import partial
from datetime import datetime
@ -29,6 +30,21 @@ def single_text(x):
x = x.strip()
return x if x else None
series_index_pat = re.compile(r'(.*)\s+\[([.0-9]+)\]$')
def get_series_values(val):
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)
def multiple_text(sep, x):
if x is None:
return ()
@ -92,7 +108,7 @@ def get_adapter(name, metadata):
elif dt == 'comments':
ans = single_text
elif dt == 'rating':
ans = lambda x: x if x is None else min(10., max(0., adapt_number(float, x))),
ans = lambda x: None if x in {None, 0} else min(10., max(0., adapt_number(float, x)))
elif dt == 'enumeration':
ans = single_text
elif dt == 'composite':
@ -128,8 +144,8 @@ def one_one_in_other(book_id_val_map, db, field, *args):
if deleted:
db.conn.executemany('DELETE FROM %s WHERE book=?'%field.metadata['table'],
deleted)
for book_id in book_id_val_map:
field.table.book_col_map.pop(book_id, None)
for book_id in deleted:
field.table.book_col_map.pop(book_id[0], None)
updated = {k:v for k, v in book_id_val_map.iteritems() if v is not None}
if updated:
db.conn.executemany('INSERT OR REPLACE INTO %s(book,%s) VALUES (?,?)'%(
@ -151,7 +167,131 @@ def custom_series_index(book_id_val_map, db, field, *args):
if sequence:
db.conn.executemany('UPDATE %s SET %s=? WHERE book=? AND value=?'%(
field.metadata['table'], field.metadata['column']), sequence)
return {s[0] for s in sequence}
return {s[1] for s in sequence}
# }}}
# Many-One fields {{{
def safe_lower(x):
try:
return icu_lower(x)
except (TypeError, ValueError, KeyError, AttributeError):
return x
def many_one(book_id_val_map, db, field, allow_case_change, *args):
dirtied = set()
m = field.metadata
table = field.table
dt = m['datatype']
is_custom_series = dt == 'series' and table.name.startswith('#')
# Map values to their canonical form for later comparison
kmap = safe_lower if dt in {'text', 'series'} else lambda x:x
# Ignore those items whose value is the same as the current value
no_changes = {k:nval for k, nval in book_id_val_map.iteritems() if
kmap(nval) == kmap(field.for_book(k, default_value=None))}
for book_id in no_changes:
del book_id_val_map[book_id]
# If we are allowed case changes check that none of the ignored items are
# case changes. If they are, update the item's case in the db.
if allow_case_change:
for book_id, nval in no_changes.iteritems():
if nval is not None and nval != field.for_book(
book_id, default_value=None):
# Change of case
item_id = table.book_col_map[book_id]
db.conn.execute('UPDATE %s SET %s=? WHERE id=?'%(
m['table'], m['column']), (nval, item_id))
table.id_map[item_id] = nval
dirtied |= table.col_book_map[item_id]
deleted = {k:v for k, v in book_id_val_map.iteritems() if v is None}
updated = {k:v for k, v in book_id_val_map.iteritems() if v is not None}
link_table = table.link_table
if deleted:
db.conn.executemany('DELETE FROM %s WHERE book=?'%link_table,
tuple((book_id,) for book_id in deleted))
for book_id in deleted:
item_id = table.book_col_map.pop(book_id, None)
if item_id is not None:
table.col_book_map[item_id].discard(book_id)
dirtied |= set(deleted)
if updated:
rid_map = {kmap(v):k for k, v in table.id_map.iteritems()}
book_id_item_id_map = {k:rid_map.get(kmap(v), None) for k, v in
book_id_val_map.iteritems()}
# items that dont yet exist
new_items = {k:v for k, v in updated.iteritems() if
book_id_item_id_map[k] is None}
# items that already exist
changed_items = {k:book_id_item_id_map[k] for k in updated if
book_id_item_id_map[k] is not None}
def sql_update(imap):
sql = (
'DELETE FROM {0} WHERE book=?; INSERT INTO {0}(book,{1},extra) VALUES(?, ?, 1.0)'
if is_custom_series else
'DELETE FROM {0} WHERE book=?; INSERT INTO {0}(book,{1}) VALUES(?, ?)'
)
db.conn.executemany(sql.format(link_table, m['link_column']),
tuple((book_id, book_id, item_id) for book_id, item_id in
imap.iteritems()))
if new_items:
item_ids = {}
val_map = {}
for val in set(new_items.itervalues()):
lval = kmap(val)
if lval in val_map:
item_id = val_map[lval]
else:
db.conn.execute('INSERT INTO %s(%s) VALUES (?)'%(
m['table'], m['column']), (val,))
item_id = val_map[lval] = db.conn.last_insert_rowid()
item_ids[val] = item_id
table.id_map[item_id] = val
imap = {}
for book_id, val in new_items.iteritems():
item_id = item_ids[val]
old_item_id = table.book_col_map.get(book_id, None)
if old_item_id is not None:
table.col_book_map[old_item_id].discard(book_id)
if item_id not in table.col_book_map:
table.col_book_map[item_id] = set()
table.col_book_map[item_id].add(book_id)
table.book_col_map[book_id] = imap[book_id] = item_id
sql_update(imap)
dirtied |= set(imap)
if changed_items:
imap = {}
sql_update(changed_items)
for book_id, item_id in changed_items.iteritems():
old_item_id = table.book_col_map.get(book_id, None)
if old_item_id != item_id:
table.book_col_map[book_id] = item_id
table.col_book_map[item_id].add(book_id)
if old_item_id is not None:
table.col_book_map[old_item_id].discard(book_id)
imap[book_id] = item_id
sql_update(imap)
dirtied |= set(imap)
# Remove no longer used items
remove = {item_id for item_id in table.id_map if not
table.col_book_map.get(item_id, False)}
if remove:
db.conn.executemany('DELETE FROM %s WHERE id=?'%m['table'],
tuple((item_id,) for item_id in remove))
for item_id in remove:
del table.id_map[item_id]
table.col_book_map.pop(item_id, None)
return dirtied
# }}}
def dummy(book_id_val_map, *args):
@ -170,10 +310,13 @@ class Writer(object):
self.set_books_func = dummy
elif self.name[0] == '#' and self.name.endswith('_index'):
self.set_books_func = custom_series_index
elif field.is_many:
elif field.is_many_many:
# TODO: Implement this
pass
# TODO: Remember to change commas to | when writing authors to sqlite
elif field.is_many:
self.set_books_func = (self.set_books_for_enum if dt ==
'enumeration' else many_one)
else:
self.set_books_func = (one_one_in_books if field.metadata['table']
== 'books' else one_one_in_other)
@ -185,6 +328,17 @@ class Writer(object):
book_id_val_map.iteritems() if self.accept_vals(v)}
if not book_id_val_map:
return set()
dirtied = self.set_books_func(book_id_val_map, db, self.field)
dirtied = self.set_books_func(book_id_val_map, db, self.field,
allow_case_change)
return dirtied
def set_books_for_enum(self, book_id_val_map, db, field,
allow_case_change):
allowed = set(field.metadata['display']['enum_values'])
book_id_val_map = {k:v for k, v in book_id_val_map.iteritems() if v is
None or v in allowed}
if not book_id_val_map:
return set()
return many_one(book_id_val_map, db, field, False)

View File

@ -23,12 +23,11 @@ It also contains interfaces to various bits of calibre that do not have
dedicated command line tools, such as font subsetting, tweaking ebooks and so
on.
''')
parser.add_option('-c', '--command', help='Run python code.', default=None)
parser.add_option('-e', '--exec-file', default=None, help='Run the python code in file.')
parser.add_option('-f', '--subset-font', default=False,
action='store_true', help='Subset the specified font')
parser.add_option('-c', '--command', help='Run python code.')
parser.add_option('-e', '--exec-file', help='Run the python code in file.')
parser.add_option('-f', '--subset-font', help='Subset the specified font')
parser.add_option('-d', '--debug-device-driver', default=False, action='store_true',
help='Debug the specified device driver.')
help='Debug device detection')
parser.add_option('-g', '--gui', default=False, action='store_true',
help='Run the GUI with debugging enabled. Debug output is '
'printed to stdout and stderr.')
@ -59,7 +58,7 @@ on.
parser.add_option('-m', '--inspect-mobi', action='store_true',
default=False,
help='Inspect the MOBI file(s) at the specified path(s)')
parser.add_option('--tweak-book', default=None,
parser.add_option('-t', '--tweak-book', default=None,
help='Tweak the book (exports the book as a collection of HTML '
'files and metadata, which you can edit using standard HTML '
'editing tools, and then rebuilds the file from the edited HTML. '
@ -174,30 +173,24 @@ def run_debug_gui(logpath):
from calibre.gui2.main import main
main(['__CALIBRE_GUI_DEBUG__', logpath])
def run_script(path, args):
# Load all user defined plugins so the script can import from the
# calibre_plugins namespace
import calibre.customize.ui as dummy
dummy
sys.argv = [path] + args
ef = os.path.abspath(path)
base = os.path.dirname(ef)
sys.path.insert(0, base)
g = globals()
g['__name__'] = '__main__'
g['__file__'] = ef
execfile(ef, g)
def main(args=sys.argv):
from calibre.constants import debug
debug()
if len(args) > 2 and args[1] in ('-e', '--exec-file'):
# Load all plugins user defined plugins so the script can import from the
# calibre_plugins namespace
import calibre.customize.ui as dummy
dummy
sys.argv = [args[2]] + args[3:]
ef = os.path.abspath(args[2])
base = os.path.dirname(ef)
sys.path.insert(0, base)
g = globals()
g['__name__'] = '__main__'
g['__file__'] = ef
execfile(ef, g)
return
if len(args) > 1 and args[1] in ('-f', '--subset-font'):
from calibre.utils.fonts.sfnt.subset import main
main(['subset-font']+args[2:])
return
opts, args = option_parser().parse_args(args)
if opts.gui:
@ -258,6 +251,13 @@ def main(args=sys.argv):
elif opts.shutdown_running_calibre:
from calibre.gui2.main import shutdown_other
shutdown_other()
elif opts.subset_font:
from calibre.utils.fonts.sfnt.subset import main
main(['subset-font']+[opts.subset_font]+args[1:])
elif opts.exec_file:
run_script(opts.exec_file, args[1:])
elif len(args) >= 2 and args[1].rpartition('.')[-1] in {'py', 'recipe'}:
run_script(args[1], args[2:])
else:
from calibre import ipython
ipython()

View File

@ -1,5 +1,6 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import division
__license__ = 'GPL v3'
__copyright__ = '2010-2012, Timothy Legge <timlegge@gmail.com>, Kovid Goyal <kovid@kovidgoyal.net> and David Forrester <davidfor@internode.on.net>'
@ -13,6 +14,7 @@ Extended to support Touch firmware 2.0.0 and later and newer devices by David Fo
'''
import os, time
from contextlib import closing
from calibre.devices.usbms.books import BookList
from calibre.devices.usbms.books import CollectionsBookList
@ -33,7 +35,7 @@ class KOBO(USBMS):
gui_name = 'Kobo Reader'
description = _('Communicate with the Kobo Reader')
author = 'Timothy Legge and David Forrester'
version = (2, 0, 5)
version = (2, 0, 6)
dbversion = 0
fwversion = 0
@ -1196,10 +1198,11 @@ class KOBO(USBMS):
class KOBOTOUCH(KOBO):
name = 'KoboTouch'
gui_name = 'Kobo Touch'
author = 'David Forrester'
name = 'KoboTouch'
gui_name = 'Kobo Touch'
author = 'David Forrester'
description = 'Communicate with the Kobo Touch, Glo and Mini firmware. Based on the existing Kobo driver by %s.' % (KOBO.author)
# icon = I('devices/kobotouch.jpg')
supported_dbversion = 75
min_supported_dbversion = 53
@ -1219,14 +1222,11 @@ class KOBOTOUCH(KOBO):
_('Delete Empty Bookshelves') +
':::'+_('Delete any empty bookshelves from the Kobo Touch when syncing is finished. This is only for firmware V2.0.0 or later.'),
_('Upload covers for books') +
':::'+_('Normally, the KOBO readers get the cover image from the'
' ebook file itself. With this option, calibre will send a '
'separate cover image to the reader, useful if you '
'have modified the cover.'),
':::'+_('Upload cover images from the calibre library when sending books to the device.'),
_('Upload Black and White Covers'),
_('Always upload covers') +
':::'+_('If the Upload covers option is selected, the driver will only replace covers already on the device.'
' Select this option if you want covers uploaded the first time you send the book to the device.'),
_('Keep cover aspect ratio') +
':::'+_('When uploading covers, do not change the aspect ratio when resizing for the device.'
' This is for firmware versions 2.3.1 and later.'),
_('Show expired books') +
':::'+_('A bug in an earlier version left non kepubs book records'
' in the database. With this option Calibre will show the '
@ -1278,7 +1278,7 @@ class KOBOTOUCH(KOBO):
OPT_DELETE_BOOKSHELVES = 2
OPT_UPLOAD_COVERS = 3
OPT_UPLOAD_GRAYSCALE_COVERS = 4
OPT_ALWAYS_UPLOAD_COVERS = 5
OPT_KEEP_COVER_ASPECT_RATIO = 5
OPT_SHOW_EXPIRED_BOOK_RECORDS = 6
OPT_SHOW_PREVIEWS = 7
OPT_SHOW_RECOMMENDATIONS = 8
@ -1290,16 +1290,27 @@ class KOBOTOUCH(KOBO):
TIMESTAMP_STRING = "%Y-%m-%dT%H:%M:%SZ"
PRODUCT_ID = [0x4163, 0x4173, 0x4183]
BCD = [0x0110, 0x0326]
GLO_PRODUCT_ID = [0x4173]
MINI_PRODUCT_ID = [0x4183]
TOUCH_PRODUCT_ID = [0x4163]
PRODUCT_ID = GLO_PRODUCT_ID + MINI_PRODUCT_ID + TOUCH_PRODUCT_ID
BCD = [0x0110, 0x0326]
# Image file name endings. Made up of: image size, min_dbversion, max_dbversion,
COVER_FILE_ENDINGS = {
' - N3_LIBRARY_FULL.parsed':[(355,473),0, 99,], # Used for Details screen
' - N3_LIBRARY_GRID.parsed':[(149,198),0, 99,], # Used for library lists
' - N3_LIBRARY_LIST.parsed':[(60,90),0, 53,],
' - N3_FULL.parsed':[(600,800),0, 99,True,], # Used for screensaver, home screen
' - N3_LIBRARY_FULL.parsed':[(355,473),0, 99,False,], # Used for Details screen
' - N3_LIBRARY_GRID.parsed':[(149,198),0, 99,False,], # Used for library lists
' - N3_LIBRARY_LIST.parsed':[(60,90),0, 53,False,],
# ' - N3_LIBRARY_SHELF.parsed': [(40,60),0, 52,],
}
GLO_COVER_FILE_ENDINGS = {
' - N3_FULL.parsed':[(758,1024),0, 99,True,], # Used for screensaver, home screen
' - N3_LIBRARY_FULL.parsed':[(355,479),0, 99,False,], # Used for Details screen
' - N3_LIBRARY_GRID.parsed':[(149,201),0, 99,False,], # Used for library lists
# ' - N3_LIBRARY_LIST.parsed':[(60,90),0, 53,],
# ' - N3_LIBRARY_SHELF.parsed': [(40,60),0, 52,],
' - N3_FULL.parsed':[(600,800),0, 99,], # Used for screensaver, home screen
}
#Following are the sizes used with pre2.1.4 firmware
# COVER_FILE_ENDINGS = {
@ -1311,6 +1322,7 @@ class KOBOTOUCH(KOBO):
# ' - N3_FULL.parsed':[(600,800),0, 99,], # Used for screensaver if "Full screen" is checked.
# }
def initialize(self):
super(KOBOTOUCH, self).initialize()
self.bookshelvelist = []
@ -1691,7 +1703,7 @@ class KOBOTOUCH(KOBO):
def imagefilename_from_imageID(self, ImageID):
show_debug = self.is_debugging_title(ImageID)
for ending, cover_options in self.COVER_FILE_ENDINGS.items():
for ending, cover_options in self.cover_file_endings().items():
fpath = self._main_prefix + '.kobo/images/' + ImageID + ending
fpath = self.normalize_path(fpath.replace('/', os.sep))
if os.path.exists(fpath):
@ -1730,15 +1742,19 @@ class KOBOTOUCH(KOBO):
cleanup_values = (contentID,)
# debug_print('KoboTouch:upload_books: Delete record left if deleted on Touch')
cursor.execute(cleanup_query, cleanup_values)
self.set_filesize_in_device_database(connection, contentID, fname)
if not self.copying_covers():
imageID = self.imageid_from_contentid(contentID)
self.delete_images(imageID)
connection.commit()
cursor.close()
except Exception as e:
debug_print('KoboTouch:upload_books - Exception: %s'%str(e))
return result
@ -1794,7 +1810,7 @@ class KOBOTOUCH(KOBO):
path_prefix = '.kobo/images/'
path = self._main_prefix + path_prefix + ImageID
for ending in self.COVER_FILE_ENDINGS.keys():
for ending in self.cover_file_endings().keys():
fpath = path + ending
fpath = self.normalize_path(fpath)
@ -2049,23 +2065,23 @@ class KOBOTOUCH(KOBO):
# debug_print("KoboTouch:upload_cover - path='%s' filename='%s'"%(path, filename))
opts = self.settings()
if not opts.extra_customization[self.OPT_UPLOAD_COVERS]:
if not self.copying_covers():
# Building thumbnails disabled
# debug_print('KoboTouch: not uploading cover')
return
# Don't upload covers if book is on the SD card
if self._card_a_prefix and path.startswith(self._card_a_prefix):
return
if not opts.extra_customization[self.OPT_UPLOAD_GRAYSCALE_COVERS]:
uploadgrayscale = False
else:
uploadgrayscale = True
if not opts.extra_customization[self.OPT_ALWAYS_UPLOAD_COVERS]:
always_upload_covers = False
else:
always_upload_covers = True
# debug_print('KoboTouch: uploading cover')
try:
self._upload_cover(path, filename, metadata, filepath, uploadgrayscale, always_upload_covers)
self._upload_cover(path, filename, metadata, filepath, uploadgrayscale, self.keep_cover_aspect())
except Exception as e:
debug_print('KoboTouch: FAILED to upload cover=%s Exception=%s'%(filepath, str(e)))
@ -2077,9 +2093,9 @@ class KOBOTOUCH(KOBO):
ImageID = ImageID.replace('.', '_')
return ImageID
def _upload_cover(self, path, filename, metadata, filepath, uploadgrayscale, always_upload_covers=False):
from calibre.utils.magick.draw import save_cover_data_to
debug_print("KoboTouch:_upload_cover - filename='%s' uploadgrayscale='%s' always_upload_covers='%s'"%(filename, uploadgrayscale, always_upload_covers))
def _upload_cover(self, path, filename, metadata, filepath, uploadgrayscale, keep_cover_aspect=False):
from calibre.utils.magick.draw import save_cover_data_to, identify_data
debug_print("KoboTouch:_upload_cover - filename='%s' uploadgrayscale='%s' "%(filename, uploadgrayscale))
if metadata.cover:
show_debug = self.is_debugging_title(filename)
@ -2122,8 +2138,8 @@ class KOBOTOUCH(KOBO):
if show_debug:
debug_print("KoboTouch:_upload_cover - About to loop over cover endings")
for ending, cover_options in self.COVER_FILE_ENDINGS.items():
resize, min_dbversion, max_dbversion = cover_options
for ending, cover_options in self.cover_file_endings().items():
resize, min_dbversion, max_dbversion, isFullsize = cover_options
if show_debug:
debug_print("KoboTouch:_upload_cover - resize=%s min_dbversion=%d max_dbversion=%d" % (resize, min_dbversion, max_dbversion))
if self.dbversion >= min_dbversion and self.dbversion <= max_dbversion:
@ -2132,19 +2148,28 @@ class KOBOTOUCH(KOBO):
fpath = path + ending
fpath = self.normalize_path(fpath.replace('/', os.sep))
if os.path.exists(fpath) or always_upload_covers:
debug_print("KoboTouch:_upload_cover - path exists or always_upload_covers%s"% always_upload_covers)
with open(cover, 'rb') as f:
data = f.read()
with open(cover, 'rb') as f:
data = f.read()
# Return the data resized and in Grayscale if
# required
data = save_cover_data_to(data, 'dummy.jpg',
grayscale=uploadgrayscale,
resize_to=resize, return_data=True)
if keep_cover_aspect:
if isFullsize:
resize = None
else:
width, height, fmt = identify_data(data)
cover_aspect = width / height
if cover_aspect > 1:
resize = (resize[0], int(resize[0] / cover_aspect ))
elif cover_aspect < 1:
resize = (int(cover_aspect * resize[1]), resize[1] )
with open(fpath, 'wb') as f:
f.write(data)
# Return the data resized and in Grayscale if
# required
data = save_cover_data_to(data, 'dummy.jpg',
grayscale=uploadgrayscale,
resize_to=resize, return_data=True)
with open(fpath, 'wb') as f:
f.write(data)
except Exception as e:
err = str(e)
debug_print("KoboTouch:_upload_cover - Exception string: %s"%err)
@ -2453,21 +2478,30 @@ class KOBOTOUCH(KOBO):
return opts
def isGlo(self):
return self.detected_device.idProduct in self.GLO_PRODUCT_ID
def isMini(self):
return self.detected_device.idProduct in self.MINI_PRODUCT_ID
def isTouch(self):
return self.detected_device.idProduct in self.TOUCH_PRODUCT_ID
def cover_file_endings(self):
return self.GLO_COVER_FILE_ENDINGS if self.isGlo() else self.COVER_FILE_ENDINGS
def copying_covers(self):
opts = self.settings()
return opts.extra_customization[self.OPT_UPLOAD_COVERS] or opts.extra_customization[self.OPT_KEEP_COVER_ASPECT_RATIO]
def keep_cover_aspect(self):
opts = self.settings()
return opts.extra_customization[self.OPT_KEEP_COVER_ASPECT_RATIO]
def supports_bookshelves(self):
return self.dbversion >= self.min_supported_dbversion
def supports_series(self):
return self.dbversion >= self.min_dbversion_series
# def is_debugging_title(self, title):
## debug_print("KoboTouch:is_debugging - title=", title)
# is_debugging = False
# opts = self.settings()
# if opts.extra_customization:
# debugging_title = opts.extra_customization[self.OPT_DEBUGGING_TITLE]
# is_debugging = len(debugging_title) > 0 and title.find(debugging_title) >= 0 or len(title) == 0
#
# return is_debugging
@classmethod
def is_debugging_title(cls, title):

View File

@ -100,6 +100,9 @@ def option_recommendation_to_cli_option(add_option, rec):
switches = ['--disable-'+opt.long_switch]
add_option(Option(*switches, **attrs))
def group_titles():
return _('INPUT OPTIONS'), _('OUTPUT OPTIONS')
def add_input_output_options(parser, plumber):
input_options, output_options = \
plumber.input_options, plumber.output_options
@ -109,14 +112,14 @@ def add_input_output_options(parser, plumber):
option_recommendation_to_cli_option(group, opt)
if input_options:
title = _('INPUT OPTIONS')
title = group_titles()[0]
io = OptionGroup(parser, title, _('Options to control the processing'
' of the input %s file')%plumber.input_fmt)
add_options(io.add_option, input_options)
parser.add_option_group(io)
if output_options:
title = _('OUTPUT OPTIONS')
title = group_titles()[1]
oo = OptionGroup(parser, title, _('Options to control the processing'
' of the output %s')%plumber.output_fmt)
add_options(oo.add_option, output_options)

View File

@ -81,6 +81,11 @@ class BookIndexing
if elem == null
pos = [body.scrollWidth+1000, body.scrollHeight+1000]
else
# Because of a bug in WebKit's getBoundingClientRect() in
# column mode, this position can be inaccurate,
# see https://bugs.launchpad.net/calibre/+bug/1132641 for a
# test case. The usual symptom of the inaccuracy is br.top is
# highly negative.
br = elem.getBoundingClientRect()
pos = viewport_to_document(br.left, br.top, elem.ownerDocument)

View File

@ -410,7 +410,22 @@ class PagedDisplay
elem.scrollIntoView()
if this.in_paged_mode
# Ensure we are scrolled to the column containing elem
this.scroll_to_xpos(calibre_utils.absleft(elem) + 5)
# Because of a bug in WebKit's getBoundingClientRect() in column
# mode, this position can be inaccurate, see
# https://bugs.launchpad.net/calibre/+bug/1132641 for a test case.
# The usual symptom of the inaccuracy is br.top is highly negative.
br = elem.getBoundingClientRect()
if br.top < -1000
# This only works because of the preceding call to
# elem.scrollIntoView(). However, in some cases it gives
# inaccurate results, so we prefer the bounding client rect,
# when possible.
left = elem.scrollLeft
else
left = br.left
this.scroll_to_xpos(calibre_utils.viewport_to_document(
left+this.margin_side, elem.scrollTop, elem.ownerDocument)[0])
snap_to_selection: () ->
# Ensure that the viewport is positioned at the start of the column

View File

@ -86,7 +86,9 @@ class CalibreUtils
absleft: (elem) -> # {{{
# The left edge of elem in document co-ords. Works in all
# circumstances, including column layout. Note that this will cause
# a relayout if the render tree is dirty.
# a relayout if the render tree is dirty. Also, because of a bug in the
# version of WebKit bundled with Qt 4.8, this does not always work, see
# https://bugs.launchpad.net/bugs/1132641 for a test case.
r = elem.getBoundingClientRect()
return this.viewport_to_document(r.left, 0, elem.ownerDocument)[0]
# }}}

View File

@ -15,7 +15,8 @@ from PyQt4.Qt import (QMenu, Qt, QInputDialog, QToolButton, QDialog,
from calibre import isbytestring, sanitize_file_name_unicode
from calibre.constants import (filesystem_encoding, iswindows,
get_portable_base)
from calibre.utils.config import prefs
from calibre.utils.config import prefs, tweaks
from calibre.utils.icu import sort_key
from calibre.gui2 import (gprefs, warning_dialog, Dispatcher, error_dialog,
question_dialog, info_dialog, open_local_file, choose_dir)
from calibre.library.database2 import LibraryDatabase2
@ -46,7 +47,7 @@ class LibraryUsageStats(object): # {{{
locs = list(self.stats.keys())
locs.sort(cmp=lambda x, y: cmp(self.stats[x], self.stats[y]),
reverse=True)
for key in locs[25:]:
for key in locs[500:]:
self.stats.pop(key)
gprefs.set('library_usage_stats', self.stats)
@ -72,8 +73,9 @@ class LibraryUsageStats(object): # {{{
locs = list(self.stats.keys())
if lpath in locs:
locs.remove(lpath)
locs.sort(cmp=lambda x, y: cmp(self.stats[x], self.stats[y]),
reverse=True)
limit = tweaks['many_libraries']
key = sort_key if len(locs) > limit else lambda x:self.stats[x]
locs.sort(key=key, reverse=len(locs)<=limit)
for loc in locs:
yield self.pretty(loc), loc

View File

@ -7,6 +7,7 @@ __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import re
from contextlib import closing
from lxml import html
@ -49,7 +50,7 @@ class AmazonEUBase(StorePlugin):
asin_xpath = '@name'
cover_xpath = './/img[@class="productImage"]/@src'
title_xpath = './/h3[@class="newaps"]/a//text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]//text()'
price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()'
for data in doc.xpath(data_xpath):
@ -57,7 +58,7 @@ class AmazonEUBase(StorePlugin):
break
# Even though we are searching digital-text only Amazon will still
# put in results for non Kindle books (author pages). Se we need
# put in results for non Kindle books (authors pages). Se we need
# to explicitly check if the item is a Kindle book and ignore it
# if it isn't.
format_ = ''.join(data.xpath(format_xpath))
@ -75,12 +76,13 @@ class AmazonEUBase(StorePlugin):
cover_url = ''.join(data.xpath(cover_xpath))
title = ''.join(data.xpath(title_xpath))
author = ''.join(data.xpath(author_xpath))
try:
if self.author_article:
author = author.split(self.author_article, 1)[1].split(" (")[0]
except:
pass
authors = ''.join(data.xpath(author_xpath))
authors = re.sub('^' + self.author_article, '', authors)
authors = re.sub(self.and_word, ' & ', authors)
mo = re.match(r'(.*)(\(\d.*)$', authors)
if mo:
authors = mo.group(1).strip()
price = ''.join(data.xpath(price_xpath))
@ -89,7 +91,7 @@ class AmazonEUBase(StorePlugin):
s = SearchResult()
s.cover_url = cover_url.strip()
s.title = title.strip()
s.author = author.strip()
s.author = authors.strip()
s.price = price.strip()
s.detail_item = asin.strip()
s.drm = SearchResult.DRM_UNKNOWN
@ -115,3 +117,5 @@ class AmazonDEKindleStore(AmazonEUBase):
search_url = 'http://www.amazon.de/s/?url=search-alias%3Ddigital-text&field-keywords='
author_article = 'von '
and_word = ' und '

View File

@ -7,6 +7,7 @@ __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import re
from contextlib import closing
from lxml import html
@ -48,7 +49,7 @@ class AmazonEUBase(StorePlugin):
asin_xpath = '@name'
cover_xpath = './/img[@class="productImage"]/@src'
title_xpath = './/h3[@class="newaps"]/a//text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]//text()'
price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()'
for data in doc.xpath(data_xpath):
@ -56,7 +57,7 @@ class AmazonEUBase(StorePlugin):
break
# Even though we are searching digital-text only Amazon will still
# put in results for non Kindle books (author pages). Se we need
# put in results for non Kindle books (authors pages). Se we need
# to explicitly check if the item is a Kindle book and ignore it
# if it isn't.
format_ = ''.join(data.xpath(format_xpath))
@ -74,12 +75,13 @@ class AmazonEUBase(StorePlugin):
cover_url = ''.join(data.xpath(cover_xpath))
title = ''.join(data.xpath(title_xpath))
author = ''.join(data.xpath(author_xpath))
try:
if self.author_article:
author = author.split(self.author_article, 1)[1].split(" (")[0]
except:
pass
authors = ''.join(data.xpath(author_xpath))
authors = re.sub('^' + self.author_article, '', authors)
authors = re.sub(self.and_word, ' & ', authors)
mo = re.match(r'(.*)(\(\d.*)$', authors)
if mo:
authors = mo.group(1).strip()
price = ''.join(data.xpath(price_xpath))
@ -88,7 +90,7 @@ class AmazonEUBase(StorePlugin):
s = SearchResult()
s.cover_url = cover_url.strip()
s.title = title.strip()
s.author = author.strip()
s.author = authors.strip()
s.price = price.strip()
s.detail_item = asin.strip()
s.drm = SearchResult.DRM_UNKNOWN
@ -113,3 +115,5 @@ class AmazonESKindleStore(AmazonEUBase):
search_url = 'http://www.amazon.es/s/?url=search-alias%3Ddigital-text&field-keywords='
author_article = 'de '
and_word = ' y '

View File

@ -7,7 +7,7 @@ __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import re
from contextlib import closing
from lxml import html
@ -50,7 +50,7 @@ class AmazonEUBase(StorePlugin):
asin_xpath = '@name'
cover_xpath = './/img[@class="productImage"]/@src'
title_xpath = './/h3[@class="newaps"]/a//text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]//text()'
price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()'
for data in doc.xpath(data_xpath):
@ -58,7 +58,7 @@ class AmazonEUBase(StorePlugin):
break
# Even though we are searching digital-text only Amazon will still
# put in results for non Kindle books (author pages). Se we need
# put in results for non Kindle books (authors pages). Se we need
# to explicitly check if the item is a Kindle book and ignore it
# if it isn't.
format_ = ''.join(data.xpath(format_xpath))
@ -76,12 +76,13 @@ class AmazonEUBase(StorePlugin):
cover_url = ''.join(data.xpath(cover_xpath))
title = ''.join(data.xpath(title_xpath))
author = ''.join(data.xpath(author_xpath))
try:
if self.author_article:
author = author.split(self.author_article, 1)[1].split(" (")[0]
except:
pass
authors = ''.join(data.xpath(author_xpath))
authors = re.sub('^' + self.author_article, '', authors)
authors = re.sub(self.and_word, ' & ', authors)
mo = re.match(r'(.*)(\(\d.*)$', authors)
if mo:
authors = mo.group(1).strip()
price = ''.join(data.xpath(price_xpath))
@ -90,7 +91,7 @@ class AmazonEUBase(StorePlugin):
s = SearchResult()
s.cover_url = cover_url.strip()
s.title = title.strip()
s.author = author.strip()
s.author = authors.strip()
s.price = price.strip()
s.detail_item = asin.strip()
s.drm = SearchResult.DRM_UNKNOWN
@ -112,3 +113,5 @@ class AmazonFRKindleStore(AmazonEUBase):
search_url = 'http://www.amazon.fr/s/?url=search-alias%3Ddigital-text&field-keywords='
author_article = 'de '
and_word = ' et '

View File

@ -7,6 +7,7 @@ __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import re
from contextlib import closing
from lxml import html
@ -48,7 +49,7 @@ class AmazonEUBase(StorePlugin):
asin_xpath = '@name'
cover_xpath = './/img[@class="productImage"]/@src'
title_xpath = './/h3[@class="newaps"]/a//text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]//text()'
price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()'
for data in doc.xpath(data_xpath):
@ -56,7 +57,7 @@ class AmazonEUBase(StorePlugin):
break
# Even though we are searching digital-text only Amazon will still
# put in results for non Kindle books (author pages). Se we need
# put in results for non Kindle books (authors pages). Se we need
# to explicitly check if the item is a Kindle book and ignore it
# if it isn't.
format_ = ''.join(data.xpath(format_xpath))
@ -74,12 +75,13 @@ class AmazonEUBase(StorePlugin):
cover_url = ''.join(data.xpath(cover_xpath))
title = ''.join(data.xpath(title_xpath))
author = ''.join(data.xpath(author_xpath))
try:
if self.author_article:
author = author.split(self.author_article, 1)[1].split(" (")[0]
except:
pass
authors = ''.join(data.xpath(author_xpath))
authors = re.sub('^' + self.author_article, '', authors)
authors = re.sub(self.and_word, ' & ', authors)
mo = re.match(r'(.*)(\(\d.*)$', authors)
if mo:
authors = mo.group(1).strip()
price = ''.join(data.xpath(price_xpath))
@ -88,7 +90,7 @@ class AmazonEUBase(StorePlugin):
s = SearchResult()
s.cover_url = cover_url.strip()
s.title = title.strip()
s.author = author.strip()
s.author = authors.strip()
s.price = price.strip()
s.detail_item = asin.strip()
s.drm = SearchResult.DRM_UNKNOWN
@ -99,7 +101,6 @@ class AmazonEUBase(StorePlugin):
def get_details(self, search_result, timeout):
pass
class AmazonITKindleStore(AmazonEUBase):
'''
For comments on the implementation, please see amazon_plugin.py
@ -114,3 +115,5 @@ class AmazonITKindleStore(AmazonEUBase):
search_url = 'http://www.amazon.it/s/?url=search-alias%3Ddigital-text&field-keywords='
author_article = 'di '
and_word = ' e '

View File

@ -133,7 +133,7 @@ class AmazonKindleStore(StorePlugin):
asin_xpath = '@name'
cover_xpath = './/img[@class="productImage"]/@src'
title_xpath = './/h3[@class="newaps"]/a//text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]//text()'
price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()'
for data in doc.xpath(data_xpath):

View File

@ -7,6 +7,7 @@ __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import re
from contextlib import closing
from lxml import html
@ -48,7 +49,7 @@ class AmazonEUBase(StorePlugin):
asin_xpath = '@name'
cover_xpath = './/img[@class="productImage"]/@src'
title_xpath = './/h3[@class="newaps"]/a//text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]//text()'
price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()'
for data in doc.xpath(data_xpath):
@ -56,7 +57,7 @@ class AmazonEUBase(StorePlugin):
break
# Even though we are searching digital-text only Amazon will still
# put in results for non Kindle books (author pages). Se we need
# put in results for non Kindle books (authors pages). Se we need
# to explicitly check if the item is a Kindle book and ignore it
# if it isn't.
format_ = ''.join(data.xpath(format_xpath))
@ -74,12 +75,13 @@ class AmazonEUBase(StorePlugin):
cover_url = ''.join(data.xpath(cover_xpath))
title = ''.join(data.xpath(title_xpath))
author = ''.join(data.xpath(author_xpath))
try:
if self.author_article:
author = author.split(self.author_article, 1)[1].split(" (")[0]
except:
pass
authors = ''.join(data.xpath(author_xpath))
authors = re.sub('^' + self.author_article, '', authors)
authors = re.sub(self.and_word, ' & ', authors)
mo = re.match(r'(.*)(\(\d.*)$', authors)
if mo:
authors = mo.group(1).strip()
price = ''.join(data.xpath(price_xpath))
@ -88,7 +90,7 @@ class AmazonEUBase(StorePlugin):
s = SearchResult()
s.cover_url = cover_url.strip()
s.title = title.strip()
s.author = author.strip()
s.author = authors.strip()
s.price = price.strip()
s.detail_item = asin.strip()
s.drm = SearchResult.DRM_UNKNOWN
@ -112,3 +114,5 @@ class AmazonUKKindleStore(AmazonEUBase):
author_article = 'by '
and_word = ' and '

View File

@ -41,7 +41,7 @@ class FoylesUKStore(BasicStoreConfig, StorePlugin):
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://ebooks.foyles.co.uk/search_for-' + urllib2.quote(query)
url = 'http://ebooks.foyles.co.uk/catalog/search/?query=' + urllib2.quote(query)
br = browser()
@ -58,7 +58,7 @@ class FoylesUKStore(BasicStoreConfig, StorePlugin):
cover_url = ''.join(data.xpath('.//p[@class="doc-cover"]/a/img/@src'))
title = ''.join(data.xpath('.//span[@class="title"]/a/text()'))
author = ', '.join(data.xpath('.//span[@class="author"]/span[@class="author"]/text()'))
price = ''.join(data.xpath('.//span[@class="price"]/text()'))
price = ''.join(data.xpath('.//span[@itemprop="price"]/text()'))
format_ = ''.join(data.xpath('.//p[@class="doc-meta-format"]/span[last()]/text()'))
format_, ign, drm = format_.partition(' ')
drm = SearchResult.DRM_LOCKED if 'DRM' in drm else SearchResult.DRM_UNLOCKED

View File

@ -357,8 +357,9 @@ def do_add_empty(db, title, authors, isbn, tags, series, series_index, cover):
mi.series, mi.series_index = series, series_index
if cover:
mi.cover = cover
db.import_book(mi, [])
book_id = db.import_book(mi, [])
write_dirtied(db)
prints(_('Added book ids: %s')%book_id)
send_message()
def command_add(args, dbpath):

View File

@ -2272,7 +2272,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# force_changes has no effect on cover manipulation
if mi.cover_data[1] is not None:
doit(self.set_cover, id, mi.cover_data[1], commit=False)
elif mi.cover is not None:
elif isinstance(mi.cover, basestring) and mi.cover:
if os.access(mi.cover, os.R_OK):
with lopen(mi.cover, 'rb') as f:
raw = f.read()

View File

@ -5,7 +5,7 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import operator, os, json, re
import operator, os, json, re, time
from binascii import hexlify, unhexlify
from collections import OrderedDict
@ -819,7 +819,7 @@ class BrowseServer(object):
raw = json.dumps('\n'.join(summs), ensure_ascii=True)
return raw
def browse_render_details(self, id_):
def browse_render_details(self, id_, add_random_button=False):
try:
mi = self.db.get_metadata(id_, index_is_id=True)
except:
@ -886,11 +886,18 @@ class BrowseServer(object):
u'<div class="comment">%s</div></div>') % (xml(c[0]),
c[1]) for c in comments]
comments = u'<div class="comments">%s</div>'%('\n\n'.join(comments))
random = ''
if add_random_button:
href = '%s/browse/random?v=%s'%(
self.opts.url_prefix, time.time())
random = '<a href="%s" id="random_button" title="%s">%s</a>' % (
xml(href, True), xml(_('Choose another random book'), True),
xml(_('Another random book')))
return self.browse_details_template.format(
id=id_, title=xml(mi.title, True), fields=fields,
get_url=args['get_url'], fmt=args['fmt'],
formats=args['formats'], comments=comments)
formats=args['formats'], comments=comments, random=random)
@Endpoint(mimetype='application/json; charset=utf-8')
def browse_details(self, id=None):
@ -908,7 +915,7 @@ class BrowseServer(object):
import random
book_id = random.choice(self.db.search_getting_ids(
'', self.search_restriction))
ans = self.browse_render_details(book_id)
ans = self.browse_render_details(book_id, add_random_button=True)
return self.browse_template('').format(
title='', script='book();', main=ans)

View File

@ -123,6 +123,274 @@ os.remove(os.path.abspath(__file__))
# }}}
class ZshCompleter(object): # {{{
def __init__(self, opts):
self.opts = opts
self.dest = None
base = os.path.dirname(self.opts.staging_sharedir)
self.detect_zsh(base)
if not self.dest and base == '/usr/share':
# Ubuntu puts site-functions in /usr/local/share
self.detect_zsh('/usr/local/share')
self.commands = {}
def detect_zsh(self, base):
for x in ('vendor-completions', 'vendor-functions', 'site-functions'):
c = os.path.join(base, 'zsh', x)
if os.path.isdir(c) and os.access(c, os.W_OK):
self.dest = os.path.join(c, '_calibre')
break
def get_options(self, parser, cover_opts=('--cover',), opf_opts=('--opf',),
file_map={}):
if hasattr(parser, 'option_list'):
options = parser.option_list
for group in parser.option_groups:
options += group.option_list
else:
options = parser
for opt in options:
lo, so = opt._long_opts, opt._short_opts
if opt.takes_value():
lo = [x+'=' for x in lo]
so = [x+'+' for x in so]
ostrings = lo + so
if len(ostrings) > 1:
ostrings = u'{%s}'%','.join(ostrings)
else:
ostrings = ostrings[0]
exclude = u''
if opt.dest is None:
exclude = u"'(- *)'"
h = opt.help or ''
h = h.replace('"', "'").replace('[', '(').replace(
']', ')').replace('\n', ' ').replace(':', '\\:')
h = h.replace('%default', type(u'')(opt.default))
arg = ''
if opt.takes_value():
arg = ':"%s":'%h
if opt.dest in {'debug_pipeline', 'to_dir', 'outbox', 'with_library', 'library_path'}:
arg += "'_path_files -/'"
elif opt.choices:
arg += "(%s)"%'|'.join(opt.choices)
elif set(file_map).intersection(set(opt._long_opts)):
k = set(file_map).intersection(set(opt._long_opts))
exts = file_map[tuple(k)[0]]
if exts:
arg += "'_files -g \"%s\"'"%(' '.join('*.%s'%x for x in
tuple(exts) + tuple(x.upper() for x in exts)))
else:
arg += "_files"
elif (opt.dest in {'pidfile', 'attachment'}):
arg += "_files"
elif set(opf_opts).intersection(set(opt._long_opts)):
arg += "'_files -g \"*.opf\"'"
elif set(cover_opts).intersection(set(opt._long_opts)):
arg += "'_files -g \"%s\"'"%(' '.join('*.%s'%x for x in
tuple(pics) + tuple(x.upper() for x in pics)))
help_txt = u'"[%s]"'%h
yield u'%s%s%s%s '%(exclude, ostrings, help_txt, arg)
def opts_and_exts(self, name, op, exts, cover_opts=('--cover',),
opf_opts=('--opf',), file_map={}):
if not self.dest: return
exts = set(exts).union(x.upper() for x in exts)
pats = ('*.%s'%x for x in exts)
extra = ("'*:filename:_files -g \"%s\"' "%' '.join(pats),)
opts = '\\\n '.join(tuple(self.get_options(
op(), cover_opts=cover_opts, opf_opts=opf_opts, file_map=file_map)) + extra)
txt = '_arguments -s \\\n ' + opts
self.commands[name] = txt
def opts_and_words(self, name, op, words, takes_files=False):
if not self.dest: return
extra = ("'*:filename:_files' ",) if takes_files else ()
opts = '\\\n '.join(tuple(self.get_options(op())) + extra)
txt = '_arguments -s \\\n ' + opts
self.commands[name] = txt
def do_ebook_convert(self, f):
from calibre.ebooks.conversion.plumber import supported_input_formats
from calibre.web.feeds.recipes.collection import get_builtin_recipe_titles
from calibre.customize.ui import available_output_formats
from calibre.ebooks.conversion.cli import create_option_parser, group_titles
from calibre.utils.logging import DevNull
input_fmts = set(supported_input_formats())
output_fmts = set(available_output_formats())
iexts = {x.upper() for x in input_fmts}.union(input_fmts)
oexts = {x.upper() for x in output_fmts}.union(output_fmts)
w = lambda x: f.write(x if isinstance(x, bytes) else x.encode('utf-8'))
# Arg 1
w('\n_ebc_input_args() {')
w('\n local extras; extras=(')
w('\n {-h,--help}":Show Help"')
w('\n "--version:Show program version"')
w('\n "--list-recipes:List builtin recipe names"')
for recipe in sorted(set(get_builtin_recipe_titles())):
recipe = recipe.replace(':', '\\:').replace('"', '\\"')
w(u'\n "%s.recipe"'%(recipe))
w('\n ); _describe -t recipes "ebook-convert builtin recipes" extras')
w('\n _files -g "%s"'%' '.join(('*.%s'%x for x in iexts)))
w('\n}\n')
# Arg 2
w('\n_ebc_output_args() {')
w('\n local extras; extras=(')
for x in output_fmts:
w('\n ".{0}:Convert to a .{0} file with the same name as the input file"'.format(x))
w('\n ); _describe -t output "ebook-convert output" extras')
w('\n _files -g "%s"'%' '.join(('*.%s'%x for x in oexts)))
w('\n _path_files -/')
w('\n}\n')
log = DevNull()
def get_parser(input_fmt='epub', output_fmt=None):
of = ('dummy2.'+output_fmt) if output_fmt else 'dummy'
return create_option_parser(('ec', 'dummy1.'+input_fmt, of, '-h'), log)[0]
# Common options
input_group, output_group = group_titles()
p = get_parser()
opts = p.option_list
for group in p.option_groups:
if group.title not in {input_group, output_group}:
opts += group.option_list
opts.append(p.get_option('--pretty-print'))
opts.append(p.get_option('--input-encoding'))
opts = '\\\n '.join(tuple(
self.get_options(opts, file_map={'--search-replace':()})))
w('\n_ebc_common_opts() {')
w('\n _arguments -s \\\n ' + opts)
w('\n}\n')
# Input/Output format options
for fmts, group_title, func in (
(input_fmts, input_group, '_ebc_input_opts_%s'),
(output_fmts, output_group, '_ebc_output_opts_%s'),
):
for fmt in fmts:
is_input = group_title == input_group
if is_input and fmt in {'rar', 'zip', 'oebzip'}: continue
p = (get_parser(input_fmt=fmt) if is_input
else get_parser(output_fmt=fmt))
opts = None
for group in p.option_groups:
if group.title == group_title:
opts = [o for o in group.option_list if
'--pretty-print' not in o._long_opts and
'--input-encoding' not in o._long_opts]
if not opts: continue
opts = '\\\n '.join(tuple(self.get_options(opts)))
w('\n%s() {'%(func%fmt))
w('\n _arguments -s \\\n ' + opts)
w('\n}\n')
w('\n_ebook_convert() {')
w('\n local iarg oarg context state_descr state line\n typeset -A opt_args\n local ret=1')
w("\n _arguments '1: :_ebc_input_args' '*::ebook-convert output:->args' && ret=0")
w("\n case $state in \n (args)")
w('\n iarg=${line[1]##*.}; ')
w("\n _arguments '1: :_ebc_output_args' '*::ebook-convert options:->args' && ret=0")
w("\n case $state in \n (args)")
w('\n oarg=${line[1]##*.}')
w('\n iarg="_ebc_input_opts_${(L)iarg}"; oarg="_ebc_output_opts_${(L)oarg}"')
w('\n _call_function - $iarg; _call_function - $oarg; _ebc_common_opts; ret=0')
w('\n ;;\n esac')
w("\n ;;\n esac\n return ret")
w('\n}\n')
def do_calibredb(self, f):
import calibre.library.cli as cli
from calibre.customize.ui import available_catalog_formats
parsers, descs = {}, {}
for command in cli.COMMANDS:
op = getattr(cli, '%s_option_parser'%command)
args = [['t.epub']] if command == 'catalog' else []
p = op(*args)
if isinstance(p, tuple):
p = p[0]
parsers[command] = p
lines = [x.strip().partition('.')[0] for x in p.usage.splitlines() if x.strip() and
not x.strip().startswith('%prog')]
descs[command] = lines[0]
f.write('\n_calibredb_cmds() {\n local commands; commands=(\n')
f.write(' {-h,--help}":Show help"\n')
f.write(' "--version:Show version"\n')
for command, desc in descs.iteritems():
f.write(' "%s:%s"\n'%(
command, desc.replace(':', '\\:').replace('"', '\'')))
f.write(' )\n _describe -t commands "calibredb command" commands \n}\n')
subcommands = []
for command, parser in parsers.iteritems():
exts = []
if command == 'catalog':
exts = [x.lower() for x in available_catalog_formats()]
elif command == 'set_metadata':
exts = ['opf']
exts = set(exts).union(x.upper() for x in exts)
pats = ('*.%s'%x for x in exts)
extra = ("'*:filename:_files -g \"%s\"' "%' '.join(pats),) if exts else ()
if command in {'add', 'add_format'}:
extra = ("'*:filename:_files' ",)
opts = '\\\n '.join(tuple(self.get_options(
parser)) + extra)
txt = ' _arguments -s \\\n ' + opts
subcommands.append('(%s)'%command)
subcommands.append(txt)
subcommands.append(';;')
f.write('\n_calibredb() {')
f.write(
r'''
local state line state_descr context
typeset -A opt_args
local ret=1
_arguments \
'1: :_calibredb_cmds' \
'*::calibredb subcommand options:->args' \
&& ret=0
case $state in
(args)
case $line[1] in
(-h|--help|--version)
_message 'no more arguments' && ret=0
;;
%s
esac
;;
esac
return ret
'''%'\n '.join(subcommands))
f.write('\n}\n\n')
def write(self):
if self.dest:
self.commands['calibredb'] = ' _calibredb "$@"'
self.commands['ebook-convert'] = ' _ebook_convert "$@"'
with open(self.dest, 'wb') as f:
f.write('#compdef ' + ' '.join(self.commands)+'\n')
self.do_ebook_convert(f)
self.do_calibredb(f)
f.write('case $service in\n')
for c, txt in self.commands.iteritems():
if isinstance(txt, type(u'')):
txt = txt.encode('utf-8')
if isinstance(c, type(u'')):
c = c.encode('utf-8')
f.write(b'%s)\n%s\n;;\n'%(c, txt))
f.write('esac\n')
# }}}
class PostInstall:
def task_failed(self, msg):
@ -217,7 +485,7 @@ class PostInstall:
def setup_completion(self): # {{{
try:
self.info('Setting up bash completion...')
self.info('Setting up command-line completion...')
from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes
from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop
from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop
@ -227,8 +495,11 @@ class PostInstall:
from calibre.utils.smtp import option_parser as smtp_op
from calibre.library.server.main import option_parser as serv_op
from calibre.ebooks.oeb.polish.main import option_parser as polish_op, SUPPORTED
from calibre.debug import option_parser as debug_op
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.customize.ui import available_input_formats
input_formats = sorted(all_input_formats())
zsh = ZshCompleter(self.opts)
bc = os.path.join(os.path.dirname(self.opts.staging_sharedir),
'bash-completion')
if os.path.exists(bc):
@ -240,6 +511,9 @@ class PostInstall:
f = os.path.join(self.opts.staging_etc, 'bash_completion.d/calibre')
if not os.path.exists(os.path.dirname(f)):
os.makedirs(os.path.dirname(f))
if zsh.dest:
self.info('Installing zsh completion to:', zsh.dest)
self.manifest.append(zsh.dest)
self.manifest.append(f)
complete = 'calibre-complete'
if getattr(sys, 'frozen_path', None):
@ -247,20 +521,35 @@ class PostInstall:
self.info('Installing bash completion to', f)
with open(f, 'wb') as f:
def o_and_e(*args, **kwargs):
f.write(opts_and_exts(*args, **kwargs))
zsh.opts_and_exts(*args, **kwargs)
def o_and_w(*args, **kwargs):
f.write(opts_and_words(*args, **kwargs))
zsh.opts_and_words(*args, **kwargs)
f.write('# calibre Bash Shell Completion\n')
f.write(opts_and_exts('calibre', guiop, BOOK_EXTENSIONS))
f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf']))
f.write(opts_and_exts('ebook-meta', metaop,
list(meta_filetypes()), cover_opts=['--cover', '-c'],
opf_opts=['--to-opf', '--from-opf']))
f.write(opts_and_exts('ebook-polish', polish_op,
[x.lower() for x in SUPPORTED], cover_opts=['--cover', '-c'],
opf_opts=['--opf', '-o']))
f.write(opts_and_exts('lrfviewer', lrfviewerop, ['lrf']))
f.write(opts_and_exts('ebook-viewer', viewer_op, input_formats))
f.write(opts_and_words('fetch-ebook-metadata', fem_op, []))
f.write(opts_and_words('calibre-smtp', smtp_op, []))
f.write(opts_and_words('calibre-server', serv_op, []))
o_and_e('calibre', guiop, BOOK_EXTENSIONS)
o_and_e('lrf2lrs', lrf2lrsop, ['lrf'], file_map={'--output':['lrs']})
o_and_e('ebook-meta', metaop,
list(meta_filetypes()), cover_opts=['--cover', '-c'],
opf_opts=['--to-opf', '--from-opf'])
o_and_e('ebook-polish', polish_op,
[x.lower() for x in SUPPORTED], cover_opts=['--cover', '-c'],
opf_opts=['--opf', '-o'])
o_and_e('lrfviewer', lrfviewerop, ['lrf'])
o_and_e('ebook-viewer', viewer_op, input_formats)
o_and_w('fetch-ebook-metadata', fem_op, [])
o_and_w('calibre-smtp', smtp_op, [])
o_and_w('calibre-server', serv_op, [])
o_and_e('calibre-debug', debug_op, ['py', 'recipe'], file_map={
'--tweak-book':['epub', 'azw3', 'mobi'],
'--subset-font':['ttf', 'otf'],
'--exec-file':['py', 'recipe'],
'--add-simple-plugin':['py'],
'--inspect-mobi':['mobi', 'azw', 'azw3'],
'--viewer':list(available_input_formats()),
})
f.write(textwrap.dedent('''
_ebook_device_ls()
{
@ -335,6 +624,7 @@ class PostInstall:
complete -o nospace -C %s ebook-convert
''')%complete)
zsh.write()
except TypeError as err:
if 'resolve_entities' in str(err):
print 'You need python-lxml >= 2.0.5 for calibre'
@ -451,7 +741,7 @@ def options(option_parser):
opts.extend(opt._long_opts)
return opts
def opts_and_words(name, op, words):
def opts_and_words(name, op, words, takes_files=False):
opts = '|'.join(options(op))
words = '|'.join([w.replace("'", "\\'") for w in words])
fname = name.replace('-', '_')
@ -481,12 +771,15 @@ def opts_and_words(name, op, words):
}
complete -F _'''%(opts, words) + fname + ' ' + name +"\n\n").encode('utf-8')
pics = {'jpg', 'jpeg', 'gif', 'png', 'bmp'}
def opts_and_exts(name, op, exts, cover_opts=('--cover',), opf_opts=()):
def opts_and_exts(name, op, exts, cover_opts=('--cover',), opf_opts=(),
file_map={}):
opts = ' '.join(options(op))
exts.extend([i.upper() for i in exts])
exts='|'.join(exts)
fname = name.replace('-', '_')
spics = '|'.join(tuple(pics) + tuple(x.upper() for x in pics))
special_exts_template = '''\
%s )
_filedir %s
@ -507,7 +800,7 @@ def opts_and_exts(name, op, exts, cover_opts=('--cover',), opf_opts=()):
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="%(opts)s"
pics="@(jpg|jpeg|png|gif|bmp|JPG|JPEG|PNG|GIF|BMP)"
pics="@(%(pics)s)"
case "${prev}" in
%(extras)s
@ -526,7 +819,7 @@ def opts_and_exts(name, op, exts, cover_opts=('--cover',), opf_opts=()):
esac
}
complete -o filenames -F _'''%dict(
complete -o filenames -F _'''%dict(pics=spics,
opts=opts, extras=extras, exts=exts) + fname + ' ' + name +"\n\n"
@ -627,6 +920,5 @@ def main():
PostInstall(opts)
return 0
if __name__ == '__main__':
sys.exit(main())

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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