Merge from trunk

This commit is contained in:
Charles Haley 2012-09-12 22:44:00 +02:00
commit 5ad16d52c1
12 changed files with 396 additions and 44 deletions

View File

@ -95,6 +95,10 @@ class DevicePlugin(Plugin):
#: call post_yank_cleanup().
MANAGES_DEVICE_PRESENCE = False
#: If set the True, calibre will call the :method:`get_driveinfo()` method
#: after the books lists have been loaded to get the driveinfo.
SLOW_DRIVEINFO = False
@classmethod
def get_gui_name(cls):
if hasattr(cls, 'gui_name'):
@ -352,6 +356,18 @@ class DevicePlugin(Plugin):
"""
raise NotImplementedError()
def get_driveinfo(self):
'''
Return the driveinfo dictionary. Usually called from
get_device_information(), but if loading the driveinfo is slow for this
driver, then it should set SLOW_DRIVEINFO. In this case, this method
will be called by calibre after the book lists have been loaded. Note
that it is not called on the device thread, so the driver should cache
the drive info in the books() method and this function should return
the cached data.
'''
return {}
def card_prefix(self, end_session=True):
'''
Return a 2 element list of the prefix to paths on the cards.

View File

@ -288,7 +288,7 @@ class KINDLE2(KINDLE):
name = 'Kindle 2/3/4/Touch Device Interface'
description = _('Communicate with the Kindle 2/3/4/Touch eBook reader.')
FORMATS = KINDLE.FORMATS + ['pdf', 'azw4', 'pobi']
FORMATS = ['azw3'] + KINDLE.FORMATS + ['pdf', 'azw4', 'pobi']
DELETE_EXTS = KINDLE.DELETE_EXTS + ['.mbp1', '.mbs', '.sdr']
PRODUCT_ID = [0x0002, 0x0004]
@ -449,7 +449,7 @@ class KINDLE_DX(KINDLE2):
name = 'Kindle DX Device Interface'
description = _('Communicate with the Kindle DX eBook reader.')
FORMATS = KINDLE2.FORMATS[1:]
PRODUCT_ID = [0x0003]
BCD = [0x0100]
@ -462,7 +462,6 @@ class KINDLE_FIRE(KINDLE2):
description = _('Communicate with the Kindle Fire')
gui_name = 'Fire'
FORMATS = list(KINDLE2.FORMATS)
FORMATS.insert(0, 'azw3')
PRODUCT_ID = [0x0006]
BCD = [0x216, 0x100]

View File

@ -35,6 +35,7 @@ class MTP_DEVICE(BASE):
MANAGES_DEVICE_PRESENCE = True
FORMATS = ['epub', 'azw3', 'mobi', 'pdf']
DEVICE_PLUGBOARD_NAME = 'MTP_DEVICE'
SLOW_DRIVEINFO = True
def __init__(self, *args, **kwargs):
BASE.__init__(self, *args, **kwargs)
@ -47,12 +48,13 @@ class MTP_DEVICE(BASE):
from calibre.library.save_to_disk import config
self._prefs = p = JSONConfig('mtp_devices')
p.defaults['format_map'] = self.FORMATS
p.defaults['send_to'] = ['Books', 'eBooks/import', 'eBooks',
'wordplayer/calibretransfer', 'sdcard/ebooks',
'kindle']
p.defaults['send_to'] = ['Calibre_Companion', 'Books',
'eBooks/import', 'eBooks', 'wordplayer/calibretransfer',
'sdcard/ebooks', 'kindle']
p.defaults['send_template'] = config().parse().send_template
p.defaults['blacklist'] = []
p.defaults['history'] = {}
p.defaults['rules'] = []
return self._prefs
@ -75,6 +77,7 @@ class MTP_DEVICE(BASE):
def open(self, devices, library_uuid):
self.current_library_uuid = library_uuid
self.location_paths = None
self.driveinfo = {}
BASE.open(self, devices, library_uuid)
h = self.prefs['history']
if self.current_serial_num:
@ -106,15 +109,19 @@ class MTP_DEVICE(BASE):
dinfo['mtp_prefix'] = storage.storage_prefix
raw = json.dumps(dinfo, default=to_json)
self.put_file(storage, self.DRIVEINFO, BytesIO(raw), len(raw))
self.driveinfo = dinfo
self.driveinfo[location_code] = dinfo
def get_driveinfo(self):
if not self.driveinfo:
self.driveinfo = {}
for sid, location_code in ( (self._main_id, 'main'), (self._carda_id,
'A'), (self._cardb_id, 'B')):
if sid is None: continue
self._update_drive_info(self.filesystem_cache.storage(sid), location_code)
return self.driveinfo
def get_device_information(self, end_session=True):
self.report_progress(1.0, _('Get device information...'))
self.driveinfo = {}
for sid, location_code in ( (self._main_id, 'main'), (self._carda_id,
'A'), (self._cardb_id, 'B')):
if sid is None: continue
self._update_drive_info(self.filesystem_cache.storage(sid), location_code)
dinfo = self.get_basic_device_information()
return tuple( list(dinfo) + [self.driveinfo] )
@ -134,6 +141,7 @@ class MTP_DEVICE(BASE):
def books(self, oncard=None, end_session=True):
from calibre.devices.mtp.books import JSONCodec
from calibre.devices.mtp.books import BookList, Book
self.get_driveinfo() # Ensure driveinfo is loaded
sid = {'carda':self._carda_id, 'cardb':self._cardb_id}.get(oncard,
self._main_id)
if sid is None:
@ -273,15 +281,18 @@ class MTP_DEVICE(BASE):
self.plugboards = plugboards
self.plugboard_func = pb_func
def create_upload_path(self, path, mdata, fname):
def create_upload_path(self, path, mdata, fname, routing):
from calibre.devices.utils import create_upload_path
from calibre.utils.filenames import ascii_filename as sanitize
ext = fname.rpartition('.')[-1].lower()
path = routing.get(ext, path)
filepath = create_upload_path(mdata, fname, self.save_template, sanitize,
prefix_path=path,
path_type=posixpath,
maxlen=self.MAX_PATH_LEN,
use_subdirs = True,
news_in_folder = self.NEWS_IN_FOLDER,
use_subdirs=True,
news_in_folder=self.NEWS_IN_FOLDER,
)
return tuple(x for x in filepath.split('/'))
@ -329,8 +340,10 @@ class MTP_DEVICE(BASE):
self.report_progress(0, _('Transferring books to device...'))
i, total = 0, len(files)
routing = {fmt:dest for fmt,dest in self.get_pref('rules')}
for infile, fname, mi in izip(files, names, metadata):
path = self.create_upload_path(prefix, mi, fname)
path = self.create_upload_path(prefix, mi, fname, routing)
parent = self.ensure_parent(storage, path)
if hasattr(infile, 'read'):
pos = infile.tell()

View File

@ -230,6 +230,9 @@ class FilesystemCache(object):
continue # Ignore .txt files in the root
yield x
def __len__(self):
return len(self.id_map)
def resolve_mtp_id_path(self, path):
if not path.startswith('mtp:::'):
raise ValueError('%s is not a valid MTP path'%path)

View File

@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import operator, traceback, pprint, sys
import operator, traceback, pprint, sys, time
from threading import RLock
from collections import namedtuple
from functools import partial
@ -16,7 +16,7 @@ from calibre import prints, as_unicode
from calibre.constants import plugins
from calibre.ptempfile import SpooledTemporaryFile
from calibre.devices.errors import OpenFailed, DeviceError, BlacklistedDevice
from calibre.devices.mtp.base import MTPDeviceBase, synchronous
from calibre.devices.mtp.base import MTPDeviceBase, synchronous, debug
MTPDevice = namedtuple('MTPDevice', 'busnum devnum vendor_id product_id '
'bcd serial manufacturer product')
@ -193,6 +193,8 @@ class MTP_DEVICE(MTPDeviceBase):
@property
def filesystem_cache(self):
if self._filesystem_cache is None:
st = time.time()
debug('Loading filesystem metadata...')
from calibre.devices.mtp.filesystem_cache import FilesystemCache
with self.lock:
storage, all_items, all_errs = [], [], []
@ -220,6 +222,8 @@ class MTP_DEVICE(MTPDeviceBase):
self.current_friendly_name,
self.format_errorstack(all_errs)))
self._filesystem_cache = FilesystemCache(storage, all_items)
debug('Filesystem metadata loaded in %g seconds (%d objects)'%(
time.time()-st, len(self._filesystem_cache)))
return self._filesystem_cache
@synchronous

View File

@ -16,7 +16,7 @@ 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, BlacklistedDevice
from calibre.devices.mtp.base import MTPDeviceBase
from calibre.devices.mtp.base import MTPDeviceBase, debug
class ThreadingViolation(Exception):
@ -199,6 +199,8 @@ class MTP_DEVICE(MTPDeviceBase):
@property
def filesystem_cache(self):
if self._filesystem_cache is None:
debug('Loading filesystem metadata...')
st = time.time()
from calibre.devices.mtp.filesystem_cache import FilesystemCache
ts = self.total_space()
all_storage = []
@ -218,6 +220,8 @@ class MTP_DEVICE(MTPDeviceBase):
all_storage.append(storage)
items.append(id_map.itervalues())
self._filesystem_cache = FilesystemCache(all_storage, chain(*items))
debug('Filesystem metadata loaded in %g seconds (%d objects)'%(
time.time()-st, len(self._filesystem_cache)))
return self._filesystem_cache
@same_thread

View File

@ -499,6 +499,7 @@ class FileIconProvider(QFileIconProvider):
self.icons = {}
for key in self.__class__.ICONS.keys():
self.icons[key] = I('mimetypes/')+self.__class__.ICONS[key]+'.png'
self.icons['calibre'] = I('lt.png')
for i in ('dir', 'default', 'zero'):
self.icons[i] = QIcon(self.icons[i])

View File

@ -152,8 +152,16 @@ def render_data(mi, use_roman_numbers=True, all_fields=False):
scheme = u'devpath' if isdevice else u'path'
url = prepare_string_for_xml(path if isdevice else
unicode(mi.id), True)
link = u'<a href="%s:%s" title="%s">%s</a>' % (scheme, url,
prepare_string_for_xml(path, True), _('Click to open'))
pathstr = _('Click to open')
extra = ''
if isdevice:
durl = url
if durl.startswith('mtp:::'):
durl = ':::'.join( (durl.split(':::'))[2:] )
extra = '<br><span style="font-size:smaller">%s</span>'%(
prepare_string_for_xml(durl))
link = u'<a href="%s:%s" title="%s">%s</a>%s' % (scheme, url,
prepare_string_for_xml(path, True), pathstr, extra)
ans.append((field, u'<td class="title">%s</td><td>%s</td>'%(name, link)))
elif field == 'formats':
if isdevice: continue

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>588</width>
<height>342</height>
<height>416</height>
</rect>
</property>
<property name="windowTitle">
@ -91,23 +91,33 @@
<item row="0" column="1">
<widget class="QComboBox" name="opt_mobi_file_type"/>
</item>
<item row="1" column="0">
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Personal Doc tag:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<item row="3" column="1">
<widget class="QLineEdit" name="opt_personal_doc"/>
</item>
<item row="2" column="0" colspan="2">
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_share_not_sync">
<property name="text">
<string>Enable sharing of book content via Facebook, etc. WARNING: Disables last read syncing</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&lt;b&gt;WARNING:&lt;/b&gt; Various Kindle devices have trouble displaying the new or both MOBI filetypes. If you wish to use the new format on your device, convert to AZW3 instead of MOBI.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -433,6 +433,15 @@ class DeviceManager(Thread): # {{{
return self.create_job_step(self._get_device_information, done,
description=_('Get device information'), to_job=add_as_step_to_job)
def slow_driveinfo(self):
''' Update the stored device information with the driveinfo if the
device indicates that getting driveinfo is slow '''
info = self._device_information['info']
if (not info[4] and self.device.SLOW_DRIVEINFO):
info = list(info)
info[4] = self.device.get_driveinfo()
self._device_information['info'] = tuple(info)
def get_current_device_information(self):
return self._device_information
@ -1023,6 +1032,7 @@ class DeviceMixin(object): # {{{
if job.failed:
self.device_job_exception(job)
return
self.device_manager.slow_driveinfo()
# set_books_in_library might schedule a sync_booklists job
self.set_books_in_library(job.result, reset=True, add_as_step_to_job=job)
mainlist, cardalist, cardblist = job.result

View File

@ -11,12 +11,14 @@ import weakref
from PyQt4.Qt import (QWidget, QListWidgetItem, Qt, QToolButton, QLabel,
QTabWidget, QGridLayout, QListWidget, QIcon, QLineEdit, QVBoxLayout,
QPushButton)
QPushButton, QGroupBox, QScrollArea, QHBoxLayout, QComboBox,
pyqtSignal, QSizePolicy, QDialog, QDialogButtonBox)
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
from calibre.gui2.device_drivers.mtp_folder_browser import Browser
class FormatsConfig(QWidget): # {{{
@ -86,7 +88,7 @@ class TemplateConfig(QWidget): # {{{
m.setBuddy(t)
l.addWidget(m, 0, 0, 1, 2)
l.addWidget(t, 1, 0, 1, 1)
b = self.b = QPushButton(_('Template editor'))
b = self.b = QPushButton(_('&Template editor'))
l.addWidget(b, 1, 1, 1, 1)
b.clicked.connect(self.edit_template)
@ -116,19 +118,36 @@ class TemplateConfig(QWidget): # {{{
class SendToConfig(QWidget): # {{{
def __init__(self, val):
def __init__(self, val, device):
QWidget.__init__(self)
self.t = t = QLineEdit(self)
t.setText(', '.join(val or []))
t.setCursorPosition(0)
self.l = l = QVBoxLayout(self)
self.l = l = QGridLayout(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)
l.addWidget(m, 0, 0, 1, 2)
l.addWidget(t, 1, 0)
self.b = b = QToolButton()
l.addWidget(b, 1, 1)
b.setIcon(QIcon(I('document_open.png')))
b.clicked.connect(self.browse)
b.setToolTip(_('Browse for a folder on the device'))
self._device = weakref.ref(device)
@property
def device(self):
return self._device()
def browse(self):
b = Browser(self.device.filesystem_cache, show_files=False,
parent=self)
if b.exec_() == b.Accepted:
sid, path = b.current_item
self.t.setText('/'.join(path[1:]))
@property
def value(self):
@ -176,6 +195,135 @@ class IgnoredDevices(QWidget): # {{{
# }}}
# Rules {{{
class Rule(QWidget):
remove = pyqtSignal(object)
def __init__(self, device, rule=None):
QWidget.__init__(self)
self._device = weakref.ref(device)
self.l = l = QHBoxLayout()
self.setLayout(l)
self.l1 = l1 = QLabel(_('Send the '))
l.addWidget(l1)
self.fmt = f = QComboBox(self)
l.addWidget(f)
self.l2 = l2 = QLabel(_(' format to the folder: '))
l.addWidget(l2)
self.folder = f = QLineEdit(self)
f.setPlaceholderText(_('Folder on the device'))
l.addWidget(f)
self.b = b = QToolButton()
l.addWidget(b)
b.setIcon(QIcon(I('document_open.png')))
b.clicked.connect(self.browse)
b.setToolTip(_('Browse for a folder on the device'))
self.rb = rb = QPushButton(QIcon(I('list_remove.png')),
_('&Remove rule'), self)
l.addWidget(rb)
rb.clicked.connect(self.removed)
for fmt in sorted(BOOK_EXTENSIONS):
self.fmt.addItem(fmt.upper(), fmt.lower())
self.fmt.setCurrentIndex(0)
if rule is not None:
fmt, folder = rule
idx = self.fmt.findText(fmt.upper())
if idx > -1:
self.fmt.setCurrentIndex(idx)
self.folder.setText(folder)
self.ignore = False
@property
def device(self):
return self._device()
def browse(self):
b = Browser(self.device.filesystem_cache, show_files=False,
parent=self)
if b.exec_() == b.Accepted:
sid, path = b.current_item
self.folder.setText('/'.join(path[1:]))
def removed(self):
self.remove.emit(self)
@property
def rule(self):
folder = unicode(self.folder.text()).strip()
if folder:
return (
unicode(self.fmt.itemData(self.fmt.currentIndex()).toString()),
folder
)
return None
class FormatRules(QGroupBox):
def __init__(self, device, rules):
QGroupBox.__init__(self, _('Format specific sending'))
self._device = weakref.ref(device)
self.l = l = QVBoxLayout()
self.setLayout(l)
self.la = la = QLabel('<p>'+_(
'''You can create rules that control where ebooks of a specific
format are sent to on the device. These will take precedence over
the folders specified above.'''))
la.setWordWrap(True)
l.addWidget(la)
self.sa = sa = QScrollArea(self)
sa.setWidgetResizable(True)
self.w = w = QWidget(self)
w.l = QVBoxLayout()
w.setLayout(w.l)
sa.setWidget(w)
l.addWidget(sa)
self.widgets = []
for rule in rules:
r = Rule(device, rule)
self.widgets.append(r)
w.l.addWidget(r)
r.remove.connect(self.remove_rule)
if not self.widgets:
self.add_rule()
self.b = b = QPushButton(QIcon(I('plus.png')), _('Add a &new rule'))
l.addWidget(b)
b.clicked.connect(self.add_rule)
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Ignored)
@property
def device(self):
return self._device()
def add_rule(self):
r = Rule(self.device)
self.widgets.append(r)
self.w.l.addWidget(r)
r.remove.connect(self.remove_rule)
self.sa.verticalScrollBar().setValue(self.sa.verticalScrollBar().maximum())
def remove_rule(self, rule):
rule.setVisible(False)
rule.ignore = True
@property
def rules(self):
for w in self.widgets:
if not w.ignore:
r = w.rule
if r is not None:
yield r
# }}}
class MTPConfig(QTabWidget):
def __init__(self, device, parent=None):
@ -185,8 +333,8 @@ class MTPConfig(QTabWidget):
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)
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:
@ -211,24 +359,28 @@ class MTPConfig(QTabWidget):
l = self.base.l = QGridLayout(self.base)
self.base.setLayout(l)
self.rules = r = FormatRules(self.device, self.get_pref('rules'))
self.formats = FormatsConfig(set(BOOK_EXTENSIONS),
self.get_pref('format_map'))
self.send_to = SendToConfig(self.get_pref('send_to'))
self.send_to = SendToConfig(self.get_pref('send_to'), self.device)
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)
la.setWordWrap(True)
l.addWidget(la, 0, 0, 1, 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.b = b = QPushButton(QIcon(I('list_remove.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)
l.addWidget(b, 0, 0, 1, 2)
l.addWidget(la, 1, 0, 1, 1)
l.addWidget(self.formats, 2, 0, 3, 1)
l.addWidget(self.send_to, 2, 1, 1, 1)
l.addWidget(self.template, 3, 1, 1, 1)
l.setRowStretch(4, 10)
l.addWidget(r, 5, 0, 1, 2)
l.setRowStretch(5, 100)
self.igntab = IgnoredDevices(self.device.prefs['history'],
self.device.prefs['blacklist'])
self.addTab(self.igntab, _('Ignored devices'))
@ -280,6 +432,11 @@ class MTPConfig(QTabWidget):
if s and s != self.device.prefs['send_to']:
p['send_to'] = s
p.pop('rules', None)
r = list(self.rules.rules)
if r and r != self.device.prefs['rules']:
p['rules'] = r
self.device.prefs[self.current_device_key] = p
self.device.prefs['blacklist'] = self.igntab.blacklist
@ -296,8 +453,16 @@ if __name__ == '__main__':
cd = dev.detect_managed_devices(s.devices)
dev.open(cd, 'test')
cw = dev.config_widget()
cw.show()
app.exec_()
d = QDialog()
d.l = QVBoxLayout()
d.setLayout(d.l)
d.l.addWidget(cw)
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
d.l.addWidget(bb)
bb.accepted.connect(d.accept)
bb.rejected.connect(d.reject)
if d.exec_() == d.Accepted:
cw.commit()
dev.shutdown()

View File

@ -0,0 +1,119 @@
#!/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'
from operator import attrgetter
from PyQt4.Qt import (QTabWidget, QTreeWidget, QTreeWidgetItem, Qt, QDialog,
QDialogButtonBox, QVBoxLayout, QSize, pyqtSignal, QIcon)
from calibre.gui2 import file_icon_provider
def item(f, parent):
name = f.name
if not f.is_folder:
name += ' [%s]'%f.last_mod_string
ans = QTreeWidgetItem(parent, [name])
ans.setData(0, Qt.UserRole, f.full_path)
if f.is_folder:
ext = 'dir'
else:
ext = f.name.rpartition('.')[-1]
ans.setData(0, Qt.DecorationRole, file_icon_provider().icon_from_ext(ext))
return ans
class Storage(QTreeWidget):
def __init__(self, storage, show_files):
QTreeWidget.__init__(self)
self.show_files = show_files
self.create_children(storage, self)
self.name = storage.name
self.object_id = storage.persistent_id
self.setMinimumHeight(350)
self.setHeaderHidden(True)
def create_children(self, f, parent):
for child in sorted(f.folders, key=attrgetter('name')):
i = item(child, parent)
self.create_children(child, i)
if self.show_files:
for child in sorted(f.files, key=attrgetter('name')):
i = item(child, parent)
@property
def current_item(self):
item = self.currentItem()
if item is not None:
return (self.object_id, item.data(0, Qt.UserRole).toPyObject())
return None
class Folders(QTabWidget):
selected = pyqtSignal()
def __init__(self, filesystem_cache, show_files=True):
QTabWidget.__init__(self)
self.fs = filesystem_cache
for storage in self.fs.entries:
w = Storage(storage, show_files)
self.addTab(w, w.name)
w.doubleClicked.connect(self.selected)
self.setCurrentIndex(0)
@property
def current_item(self):
w = self.currentWidget()
if w is not None:
return w.current_item
class Browser(QDialog):
def __init__(self, filesystem_cache, show_files=True, parent=None):
QDialog.__init__(self, parent)
self.l = l = QVBoxLayout()
self.setLayout(l)
self.folders = cw = Folders(filesystem_cache, show_files=show_files)
l.addWidget(cw)
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
l.addWidget(bb)
bb.accepted.connect(self.accept)
bb.rejected.connect(self.reject)
self.setMinimumSize(QSize(500, 500))
self.folders.selected.connect(self.accept)
self.setWindowTitle(_('Choose folder on device'))
self.setWindowIcon(QIcon(I('devices/galaxy_s3.png')))
@property
def current_item(self):
return self.folders.current_item
def browse():
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([])
app
dev = MTP_DEVICE(None)
dev.startup()
cd = dev.detect_managed_devices(s.devices)
if cd is None:
raise ValueError('No MTP device found')
dev.open(cd, 'test')
d = Browser(dev.filesystem_cache)
d.exec_()
dev.shutdown()
return d.current_item
if __name__ == '__main__':
print (browse())