This commit is contained in:
Sengian 2011-11-12 23:06:20 +01:00
commit 533e75ed65
130 changed files with 18975 additions and 14048 deletions

View File

@ -19,6 +19,126 @@
# new recipes: # new recipes:
# - title: # - title:
- version: 0.8.26
date: 2011-11-12
new features:
- title: "Tweak to control sorting of date type columns. You can choose to have them sorted only by displayed fields"
- title: "Driver for the Trekstor 3.0"
- title: "Performance improvements when evaluating templates, and in particular general program mode templates"
bug fixes:
- title: "ODT Input: When converting to EPUB improve handling of large images placed inside small frames, to prevent them from obscuring text."
tickets: [860272,884759]
- title: "EPUB Input: Automatically strip entries of type application/text from the spine. Apparently there are EPUB production tools out there that create them."
tickets: [884792]
- title: "Keep the startup splash screen visible until the GUI has fully completed initializing."
tickets: [885827]
- title: "ODT Input: Fix handling of span tags containing only whitespace."
tickets: [887311]
- title: "On windows when changing title or author via the main book list, handle the case of one of the books files being open in another program more gracefully."
tickets: [880585]
- title: "When adding a format to an existing book record, ensure that no changes are made to the database until after the file operations have succeeded."
- title: "Fix bug that prevented configuring which fields to download metadata for when adding books by ISBN"
tickets: [856076]
- title: "Fix Japanese characters not being crrectly displayed on index pages in news downloads for the SONY T1"
tickets: [888029]
- title: "Get Books: Fix booleans in search expressions not working in non-English calibre versions"
tickets: [887554]
- title: "Fix a bug in the support for hours/minutes/seconds in datetime format strings"
tickets: [887412]
- title: "Treat an author_sort value of 'Unknown' the same way as unknown authors are treated in template processing"
- title: "Detect SD card in Kobo Vox"
- title: "Amazon metadata download: Workaround for change in Amazon website causing some books to have incorrect ratings downloaded"
improved recipes:
- Metro NL
- The Independent
- Frankfurter Rundschau
- L'Espresso
- Il Giornale
- Berlingske.dk
- Suedeutsche Zeitung
new recipes:
- title: Techtarget
author: Julio Map
- version: 0.8.25
date: 2011-11-06
new features:
- title: "Drivers for the LG Optimus 2X, HTC Incredible S, Samsung Stratosphere and the Kobo Vox"
tickets: [886558, 885058, 884762, 884039]
- title: "Get books: Add ebookpoint.pl store"
- title: "Support hour/minute/seconds in datetime format strings in the template language and in tweaks"
bug fixes:
- title: "Fix Book detils preferences showing custom columns even after they have been deleted"
tickets: [884799]
- title: "Replace use of insecure tempfile in the bundled rtf2xml library."
tickets: [885245]
- title: "Remove the suid mount helper used on linux and bsd, as it proved impossible to make it secure."
description: "This means that if you are on BSD or an older linux distribution, without support for udisks, device detection will no longer work in calibre. You will have to either mount the devices by hand before starting calibre, or stick with version 0.8.24 (the vulnerability in the mount helper is a privilege escalation, which is relatively harmless on the vast majority of single user systems)."
tickets: [885027]
- title: "Do not error out if there is an invalid regex for title sort set in tweaks"
- title: "Content server: Fix another place where --url-prefix was forgotten"
tickets: [885332]
- title: "HTML Input: Limit memory consumption when converting HTML files that link to large binary files."
tickets: [884821]
- title: "T1 driver: Workaround for T1 showing error messages when opening some news downloads on the device"
- title: "Kobo driver: Fix longstanding bug that would prevent re-adding a epub that has been previously deleted from the Kobo using calibre"
- title: "Fix partial cover search not resuming after pressing back in the metadata download dialog"
tickets: [875196]
- title: "T1 driver: Fix auto refresh covers option"
- title: "Content server: Do not show tracebacks in HTML output when not running in develop mode"
- title: "Textile output; Fix out of memory issue when dealing with large margins."
improved recipes:
- The Independent
- Die Zeit subscription version
- NIN online
- Science News
- Updated Daily Mirror
- Science AAAS
new recipes:
- title: b365 Realitatea and Catavencii
author: Silviu Cotoara
- title: Various Greek news sources
author: Stelios
- title: Real world economics blog
author: Julio Map
- version: 0.8.24 - version: 0.8.24
date: 2011-10-27 date: 2011-10-27

View File

