summaryrefslogtreecommitdiffstats
path: root/dom/system/gonk/MozMtpDatabase.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/system/gonk/MozMtpDatabase.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/system/gonk/MozMtpDatabase.cpp')
-rw-r--r--dom/system/gonk/MozMtpDatabase.cpp1542
1 files changed, 1542 insertions, 0 deletions
diff --git a/dom/system/gonk/MozMtpDatabase.cpp b/dom/system/gonk/MozMtpDatabase.cpp
new file mode 100644
index 000000000..29fe23e8d
--- /dev/null
+++ b/dom/system/gonk/MozMtpDatabase.cpp
@@ -0,0 +1,1542 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MozMtpDatabase.h"
+#include "MozMtpServer.h"
+
+#include "base/message_loop.h"
+#include "DeviceStorage.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/Services.h"
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "prio.h"
+
+#include <dirent.h>
+#include <libgen.h>
+#include <utime.h>
+#include <sys/stat.h>
+
+using namespace android;
+using namespace mozilla;
+
+namespace mozilla {
+MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCloseDir, PRDir, PR_CloseDir)
+}
+
+BEGIN_MTP_NAMESPACE
+
+static const char* kMtpWatcherNotify = "mtp-watcher-notify";
+
+#if 0
+// Some debug code for figuring out deadlocks, if you happen to run into
+// that scenario
+
+class DebugMutexAutoLock: public MutexAutoLock
+{
+public:
+ DebugMutexAutoLock(mozilla::Mutex& aMutex)
+ : MutexAutoLock(aMutex)
+ {
+ MTP_LOG("Mutex acquired");
+ }
+
+ ~DebugMutexAutoLock()
+ {
+ MTP_LOG("Releasing mutex");
+ }
+};
+#define MutexAutoLock MTP_LOG("About to enter mutex"); DebugMutexAutoLock
+
+#endif
+
+static const char *
+ObjectPropertyAsStr(MtpObjectProperty aProperty)
+{
+ switch (aProperty) {
+ case MTP_PROPERTY_STORAGE_ID: return "MTP_PROPERTY_STORAGE_ID";
+ case MTP_PROPERTY_OBJECT_FORMAT: return "MTP_PROPERTY_OBJECT_FORMAT";
+ case MTP_PROPERTY_PROTECTION_STATUS: return "MTP_PROPERTY_PROTECTION_STATUS";
+ case MTP_PROPERTY_OBJECT_SIZE: return "MTP_PROPERTY_OBJECT_SIZE";
+ case MTP_PROPERTY_OBJECT_FILE_NAME: return "MTP_PROPERTY_OBJECT_FILE_NAME";
+ case MTP_PROPERTY_DATE_CREATED: return "MTP_PROPERTY_DATE_CREATED";
+ case MTP_PROPERTY_DATE_MODIFIED: return "MTP_PROPERTY_DATE_MODIFIED";
+ case MTP_PROPERTY_PARENT_OBJECT: return "MTP_PROPERTY_PARENT_OBJECT";
+ case MTP_PROPERTY_PERSISTENT_UID: return "MTP_PROPERTY_PERSISTENT_UID";
+ case MTP_PROPERTY_NAME: return "MTP_PROPERTY_NAME";
+ case MTP_PROPERTY_DATE_ADDED: return "MTP_PROPERTY_DATE_ADDED";
+ case MTP_PROPERTY_WIDTH: return "MTP_PROPERTY_WIDTH";
+ case MTP_PROPERTY_HEIGHT: return "MTP_PROPERTY_HEIGHT";
+ case MTP_PROPERTY_IMAGE_BIT_DEPTH: return "MTP_PROPERTY_IMAGE_BIT_DEPTH";
+ case MTP_PROPERTY_DISPLAY_NAME: return "MTP_PROPERTY_DISPLAY_NAME";
+ }
+ return "MTP_PROPERTY_???";
+}
+
+static char*
+FormatDate(time_t aTime, char *aDateStr, size_t aDateStrSize)
+{
+ struct tm tm;
+ localtime_r(&aTime, &tm);
+ MTP_LOG("(%ld) tm_zone = %s off = %ld", aTime, tm.tm_zone, tm.tm_gmtoff);
+ strftime(aDateStr, aDateStrSize, "%Y%m%dT%H%M%S", &tm);
+ return aDateStr;
+}
+
+MozMtpDatabase::MozMtpDatabase()
+ : mMutex("MozMtpDatabase::mMutex"),
+ mDb(mMutex),
+ mStorage(mMutex),
+ mBeginSendObjectCalled(false)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ // We use the index into the array as the handle. Since zero isn't a valid
+ // index, we stick a dummy entry there.
+
+ RefPtr<DbEntry> dummy;
+
+ MutexAutoLock lock(mMutex);
+ mDb.AppendElement(dummy);
+}
+
+//virtual
+MozMtpDatabase::~MozMtpDatabase()
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+}
+
+void
+MozMtpDatabase::AddEntry(DbEntry *entry)
+{
+ MutexAutoLock lock(mMutex);
+
+ entry->mHandle = GetNextHandle();
+ MOZ_ASSERT(mDb.Length() == entry->mHandle);
+ mDb.AppendElement(entry);
+
+ MTP_DBG("Handle: 0x%08x Parent: 0x%08x Path:'%s'",
+ entry->mHandle, entry->mParent, entry->mPath.get());
+}
+
+void
+MozMtpDatabase::AddEntryAndNotify(DbEntry* entry, RefCountedMtpServer* aMtpServer)
+{
+ AddEntry(entry);
+ aMtpServer->sendObjectAdded(entry->mHandle);
+}
+
+void
+MozMtpDatabase::DumpEntries(const char* aLabel)
+{
+ MutexAutoLock lock(mMutex);
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ MTP_LOG("%s: numEntries = %d", aLabel, numEntries);
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry) {
+ MTP_LOG("%s: mDb[%d]: mHandle: 0x%08x mParent: 0x%08x StorageID: 0x%08x path: '%s'",
+ aLabel, entryIndex, entry->mHandle, entry->mParent, entry->mStorageID, entry->mPath.get());
+ } else {
+ MTP_LOG("%s: mDb[%2d]: entry is NULL", aLabel, entryIndex);
+ }
+ }
+}
+
+MtpObjectHandle
+MozMtpDatabase::FindEntryByPath(const nsACString& aPath)
+{
+ MutexAutoLock lock(mMutex);
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry && entry->mPath.Equals(aPath)) {
+ return entryIndex;
+ }
+ }
+ return 0;
+}
+
+already_AddRefed<MozMtpDatabase::DbEntry>
+MozMtpDatabase::GetEntry(MtpObjectHandle aHandle)
+{
+ MutexAutoLock lock(mMutex);
+
+ RefPtr<DbEntry> entry;
+
+ if (aHandle > 0 && aHandle < mDb.Length()) {
+ entry = mDb[aHandle];
+ }
+ return entry.forget();
+}
+
+void
+MozMtpDatabase::RemoveEntry(MtpObjectHandle aHandle)
+{
+ MutexAutoLock lock(mMutex);
+ if (!IsValidHandle(aHandle)) {
+ return;
+ }
+
+ RefPtr<DbEntry> removedEntry = mDb[aHandle];
+ mDb[aHandle] = nullptr;
+ MTP_DBG("0x%08x removed", aHandle);
+ // if the entry is not a folder, just return.
+ if (removedEntry->mObjectFormat != MTP_FORMAT_ASSOCIATION) {
+ return;
+ }
+
+ // Find out and remove the children of aHandle.
+ // Since the index for a directory will always be less than the index of any of its children,
+ // we can remove the entire subtree in one pass.
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = aHandle+1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry && IsValidHandle(entry->mParent) && !mDb[entry->mParent]) {
+ mDb[entryIndex] = nullptr;
+ MTP_DBG("0x%08x removed", aHandle);
+ }
+ }
+}
+
+void
+MozMtpDatabase::RemoveEntryAndNotify(MtpObjectHandle aHandle, RefCountedMtpServer* aMtpServer)
+{
+ RemoveEntry(aHandle);
+ aMtpServer->sendObjectRemoved(aHandle);
+}
+
+void
+MozMtpDatabase::UpdateEntryAndNotify(MtpObjectHandle aHandle, DeviceStorageFile* aFile, RefCountedMtpServer* aMtpServer)
+{
+ UpdateEntry(aHandle, aFile);
+ aMtpServer->sendObjectAdded(aHandle);
+}
+
+
+void
+MozMtpDatabase::UpdateEntry(MtpObjectHandle aHandle, DeviceStorageFile* aFile)
+{
+ MutexAutoLock lock(mMutex);
+
+ RefPtr<DbEntry> entry = mDb[aHandle];
+
+ int64_t fileSize = 0;
+ aFile->mFile->GetFileSize(&fileSize);
+ entry->mObjectSize = fileSize;
+
+ PRTime dateModifiedMsecs;
+ // GetLastModifiedTime returns msecs
+ aFile->mFile->GetLastModifiedTime(&dateModifiedMsecs);
+ entry->mDateModified = dateModifiedMsecs / PR_MSEC_PER_SEC;
+ entry->mDateCreated = entry->mDateModified;
+ entry->mDateAdded = entry->mDateModified;
+
+ #if USE_DEBUG
+ char dateStr[20];
+ MTP_DBG("UpdateEntry (0x%08x file %s) modified (%ld) %s",
+ entry->mHandle, entry->mPath.get(),
+ entry->mDateModified,
+ FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+ #endif
+}
+
+
+class MtpWatcherNotifyRunnable final : public Runnable
+{
+public:
+ MtpWatcherNotifyRunnable(nsACString& aStorageName,
+ nsACString& aPath,
+ const char* aEventType)
+ : mStorageName(aStorageName),
+ mPath(aPath),
+ mEventType(aEventType)
+ {}
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ NS_ConvertUTF8toUTF16 storageName(mStorageName);
+ NS_ConvertUTF8toUTF16 path(mPath);
+
+ RefPtr<DeviceStorageFile> dsf(
+ new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_SDCARD),
+ storageName, path));
+ NS_ConvertUTF8toUTF16 eventType(mEventType);
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+
+ MTP_DBG("Sending mtp-watcher-notify %s %s %s",
+ mEventType.get(), mStorageName.get(), mPath.get());
+
+ obs->NotifyObservers(dsf, kMtpWatcherNotify, eventType.get());
+ return NS_OK;
+ }
+
+private:
+ nsCString mStorageName;
+ nsCString mPath;
+ nsCString mEventType;
+};
+
+// MtpWatcherNotify is used to tell DeviceStorage when a file was changed
+// through the MTP server.
+void
+MozMtpDatabase::MtpWatcherNotify(DbEntry* aEntry, const char* aEventType)
+{
+ // This function gets called from the MozMtpServer::mServerThread
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ MTP_DBG("file: %s %s", aEntry->mPath.get(), aEventType);
+
+ // Tell interested parties that a file was created, deleted, or modified.
+
+ RefPtr<StorageEntry> storageEntry;
+ {
+ MutexAutoLock lock(mMutex);
+
+ // FindStorage and the mStorage[] access both need to have the mutex held.
+ StorageArray::index_type storageIndex = FindStorage(aEntry->mStorageID);
+ if (storageIndex == StorageArray::NoIndex) {
+ return;
+ }
+ storageEntry = mStorage[storageIndex];
+ }
+
+ // DeviceStorage wants the storageName and the path relative to the root
+ // of the storage area, so we need to strip off the storagePath
+
+ nsAutoCString relPath(Substring(aEntry->mPath,
+ storageEntry->mStoragePath.Length() + 1));
+
+ RefPtr<MtpWatcherNotifyRunnable> r =
+ new MtpWatcherNotifyRunnable(storageEntry->mStorageName, relPath, aEventType);
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(r);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+// Called to tell the MTP server about new or deleted files,
+void
+MozMtpDatabase::MtpWatcherUpdate(RefCountedMtpServer* aMtpServer,
+ DeviceStorageFile* aFile,
+ const nsACString& aEventType)
+{
+ // Runs on the MtpWatcherUpdate->mIOThread (see MozMtpServer.cpp)
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Figure out which storage the belongs to (if any)
+
+ if (!aFile->mFile) {
+ // No path - don't bother looking.
+ return;
+ }
+ nsString wideFilePath;
+ aFile->mFile->GetPath(wideFilePath);
+ NS_ConvertUTF16toUTF8 filePath(wideFilePath);
+
+ nsCString evtType(aEventType);
+ MTP_LOG("file %s %s", filePath.get(), evtType.get());
+
+ MtpObjectHandle entryHandle = FindEntryByPath(filePath);
+
+ if (aEventType.EqualsLiteral("modified")) {
+ // To update the file information to the newest, we remove the entry for
+ // the existing file, then re-add the entry for the file.
+
+ if (entryHandle != 0) {
+ // Update entry for the file and tell MTP.
+ MTP_LOG("About to update handle 0x%08x file %s", entryHandle, filePath.get());
+ UpdateEntryAndNotify(entryHandle, aFile, aMtpServer);
+ }
+ else {
+ // Create entry for the file and tell MTP.
+ CreateEntryForFileAndNotify(filePath, aFile, aMtpServer);
+ }
+ return;
+ }
+
+ if (aEventType.EqualsLiteral("deleted")) {
+ if (entryHandle == 0) {
+ // The entry has already been removed. We can't tell MTP.
+ return;
+ }
+ MTP_LOG("About to call sendObjectRemoved Handle 0x%08x file %s", entryHandle, filePath.get());
+ RemoveEntryAndNotify(entryHandle, aMtpServer);
+ return;
+ }
+}
+
+nsCString
+MozMtpDatabase::BaseName(const nsCString& path)
+{
+ nsCOMPtr<nsIFile> file;
+ NS_NewNativeLocalFile(path, false, getter_AddRefs(file));
+ if (file) {
+ nsCString leafName;
+ file->GetNativeLeafName(leafName);
+ return leafName;
+ }
+ return path;
+}
+
+static nsCString
+GetPathWithoutFileName(const nsCString& aFullPath)
+{
+ nsCString path;
+
+ int32_t offset = aFullPath.RFindChar('/');
+ if (offset != kNotFound) {
+ // The trailing slash will be as part of 'path'
+ path = StringHead(aFullPath, offset + 1);
+ }
+
+ MTP_LOG("returning '%s'", path.get());
+
+ return path;
+}
+
+void
+MozMtpDatabase::CreateEntryForFileAndNotify(const nsACString& aPath,
+ DeviceStorageFile* aFile,
+ RefCountedMtpServer* aMtpServer)
+{
+ // Find the StorageID that this path corresponds to.
+
+ nsCString remainder;
+ MtpStorageID storageID = FindStorageIDFor(aPath, remainder);
+ if (storageID == 0) {
+ // The path in question isn't for a storage area we're monitoring.
+ nsCString path(aPath);
+ return;
+ }
+
+ bool exists = false;
+ aFile->mFile->Exists(&exists);
+ if (!exists) {
+ // File doesn't exist, no sense telling MTP about it.
+ // This could happen if Device Storage created and deleted a file right
+ // away. Since the notifications wind up being async, the file might
+ // not exist any more.
+ return;
+ }
+
+ // Now walk the remaining directories, finding or creating as required.
+
+ MtpObjectHandle parent = MTP_PARENT_ROOT;
+ bool doFind = true;
+ int32_t offset = aPath.Length() - remainder.Length();
+ int32_t slash;
+
+ do {
+ nsDependentCSubstring component;
+ slash = aPath.FindChar('/', offset);
+ if (slash == kNotFound) {
+ component.Rebind(aPath, 0, aPath.Length());
+ } else {
+ component.Rebind(aPath, 0 , slash);
+ }
+ if (doFind) {
+ MtpObjectHandle entryHandle = FindEntryByPath(component);
+ if (entryHandle != 0) {
+ // We found an entry.
+ parent = entryHandle;
+ offset = slash + 1 ;
+ continue;
+ }
+ }
+
+ // We've got a directory component that doesn't exist. This means that all
+ // further subdirectories won't exist either, so we can skip searching
+ // for them.
+ doFind = false;
+
+ // This directory and the file don't exist, create them
+
+ RefPtr<DbEntry> entry = new DbEntry;
+
+ entry->mStorageID = storageID;
+ entry->mObjectName = Substring(aPath, offset, slash - offset);
+ entry->mParent = parent;
+ entry->mDisplayName = entry->mObjectName;
+ entry->mPath = component;
+
+ if (slash == kNotFound) {
+ // No slash - this is the file component
+ entry->mObjectFormat = MTP_FORMAT_DEFINED;
+
+ int64_t fileSize = 0;
+ aFile->mFile->GetFileSize(&fileSize);
+ entry->mObjectSize = fileSize;
+
+ // Note: Even though PRTime records usec, GetLastModifiedTime returns
+ // msecs.
+ PRTime dateModifiedMsecs;
+ aFile->mFile->GetLastModifiedTime(&dateModifiedMsecs);
+ entry->mDateModified = dateModifiedMsecs / PR_MSEC_PER_SEC;
+ } else {
+ // Found a slash, this makes this a directory component
+ entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
+ entry->mObjectSize = 0;
+ time(&entry->mDateModified);
+ }
+ entry->mDateCreated = entry->mDateModified;
+ entry->mDateAdded = entry->mDateModified;
+
+ AddEntryAndNotify(entry, aMtpServer);
+ MTP_LOG("About to call sendObjectAdded Handle 0x%08x file %s", entry->mHandle, entry->mPath.get());
+
+ parent = entry->mHandle;
+ offset = slash + 1;
+ } while (slash != kNotFound);
+
+ return;
+}
+
+void
+MozMtpDatabase::AddDirectory(MtpStorageID aStorageID,
+ const char* aPath,
+ MtpObjectHandle aParent)
+{
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ ScopedCloseDir dir;
+
+ if (!(dir = PR_OpenDir(aPath))) {
+ MTP_ERR("Unable to open directory '%s'", aPath);
+ return;
+ }
+
+ PRDirEntry* dirEntry;
+ while ((dirEntry = PR_ReadDir(dir, PR_SKIP_BOTH))) {
+ nsPrintfCString filename("%s/%s", aPath, dirEntry->name);
+ PRFileInfo64 fileInfo;
+ if (PR_GetFileInfo64(filename.get(), &fileInfo) != PR_SUCCESS) {
+ MTP_ERR("Unable to retrieve file information for '%s'", filename.get());
+ continue;
+ }
+
+ RefPtr<DbEntry> entry = new DbEntry;
+
+ entry->mStorageID = aStorageID;
+ entry->mParent = aParent;
+ entry->mObjectName = dirEntry->name;
+ entry->mDisplayName = dirEntry->name;
+ entry->mPath = filename;
+
+ // PR_GetFileInfo64 returns timestamps in usecs
+ entry->mDateModified = fileInfo.modifyTime / PR_USEC_PER_SEC;
+ entry->mDateCreated = fileInfo.creationTime / PR_USEC_PER_SEC;
+ time(&entry->mDateAdded);
+
+ if (fileInfo.type == PR_FILE_FILE) {
+ entry->mObjectFormat = MTP_FORMAT_DEFINED;
+ //TODO: Check how 64-bit filesize are dealt with
+ entry->mObjectSize = fileInfo.size;
+ AddEntry(entry);
+ } else if (fileInfo.type == PR_FILE_DIRECTORY) {
+ entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
+ entry->mObjectSize = 0;
+ AddEntry(entry);
+ AddDirectory(aStorageID, filename.get(), entry->mHandle);
+ }
+ }
+}
+
+MozMtpDatabase::StorageArray::index_type
+MozMtpDatabase::FindStorage(MtpStorageID aStorageID)
+{
+ // Currently, this routine is called from MozMtpDatabase::RemoveStorage
+ // and MozMtpDatabase::MtpWatcherNotify, which both hold mMutex.
+
+ StorageArray::size_type numStorages = mStorage.Length();
+ StorageArray::index_type storageIndex;
+
+ for (storageIndex = 0; storageIndex < numStorages; storageIndex++) {
+ RefPtr<StorageEntry> storage = mStorage[storageIndex];
+ if (storage->mStorageID == aStorageID) {
+ return storageIndex;
+ }
+ }
+ return StorageArray::NoIndex;
+}
+
+// Find the storage ID for the storage area that contains aPath.
+MtpStorageID
+MozMtpDatabase::FindStorageIDFor(const nsACString& aPath, nsCSubstring& aRemainder)
+{
+ MutexAutoLock lock(mMutex);
+
+ aRemainder.Truncate();
+
+ StorageArray::size_type numStorages = mStorage.Length();
+ StorageArray::index_type storageIndex;
+
+ for (storageIndex = 0; storageIndex < numStorages; storageIndex++) {
+ RefPtr<StorageEntry> storage = mStorage[storageIndex];
+ if (StringHead(aPath, storage->mStoragePath.Length()).Equals(storage->mStoragePath)) {
+ if (aPath.Length() == storage->mStoragePath.Length()) {
+ return storage->mStorageID;
+ }
+ if (aPath[storage->mStoragePath.Length()] == '/') {
+ aRemainder = Substring(aPath, storage->mStoragePath.Length() + 1);
+ return storage->mStorageID;
+ }
+ }
+ }
+ return 0;
+}
+
+void
+MozMtpDatabase::AddStorage(MtpStorageID aStorageID,
+ const char* aPath,
+ const char* aName)
+{
+ // This is called on the IOThread from MozMtpStorage::StorageAvailable
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ MTP_DBG("StorageID: 0x%08x aPath: '%s' aName: '%s'",
+ aStorageID, aPath, aName);
+
+ PRFileInfo fileInfo;
+ if (PR_GetFileInfo(aPath, &fileInfo) != PR_SUCCESS) {
+ MTP_ERR("'%s' doesn't exist", aPath);
+ return;
+ }
+ if (fileInfo.type != PR_FILE_DIRECTORY) {
+ MTP_ERR("'%s' isn't a directory", aPath);
+ return;
+ }
+
+ RefPtr<StorageEntry> storageEntry = new StorageEntry;
+
+ storageEntry->mStorageID = aStorageID;
+ storageEntry->mStoragePath = aPath;
+ storageEntry->mStorageName = aName;
+ {
+ MutexAutoLock lock(mMutex);
+ mStorage.AppendElement(storageEntry);
+ }
+
+ AddDirectory(aStorageID, aPath, MTP_PARENT_ROOT);
+ {
+ MutexAutoLock lock(mMutex);
+ MTP_LOG("added %d items from tree '%s'", mDb.Length(), aPath);
+ }
+}
+
+void
+MozMtpDatabase::RemoveStorage(MtpStorageID aStorageID)
+{
+ MutexAutoLock lock(mMutex);
+
+ // This is called on the IOThread from MozMtpStorage::StorageAvailable
+ MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry && entry->mStorageID == aStorageID) {
+ mDb[entryIndex] = nullptr;
+ }
+ }
+ StorageArray::index_type storageIndex = FindStorage(aStorageID);
+ if (storageIndex != StorageArray::NoIndex) {
+ mStorage.RemoveElementAt(storageIndex);
+ }
+}
+
+// called from SendObjectInfo to reserve a database entry for the incoming file
+//virtual
+MtpObjectHandle
+MozMtpDatabase::beginSendObject(const char* aPath,
+ MtpObjectFormat aFormat,
+ MtpObjectHandle aParent,
+ MtpStorageID aStorageID,
+ uint64_t aSize,
+ time_t aModified)
+{
+ // If MtpServer::doSendObjectInfo receives a request with a parent of
+ // MTP_PARENT_ROOT, then it fills in aPath with the fully qualified path
+ // and then passes in a parent of zero.
+
+ if (aParent == 0) {
+ // Undo what doSendObjectInfo did
+ aParent = MTP_PARENT_ROOT;
+ }
+
+ RefPtr<DbEntry> entry = new DbEntry;
+
+ entry->mStorageID = aStorageID;
+ entry->mParent = aParent;
+ entry->mPath = aPath;
+ entry->mObjectName = BaseName(entry->mPath);
+ entry->mDisplayName = entry->mObjectName;
+ entry->mObjectFormat = aFormat;
+ entry->mObjectSize = aSize;
+
+ if (aModified != 0) {
+ // Currently, due to the way that parseDateTime is coded in
+ // frameworks/av/media/mtp/MtpUtils.cpp, aModified winds up being the number
+ // of seconds from the epoch in local time, rather than UTC time. So we
+ // need to convert it back to being relative to UTC since that's what linux
+ // expects time_t to contain.
+ //
+ // In more concrete testable terms, if the host parses 2015-08-02 02:22:00
+ // as a local time in the Pacific timezone, aModified will come to us as
+ // 1438482120.
+ //
+ // What we want is what mktime would pass us with the same date. Using python
+ // (because its simple) with the current timezone set to be America/Vancouver:
+ //
+ // >>> import time
+ // >>> time.mktime((2015, 8, 2, 2, 22, 0, 0, 0, -1))
+ // 1438507320.0
+ // >>> time.localtime(1438507320)
+ // time.struct_time(tm_year=2015, tm_mon=8, tm_mday=2, tm_hour=2, tm_min=22, tm_sec=0, tm_wday=6, tm_yday=214, tm_isdst=1)
+ //
+ // Currently, when a file has a modification time of 2015-08-22 02:22:00 PDT
+ // then aModified will come in as 1438482120 which corresponds to
+ // 2015-08-22 02:22:00 UTC
+
+ struct tm tm;
+ if (gmtime_r(&aModified, &tm) != NULL) {
+ // GMT always comes back with tm_isdst = 0, so we set it to -1 in order
+ // to have mktime figure out dst based on the date.
+ tm.tm_isdst = -1;
+ aModified = mktime(&tm);
+ if (aModified == (time_t)-1) {
+ aModified = 0;
+ }
+ } else {
+ aModified = 0;
+ }
+ }
+ if (aModified == 0) {
+ // The ubuntu host doesn't pass in the modified/created times in the
+ // SENDOBJECT packet, so aModified winds up being zero. About the best
+ // we can do with that is to use the current time.
+ time(&aModified);
+ }
+
+ // And just an FYI for anybody else looking at timestamps. Under OSX you
+ // need to use the Android File Transfer program to copy files into the
+ // phone. That utility passes in both date modified and date created
+ // timestamps, but they're both equal to the time that the file was copied
+ // and not the times that are associated with the files.
+
+ // Now we have aModified in a traditional time_t format, which is the number
+ // of seconds from the UTC epoch.
+
+ entry->mDateModified = aModified;
+ entry->mDateCreated = entry->mDateModified;
+ entry->mDateAdded = entry->mDateModified;
+
+ AddEntry(entry);
+
+ #if USE_DEBUG
+ char dateStr[20];
+ MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path: '%s' aModified %ld %s",
+ entry->mHandle, aParent, aPath, aModified,
+ FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+ #endif
+
+ mBeginSendObjectCalled = true;
+ return entry->mHandle;
+}
+
+// called to report success or failure of the SendObject file transfer
+// success should signal a notification of the new object's creation,
+// failure should remove the database entry created in beginSendObject
+
+//virtual
+void
+MozMtpDatabase::endSendObject(const char* aPath,
+ MtpObjectHandle aHandle,
+ MtpObjectFormat aFormat,
+ bool aSucceeded)
+{
+ MTP_LOG("Handle: 0x%08x Path: '%s'", aHandle, aPath);
+
+ if (aSucceeded) {
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (entry) {
+ // The android MTP server only copies the data in, it doesn't set the
+ // modified timestamp, so we do that here.
+
+ struct utimbuf new_times;
+ struct stat sb;
+
+ char dateStr[20];
+ MTP_LOG("Path: '%s' setting modified time to (%ld) %s",
+ entry->mPath.get(), entry->mDateModified,
+ FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+
+ stat(entry->mPath.get(), &sb);
+ new_times.actime = sb.st_atime; // Preserve atime
+ new_times.modtime = entry->mDateModified;
+ utime(entry->mPath.get(), &new_times);
+
+ MtpWatcherNotify(entry, "modified");
+ }
+ } else {
+ RemoveEntry(aHandle);
+ }
+ mBeginSendObjectCalled = false;
+}
+
+//virtual
+MtpObjectHandleList*
+MozMtpDatabase::getObjectList(MtpStorageID aStorageID,
+ MtpObjectFormat aFormat,
+ MtpObjectHandle aParent)
+{
+ MTP_LOG("StorageID: 0x%08x Format: 0x%04x Parent: 0x%08x",
+ aStorageID, aFormat, aParent);
+
+ // aStorageID == 0xFFFFFFFF for all storage
+ // aFormat == 0 for all formats
+ // aParent == 0xFFFFFFFF for objects with no parents
+ // aParent == 0 for all objects
+
+ //TODO: Optimize
+
+ UniquePtr<MtpObjectHandleList> list(new MtpObjectHandleList());
+
+ MutexAutoLock lock(mMutex);
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry &&
+ (aStorageID == 0xFFFFFFFF || entry->mStorageID == aStorageID) &&
+ (aFormat == 0 || entry->mObjectFormat == aFormat) &&
+ (aParent == 0 || entry->mParent == aParent)) {
+ list->push(entry->mHandle);
+ }
+ }
+ MTP_LOG(" returning %d items", list->size());
+ return list.release();
+}
+
+//virtual
+int
+MozMtpDatabase::getNumObjects(MtpStorageID aStorageID,
+ MtpObjectFormat aFormat,
+ MtpObjectHandle aParent)
+{
+ MTP_LOG("");
+
+ // aStorageID == 0xFFFFFFFF for all storage
+ // aFormat == 0 for all formats
+ // aParent == 0xFFFFFFFF for objects with no parents
+ // aParent == 0 for all objects
+
+ int count = 0;
+
+ MutexAutoLock lock(mMutex);
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIndex;
+ for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+ RefPtr<DbEntry> entry = mDb[entryIndex];
+ if (entry &&
+ (aStorageID == 0xFFFFFFFF || entry->mStorageID == aStorageID) &&
+ (aFormat == 0 || entry->mObjectFormat == aFormat) &&
+ (aParent == 0 || entry->mParent == aParent)) {
+ count++;
+ }
+ }
+
+ MTP_LOG(" returning %d items", count);
+ return count;
+}
+
+//virtual
+MtpObjectFormatList*
+MozMtpDatabase::getSupportedPlaybackFormats()
+{
+ static const uint16_t init_data[] = {MTP_FORMAT_UNDEFINED, MTP_FORMAT_ASSOCIATION,
+ MTP_FORMAT_TEXT, MTP_FORMAT_HTML, MTP_FORMAT_WAV,
+ MTP_FORMAT_MP3, MTP_FORMAT_MPEG, MTP_FORMAT_EXIF_JPEG,
+ MTP_FORMAT_TIFF_EP, MTP_FORMAT_BMP, MTP_FORMAT_GIF,
+ MTP_FORMAT_PNG, MTP_FORMAT_TIFF, MTP_FORMAT_WMA,
+ MTP_FORMAT_OGG, MTP_FORMAT_AAC, MTP_FORMAT_MP4_CONTAINER,
+ MTP_FORMAT_MP2, MTP_FORMAT_3GP_CONTAINER, MTP_FORMAT_FLAC};
+
+ MtpObjectFormatList *list = new MtpObjectFormatList();
+ list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
+
+ MTP_LOG("returning Supported Playback Formats");
+ return list;
+}
+
+//virtual
+MtpObjectFormatList*
+MozMtpDatabase::getSupportedCaptureFormats()
+{
+ static const uint16_t init_data[] = {MTP_FORMAT_ASSOCIATION, MTP_FORMAT_PNG};
+
+ MtpObjectFormatList *list = new MtpObjectFormatList();
+ list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
+ MTP_LOG("returning MTP_FORMAT_ASSOCIATION, MTP_FORMAT_PNG");
+ return list;
+}
+
+static const MtpObjectProperty sSupportedObjectProperties[] =
+{
+ MTP_PROPERTY_STORAGE_ID,
+ MTP_PROPERTY_OBJECT_FORMAT,
+ MTP_PROPERTY_PROTECTION_STATUS, // UINT16 - always 0
+ MTP_PROPERTY_OBJECT_SIZE,
+ MTP_PROPERTY_OBJECT_FILE_NAME, // just the filename - no directory
+ MTP_PROPERTY_NAME,
+ MTP_PROPERTY_DATE_CREATED,
+ MTP_PROPERTY_DATE_MODIFIED,
+ MTP_PROPERTY_PARENT_OBJECT,
+ MTP_PROPERTY_PERSISTENT_UID,
+ MTP_PROPERTY_DATE_ADDED,
+};
+
+//virtual
+MtpObjectPropertyList*
+MozMtpDatabase::getSupportedObjectProperties(MtpObjectFormat aFormat)
+{
+ MTP_LOG("");
+ MtpObjectPropertyList *list = new MtpObjectPropertyList();
+ list->appendArray(sSupportedObjectProperties,
+ MOZ_ARRAY_LENGTH(sSupportedObjectProperties));
+ return list;
+}
+
+//virtual
+MtpDevicePropertyList*
+MozMtpDatabase::getSupportedDeviceProperties()
+{
+ MTP_LOG("");
+ static const uint16_t init_data[] = { MTP_DEVICE_PROPERTY_UNDEFINED };
+
+ MtpDevicePropertyList *list = new MtpDevicePropertyList();
+ list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
+ return list;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::getObjectPropertyValue(MtpObjectHandle aHandle,
+ MtpObjectProperty aProperty,
+ MtpDataPacket& aPacket)
+{
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (!entry) {
+ MTP_ERR("Invalid Handle: 0x%08x", aHandle);
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+
+ MTP_LOG("Handle: 0x%08x '%s' Property: %s 0x%08x",
+ aHandle, entry->mDisplayName.get(), ObjectPropertyAsStr(aProperty), aProperty);
+
+ switch (aProperty)
+ {
+ case MTP_PROPERTY_STORAGE_ID: aPacket.putUInt32(entry->mStorageID); break;
+ case MTP_PROPERTY_PARENT_OBJECT: aPacket.putUInt32(entry->mParent); break;
+ case MTP_PROPERTY_OBJECT_FORMAT: aPacket.putUInt16(entry->mObjectFormat); break;
+ case MTP_PROPERTY_OBJECT_SIZE: aPacket.putUInt64(entry->mObjectSize); break;
+ case MTP_PROPERTY_DISPLAY_NAME: aPacket.putString(entry->mDisplayName.get()); break;
+ case MTP_PROPERTY_PERSISTENT_UID:
+ // the same as aPacket.putUInt128
+ aPacket.putUInt64(entry->mHandle);
+ aPacket.putUInt64(entry->mStorageID);
+ break;
+ case MTP_PROPERTY_NAME: aPacket.putString(entry->mDisplayName.get()); break;
+
+ default:
+ MTP_LOG("Invalid Property: 0x%08x", aProperty);
+ return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE;
+ }
+
+ return MTP_RESPONSE_OK;
+}
+
+static int
+GetTypeOfObjectProp(MtpObjectProperty aProperty)
+{
+ struct PropertyTableEntry {
+ MtpObjectProperty property;
+ int type;
+ };
+
+ static const PropertyTableEntry kObjectPropertyTable[] = {
+ {MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32 },
+ {MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16 },
+ {MTP_PROPERTY_PROTECTION_STATUS, MTP_TYPE_UINT16 },
+ {MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64 },
+ {MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR },
+ {MTP_PROPERTY_DATE_CREATED, MTP_TYPE_STR },
+ {MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR },
+ {MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 },
+ {MTP_PROPERTY_DISPLAY_NAME, MTP_TYPE_STR },
+ {MTP_PROPERTY_NAME, MTP_TYPE_STR },
+ {MTP_PROPERTY_PERSISTENT_UID, MTP_TYPE_UINT128 },
+ {MTP_PROPERTY_DATE_ADDED, MTP_TYPE_STR },
+ };
+
+ int count = sizeof(kObjectPropertyTable) / sizeof(kObjectPropertyTable[0]);
+ const PropertyTableEntry* entryProp = kObjectPropertyTable;
+ int type = 0;
+
+ for (int i = 0; i < count; ++i, ++entryProp) {
+ if (entryProp->property == aProperty) {
+ type = entryProp->type;
+ break;
+ }
+ }
+
+ return type;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::setObjectPropertyValue(MtpObjectHandle aHandle,
+ MtpObjectProperty aProperty,
+ MtpDataPacket& aPacket)
+{
+ MTP_LOG("Handle: 0x%08x Property: 0x%08x", aHandle, aProperty);
+
+ // Only support file name change
+ if (aProperty != MTP_PROPERTY_OBJECT_FILE_NAME) {
+ MTP_ERR("property 0x%x not supported", aProperty);
+ return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
+ }
+
+ if (GetTypeOfObjectProp(aProperty) != MTP_TYPE_STR) {
+ MTP_ERR("property type 0x%x not supported", GetTypeOfObjectProp(aProperty));
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (!entry) {
+ MTP_ERR("Invalid Handle: 0x%08x", aHandle);
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+
+ MtpStringBuffer buf;
+ aPacket.getString(buf);
+
+ nsDependentCString newFileName(buf);
+ nsCString newFileFullPath(GetPathWithoutFileName(entry->mPath) + newFileName);
+
+ if (PR_Rename(entry->mPath.get(), newFileFullPath.get()) != PR_SUCCESS) {
+ MTP_ERR("Failed to rename '%s' to '%s'",
+ entry->mPath.get(), newFileFullPath.get());
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+
+ MTP_LOG("renamed '%s' to '%s'", entry->mPath.get(), newFileFullPath.get());
+
+ entry->mPath = newFileFullPath;
+ entry->mObjectName = BaseName(entry->mPath);
+ entry->mDisplayName = entry->mObjectName;
+
+ return MTP_RESPONSE_OK;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::getDevicePropertyValue(MtpDeviceProperty aProperty,
+ MtpDataPacket& aPacket)
+{
+ MTP_LOG("(GENERAL ERROR)");
+ return MTP_RESPONSE_GENERAL_ERROR;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::setDevicePropertyValue(MtpDeviceProperty aProperty,
+ MtpDataPacket& aPacket)
+{
+ MTP_LOG("(NOT SUPPORTED)");
+ return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::resetDeviceProperty(MtpDeviceProperty aProperty)
+{
+ MTP_LOG("(NOT SUPPORTED)");
+ return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
+}
+
+void
+MozMtpDatabase::QueryEntries(MozMtpDatabase::MatchType aMatchType,
+ uint32_t aMatchField1,
+ uint32_t aMatchField2,
+ UnprotectedDbArray &result)
+{
+ MutexAutoLock lock(mMutex);
+
+ ProtectedDbArray::size_type numEntries = mDb.Length();
+ ProtectedDbArray::index_type entryIdx;
+ RefPtr<DbEntry> entry;
+
+ result.Clear();
+
+ switch (aMatchType) {
+
+ case MatchAll:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ if (mDb[entryIdx]) {
+ result.AppendElement(mDb[entryIdx]);
+ }
+ }
+ break;
+
+ case MatchHandle:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ entry = mDb[entryIdx];
+ if (entry && entry->mHandle == aMatchField1) {
+ result.AppendElement(entry);
+ // Handles are unique - return the one that we found.
+ return;
+ }
+ }
+ break;
+
+ case MatchParent:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ entry = mDb[entryIdx];
+ if (entry && entry->mParent == aMatchField1) {
+ result.AppendElement(entry);
+ }
+ }
+ break;
+
+ case MatchFormat:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ entry = mDb[entryIdx];
+ if (entry && entry->mObjectFormat == aMatchField1) {
+ result.AppendElement(entry);
+ }
+ }
+ break;
+
+ case MatchHandleFormat:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ entry = mDb[entryIdx];
+ if (entry && entry->mHandle == aMatchField1) {
+ if (entry->mObjectFormat == aMatchField2) {
+ result.AppendElement(entry);
+ }
+ // Only 1 entry can match my aHandle. So we can return early.
+ return;
+ }
+ }
+ break;
+
+ case MatchParentFormat:
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ entry = mDb[entryIdx];
+ if (entry && entry->mParent == aMatchField1 && entry->mObjectFormat == aMatchField2) {
+ result.AppendElement(entry);
+ }
+ }
+ break;
+
+ default:
+ MOZ_ASSERT(!"Invalid MatchType");
+ }
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::getObjectPropertyList(MtpObjectHandle aHandle,
+ uint32_t aFormat,
+ uint32_t aProperty,
+ int aGroupCode,
+ int aDepth,
+ MtpDataPacket& aPacket)
+{
+ MTP_LOG("Handle: 0x%08x Format: 0x%08x aProperty: 0x%08x aGroupCode: %d aDepth %d",
+ aHandle, aFormat, aProperty, aGroupCode, aDepth);
+
+ if (aDepth > 1) {
+ return MTP_RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED;
+ }
+ if (aGroupCode != 0) {
+ return MTP_RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED;
+ }
+
+ MatchType matchType = MatchAll;
+ uint32_t matchField1 = 0;
+ uint32_t matchField2 = 0;
+
+ // aHandle == 0 implies all objects at the root level
+ // further specificed by aFormat and/or aDepth
+
+ if (aFormat == 0) {
+ if (aHandle == 0xffffffff) {
+ // select all objects
+ matchType = MatchAll;
+ } else {
+ if (aDepth == 1) {
+ // select objects whose Parent matches aHandle
+ matchType = MatchParent;
+ matchField1 = aHandle;
+ } else {
+ // select object whose handle matches aHandle
+ matchType = MatchHandle;
+ matchField1 = aHandle;
+ }
+ }
+ } else {
+ if (aHandle == 0xffffffff) {
+ // select all objects whose format matches aFormat
+ matchType = MatchFormat;
+ matchField1 = aFormat;
+ } else {
+ if (aDepth == 1) {
+ // select objects whose Parent is aHandle and format matches aFormat
+ matchType = MatchParentFormat;
+ matchField1 = aHandle;
+ matchField2 = aFormat;
+ } else {
+ // select objects whose handle is aHandle and format matches aFormat
+ matchType = MatchHandleFormat;
+ matchField1 = aHandle;
+ matchField2 = aFormat;
+ }
+ }
+ }
+
+ UnprotectedDbArray result;
+ QueryEntries(matchType, matchField1, matchField2, result);
+
+ const MtpObjectProperty *objectPropertyList;
+ size_t numObjectProperties = 0;
+ MtpObjectProperty objectProperty;
+
+ if (aProperty == 0xffffffff) {
+ // return all supported properties
+ numObjectProperties = MOZ_ARRAY_LENGTH(sSupportedObjectProperties);
+ objectPropertyList = sSupportedObjectProperties;
+ } else {
+ // return property indicated by aProperty
+ numObjectProperties = 1;
+ objectProperty = aProperty;
+ objectPropertyList = &objectProperty;
+ }
+
+ UnprotectedDbArray::size_type numEntries = result.Length();
+ UnprotectedDbArray::index_type entryIdx;
+
+ char dateStr[20];
+
+ aPacket.putUInt32(numObjectProperties * numEntries);
+ for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
+ RefPtr<DbEntry> entry = result[entryIdx];
+
+ for (size_t propertyIdx = 0; propertyIdx < numObjectProperties; propertyIdx++) {
+ aPacket.putUInt32(entry->mHandle);
+ MtpObjectProperty prop = objectPropertyList[propertyIdx];
+ aPacket.putUInt16(prop);
+ switch (prop) {
+
+ case MTP_PROPERTY_STORAGE_ID:
+ aPacket.putUInt16(MTP_TYPE_UINT32);
+ aPacket.putUInt32(entry->mStorageID);
+ break;
+
+ case MTP_PROPERTY_PARENT_OBJECT:
+ aPacket.putUInt16(MTP_TYPE_UINT32);
+ aPacket.putUInt32(entry->mParent);
+ break;
+
+ case MTP_PROPERTY_PERSISTENT_UID:
+ aPacket.putUInt16(MTP_TYPE_UINT128);
+ // the same as aPacket.putUInt128
+ aPacket.putUInt64(entry->mHandle);
+ aPacket.putUInt64(entry->mStorageID);
+ break;
+
+ case MTP_PROPERTY_OBJECT_FORMAT:
+ aPacket.putUInt16(MTP_TYPE_UINT16);
+ aPacket.putUInt16(entry->mObjectFormat);
+ break;
+
+ case MTP_PROPERTY_OBJECT_SIZE:
+ aPacket.putUInt16(MTP_TYPE_UINT64);
+ aPacket.putUInt64(entry->mObjectSize);
+ break;
+
+ case MTP_PROPERTY_OBJECT_FILE_NAME:
+ case MTP_PROPERTY_NAME:
+ aPacket.putUInt16(MTP_TYPE_STR);
+ aPacket.putString(entry->mObjectName.get());
+ break;
+
+ case MTP_PROPERTY_PROTECTION_STATUS:
+ aPacket.putUInt16(MTP_TYPE_UINT16);
+ aPacket.putUInt16(0); // 0 = No Protection
+ break;
+
+ case MTP_PROPERTY_DATE_CREATED: {
+ aPacket.putUInt16(MTP_TYPE_STR);
+ aPacket.putString(FormatDate(entry->mDateCreated, dateStr, sizeof(dateStr)));
+ MTP_LOG("mDateCreated: (%ld) %s", entry->mDateCreated, dateStr);
+ break;
+ }
+
+ case MTP_PROPERTY_DATE_MODIFIED: {
+ aPacket.putUInt16(MTP_TYPE_STR);
+ aPacket.putString(FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
+ MTP_LOG("mDateModified: (%ld) %s", entry->mDateModified, dateStr);
+ break;
+ }
+
+ case MTP_PROPERTY_DATE_ADDED: {
+ aPacket.putUInt16(MTP_TYPE_STR);
+ aPacket.putString(FormatDate(entry->mDateAdded, dateStr, sizeof(dateStr)));
+ MTP_LOG("mDateAdded: (%ld) %s", entry->mDateAdded, dateStr);
+ break;
+ }
+
+ default:
+ MTP_ERR("Unrecognized property code: %u", prop);
+ return MTP_RESPONSE_GENERAL_ERROR;
+ }
+ }
+ }
+ return MTP_RESPONSE_OK;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::getObjectInfo(MtpObjectHandle aHandle,
+ MtpObjectInfo& aInfo)
+{
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (!entry) {
+ MTP_ERR("Handle 0x%08x is invalid", aHandle);
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+
+ MTP_LOG("Handle: 0x%08x Display:'%s' Object:'%s'", aHandle, entry->mDisplayName.get(), entry->mObjectName.get());
+
+ aInfo.mHandle = aHandle;
+ aInfo.mStorageID = entry->mStorageID;
+ aInfo.mFormat = entry->mObjectFormat;
+ aInfo.mProtectionStatus = 0x0;
+
+ if (entry->mObjectSize > 0xFFFFFFFFuLL) {
+ aInfo.mCompressedSize = 0xFFFFFFFFuLL;
+ } else {
+ aInfo.mCompressedSize = entry->mObjectSize;
+ }
+
+ aInfo.mThumbFormat = MTP_FORMAT_UNDEFINED;
+ aInfo.mThumbCompressedSize = 0;
+ aInfo.mThumbPixWidth = 0;
+ aInfo.mThumbPixHeight = 0;
+ aInfo.mImagePixWidth = 0;
+ aInfo.mImagePixHeight = 0;
+ aInfo.mImagePixDepth = 0;
+ aInfo.mParent = entry->mParent;
+ aInfo.mAssociationType = 0;
+ aInfo.mAssociationDesc = 0;
+ aInfo.mSequenceNumber = 0;
+ aInfo.mName = ::strdup(entry->mObjectName.get());
+ aInfo.mDateCreated = entry->mDateCreated;
+ aInfo.mDateModified = entry->mDateModified;
+
+ MTP_LOG("aInfo.mDateCreated = %ld entry->mDateCreated = %ld",
+ aInfo.mDateCreated, entry->mDateCreated);
+ MTP_LOG("aInfo.mDateModified = %ld entry->mDateModified = %ld",
+ aInfo.mDateModified, entry->mDateModified);
+
+ aInfo.mKeywords = ::strdup("fxos,touch");
+
+ return MTP_RESPONSE_OK;
+}
+
+//virtual
+void*
+MozMtpDatabase::getThumbnail(MtpObjectHandle aHandle, size_t& aOutThumbSize)
+{
+ MTP_LOG("Handle: 0x%08x (returning nullptr)", aHandle);
+
+ aOutThumbSize = 0;
+
+ return nullptr;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::getObjectFilePath(MtpObjectHandle aHandle,
+ MtpString& aOutFilePath,
+ int64_t& aOutFileLength,
+ MtpObjectFormat& aOutFormat)
+{
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (!entry) {
+ MTP_ERR("Handle 0x%08x is invalid", aHandle);
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+
+ MTP_LOG("Handle: 0x%08x FilePath: '%s'", aHandle, entry->mPath.get());
+
+ aOutFilePath = entry->mPath.get();
+ aOutFileLength = entry->mObjectSize;
+ aOutFormat = entry->mObjectFormat;
+
+ return MTP_RESPONSE_OK;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::deleteFile(MtpObjectHandle aHandle)
+{
+ RefPtr<DbEntry> entry = GetEntry(aHandle);
+ if (!entry) {
+ MTP_ERR("Invalid Handle: 0x%08x", aHandle);
+ return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+ }
+
+ MTP_LOG("Handle: 0x%08x '%s'", aHandle, entry->mPath.get());
+
+ // File deletion will happen in lower level implementation.
+ // The only thing we need to do is removing the entry from the db.
+ RemoveEntry(aHandle);
+
+ // Tell Device Storage that the file is gone.
+ MtpWatcherNotify(entry, "deleted");
+
+ return MTP_RESPONSE_OK;
+}
+
+#if 0
+//virtual
+MtpResponseCode
+MozMtpDatabase::moveFile(MtpObjectHandle aHandle, MtpObjectHandle aNewParent)
+{
+ MTP_LOG("Handle: 0x%08x NewParent: 0x%08x", aHandle, aNewParent);
+
+ // change parent
+
+ return MTP_RESPONSE_OK
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::copyFile(MtpObjectHandle aHandle, MtpObjectHandle aNewParent)
+{
+ MTP_LOG("Handle: 0x%08x NewParent: 0x%08x", aHandle, aNewParent);
+
+ // duplicate DbEntry
+ // change parent
+
+ return MTP_RESPONSE_OK
+}
+#endif
+
+//virtual
+MtpObjectHandleList*
+MozMtpDatabase::getObjectReferences(MtpObjectHandle aHandle)
+{
+ MTP_LOG("Handle: 0x%08x (returning nullptr)", aHandle);
+ return nullptr;
+}
+
+//virtual
+MtpResponseCode
+MozMtpDatabase::setObjectReferences(MtpObjectHandle aHandle,
+ MtpObjectHandleList* aReferences)
+{
+ MTP_LOG("Handle: 0x%08x (NOT SUPPORTED)", aHandle);
+ return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
+}
+
+//virtual
+MtpProperty*
+MozMtpDatabase::getObjectPropertyDesc(MtpObjectProperty aProperty,
+ MtpObjectFormat aFormat)
+{
+ MTP_LOG("Property: %s 0x%08x", ObjectPropertyAsStr(aProperty), aProperty);
+
+ MtpProperty* result = nullptr;
+ switch (aProperty)
+ {
+ case MTP_PROPERTY_PROTECTION_STATUS:
+ result = new MtpProperty(aProperty, MTP_TYPE_UINT16);
+ break;
+ case MTP_PROPERTY_OBJECT_FORMAT:
+ result = new MtpProperty(aProperty, MTP_TYPE_UINT16, false, aFormat);
+ break;
+ case MTP_PROPERTY_STORAGE_ID:
+ case MTP_PROPERTY_PARENT_OBJECT:
+ case MTP_PROPERTY_WIDTH:
+ case MTP_PROPERTY_HEIGHT:
+ case MTP_PROPERTY_IMAGE_BIT_DEPTH:
+ result = new MtpProperty(aProperty, MTP_TYPE_UINT32);
+ break;
+ case MTP_PROPERTY_OBJECT_SIZE:
+ result = new MtpProperty(aProperty, MTP_TYPE_UINT64);
+ break;
+ case MTP_PROPERTY_DISPLAY_NAME:
+ case MTP_PROPERTY_NAME:
+ result = new MtpProperty(aProperty, MTP_TYPE_STR);
+ break;
+ case MTP_PROPERTY_OBJECT_FILE_NAME:
+ result = new MtpProperty(aProperty, MTP_TYPE_STR, true);
+ break;
+ case MTP_PROPERTY_DATE_CREATED:
+ case MTP_PROPERTY_DATE_MODIFIED:
+ case MTP_PROPERTY_DATE_ADDED:
+ result = new MtpProperty(aProperty, MTP_TYPE_STR);
+ result->setFormDateTime();
+ break;
+ case MTP_PROPERTY_PERSISTENT_UID:
+ result = new MtpProperty(aProperty, MTP_TYPE_UINT128);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+}
+
+//virtual
+MtpProperty*
+MozMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty aProperty)
+{
+ MTP_LOG("(returning MTP_DEVICE_PROPERTY_UNDEFINED)");
+ return new MtpProperty(MTP_DEVICE_PROPERTY_UNDEFINED, MTP_TYPE_UNDEFINED);
+}
+
+//virtual
+void
+MozMtpDatabase::sessionStarted()
+{
+ MTP_LOG("");
+}
+
+//virtual
+void
+MozMtpDatabase::sessionEnded()
+{
+ MTP_LOG("");
+}
+
+END_MTP_NAMESPACE