Merge from custcol trunk

This commit is contained in:
Charles Haley 2010-05-17 23:35:40 +01:00
commit 6f504a1009
10 changed files with 108 additions and 62 deletions

View File

@ -1,11 +1,10 @@
#!/usr/bin/env python
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2009-2010, Darko Miletic <darko.miletic at gmail.com>'
''' '''
www.instapaper.com www.instapaper.com
''' '''
import urllib
from calibre import strftime from calibre import strftime
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
@ -22,18 +21,15 @@ class Instapaper(BasicNewsRecipe):
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
remove_javascript = True
needs_subscription = True needs_subscription = True
INDEX = u'http://www.instapaper.com' INDEX = u'http://www.instapaper.com'
LOGIN = INDEX + u'/user/login' LOGIN = INDEX + u'/user/login'
html2lrf_options = [ conversion_options = {
'--comment', description 'comment' : description
, '--category', category , 'tags' : category
, '--publisher', publisher , '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}"'
feeds = [ feeds = [
(u'Unread articles' , INDEX + u'/u' ) (u'Unread articles' , INDEX + u'/u' )
@ -63,7 +59,7 @@ class Instapaper(BasicNewsRecipe):
description = self.tag_to_string(item.div) description = self.tag_to_string(item.div)
atag = item.a atag = item.a
if atag and atag.has_key('href'): if atag and atag.has_key('href'):
url = self.INDEX + atag['href'] + '/text' url = atag['href']
title = self.tag_to_string(atag) title = self.tag_to_string(atag)
date = strftime(self.timefmt) date = strftime(self.timefmt)
articles.append({ articles.append({
@ -75,3 +71,6 @@ class Instapaper(BasicNewsRecipe):
totalfeeds.append((feedtitle, articles)) totalfeeds.append((feedtitle, articles))
return totalfeeds return totalfeeds
def print_version(self, url):
return self.INDEX + '/text?u=' + urllib.quote(url)

View File

@ -23,7 +23,7 @@ class NewYorkReviewOfBooks(BasicNewsRecipe):
no_javascript = True no_javascript = True
needs_subscription = 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', remove_tags = [dict(attrs={'class':['article-tools', 'article-links',
'center advertisement']})] 'center advertisement']})]

View File

@ -21,7 +21,7 @@ class NewYorkReviewOfBooks(BasicNewsRecipe):
no_stylesheets = True no_stylesheets = True
no_javascript = 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', remove_tags = [dict(attrs={'class':['article-tools', 'article-links',
'center advertisement']})] 'center advertisement']})]

View File

@ -380,7 +380,9 @@ class BookList(list):
3. size (file size of the book) 3. size (file size of the book)
4. datetime (a UTC time tuple) 4. datetime (a UTC time tuple)
5. path (path on the device to the book) 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). 7. tags (a list of strings, can be empty).
''' '''

View File

@ -38,8 +38,10 @@ class Book(MetaInformation):
self.lpath = lpath self.lpath = lpath
self.mime = mime_type_ext(path_to_ext(lpath)) self.mime = mime_type_ext(path_to_ext(lpath))
self.size = size # will be set later if None 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: if other:
self.smart_update(other) self.smart_update(other)

View File

@ -276,18 +276,22 @@ class USBMS(CLI, Device):
# bl = cls.booklist_class() # bl = cls.booklist_class()
js = [] js = []
need_sync = False need_sync = False
try: cache_file = cls.normalize_path(os.path.join(prefix, name))
with open(cls.normalize_path(os.path.join(prefix, name)), 'rb') as f: if os.access(cache_file, os.R_OK):
js = json.load(f, encoding='utf-8') try:
for item in js: with open(cache_file, 'rb') as f:
book = cls.book_class(prefix, item.get('lpath', None)) js = json.load(f, encoding='utf-8')
for key in item.keys(): for item in js:
setattr(book, key, item[key]) book = cls.book_class(prefix, item.get('lpath', None))
bl.append(book) for key in item.keys():
except: setattr(book, key, item[key])
import traceback bl.append(book)
traceback.print_exc() except:
bl = [] import traceback
traceback.print_exc()
bl = []
need_sync = True
else:
need_sync = True need_sync = True
return need_sync return need_sync

View File

