Merge from trunk

This commit is contained in:
Charles Haley 2012-08-23 08:13:17 +02:00
commit 6bb6900d85
11 changed files with 457 additions and 130 deletions

View File

@ -487,7 +487,8 @@ class DevicePlugin(Plugin):
Set the device name in the driveinfo file to 'name'. This setting will
persist until the file is re-created or the name is changed again.
Non-disk devices will ignore this request.
Non-disk devices should implement this method based on the location
codes returned by the get_device_information() method.
'''
pass

View File

@ -26,11 +26,6 @@ class MTPDeviceBase(DevicePlugin):
author = 'Kovid Goyal'
version = (1, 0, 0)
# Invalid USB vendor information so the scanner will never match
VENDOR_ID = [0xffff]
PRODUCT_ID = [0xffff]
BCD = [0xffff]
THUMBNAIL_HEIGHT = 128
CAN_SET_METADATA = []
@ -51,4 +46,10 @@ class MTPDeviceBase(DevicePlugin):
def get_gui_name(self):
return self.current_friendly_name or self.name
def is_usb_connected(self, devices_on_system, debug=False,
only_presence=False):
# We manage device presence ourselves, so this method should always
# return False
return False

View File

@ -8,11 +8,12 @@ __copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import weakref, sys
from collections import deque
from operator import attrgetter
from future_builtins import map
from calibre import human_readable, prints, force_unicode
from calibre.utils.icu import sort_key
from calibre.utils.icu import sort_key, lower
class FileOrFolder(object):
@ -20,15 +21,19 @@ class FileOrFolder(object):
self.object_id = entry['id']
self.is_folder = entry['is_folder']
self.name = force_unicode(entry.get('name', '___'), 'utf-8')
self.storage_id = entry.get('storage_id', None)
self.persistent_id = entry.get('persistent_id', self.object_id)
self.size = entry.get('size', 0)
# self.parent_id is None for storage objects
self.parent_id = entry.get('parent_id', None)
if self.parent_id == 0:
sid = entry['storage_id']
sid = self.storage_id
if sid not in all_storage_ids:
sid = all_storage_ids[0]
self.parent_id = sid
if self.parent_id is None and self.storage_id is None:
# A storage object
self.storage_id = self.object_id
self.is_hidden = entry.get('is_hidden', False)
self.is_system = entry.get('is_system', False)
self.can_delete = entry.get('can_delete', True)
@ -46,12 +51,28 @@ class FileOrFolder(object):
def parent(self):
return None if self.parent_id is None else self.id_map[self.parent_id]
@property
def full_path(self):
parts = deque()
parts.append(self.name)
p = self.parent
while p is not None:
parts.appendleft(p.name)
p = p.parent
return tuple(parts)
def __iter__(self):
for e in self.folders:
yield e
for e in self.files:
yield e
def add_child(self, entry):
ans = FileOrFolder(entry, self.id_map)
t = self.folders if ans.is_folder else self.files
t.append(ans)
return ans
def dump(self, prefix='', out=sys.stdout):
c = '+' if self.is_folder else '-'
data = ('%s children'%(sum(map(len, (self.files, self.folders))))
@ -62,6 +83,20 @@ class FileOrFolder(object):
for e in sorted(c, key=lambda x:sort_key(x.name)):
e.dump(prefix=prefix+' ', out=out)
def folder_named(self, name):
name = lower(name)
for e in self.folders:
if e.name and lower(e.name) == name:
return e
return None
def file_named(self, name):
name = lower(name)
for e in self.files:
if e.name and lower(e.name) == name:
return e
return None
class FilesystemCache(object):
def __init__(self, all_storage, entries):
@ -79,7 +114,17 @@ class FilesystemCache(object):
FileOrFolder(entry, self, all_storage_ids)
for item in self.id_map.itervalues():
try:
p = item.parent
except KeyError:
# Parent does not exist, set the parent to be the storage
# object
sid = p.storage_id
if sid not in all_storage_ids:
sid = all_storage_ids[0]
item.parent_id = sid
p = item.parent
if p is not None:
t = p.folders if item.is_folder else p.files
t.append(item)

View File

@ -1,71 +0,0 @@
#!/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 calibre.constants import plugins
class MTPDetect(object):
def __init__(self):
p = plugins['libmtp']
self.libmtp = p[0]
if self.libmtp is None:
print ('Failed to load libmtp, MTP device detection disabled')
print (p[1])
self.cache = {}
def __call__(self, devices):
'''
Given a list of devices as returned by LinuxScanner, return the set of
devices that are likely to be MTP devices. This class maintains a cache
to minimize USB polling. Note that detection is partially based on a
list of known vendor and product ids. This is because polling some
older devices causes problems. Therefore, if this method identifies a
device as MTP, it is not actually guaranteed that it will be a working
MTP device.
'''
# First drop devices that have been disconnected from the cache
connected_devices = {(d.busnum, d.devnum, d.vendor_id, d.product_id,
d.bcd, d.serial) for d in devices}
for d in tuple(self.cache.iterkeys()):
if d not in connected_devices:
del self.cache[d]
# Since is_mtp_device() can cause USB traffic by probing the device, we
# cache its result
mtp_devices = set()
if self.libmtp is None:
return mtp_devices
for d in devices:
ans = self.cache.get((d.busnum, d.devnum, d.vendor_id, d.product_id,
d.bcd, d.serial), None)
if ans is None:
ans = self.libmtp.is_mtp_device(d.busnum, d.devnum,
d.vendor_id, d.product_id)
self.cache[(d.busnum, d.devnum, d.vendor_id, d.product_id,
d.bcd, d.serial)] = ans
if ans:
mtp_devices.add(d)
return mtp_devices
def create_device(self, connected_device):
d = connected_device
return self.libmtp.Device(d.busnum, d.devnum, d.vendor_id,
d.product_id, d.manufacturer, d.product, d.serial)
if __name__ == '__main__':
from calibre.devices.scanner import linux_scanner
mtp_detect = MTPDetect()
devs = mtp_detect(linux_scanner())
print ('Found %d MTP devices:'%len(devs))
for dev in devs:
print (dev, 'at busnum=%d and devnum=%d'%(dev.busnum, dev.devnum))
print()

View File

@ -10,11 +10,19 @@ __docformat__ = 'restructuredtext en'
import time, operator
from threading import RLock
from io import BytesIO
from collections import namedtuple
from calibre.constants import plugins
from calibre.devices.errors import OpenFailed, DeviceError
from calibre.devices.mtp.base import MTPDeviceBase, synchronous
from calibre.devices.mtp.filesystem_cache import FilesystemCache
from calibre.devices.mtp.unix.detect import MTPDetect
MTPDevice = namedtuple('MTPDevice', 'busnum devnum vendor_id product_id '
'bcd serial manufacturer product')
def fingerprint(d):
return MTPDevice(d.busnum, d.devnum, d.vendor_id, d.product_id, d.bcd,
d.serial, d.manufacturer, d.product)
class MTP_DEVICE(MTPDeviceBase):
@ -22,13 +30,18 @@ class MTP_DEVICE(MTPDeviceBase):
def __init__(self, *args, **kwargs):
MTPDeviceBase.__init__(self, *args, **kwargs)
self.libmtp = None
self.detect_cache = {}
self.dev = None
self._filesystem_cache = None
self.lock = RLock()
self.blacklisted_devices = set()
self.ejected_devices = set()
self.currently_connected_dev = None
def set_debug_level(self, lvl):
self.detect.libmtp.set_debug_level(lvl)
self.libmtp.set_debug_level(lvl)
def report_progress(self, sent, total):
try:
@ -39,40 +52,67 @@ class MTP_DEVICE(MTPDeviceBase):
self.progress_reporter(p)
@synchronous
def is_usb_connected(self, devices_on_system, debug=False,
only_presence=False):
def detect_managed_devices(self, devices_on_system):
if self.libmtp is None: return None
# First remove blacklisted devices.
devs = []
devs = set()
for d in devices_on_system:
if (d.busnum, d.devnum, d.vendor_id,
d.product_id, d.bcd, d.serial) not in self.blacklisted_devices:
devs.append(d)
fp = fingerprint(d)
if fp not in self.blacklisted_devices:
devs.add(fp)
devs = self.detect(devs)
if self.dev is not None:
# Check if the currently opened device is still connected
ids = self.dev.ids
found = False
# Clean up ejected devices
self.ejected_devices = devs.intersection(self.ejected_devices)
# Check if the currently connected device is still present
if self.currently_connected_dev is not None:
return (self.currently_connected_dev if
self.currently_connected_dev in devs else None)
# Remove ejected devices
devs = devs - self.ejected_devices
# Now check for MTP devices
cache = self.detect_cache
for d in devs:
if ( (d.busnum, d.devnum, d.vendor_id, d.product_id, d.serial)
== ids ):
found = True
break
return found
# Check if any MTP capable device is present
return len(devs) > 0
ans = cache.get(d, None)
if ans is None:
ans = self.libmtp.is_mtp_device(d.busnum, d.devnum,
d.vendor_id, d.product_id)
cache[d] = ans
if ans:
return d
return None
@synchronous
def create_device(self, connected_device):
d = connected_device
return self.libmtp.Device(d.busnum, d.devnum, d.vendor_id,
d.product_id, d.manufacturer, d.product, d.serial)
@synchronous
def eject(self):
if self.currently_connected_dev is None: return
self.ejected_devices.add(self.currently_connected_dev)
self.post_yank_cleanup()
@synchronous
def post_yank_cleanup(self):
self.dev = self._filesystem_cache = self.current_friendly_name = None
self.currently_connected_dev = None
@synchronous
def startup(self):
self.detect = MTPDetect()
for x in vars(self.detect.libmtp):
p = plugins['libmtp']
self.libmtp = p[0]
if self.libmtp is None:
print ('Failed to load libmtp, MTP device detection disabled')
print (p[1])
for x in vars(self.libmtp):
if x.startswith('LIBMTP'):
setattr(self, x, getattr(self.detect.libmtp, x))
setattr(self, x, getattr(self.libmtp, x))
@synchronous
def shutdown(self):
@ -85,29 +125,25 @@ class MTP_DEVICE(MTPDeviceBase):
@synchronous
def open(self, connected_device, library_uuid):
self.dev = self._filesystem_cache = None
def blacklist_device():
d = connected_device
self.blacklisted_devices.add((d.busnum, d.devnum, d.vendor_id,
d.product_id, d.bcd, d.serial))
try:
self.dev = self.detect.create_device(connected_device)
except ValueError:
self.dev = self.create_device(connected_device)
except self.libmtp.MTPError:
# Give the device some time to settle
time.sleep(2)
try:
self.dev = self.detect.create_device(connected_device)
except ValueError:
self.dev = self.create_device(connected_device)
except self.libmtp.MTPError:
# Black list this device so that it is ignored for the
# remainder of this session.
blacklist_device()
self.blacklisted_devices.add(connected_device)
raise OpenFailed('%s is not a MTP device'%(connected_device,))
except TypeError:
blacklist_device()
self.blacklisted_devices.add(connected_device)
raise OpenFailed('')
storage = sorted(self.dev.storage_info, key=operator.itemgetter('id'))
if not storage:
blacklist_device()
self.blacklisted_devices.add(connected_device)
raise OpenFailed('No storage found for device %s'%(connected_device,))
self._main_id = storage[0]['id']
self._carda_id = self._cardb_id = None
@ -186,6 +222,16 @@ class MTP_DEVICE(MTPDeviceBase):
ans[i] = s['freespace_bytes']
return tuple(ans)
@synchronous
def create_folder(self, parent_id, name):
parent = self.filesystem_cache.id_map[parent_id]
if not parent.is_folder:
raise ValueError('%s is not a folder'%parent.full_path)
e = parent.folder_named(name)
if e is not None:
return e
ans = self.dev.create_folder(parent.storage_id, parent_id, name)
return parent.add_child(ans)
if __name__ == '__main__':
BytesIO
@ -198,8 +244,8 @@ if __name__ == '__main__':
dev.startup()
from calibre.devices.scanner import linux_scanner
devs = linux_scanner()
mtp_devs = dev.detect(devs)
dev.open(list(mtp_devs)[0], 'xxx')
cd = dev.detect_managed_devices(devs)
dev.open(cd, 'xxx')
d = dev.dev
print ("Opened device:", dev.get_gui_name())
print ("Storage info:")

View File

@ -28,6 +28,7 @@ static IPortableDeviceKeyCollection* create_filesystem_properties_collection() {
ADDPROP(WPD_OBJECT_PARENT_ID);
ADDPROP(WPD_OBJECT_PERSISTENT_UNIQUE_ID);
ADDPROP(WPD_OBJECT_NAME);
ADDPROP(WPD_OBJECT_ORIGINAL_FILE_NAME);
// ADDPROP(WPD_OBJECT_SYNC_ID);
ADDPROP(WPD_OBJECT_ISSYSTEM);
ADDPROP(WPD_OBJECT_ISHIDDEN);
@ -92,8 +93,9 @@ static void set_properties(PyObject *obj, IPortableDeviceValues *values) {
set_content_type_property(obj, values);
set_string_property(obj, WPD_OBJECT_PARENT_ID, "parent_id", values);
set_string_property(obj, WPD_OBJECT_NAME, "name", values);
set_string_property(obj, WPD_OBJECT_NAME, "nominal_name", values);
// set_string_property(obj, WPD_OBJECT_SYNC_ID, "sync_id", values);
set_string_property(obj, WPD_OBJECT_ORIGINAL_FILE_NAME, "name", values);
set_string_property(obj, WPD_OBJECT_PERSISTENT_UNIQUE_ID, "persistent_id", values);
set_bool_property(obj, WPD_OBJECT_ISHIDDEN, "is_hidden", values);
@ -370,6 +372,42 @@ end:
}
// }}}
static IPortableDeviceValues* create_object_properties(const wchar_t *parent_id, const wchar_t *name, const GUID content_type, unsigned PY_LONG_LONG size) { // {{{
IPortableDeviceValues *values = NULL;
HRESULT hr;
BOOL ok = FALSE;
hr = CoCreateInstance(CLSID_PortableDeviceValues, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&values));
if (FAILED(hr)) { hresult_set_exc("Failed to create values interface", hr); goto end; }
hr = values->SetStringValue(WPD_OBJECT_PARENT_ID, parent_id);
if (FAILED(hr)) { hresult_set_exc("Failed to set parent_id value", hr); goto end; }
hr = values->SetStringValue(WPD_OBJECT_NAME, name);
if (FAILED(hr)) { hresult_set_exc("Failed to set name value", hr); goto end; }
hr = values->SetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, name);
if (FAILED(hr)) { hresult_set_exc("Failed to set original_file_name value", hr); goto end; }
hr = values->SetGuidValue(WPD_OBJECT_FORMAT, WPD_OBJECT_FORMAT_UNSPECIFIED);
if (FAILED(hr)) { hresult_set_exc("Failed to set object_format value", hr); goto end; }
hr = values->SetGuidValue(WPD_OBJECT_CONTENT_TYPE, content_type);
if (FAILED(hr)) { hresult_set_exc("Failed to set content_type value", hr); goto end; }
if (!IsEqualGUID(WPD_CONTENT_TYPE_FOLDER, content_type)) {
hr = values->SetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE, size);
if (FAILED(hr)) { hresult_set_exc("Failed to set size value", hr); goto end; }
}
ok = TRUE;
end:
if (!ok && values != NULL) { values->Release(); values = NULL; }
return values;
} // }}}
PyObject* wpd::get_filesystem(IPortableDevice *device, const wchar_t *storage_id, IPortableDevicePropertiesBulk *bulk_properties) { // {{{
PyObject *folders = NULL;
IPortableDevicePropVariantCollection *object_ids = NULL;
@ -467,7 +505,7 @@ PyObject* wpd::get_file(IPortableDevice *device, const wchar_t *object_id, PyObj
total_read = total_read + bytes_read;
if (hr == STG_E_ACCESSDENIED) {
PyErr_SetString(PyExc_IOError, "Read access is denied to this object"); break;
} else if (hr == S_OK || hr == S_FALSE) {
} else if (SUCCEEDED(hr)) {
if (bytes_read > 0) {
res = PyObject_CallMethod(dest, "write", "s#", buf, bytes_read);
if (res == NULL) break;
@ -476,7 +514,7 @@ PyObject* wpd::get_file(IPortableDevice *device, const wchar_t *object_id, PyObj
}
} else { hresult_set_exc("Failed to read file from device", hr); break; }
if (hr == S_FALSE || bytes_read < bufsize) {
if (bytes_read == 0) {
ok = TRUE;
Py_XDECREF(PyObject_CallMethod(dest, "flush", NULL));
break;
@ -500,4 +538,170 @@ end:
Py_RETURN_NONE;
} // }}}
PyObject* wpd::create_folder(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name) { // {{{
IPortableDeviceContent *content = NULL;
IPortableDeviceValues *values = NULL;
IPortableDeviceProperties *devprops = NULL;
IPortableDeviceKeyCollection *properties = NULL;
wchar_t *newid = NULL;
PyObject *ans = NULL;
HRESULT hr;
values = create_object_properties(parent_id, name, WPD_CONTENT_TYPE_FOLDER, 0);
if (values == NULL) goto end;
Py_BEGIN_ALLOW_THREADS;
hr = device->Content(&content);
Py_END_ALLOW_THREADS;
if (FAILED(hr)) { hresult_set_exc("Failed to create content interface", hr); goto end; }
hr = content->Properties(&devprops);
if (FAILED(hr)) { hresult_set_exc("Failed to get IPortableDeviceProperties interface", hr); goto end; }
properties = create_filesystem_properties_collection();
if (properties == NULL) goto end;
Py_BEGIN_ALLOW_THREADS;
hr = content->CreateObjectWithPropertiesOnly(values, &newid);
Py_END_ALLOW_THREADS;
if (FAILED(hr) || newid == NULL) { hresult_set_exc("Failed to create folder", hr); goto end; }
ans = get_object_properties(devprops, properties, newid);
end:
if (content != NULL) content->Release();
if (values != NULL) values->Release();
if (devprops != NULL) devprops->Release();
if (properties != NULL) properties->Release();
if (newid != NULL) CoTaskMemFree(newid);
return ans;
} // }}}
PyObject* wpd::delete_object(IPortableDevice *device, const wchar_t *object_id) { // {{{
IPortableDeviceContent *content = NULL;
HRESULT hr;
BOOL ok = FALSE;
PROPVARIANT pv;
IPortableDevicePropVariantCollection *object_ids = NULL;
PropVariantInit(&pv);
pv.vt = VT_LPWSTR;
Py_BEGIN_ALLOW_THREADS;
hr = CoCreateInstance(CLSID_PortableDevicePropVariantCollection, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&object_ids));
Py_END_ALLOW_THREADS;
if (FAILED(hr)) { hresult_set_exc("Failed to create propvariantcollection", hr); goto end; }
pv.pwszVal = (wchar_t*)object_id;
hr = object_ids->Add(&pv);
pv.pwszVal = NULL;
if (FAILED(hr)) { hresult_set_exc("Failed to add device id to propvariantcollection", hr); goto end; }
Py_BEGIN_ALLOW_THREADS;
hr = device->Content(&content);
Py_END_ALLOW_THREADS;
if (FAILED(hr)) { hresult_set_exc("Failed to create content interface", hr); goto end; }
hr = content->Delete(PORTABLE_DEVICE_DELETE_NO_RECURSION, object_ids, NULL);
if (hr == E_ACCESSDENIED) PyErr_SetString(WPDError, "Do not have permission to delete this object");
else if (hr == HRESULT_FROM_WIN32(ERROR_DIR_NOT_EMPTY) || hr == HRESULT_FROM_WIN32(ERROR_INVALID_OPERATION)) PyErr_SetString(WPDError, "Cannot delete object as it has children");
else if (hr == HRESULT_FROM_WIN32(ERROR_NOT_FOUND) || SUCCEEDED(hr)) ok = TRUE;
else hresult_set_exc("Cannot delete object", hr);
end:
PropVariantClear(&pv);
if (content != NULL) content->Release();
if (object_ids != NULL) object_ids->Release();
if (!ok) return NULL;
Py_RETURN_NONE;
} // }}}
PyObject* wpd::put_file(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name, PyObject *src, unsigned PY_LONG_LONG size, PyObject *callback) { // {{{
IPortableDeviceContent *content = NULL;
IPortableDeviceValues *values = NULL;
IPortableDeviceProperties *devprops = NULL;
IPortableDeviceKeyCollection *properties = NULL;
IStream *temp = NULL;
IPortableDeviceDataStream *dest = NULL;
char *buf = NULL;
wchar_t *newid = NULL;
PyObject *ans = NULL, *raw;
HRESULT hr;
DWORD bufsize = 0;
BOOL ok = FALSE;
Py_ssize_t bytes_read = 0;
ULONG bytes_written = 0, total_written = 0;
values = create_object_properties(parent_id, name, WPD_CONTENT_TYPE_GENERIC_FILE, size);
if (values == NULL) goto end;
Py_BEGIN_ALLOW_THREADS;
hr = device->Content(&content);
Py_END_ALLOW_THREADS;
if (FAILED(hr)) { hresult_set_exc("Failed to create content interface", hr); goto end; }
hr = content->Properties(&devprops);
if (FAILED(hr)) { hresult_set_exc("Failed to get IPortableDeviceProperties interface", hr); goto end; }
properties = create_filesystem_properties_collection();
if (properties == NULL) goto end;
Py_BEGIN_ALLOW_THREADS;
hr = content->CreateObjectWithPropertiesAndData(values, &temp, &bufsize, NULL);
Py_END_ALLOW_THREADS;
if (FAILED(hr)) {
if (HRESULT_FROM_WIN32(ERROR_BUSY) == hr) {
PyErr_SetString(WPDFileBusy, "Object is in use");
} else hresult_set_exc("Failed to create stream interface to write to object", hr);
goto end;
}
hr = temp->QueryInterface(IID_PPV_ARGS(&dest));
if (FAILED(hr)) { hresult_set_exc("Failed to create IPortableDeviceStream", hr); goto end; }
while(TRUE) {
raw = PyObject_CallMethod(src, "read", "k", bufsize);
if (raw == NULL) break;
PyBytes_AsStringAndSize(raw, &buf, &bytes_read);
if (bytes_read > 0) {
Py_BEGIN_ALLOW_THREADS;
hr = dest->Write(buf, bytes_read, &bytes_written);
Py_END_ALLOW_THREADS;
Py_DECREF(raw);
if (hr == STG_E_MEDIUMFULL) { PyErr_SetString(WPDError, "Cannot write to device as it is full"); break; }
if (hr == STG_E_ACCESSDENIED) { PyErr_SetString(WPDError, "Cannot write to file as access is denied"); break; }
if (hr == STG_E_WRITEFAULT) { PyErr_SetString(WPDError, "Cannot write to file as there was a disk I/O error"); break; }
if (FAILED(hr)) { hresult_set_exc("Cannot write to file", hr); break; }
if (bytes_written != bytes_read) { PyErr_SetString(WPDError, "Writing to file failed, not all bytes were written"); break; }
total_written += bytes_written;
if (callback != NULL) Py_XDECREF(PyObject_CallFunction(callback, "kK", total_written, size));
} else Py_DECREF(raw);
if (bytes_read == 0) { ok = TRUE; break; }
}
if (!ok) {dest->Revert(); goto end;}
Py_BEGIN_ALLOW_THREADS;
hr = dest->Commit(STGC_DEFAULT);
Py_END_ALLOW_THREADS;
if (FAILED(hr)) { hresult_set_exc("Failed to write data to file, commit failed", hr); goto end; }
if (callback != NULL) Py_XDECREF(PyObject_CallFunction(callback, "kK", total_written, size));
Py_BEGIN_ALLOW_THREADS;
hr = dest->GetObjectID(&newid);
Py_END_ALLOW_THREADS;
if (FAILED(hr)) { hresult_set_exc("Failed to get id of newly created file", hr); goto end; }
ans = get_object_properties(devprops, properties, newid);
end:
if (content != NULL) content->Release();
if (values != NULL) values->Release();
if (devprops != NULL) devprops->Release();
if (properties != NULL) properties->Release();
if (temp != NULL) temp->Release();
if (dest != NULL) dest->Release();
if (newid != NULL) CoTaskMemFree(newid);
return ans;
} // }}}
} // namespace wpd

View File

@ -78,20 +78,22 @@ update_data(Device *self, PyObject *args, PyObject *kwargs) {
// get_filesystem() {{{
static PyObject*
py_get_filesystem(Device *self, PyObject *args, PyObject *kwargs) {
PyObject *storage_id;
PyObject *storage_id, *ret;
wchar_t *storage;
if (!PyArg_ParseTuple(args, "O", &storage_id)) return NULL;
storage = unicode_to_wchar(storage_id);
if (storage == NULL) return NULL;
return wpd::get_filesystem(self->device, storage, self->bulk_properties);
ret = wpd::get_filesystem(self->device, storage, self->bulk_properties);
free(storage);
return ret;
} // }}}
// get_file() {{{
static PyObject*
py_get_file(Device *self, PyObject *args, PyObject *kwargs) {
PyObject *object_id, *stream, *callback = NULL;
PyObject *object_id, *stream, *callback = NULL, *ret;
wchar_t *object;
if (!PyArg_ParseTuple(args, "OO|O", &object_id, &stream, &callback)) return NULL;
@ -100,7 +102,59 @@ py_get_file(Device *self, PyObject *args, PyObject *kwargs) {
if (callback == NULL || !PyCallable_Check(callback)) callback = NULL;
return wpd::get_file(self->device, object, stream, callback);
ret = wpd::get_file(self->device, object, stream, callback);
free(object);
return ret;
} // }}}
// create_folder() {{{
static PyObject*
py_create_folder(Device *self, PyObject *args, PyObject *kwargs) {
PyObject *pparent_id, *pname, *ret;
wchar_t *parent_id, *name;
if (!PyArg_ParseTuple(args, "OO", &pparent_id, &pname)) return NULL;
parent_id = unicode_to_wchar(pparent_id);
name = unicode_to_wchar(pname);
if (parent_id == NULL || name == NULL) return NULL;
ret = wpd::create_folder(self->device, parent_id, name);
free(parent_id); free(name);
return ret;
} // }}}
// delete_object() {{{
static PyObject*
py_delete_object(Device *self, PyObject *args, PyObject *kwargs) {
PyObject *pobject_id, *ret;
wchar_t *object_id;
if (!PyArg_ParseTuple(args, "O", &pobject_id)) return NULL;
object_id = unicode_to_wchar(pobject_id);
if (object_id == NULL) return NULL;
ret = wpd::delete_object(self->device, object_id);
free(object_id);
return ret;
} // }}}
// get_file() {{{
static PyObject*
py_put_file(Device *self, PyObject *args, PyObject *kwargs) {
PyObject *pparent_id, *pname, *stream, *callback = NULL, *ret;
wchar_t *parent_id, *name;
unsigned PY_LONG_LONG size;
if (!PyArg_ParseTuple(args, "OOOK|O", &pparent_id, &pname, &stream, &size, &callback)) return NULL;
parent_id = unicode_to_wchar(pparent_id);
name = unicode_to_wchar(pname);
if (parent_id == NULL || name == NULL) return NULL;
if (callback == NULL || !PyCallable_Check(callback)) callback = NULL;
ret = wpd::put_file(self->device, parent_id, name, stream, size, callback);
free(parent_id); free(name);
return ret;
} // }}}
static PyMethodDef Device_methods[] = {
@ -116,6 +170,18 @@ static PyMethodDef Device_methods[] = {
"get_file(object_id, stream, callback=None) -> Get the file identified by object_id from the device. The file is written to the stream object, which must be a file like object. If callback is not None, it must be a callable that accepts two arguments: (bytes_read, total_size). It will be called after each chunk is read from the device. Note that it can be called multiple times with the same values."
},
{"create_folder", (PyCFunction)py_create_folder, METH_VARARGS,
"create_folder(parent_id, name) -> Create a folder. Returns the folder metadata."
},
{"delete_object", (PyCFunction)py_delete_object, METH_VARARGS,
"delete_object(object_id) -> Delete the object identified by object_id. Note that trying to delete a non-empty folder will raise an error."
},
{"put_file", (PyCFunction)py_put_file, METH_VARARGS,
"put_file(parent_id, name, stream, size_in_bytes, callback=None) -> Copy a file from the stream object, creating a new file on the device with parent identified by parent_id. Returns the file metadata of the newly created file. callback should be a callable that accepts two argument: (bytes_written, total_size). It will be called after each chunk is written to the device. Note that it can be called multiple times with the same arguments."
},
{NULL}
};

View File

@ -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
from calibre.devices.errors import OpenFailed, DeviceError
from calibre.devices.mtp.base import MTPDeviceBase
from calibre.devices.mtp.filesystem_cache import FilesystemCache
@ -156,6 +156,7 @@ class MTP_DEVICE(MTPDeviceBase):
storage = {'id':storage_id, 'size':capacity, 'name':name,
'is_folder':True}
id_map = self.dev.get_filesystem(storage_id)
for x in id_map.itervalues(): x['storage_id'] = storage_id
all_storage.append(storage)
items.append(id_map.itervalues())
self._filesystem_cache = FilesystemCache(all_storage, chain(*items))
@ -204,6 +205,10 @@ class MTP_DEVICE(MTPDeviceBase):
def get_device_information(self, end_session=True):
d = self.dev.data
dv = d.get('device_version', '')
for sid, location_code in ( (self._main_id, 'main'), (self._carda_id,
'A'), (self._cardb_id, 'B')):
if sid is None: continue
# TODO: Implement the drive info dict
return (self.current_friendly_name, dv, dv, '')
@same_thread
@ -240,12 +245,31 @@ class MTP_DEVICE(MTPDeviceBase):
@same_thread
def get_file(self, object_id, stream=None, callback=None):
f = self.filesystem_cache.id_map[object_id]
if f.is_folder:
raise ValueError('%s is a folder on the device'%f.full_path)
if stream is None:
stream = SpooledTemporaryFile(5*1024*1024, '_wpd_receive_file.dat')
try:
try:
self.dev.get_file(object_id, stream, callback)
except self.wpd.WPDFileBusy:
time.sleep(2)
self.dev.get_file(object_id, stream, callback)
except Exception as e:
raise DeviceError('Failed to fetch the file %s with error: %s'%
f.full_path, as_unicode(e))
return stream
@same_thread
def create_folder(self, parent_id, name):
parent = self.filesystem_cache.id_map[parent_id]
if not parent.is_folder:
raise ValueError('%s is not a folder'%parent.full_path)
e = parent.folder_named(name)
if e is not None:
return e
ans = self.dev.create_folder(parent_id, name)
ans['storage_id'] = parent.storage_id
return parent.add_child(ans)

View File

@ -58,6 +58,9 @@ extern IPortableDevice* open_device(const wchar_t *pnp_id, IPortableDeviceValues
extern PyObject* get_device_information(IPortableDevice *device, IPortableDevicePropertiesBulk **bulk_properties);
extern PyObject* get_filesystem(IPortableDevice *device, const wchar_t *storage_id, IPortableDevicePropertiesBulk *bulk_properties);
extern PyObject* get_file(IPortableDevice *device, const wchar_t *object_id, PyObject *dest, PyObject *callback);
extern PyObject* create_folder(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name);
extern PyObject* delete_object(IPortableDevice *device, const wchar_t *object_id);
extern PyObject* put_file(IPortableDevice *device, const wchar_t *parent_id, const wchar_t *name, PyObject *src, unsigned PY_LONG_LONG size, PyObject *callback);
}

View File

@ -71,12 +71,16 @@ def main():
print ('Total space', dev.total_space())
print ('Free space', dev.free_space())
dev.filesystem_cache.dump()
# pprint.pprint(dev.dev.create_folder(dev.filesystem_cache.entries[0].object_id,
# 'zzz'))
# print ('Fetching file: oFF (198214 bytes)')
# stream = dev.get_file('oFF')
# print ("Fetched size: ", stream.tell())
finally:
dev.shutdown()
print ('Device connection shutdown')
if __name__ == '__main__':
main()

View File

@ -27,6 +27,10 @@ def get_newest_version():
'win' if iswindows else 'osx' if isosx else 'oth')
req.add_header('CALIBRE_INSTALL_UUID', prefs['installation_uuid'])
version = br.open(req).read().strip()
try:
version = version.decode('utf-8')
except UnicodeDecodeError:
version = u''
return version
class CheckForUpdates(QThread):