gecko-dev/dom/system/gonk/MozMtpDatabase.cpp
Dave Hylands 59280e28fd Bug 1188796 - Have MTP record modified timestamps when they're passed. r=alchen
Of the 3 popular host platforms, only Windows properly passes in the
modification timestamp of the file.

Under linux, it passes in an empty string, which then gets passed
to beginSendObject as zero. This patch now treats that as the current
date/time.

Under OSX, the Android FIle Transfer program passes in the date modified
and date created, but sets both of these fields to be the current
date/time and not the times from the files being copied.


Hl: Enter commit message.  Lines beginning with 'HG:' are removed.
2015-09-14 17:38:19 -07:00

1544 lines
46 KiB
C++

/* -*- 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 "nsAutoPtr.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
#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 FileWatcherNotifyRunnable final : public nsRunnable
{
public:
FileWatcherNotifyRunnable(nsACString& aStorageName,
nsACString& aPath,
const char* aEventType)
: mStorageName(aStorageName),
mPath(aPath),
mEventType(aEventType)
{}
NS_IMETHOD Run()
{
MOZ_ASSERT(NS_IsMainThread());
NS_ConvertUTF8toUTF16 storageName(mStorageName);
NS_ConvertUTF8toUTF16 path(mPath);
nsRefPtr<DeviceStorageFile> dsf(
new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_SDCARD),
storageName, path));
NS_ConvertUTF8toUTF16 eventType(mEventType);
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
MTP_DBG("Sending file-watcher-notify %s %s %s",
mEventType.get(), mStorageName.get(), mPath.get());
obs->NotifyObservers(dsf, "file-watcher-notify", eventType.get());
return NS_OK;
}
private:
nsCString mStorageName;
nsCString mPath;
nsCString mEventType;
};
// FileWatcherNotify is used to tell DeviceStorage when a file was changed
// through the MTP server.
void
MozMtpDatabase::FileWatcherNotify(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));
nsRefPtr<FileWatcherNotifyRunnable> r =
new FileWatcherNotifyRunnable(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::FileWatcherUpdate(RefCountedMtpServer* aMtpServer,
DeviceStorageFile* aFile,
const nsACString& aEventType)
{
// Runs on the FileWatcherUpdate->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::FileWatcherNotify, 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;
}
nsRefPtr<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);
FileWatcherNotify(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
ScopedDeletePtr<MtpObjectHandleList> list;
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.forget();
}
//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.
FileWatcherNotify(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