1 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "MozMtpDatabase.h"
8 #include "MozMtpServer.h"
9
10 #include "base/message_loop.h"
11 #include "DeviceStorage.h"
12 #include "mozilla/ArrayUtils.h"
13 #include "mozilla/AutoRestore.h"
14 #include "mozilla/Scoped.h"
15 #include "mozilla/Services.h"
16 #include "nsIFile.h"
17 #include "nsIObserverService.h"
18 #include "nsPrintfCString.h"
19 #include "nsString.h"
20 #include "prio.h"
21
22 #include <dirent.h>
23 #include <libgen.h>
24 #include <utime.h>
25 #include <sys/stat.h>
26
27 using namespace android;
28 using namespace mozilla;
29
30 namespace mozilla {
31 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCloseDir, PRDir, PR_CloseDir)
32 }
33
34 BEGIN_MTP_NAMESPACE
35
36 static const char* kMtpWatcherNotify = "mtp-watcher-notify";
37
38 #if 0
39 // Some debug code for figuring out deadlocks, if you happen to run into
40 // that scenario
41
42 class DebugMutexAutoLock: public MutexAutoLock
43 {
44 public:
45 DebugMutexAutoLock(mozilla::Mutex& aMutex)
46 : MutexAutoLock(aMutex)
47 {
48 MTP_LOG("Mutex acquired");
49 }
50
51 ~DebugMutexAutoLock()
52 {
53 MTP_LOG("Releasing mutex");
54 }
55 };
56 #define MutexAutoLock MTP_LOG("About to enter mutex"); DebugMutexAutoLock
57
58 #endif
59
60 static const char *
ObjectPropertyAsStr(MtpObjectProperty aProperty)61 ObjectPropertyAsStr(MtpObjectProperty aProperty)
62 {
63 switch (aProperty) {
64 case MTP_PROPERTY_STORAGE_ID: return "MTP_PROPERTY_STORAGE_ID";
65 case MTP_PROPERTY_OBJECT_FORMAT: return "MTP_PROPERTY_OBJECT_FORMAT";
66 case MTP_PROPERTY_PROTECTION_STATUS: return "MTP_PROPERTY_PROTECTION_STATUS";
67 case MTP_PROPERTY_OBJECT_SIZE: return "MTP_PROPERTY_OBJECT_SIZE";
68 case MTP_PROPERTY_OBJECT_FILE_NAME: return "MTP_PROPERTY_OBJECT_FILE_NAME";
69 case MTP_PROPERTY_DATE_CREATED: return "MTP_PROPERTY_DATE_CREATED";
70 case MTP_PROPERTY_DATE_MODIFIED: return "MTP_PROPERTY_DATE_MODIFIED";
71 case MTP_PROPERTY_PARENT_OBJECT: return "MTP_PROPERTY_PARENT_OBJECT";
72 case MTP_PROPERTY_PERSISTENT_UID: return "MTP_PROPERTY_PERSISTENT_UID";
73 case MTP_PROPERTY_NAME: return "MTP_PROPERTY_NAME";
74 case MTP_PROPERTY_DATE_ADDED: return "MTP_PROPERTY_DATE_ADDED";
75 case MTP_PROPERTY_WIDTH: return "MTP_PROPERTY_WIDTH";
76 case MTP_PROPERTY_HEIGHT: return "MTP_PROPERTY_HEIGHT";
77 case MTP_PROPERTY_IMAGE_BIT_DEPTH: return "MTP_PROPERTY_IMAGE_BIT_DEPTH";
78 case MTP_PROPERTY_DISPLAY_NAME: return "MTP_PROPERTY_DISPLAY_NAME";
79 }
80 return "MTP_PROPERTY_???";
81 }
82
83 static char*
FormatDate(time_t aTime,char * aDateStr,size_t aDateStrSize)84 FormatDate(time_t aTime, char *aDateStr, size_t aDateStrSize)
85 {
86 struct tm tm;
87 localtime_r(&aTime, &tm);
88 MTP_LOG("(%ld) tm_zone = %s off = %ld", aTime, tm.tm_zone, tm.tm_gmtoff);
89 strftime(aDateStr, aDateStrSize, "%Y%m%dT%H%M%S", &tm);
90 return aDateStr;
91 }
92
MozMtpDatabase()93 MozMtpDatabase::MozMtpDatabase()
94 : mMutex("MozMtpDatabase::mMutex"),
95 mDb(mMutex),
96 mStorage(mMutex),
97 mBeginSendObjectCalled(false)
98 {
99 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
100
101 // We use the index into the array as the handle. Since zero isn't a valid
102 // index, we stick a dummy entry there.
103
104 RefPtr<DbEntry> dummy;
105
106 MutexAutoLock lock(mMutex);
107 mDb.AppendElement(dummy);
108 }
109
110 //virtual
~MozMtpDatabase()111 MozMtpDatabase::~MozMtpDatabase()
112 {
113 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
114 }
115
116 void
AddEntry(DbEntry * entry)117 MozMtpDatabase::AddEntry(DbEntry *entry)
118 {
119 MutexAutoLock lock(mMutex);
120
121 entry->mHandle = GetNextHandle();
122 MOZ_ASSERT(mDb.Length() == entry->mHandle);
123 mDb.AppendElement(entry);
124
125 MTP_DBG("Handle: 0x%08x Parent: 0x%08x Path:'%s'",
126 entry->mHandle, entry->mParent, entry->mPath.get());
127 }
128
129 void
AddEntryAndNotify(DbEntry * entry,RefCountedMtpServer * aMtpServer)130 MozMtpDatabase::AddEntryAndNotify(DbEntry* entry, RefCountedMtpServer* aMtpServer)
131 {
132 AddEntry(entry);
133 aMtpServer->sendObjectAdded(entry->mHandle);
134 }
135
136 void
DumpEntries(const char * aLabel)137 MozMtpDatabase::DumpEntries(const char* aLabel)
138 {
139 MutexAutoLock lock(mMutex);
140
141 ProtectedDbArray::size_type numEntries = mDb.Length();
142 MTP_LOG("%s: numEntries = %d", aLabel, numEntries);
143 ProtectedDbArray::index_type entryIndex;
144 for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
145 RefPtr<DbEntry> entry = mDb[entryIndex];
146 if (entry) {
147 MTP_LOG("%s: mDb[%d]: mHandle: 0x%08x mParent: 0x%08x StorageID: 0x%08x path: '%s'",
148 aLabel, entryIndex, entry->mHandle, entry->mParent, entry->mStorageID, entry->mPath.get());
149 } else {
150 MTP_LOG("%s: mDb[%2d]: entry is NULL", aLabel, entryIndex);
151 }
152 }
153 }
154
155 MtpObjectHandle
FindEntryByPath(const nsACString & aPath)156 MozMtpDatabase::FindEntryByPath(const nsACString& aPath)
157 {
158 MutexAutoLock lock(mMutex);
159
160 ProtectedDbArray::size_type numEntries = mDb.Length();
161 ProtectedDbArray::index_type entryIndex;
162 for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
163 RefPtr<DbEntry> entry = mDb[entryIndex];
164 if (entry && entry->mPath.Equals(aPath)) {
165 return entryIndex;
166 }
167 }
168 return 0;
169 }
170
171 already_AddRefed<MozMtpDatabase::DbEntry>
GetEntry(MtpObjectHandle aHandle)172 MozMtpDatabase::GetEntry(MtpObjectHandle aHandle)
173 {
174 MutexAutoLock lock(mMutex);
175
176 RefPtr<DbEntry> entry;
177
178 if (aHandle > 0 && aHandle < mDb.Length()) {
179 entry = mDb[aHandle];
180 }
181 return entry.forget();
182 }
183
184 void
RemoveEntry(MtpObjectHandle aHandle)185 MozMtpDatabase::RemoveEntry(MtpObjectHandle aHandle)
186 {
187 MutexAutoLock lock(mMutex);
188 if (!IsValidHandle(aHandle)) {
189 return;
190 }
191
192 RefPtr<DbEntry> removedEntry = mDb[aHandle];
193 mDb[aHandle] = nullptr;
194 MTP_DBG("0x%08x removed", aHandle);
195 // if the entry is not a folder, just return.
196 if (removedEntry->mObjectFormat != MTP_FORMAT_ASSOCIATION) {
197 return;
198 }
199
200 // Find out and remove the children of aHandle.
201 // Since the index for a directory will always be less than the index of any of its children,
202 // we can remove the entire subtree in one pass.
203 ProtectedDbArray::size_type numEntries = mDb.Length();
204 ProtectedDbArray::index_type entryIndex;
205 for (entryIndex = aHandle+1; entryIndex < numEntries; entryIndex++) {
206 RefPtr<DbEntry> entry = mDb[entryIndex];
207 if (entry && IsValidHandle(entry->mParent) && !mDb[entry->mParent]) {
208 mDb[entryIndex] = nullptr;
209 MTP_DBG("0x%08x removed", aHandle);
210 }
211 }
212 }
213
214 void
RemoveEntryAndNotify(MtpObjectHandle aHandle,RefCountedMtpServer * aMtpServer)215 MozMtpDatabase::RemoveEntryAndNotify(MtpObjectHandle aHandle, RefCountedMtpServer* aMtpServer)
216 {
217 RemoveEntry(aHandle);
218 aMtpServer->sendObjectRemoved(aHandle);
219 }
220
221 void
UpdateEntryAndNotify(MtpObjectHandle aHandle,DeviceStorageFile * aFile,RefCountedMtpServer * aMtpServer)222 MozMtpDatabase::UpdateEntryAndNotify(MtpObjectHandle aHandle, DeviceStorageFile* aFile, RefCountedMtpServer* aMtpServer)
223 {
224 UpdateEntry(aHandle, aFile);
225 aMtpServer->sendObjectAdded(aHandle);
226 }
227
228
229 void
UpdateEntry(MtpObjectHandle aHandle,DeviceStorageFile * aFile)230 MozMtpDatabase::UpdateEntry(MtpObjectHandle aHandle, DeviceStorageFile* aFile)
231 {
232 MutexAutoLock lock(mMutex);
233
234 RefPtr<DbEntry> entry = mDb[aHandle];
235
236 int64_t fileSize = 0;
237 aFile->mFile->GetFileSize(&fileSize);
238 entry->mObjectSize = fileSize;
239
240 PRTime dateModifiedMsecs;
241 // GetLastModifiedTime returns msecs
242 aFile->mFile->GetLastModifiedTime(&dateModifiedMsecs);
243 entry->mDateModified = dateModifiedMsecs / PR_MSEC_PER_SEC;
244 entry->mDateCreated = entry->mDateModified;
245 entry->mDateAdded = entry->mDateModified;
246
247 #if USE_DEBUG
248 char dateStr[20];
249 MTP_DBG("UpdateEntry (0x%08x file %s) modified (%ld) %s",
250 entry->mHandle, entry->mPath.get(),
251 entry->mDateModified,
252 FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
253 #endif
254 }
255
256
257 class MtpWatcherNotifyRunnable final : public Runnable
258 {
259 public:
MtpWatcherNotifyRunnable(nsACString & aStorageName,nsACString & aPath,const char * aEventType)260 MtpWatcherNotifyRunnable(nsACString& aStorageName,
261 nsACString& aPath,
262 const char* aEventType)
263 : mStorageName(aStorageName),
264 mPath(aPath),
265 mEventType(aEventType)
266 {}
267
Run()268 NS_IMETHOD Run() override
269 {
270 MOZ_ASSERT(NS_IsMainThread());
271
272 NS_ConvertUTF8toUTF16 storageName(mStorageName);
273 NS_ConvertUTF8toUTF16 path(mPath);
274
275 RefPtr<DeviceStorageFile> dsf(
276 new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_SDCARD),
277 storageName, path));
278 NS_ConvertUTF8toUTF16 eventType(mEventType);
279 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
280
281 MTP_DBG("Sending mtp-watcher-notify %s %s %s",
282 mEventType.get(), mStorageName.get(), mPath.get());
283
284 obs->NotifyObservers(dsf, kMtpWatcherNotify, eventType.get());
285 return NS_OK;
286 }
287
288 private:
289 nsCString mStorageName;
290 nsCString mPath;
291 nsCString mEventType;
292 };
293
294 // MtpWatcherNotify is used to tell DeviceStorage when a file was changed
295 // through the MTP server.
296 void
MtpWatcherNotify(DbEntry * aEntry,const char * aEventType)297 MozMtpDatabase::MtpWatcherNotify(DbEntry* aEntry, const char* aEventType)
298 {
299 // This function gets called from the MozMtpServer::mServerThread
300 MOZ_ASSERT(!NS_IsMainThread());
301
302 MTP_DBG("file: %s %s", aEntry->mPath.get(), aEventType);
303
304 // Tell interested parties that a file was created, deleted, or modified.
305
306 RefPtr<StorageEntry> storageEntry;
307 {
308 MutexAutoLock lock(mMutex);
309
310 // FindStorage and the mStorage[] access both need to have the mutex held.
311 StorageArray::index_type storageIndex = FindStorage(aEntry->mStorageID);
312 if (storageIndex == StorageArray::NoIndex) {
313 return;
314 }
315 storageEntry = mStorage[storageIndex];
316 }
317
318 // DeviceStorage wants the storageName and the path relative to the root
319 // of the storage area, so we need to strip off the storagePath
320
321 nsAutoCString relPath(Substring(aEntry->mPath,
322 storageEntry->mStoragePath.Length() + 1));
323
324 RefPtr<MtpWatcherNotifyRunnable> r =
325 new MtpWatcherNotifyRunnable(storageEntry->mStorageName, relPath, aEventType);
326 DebugOnly<nsresult> rv = NS_DispatchToMainThread(r);
327 MOZ_ASSERT(NS_SUCCEEDED(rv));
328 }
329
330 // Called to tell the MTP server about new or deleted files,
331 void
MtpWatcherUpdate(RefCountedMtpServer * aMtpServer,DeviceStorageFile * aFile,const nsACString & aEventType)332 MozMtpDatabase::MtpWatcherUpdate(RefCountedMtpServer* aMtpServer,
333 DeviceStorageFile* aFile,
334 const nsACString& aEventType)
335 {
336 // Runs on the MtpWatcherUpdate->mIOThread (see MozMtpServer.cpp)
337 MOZ_ASSERT(!NS_IsMainThread());
338
339 // Figure out which storage the belongs to (if any)
340
341 if (!aFile->mFile) {
342 // No path - don't bother looking.
343 return;
344 }
345 nsString wideFilePath;
346 aFile->mFile->GetPath(wideFilePath);
347 NS_ConvertUTF16toUTF8 filePath(wideFilePath);
348
349 nsCString evtType(aEventType);
350 MTP_LOG("file %s %s", filePath.get(), evtType.get());
351
352 MtpObjectHandle entryHandle = FindEntryByPath(filePath);
353
354 if (aEventType.EqualsLiteral("modified")) {
355 // To update the file information to the newest, we remove the entry for
356 // the existing file, then re-add the entry for the file.
357
358 if (entryHandle != 0) {
359 // Update entry for the file and tell MTP.
360 MTP_LOG("About to update handle 0x%08x file %s", entryHandle, filePath.get());
361 UpdateEntryAndNotify(entryHandle, aFile, aMtpServer);
362 }
363 else {
364 // Create entry for the file and tell MTP.
365 CreateEntryForFileAndNotify(filePath, aFile, aMtpServer);
366 }
367 return;
368 }
369
370 if (aEventType.EqualsLiteral("deleted")) {
371 if (entryHandle == 0) {
372 // The entry has already been removed. We can't tell MTP.
373 return;
374 }
375 MTP_LOG("About to call sendObjectRemoved Handle 0x%08x file %s", entryHandle, filePath.get());
376 RemoveEntryAndNotify(entryHandle, aMtpServer);
377 return;
378 }
379 }
380
381 nsCString
BaseName(const nsCString & path)382 MozMtpDatabase::BaseName(const nsCString& path)
383 {
384 nsCOMPtr<nsIFile> file;
385 NS_NewNativeLocalFile(path, false, getter_AddRefs(file));
386 if (file) {
387 nsCString leafName;
388 file->GetNativeLeafName(leafName);
389 return leafName;
390 }
391 return path;
392 }
393
394 static nsCString
GetPathWithoutFileName(const nsCString & aFullPath)395 GetPathWithoutFileName(const nsCString& aFullPath)
396 {
397 nsCString path;
398
399 int32_t offset = aFullPath.RFindChar('/');
400 if (offset != kNotFound) {
401 // The trailing slash will be as part of 'path'
402 path = StringHead(aFullPath, offset + 1);
403 }
404
405 MTP_LOG("returning '%s'", path.get());
406
407 return path;
408 }
409
410 void
CreateEntryForFileAndNotify(const nsACString & aPath,DeviceStorageFile * aFile,RefCountedMtpServer * aMtpServer)411 MozMtpDatabase::CreateEntryForFileAndNotify(const nsACString& aPath,
412 DeviceStorageFile* aFile,
413 RefCountedMtpServer* aMtpServer)
414 {
415 // Find the StorageID that this path corresponds to.
416
417 nsCString remainder;
418 MtpStorageID storageID = FindStorageIDFor(aPath, remainder);
419 if (storageID == 0) {
420 // The path in question isn't for a storage area we're monitoring.
421 nsCString path(aPath);
422 return;
423 }
424
425 bool exists = false;
426 aFile->mFile->Exists(&exists);
427 if (!exists) {
428 // File doesn't exist, no sense telling MTP about it.
429 // This could happen if Device Storage created and deleted a file right
430 // away. Since the notifications wind up being async, the file might
431 // not exist any more.
432 return;
433 }
434
435 // Now walk the remaining directories, finding or creating as required.
436
437 MtpObjectHandle parent = MTP_PARENT_ROOT;
438 bool doFind = true;
439 int32_t offset = aPath.Length() - remainder.Length();
440 int32_t slash;
441
442 do {
443 nsDependentCSubstring component;
444 slash = aPath.FindChar('/', offset);
445 if (slash == kNotFound) {
446 component.Rebind(aPath, 0, aPath.Length());
447 } else {
448 component.Rebind(aPath, 0 , slash);
449 }
450 if (doFind) {
451 MtpObjectHandle entryHandle = FindEntryByPath(component);
452 if (entryHandle != 0) {
453 // We found an entry.
454 parent = entryHandle;
455 offset = slash + 1 ;
456 continue;
457 }
458 }
459
460 // We've got a directory component that doesn't exist. This means that all
461 // further subdirectories won't exist either, so we can skip searching
462 // for them.
463 doFind = false;
464
465 // This directory and the file don't exist, create them
466
467 RefPtr<DbEntry> entry = new DbEntry;
468
469 entry->mStorageID = storageID;
470 entry->mObjectName = Substring(aPath, offset, slash - offset);
471 entry->mParent = parent;
472 entry->mDisplayName = entry->mObjectName;
473 entry->mPath = component;
474
475 if (slash == kNotFound) {
476 // No slash - this is the file component
477 entry->mObjectFormat = MTP_FORMAT_DEFINED;
478
479 int64_t fileSize = 0;
480 aFile->mFile->GetFileSize(&fileSize);
481 entry->mObjectSize = fileSize;
482
483 // Note: Even though PRTime records usec, GetLastModifiedTime returns
484 // msecs.
485 PRTime dateModifiedMsecs;
486 aFile->mFile->GetLastModifiedTime(&dateModifiedMsecs);
487 entry->mDateModified = dateModifiedMsecs / PR_MSEC_PER_SEC;
488 } else {
489 // Found a slash, this makes this a directory component
490 entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
491 entry->mObjectSize = 0;
492 time(&entry->mDateModified);
493 }
494 entry->mDateCreated = entry->mDateModified;
495 entry->mDateAdded = entry->mDateModified;
496
497 AddEntryAndNotify(entry, aMtpServer);
498 MTP_LOG("About to call sendObjectAdded Handle 0x%08x file %s", entry->mHandle, entry->mPath.get());
499
500 parent = entry->mHandle;
501 offset = slash + 1;
502 } while (slash != kNotFound);
503
504 return;
505 }
506
507 void
AddDirectory(MtpStorageID aStorageID,const char * aPath,MtpObjectHandle aParent)508 MozMtpDatabase::AddDirectory(MtpStorageID aStorageID,
509 const char* aPath,
510 MtpObjectHandle aParent)
511 {
512 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
513
514 ScopedCloseDir dir;
515
516 if (!(dir = PR_OpenDir(aPath))) {
517 MTP_ERR("Unable to open directory '%s'", aPath);
518 return;
519 }
520
521 PRDirEntry* dirEntry;
522 while ((dirEntry = PR_ReadDir(dir, PR_SKIP_BOTH))) {
523 nsPrintfCString filename("%s/%s", aPath, dirEntry->name);
524 PRFileInfo64 fileInfo;
525 if (PR_GetFileInfo64(filename.get(), &fileInfo) != PR_SUCCESS) {
526 MTP_ERR("Unable to retrieve file information for '%s'", filename.get());
527 continue;
528 }
529
530 RefPtr<DbEntry> entry = new DbEntry;
531
532 entry->mStorageID = aStorageID;
533 entry->mParent = aParent;
534 entry->mObjectName = dirEntry->name;
535 entry->mDisplayName = dirEntry->name;
536 entry->mPath = filename;
537
538 // PR_GetFileInfo64 returns timestamps in usecs
539 entry->mDateModified = fileInfo.modifyTime / PR_USEC_PER_SEC;
540 entry->mDateCreated = fileInfo.creationTime / PR_USEC_PER_SEC;
541 time(&entry->mDateAdded);
542
543 if (fileInfo.type == PR_FILE_FILE) {
544 entry->mObjectFormat = MTP_FORMAT_DEFINED;
545 //TODO: Check how 64-bit filesize are dealt with
546 entry->mObjectSize = fileInfo.size;
547 AddEntry(entry);
548 } else if (fileInfo.type == PR_FILE_DIRECTORY) {
549 entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
550 entry->mObjectSize = 0;
551 AddEntry(entry);
552 AddDirectory(aStorageID, filename.get(), entry->mHandle);
553 }
554 }
555 }
556
557 MozMtpDatabase::StorageArray::index_type
FindStorage(MtpStorageID aStorageID)558 MozMtpDatabase::FindStorage(MtpStorageID aStorageID)
559 {
560 // Currently, this routine is called from MozMtpDatabase::RemoveStorage
561 // and MozMtpDatabase::MtpWatcherNotify, which both hold mMutex.
562
563 StorageArray::size_type numStorages = mStorage.Length();
564 StorageArray::index_type storageIndex;
565
566 for (storageIndex = 0; storageIndex < numStorages; storageIndex++) {
567 RefPtr<StorageEntry> storage = mStorage[storageIndex];
568 if (storage->mStorageID == aStorageID) {
569 return storageIndex;
570 }
571 }
572 return StorageArray::NoIndex;
573 }
574
575 // Find the storage ID for the storage area that contains aPath.
576 MtpStorageID
FindStorageIDFor(const nsACString & aPath,nsCSubstring & aRemainder)577 MozMtpDatabase::FindStorageIDFor(const nsACString& aPath, nsCSubstring& aRemainder)
578 {
579 MutexAutoLock lock(mMutex);
580
581 aRemainder.Truncate();
582
583 StorageArray::size_type numStorages = mStorage.Length();
584 StorageArray::index_type storageIndex;
585
586 for (storageIndex = 0; storageIndex < numStorages; storageIndex++) {
587 RefPtr<StorageEntry> storage = mStorage[storageIndex];
588 if (StringHead(aPath, storage->mStoragePath.Length()).Equals(storage->mStoragePath)) {
589 if (aPath.Length() == storage->mStoragePath.Length()) {
590 return storage->mStorageID;
591 }
592 if (aPath[storage->mStoragePath.Length()] == '/') {
593 aRemainder = Substring(aPath, storage->mStoragePath.Length() + 1);
594 return storage->mStorageID;
595 }
596 }
597 }
598 return 0;
599 }
600
601 void
AddStorage(MtpStorageID aStorageID,const char * aPath,const char * aName)602 MozMtpDatabase::AddStorage(MtpStorageID aStorageID,
603 const char* aPath,
604 const char* aName)
605 {
606 // This is called on the IOThread from MozMtpStorage::StorageAvailable
607 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
608
609 MTP_DBG("StorageID: 0x%08x aPath: '%s' aName: '%s'",
610 aStorageID, aPath, aName);
611
612 PRFileInfo fileInfo;
613 if (PR_GetFileInfo(aPath, &fileInfo) != PR_SUCCESS) {
614 MTP_ERR("'%s' doesn't exist", aPath);
615 return;
616 }
617 if (fileInfo.type != PR_FILE_DIRECTORY) {
618 MTP_ERR("'%s' isn't a directory", aPath);
619 return;
620 }
621
622 RefPtr<StorageEntry> storageEntry = new StorageEntry;
623
624 storageEntry->mStorageID = aStorageID;
625 storageEntry->mStoragePath = aPath;
626 storageEntry->mStorageName = aName;
627 {
628 MutexAutoLock lock(mMutex);
629 mStorage.AppendElement(storageEntry);
630 }
631
632 AddDirectory(aStorageID, aPath, MTP_PARENT_ROOT);
633 {
634 MutexAutoLock lock(mMutex);
635 MTP_LOG("added %d items from tree '%s'", mDb.Length(), aPath);
636 }
637 }
638
639 void
RemoveStorage(MtpStorageID aStorageID)640 MozMtpDatabase::RemoveStorage(MtpStorageID aStorageID)
641 {
642 MutexAutoLock lock(mMutex);
643
644 // This is called on the IOThread from MozMtpStorage::StorageAvailable
645 MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
646
647 ProtectedDbArray::size_type numEntries = mDb.Length();
648 ProtectedDbArray::index_type entryIndex;
649 for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
650 RefPtr<DbEntry> entry = mDb[entryIndex];
651 if (entry && entry->mStorageID == aStorageID) {
652 mDb[entryIndex] = nullptr;
653 }
654 }
655 StorageArray::index_type storageIndex = FindStorage(aStorageID);
656 if (storageIndex != StorageArray::NoIndex) {
657 mStorage.RemoveElementAt(storageIndex);
658 }
659 }
660
661 // called from SendObjectInfo to reserve a database entry for the incoming file
662 //virtual
663 MtpObjectHandle
beginSendObject(const char * aPath,MtpObjectFormat aFormat,MtpObjectHandle aParent,MtpStorageID aStorageID,uint64_t aSize,time_t aModified)664 MozMtpDatabase::beginSendObject(const char* aPath,
665 MtpObjectFormat aFormat,
666 MtpObjectHandle aParent,
667 MtpStorageID aStorageID,
668 uint64_t aSize,
669 time_t aModified)
670 {
671 // If MtpServer::doSendObjectInfo receives a request with a parent of
672 // MTP_PARENT_ROOT, then it fills in aPath with the fully qualified path
673 // and then passes in a parent of zero.
674
675 if (aParent == 0) {
676 // Undo what doSendObjectInfo did
677 aParent = MTP_PARENT_ROOT;
678 }
679
680 RefPtr<DbEntry> entry = new DbEntry;
681
682 entry->mStorageID = aStorageID;
683 entry->mParent = aParent;
684 entry->mPath = aPath;
685 entry->mObjectName = BaseName(entry->mPath);
686 entry->mDisplayName = entry->mObjectName;
687 entry->mObjectFormat = aFormat;
688 entry->mObjectSize = aSize;
689
690 if (aModified != 0) {
691 // Currently, due to the way that parseDateTime is coded in
692 // frameworks/av/media/mtp/MtpUtils.cpp, aModified winds up being the number
693 // of seconds from the epoch in local time, rather than UTC time. So we
694 // need to convert it back to being relative to UTC since that's what linux
695 // expects time_t to contain.
696 //
697 // In more concrete testable terms, if the host parses 2015-08-02 02:22:00
698 // as a local time in the Pacific timezone, aModified will come to us as
699 // 1438482120.
700 //
701 // What we want is what mktime would pass us with the same date. Using python
702 // (because its simple) with the current timezone set to be America/Vancouver:
703 //
704 // >>> import time
705 // >>> time.mktime((2015, 8, 2, 2, 22, 0, 0, 0, -1))
706 // 1438507320.0
707 // >>> time.localtime(1438507320)
708 // 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)
709 //
710 // Currently, when a file has a modification time of 2015-08-22 02:22:00 PDT
711 // then aModified will come in as 1438482120 which corresponds to
712 // 2015-08-22 02:22:00 UTC
713
714 struct tm tm;
715 if (gmtime_r(&aModified, &tm) != NULL) {
716 // GMT always comes back with tm_isdst = 0, so we set it to -1 in order
717 // to have mktime figure out dst based on the date.
718 tm.tm_isdst = -1;
719 aModified = mktime(&tm);
720 if (aModified == (time_t)-1) {
721 aModified = 0;
722 }
723 } else {
724 aModified = 0;
725 }
726 }
727 if (aModified == 0) {
728 // The ubuntu host doesn't pass in the modified/created times in the
729 // SENDOBJECT packet, so aModified winds up being zero. About the best
730 // we can do with that is to use the current time.
731 time(&aModified);
732 }
733
734 // And just an FYI for anybody else looking at timestamps. Under OSX you
735 // need to use the Android File Transfer program to copy files into the
736 // phone. That utility passes in both date modified and date created
737 // timestamps, but they're both equal to the time that the file was copied
738 // and not the times that are associated with the files.
739
740 // Now we have aModified in a traditional time_t format, which is the number
741 // of seconds from the UTC epoch.
742
743 entry->mDateModified = aModified;
744 entry->mDateCreated = entry->mDateModified;
745 entry->mDateAdded = entry->mDateModified;
746
747 AddEntry(entry);
748
749 #if USE_DEBUG
750 char dateStr[20];
751 MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path: '%s' aModified %ld %s",
752 entry->mHandle, aParent, aPath, aModified,
753 FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
754 #endif
755
756 mBeginSendObjectCalled = true;
757 return entry->mHandle;
758 }
759
760 // called to report success or failure of the SendObject file transfer
761 // success should signal a notification of the new object's creation,
762 // failure should remove the database entry created in beginSendObject
763
764 //virtual
765 void
endSendObject(const char * aPath,MtpObjectHandle aHandle,MtpObjectFormat aFormat,bool aSucceeded)766 MozMtpDatabase::endSendObject(const char* aPath,
767 MtpObjectHandle aHandle,
768 MtpObjectFormat aFormat,
769 bool aSucceeded)
770 {
771 MTP_LOG("Handle: 0x%08x Path: '%s'", aHandle, aPath);
772
773 if (aSucceeded) {
774 RefPtr<DbEntry> entry = GetEntry(aHandle);
775 if (entry) {
776 // The android MTP server only copies the data in, it doesn't set the
777 // modified timestamp, so we do that here.
778
779 struct utimbuf new_times;
780 struct stat sb;
781
782 char dateStr[20];
783 MTP_LOG("Path: '%s' setting modified time to (%ld) %s",
784 entry->mPath.get(), entry->mDateModified,
785 FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
786
787 stat(entry->mPath.get(), &sb);
788 new_times.actime = sb.st_atime; // Preserve atime
789 new_times.modtime = entry->mDateModified;
790 utime(entry->mPath.get(), &new_times);
791
792 MtpWatcherNotify(entry, "modified");
793 }
794 } else {
795 RemoveEntry(aHandle);
796 }
797 mBeginSendObjectCalled = false;
798 }
799
800 //virtual
801 MtpObjectHandleList*
getObjectList(MtpStorageID aStorageID,MtpObjectFormat aFormat,MtpObjectHandle aParent)802 MozMtpDatabase::getObjectList(MtpStorageID aStorageID,
803 MtpObjectFormat aFormat,
804 MtpObjectHandle aParent)
805 {
806 MTP_LOG("StorageID: 0x%08x Format: 0x%04x Parent: 0x%08x",
807 aStorageID, aFormat, aParent);
808
809 // aStorageID == 0xFFFFFFFF for all storage
810 // aFormat == 0 for all formats
811 // aParent == 0xFFFFFFFF for objects with no parents
812 // aParent == 0 for all objects
813
814 //TODO: Optimize
815
816 UniquePtr<MtpObjectHandleList> list(new MtpObjectHandleList());
817
818 MutexAutoLock lock(mMutex);
819
820 ProtectedDbArray::size_type numEntries = mDb.Length();
821 ProtectedDbArray::index_type entryIndex;
822 for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
823 RefPtr<DbEntry> entry = mDb[entryIndex];
824 if (entry &&
825 (aStorageID == 0xFFFFFFFF || entry->mStorageID == aStorageID) &&
826 (aFormat == 0 || entry->mObjectFormat == aFormat) &&
827 (aParent == 0 || entry->mParent == aParent)) {
828 list->push(entry->mHandle);
829 }
830 }
831 MTP_LOG(" returning %d items", list->size());
832 return list.release();
833 }
834
835 //virtual
836 int
getNumObjects(MtpStorageID aStorageID,MtpObjectFormat aFormat,MtpObjectHandle aParent)837 MozMtpDatabase::getNumObjects(MtpStorageID aStorageID,
838 MtpObjectFormat aFormat,
839 MtpObjectHandle aParent)
840 {
841 MTP_LOG("");
842
843 // aStorageID == 0xFFFFFFFF for all storage
844 // aFormat == 0 for all formats
845 // aParent == 0xFFFFFFFF for objects with no parents
846 // aParent == 0 for all objects
847
848 int count = 0;
849
850 MutexAutoLock lock(mMutex);
851
852 ProtectedDbArray::size_type numEntries = mDb.Length();
853 ProtectedDbArray::index_type entryIndex;
854 for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
855 RefPtr<DbEntry> entry = mDb[entryIndex];
856 if (entry &&
857 (aStorageID == 0xFFFFFFFF || entry->mStorageID == aStorageID) &&
858 (aFormat == 0 || entry->mObjectFormat == aFormat) &&
859 (aParent == 0 || entry->mParent == aParent)) {
860 count++;
861 }
862 }
863
864 MTP_LOG(" returning %d items", count);
865 return count;
866 }
867
868 //virtual
869 MtpObjectFormatList*
getSupportedPlaybackFormats()870 MozMtpDatabase::getSupportedPlaybackFormats()
871 {
872 static const uint16_t init_data[] = {MTP_FORMAT_UNDEFINED, MTP_FORMAT_ASSOCIATION,
873 MTP_FORMAT_TEXT, MTP_FORMAT_HTML, MTP_FORMAT_WAV,
874 MTP_FORMAT_MP3, MTP_FORMAT_MPEG, MTP_FORMAT_EXIF_JPEG,
875 MTP_FORMAT_TIFF_EP, MTP_FORMAT_BMP, MTP_FORMAT_GIF,
876 MTP_FORMAT_PNG, MTP_FORMAT_TIFF, MTP_FORMAT_WMA,
877 MTP_FORMAT_OGG, MTP_FORMAT_AAC, MTP_FORMAT_MP4_CONTAINER,
878 MTP_FORMAT_MP2, MTP_FORMAT_3GP_CONTAINER, MTP_FORMAT_FLAC};
879
880 MtpObjectFormatList *list = new MtpObjectFormatList();
881 list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
882
883 MTP_LOG("returning Supported Playback Formats");
884 return list;
885 }
886
887 //virtual
888 MtpObjectFormatList*
getSupportedCaptureFormats()889 MozMtpDatabase::getSupportedCaptureFormats()
890 {
891 static const uint16_t init_data[] = {MTP_FORMAT_ASSOCIATION, MTP_FORMAT_PNG};
892
893 MtpObjectFormatList *list = new MtpObjectFormatList();
894 list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
895 MTP_LOG("returning MTP_FORMAT_ASSOCIATION, MTP_FORMAT_PNG");
896 return list;
897 }
898
899 static const MtpObjectProperty sSupportedObjectProperties[] =
900 {
901 MTP_PROPERTY_STORAGE_ID,
902 MTP_PROPERTY_OBJECT_FORMAT,
903 MTP_PROPERTY_PROTECTION_STATUS, // UINT16 - always 0
904 MTP_PROPERTY_OBJECT_SIZE,
905 MTP_PROPERTY_OBJECT_FILE_NAME, // just the filename - no directory
906 MTP_PROPERTY_NAME,
907 MTP_PROPERTY_DATE_CREATED,
908 MTP_PROPERTY_DATE_MODIFIED,
909 MTP_PROPERTY_PARENT_OBJECT,
910 MTP_PROPERTY_PERSISTENT_UID,
911 MTP_PROPERTY_DATE_ADDED,
912 };
913
914 //virtual
915 MtpObjectPropertyList*
getSupportedObjectProperties(MtpObjectFormat aFormat)916 MozMtpDatabase::getSupportedObjectProperties(MtpObjectFormat aFormat)
917 {
918 MTP_LOG("");
919 MtpObjectPropertyList *list = new MtpObjectPropertyList();
920 list->appendArray(sSupportedObjectProperties,
921 MOZ_ARRAY_LENGTH(sSupportedObjectProperties));
922 return list;
923 }
924
925 //virtual
926 MtpDevicePropertyList*
getSupportedDeviceProperties()927 MozMtpDatabase::getSupportedDeviceProperties()
928 {
929 MTP_LOG("");
930 static const uint16_t init_data[] = { MTP_DEVICE_PROPERTY_UNDEFINED };
931
932 MtpDevicePropertyList *list = new MtpDevicePropertyList();
933 list->appendArray(init_data, MOZ_ARRAY_LENGTH(init_data));
934 return list;
935 }
936
937 //virtual
938 MtpResponseCode
getObjectPropertyValue(MtpObjectHandle aHandle,MtpObjectProperty aProperty,MtpDataPacket & aPacket)939 MozMtpDatabase::getObjectPropertyValue(MtpObjectHandle aHandle,
940 MtpObjectProperty aProperty,
941 MtpDataPacket& aPacket)
942 {
943 RefPtr<DbEntry> entry = GetEntry(aHandle);
944 if (!entry) {
945 MTP_ERR("Invalid Handle: 0x%08x", aHandle);
946 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
947 }
948
949 MTP_LOG("Handle: 0x%08x '%s' Property: %s 0x%08x",
950 aHandle, entry->mDisplayName.get(), ObjectPropertyAsStr(aProperty), aProperty);
951
952 switch (aProperty)
953 {
954 case MTP_PROPERTY_STORAGE_ID: aPacket.putUInt32(entry->mStorageID); break;
955 case MTP_PROPERTY_PARENT_OBJECT: aPacket.putUInt32(entry->mParent); break;
956 case MTP_PROPERTY_OBJECT_FORMAT: aPacket.putUInt16(entry->mObjectFormat); break;
957 case MTP_PROPERTY_OBJECT_SIZE: aPacket.putUInt64(entry->mObjectSize); break;
958 case MTP_PROPERTY_DISPLAY_NAME: aPacket.putString(entry->mDisplayName.get()); break;
959 case MTP_PROPERTY_PERSISTENT_UID:
960 // the same as aPacket.putUInt128
961 aPacket.putUInt64(entry->mHandle);
962 aPacket.putUInt64(entry->mStorageID);
963 break;
964 case MTP_PROPERTY_NAME: aPacket.putString(entry->mDisplayName.get()); break;
965
966 default:
967 MTP_LOG("Invalid Property: 0x%08x", aProperty);
968 return MTP_RESPONSE_INVALID_OBJECT_PROP_CODE;
969 }
970
971 return MTP_RESPONSE_OK;
972 }
973
974 static int
GetTypeOfObjectProp(MtpObjectProperty aProperty)975 GetTypeOfObjectProp(MtpObjectProperty aProperty)
976 {
977 struct PropertyTableEntry {
978 MtpObjectProperty property;
979 int type;
980 };
981
982 static const PropertyTableEntry kObjectPropertyTable[] = {
983 {MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32 },
984 {MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16 },
985 {MTP_PROPERTY_PROTECTION_STATUS, MTP_TYPE_UINT16 },
986 {MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64 },
987 {MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR },
988 {MTP_PROPERTY_DATE_CREATED, MTP_TYPE_STR },
989 {MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR },
990 {MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 },
991 {MTP_PROPERTY_DISPLAY_NAME, MTP_TYPE_STR },
992 {MTP_PROPERTY_NAME, MTP_TYPE_STR },
993 {MTP_PROPERTY_PERSISTENT_UID, MTP_TYPE_UINT128 },
994 {MTP_PROPERTY_DATE_ADDED, MTP_TYPE_STR },
995 };
996
997 int count = sizeof(kObjectPropertyTable) / sizeof(kObjectPropertyTable[0]);
998 const PropertyTableEntry* entryProp = kObjectPropertyTable;
999 int type = 0;
1000
1001 for (int i = 0; i < count; ++i, ++entryProp) {
1002 if (entryProp->property == aProperty) {
1003 type = entryProp->type;
1004 break;
1005 }
1006 }
1007
1008 return type;
1009 }
1010
1011 //virtual
1012 MtpResponseCode
setObjectPropertyValue(MtpObjectHandle aHandle,MtpObjectProperty aProperty,MtpDataPacket & aPacket)1013 MozMtpDatabase::setObjectPropertyValue(MtpObjectHandle aHandle,
1014 MtpObjectProperty aProperty,
1015 MtpDataPacket& aPacket)
1016 {
1017 MTP_LOG("Handle: 0x%08x Property: 0x%08x", aHandle, aProperty);
1018
1019 // Only support file name change
1020 if (aProperty != MTP_PROPERTY_OBJECT_FILE_NAME) {
1021 MTP_ERR("property 0x%x not supported", aProperty);
1022 return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
1023 }
1024
1025 if (GetTypeOfObjectProp(aProperty) != MTP_TYPE_STR) {
1026 MTP_ERR("property type 0x%x not supported", GetTypeOfObjectProp(aProperty));
1027 return MTP_RESPONSE_GENERAL_ERROR;
1028 }
1029
1030 RefPtr<DbEntry> entry = GetEntry(aHandle);
1031 if (!entry) {
1032 MTP_ERR("Invalid Handle: 0x%08x", aHandle);
1033 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
1034 }
1035
1036 MtpStringBuffer buf;
1037 aPacket.getString(buf);
1038
1039 nsDependentCString newFileName(buf);
1040 nsCString newFileFullPath(GetPathWithoutFileName(entry->mPath) + newFileName);
1041
1042 if (PR_Rename(entry->mPath.get(), newFileFullPath.get()) != PR_SUCCESS) {
1043 MTP_ERR("Failed to rename '%s' to '%s'",
1044 entry->mPath.get(), newFileFullPath.get());
1045 return MTP_RESPONSE_GENERAL_ERROR;
1046 }
1047
1048 MTP_LOG("renamed '%s' to '%s'", entry->mPath.get(), newFileFullPath.get());
1049
1050 entry->mPath = newFileFullPath;
1051 entry->mObjectName = BaseName(entry->mPath);
1052 entry->mDisplayName = entry->mObjectName;
1053
1054 return MTP_RESPONSE_OK;
1055 }
1056
1057 //virtual
1058 MtpResponseCode
getDevicePropertyValue(MtpDeviceProperty aProperty,MtpDataPacket & aPacket)1059 MozMtpDatabase::getDevicePropertyValue(MtpDeviceProperty aProperty,
1060 MtpDataPacket& aPacket)
1061 {
1062 MTP_LOG("(GENERAL ERROR)");
1063 return MTP_RESPONSE_GENERAL_ERROR;
1064 }
1065
1066 //virtual
1067 MtpResponseCode
setDevicePropertyValue(MtpDeviceProperty aProperty,MtpDataPacket & aPacket)1068 MozMtpDatabase::setDevicePropertyValue(MtpDeviceProperty aProperty,
1069 MtpDataPacket& aPacket)
1070 {
1071 MTP_LOG("(NOT SUPPORTED)");
1072 return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
1073 }
1074
1075 //virtual
1076 MtpResponseCode
resetDeviceProperty(MtpDeviceProperty aProperty)1077 MozMtpDatabase::resetDeviceProperty(MtpDeviceProperty aProperty)
1078 {
1079 MTP_LOG("(NOT SUPPORTED)");
1080 return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
1081 }
1082
1083 void
QueryEntries(MozMtpDatabase::MatchType aMatchType,uint32_t aMatchField1,uint32_t aMatchField2,UnprotectedDbArray & result)1084 MozMtpDatabase::QueryEntries(MozMtpDatabase::MatchType aMatchType,
1085 uint32_t aMatchField1,
1086 uint32_t aMatchField2,
1087 UnprotectedDbArray &result)
1088 {
1089 MutexAutoLock lock(mMutex);
1090
1091 ProtectedDbArray::size_type numEntries = mDb.Length();
1092 ProtectedDbArray::index_type entryIdx;
1093 RefPtr<DbEntry> entry;
1094
1095 result.Clear();
1096
1097 switch (aMatchType) {
1098
1099 case MatchAll:
1100 for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
1101 if (mDb[entryIdx]) {
1102 result.AppendElement(mDb[entryIdx]);
1103 }
1104 }
1105 break;
1106
1107 case MatchHandle:
1108 for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
1109 entry = mDb[entryIdx];
1110 if (entry && entry->mHandle == aMatchField1) {
1111 result.AppendElement(entry);
1112 // Handles are unique - return the one that we found.
1113 return;
1114 }
1115 }
1116 break;
1117
1118 case MatchParent:
1119 for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
1120 entry = mDb[entryIdx];
1121 if (entry && entry->mParent == aMatchField1) {
1122 result.AppendElement(entry);
1123 }
1124 }
1125 break;
1126
1127 case MatchFormat:
1128 for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
1129 entry = mDb[entryIdx];
1130 if (entry && entry->mObjectFormat == aMatchField1) {
1131 result.AppendElement(entry);
1132 }
1133 }
1134 break;
1135
1136 case MatchHandleFormat:
1137 for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
1138 entry = mDb[entryIdx];
1139 if (entry && entry->mHandle == aMatchField1) {
1140 if (entry->mObjectFormat == aMatchField2) {
1141 result.AppendElement(entry);
1142 }
1143 // Only 1 entry can match my aHandle. So we can return early.
1144 return;
1145 }
1146 }
1147 break;
1148
1149 case MatchParentFormat:
1150 for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
1151 entry = mDb[entryIdx];
1152 if (entry && entry->mParent == aMatchField1 && entry->mObjectFormat == aMatchField2) {
1153 result.AppendElement(entry);
1154 }
1155 }
1156 break;
1157
1158 default:
1159 MOZ_ASSERT(!"Invalid MatchType");
1160 }
1161 }
1162
1163 //virtual
1164 MtpResponseCode
getObjectPropertyList(MtpObjectHandle aHandle,uint32_t aFormat,uint32_t aProperty,int aGroupCode,int aDepth,MtpDataPacket & aPacket)1165 MozMtpDatabase::getObjectPropertyList(MtpObjectHandle aHandle,
1166 uint32_t aFormat,
1167 uint32_t aProperty,
1168 int aGroupCode,
1169 int aDepth,
1170 MtpDataPacket& aPacket)
1171 {
1172 MTP_LOG("Handle: 0x%08x Format: 0x%08x aProperty: 0x%08x aGroupCode: %d aDepth %d",
1173 aHandle, aFormat, aProperty, aGroupCode, aDepth);
1174
1175 if (aDepth > 1) {
1176 return MTP_RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED;
1177 }
1178 if (aGroupCode != 0) {
1179 return MTP_RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED;
1180 }
1181
1182 MatchType matchType = MatchAll;
1183 uint32_t matchField1 = 0;
1184 uint32_t matchField2 = 0;
1185
1186 // aHandle == 0 implies all objects at the root level
1187 // further specificed by aFormat and/or aDepth
1188
1189 if (aFormat == 0) {
1190 if (aHandle == 0xffffffff) {
1191 // select all objects
1192 matchType = MatchAll;
1193 } else {
1194 if (aDepth == 1) {
1195 // select objects whose Parent matches aHandle
1196 matchType = MatchParent;
1197 matchField1 = aHandle;
1198 } else {
1199 // select object whose handle matches aHandle
1200 matchType = MatchHandle;
1201 matchField1 = aHandle;
1202 }
1203 }
1204 } else {
1205 if (aHandle == 0xffffffff) {
1206 // select all objects whose format matches aFormat
1207 matchType = MatchFormat;
1208 matchField1 = aFormat;
1209 } else {
1210 if (aDepth == 1) {
1211 // select objects whose Parent is aHandle and format matches aFormat
1212 matchType = MatchParentFormat;
1213 matchField1 = aHandle;
1214 matchField2 = aFormat;
1215 } else {
1216 // select objects whose handle is aHandle and format matches aFormat
1217 matchType = MatchHandleFormat;
1218 matchField1 = aHandle;
1219 matchField2 = aFormat;
1220 }
1221 }
1222 }
1223
1224 UnprotectedDbArray result;
1225 QueryEntries(matchType, matchField1, matchField2, result);
1226
1227 const MtpObjectProperty *objectPropertyList;
1228 size_t numObjectProperties = 0;
1229 MtpObjectProperty objectProperty;
1230
1231 if (aProperty == 0xffffffff) {
1232 // return all supported properties
1233 numObjectProperties = MOZ_ARRAY_LENGTH(sSupportedObjectProperties);
1234 objectPropertyList = sSupportedObjectProperties;
1235 } else {
1236 // return property indicated by aProperty
1237 numObjectProperties = 1;
1238 objectProperty = aProperty;
1239 objectPropertyList = &objectProperty;
1240 }
1241
1242 UnprotectedDbArray::size_type numEntries = result.Length();
1243 UnprotectedDbArray::index_type entryIdx;
1244
1245 char dateStr[20];
1246
1247 aPacket.putUInt32(numObjectProperties * numEntries);
1248 for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
1249 RefPtr<DbEntry> entry = result[entryIdx];
1250
1251 for (size_t propertyIdx = 0; propertyIdx < numObjectProperties; propertyIdx++) {
1252 aPacket.putUInt32(entry->mHandle);
1253 MtpObjectProperty prop = objectPropertyList[propertyIdx];
1254 aPacket.putUInt16(prop);
1255 switch (prop) {
1256
1257 case MTP_PROPERTY_STORAGE_ID:
1258 aPacket.putUInt16(MTP_TYPE_UINT32);
1259 aPacket.putUInt32(entry->mStorageID);
1260 break;
1261
1262 case MTP_PROPERTY_PARENT_OBJECT:
1263 aPacket.putUInt16(MTP_TYPE_UINT32);
1264 aPacket.putUInt32(entry->mParent);
1265 break;
1266
1267 case MTP_PROPERTY_PERSISTENT_UID:
1268 aPacket.putUInt16(MTP_TYPE_UINT128);
1269 // the same as aPacket.putUInt128
1270 aPacket.putUInt64(entry->mHandle);
1271 aPacket.putUInt64(entry->mStorageID);
1272 break;
1273
1274 case MTP_PROPERTY_OBJECT_FORMAT:
1275 aPacket.putUInt16(MTP_TYPE_UINT16);
1276 aPacket.putUInt16(entry->mObjectFormat);
1277 break;
1278
1279 case MTP_PROPERTY_OBJECT_SIZE:
1280 aPacket.putUInt16(MTP_TYPE_UINT64);
1281 aPacket.putUInt64(entry->mObjectSize);
1282 break;
1283
1284 case MTP_PROPERTY_OBJECT_FILE_NAME:
1285 case MTP_PROPERTY_NAME:
1286 aPacket.putUInt16(MTP_TYPE_STR);
1287 aPacket.putString(entry->mObjectName.get());
1288 break;
1289
1290 case MTP_PROPERTY_PROTECTION_STATUS:
1291 aPacket.putUInt16(MTP_TYPE_UINT16);
1292 aPacket.putUInt16(0); // 0 = No Protection
1293 break;
1294
1295 case MTP_PROPERTY_DATE_CREATED: {
1296 aPacket.putUInt16(MTP_TYPE_STR);
1297 aPacket.putString(FormatDate(entry->mDateCreated, dateStr, sizeof(dateStr)));
1298 MTP_LOG("mDateCreated: (%ld) %s", entry->mDateCreated, dateStr);
1299 break;
1300 }
1301
1302 case MTP_PROPERTY_DATE_MODIFIED: {
1303 aPacket.putUInt16(MTP_TYPE_STR);
1304 aPacket.putString(FormatDate(entry->mDateModified, dateStr, sizeof(dateStr)));
1305 MTP_LOG("mDateModified: (%ld) %s", entry->mDateModified, dateStr);
1306 break;
1307 }
1308
1309 case MTP_PROPERTY_DATE_ADDED: {
1310 aPacket.putUInt16(MTP_TYPE_STR);
1311 aPacket.putString(FormatDate(entry->mDateAdded, dateStr, sizeof(dateStr)));
1312 MTP_LOG("mDateAdded: (%ld) %s", entry->mDateAdded, dateStr);
1313 break;
1314 }
1315
1316 default:
1317 MTP_ERR("Unrecognized property code: %u", prop);
1318 return MTP_RESPONSE_GENERAL_ERROR;
1319 }
1320 }
1321 }
1322 return MTP_RESPONSE_OK;
1323 }
1324
1325 //virtual
1326 MtpResponseCode
getObjectInfo(MtpObjectHandle aHandle,MtpObjectInfo & aInfo)1327 MozMtpDatabase::getObjectInfo(MtpObjectHandle aHandle,
1328 MtpObjectInfo& aInfo)
1329 {
1330 RefPtr<DbEntry> entry = GetEntry(aHandle);
1331 if (!entry) {
1332 MTP_ERR("Handle 0x%08x is invalid", aHandle);
1333 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
1334 }
1335
1336 MTP_LOG("Handle: 0x%08x Display:'%s' Object:'%s'", aHandle, entry->mDisplayName.get(), entry->mObjectName.get());
1337
1338 aInfo.mHandle = aHandle;
1339 aInfo.mStorageID = entry->mStorageID;
1340 aInfo.mFormat = entry->mObjectFormat;
1341 aInfo.mProtectionStatus = 0x0;
1342
1343 if (entry->mObjectSize > 0xFFFFFFFFuLL) {
1344 aInfo.mCompressedSize = 0xFFFFFFFFuLL;
1345 } else {
1346 aInfo.mCompressedSize = entry->mObjectSize;
1347 }
1348
1349 aInfo.mThumbFormat = MTP_FORMAT_UNDEFINED;
1350 aInfo.mThumbCompressedSize = 0;
1351 aInfo.mThumbPixWidth = 0;
1352 aInfo.mThumbPixHeight = 0;
1353 aInfo.mImagePixWidth = 0;
1354 aInfo.mImagePixHeight = 0;
1355 aInfo.mImagePixDepth = 0;
1356 aInfo.mParent = entry->mParent;
1357 aInfo.mAssociationType = 0;
1358 aInfo.mAssociationDesc = 0;
1359 aInfo.mSequenceNumber = 0;
1360 aInfo.mName = ::strdup(entry->mObjectName.get());
1361 aInfo.mDateCreated = entry->mDateCreated;
1362 aInfo.mDateModified = entry->mDateModified;
1363
1364 MTP_LOG("aInfo.mDateCreated = %ld entry->mDateCreated = %ld",
1365 aInfo.mDateCreated, entry->mDateCreated);
1366 MTP_LOG("aInfo.mDateModified = %ld entry->mDateModified = %ld",
1367 aInfo.mDateModified, entry->mDateModified);
1368
1369 aInfo.mKeywords = ::strdup("fxos,touch");
1370
1371 return MTP_RESPONSE_OK;
1372 }
1373
1374 //virtual
1375 void*
getThumbnail(MtpObjectHandle aHandle,size_t & aOutThumbSize)1376 MozMtpDatabase::getThumbnail(MtpObjectHandle aHandle, size_t& aOutThumbSize)
1377 {
1378 MTP_LOG("Handle: 0x%08x (returning nullptr)", aHandle);
1379
1380 aOutThumbSize = 0;
1381
1382 return nullptr;
1383 }
1384
1385 //virtual
1386 MtpResponseCode
getObjectFilePath(MtpObjectHandle aHandle,MtpString & aOutFilePath,int64_t & aOutFileLength,MtpObjectFormat & aOutFormat)1387 MozMtpDatabase::getObjectFilePath(MtpObjectHandle aHandle,
1388 MtpString& aOutFilePath,
1389 int64_t& aOutFileLength,
1390 MtpObjectFormat& aOutFormat)
1391 {
1392 RefPtr<DbEntry> entry = GetEntry(aHandle);
1393 if (!entry) {
1394 MTP_ERR("Handle 0x%08x is invalid", aHandle);
1395 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
1396 }
1397
1398 MTP_LOG("Handle: 0x%08x FilePath: '%s'", aHandle, entry->mPath.get());
1399
1400 aOutFilePath = entry->mPath.get();
1401 aOutFileLength = entry->mObjectSize;
1402 aOutFormat = entry->mObjectFormat;
1403
1404 return MTP_RESPONSE_OK;
1405 }
1406
1407 //virtual
1408 MtpResponseCode
deleteFile(MtpObjectHandle aHandle)1409 MozMtpDatabase::deleteFile(MtpObjectHandle aHandle)
1410 {
1411 RefPtr<DbEntry> entry = GetEntry(aHandle);
1412 if (!entry) {
1413 MTP_ERR("Invalid Handle: 0x%08x", aHandle);
1414 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
1415 }
1416
1417 MTP_LOG("Handle: 0x%08x '%s'", aHandle, entry->mPath.get());
1418
1419 // File deletion will happen in lower level implementation.
1420 // The only thing we need to do is removing the entry from the db.
1421 RemoveEntry(aHandle);
1422
1423 // Tell Device Storage that the file is gone.
1424 MtpWatcherNotify(entry, "deleted");
1425
1426 return MTP_RESPONSE_OK;
1427 }
1428
1429 #if 0
1430 //virtual
1431 MtpResponseCode
1432 MozMtpDatabase::moveFile(MtpObjectHandle aHandle, MtpObjectHandle aNewParent)
1433 {
1434 MTP_LOG("Handle: 0x%08x NewParent: 0x%08x", aHandle, aNewParent);
1435
1436 // change parent
1437
1438 return MTP_RESPONSE_OK
1439 }
1440
1441 //virtual
1442 MtpResponseCode
1443 MozMtpDatabase::copyFile(MtpObjectHandle aHandle, MtpObjectHandle aNewParent)
1444 {
1445 MTP_LOG("Handle: 0x%08x NewParent: 0x%08x", aHandle, aNewParent);
1446
1447 // duplicate DbEntry
1448 // change parent
1449
1450 return MTP_RESPONSE_OK
1451 }
1452 #endif
1453
1454 //virtual
1455 MtpObjectHandleList*
getObjectReferences(MtpObjectHandle aHandle)1456 MozMtpDatabase::getObjectReferences(MtpObjectHandle aHandle)
1457 {
1458 MTP_LOG("Handle: 0x%08x (returning nullptr)", aHandle);
1459 return nullptr;
1460 }
1461
1462 //virtual
1463 MtpResponseCode
setObjectReferences(MtpObjectHandle aHandle,MtpObjectHandleList * aReferences)1464 MozMtpDatabase::setObjectReferences(MtpObjectHandle aHandle,
1465 MtpObjectHandleList* aReferences)
1466 {
1467 MTP_LOG("Handle: 0x%08x (NOT SUPPORTED)", aHandle);
1468 return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
1469 }
1470
1471 //virtual
1472 MtpProperty*
getObjectPropertyDesc(MtpObjectProperty aProperty,MtpObjectFormat aFormat)1473 MozMtpDatabase::getObjectPropertyDesc(MtpObjectProperty aProperty,
1474 MtpObjectFormat aFormat)
1475 {
1476 MTP_LOG("Property: %s 0x%08x", ObjectPropertyAsStr(aProperty), aProperty);
1477
1478 MtpProperty* result = nullptr;
1479 switch (aProperty)
1480 {
1481 case MTP_PROPERTY_PROTECTION_STATUS:
1482 result = new MtpProperty(aProperty, MTP_TYPE_UINT16);
1483 break;
1484 case MTP_PROPERTY_OBJECT_FORMAT:
1485 result = new MtpProperty(aProperty, MTP_TYPE_UINT16, false, aFormat);
1486 break;
1487 case MTP_PROPERTY_STORAGE_ID:
1488 case MTP_PROPERTY_PARENT_OBJECT:
1489 case MTP_PROPERTY_WIDTH:
1490 case MTP_PROPERTY_HEIGHT:
1491 case MTP_PROPERTY_IMAGE_BIT_DEPTH:
1492 result = new MtpProperty(aProperty, MTP_TYPE_UINT32);
1493 break;
1494 case MTP_PROPERTY_OBJECT_SIZE:
1495 result = new MtpProperty(aProperty, MTP_TYPE_UINT64);
1496 break;
1497 case MTP_PROPERTY_DISPLAY_NAME:
1498 case MTP_PROPERTY_NAME:
1499 result = new MtpProperty(aProperty, MTP_TYPE_STR);
1500 break;
1501 case MTP_PROPERTY_OBJECT_FILE_NAME:
1502 result = new MtpProperty(aProperty, MTP_TYPE_STR, true);
1503 break;
1504 case MTP_PROPERTY_DATE_CREATED:
1505 case MTP_PROPERTY_DATE_MODIFIED:
1506 case MTP_PROPERTY_DATE_ADDED:
1507 result = new MtpProperty(aProperty, MTP_TYPE_STR);
1508 result->setFormDateTime();
1509 break;
1510 case MTP_PROPERTY_PERSISTENT_UID:
1511 result = new MtpProperty(aProperty, MTP_TYPE_UINT128);
1512 break;
1513 default:
1514 break;
1515 }
1516
1517 return result;
1518 }
1519
1520 //virtual
1521 MtpProperty*
getDevicePropertyDesc(MtpDeviceProperty aProperty)1522 MozMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty aProperty)
1523 {
1524 MTP_LOG("(returning MTP_DEVICE_PROPERTY_UNDEFINED)");
1525 return new MtpProperty(MTP_DEVICE_PROPERTY_UNDEFINED, MTP_TYPE_UNDEFINED);
1526 }
1527
1528 //virtual
1529 void
sessionStarted()1530 MozMtpDatabase::sessionStarted()
1531 {
1532 MTP_LOG("");
1533 }
1534
1535 //virtual
1536 void
sessionEnded()1537 MozMtpDatabase::sessionEnded()
1538 {
1539 MTP_LOG("");
1540 }
1541
1542 END_MTP_NAMESPACE
1543