From 3853b6bdbeccb6bb56a9e45a93621fb26a91d09b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 14 Jun 2010 14:19:38 -0600 Subject: [PATCH 1/4] Book detail view: When showing device replace path with click to open link as well --- src/calibre/ebooks/metadata/archive.py | 20 +++++++++++++++++++- src/calibre/gui2/book_details.py | 15 ++++++++++++--- src/calibre/gui2/status.py | 8 ++++++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/calibre/ebooks/metadata/archive.py b/src/calibre/ebooks/metadata/archive.py index 45d549b6ea..624c2ad5e5 100644 --- a/src/calibre/ebooks/metadata/archive.py +++ b/src/calibre/ebooks/metadata/archive.py @@ -10,12 +10,31 @@ import os from contextlib import closing from calibre.customize import FileTypePlugin +from calibre.utils.zipfile import ZipFile, stringFileHeader def is_comic(list_of_names): extensions = set([x.rpartition('.')[-1].lower() for x in list_of_names]) comic_extensions = set(['jpg', 'jpeg', 'png']) return len(extensions - comic_extensions) == 0 +def archive_type(stream): + try: + pos = stream.tell() + except: + pos = 0 + id_ = stream.read(4) + ans = None + if id_ == stringFileHeader: + ans = 'zip' + elif id_.startswith('Rar'): + ans = 'rar' + try: + stream.seek(pos) + except: + pass + return ans + + class ArchiveExtract(FileTypePlugin): name = 'Archive Extract' author = 'Kovid Goyal' @@ -31,7 +50,6 @@ class ArchiveExtract(FileTypePlugin): if is_rar: from calibre.libunrar import extract_member, names else: - from calibre.utils.zipfile import ZipFile zf = ZipFile(archive, 'r') if is_rar: diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 8ec9e75f72..6978c68fc2 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -9,7 +9,7 @@ import os, collections from PyQt4.Qt import QLabel, QPixmap, QSize, QWidget, Qt, pyqtSignal, \ QVBoxLayout, QScrollArea, QPropertyAnimation, QEasingCurve, \ - QSizePolicy, QPainter, QRect, pyqtProperty + QSizePolicy, QPainter, QRect, pyqtProperty, QDesktopServices, QUrl from calibre import fit_image, prepare_string_for_xml from calibre.gui2.widgets import IMAGE_EXTENSIONS @@ -42,7 +42,6 @@ def render_rows(data): txt = prepare_string_for_xml(txt) if 'id' in data: if key == _('Path'): - txt = '...'+os.sep+os.sep.join(txt.split(os.sep)[-2:]) txt = u'%s'%(data['id'], _('Click to open')) if key == _('Formats') and txt and txt != _('None'): @@ -50,6 +49,11 @@ def render_rows(data): fmts = [u'%s' % (data['id'], x, x) for x in fmts] txt = ', '.join(fmts) + else: + if key == _('Path'): + txt = u'%s'%(txt, + _('Click to open')) + rows.append((key, txt)) return rows @@ -150,6 +154,7 @@ class Label(QLabel): def __init__(self): QLabel.__init__(self) + self.setTextFormat(Qt.RichText) self.setText('') self.setWordWrap(True) self.linkActivated.connect(self.link_activated) @@ -249,9 +254,13 @@ class BookDetails(QWidget): typ, _, val = link.partition(':') if typ == 'path': self.open_containing_folder.emit(int(val)) - if typ == 'format': + elif typ == 'format': id_, fmt = val.split(':') self.view_specific_format.emit(int(id_), fmt) + elif typ == 'devpath': + path = os.path.dirname(val) + QDesktopServices.openUrl(QUrl.fromLocalFile(path)) + def mouseReleaseEvent(self, ev): ev.accept() diff --git a/src/calibre/gui2/status.py b/src/calibre/gui2/status.py index 10775ec327..876d4cdb05 100644 --- a/src/calibre/gui2/status.py +++ b/src/calibre/gui2/status.py @@ -5,7 +5,7 @@ import os from PyQt4.Qt import QStatusBar, QLabel, QWidget, QHBoxLayout, QPixmap, \ QSizePolicy, QScrollArea, Qt, QSize, pyqtSignal, \ - QPropertyAnimation, QEasingCurve + QPropertyAnimation, QEasingCurve, QDesktopServices, QUrl from calibre import fit_image, preferred_encoding, isosx @@ -231,9 +231,13 @@ class StatusBar(QStatusBar, StatusBarInterface, BookDetailsInterface): typ, _, val = link.partition(':') if typ == 'path': self.open_containing_folder.emit(int(val)) - if typ == 'format': + elif typ == 'format': id_, fmt = val.split(':') self.view_specific_format.emit(int(id_), fmt) + elif typ == 'devpath': + path = os.path.dirname(val) + QDesktopServices.openUrl(QUrl.fromLocalFile(path)) + def resizeEvent(self, ev): self.resized.emit(self.size()) From 33e9a1a0e3f0f79d2bcb370ec71b9ab2c37d9bc5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 14 Jun 2010 14:46:53 -0600 Subject: [PATCH 2/4] Kindle driver: Add support displaying collections --- src/calibre/devices/kindle/driver.py | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py index c88a443689..31c7fae48e 100644 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en' ''' Device driver for Amazon's Kindle ''' -import datetime, os, re, sys +import datetime, os, re, sys, json, hashlib from cStringIO import StringIO from struct import unpack @@ -60,6 +60,7 @@ class KINDLE(USBMS): 'replace') return mi + def get_annotations(self, path_map): MBP_FORMATS = [u'azw', u'mobi', u'prc', u'txt'] mbp_formats = set(MBP_FORMATS) @@ -150,6 +151,37 @@ class KINDLE2(KINDLE): PRODUCT_ID = [0x0002] BCD = [0x0100] + def books(self, oncard=None, end_session=True): + bl = USBMS.books(self, oncard=oncard, end_session=end_session) + # Read collections information + collections = os.path.join(self._main_prefix, 'system', 'collections.json') + if os.access(collections, os.R_OK): + try: + self.kindle_update_booklist(bl, collections) + except: + import traceback + traceback.print_exc() + return bl + + def kindle_update_booklist(self, bl, collections): + with open(collections, 'rb') as f: + collections = f.read() + collections = json.loads(collections) + path_map = {} + for name, val in collections.items(): + col = name.split('@')[0] + items = val.get('items', []) + for x in items: + x = x[-40:] + if x not in path_map: + path_map[x] = set([]) + path_map[x].add(col) + if path_map: + for book in bl: + path = '/mnt/us/'+book.lpath + h = hashlib.sha1(path).hexdigest() + if h in path_map: + book.device_collections = list(sorted(path_map[h])) class KINDLE_DX(KINDLE2): From e882689f1752a1e0d7ef0dffbeb42be35c82fe44 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 14 Jun 2010 15:13:14 -0600 Subject: [PATCH 3/4] Corriero dello sport and Auto and Formula 1 by Gabriele Marini --- resources/recipes/auto.recipe | 63 +++++++++++++++++++ resources/recipes/corriere_dello_sport.recipe | 60 ++++++++++++++++++ src/calibre/devices/kindle/driver.py | 21 +++++++ 3 files changed, 144 insertions(+) create mode 100644 resources/recipes/auto.recipe create mode 100644 resources/recipes/corriere_dello_sport.recipe diff --git a/resources/recipes/auto.recipe b/resources/recipes/auto.recipe new file mode 100644 index 0000000000..959b9997a8 --- /dev/null +++ b/resources/recipes/auto.recipe @@ -0,0 +1,63 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'GabrieleMarini, based on Darko Miletic' +__copyright__ = '2009, Darko Miletic , Gabriele Marini' +__version__ = 'v1.02 Marini Gabriele ' +__date__ = '14062010' +__description__ = 'Italian daily newspaper' + +''' +http://www.corrieredellosport.it/ +''' +from calibre.web.feeds.news import BasicNewsRecipe + +class Auto(BasicNewsRecipe): + __author__ = 'Gabriele Marini' + description = 'Auto and Formula 1' + + cover_url = 'http://www.auto.it/res/imgs/logo_Auto.png' + + + title = u'Auto' + publisher = 'CONTE Editore' + category = 'Sport' + + language = 'it' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 60 + max_articles_per_feed = 30 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + html2lrf_options = [ + '--comment', description + , '--category', category + , '--publisher', publisher + , '--ignore-tables' + ] + + html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True' + + keep_only_tags = [ + dict(name='h2', attrs={'class':['tit_Article y_Txt']}), + dict(name='h2', attrs={'class':['tit_Article']}), + dict(name='div', attrs={'class':['box_Img newsdet_new ']}), + dict(name='div', attrs={'class':['box_Img newsdet_as ']}), + dict(name='table', attrs={'class':['table_A']}), + dict(name='div', attrs={'class':['txt_Article txtBox_cms']}), + dict(name='testoscheda')] + + + feeds = [ + (u'Tutte le News' , u'http://www.auto.it/rss/articoli.xml' ), + (u'Prove su Strada' , u'http://www.auto.it/rss/prove+6.xml'), + (u'Novit\xe0' , u'http://www.auto.it/rss/novita+3.xml') + ] + + + + diff --git a/resources/recipes/corriere_dello_sport.recipe b/resources/recipes/corriere_dello_sport.recipe new file mode 100644 index 0000000000..ac93c702e3 --- /dev/null +++ b/resources/recipes/corriere_dello_sport.recipe @@ -0,0 +1,60 @@ +#!/usr/bin/env python +__license__ = 'GPL v3' +__author__ = 'GabrieleMarini, based on Darko Miletic' +__copyright__ = '2009, Darko Miletic , Gabriele Marini' +__version__ = ' ' +__date__ = '14-06-2010' +__description__ = 'Italian daily newspaper' + +''' +http://www.corrieredellosport.it/ +''' +from calibre.web.feeds.news import BasicNewsRecipe + +class ilCorrieredelloSport(BasicNewsRecipe): + __author__ = 'Gabriele Marini' + description = 'Italian daily newspaper' + + cover_url = 'http://edicola.corrieredellosport.it/newsmem/corsport/prima/nazionale_prima.jpg' + + + title = u'Il Corriere dello Sport' + publisher = 'CORRIERE DELLO SPORT s.r.l. ' + category = 'Sport' + + language = 'it' + timefmt = '[%a, %d %b, %Y]' + + oldest_article = 10 + max_articles_per_feed = 100 + use_embedded_content = False + recursion = 10 + + remove_javascript = True + no_stylesheets = True + + html2lrf_options = [ + '--comment', description + , '--category', category + , '--publisher', publisher + , '--ignore-tables' + ] + + html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\nlinearize_tables=True' + + keep_only_tags = [ + dict(name='h1', attrs={'class':['tit_Article']}), + dict(name='h1', attrs={'class':['tit_Article_mondiali']}), + dict(name='div', attrs={'class':['box_Img']}), + dict(name='p', attrs={'class':['summary','text']})] + + + + + feeds = [ + (u'Primo Piano' , u'http://www.corrieredellosport.it/rss/primo_piano.xml' ), + (u'Calcio' , u'http://www.corrieredellosport.it/rss/Calcio-3.xml'), + (u'Formula 1' , u'http://www.corrieredellosport.it/rss/Formula-1-7.xml'), + (u'Moto' , u'http://www.corrieredellosport.it/rss/Moto-8.xml'), + (u'Piu visti' , u'http://www.corrieredellosport.it/rss/piu_visti.xml') + ] diff --git a/src/calibre/devices/kindle/driver.py b/src/calibre/devices/kindle/driver.py index 31c7fae48e..cd56d210e1 100644 --- a/src/calibre/devices/kindle/driver.py +++ b/src/calibre/devices/kindle/driver.py @@ -13,6 +13,27 @@ from struct import unpack from calibre.devices.usbms.driver import USBMS +''' +Notes on collections: + +A collections cache is stored at system/collections.json +The cache is read only, changes made to it are overwritten (it is regenerated) +on device disconnect + +A log of collection creation/manipulation is available at +system/userannotationlog + +collections.json refers to books via a SHA1 hash of the absolute path to the +book (prefix is /mnt/us on my Kindle). The SHA1 hash may or may not be prefixed +by some characters, use the last 40 characters. + +Changing the metadata and resending the file doesn't seem to affect collections + +Adding a book to a collection on the Kindle does not change the book file at all +(i.e. it is binary identical). Therefore collection information is not stored in +file metadata. +''' + class KINDLE(USBMS): name = 'Kindle Device Interface' From b67ac4fc1d8af1ee0abc67c634cd3a46a7291516 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 14 Jun 2010 15:38:51 -0600 Subject: [PATCH 4/4] Add tooltip to book details pane --- src/calibre/gui2/book_details.py | 8 ++++++-- src/calibre/gui2/status.py | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/calibre/gui2/book_details.py b/src/calibre/gui2/book_details.py index 6978c68fc2..75c045d011 100644 --- a/src/calibre/gui2/book_details.py +++ b/src/calibre/gui2/book_details.py @@ -42,8 +42,8 @@ def render_rows(data): txt = prepare_string_for_xml(txt) if 'id' in data: if key == _('Path'): - txt = u'%s'%(data['id'], - _('Click to open')) + txt = u'%s'%(data['id'], + txt, _('Click to open')) if key == _('Formats') and txt and txt != _('None'): fmts = [x.strip() for x in txt.split(',')] fmts = [u'%s' % (data['id'], x, x) for x @@ -272,6 +272,10 @@ class BookDetails(QWidget): def show_data(self, data): self.cover_view.show_data(data) self.book_info.show_data(data) + self.setToolTip('

'+_('Click to open Book Details window') + + '

' + _('Path') + ': ' + data.get(_('Path'), '')) + + def reset_info(self): self.show_data({}) diff --git a/src/calibre/gui2/status.py b/src/calibre/gui2/status.py index 876d4cdb05..90426f8021 100644 --- a/src/calibre/gui2/status.py +++ b/src/calibre/gui2/status.py @@ -163,6 +163,10 @@ class BookInfoDisplay(QWidget): self.book_data.updateGeometry() self.updateGeometry() self.setVisible(True) + self.setToolTip('

'+_('Click to open Book Details window') + + '

' + _('Path') + ': ' + data.get(_('Path'), '')) + + class StatusBarInterface(object):