diff --git a/resources/recipes/instapaper.recipe b/resources/recipes/instapaper.recipe index ba74faf7ae..73c32d08a7 100644 --- a/resources/recipes/instapaper.recipe +++ b/resources/recipes/instapaper.recipe @@ -1,11 +1,10 @@ -#!/usr/bin/env python - __license__ = 'GPL v3' -__copyright__ = '2009, Darko Miletic ' +__copyright__ = '2009-2010, Darko Miletic ' ''' www.instapaper.com ''' +import urllib from calibre import strftime from calibre.web.feeds.news import BasicNewsRecipe @@ -22,18 +21,15 @@ class Instapaper(BasicNewsRecipe): max_articles_per_feed = 100 no_stylesheets = True use_embedded_content = False - remove_javascript = True needs_subscription = True INDEX = u'http://www.instapaper.com' LOGIN = INDEX + u'/user/login' - html2lrf_options = [ - '--comment', description - , '--category', category - , '--publisher', publisher - ] - - html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"\noverride_css=" p {text-indent: 0em; margin-top: 0em; margin-bottom: 0.5em} img {margin-top: 0em; margin-bottom: 0.4em}"' + conversion_options = { + 'comment' : description + , 'tags' : category + , 'publisher' : publisher + } feeds = [ (u'Unread articles' , INDEX + u'/u' ) @@ -63,7 +59,7 @@ class Instapaper(BasicNewsRecipe): description = self.tag_to_string(item.div) atag = item.a if atag and atag.has_key('href'): - url = self.INDEX + atag['href'] + '/text' + url = atag['href'] title = self.tag_to_string(atag) date = strftime(self.timefmt) articles.append({ @@ -75,3 +71,6 @@ class Instapaper(BasicNewsRecipe): totalfeeds.append((feedtitle, articles)) return totalfeeds + def print_version(self, url): + return self.INDEX + '/text?u=' + urllib.quote(url) + diff --git a/resources/recipes/new_york_review_of_books.recipe b/resources/recipes/new_york_review_of_books.recipe index f7b3fedbec..bd18b95c43 100644 --- a/resources/recipes/new_york_review_of_books.recipe +++ b/resources/recipes/new_york_review_of_books.recipe @@ -23,7 +23,7 @@ class NewYorkReviewOfBooks(BasicNewsRecipe): no_javascript = True needs_subscription = True - keep_only_tags = [dict(id='article-body')] + keep_only_tags = [dict(id=['article-body','page-title'])] remove_tags = [dict(attrs={'class':['article-tools', 'article-links', 'center advertisement']})] diff --git a/resources/recipes/new_york_review_of_books_no_sub.recipe b/resources/recipes/new_york_review_of_books_no_sub.recipe index c851cf7b2f..e462689403 100644 --- a/resources/recipes/new_york_review_of_books_no_sub.recipe +++ b/resources/recipes/new_york_review_of_books_no_sub.recipe @@ -21,7 +21,7 @@ class NewYorkReviewOfBooks(BasicNewsRecipe): no_stylesheets = True no_javascript = True - keep_only_tags = [dict(id='article-body')] + keep_only_tags = [dict(id=['article-body', 'page-title'])] remove_tags = [dict(attrs={'class':['article-tools', 'article-links', 'center advertisement']})] diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py index 356ebfc876..40cac4d615 100644 --- a/src/calibre/devices/interface.py +++ b/src/calibre/devices/interface.py @@ -380,7 +380,9 @@ class BookList(list): 3. size (file size of the book) 4. datetime (a UTC time tuple) 5. path (path on the device to the book) - 6. thumbnail (can be None) + 6. thumbnail (can be None) thumbnail is either a str/bytes object with the + image data or it should have an attribute image_path that stores an + absolute (platform native) path to the image 7. tags (a list of strings, can be empty). ''' diff --git a/src/calibre/devices/usbms/books.py b/src/calibre/devices/usbms/books.py index ced46ea2a1..e7462bdb73 100644 --- a/src/calibre/devices/usbms/books.py +++ b/src/calibre/devices/usbms/books.py @@ -38,8 +38,10 @@ class Book(MetaInformation): self.lpath = lpath self.mime = mime_type_ext(path_to_ext(lpath)) self.size = size # will be set later if None - self.datetime = time.gmtime() - + try: + self.datetime = time.gmtime(os.path.getctime(self.path)) + except: + self.datetime = time.gmtime() if other: self.smart_update(other) diff --git a/src/calibre/devices/usbms/driver.py b/src/calibre/devices/usbms/driver.py index 1291ffa834..4589b48af3 100644 --- a/src/calibre/devices/usbms/driver.py +++ b/src/calibre/devices/usbms/driver.py @@ -276,18 +276,22 @@ class USBMS(CLI, Device): # bl = cls.booklist_class() js = [] need_sync = False - try: - with open(cls.normalize_path(os.path.join(prefix, name)), 'rb') as f: - js = json.load(f, encoding='utf-8') - for item in js: - book = cls.book_class(prefix, item.get('lpath', None)) - for key in item.keys(): - setattr(book, key, item[key]) - bl.append(book) - except: - import traceback - traceback.print_exc() - bl = [] + cache_file = cls.normalize_path(os.path.join(prefix, name)) + if os.access(cache_file, os.R_OK): + try: + with open(cache_file, 'rb') as f: + js = json.load(f, encoding='utf-8') + for item in js: + book = cls.book_class(prefix, item.get('lpath', None)) + for key in item.keys(): + setattr(book, key, item[key]) + bl.append(book) + except: + import traceback + traceback.print_exc() + bl = [] + need_sync = True + else: need_sync = True return need_sync diff --git a/src/calibre/ebooks/epub/input.py b/src/calibre/ebooks/epub/input.py index 0f94fb674a..5c4e255177 100644 --- a/src/calibre/ebooks/epub/input.py +++ b/src/calibre/ebooks/epub/input.py @@ -37,8 +37,13 @@ class EPUBInput(InputFormatPlugin): scheme = item.get(xkey) if (scheme and scheme.lower() == 'uuid') or \ (item.text and item.text.startswith('urn:uuid:')): - key = str(item.text).rpartition(':')[-1] - key = list(map(ord, uuid.UUID(key).bytes)) + try: + key = str(item.text).rpartition(':')[-1] + key = list(map(ord, uuid.UUID(key).bytes)) + except: + import traceback + traceback.print_exc() + key = None try: root = etree.parse(encfile) @@ -49,7 +54,7 @@ class EPUBInput(InputFormatPlugin): cr = em.getparent().xpath('descendant::*[contains(name(), "CipherReference")]')[0] uri = cr.get('URI') path = os.path.abspath(os.path.join(os.path.dirname(encfile), '..', *uri.split('/'))) - if os.path.exists(path): + if key is not None and os.path.exists(path): self._encrypted_font_uris.append(uri) self.decrypt_font(key, path) return True diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py index edf0e763f7..1bc35b6a2b 100644 --- a/src/calibre/gui2/device.py +++ b/src/calibre/gui2/device.py @@ -20,7 +20,7 @@ from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \ pixmap_to_data, warning_dialog, \ question_dialog from calibre.ebooks.metadata import authors_to_string, authors_to_sort_string -from calibre import preferred_encoding +from calibre import preferred_encoding, prints from calibre.utils.filenames import ascii_filename from calibre.devices.errors import FreeSpaceError from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \ @@ -88,7 +88,7 @@ class DeviceManager(Thread): self.connected_device = None self.ejected_devices = set([]) self.connected_device_is_folder = False - self.folder_connection_path = None + self.folder_connection_requests = Queue.Queue(0) def report_progress(self, *args): pass @@ -175,15 +175,20 @@ class DeviceManager(Thread): def run(self): while self.keep_going: - if not self.is_device_connected and \ - self.folder_connection_path is not None: - f = self.folder_connection_path - self.folder_connection_path = None # Make sure we try this folder only once + folder_path = None + while True: try: - dev = FOLDER_DEVICE(f) + folder_path = self.folder_connection_requests.get_nowait() + except Queue.Empty: + break + if not folder_path or not os.access(folder_path, os.R_OK): + folder_path = None + if not self.is_device_connected and folder_path is not None: + try: + dev = FOLDER_DEVICE(folder_path) self.do_connect([[dev, None],], is_folder_device=True) except: - print 'Unable to open folder as device', f + prints('Unable to open folder as device', folder_path) traceback.print_exc() else: self.detect_device() @@ -226,7 +231,7 @@ class DeviceManager(Thread): # This will be called on the GUI thread. Because of this, we must store # information that the scanner thread will use to do the real work. def connect_to_folder(self, path): - self.folder_connection_path = path + self.folder_connection_requests.put(path) # This is called on the GUI thread. No problem here, because it calls the # device driver, telling it to tell the scanner when it passes by that the diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index d08c7d50c8..b5d2d653e5 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -29,7 +29,7 @@ from calibre.utils.date import dt_factory, qt_to_dt, isoformat, now from calibre.utils.pyparsing import ParseException from calibre.utils.search_query_parser import SearchQueryParser - +# Delegates {{{ class RatingDelegate(QStyledItemDelegate): COLOR = QColor("blue") SIZE = 16 @@ -303,7 +303,9 @@ class CcBoolDelegate(QStyledItemDelegate): val = 2 if val is None else 1 if not val else 0 editor.setCurrentIndex(val) -class BooksModel(QAbstractTableModel): +# }}} + +class BooksModel(QAbstractTableModel): # {{{ about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted') sorting_done = pyqtSignal(object, name='sortingDone') @@ -973,13 +975,13 @@ class BooksModel(QAbstractTableModel): self.db.set(row, column, val) self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \ index, index) - #if column == self.sorted_on[0]: - # self.resort() return True def set_search_restriction(self, s): self.db.data.set_search_restriction(s) +# }}} + class BooksView(TableView): TIME_FMT = '%d %b %Y' wrapper = textwrap.TextWrapper(width=20) @@ -1084,6 +1086,11 @@ class BooksView(TableView): if not self.restore_column_widths(): self.resizeColumnsToContents() + sort_col = self._model.sorted_on[0] + if sort_col in cm: + idx = cm.index(sort_col) + self.horizontalHeader().setSortIndicator(idx, self._model.sorted_on[1]) + def set_context_menu(self, edit_metadata, send_to_device, convert, view, save, open_folder, book_details, delete, similar_menu=None): self.setContextMenuPolicy(Qt.DefaultContextMenu) @@ -1418,9 +1425,12 @@ class DeviceBooksModel(BooksModel): data = {} item = self.db[self.map[current.row()]] cdata = item.thumbnail - if cdata: + if cdata is not None: img = QImage() - img.loadFromData(cdata) + if hasattr(cdata, 'image_path'): + img.load(cdata.image_path) + else: + img.loadFromData(cdata) if img.isNull(): img = self.default_image data['cover'] = img diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 1aecadba88..16b003a5c6 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -348,6 +348,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.save_menu.addAction(_('Save to disk in a single directory')) self.save_menu.addAction(_('Save only %s format to disk')% prefs['output_format'].upper()) + self.save_menu.addAction( + _('Save only %s format to disk in a single directory')% + prefs['output_format'].upper()) + self.save_sub_menu = SaveMenu(self) self.save_menu.addMenu(self.save_sub_menu) self.connect(self.save_sub_menu, SIGNAL('save_fmt(PyQt_PyObject)'), @@ -376,6 +380,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.save_to_single_dir) QObject.connect(self.save_menu.actions()[2], SIGNAL("triggered(bool)"), self.save_single_format_to_disk) + QObject.connect(self.save_menu.actions()[3], SIGNAL("triggered(bool)"), + self.save_single_fmt_to_single_dir) QObject.connect(self.action_view, SIGNAL("triggered(bool)"), self.view_book) QObject.connect(self.view_menu.actions()[0], @@ -670,7 +676,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.search.setMaximumWidth(self.width()-150) def connect_to_folder(self): - dir = choose_dir(self, 'Select Device Folder', 'Select folder to open') + dir = choose_dir(self, 'Select Device Folder', + _('Select folder to open as device')) if dir is not None: self.device_manager.connect_to_folder(dir) @@ -1809,6 +1816,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): def save_to_single_dir(self, checked): self.save_to_disk(checked, True) + def save_single_fmt_to_single_dir(self, *args): + self.save_to_disk(False, single_dir=True, + single_format=prefs['output_format']) + def save_to_disk(self, checked, single_dir=False, single_format=None): rows = self.current_view().selectionModel().selectedRows() if not rows or len(rows) == 0: @@ -2151,14 +2162,25 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): format = d.format() self.view_format(row, format) + def _view_check(self, num, max_=3): + if num <= max_: + return True + return question_dialog(self, _('Multiple Books Selected'), + _('You are attempting to open %d books. Opening too many ' + 'books at once can be slow and have a negative effect on the ' + 'responsiveness of your computer. Once started the process ' + 'cannot be stopped until complete. Do you wish to continue?' + ) % num) + def view_folder(self, *args): 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 open folder'), - _('No book selected')) - d.exec_() - return + if not rows or len(rows) == 0: + d = error_dialog(self, _('Cannot open folder'), + _('No book selected')) + d.exec_() + return + if not self._view_check(len(rows)): + return for row in rows: path = self.library_view.model().db.abspath(row.row()) QDesktopServices.openUrl(QUrl.fromLocalFile(path)) @@ -2176,14 +2198,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self._launch_viewer() return - if len(rows) >= 3: - if not question_dialog(self, _('Multiple Books Selected'), - _('You are attempting to open %d books. Opening too many ' - 'books at once can be slow and have a negative effect on the ' - 'responsiveness of your computer. Once started the process ' - 'cannot be stopped until complete. Do you wish to continue?' - )% len(rows)): - return + if not self._view_check(len(rows)): + return if self.current_view() is self.library_view: for row in rows: @@ -2261,6 +2277,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI): self.save_menu.actions()[2].setText( _('Save only %s format to disk')% prefs['output_format'].upper()) + self.save_menu.actions()[3].setText( + _('Save only %s format to disk in a single directory')% + prefs['output_format'].upper()) self.library_view.model().read_config() self.library_view.model().refresh() self.library_view.model().research()