@ -1,4 +1,3 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
''' '''
@ -18,11 +17,17 @@ class Berlingske_dk(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
remove_empty_feeds = True remove_empty_feeds = True
use_embedded_content = False use_embedded_content = False
remove_javascript = True
publication_type = 'newspaper' publication_type = 'newspaper'
encoding = 'utf8' encoding = 'utf8'
language = 'da' language = 'da'
masthead_url = 'http://www.berlingske.dk/sites/all/themes/bm/img/layout/masthead_bg.gif' auto_cleanup = True
extra_css = ' body{font-family: Arial,Helvetica,sans-serif } h1,.manchet,.byline{font-family: Cambria,Georgia,Times,"Times New Roman",serif } ' extra_css = '''
.manchet {color:#888888;}
.dateline {font-size: x-small; color:#444444;}
.manchet,.dateline { font-family: Cambria,Georgia,Times,"Times New Roman",serif }
.body {font-family: Arial,Helvetica,sans-serif }
'''
conversion_options = { conversion_options = {
'comment' : description 'comment' : description
@ -32,18 +37,14 @@ class Berlingske_dk(BasicNewsRecipe):
} }
feeds = [ feeds = [
(u'Breaking news' , u'http://www.berlingske.dk/breaking/rss' ) (u'Breaking news' , u'http://www.b.dk/breaking/rss' )
,(u'Seneste nyt' , u'http://www.berlingske.dk/seneste/rss' ) ,(u'Seneste nyt' , u'http://www.b.dk/seneste/rss' )
,(u'Topnyheder' , u'http://www.berlingske.dk/top/rss' ) ,(u'Topnyheder' , u'http://www.b.dk/top/rss' )
,(u'Danmark' , u'http://www.berlingske.dk/danmark/seneste/rss' ) ,(u'Danmark' , u'http://www.b.dk/danmark/seneste/rss' )
,(u'Verden' , u'http://www.berlingske.dk/verden/seneste/rss' ) ,(u'Verden' , u'http://www.b.dk/verden/seneste/rss' )
,(u'Klima' , u'http://www.berlingske.dk/klima/seneste/rss' ) ,(u'Klima' , u'http://www.b.dk/klima/seneste/rss' )
,(u'Debat' , u'http://www.berlingske.dk/debat/seneste/rss' ) ,(u'Debat' , u'http://www.b.dk/debat/seneste/rss' )
,(u'Koebenhavn' , u'http://www.berlingske.dk/koebenhavn/seneste/rss') ,(u'Koebenhavn' , u'http://www.b.dk/koebenhavn/seneste/rss')
,(u'Politik' , u'http://www.berlingske.dk/politik/seneste/rss' ) ,(u'Politik' , u'http://www.b.dk/politik/seneste/rss' )
,(u'Kultur' , u'http://www.berlingske.dk/kultur/seneste/rss' ) ,(u'Kultur' , u'http://www.b.dk/kultur/seneste/rss' )
] ]
keep_only_tags = [dict(attrs={'class':['first','pt-article']})]
remove_tags = [dict(name=['object','link','base','iframe','embed'])]

View File

@ -55,12 +55,17 @@ class Economist(BasicNewsRecipe):
''' '''
def get_cover_url(self): def get_cover_url(self):
br = self.browser soup = self.index_to_soup('http://www.economist.com/printedition/covers')
br.open(self.INDEX) div = soup.find('div', attrs={'class':lambda x: x and
issue = br.geturl().split('/')[4] 'print-cover-links' in x})
self.log('Fetching cover for issue: %s'%issue) a = div.find('a', href=True)
cover_url = "http://media.economist.com/sites/default/files/imagecache/print-cover-full/print-covers/%s_CNA400.jpg" %(issue.translate(None,'-')) url = a.get('href')
return cover_url if url.startswith('/'):
url = 'http://www.economist.com' + url
soup = self.index_to_soup(url)
div = soup.find('div', attrs={'class':'cover-content'})
img = div.find('img', src=True)
return img.get('src')
def parse_index(self): def parse_index(self):
return self.economist_parse_index() return self.economist_parse_index()

View File

@ -39,13 +39,17 @@ class Economist(BasicNewsRecipe):
delay = 1 delay = 1
def get_cover_url(self): def get_cover_url(self):
br = self.browser soup = self.index_to_soup('http://www.economist.com/printedition/covers')
br.open(self.INDEX) div = soup.find('div', attrs={'class':lambda x: x and
issue = br.geturl().split('/')[4] 'print-cover-links' in x})
self.log('Fetching cover for issue: %s'%issue) a = div.find('a', href=True)
cover_url = "http://media.economist.com/sites/default/files/imagecache/print-cover-full/print-covers/%s_CNA400.jpg" %(issue.translate(None,'-')) url = a.get('href')
return cover_url if url.startswith('/'):
url = 'http://www.economist.com' + url
soup = self.index_to_soup(url)
div = soup.find('div', attrs={'class':'cover-content'})
img = div.find('img', src=True)
return img.get('src')
def parse_index(self): def parse_index(self):
try: try:

View File

@ -1,35 +1,61 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2010-2011, Christian Schmitt'
'''
fr-online.de
'''
from calibre.web.feeds.recipes import BasicNewsRecipe from calibre.web.feeds.recipes import BasicNewsRecipe
class AdvancedUserRecipe(BasicNewsRecipe):
title = u'Frankfurter Rundschau' class FROnlineRecipe(BasicNewsRecipe):
__author__ = 'schuster' title = 'Frankfurter Rundschau'
oldest_article = 1 __author__ = 'maccs'
max_articles_per_feed = 100 description = 'Nachrichten aus D und aller Welt'
no_stylesheets = True encoding = 'utf-8'
use_embedded_content = False masthead_url = 'http://www.fr-online.de/image/view/-/1474018/data/823552/-/logo.png'
language = 'de' publisher = 'Druck- und Verlagshaus Frankfurt am Main GmbH'
remove_javascript = True category = 'news, germany, world'
cover_url = 'http://www.fr-online.de/image/view/-/1474018/data/823538/-/logo.png' language = 'de'
extra_css = ''' publication_type = 'newspaper'
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;} use_embedded_content = False
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;} remove_javascript = True
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px} no_stylesheets = True
p{font-family:Arial,Helvetica,sans-serif;font-size:small;} oldest_article = 1 # Increase this number if you're interested in older articles
body{font-family:Helvetica,Arial,sans-serif;font-size:small;} max_articles_per_feed = 50 # Seems a reasonable number to me
''' extra_css = '''
body { font-family: "arial", "verdana", "geneva", sans-serif; font-size: 12px; margin: 0px; background-color: #ffffff;}
feeds = [(u'Startseite', u'http://www.fr-online.de/home/-/1472778/1472778/-/view/asFeed/-/index.xml'), .imgSubline{background-color: #f4f4f4; font-size: 0.8em;}
(u'Politik', u'http://www.fr-online.de/politik/-/1472596/1472596/-/view/asFeed/-/index.xml'), .p--heading-1 {font-weight: bold;}
(u'Meinungen', u'http://www.fr-online.de/politik/meinung/-/1472602/1472602/-/view/asFeed/-/index.xml'), .calibre_navbar {font-size: 0.8em; font-family: "arial", "verdana", "geneva", sans-serif;}
(u'Wirtschaft', u'http://www.fr-online.de/wirtschaft/-/1472780/1472780/-/view/asFeed/-/index.xml'), '''
(u'Sport', u'http://www.fr-online.de/sport/-/1472784/1472784/-/view/asFeed/-/index.xml'), keep_only_tags = [{'class':'ArticleHeadlineH1'}, {'class':'article_text'}]
(u'Kultur', u'http://www.fr-online.de/kultur/-/1472786/1472786/-/view/asFeed/-/index.xml'), cover_url = 'http://www.fr-online.de/image/view/-/1474018/data/823552/-/logo.png'
(u'Panorama', u'http://www.fr-online.de/panorama/-/1472782/1472782/-/view/asFeed/-/index.xml'), cover_margins = (100, 150, '#ffffff')
(u'Digital', u'http://www.fr-online.de/digital/-/1472406/1472406/-/view/asFeed/-/index.xml'),
(u'Wissenschaft', u'http://www.fr-online.de/wissenschaft/-/1472788/1472788/-/view/asFeed/-/index.xml')
]
def print_version(self, url): feeds = []
return url.replace('index.html', 'view/printVersion/-/index.html') feeds.append(('Startseite', u'http://www.fr-online.de/home/-/1472778/1472778/-/view/asFeed/-/index.xml'))
feeds.append(('Politik', u'http://www.fr-online.de/politik/-/1472596/1472596/-/view/asFeed/-/index.xml'))
feeds.append(('Meinung', u'http://www.fr-online.de/politik/meinung/-/1472602/1472602/-/view/asFeed/-/index.xml'))
feeds.append(('Wirtschaft', u'http://www.fr-online.de/wirtschaft/-/1472780/1472780/-/view/asFeed/-/index.xml'))
feeds.append(('Sport', u'http://www.fr-online.de/sport/-/1472784/1472784/-/view/asFeed/-/index.xml'))
feeds.append(('Eintracht Frankfurt', u'http://www.fr-online.de/sport/eintracht-frankfurt/-/1473446/1473446/-/view/asFeed/-/index.xml'))
feeds.append(('Kultur und Medien', u'http://www.fr-online.de/kultur/-/1472786/1472786/-/view/asFeed/-/index.xml'))
feeds.append(('Panorama', u'http://www.fr-online.de/panorama/-/1472782/1472782/-/view/asFeed/-/index.xml'))
feeds.append(('Frankfurt', u'http://www.fr-online.de/frankfurt/-/1472798/1472798/-/view/asFeed/-/index.xml'))
feeds.append(('Rhein-Main', u'http://www.fr-online.de/rhein-main/-/1472796/1472796/-/view/asFeed/-/index.xml'))
feeds.append(('Hanau', u'http://www.fr-online.de/rhein-main/hanau/-/1472866/1472866/-/view/asFeed/-/index.xml'))
feeds.append(('Darmstadt', u'http://www.fr-online.de/rhein-main/darmstadt/-/1472858/1472858/-/view/asFeed/-/index.xml'))
feeds.append(('Wiesbaden', u'http://www.fr-online.de/rhein-main/wiesbaden/-/1472860/1472860/-/view/asFeed/-/index.xml'))
feeds.append(('Offenbach', u'http://www.fr-online.de/rhein-main/offenbach/-/1472856/1472856/-/view/asFeed/-/index.xml'))
feeds.append(('Bad Homburg', u'http://www.fr-online.de/rhein-main/bad-homburg/-/1472864/1472864/-/view/asFeed/-/index.xml'))
feeds.append(('Digital', u'http://www.fr-online.de/digital/-/1472406/1472406/-/view/asFeed/-/index.xml'))
feeds.append(('Wissenschaft', u'http://www.fr-online.de/wissenschaft/-/1472788/1472788/-/view/asFeed/-/index.xml'))
def print_version(self, url):
return url.replace('index.html', 'view/printVersion/-/index.html')

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__author__ = 'Gabriele Marini, based on Darko Miletic' __author__ = 'Gambarini, based on Darko Miletic'
__copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
description = 'Italian daily newspaper - 19-04-2010' description = 'Italian daily newspaper - 09-11-2011'
''' '''
http://www.ilgiornale.it/ http://www.ilgiornale.it/
@ -11,7 +11,7 @@ from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class IlGiornale(BasicNewsRecipe): class IlGiornale(BasicNewsRecipe):
__author__ = 'Marini Gabriele' __author__ = 'GAMBARINI'
description = 'Italian daily newspaper' description = 'Italian daily newspaper'
cover_url = 'http://www.ilgiornale.it/img_v1/logo.gif' cover_url = 'http://www.ilgiornale.it/img_v1/logo.gif'
@ -23,9 +23,8 @@ class IlGiornale(BasicNewsRecipe):
timefmt = '[%a, %d %b, %Y]' timefmt = '[%a, %d %b, %Y]'
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 50 max_articles_per_feed = 100
use_embedded_content = False use_embedded_content = False
recursion = 100
no_stylesheets = True no_stylesheets = True
conversion_options = {'linearize_tables':True} conversion_options = {'linearize_tables':True}
@ -38,11 +37,11 @@ class IlGiornale(BasicNewsRecipe):
def print_version(self, url): def print_version(self, url):
raw = self.browser.open(url).read() raw = self.browser.open(url).read()
soup = BeautifulSoup(raw.decode('utf8', 'replace')) soup = BeautifulSoup(raw.decode('utf8', 'replace'))
all_print_tags = soup.find('div', {'style':'float:left; width:35%;'}) all_print_tags = soup.find('div', {'id':'print_article'})
print_link = all_print_tags.contents[1] print_link = all_print_tags.a
if all_print_tags is None: if print_link is None:
return url return url
return print_link['href'] return 'http://www.ilgiornale.it' + print_link['href']
feeds = [ feeds = [

View File

@ -1,13 +1,379 @@
from calibre.web.feeds.news import BasicNewsRecipe # adapted from old recipe by Darko Miletic <darko.miletic at gmail.com>
class AdvancedUserRecipe1320474488(BasicNewsRecipe): import re
from calibre.web.feeds.recipes import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag, NavigableString
class TheIndependentNew(BasicNewsRecipe):
# flag to enable/disable article graphics on business pages/some others
# eg http://www.independent.co.uk/news/world/europe/berlusconi-departure-fails-to-calm-the-markets-6259682.html
# -max dimensions can be altered using the .pictureContainer img selector in the css
_FETCH_ARTICLE_GRAPHICS = True
#Flag to enable/disable image fetching (not business)
_FETCH_IMAGES = True
#used for converting rating to stars
_STAR_URL = 'http://www.independent.co.uk/skins/ind/images/rating_star.png'
_NO_STAR_URL = 'http://www.independent.co.uk/skins/ind/images/rating_star_grey.png'
title = u'The Independent'
__author__ = 'Will'
description = 'The latest in UK News and World News from The \
Independent. Wide range of international and local news, sports \
news, commentary and opinion pieces.Independent News - Breaking news \
that matters. Your daily comprehensive news source - The \
Independent Newspaper'
publisher = 'The Independent'
category = 'news, UK'
no_stylesheets = True
use_embedded_content = False
remove_empty_feeds = True
language = 'en_GB'
publication_type = 'newspaper'
masthead_url = 'http://www.independent.co.uk/independent.co.uk/editorial/logo/independent_Masthead.png'
encoding = 'utf-8'
remove_tags =[
dict(attrs={'id' : ['RelatedArtTag','renderBiography']}),
dict(attrs={'class' : ['autoplay','openBiogPopup']})
]
keep_only_tags =[dict(attrs={'id':'main'})]
recursions = 0
# fixes non compliant html nesting and 'marks' article graphics links
preprocess_regexps = [
(re.compile('<span class="storyTop ">(?P<nested>.*?)</span>', re.DOTALL),
lambda match: '<div class="storyTop">' + match.group('nested') + '</div>'),
(re.compile('<strong>.*?Click.*?to view graphic.*?</strong>', re.DOTALL),
lambda match: '<div class="article-graphic">' + match.group(0) + '</div>'),
]
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
extra_css = """
h1{font-family: Georgia,serif }
body{font-family: Verdana,Arial,Helvetica,sans-serif}
img{margin-bottom: 0.4em; display:block}
.starRating img {float: left}
.starRating {margin-top:0.4em; display: block}
.image {clear:left; font-size: x-small; color:#888888;}
.articleByTimeLocation {font-size: x-small; color:#888888;
margin-bottom:0.2em ; margin-top:0.2em ; display:block}
.subtitle {clear:left}
.column-1 h1 { color: #191919}
.column-1 h2 { color: #333333}
.column-1 h3 { color: #444444}
.column-1 p { color: #777777}
.column-1 p,a,h1,h2,h3 { margin: 0; }
.column-1 div{color:#888888; margin: 0;}
.articleContent {display: block; clear:left;}
.storyTop{}
.pictureContainer img { max-width: 400px; max-height: 400px;}
"""
title = u'The Independent'
oldest_article = 1 oldest_article = 1
max_articles_per_feed = 100 max_articles_per_feed = 100
auto_cleanup = True
language = 'en_GB' _processed_urls = []
__author__ = 'NotTaken'
def get_article_url(self, article):
url = super(self.__class__,self).get_article_url(article)
title = article.get('title', None)
if title and re.search("^Video:",title):
return None
#remove duplicates
if not (url in self._processed_urls):
self._processed_urls.append(url)
else:
url = None
return url
def preprocess_html(self, soup):
items_to_extract = []
for item in soup.findAll(attrs={'class' : re.compile("widget.*")}):
remove = True
pattern = re.compile('((articleContent)|(title))$')
if (pattern.search(item['class'])) is not None:
remove = False
# corrections
# story content always good
pattern = re.compile('storyContent')
if (pattern.search(item['class'])) is not None:
remove = False
#images
pattern = re.compile('slideshow')
if (pattern.search(item['class'])) is not None:
if self._FETCH_IMAGES:
remove = False
else:
remove = True
#social widgets always bad
pattern = re.compile('socialwidget')
if (pattern.search(item['class'])) is not None:
remove = True
if remove:
items_to_extract.append(item)
for item in items_to_extract:
item.extract()
items_to_extract = []
if self._FETCH_IMAGES:
for item in soup.findAll('a',attrs={'href' : re.compile('.*')}):
if item.img is not None:
#use full size image
img = item.findNext('img')
img['src'] = item['href']
#insert caption if available
if img['title'] is not None and (len(img['title']) > 1):
tag = Tag(soup,'h3')
text = NavigableString(img['title'])
tag.insert(0,text)
#picture before text
img.extract()
item.insert(0,img)
item.insert(1,tag)
# remove link
item.name = "div"
item["class"]='image'
del item["href"]
#remove empty subtitles
"""
currently the subtitle is located in first paragraph after
sibling <h3 class="subtitle"> tag. This may be 'fixed' at
some point.
"""
subtitle = soup.find('h3',attrs={'class' : 'subtitle'})
if subtitle is not None:
subtitleText = subtitle.findNext('p')
if subtitleText is not None:
if len(subtitleText.contents[0]) <= 1 :
subtitleText.extract()
subtitle.extract()
#replace rating numbers with stars
for item in soup.findAll('div',attrs={ 'class' : 'starRating'}):
if item is not None:
soup2 = self._insertRatingStars(soup,item)
if soup2 is not None:
soup = soup2
#remove empty paragraph tags in storyTop which can leave a space
#between first paragraph and rest of story
storyTop = soup.find('div',attrs={ 'class' : ['storyTop']})
for item in storyTop.findAll('p'):
if item.contents is not None and len(item.contents[0]) <= 1 :
items_to_extract.append(item)
for item in items_to_extract:
item.extract()
items_to_extract = []
#remove line breaks immediately next to tags with default margins
#to prevent double line spacing and narrow columns of text
storyTop = soup.find('div',attrs={ 'class' : ['storyTop']})
self._remove_undesired_line_breaks_from_tag(storyTop,soup)
#replace article graphics link with the graphics themselves
if self._FETCH_ARTICLE_GRAPHICS:
items_to_insert = []
for item in soup.findAll('div', attrs={'class' : ['article-graphic']}):
strong = item.find('strong')
for child in strong:
if isinstance(child,Tag):
if str(child.name) == 'a':
items_to_insert.extend(self._get_article_graphic(strong,child['href'],soup))
for item in items_to_insert:
item[0].replaceWith(item[1])
for item in items_to_extract:
item.extract()
return soup
def _get_article_graphic(self,old_item,url,soup):
items_to_insert = []
if re.search('\.jpg$',str(url)):
div = Tag(soup,'div')
div['class'] = 'pictureContainer'
img = Tag(soup,'img')
img['src'] = url
img['alt'] = 'article graphic'
div.insert(0,img)
items_to_insert.append((old_item,div,))
return items_to_insert
soup2 = self.index_to_soup(url)
for item in soup2.findAll('div',attrs={'class' : re.compile("widget picture article.*")}):
items_to_insert.append((old_item,item),)
return items_to_insert
def _insertRatingStars(self,soup,item):
if item.contents is None:
return
rating = item.contents[0]
if not rating.isdigit():
return None
rating = int(item.contents[0])
for i in range(1,6):
star = Tag(soup,'img')
if i <= rating:
star['src'] = self._STAR_URL
else:
star['src'] = self._NO_STAR_URL
star['alt'] = 'star number ' + str(i)
item.insert(i,star)
#item.contents[0] = NavigableString('(' + str(rating) + ')')
item.contents[0] = ''
def postprocess_html(self,soup, first_fetch):
#find broken images and remove captions
items_to_extract = []
for item in soup.findAll('div', attrs={'class' : 'image'}):
img = item.findNext('img')
if img is not None and img['src'] is not None:
# broken images still point to remote url
pattern = re.compile('http://www.independent.co.uk.*')
if pattern.match(img["src"]) is not None:
caption = img.findNextSibling('h3')
if caption is not None:
items_to_extract.append(caption)
items_to_extract.append(img)
for item in items_to_extract:
item.extract()
return soup
def _recurisvely_linearise_tag_tree(
self,
item,
linearised= None,
count=0,
limit = 100
):
linearised = linearised or []
count = count + 1
if count > limit:
return linearised
if not (isinstance(item,Tag)):
return linearised
for nested in item:
linearised.append(nested)
linearised = self._recurisvely_linearise_tag_tree(nested,linearised, count)
return linearised
def _get_previous_tag(self,current_index, tag_tree):
if current_index == 0:
return None
else:
return tag_tree[current_index - 1]
def _get_next_tag(self,current_index, tag_tree):
if current_index < len(tag_tree) - 1:
return tag_tree[current_index + 1]
else:
return None
def _list_match(self,test_str, list_regex):
for regex in list_regex:
match = re.match(regex, test_str)
if match is not None:
return True
return False
def _remove_undesired_line_breaks_from_tag(self,parent,soup):
if parent is None:
return
tag_tree = self._recurisvely_linearise_tag_tree(parent)
items_to_remove = []
for item in tag_tree:
if item == u'\n':
items_to_remove.append(item)
continue;
for item in items_to_remove:
tag_tree.remove(item)
spaced_tags = [r'p', r'h\d', r'blockquote']
tags_to_extract = []
tags_to_replace = []
for (i, tag) in enumerate(tag_tree):
if isinstance(tag, Tag):
if str(tag) == '<br />':
previous_tag = self._get_previous_tag(i, tag_tree)
if isinstance(previous_tag, Tag):
previous_tag_is_spaced = previous_tag is not None\
and self._list_match(str(previous_tag.name),
spaced_tags)
else:
previous_tag_is_spaced = False
next_tag = self._get_next_tag(i, tag_tree)
if isinstance(next_tag, Tag):
next_tag_is_spaced = next_tag is not None\
and self._list_match(str(next_tag.name), spaced_tags)
else:
next_tag_is_spaced = False
if previous_tag_is_spaced or next_tag_is_spaced or i == 0\
or i == len(tag_tree) - 1:
tags_to_extract.append(tag)
else:
tags_to_replace.append((tag,NavigableString(' '),))
for pair in tags_to_replace:
pair[0].replaceWith(pair[1])
for tag in tags_to_extract:
tag.extract()
feeds = [ feeds = [
(u'News - UK', (u'News - UK',
@ -25,7 +391,7 @@ class AdvancedUserRecipe1320474488(BasicNewsRecipe):
(u'News - Education', (u'News - Education',
u'http://www.independent.co.uk/news/education/?service=rss'), u'http://www.independent.co.uk/news/education/?service=rss'),
(u'News - Obituaries', (u'News - Obituaries',
u'http://rss.feedsportal.com/c/266/f/3531/index.rss'), u'http://www.independent.co.uk/news/obituaries/?service=rss'),
(u'News - Corrections', (u'News - Corrections',
u'http://www.independent.co.uk/news/corrections/?service=rss' u'http://www.independent.co.uk/news/corrections/?service=rss'
), ),
@ -46,11 +412,11 @@ class AdvancedUserRecipe1320474488(BasicNewsRecipe):
u'http://www.independent.co.uk/sport/motor-racing/?service=rss' u'http://www.independent.co.uk/sport/motor-racing/?service=rss'
), ),
(u'Sport - Olympics', (u'Sport - Olympics',
u'http://rss.feedsportal.com/c/266/f/3800/index.rss'), u'http://www.independent.co.uk/sport/olympics/?service=rss'),
(u'Sport - Racing', (u'Sport - Racing',
u'http://www.independent.co.uk/sport/racing/?service=rss'), u'http://www.independent.co.uk/sport/racing/?service=rss'),
(u'Sport - Rugby League', (u'Sport - Rugby League',
u'http://rss.feedsportal.com/c/266/f/3795/index.rss'), u'http://www.independent.co.uk/sport/general/rugby-league/?service=rss'),
(u'Sport - Rugby Union', (u'Sport - Rugby Union',
u'http://www.independent.co.uk/sport/rugby/rugby-union/?service=rss' u'http://www.independent.co.uk/sport/rugby/rugby-union/?service=rss'
), ),
@ -114,6 +480,5 @@ class AdvancedUserRecipe1320474488(BasicNewsRecipe):
(u'Money', u'http://www.independent.co.uk/money/?service=rss'), (u'Money', u'http://www.independent.co.uk/money/?service=rss'),
(u'IndyBest', (u'IndyBest',
u'http://www.independent.co.uk/extras/indybest/?service=rss'), u'http://www.independent.co.uk/extras/indybest/?service=rss'),
(u'Blogs', u'http://blogs.independent.co.uk/feed/rss/'),
] ]

View File

@ -11,7 +11,7 @@ __description__ = 'Italian weekly magazine'
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class Espresso(BasicNewsRecipe): class Espresso(BasicNewsRecipe):
__author__ = 'Lorenzo Vigentini, Gabriele Marini' __author__ = 'Lorenzo Vigentini, Gabriele Marini, Krittika Goyal'
description = 'Italian weekly magazine' description = 'Italian weekly magazine'
cover_url = 'http://espresso.repubblica.it/images/logo_espresso.gif' cover_url = 'http://espresso.repubblica.it/images/logo_espresso.gif'
@ -26,10 +26,9 @@ class Espresso(BasicNewsRecipe):
oldest_article = 16 oldest_article = 16
max_articles_per_feed = 100 max_articles_per_feed = 100
use_embedded_content = False use_embedded_content = False
recursion = 10
remove_javascript = True
no_stylesheets = True no_stylesheets = True
auto_cleanup = True
feeds = [ feeds = [
@ -42,36 +41,3 @@ class Espresso(BasicNewsRecipe):
(u'Chiesa: HomePage', u'http://data.kataweb.it/rss/chiesa/homepage/it'), (u'Chiesa: HomePage', u'http://data.kataweb.it/rss/chiesa/homepage/it'),
(u'Chiesa: Speciali e Focus', u'http://data.kataweb.it/rss/chiesa/speciali_e_focus/it') (u'Chiesa: Speciali e Focus', u'http://data.kataweb.it/rss/chiesa/speciali_e_focus/it')
] ]
def print_version(self,url):
print url[7:25]
if url[7:25] == 'temi.repubblica.it':
return url + '/?printpage=undefined'
elif url[7:25] == 'www.chiesa.espress':
return url
return url + '/&print=true'
keep_only_tags = [
dict(name='div', attrs={'class':['testo','copertina','occhiello','firma','didascalia','content-second-right','detail-articles','titolo-local','generic-articles']}),
dict(name='div', attrs={'class':['generic-articles','summary','detail-articles']}),
dict(name='div', attrs={'id':['content-second-right','content2']})
]
remove_tags = [
dict(name='div',attrs={'class':['servizi','aggiungi','label-web','bottom-mobile','box-abbonamenti','box-cerca','big','little','stampaweb']}),
dict(name='div',attrs={'id':['topheader','header','navigation-new','navigation','content-second-left','menutext']}),
dict(name='ul',attrs={'id':'user-utility'}),
dict(name=['script','noscript','iframe'])
]
# extra_css = '''
# h1 {font-family:Times New Roman,"Trebuchet MS",Arial,Helvetica,sans-serif; font-size:24px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:18px;}
# h2 {font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif; font-size:18px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:16px; }
# h3 {color:#333333;font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif; font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px;}
# h4 {color:#333333; font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif;font-size:16px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; }
# h5 {color:#333333; font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif; font-size:12px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:bold; line-height:14px; text-transform:uppercase;}
# .firma {color:#333333;font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif;font-size:12px; font-size-adjust:none; font-stretch:normal; font-style:italic; font-variant:normal; font-weight:bold; line-height:15px; text-decoration:none;}
# .testo {font-family:Times New Roman, "Trebuchet MS",Arial,Helvetica,sans-serif; font-size:10px;}
# '''

View File

@ -1,3 +1,4 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
import re import re
from calibre.utils.magick import Image from calibre.utils.magick import Image
@ -8,21 +9,27 @@ from calibre.utils.magick import Image
version 1.4 Updated tags, delay and added autoclean 22-09-2011 version 1.4 Updated tags, delay and added autoclean 22-09-2011
version 1.5 Changes due to changes in site version 1.5 Changes due to changes in site
version 1.6 Added css, removed auto cleanup, added buitenland section, added use_embedded_content, added remove_attributes version 1.6 Added css, removed auto cleanup, added buitenland section, added use_embedded_content, added remove_attributes
Added som processing on pictures Added some processing on pictures
Removed links in html Removed links in html
Removed extre white characters Removed extre white characters
changed handling of self closing span changed handling of self closing span
''' Version 1.7 11-11-2011 Changed oldest_article back to 1.5
changed è into &egrave;
updated remove tags
removed keep_only tags
'''
class AdvancedUserRecipe1306097511(BasicNewsRecipe): class AdvancedUserRecipe1306097511(BasicNewsRecipe):
title = u'Metro Nieuws NL' title = u'Metro Nieuws NL'
oldest_article = 2 oldest_article = 1.5
max_articles_per_feed = 100 max_articles_per_feed = 100
__author__ = u'DrMerry' __author__ = u'DrMerry'
description = u'Metro Nederland' description = u'Metro Nederland'
language = u'nl' language = u'nl'
simultaneous_downloads = 5 simultaneous_downloads = 5
timeout = 2
#delay = 1 #delay = 1
center_navbar = True
#auto_cleanup = True #auto_cleanup = True
#auto_cleanup_keep = '//div[@class="article-image-caption-2column"]/*|//div[@id="date"]/*|//div[@class="article-image-caption-3column"]/*' #auto_cleanup_keep = '//div[@class="article-image-caption-2column"]/*|//div[@id="date"]/*|//div[@class="article-image-caption-3column"]/*'
timefmt = ' [%A, %d %b %Y]' timefmt = ' [%A, %d %b %Y]'
@ -31,31 +38,32 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
remove_empty_feeds = True remove_empty_feeds = True
cover_url = 'http://www.oldreadmetro.com/img/en/metroholland/last/1/small.jpg' cover_url = 'http://www.oldreadmetro.com/img/en/metroholland/last/1/small.jpg'
publication_type = 'newspaper' publication_type = 'newspaper'
remove_tags_before = dict(name='div', attrs={'id':'date'}) remove_tags_before = dict(id='date')
remove_tags_after = dict(name='div', attrs={'class':'article-body'}) remove_tags_after = dict(name='div', attrs={'class':'article-body'})
encoding = 'utf-8' encoding = 'utf-8'
remove_attributes = ['style', 'font', 'width', 'height'] remove_attributes = ['style', 'font', 'width', 'height']
use_embedded_content = False use_embedded_content = False
conversion_options = {
'authors' : 'Metro Nederland',
'author_sort' : 'Metro Nederland',
'publisher' : 'DrMerry/Metro Nederland'
}
extra_css = 'body {padding:5px 0px; background:#fff;font-size: 13px;}\ extra_css = 'body {padding:5px 0px; background:#fff;font-size: 13px;}\
#date {clear: both;margin-left: 19px;font-size: 11px;font-weight: 300;color: #616262;height: 15px;}\ #date {clear: both;margin-left: 19px;font-size: 11px;font-weight: 300;color: #616262;height: 15px;}\
.article-box-fact.module-title {clear:both;border-top:1px solid black;border-bottom:4px solid black;padding: 8px 0;color: #24763b;font-family: arial, sans-serif;font-size: 14px;font-weight: bold;}\ .article-box-fact.module-title {clear:both;padding: 8px 0;color: #24763b;font-family: arial, sans-serif;font-size: 14px;font-weight: bold;}\
h1.title {color: #000000;font-size: 44px;padding-bottom: 10px;line-height: 1.15;font-weight: 300;} h2.subtitle {font-size: 13px;font-weight: 700;padding-bottom: 10px;}\ h1.title {color: #000000;font-size: 44px;padding-bottom: 10px;font-weight: 300;} h2.subtitle {font-size: 13px;font-weight: 700;padding-bottom: 10px;}\
.article-body p{padding-bottom:10px;}div.column-1-3{float: left;display: inline;width: 567px;margin-left: 19px;border-right: 1px solid #CACACA;padding-right: 9px;}\ .article-body p{padding-bottom:10px;}div.column-1-3{margin-left: 19px;padding-right: 9px;}\
div.column-1-2 {float: left;display: inline;width: 373px;padding-right: 7px;border-right: 1px solid #CACACA;}\ div.column-1-2 {display: inline;padding-right: 7px;}\
p.article-image-caption {font-size: 12px;font-weight: 300;line-height: 1.4;color: #616262;margin-top: 5px;} \ p.article-image-caption {font-size: 12px;font-weight: 300;color: #616262;margin-top: 5px;} \
p.article-image-caption .credits {font-style: italic;font-size: 10px;}\ p.article-image-caption .credits {font-style: italic;font-size: 10px;}\
div.article-image-caption {width: 246px;margin-bottom: 5px;margin-left: 10px;}\ div.article-image-caption {width: 246px;margin-bottom: 5px;margin-left: 10px;}\
div.article-image-caption-2column {margin-bottom: 10px;width: 373px;} div.article-image-caption-3column {}\ div.article-image-caption-2column {margin-bottom: 10px;width: 373px;} div.article-image-caption-3column {}\
img {border:0px;} .img-mask {position:absolute;top:0px;left:0px;}' img {border:0px;} .img-mask {position:absolute;top:0px;left:0px;}'
keep_only_tags = [dict(name='div', attrs={'class':[ 'article-image-caption-2column', 'article-image-caption-3column', 'article-body', 'article-box-fact']}), remove_tags = [dict(name='div', attrs={'class':[ 'metroCommentFormWrap', 'related-links'
dict(name='div', attrs={'id':['date']}),
dict(name='h1', attrs={'class':['title']}),
dict(name='h2', attrs={'class':['subtitle']})]
remove_tags = [dict(name='div', attrs={'class':[ 'metroCommentFormWrap',
'commentForm', 'metroCommentInnerWrap', 'article-slideshow-counter-container', 'article-slideshow-control', 'ad', 'header-links', 'commentForm', 'metroCommentInnerWrap', 'article-slideshow-counter-container', 'article-slideshow-control', 'ad', 'header-links',
'art-rgt','pluck-app pluck-comm', 'share-and-byline', 'article-tools-below-title', 'col-179 ', 'related-links', 'clear padding-top-15', 'share-tools', 'article-page-auto-pushes', 'footer-edit']}), 'art-rgt','pluck-app pluck-comm', 'share-and-byline', 'article-tools-below-title', 'col-179 ', 'related-links', 'clear padding-top-15', 'share-tools',
'article1','article-page-auto-pushes', 'footer-edit','clear']}),
dict(name='div', attrs={'id':['article-2', 'article-4', 'article-1', 'navigation', 'footer', 'header', 'comments', 'sidebar', 'share-and-byline']}), dict(name='div', attrs={'id':['article-2', 'article-4', 'article-1', 'navigation', 'footer', 'header', 'comments', 'sidebar', 'share-and-byline']}),
dict(name='iframe')] dict(name='iframe')]
@ -70,26 +78,8 @@ class AdvancedUserRecipe1306097511(BasicNewsRecipe):
iurl = tag['src'] iurl = tag['src']
img = Image() img = Image()
img.open(iurl) img.open(iurl)
#width, height = img.size
#print '***img is: ', iurl, '\n****width is: ', width, 'height is: ', height
img.trim(0) img.trim(0)
img.save(iurl) img.save(iurl)
'''
#width, height = img.size
#print '***TRIMMED img width is: ', width, 'height is: ', height
left=0
top=0
border_color='#ffffff'
width, height = img.size
#print '***retrieved img width is: ', width, 'height is: ', height
height_correction = 1.17
canvas = create_canvas(width, height*height_correction,border_color)
canvas.compose(img, left, top)
#img = canvas
canvas.save(iurl)
#width, height = canvas.size
#print '***NEW img width is: ', width, 'height is: ', height
'''
return soup return soup
feeds = [ feeds = [

View File

@ -1,3 +1,4 @@
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
''' '''
@ -8,17 +9,18 @@ from calibre.web.feeds.news import BasicNewsRecipe
from calibre import strftime from calibre import strftime
class SueddeutcheZeitung(BasicNewsRecipe): class SueddeutcheZeitung(BasicNewsRecipe):
title = 'Sueddeutche Zeitung' title = 'Süddeutsche Zeitung'
__author__ = 'Darko Miletic' __author__ = 'Darko Miletic'
description = 'News from Germany. Access to paid content.' description = 'News from Germany. Access to paid content.'
publisher = 'Sueddeutche Zeitung' publisher = 'Süddeutsche Zeitung'
category = 'news, politics, Germany' category = 'news, politics, Germany'
no_stylesheets = True no_stylesheets = True
oldest_article = 2 oldest_article = 2
encoding = 'cp1252' encoding = 'iso-8859-1'
needs_subscription = True needs_subscription = True
remove_empty_feeds = True remove_empty_feeds = True
delay = 1 delay = 1
cover_source = 'http://www.sueddeutsche.de/verlag'
PREFIX = 'http://www.sueddeutsche.de' PREFIX = 'http://www.sueddeutsche.de'
INDEX = PREFIX + '/app/epaper/textversion/' INDEX = PREFIX + '/app/epaper/textversion/'
use_embedded_content = False use_embedded_content = False
@ -58,6 +60,7 @@ class SueddeutcheZeitung(BasicNewsRecipe):
feeds = [ feeds = [
(u'Politik' , INDEX + 'Politik/' ) (u'Politik' , INDEX + 'Politik/' )
,(u'Seite drei' , INDEX + 'Seite+drei/' ) ,(u'Seite drei' , INDEX + 'Seite+drei/' )
,(u'Thema des Tages' , INDEX + 'Thema+des+Tages/' )
,(u'Meinungsseite' , INDEX + 'Meinungsseite/') ,(u'Meinungsseite' , INDEX + 'Meinungsseite/')
,(u'Wissen' , INDEX + 'Wissen/' ) ,(u'Wissen' , INDEX + 'Wissen/' )
,(u'Panorama' , INDEX + 'Panorama/' ) ,(u'Panorama' , INDEX + 'Panorama/' )
@ -82,6 +85,11 @@ class SueddeutcheZeitung(BasicNewsRecipe):
,(u'Beilage' , INDEX + 'Beilage/' ) ,(u'Beilage' , INDEX + 'Beilage/' )
] ]
def get_cover_url(self):
cover_source_soup = self.index_to_soup(self.cover_source)
preview_image_div = cover_source_soup.find(attrs={'class':'preview-image'})
return preview_image_div.div.img['src']
def parse_index(self): def parse_index(self):
src = self.index_to_soup(self.INDEX) src = self.index_to_soup(self.INDEX)
id = '' id = ''
@ -92,7 +100,7 @@ class SueddeutcheZeitung(BasicNewsRecipe):
lfeeds = self.get_feeds() lfeeds = self.get_feeds()
for feedobj in lfeeds: for feedobj in lfeeds:
feedtitle, feedurl = feedobj feedtitle, feedurl = feedobj
self.report_progress(0, _('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl)) self.report_progress(0, ('Fetching feed')+' %s...'%(feedtitle if feedtitle else feedurl))
articles = [] articles = []
soup = self.index_to_soup(feedurl + id) soup = self.index_to_soup(feedurl + id)
tbl = soup.find(attrs={'class':'szprintd'}) tbl = soup.find(attrs={'class':'szprintd'})

50
recipes/techtarget.recipe Normal file
View File

@ -0,0 +1,50 @@
from calibre.web.feeds.news import BasicNewsRecipe
class TechTarget(BasicNewsRecipe):
title = u'Techtarget'
__author__ = 'Julio:map'
description = '''IT Infrastructure related blogs
from Techtarget'''
publisher = 'Techtarget'
category = 'IT, Infrastructure'
oldest_article = 7
language = 'en'
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
needs_subscription = True
auto_cleanup = False
LOGIN = u'http://searchservervirtualization.techtarget.com/login'
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None:
br.open(self.LOGIN)
br.select_form(nr=1)
br['email'] = self.username
if self.password is not None:
br['password'] = self.password
br.submit()
return br
keep_only_tags = [dict(name='div', attrs={'id':'article'}),dict(name='div', attrs={'class':'entry'})]
remove_tags= [dict(name='div', attrs={'id':['articleToolbar','relatedContent']})]
remove_tags_after = [dict(name='div', attrs={'id':'relatedContent'})]
feeds = [
(u'IT news and analysis for CIOs', u'http://feeds.pheedo.com/SearchCIOITNewsAndAnalysisForCIOs'),
(u'TotalCIO', u'http://feeds.pheedo.com/1532.xml'),
(u'SearchCIO-Midmarket: Technology news and tips for midmarket CIOs', u'http://feeds.pheedo.com/techtarget/Searchsmb/Smbs'),
(u'Compliance news and advice for senior IT and business managers', u'http://feeds.pheedo.com/tt/1200'),
(u'Server virtualization news and opinions', u'http://feeds.pheedo.com/SearchservervirtualizationServerVirtualizationNewsAndOpinions'),
(u'The Virtualization Room', u'http://feeds.pheedo.com/techtarget/nzLe'),
(u'Server virtualization technical tips and expert advice', u'http://feeds.pheedo.com/SearchservervirtualizationServerVirtualizationTechnicalTipsAndExpertAdvice'),
(u'Cloud Computing news and Technical Advice', u'http://feeds.pheedo.com/1260'),
(u'IT infrastructure news', u'http://feeds.pheedo.com/techtarget/Searchdatacenter/ItInfrastructure'),
(u'Storage Channel Update', u'http://feeds.pheedo.com/ChannelMarker-TheItChannelWeblog'),
(u'VMware Tips and News', u'http://feeds.pheedo.com/SearchvmwarecomVmwareTipsAndTricks'),
(u'Enterprise IT news roundup', u'http://feeds.pheedo.com/WhatisEnterpriseItNewsRoundup'),
(u'WhatIs: Enterprise IT tips and expert advice', u'http://feeds.pheedo.com/WhatisEnterpriseItTipsAndExpertAdvice'),
(u'WhatIs: Enterprise IT news roundup', u'http://feeds.pheedo.com/WhatisEnterpriseItNewsRoundup'),
]

View File

@ -315,6 +315,12 @@ content_server_wont_display = []
# level sorts, and if you are seeing a slowdown, reduce the value of this tweak. # level sorts, and if you are seeing a slowdown, reduce the value of this tweak.
maximum_resort_levels = 5 maximum_resort_levels = 5
#: Choose whether dates are sorted using visible fields
# Date values contain both a date and a time. When sorted, all the fields are
# used, regardless of what is displayed. Set this tweak to True to use only
# the fields that are being displayed.
sort_dates_using_visible_fields = False
#: Specify which font to use when generating a default cover #: Specify which font to use when generating a default cover
# Absolute path to .ttf font files to use as the fonts for the title, author # Absolute path to .ttf font files to use as the fonts for the title, author
# and footer when generating a default cover. Useful if the default font (Liberation # and footer when generating a default cover. Useful if the default font (Liberation

File diff suppressed because it is too large Load Diff

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: calibre\n" "Project-Id-Version: calibre\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" "Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2011-09-27 14:31+0000\n" "POT-Creation-Date: 2011-09-27 14:31+0000\n"
"PO-Revision-Date: 2011-10-22 22:04+0000\n" "PO-Revision-Date: 2011-10-28 15:37+0000\n"
"Last-Translator: Fitoschido <fitoschido@gmail.com>\n" "Last-Translator: Jellby <Unknown>\n"
"Language-Team: Spanish <es@li.org>\n" "Language-Team: Spanish <es@li.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-10-23 05:13+0000\n" "X-Launchpad-Export-Date: 2011-10-29 05:16+0000\n"
"X-Generator: Launchpad (build 14170)\n" "X-Generator: Launchpad (build 14197)\n"
#. name for aaa #. name for aaa
msgid "Ghotuo" msgid "Ghotuo"
@ -5911,11 +5911,11 @@ msgstr "Gwahatike"
#. name for dai #. name for dai
msgid "Day" msgid "Day"
msgstr "Día" msgstr "Day"
#. name for daj #. name for daj
msgid "Daju; Dar Fur" msgid "Daju; Dar Fur"
msgstr "" msgstr "Daju de Darfur"
#. name for dak #. name for dak
msgid "Dakota" msgid "Dakota"
@ -5955,7 +5955,7 @@ msgstr ""
#. name for dau #. name for dau
msgid "Daju; Dar Sila" msgid "Daju; Dar Sila"
msgstr "" msgstr "Daju de Dar Sila"
#. name for dav #. name for dav
msgid "Taita" msgid "Taita"
@ -6379,7 +6379,7 @@ msgstr ""
#. name for djc #. name for djc
msgid "Daju; Dar Daju" msgid "Daju; Dar Daju"
msgstr "" msgstr "Daju de Dar Daju"
#. name for djd #. name for djd
msgid "Djamindjung" msgid "Djamindjung"

View File

@ -9,101 +9,101 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-" "Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n" "devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-09-27 14:31+0000\n" "POT-Creation-Date: 2011-09-27 14:31+0000\n"
"PO-Revision-Date: 2011-10-15 17:29+0000\n" "PO-Revision-Date: 2011-11-10 07:13+0000\n"
"Last-Translator: Devilinside <Unknown>\n" "Last-Translator: Devilinside <Unknown>\n"
"Language-Team: Hungarian <debian-l10n-hungarian@lists.d.o>\n" "Language-Team: Hungarian <debian-l10n-hungarian@lists.d.o>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-10-16 05:14+0000\n" "X-Launchpad-Export-Date: 2011-11-11 04:52+0000\n"
"X-Generator: Launchpad (build 14124)\n" "X-Generator: Launchpad (build 14277)\n"
"X-Poedit-Country: HUNGARY\n" "X-Poedit-Country: HUNGARY\n"
"Language: hu\n" "Language: hu\n"
"X-Poedit-Language: Hungarian\n" "X-Poedit-Language: Hungarian\n"
#. name for aaa #. name for aaa
msgid "Ghotuo" msgid "Ghotuo"
msgstr "Ghotuo" msgstr "ghotuo"
#. name for aab #. name for aab
msgid "Alumu-Tesu" msgid "Alumu-Tesu"
msgstr "Alumu-Tesu" msgstr "alumu-tesu"
#. name for aac #. name for aac
msgid "Ari" msgid "Ari"
msgstr "Ari" msgstr "ari"
#. name for aad #. name for aad
msgid "Amal" msgid "Amal"
msgstr "Amal" msgstr "amal"
#. name for aae #. name for aae
msgid "Albanian; Arbëreshë" msgid "Albanian; Arbëreshë"
msgstr "Albán; Arbëreshë" msgstr "albán; Arbëreshë"
#. name for aaf #. name for aaf
msgid "Aranadan" msgid "Aranadan"
msgstr "Aranadan" msgstr "aranadan"
#. name for aag #. name for aag
msgid "Ambrak" msgid "Ambrak"
msgstr "Ambrak" msgstr "ambrak"
#. name for aah #. name for aah
msgid "Arapesh; Abu'" msgid "Arapesh; Abu'"
msgstr "Arapesh; Abu'" msgstr "arapesh; Abu'"
#. name for aai #. name for aai
msgid "Arifama-Miniafia" msgid "Arifama-Miniafia"
msgstr "" msgstr "arifama-miniafia"
#. name for aak #. name for aak
msgid "Ankave" msgid "Ankave"
msgstr "" msgstr "ankave"
#. name for aal #. name for aal
msgid "Afade" msgid "Afade"
msgstr "" msgstr "afade"
#. name for aam #. name for aam
msgid "Aramanik" msgid "Aramanik"
msgstr "" msgstr "aramanik"
#. name for aan #. name for aan
msgid "Anambé" msgid "Anambé"
msgstr "" msgstr "anambé"
#. name for aao #. name for aao
msgid "Arabic; Algerian Saharan" msgid "Arabic; Algerian Saharan"
msgstr "Arab; Algériai Szaharai" msgstr "arab; algériai-szaharai"
#. name for aap #. name for aap
msgid "Arára; Pará" msgid "Arára; Pará"
msgstr "" msgstr "arára; Pará"
#. name for aaq #. name for aaq
msgid "Abnaki; Eastern" msgid "Abnaki; Eastern"
msgstr "" msgstr "abnaki; keleti"
#. name for aar #. name for aar
msgid "Afar" msgid "Afar"
msgstr "Afar" msgstr "afar"
#. name for aas #. name for aas
msgid "Aasáx" msgid "Aasáx"
msgstr "" msgstr "aasáx"
#. name for aat #. name for aat
msgid "Albanian; Arvanitika" msgid "Albanian; Arvanitika"
msgstr "" msgstr "albán; Arvanitika"
#. name for aau #. name for aau
msgid "Abau" msgid "Abau"
msgstr "" msgstr "abau"
#. name for aaw #. name for aaw
msgid "Solong" msgid "Solong"
msgstr "" msgstr "szolong"
#. name for aax #. name for aax
msgid "Mandobo Atas" msgid "Mandobo Atas"
@ -115,7 +115,7 @@ msgstr ""
#. name for aba #. name for aba
msgid "Abé" msgid "Abé"
msgstr "" msgstr "abé"
#. name for abb #. name for abb
msgid "Bankon" msgid "Bankon"
@ -131,7 +131,7 @@ msgstr ""
#. name for abe #. name for abe
msgid "Abnaki; Western" msgid "Abnaki; Western"
msgstr "" msgstr "abnaki; nyugati"
#. name for abf #. name for abf
msgid "Abai Sungai" msgid "Abai Sungai"
@ -143,7 +143,7 @@ msgstr ""
#. name for abh #. name for abh
msgid "Arabic; Tajiki" msgid "Arabic; Tajiki"
msgstr "" msgstr "arab; tadzsik"
#. name for abi #. name for abi
msgid "Abidji" msgid "Abidji"
@ -151,7 +151,7 @@ msgstr ""
#. name for abj #. name for abj
msgid "Aka-Bea" msgid "Aka-Bea"
msgstr "" msgstr "aka-bea"
#. name for abk #. name for abk
msgid "Abkhazian" msgid "Abkhazian"
@ -187,7 +187,7 @@ msgstr ""
#. name for abs #. name for abs
msgid "Malay; Ambonese" msgid "Malay; Ambonese"
msgstr "" msgstr "maláj; amboni"
#. name for abt #. name for abt
msgid "Ambulas" msgid "Ambulas"
@ -199,7 +199,7 @@ msgstr ""
#. name for abv #. name for abv
msgid "Arabic; Baharna" msgid "Arabic; Baharna"
msgstr "" msgstr "arab; Baharna"
#. name for abw #. name for abw
msgid "Pal" msgid "Pal"
@ -236,7 +236,7 @@ msgstr "akinéz"
#. name for acf #. name for acf
msgid "Creole French; Saint Lucian" msgid "Creole French; Saint Lucian"
msgstr "" msgstr "francia (kreol); Sainte-Lucie"
#. name for ach #. name for ach
msgid "Acoli" msgid "Acoli"
@ -256,7 +256,7 @@ msgstr ""
#. name for acm #. name for acm
msgid "Arabic; Mesopotamian" msgid "Arabic; Mesopotamian"
msgstr "" msgstr "arab; mezopotámiai"
#. name for acn #. name for acn
msgid "Achang" msgid "Achang"
@ -268,7 +268,7 @@ msgstr ""
#. name for acq #. name for acq
msgid "Arabic; Ta'izzi-Adeni" msgid "Arabic; Ta'izzi-Adeni"
msgstr "" msgstr "arabic; ta'izzi-adeni"
#. name for acr #. name for acr
msgid "Achi" msgid "Achi"
@ -296,11 +296,11 @@ msgstr ""
#. name for acx #. name for acx
msgid "Arabic; Omani" msgid "Arabic; Omani"
msgstr "" msgstr "arab; ománi"
#. name for acy #. name for acy
msgid "Arabic; Cypriot" msgid "Arabic; Cypriot"
msgstr "" msgstr "arab; ciprusi"
#. name for acz #. name for acz
msgid "Acheron" msgid "Acheron"
@ -384,11 +384,11 @@ msgstr ""
#. name for adx #. name for adx
msgid "Tibetan; Amdo" msgid "Tibetan; Amdo"
msgstr "" msgstr "tibeti; Amdo"
#. name for ady #. name for ady
msgid "Adyghe" msgid "Adyghe"
msgstr "" msgstr "adyghe"
#. name for adz #. name for adz
msgid "Adzera" msgid "Adzera"
@ -408,7 +408,7 @@ msgstr ""
#. name for aed #. name for aed
msgid "Argentine Sign Language" msgid "Argentine Sign Language"
msgstr "" msgstr "argentín jelnyelv"
#. name for aee #. name for aee
msgid "Pashayi; Northeast" msgid "Pashayi; Northeast"
@ -500,7 +500,7 @@ msgstr ""
#. name for afr #. name for afr
msgid "Afrikaans" msgid "Afrikaans"
msgstr "Afrikaans" msgstr "afrikaansz"
#. name for afs #. name for afs
msgid "Creole; Afro-Seminole" msgid "Creole; Afro-Seminole"
@ -680,7 +680,7 @@ msgstr ""
#. name for aib #. name for aib
msgid "Ainu (China)" msgid "Ainu (China)"
msgstr "" msgstr "ainu (Kína)"
#. name for aic #. name for aic
msgid "Ainbai" msgid "Ainbai"
@ -700,7 +700,7 @@ msgstr ""
#. name for aig #. name for aig
msgid "Creole English; Antigua and Barbuda" msgid "Creole English; Antigua and Barbuda"
msgstr "" msgstr "angol (kreol); Antigua és Barbuda"
#. name for aih #. name for aih
msgid "Ai-Cham" msgid "Ai-Cham"
@ -708,7 +708,7 @@ msgstr ""
#. name for aii #. name for aii
msgid "Neo-Aramaic; Assyrian" msgid "Neo-Aramaic; Assyrian"
msgstr "" msgstr "arámi; új-arámi; asszír"
#. name for aij #. name for aij
msgid "Lishanid Noshan" msgid "Lishanid Noshan"
@ -728,7 +728,7 @@ msgstr ""
#. name for ain #. name for ain
msgid "Ainu (Japan)" msgid "Ainu (Japan)"
msgstr "" msgstr "ainu (Japán)"
#. name for aio #. name for aio
msgid "Aiton" msgid "Aiton"
@ -800,7 +800,7 @@ msgstr ""
#. name for aka #. name for aka
msgid "Akan" msgid "Akan"
msgstr "Akan" msgstr "akan"
#. name for akb #. name for akb
msgid "Batak Angkola" msgid "Batak Angkola"
@ -968,7 +968,7 @@ msgstr ""
#. name for alt #. name for alt
msgid "Altai; Southern" msgid "Altai; Southern"
msgstr "" msgstr "altáji; déli"
#. name for alu #. name for alu
msgid "'Are'are" msgid "'Are'are"
@ -1014,9 +1014,10 @@ msgstr ""
msgid "Amarag" msgid "Amarag"
msgstr "" msgstr ""
# src/trans.h:283
#. name for amh #. name for amh
msgid "Amharic" msgid "Amharic"
msgstr "Amhara" msgstr "amhara"
#. name for ami #. name for ami
msgid "Amis" msgid "Amis"
@ -1076,7 +1077,7 @@ msgstr ""
#. name for amw #. name for amw
msgid "Neo-Aramaic; Western" msgid "Neo-Aramaic; Western"
msgstr "" msgstr "arámi; új-arámi; nyugati"
#. name for amx #. name for amx
msgid "Anmatyerre" msgid "Anmatyerre"
@ -1116,7 +1117,7 @@ msgstr ""
#. name for ang #. name for ang
msgid "English; Old (ca. 450-1100)" msgid "English; Old (ca. 450-1100)"
msgstr "" msgstr "angol; óangol (kb. 450-1100)"
#. name for anh #. name for anh
msgid "Nend" msgid "Nend"
@ -1413,7 +1414,7 @@ msgstr ""
#. name for arc #. name for arc
msgid "Aramaic; Official (700-300 BCE)" msgid "Aramaic; Official (700-300 BCE)"
msgstr "" msgstr "arámi; hivatalos (i.e. 700- i.e. 300)"
#. name for ard #. name for ard
msgid "Arabana" msgid "Arabana"
@ -1423,9 +1424,10 @@ msgstr ""
msgid "Arrarnta; Western" msgid "Arrarnta; Western"
msgstr "" msgstr ""
# src/trans.h:294
#. name for arg #. name for arg
msgid "Aragonese" msgid "Aragonese"
msgstr "Aragóniai" msgstr "aragóniai"
#. name for arh #. name for arh
msgid "Arhuaco" msgid "Arhuaco"
@ -1545,7 +1547,7 @@ msgstr ""
#. name for asm #. name for asm
msgid "Assamese" msgid "Assamese"
msgstr "Asszámi" msgstr "asszámi"
#. name for asn #. name for asn
msgid "Asuriní; Xingú" msgid "Asuriní; Xingú"
@ -1677,7 +1679,7 @@ msgstr ""
#. name for atv #. name for atv
msgid "Altai; Northern" msgid "Altai; Northern"
msgstr "" msgstr "altáji; északi"
#. name for atw #. name for atw
msgid "Atsugewi" msgid "Atsugewi"
@ -1787,9 +1789,10 @@ msgstr ""
msgid "Arabic; Uzbeki" msgid "Arabic; Uzbeki"
msgstr "" msgstr ""
# src/trans.h:283
#. name for ava #. name for ava
msgid "Avaric" msgid "Avaric"
msgstr "Avar" msgstr "avar"
#. name for avb #. name for avb
msgid "Avau" msgid "Avau"
@ -1801,7 +1804,7 @@ msgstr ""
#. name for ave #. name for ave
msgid "Avestan" msgid "Avestan"
msgstr "Avesztai" msgstr "avesztai"
#. name for avi #. name for avi
msgid "Avikam" msgid "Avikam"
@ -1941,7 +1944,7 @@ msgstr ""
#. name for ayc #. name for ayc
msgid "Aymara; Southern" msgid "Aymara; Southern"
msgstr "Ajmara; Déli" msgstr "ajmara; Déli"
#. name for ayd #. name for ayd
msgid "Ayabadhu" msgid "Ayabadhu"
@ -1973,7 +1976,7 @@ msgstr ""
#. name for aym #. name for aym
msgid "Aymara" msgid "Aymara"
msgstr "Ajmara" msgstr "ajmara"
#. name for ayn #. name for ayn
msgid "Arabic; Sanaani" msgid "Arabic; Sanaani"
@ -1993,7 +1996,7 @@ msgstr ""
#. name for ayr #. name for ayr
msgid "Aymara; Central" msgid "Aymara; Central"
msgstr "Ajmara; Közép" msgstr "ajmara; Közép"
#. name for ays #. name for ays
msgid "Ayta; Sorsogon" msgid "Ayta; Sorsogon"
@ -2021,11 +2024,12 @@ msgstr ""
#. name for azb #. name for azb
msgid "Azerbaijani; South" msgid "Azerbaijani; South"
msgstr "Azeri; Déli" msgstr "azeri; Déli"
# src/trans.h:311
#. name for aze #. name for aze
msgid "Azerbaijani" msgid "Azerbaijani"
msgstr "Azeri" msgstr "azeri"
#. name for azg #. name for azg
msgid "Amuzgo; San Pedro Amuzgos" msgid "Amuzgo; San Pedro Amuzgos"
@ -2033,7 +2037,7 @@ msgstr ""
#. name for azj #. name for azj
msgid "Azerbaijani; North" msgid "Azerbaijani; North"
msgstr "Azeri; Északi" msgstr "azeri; Északi"
#. name for azm #. name for azm
msgid "Amuzgo; Ipalapa" msgid "Amuzgo; Ipalapa"
@ -2077,7 +2081,7 @@ msgstr ""
#. name for bah #. name for bah
msgid "Creole English; Bahamas" msgid "Creole English; Bahamas"
msgstr "" msgstr "angol (kreol); Bahamák"
#. name for baj #. name for baj
msgid "Barakai" msgid "Barakai"
@ -2085,7 +2089,7 @@ msgstr ""
#. name for bak #. name for bak
msgid "Bashkir" msgid "Bashkir"
msgstr "Baskír" msgstr "baskír"
#. name for bal #. name for bal
msgid "Baluchi" msgid "Baluchi"
@ -2110,7 +2114,7 @@ msgstr ""
#. name for bar #. name for bar
msgid "Bavarian" msgid "Bavarian"
msgstr "Bajor" msgstr "bajor"
#. name for bas #. name for bas
msgid "Basa (Cameroon)" msgid "Basa (Cameroon)"
@ -2494,7 +2498,7 @@ msgstr ""
#. name for bel #. name for bel
msgid "Belarusian" msgid "Belarusian"
msgstr "Belarusz" msgstr "belarusz"
#. name for bem #. name for bem
msgid "Bemba (Zambia)" msgid "Bemba (Zambia)"
@ -2502,7 +2506,7 @@ msgstr ""
#. name for ben #. name for ben
msgid "Bengali" msgid "Bengali"
msgstr "Bengáli" msgstr "bengáli"
#. name for beo #. name for beo
msgid "Beami" msgid "Beami"
@ -2798,7 +2802,7 @@ msgstr ""
#. name for bhn #. name for bhn
msgid "Neo-Aramaic; Bohtan" msgid "Neo-Aramaic; Bohtan"
msgstr "" msgstr "arámi; új-arámi; bohtan"
#. name for bho #. name for bho
msgid "Bhojpuri" msgid "Bhojpuri"
@ -2966,7 +2970,7 @@ msgstr ""
#. name for bjf #. name for bjf
msgid "Neo-Aramaic; Barzani Jewish" msgid "Neo-Aramaic; Barzani Jewish"
msgstr "" msgstr "arámi; új-arámi; barzani zsidó"
#. name for bjg #. name for bjg
msgid "Bidyogo" msgid "Bidyogo"
@ -3366,7 +3370,7 @@ msgstr ""
#. name for bng #. name for bng
msgid "Benga" msgid "Benga"
msgstr "" msgstr "benga"
#. name for bni #. name for bni
msgid "Bangi" msgid "Bangi"
@ -3504,9 +3508,10 @@ msgstr ""
msgid "Borôro" msgid "Borôro"
msgstr "" msgstr ""
# src/trans.h:309
#. name for bos #. name for bos
msgid "Bosnian" msgid "Bosnian"
msgstr "Bosnyák" msgstr "bosnyák"
#. name for bot #. name for bot
msgid "Bongo" msgid "Bongo"
@ -3678,7 +3683,7 @@ msgstr ""
#. name for bqn #. name for bqn
msgid "Bulgarian Sign Language" msgid "Bulgarian Sign Language"
msgstr "Bolgár jelnyelv" msgstr "bolgár jelnyelv"
#. name for bqo #. name for bqo
msgid "Balo" msgid "Balo"
@ -4071,9 +4076,10 @@ msgstr ""
msgid "Bugawac" msgid "Bugawac"
msgstr "" msgstr ""
# src/trans.h:285
#. name for bul #. name for bul
msgid "Bulgarian" msgid "Bulgarian"
msgstr "Bolgár" msgstr "bolgár"
#. name for bum #. name for bum
msgid "Bulu (Cameroon)" msgid "Bulu (Cameroon)"
@ -4557,11 +4563,11 @@ msgstr ""
#. name for bzj #. name for bzj
msgid "Kriol English; Belize" msgid "Kriol English; Belize"
msgstr "" msgstr "angol (kreol); Belize"
#. name for bzk #. name for bzk
msgid "Creole English; Nicaragua" msgid "Creole English; Nicaragua"
msgstr "" msgstr "angol (kreol); Nicaragua"
#. name for bzl #. name for bzl
msgid "Boano (Sulawesi)" msgid "Boano (Sulawesi)"
@ -5036,7 +5042,7 @@ msgstr ""
#. name for chu #. name for chu
msgid "Slavonic; Old" msgid "Slavonic; Old"
msgstr "" msgstr "szláv; ószláv"
#. name for chv #. name for chv
msgid "Chuvash" msgid "Chuvash"
@ -5224,7 +5230,7 @@ msgstr ""
#. name for cld #. name for cld
msgid "Neo-Aramaic; Chaldean" msgid "Neo-Aramaic; Chaldean"
msgstr "" msgstr "arámi; új-arámi; új-babilóniai"
#. name for cle #. name for cle
msgid "Chinantec; Lealao" msgid "Chinantec; Lealao"
@ -5622,7 +5628,7 @@ msgstr "kasubi"
#. name for csc #. name for csc
msgid "Catalan Sign Language" msgid "Catalan Sign Language"
msgstr "" msgstr "katalán jelnyelv"
#. name for csd #. name for csd
msgid "Chiangmai Sign Language" msgid "Chiangmai Sign Language"
@ -5630,7 +5636,7 @@ msgstr ""
#. name for cse #. name for cse
msgid "Czech Sign Language" msgid "Czech Sign Language"
msgstr "" msgstr "cseh jelnyelv"
#. name for csf #. name for csf
msgid "Cuba Sign Language" msgid "Cuba Sign Language"
@ -7261,7 +7267,7 @@ msgstr ""
#. name for enm #. name for enm
msgid "English; Middle (1100-1500)" msgid "English; Middle (1100-1500)"
msgstr "közép-angol (1100-1500)" msgstr "angol; középkori (1100-1500)"
#. name for enn #. name for enn
msgid "Engenni" msgid "Engenni"
@ -7697,7 +7703,7 @@ msgstr ""
#. name for fpe #. name for fpe
msgid "Creole English; Fernando Po" msgid "Creole English; Fernando Po"
msgstr "" msgstr "angol (kreol); Fernando Po"
#. name for fqs #. name for fqs
msgid "Fas" msgid "Fas"
@ -7726,7 +7732,7 @@ msgstr ""
#. name for fro #. name for fro
msgid "French; Old (842-ca. 1400)" msgid "French; Old (842-ca. 1400)"
msgstr "" msgstr "francia; ófrancia (842- kb. 1400)"
#. name for frp #. name for frp
msgid "Arpitan" msgid "Arpitan"
@ -8059,7 +8065,7 @@ msgstr ""
#. name for gcl #. name for gcl
msgid "Creole English; Grenadian" msgid "Creole English; Grenadian"
msgstr "" msgstr "angol (kreol); Grenada"
#. name for gcn #. name for gcn
msgid "Gaina" msgid "Gaina"
@ -8442,7 +8448,7 @@ msgstr "ír"
#. name for glg #. name for glg
msgid "Galician" msgid "Galician"
msgstr "" msgstr "galíciai"
#. name for glh #. name for glh
msgid "Pashayi; Northwest" msgid "Pashayi; Northwest"
@ -8634,7 +8640,7 @@ msgstr ""
#. name for goh #. name for goh
msgid "German; Old High (ca. 750-1050)" msgid "German; Old High (ca. 750-1050)"
msgstr "" msgstr "német; ónémet (kb. 750-1050)"
#. name for goi #. name for goi
msgid "Gobasi" msgid "Gobasi"
@ -9716,7 +9722,7 @@ msgstr ""
#. name for hsh #. name for hsh
msgid "Hungarian Sign Language" msgid "Hungarian Sign Language"
msgstr "" msgstr "magyar jelnyelv"
#. name for hsl #. name for hsl
msgid "Hausa Sign Language" msgid "Hausa Sign Language"
@ -10931,7 +10937,7 @@ msgstr ""
#. name for jpa #. name for jpa
msgid "Aramaic; Jewish Palestinian" msgid "Aramaic; Jewish Palestinian"
msgstr "" msgstr "arámi; zsidó palesztin"
# src/trans.h:222 # src/trans.h:222
#. name for jpn #. name for jpn
@ -11799,7 +11805,7 @@ msgstr ""
#. name for khg #. name for khg
msgid "Tibetan; Khams" msgid "Tibetan; Khams"
msgstr "" msgstr "tibeti; Khams"
#. name for khh #. name for khh
msgid "Kehu" msgid "Kehu"
@ -12076,7 +12082,7 @@ msgstr ""
#. name for kka #. name for kka
msgid "Kakanda" msgid "Kakanda"
msgstr "" msgstr "kakanda"
#. name for kkb #. name for kkb
msgid "Kwerisa" msgid "Kwerisa"
@ -18256,7 +18262,7 @@ msgstr ""
#. name for nhb #. name for nhb
msgid "Beng" msgid "Beng"
msgstr "" msgstr "beng"
#. name for nhc #. name for nhc
msgid "Nahuatl; Tabasco" msgid "Nahuatl; Tabasco"
@ -18926,7 +18932,7 @@ msgstr ""
#. name for non #. name for non
msgid "Norse; Old" msgid "Norse; Old"
msgstr "ónorvég" msgstr "norvég; ónorvég"
#. name for nop #. name for nop
msgid "Numanggang" msgid "Numanggang"
@ -19347,7 +19353,7 @@ msgstr ""
#. name for nwc #. name for nwc
msgid "Newari; Old" msgid "Newari; Old"
msgstr "" msgstr "newari; ónewari"
#. name for nwe #. name for nwe
msgid "Ngwe" msgid "Ngwe"
@ -19567,11 +19573,11 @@ msgstr ""
#. name for oar #. name for oar
msgid "Aramaic; Old (up to 700 BCE)" msgid "Aramaic; Old (up to 700 BCE)"
msgstr "" msgstr "arámi; óarámi (i.e. 700-ig)"
#. name for oav #. name for oav
msgid "Avar; Old" msgid "Avar; Old"
msgstr "" msgstr "avar; óavar"
#. name for obi #. name for obi
msgid "Obispeño" msgid "Obispeño"
@ -19595,11 +19601,11 @@ msgstr ""
#. name for obr #. name for obr
msgid "Burmese; Old" msgid "Burmese; Old"
msgstr "" msgstr "burmai; óburmai"
#. name for obt #. name for obt
msgid "Breton; Old" msgid "Breton; Old"
msgstr "" msgstr "breton; óbreton"
#. name for obu #. name for obu
msgid "Obulom" msgid "Obulom"
@ -19611,7 +19617,7 @@ msgstr ""
#. name for och #. name for och
msgid "Chinese; Old" msgid "Chinese; Old"
msgstr "" msgstr "kínai; ókínai"
#. name for oci #. name for oci
msgid "Occitan (post 1500)" msgid "Occitan (post 1500)"
@ -19635,7 +19641,7 @@ msgstr ""
#. name for odt #. name for odt
msgid "Dutch; Old" msgid "Dutch; Old"
msgstr "" msgstr "holland; óholland"
#. name for odu #. name for odu
msgid "Odual" msgid "Odual"
@ -19647,7 +19653,7 @@ msgstr ""
#. name for ofs #. name for ofs
msgid "Frisian; Old" msgid "Frisian; Old"
msgstr "" msgstr "fríz; ófríz"
#. name for ofu #. name for ofu
msgid "Efutop" msgid "Efutop"
@ -19663,7 +19669,7 @@ msgstr ""
#. name for oge #. name for oge
msgid "Georgian; Old" msgid "Georgian; Old"
msgstr "" msgstr "grúz; ógrúz"
#. name for ogg #. name for ogg
msgid "Ogbogolo" msgid "Ogbogolo"
@ -19679,11 +19685,11 @@ msgstr ""
#. name for oht #. name for oht
msgid "Hittite; Old" msgid "Hittite; Old"
msgstr "" msgstr "hettita; óhettita"
#. name for ohu #. name for ohu
msgid "Hungarian; Old" msgid "Hungarian; Old"
msgstr "" msgstr "magyar; ómagyar"
#. name for oia #. name for oia
msgid "Oirata" msgid "Oirata"
@ -19711,7 +19717,7 @@ msgstr "odzsibwa"
#. name for ojp #. name for ojp
msgid "Japanese; Old" msgid "Japanese; Old"
msgstr "" msgstr "japán; ójapán"
#. name for ojs #. name for ojs
msgid "Ojibwa; Severn" msgid "Ojibwa; Severn"
@ -19771,7 +19777,7 @@ msgstr ""
#. name for oko #. name for oko
msgid "Korean; Old (3rd-9th cent.)" msgid "Korean; Old (3rd-9th cent.)"
msgstr "" msgstr "koreai; ókoreai (III--IX. sz.)"
#. name for okr #. name for okr
msgid "Kirike" msgid "Kirike"
@ -19939,7 +19945,7 @@ msgstr ""
#. name for onw #. name for onw
msgid "Nubian; Old" msgid "Nubian; Old"
msgstr "" msgstr "núbiai; ónúbiai"
#. name for onx #. name for onx
msgid "Onin Based Pidgin" msgid "Onin Based Pidgin"
@ -20043,7 +20049,7 @@ msgstr ""
#. name for orv #. name for orv
msgid "Russian; Old" msgid "Russian; Old"
msgstr "" msgstr "orosz; óorosz"
#. name for orw #. name for orw
msgid "Oro Win" msgid "Oro Win"
@ -20075,7 +20081,7 @@ msgstr ""
#. name for osp #. name for osp
msgid "Spanish; Old" msgid "Spanish; Old"
msgstr "" msgstr "spanyol; óspanyol"
#. name for oss #. name for oss
msgid "Ossetian" msgid "Ossetian"
@ -20091,7 +20097,7 @@ msgstr ""
#. name for osx #. name for osx
msgid "Saxon; Old" msgid "Saxon; Old"
msgstr "" msgstr "szász; ószász"
#. name for ota #. name for ota
msgid "Turkish; Ottoman (1500-1928)" msgid "Turkish; Ottoman (1500-1928)"
@ -20099,7 +20105,7 @@ msgstr "török (ottomán) (1500-1928)"
#. name for otb #. name for otb
msgid "Tibetan; Old" msgid "Tibetan; Old"
msgstr "" msgstr "tibeti; ótibeti"
#. name for otd #. name for otd
msgid "Ot Danum" msgid "Ot Danum"
@ -20115,7 +20121,7 @@ msgstr ""
#. name for otk #. name for otk
msgid "Turkish; Old" msgid "Turkish; Old"
msgstr "" msgstr "török; ótörök"
#. name for otl #. name for otl
msgid "Otomi; Tilapa" msgid "Otomi; Tilapa"
@ -20159,7 +20165,7 @@ msgstr ""
#. name for oty #. name for oty
msgid "Tamil; Old" msgid "Tamil; Old"
msgstr "" msgstr "tamil; ótamil"
#. name for otz #. name for otz
msgid "Otomi; Ixtenco" msgid "Otomi; Ixtenco"
@ -20179,7 +20185,7 @@ msgstr ""
#. name for oui #. name for oui
msgid "Uighur; Old" msgid "Uighur; Old"
msgstr "" msgstr "ujgur; óujgur"
#. name for oum #. name for oum
msgid "Ouma" msgid "Ouma"
@ -20195,7 +20201,7 @@ msgstr ""
#. name for owl #. name for owl
msgid "Welsh; Old" msgid "Welsh; Old"
msgstr "" msgstr "walesi; ówalesi"
#. name for oyb #. name for oyb
msgid "Oy" msgid "Oy"
@ -20532,7 +20538,7 @@ msgstr ""
#. name for peo #. name for peo
msgid "Persian; Old (ca. 600-400 B.C.)" msgid "Persian; Old (ca. 600-400 B.C.)"
msgstr "" msgstr "perzsa"
#. name for pep #. name for pep
msgid "Kunja" msgid "Kunja"
@ -22487,7 +22493,7 @@ msgstr ""
#. name for sam #. name for sam
msgid "Aramaic; Samaritan" msgid "Aramaic; Samaritan"
msgstr "" msgstr "arámi; szamaritánus"
# src/trans.h:193 # src/trans.h:193
#. name for san #. name for san
@ -22914,7 +22920,7 @@ msgstr ""
#. name for sga #. name for sga
msgid "Irish; Old (to 900)" msgid "Irish; Old (to 900)"
msgstr "óír (900-ig)" msgstr "ír; óír (900-ig)"
#. name for sgb #. name for sgb
msgid "Ayta; Mag-antsi" msgid "Ayta; Mag-antsi"
@ -25571,7 +25577,7 @@ msgstr "tumleo"
#. name for tmr #. name for tmr
msgid "Aramaic; Jewish Babylonian (ca. 200-1200 CE)" msgid "Aramaic; Jewish Babylonian (ca. 200-1200 CE)"
msgstr "" msgstr "arámi; zsidó babilóniai (kb. 200-1200)"
#. name for tms #. name for tms
msgid "Tima" msgid "Tima"
@ -28446,10 +28452,9 @@ msgstr ""
msgid "Kombio" msgid "Kombio"
msgstr "kombio" msgstr "kombio"
# src/trans.h:285
#. name for xbm #. name for xbm
msgid "Breton; Middle" msgid "Breton; Middle"
msgstr "breton; közép" msgstr "breton; Középkori"
#. name for xbn #. name for xbn
msgid "Kenaboi" msgid "Kenaboi"
@ -28514,7 +28519,7 @@ msgstr ""
#. name for xct #. name for xct
msgid "Tibetan; Classical" msgid "Tibetan; Classical"
msgstr "" msgstr "tibeti; klasszikus"
#. name for xcu #. name for xcu
msgid "Curonian" msgid "Curonian"
@ -30792,7 +30797,7 @@ msgstr ""
#. name for zpg #. name for zpg
msgid "Zapotec; Guevea De Humboldt" msgid "Zapotec; Guevea De Humboldt"
msgstr "" msgstr "zapoték; Guevea De Humboldt"
#. name for zph #. name for zph
msgid "Zapotec; Totomachapan" msgid "Zapotec; Totomachapan"

View File

@ -12,14 +12,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-" "Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n" "devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-09-27 14:31+0000\n" "POT-Creation-Date: 2011-09-27 14:31+0000\n"
"PO-Revision-Date: 2011-09-27 16:17+0000\n" "PO-Revision-Date: 2011-11-03 23:08+0000\n"
"Last-Translator: Kovid Goyal <Unknown>\n" "Last-Translator: drMerry <Unknown>\n"
"Language-Team: Dutch <vertaling@vrijschrift.org>\n" "Language-Team: Dutch <vertaling@vrijschrift.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-09-28 04:42+0000\n" "X-Launchpad-Export-Date: 2011-11-05 04:47+0000\n"
"X-Generator: Launchpad (build 14049)\n" "X-Generator: Launchpad (build 14231)\n"
"Language: nl\n" "Language: nl\n"
#. name for aaa #. name for aaa
@ -23624,7 +23624,7 @@ msgstr ""
#. name for som #. name for som
msgid "Somali" msgid "Somali"
msgstr "Somali" msgstr "Somalisch"
#. name for soo #. name for soo
msgid "Songo" msgid "Songo"
@ -24504,7 +24504,7 @@ msgstr ""
#. name for tat #. name for tat
msgid "Tatar" msgid "Tatar"
msgstr "Tatar" msgstr "Tataars"
#. name for tau #. name for tau
msgid "Tanana; Upper" msgid "Tanana; Upper"

View File

@ -10,14 +10,14 @@ msgstr ""
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-" "Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
"devel@lists.alioth.debian.org>\n" "devel@lists.alioth.debian.org>\n"
"POT-Creation-Date: 2011-09-27 14:31+0000\n" "POT-Creation-Date: 2011-09-27 14:31+0000\n"
"PO-Revision-Date: 2011-10-25 19:06+0000\n" "PO-Revision-Date: 2011-11-11 00:16+0000\n"
"Last-Translator: zeugma <Unknown>\n" "Last-Translator: kulkke <Unknown>\n"
"Language-Team: Turkish <gnome-turk@gnome.org>\n" "Language-Team: Turkish <gnome-turk@gnome.org>\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2011-10-26 05:13+0000\n" "X-Launchpad-Export-Date: 2011-11-11 04:53+0000\n"
"X-Generator: Launchpad (build 14189)\n" "X-Generator: Launchpad (build 14277)\n"
"Language: tr\n" "Language: tr\n"
#. name for aaa #. name for aaa
@ -18891,7 +18891,7 @@ msgstr ""
#. name for nor #. name for nor
msgid "Norwegian" msgid "Norwegian"
msgstr "Norveççe" msgstr "Norveçce"
#. name for nos #. name for nos
msgid "Nisu; Eastern" msgid "Nisu; Eastern"

View File

@ -116,8 +116,14 @@ class UploadToGoogleCode(Command): # {{{
return self.re_upload() return self.re_upload()
for fname in installers(): for fname in installers():
path = self.upload_one(fname) bname = os.path.basename(fname)
self.paths[os.path.basename(fname)] = path if bname in self.old_files:
path = 'http://calibre-ebook.googlecode.com/files/'+bname
self.info('%s already uploaded, skipping. Assuming URL is: %s',
bname, path)
else:
path = self.upload_one(fname)
self.paths[bname] = path
self.info('Updating path map') self.info('Updating path map')
self.info(repr(self.paths)) self.info(repr(self.paths))
raw = subprocess.Popen(['ssh', 'divok', 'cat', self.GPATHS], raw = subprocess.Popen(['ssh', 'divok', 'cat', self.GPATHS],

View File

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

View File

@ -66,4 +66,6 @@ Various things that require other things before they can be migrated:
functionality. functionality.
2. Catching DatabaseException and sqlite.Error when creating new 2. Catching DatabaseException and sqlite.Error when creating new
libraries/switching/on calibre startup. libraries/switching/on calibre startup.
3. From refresh in the legacy interface: Rember to flush the composite
column template cache.
''' '''

View File

@ -48,6 +48,7 @@ class Cache(object):
self.read_lock, self.write_lock = create_locks() self.read_lock, self.write_lock = create_locks()
self.record_lock = RecordLock(self.read_lock) self.record_lock = RecordLock(self.read_lock)
self.format_metadata_cache = defaultdict(dict) self.format_metadata_cache = defaultdict(dict)
self.formatter_template_cache = {}
# Implement locking for all simple read/write API methods # Implement locking for all simple read/write API methods
# An unlocked version of the method is stored with the name starting # An unlocked version of the method is stored with the name starting
@ -89,7 +90,7 @@ class Cache(object):
return self.backend.format_abspath(book_id, fmt, name, path) return self.backend.format_abspath(book_id, fmt, name, path)
def _get_metadata(self, book_id, get_user_categories=True): # {{{ def _get_metadata(self, book_id, get_user_categories=True): # {{{
mi = Metadata(None) mi = Metadata(None, template_cache=self.formatter_template_cache)
author_ids = self._field_ids_for('authors', book_id) author_ids = self._field_ids_for('authors', book_id)
aut_list = [self._author_data(i) for i in author_ids] aut_list = [self._author_data(i) for i in author_ids]
aum = [] aum = []

View File

@ -166,11 +166,12 @@ class ANDROID(USBMS):
'MB525', 'ANDROID2.3', 'SGH-I997', 'GT-I5800_CARD', 'MB612', 'MB525', 'ANDROID2.3', 'SGH-I997', 'GT-I5800_CARD', 'MB612',
'GT-S5830_CARD', 'GT-S5570_CARD', 'MB870', 'MID7015A', 'GT-S5830_CARD', 'GT-S5570_CARD', 'MB870', 'MID7015A',
'ALPANDIGITAL', 'ANDROID_MID', 'VTAB1008', 'EMX51_BBG_ANDROI', 'ALPANDIGITAL', 'ANDROID_MID', 'VTAB1008', 'EMX51_BBG_ANDROI',
'UMS', '.K080'] 'UMS', '.K080', 'P990']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD', 'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD',
'__UMS_COMPOSITE', 'SGH-I997_CARD', 'MB870', 'ALPANDIGITAL', 'ANDROID_MID'] '__UMS_COMPOSITE', 'SGH-I997_CARD', 'MB870', 'ALPANDIGITAL',
'ANDROID_MID', 'P990_SD_CARD', '.K080']
OSX_MAIN_MEM = 'Android Device Main Memory' OSX_MAIN_MEM = 'Android Device Main Memory'

View File

@ -256,6 +256,8 @@ class DevicePlugin(Plugin):
def set_progress_reporter(self, report_progress): def set_progress_reporter(self, report_progress):
''' '''
Set a function to report progress information.
:param report_progress: Function that is called with a % progress :param report_progress: Function that is called with a % progress
(number between 0 and 100) for various tasks (number between 0 and 100) for various tasks
If it is called with -1 that means that the If it is called with -1 that means that the

View File

@ -40,7 +40,7 @@ class KOBO(USBMS):
CAN_SET_METADATA = ['collections'] CAN_SET_METADATA = ['collections']
VENDOR_ID = [0x2237] VENDOR_ID = [0x2237]
PRODUCT_ID = [0x4161, 0x4163] PRODUCT_ID = [0x4161, 0x4163, 0x4165]
BCD = [0x0110, 0x0323, 0x0326] BCD = [0x0110, 0x0323, 0x0326]
VENDOR_NAME = ['KOBO_INC', 'KOBO'] VENDOR_NAME = ['KOBO_INC', 'KOBO']

View File

@ -233,7 +233,7 @@ class TREKSTOR(USBMS):
VENDOR_NAME = 'TREKSTOR' VENDOR_NAME = 'TREKSTOR'
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['EBOOK_PLAYER_7', WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['EBOOK_PLAYER_7',
'EBOOK_PLAYER_5M'] 'EBOOK_PLAYER_5M', 'EBOOK-READER_3.0']
class EEEREADER(USBMS): class EEEREADER(USBMS):

View File

@ -503,7 +503,10 @@ class PRST1(USBMS):
def upload_book_cover(self, connection, book, source_id): def upload_book_cover(self, connection, book, source_id):
debug_print('PRST1: Uploading/Refreshing Cover for ' + book.title) debug_print('PRST1: Uploading/Refreshing Cover for ' + book.title)
if not book.thumbnail or not book.thumbnail[-1]: if (not book.thumbnail or isinstance(book.thumbnail, ImageWrapper) or
not book.thumbnail[-1]):
# If the thumbnail is an ImageWrapper instance, it refers to a book
# not in the calibre library
return return
cursor = connection.cursor() cursor = connection.cursor()

View File

@ -180,7 +180,7 @@ class EPUBInput(InputFormatPlugin):
for y in opf.itermanifest(): for y in opf.itermanifest():
id_ = y.get('id', None) id_ = y.get('id', None)
if id_ and y.get('media-type', None) in \ if id_ and y.get('media-type', None) in \
('application/vnd.adobe-page-template+xml',): ('application/vnd.adobe-page-template+xml','application/text'):
not_for_spine.add(id_) not_for_spine.add(id_)
for x in list(opf.iterspine()): for x in list(opf.iterspine()):
@ -189,6 +189,9 @@ class EPUBInput(InputFormatPlugin):
x.getparent().remove(x) x.getparent().remove(x)
continue continue
if len(list(opf.iterspine())) == 0:
raise ValueError('No valid entries in the spine of this EPUB')
with open('content.opf', 'wb') as nopf: with open('content.opf', 'wb') as nopf:
nopf.write(opf.render()) nopf.write(opf.render())

View File

@ -13,7 +13,7 @@ Input plugin for HTML or OPF ebooks.
import os, re, sys, uuid, tempfile, errno as gerrno import os, re, sys, uuid, tempfile, errno as gerrno
from urlparse import urlparse, urlunparse from urlparse import urlparse, urlunparse
from urllib import unquote from urllib import unquote, quote
from functools import partial from functools import partial
from itertools import izip from itertools import izip
@ -468,7 +468,10 @@ class HTMLInput(InputFormatPlugin):
self.oeb.log, ignore_opf=True) self.oeb.log, ignore_opf=True)
# Load into memory # Load into memory
item = self.oeb.manifest.add(id, href, media_type) item = self.oeb.manifest.add(id, href, media_type)
item.html_input_href = bhref # bhref refers to an already existing file. The read() method of
# DirContainer will call unquote on it before trying to read the
# file, therefore we quote it here.
item.html_input_href = quote(bhref)
if guessed in self.OEB_STYLES: if guessed in self.OEB_STYLES:
item.override_css_fetch = partial( item.override_css_fetch = partial(
self.css_import_handler, os.path.dirname(link)) self.css_import_handler, os.path.dirname(link))

View File

@ -32,6 +32,7 @@ NULL_VALUES = {
'device_collections': [], 'device_collections': [],
'author_sort_map': {}, 'author_sort_map': {},
'authors' : [_('Unknown')], 'authors' : [_('Unknown')],
'author_sort' : _('Unknown'),
'title' : _('Unknown'), 'title' : _('Unknown'),
'user_categories' : {}, 'user_categories' : {},
'author_link_map' : {}, 'author_link_map' : {},
@ -45,9 +46,9 @@ class SafeFormat(TemplateFormatter):
def get_value(self, orig_key, args, kwargs): def get_value(self, orig_key, args, kwargs):
if not orig_key: if not orig_key:
return '' return ''
orig_key = orig_key.lower() key = orig_key = orig_key.lower()
key = orig_key if key != 'title_sort' and key not in TOP_LEVEL_IDENTIFIERS and \
if key != 'title_sort' and key not in TOP_LEVEL_IDENTIFIERS: key not in ALL_METADATA_FIELDS:
key = field_metadata.search_term_to_field_key(key) key = field_metadata.search_term_to_field_key(key)
if key is None or (self.book and if key is None or (self.book and
key not in self.book.all_field_keys()): key not in self.book.all_field_keys()):
@ -59,9 +60,8 @@ class SafeFormat(TemplateFormatter):
b = self.book.get_user_metadata(key, False) b = self.book.get_user_metadata(key, False)
except: except:
b = None b = None
if b and b['datatype'] == 'int' and self.book.get(key, 0) == 0: if b and ((b['datatype'] == 'int' and self.book.get(key, 0) == 0) or
v = '' (b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0)):
elif b and b['datatype'] == 'float' and self.book.get(key, 0.0) == 0.0:
v = '' v = ''
else: else:
v = self.book.format_field(key, series_with_index=False)[1] v = self.book.format_field(key, series_with_index=False)[1]
@ -95,7 +95,7 @@ class Metadata(object):
becomes a reserved field name. becomes a reserved field name.
''' '''
def __init__(self, title, authors=(_('Unknown'),), other=None): def __init__(self, title, authors=(_('Unknown'),), other=None, template_cache=None):
''' '''
@param title: title or ``_('Unknown')`` @param title: title or ``_('Unknown')``
@param authors: List of strings or [] @param authors: List of strings or []
@ -114,6 +114,7 @@ class Metadata(object):
self.author = list(authors) if authors else []# Needed for backward compatibility self.author = list(authors) if authors else []# Needed for backward compatibility
self.authors = list(authors) if authors else [] self.authors = list(authors) if authors else []
self.formatter = SafeFormat() self.formatter = SafeFormat()
self.template_cache = template_cache
def is_null(self, field): def is_null(self, field):
''' '''
@ -159,7 +160,8 @@ class Metadata(object):
d['display']['composite_template'], d['display']['composite_template'],
self, self,
_('TEMPLATE ERROR'), _('TEMPLATE ERROR'),
self).strip() self, column_name=field,
template_cache=self.template_cache).strip()
return val return val
if field.startswith('#') and field.endswith('_index'): if field.startswith('#') and field.endswith('_index'):
try: try:

View File

@ -341,11 +341,11 @@ class Worker(Thread): # Get details {{{
return authors return authors
def parse_rating(self, root): def parse_rating(self, root):
ratings = root.xpath('//div[@class="jumpBar"]/descendant::span[@class="asinReviewsSummary"]') ratings = root.xpath('//div[@class="jumpBar"]/descendant::span[contains(@class,"asinReviewsSummary")]')
if not ratings: if not ratings:
ratings = root.xpath('//div[@class="buying"]/descendant::span[@class="asinReviewsSummary"]') ratings = root.xpath('//div[@class="buying"]/descendant::span[contains(@class,"asinReviewsSummary")]')
if not ratings: if not ratings:
ratings = root.xpath('//span[@class="crAvgStars"]/descendant::span[@class="asinReviewsSummary"]') ratings = root.xpath('//span[@class="crAvgStars"]/descendant::span[contains(@class,"asinReviewsSummary")]')
if ratings: if ratings:
for elem in ratings[0].xpath('descendant::*[@title]'): for elem in ratings[0].xpath('descendant::*[@title]'):
t = elem.get('title').strip() t = elem.get('title').strip()

View File

@ -44,15 +44,19 @@ class Extract(ODF2XHTML):
# Remove the position:relative as it causes problems with some epub # Remove the position:relative as it causes problems with some epub
# renderers. Remove display: block on an image inside a div as it is # renderers. Remove display: block on an image inside a div as it is
# redundant and prevents text-align:center from working in ADE # redundant and prevents text-align:center from working in ADE
# Also ensure that the img is contained in its containing div
imgpath = XPath('//h:div/h:img[@style]') imgpath = XPath('//h:div/h:img[@style]')
for img in imgpath(root): for img in imgpath(root):
div = img.getparent() div = img.getparent()
if len(div) == 1: if len(div) == 1:
style = div.attrib['style'].replace('position:relative', '') style = div.attrib.get('style', '')
if style.startswith(';'): style = style[1:] if style and not style.endswith(';'):
style = style + ';'
style += 'position:static' # Ensures position of containing
# div is static
# Ensure that the img is always contained in its frame
div.attrib['style'] = style div.attrib['style'] = style
if img.attrib.get('style', '') == 'display: block;': img.attrib['style'] = 'max-width: 100%; max-height: 100%'
del img.attrib['style']
# A div/div/img construct causes text-align:center to not work in ADE # A div/div/img construct causes text-align:center to not work in ADE
# so set the display of the second div to inline. This should have no # so set the display of the second div to inline. This should have no

View File

@ -6,7 +6,7 @@ from threading import RLock
from urllib import unquote from urllib import unquote
from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt,
QByteArray, QTranslator, QCoreApplication, QThread, QByteArray, QTranslator, QCoreApplication, QThread,
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, QEvent, QTimer, pyqtSignal, QDateTime, QDesktopServices,
QFileDialog, QFileIconProvider, QSettings, QFileDialog, QFileIconProvider, QSettings,
QIcon, QApplication, QDialog, QUrl, QFont) QIcon, QApplication, QDialog, QUrl, QFont)
@ -104,7 +104,7 @@ gprefs.defaults['show_files_after_save'] = True
# }}} # }}}
NONE = QVariant() #: Null value to return from the data function of item models NONE = QVariant() #: Null value to return from the data function of item models
UNDEFINED_QDATE = QDate(UNDEFINED_DATE) UNDEFINED_QDATETIME = QDateTime(UNDEFINED_DATE)
ALL_COLUMNS = ['title', 'ondevice', 'authors', 'size', 'timestamp', 'rating', 'publisher', ALL_COLUMNS = ['title', 'ondevice', 'authors', 'size', 'timestamp', 'rating', 'publisher',
'tags', 'series', 'pubdate'] 'tags', 'series', 'pubdate']

View File

@ -22,7 +22,6 @@ from calibre.constants import preferred_encoding, filesystem_encoding
from calibre.gui2.actions import InterfaceAction from calibre.gui2.actions import InterfaceAction
from calibre.gui2 import question_dialog from calibre.gui2 import question_dialog
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata.sources.base import msprefs
def get_filters(): def get_filters():
return [ return [
@ -181,17 +180,9 @@ class AddAction(InterfaceAction):
except IndexError: except IndexError:
self.gui.library_view.model().books_added(self.isbn_add_dialog.value) self.gui.library_view.model().books_added(self.isbn_add_dialog.value)
self.isbn_add_dialog.accept() self.isbn_add_dialog.accept()
orig = msprefs['ignore_fields'] self.gui.iactions['Edit Metadata'].download_metadata(
new = list(orig) ids=self.add_by_isbn_ids, ensure_fields=frozenset(['title',
for x in ('title', 'authors'): 'authors']))
if x in new:
new.remove(x)
msprefs['ignore_fields'] = new
try:
self.gui.iactions['Edit Metadata'].download_metadata(
ids=self.add_by_isbn_ids)
finally:
msprefs['ignore_fields'] = orig
return return

View File

@ -66,7 +66,7 @@ class EditMetadataAction(InterfaceAction):
self.action_merge.setEnabled(enabled) self.action_merge.setEnabled(enabled)
# Download metadata {{{ # Download metadata {{{
def download_metadata(self, ids=None): def download_metadata(self, ids=None, ensure_fields=None):
if ids is None: if ids is None:
rows = self.gui.library_view.selectionModel().selectedRows() rows = self.gui.library_view.selectionModel().selectedRows()
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
@ -76,7 +76,8 @@ class EditMetadataAction(InterfaceAction):
ids = [db.id(row.row()) for row in rows] ids = [db.id(row.row()) for row in rows]
from calibre.gui2.metadata.bulk_download import start_download from calibre.gui2.metadata.bulk_download import start_download
start_download(self.gui, ids, start_download(self.gui, ids,
Dispatcher(self.metadata_downloaded)) Dispatcher(self.metadata_downloaded),
ensure_fields=ensure_fields)
def metadata_downloaded(self, job): def metadata_downloaded(self, job):
if job.failed: if job.failed:

View File

@ -7,15 +7,15 @@ __docformat__ = 'restructuredtext en'
from functools import partial from functools import partial
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \ from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit, \
QDate, QGroupBox, QVBoxLayout, QSizePolicy, \ QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, \
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \ QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \
QPushButton QPushButton
from calibre.utils.date import qt_to_dt, now from calibre.utils.date import qt_to_dt, now
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
from calibre.gui2.comments_editor import Editor as CommentsEditor from calibre.gui2.comments_editor import Editor as CommentsEditor
from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
from calibre.library.comments import comments_to_html from calibre.library.comments import comments_to_html
@ -142,27 +142,27 @@ class Rating(Int):
val *= 2 val *= 2
return val return val
class DateEdit(QDateEdit): class DateTimeEdit(QDateTimeEdit):
def focusInEvent(self, x): def focusInEvent(self, x):
self.setSpecialValueText('') self.setSpecialValueText('')
QDateEdit.focusInEvent(self, x) QDateTimeEdit.focusInEvent(self, x)
def focusOutEvent(self, x): def focusOutEvent(self, x):
self.setSpecialValueText(_('Undefined')) self.setSpecialValueText(_('Undefined'))
QDateEdit.focusOutEvent(self, x) QDateTimeEdit.focusOutEvent(self, x)
def set_to_today(self): def set_to_today(self):
self.setDate(now()) self.setDateTime(now())
def set_to_clear(self): def set_to_clear(self):
self.setDate(UNDEFINED_QDATE) self.setDateTime(UNDEFINED_QDATETIME)
class DateTime(Base): class DateTime(Base):
def setup_ui(self, parent): def setup_ui(self, parent):
cm = self.col_metadata cm = self.col_metadata
self.widgets = [QLabel('&'+cm['name']+':', parent), DateEdit(parent)] self.widgets = [QLabel('&'+cm['name']+':', parent), DateTimeEdit(parent)]
self.widgets.append(QLabel('')) self.widgets.append(QLabel(''))
w = QWidget(parent) w = QWidget(parent)
self.widgets.append(w) self.widgets.append(w)
@ -179,24 +179,24 @@ class DateTime(Base):
w = self.widgets[1] w = self.widgets[1]
format = cm['display'].get('date_format','') format = cm['display'].get('date_format','')
if not format: if not format:
format = 'dd MMM yyyy' format = 'dd MMM yyyy hh:mm'
w.setDisplayFormat(format) w.setDisplayFormat(format)
w.setCalendarPopup(True) w.setCalendarPopup(True)
w.setMinimumDate(UNDEFINED_QDATE) w.setMinimumDateTime(UNDEFINED_QDATETIME)
w.setSpecialValueText(_('Undefined')) w.setSpecialValueText(_('Undefined'))
self.today_button.clicked.connect(w.set_to_today) self.today_button.clicked.connect(w.set_to_today)
self.clear_button.clicked.connect(w.set_to_clear) self.clear_button.clicked.connect(w.set_to_clear)
def setter(self, val): def setter(self, val):
if val is None: if val is None:
val = self.widgets[1].minimumDate() val = self.widgets[1].minimumDateTime()
else: else:
val = QDate(val.year, val.month, val.day) val = QDateTime(val)
self.widgets[1].setDate(val) self.widgets[1].setDateTime(val)
def getter(self): def getter(self):
val = self.widgets[1].date() val = self.widgets[1].dateTime()
if val == UNDEFINED_QDATE: if val <= UNDEFINED_QDATETIME:
val = None val = None
else: else:
val = qt_to_dt(val) val = qt_to_dt(val)
@ -537,9 +537,9 @@ class BulkBase(Base):
if hasattr(self.main_widget, 'valueChanged'): if hasattr(self.main_widget, 'valueChanged'):
# spinbox widgets # spinbox widgets
self.main_widget.valueChanged.connect(self.a_c_checkbox_changed) self.main_widget.valueChanged.connect(self.a_c_checkbox_changed)
if hasattr(self.main_widget, 'dateChanged'): if hasattr(self.main_widget, 'dateTimeChanged'):
# dateEdit widgets # dateEdit widgets
self.main_widget.dateChanged.connect(self.a_c_checkbox_changed) self.main_widget.dateTimeChanged.connect(self.a_c_checkbox_changed)
def a_c_checkbox_changed(self): def a_c_checkbox_changed(self):
if not self.ignore_change_signals: if not self.ignore_change_signals:
@ -658,7 +658,7 @@ class BulkDateTime(BulkBase):
def setup_ui(self, parent): def setup_ui(self, parent):
cm = self.col_metadata cm = self.col_metadata
self.make_widgets(parent, DateEdit) self.make_widgets(parent, DateTimeEdit)
self.widgets.append(QLabel('')) self.widgets.append(QLabel(''))
w = QWidget(parent) w = QWidget(parent)
self.widgets.append(w) self.widgets.append(w)
@ -678,22 +678,22 @@ class BulkDateTime(BulkBase):
format = 'dd MMM yyyy' format = 'dd MMM yyyy'
w.setDisplayFormat(format) w.setDisplayFormat(format)
w.setCalendarPopup(True) w.setCalendarPopup(True)
w.setMinimumDate(UNDEFINED_QDATE) w.setMinimumDateTime(UNDEFINED_QDATETIME)
w.setSpecialValueText(_('Undefined')) w.setSpecialValueText(_('Undefined'))
self.today_button.clicked.connect(w.set_to_today) self.today_button.clicked.connect(w.set_to_today)
self.clear_button.clicked.connect(w.set_to_clear) self.clear_button.clicked.connect(w.set_to_clear)
def setter(self, val): def setter(self, val):
if val is None: if val is None:
val = self.main_widget.minimumDate() val = self.main_widget.minimumDateTime()
else: else:
val = QDate(val.year, val.month, val.day) val = QDateTime(val)
self.main_widget.setDate(val) self.main_widget.setDateTime(val)
self.ignore_change_signals = False self.ignore_change_signals = False
def getter(self): def getter(self):
val = self.main_widget.date() val = self.main_widget.dateTime()
if val == UNDEFINED_QDATE: if val <= UNDEFINED_QDATETIME:
val = None val = None
else: else:
val = qt_to_dt(val) val = qt_to_dt(val)

View File

@ -7,14 +7,14 @@ import re, os, inspect
from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \ from PyQt4.Qt import Qt, QDialog, QGridLayout, QVBoxLayout, QFont, QLabel, \
pyqtSignal, QDialogButtonBox, QInputDialog, QLineEdit, \ pyqtSignal, QDialogButtonBox, QInputDialog, QLineEdit, \
QDate, QCompleter QDateTime, QCompleter
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.ebooks.metadata import string_to_authors, authors_to_string, title_sort from calibre.ebooks.metadata import string_to_authors, authors_to_string, title_sort
from calibre.ebooks.metadata.book.base import SafeFormat from calibre.ebooks.metadata.book.base import SafeFormat
from calibre.gui2.custom_column_widgets import populate_metadata_page from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATE, \ from calibre.gui2 import error_dialog, ResizableDialog, UNDEFINED_QDATETIME, \
gprefs, question_dialog gprefs, question_dialog
from calibre.gui2.progress_indicator import ProgressIndicator from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.utils.config import dynamic, JSONConfig from calibre.utils.config import dynamic, JSONConfig
@ -306,18 +306,21 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.series.editTextChanged.connect(self.series_changed) self.series.editTextChanged.connect(self.series_changed)
self.tag_editor_button.clicked.connect(self.tag_editor) self.tag_editor_button.clicked.connect(self.tag_editor)
self.autonumber_series.stateChanged[int].connect(self.auto_number_changed) self.autonumber_series.stateChanged[int].connect(self.auto_number_changed)
self.pubdate.setMinimumDate(UNDEFINED_QDATE) self.pubdate.setMinimumDateTime(UNDEFINED_QDATETIME)
pubdate_format = tweaks['gui_pubdate_display_format'] pubdate_format = tweaks['gui_pubdate_display_format']
if pubdate_format is not None: if pubdate_format is not None:
self.pubdate.setDisplayFormat(pubdate_format) self.pubdate.setDisplayFormat(pubdate_format)
self.pubdate.setSpecialValueText(_('Undefined')) self.pubdate.setSpecialValueText(_('Undefined'))
self.clear_pubdate_button.clicked.connect(self.clear_pubdate) self.clear_pubdate_button.clicked.connect(self.clear_pubdate)
self.pubdate.dateChanged.connect(self.do_apply_pubdate) self.pubdate.dateTimeChanged.connect(self.do_apply_pubdate)
self.adddate.setDate(QDate.currentDate()) self.adddate.setDateTime(QDateTime.currentDateTime())
self.adddate.setMinimumDate(UNDEFINED_QDATE) self.adddate.setMinimumDateTime(UNDEFINED_QDATETIME)
adddate_format = tweaks['gui_timestamp_display_format']
if adddate_format is not None:
self.adddate.setDisplayFormat(adddate_format)
self.adddate.setSpecialValueText(_('Undefined')) self.adddate.setSpecialValueText(_('Undefined'))
self.clear_adddate_button.clicked.connect(self.clear_adddate) self.clear_adddate_button.clicked.connect(self.clear_adddate)
self.adddate.dateChanged.connect(self.do_apply_adddate) self.adddate.dateTimeChanged.connect(self.do_apply_adddate)
if len(self.db.custom_field_keys(include_composites=False)) == 0: if len(self.db.custom_field_keys(include_composites=False)) == 0:
self.central_widget.removeTab(1) self.central_widget.removeTab(1)
@ -347,13 +350,13 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
self.apply_pubdate.setChecked(True) self.apply_pubdate.setChecked(True)
def clear_pubdate(self, *args): def clear_pubdate(self, *args):
self.pubdate.setDate(UNDEFINED_QDATE) self.pubdate.setMinimumDateTime(UNDEFINED_QDATETIME)
def do_apply_adddate(self, *args): def do_apply_adddate(self, *args):
self.apply_adddate.setChecked(True) self.apply_adddate.setChecked(True)
def clear_adddate(self, *args): def clear_adddate(self, *args):
self.adddate.setDate(UNDEFINED_QDATE) self.adddate.setMinimumDateTime(UNDEFINED_QDATETIME)
def button_clicked(self, which): def button_clicked(self, which):
if which == self.button_box.button(QDialogButtonBox.Apply): if which == self.button_box.button(QDialogButtonBox.Apply):
@ -935,9 +938,9 @@ class MetadataBulkDialog(ResizableDialog, Ui_MetadataBulkDialog):
languages = self.languages.lang_codes languages = self.languages.lang_codes
pubdate = adddate = None pubdate = adddate = None
if self.apply_pubdate.isChecked(): if self.apply_pubdate.isChecked():
pubdate = qt_to_dt(self.pubdate.date()) pubdate = qt_to_dt(self.pubdate.dateTime())
if self.apply_adddate.isChecked(): if self.apply_adddate.isChecked():
adddate = qt_to_dt(self.adddate.date()) adddate = qt_to_dt(self.adddate.dateTime())
cover_action = None cover_action = None
if self.cover_remove.isChecked(): if self.cover_remove.isChecked():

View File

@ -366,7 +366,7 @@ from the value in the box</string>
<item row="9" column="1"> <item row="9" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5"> <layout class="QHBoxLayout" name="horizontalLayout_5">
<item> <item>
<widget class="QDateEdit" name="adddate"> <widget class="QDateTimeEdit" name="adddate">
<property name="displayFormat"> <property name="displayFormat">
<string>d MMM yyyy</string> <string>d MMM yyyy</string>
</property> </property>
@ -411,7 +411,7 @@ from the value in the box</string>
<item row="10" column="1"> <item row="10" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<item> <item>
<widget class="QDateEdit" name="pubdate"> <widget class="QDateTimeEdit" name="pubdate">
<property name="displayFormat"> <property name="displayFormat">
<string>MMM yyyy</string> <string>MMM yyyy</string>
</property> </property>

View File

@ -14,10 +14,10 @@ from PyQt4.Qt import (QColor, Qt, QModelIndex, QSize, QApplication,
QStyledItemDelegate, QComboBox, QTextDocument, QStyledItemDelegate, QComboBox, QTextDocument,
QAbstractTextDocumentLayout) QAbstractTextDocumentLayout)
from calibre.gui2 import UNDEFINED_QDATE, error_dialog from calibre.gui2 import UNDEFINED_QDATETIME, error_dialog
from calibre.gui2.widgets import EnLineEdit from calibre.gui2.widgets import EnLineEdit
from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox from calibre.gui2.complete import MultiCompleteLineEdit, MultiCompleteComboBox
from calibre.utils.date import now, format_date from calibre.utils.date import now, format_date, qt_to_dt
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre.utils.formatter import validation_formatter from calibre.utils.formatter import validation_formatter
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
@ -107,25 +107,23 @@ class RatingDelegate(QStyledItemDelegate): # {{{
class DateDelegate(QStyledItemDelegate): # {{{ class DateDelegate(QStyledItemDelegate): # {{{
def __init__(self, parent, tweak_name='gui_timestamp_display_format', def __init__(self, parent, tweak_name='gui_timestamp_display_format',
default_format='dd MMM yyyy', editor_format='dd MMM yyyy'): default_format='dd MMM yyyy'):
QStyledItemDelegate.__init__(self, parent) QStyledItemDelegate.__init__(self, parent)
self.tweak_name = tweak_name self.tweak_name = tweak_name
self.default_format = default_format self.format = tweaks[self.tweak_name]
self.editor_format = editor_format if self.format is None:
self.format = default_format
def displayText(self, val, locale): def displayText(self, val, locale):
d = val.toDate() d = val.toDateTime()
if d <= UNDEFINED_QDATE: if d <= UNDEFINED_QDATETIME:
return '' return ''
format = tweaks[self.tweak_name] return format_date(qt_to_dt(d, as_utc=False), self.format)
if format is None:
format = self.default_format
return format_date(d.toPyDate(), format)
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index) qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat(self.editor_format) qde.setDisplayFormat(self.format)
qde.setMinimumDate(UNDEFINED_QDATE) qde.setMinimumDateTime(UNDEFINED_QDATETIME)
qde.setSpecialValueText(_('Undefined')) qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True) qde.setCalendarPopup(True)
return qde return qde
@ -134,18 +132,18 @@ class DateDelegate(QStyledItemDelegate): # {{{
class PubDateDelegate(QStyledItemDelegate): # {{{ class PubDateDelegate(QStyledItemDelegate): # {{{
def displayText(self, val, locale): def displayText(self, val, locale):
d = val.toDate() d = val.toDateTime()
if d <= UNDEFINED_QDATE: if d <= UNDEFINED_QDATETIME:
return '' return ''
format = tweaks['gui_pubdate_display_format'] self.format = tweaks['gui_pubdate_display_format']
if format is None: if self.format is None:
format = 'MMM yyyy' self.format = 'MMM yyyy'
return format_date(d.toPyDate(), format) return format_date(qt_to_dt(d, as_utc=False), self.format)
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index) qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat('MM yyyy') qde.setDisplayFormat(self.format)
qde.setMinimumDate(UNDEFINED_QDATE) qde.setMinimumDateTime(UNDEFINED_QDATETIME)
qde.setSpecialValueText(_('Undefined')) qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True) qde.setCalendarPopup(True)
return qde return qde
@ -259,15 +257,15 @@ class CcDateDelegate(QStyledItemDelegate): # {{{
self.format = format self.format = format
def displayText(self, val, locale): def displayText(self, val, locale):
d = val.toDate() d = val.toDateTime()
if d <= UNDEFINED_QDATE: if d <= UNDEFINED_QDATETIME:
return '' return ''
return format_date(d.toPyDate(), self.format) return format_date(qt_to_dt(d, as_utc=False), self.format)
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index) qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat(self.format) qde.setDisplayFormat(self.format)
qde.setMinimumDate(UNDEFINED_QDATE) qde.setMinimumDateTime(UNDEFINED_QDATETIME)
qde.setSpecialValueText(_('Undefined')) qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True) qde.setCalendarPopup(True)
return qde return qde
@ -279,11 +277,11 @@ class CcDateDelegate(QStyledItemDelegate): # {{{
val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']] val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']]
if val is None: if val is None:
val = now() val = now()
editor.setDate(val) editor.setDateTime(val)
def setModelData(self, editor, model, index): def setModelData(self, editor, model, index):
val = editor.date() val = editor.dateTime()
if val <= UNDEFINED_QDATE: if val <= UNDEFINED_QDATETIME:
val = None val = None
model.setData(index, QVariant(val), Qt.EditRole) model.setData(index, QVariant(val), Qt.EditRole)

