Add basic per-device config for MTP devices

This commit is contained in:
Kovid Goyal 2012-09-06 18:51:57 +05:30
parent 7a5049d2a1
commit 8288da34fc
4 changed files with 296 additions and 19 deletions

View File

@ -291,7 +291,7 @@ class ANDROID(USBMS):
@classmethod
def configure_for_kindle_app(cls):
proxy = cls._configProxy()
proxy['format_map'] = ['mobi', 'azw', 'azw1', 'azw4', 'pdf']
proxy['format_map'] = ['azw3', 'mobi', 'azw', 'azw1', 'azw4', 'pdf']
proxy['use_subdirs'] = False
proxy['extra_customization'] = [
','.join(['kindle']+cls.EBOOK_DIR_MAIN), '']

View File

@ -46,9 +46,8 @@ class MTPDeviceBase(DevicePlugin):
def set_progress_reporter(self, report_progress):
self.report_progress = report_progress
@classmethod
def get_gui_name(cls):
return getattr(cls, 'current_friendly_name', cls.gui_name)
def get_gui_name(self):
return getattr(self, 'current_friendly_name', self.gui_name)
def is_usb_connected(self, devices_on_system, debug=False,
only_presence=False):
@ -60,13 +59,4 @@ class MTPDeviceBase(DevicePlugin):
from calibre.devices.utils import build_template_regexp
return build_template_regexp(self.save_template)
@property
def default_save_template(cls):
from calibre.library.save_to_disk import config
return config().parse().send_template
@property
def save_template(self):
# TODO: Use the device specific template here
return self.default_save_template

View File

