diff --git a/recipes/b365realitatea.recipe b/recipes/b365realitatea.recipe new file mode 100644 index 0000000000..80a1ee225b --- /dev/null +++ b/recipes/b365realitatea.recipe @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +__license__ = 'GPL v3' +__copyright__ = u'2011, Silviu Cotoar\u0103' +''' +b365.realitatea.net +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class b365Realitatea(BasicNewsRecipe): + title = u'b365 Realitatea' + __author__ = u'Silviu Cotoar\u0103' + publisher = u'b365 Realitatea' + description = u'b365 Realitatea' + oldest_article = 5 + language = 'ro' + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + category = 'Ziare,Romania,Bucuresti' + encoding = 'utf-8' + cover_url = 'http://b365.realitatea.net/wp-content/themes/b/images/b365-logo.png' + + conversion_options = { + 'comments' : description + ,'tags' : category + ,'language' : language + ,'publisher' : publisher + } + + keep_only_tags = [ + dict(name='div', attrs={'class':'newsArticle'}) + ] + + remove_tags = [ + dict(name='div', attrs={'class':'date'}) + , dict(name='dic', attrs={'class':'addthis_toolbox addthis_default_style'}) + , dict(name='div', attrs={'class':'related_posts'}) + , dict(name='div', attrs={'id':'RelevantiWidget'}) + ] + + remove_tags_after = [ + dict(name='div', attrs={'id':'RelevantiWidget'}) + ] + feeds = [ + (u'\u0218tiri', u'http://b365.realitatea.net/rss-full/') + ] + + def preprocess_html(self, soup): + return self.adeify_images(soup) + diff --git a/recipes/capital_gr.recipe b/recipes/capital_gr.recipe new file mode 100644 index 0000000000..8dac48c95b --- /dev/null +++ b/recipes/capital_gr.recipe @@ -0,0 +1,35 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe + +class Capital(BasicNewsRecipe): + title = 'Capital.gr' + __author__ ='Stelios' + description = 'Financial News from Greece' + #max_articles_per_feed = 100 + oldest_article = 3 + publisher = 'Capital.gr' + category = 'news, GR' + language = 'el' + encoding = 'windows-1253' + cover_url = 'http://files.capital.gr/images/caplogo.gif' + no_stylesheets = True + use_embedded_content = False + remove_empty_feeds = True + keep_only_tags = [ + dict(name='h1'), + dict(name='p'), + dict(name='span', attrs={'id' : ["textbody"]}) + ] + +#3 posts seemed to have utf8 encoding + feeds = [ + (u'\u039F\u039B\u0395\u03A3 \u039F\u0399 \u0395\u0399\u0394\u0397\u03A3\u0395\u0399\u03A3', 'http://www.capital.gr/news/newsrss.asp?s=-1'), + (u'\u0395\u03A0\u0399\u03A7\u0395\u0399\u03A1\u0397\u03A3\u0395\u0399\u03A3', 'http://www.capital.gr/news/newsrss.asp?s=-2'), + (u'\u0391\u0393\u039F\u03A1\u0395\u03A3', 'http://www.capital.gr/news/newsrss.asp?s=-3'), + (u'\u039F\u0399\u039A\u039F\u039D\u039F\u039C\u0399\u0391', 'http://www.capital.gr/news/newsrss.asp?s=-4'), + (u'\u03A7\u03A1\u0397\u039C. \u0391\u039D\u0391\u039A\u039F\u0399\u039D\u03A9\u03A3\u0395\u0399\u03A3', 'http://www.capital.gr/news/newsrss.asp?s=-6'), + (u'\u039C\u03CC\u03BD\u03B9\u03BC\u03B5\u03C2 \u03C3\u03C4\u03AE\u03BB\u03B5\u03C2: \u039C\u0395 \u0391\u03A0\u039F\u03A8\u0397', 'http://www.capital.gr/articles/articlesrss.asp?catid=4'), + (u'\u039C\u03CC\u03BD\u03B9\u03BC\u03B5\u03C2 \u03C3\u03C4\u03AE\u03BB\u03B5\u03C2: \u03A3\u0399\u03A9\u03A0\u0397\u03A4\u0397\u03A1\u0399\u039F', 'http://www.capital.gr/articles/articlesrss.asp?catid=6'), + (u'\u039C\u03CC\u03BD\u03B9\u03BC\u03B5\u03C2 \u03C3\u03C4\u03AE\u03BB\u03B5\u03C2: \u03A0\u0399\u03A3\u03A9 \u0391\u03A0\u039F \u03A4\u0399\u03A3 \u0393\u03A1\u0391\u039C\u039C\u0395\u03A3', 'http://www.capital.gr/articles/articlesrss.asp?catid=8'), + #(u'\u039C\u03CC\u03BD\u03B9\u03BC\u03B5\u03C2 \u03C3\u03C4\u03AE\u03BB\u03B5\u03C2: \u03A4\u0395\u03A7\u039D\u039F\u039B\u039F\u0393\u0399\u0391', 'http://www.capital.gr/news/newsrss.asp?s=-8') not working for now +] + diff --git a/recipes/catavencii.recipe b/recipes/catavencii.recipe new file mode 100644 index 0000000000..7dff212d74 --- /dev/null +++ b/recipes/catavencii.recipe @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = u'2011, Silviu Cotoar\u0103' +''' +catavencii.ro +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class Catavencii(BasicNewsRecipe): + title = u'Ca\u0163avencii' + __author__ = u'Silviu Cotoar\u0103' + publisher = u'Ca\u0163avencii' + description = u'Ca\u0163avencii' + oldest_article = 5 + language = 'ro' + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + category = 'Ziare,Romania' + encoding = 'utf-8' + cover_url = 'http://www.simonatache.ro/wp-content/uploads/2011/06/catavencii-logo.png' + + conversion_options = { + 'comments' : description + ,'tags' : category + ,'language' : language + ,'publisher' : publisher + } + + keep_only_tags = [ + dict(name='div', attrs={'id':'content'}) + ] + + remove_tags = [ + dict(name='div', attrs={'id':'breadcrumbs'}) + , dict(name='span', attrs={'class':'info'}) + , dict(name='div', attrs={'id':'social-media-article'}) + ] + + remove_tags_after = [ + dict(name='div', attrs={'id':'social-media-article'}) + ] + feeds = [ + (u'\u0218tiri', u'http://www.catavencii.ro/rss') + ] + + def preprocess_html(self, soup): + return self.adeify_images(soup) diff --git a/recipes/daily_mirror.recipe b/recipes/daily_mirror.recipe index 5d4dbe3f4b..f0d28c72e7 100644 --- a/recipes/daily_mirror.recipe +++ b/recipes/daily_mirror.recipe @@ -1,10 +1,11 @@ from calibre.web.feeds.news import BasicNewsRecipe - +import re class AdvancedUserRecipe1306061239(BasicNewsRecipe): title = u'The Daily Mirror' description = 'News as provide by The Daily Mirror -UK' __author__ = 'Dave Asbury' + # last updated 30/10/11 language = 'en_GB' cover_url = 'http://yookeo.com/screens/m/i/mirror.co.uk.jpg' @@ -12,26 +13,30 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe): masthead_url = 'http://www.nmauk.co.uk/nma/images/daily_mirror.gif' - oldest_article = 1 - max_articles_per_feed = 100 + oldest_article = 2 + max_articles_per_feed = 30 remove_empty_feeds = True remove_javascript = True no_stylesheets = True + extra_css = ''' + body{ text-align: justify; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal;} + ''' keep_only_tags = [ - dict(name='h1'), - dict(attrs={'class':['article-attr']}), - dict(name='div', attrs={'class' : [ 'article-body', 'crosshead']}) + dict(name='div',attrs={'id' : 'body-content'}) + ] - - ] + remove_tags_after = [dict (name='div',attrs={'class' : 'related'})] remove_tags = [ - dict(name='div', attrs={'class' : ['caption', 'article-resize']}), - dict( attrs={'class':'append-html'}) - ] - + dict(name='div',attrs={'id' : ['sidebar','menu','search-box','roffers-top']}), + dict(name='div',attrs={'class' :['inline-ad span-16 last','article-resize','related','list teasers']}), + dict(attrs={'class' : ['channellink','article-tags','replace','append-html']}), + dict(name='div',attrs={'class' : 'span-12 last sl-others addthis_toolbox addthis_default_style'}) + ] + preprocess_regexps = [ + (re.compile(r'', re.IGNORECASE | re.DOTALL), lambda match: '')] feeds = [ @@ -43,10 +48,10 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe): ,(u'Music News','http://www.mirror.co.uk/celebs/music/rss.xml') ,(u'Celebs and Tv Gossip','http://www.mirror.co.uk/celebs/tv/rss.xml') ,(u'Sport','http://www.mirror.co.uk/sport/rss.xml') - ,(u'Life Style','http://www.mirror.co.uk/life-style/rss.xml') - ,(u'Advice','http://www.mirror.co.uk/advice/rss.xml') - ,(u'Travel','http://www.mirror.co.uk/advice/travel/rss.xml') + ,(u'Life Style','http://www.mirror.co.uk/life-style/rss.xml') + ,(u'Advice','http://www.mirror.co.uk/advice/rss.xml') + ,(u'Travel','http://www.mirror.co.uk/advice/travel/rss.xml') # example of commented out feed not needed ,(u'Travel','http://www.mirror.co.uk/advice/travel/rss.xml') - ] + ] diff --git a/recipes/deutsche_welle_es.recipe b/recipes/deutsche_welle_es.recipe index c68a03b981..7cf58b0a55 100644 --- a/recipes/deutsche_welle_es.recipe +++ b/recipes/deutsche_welle_es.recipe @@ -16,7 +16,7 @@ class DeutscheWelle_es(BasicNewsRecipe): max_articles_per_feed = 100 use_embedded_content = False no_stylesheets = True - language = 'de_ES' + language = 'de' publication_type = 'newsportal' remove_empty_feeds = True masthead_url = 'http://www.dw-world.de/skins/std/channel1/pics/dw_logo1024.gif' diff --git a/recipes/icons/b365realitatea.png b/recipes/icons/b365realitatea.png new file mode 100644 index 0000000000..040169ca88 Binary files /dev/null and b/recipes/icons/b365realitatea.png differ diff --git a/recipes/icons/catavencii.png b/recipes/icons/catavencii.png new file mode 100644 index 0000000000..87d87ba9dd Binary files /dev/null and b/recipes/icons/catavencii.png differ diff --git a/recipes/in_gr.recipe b/recipes/in_gr.recipe new file mode 100644 index 0000000000..68ad523c0f --- /dev/null +++ b/recipes/in_gr.recipe @@ -0,0 +1,34 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe + +class ingr(BasicNewsRecipe): + title = 'in.gr' + __author__ = 'Stelios' + description = 'News from Greece' +# max_articles_per_feed = 100 + oldest_article = 4 + publisher = 'in.gr' + category = 'news, GR' + language = 'el' + encoding = 'utf8' + no_stylesheets = True + use_embedded_content = False + remove_empty_feeds = True + encoding = 'utf8' + keep_only_tags = [ + dict(name='h1'), + + dict(name='div', attrs={'id' : ['in-news-article']}) + ] + remove_tags = [ +dict(name='em', attrs={'class' : ['credits']}), +dict(name='div', attrs={'class' : ['article-tools-hor', 'promo-banners gAds', 'main', 'article-listen-player', 'article-tools-hor-bttm', 'tools-sec', 'article-tools', 'article-listen-player-ver']}) +] + + + feeds = [ + (u'\u0395\u03BB\u03BB\u03AC\u03B4\u03B1', 'http://rss.in.gr/feed/news/greece'), + (u'\u0395\u03B9\u03B4\u03AE\u03C3\u03B5\u03B9\u03C2', 'http://rss.in.gr/feed/news'), + (u'\u039A\u03CC\u03C3\u03BC\u03BF\u03C2', 'http://rss.in.gr/feed/news/world'), + (u'\u0395\u03C0\u03B9\u03C3\u03C4\u03AE\u03BC\u03B7', 'http://rss.in.gr/feed/news/science'), + (u'\u03A0\u03BF\u03BB\u03B9\u03C4\u03B9\u03C3\u03BC\u03CC\u03C2', 'http://rss.in.gr/feed/news/culture') + ] diff --git a/recipes/men24_gr.recipe b/recipes/men24_gr.recipe new file mode 100644 index 0000000000..8ce68e6c86 --- /dev/null +++ b/recipes/men24_gr.recipe @@ -0,0 +1,43 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe + +class Men24(BasicNewsRecipe): + title = 'Men24.gr' + __author__ = 'Stelios' + description = 'Greek Mens portal' + oldest_article = 14 + max_articles_per_feed = 100 + language = 'el' + cover_url = 'http://www.men24.gr/ast/img/men24Logo.jpg' + category = 'magazines, GR' + language = 'el' + encoding = 'windows-1253' + no_stylesheets = True + use_embedded_content = False + remove_empty_feeds = True + extra_css = ''' + .artPrintTitle{font-family :Arial,Helvetica,sans-serif; font-weight: bold; font-size:large;} + .artPrintSubtitle{font-family :Arial,Helvetica,sans-serif; font-size:x-small;} + ''' + remove_tags = [ + dict(name='td', attrs={'class':['artPrintCategory']}), + dict(name='table', attrs={'class':['footer']}), + dict(name='img') +] + feeds = [ + (u'\u038C\u03BB\u03B5\u03C2 \u03BF\u03B9 \u03B5\u03B9\u03B4\u03AE\u03C3\u03B5\u03B9\u03C2', 'http://www.men24.gr/svc/rss/lastNews/'), + (u'\u03A3\u03C4\u03C5\u03BB', 'http://www.men24.gr/svc/rss/categoryNews/?category=style'), + (u'Fitness', 'http://www.men24.gr/svc/rss/categoryNews/?category=fitness'), + (u'Gadgets', 'http://www.men24.gr/svc/rss/categoryNews/?category=gadgets'), + (u'\u0394\u03B9\u03B1\u03C3\u03BA\u03AD\u03B4\u03B1\u03C3\u03B7', 'http://www.men24.gr/svc/rss/categoryNews/?category=fun'), + (u'\u03A7\u03C1\u03AE\u03BC\u03B1 \u03BA\u03B1\u03B9 \u039A\u03B1\u03C1\u03B9\u03AD\u03C1\u03B1', 'http://www.men24.gr/svc/rss/categoryNews/?category=money'), + (u'Special Edition', 'http://www.men24.gr/svc/rss/categoryNews/?category=special'), + (u'\u0388\u03C1\u03C9\u03C4\u03B1\u03C2 \u03BA\u03B1\u03B9 Sex', 'http://www.men24.gr/svc/rss/categoryNews/?category=love'), + (u'\u0386\u03BD\u03C4\u03C1\u03B5\u03C2 \u03C4\u03BF\u03C5 24', 'http://www.men24.gr/svc/rss/categoryNews/?category=men'), + (u'\u0393\u03C5\u03BD\u03B1\u03AF\u03BA\u03B5\u03C2', 'http://www.men24.gr/svc/rss/categoryNews/?category=women'), + (u'\u039F\u03B4\u03B7\u03B3\u03BF\u03AF', 'http://www.men24.gr/svc/rss/categoryNews/?category=guides'), + (u'\u03A4\u03B6\u03CC\u03B3\u03BF\u03C2', 'http://www.men24.gr/svc/rss/categoryNews/?category=gamble') + +] + + def print_version(self, url): + return url.replace('.asp', '.print.asp') diff --git a/recipes/newsbeast.recipe b/recipes/newsbeast.recipe new file mode 100644 index 0000000000..ed81af8fe3 --- /dev/null +++ b/recipes/newsbeast.recipe @@ -0,0 +1,48 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe + + +class newsbeast(BasicNewsRecipe): + title = 'Newsbeast' + __author__ = 'Stelios' + description = 'News from Greece' + oldest_article = 2 + max_articles_per_feed = 100 + publisher = 'newsbeast' + category = 'news, GR' + language = 'el' + encoding = 'utf8' + no_stylesheets = True + use_embedded_content = False + remove_empty_feeds = True + encoding = 'utf8' + keep_only_tags = [ + dict(name='div', attrs={'class' : ['article-title']}), + # dict(name='img', attrs={'class' : ['article_photo']}), + #If enabled feeds exceede 15MB + dict(name='div', attrs={'class' : ['txt']}) +] + remove_tags = [ + dict(name='table', attrs={'id':['artFoot']}), + dict(name='img'), + #If removed feeds exceede 15MB + dict(name='p', attrs={'class':['article-details']}) +] + + feeds = [ + (u'\u0395\u03BB\u03BB\u03AC\u03B4\u03B1', 'http://www.newsbeast.gr/feeds/greece'), + (u'\u039A\u03CC\u03C3\u03BC\u03BF\u03C2', 'http://www.newsbeast.gr/feeds/world'), + (u'\u03A0\u03BF\u03BB\u03B9\u03C4\u03B9\u03BA\u03AE', 'http://www.newsbeast.gr/feeds/politiki'), + (u'\u039F\u03B9\u03BA\u03BF\u03BD\u03BF\u03BC\u03AF\u03B1', 'http://www.newsbeast.gr/feeds/financial'), + (u'\u0391\u03B8\u03BB\u03B7\u03C4\u03B9\u03BA\u03AC', 'http://www.newsbeast.gr/feeds/sports'), + (u'\u039A\u03BF\u03B9\u03BD\u03C9\u03BD\u03AF\u03B1', 'http://www.newsbeast.gr/feeds/society'), + (u'\u03A0\u03B5\u03C1\u03B9\u03B2\u03AC\u03BB\u03BB\u03BF\u03BD', 'http://www.newsbeast.gr/feeds/environment'), + (u'Media', 'http://www.newsbeast.gr/feeds/media'), + (u'\u0394\u03B9\u03B1\u03C3\u03BA\u03AD\u03B4\u03B1\u03C3\u03B7', 'http://www.newsbeast.gr/feeds/entertainment'), + (u'Lifestyle', 'http://www.newsbeast.gr/feeds/lifestyle'), + (u'\u03A4\u03B5\u03C7\u03BD\u03BF\u03BB\u03BF\u03B3\u03AF\u03B1', 'http://www.newsbeast.gr/feeds/technology'), + (u'\u0391\u03C5\u03C4\u03BF\u03BA\u03AF\u03BD\u03B7\u03C4\u03BF', 'http://www.newsbeast.gr/feeds/car'), + (u'\u0393\u03C5\u03BD\u03B1\u03AF\u03BA\u03B1', 'http://www.newsbeast.gr/feeds/woman'), + (u'\u03A5\u03B3\u03B5\u03AF\u03B1', 'http://www.newsbeast.gr/feeds/health'), + (u'\u03A0\u03BF\u03BB\u03B9\u03C4\u03B9\u03C3\u03BC\u03CC\u03C2', 'http://www.newsbeast.gr/feeds/culture'), + (u'\u038C,\u03C4\u03B9 \u03BD\u03B1 \u03BD\u03B1\u03B9', 'http://www.newsbeast.gr/feeds/weird') + ] diff --git a/recipes/nin.recipe b/recipes/nin.recipe index 66dd58330e..33c7a683be 100644 --- a/recipes/nin.recipe +++ b/recipes/nin.recipe @@ -1,6 +1,6 @@ __license__ = 'GPL v3' -__copyright__ = '2008-2010, Darko Miletic ' +__copyright__ = '2008-2011, Darko Miletic ' ''' www.nin.co.rs ''' @@ -29,6 +29,7 @@ class Nin(BasicNewsRecipe): use_embedded_content = False language = 'sr' publication_type = 'magazine' + masthead_url = 'http://www.nin.co.rs/img/head/logo.jpg' extra_css = """ @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: Verdana, Lucida, sans1, sans-serif} @@ -72,9 +73,11 @@ class Nin(BasicNewsRecipe): def get_cover_url(self): cover_url = None soup = self.index_to_soup(self.INDEX) - link_item = soup.find('img',attrs={'width':'100','border':'0'}) - if link_item: - cover_url = self.PREFIX + link_item['src'] + for item in soup.findAll('a', href=True): + if item['href'].startswith('/pages/issue.php?id='): + simg = item.find('img') + if simg: + return self.PREFIX + item.img['src'] return cover_url def parse_index(self): diff --git a/recipes/protagon.recipe b/recipes/protagon.recipe new file mode 100644 index 0000000000..6f537890a8 --- /dev/null +++ b/recipes/protagon.recipe @@ -0,0 +1,26 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe + + +class protagon(BasicNewsRecipe): + title = 'Protagon' + __author__ = 'Stelios' + description = 'Opinion articles in Greek' + oldest_article = 7 + max_articles_per_feed = 100 + publisher = 'Various' + category = 'GR' + language = 'el' + encoding = 'utf8' + no_stylesheets = True + use_embedded_content = False + remove_empty_feeds = True + + keep_only_tags = [ + dict(name='h1', attrs={'id' : ['title']}), + dict(name='div', attrs={'class' : ['freetext']}) +] + + feeds = [ + (u'\u0398\u03AD\u03BC\u03B1\u03C4\u03B1', 'http://www.protagon.gr/rss?i=protagon.el.8emata') +] + diff --git a/recipes/science_news.recipe b/recipes/science_news.recipe index b1862e9112..01a01d1787 100644 --- a/recipes/science_news.recipe +++ b/recipes/science_news.recipe @@ -40,7 +40,7 @@ class Sciencenews(BasicNewsRecipe): ,dict(name='div', attrs={'class': 'embiggen'}) ] - feeds = [(u"Science News / News Items", u'http://sciencenews.org/view/feed/type/news/name/news.rss')] + feeds = [(u"Science News / News Items", u'http://sciencenews.org/index.php/feed/type/news/name/news.rss/view/feed/name/all.rss')] def get_cover_url(self): cover_url = None diff --git a/recipes/sigma_live.recipe b/recipes/sigma_live.recipe new file mode 100644 index 0000000000..44da1d8180 --- /dev/null +++ b/recipes/sigma_live.recipe @@ -0,0 +1,14 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class sigmalive(BasicNewsRecipe): + title = u'SigmaLive' + __author__ = 'Stelios' + oldest_article = 7 + max_articles_per_feed = 100 + auto_cleanup = True + category = 'news, CY' + description = 'Cypriot News' + language = 'el' + encoding = 'utf8' + feeds = [(u'sigmalive', u'http://sigmalive.com/rss/latest')] + diff --git a/recipes/skai.recipe b/recipes/skai.recipe new file mode 100644 index 0000000000..e742845a78 --- /dev/null +++ b/recipes/skai.recipe @@ -0,0 +1,37 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe + + +class SKAI(BasicNewsRecipe): + title = 'SKAI' + __author__ = 'Stelios' + description = 'News from Greece' + oldest_article = 2 + max_articles_per_feed = 100 + publisher = 'skai.gr' + category = 'news, GR' + language = 'el' + encoding = 'utf8' + no_stylesheets = True + use_embedded_content = False + remove_empty_feeds = True + encoding = 'utf8' + keep_only_tags = [ + dict(name='h1'), + dict(name='div', attrs={'class' : ['articleText']}) +] + + + feeds = [ + (u'\u039A\u03C5\u03C1\u03B9\u03CC\u03C4\u03B5\u03C1\u03B5\u03C2 \u0395\u03B9\u03B4\u03AE\u03C3\u03B5\u03B9\u03C2', 'http://feeds.feedburner.com/skai/Uulu'), + (u'\u0395\u03BB\u03BB\u03AC\u03B4\u03B1', 'http://feeds.feedburner.com/skai/PLwa'), + (u'\u039A\u03CC\u03C3\u03BC\u03BF\u03C2', 'http://feeds.feedburner.com/skai/aqOL'), + (u'\u03A0\u03BF\u03BB\u03B9\u03C4\u03B9\u03BA\u03AE','http://feeds.feedburner.com/skai/yinm'), + (u'\u039F\u03B9\u03BA\u03BF\u03BD\u03BF\u03BC\u03AF\u03B1', 'http://feeds.feedburner.com/skai/oPUt'), + (u'\u03A4\u03B5\u03C7\u03BD\u03BF\u03BB\u03BF\u03B3\u03AF\u03B1', 'http://feeds.feedburner.com/skai/fqsg'), + (u'\u0391\u03B8\u03BB\u03B7\u03C4\u03B9\u03C3\u03BC\u03CC\u03C2', 'http://feeds.feedburner.com/skai/TfmK'), + (u'\u03A5\u03B3\u03B5\u03AF\u03B1', 'http://feeds.feedburner.com/skai/TABn'), + (u'\u03A0\u03BF\u03BB\u03B9\u03C4\u03B9\u03C3\u03BC\u03CC\u03C2', 'http://feeds.feedburner.com/skai/ppGl'), + (u'\u0391\u03C5\u03C4\u03BF\u03BA\u03AF\u03BD\u03B7\u03C3\u03B7', 'http://feeds.feedburner.com/skai/HCCc'), + (u'\u03A0\u03B5\u03C1\u03B9\u03B2\u03AC\u03BB\u03BB\u03BF\u03BD', 'http://feeds.feedburner.com/skai/jVWs'), + (u'\u03A0\u03B1\u03C1\u03AC\u03BE\u03B5\u03BD\u03B1', 'http://feeds.feedburner.com/skai/bpAR') +] diff --git a/recipes/tovima.recipe b/recipes/tovima.recipe new file mode 100644 index 0000000000..ba7b50afb8 --- /dev/null +++ b/recipes/tovima.recipe @@ -0,0 +1,39 @@ +from calibre.web.feeds.recipes import BasicNewsRecipe + +class Tovima(BasicNewsRecipe): + title = 'To Vima' + __author__ = 'Stelios' + description = ' News from Greece' + #max_articles_per_feed = 100 + oldest_article = 3 + publisher = 'To Vima' + category = 'news, GR' + language = 'el' + encoding = 'utf8' + cover_url = 'http://www.tovima.gr/Themes/1/Default/Media/Home//small-n-short-logo.jpg' + no_stylesheets = True + use_embedded_content = False + remove_empty_feeds = True + extra_css = ''' + .article_title{font-family :Arial,Helvetica,sans-serif; font-weight: bold; font-size:large;} + .article_text{font-family :Arial,Helvetica,sans-serif; font-size:x-small;} + ''' + keep_only_tags = [ + + dict(name='div', attrs={'class' : ['article_title']}), + dict(name='div', attrs={'class' : ['article_text']}) + ] + remove_tags = [ + dict(name='div', attrs={'class' : ['article_cat']}) + ] + feeds = [ + (u'\u03C0\u03BF\u03BB\u03B9\u03C4\u03B9\u03BA\u03AE', 'http://www.tovima.gr/feed/politics/'), + (u'\u03BF\u03B9\u03BA\u03BF\u03BD\u03BF\u03BC\u03AF\u03B1', 'http://www.tovima.gr/feed/finance/'), + (u'\u03B3\u03BD\u03CE\u03BC\u03B5\u03C2', 'http://www.tovima.gr/feed/opinions/'), + (u'blogs', 'http://www.tovima.gr/feed/blogs/'), + (u'\u03BA\u03CC\u03C3\u03BC\u03BF\u03C2','http://www.tovima.gr/feed/world/'), + (u'science', 'http://www.tovima.gr/feed/science/'), + (u'\u03BA\u03BF\u03B9\u03BD\u03C9\u03BD\u03AF\u03B1', 'http://www.tovima.gr/feed/society/'), + (u'\u03C0\u03BF\u03BB\u03B9\u03C4\u03B9\u03C3\u03BC\u03CC\u03C2', 'http://www.tovima.gr/feed/culture/'), + (u'\u03B1\u03B8\u03BB\u03B7\u03C4\u03B9\u03C3\u03BC\u03CC\u03C2', 'http://www.tovima.gr/feed/sports/') +] diff --git a/recipes/zeitde_sub.recipe b/recipes/zeitde_sub.recipe index 25fe0f5b23..583e86e7ac 100644 --- a/recipes/zeitde_sub.recipe +++ b/recipes/zeitde_sub.recipe @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 # -*- coding: utf-8 mode: python -*- __license__ = 'GPL v3' @@ -123,6 +123,9 @@ class ZeitEPUBAbo(BasicNewsRecipe): # new login process response = browser.open(url) + # Get rid of nested form + response.set_data(response.get_data().replace('
', '')) + browser.set_response(response) browser.select_form(nr=2) browser.form['name']=self.username browser.form['pass']=self.password @@ -178,7 +181,11 @@ class ZeitEPUBAbo(BasicNewsRecipe): browser = self.get_browser() # new login process - browser.open(url) + response=browser.open(url) + # Get rid of nested form + response.set_data(response.get_data().replace('
', '')) + browser.set_response(response) + browser.select_form(nr=2) browser.form['name']=self.username browser.form['pass']=self.password @@ -211,4 +218,3 @@ class ZeitEPUBAbo(BasicNewsRecipe): self.log.warning('Using static old low-res cover') cover_url = 'http://images.zeit.de/bilder/titelseiten_zeit/1946/001_001.jpg' return cover_url - diff --git a/recipes/zougla.recipe b/recipes/zougla.recipe new file mode 100644 index 0000000000..72175f6a98 --- /dev/null +++ b/recipes/zougla.recipe @@ -0,0 +1,12 @@ +from calibre.web.feeds.news import BasicNewsRecipe + +class AdvancedUserRecipe1320264153(BasicNewsRecipe): + title = u'zougla' + __author__ = 'Stelios' + language = 'el' + oldest_article = 7 + max_articles_per_feed = 100 + auto_cleanup = True + + feeds = [(u'zougla', u'http://www.zougla.gr/ArticleRss.xml')] + diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 80f3fcbf4a..5b707a9cd1 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -36,7 +36,8 @@ class ANDROID(USBMS): 0xca2 : [0x100, 0x0227, 0x0226, 0x222], 0xca3 : [0x100, 0x0227, 0x0226, 0x222], 0xca4 : [0x100, 0x0227, 0x0226, 0x222], - 0xca9 : [0x100, 0x0227, 0x0226, 0x222] + 0xca9 : [0x100, 0x0227, 0x0226, 0x222], + 0xcac : [0x100, 0x0227, 0x0226, 0x222], }, # Eken @@ -138,8 +139,12 @@ class ANDROID(USBMS): # Advent 0x0955 : { 0x7100 : [0x9999] }, # This is the same as the Notion Ink Adam + # Kobo + 0x2237: { 0x2208 : [0x0226] }, + } - EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books'] + EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books', + 'sdcard/ebooks'] EXTRA_CUSTOMIZATION_MESSAGE = _('Comma separated list of directories to ' 'send e-books to on the device. The first one that exists will ' 'be used') @@ -149,7 +154,7 @@ class ANDROID(USBMS): 'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX', 'GOOGLE', 'ARCHOS', 'TELECHIP', 'HUAWEI', 'T-MOBILE', 'SEMC', 'LGE', 'NVIDIA', 'GENERIC-', 'ZTE', 'MID', 'QUALCOMM', 'PANDIGIT', 'HYSTON', - 'VIZIO', 'GOOGLE', 'FREESCAL'] + 'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC'] WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE', '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897', 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', @@ -160,7 +165,8 @@ class ANDROID(USBMS): 'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE', 'A7EB', 'STREAK', 'MB525', 'ANDROID2.3', 'SGH-I997', 'GT-I5800_CARD', 'MB612', 'GT-S5830_CARD', 'GT-S5570_CARD', 'MB870', 'MID7015A', - 'ALPANDIGITAL', 'ANDROID_MID', 'VTAB1008', 'EMX51_BBG_ANDROI'] + 'ALPANDIGITAL', 'ANDROID_MID', 'VTAB1008', 'EMX51_BBG_ANDROI', + 'UMS', '.K080'] WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD', diff --git a/src/calibre/devices/kobo/driver.py b/src/calibre/devices/kobo/driver.py index 479beed089..1e4cb6c63c 100644 --- a/src/calibre/devices/kobo/driver.py +++ b/src/calibre/devices/kobo/driver.py @@ -61,18 +61,25 @@ class KOBO(USBMS): ' ebook file itself. With this option, calibre will send a ' 'separate cover image to the reader, useful if you ' 'have modified the cover.'), - _('Upload Black and White Covers') + _('Upload Black and White Covers'), + _('Show expired books') + + ':::'+_('A bug in an earlier version left non kepubs book records' + ' in the datbase. With this option Calibre will show the ' + 'expired records and allow you to delete them with ' + 'the new delete logic.'), ] EXTRA_CUSTOMIZATION_DEFAULT = [ ', '.join(['tags']), True, + True, True ] OPT_COLLECTIONS = 0 OPT_UPLOAD_COVERS = 1 OPT_UPLOAD_GRAYSCALE_COVERS = 2 + OPT_SHOW_EXPIRED_BOOK_RECORDS = 3 def initialize(self): USBMS.initialize(self) @@ -232,18 +239,23 @@ class KOBO(USBMS): self.dbversion = result[0] debug_print("Database Version: ", self.dbversion) + + opts = self.settings() if self.dbversion >= 16: - query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ + query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, Accessibility from content where ' \ - 'BookID is Null and ( ___ExpirationStatus <> "3" or ___ExpirationStatus is Null)' + 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \ + if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')') elif self.dbversion < 16 and self.dbversion >= 14: - query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ + query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ 'ImageID, ReadStatus, ___ExpirationStatus, FavouritesIndex, "-1" as Accessibility from content where ' \ - 'BookID is Null and ( ___ExpirationStatus <> "3" or ___ExpirationStatus is Null)' + 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \ + if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')') elif self.dbversion < 14 and self.dbversion >= 8: - query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ + query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ 'ImageID, ReadStatus, ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility from content where ' \ - 'BookID is Null and ( ___ExpirationStatus <> "3" or ___ExpirationStatus is Null)' + 'BookID is Null and not ((___ExpirationStatus=3 or ___ExpirationStatus is Null) %(expiry)s') % dict(expiry=' and ContentType = 6)' \ + if opts.extra_customization[self.OPT_SHOW_EXPIRED_BOOK_RECORDS] else ')') else: query= 'select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \ 'ImageID, ReadStatus, "-1" as ___ExpirationStatus, "-1" as FavouritesIndex, "-1" as Accessibility from content where BookID is Null' @@ -343,21 +355,23 @@ class KOBO(USBMS): # Kobo does not delete the Book row (ie the row where the BookID is Null) # The next server sync should remove the row cursor.execute('delete from content where BookID = ?', t) - try: - cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0, ___ExpirationStatus=3 ' \ - 'where BookID is Null and ContentID =?',t) - except Exception as e: - if 'no such column' not in str(e): - raise + if ContentType == 6: try: - cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0 ' \ + cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0, ___ExpirationStatus=3 ' \ 'where BookID is Null and ContentID =?',t) except Exception as e: if 'no such column' not in str(e): raise - cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\' ' \ - 'where BookID is Null and ContentID =?',t) - + try: + cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\', ___PercentRead=0 ' \ + 'where BookID is Null and ContentID =?',t) + except Exception as e: + if 'no such column' not in str(e): + raise + cursor.execute('update content set ReadStatus=0, FirstTimeReading = \'true\' ' \ + 'where BookID is Null and ContentID =?',t) + else: + cursor.execute('delete from content where BookID is Null and ContentID =?',t) connection.commit() diff --git a/src/calibre/devices/linux_mount_helper.c b/src/calibre/devices/linux_mount_helper.c index 550510106e..41ce2c84b2 100644 --- a/src/calibre/devices/linux_mount_helper.c +++ b/src/calibre/devices/linux_mount_helper.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -7,8 +8,11 @@ #include #include #include +#include #define MARKER ".created_by_calibre_mount_helper" +#define DEV "/dev/" +#define MEDIA "/media/" #define False 0 #define True 1 @@ -33,6 +37,20 @@ void ensure_root() { } } +void check_mount_point(const char *mp) { + + if (mp == NULL || strlen(mp) < strlen(MEDIA)) { + fprintf(stderr, "Invalid arguments\n"); + exit(EXIT_FAILURE); + } + + if (strncmp(MEDIA, mp, strlen(MEDIA)) != 0) { + fprintf(stderr, "Trying to operate on a mount point not under /media is not allowed\n"); + exit(EXIT_FAILURE); + } + +} + int do_mount(const char *dev, const char *mp) { char options[1000], marker[2000]; #ifdef __NetBSD__ @@ -44,12 +62,24 @@ int do_mount(const char *dev, const char *mp) { fprintf(stderr, "Specified device node does not exist\n"); return EXIT_FAILURE; } + if (!exists(mp)) { if (mkdir(mp, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) != 0) { errsv = errno; fprintf(stderr, "Failed to create mount point with error: %s\n", strerror(errsv)); } } + /* only mount if mp is under /media */ + mp = realpath(mp, NULL); + if (mp == NULL) { + fprintf(stderr, "realpath on mp failed.\n"); + exit(EXIT_FAILURE); + } + if (strncmp(MEDIA, mp, strlen(MEDIA)) != 0) { + fprintf(stderr, "mount point is not under /media\n"); + exit(EXIT_FAILURE); + } + snprintf(marker, 2000, "%s/%s", mp, MARKER); if (!exists(marker)) { int fd = creat(marker, S_IRUSR|S_IWUSR); @@ -195,10 +225,42 @@ int cleanup(const char *dev, const char *mp) { return cleanup_mount_point(mp); } +void check_dev(const char *dev) { + struct stat file_info; + + if (dev == NULL || strlen(dev) < strlen(DEV)) { + fprintf(stderr, "Invalid arguments\n"); + exit(EXIT_FAILURE); + } + + if (strncmp(DEV, dev, strlen(DEV)) != 0) { + fprintf(stderr, "Trying to operate on a dev node not under /dev\n"); + exit(EXIT_FAILURE); + } + + if (stat(dev, &file_info) != 0) { + fprintf(stderr, "stat call on dev node failed\n"); + exit(EXIT_FAILURE); + } + + if (strstr(dev, "/shm/") != NULL) { + fprintf(stderr, "naughty, naughty!\n"); + exit(EXIT_FAILURE); + } + + if (!S_ISBLK(file_info.st_mode)) { + fprintf(stderr, "dev node is not a block device\n"); + exit(EXIT_FAILURE); + } + +} + + int main(int argc, char** argv) { - char *action, *dev, *mp; + char *action, *dev, *mp, *temp; int status = EXIT_FAILURE; + exit(EXIT_FAILURE); /*printf("Real UID\t= %d\n", getuid()); printf("Effective UID\t= %d\n", geteuid()); @@ -211,11 +273,46 @@ int main(int argc, char** argv) } action = argv[1]; dev = argv[2]; mp = argv[3]; + /* Ensure that PATH only contains system directories to prevent execution of + arbitrary executables as root */ + if (setenv("PATH", + "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin\0", + 1) != 0) { + fprintf(stderr, "Failed to restrict PATH env var, aborting.\n"); + exit(EXIT_FAILURE); + } + if (strncmp(action, "mount", 5) == 0) { + dev = realpath(argv[2], NULL); + if (dev == NULL) { + fprintf(stderr, "Failed to resolve device node.\n"); + exit(EXIT_FAILURE); + } + mp = get_real_mount_point(mp); + check_dev(dev); check_mount_point(mp); status = do_mount(dev, mp); } else if (strncmp(action, "eject", 5) == 0) { + dev = realpath(argv[2], NULL); + if (dev == NULL) { + fprintf(stderr, "Failed to resolve device node.\n"); + exit(EXIT_FAILURE); + } + temp = realpath(mp, NULL); + if (temp == NULL) { + fprintf(stderr, "Mount point does not exist\n"); + exit(EXIT_FAILURE); + } + chdir(temp); + check_dev(dev); check_mount_point(mp); status = do_eject(dev, mp); } else if (strncmp(action, "cleanup", 7) == 0) { + temp = realpath(mp, NULL); + if (temp == NULL) { + fprintf(stderr, "Mount point does not exist\n"); + exit(EXIT_FAILURE); + } + mp = temp; + check_mount_point(mp); status = cleanup(dev, mp); } else { fprintf(stderr, "Unrecognized action: must be mount, eject or cleanup\n"); diff --git a/src/calibre/devices/prst1/driver.py b/src/calibre/devices/prst1/driver.py index 342a32bb37..737371e245 100644 --- a/src/calibre/devices/prst1/driver.py +++ b/src/calibre/devices/prst1/driver.py @@ -560,14 +560,21 @@ class PRST1(USBMS): cursor = connection.cursor() + periodical_schema = \ + "'http://xmlns.sony.net/e-book/prs/periodicals/1.0/newspaper/1.0'" + # Setting this to the SONY periodical schema apparently causes errors + # with some periodicals, therefore set it to null, since the special + # periodical navigation doesn't work anyway. + periodical_schema = 'null' + query = ''' UPDATE books - SET conforms_to = 'http://xmlns.sony.net/e-book/prs/periodicals/1.0/newspaper/1.0', + SET conforms_to = %s, periodical_name = ?, description = ?, publication_date = ? WHERE _id = ? - ''' + '''%periodical_schema t = (name, None, pubdate, book.bookId,) cursor.execute(query, t) diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index e6120f337f..0e899517a9 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -622,8 +622,11 @@ class Device(DeviceConfig, DevicePlugin): if getattr(sys, 'frozen', False): cmd = os.path.join(sys.executables_location, 'bin', cmd) cmd = [cmd, 'mount'] + mlabel = label + if mlabel.endswith('/'): + mlabel = mlabel[:-1] try: - p = subprocess.Popen(cmd + [node, '/media/'+label]) + p = subprocess.Popen(cmd + [node, '/media/'+mlabel]) except OSError: raise DeviceError( _('Could not find mount helper: %s.')%cmd[0]) @@ -777,9 +780,12 @@ class Device(DeviceConfig, DevicePlugin): # try all the nodes to see what we can mount for dev in devs[i].split(): mp='/media/'+label+'-'+dev + mmp = mp + if mmp.endswith('/'): + mmp = mmp[:-1] #print "trying ", dev, "on", mp try: - p = subprocess.Popen(cmd + ["/dev/"+dev, mp]) + p = subprocess.Popen(cmd + ["/dev/"+dev, mmp]) except OSError: raise DeviceError(_('Could not find mount helper: %s.')%cmd[0]) while p.poll() is None: diff --git a/src/calibre/ebooks/html/input.py b/src/calibre/ebooks/html/input.py index 3de116c411..3688668bfe 100644 --- a/src/calibre/ebooks/html/input.py +++ b/src/calibre/ebooks/html/input.py @@ -109,14 +109,16 @@ class HTMLFile(object): try: with open(self.path, 'rb') as f: - src = f.read() + src = f.read(4096) + self.is_binary = level > 0 and not bool(self.HTML_PAT.search(src)) + if not self.is_binary: + src += f.read() except IOError as err: msg = 'Could not read from file: %s with error: %s'%(self.path, as_unicode(err)) if level == 0: raise IOError(msg) raise IgnoreFile(msg, err.errno) - self.is_binary = level > 0 and not bool(self.HTML_PAT.search(src[:4096])) if not self.is_binary: if not encoding: encoding = xml_to_unicode(src[:4096], verbose=verbose)[-1] diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index decba3b780..a6b3c1ad21 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -116,10 +116,14 @@ def title_sort(title, order=None): title = title[1:] match = _title_pat.search(title) if match: - prep = match.group(1) - title = title[len(prep):] + ', ' + prep - if title[0] in _ignore_starts: - title = title[1:] + try: + prep = match.group(1) + except IndexError: + pass + else: + title = title[len(prep):] + ', ' + prep + if title[0] in _ignore_starts: + title = title[1:] return title.strip() coding = zip( diff --git a/src/calibre/ebooks/pdf/output.py b/src/calibre/ebooks/pdf/output.py index 24050abf6a..0ea1c52e2e 100644 --- a/src/calibre/ebooks/pdf/output.py +++ b/src/calibre/ebooks/pdf/output.py @@ -15,7 +15,6 @@ from calibre.customize.conversion import OutputFormatPlugin, \ OptionRecommendation from calibre.ebooks.metadata.opf2 import OPF from calibre.ptempfile import TemporaryDirectory -from calibre.ebooks.pdf.writer import PDFWriter, ImagePDFWriter, PDFMetadata from calibre.ebooks.pdf.pageoptions import UNITS, PAPER_SIZES, \ ORIENTATIONS @@ -90,6 +89,7 @@ class PDFOutput(OutputFormatPlugin): self.convert_text(oeb_book) def convert_images(self, images): + from calibre.ebooks.pdf.writer import ImagePDFWriter self.write(ImagePDFWriter, images) def get_cover_data(self): @@ -105,6 +105,7 @@ class PDFOutput(OutputFormatPlugin): self.cover_data = None def convert_text(self, oeb_book): + from calibre.ebooks.pdf.writer import PDFWriter self.log.debug('Serializing oeb input to disk for processing...') self.get_cover_data() @@ -119,6 +120,7 @@ class PDFOutput(OutputFormatPlugin): self.write(PDFWriter, [s.path for s in opf.spine]) def write(self, Writer, items): + from calibre.ebooks.pdf.writer import PDFMetadata writer = Writer(self.opts, self.log, cover_data=self.cover_data) close = False diff --git a/src/calibre/gui2/metadata/single_download.py b/src/calibre/gui2/metadata/single_download.py index 255abe3999..70b32a78c6 100644 --- a/src/calibre/gui2/metadata/single_download.py +++ b/src/calibre/gui2/metadata/single_download.py @@ -587,7 +587,6 @@ class CoversModel(QAbstractListModel): # {{{ return 1 return pmap.width()*pmap.height() - def clear_failed(self): good = [] pmap = {} @@ -729,7 +728,8 @@ class CoversWidget(QWidget): # {{{ except Empty: break - self.covers_view.clear_failed() + if self.continue_processing: + self.covers_view.clear_failed() if self.worker.error is not None: error_dialog(self, _('Download failed'), @@ -759,7 +759,7 @@ class CoversWidget(QWidget): # {{{ self.continue_processing = False def cancel(self): - self.continue_processing = False + self.cleanup() self.abort.set() def cover_pixmap(self): diff --git a/src/calibre/gui2/preferences/look_feel.py b/src/calibre/gui2/preferences/look_feel.py index c017fe69c2..06ec5392c9 100644 --- a/src/calibre/gui2/preferences/look_feel.py +++ b/src/calibre/gui2/preferences/look_feel.py @@ -146,9 +146,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form): r('default_author_link', gprefs) choices = set([k for k in db.field_metadata.all_field_keys() - if db.field_metadata[k]['is_category'] and + if (db.field_metadata[k]['is_category'] and (db.field_metadata[k]['datatype'] in ['text', 'series', 'enumeration']) and - not db.field_metadata[k]['display'].get('is_names', False)]) + not db.field_metadata[k]['display'].get('is_names', False)) + or + (db.field_metadata[k]['datatype'] in ['composite'] and + db.field_metadata[k]['display'].get('make_category', False))]) choices -= set(['authors', 'publisher', 'formats', 'news', 'identifiers']) choices |= set(['search']) self.opt_categories_using_hierarchy.update_items_cache(choices) diff --git a/src/calibre/library/server/mobile.py b/src/calibre/library/server/mobile.py index 0cb7a86126..a6b5c389de 100644 --- a/src/calibre/library/server/mobile.py +++ b/src/calibre/library/server/mobile.py @@ -156,7 +156,7 @@ def build_index(books, num, search, sort, order, start, total, url_base, CKEYS, body.append(HR()) body.append(DIV( A(_('Switch to the full interface (non-mobile interface)'), - href="/browse", + href=prefix+"/browse", style="text-decoration: none; color: blue", title=_('The full interface gives you many more features, ' 'but it may not work well on a small screen')), diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index 26c03d8491..fc49b1296d 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -258,6 +258,14 @@ The following functions are available in addition to those described in single-f MMMM : the long localized month name (e.g. "January" to "December"). yy : the year as two digit number (00 to 99). yyyy : the year as four digit number. + h : the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm) + hh : the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm) + m : the minutes without a leading 0 (0 to 59) + mm : the minutes with a leading 0 (00 to 59) + s : the seconds without a leading 0 (0 to 59) + ss : the seconds with a leading 0 (00 to 59) + ap : use a 12-hour clock instead of a 24-hour clock, with 'ap' replaced by the localized string for am or pm. + AP : use a 12-hour clock instead of a 24-hour clock, with 'AP' replaced by the localized string for AM or PM. iso : the date with time and timezone. Must be the only format present. * ``eval(string)`` -- evaluates the string as a program, passing the local variables (those ``assign`` ed to). This permits using the template processor to construct complex results from local variables. diff --git a/src/calibre/ptempfile.py b/src/calibre/ptempfile.py index aca8397f53..9343c88efa 100644 --- a/src/calibre/ptempfile.py +++ b/src/calibre/ptempfile.py @@ -194,4 +194,8 @@ class SpooledTemporaryFile(tempfile.SpooledTemporaryFile): tempfile.SpooledTemporaryFile.__init__(self, max_size=max_size, suffix=suffix, prefix=prefix, dir=dir, mode=mode, bufsize=bufsize) +def better_mktemp(*args, **kwargs): + fd, path = tempfile.mkstemp(*args, **kwargs) + os.close(fd) + return path diff --git a/src/calibre/utils/date.py b/src/calibre/utils/date.py index 00190ac23d..baaac05260 100644 --- a/src/calibre/utils/date.py +++ b/src/calibre/utils/date.py @@ -170,11 +170,37 @@ def format_date(dt, format, assume_utc=False, as_utc=False): if format == 'iso': return isoformat(dt, assume_utc=assume_utc, as_utc=as_utc) + ampm = 'ap' in format.lower() + if dt == UNDEFINED_DATE: return '' strf = partial(strftime, t=dt.timetuple()) + def format_hour(hr): + l = len(hr) + h = dt.hour + if ampm: + h = h%12 + if l == 1: return '%d'%h + return '%02d'%h + + def format_minute(min): + l = len(min) + if l == 1: return '%d'%dt.minute + return '%02d'%dt.minute + + def format_second(min): + l = len(min) + if l == 1: return '%d'%dt.second + return '%02d'%dt.second + + def format_ampm(ap): + res = strf('%p') + if ap == 'AP': + return res + return res.lower() + def format_day(dy): l = len(dy) if l == 1: return '%d'%dt.day @@ -193,17 +219,25 @@ def format_date(dt, format, assume_utc=False, as_utc=False): if len(yr) == 2: return '%02d'%(dt.year % 100) return '%04d'%dt.year + function_index = { + 'd': format_day, + 'M': format_month, + 'y': format_year, + 'h': format_hour, + 'm': format_minute, + 's': format_second, + 'a': format_ampm, + 'A': format_ampm, + } def repl_func(mo): s = mo.group(0) if s is None: return '' - if s[0] == 'd': - return format_day(s) - if s[0] == 'M': - return format_month(s) - return format_year(s) + return function_index[s[0]](s) - return re.sub('(d{1,4}|M{1,4}|(?:yyyy|yy))', repl_func, format) + return re.sub( + '(s{1,2})|(m{1,2})|(h{1,2})|(ap)|(AP)|(d{1,4}|M{1,4}|(?:yyyy|yy))', + repl_func, format) def replace_months(datestr, clang): # Replace months by english equivalent for parse_date diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index 964120f550..658dfa8578 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -747,6 +747,14 @@ class BuiltinFormatDate(BuiltinFormatterFunction): 'MMMM : the long localized month name (e.g. "January" to "December"). ' 'yy : the year as two digit number (00 to 99). ' 'yyyy : the year as four digit number. ' + 'h : the hours without a leading 0 (0 to 11 or 0 to 23, depending on am/pm) ' + 'hh : the hours with a leading 0 (00 to 11 or 00 to 23, depending on am/pm) ' + 'm : the minutes without a leading 0 (0 to 59) ' + 'mm : the minutes with a leading 0 (00 to 59) ' + 's : the seconds without a leading 0 (0 to 59) ' + 'ss : the seconds with a leading 0 (00 to 59) ' + 'ap : use a 12-hour clock instead of a 24-hour clock, with "ap" replaced by the localized string for am or pm ' + 'AP : use a 12-hour clock instead of a 24-hour clock, with "AP" replaced by the localized string for AM or PM ' 'iso : the date with time and timezone. Must be the only format present') def evaluate(self, formatter, kwargs, mi, locals, val, format_string): diff --git a/src/calibre/utils/localization.py b/src/calibre/utils/localization.py index a4f0aba73b..925e8847be 100644 --- a/src/calibre/utils/localization.py +++ b/src/calibre/utils/localization.py @@ -121,7 +121,7 @@ _extra_lang_codes = { 'en_TH' : _('English (Thailand)'), 'en_TR' : _('English (Turkey)'), 'en_CY' : _('English (Cyprus)'), - 'en_CZ' : _('English (Czechoslovakia)'), + 'en_CZ' : _('English (Czech Republic)'), 'en_PK' : _('English (Pakistan)'), 'en_HR' : _('English (Croatia)'), 'en_ID' : _('English (Indonesia)'),