View File

@ -5,13 +5,13 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import functools, re, os, traceback import functools, re, os, traceback, errno
from collections import defaultdict from collections import defaultdict
from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage,
QModelIndex, QVariant, QDate, QColor) QModelIndex, QVariant, QDateTime, QColor)
from calibre.gui2 import NONE, UNDEFINED_QDATE from calibre.gui2 import NONE, UNDEFINED_QDATETIME, error_dialog
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors from calibre.ebooks.metadata import fmt_sidx, authors_to_string, string_to_authors
from calibre.ebooks.metadata.book.base import SafeFormat from calibre.ebooks.metadata.book.base import SafeFormat
@ -580,9 +580,9 @@ class BooksModel(QAbstractTableModel): # {{{
def datetime_type(r, idx=-1): def datetime_type(r, idx=-1):
val = self.db.data[r][idx] val = self.db.data[r][idx]
if val is not None: if val is not None:
return QVariant(QDate(val)) return QVariant(QDateTime(val))
else: else:
return QVariant(UNDEFINED_QDATE) return QVariant(UNDEFINED_QDATETIME)
def bool_type(r, idx=-1): def bool_type(r, idx=-1):
return None # displayed using a decorator return None # displayed using a decorator
@ -815,7 +815,7 @@ class BooksModel(QAbstractTableModel): # {{{
if not val: if not val:
val = None val = None
elif typ == 'datetime': elif typ == 'datetime':
val = value.toDate() val = value.toDateTime()
if val.isNull(): if val.isNull():
val = None val = None
else: else:
@ -851,59 +851,78 @@ class BooksModel(QAbstractTableModel): # {{{
def setData(self, index, value, role): def setData(self, index, value, role):
if role == Qt.EditRole: if role == Qt.EditRole:
row, col = index.row(), index.column() from calibre.gui2.ui import get_gui
column = self.column_map[col] try:
if self.is_custom_column(column): return self._set_data(index, value)
if not self.set_custom_column_data(row, column, value): except (IOError, OSError) as err:
return False if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
else: import traceback
if column not in self.editable_cols: error_dialog(get_gui(), _('Permission denied'),
return False _('Could not change the on disk location of this'
val = int(value.toInt()[0]) if column == 'rating' else \ ' book. Is it open in another program?'),
value.toDate() if column in ('timestamp', 'pubdate') else \ det_msg=traceback.format_exc(), show=True)
unicode(value.toString()).strip() except:
id = self.db.id(row) import traceback
books_to_refresh = set([id]) traceback.print_exc()
if column == 'rating': error_dialog(get_gui(), _('Failed to set data'),
val = 0 if val < 0 else 5 if val > 5 else val _('Could not set data, click Show Details to see why.'),
val *= 2 det_msg=traceback.format_exc(), show=True)
self.db.set_rating(id, val) return False
elif column == 'series':
val = val.strip() def _set_data(self, index, value):
if not val: row, col = index.row(), index.column()
books_to_refresh |= self.db.set_series(id, val, column = self.column_map[col]
allow_case_change=True) if self.is_custom_column(column):
self.db.set_series_index(id, 1.0) if not self.set_custom_column_data(row, column, value):
else: return False
pat = re.compile(r'\[([.0-9]+)\]') else:
match = pat.search(val) if column not in self.editable_cols:
if match is not None: return False
self.db.set_series_index(id, float(match.group(1))) val = (int(value.toInt()[0]) if column == 'rating' else
val = pat.sub('', val).strip() value.toDateTime() if column in ('timestamp', 'pubdate')
elif val: else unicode(value.toString()).strip())
if tweaks['series_index_auto_increment'] != 'const': id = self.db.id(row)
ni = self.db.get_next_series_num_for(val) books_to_refresh = set([id])
if ni != 1: if column == 'rating':
self.db.set_series_index(id, ni) val = 0 if val < 0 else 5 if val > 5 else val
if val: val *= 2
books_to_refresh |= self.db.set_series(id, val, self.db.set_rating(id, val)
allow_case_change=True) elif column == 'series':
elif column == 'timestamp': val = val.strip()
if val.isNull() or not val.isValid(): if not val:
return False books_to_refresh |= self.db.set_series(id, val,
self.db.set_timestamp(id, qt_to_dt(val, as_utc=False))
elif column == 'pubdate':
if val.isNull() or not val.isValid():
return False
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
elif column == 'languages':
val = val.split(',')
self.db.set_languages(id, val)
else:
books_to_refresh |= self.db.set(row, column, val,
allow_case_change=True) allow_case_change=True)
self.refresh_ids(list(books_to_refresh), row) self.db.set_series_index(id, 1.0)
self.dataChanged.emit(index, index) else:
pat = re.compile(r'\[([.0-9]+)\]')
match = pat.search(val)
if match is not None:
self.db.set_series_index(id, float(match.group(1)))
val = pat.sub('', val).strip()
elif val:
if tweaks['series_index_auto_increment'] != 'const':
ni = self.db.get_next_series_num_for(val)
if ni != 1:
self.db.set_series_index(id, ni)
if val:
books_to_refresh |= self.db.set_series(id, val,
allow_case_change=True)
elif column == 'timestamp':
if val.isNull() or not val.isValid():
return False
self.db.set_timestamp(id, qt_to_dt(val, as_utc=False))
elif column == 'pubdate':
if val.isNull() or not val.isValid():
return False
self.db.set_pubdate(id, qt_to_dt(val, as_utc=False))
elif column == 'languages':
val = val.split(',')
self.db.set_languages(id, val)
else:
books_to_refresh |= self.db.set(row, column, val,
allow_case_change=True)
self.refresh_ids(list(books_to_refresh), row)
self.dataChanged.emit(index, index)
return True return True
# }}} # }}}

