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.'''
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
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.ptempfile import SpooledTemporaryFile, PersistentTemporaryDirectory
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'%(
'windows' if iswindows else 'unix')).MTP_DEVICE
@ -51,6 +51,8 @@ class MTP_DEVICE(BASE):
'wordplayer/calibretransfer', 'Books', 'sdcard/ebooks',
'eBooks', 'kindle']
p.defaults['send_template'] = config().parse().send_template
p.defaults['blacklist'] = []
p.defaults['history'] = {}
return self._prefs
@ -74,6 +76,11 @@ class MTP_DEVICE(BASE):
self.current_library_uuid = library_uuid
self.location_paths = None
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 {{{
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.constants import plugins
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
MTPDevice = namedtuple('MTPDevice', 'busnum devnum vendor_id product_id '
@ -99,19 +99,25 @@ class MTP_DEVICE(MTPDeviceBase):
return False
p('Known MTP devices connected:')
for d in devs: p(d)
d = devs[0]
p('\nTrying to open:', d)
try:
self.open(d, 'debug')
except:
p('Opening device failed:')
p(traceback.format_exc())
return False
p('Opened', self.current_friendly_name, 'successfully')
p('Storage info:')
p(pprint.pformat(self.dev.storage_info))
self.eject()
return True
for d in devs:
p('\nTrying to open:', d)
try:
self.open(d, 'debug')
except BlacklistedDevice:
p('This device has been blacklisted by the user')
continue
except:
p('Opening device failed:')
p(traceback.format_exc())
return False
else:
p('Opened', self.current_friendly_name, 'successfully')
p('Storage info:')
p(pprint.pformat(self.dev.storage_info))
self.post_yank_cleanup()
return True
return False
@synchronous
def create_device(self, connected_device):
@ -167,6 +173,12 @@ class MTP_DEVICE(MTPDeviceBase):
if not storage:
self.blacklisted_devices.add(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._carda_id = self._cardb_id = None
if len(storage) > 1:
@ -176,7 +188,7 @@ class MTP_DEVICE(MTPDeviceBase):
self.current_friendly_name = self.dev.friendly_name
if not self.current_friendly_name:
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
def filesystem_cache(self):

View File

@ -15,7 +15,7 @@ from itertools import chain
from calibre import as_unicode, prints
from calibre.constants import plugins, __appname__, numeric_version
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
class ThreadingViolation(Exception):
@ -163,6 +163,9 @@ class MTP_DEVICE(MTPDeviceBase):
p('\nTrying to open:', pnp_id)
try:
self.open(pnp_id, 'debug-detection')
except BlacklistedDevice:
p('This device has been blacklisted by the user')
continue
except:
p('Open failed:')
p(traceback.format_exc())
@ -172,7 +175,7 @@ class MTP_DEVICE(MTPDeviceBase):
p('Opened', self.current_friendly_name, 'successfully')
p('Device info:')
p(pprint.pformat(self.dev.data))
self.eject()
self.post_yank_cleanup()
return True
p('No suitable MTP devices found')
return False
@ -225,7 +228,6 @@ class MTP_DEVICE(MTPDeviceBase):
self._main_id = self._carda_id = self._cardb_id = None
self.dev = self._filesystem_cache = None
@same_thread
def post_yank_cleanup(self):
self.currently_connected_pnp_id = self.current_friendly_name = None
@ -256,6 +258,13 @@ class MTP_DEVICE(MTPDeviceBase):
if not storage:
self.blacklisted_devices.add(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']
if len(storage) > 1:
self._carda_id = storage[1]['id']
@ -266,7 +275,7 @@ class MTP_DEVICE(MTPDeviceBase):
self.current_friendly_name = devdata.get('model_name',
_('Unknown MTP device'))
self.currently_connected_pnp_id = connected_device
self.current_serial_num = devdata.get('serial_number', None)
self.current_serial_num = snum
@same_thread
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 import preferred_encoding, prints, force_unicode, as_unicode
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.folder_device.driver import FOLDER_DEVICE
from calibre.devices.bambook.driver import BAMBOOK, BAMBOOKWifi
@ -252,6 +253,9 @@ class DeviceManager(Thread): # {{{
if cd is not None:
try:
dev.open(cd, self.current_library_uuid)
except BlacklistedDevice as e:
prints('Ignoring blacklisted device: %s'%
as_unicode(e))
except:
prints('Error while trying to open %s (Driver: %s)'%
(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.gui2 import error_dialog
from calibre.gui2.dialogs.template_dialog import TemplateDialog
from calibre.utils.date import parse_date
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):
def __init__(self, device, parent=None):
@ -162,6 +202,8 @@ class MTPConfig(QTabWidget):
l = QLabel(msg)
l.setWordWrap(True)
l.setStyleSheet('QLabel { margin-left: 2em }')
l.setMinimumWidth(500)
l.setMinimumHeight(400)
self.insertTab(0, l, _('Cannot configure'))
else:
self.base = QWidget(self)
@ -173,16 +215,34 @@ class MTPConfig(QTabWidget):
self.get_pref('format_map'))
self.send_to = SendToConfig(self.get_pref('send_to'))
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)
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.template, 2, 1, 1, 1)
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)
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):
p = self.device.prefs.get(self.current_device_key, {})
if not p:
@ -194,31 +254,35 @@ class MTPConfig(QTabWidget):
return self._device()
def validate(self):
if not self.formats.validate():
return False
if not self.template.validate():
return False
if hasattr(self, 'formats'):
if not self.formats.validate():
return False
if not self.template.validate():
return False
return True
def commit(self):
p = self.device.prefs.get(self.current_device_key, {})
p.pop('format_map', None)
f = self.formats.format_map
if f and f != self.device.prefs['format_map']:
p['format_map'] = f
if hasattr(self, 'formats'):
p.pop('format_map', None)
f = self.formats.format_map
if f and f != self.device.prefs['format_map']:
p['format_map'] = f
p.pop('send_template', None)
t = self.template.template
if t and t != self.device.prefs['send_template']:
p['send_template'] = t
p.pop('send_template', None)
t = self.template.template
if t and t != self.device.prefs['send_template']:
p['send_template'] = t
p.pop('send_to', None)
s = self.send_to.value
if s and s != self.device.prefs['send_to']:
p['send_to'] = s
p.pop('send_to', None)
s = self.send_to.value
if s and s != self.device.prefs['send_to']:
p['send_to'] = s
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__':
from calibre.gui2 import Application