@ -15,7 +15,7 @@ from calibre import prints
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
from calibre.utils.config import from_json, to_json, JSONConfig
from calibre.utils.date import now, isoformat
BASE = importlib.import_module('calibre.devices.mtp.%s.driver'%(
@ -39,9 +39,40 @@ class MTP_DEVICE(BASE):
def __init__(self, *args, **kwargs):
BASE.__init__(self, *args, **kwargs)
self.plugboards = self.plugboard_func = None
self._prefs = None
@property
def prefs(self):
if self._prefs is None:
from calibre.library.save_to_disk import config
self._prefs = p = JSONConfig('mtp_devices')
p.defaults['format_map'] = self.FORMATS
p.defaults['send_to'] = ['eBooks/import',
'wordplayer/calibretransfer', 'Books', 'sdcard/ebooks',
'eBooks', 'kindle']
p.defaults['send_template'] = config().parse().send_template
return self._prefs
def configure_for_kindle_app(self):
proxy = self.prefs
with proxy:
proxy['format_map'] = ['azw3', 'mobi', 'azw', 'azw1', 'azw4', 'pdf']
proxy['send_template'] = '{title} - {authors}'
orig = list(proxy['send_to'])
if 'kindle' in orig:
orig.remove('kindle')
orig.insert(0, 'kindle')
proxy['send_to'] = orig
def configure_for_generic_epub_app(self):
with self.prefs:
for x in ('format_map', 'send_template', 'send_to'):
del self.prefs[x]
def open(self, devices, library_uuid):
self.current_library_uuid = library_uuid
self.location_paths = None
BASE.open(self, devices, library_uuid)
# Device information {{{
@ -248,8 +279,24 @@ class MTP_DEVICE(BASE):
return tuple(x for x in filepath.split('/'))
def prefix_for_location(self, on_card):
# TODO: Implement this
return 'calibre'
if self.location_paths is None:
self.location_paths = {}
for sid, loc in ( (self._main_id, None), (self._carda_id, 'carda'),
(self._cardb_id, 'cardb') ):
if sid is not None:
storage = self.filesystem_cache.storage(sid)
prefixes = self.get_pref('send_to')
p = None
for path in prefixes:
path = path.replace(os.sep, '/')
if storage.find_path(path.split('/')) is not None:
p = path
break
if p is None:
p = 'eBooks'
self.location_paths[loc] = p
return self.location_paths[on_card]
def ensure_parent(self, storage, path):
parent = storage
@ -366,14 +413,28 @@ class MTP_DEVICE(BASE):
# }}}
# Settings {{{
@classmethod
def get_pref(self, key):
return self.prefs.get('device-%s'%self.current_serial_num, {}).get(key,
self.prefs[key])
def config_widget(self):
from calibre.gui2.device_drivers.mtp_config import MTPConfig
return MTPConfig(self)
def save_settings(self, cw):
cw.commit()
def settings(self):
# TODO: Implement this
class Opts(object):
def __init__(s):
s.format_map = self.FORMATS
s.format_map = self.get_pref('format_map')
return Opts()
@property
def save_template(self):
return self.prefs['send_template']
# }}}
if __name__ == '__main__':
@ -390,6 +451,7 @@ if __name__ == '__main__':
dev.set_progress_reporter(prints)
dev.open(cd, None)
dev.filesystem_cache.dump()
print ('Prefix for main mem:', dev.prefix_for_location(None))
finally:
dev.shutdown()

View File

@ -0,0 +1,225 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import weakref
from PyQt4.Qt import (QWidget, QListWidgetItem, Qt, QToolButton, QLabel,
QTabWidget, QGridLayout, QListWidget, QIcon, QLineEdit, QVBoxLayout)
from calibre.ebooks import BOOK_EXTENSIONS
from calibre.gui2 import error_dialog
class FormatsConfig(QWidget): # {{{
def __init__(self, all_formats, format_map):
QWidget.__init__(self)
self.l = l = QGridLayout()
self.setLayout(l)
self.f = f = QListWidget(self)
l.addWidget(f, 0, 0, 3, 1)
unchecked_formats = sorted(all_formats - set(format_map))
for fmt in format_map + unchecked_formats:
item = QListWidgetItem(fmt, f)
item.setData(Qt.UserRole, fmt)
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
item.setCheckState(Qt.Checked if fmt in format_map else Qt.Unchecked)
self.button_up = b = QToolButton(self)
b.setIcon(QIcon(I('arrow-up.png')))
l.addWidget(b, 0, 1)
b.clicked.connect(self.up)
self.button_down = b = QToolButton(self)
b.setIcon(QIcon(I('arrow-down.png')))
l.addWidget(b, 2, 1)
b.clicked.connect(self.down)
@property
def format_map(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 validate(self):
if not self.format_map:
error_dialog(self, _('No formats selected'),
_('You must choose at least one format to send to the'
' device'), show=True)
return False
return True
def up(self):
idx = self.f.currentRow()
if idx > 0:
self.f.insertItem(idx-1, self.f.takeItem(idx))
self.f.setCurrentRow(idx-1)
def down(self):
idx = self.f.currentRow()
if idx < self.f.count()-1:
self.f.insertItem(idx+1, self.f.takeItem(idx))
self.f.setCurrentRow(idx+1)
# }}}
class TemplateConfig(QWidget): # {{{
def __init__(self, val):
QWidget.__init__(self)
self.t = t = QLineEdit(self)
t.setText(val or '')
t.setCursorPosition(0)
self.setMinimumWidth(400)
self.l = l = QVBoxLayout(self)
self.setLayout(l)
self.m = m = QLabel('<p>'+_('''<b>Save &template</b> to control the filename and
location of files sent to the device:'''))
m.setWordWrap(True)
m.setBuddy(t)
l.addWidget(m)
l.addWidget(t)
@property
def template(self):
return unicode(self.t.text()).strip()
def validate(self):
from calibre.utils.formatter import validation_formatter
tmpl = self.template
try:
validation_formatter.validate(tmpl)
return True
except Exception as err:
error_dialog(self, _('Invalid template'),
'<p>'+_('The template %s is invalid:')%tmpl + \
'<br>'+unicode(err), show=True)
return False
# }}}
class SendToConfig(QWidget): # {{{
def __init__(self, val):
QWidget.__init__(self)
self.t = t = QLineEdit(self)
t.setText(', '.join(val or []))
t.setCursorPosition(0)
self.l = l = QVBoxLayout(self)
self.setLayout(l)
self.m = m = QLabel('<p>'+_('''A <b>list of &folders</b> on the device to
which to send ebooks. The first one that exists will be used:'''))
m.setWordWrap(True)
m.setBuddy(t)
l.addWidget(m)
l.addWidget(t)
@property
def value(self):
ans = [x.strip() for x in unicode(self.t.text()).strip().split(',')]
return [x for x in ans if x]
# }}}
class MTPConfig(QTabWidget):
def __init__(self, device, parent=None):
QTabWidget.__init__(self, parent)
self._device = weakref.ref(device)
cd = msg = None
if device.current_friendly_name is not None:
if device.current_serial_num is None:
msg = '<p>' + _('The <b>%s</b> device has no serial number, '
'it cannot be configured'%device.current_friendly_name)
else:
cd = 'device-'+device.current_serial_num
else:
msg = '<p>' + _('<b>No MTP device connected.</b><p>'
' You can only configure the MTP device plugin when a device'
' is connected.')
self.current_device_key = cd
if msg:
msg += '<p>' + _('If you want to un-ignore a previously'
' ignored MTP device, use the "Ignored devices" tab.')
l = QLabel(msg)
l.setWordWrap(True)
l.setStyleSheet('QLabel { margin-left: 2em }')
self.insertTab(0, l, _('Cannot configure'))
else:
self.base = QWidget(self)
self.insertTab(0, self.base, _('Configure %s')%self.device.current_friendly_name)
l = self.base.l = QGridLayout(self.base)
self.base.setLayout(l)
self.formats = FormatsConfig(set(BOOK_EXTENSIONS),
self.get_pref('format_map'))
self.send_to = SendToConfig(self.get_pref('send_to'))
self.template = TemplateConfig(self.get_pref('send_template'))
l.addWidget(self.formats, 0, 0, 3, 1)
l.addWidget(self.send_to, 0, 1, 1, 1)
l.addWidget(self.template, 1, 1, 1, 1)
l.setRowStretch(2, 10)
self.setCurrentIndex(0)
def get_pref(self, key):
p = self.device.prefs.get(self.current_device_key, {})
if not p:
self.device.prefs[self.current_device_key] = p
return p.get(key, self.device.prefs[key])
@property
def device(self):
return self._device()
def validate(self):
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
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
self.device.prefs[self.current_device_key] = p
if __name__ == '__main__':
from calibre.gui2 import Application
from calibre.devices.mtp.driver import MTP_DEVICE
from calibre.devices.scanner import DeviceScanner
s = DeviceScanner()
s.scan()
app = Application([])
dev = MTP_DEVICE(None)
dev.startup()
cd = dev.detect_managed_devices(s.devices)
dev.open(cd, 'test')
cw = dev.config_widget()
cw.show()
app.exec_()
dev.shutdown()