mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from custcol trunk
This commit is contained in:
commit
6f504a1009
@ -1,11 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
__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
|
||||
'''
|
||||
|
||||
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)
|
||||
|
||||
|
@ -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']})]
|
||||
|
||||
|
@ -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']})]
|
||||
|
||||
|
@ -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).
|
||||
'''
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user