diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 8c0e98c263..2d5e73bece 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -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), ''] diff --git a/src/calibre/devices/mtp/base.py b/src/calibre/devices/mtp/base.py index 90369221d1..a5885ca964 100644 --- a/src/calibre/devices/mtp/base.py +++ b/src/calibre/devices/mtp/base.py @@ -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 diff --git a/src/calibre/devices/mtp/driver.py b/src/calibre/devices/mtp/driver.py index d716d15de5..92184af8ff 100644 --- a/src/calibre/devices/mtp/driver.py +++ b/src/calibre/devices/mtp/driver.py @@ -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() diff --git a/src/calibre/gui2/device_drivers/mtp_config.py b/src/calibre/gui2/device_drivers/mtp_config.py new file mode 100644 index 0000000000..51f439b577 --- /dev/null +++ b/src/calibre/gui2/device_drivers/mtp_config.py @@ -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 ' +__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('

'+_('''Save &template 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'), + '

'+_('The template %s is invalid:')%tmpl + \ + '
'+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('

'+_('''A list of &folders 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 = '

' + _('The %s device has no serial number, ' + 'it cannot be configured'%device.current_friendly_name) + else: + cd = 'device-'+device.current_serial_num + else: + msg = '

' + _('No MTP device connected.

' + ' You can only configure the MTP device plugin when a device' + ' is connected.') + + self.current_device_key = cd + + if msg: + msg += '

' + _('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() + +