mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-13 23:17:57 +00:00
59280e28fd
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.
1544 lines
46 KiB
C++
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
|