MTP: Implement user specified ignoring of devices

This commit is contained in:
Kovid Goyal 2012-09-08 17:54:20 +05:30
parent 4a419d2d1d
commit ceebebf54f
7 changed files with 145 additions and 40 deletions

View File

@ -110,3 +110,9 @@ class WrongDestinationError(PathError):
trying to send books to a non existant storage card.''' trying to send books to a non existant storage card.'''
pass pass
class BlacklistedDevice(OpenFailed):
''' Raise this error during open() when the device being opened has been
blacklisted by the user. Only used in drivers that manage device presence,
like the MTP driver. '''
pass

View File

@ -59,4 +59,7 @@ class MTPDeviceBase(DevicePlugin):
from calibre.devices.utils import build_template_regexp from calibre.devices.utils import build_template_regexp
return build_template_regexp(self.save_template) return build_template_regexp(self.save_template)
def is_customizable(self):
return True

View File

@ -16,7 +16,7 @@ from calibre.constants import iswindows, numeric_version
from calibre.devices.mtp.base import debug from calibre.devices.mtp.base import debug
from calibre.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory from calibre.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory
from calibre.utils.config import from_json, to_json, JSONConfig from calibre.utils.config import from_json, to_json, JSONConfig
from calibre.utils.date import now, isoformat from calibre.utils.date import now, isoformat, utcnow
BASE = importlib.import_module('calibre.devices.mtp.%s.driver'%( BASE = importlib.import_module('calibre.devices.mtp.%s.driver'%(
'windows' if iswindows else 'unix')).MTP_DEVICE 'windows' if iswindows else 'unix')).MTP_DEVICE
@ -51,6 +51,8 @@ class MTP_DEVICE(BASE):
'wordplayer/calibretransfer', 'Books', 'sdcard/ebooks', 'wordplayer/calibretransfer', 'Books', 'sdcard/ebooks',
'eBooks', 'kindle'] 'eBooks', 'kindle']
p.defaults['send_template'] = config().parse().send_template p.defaults['send_template'] = config().parse().send_template
p.defaults['blacklist'] = []
p.defaults['history'] = {}
return self._prefs return self._prefs
@ -74,6 +76,11 @@ class MTP_DEVICE(BASE):
self.current_library_uuid = library_uuid self.current_library_uuid = library_uuid
self.location_paths = None self.location_paths = None
BASE.open(self, devices, library_uuid) BASE.open(self, devices, library_uuid)
h = self.prefs['history']
if self.current_serial_num:
h[self.current_serial_num] = (self.current_friendly_name,
isoformat(utcnow()))
self.prefs['history'] = h
# Device information {{{ # Device information {{{
def _update_drive_info(self, storage, location_code, name=None): def _update_drive_info(self, storage, location_code, name=None):

View File