@ -37,8 +37,13 @@ class EPUBInput(InputFormatPlugin):
scheme = item.get(xkey) scheme = item.get(xkey)
if (scheme and scheme.lower() == 'uuid') or \ if (scheme and scheme.lower() == 'uuid') or \
(item.text and item.text.startswith('urn:uuid:')): (item.text and item.text.startswith('urn:uuid:')):
key = str(item.text).rpartition(':')[-1] try:
key = list(map(ord, uuid.UUID(key).bytes)) key = str(item.text).rpartition(':')[-1]
key = list(map(ord, uuid.UUID(key).bytes))
except:
import traceback
traceback.print_exc()
key = None
try: try:
root = etree.parse(encfile) root = etree.parse(encfile)
@ -49,7 +54,7 @@ class EPUBInput(InputFormatPlugin):
cr = em.getparent().xpath('descendant::*[contains(name(), "CipherReference")]')[0] cr = em.getparent().xpath('descendant::*[contains(name(), "CipherReference")]')[0]
uri = cr.get('URI') uri = cr.get('URI')
path = os.path.abspath(os.path.join(os.path.dirname(encfile), '..', *uri.split('/'))) 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._encrypted_font_uris.append(uri)
self.decrypt_font(key, path) self.decrypt_font(key, path)
return True return True

View File

@ -20,7 +20,7 @@ from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
pixmap_to_data, warning_dialog, \ pixmap_to_data, warning_dialog, \
question_dialog question_dialog
from calibre.ebooks.metadata import authors_to_string, authors_to_sort_string 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.utils.filenames import ascii_filename
from calibre.devices.errors import FreeSpaceError from calibre.devices.errors import FreeSpaceError
from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \ from calibre.utils.smtp import compose_mail, sendmail, extract_email_address, \
@ -88,7 +88,7 @@ class DeviceManager(Thread):
self.connected_device = None self.connected_device = None
self.ejected_devices = set([]) self.ejected_devices = set([])
self.connected_device_is_folder = False self.connected_device_is_folder = False
self.folder_connection_path = None self.folder_connection_requests = Queue.Queue(0)
def report_progress(self, *args): def report_progress(self, *args):
pass pass
@ -175,15 +175,20 @@ class DeviceManager(Thread):
def run(self): def run(self):
while self.keep_going: while self.keep_going:
if not self.is_device_connected and \ folder_path = None
self.folder_connection_path is not None: while True:
f = self.folder_connection_path
self.folder_connection_path = None # Make sure we try this folder only once
try: 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) self.do_connect([[dev, None],], is_folder_device=True)
except: except:
print 'Unable to open folder as device', f prints('Unable to open folder as device', folder_path)
traceback.print_exc() traceback.print_exc()
else: else:
self.detect_device() self.detect_device()
@ -226,7 +231,7 @@ class DeviceManager(Thread):
# This will be called on the GUI thread. Because of this, we must store # 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. # information that the scanner thread will use to do the real work.
def connect_to_folder(self, path): 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 # 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 # device driver, telling it to tell the scanner when it passes by that the

View File

@ -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.pyparsing import ParseException
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
# Delegates {{{
class RatingDelegate(QStyledItemDelegate): class RatingDelegate(QStyledItemDelegate):
COLOR = QColor("blue") COLOR = QColor("blue")
SIZE = 16 SIZE = 16
@ -303,7 +303,9 @@ class CcBoolDelegate(QStyledItemDelegate):
val = 2 if val is None else 1 if not val else 0 val = 2 if val is None else 1 if not val else 0
editor.setCurrentIndex(val) editor.setCurrentIndex(val)
class BooksModel(QAbstractTableModel): # }}}
class BooksModel(QAbstractTableModel): # {{{
about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted') about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted')
sorting_done = pyqtSignal(object, name='sortingDone') sorting_done = pyqtSignal(object, name='sortingDone')
@ -973,13 +975,13 @@ class BooksModel(QAbstractTableModel):
self.db.set(row, column, val) self.db.set(row, column, val)
self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \ self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \
index, index) index, index)
#if column == self.sorted_on[0]:
# self.resort()
return True return True
def set_search_restriction(self, s): def set_search_restriction(self, s):
self.db.data.set_search_restriction(s) self.db.data.set_search_restriction(s)
# }}}
class BooksView(TableView): class BooksView(TableView):
TIME_FMT = '%d %b %Y' TIME_FMT = '%d %b %Y'
wrapper = textwrap.TextWrapper(width=20) wrapper = textwrap.TextWrapper(width=20)
@ -1084,6 +1086,11 @@ class BooksView(TableView):
if not self.restore_column_widths(): if not self.restore_column_widths():
self.resizeColumnsToContents() 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, def set_context_menu(self, edit_metadata, send_to_device, convert, view,
save, open_folder, book_details, delete, similar_menu=None): save, open_folder, book_details, delete, similar_menu=None):
self.setContextMenuPolicy(Qt.DefaultContextMenu) self.setContextMenuPolicy(Qt.DefaultContextMenu)
@ -1418,9 +1425,12 @@ class DeviceBooksModel(BooksModel):
data = {} data = {}
item = self.db[self.map[current.row()]] item = self.db[self.map[current.row()]]
cdata = item.thumbnail cdata = item.thumbnail
if cdata: if cdata is not None:
img = QImage() img = QImage()
img.loadFromData(cdata) if hasattr(cdata, 'image_path'):
img.load(cdata.image_path)
else:
img.loadFromData(cdata)
if img.isNull(): if img.isNull():
img = self.default_image img = self.default_image
data['cover'] = img data['cover'] = img

