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