mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
MTP: Implement user specified ignoring of devices
This commit is contained in:
parent
4a419d2d1d
commit
ceebebf54f
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user