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.'''
|
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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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]
|
|
||||||
p('\nTrying to open:', d)
|
for d in devs:
|
||||||
try:
|
p('\nTrying to open:', d)
|
||||||
self.open(d, 'debug')
|
try:
|
||||||
except:
|
self.open(d, 'debug')
|
||||||
p('Opening device failed:')
|
except BlacklistedDevice:
|
||||||
p(traceback.format_exc())
|
p('This device has been blacklisted by the user')
|
||||||
return False
|
continue
|
||||||
p('Opened', self.current_friendly_name, 'successfully')
|
except:
|
||||||
p('Storage info:')
|
p('Opening device failed:')
|
||||||
p(pprint.pformat(self.dev.storage_info))
|
p(traceback.format_exc())
|
||||||
self.eject()
|
return False
|
||||||
return True
|
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
|
@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):
|
||||||
|
@ -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):
|
||||||
|
@ -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))
|
||||||
|
@ -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,31 +254,35 @@ class MTPConfig(QTabWidget):
|
|||||||
return self._device()
|
return self._device()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if not self.formats.validate():
|
if hasattr(self, 'formats'):
|
||||||
return False
|
if not self.formats.validate():
|
||||||
if not self.template.validate():
|
return False
|
||||||
return False
|
if not self.template.validate():
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
p = self.device.prefs.get(self.current_device_key, {})
|
p = self.device.prefs.get(self.current_device_key, {})
|
||||||
|
|
||||||
p.pop('format_map', None)
|
if hasattr(self, 'formats'):
|
||||||
f = self.formats.format_map
|
p.pop('format_map', None)
|
||||||
if f and f != self.device.prefs['format_map']:
|
f = self.formats.format_map
|
||||||
p['format_map'] = f
|
if f and f != self.device.prefs['format_map']:
|
||||||
|
p['format_map'] = f
|
||||||
|
|
||||||
p.pop('send_template', None)
|
p.pop('send_template', None)
|
||||||
t = self.template.template
|
t = self.template.template
|
||||||
if t and t != self.device.prefs['send_template']:
|
if t and t != self.device.prefs['send_template']:
|
||||||
p['send_template'] = t
|
p['send_template'] = t
|
||||||
|
|
||||||
p.pop('send_to', None)
|
p.pop('send_to', None)
|
||||||
s = self.send_to.value
|
s = self.send_to.value
|
||||||
if s and s != self.device.prefs['send_to']:
|
if s and s != self.device.prefs['send_to']:
|
||||||
p['send_to'] = s
|
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__':
|
if __name__ == '__main__':
|
||||||
from calibre.gui2 import Application
|
from calibre.gui2 import Application
|
||||||
|
Loading…
x
Reference in New Issue
Block a user