View File

@ -134,10 +134,12 @@ class GuiRunner(QObject):
main = Main(self.opts, gui_debug=self.gui_debug) main = Main(self.opts, gui_debug=self.gui_debug)
if self.splash_screen is not None: if self.splash_screen is not None:
self.splash_screen.showMessage(_('Initializing user interface...')) self.splash_screen.showMessage(_('Initializing user interface...'))
self.splash_screen.finish(main)
main.initialize(self.library_path, db, self.listener, self.actions) main.initialize(self.library_path, db, self.listener, self.actions)
if self.splash_screen is not None:
self.splash_screen.finish(main)
if DEBUG: if DEBUG:
prints('Started up in', time.time() - self.startup_time) prints('Started up in', time.time() - self.startup_time, 'with',
len(db.data), 'books')
add_filesystem_book = partial(main.iactions['Add Books'].add_filesystem_book, allow_device=False) add_filesystem_book = partial(main.iactions['Add Books'].add_filesystem_book, allow_device=False)
sys.excepthook = main.unhandled_exception sys.excepthook = main.unhandled_exception
if len(self.args) > 1: if len(self.args) > 1:
@ -347,7 +349,8 @@ def main(args=sys.argv):
except socket.error: except socket.error:
if iswindows: if iswindows:
cant_start() cant_start()
os.remove(ADDRESS) if os.path.exists(ADDRESS):
os.remove(ADDRESS)
try: try:
listener = Listener(address=ADDRESS) listener = Listener(address=ADDRESS)
except socket.error: except socket.error:

