diff options
Diffstat (limited to '')
-rwxr-xr-x | mtp/ffs/MtpStorage.cpp | 810 |
1 files changed, 810 insertions, 0 deletions
diff --git a/mtp/ffs/MtpStorage.cpp b/mtp/ffs/MtpStorage.cpp new file mode 100755 index 000000000..8c67b5bd6 --- /dev/null +++ b/mtp/ffs/MtpStorage.cpp @@ -0,0 +1,810 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "MtpStorage" + +#include "MtpDebug.h" +#include "MtpStorage.h" +#include "btree.hpp" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <unistd.h> +#include <dirent.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <limits.h> +#include <iterator> +#include <sys/inotify.h> + +#define WATCH_FLAGS ( IN_CREATE | IN_DELETE | IN_MOVE | IN_MODIFY ) + +MtpStorage::MtpStorage(MtpStorageID id, const char* filePath, + const char* description, bool removable, uint64_t maxFileSize, MtpServer* refserver) + : mStorageID(id), + mFilePath(filePath), + mDescription(description), + mMaxCapacity(0), + mMaxFileSize(maxFileSize), + mRemovable(removable), + mServer(refserver) +{ + MTPD("MtpStorage id: %d path: %s\n", id, filePath); + inotify_thread = 0; + inotify_fd = -1; + // Threading has not started yet so we should be safe to set these directly instead of using atomics + inotify_thread_kill.set_value(0); + sendEvents = false; + handleCurrentlySending = 0; + use_mutex = true; + if (pthread_mutex_init(&mtpMutex, NULL) != 0) { + MTPE("Failed to init mtpMutex\n"); + use_mutex = false; + } + if (pthread_mutex_init(&inMutex, NULL) != 0) { + MTPE("Failed to init inMutex\n"); + pthread_mutex_destroy(&mtpMutex); + use_mutex = false; + } +} + +MtpStorage::~MtpStorage() { + if (inotify_thread) { + inotify_thread_kill.set_value(1); + MTPD("joining inotify_thread after sending the kill notification.\n"); + pthread_join(inotify_thread, NULL); // There's not much we can do if there's an error here + inotify_thread = 0; + MTPD("~MtpStorage removing inotify watches and closing inotify_fd\n"); + for (std::map<int, Tree*>::iterator i = inotifymap.begin(); i != inotifymap.end(); i++) { + inotify_rm_watch(inotify_fd, i->first); + } + close(inotify_fd); + inotifymap.clear(); + } + // Deleting the root tree causes a cascade in btree.cpp that ends up + // deleting all of the trees and nodes. + delete mtpmap[0]; + mtpmap.clear(); + if (use_mutex) { + use_mutex = false; + MTPD("~MtpStorage destroying mutexes\n"); + pthread_mutex_destroy(&mtpMutex); + pthread_mutex_destroy(&inMutex); + } + +} + +int MtpStorage::getType() const { + return (mRemovable ? MTP_STORAGE_REMOVABLE_RAM : MTP_STORAGE_FIXED_RAM); +} + +int MtpStorage::getFileSystemType() const { + return MTP_STORAGE_FILESYSTEM_HIERARCHICAL; +} + +int MtpStorage::getAccessCapability() const { + return MTP_STORAGE_READ_WRITE; +} + +uint64_t MtpStorage::getMaxCapacity() { + if (mMaxCapacity == 0) { + struct statfs stat; + if (statfs(getPath(), &stat)) + return -1; + mMaxCapacity = (uint64_t)stat.f_blocks * (uint64_t)stat.f_bsize; + } + return mMaxCapacity; +} + +uint64_t MtpStorage::getFreeSpace() { + struct statfs stat; + if (statfs(getPath(), &stat)) + return -1; + return (uint64_t)stat.f_bavail * (uint64_t)stat.f_bsize; +} + +const char* MtpStorage::getDescription() const { + return (const char *)mDescription; +} + +int MtpStorage::renameObject(MtpObjectHandle handle, std::string newName) { + MTPD("MtpStorage::renameObject, handle: %u, new name: '%s'\n", handle, newName.c_str()); + if (handle == MTP_PARENT_ROOT) { + MTPE("parent == MTP_PARENT_ROOT, cannot rename root\n"); + return -1; + } else { + for (iter i = mtpmap.begin(); i != mtpmap.end(); i++) { + Node* node = i->second->findNode(handle); + if (node != NULL) { + std::string oldName = getNodePath(node); + std::string parentdir = oldName.substr(0, oldName.find_last_of('/')); + std::string newFullName = parentdir + "/" + newName; + MTPD("old: '%s', new: '%s'\n", oldName.c_str(), newFullName.c_str()); + if (rename(oldName.c_str(), newFullName.c_str()) == 0) { + node->rename(newName); + return 0; + } else { + MTPE("MtpStorage::renameObject failed, handle: %u, new name: '%s'\n", handle, newName.c_str()); + return -1; + } + } + } + } + // handle not found on this storage + return -1; +} + +MtpObjectHandle MtpStorage::beginSendObject(const char* path, + MtpObjectFormat format, + MtpObjectHandle parent, + __attribute__((unused)) uint64_t size, + __attribute__((unused)) time_t modified) { + MTPD("MtpStorage::beginSendObject(), path: '%s', parent: %u, format: %04x\n", path, parent, format); + iter it = mtpmap.find(parent); + if (it == mtpmap.end()) { + MTPE("parent node not found, returning error\n"); + return kInvalidObjectHandle; + } + Tree* tree = it->second; + + std::string pathstr(path); + size_t slashpos = pathstr.find_last_of('/'); + if (slashpos == std::string::npos) { + MTPE("path has no slash, returning error\n"); + return kInvalidObjectHandle; + } + std::string parentdir = pathstr.substr(0, slashpos); + std::string basename = pathstr.substr(slashpos + 1); + if (parent != 0 && parentdir != getNodePath(tree)) { + MTPE("beginSendObject into path '%s' but parent tree has path '%s', returning error\n", parentdir.c_str(), getNodePath(tree).c_str()); + return kInvalidObjectHandle; + } + + MTPD("MtpStorage::beginSendObject() parentdir: %s basename: %s\n", parentdir.c_str(), basename.c_str()); + // note: for directories, the mkdir call is done later in MtpServer, here we just reserve a handle + bool isDir = format == MTP_FORMAT_ASSOCIATION; + Node* node = addNewNode(isDir, tree, basename); + handleCurrentlySending = node->Mtpid(); // suppress inotify for this node while sending + + return node->Mtpid(); +} + +int MtpStorage::createDB() { + std::string mtpParent = ""; + mtpstorageparent = getPath(); + // root directory is special: handle 0, parent 0, and empty path + mtpmap[0] = new Tree(0, 0, ""); + if (use_mutex) { + sendEvents = true; + MTPD("inotify_init\n"); + inotify_fd = inotify_init(); + if (inotify_fd < 0) { + MTPE("Can't run inotify_init for mtp server: %s\n", strerror(errno)); + } else { + MTPD("Starting inotify thread\n"); + inotify_thread = inotify(); + } + } else { + MTPD("NOT starting inotify thread\n"); + } + // for debugging and caching purposes, read the root dir already now + readDir(mtpstorageparent, mtpmap[0]); + // all other dirs are read on demand + // + MTPD("MtpStorage::createDB DONE\n"); + return 0; +} + +Node* MtpStorage::findNode(MtpObjectHandle handle) { + for (iter i = mtpmap.begin(); i != mtpmap.end(); i++) { + Node* node = i->second->findNode(handle); + if (node != NULL) { + MTPD("findNode: found node %p for handle %u, name: %s\n", node, handle, node->getName().c_str()); + if (node->Mtpid() != handle) + { + MTPE("BUG: entry for handle %u points to node with handle %u\n", handle, node->Mtpid()); + } + return node; + } + } + // Item is not on this storage device + MTPD("MtpStorage::findNode: no node found for handle %u on storage %u, searched %u trees\n", handle, mStorageID, mtpmap.size()); + return NULL; +} + +std::string MtpStorage::getNodePath(Node* node) { + std::string path; + MTPD("getNodePath: node %p, handle %u\n", node, node->Mtpid()); + while (node) + { + path = "/" + node->getName() + path; + MtpObjectHandle parent = node->getMtpParentId(); + if (parent == 0) // root + break; + node = findNode(parent); + } + path = mtpstorageparent + path; + MTPD("getNodePath: path %s\n", path.c_str()); + return path; +} + +MtpObjectHandleList* MtpStorage::getObjectList(__attribute__((unused)) MtpStorageID storageID, MtpObjectHandle parent) { + MTPD("MtpStorage::getObjectList, parent: %u\n", parent); + //append object id (numerical #s) of database to int array + MtpObjectHandleList* list = new MtpObjectHandleList(); + if (parent == MTP_PARENT_ROOT) { + MTPD("parent == MTP_PARENT_ROOT\n"); + parent = 0; + } + + if (mtpmap.find(parent) == mtpmap.end()) { + MTPE("parent handle not found, returning empty list\n"); + return list; + } + + Tree* tree = mtpmap[parent]; + if (!tree->wasAlreadyRead()) + { + std::string path = getNodePath(tree); + MTPD("reading directory on demand for tree %p (%u), path: %s\n", tree, tree->Mtpid(), path.c_str()); + readDir(path, tree); + } + + mtpmap[parent]->getmtpids(list); + MTPD("returning %u objects in %s.\n", list->size(), tree->getName().c_str()); + return list; +} + +Node* MtpStorage::addNewNode(bool isDir, Tree* tree, const std::string& name) +{ + // global counter for new object handles + static MtpObjectHandle mtpid = 0; + + ++mtpid; + MTPD("adding new %s node for %s, new handle: %u\n", isDir ? "dir" : "file", name.c_str(), mtpid); + MtpObjectHandle parent = tree->Mtpid(); + MTPD("parent tree: %x, handle: %u, name: %s\n", tree, parent, tree->getName().c_str()); + Node* node; + if (isDir) + node = mtpmap[mtpid] = new Tree(mtpid, parent, name); + else + node = new Node(mtpid, parent, name); + tree->addEntry(node); + return node; +} + +int MtpStorage::readDir(const std::string& path, Tree* tree) +{ + struct dirent *de; + int storageID = getStorageID(); + MtpObjectHandle parent = tree->Mtpid(); + + DIR *d = opendir(path.c_str()); + MTPD("reading dir '%s', parent handle %u\n", path.c_str(), parent); + if (d == NULL) { + MTPE("error opening '%s' -- error: %s\n", path.c_str(), strerror(errno)); + return -1; + } + // TODO: for refreshing dirs: capture old entries here + while ((de = readdir(d)) != NULL) { + // Because exfat-fuse causes issues with dirent, we will use stat + // for some things that dirent should be able to do + std::string item = path + "/" + de->d_name; + struct stat st; + if (lstat(item.c_str(), &st)) { + MTPE("Error running lstat on '%s'\n", item.c_str()); + return -1; + } + // TODO: if we want to use this for refreshing dirs too, first find existing name and overwrite + if (strcmp(de->d_name, ".") == 0) + continue; + if (strcmp(de->d_name, "..") == 0) + continue; + Node* node = addNewNode(st.st_mode & S_IFDIR, tree, de->d_name); + node->addProperties(item, storageID); + //if (sendEvents) + // mServer->sendObjectAdded(node->Mtpid()); + // sending events here makes simple-mtpfs very slow, and it is probably the wrong thing to do anyway + } + closedir(d); + // TODO: for refreshing dirs: remove entries that no longer exist (with their nodes) + tree->setAlreadyRead(true); + addInotify(tree); + return 0; +} + +int MtpStorage::addInotify(Tree* tree) { + if (inotify_fd < 0) { + MTPE("inotify_fd not set or error: %i\n", inotify_fd); + return -1; + } + std::string path = getNodePath(tree); + MTPD("adding inotify for tree %x, dir: %s\n", tree, path.c_str()); + int wd = inotify_add_watch(inotify_fd, path.c_str(), WATCH_FLAGS); + if (wd < 0) { + MTPE("inotify_add_watch failed: %s\n", strerror(errno)); + return -1; + } + inotifymap[wd] = tree; + return 0; +} + +pthread_t MtpStorage::inotify(void) { + pthread_t thread; + pthread_attr_t tattr; + + if (pthread_attr_init(&tattr)) { + MTPE("Unable to pthread_attr_init\n"); + return 0; + } + if (pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_JOINABLE)) { + MTPE("Error setting pthread_attr_setdetachstate\n"); + return 0; + } + ThreadPtr inotifyptr = &MtpStorage::inotify_t; + PThreadPtr p = *(PThreadPtr*)&inotifyptr; + pthread_create(&thread, &tattr, p, this); + if (pthread_attr_destroy(&tattr)) { + MTPE("Failed to pthread_attr_destroy\n"); + } + return thread; +} + +int MtpStorage::inotify_t(void) { + #define EVENT_SIZE ( sizeof(struct inotify_event) ) + #define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16) ) + char buf[EVENT_BUF_LEN]; + fd_set fdset; + struct timeval seltmout; + int sel_ret; + + MTPD("inotify thread starting.\n"); + + while (inotify_thread_kill.get_value() == 0) { + FD_ZERO(&fdset); + FD_SET(inotify_fd, &fdset); + seltmout.tv_sec = 0; + seltmout.tv_usec = 25000; + sel_ret = select(inotify_fd + 1, &fdset, NULL, NULL, &seltmout); + if (sel_ret == 0) + continue; + int i = 0; + int len = read(inotify_fd, buf, EVENT_BUF_LEN); + + if (len < 0) { + if (errno == EINTR) + continue; + MTPE("inotify_t Can't read inotify events\n"); + } + + while (i < len && inotify_thread_kill.get_value() == 0) { + struct inotify_event *event = (struct inotify_event *) &buf[i]; + if (event->len) { + MTPD("inotify event: wd: %i, mask: %x, name: %s\n", event->wd, event->mask, event->name); + lockMutex(1); + handleInotifyEvent(event); + unlockMutex(1); + } + i += EVENT_SIZE + event->len; + } + } + MTPD("inotify_thread_kill received!\n"); + // This cleanup is handled in the destructor. + /*for (std::map<int, Tree*>::iterator i = inotifymap.begin(); i != inotifymap.end(); i++) { + inotify_rm_watch(inotify_fd, i->first); + } + close(inotify_fd);*/ + return 0; +} + +void MtpStorage::handleInotifyEvent(struct inotify_event* event) +{ + std::map<int, Tree*>::iterator it = inotifymap.find(event->wd); + if (it == inotifymap.end()) { + MTPE("Unable to locate inotify_wd: %i\n", event->wd); + return; + } + Tree* tree = it->second; + MTPD("inotify_t tree: %x '%s'\n", tree, tree->getName().c_str()); + Node* node = tree->findEntryByName(basename(event->name)); + if (node && node->Mtpid() == handleCurrentlySending) { + MTPD("ignoring inotify event for currently uploading file, handle: %u\n", node->Mtpid()); + return; + } + if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) { + if (event->mask & IN_ISDIR) { + MTPD("inotify_t create is dir\n"); + } else { + MTPD("inotify_t create is file\n"); + } + if (node == NULL) { + node = addNewNode(event->mask & IN_ISDIR, tree, event->name); + std::string item = getNodePath(tree) + "/" + event->name; + node->addProperties(item, getStorageID()); + mServer->sendObjectAdded(node->Mtpid()); + } else { + MTPD("inotify_t item already exists.\n"); + } + if (event->mask & IN_ISDIR) { + // TODO: do we need to do anything here? probably not until someone reads from the dir... + } + } else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) { + if (event->mask & IN_ISDIR) { + MTPD("inotify_t Directory %s deleted\n", event->name); + } else { + MTPD("inotify_t File %s deleted\n", event->name); + } + if (node) + { + if (event->mask & IN_ISDIR) { + for (std::map<int, Tree*>::iterator it = inotifymap.begin(); it != inotifymap.end(); ++it) { + if (it->second == node) { + inotify_rm_watch(inotify_fd, it->first); + MTPD("inotify_t removing watch on '%s'\n", getNodePath(it->second).c_str()); + inotifymap.erase(it->first); + break; + } + + } + } + MtpObjectHandle handle = node->Mtpid(); + deleteFile(handle); + mServer->sendObjectRemoved(handle); + } else { + MTPD("inotify_t already removed.\n"); + } + } else if (event->mask & IN_MODIFY) { + MTPD("inotify_t item %s modified.\n", event->name); + if (node != NULL) { + uint64_t orig_size = node->getProperty(MTP_PROPERTY_OBJECT_SIZE).valueInt; + struct stat st; + uint64_t new_size = 0; + if (lstat(getNodePath(node).c_str(), &st) == 0) + new_size = (uint64_t)st.st_size; + if (orig_size != new_size) { + MTPD("size changed from %llu to %llu on mtpid: %u\n", orig_size, new_size, node->Mtpid()); + node->updateProperty(MTP_PROPERTY_OBJECT_SIZE, new_size, "", MTP_TYPE_UINT64); + mServer->sendObjectUpdated(node->Mtpid()); + } + } else { + MTPE("inotify_t modified item not found\n"); + } + } else if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF) { + // TODO: is this always already handled by IN_DELETE for the parent dir? + } +} + +void MtpStorage::lockMutex(int thread_type) { + if (!use_mutex) + return; // mutex is disabled + if (thread_type) { + // inotify thread + pthread_mutex_lock(&inMutex); + while (pthread_mutex_trylock(&mtpMutex)) { + pthread_mutex_unlock(&inMutex); + usleep(32000); + pthread_mutex_lock(&inMutex); + } + } else { + // main mtp thread + pthread_mutex_lock(&mtpMutex); + while (pthread_mutex_trylock(&inMutex)) { + pthread_mutex_unlock(&mtpMutex); + usleep(13000); + pthread_mutex_lock(&mtpMutex); + } + } +} + +void MtpStorage::unlockMutex( __attribute__((unused)) int thread_type) { + if (!use_mutex) + return; // mutex is disabled + pthread_mutex_unlock(&inMutex); + pthread_mutex_unlock(&mtpMutex); +} + +int MtpStorage::getObjectPropertyValue(MtpObjectHandle handle, MtpObjectProperty property, MtpStorage::PropEntry& pe) { + Node *node; + for (iter i = mtpmap.begin(); i != mtpmap.end(); i++) { + node = i->second->findNode(handle); + if (node != NULL) { + const Node::mtpProperty& prop = node->getProperty(property); + if (prop.property != property) { + MTPD("getObjectPropertyValue: unknown property %x for handle %u\n", property, handle); + return -1; + } + pe.datatype = prop.dataType; + pe.intvalue = prop.valueInt; + pe.strvalue = prop.valueStr; + pe.handle = handle; + pe.property = property; + return 0; + } + } + // handle not found on this storage + return -1; +} + +void MtpStorage::endSendObject(const char* path, MtpObjectHandle handle, __attribute__((unused)) MtpObjectFormat format, __attribute__((unused)) bool succeeded) +{ + Node* node = findNode(handle); + if (!node) + return; // just ignore if this is for another storage + + node->addProperties(path, mStorageID); + handleCurrentlySending = 0; + // TODO: are we supposed to send an event about an upload by the initiator? + if (sendEvents) + mServer->sendObjectAdded(node->Mtpid()); +} + +int MtpStorage::getObjectPropertyList(MtpObjectHandle handle, uint32_t format, uint32_t property, int groupCode, __attribute__((unused)) int depth, MtpDataPacket& packet) { + MTPD("MtpStorage::getObjectPropertyList handle: %u, format: %x, property: %x\n", handle, format, property); + if (groupCode != 0) + { + MTPE("getObjectPropertyList: groupCode unsupported\n"); + return -1; // TODO: RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED + } + // TODO: support all the special stuff, like: + // handle == 0 -> all objects at the root level + // handle == 0xffffffff -> all objects (on all storages? how could we support that?) + // format == 0 -> all formats, otherwise filter by ObjectFormatCode + // property == 0xffffffff -> all properties except those with group code 0xffffffff + // if property == 0 then use groupCode + // groupCode == 0 -> return Specification_By_Group_Unsupported + // depth == 0xffffffff -> all objects incl. and below handle + + std::vector<PropEntry> results; + + if (handle == 0xffffffff) { + // TODO: all object on all storages (needs a different design, result packet needs to be built by server instead of storage) + } else if (handle == 0) { + // all objects at the root level + Tree* root = mtpmap[0]; + MtpObjectHandleList list; + root->getmtpids(&list); + for (MtpObjectHandleList::iterator it = list.begin(); it != list.end(); ++it) { + Node* node = root->findNode(*it); + if (!node) { + MTPE("BUG: node not found for root entry with handle %u\n", *it); + break; + } + queryNodeProperties(results, node, property, groupCode, mStorageID); + } + } else { + // single object + Node* node = findNode(handle); + if (!node) { + // Item is not on this storage device + return -1; + } + queryNodeProperties(results, node, property, groupCode, mStorageID); + } + + MTPD("MtpStorage::getObjectPropertyList::count: %u\n", results.size()); + packet.putUInt32(results.size()); + + for (size_t i = 0; i < results.size(); ++i) { + PropEntry& p = results[i]; + MTPD("handle: %u, propertyCode: %x = %s, datatype: %x, value: %llu\n", + p.handle, p.property, MtpDebug::getObjectPropCodeName(p.property), + p.datatype, p.intvalue); + packet.putUInt32(p.handle); + packet.putUInt16(p.property); + packet.putUInt16(p.datatype); + switch (p.datatype) { + case MTP_TYPE_INT8: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT8\n"); + packet.putInt8(p.intvalue); + break; + case MTP_TYPE_UINT8: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT8\n"); + packet.putUInt8(p.intvalue); + break; + case MTP_TYPE_INT16: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT16\n"); + packet.putInt16(p.intvalue); + break; + case MTP_TYPE_UINT16: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT16\n"); + packet.putUInt16(p.intvalue); + break; + case MTP_TYPE_INT32: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT32\n"); + packet.putInt32(p.intvalue); + break; + case MTP_TYPE_UINT32: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT32\n"); + packet.putUInt32(p.intvalue); + break; + case MTP_TYPE_INT64: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT64\n"); + packet.putInt64(p.intvalue); + break; + case MTP_TYPE_UINT64: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT64\n"); + packet.putUInt64(p.intvalue); + break; + case MTP_TYPE_INT128: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT128\n"); + packet.putInt128(p.intvalue); + break; + case MTP_TYPE_UINT128: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT128\n"); + packet.putUInt128(p.intvalue); + break; + case MTP_TYPE_STR: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_STR: %s\n", p.strvalue.c_str()); + packet.putString(p.strvalue.c_str()); + break; + default: + MTPE("bad or unsupported data type: %x in MyMtpDatabase::getObjectPropertyList", p.datatype); + break; + } + } + return 0; +} + +int MtpStorage::getObjectInfo(MtpObjectHandle handle, MtpObjectInfo& info) { + struct stat st; + uint64_t size = 0; + MTPD("MtpStorage::getObjectInfo, handle: %u\n", handle); + Node* node = findNode(handle); + if (!node) { + // Item is not on this storage device + return -1; + } + + info.mStorageID = getStorageID(); + MTPD("info.mStorageID: %u\n", info.mStorageID); + info.mParent = node->getMtpParentId(); + MTPD("mParent: %u\n", info.mParent); + // TODO: do we want to lstat again here, or read from the node properties? + if (lstat(getNodePath(node).c_str(), &st) == 0) + size = st.st_size; + MTPD("size is: %llu\n", size); + info.mCompressedSize = (size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size); + info.mDateModified = st.st_mtime; + if (S_ISDIR(st.st_mode)) { + info.mFormat = MTP_FORMAT_ASSOCIATION; + } + else { + info.mFormat = MTP_FORMAT_UNDEFINED; + } + info.mName = strdup(node->getName().c_str()); + MTPD("MtpStorage::getObjectInfo found, Exiting getObjectInfo()\n"); + return 0; +} + +int MtpStorage::getObjectFilePath(MtpObjectHandle handle, MtpStringBuffer& outFilePath, int64_t& outFileLength, MtpObjectFormat& outFormat) { + MTPD("MtpStorage::getObjectFilePath handle: %u\n", handle); + Node* node = findNode(handle); + if (!node) + { + // Item is not on this storage device + return -1; + } + // TODO: do we want to lstat here, or just read the info from the node? + struct stat st; + if (lstat(getNodePath(node).c_str(), &st) == 0) + outFileLength = st.st_size; + else + outFileLength = 0; + outFilePath.set(getNodePath(node).c_str()); + MTPD("outFilePath: %s\n", (const char*) outFilePath); + outFormat = node->isDir() ? MTP_FORMAT_ASSOCIATION : MTP_FORMAT_UNDEFINED; + return 0; +} + +int MtpStorage::deleteFile(MtpObjectHandle handle) { + MTPD("MtpStorage::deleteFile handle: %u\n", handle); + Node* node = findNode(handle); + if (!node) { + // Item is not on this storage device + return -1; + } + MtpObjectHandle parent = node->getMtpParentId(); + Tree* tree = mtpmap[parent]; + if (!tree) { + MTPE("parent tree for handle %u not found\n", parent); + return -1; + } + if (node->isDir()) { + MTPD("deleting tree from mtpmap: %u\n", handle); + mtpmap.erase(handle); + } + + MTPD("deleting handle: %u\n", handle); + tree->deleteNode(handle); + MTPD("deleted\n"); + return 0; +} + +void MtpStorage::queryNodeProperties(std::vector<MtpStorage::PropEntry>& results, Node* node, uint32_t property, __attribute__((unused)) int groupCode, MtpStorageID storageID) +{ + MTPD("queryNodeProperties handle %u, path: %s\n", node->Mtpid(), getNodePath(node).c_str()); + PropEntry pe; + pe.handle = node->Mtpid(); + pe.property = property; + + if (property == 0xffffffff) + { + // add all properties + MTPD("MtpStorage::queryNodeProperties for all properties\n"); + std::vector<Node::mtpProperty> mtpprop = node->getMtpProps(); + for (size_t i = 0; i < mtpprop.size(); ++i) { + pe.property = mtpprop[i].property; + pe.datatype = mtpprop[i].dataType; + pe.intvalue = mtpprop[i].valueInt; + pe.strvalue = mtpprop[i].valueStr; + results.push_back(pe); + } + return; + } + else if (property == 0) + { + // TODO: use groupCode + } + + // single property + // TODO: this should probably be moved to the Node class and/or merged with getObjectPropertyValue + switch (property) { +// case MTP_PROPERTY_OBJECT_FORMAT: +// pe.datatype = MTP_TYPE_UINT16; +// pe.intvalue = node->getIntProperty(MTP_PROPERTY_OBJECT_FORMAT); +// break; + + case MTP_PROPERTY_STORAGE_ID: + pe.datatype = MTP_TYPE_UINT32; + pe.intvalue = storageID; + break; + + case MTP_PROPERTY_PROTECTION_STATUS: + pe.datatype = MTP_TYPE_UINT16; + pe.intvalue = 0; + break; + + case MTP_PROPERTY_OBJECT_SIZE: + { + pe.datatype = MTP_TYPE_UINT64; + struct stat st; + pe.intvalue = 0; + if (lstat(getNodePath(node).c_str(), &st) == 0) + pe.intvalue = st.st_size; + break; + } + + default: + { + const Node::mtpProperty& prop = node->getProperty(property); + if (prop.property != property) + { + MTPD("queryNodeProperties: unknown property %x\n", property); + return; + } + pe.datatype = prop.dataType; + pe.intvalue = prop.valueInt; + pe.strvalue = prop.valueStr; + // TODO: all the special case stuff in MyMtpDatabase::getObjectPropertyValue is missing here + } + + } + results.push_back(pe); +} + + |