mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Merge from trunk
This commit is contained in:
commit
c5499de545
@ -14,8 +14,8 @@ class LiberoNews(BasicNewsRecipe):
|
||||
__author__ = 'Marini Gabriele'
|
||||
description = 'Italian daily newspaper'
|
||||
|
||||
cover_url = 'http://www.ilgiornale.it/img_v1/logo.gif'
|
||||
title = u'Libero'
|
||||
cover_url = 'http://www.libero-news.it/images/logo.png'
|
||||
title = u'Libero '
|
||||
publisher = 'EDITORIALE LIBERO s.r.l 2006'
|
||||
category = 'News, politics, culture, economy, general interest'
|
||||
|
||||
|
1197
src/calibre/gui2/actions.py
Normal file
1197
src/calibre/gui2/actions.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -3,24 +3,26 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
# Imports {{{
|
||||
import os, traceback, Queue, time, socket, cStringIO, re
|
||||
import os, traceback, Queue, time, socket, cStringIO, re, sys
|
||||
from threading import Thread, RLock
|
||||
from itertools import repeat
|
||||
from functools import partial
|
||||
from binascii import unhexlify
|
||||
|
||||
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, QPixmap, \
|
||||
Qt, pyqtSignal
|
||||
Qt, pyqtSignal, QColor, QPainter
|
||||
from PyQt4.QtSvg import QSvgRenderer
|
||||
|
||||
from calibre.customize.ui import available_input_formats, available_output_formats, \
|
||||
device_plugins
|
||||
from calibre.devices.interface import DevicePlugin
|
||||
from calibre.devices.errors import UserFeedback
|
||||
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
|
||||
from calibre.utils.ipc.job import BaseJob
|
||||
from calibre.devices.scanner import DeviceScanner
|
||||
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
|
||||
pixmap_to_data, warning_dialog, \
|
||||
question_dialog
|
||||
question_dialog, info_dialog, choose_dir
|
||||
from calibre.ebooks.metadata import authors_to_string, authors_to_sort_string
|
||||
from calibre import preferred_encoding, prints
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
@ -597,10 +599,204 @@ class Emailer(Thread): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class DeviceMixin(object):
|
||||
class DeviceMixin(object): # {{{
|
||||
|
||||
def __init__(self):
|
||||
self.db_book_uuid_cache = set()
|
||||
self.device_error_dialog = error_dialog(self, _('Error'),
|
||||
_('Error communicating with device'), ' ')
|
||||
self.device_error_dialog.setModal(Qt.NonModal)
|
||||
self.device_connected = None
|
||||
self.emailer = Emailer()
|
||||
self.emailer.start()
|
||||
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
|
||||
self.job_manager, Dispatcher(self.status_bar.show_message))
|
||||
self.device_manager.start()
|
||||
|
||||
def set_default_thumbnail(self, height):
|
||||
r = QSvgRenderer(I('book.svg'))
|
||||
pixmap = QPixmap(height, height)
|
||||
pixmap.fill(QColor(255,255,255))
|
||||
p = QPainter(pixmap)
|
||||
r.render(p)
|
||||
p.end()
|
||||
self.default_thumbnail = (pixmap.width(), pixmap.height(),
|
||||
pixmap_to_data(pixmap))
|
||||
|
||||
def connect_to_folder(self):
|
||||
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)
|
||||
|
||||
def disconnect_from_folder(self):
|
||||
self.device_manager.disconnect_folder()
|
||||
|
||||
def _sync_action_triggered(self, *args):
|
||||
m = getattr(self, '_sync_menu', None)
|
||||
if m is not None:
|
||||
m.trigger_default()
|
||||
|
||||
def create_device_menu(self):
|
||||
self._sync_menu = DeviceMenu(self)
|
||||
self.action_sync.setMenu(self._sync_menu)
|
||||
self.connect(self._sync_menu,
|
||||
SIGNAL('sync(PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)'),
|
||||
self.dispatch_sync_event)
|
||||
self._sync_menu.fetch_annotations.connect(self.fetch_annotations)
|
||||
self._sync_menu.connect_to_folder.connect(self.connect_to_folder)
|
||||
self._sync_menu.disconnect_from_folder.connect(self.disconnect_from_folder)
|
||||
if self.device_connected:
|
||||
self._sync_menu.connect_to_folder_action.setEnabled(False)
|
||||
if self.device_connected == 'folder':
|
||||
self._sync_menu.disconnect_from_folder_action.setEnabled(True)
|
||||
else:
|
||||
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
|
||||
else:
|
||||
self._sync_menu.connect_to_folder_action.setEnabled(True)
|
||||
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
|
||||
|
||||
|
||||
|
||||
def device_job_exception(self, job):
|
||||
'''
|
||||
Handle exceptions in threaded device jobs.
|
||||
'''
|
||||
if isinstance(getattr(job, 'exception', None), UserFeedback):
|
||||
ex = job.exception
|
||||
func = {UserFeedback.ERROR:error_dialog,
|
||||
UserFeedback.WARNING:warning_dialog,
|
||||
UserFeedback.INFO:info_dialog}[ex.level]
|
||||
return func(self, _('Failed'), ex.msg, det_msg=ex.details if
|
||||
ex.details else '', show=True)
|
||||
|
||||
try:
|
||||
if 'Could not read 32 bytes on the control bus.' in \
|
||||
unicode(job.details):
|
||||
error_dialog(self, _('Error talking to device'),
|
||||
_('There was a temporary error talking to the '
|
||||
'device. Please unplug and reconnect the device '
|
||||
'and or reboot.')).show()
|
||||
return
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
prints(job.details, file=sys.stderr)
|
||||
except:
|
||||
pass
|
||||
if not self.device_error_dialog.isVisible():
|
||||
self.device_error_dialog.setDetailedText(job.details)
|
||||
self.device_error_dialog.show()
|
||||
|
||||
# Device connected {{{
|
||||
def device_detected(self, connected, is_folder_device):
|
||||
'''
|
||||
Called when a device is connected to the computer.
|
||||
'''
|
||||
if connected:
|
||||
self._sync_menu.connect_to_folder_action.setEnabled(False)
|
||||
if is_folder_device:
|
||||
self._sync_menu.disconnect_from_folder_action.setEnabled(True)
|
||||
self.device_manager.get_device_information(\
|
||||
Dispatcher(self.info_read))
|
||||
self.set_default_thumbnail(\
|
||||
self.device_manager.device.THUMBNAIL_HEIGHT)
|
||||
self.status_bar.show_message(_('Device: ')+\
|
||||
self.device_manager.device.__class__.get_gui_name()+\
|
||||
_(' detected.'), 3000)
|
||||
self.device_connected = 'device' if not is_folder_device else 'folder'
|
||||
self._sync_menu.enable_device_actions(True,
|
||||
self.device_manager.device.card_prefix(),
|
||||
self.device_manager.device)
|
||||
self.location_view.model().device_connected(self.device_manager.device)
|
||||
self.eject_action.setEnabled(True)
|
||||
self.refresh_ondevice_info (device_connected = True, reset_only = True)
|
||||
else:
|
||||
self._sync_menu.connect_to_folder_action.setEnabled(True)
|
||||
self._sync_menu.disconnect_from_folder_action.setEnabled(False)
|
||||
self.device_connected = None
|
||||
self._sync_menu.enable_device_actions(False)
|
||||
self.location_view.model().update_devices()
|
||||
self.vanity.setText(self.vanity_template%\
|
||||
dict(version=self.latest_version, device=' '))
|
||||
self.device_info = ' '
|
||||
if self.current_view() != self.library_view:
|
||||
self.book_details.reset_info()
|
||||
self.location_view.setCurrentIndex(self.location_view.model().index(0))
|
||||
self.eject_action.setEnabled(False)
|
||||
self.refresh_ondevice_info (device_connected = False)
|
||||
|
||||
def info_read(self, job):
|
||||
'''
|
||||
Called once device information has been read.
|
||||
'''
|
||||
if job.failed:
|
||||
return self.device_job_exception(job)
|
||||
info, cp, fs = job.result
|
||||
self.location_view.model().update_devices(cp, fs)
|
||||
self.device_info = _('Connected ')+info[0]
|
||||
self.vanity.setText(self.vanity_template%\
|
||||
dict(version=self.latest_version, device=self.device_info))
|
||||
|
||||
self.device_manager.books(Dispatcher(self.metadata_downloaded))
|
||||
|
||||
def metadata_downloaded(self, job):
|
||||
'''
|
||||
Called once metadata has been read for all books on the device.
|
||||
'''
|
||||
if job.failed:
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
self.set_books_in_library(job.result, reset=True)
|
||||
mainlist, cardalist, cardblist = job.result
|
||||
self.memory_view.set_database(mainlist)
|
||||
self.memory_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
|
||||
self.card_a_view.set_database(cardalist)
|
||||
self.card_a_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
|
||||
self.card_b_view.set_database(cardblist)
|
||||
self.card_b_view.set_editable(self.device_manager.device.CAN_SET_METADATA)
|
||||
self.sync_news()
|
||||
self.sync_catalogs()
|
||||
self.refresh_ondevice_info(device_connected = True)
|
||||
|
||||
def refresh_ondevice_info(self, device_connected, reset_only = False):
|
||||
'''
|
||||
Force the library view to refresh, taking into consideration
|
||||
books information
|
||||
'''
|
||||
self.book_on_device(None, reset=True)
|
||||
if reset_only:
|
||||
return
|
||||
self.library_view.set_device_connected(device_connected)
|
||||
|
||||
# }}}
|
||||
|
||||
def remove_paths(self, paths):
|
||||
return self.device_manager.delete_books(
|
||||
Dispatcher(self.books_deleted), paths)
|
||||
|
||||
def books_deleted(self, job):
|
||||
'''
|
||||
Called once deletion is done on the device
|
||||
'''
|
||||
for view in (self.memory_view, self.card_a_view, self.card_b_view):
|
||||
view.model().deletion_done(job, job.failed)
|
||||
if job.failed:
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
|
||||
if self.delete_memory.has_key(job):
|
||||
paths, model = self.delete_memory.pop(job)
|
||||
self.device_manager.remove_books_from_metadata(paths,
|
||||
self.booklists())
|
||||
model.paths_deleted(paths)
|
||||
self.upload_booklists()
|
||||
# Clear the ondevice info so it will be recomputed
|
||||
self.book_on_device(None, None, reset=True)
|
||||
# We want to reset all the ondevice flags in the library. Use a big
|
||||
# hammer, so we don't need to worry about whether some succeeded or not
|
||||
self.library_view.model().refresh()
|
||||
|
||||
|
||||
def dispatch_sync_event(self, dest, delete, specific):
|
||||
rows = self.library_view.selectionModel().selectedRows()
|
||||
@ -1220,3 +1416,6 @@ class DeviceMixin(object):
|
||||
# Correct the metadata cache on device.
|
||||
if self.device_manager.is_device_connected:
|
||||
self.device_manager.sync_booklists(None, booklists)
|
||||
|
||||
# }}}
|
||||
|
||||
|
@ -47,7 +47,7 @@ class ToolbarMixin(object): # {{{
|
||||
def __init__(self):
|
||||
md = QMenu()
|
||||
md.addAction(_('Edit metadata individually'),
|
||||
partial(self.edit_metadata, False))
|
||||
partial(self.edit_metadata, False, bulk=False))
|
||||
md.addSeparator()
|
||||
md.addAction(_('Edit metadata in bulk'),
|
||||
partial(self.edit_metadata, False, bulk=True))
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,12 +3,13 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import traceback
|
||||
|
||||
from PyQt4.QtCore import QThread, pyqtSignal
|
||||
from PyQt4.Qt import QThread, pyqtSignal, QDesktopServices, QUrl, Qt
|
||||
import mechanize
|
||||
|
||||
from calibre.constants import __version__, iswindows, isosx
|
||||
from calibre.constants import __appname__, __version__, iswindows, isosx
|
||||
from calibre import browser
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.gui2 import config, dynamic, question_dialog
|
||||
|
||||
URL = 'http://status.calibre-ebook.com/latest'
|
||||
|
||||
@ -36,3 +37,35 @@ class CheckForUpdates(QThread):
|
||||
traceback.print_exc()
|
||||
self.sleep(self.INTERVAL)
|
||||
|
||||
class UpdateMixin(object):
|
||||
|
||||
def __init__(self, opts):
|
||||
if not opts.no_update_check:
|
||||
self.update_checker = CheckForUpdates(self)
|
||||
self.update_checker.update_found.connect(self.update_found,
|
||||
type=Qt.QueuedConnection)
|
||||
self.update_checker.start()
|
||||
|
||||
def update_found(self, version):
|
||||
os = 'windows' if iswindows else 'osx' if isosx else 'linux'
|
||||
url = 'http://calibre-ebook.com/download_%s'%os
|
||||
self.latest_version = '<br>' + _('<span style="color:red; font-weight:bold">'
|
||||
'Latest version: <a href="%s">%s</a></span>')%(url, version)
|
||||
self.vanity.setText(self.vanity_template%\
|
||||
(dict(version=self.latest_version,
|
||||
device=self.device_info)))
|
||||
self.vanity.update()
|
||||
if config.get('new_version_notification') and \
|
||||
dynamic.get('update to version %s'%version, True):
|
||||
if question_dialog(self, _('Update available'),
|
||||
_('%s has been updated to version %s. '
|
||||
'See the <a href="http://calibre-ebook.com/whats-new'
|
||||
'">new features</a>. Visit the download pa'
|
||||
'ge?')%(__appname__, version)):
|
||||
url = 'http://calibre-ebook.com/download_'+\
|
||||
('windows' if iswindows else 'osx' if isosx else 'linux')
|
||||
QDesktopServices.openUrl(QUrl(url))
|
||||
dynamic.set('update to version %s'%version, False)
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user