View File

@ -9,7 +9,7 @@ __docformat__ = 'restructuredtext en'
import textwrap, re, os, errno, shutil import textwrap, re, os, errno, shutil
from PyQt4.Qt import (Qt, QDateEdit, QDate, pyqtSignal, QMessageBox, from PyQt4.Qt import (Qt, QDateTimeEdit, pyqtSignal, QMessageBox,
QIcon, QToolButton, QWidget, QLabel, QGridLayout, QApplication, QIcon, QToolButton, QWidget, QLabel, QGridLayout, QApplication,
QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, QDialog, QMenu, QDoubleSpinBox, QListWidgetItem, QSize, QPixmap, QDialog, QMenu,
QPushButton, QSpinBox, QLineEdit, QSizePolicy, QDialogButtonBox, QAction) QPushButton, QSpinBox, QLineEdit, QSizePolicy, QDialogButtonBox, QAction)
@ -21,7 +21,7 @@ from calibre.utils.config import tweaks, prefs
from calibre.ebooks.metadata import (title_sort, authors_to_string, from calibre.ebooks.metadata import (title_sort, authors_to_string,
string_to_authors, check_isbn, authors_to_sort_string) string_to_authors, check_isbn, authors_to_sort_string)
from calibre.ebooks.metadata.meta import get_metadata from calibre.ebooks.metadata.meta import get_metadata
from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATE, from calibre.gui2 import (file_icon_provider, UNDEFINED_QDATETIME,
choose_files, error_dialog, choose_images) choose_files, error_dialog, choose_images)
from calibre.utils.date import (local_tz, qt_to_dt, as_local_time, from calibre.utils.date import (local_tz, qt_to_dt, as_local_time,
UNDEFINED_DATE) UNDEFINED_DATE)
@ -1377,25 +1377,24 @@ class PublisherEdit(MultiCompleteComboBox): # {{{
# }}} # }}}
class DateEdit(QDateEdit): # {{{ class DateEdit(QDateTimeEdit): # {{{
TOOLTIP = '' TOOLTIP = ''
LABEL = _('&Date:') LABEL = _('&Date:')
FMT = 'd MMM yyyy' FMT = 'dd MMM yyyy hh:mm:ss'
ATTR = 'timestamp' ATTR = 'timestamp'
TWEAK = 'gui_timestamp_display_format'
def __init__(self, parent): def __init__(self, parent):
QDateEdit.__init__(self, parent) QDateTimeEdit.__init__(self, parent)
self.setToolTip(self.TOOLTIP) self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP)
fmt = self.FMT fmt = tweaks[self.TWEAK]
if fmt is None: if fmt is None:
fmt = tweaks['gui_pubdate_display_format'] fmt = self.FMT
if fmt is None:
fmt = 'MMM yyyy'
self.setDisplayFormat(fmt) self.setDisplayFormat(fmt)
self.setCalendarPopup(True) self.setCalendarPopup(True)
self.setMinimumDate(UNDEFINED_QDATE) self.setMinimumDateTime(UNDEFINED_QDATETIME)
self.setSpecialValueText(_('Undefined')) self.setSpecialValueText(_('Undefined'))
self.clear_button = QToolButton(parent) self.clear_button = QToolButton(parent)
self.clear_button.setIcon(QIcon(I('trash.png'))) self.clear_button.setIcon(QIcon(I('trash.png')))
@ -1408,12 +1407,13 @@ class DateEdit(QDateEdit): # {{{
@dynamic_property @dynamic_property
def current_val(self): def current_val(self):
def fget(self): def fget(self):
return qt_to_dt(self.date(), as_utc=False) return qt_to_dt(self.dateTime(), as_utc=False)
def fset(self, val): def fset(self, val):
if val is None: if val is None:
val = UNDEFINED_DATE val = UNDEFINED_DATE
val = as_local_time(val) else:
self.setDate(QDate(val.year, val.month, val.day)) val = as_local_time(val)
self.setDateTime(val)
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def initialize(self, db, id_): def initialize(self, db, id_):
@ -1429,11 +1429,12 @@ class DateEdit(QDateEdit): # {{{
@property @property
def changed(self): def changed(self):
o, c = self.original_val, self.current_val o, c = self.original_val, self.current_val
return o.year != c.year or o.month != c.month or o.day != c.day return o != c
class PubdateEdit(DateEdit): class PubdateEdit(DateEdit):
LABEL = _('Publishe&d:') LABEL = _('Publishe&d:')
FMT = None FMT = 'MMM yyyy'
ATTR = 'pubdate' ATTR = 'pubdate'
TWEAK = 'gui_pubdate_display_format'
# }}} # }}}

