Merge from trunk

This commit is contained in:
Charles Haley 2010-06-12 17:19:39 +01:00
commit c5499de545
6 changed files with 1464 additions and 1466 deletions

View File

@ -11,11 +11,11 @@ http://www.libero-news.it/
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
class LiberoNews(BasicNewsRecipe): class LiberoNews(BasicNewsRecipe):
__author__ = 'Marini Gabriele' __author__ = 'Marini Gabriele'
description = 'Italian daily newspaper' description = 'Italian daily newspaper'
cover_url = 'http://www.ilgiornale.it/img_v1/logo.gif' cover_url = 'http://www.libero-news.it/images/logo.png'
title = u'Libero' title = u'Libero '
publisher = 'EDITORIALE LIBERO s.r.l 2006' publisher = 'EDITORIALE LIBERO s.r.l 2006'
category = 'News, politics, culture, economy, general interest' category = 'News, politics, culture, economy, general interest'

1197
src/calibre/gui2/actions.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,24 +3,26 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
# Imports {{{ # Imports {{{
import os, traceback, Queue, time, socket, cStringIO, re import os, traceback, Queue, time, socket, cStringIO, re, sys
from threading import Thread, RLock from threading import Thread, RLock
from itertools import repeat from itertools import repeat
from functools import partial from functools import partial
from binascii import unhexlify from binascii import unhexlify
from PyQt4.Qt import QMenu, QAction, QActionGroup, QIcon, SIGNAL, QPixmap, \ 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, \ from calibre.customize.ui import available_input_formats, available_output_formats, \
device_plugins device_plugins
from calibre.devices.interface import DevicePlugin from calibre.devices.interface import DevicePlugin
from calibre.devices.errors import UserFeedback
from calibre.gui2.dialogs.choose_format import ChooseFormatDialog from calibre.gui2.dialogs.choose_format import ChooseFormatDialog
from calibre.utils.ipc.job import BaseJob from calibre.utils.ipc.job import BaseJob
from calibre.devices.scanner import DeviceScanner from calibre.devices.scanner import DeviceScanner
from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \ from calibre.gui2 import config, error_dialog, Dispatcher, dynamic, \
pixmap_to_data, warning_dialog, \ 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.ebooks.metadata import authors_to_string, authors_to_sort_string
from calibre import preferred_encoding, prints from calibre import preferred_encoding, prints
from calibre.utils.filenames import ascii_filename from calibre.utils.filenames import ascii_filename
@ -597,10 +599,204 @@ class Emailer(Thread): # {{{
# }}} # }}}
class DeviceMixin(object): class DeviceMixin(object): # {{{
def __init__(self): def __init__(self):
self.db_book_uuid_cache = set() 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): def dispatch_sync_event(self, dest, delete, specific):
rows = self.library_view.selectionModel().selectedRows() rows = self.library_view.selectionModel().selectedRows()
@ -1220,3 +1416,6 @@ class DeviceMixin(object):
# Correct the metadata cache on device. # Correct the metadata cache on device.
if self.device_manager.is_device_connected: if self.device_manager.is_device_connected:
self.device_manager.sync_booklists(None, booklists) self.device_manager.sync_booklists(None, booklists)
# }}}

View File

@ -47,7 +47,7 @@ class ToolbarMixin(object): # {{{
def __init__(self): def __init__(self):
md = QMenu() md = QMenu()
md.addAction(_('Edit metadata individually'), md.addAction(_('Edit metadata individually'),
partial(self.edit_metadata, False)) partial(self.edit_metadata, False, bulk=False))
md.addSeparator() md.addSeparator()
md.addAction(_('Edit metadata in bulk'), md.addAction(_('Edit metadata in bulk'),
partial(self.edit_metadata, False, bulk=True)) partial(self.edit_metadata, False, bulk=True))

File diff suppressed because it is too large Load Diff

View File

@ -3,12 +3,13 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import traceback import traceback
from PyQt4.QtCore import QThread, pyqtSignal from PyQt4.Qt import QThread, pyqtSignal, QDesktopServices, QUrl, Qt
import mechanize import mechanize
from calibre.constants import __version__, iswindows, isosx from calibre.constants import __appname__, __version__, iswindows, isosx
from calibre import browser from calibre import browser
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.gui2 import config, dynamic, question_dialog
URL = 'http://status.calibre-ebook.com/latest' URL = 'http://status.calibre-ebook.com/latest'
@ -36,3 +37,35 @@ class CheckForUpdates(QThread):
traceback.print_exc() traceback.print_exc()
self.sleep(self.INTERVAL) 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)