View File

@ -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 to disk in a single directory'))
self.save_menu.addAction(_('Save only %s format to disk')% self.save_menu.addAction(_('Save only %s format to disk')%
prefs['output_format'].upper()) 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_sub_menu = SaveMenu(self)
self.save_menu.addMenu(self.save_sub_menu) self.save_menu.addMenu(self.save_sub_menu)
self.connect(self.save_sub_menu, SIGNAL('save_fmt(PyQt_PyObject)'), 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) self.save_to_single_dir)
QObject.connect(self.save_menu.actions()[2], SIGNAL("triggered(bool)"), QObject.connect(self.save_menu.actions()[2], SIGNAL("triggered(bool)"),
self.save_single_format_to_disk) 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)"), QObject.connect(self.action_view, SIGNAL("triggered(bool)"),
self.view_book) self.view_book)
QObject.connect(self.view_menu.actions()[0], QObject.connect(self.view_menu.actions()[0],
@ -670,7 +676,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search.setMaximumWidth(self.width()-150) self.search.setMaximumWidth(self.width()-150)
def connect_to_folder(self): 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: if dir is not None:
self.device_manager.connect_to_folder(dir) self.device_manager.connect_to_folder(dir)
@ -1809,6 +1816,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
def save_to_single_dir(self, checked): def save_to_single_dir(self, checked):
self.save_to_disk(checked, True) 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): def save_to_disk(self, checked, single_dir=False, single_format=None):
rows = self.current_view().selectionModel().selectedRows() rows = self.current_view().selectionModel().selectedRows()
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
@ -2151,14 +2162,25 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
format = d.format() format = d.format()
self.view_format(row, 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): def view_folder(self, *args):
rows = self.current_view().selectionModel().selectedRows() rows = self.current_view().selectionModel().selectedRows()
if self.current_view() is self.library_view: if not rows or len(rows) == 0:
if not rows or len(rows) == 0: d = error_dialog(self, _('Cannot open folder'),
d = error_dialog(self, _('Cannot open folder'), _('No book selected'))
_('No book selected')) d.exec_()
d.exec_() return
return if not self._view_check(len(rows)):
return
for row in rows: for row in rows:
path = self.library_view.model().db.abspath(row.row()) path = self.library_view.model().db.abspath(row.row())
QDesktopServices.openUrl(QUrl.fromLocalFile(path)) QDesktopServices.openUrl(QUrl.fromLocalFile(path))
@ -2176,14 +2198,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self._launch_viewer() self._launch_viewer()
return return
if len(rows) >= 3: if not self._view_check(len(rows)):
if not question_dialog(self, _('Multiple Books Selected'), return
_('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 self.current_view() is self.library_view: if self.current_view() is self.library_view:
for row in rows: for row in rows:
@ -2261,6 +2277,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.save_menu.actions()[2].setText( self.save_menu.actions()[2].setText(
_('Save only %s format to disk')% _('Save only %s format to disk')%
prefs['output_format'].upper()) 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().read_config()
self.library_view.model().refresh() self.library_view.model().refresh()
self.library_view.model().research() self.library_view.model().research()