View File

@ -98,7 +98,7 @@ def split_jobs(ids, batch_size=100):
ids = ids[batch_size:] ids = ids[batch_size:]
return ans return ans
def start_download(gui, ids, callback): def start_download(gui, ids, callback, ensure_fields=None):
d = ConfirmDialog(ids, gui) d = ConfirmDialog(ids, gui)
ret = d.exec_() ret = d.exec_()
d.b.clicked.disconnect() d.b.clicked.disconnect()
@ -108,7 +108,8 @@ def start_download(gui, ids, callback):
for batch in split_jobs(ids): for batch in split_jobs(ids):
job = ThreadedJob('metadata bulk download', job = ThreadedJob('metadata bulk download',
_('Download metadata for %d books')%len(batch), _('Download metadata for %d books')%len(batch),
download, (batch, gui.current_db, d.identify, d.covers), {}, callback) download, (batch, gui.current_db, d.identify, d.covers,
ensure_fields), {}, callback)
gui.job_manager.run_threaded_job(job) gui.job_manager.run_threaded_job(job)
gui.status_bar.show_message(_('Metadata download started'), 3000) gui.status_bar.show_message(_('Metadata download started'), 3000)
@ -127,10 +128,10 @@ def get_job_details(job):
det_msg = '\n'.join(det_msg) det_msg = '\n'.join(det_msg)
return id_map, failed_ids, failed_covers, all_failed, det_msg return id_map, failed_ids, failed_covers, all_failed, det_msg
def merge_result(oldmi, newmi): def merge_result(oldmi, newmi, ensure_fields=None):
dummy = Metadata(_('Unknown')) dummy = Metadata(_('Unknown'))
for f in msprefs['ignore_fields']: for f in msprefs['ignore_fields']:
if ':' not in f: if ':' not in f and (ensure_fields and f not in ensure_fields):
setattr(newmi, f, getattr(dummy, f)) setattr(newmi, f, getattr(dummy, f))
fields = set() fields = set()
for plugin in metadata_plugins(['identify']): for plugin in metadata_plugins(['identify']):
@ -154,7 +155,7 @@ def merge_result(oldmi, newmi):
return newmi return newmi
def download(ids, db, do_identify, covers, def download(ids, db, do_identify, covers, ensure_fields,
log=None, abort=None, notifications=None): log=None, abort=None, notifications=None):
ids = list(ids) ids = list(ids)
metadata = [db.get_metadata(i, index_is_id=True, get_user_categories=False) metadata = [db.get_metadata(i, index_is_id=True, get_user_categories=False)
@ -184,7 +185,7 @@ def download(ids, db, do_identify, covers,
pass pass
if results: if results:
all_failed = False all_failed = False
mi = merge_result(mi, results[0]) mi = merge_result(mi, results[0], ensure_fields=ensure_fields)
identifiers = mi.identifiers identifiers = mi.identifiers
if not mi.is_null('rating'): if not mi.is_null('rating'):
# set_metadata expects a rating out of 10 # set_metadata expects a rating out of 10
@ -193,7 +194,7 @@ def download(ids, db, do_identify, covers,
log.error('Failed to download metadata for', title) log.error('Failed to download metadata for', title)
failed_ids.add(i) failed_ids.add(i)
# We don't want set_metadata operating on anything but covers # We don't want set_metadata operating on anything but covers
mi = merge_result(mi, mi) mi = merge_result(mi, mi, ensure_fields=ensure_fields)
if covers: if covers:
cdata = download_cover(log, title=title, authors=authors, cdata = download_cover(log, title=title, authors=authors,
identifiers=identifiers) identifiers=identifiers)

View File

@ -440,8 +440,8 @@ class MetadataSingleDialogBase(ResizableDialog):
return False return False
self.books_to_refresh |= getattr(widget, 'books_to_refresh', self.books_to_refresh |= getattr(widget, 'books_to_refresh',
set([])) set([]))
except IOError as err: except (IOError, OSError) as err:
if err.errno == errno.EACCES: # Permission denied if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
import traceback import traceback
fname = err.filename if err.filename else 'file' fname = err.filename if err.filename else 'file'
error_dialog(self, _('Permission denied'), error_dialog(self, _('Permission denied'),

View File

@ -121,13 +121,13 @@ class AdvSearchBuilderDialog(QDialog, Ui_Dialog):
format = unicode(self.format_box.text()).strip() format = unicode(self.format_box.text()).strip()
if format: if format:
ans.append('format:"' + self.mc + format + '"') ans.append('format:"' + self.mc + format + '"')
drm = unicode(self.drm_combo.currentText()).strip() drm = '' if self.drm_combo.currentIndex() == 0 else 'true' if self.drm_combo.currentIndex() == 1 else 'false'
if drm: if drm:
ans.append('drm:' + drm) ans.append('drm:' + drm)
download = unicode(self.download_combo.currentText()).strip() download = '' if self.download_combo.currentIndex() == 0 else 'true' if self.download_combo.currentIndex() == 1 else 'false'
if download: if download:
ans.append('download:' + download) ans.append('download:' + download)
affiliate = unicode(self.affiliate_combo.currentText()).strip() affiliate = '' if self.affiliate_combo.currentIndex() == 0 else 'true' if self.affiliate_combo.currentIndex() == 1 else 'false'
if affiliate: if affiliate:
ans.append('affiliate:' + affiliate) ans.append('affiliate:' + affiliate)
if ans: if ans:

View File

@ -122,6 +122,11 @@ class Kobo(Device):
output_format = 'EPUB' output_format = 'EPUB'
id = 'kobo' id = 'kobo'
class KoboVox(Kobo):
name = 'Kobo Vox'
output_profile = 'tablet'
id = 'kobo_vox'
class Booq(Device): class Booq(Device):
name = 'bq Classic' name = 'bq Classic'
manufacturer = 'Booq' manufacturer = 'Booq'

View File

@ -12,7 +12,7 @@ from datetime import timedelta
from threading import Thread from threading import Thread
from calibre.utils.config import tweaks, prefs from calibre.utils.config import tweaks, prefs
from calibre.utils.date import parse_date, now, UNDEFINED_DATE from calibre.utils.date import parse_date, now, UNDEFINED_DATE, clean_date_for_sort
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
from calibre.utils.localization import canonicalize_lang, lang_map from calibre.utils.localization import canonicalize_lang, lang_map
@ -936,6 +936,9 @@ class ResultCache(SearchQueryParser): # {{{
item.refresh_composites() item.refresh_composites()
def refresh(self, db, field=None, ascending=True): def refresh(self, db, field=None, ascending=True):
# reinitialize the template cache in case a composite column has changed
db.initialize_template_cache()
temp = db.conn.get('SELECT * FROM meta2') temp = db.conn.get('SELECT * FROM meta2')
self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else [] self._data = list(itertools.repeat(None, temp[-1][0]+2)) if temp else []
for r in temp: for r in temp:
@ -1059,7 +1062,17 @@ class SortKeyGenerator(object):
if dt == 'datetime': if dt == 'datetime':
if val is None: if val is None:
val = UNDEFINED_DATE val = UNDEFINED_DATE
if tweaks['sort_dates_using_visible_fields']:
format = None
if name == 'timestamp':
format = tweaks['gui_timestamp_display_format']
elif name == 'pubdate':
format = tweaks['gui_pubdate_display_format']
elif name == 'last_modified':
format = tweaks['gui_last_modified_display_format']
elif fm['is_custom']:
format = fm['display'].get('date_format', None)
val = clean_date_for_sort(val, format)
elif dt == 'series': elif dt == 'series':
if val is None: if val is None:
val = ('', 1) val = ('', 1)

View File

@ -216,8 +216,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.create_custom_column(f['label'], f['name'], f['datatype'], self.create_custom_column(f['label'], f['name'], f['datatype'],
f['is_multiple'] is not None and len(f['is_multiple']) > 0, f['is_multiple'] is not None and len(f['is_multiple']) > 0,
f['is_editable'], f['display']) f['is_editable'], f['display'])
self.initialize_template_cache()
self.initialize_dynamic() self.initialize_dynamic()
def initialize_template_cache(self):
self.formatter_template_cache = {}
def get_property(self, idx, index_is_id=False, loc=-1): def get_property(self, idx, index_is_id=False, loc=-1):
row = self.data._data[idx] if index_is_id else self.data[idx] row = self.data._data[idx] if index_is_id else self.data[idx]
if row is not None: if row is not None:
@ -897,7 +901,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
''' '''
row = self.data._data[idx] if index_is_id else self.data[idx] row = self.data._data[idx] if index_is_id else self.data[idx]
fm = self.FIELD_MAP fm = self.FIELD_MAP
mi = Metadata(None) mi = Metadata(None, template_cache=self.formatter_template_cache)
aut_list = row[fm['au_map']] aut_list = row[fm['au_map']]
if aut_list: if aut_list:
@ -955,6 +959,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
mi.set_identifiers(self.get_identifiers(id, index_is_id=True)) mi.set_identifiers(self.get_identifiers(id, index_is_id=True))
mi.application_id = id mi.application_id = id
mi.id = id mi.id = id
for key, meta in self.field_metadata.custom_iteritems(): for key, meta in self.field_metadata.custom_iteritems():
mi.set_user_metadata(key, meta) mi.set_user_metadata(key, meta)
if meta['datatype'] == 'composite': if meta['datatype'] == 'composite':
@ -1312,10 +1317,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if path is None: if path is None:
path = os.path.join(self.library_path, self.path(id, index_is_id=True)) path = os.path.join(self.library_path, self.path(id, index_is_id=True))
name = self.conn.get('SELECT name FROM data WHERE book=? AND format=?', (id, format), all=False) name = self.conn.get('SELECT name FROM data WHERE book=? AND format=?', (id, format), all=False)
if name: if name and not replace:
if not replace: return False
return False
self.conn.execute('DELETE FROM data WHERE book=? AND format=?', (id, format))
name = self.construct_file_name(id) name = self.construct_file_name(id)
ext = ('.' + format.lower()) if format else '' ext = ('.' + format.lower()) if format else ''
dest = os.path.join(path, name+ext) dest = os.path.join(path, name+ext)
@ -1328,7 +1331,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
shutil.copyfileobj(stream, f) shutil.copyfileobj(stream, f)
stream.seek(0, 2) stream.seek(0, 2)
size=stream.tell() size=stream.tell()
self.conn.execute('INSERT INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)', self.conn.execute('INSERT OR REPLACE INTO data (book,format,uncompressed_size,name) VALUES (?,?,?,?)',
(id, format.upper(), size, name)) (id, format.upper(), size, name))
self.conn.commit() self.conn.commit()
self.refresh_ids([id]) self.refresh_ids([id])

View File

@ -635,9 +635,7 @@ class FieldMetadata(dict):
self._search_term_map[t] = key self._search_term_map[t] = key
def search_term_to_field_key(self, term): def search_term_to_field_key(self, term):
if term in self._search_term_map: return self._search_term_map.get(term, term)
return self._search_term_map[term]
return term
def searchable_fields(self): def searchable_fields(self):
return [k for k in self._tb_cats.keys() return [k for k in self._tb_cats.keys()

View File

@ -114,7 +114,7 @@ html_short_title = 'Start'
html_logo = 'resources/logo.png' html_logo = 'resources/logo.png'
epub_author = 'Kovid Goyal' epub_author = 'Kovid Goyal'
epub_cover = 'epub_cover.jpg' kovid_epub_cover = 'epub_cover.jpg'
epub_publisher = 'Kovid Goyal' epub_publisher = 'Kovid Goyal'
epub_identifier = 'http://manual.calibre-ebook.com' epub_identifier = 'http://manual.calibre-ebook.com'
epub_scheme = 'url' epub_scheme = 'url'

View File

@ -251,7 +251,7 @@ def template_docs(app):
update_cli_doc('template_ref.rst', raw, info) update_cli_doc('template_ref.rst', raw, info)
def setup(app): def setup(app):
app.add_config_value('epub_cover', None, False) app.add_config_value('kovid_epub_cover', None, False)
app.add_builder(EPUBHelpBuilder) app.add_builder(EPUBHelpBuilder)
app.connect('doctree-read', substitute) app.connect('doctree-read', substitute)
app.connect('builder-inited', generate_docs) app.connect('builder-inited', generate_docs)

View File

@ -136,7 +136,7 @@ the previously checked out |app| code directory, for example::
cd /Users/kovid/work/calibre cd /Users/kovid/work/calibre
calibre is the directory that contains the src and resources sub-directories. Ensure you have installed the |app| commandline tools via :guilabel:Preferences->Advanced->Miscellaneous in the |app| GUI. calibre is the directory that contains the src and resources sub-directories. Ensure you have installed the |app| commandline tools via :guilabel:`Preferences->Advanced->Miscellaneous` in the |app| GUI.
The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory. The next step is to set the environment variable ``CALIBRE_DEVELOP_FROM`` to the absolute path of the src directory.
So, following the example above, it would be ``/Users/kovid/work/calibre/src``. Apple So, following the example above, it would be ``/Users/kovid/work/calibre/src``. Apple

View File

@ -55,8 +55,8 @@ class EPUBHelpBuilder(EpubBuilder):
open(opf, 'wb').write(raw) open(opf, 'wb').write(raw)
def build_epub(self, outdir, *args, **kwargs): def build_epub(self, outdir, *args, **kwargs):
if self.config.epub_cover: if self.config.kovid_epub_cover:
self.add_cover(outdir, self.config.epub_cover) self.add_cover(outdir, self.config.kovid_epub_cover)
self.fix_duplication_bugs(outdir) self.fix_duplication_bugs(outdir)
EpubBuilder.build_epub(self, outdir, *args, **kwargs) EpubBuilder.build_epub(self, outdir, *args, **kwargs)

View File

@ -242,10 +242,6 @@ Replace ``192.168.1.2`` with the local IP address of the computer running |app|.
If you get timeout errors while browsing the calibre catalog in Stanza, try increasing the connection timeout value in the stanza settings. Go to Info->Settings and increase the value of Download Timeout. If you get timeout errors while browsing the calibre catalog in Stanza, try increasing the connection timeout value in the stanza settings. Go to Info->Settings and increase the value of Download Timeout.
.. note::
As of iOS version 5 Stanza no longer works on Apple devices. Alternatives to Stanza are discussed `in this forum <http://www.mobileread.com/forums/showthread.php?t=152789>`_.
Using iBooks Using iBooks
************** **************

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