From 6afe7f41561bdb4a3166906e62d463ca22c57c35 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 2 Apr 2009 15:22:38 -0700 Subject: [PATCH 01/31] IGN:Tag release From ba3ca34c045630781184d725194aef0307220a92 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 4 Apr 2009 06:58:22 -0700 Subject: [PATCH 02/31] New recipe for the Arizona Star by Darko Miletic --- src/calibre/ebooks/__init__.py | 1 + src/calibre/gui2/__init__.py | 2 - src/calibre/gui2/images/news/azstarnet.png | Bin 0 -> 360 bytes src/calibre/web/feeds/recipes/__init__.py | 2 +- .../web/feeds/recipes/recipe_azstarnet.py | 63 ++++++++++++++++++ .../web/feeds/recipes/recipe_wash_post.py | 12 ++-- 6 files changed, 71 insertions(+), 9 deletions(-) create mode 100644 src/calibre/gui2/images/news/azstarnet.png create mode 100644 src/calibre/web/feeds/recipes/recipe_azstarnet.py diff --git a/src/calibre/ebooks/__init__.py b/src/calibre/ebooks/__init__.py index e208b5a688..26d2394818 100644 --- a/src/calibre/ebooks/__init__.py +++ b/src/calibre/ebooks/__init__.py @@ -1,3 +1,4 @@ +from __future__ import with_statement __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index ffea1ddc20..225f7a9e33 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -466,5 +466,3 @@ class Application(QApplication): self.translator.loadFromData(data) self.installTranslator(self.translator) - - diff --git a/src/calibre/gui2/images/news/azstarnet.png b/src/calibre/gui2/images/news/azstarnet.png new file mode 100644 index 0000000000000000000000000000000000000000..33ad360ca0690e1089caa876cbea823111649764 GIT binary patch literal 360 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*VRoQa+;jv*GOw+2OXu_*GaF5YAtwI?#~+RqDo zp~`xja~3>U?I~sQo1J0DY+lv_{ko=I zEOiY`fGEV!*viz@%EVmTz`)AD;CX2IMwo`&{FKbJN=yw#R)&^9r4S8$Rn-+h4Gf;H KelF{r5}E)Vp?bOi literal 0 HcmV?d00001 diff --git a/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py index 33763f83ce..0de3f66bc8 100644 --- a/src/calibre/web/feeds/recipes/__init__.py +++ b/src/calibre/web/feeds/recipes/__init__.py @@ -37,7 +37,7 @@ recipe_modules = ['recipe_' + r for r in ( 'new_york_review_of_books_no_sub', 'politico', 'adventuregamers', 'mondedurable', 'instapaper', 'dnevnik_cro', 'vecernji_list', 'nacional_cro', '24sata', 'dnevni_avaz', 'glas_srpske', '24sata_rs', - 'krstarica', 'krstarica_en', 'tanjug', 'laprensa_ni', + 'krstarica', 'krstarica_en', 'tanjug', 'laprensa_ni', 'azstarnet', )] import re, imp, inspect, time, os diff --git a/src/calibre/web/feeds/recipes/recipe_azstarnet.py b/src/calibre/web/feeds/recipes/recipe_azstarnet.py new file mode 100644 index 0000000000..55cfcf78f1 --- /dev/null +++ b/src/calibre/web/feeds/recipes/recipe_azstarnet.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python + +__license__ = 'GPL v3' +__copyright__ = '2009, Darko Miletic ' +''' +www.azstarnet.com +''' + +from calibre.web.feeds.news import BasicNewsRecipe + +class Azstarnet(BasicNewsRecipe): + title = 'Arizona Daily Star' + __author__ = 'Darko Miletic' + description = 'news from Arizona' + publisher = 'azstarnet.com' + category = 'news, politics, Arizona, USA' + delay = 1 + oldest_article = 1 + max_articles_per_feed = 100 + no_stylesheets = True + use_embedded_content = False + encoding = 'utf-8' + needs_subscription = True + remove_javascript = True + + html2lrf_options = [ + '--comment', description + , '--category', category + , '--publisher', publisher + ] + + html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' + + def get_browser(self): + br = BasicNewsRecipe.get_browser() + if self.username is not None and self.password is not None: + br.open('http://azstarnet.com/registration/retro.php') + br.select_form(nr=1) + br['email'] = self.username + br['pass' ] = self.password + br.submit() + return br + + + keep_only_tags = [dict(name='div', attrs={'id':'storycontent'})] + + remove_tags = [ + dict(name=['object','link','iframe','base','img']) + ,dict(name='div',attrs={'class':'bannerinstory'}) + ] + + + feeds = [(u'Tucson Region', u'http://rss.azstarnet.com/index.php?site=metro')] + + def preprocess_html(self, soup): + soup.html['dir' ] = 'ltr' + soup.html['lang'] = 'en-US' + mtag = '\n\n\n' + soup.head.insert(0,mtag) + for item in soup.findAll(style=True): + del item['style'] + return soup + \ No newline at end of file diff --git a/src/calibre/web/feeds/recipes/recipe_wash_post.py b/src/calibre/web/feeds/recipes/recipe_wash_post.py index dc85126065..144e323f20 100644 --- a/src/calibre/web/feeds/recipes/recipe_wash_post.py +++ b/src/calibre/web/feeds/recipes/recipe_wash_post.py @@ -11,13 +11,13 @@ class WashingtonPost(BasicNewsRecipe): max_articles_per_feed = 20 language = _('English') - - remove_javascript = True - + remove_javascript = True + + feeds = [ ('Today\'s Highlights', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/03/24/LI2005032400102.xml'), ('Politics', 'http://www.washingtonpost.com/wp-dyn/rss/politics/index.xml'), - ('Nation', 'http://www.www.washingtonpost.com/wp-dyn/rss/nation/index.xml'), + ('Nation', 'http://www.washingtonpost.com/wp-dyn/rss/nation/index.xml'), ('World', 'http://www.washingtonpost.com/wp-dyn/rss/world/index.xml'), ('Business', 'http://www.washingtonpost.com/wp-dyn/rss/business/index.xml'), ('Technology', 'http://www.washingtonpost.com/wp-dyn/rss/technology/index.xml'), @@ -25,7 +25,7 @@ class WashingtonPost(BasicNewsRecipe): ('Education', 'http://www.washingtonpost.com/wp-dyn/rss/education/index.xml'), ('Editorials', 'http://www.washingtonpost.com/wp-dyn/rss/linkset/2005/05/30/LI2005053000331.xml'), ] - + remove_tags = [{'id':['pfmnav', 'ArticleCommentsWrapper']}] @@ -34,7 +34,7 @@ class WashingtonPost(BasicNewsRecipe): def print_version(self, url): return url.rpartition('.')[0] + '_pf.html' - + def postprocess_html(self, soup, first): for div in soup.findAll(name='div', style=re.compile('margin')): div['style'] = '' From 062f0bc7243df231828a2f4b9828867045af446b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 4 Apr 2009 07:52:39 -0700 Subject: [PATCH 03/31] Fix #2202 (Can't Manually Set Series to Book 0) --- src/calibre/gui2/dialogs/metadata_single.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/dialogs/metadata_single.ui b/src/calibre/gui2/dialogs/metadata_single.ui index 8a5fc4f99d..6e96c8d741 100644 --- a/src/calibre/gui2/dialogs/metadata_single.ui +++ b/src/calibre/gui2/dialogs/metadata_single.ui @@ -324,7 +324,7 @@ Book - 1 + 0 10000 From c9c4c15f3fae96762949d46b3520d0358b7ddbfc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sat, 4 Apr 2009 15:57:34 -0700 Subject: [PATCH 04/31] Fix #2205 (Ebook viewer doesn't start if no book selected) --- src/calibre/gui2/main.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index ee97f16568..bbe53cb7ef 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -1067,27 +1067,30 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): return self._view_file(job.result) - def _view_file(self, name): + def _launch_viewer(self, name=None, viewer='ebook-viewer', internal=True): self.setCursor(Qt.BusyCursor) try: - ext = os.path.splitext(name)[1].upper().replace('.', '') - if ext in config['internally_viewed_formats']: - if ext == 'LRF': - args = ['lrfviewer', name] - self.job_manager.server.run_free_job('lrfviewer', - kwdargs=dict(args=args)) - else: - args = ['ebook-viewer', name] - if isosx: - args.append('--raise-window') - self.job_manager.server.run_free_job('ebook-viewer', - kwdargs=dict(args=args)) + if internal: + args = [viewer] + if isosx and 'ebook' in viewer: + args.append('--raise-window') + if name is not None: + args.append(name) + self.job_manager.server.run_free_job(viewer, + kwdargs=dict(args=args)) else: - QDesktopServices.openUrl(QUrl('file:'+name))#launch(name) + QDesktopServices.openUrl(QUrl.fromLocalFile(name))#launch(name) + time.sleep(5) # User feedback finally: self.unsetCursor() + def _view_file(self, name): + ext = os.path.splitext(name)[1].upper().replace('.', '') + viewer = 'lrfviewer' if ext == 'LRF' else 'ebook-viewer' + internal = ext in config['internally_viewed_formats'] + self._launch_viewer(name, viewer, internal) + def view_specific_format(self, triggered): rows = self.library_view.selectionModel().selectedRows() if not rows or len(rows) == 0: @@ -1122,8 +1125,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): rows = self.current_view().selectionModel().selectedRows() if self.current_view() is self.library_view: if not rows or len(rows) == 0: - d = error_dialog(self, _('Cannot view'), _('No book selected')) - d.exec_() + self._launch_viewer() return row = rows[0].row() From 02363a7ae045ef6b3e73623f9111f1d23089693c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 5 Apr 2009 14:50:10 -0700 Subject: [PATCH 05/31] Fix #2217 (New recipe: Corriere della Sera (Italian and English)) --- .../images/news/corriere_della_sera_en.png | Bin 0 -> 524 bytes .../images/news/corriere_della_sera_it.png | Bin 0 -> 524 bytes src/calibre/web/feeds/recipes/__init__.py | 1 + .../recipes/recipe_corriere_della_sera_en.py | 45 ++++++++++++++ .../recipes/recipe_corriere_della_sera_it.py | 55 ++++++++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 src/calibre/gui2/images/news/corriere_della_sera_en.png create mode 100644 src/calibre/gui2/images/news/corriere_della_sera_it.png create mode 100644 src/calibre/web/feeds/recipes/recipe_corriere_della_sera_en.py create mode 100644 src/calibre/web/feeds/recipes/recipe_corriere_della_sera_it.py diff --git a/src/calibre/gui2/images/news/corriere_della_sera_en.png b/src/calibre/gui2/images/news/corriere_della_sera_en.png new file mode 100644 index 0000000000000000000000000000000000000000..71b379bee004ef7176199accb8b64a8ca8185fc6 GIT binary patch literal 524 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87#K}GT^vI!PR|W8^giq$Q9F?@e8q)>iq34t zVlq4yGv4X3%TB6SknP^TV29}PM@IhKo;9Bq*R_ZqeJPdTxIn zJb1US{C@5Gz2_A@uN>vyP!oOVdrgV|t{Z#uC#*GMaIk;l%)xj;Xmh(HOKc_|(w0zyoYzvJ2-{r`xvLTP?AvpCfJbMBcLNJk!C-1@E3ry~E5`&#&+N zR^4)fmO6XT-SqV{=Q?fu`Za59(YohBB`mJ)8B^w7I`;Ou*xSe3uKmmBo4(M*BR)v? zVO8GaWZ!13|1bZBY@NA;^RY&n%+VMd-ID89mM#5!{_~wxzMtm&S$p6~p3@|m`+Z*- ze=XbVP#|dHH^2Usbn^Nb28QE>v%ahvYR=9R)6#M^9Siq34t zVlq4yGv4X3%TB6SknP^TV29}PM@IhKo;9Bq*R_ZqeJPdTxIn zJb1US{C@5Gz2_A@uN>vyP!oOVdrgV|t{Z#uC#*GMaIk;l%)xj;Xmh(HOKc_|(w0zyoYzvJ2-{r`xvLTP?AvpCfJbMBcLNJk!C-1@E3ry~E5`&#&+N zR^4)fmO6XT-SqV{=Q?fu`Za59(YohBB`mJ)8B^w7I`;Ou*xSe3uKmmBo4(M*BR)v? zVO8GaWZ!13|1bZBY@NA;^RY&n%+VMd-ID89mM#5!{_~wxzMtm&S$p6~p3@|m`+Z*- ze=XbVP#|dHH^2Usbn^Nb28QE>v%ahvYR=9R)6#M^9S Date: Mon, 6 Apr 2009 21:32:25 -0700 Subject: [PATCH 06/31] New recipe for MSDN Magazine by Darko Miletic --- src/calibre/gui2/dialogs/config.py | 2 +- src/calibre/gui2/images/news/msdnmag_en.png | Bin 0 -> 694 bytes src/calibre/web/feeds/recipes/__init__.py | 2 +- .../web/feeds/recipes/recipe_msdnmag_en.py | 61 ++++++++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/calibre/gui2/images/news/msdnmag_en.png create mode 100644 src/calibre/web/feeds/recipes/recipe_msdnmag_en.py diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py index a2c77187b5..1d1b220f60 100644 --- a/src/calibre/gui2/dialogs/config.py +++ b/src/calibre/gui2/dialogs/config.py @@ -199,7 +199,7 @@ class EmailAccounts(QAbstractTableModel): return (account, self.accounts[account]) if role == Qt.ToolTipRole: return self.tooltips[col] - if role == Qt.DisplayRole: + if role in [Qt.DisplayRole, Qt.EditRole]: if col == 0: return QVariant(account) if col == 1: diff --git a/src/calibre/gui2/images/news/msdnmag_en.png b/src/calibre/gui2/images/news/msdnmag_en.png new file mode 100644 index 0000000000000000000000000000000000000000..05308b62c4ebc977fa5230ce11c1851183e33c6f GIT binary patch literal 694 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87#Qz+x;TbdoW44FZ+1waz|nf+?|EhBg{-WB z2NkA8b;!5w)zry#wN{MEQkK{tDlD*NV<3z4!D|UKuQW9+sCU?6#5L>9x=Q))d#m3^ ztkE>vW+rEG^^D~lV}lR(onI~Q?rZ*h{kGDblhL!MKAE8E9wfbS%1O?mQ%_PjJdCy8 z-k7{~^KQ0%<&Ps*#p~_8_~?1%w*7am+?lj$l7s?_N1*3g!Dz*;3itd~0~+paWuI;I ztIT>zrs|CaWxv_ZoGJTwm+uwpa~5^Uv%Kdf|2JZo5gWDkaM?n`H2rnvf>n-l75oI& zcLZ;-HlBKbSwwsK?uW+@ix#vbvU#`|j+%5~a(PRC%kRi2W8@ zHuDkAt4bX~Q|F$2`25wog5)g%Tyk0K=g&2tyDGuAb1P%Ec86)w#2Z_B_AmdMG-I0o z+I_LiKeiOSs|&NAG57WTSw<(Go|vp;V!BdiW!qP`ouc!?AOCsu Date: Tue, 7 Apr 2009 08:27:40 -0700 Subject: [PATCH 07/31] Fix #2236 (Updated Tom's Hardware recipe) --- .../web/feeds/recipes/recipe_tomshardware.py | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/calibre/web/feeds/recipes/recipe_tomshardware.py b/src/calibre/web/feeds/recipes/recipe_tomshardware.py index 2e4f5cfb9e..13d164d0e6 100644 --- a/src/calibre/web/feeds/recipes/recipe_tomshardware.py +++ b/src/calibre/web/feeds/recipes/recipe_tomshardware.py @@ -1,38 +1,47 @@ #!/usr/bin/env python __license__ = 'GPL v3' -__copyright__ = '2008, Darko Miletic ' +__copyright__ = '2008-2009, Darko Miletic ' ''' -tomshardware.com +tomshardware.com/us ''' +import urllib +from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.web.feeds.recipes import BasicNewsRecipe -class Tomshardware(BasicNewsRecipe): +class Tomshardware(BasicNewsRecipe): + title = "Tom's Hardware US" + __author__ = 'Darko Miletic' + description = 'Hardware reviews and News' + publisher = "Tom's Hardware" + category = 'news, IT, hardware, USA' + no_stylesheets = True + needs_subscription = True + language = _('English') + INDEX = 'http://www.tomshardware.com' + LOGIN = INDEX + '/membres/' + remove_javascript = True + use_embedded_content= False - title = "Tom's Hardware US" - __author__ = 'Darko Miletic' - description = 'Hardware reviews and News' - no_stylesheets = True - needs_subscription = True - language = _('English') - INDEX = 'http://www.tomshardware.com' - LOGIN = 'http://www.tomshardware.com/membres/?r=%2Fus%2F#loginForm' - cover_url = 'http://img.bestofmedia.com/img/tomshardware/design/tomshardware.jpg' + html2lrf_options = [ + '--comment', description + , '--category', category + , '--publisher', publisher + ] - html2lrf_options = [ '--comment' , description - , '--category' , 'hardware,news' - , '--base-font-size', '10' - ] + html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' def get_browser(self): br = BasicNewsRecipe.get_browser() + br.open(self.INDEX+'/us/') if self.username is not None and self.password is not None: - br.open(self.LOGIN) - br.select_form(name='connexion') - br['login'] = self.username - br['mdp' ] = self.password - br.submit() + data = urllib.urlencode({ 'action':'login_action' + ,'r':self.INDEX+'/us/' + ,'login':self.username + ,'mdp':self.password + }) + br.open(self.LOGIN,data) return br remove_tags = [ @@ -41,17 +50,18 @@ class Tomshardware(BasicNewsRecipe): ] feeds = [ - (u'Latest Articles', u'http://www.tomshardware.com/feeds/atom/tom-s-hardware-us,18-2.xml') + (u'Latest Articles', u'http://www.tomshardware.com/feeds/atom/tom-s-hardware-us,18-2.xml' ) ,(u'Latest News' , u'http://www.tomshardware.com/feeds/atom/tom-s-hardware-us,18-1.xml') ] - + def print_version(self, url): main, sep, rest = url.rpartition('.html') rmain, rsep, article_id = main.rpartition(',') tmain, tsep, trest = rmain.rpartition('/reviews/') + rind = 'http://www.tomshardware.com/news_print.php?p1=' if tsep: - return 'http://www.tomshardware.com/review_print.php?p1=' + article_id - return 'http://www.tomshardware.com/news_print.php?p1=' + article_id + rind = 'http://www.tomshardware.com/review_print.php?p1=' + return rind + article_id def preprocess_html(self, soup): del(soup.body['onload']) From 56b9ceed1085b2e004e4f9c1ecef4e4de83303b7 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Apr 2009 09:01:06 -0700 Subject: [PATCH 08/31] Speed up adding books to an already large collection --- src/calibre/library/database2.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 999a242986..6ccfd6de67 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -622,7 +622,11 @@ class LibraryDatabase2(LibraryDatabase): if title: if not isinstance(title, unicode): title = title.decode(preferred_encoding, 'replace') - return bool(self.conn.get('SELECT id FROM books where title=?', (title,), all=False)) + tf = FIELD_MAP['title'] + q = title.lower() + for record in self.data._data: + if record is not None and record[tf].lower() == q: + return True return False def has_cover(self, index, index_is_id=False): From b1d6b831d98d9e1e93658fc4de13db5ac7526bfe Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Apr 2009 09:33:26 -0700 Subject: [PATCH 09/31] Fix #2061 (Proper locale support) --- src/calibre/startup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/calibre/startup.py b/src/calibre/startup.py index 6cf155792e..52648a20a0 100644 --- a/src/calibre/startup.py +++ b/src/calibre/startup.py @@ -24,7 +24,7 @@ from calibre.translations.msgfmt import make _run_once = False if not _run_once: _run_once = True - + ################################################################################ # Setup translations @@ -32,7 +32,8 @@ if not _run_once: lang = prefs['language'] if lang is not None: return lang - lang = locale.getdefaultlocale()[0] + lang = locale.getdefaultlocale(['LANGUAGE', 'LC_ALL', 'LC_CTYPE', + 'LC_MESSAGES', 'LANG'])[0] if lang is None and os.environ.has_key('LANG'): # Needed for OS X try: lang = os.environ['LANG'] From 0847fcfb2d1944b3f98f20399e65143f95f5b52c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Apr 2009 11:04:28 -0700 Subject: [PATCH 10/31] Preliminary support for the Netronix EB600 --- src/calibre/devices/__init__.py | 6 ++-- src/calibre/devices/cybookg3/driver.py | 49 +++++++++++++------------- src/calibre/devices/eb600/__init__.py | 2 ++ src/calibre/devices/eb600/driver.py | 31 ++++++++++++++++ 4 files changed, 61 insertions(+), 27 deletions(-) create mode 100755 src/calibre/devices/eb600/__init__.py create mode 100755 src/calibre/devices/eb600/driver.py diff --git a/src/calibre/devices/__init__.py b/src/calibre/devices/__init__.py index a673d3fe09..9c515c07fd 100644 --- a/src/calibre/devices/__init__.py +++ b/src/calibre/devices/__init__.py @@ -13,12 +13,14 @@ def devices(): from calibre.devices.kindle.driver import KINDLE from calibre.devices.kindle.driver import KINDLE2 from calibre.devices.blackberry.driver import BLACKBERRY - return (PRS500, PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2, BLACKBERRY) + from calibre.devices.eb600.driver import EB600 + return (PRS500, PRS505, PRS700, CYBOOKG3, KINDLE, KINDLE2, + BLACKBERRY, EB600) import time DAY_MAP = dict(Sun=0, Mon=1, Tue=2, Wed=3, Thu=4, Fri=5, Sat=6) -MONTH_MAP = dict(Jan=1, Feb=2, Mar=3, Apr=4, May=5, Jun=6, Jul=7, Aug=8, Sep=9, Oct=10, Nov=11, Dec=12) +MONTH_MAP = dict(Jan=1, Feb=2, Mar=3, Apr=4, May=5, Jun=6, Jul=7, Aug=8, Sep=9, Oct=10, Nov=11, Dec=12) INVERSE_DAY_MAP = dict(zip(DAY_MAP.values(), DAY_MAP.keys())) INVERSE_MONTH_MAP = dict(zip(MONTH_MAP.values(), MONTH_MAP.keys())) diff --git a/src/calibre/devices/cybookg3/driver.py b/src/calibre/devices/cybookg3/driver.py index eef32594eb..dcde8b873c 100644 --- a/src/calibre/devices/cybookg3/driver.py +++ b/src/calibre/devices/cybookg3/driver.py @@ -10,37 +10,36 @@ from itertools import cycle from calibre.devices.errors import FreeSpaceError from calibre.devices.usbms.driver import USBMS import calibre.devices.cybookg3.t2b as t2b -from calibre.devices.errors import FreeSpaceError class CYBOOKG3(USBMS): # Ordered list of supported formats # Be sure these have an entry in calibre.devices.mime FORMATS = ['mobi', 'prc', 'html', 'pdf', 'rtf', 'txt'] - + VENDOR_ID = [0x0bda, 0x3034] PRODUCT_ID = [0x0703, 0x1795] BCD = [0x110, 0x132] - + VENDOR_NAME = 'BOOKEEN' WINDOWS_MAIN_MEM = 'CYBOOK_GEN3__-FD' WINDOWS_CARD_MEM = 'CYBOOK_GEN3__-SD' - + OSX_MAIN_MEM = 'Bookeen Cybook Gen3 -FD Media' OSX_CARD_MEM = 'Bookeen Cybook Gen3 -SD Media' - + MAIN_MEMORY_VOLUME_LABEL = 'Cybook Gen 3 Main Memory' STORAGE_CARD_VOLUME_LABEL = 'Cybook Gen 3 Storage Card' - + EBOOK_DIR_MAIN = "eBooks" EBOOK_DIR_CARD = "eBooks" THUMBNAIL_HEIGHT = 144 SUPPORTS_SUB_DIRS = True - - def upload_books(self, files, names, on_card=False, end_session=True, + + def upload_books(self, files, names, on_card=False, end_session=True, metadata=None): if on_card and not self._card_prefix: raise ValueError(_('The reader has no storage card connected.')) - + if not on_card: path = os.path.join(self._main_prefix, self.EBOOK_DIR_MAIN) else: @@ -59,17 +58,17 @@ class CYBOOKG3(USBMS): if on_card and size > self.free_space()[2] - 1024*1024: raise FreeSpaceError(_("There is insufficient free space on the storage card")) - if not on_card and size > self.free_space()[0] - 2*1024*1024: + if not on_card and size > self.free_space()[0] - 2*1024*1024: raise FreeSpaceError(_("There is insufficient free space in main memory")) paths = [] names = iter(names) metadata = iter(metadata) - + for infile in files: newpath = path mdata = metadata.next() - + if self.SUPPORTS_SUB_DIRS: if 'tags' in mdata.keys(): for tag in mdata['tags']: @@ -80,47 +79,47 @@ class CYBOOKG3(USBMS): if not os.path.exists(newpath): os.makedirs(newpath) - - filepath = os.path.join(newpath, names.next()) + + filepath = os.path.join(newpath, names.next()) paths.append(filepath) - + if hasattr(infile, 'read'): infile.seek(0) - + dest = open(filepath, 'wb') shutil.copyfileobj(infile, dest, 10*1024*1024) - dest.flush() + dest.flush() dest.close() else: shutil.copy2(infile, filepath) - - coverdata = None + + coverdata = None if 'cover' in mdata.keys(): if mdata['cover'] != None: coverdata = mdata['cover'][2] - + t2bfile = open('%s_6090.t2b' % (os.path.splitext(filepath)[0]), 'wb') t2b.write_t2b(t2bfile, coverdata) t2bfile.close() - + return zip(paths, cycle([on_card])) def delete_books(self, paths, end_session=True): for path in paths: if os.path.exists(path): os.unlink(path) - + filepath, ext = os.path.splitext(path) - + # Delete the ebook auxiliary file if os.path.exists(filepath + '.mbp'): os.unlink(filepath + '.mbp') - + # Delete the thumbnails file auto generated for the ebook if os.path.exists(filepath + '_6090.t2b'): os.unlink(filepath + '_6090.t2b') - + try: os.removedirs(os.path.dirname(path)) except: diff --git a/src/calibre/devices/eb600/__init__.py b/src/calibre/devices/eb600/__init__.py new file mode 100755 index 0000000000..c705e32a66 --- /dev/null +++ b/src/calibre/devices/eb600/__init__.py @@ -0,0 +1,2 @@ +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal ' \ No newline at end of file diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py new file mode 100755 index 0000000000..c108e8b3ed --- /dev/null +++ b/src/calibre/devices/eb600/driver.py @@ -0,0 +1,31 @@ +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' +''' +Device driver for Amazon's Kindle +''' + +from calibre.devices.usbms.driver import USBMS + +class EB600(USBMS): + # Ordered list of supported formats + FORMATS = ['epub', 'pdf'] + + VENDOR_ID = [0x1f85] + PRODUCT_ID = [0x1688] + BCD = [0x110] + + VENDOR_NAME = 'NETRONIX' + WINDOWS_MAIN_MEM = 'EBOOK' + WINDOWS_CARD_MEM = 'CARD_STORAGE' + + OSX_MAIN_MEM = 'EB600 Internal Storage Media' + OSX_CARD_MEM = 'EB600 Card Storage Media' + + MAIN_MEMORY_VOLUME_LABEL = 'EB600 Main Memory' + STORAGE_CARD_VOLUME_LABEL = 'EB600 Storage Card' + + EBOOK_DIR_MAIN = '' + EBOOK_DIR_CARD = '' + SUPPORTS_SUB_DIRS = True + + From 5254aec93e4d660af3f276df0897b6af54ca114a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Apr 2009 11:06:41 -0700 Subject: [PATCH 11/31] IGN:... --- src/calibre/devices/eb600/driver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py index c108e8b3ed..354a6910cc 100755 --- a/src/calibre/devices/eb600/driver.py +++ b/src/calibre/devices/eb600/driver.py @@ -1,7 +1,7 @@ __license__ = 'GPL v3' -__copyright__ = '2009, John Schember ' +__copyright__ = '2009, Kovid Goyal ' ''' -Device driver for Amazon's Kindle +Device driver for the Netronix EB600 ''' from calibre.devices.usbms.driver import USBMS From c4f554dc0eed7614dc56acade33147e601019017 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Apr 2009 11:57:47 -0700 Subject: [PATCH 12/31] Fix #2204 ("Delete news when sent" does not work) --- src/calibre/ebooks/metadata/__init__.py | 99 ++++++++++++------------- src/calibre/gui2/dialogs/config.py | 3 + src/calibre/gui2/dialogs/config.ui | 2 +- 3 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 20a0d9c608..6d3ecfd1a0 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -4,14 +4,13 @@ __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en' """ -Provides abstraction for metadata reading.writing from a variety of ebook formats. +Provides abstraction for metadata reading.writing from a variety of ebook formats. """ import os, mimetypes, sys from urllib import unquote, quote from urlparse import urlparse -from calibre.constants import __version__ as VERSION from calibre import relpath from calibre.utils.config import OptionParser @@ -51,17 +50,17 @@ def get_parser(extension): class Resource(object): ''' - Represents a resource (usually a file on the filesystem or a URL pointing + Represents a resource (usually a file on the filesystem or a URL pointing to the web. Such resources are commonly referred to in OPF files. - + They have the interface: - + :member:`path` :member:`mime_type` :method:`href` - + ''' - + def __init__(self, href_or_path, basedir=os.getcwd(), is_path=True): self._href = None self._basedir = basedir @@ -91,13 +90,13 @@ class Resource(object): pc = unquote(pc).decode('utf-8') self.path = os.path.abspath(os.path.join(basedir, pc.replace('/', os.sep))) self.fragment = unquote(url[-1]) - - + + def href(self, basedir=None): ''' Return a URL pointing to this resource. If it is a file on the filesystem the URL is relative to `basedir`. - + `basedir`: If None, the basedir of this resource is used (see :method:`set_basedir`). If this resource has no basedir, then the current working directory is used as the basedir. ''' @@ -119,54 +118,54 @@ class Resource(object): if isinstance(rpath, unicode): rpath = rpath.encode('utf-8') return quote(rpath.replace(os.sep, '/'))+frag - + def set_basedir(self, path): self._basedir = path - + def basedir(self): return self._basedir - + def __repr__(self): return 'Resource(%s, %s)'%(repr(self.path), repr(self.href())) - - + + class ResourceCollection(object): - + def __init__(self): self._resources = [] - + def __iter__(self): for r in self._resources: yield r - + def __len__(self): return len(self._resources) - + def __getitem__(self, index): return self._resources[index] - + def __bool__(self): return len(self._resources) > 0 - + def __str__(self): resources = map(repr, self) return '[%s]'%', '.join(resources) - + def __repr__(self): return str(self) - + def append(self, resource): if not isinstance(resource, Resource): raise ValueError('Can only append objects of type Resource') self._resources.append(resource) - + def remove(self, resource): self._resources.remove(resource) - + def replace(self, start, end, items): 'Same as list[start:end] = items' self._resources[start:end] = items - + @staticmethod def from_directory_contents(top, topdown=True): collection = ResourceCollection() @@ -176,28 +175,28 @@ class ResourceCollection(object): res.set_basedir(top) collection.append(res) return collection - + def set_basedir(self, path): for res in self: res.set_basedir(path) - + class MetaInformation(object): '''Convenient encapsulation of book metadata''' - + @staticmethod def copy(mi): ans = MetaInformation(mi.title, mi.authors) for attr in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'cover_data', 'application_id', 'guide', - 'manifest', 'spine', 'toc', 'cover', 'language', + 'manifest', 'spine', 'toc', 'cover', 'language', 'book_producer', 'timestamp'): if hasattr(mi, attr): setattr(ans, attr, getattr(mi, attr)) - - def __init__(self, title, authors=[_('Unknown')]): + + def __init__(self, title, authors=(_('Unknown'),)): ''' @param title: title or "Unknown" or a MetaInformation object @param authors: List of strings or [] @@ -208,20 +207,20 @@ class MetaInformation(object): title = mi.title authors = mi.authors self.title = title - self.author = authors # Needed for backward compatibility + self.author = list(authors) # Needed for backward compatibility #: List of strings or [] - self.authors = authors + self.authors = list(authors) self.tags = getattr(mi, 'tags', []) #: mi.cover_data = (ext, data) self.cover_data = getattr(mi, 'cover_data', (None, None)) - + for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'language', 'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover', 'book_producer', 'timestamp' ): setattr(self, x, getattr(mi, x, None)) - + def smart_update(self, mi): ''' Merge the information in C{mi} into self. In case of conflicts, the information @@ -229,26 +228,26 @@ class MetaInformation(object): ''' if mi.title and mi.title != _('Unknown'): self.title = mi.title - + if mi.authors and mi.authors[0] != _('Unknown'): self.authors = mi.authors - + for attr in ('author_sort', 'title_sort', 'category', 'publisher', 'series', 'series_index', 'rating', - 'isbn', 'application_id', 'manifest', 'spine', 'toc', - 'cover', 'language', 'guide', 'book_producer', + 'isbn', 'application_id', 'manifest', 'spine', 'toc', + 'cover', 'language', 'guide', 'book_producer', 'timestamp'): if hasattr(mi, attr): val = getattr(mi, attr) if val is not None: setattr(self, attr, val) - + self.tags += mi.tags self.tags = list(set(self.tags)) - + if getattr(mi, 'cover_data', None) and mi.cover_data[0] is not None: self.cover_data = mi.cover_data - + my_comments = getattr(self, 'comments', '') other_comments = getattr(mi, 'comments', '') if not my_comments: @@ -257,14 +256,14 @@ class MetaInformation(object): other_comments = '' if len(other_comments.strip()) > len(my_comments.strip()): self.comments = other_comments - + def format_series_index(self): try: x = float(self.series_index) except ValueError: x = 1.0 return '%d'%x if int(x) == x else '%.2f'%x - + def __unicode__(self): ans = u'' ans += u'Title : ' + unicode(self.title) + u'\n' @@ -275,7 +274,7 @@ class MetaInformation(object): ans += u'Publisher: '+ unicode(self.publisher) + u'\n' if getattr(self, 'book_producer', False): ans += u'Producer : '+ unicode(self.book_producer) + u'\n' - if self.category: + if self.category: ans += u'Category : ' + unicode(self.category) + u'\n' if self.comments: ans += u'Comments : ' + unicode(self.comments) + u'\n' @@ -284,13 +283,13 @@ class MetaInformation(object): if self.tags: ans += u'Tags : ' + u', '.join([unicode(t) for t in self.tags]) + '\n' if self.series: - ans += u'Series : '+unicode(self.series) + ' #%s\n'%self.format_series_index() + ans += u'Series : '+unicode(self.series) + ' #%s\n'%self.format_series_index() if self.language: ans += u'Language : ' + unicode(self.language) + u'\n' if self.timestamp is not None: ans += u'Timestamp : ' + self.timestamp.isoformat(' ') return ans.strip() - + def to_html(self): ans = [(_('Title'), unicode(self.title))] ans += [(_('Author(s)'), (authors_to_string(self.authors) if self.authors else _('Unknown')))] @@ -307,9 +306,9 @@ class MetaInformation(object): for i, x in enumerate(ans): ans[i] = u'%s%s'%x return u'%s
'%u'\n'.join(ans) - + def __str__(self): return self.__unicode__().encode('utf-8') - + def __nonzero__(self): return bool(self.title or self.author or self.comments or self.category) diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py index 1d1b220f60..831d44251e 100644 --- a/src/calibre/gui2/dialogs/config.py +++ b/src/calibre/gui2/dialogs/config.py @@ -397,6 +397,9 @@ class ConfigDialog(QDialog, Ui_Dialog): self.separate_cover_flow.setChecked(config['separate_cover_flow']) self.setup_email_page() self.category_view.setCurrentIndex(self.category_view.model().index(0)) + self.delete_news.setEnabled(bool(self.sync_news.isChecked())) + self.connect(self.sync_news, SIGNAL('toggled(bool)'), + self.delete_news.setEnabled) def setup_email_page(self): opts = smtp_prefs().parse() diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui index a75a6f0a8d..9afcac8914 100644 --- a/src/calibre/gui2/dialogs/config.ui +++ b/src/calibre/gui2/dialogs/config.ui @@ -371,7 +371,7 @@ - &Delete news from library when it is sent to reader + &Delete news from library when it is automatically sent to reader From ceb3378d8c75d28c0e641d66ff5859b7fbd619dc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Apr 2009 14:13:02 -0700 Subject: [PATCH 13/31] IGN:... --- src/calibre/ebooks/metadata/__init__.py | 4 ++-- src/calibre/library/cli.py | 27 +++++++++++++------------ src/calibre/library/database2.py | 6 +----- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 6d3ecfd1a0..4a9db028d8 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -207,9 +207,9 @@ class MetaInformation(object): title = mi.title authors = mi.authors self.title = title - self.author = list(authors) # Needed for backward compatibility + self.author = list(authors) if authors else []# Needed for backward compatibility #: List of strings or [] - self.authors = list(authors) + self.authors = list(authors) if authors else [] self.tags = getattr(mi, 'tags', []) #: mi.cover_data = (ext, data) self.cover_data = getattr(mi, 'cover_data', (None, None)) diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index 08a18fbe60..8bffd05764 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -15,6 +15,7 @@ from calibre import terminal_controller, preferred_encoding from calibre.utils.config import OptionParser, prefs try: from calibre.utils.single_qt_application import send_message + send_message except: send_message = None from calibre.ebooks.metadata.meta import get_metadata @@ -55,7 +56,7 @@ XML_TEMPLATE = '''\ - + ''' @@ -114,7 +115,7 @@ def get_db(dbpath, options): dbpath = os.path.abspath(dbpath) return LibraryDatabase2(dbpath) -def do_list(db, fields, sort_by, ascending, search_text, line_width, separator, +def do_list(db, fields, sort_by, ascending, search_text, line_width, separator, prefix, output_format, subtitle='Books in the calibre database'): if sort_by: db.sort(sort_by, ascending) @@ -134,13 +135,13 @@ def do_list(db, fields, sort_by, ascending, search_text, line_width, separator, for i in data: for j, field in enumerate(fields): widths[j] = max(widths[j], len(unicode(i[str(field)]))) - + screen_width = terminal_controller.COLS if line_width < 0 else line_width if not screen_width: screen_width = 80 field_width = screen_width//len(fields) base_widths = map(lambda x: min(x+1, field_width), widths) - + while sum(base_widths) < screen_width: adjusted = False for i in range(len(widths)): @@ -150,14 +151,14 @@ def do_list(db, fields, sort_by, ascending, search_text, line_width, separator, break if not adjusted: break - + widths = list(base_widths) titles = map(lambda x, y: '%-*s'%(x, y), widths, fields) print terminal_controller.GREEN + ''.join(titles)+terminal_controller.NORMAL - + wrappers = map(lambda x: TextWrapper(x-1), widths) o = cStringIO.StringIO() - + for record in data: text = [wrappers[i].wrap(unicode(record[field]).encode('utf-8')) for i, field in enumerate(fields)] lines = max(map(len, text)) @@ -178,9 +179,9 @@ def do_list(db, fields, sort_by, ascending, search_text, line_width, separator, if isinstance(x['fmt_epub'], unicode): x['fmt_epub'] = x['fmt_epub'].encode('utf-8') template = MarkupTemplate(STANZA_TEMPLATE) - return template.generate(id="urn:calibre:main", data=data, subtitle=subtitle, + return template.generate(id="urn:calibre:main", data=data, subtitle=subtitle, sep=os.sep, quote=quote, updated=db.last_modified()).render('xml') - + def command_list(args, dbpath): @@ -199,7 +200,7 @@ List the books available in the calibre database. help=_('Sort results in ascending order')) parser.add_option('-s', '--search', default=None, help=_('Filter the results by the search query. For the format of the search query, please see the search related documentation in the User Manual. Default is to do no filtering.')) - parser.add_option('-w', '--line-width', default=-1, type=int, + parser.add_option('-w', '--line-width', default=-1, type=int, help=_('The maximum width of a single line in the output. Defaults to detecting screen size.')) parser.add_option('--separator', default=' ', help=_('The string used to separate fields. Default is a space.')) parser.add_option('--prefix', default=None, help=_('The prefix for all file paths. Default is the absolute path to the library folder.')) @@ -264,14 +265,14 @@ def do_add(db, paths, one_book_per_directory, recurse, add_duplicates): formats.append(format) metadata.append(mi) - + file_duplicates = [] if files: - file_duplicates = db.add_books(files, formats, metadata, + file_duplicates = db.add_books(files, formats, metadata, add_duplicates=add_duplicates) if file_duplicates: file_duplicates = file_duplicates[0] - + dir_dups = [] for dir in dirs: diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 6ccfd6de67..999a242986 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -622,11 +622,7 @@ class LibraryDatabase2(LibraryDatabase): if title: if not isinstance(title, unicode): title = title.decode(preferred_encoding, 'replace') - tf = FIELD_MAP['title'] - q = title.lower() - for record in self.data._data: - if record is not None and record[tf].lower() == q: - return True + return bool(self.conn.get('SELECT id FROM books where title=?', (title,), all=False)) return False def has_cover(self, index, index_is_id=False): From ee8592dd0f330016c73840b1f206a9730fcb17a3 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Apr 2009 19:58:18 -0700 Subject: [PATCH 14/31] IGN:Add LCC, LCCN and DDC fields to MetaInformation --- src/calibre/ebooks/metadata/__init__.py | 20 ++++++++++++++++---- src/calibre/ebooks/mobi/reader.py | 8 ++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 4a9db028d8..e802379c2c 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -192,7 +192,7 @@ class MetaInformation(object): 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'cover_data', 'application_id', 'guide', 'manifest', 'spine', 'toc', 'cover', 'language', - 'book_producer', 'timestamp'): + 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc'): if hasattr(mi, attr): setattr(ans, attr, getattr(mi, attr)) @@ -217,7 +217,7 @@ class MetaInformation(object): for x in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'language', 'application_id', 'manifest', 'toc', 'spine', 'guide', 'cover', - 'book_producer', 'timestamp' + 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc' ): setattr(self, x, getattr(mi, x, None)) @@ -236,7 +236,7 @@ class MetaInformation(object): 'publisher', 'series', 'series_index', 'rating', 'isbn', 'application_id', 'manifest', 'spine', 'toc', 'cover', 'language', 'guide', 'book_producer', - 'timestamp'): + 'timestamp', 'lccn', 'lcc', 'ddc'): if hasattr(mi, attr): val = getattr(mi, attr) if val is not None: @@ -287,7 +287,13 @@ class MetaInformation(object): if self.language: ans += u'Language : ' + unicode(self.language) + u'\n' if self.timestamp is not None: - ans += u'Timestamp : ' + self.timestamp.isoformat(' ') + ans += u'Timestamp : ' + self.timestamp.isoformat(' ') + u'\n' + if self.lccn: + ans += u'LCCN : ' + unicode(self.lccn) + u'\n' + if self.lcc: + ans += u'LCC : ' + unicode(self.lcc) + u'\n' + if self.ddc: + ans += u'DDC : ' + unicode(self.ddc) + u'\n' return ans.strip() def to_html(self): @@ -297,6 +303,12 @@ class MetaInformation(object): ans += [(_('Producer'), unicode(self.book_producer))] ans += [(_('Comments'), unicode(self.comments))] ans += [('ISBN', unicode(self.isbn))] + if self.lccn: + ans += [('LCCN', unicode(self.lccn))] + if self.lcc: + ans += [('LCC', unicode(self.lcc))] + if self.ddc: + ans += [('DDC', unicode(self.ddc))] ans += [(_('Tags'), u', '.join([unicode(t) for t in self.tags]))] if self.series: ans += [(_('Series'), unicode(self.series)+ ' #%s'%self.format_series_index())] diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 62f1f0fd4a..a6632b81ca 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -59,8 +59,8 @@ class EXTHHeader(object): pass elif id == 503 and (not title or title == _('Unknown')): title = content - #else: - # print 'unknown record', id, repr(content) + else: + print 'unknown record', id, repr(content) if title: self.mi.title = title @@ -85,8 +85,8 @@ class EXTHHeader(object): content, '%Y-%m-%d',).date() except: pass - #else: - # print 'unhandled metadata record', id, repr(content) + else: + print 'unhandled metadata record', id, repr(content) class BookHeader(object): From a487219a4b0662a7ead660b11fb61cb10ad9cb2a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 7 Apr 2009 20:34:02 -0700 Subject: [PATCH 15/31] IGN:... --- src/calibre/ebooks/mobi/reader.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index a6632b81ca..79676c1d52 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -57,10 +57,11 @@ class EXTHHeader(object): elif id == 502: # last update time pass - elif id == 503 and (not title or title == _('Unknown')): - title = content - else: - print 'unknown record', id, repr(content) + elif id == 503: # Long title + if not title or title == _('Unknown'): + title = content + #else: + # print 'unknown record', id, repr(content) if title: self.mi.title = title @@ -85,8 +86,10 @@ class EXTHHeader(object): content, '%Y-%m-%d',).date() except: pass - else: - print 'unhandled metadata record', id, repr(content) + elif id == 108: + pass # Producer + #else: + # print 'unhandled metadata record', id, repr(content) class BookHeader(object): From 593db09fdf1aca60b367db67443f007bf7d1d625 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 8 Apr 2009 08:21:35 -0700 Subject: [PATCH 16/31] MOBI Input: Strip null characters. Fixes #2242 (Calibre only converts first chapter of document) --- src/calibre/ebooks/mobi/reader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/calibre/ebooks/mobi/reader.py b/src/calibre/ebooks/mobi/reader.py index 79676c1d52..07b8e153ba 100644 --- a/src/calibre/ebooks/mobi/reader.py +++ b/src/calibre/ebooks/mobi/reader.py @@ -494,6 +494,7 @@ class MobiReader(object): raise MobiError('Unknown compression algorithm: %s'%repr(self.book_header.compression_type)) if self.book_header.ancient and ' Date: Wed, 8 Apr 2009 08:24:04 -0700 Subject: [PATCH 17/31] IGN:Add support for the storage card in the EB600 --- src/calibre/devices/eb600/driver.py | 16 +++++++++++++--- src/calibre/devices/usbms/device.py | 13 ++++++++++++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/calibre/devices/eb600/driver.py b/src/calibre/devices/eb600/driver.py index 354a6910cc..44690655a0 100755 --- a/src/calibre/devices/eb600/driver.py +++ b/src/calibre/devices/eb600/driver.py @@ -8,15 +8,16 @@ from calibre.devices.usbms.driver import USBMS class EB600(USBMS): # Ordered list of supported formats - FORMATS = ['epub', 'pdf'] + FORMATS = ['epub', 'prc', 'chm', 'djvu', 'html', 'rtf', 'txt', 'pdf'] + DRM_FORMATS = ['prc', 'mobi', 'html', 'pdf', 'txt'] VENDOR_ID = [0x1f85] PRODUCT_ID = [0x1688] BCD = [0x110] - VENDOR_NAME = 'NETRONIX' + VENDOR_NAME = 'NETRONIX' WINDOWS_MAIN_MEM = 'EBOOK' - WINDOWS_CARD_MEM = 'CARD_STORAGE' + WINDOWS_CARD_MEM = 'EBOOK' OSX_MAIN_MEM = 'EB600 Internal Storage Media' OSX_CARD_MEM = 'EB600 Card Storage Media' @@ -28,4 +29,13 @@ class EB600(USBMS): EBOOK_DIR_CARD = '' SUPPORTS_SUB_DIRS = True + def windows_sort_drives(self, drives): + main = drives['main'] + card = drives['card'] + if card and main and card < main: + drives['main'] = card + drives['card'] = main + + return drives + diff --git a/src/calibre/devices/usbms/device.py b/src/calibre/devices/usbms/device.py index eb86cb7edd..5a1b5ef40d 100644 --- a/src/calibre/devices/usbms/device.py +++ b/src/calibre/devices/usbms/device.py @@ -174,6 +174,14 @@ class Device(_Device): return prefix + def windows_sort_drives(self, drives): + ''' + Called to disambiguate main memory and storage card for devices that + do not distinguish between them on the basis of `WINDOWS_CARD_NAME`. + For e.g.: The EB600 + ''' + return drives + def open_windows(self): time.sleep(6) drives = {} @@ -188,11 +196,14 @@ class Device(_Device): if 'main' in drives.keys() and 'card' in drives.keys(): break + drives = self.windows_sort_drives(drives) self._main_prefix = drives.get('main') self._card_prefix = drives.get('card') if not self._main_prefix: - raise DeviceError(_('Unable to detect the %s disk drive. Try rebooting.') % self.__class__.__name__) + raise DeviceError( + _('Unable to detect the %s disk drive. Try rebooting.') % + self.__class__.__name__) def get_osx_mountpoints(self, raw=None): if raw is None: From c26c45ce9d36f74f31f96c405c247242430c2156 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 8 Apr 2009 09:09:16 -0700 Subject: [PATCH 18/31] Remove recipe for The International Herald Tribune, since the paper no longer exists --- src/calibre/gui2/dialogs/scheduler.py | 129 +++++++++--------- src/calibre/web/feeds/recipes/__init__.py | 2 +- src/calibre/web/feeds/recipes/recipe_iht.py | 12 +- .../web/feeds/recipes/recipe_new_yorker.py | 7 +- 4 files changed, 76 insertions(+), 74 deletions(-) diff --git a/src/calibre/gui2/dialogs/scheduler.py b/src/calibre/gui2/dialogs/scheduler.py index ea30f0e97b..53c4223750 100644 --- a/src/calibre/gui2/dialogs/scheduler.py +++ b/src/calibre/gui2/dialogs/scheduler.py @@ -25,7 +25,7 @@ from calibre.gui2.dialogs.user_profiles import UserProfiles config = DynamicConfig('scheduler') class Recipe(object): - + def __init__(self, id=None, recipe_class=None, builtin=True): self.id = id self.title = getattr(recipe_class, 'title', None) @@ -39,14 +39,14 @@ class Recipe(object): if self.author == _('Unknown') and not builtin: self.author = _('You') self.needs_subscription = getattr(recipe_class, 'needs_subscription', False) - + def pickle(self): return self.__dict__.copy() - + def unpickle(self, dict): self.__dict__.update(dict) return self - + def __cmp__(self, other): if self.id == getattr(other, 'id', None): return 0 @@ -59,38 +59,39 @@ class Recipe(object): if not self.builtin and getattr(other, 'builtin', True): return -1 return english_sort(self.title, getattr(other, 'title', '')) - + def __hash__(self): return hash(self.id) - + def __eq__(self, other): return self.id == getattr(other, 'id', None) - + def __repr__(self): schedule = self.schedule if schedule and schedule > 1e5: schedule = decode_schedule(schedule) return u'%s|%s|%s|%s'%(self.id, self.title, self.last_downloaded.ctime(), schedule) - + builtin_recipes = [Recipe(m, r, True) for r, m in zip(recipes, recipe_modules)] def save_recipes(recipes): config['scheduled_recipes'] = [r.pickle() for r in recipes] - + def load_recipes(): config.refresh() recipes = [] for r in config.get('scheduled_recipes', []): r = Recipe().unpickle(r) - if r.builtin and not str(r.id).startswith('recipe_'): + if r.builtin and \ + (not str(r.id).startswith('recipe_') or not str(r.id) in recipe_modules): continue recipes.append(r) return recipes class RecipeModel(QAbstractItemModel, SearchQueryParser): - + LOCATIONS = ['all'] - + def __init__(self, db, *args): QAbstractItemModel.__init__(self, *args) SearchQueryParser.__init__(self) @@ -104,18 +105,18 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): self.bold_font = QFont() self.bold_font.setBold(True) self.bold_font = QVariant(self.bold_font) - - + + def refresh(self): sr = load_recipes() for recipe in self.recipes: if recipe in sr: recipe.schedule = sr[sr.index(recipe)].schedule recipe.last_downloaded = sr[sr.index(recipe)].last_downloaded - + self.recipes.sort() self.num_of_recipes = len(self.recipes) - + self.category_map = {} for r in self.recipes: category = getattr(r, 'language', _('Unknown')) @@ -126,12 +127,12 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): if category not in self.category_map.keys(): self.category_map[category] = [] self.category_map[category].append(r) - + self.categories = sorted(self.category_map.keys(), cmp=self.sort_categories) self._map = dict(self.category_map) - + def sort_categories(self, x, y): - + def decorate(x): if x == _('Scheduled'): x = '0' + x @@ -140,13 +141,13 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): else: x = '2' + x return x - + return cmp(decorate(x), decorate(y)) - - + + def universal_set(self): return set(self.recipes) - + def get_matches(self, location, query): query = query.strip().lower() if not query: @@ -154,9 +155,9 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): results = set([]) for recipe in self.recipes: if query in recipe.title.lower() or query in recipe.description.lower(): - results.add(recipe) + results.add(recipe) return results - + def search(self, query): try: results = self.parse(unicode(query)) @@ -170,24 +171,24 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): if recipe in results: self._map[category].append(recipe) self.reset() - + def resort(self): self.recipes.sort() self.reset() - + def index(self, row, column, parent): return self.createIndex(row, column, parent.row() if parent.isValid() else -1) - + def parent(self, index): if index.internalId() == -1: return QModelIndex() return self.createIndex(index.internalId(), 0, -1) - + def columnCount(self, parent): if not parent.isValid() or not parent.parent().isValid(): return 1 return 0 - + def rowCount(self, parent): if not parent.isValid(): return len(self.categories) @@ -195,7 +196,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): category = self.categories[parent.row()] return len(self._map[category]) return 0 - + def data(self, index, role): if index.parent().isValid(): category = self.categories[index.parent().row()] @@ -206,7 +207,7 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): return recipe elif role == Qt.DecorationRole: icon = self.default_icon - icon_path = (':/images/news/%s.png'%recipe.id).replace('recipe_', '') + icon_path = (':/images/news/%s.png'%recipe.id).replace('recipe_', '') if not recipe.builtin: icon = self.custom_icon elif QFile().exists(icon_path): @@ -222,18 +223,18 @@ class RecipeModel(QAbstractItemModel, SearchQueryParser): elif role == Qt.ForegroundRole and category == _('Scheduled'): return QVariant(QColor(0, 255, 0)) return NONE - + def update_recipe_schedule(self, recipe): for srecipe in self.recipes: if srecipe == recipe: srecipe.schedule = recipe.schedule - + class Search(QLineEdit): - + HELP_TEXT = _('Search') INTERVAL = 500 #: Time to wait before emitting search signal - + def __init__(self, *args): QLineEdit.__init__(self, *args) self.default_palette = QApplication.palette(self) @@ -244,20 +245,20 @@ class Search(QLineEdit): self.clear_to_help_mode() self.timer = None self.connect(self, SIGNAL('textEdited(QString)'), self.text_edited_slot) - + def focusInEvent(self, ev): self.setPalette(QApplication.palette(self)) if self.in_help_mode(): self.setText('') return QLineEdit.focusInEvent(self, ev) - + def in_help_mode(self): return unicode(self.text()) == self.HELP_TEXT - + def clear_to_help_mode(self): self.setPalette(self.gray) self.setText(self.HELP_TEXT) - + def text_edited_slot(self, text): text = unicode(text) self.timer = self.startTimer(self.INTERVAL) @@ -281,7 +282,7 @@ def decode_schedule(num): return day-1, hour-1, minute-1 class SchedulerDialog(QDialog, Ui_Dialog): - + def __init__(self, db, *args): QDialog.__init__(self, *args) self.setupUi(self) @@ -308,25 +309,25 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.search.setFocus(Qt.OtherFocusReason) self.old_news.setValue(gconf['oldest_news']) self.rnumber.setText(_('%d recipes')%self._model.num_of_recipes) - for day in (_('day'), _('Monday'), _('Tuesday'), _('Wednesday'), + for day in (_('day'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday'), _('Sunday')): self.day.addItem(day) - + def currentChanged(self, current, previous): if current.parent().isValid(): self.show_recipe(current) - + def download_now(self): recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole) self.emit(SIGNAL('download_now(PyQt_PyObject)'), recipe) - + def set_account_info(self, *args): username, password = map(unicode, (self.username.text(), self.password.text())) username, password = username.strip(), password.strip() recipe = self._model.data(self.recipes.currentIndex(), Qt.UserRole) key = 'recipe_account_info_%s'%recipe.id config[key] = (username, password) if username and password else None - + def do_schedule(self, *args): if not getattr(self, 'allow_scheduling', False): return @@ -342,7 +343,7 @@ class SchedulerDialog(QDialog, Ui_Dialog): recipe.last_downloaded = datetime.fromordinal(1) recipes.append(recipe) if recipe.needs_subscription and not config['recipe_account_info_%s'%recipe.id]: - error_dialog(self, _('Must set account information'), + error_dialog(self, _('Must set account information'), _('This recipe requires a username and password')).exec_() self.schedule.setCheckState(Qt.Unchecked) return @@ -364,7 +365,7 @@ class SchedulerDialog(QDialog, Ui_Dialog): save_recipes(recipes) self._model.update_recipe_schedule(recipe) self.emit(SIGNAL('new_schedule(PyQt_PyObject)'), recipes) - + def show_recipe(self, index): recipe = self._model.data(index, Qt.UserRole) self.current_recipe = recipe @@ -395,7 +396,7 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.interval_button.setChecked(False) self.interval.setEnabled(False) self.schedule.setChecked(recipe.schedule is not None) - self.allow_scheduling = True + self.allow_scheduling = True self.detail_box.setVisible(True) self.account.setVisible(recipe.needs_subscription) self.interval.setEnabled(self.schedule.checkState() == Qt.Checked) @@ -417,11 +418,11 @@ class SchedulerDialog(QDialog, Ui_Dialog): self.last_downloaded.setText(_('Last downloaded')+': '+tm) else: self.last_downloaded.setText(_('Last downloaded: never')) - + class Scheduler(QObject): - + INTERVAL = 1 # minutes - + def __init__(self, main): self.main = main self.verbose = main.verbose @@ -439,7 +440,7 @@ class Scheduler(QObject): self.oldest = gconf['oldest_news'] self.oldest_timer.start(int(60 * 60000)) self.oldest_check() - + self.news_menu = QMenu() self.news_icon = QIcon(':/images/news.svg') self.scheduler_action = QAction(QIcon(':/images/scheduler.svg'), _('Schedule news download'), self) @@ -448,27 +449,27 @@ class Scheduler(QObject): self.cac = QAction(QIcon(':/images/user_profile.svg'), _('Add a custom news source'), self) self.connect(self.cac, SIGNAL('triggered(bool)'), self.customize_feeds) self.news_menu.addAction(self.cac) - + def oldest_check(self): if self.oldest > 0: delta = timedelta(days=self.oldest) ids = self.main.library_view.model().db.tags_older_than(_('News'), delta) if ids: self.main.library_view.model().delete_books_by_id(ids) - + def customize_feeds(self, *args): main = self.main d = UserProfiles(main, main.library_view.model().db.get_feeds()) d.exec_() feeds = tuple(d.profiles()) main.library_view.model().db.set_feeds(feeds) - - + + def debug(self, *args): if self.verbose: sys.stdout.write(' '.join(map(unicode, args))+'\n') sys.stdout.flush() - + def check(self): if not self.lock.tryLock(): return @@ -494,15 +495,15 @@ class Scheduler(QObject): matches = day_matches and (hour*60+minute) < tnow if matches and recipe.last_downloaded.toordinal() < date.today().toordinal(): needs_downloading.add(recipe) - + self.debug('Needs downloading:', needs_downloading) - + needs_downloading = [r for r in needs_downloading if r not in self.queue] for recipe in needs_downloading: self.do_download(recipe) finally: self.lock.unlock() - + def do_download(self, recipe): try: id = int(recipe.id) @@ -538,7 +539,7 @@ class Scheduler(QObject): finally: self.lock.unlock() self.debug('Downloaded:', recipe) - + def download(self, recipe): self.lock.lock() try: @@ -548,10 +549,10 @@ class Scheduler(QObject): self.do_download(recipe) finally: self.lock.unlock() - + def refresh_schedule(self, recipes): self.recipes = recipes - + def show_dialog(self, *args): self.lock.lock() try: diff --git a/src/calibre/web/feeds/recipes/__init__.py b/src/calibre/web/feeds/recipes/__init__.py index 43d25b876f..a2dbcd7d24 100644 --- a/src/calibre/web/feeds/recipes/__init__.py +++ b/src/calibre/web/feeds/recipes/__init__.py @@ -8,7 +8,7 @@ recipe_modules = ['recipe_' + r for r in ( 'newsweek', 'atlantic', 'economist', 'portfolio', 'the_register', 'usatoday', 'outlook_india', 'bbc', 'greader', 'wsj', 'wired', 'globe_and_mail', 'smh', 'espn', 'business_week', 'miami_herald', - 'ars_technica', 'upi', 'new_yorker', 'irish_times', 'iht', 'lanacion', + 'ars_technica', 'upi', 'new_yorker', 'irish_times', 'lanacion', 'discover_magazine', 'scientific_american', 'new_york_review_of_books', 'daily_telegraph', 'guardian', 'el_pais', 'new_scientist', 'b92', 'politika', 'moscow_times', 'latimes', 'japan_times', 'san_fran_chronicle', diff --git a/src/calibre/web/feeds/recipes/recipe_iht.py b/src/calibre/web/feeds/recipes/recipe_iht.py index 1bee27d061..b81b11d579 100644 --- a/src/calibre/web/feeds/recipes/recipe_iht.py +++ b/src/calibre/web/feeds/recipes/recipe_iht.py @@ -12,7 +12,7 @@ from calibre.ptempfile import PersistentTemporaryFile class InternationalHeraldTribune(BasicNewsRecipe): title = u'The International Herald Tribune' __author__ = 'Derry FitzGerald' - language = _('English') + language = _('English') oldest_article = 1 max_articles_per_feed = 10 no_stylesheets = True @@ -20,13 +20,13 @@ class InternationalHeraldTribune(BasicNewsRecipe): remove_tags = [dict(name='div', attrs={'class':'footer'}), dict(name=['form'])] preprocess_regexps = [ - (re.compile(r'