diff --git a/resources/images/news/welt.png b/resources/images/news/welt.png
new file mode 100644
index 0000000000..27204dad42
Binary files /dev/null and b/resources/images/news/welt.png differ
diff --git a/resources/recipes/an_druma_mor.recipe b/resources/recipes/an_druma_mor.recipe
index 7ddd9a8134..d214cf8025 100644
--- a/resources/recipes/an_druma_mor.recipe
+++ b/resources/recipes/an_druma_mor.recipe
@@ -1,8 +1,8 @@
from calibre.web.feeds.news import BasicNewsRecipe
-class AdvancedUserRecipe1257775999(BasicNewsRecipe):
+class AnDrumaMor(BasicNewsRecipe):
title = u'An Druma M\xf3r'
- __author__ = "David O'Calaghan"
+ __author__ = "David O'Callaghan"
oldest_article = 7
max_articles_per_feed = 100
language = 'ga'
diff --git a/resources/recipes/barrons.recipe b/resources/recipes/barrons.recipe
index 8040fcc11f..e7df57c704 100644
--- a/resources/recipes/barrons.recipe
+++ b/resources/recipes/barrons.recipe
@@ -17,11 +17,11 @@ class Barrons(BasicNewsRecipe):
needs_subscription = True
language = 'en'
- __author__ = 'Kovid Goyal'
+ __author__ = 'Kovid Goyal and Sujata Raman'
description = 'Weekly publication for investors from the publisher of the Wall Street Journal'
timefmt = ' [%a, %b %d, %Y]'
use_embedded_content = False
- no_stylesheets = False
+ no_stylesheets = True
match_regexps = ['http://online.barrons.com/.*?html\?mod=.*?|file:.*']
conversion_options = {'linearize_tables': True}
##delay = 1
@@ -29,6 +29,20 @@ class Barrons(BasicNewsRecipe):
## Don't grab articles more than 7 days old
oldest_article = 7
+ extra_css = '''
+ .datestamp{color:#666666; font-family:Verdana,Geneva,Kalimati,sans-serif; font-size:x-small;}
+ h3{color:#FF0000; font-family:Georgia,"Times New Roman",Times,serif; }
+ h2{font-family:Georgia,"Times New Roman",Times,serif; }
+ h1{ font-family:Georgia,"Times New Roman",Times,serif; }
+ .byline{color:#AAAAAA; font-family:Verdana,Geneva,Kalimati,sans-serif; font-size:x-small;}
+ .subhead{color:#666666; font-family:Georgia,"Times New Roman",Times,serif; font-size: small;}
+ .articlePage{ font-family:Georgia,"Century Schoolbook","Times New Roman",Times,serif;color:#333333;}
+ .insettipUnit{font-size: x-small;}
+ '''
+ remove_tags = [
+ dict(name ='div', attrs={'class':['tabContainer artTabbedNav','rssToolBox hidden','articleToolbox']}),
+ dict(name = 'a', attrs ={'class':'insetClose'})
+ ]
preprocess_regexps = [(re.compile(i[0], re.IGNORECASE | re.DOTALL), i[1]) for i in
[
@@ -56,10 +70,20 @@ def get_browser(self):
br.submit()
return br
-## Use the print version of a page when available.
+ ## Use the print version of a page when available.
def print_version(self, url):
- return url.replace('/article/', '/article_print/')
+ main, sep, rest = url.rpartition('?')
+ return main + '#printmode'
+
+ def postprocess_html(self, soup, first):
+
+ for tag in soup.findAll(name=['ul', 'li']):
+ tag.name = 'div'
+ for tag in soup.findAll(name ='div', attrs={'id': "articleThumbnail_1"}):
+ tag.extract()
+
+ return soup
## Comment out the feeds you don't want retrieved.
## Because these feeds are sorted alphabetically when converted to LRF, you may want to number them to put them in the order you desire
@@ -74,6 +98,17 @@ def get_feeds(self):
('Funds/Q&A', 'http://online.barrons.com/xml/rss/3_7519.xml'),
]
+
+ def get_cover_url(self):
+ cover_url = None
+ index = 'http://online.barrons.com/home-page'
+ soup = self.index_to_soup(index)
+ link_item = soup.find('ul',attrs={'class':'newsItem barronsMag'})
+ if link_item:
+ cover_url = link_item.img['src']
+ return cover_url
+
+
## Logout of website
## NOT CURRENTLY WORKING
# def cleanup(self):
diff --git a/resources/recipes/criticadigital.recipe b/resources/recipes/criticadigital.recipe
index e1e5030a00..d1ef97aef9 100644
--- a/resources/recipes/criticadigital.recipe
+++ b/resources/recipes/criticadigital.recipe
@@ -10,7 +10,7 @@
class CriticaDigital(BasicNewsRecipe):
title = 'Critica de la Argentina'
- __author__ = 'Darko Miletic'
+ __author__ = 'Darko Miletic and Sujata Raman'
description = 'Noticias de Argentina'
oldest_article = 2
max_articles_per_feed = 100
@@ -20,17 +20,22 @@ class CriticaDigital(BasicNewsRecipe):
use_embedded_content = False
encoding = 'cp1252'
- html2lrf_options = [
- '--comment' , description
- , '--category' , 'news, Argentina'
- , '--publisher' , title
- ]
-
+ extra_css = '''
+ h1{font-family:"Trebuchet MS";}
+ h3{color:#9A0000; font-family:Tahoma; font-size:x-small;}
+ h2{color:#504E53; font-family:Arial,Helvetica,sans-serif ;font-size:small;}
+ #epigrafe{font-family:Arial,Helvetica,sans-serif ;color:#666666 ; font-size:x-small;}
+ p {font-family:Arial,Helvetica,sans-serif;}
+ #fecha{color:#858585; font-family:Tahoma; font-size:x-small;}
+ #autor{color:#858585; font-family:Tahoma; font-size:x-small;}
+ #hora{color:#F00000;font-family:Tahoma; font-size:x-small;}
+ '''
keep_only_tags = [
- dict(name='div', attrs={'class':'bloqueTitulosNoticia'})
- ,dict(name='div', attrs={'id':'c453-1' })
+ dict(name='div', attrs={'class':['bloqueTitulosNoticia','cfotonota']})
+ ,dict(name='div', attrs={'id':'boxautor'})
+ ,dict(name='p', attrs={'id':'textoNota'})
]
-
+
remove_tags = [
dict(name='div', attrs={'class':'box300' })
,dict(name='div', style=True )
@@ -38,7 +43,7 @@ class CriticaDigital(BasicNewsRecipe):
,dict(name='div', attrs={'class':'comentario' })
,dict(name='div', attrs={'class':'paginador' })
]
-
+
feeds = [
(u'Politica', u'http://www.criticadigital.com/herramientas/rss.php?ch=politica' )
,(u'Economia', u'http://www.criticadigital.com/herramientas/rss.php?ch=economia' )
@@ -60,3 +65,5 @@ def get_cover_url(self):
if link_item:
cover_url = index + link_item.img['src']
return cover_url
+
+
diff --git a/resources/recipes/infobae.recipe b/resources/recipes/infobae.recipe
index 78d00677b6..cda9bf83d2 100644
--- a/resources/recipes/infobae.recipe
+++ b/resources/recipes/infobae.recipe
@@ -5,55 +5,92 @@
'''
infobae.com
'''
+import re
+import urllib, urlparse
from calibre.web.feeds.news import BasicNewsRecipe
class Infobae(BasicNewsRecipe):
title = 'Infobae.com'
- __author__ = 'Darko Miletic'
+ __author__ = 'Darko Miletic and Sujata Raman'
description = 'Informacion Libre las 24 horas'
publisher = 'Infobae.com'
- category = 'news, politics, Argentina'
+ category = 'news, politics, Argentina'
oldest_article = 1
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
language = 'es'
+ lang = 'es-AR'
encoding = 'cp1252'
cover_url = 'http://www.infobae.com/imgs/header/header.gif'
remove_javascript = True
-
- html2lrf_options = [
- '--comment' , description
- , '--category' , category
- , '--publisher', publisher
- , '--ignore-tables'
- , '--ignore-colors'
- ]
-
+ preprocess_regexps = [(re.compile(
+ r''), lambda m:'')]
+
+
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True'
+ extra_css = '''
+ .col-center{font-family:Arial,Helvetica,sans-serif;}
+ h1{font-family:Arial,Helvetica,sans-serif; color:#0D4261;}
+ .fuenteIntNota{font-family:Arial,Helvetica,sans-serif; color:#1D1D1D; font-size:x-small;}
+ '''
+
+ keep_only_tags = [dict(name='div', attrs={'class':['content']})]
+
+
remove_tags = [
- dict(name=['embed','link','object'])
- ,dict(name='a', attrs={'onclick':'javascript:window.print()'})
- ]
-
- feeds = [
+ dict(name='div', attrs={'class':['options','col-right','controles', 'bannerLibre','tiulo-masleidas','masleidas-h']}),
+ dict(name='a', attrs={'name' : 'comentario',}),
+ dict(name='iframe'),
+ dict(name='img', alt = "Ver galerias de imagenes"),
+
+ ]
+
+
+ feeds = [
(u'Noticias' , u'http://www.infobae.com/adjuntos/html/RSS/hoy.xml' )
,(u'Salud' , u'http://www.infobae.com/adjuntos/html/RSS/salud.xml' )
,(u'Tecnologia', u'http://www.infobae.com/adjuntos/html/RSS/tecnologia.xml')
,(u'Deportes' , u'http://www.infobae.com/adjuntos/html/RSS/deportes.xml' )
]
- def print_version(self, url):
- main, sep, article_part = url.partition('contenidos/')
- article_id, rsep, rrest = article_part.partition('-')
- return u'http://www.infobae.com/notas/nota_imprimir.php?Idx=' + article_id
+# def print_version(self, url):
+# main, sep, article_part = url.partition('contenidos/')
+# article_id, rsep, rrest = article_part.partition('-')
+# return u'http://www.infobae.com/notas/nota_imprimir.php?Idx=' + article_id
+
+ def get_article_url(self, article):
+ ans = article.get('link').encode('utf-8')
+ parts = list(urlparse.urlparse(ans))
+ parts[2] = urllib.quote(parts[2])
+ ans = urlparse.urlunparse(parts)
+ return ans.decode('utf-8')
+
def preprocess_html(self, soup):
- mtag = '\n\n'
- soup.head.insert(0,mtag)
+
+ for tag in soup.head.findAll('strong'):
+ tag.extract()
+ for tag in soup.findAll('meta'):
+ del tag['content']
+ tag.extract()
+
+ mtag = '\n\n'
+ soup.head.insert(0,mtag)
for item in soup.findAll(style=True):
del item['style']
+
return soup
+
+ def postprocess_html(self, soup, first):
+
+ for tag in soup.findAll(name='strong'):
+ tag.name = 'b'
+
+ return soup
+
+
+
diff --git a/resources/recipes/npr.recipe b/resources/recipes/npr.recipe
new file mode 100644
index 0000000000..167c2125c7
--- /dev/null
+++ b/resources/recipes/npr.recipe
@@ -0,0 +1,19 @@
+from calibre.web.feeds.news import BasicNewsRecipe
+
+class AdvancedUserRecipe1257302745(BasicNewsRecipe):
+ title = u'National Public Radio'
+ oldest_article = 7
+ language = 'en'
+ __author__ = 'onyxrev'
+ max_articles_per_feed = 100
+
+ remove_tags_before = {'class':'storytitle'}
+ remove_tags_after = dict(name='div', attrs={'id':'storytext' })
+ remove_tags = [
+ dict(name='body', attrs={'id':'blog'}),
+ dict(name='div', attrs={'class':'enlarge_measure'}),
+ dict(name='div', attrs={'class':'enlarge_html'}),
+ dict(name='div', attrs={'class':'bucketwrap primary'})
+ ]
+
+ feeds = [(u'National Public Radio', u'http://www.npr.org/rss/rss.php?id=1001')]
diff --git a/resources/recipes/nytimes.recipe b/resources/recipes/nytimes.recipe
index 8b95c8432b..89d5656741 100644
--- a/resources/recipes/nytimes.recipe
+++ b/resources/recipes/nytimes.recipe
@@ -14,7 +14,7 @@ class NYTimes(BasicNewsRecipe):
title = 'New York Times Top Stories'
__author__ = 'GRiker'
- language = 'en'
+ language = _('English')
description = 'Top Stories from the New York Times'
# List of sections typically included in Top Stories. Use a keyword from the
@@ -79,6 +79,14 @@ class NYTimes(BasicNewsRecipe):
.authorId {text-align: left; \
font-style: italic;}\n '
+# def get_cover_url(self):
+# st = time.localtime()
+# year = str(st.tm_year)
+# month = "%.2d" % st.tm_mon
+# day = "%.2d" % st.tm_mday
+# cover = 'http://graphics8.nytimes.com/images/' + year + '/' + month +'/' + day +'/nytfrontpage/' + 'scan.jpg'
+# return cover
+
def get_browser(self):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
@@ -202,21 +210,25 @@ def parse_index(self):
# Get the bylines and descriptions
if not skipThisSection :
- for (x,i) in enumerate(sectionblock.contents) :
+ lines = sectionblock.contents
+ contentStrings = []
- # Extract the bylines and descriptions
- if (i.string is not None) and \
- (i.string.strip() > "") and \
- not isinstance(i,Comment):
- contentString = i.strip().encode('utf-8')
- if contentString[0:3] == 'By ' and contentString[4].isupper() :
- bylines.append(contentString)
- else :
- descriptions.append(contentString)
+ for line in lines:
+ if not isinstance(line, Comment) and line.strip and line.strip() > "":
+ contentStrings.append(line.strip())
+
+ # Gather the byline/description pairs
+ bylines = []
+ descriptions = []
+ for contentString in contentStrings:
+ if contentString[0:3] == 'By ' and contentString[3].isupper() :
+ bylines.append(contentString)
+ else:
+ descriptions.append(contentString)
# Fetch the article titles and URLs
articleCount = len(sectionblock.findAll('span'))
- for (i,span) in enumerate(sectionblock.findAll('span')) :
+ for (i,span) in enumerate(sectionblock.findAll(attrs={'class':'headlineWrapper'})) :
a = span.find('a', href=True)
url = re.sub(r'\?.*', '', a['href'])
url += '?pagewanted=all'
@@ -228,7 +240,11 @@ def parse_index(self):
if not isinstance(title, unicode):
title = title.decode('utf-8', 'replace')
- description = descriptions[i]
+ # Allow for unattributed, undescribed entries "Editor's Note"
+ if i >= len(descriptions) :
+ description = None
+ else :
+ description = descriptions[i]
if len(bylines) == articleCount :
author = bylines[i]
@@ -291,7 +307,7 @@ def preprocess_html(self, soup):
return soup
else:
print "no allowed content found, removing article"
- raise Exception()
+ raise Exception
def postprocess_html(self,soup, True):
diff --git a/resources/recipes/spiegel_int.recipe b/resources/recipes/spiegel_int.recipe
index ce54342cd0..7af5c8a41e 100644
--- a/resources/recipes/spiegel_int.recipe
+++ b/resources/recipes/spiegel_int.recipe
@@ -8,9 +8,10 @@
from calibre.web.feeds.news import BasicNewsRecipe
+
class Spiegel_int(BasicNewsRecipe):
title = 'Spiegel Online International'
- __author__ = 'Darko Miletic'
+ __author__ = 'Darko Miletic and Sujata Raman'
description = "News and POV from Europe's largest newsmagazine"
oldest_article = 7
max_articles_per_feed = 100
@@ -21,8 +22,9 @@ class Spiegel_int(BasicNewsRecipe):
publisher = 'SPIEGEL ONLINE GmbH'
category = 'news, politics, Germany'
lang = 'en'
-
- conversion_options = {
+ recursions = 1
+ match_regexps = [r'http://www.spiegel.de/.*-[1-9],00.html']
+ conversion_options = {
'comments' : description
,'tags' : category
,'language' : lang
@@ -30,11 +32,63 @@ class Spiegel_int(BasicNewsRecipe):
,'pretty_print': True
}
- remove_tags_after = dict(name='div', attrs={'id':'spArticleBody'})
+ extra_css = '''
+ #spArticleColumn{font-family:verdana,arial,helvetica,geneva,sans-serif ; }
+ h1{color:#666666; font-weight:bold;}
+ h2{color:#990000;}
+ h3{color:#990000;}
+ h4 {color:#990000;}
+ a{color:#990000;}
+ .spAuthor{font-style:italic;}
+ #spIntroTeaser{font-weight:bold;}
+ .spCredit{color:#666666; font-size:x-small;}
+ .spShortDate{font-size:x-small;}
+ .spArticleImageBox {font-size:x-small;}
+ .spPhotoGallery{font-size:x-small; color:#990000 ;}
+ '''
+
+ keep_only_tags = [
+ dict(name ='div', attrs={'id': ['spArticleImageBox spAssetAlignleft','spArticleColumn']}),
+ ]
+
+ remove_tags = [
+ dict(name='div', attrs={'id':['spSocialBookmark','spArticleFunctions','spMultiPagerHeadlines',]}),
+ dict(name='div', attrs={'class':['spCommercial spM520','spArticleCredit','spPicZoom']}),
+ ]
feeds = [(u'Spiegel Online', u'http://www.spiegel.de/schlagzeilen/rss/0,5291,676,00.xml')]
- def print_version(self, url):
- main, sep, rest = url.rpartition(',')
- rmain, rsep, rrest = main.rpartition(',')
- return rmain + ',druck-' + rrest + ',' + rest
+ def postprocess_html(self, soup,first):
+
+ for tag in soup.findAll(name='div',attrs={'id':"spMultiPagerControl"}):
+ tag.extract()
+
+ p = soup.find(name = 'p', attrs={'id':'spIntroTeaser'})
+
+ if p.string is not None:
+ t = p.string.rpartition(':')[0]
+
+ if 'Part'in t:
+ if soup.h1 is not None:
+ soup.h1.extract()
+ if soup.h2 is not None:
+ soup.h2.extract()
+ functag = soup.find(name= 'div', attrs={'id':"spArticleFunctions"})
+ if functag is not None:
+ functag.extract()
+ auttag = soup.find(name= 'p', attrs={'class':"spAuthor"})
+ if auttag is not None:
+ auttag.extract()
+
+ pictag = soup.find(name= 'div', attrs={'id':"spArticleTopAsset"})
+ if pictag is not None:
+ pictag.extract()
+
+
+ return soup
+
+ # def print_version(self, url):
+ # main, sep, rest = url.rpartition(',')
+ # rmain, rsep, rrest = main.rpartition(',')
+ # return rmain + ',druck-' + rrest + ',' + rest
+
diff --git a/resources/recipes/welt.recipe b/resources/recipes/welt.recipe
new file mode 100644
index 0000000000..b1bf20235d
--- /dev/null
+++ b/resources/recipes/welt.recipe
@@ -0,0 +1,87 @@
+__license__ = 'GPL v3'
+__copyright__ = '2008, Kovid Goyal '
+
+'''
+Fetch Weltonline.
+'''
+
+from calibre.web.feeds.news import BasicNewsRecipe
+
+
+class weltDe(BasicNewsRecipe):
+
+ title = 'Weltonline'
+ description = 'german newspaper'
+ __author__ = 'Oliver Niesner'
+ use_embedded_content = False
+ timefmt = ' [%d %b %Y]'
+ max_articles_per_feed = 15 # reduced to this value to prevent too many articles (suggested by Gregory Riker
+ no_stylesheets = True
+ remove_stylesheets = True
+ remove_javascript = True
+ language = 'de'
+ encoding = 'iso-8859-1'
+
+
+ remove_tags = [dict(id='jumplinks'),
+ dict(id='ad1'),
+ dict(id='fullimage_index'),
+ dict(id='additionalNav'),
+ dict(id='printMenu'),
+ dict(id='topteaser1'),
+ dict(id='topteaser2'),
+ dict(id='servicesBox'),
+ dict(id='servicesNav'),
+ dict(id='ad2'),
+ dict(id='brandingWrapper'),
+ dict(id='links-intern'),
+ dict(id='navigation'),
+ dict(id='subNav'),
+ dict(id='branding'),
+ dict(id='servicesNav'),
+ dict(id='searchArea'),
+ dict(id='servicesBox'),
+ dict(id='toggleAdvancedSearch'),
+ dict(id='mainNav'),
+ dict(id='ratingBox5136466_1'),
+ dict(id='ratingBox5136466_2'),
+ dict(id='articleInlineMediaBox0'),
+ dict(id='sectionSponsor'),
+ #dict(id=''),
+ dict(name='span'),
+ dict(name='div', attrs={'class':'printURL'}),
+ dict(name='div', attrs={'class':'ad'}),
+ dict(name='div', attrs={'class':'inlineBox inlineFurtherLinks'}),
+ dict(name='div', attrs={'class':'inlineBox videoInlineBox'}),
+ dict(name='div', attrs={'class':'inlineGallery'}),
+ dict(name='div', attrs={'class':'ratingBox'}),
+ dict(name='div', attrs={'class':'socialBookmarks clear'}),
+ dict(name='div', attrs={'class':'articleOptions clear'}),
+ dict(name='div', attrs={'class':'noPrint galleryIndex'}),
+ dict(name='div', attrs={'class':'inlineBox inlineTagCloud'}),
+ dict(name='p', attrs={'class':'jump'}),
+ dict(name='a', attrs={'class':'commentLink'}),
+ dict(name='h2', attrs={'class':'jumpHeading'}),
+ dict(name='ul', attrs={'class':'optionsSubNav clear'}),
+ dict(name='li', attrs={'class':'next'}),
+ dict(name='li', attrs={'class':'prev'}),
+ dict(name='li', attrs={'class':'active'})]
+
+ remove_tags_after = [dict(id='tw_link_widget')]
+
+ feeds = [ ('Politik', 'http://welt.de/politik/?service=Rss'),
+ ('Deutsche Dinge', 'http://www.welt.de/deutsche-dinge/?service=Rss'),
+ ('Wirtschaft', 'http://welt.de/wirtschaft/?service=Rss'),
+ ('Finanzen', 'http://welt.de/finanzen/?service=Rss'),
+ ('Sport', 'http://welt.de/sport/?service=Rss'),
+ ('Webwelt', 'http://welt.de/webwelt/?service=Rss'),
+ ('Kultur', 'http://welt.de/kultur/?service=Rss'),
+ ('Literarische Welt', 'http://welt.de/kultur/literarischewelt/?service=Rss'),
+ ('Wissenschaft', 'http://welt.de/wissenschaft/?service=Rss'),
+ ('Satire', 'http://welt.de/satire/?service=Rss'),
+ ('Motor', 'http://welt.de/motor/?service=Rss'),
+ ('Vermischtes', 'http://welt.de/vermischtes/?service=Rss')]
+
+
+ def print_version(self, url):
+ return url.replace ('.html', '.html?print=yes')
diff --git a/resources/recipes/zaobao.recipe b/resources/recipes/zaobao.recipe
index ef4221e896..bce594bafa 100644
--- a/resources/recipes/zaobao.recipe
+++ b/resources/recipes/zaobao.recipe
@@ -97,7 +97,8 @@ def parse_feeds(self):
})
pfeeds = feeds_from_index([(title, articles)], oldest_article=self.oldest_article,
- max_articles_per_feed=self.max_articles_per_feed)
+ max_articles_per_feed=self.max_articles_per_feed,
+ log=self.log)
self.log.debug('adding %s to feed'%(title))
for feed in pfeeds:
diff --git a/setup/publish.py b/setup/publish.py
index 8eadf7c281..bc42b07346 100644
--- a/setup/publish.py
+++ b/setup/publish.py
@@ -44,8 +44,8 @@ class Stage3(Command):
description = 'Stage 3 of the publish process'
sub_commands = ['upload_rss', 'upload_user_manual', 'upload_demo', 'sdist',
- 'upload_to_sourceforge', 'upload_to_google_code', 'tag_release',
- 'upload_to_server', 'upload_to_mobileread',
+ 'upload_to_google_code', 'tag_release', 'upload_to_server',
+ 'upload_to_sourceforge', 'upload_to_mobileread',
]
class Stage4(Command):
diff --git a/setup/upload.py b/setup/upload.py
index 52b8e8f49b..03ed9a9f5c 100644
--- a/setup/upload.py
+++ b/setup/upload.py
@@ -200,22 +200,10 @@ class UploadToSourceForge(Command):
PROJECT = 'calibre'
BASE = '/home/frs/project/c/ca/'+PROJECT
- def create(self):
- self.info('Creating shell...')
- check_call(['ssh', '-x',
- '%s,%s@shell.sourceforge.net'%(self.USERNAME, self.PROJECT),
- 'create'])
-
@property
def rdir(self):
return self.BASE+'/'+__version__
- def mk_release_dir(self):
- self.info('Creating release directory...')
- check_call(['ssh', '-x',
- '%s,%s@shell.sourceforge.net'%(self.USERNAME, self.PROJECT),
- 'mkdir', '-p', self.rdir])
-
def upload_installers(self):
for x in installers():
if not os.path.exists(x): continue
@@ -226,8 +214,6 @@ def upload_installers(self):
def run(self, opts):
self.opts = opts
- self.create()
- self.mk_release_dir()
self.upload_installers()
diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py
index 2c3d14e2c2..ccef0f39e4 100644
--- a/src/calibre/__init__.py
+++ b/src/calibre/__init__.py
@@ -193,7 +193,7 @@ def extract(path, dir):
raise Exception('Unknown archive type')
extractor(path, dir)
-def get_proxies():
+def get_proxies(debug=True):
proxies = {}
for q in ('http', 'ftp'):
@@ -226,10 +226,40 @@ def get_proxies():
if len(proxies[x]) < 5:
prints('Removing invalid', x, 'proxy:', proxies[x])
del proxies[x]
- if proxies:
+ if proxies and debug:
prints('Using proxies:', proxies)
return proxies
+def get_parsed_proxy(typ='http', debug=True):
+ proxies = get_proxies(debug)
+ if typ not in proxies:
+ return
+ pattern = re.compile((
+ '(?:ptype://)?' \
+ '(?:(?P\w+):(?P.*)@)?' \
+ '(?P[\w\-\.]+)' \
+ '(?::(?P\d+))?').replace('ptype', typ)
+ )
+
+ match = pattern.match(proxies['typ'])
+ if match:
+ try:
+ ans = {
+ 'host' : match.group('host'),
+ 'port' : match.group('port'),
+ 'user' : match.group('user'),
+ 'pass' : match.group('pass')
+ }
+ if ans['port']:
+ ans['port'] = int(ans['port'])
+ except:
+ if debug:
+ traceback.print_exc()
+ return
+ if debug:
+ prints('Using http proxy', ans)
+ return ans
+
def browser(honor_time=True, max_time=2, mobile_browser=False):
'''
diff --git a/src/calibre/constants.py b/src/calibre/constants.py
index d2efd4364d..c5871d4a49 100644
--- a/src/calibre/constants.py
+++ b/src/calibre/constants.py
@@ -2,7 +2,7 @@
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
-__version__ = '0.6.21'
+__version__ = '0.6.23'
__author__ = "Kovid Goyal "
import re
diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py
index 061a4409a6..45a9bd29d1 100644
--- a/src/calibre/customize/builtins.py
+++ b/src/calibre/customize/builtins.py
@@ -372,7 +372,8 @@ def set_metadata(self, stream, mi, type):
from calibre.devices.bebook.driver import BEBOOK, BEBOOK_MINI
from calibre.devices.blackberry.driver import BLACKBERRY
from calibre.devices.cybookg3.driver import CYBOOKG3, CYBOOK_OPUS
-from calibre.devices.eb600.driver import EB600, COOL_ER, SHINEBOOK
+from calibre.devices.eb600.driver import EB600, COOL_ER, SHINEBOOK, \
+ POCKETBOOK360
from calibre.devices.iliad.driver import ILIAD
from calibre.devices.irexdr.driver import IREXDR1000
from calibre.devices.jetbook.driver import JETBOOK
@@ -385,8 +386,8 @@ def set_metadata(self, stream, mi, type):
from calibre.devices.nuut2.driver import NUUT2
from calibre.devices.iriver.driver import IRIVER_STORY
-from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB
-plugins = [HTML2ZIP, GoogleBooks, ISBNDB]
+from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon
+plugins = [HTML2ZIP, GoogleBooks, ISBNDB, Amazon]
plugins += [
ComicInput,
EPUBInput,
@@ -441,7 +442,8 @@ def set_metadata(self, stream, mi, type):
SHINEBOOK,
ESLICK,
NUUT2,
- IRIVER_STORY
+ IRIVER_STORY,
+ POCKETBOOK360
]
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
x.__name__.endswith('MetadataReader')]
diff --git a/src/calibre/customize/ui.py b/src/calibre/customize/ui.py
index 66716fe4bb..11810b1644 100644
--- a/src/calibre/customize/ui.py
+++ b/src/calibre/customize/ui.py
@@ -90,9 +90,10 @@ def output_profiles():
if isinstance(plugin, OutputProfile):
yield plugin
-def metadata_sources(customize=True, isbndb_key=None):
+def metadata_sources(metadata_type='basic', customize=True, isbndb_key=None):
for plugin in _initialized_plugins:
- if isinstance(plugin, MetadataSource):
+ if isinstance(plugin, MetadataSource) and \
+ plugin.metadata_type == metadata_type:
if is_disabled(plugin):
continue
if customize:
diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py
index ae0f4ba05e..2a55097c14 100644
--- a/src/calibre/devices/eb600/driver.py
+++ b/src/calibre/devices/eb600/driver.py
@@ -82,3 +82,13 @@ class SHINEBOOK(EB600):
VENDOR_NAME = 'LONGSHIN'
WINDOWS_MAIN_MEM = 'ESHINEBOOK'
+class POCKETBOOK360(EB600):
+
+ name = 'PocketBook 360 Device Interface'
+
+ gui_name = 'PocketBook 360'
+
+ FORMATS = ['epub', 'fb2', 'pdf', 'djvu', 'rtf', 'chm', 'txt']
+
+ VENDOR_NAME = 'PHILIPS'
+ WINDOWS_MAIN_MEM = 'MASS_STORGE'
diff --git a/src/calibre/ebooks/metadata/amazon.py b/src/calibre/ebooks/metadata/amazon.py
index 102441e844..e988fb8234 100644
--- a/src/calibre/ebooks/metadata/amazon.py
+++ b/src/calibre/ebooks/metadata/amazon.py
@@ -6,45 +6,83 @@
'''
Fetch metadata using Amazon AWS
'''
-import re
+import sys, re
+from datetime import datetime
+
+from lxml import etree
+from dateutil import parser
from calibre import browser
+from calibre.ebooks.metadata import MetaInformation, string_to_authors
+
+AWS_NS = 'http://webservices.amazon.com/AWSECommerceService/2005-10-05'
+
+def AWS(tag):
+ return '{%s}%s'%(AWS_NS, tag)
+
+def check_for_errors(root):
+ err = root.find('.//'+AWS('Error'))
+ if err is not None:
+ raise Exception('Failed to get metadata with error: '\
+ + etree.tostring(err, method='text', pretty_print=True,
+ encoding=unicode))
+
+def get_social_metadata(title, authors, publisher, isbn):
+ mi = MetaInformation(title, authors)
+ if isbn:
+ br = browser()
+ response_xml = br.open('http://status.calibre-ebook.com/aws/metadata/'+isbn).read()
+ root = etree.fromstring(response_xml)
+ check_for_errors(root)
+ mi.title = root.findtext('.//'+AWS('Title'))
+ authors = [x.text for x in root.findall('.//'+AWS('Author'))]
+ if authors:
+ mi.authors = []
+ for x in authors:
+ mi.authors.extend(string_to_authors(x))
+ mi.publisher = root.findtext('.//'+AWS('Publisher'))
+ try:
+ d = root.findtext('.//'+AWS('PublicationDate'))
+ if d:
+ default = datetime.utcnow()
+ default = datetime(default.year, default.month, 15)
+ d = parser.parse(d[0].text, default=default)
+ mi.pubdate = d
+ except:
+ pass
+ try:
+ rating = float(root.findtext('.//'+AWS('AverageRating')))
+ num_of_reviews = int(root.findtext('.//'+AWS('TotalReviews')))
+ if num_of_reviews > 4 and rating > 0 and rating < 5:
+ mi.rating = rating
+ except:
+ pass
+ tags = [x.text for x in root.findall('.//%s/%s'%(AWS('Subjects'),
+ AWS('Subject')))]
+ if tags:
+ mi.tags = []
+ for x in tags:
+ mi.tags.extend([y.strip() for y in x.split('/')])
+ comments = root.find('.//%s/%s'%(AWS('EditorialReview'),
+ AWS('Content')))
+ if comments is not None:
+ mi.comments = etree.tostring(comments,
+ method='text', encoding=unicode)
+ mi.comments = re.sub('<([pP]|DIV)>', '\n\n', mi.comments)
+ mi.comments = re.sub('?[iI]>', '*', mi.comments)
+ mi.comments = re.sub('?[bB]>', '**', mi.comments)
+ mi.comments = re.sub('
', '\n\n', mi.comments)
+ mi.comments = re.sub('<[^>]+>', '', mi.comments)
+ mi.comments = mi.comments.strip()
+ mi.comments = _('EDITORIAL REVIEW')+':\n\n'+mi.comments
+
+ return mi
-BASE_URL = 'http://ecs.amazonaws.com/onca/xml?Service=AWSECommerceService&AWSAccessKeyId=%(key)s&Operation=ItemLookup&ItemId=1416551727&ResponseGroup=%(group)s'
-
-import sys
-
-def get_rating(isbn, key):
- br = browser()
- url = BASE_URL%dict(key=key, group='Reviews')
- raw = br.open(url).read()
- match = re.search(r'([\d.]+)', raw)
- if match:
- return float(match.group(1))
-
-def get_cover_url(isbn, key):
- br = browser()
- url = BASE_URL%dict(key=key, group='Images')
- raw = br.open(url).read()
- match = re.search(r'(.+?)', raw)
- if match:
- return match.group(1)
-
-def get_editorial_review(isbn, key):
- br = browser()
- url = BASE_URL%dict(key=key, group='EditorialReview')
- raw = br.open(url).read()
- match = re.compile(r'.*?(.+?)', re.DOTALL).search(raw)
- if match:
- return match.group(1)
def main(args=sys.argv):
- print 'Rating:', get_rating(args[1], args[2])
- print 'Cover:', get_rating(args[1], args[2])
- print 'EditorialReview:', get_editorial_review(args[1], args[2])
-
+ print get_social_metadata(None, None, None, '9781416551720')
return 0
if __name__ == '__main__':
- sys.exit(main())
\ No newline at end of file
+ sys.exit(main())
diff --git a/src/calibre/ebooks/metadata/fetch.py b/src/calibre/ebooks/metadata/fetch.py
index 5c90914bee..0b6753fd1b 100644
--- a/src/calibre/ebooks/metadata/fetch.py
+++ b/src/calibre/ebooks/metadata/fetch.py
@@ -6,7 +6,7 @@
import traceback, sys, textwrap, re
from threading import Thread
-from calibre import preferred_encoding
+from calibre import prints
from calibre.utils.config import OptionParser
from calibre.utils.logging import default_log
@@ -15,7 +15,14 @@
class MetadataSource(Plugin):
author = 'Kovid Goyal'
+
supported_platforms = ['windows', 'osx', 'linux']
+
+ #: The type of metadata fetched. 'basic' means basic metadata like
+ #: title/author/isbn/etc. 'social' means social metadata like
+ #: tags/rating/reviews/etc.
+ metadata_type = 'basic'
+
type = _('Metadata download')
def __call__(self, title, author, publisher, isbn, verbose, log=None,
@@ -49,12 +56,11 @@ def is_ok(self):
def join(self):
return self.worker.join()
+
class GoogleBooks(MetadataSource):
name = 'Google Books'
-
- def is_ok(self):
- return bool(self.site_customization)
+ description = _('Downloads metadata from Google Books')
def fetch(self):
from calibre.ebooks.metadata.google_books import search
@@ -70,6 +76,7 @@ def fetch(self):
class ISBNDB(MetadataSource):
name = 'IsbnDB'
+ description = _('Downloads metadata from isbndb.com')
def fetch(self):
if not self.site_customization:
@@ -104,6 +111,23 @@ def customization_help(self, gui=False):
ans = ans.replace('%s', '')
return ans
+class Amazon(MetadataSource):
+
+ name = 'Amazon'
+ metadata_type = 'social'
+ description = _('Downloads social metadata from amazon.com')
+
+ def fetch(self):
+ if not self.isbn:
+ return
+ from calibre.ebooks.metadata.amazon import get_social_metadata
+ try:
+ self.results = get_social_metadata(self.title, self.author,
+ self.publisher, self.isbn)
+ except Exception, e:
+ self.exception = e
+ self.tb = traceback.format_exc()
+
def result_index(source, result):
if not result.isbn:
return -1
@@ -134,16 +158,58 @@ def search(title=None, author=None, publisher=None, isbn=None, isbndb_key=None,
fetcher(title, author, publisher, isbn, verbose)
for fetcher in fetchers:
fetcher.join()
+ results = list(fetchers[0].results)
for fetcher in fetchers[1:]:
- merge_results(fetchers[0].results, fetcher.results)
+ merge_results(results, fetcher.results)
- results = sorted(fetchers[0].results, cmp=lambda x, y : cmp(
+ results = sorted(results, cmp=lambda x, y : cmp(
(x.comments.strip() if x.comments else ''),
(y.comments.strip() if y.comments else '')
), reverse=True)
return results, [(x.name, x.exception, x.tb) for x in fetchers]
+def get_social_metadata(mi, verbose=0):
+ from calibre.customize.ui import metadata_sources
+ fetchers = list(metadata_sources(metadata_type='social'))
+ for fetcher in fetchers:
+ fetcher(mi.title, mi.authors, mi.publisher, mi.isbn, verbose)
+ for fetcher in fetchers:
+ fetcher.join()
+ ratings, tags, comments = [], set([]), set([])
+ for fetcher in fetchers:
+ if fetcher.results:
+ dmi = fetcher.results
+ if dmi.rating is not None:
+ ratings.append(dmi.rating)
+ if dmi.tags:
+ for t in dmi.tags:
+ tags.add(t)
+ if mi.pubdate is None and dmi.pubdate is not None:
+ mi.pubdate = dmi.pubdate
+ if dmi.comments:
+ comments.add(dmi.comments)
+ if ratings:
+ rating = sum(ratings)/float(len(ratings))
+ if mi.rating is None:
+ mi.rating = rating
+ else:
+ mi.rating = (mi.rating + rating)/2.0
+ if tags:
+ if not mi.tags:
+ mi.tags = []
+ mi.tags += list(tags)
+ mi.tags = list(sorted(list(set(mi.tags))))
+ if comments:
+ if not mi.comments or len(mi.comments)+20 < len(' '.join(comments)):
+ mi.comments = ''
+ for x in comments:
+ mi.comments += x+'\n\n'
+
+ return [(x.name, x.exception, x.tb) for x in fetchers if x.exception is not
+ None]
+
+
def option_parser():
parser = OptionParser(textwrap.dedent(
@@ -174,11 +240,13 @@ def main(args=sys.argv):
opts, args = parser.parse_args(args)
results, exceptions = search(opts.title, opts.author, opts.publisher,
opts.isbn, opts.isbndb_key, opts.verbose)
+ social_exceptions = []
for result in results:
- print unicode(result).encode(preferred_encoding)
+ social_exceptions.extend(get_social_metadata(result, opts.verbose))
+ prints(unicode(result))
print
- for name, exception, tb in exceptions:
+ for name, exception, tb in exceptions+social_exceptions:
if exception is not None:
print 'WARNING: Fetching from', name, 'failed with error:'
print exception
diff --git a/src/calibre/ebooks/metadata/google_books.py b/src/calibre/ebooks/metadata/google_books.py
index fea3117f77..a02e13add6 100644
--- a/src/calibre/ebooks/metadata/google_books.py
+++ b/src/calibre/ebooks/metadata/google_books.py
@@ -135,7 +135,11 @@ def get_identifiers(self, entry, mi):
def get_tags(self, entry, verbose):
try:
- tags = [x.text for x in subject(entry)]
+ btags = [x.text for x in subject(entry)]
+ tags = []
+ for t in btags:
+ tags.extend([y.strip() for y in t.split('/')])
+ tags = list(sorted(list(set(tags))))
except:
report(verbose)
tags = []
diff --git a/src/calibre/ebooks/metadata/isbndb.py b/src/calibre/ebooks/metadata/isbndb.py
index b54bab7f98..d9f376c83d 100644
--- a/src/calibre/ebooks/metadata/isbndb.py
+++ b/src/calibre/ebooks/metadata/isbndb.py
@@ -125,7 +125,16 @@ def create_books(opts, args, timeout=5.):
if opts.verbose:
print ('ISBNDB query: '+url)
- return [ISBNDBMetadata(book) for book in fetch_metadata(url, timeout=timeout)]
+ tans = [ISBNDBMetadata(book) for book in fetch_metadata(url, timeout=timeout)]
+ ans = []
+ for x in tans:
+ add = True
+ for y in ans:
+ if y.isbn == x.isbn:
+ add = False
+ if add:
+ ans.append(x)
+ return ans
def main(args=sys.argv):
parser = option_parser()
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 51d88b06e2..01ce266e4f 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -83,6 +83,8 @@ def _config():
help='Search history for the recipe scheduler')
c.add_opt('worker_limit', default=6,
help=_('Maximum number of waiting worker processes'))
+ c.add_opt('get_social_metadata', default=True,
+ help=_('Download social metadata (tags/rating/etc.)'))
return ConfigProxy(c)
diff --git a/src/calibre/gui2/dialogs/config/__init__.py b/src/calibre/gui2/dialogs/config/__init__.py
index 62f68054f1..4f84ae968c 100644
--- a/src/calibre/gui2/dialogs/config/__init__.py
+++ b/src/calibre/gui2/dialogs/config/__init__.py
@@ -455,6 +455,7 @@ def __init__(self, window, db, server=None):
self.opt_worker_limit.setValue(config['worker_limit'])
self.connect(self.button_open_config_dir, SIGNAL('clicked()'),
self.open_config_dir)
+ self.opt_get_social_metadata.setChecked(config['get_social_metadata'])
def open_config_dir(self):
from calibre.utils.config import config_dir
@@ -740,6 +741,7 @@ def accept(self):
config['delete_news_from_library_on_upload'] = self.delete_news.isChecked()
config['upload_news_to_device'] = self.sync_news.isChecked()
config['search_as_you_type'] = self.search_as_you_type.isChecked()
+ config['get_social_metadata'] = self.opt_get_social_metadata.isChecked()
fmts = []
for i in range(self.viewer.count()):
if self.viewer.item(i).checkState() == Qt.Checked:
diff --git a/src/calibre/gui2/dialogs/config/config.ui b/src/calibre/gui2/dialogs/config/config.ui
index c8044887ec..8f9c28aa8b 100644
--- a/src/calibre/gui2/dialogs/config/config.ui
+++ b/src/calibre/gui2/dialogs/config/config.ui
@@ -90,7 +90,7 @@
0
0
524
- 682
+ 680
@@ -164,6 +164,13 @@
+ -
+
+
+ Download &social metadata (tags/ratings/etc.) by default
+
+
+
-
-
diff --git a/src/calibre/gui2/dialogs/config/social.py b/src/calibre/gui2/dialogs/config/social.py
new file mode 100644
index 0000000000..6a767e7b3b
--- /dev/null
+++ b/src/calibre/gui2/dialogs/config/social.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+from __future__ import with_statement
+
+__license__ = 'GPL v3'
+__copyright__ = '2009, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+
+from PyQt4.Qt import QDialog, QDialogButtonBox, Qt, QLabel, QVBoxLayout, \
+ SIGNAL, QThread
+
+from calibre.ebooks.metadata import MetaInformation
+
+class Worker(QThread):
+
+ def __init__(self, mi, parent):
+ QThread.__init__(self, parent)
+ self.mi = MetaInformation(mi)
+ self.exceptions = []
+
+ def run(self):
+ from calibre.ebooks.metadata.fetch import get_social_metadata
+ self.exceptions = get_social_metadata(self.mi)
+
+class SocialMetadata(QDialog):
+
+ def __init__(self, mi, parent):
+ QDialog.__init__(self, parent)
+
+ self.bbox = QDialogButtonBox(QDialogButtonBox.Ok, Qt.Horizontal, self)
+ self.mi = mi
+ self.layout = QVBoxLayout(self)
+ self.label = QLabel(_('Downloading social metadata, please wait...'), self)
+ self.label.setWordWrap(True)
+ self.layout.addWidget(self.label)
+ self.layout.addWidget(self.bbox)
+
+ self.worker = Worker(mi, self)
+ self.connect(self.worker, SIGNAL('finished()'), self.accept)
+ self.connect(self.bbox, SIGNAL('rejected()'), self.reject)
+ self.worker.start()
+
+ def reject(self):
+ self.disconnect(self.worker, SIGNAL('finished()'), self.accept)
+ QDialog.reject(self)
+
+ def accept(self):
+ self.mi.tags = self.worker.mi.tags
+ self.mi.rating = self.worker.mi.rating
+ self.mi.comments = self.worker.mi.comments
+ QDialog.accept(self)
+
+ @property
+ def exceptions(self):
+ return self.worker.exceptions
diff --git a/src/calibre/gui2/dialogs/fetch_metadata.py b/src/calibre/gui2/dialogs/fetch_metadata.py
index d092c1f5c4..a9de5b131c 100644
--- a/src/calibre/gui2/dialogs/fetch_metadata.py
+++ b/src/calibre/gui2/dialogs/fetch_metadata.py
@@ -11,7 +11,7 @@
from PyQt4.QtGui import QDialog, QItemSelectionModel
from calibre.gui2.dialogs.fetch_metadata_ui import Ui_FetchMetadata
-from calibre.gui2 import error_dialog, NONE, info_dialog
+from calibre.gui2 import error_dialog, NONE, info_dialog, config
from calibre.gui2.widgets import ProgressIndicator
from calibre import strftime
from calibre.customize.ui import get_isbndb_key, set_isbndb_key
@@ -101,7 +101,10 @@ def __init__(self, parent, isbn, title, author, publisher, timeout):
self.timeout = timeout
QObject.connect(self.fetch, SIGNAL('clicked()'), self.fetch_metadata)
- self.key.setText(get_isbndb_key())
+ isbndb_key = get_isbndb_key()
+ if not isbndb_key:
+ isbndb_key = ''
+ self.key.setText(isbndb_key)
self.setWindowTitle(title if title else _('Unknown'))
self.isbn = isbn
@@ -115,6 +118,7 @@ def __init__(self, parent, isbn, title, author, publisher, timeout):
self.show_summary)
self.matches.setMouseTracking(True)
self.fetch_metadata()
+ self.opt_get_social_metadata.setChecked(config['get_social_metadata'])
def show_summary(self, current, *args):
diff --git a/src/calibre/gui2/dialogs/fetch_metadata.ui b/src/calibre/gui2/dialogs/fetch_metadata.ui
index edf4207b45..fe97b32f28 100644
--- a/src/calibre/gui2/dialogs/fetch_metadata.ui
+++ b/src/calibre/gui2/dialogs/fetch_metadata.ui
@@ -1,10 +1,11 @@
-
+
+
FetchMetadata
-
-
+
+
Qt::WindowModal
-
+
0
0
@@ -12,48 +13,48 @@
642
-
+
Fetch metadata
-
-
+
+
:/images/metadata.svg:/images/metadata.svg
-
+
-
-
-
- <p>calibre can find metadata for your books from two locations: <b>Google Books</b> and <b>isbndb.com</b>. <p>To use isbndb.com you must sign up for a <a href="http://www.isbndb.com">free account</a> and enter your access key below.
+
+
+ <p>calibre can find metadata for your books from two locations: <b>Google Books</b> and <b>isbndb.com</b>. <p>To use isbndb.com you must sign up for a <a href="http://www.isbndb.com">free account</a> and enter your access key below.
-
+
Qt::AlignCenter
-
+
true
-
+
true
-
-
+
-
-
-
+
+
&Access Key:
-
+
key
-
-
+
-
-
-
+
+
Fetch
@@ -61,56 +62,63 @@
-
-
-
+
+
-
+
true
-
-
-
+
+
Matches
-
+
-
-
-
+
+
Select the book that most closely matches your copy from the list below
-
-
-
-
+
+
+
0
1
-
+
true
-
+
QAbstractItemView::SingleSelection
-
+
QAbstractItemView::SelectRows
-
-
+
-
-
-
+
+
+ Download &social metadata (tags/rating/etc.) for the selected book
+
+
+
+ -
+
+
QDialogButtonBox::Cancel|QDialogButtonBox::Ok
@@ -118,7 +126,7 @@
-
+
@@ -127,11 +135,11 @@
FetchMetadata
accept()
-
+
460
599
-
+
657
530
@@ -143,11 +151,11 @@
FetchMetadata
reject()
-
+
417
599
-
+
0
491
diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py
index 77f214a793..63195f025a 100644
--- a/src/calibre/gui2/dialogs/metadata_single.py
+++ b/src/calibre/gui2/dialogs/metadata_single.py
@@ -16,7 +16,8 @@
QPixmap, QListWidgetItem, QDialog
from calibre.gui2 import qstring_to_unicode, error_dialog, file_icon_provider, \
- choose_files, choose_images, ResizableDialog
+ choose_files, choose_images, ResizableDialog, \
+ warning_dialog
from calibre.gui2.dialogs.metadata_single_ui import Ui_MetadataSingleDialog
from calibre.gui2.dialogs.fetch_metadata import FetchMetadata
from calibre.gui2.dialogs.tag_editor import TagEditor
@@ -28,6 +29,7 @@
from calibre.ebooks.metadata.meta import get_metadata
from calibre.utils.config import prefs
from calibre.customize.ui import run_plugins_on_import, get_isbndb_key
+from calibre.gui2.dialogs.config.social import SocialMetadata
class CoverFetcher(QThread):
@@ -541,6 +543,15 @@ def fetch_metadata(self):
if d.exec_() == QDialog.Accepted:
book = d.selected_book()
if book:
+ if d.opt_get_social_metadata.isChecked():
+ d2 = SocialMetadata(book, self)
+ d2.exec_()
+ if d2.exceptions:
+ det = '\n'.join([x[0]+'\n\n'+x[-1]+'\n\n\n' for
+ x in d2.exceptions])
+ warning_dialog(self, _('There were errors'),
+ _('There were errors downloading social metadata'),
+ det_msg=det, show=True)
self.title.setText(book.title)
self.authors.setText(authors_to_string(book.authors))
if book.author_sort: self.author_sort.setText(book.author_sort)
@@ -552,14 +563,18 @@ def fetch_metadata(self):
self.pubdate.setDate(QDate(d.year, d.month, d.day))
summ = book.comments
if summ:
- prefix = qstring_to_unicode(self.comments.toPlainText())
+ prefix = unicode(self.comments.toPlainText())
if prefix:
prefix += '\n'
self.comments.setText(prefix + summ)
+ if book.rating is not None:
+ self.rating.setValue(int(book.rating))
+ if book.tags:
+ self.tags.setText(', '.join(book.tags))
else:
error_dialog(self, _('Cannot fetch metadata'),
_('You must specify at least one of ISBN, Title, '
- 'Authors or Publisher'))
+ 'Authors or Publisher'), show=True)
def enable_series_index(self, *args):
self.series_index.setEnabled(True)
diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py
index 6b27140c76..795841517e 100644
--- a/src/calibre/gui2/main.py
+++ b/src/calibre/gui2/main.py
@@ -1039,14 +1039,16 @@ def download_metadata(self, checked, covers=True, set_metadata=True):
ids = [db.id(row.row()) for row in rows]
from calibre.gui2.metadata import DownloadMetadata
self._download_book_metadata = DownloadMetadata(db, ids,
- get_covers=covers, set_metadata=set_metadata)
+ get_covers=covers, set_metadata=set_metadata,
+ get_social_metadata=config['get_social_metadata'])
self._download_book_metadata.start()
x = _('covers') if covers and not set_metadata else _('metadata')
self.progress_indicator.start(
_('Downloading %s for %d book(s)')%(x, len(ids)))
self._book_metadata_download_check = QTimer(self)
self.connect(self._book_metadata_download_check,
- SIGNAL('timeout()'), self.book_metadata_download_check)
+ SIGNAL('timeout()'), self.book_metadata_download_check,
+ Qt.QueuedConnection)
self._book_metadata_download_check.start(100)
def book_metadata_download_check(self):
diff --git a/src/calibre/gui2/metadata.py b/src/calibre/gui2/metadata.py
index adbc2bfb9a..d6c11c3bb6 100644
--- a/src/calibre/gui2/metadata.py
+++ b/src/calibre/gui2/metadata.py
@@ -6,11 +6,12 @@
__copyright__ = '2009, Kovid Goyal '
__docformat__ = 'restructuredtext en'
+import traceback
from threading import Thread
from Queue import Queue, Empty
-from calibre.ebooks.metadata.fetch import search
+from calibre.ebooks.metadata.fetch import search, get_social_metadata
from calibre.ebooks.metadata.library_thing import cover_from_isbn
from calibre.customize.ui import get_isbndb_key
@@ -27,9 +28,12 @@ def run(self):
isbn = self.jobs.get()
if not isbn:
break
- cdata, _ = cover_from_isbn(isbn)
- if cdata:
- self.results.put((isbn, cdata))
+ try:
+ cdata, _ = cover_from_isbn(isbn)
+ if cdata:
+ self.results.put((isbn, cdata))
+ except:
+ traceback.print_exc()
def __enter__(self):
self.start()
@@ -41,12 +45,15 @@ def __exit__(self, *args):
class DownloadMetadata(Thread):
- def __init__(self, db, ids, get_covers, set_metadata=True):
+ def __init__(self, db, ids, get_covers, set_metadata=True,
+ get_social_metadata=True):
Thread.__init__(self)
self.setDaemon(True)
self.metadata = {}
self.covers = {}
self.set_metadata = set_metadata
+ self.get_social_metadata = get_social_metadata
+ self.social_metadata_exceptions = []
self.db = db
self.updated = set([])
self.get_covers = get_covers
@@ -91,6 +98,8 @@ def _run(self):
if fmi.isbn and self.get_covers:
self.worker.jobs.put(fmi.isbn)
mi.smart_update(fmi)
+ if mi.isbn and self.get_social_metadata:
+ self.social_metadata_exceptions = get_social_metadata(mi)
else:
self.failures[id] = (mi.title,
_('No matches found for this book'))
diff --git a/src/calibre/manual/custom.py b/src/calibre/manual/custom.py
index e174d2f838..75726951d6 100644
--- a/src/calibre/manual/custom.py
+++ b/src/calibre/manual/custom.py
@@ -40,6 +40,9 @@ def substitute(app, doctree):
.. image:: ../images/cli.png
+On OS X you have to go to Preferences->Advanced and click install command line
+tools to make the command line tools available. On other platforms, just start
+a terminal and type the command.
Documented Commands
--------------------
diff --git a/src/calibre/manual/plugins.rst b/src/calibre/manual/plugins.rst
index 8ba33e036d..1969a70a4b 100644
--- a/src/calibre/manual/plugins.rst
+++ b/src/calibre/manual/plugins.rst
@@ -122,6 +122,8 @@ Metadata download plugins
:class:`MetaInformation` objects. If there is an error, it should be stored
in `self.exception` and `self.tb` (for the traceback).
+.. automember:: calibre.ebooks.metadata.fetch.MetadataSource.metadata_type
+
.. automethod:: calibre.ebooks.metadata.fetch.MetadataSource.fetch
.. automethod:: calibre.ebooks.metadata.fetch.MetadataSource.is_ok
diff --git a/src/calibre/trac/plugins/download.py b/src/calibre/trac/plugins/download.py
index 2dde7024bf..bcd936c6f4 100644
--- a/src/calibre/trac/plugins/download.py
+++ b/src/calibre/trac/plugins/download.py
@@ -9,7 +9,7 @@
('Python Imaging Library', '1.1.6', 'imaging', 'python-imaging', 'python-imaging'),
('libusb', '0.1.12', None, None, None),
('Qt', '4.5.1', 'qt', 'libqt4-core libqt4-gui', 'qt4'),
- ('PyQt', '4.5.1', 'PyQt4', 'python-qt4', 'PyQt4'),
+ ('PyQt', '4.6.1', 'PyQt4', 'python-qt4', 'PyQt4'),
('python-mechanize', '0.1.11', 'dev-python/mechanize', 'python-mechanize', 'python-mechanize'),
('ImageMagick', '6.3.5', 'imagemagick', 'imagemagick', 'ImageMagick'),
('xdg-utils', '1.0.2', 'xdg-utils', 'xdg-utils', 'xdg-utils'),
diff --git a/src/calibre/trac/plugins/templates/linux.html b/src/calibre/trac/plugins/templates/linux.html
index 306ee8a01b..032d7c0996 100644
--- a/src/calibre/trac/plugins/templates/linux.html
+++ b/src/calibre/trac/plugins/templates/linux.html
@@ -155,6 +155,11 @@ sudo python setup.py install
${dep[0]} | ${dep[1]} |
+ Current calibre versions available in various distros
+
+
+
+