diff --git a/src/calibre/devices/mtp/unix/driver.py b/src/calibre/devices/mtp/unix/driver.py index cddd1c0f72..46ffdd236e 100644 --- a/src/calibre/devices/mtp/unix/driver.py +++ b/src/calibre/devices/mtp/unix/driver.py @@ -11,7 +11,8 @@ import time, operator from threading import RLock from functools import wraps from itertools import chain -from collections import deque +from collections import deque, OrderedDict +from io import BytesIO from calibre import prints from calibre.devices.errors import OpenFailed @@ -34,6 +35,7 @@ class FilesystemCache(object): self.folder_id_map = {f['id']:f for f in self.iterfolders(set_level=0)} # Set the parents of each file + self.files_in_root = OrderedDict() for f in files: parents = deque() pid = f['parent_id'] @@ -44,6 +46,9 @@ class FilesystemCache(object): break parents.appendleft(pid) pid = parent['parent_id'] + f['parents'] = parents + if not parents: + self.files_in_root[f['id']] = f # Set the files in each folder for f in self.iterfolders(): @@ -79,6 +84,9 @@ class FilesystemCache(object): prints(prefix, ' '*indent, '-', leaf['name'], 'id=%d'%leaf['id'], 'size=%d'%leaf['size'], 'modtime=%d'%leaf['modtime']) + for leaf in self.files_in_root.itervalues(): + prints('-', leaf['name'], 'id=%d'%leaf['id'], + 'size=%d'%leaf['size'], 'modtime=%d'%leaf['modtime']) class MTP_DEVICE(MTPDeviceBase): @@ -138,11 +146,11 @@ class MTP_DEVICE(MTPDeviceBase): @synchronous def post_yank_cleanup(self): - self.dev = None + self.dev = self.filesystem_cache = None @synchronous def shutdown(self): - self.dev = None + self.dev = self.filesystem_cache = None def format_errorstack(self, errs): return '\n'.join(['%d:%s'%(code, msg.decode('utf-8', 'replace')) for @@ -244,6 +252,10 @@ if __name__ == '__main__': print ("Storage info:") pprint(d.storage_info) print("Free space:", dev.free_space()) + raw = b'test' + fname = b'moose.txt' + src = BytesIO(raw) + print (d.put_file(dev._main_id, 0, fname, src, len(raw), PR())) # dev.filesystem_cache.dump_filesystem() # with open('/tmp/flint.epub', 'wb') as f: # print(d.get_file(786, f, PR())) diff --git a/src/calibre/devices/mtp/unix/libmtp.c b/src/calibre/devices/mtp/unix/libmtp.c index 15842552ba..02b2db0696 100644 --- a/src/calibre/devices/mtp/unix/libmtp.c +++ b/src/calibre/devices/mtp/unix/libmtp.c @@ -455,6 +455,54 @@ libmtp_Device_get_file(libmtp_Device *self, PyObject *args, PyObject *kwargs) { } // }}} +// Device.put_file {{{ +static PyObject * +libmtp_Device_put_file(libmtp_Device *self, PyObject *args, PyObject *kwargs) { + PyObject *stream, *callback = NULL, *errs, *fo; + ProgressCallback cb; + uint32_t parent_id, storage_id; + uint64_t filesize; + int ret; + char *name; + LIBMTP_file_t f, *nf; + + ENSURE_DEV(NULL); ENSURE_STORAGE(NULL); + + if (!PyArg_ParseTuple(args, "kksOK|O", &storage_id, &parent_id, &name, &stream, &filesize, &callback)) return NULL; + errs = PyList_New(0); + if (errs == NULL) { PyErr_NoMemory(); return NULL; } + + cb.obj = callback; cb.extra = stream; + f.parent_id = parent_id; f.storage_id = storage_id; f.item_id = 0; f.filename = name; f.filetype = LIBMTP_FILETYPE_UNKNOWN; f.filesize = filesize; + Py_XINCREF(callback); Py_INCREF(stream); + cb.state = PyEval_SaveThread(); + ret = LIBMTP_Send_File_From_Handler(self->device, data_from_python, &cb, &f, report_progress, &cb); + PyEval_RestoreThread(cb.state); + Py_XDECREF(callback); Py_DECREF(stream); + + if (ret != 0) { + dump_errorstack(self->device, errs); + fo = Py_None; Py_INCREF(fo); + } else { + nf = LIBMTP_Get_Filemetadata(self->device, f.item_id); + if (nf == NULL) { + dump_errorstack(self->device, errs); + fo = Py_None; Py_INCREF(fo); + } else { + fo = Py_BuildValue("{s:k,s:k,s:k,s:s,s:K,s:k}", + "id", nf->item_id, + "parent_id", nf->parent_id, + "storage_id", nf->storage_id, + "name", nf->filename, + "size", nf->filesize, + "modtime", nf->modificationdate + ); + } + } + + return Py_BuildValue("ONN", (ret == 0) ? Py_True : Py_False, fo, errs); + +} // }}} static PyMethodDef libmtp_Device_methods[] = { {"update_storage_info", (PyCFunction)libmtp_Device_update_storage_info, METH_VARARGS, "update_storage_info() -> Reread the storage info from the device (total, space, free space, storage locations, etc.)" @@ -472,6 +520,10 @@ static PyMethodDef libmtp_Device_methods[] = { "get_file(fileid, stream, callback=None) -> Get the file specified by fileid from the device. stream must be a file-like object. The file will be written to it. callback works the same as in get_filelist(). Returns ok, errs, where errs is a list of errors (if any)." }, + {"put_file", (PyCFunction)libmtp_Device_put_file, METH_VARARGS, + "put_file(storage_id, parent_id, filename, stream, size, callback=None) -> Put a file on the device. The file is read from stream. It is put inside the folder identified by parent_id on the storage identified by storage_id. Use parent_id=0 to put it in the root. Use storage_id=0 to put it on the primary storage. stream must be a file-like object. size is the size in bytes of the data in stream. callback works the same as in get_filelist(). Returns ok, fileinfo, errs, where errs is a list of errors (if any), and fileinfo is a file information dictionary, as returned by get_filelist()." + }, + {NULL} /* Sentinel */ };