@ -15,7 +15,7 @@ from functools import partial
from calibre import prints, as_unicode from calibre import prints, as_unicode
from calibre.constants import plugins from calibre.constants import plugins
from calibre.ptempfile import SpooledTemporaryFile from calibre.ptempfile import SpooledTemporaryFile
from calibre.devices.errors import OpenFailed, DeviceError from calibre.devices.errors import OpenFailed, DeviceError, BlacklistedDevice
from calibre.devices.mtp.base import MTPDeviceBase, synchronous from calibre.devices.mtp.base import MTPDeviceBase, synchronous
MTPDevice = namedtuple('MTPDevice', 'busnum devnum vendor_id product_id ' MTPDevice = namedtuple('MTPDevice', 'busnum devnum vendor_id product_id '
@ -99,19 +99,25 @@ class MTP_DEVICE(MTPDeviceBase):
return False return False
p('Known MTP devices connected:') p('Known MTP devices connected:')
for d in devs: p(d) for d in devs: p(d)
d = devs[0]
for d in devs:
p('\nTrying to open:', d) p('\nTrying to open:', d)
try: try:
self.open(d, 'debug') self.open(d, 'debug')
except BlacklistedDevice:
p('This device has been blacklisted by the user')
continue
except: except:
p('Opening device failed:') p('Opening device failed:')
p(traceback.format_exc()) p(traceback.format_exc())
return False return False
else:
p('Opened', self.current_friendly_name, 'successfully') p('Opened', self.current_friendly_name, 'successfully')
p('Storage info:') p('Storage info:')
p(pprint.pformat(self.dev.storage_info)) p(pprint.pformat(self.dev.storage_info))
self.eject() self.post_yank_cleanup()
return True return True
return False
@synchronous @synchronous
def create_device(self, connected_device): def create_device(self, connected_device):
@ -167,6 +173,12 @@ class MTP_DEVICE(MTPDeviceBase):
if not storage: if not storage:
self.blacklisted_devices.add(connected_device) self.blacklisted_devices.add(connected_device)
raise OpenFailed('No storage found for device %s'%(connected_device,)) raise OpenFailed('No storage found for device %s'%(connected_device,))
snum = self.dev.serial_number
if snum in self.prefs.get('blacklist', []):
self.blacklisted_devices.add(connected_device)
self.dev = None
raise BlacklistedDevice(
'The %s device has been blacklisted by the user'%(connected_device,))
self._main_id = storage[0]['id'] self._main_id = storage[0]['id']
self._carda_id = self._cardb_id = None self._carda_id = self._cardb_id = None
if len(storage) > 1: if len(storage) > 1:
@ -176,7 +188,7 @@ class MTP_DEVICE(MTPDeviceBase):
self.current_friendly_name = self.dev.friendly_name self.current_friendly_name = self.dev.friendly_name
if not self.current_friendly_name: if not self.current_friendly_name:
self.current_friendly_name = self.dev.model_name or _('Unknown MTP device') self.current_friendly_name = self.dev.model_name or _('Unknown MTP device')
self.current_serial_num = self.dev.serial_number self.current_serial_num = snum
@property @property
def filesystem_cache(self): def filesystem_cache(self):

View File

@ -15,7 +15,7 @@ from itertools import chain
from calibre import as_unicode, prints from calibre import as_unicode, prints
from calibre.constants import plugins, __appname__, numeric_version from calibre.constants import plugins, __appname__, numeric_version
from calibre.ptempfile import SpooledTemporaryFile from calibre.ptempfile import SpooledTemporaryFile
from calibre.devices.errors import OpenFailed, DeviceError from calibre.devices.errors import OpenFailed, DeviceError, BlacklistedDevice
from calibre.devices.mtp.base import MTPDeviceBase from calibre.devices.mtp.base import MTPDeviceBase
class ThreadingViolation(Exception): class ThreadingViolation(Exception):
@ -163,6 +163,9 @@ class MTP_DEVICE(MTPDeviceBase):
p('\nTrying to open:', pnp_id) p('\nTrying to open:', pnp_id)
try: try:
self.open(pnp_id, 'debug-detection') self.open(pnp_id, 'debug-detection')
except BlacklistedDevice:
p('This device has been blacklisted by the user')
continue
except: except:
p('Open failed:') p('Open failed:')
p(traceback.format_exc()) p(traceback.format_exc())
@ -172,7 +175,7 @@ class MTP_DEVICE(MTPDeviceBase):
p('Opened', self.current_friendly_name, 'successfully') p('Opened', self.current_friendly_name, 'successfully')
p('Device info:') p('Device info:')
p(pprint.pformat(self.dev.data)) p(pprint.pformat(self.dev.data))
self.eject() self.post_yank_cleanup()
return True return True
p('No suitable MTP devices found') p('No suitable MTP devices found')
return False return False
@ -225,7 +228,6 @@ class MTP_DEVICE(MTPDeviceBase):
self._main_id = self._carda_id = self._cardb_id = None self._main_id = self._carda_id = self._cardb_id = None
self.dev = self._filesystem_cache = None self.dev = self._filesystem_cache = None
@same_thread @same_thread
def post_yank_cleanup(self): def post_yank_cleanup(self):
self.currently_connected_pnp_id = self.current_friendly_name = None self.currently_connected_pnp_id = self.current_friendly_name = None
@ -256,6 +258,13 @@ class MTP_DEVICE(MTPDeviceBase):
if not storage: if not storage:
self.blacklisted_devices.add(connected_device) self.blacklisted_devices.add(connected_device)
raise OpenFailed('No storage found for device %s'%(connected_device,)) raise OpenFailed('No storage found for device %s'%(connected_device,))
snum = devdata.get('serial_number', None)
if snum in self.prefs.get('blacklist', []):
self.blacklisted_devices.add(connected_device)
self.dev = None
raise BlacklistedDevice(
'The %s device has been blacklisted by the user'%(connected_device,))
self._main_id = storage[0]['id'] self._main_id = storage[0]['id']
if len(storage) > 1: if len(storage) > 1:
self._carda_id = storage[1]['id'] self._carda_id = storage[1]['id']
@ -266,7 +275,7 @@ class MTP_DEVICE(MTPDeviceBase):
self.current_friendly_name = devdata.get('model_name', self.current_friendly_name = devdata.get('model_name',
_('Unknown MTP device')) _('Unknown MTP device'))
self.currently_connected_pnp_id = connected_device self.currently_connected_pnp_id = connected_device
self.current_serial_num = devdata.get('serial_number', None) self.current_serial_num = snum
@same_thread @same_thread
def get_basic_device_information(self): def get_basic_device_information(self):

View File

@ -24,7 +24,8 @@ from calibre.gui2 import (config, error_dialog, Dispatcher, dynamic,
from calibre.ebooks.metadata import authors_to_string from calibre.ebooks.metadata import authors_to_string
from calibre import preferred_encoding, prints, force_unicode, as_unicode from calibre import preferred_encoding, prints, force_unicode, as_unicode
from calibre.utils.filenames import ascii_filename from calibre.utils.filenames import ascii_filename
from calibre.devices.errors import FreeSpaceError, WrongDestinationError from calibre.devices.errors import (FreeSpaceError, WrongDestinationError,
BlacklistedDevice)
from calibre.devices.apple.driver import ITUNES_ASYNC from calibre.devices.apple.driver import ITUNES_ASYNC
from calibre.devices.folder_device.driver import FOLDER_DEVICE from calibre.devices.folder_device.driver import FOLDER_DEVICE
from calibre.devices.bambook.driver import BAMBOOK, BAMBOOKWifi from calibre.devices.bambook.driver import BAMBOOK, BAMBOOKWifi
@ -252,6 +253,9 @@ class DeviceManager(Thread): # {{{
if cd is not None: if cd is not None:
try: try:
dev.open(cd, self.current_library_uuid) dev.open(cd, self.current_library_uuid)
except BlacklistedDevice as e:
prints('Ignoring blacklisted device: %s'%
as_unicode(e))
except: except:
prints('Error while trying to open %s (Driver: %s)'% prints('Error while trying to open %s (Driver: %s)'%
(cd, dev)) (cd, dev))

View File

@ -16,6 +16,7 @@ from PyQt4.Qt import (QWidget, QListWidgetItem, Qt, QToolButton, QLabel,
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
from calibre.gui2.dialogs.template_dialog import TemplateDialog from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.utils.date import parse_date
class FormatsConfig(QWidget): # {{{ class FormatsConfig(QWidget): # {{{
@ -136,6 +137,45 @@ class SendToConfig(QWidget): # {{{
# }}} # }}}
class IgnoredDevices(QWidget): # {{{
def __init__(self, devs, blacklist):
QWidget.__init__(self)
self.l = l = QVBoxLayout()
self.setLayout(l)
self.la = la = QLabel('<p>'+_(
'''Select the devices to be <b>ignored</b>. calibre will not
connect to devices with a checkmark next to their names.'''))
la.setWordWrap(True)
l.addWidget(la)
self.f = f = QListWidget(self)
l.addWidget(f)
devs = [(snum, (x[0], parse_date(x[1]))) for snum, x in
devs.iteritems()]
for dev, x in sorted(devs, key=lambda x:x[1][1], reverse=True):
name = x[0]
name = '%s [%s]'%(name, dev)
item = QListWidgetItem(name, f)
item.setData(Qt.UserRole, dev)
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
item.setCheckState(Qt.Checked if dev in blacklist else Qt.Unchecked)
@property
def blacklist(self):
return [unicode(self.f.item(i).data(Qt.UserRole).toString()) for i in
xrange(self.f.count()) if self.f.item(i).checkState()==Qt.Checked]
def ignore_device(self, snum):
for i in xrange(self.f.count()):
i = self.f.item(i)
c = unicode(i.data(Qt.UserRole).toString())
if c == snum:
i.setCheckState(Qt.Checked)
break
# }}}
class MTPConfig(QTabWidget): class MTPConfig(QTabWidget):
def __init__(self, device, parent=None): def __init__(self, device, parent=None):
@ -162,6 +202,8 @@ class MTPConfig(QTabWidget):
l = QLabel(msg) l = QLabel(msg)
l.setWordWrap(True) l.setWordWrap(True)
l.setStyleSheet('QLabel { margin-left: 2em }') l.setStyleSheet('QLabel { margin-left: 2em }')
l.setMinimumWidth(500)
l.setMinimumHeight(400)
self.insertTab(0, l, _('Cannot configure')) self.insertTab(0, l, _('Cannot configure'))
else: else:
self.base = QWidget(self) self.base = QWidget(self)
@ -173,16 +215,34 @@ class MTPConfig(QTabWidget):
self.get_pref('format_map')) self.get_pref('format_map'))
self.send_to = SendToConfig(self.get_pref('send_to')) self.send_to = SendToConfig(self.get_pref('send_to'))
self.template = TemplateConfig(self.get_pref('send_template')) self.template = TemplateConfig(self.get_pref('send_template'))
self.base.la = la = QLabel(_('Choose the formats to send to the %s')%self.device.current_friendly_name) self.base.la = la = QLabel(_(
'Choose the formats to send to the %s')%self.device.current_friendly_name)
la.setWordWrap(True) la.setWordWrap(True)
l.addWidget(la, 0, 0, 1, 1) l.addWidget(la, 0, 0, 1, 1)
l.addWidget(self.formats, 1, 0, 3, 1) l.addWidget(self.formats, 1, 0, 2, 1)
l.addWidget(self.send_to, 1, 1, 1, 1) l.addWidget(self.send_to, 1, 1, 1, 1)
l.addWidget(self.template, 2, 1, 1, 1) l.addWidget(self.template, 2, 1, 1, 1)
l.setRowStretch(2, 10) l.setRowStretch(2, 10)
self.base.b = b = QPushButton(QIcon(I('minus.png')),
_('Ignore the %s in calibre')%device.current_friendly_name,
self.base)
l.addWidget(b, 3, 0, 1, 2)
b.clicked.connect(self.ignore_device)
self.igntab = IgnoredDevices(self.device.prefs['history'],
self.device.prefs['blacklist'])
self.addTab(self.igntab, _('Ignored devices'))
self.setCurrentIndex(0) self.setCurrentIndex(0)
def ignore_device(self):
self.igntab.ignore_device(self.device.current_serial_num)
self.base.b.setEnabled(False)
self.base.b.setText(_('The %s will be ignored in calibre')%
self.device.current_friendly_name)
self.base.b.setStyleSheet('QPushButton { font-weight: bold }')
self.base.setEnabled(False)
def get_pref(self, key): def get_pref(self, key):
p = self.device.prefs.get(self.current_device_key, {}) p = self.device.prefs.get(self.current_device_key, {})
if not p: if not p:
@ -194,6 +254,7 @@ class MTPConfig(QTabWidget):
return self._device() return self._device()
def validate(self): def validate(self):
if hasattr(self, 'formats'):
if not self.formats.validate(): if not self.formats.validate():
return False return False
if not self.template.validate(): if not self.template.validate():
@ -203,6 +264,7 @@ class MTPConfig(QTabWidget):
def commit(self): def commit(self):
p = self.device.prefs.get(self.current_device_key, {}) p = self.device.prefs.get(self.current_device_key, {})
if hasattr(self, 'formats'):
p.pop('format_map', None) p.pop('format_map', None)
f = self.formats.format_map f = self.formats.format_map
if f and f != self.device.prefs['format_map']: if f and f != self.device.prefs['format_map']:
@ -220,6 +282,8 @@ class MTPConfig(QTabWidget):
self.device.prefs[self.current_device_key] = p self.device.prefs[self.current_device_key] = p
self.device.prefs['blacklist'] = self.igntab.blacklist
if __name__ == '__main__': if __name__ == '__main__':
from calibre.gui2 import Application from calibre.gui2 import Application
from calibre.devices.mtp.driver import MTP_DEVICE from calibre.devices.mtp.driver import MTP_DEVICE