Bug 1049243 - Integrate MTP and Device Storage. r=echou
authorDave Hylands <dhylands@mozilla.com>
Thu, 28 Aug 2014 13:13:06 -0700
changeset 202393 812f62c9f703fbce4680618019db53e193218689
parent 202392 4e4032a5d768363f34149aa74bd15ec6dcae74bf
child 202394 4eaaa8ce26afcd114e9423cd2ce47d34c15ad0d4
push id27399
push userryanvm@gmail.com
push dateFri, 29 Aug 2014 19:31:08 +0000
treeherdermozilla-central@2a354048f964 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersechou
bugs1049243
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1049243 - Integrate MTP and Device Storage. r=echou
dom/system/gonk/AutoMounter.cpp
dom/system/gonk/MozMtpCommon.h
dom/system/gonk/MozMtpDatabase.cpp
dom/system/gonk/MozMtpDatabase.h
dom/system/gonk/MozMtpServer.cpp
--- a/dom/system/gonk/AutoMounter.cpp
+++ b/dom/system/gonk/AutoMounter.cpp
@@ -670,27 +670,27 @@ AutoMounter::UpdateState()
     }
     DBG("UpdateState: USB functions: '%s'", functionsStr);
 
     bool  usbConfigured = IsUsbConfigured();
     umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0);
     if (umsAvail) {
       umsConfigured = usbConfigured && strstr(functionsStr, USB_FUNC_UMS) != nullptr;
       umsEnabled = (mMode == AUTOMOUNTER_ENABLE_UMS) ||
-                   (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && umsConfigured;
+                   ((mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && umsConfigured);
     } else {
       umsConfigured = false;
       umsEnabled = false;
     }
 
     mtpAvail = (access(ICS_SYS_MTP_DIRECTORY, F_OK) == 0);
     if (mtpAvail) {
       mtpConfigured = usbConfigured && strstr(functionsStr, USB_FUNC_MTP) != nullptr;
       mtpEnabled = (mMode == AUTOMOUNTER_ENABLE_MTP) ||
-                   (mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && mtpConfigured;
+                   ((mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && mtpConfigured);
     } else {
       mtpConfigured = false;
       mtpEnabled = false;
     }
   }
 
   bool enabled = mtpEnabled || umsEnabled;
 
--- a/dom/system/gonk/MozMtpCommon.h
+++ b/dom/system/gonk/MozMtpCommon.h
@@ -5,23 +5,33 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_system_mozmtpcommon_h__
 #define mozilla_system_mozmtpcommon_h__
 
 #include "mozilla/Types.h"
 #include <android/log.h>
 
+#define USE_DEBUG 0
+
+#if USE_DEBUG
+#define MTP_DBG(msg, ...)                                            \
+  __android_log_print(ANDROID_LOG_DEBUG, "MozMtp",                    \
+                      "%s: " msg, __FUNCTION__, ##__VA_ARGS__)
+#else
+#define MTP_DBG(msg, ...)
+#endif
+
 #define MTP_LOG(msg, ...)                                            \
   __android_log_print(ANDROID_LOG_INFO, "MozMtp",                    \
-                      "%s: " msg, __FUNCTION__, ##__VA_ARGS__)       \
+                      "%s: " msg, __FUNCTION__, ##__VA_ARGS__)
 
 #define MTP_ERR(msg, ...)                                            \
   __android_log_print(ANDROID_LOG_ERROR, "MozMtp",                   \
-                      "%s: " msg, __FUNCTION__, ##__VA_ARGS__)       \
+                      "%s: " msg, __FUNCTION__, ##__VA_ARGS__)
 
 #define BEGIN_MTP_NAMESPACE \
   namespace mozilla { namespace system { namespace mtp {
 #define END_MTP_NAMESPACE \
   } /* namespace mtp */ } /* namespace system */ } /* namespace mozilla */
 #define USING_MTP_NAMESPACE \
   using namespace mozilla::system::mtp;
 
--- a/dom/system/gonk/MozMtpDatabase.cpp
+++ b/dom/system/gonk/MozMtpDatabase.cpp
@@ -1,98 +1,308 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MozMtpDatabase.h"
+#include "MozMtpServer.h"
 
+#include "base/message_loop.h"
+#include "DeviceStorage.h"
 #include "mozilla/ArrayUtils.h"
+#include "mozilla/AutoRestore.h"
 #include "mozilla/Scoped.h"
+#include "mozilla/Services.h"
+#include "nsAutoPtr.h"
 #include "nsIFile.h"
+#include "nsIObserverService.h"
 #include "nsPrintfCString.h"
+#include "nsString.h"
 #include "prio.h"
 
 #include <dirent.h>
 #include <libgen.h>
 
 using namespace android;
 using namespace mozilla;
 
 namespace mozilla {
 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedCloseDir, PRDir, PR_CloseDir)
 }
 
 BEGIN_MTP_NAMESPACE
 
+#if 0
+// Some debug code for figuring out deadlocks, if you happen to run into
+// that scenario
+
+class DebugMutexAutoLock: public MutexAutoLock
+{
+public:
+  DebugMutexAutoLock(mozilla::Mutex& aMutex)
+    : MutexAutoLock(aMutex)
+  {
+    MTP_LOG("Mutex acquired");
+  }
+
+  ~DebugMutexAutoLock()
+  {
+    MTP_LOG("Releasing mutex");
+  }
+};
+#define MutexAutoLock  MTP_LOG("About to enter mutex"); DebugMutexAutoLock
+
+#endif
+
 static const char *
 ObjectPropertyAsStr(MtpObjectProperty aProperty)
 {
   switch (aProperty) {
-    case MTP_PROPERTY_STORAGE_ID:       return "MTP_PROPERTY_STORAGE_ID";
-    case MTP_PROPERTY_OBJECT_FILE_NAME: return "MTP_PROPERTY_OBJECT_FILE_NAME";
-    case MTP_PROPERTY_OBJECT_FORMAT:    return "MTP_PROPERTY_OBJECT_FORMAT";
-    case MTP_PROPERTY_OBJECT_SIZE:      return "MTP_PROPERTY_OBJECT_SIZE";
-    case MTP_PROPERTY_WIDTH:            return "MTP_PROPERTY_WIDTH";
-    case MTP_PROPERTY_HEIGHT:           return "MTP_PROPERTY_HEIGHT";
-    case MTP_PROPERTY_IMAGE_BIT_DEPTH:  return "MTP_PROPERTY_IMAGE_BIT_DEPTH";
-    case MTP_PROPERTY_DISPLAY_NAME:     return "MTP_PROPERTY_DISPLAY_NAME";
+    case MTP_PROPERTY_STORAGE_ID:         return "MTP_PROPERTY_STORAGE_ID";
+    case MTP_PROPERTY_OBJECT_FORMAT:      return "MTP_PROPERTY_OBJECT_FORMAT";
+    case MTP_PROPERTY_PROTECTION_STATUS:  return "MTP_PROPERTY_PROTECTION_STATUS";
+    case MTP_PROPERTY_OBJECT_SIZE:        return "MTP_PROPERTY_OBJECT_SIZE";
+    case MTP_PROPERTY_OBJECT_FILE_NAME:   return "MTP_PROPERTY_OBJECT_FILE_NAME";
+    case MTP_PROPERTY_DATE_MODIFIED:      return "MTP_PROPERTY_DATE_MODIFIED";
+    case MTP_PROPERTY_PARENT_OBJECT:      return "MTP_PROPERTY_PARENT_OBJECT";
+    case MTP_PROPERTY_PERSISTENT_UID:     return "MTP_PROPERTY_PERSISTENT_UID";
+    case MTP_PROPERTY_NAME:               return "MTP_PROPERTY_NAME";
+    case MTP_PROPERTY_DATE_ADDED:         return "MTP_PROPERTY_DATE_ADDED";
+    case MTP_PROPERTY_WIDTH:              return "MTP_PROPERTY_WIDTH";
+    case MTP_PROPERTY_HEIGHT:             return "MTP_PROPERTY_HEIGHT";
+    case MTP_PROPERTY_IMAGE_BIT_DEPTH:    return "MTP_PROPERTY_IMAGE_BIT_DEPTH";
+    case MTP_PROPERTY_DISPLAY_NAME:       return "MTP_PROPERTY_DISPLAY_NAME";
   }
   return "MTP_PROPERTY_???";
 }
 
 MozMtpDatabase::MozMtpDatabase()
+  : mMutex("MozMtpDatabase::mMutex"),
+    mDb(mMutex),
+    mStorage(mMutex),
+    mBeginSendObjectCalled(false)
 {
-  MTP_LOG("constructed");
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
 
   // We use the index into the array as the handle. Since zero isn't a valid
   // index, we stick a dummy entry there.
 
   RefPtr<DbEntry> dummy;
 
+  MutexAutoLock lock(mMutex);
   mDb.AppendElement(dummy);
 }
 
 //virtual
 MozMtpDatabase::~MozMtpDatabase()
 {
-  MTP_LOG("destructed");
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
 }
 
 void
 MozMtpDatabase::AddEntry(DbEntry *entry)
 {
+  MutexAutoLock lock(mMutex);
+
   entry->mHandle = GetNextHandle();
   MOZ_ASSERT(mDb.Length() == entry->mHandle);
   mDb.AppendElement(entry);
 
-  MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path:'%s'",
+  MTP_DBG("Handle: 0x%08x Parent: 0x%08x Path:'%s'",
           entry->mHandle, entry->mParent, entry->mPath.get());
 }
 
+void
+MozMtpDatabase::DumpEntries(const char* aLabel)
+{
+  MutexAutoLock lock(mMutex);
+
+  ProtectedDbArray::size_type numEntries = mDb.Length();
+  MTP_LOG("%s: numEntries = %d", aLabel, numEntries);
+  ProtectedDbArray::index_type entryIndex;
+  for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+    RefPtr<DbEntry> entry = mDb[entryIndex];
+    if (entry) {
+      MTP_LOG("%s: mDb[%d]: mHandle: 0x%08x mParent: 0x%08x StorageID: 0x%08x path: '%s'",
+              aLabel, entryIndex, entry->mHandle, entry->mParent, entry->mStorageID, entry->mPath.get());
+    } else {
+      MTP_LOG("%s: mDb[%2d]: entry is NULL", aLabel, entryIndex);
+    }
+  }
+}
+
+MtpObjectHandle
+MozMtpDatabase::FindEntryByPath(const nsACString& aPath)
+{
+  MutexAutoLock lock(mMutex);
+
+  ProtectedDbArray::size_type numEntries = mDb.Length();
+  ProtectedDbArray::index_type entryIndex;
+  for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
+    RefPtr<DbEntry> entry = mDb[entryIndex];
+    if (entry && entry->mPath.Equals(aPath)) {
+      return entryIndex;
+    }
+  }
+  return 0;
+}
+
 TemporaryRef<MozMtpDatabase::DbEntry>
 MozMtpDatabase::GetEntry(MtpObjectHandle aHandle)
 {
+  MutexAutoLock lock(mMutex);
+
   RefPtr<DbEntry> entry;
 
   if (aHandle > 0 && aHandle < mDb.Length()) {
     entry = mDb[aHandle];
   }
   return entry;
 }
 
 void
 MozMtpDatabase::RemoveEntry(MtpObjectHandle aHandle)
 {
+  MutexAutoLock lock(mMutex);
+
   if (aHandle > 0 && aHandle < mDb.Length()) {
     mDb[aHandle] = nullptr;
   }
 }
 
+class FileWatcherNotifyRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  FileWatcherNotifyRunnable(nsACString& aStorageName,
+                            nsACString& aPath,
+                            const char* aEventType)
+    : mStorageName(aStorageName),
+      mPath(aPath),
+      mEventType(aEventType)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    NS_ConvertUTF8toUTF16 storageName(mStorageName);
+    NS_ConvertUTF8toUTF16 path(mPath);
+
+    nsRefPtr<DeviceStorageFile> dsf(
+      new DeviceStorageFile(NS_LITERAL_STRING(DEVICESTORAGE_SDCARD),
+                            storageName, path));
+    NS_ConvertUTF8toUTF16 eventType(mEventType);
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+
+    MTP_DBG("Sending file-watcher-notify %s %s %s",
+            mEventType.get(), mStorageName.get(), mPath.get());
+
+    obs->NotifyObservers(dsf, "file-watcher-notify", eventType.get());
+    return NS_OK;
+  }
+
+private:
+  nsCString mStorageName;
+  nsCString mPath;
+  nsCString mEventType;
+};
+
+// FileWatcherNotify is used to tell DeviceStorage when a file was changed
+// through the MTP server.
+void
+MozMtpDatabase::FileWatcherNotify(DbEntry* aEntry, const char* aEventType)
+{
+  // This function gets called from the MozMtpServer::mServerThread
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  MTP_DBG("file: %s %s", aEntry->mPath.get(), aEventType);
+
+  // Tell interested parties that a file was created, deleted, or modified.
+
+  RefPtr<StorageEntry> storageEntry;
+  {
+    MutexAutoLock lock(mMutex);
+
+    // FindStorage and the mStorage[] access both need to have the mutex held.
+    StorageArray::index_type storageIndex = FindStorage(aEntry->mStorageID);
+    if (storageIndex == StorageArray::NoIndex) {
+      return;
+    }
+    storageEntry = mStorage[storageIndex];
+  }
+
+  // DeviceStorage wants the storageName and the path relative to the root
+  // of the storage area, so we need to strip off the storagePath
+
+  nsAutoCString relPath(Substring(aEntry->mPath,
+                                  storageEntry->mStoragePath.Length() + 1));
+
+  nsRefPtr<FileWatcherNotifyRunnable> r =
+    new FileWatcherNotifyRunnable(storageEntry->mStorageName, relPath, aEventType);
+  DebugOnly<nsresult> rv = NS_DispatchToMainThread(r);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+// Called to tell the MTP server about new or deleted files,
+void
+MozMtpDatabase::FileWatcherUpdate(RefCountedMtpServer* aMtpServer,
+                                  DeviceStorageFile* aFile,
+                                  const nsACString& aEventType)
+{
+  // Runs on the FileWatcherUpdate->mIOThread (see MozMtpServer.cpp)
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  // Figure out which storage the belongs to (if any)
+
+  if (!aFile->mFile) {
+    // No path - don't bother looking.
+    return;
+  }
+  nsString wideFilePath;
+  aFile->mFile->GetPath(wideFilePath);
+  NS_ConvertUTF16toUTF8 filePath(wideFilePath);
+
+  nsCString evtType(aEventType);
+  MTP_LOG("file %s %s", filePath.get(), evtType.get());
+
+  MtpObjectHandle entryHandle = FindEntryByPath(filePath);
+
+  if (aEventType.EqualsLiteral("created")) {
+    if (entryHandle != 0) {
+      // The entry already exists. This means that we're being notified
+      // about a file added by MTP. So we can ignore it.
+
+      return;
+    }
+    entryHandle = CreateEntryForFile(filePath, aFile);
+    if (entryHandle == 0) {
+      // CreateEntryForFile didn't create a new entry. We can't tell MTP.
+      return;
+    }
+    MTP_LOG("About to call sendObjectAdded Handle 0x%08x file %s", entryHandle, filePath.get());
+    aMtpServer->sendObjectAdded(entryHandle);
+    return;
+  }
+
+  if (aEventType.EqualsLiteral("deleted")) {
+    if (entryHandle == 0) {
+      // The entry has already been removed. We can't tell MTP.
+      return;
+    }
+
+    MTP_LOG("About to call sendObjectRemoved Handle 0x%08x file %s", entryHandle, filePath.get());
+    aMtpServer->sendObjectRemoved(entryHandle);
+
+    RemoveEntry(entryHandle);
+    return;
+  }
+}
+
 nsCString
 MozMtpDatabase::BaseName(const nsCString& path)
 {
   nsCOMPtr<nsIFile> file;
   NS_NewNativeLocalFile(path, false, getter_AddRefs(file));
   if (file) {
     nsCString leafName;
     file->GetNativeLeafName(leafName);
@@ -112,21 +322,109 @@ GetPathWithoutFileName(const nsCString& 
     path = StringHead(aFullPath, offset + 1);
   }
 
   MTP_LOG("returning '%s'", path.get());
 
   return path;
 }
 
+MtpObjectHandle
+MozMtpDatabase::CreateEntryForFile(const nsACString& aPath, DeviceStorageFile* aFile)
+{
+  // Find the StorageID that this path corresponds to.
+
+  nsCString remainder;
+  MtpStorageID storageID = FindStorageIDFor(aPath, remainder);
+  if (storageID == 0) {
+    // The path in question isn't for a storage area we're monitoring.
+    nsCString path(aPath);
+    return 0;
+  }
+
+  bool exists = false;
+  aFile->mFile->Exists(&exists);
+  if (!exists) {
+    // File doesn't exist, no sense telling MTP about it.
+    // This could happen if Device Storage created and deleted a file right
+    // away. Since the notifications wind up being async, the file might
+    // not exist any more.
+    return 0;
+  }
+
+  // Now walk the remaining directories, finding or creating as required.
+
+  MtpObjectHandle parent = MTP_PARENT_ROOT;
+  bool doFind = true;
+  int32_t offset = aPath.Length() - remainder.Length();
+  int32_t slash;
+
+  do {
+    nsDependentCSubstring component;
+    slash = aPath.FindChar('/', offset);
+    if (slash == kNotFound) {
+      component.Rebind(aPath, 0, aPath.Length());
+    } else {
+      component.Rebind(aPath, slash, aPath.Length() - slash);
+    }
+    if (doFind) {
+      MtpObjectHandle entryHandle = FindEntryByPath(component);
+      if (entryHandle != 0) {
+        // We found an entry.
+        parent = entryHandle;
+        continue;
+      }
+    }
+
+    // We've got a directory component that doesn't exist. This means that all
+    // further subdirectories won't exist either, so we can skip searching
+    // for them.
+    doFind = false;
+
+    // This directory and the file don't exist, create them
+
+    RefPtr<DbEntry> entry = new DbEntry;
+
+    entry->mStorageID = storageID;
+    entry->mObjectName = Substring(aPath, offset, slash - offset);
+    entry->mParent = parent;
+    entry->mDisplayName = entry->mObjectName;
+    entry->mPath = component;
+
+    if (slash == kNotFound) {
+      // No slash - this is the file component
+      entry->mObjectFormat = MTP_FORMAT_DEFINED;
+
+      int64_t fileSize = 0;
+      aFile->mFile->GetFileSize(&fileSize);
+      entry->mObjectSize = fileSize;
+
+      aFile->mFile->GetLastModifiedTime(&entry->mDateCreated);
+    } else {
+      // Found a slash, this makes this a directory component
+      entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
+      entry->mObjectSize = 0;
+      entry->mDateCreated = PR_Now();
+    }
+    entry->mDateModified = entry->mDateCreated;
+
+    AddEntry(entry);
+    parent = entry->mHandle;
+  } while (slash != kNotFound);
+
+  return parent; // parent will be entry->mHandle
+}
+
 void
 MozMtpDatabase::AddDirectory(MtpStorageID aStorageID,
                              const char* aPath,
                              MtpObjectHandle aParent)
 {
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
   ScopedCloseDir dir;
 
   if (!(dir = PR_OpenDir(aPath))) {
     MTP_ERR("Unable to open directory '%s'", aPath);
     return;
   }
 
   PRDirEntry* dirEntry;
@@ -157,163 +455,249 @@ MozMtpDatabase::AddDirectory(MtpStorageI
       entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
       entry->mObjectSize = 0;
       AddEntry(entry);
       AddDirectory(aStorageID, filename.get(), entry->mHandle);
     }
   }
 }
 
+MozMtpDatabase::StorageArray::index_type
+MozMtpDatabase::FindStorage(MtpStorageID aStorageID)
+{
+  // Currently, this routine is called from MozMtpDatabase::RemoveStorage
+  // and MozMtpDatabase::FileWatcherNotify, which both hold mMutex.
+
+  StorageArray::size_type numStorages = mStorage.Length();
+  StorageArray::index_type storageIndex;
+
+  for (storageIndex = 0; storageIndex < numStorages; storageIndex++) {
+    RefPtr<StorageEntry> storage = mStorage[storageIndex];
+    if (storage->mStorageID == aStorageID) {
+      return storageIndex;
+    }
+  }
+  return StorageArray::NoIndex;
+}
+
+// Find the storage ID for the storage area that contains aPath.
+MtpStorageID
+MozMtpDatabase::FindStorageIDFor(const nsACString& aPath, nsCSubstring& aRemainder)
+{
+  MutexAutoLock lock(mMutex);
+
+  aRemainder.Truncate();
+
+  StorageArray::size_type numStorages = mStorage.Length();
+  StorageArray::index_type storageIndex;
+
+  for (storageIndex = 0; storageIndex < numStorages; storageIndex++) {
+    RefPtr<StorageEntry> storage = mStorage[storageIndex];
+    if (StringHead(aPath, storage->mStoragePath.Length()).Equals(storage->mStoragePath)) {
+      if (aPath.Length() == storage->mStoragePath.Length()) {
+        return storage->mStorageID;
+      }
+      if (aPath[storage->mStoragePath.Length()] == '/') {
+        aRemainder = Substring(aPath, storage->mStoragePath.Length() + 1);
+        return storage->mStorageID;
+      }
+    }
+  }
+  return 0;
+}
+
 void
 MozMtpDatabase::AddStorage(MtpStorageID aStorageID,
                            const char* aPath,
                            const char* aName)
 {
-  //TODO: Add an assert re thread being run on
+  // This is called on the IOThread from MozMtpStorage::StorageAvailable
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
 
   PRFileInfo  fileInfo;
-
   if (PR_GetFileInfo(aPath, &fileInfo) != PR_SUCCESS) {
     MTP_ERR("'%s' doesn't exist", aPath);
     return;
   }
   if (fileInfo.type != PR_FILE_DIRECTORY) {
     MTP_ERR("'%s' isn't a directory", aPath);
     return;
   }
 
-#if 0
-  RefPtr<DbEntry> entry = new DbEntry;
+  nsRefPtr<StorageEntry> storageEntry = new StorageEntry;
 
-  entry->mStorageID = aStorageID;
-  entry->mParent = MTP_PARENT_ROOT;
-  entry->mObjectName = aName;
-  entry->mDisplayName = aName;
-  entry->mPath = aPath;
-  entry->mObjectFormat = MTP_FORMAT_ASSOCIATION;
-  entry->mObjectSize = 0;
+  storageEntry->mStorageID = aStorageID;
+  storageEntry->mStoragePath = aPath;
+  storageEntry->mStorageName = aName;
+  {
+    MutexAutoLock lock(mMutex);
+    mStorage.AppendElement(storageEntry);
+  }
 
-  AddEntry(entry);
-
-  AddDirectory(aStorageID, aPath, entry->mHandle);
-#else
   AddDirectory(aStorageID, aPath, MTP_PARENT_ROOT);
-#endif
+  {
+    MutexAutoLock lock(mMutex);
+    MTP_LOG("added %d items from tree '%s'", mDb.Length(), aPath);
+  }
 }
 
 void
 MozMtpDatabase::RemoveStorage(MtpStorageID aStorageID)
 {
-  DbArray::size_type numEntries = mDb.Length();
-  DbArray::index_type entryIndex;
+  MutexAutoLock lock(mMutex);
+
+  // This is called on the IOThread from MozMtpStorage::StorageAvailable
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+  ProtectedDbArray::size_type numEntries = mDb.Length();
+  ProtectedDbArray::index_type entryIndex;
   for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
     RefPtr<DbEntry> entry = mDb[entryIndex];
     if (entry && entry->mStorageID == aStorageID) {
       mDb[entryIndex] = nullptr;
     }
   }
+  StorageArray::index_type storageIndex = FindStorage(aStorageID);
+  if (storageIndex != StorageArray::NoIndex) {
+    mStorage.RemoveElementAt(storageIndex);
+  }
 }
 
 // called from SendObjectInfo to reserve a database entry for the incoming file
 //virtual
 MtpObjectHandle
 MozMtpDatabase::beginSendObject(const char* aPath,
-                              MtpObjectFormat aFormat,
-                              MtpObjectHandle aParent,
-                              MtpStorageID aStorageID,
-                              uint64_t aSize,
-                              time_t aModified)
+                                MtpObjectFormat aFormat,
+                                MtpObjectHandle aParent,
+                                MtpStorageID aStorageID,
+                                uint64_t aSize,
+                                time_t aModified)
 {
-  if (!aParent) {
-    MTP_LOG("aParent is NULL");
-    return kInvalidObjectHandle;
+  // If MtpServer::doSendObjectInfo receives a request with a parent of
+  // MTP_PARENT_ROOT, then it fills in aPath with the fully qualified path
+  // and then passes in a parent of zero.
+
+  if (aParent == 0) {
+    // Undo what doSendObjectInfo did
+    aParent = MTP_PARENT_ROOT;
   }
 
   RefPtr<DbEntry> entry = new DbEntry;
 
   entry->mStorageID = aStorageID;
   entry->mParent = aParent;
   entry->mPath = aPath;
   entry->mObjectName = BaseName(entry->mPath);
   entry->mDisplayName = entry->mObjectName;
   entry->mObjectFormat = aFormat;
   entry->mObjectSize = aSize;
 
   AddEntry(entry);
 
   MTP_LOG("Handle: 0x%08x Parent: 0x%08x Path: '%s'", entry->mHandle, aParent, aPath);
 
+  mBeginSendObjectCalled = true;
   return entry->mHandle;
 }
 
 // called to report success or failure of the SendObject file transfer
 // success should signal a notification of the new object's creation,
 // failure should remove the database entry created in beginSendObject
 
 //virtual
 void
 MozMtpDatabase::endSendObject(const char* aPath,
                               MtpObjectHandle aHandle,
                               MtpObjectFormat aFormat,
                               bool aSucceeded)
 {
   MTP_LOG("Handle: 0x%08x Path: '%s'", aHandle, aPath);
-  if (!aSucceeded) {
+
+  if (aSucceeded) {
+    RefPtr<DbEntry> entry = GetEntry(aHandle);
+    if (entry) {
+      if (mBeginSendObjectCalled) {
+        FileWatcherNotify(entry, "created");
+      } else {
+        FileWatcherNotify(entry, "modified");
+      }
+    }
+  } else {
     RemoveEntry(aHandle);
   }
+  mBeginSendObjectCalled = false;
 }
 
 //virtual
 MtpObjectHandleList*
 MozMtpDatabase::getObjectList(MtpStorageID aStorageID,
                               MtpObjectFormat aFormat,
                               MtpObjectHandle aParent)
 {
   MTP_LOG("StorageID: 0x%08x Format: 0x%04x Parent: 0x%08x",
           aStorageID, aFormat, aParent);
 
+  // aStorageID == 0xFFFFFFFF for all storage
+  // aFormat    == 0          for all formats
+  // aParent    == 0xFFFFFFFF for objects with no parents
+  // aParent    == 0          for all objects
+
   //TODO: Optimize
 
   ScopedDeletePtr<MtpObjectHandleList> list;
 
   list = new MtpObjectHandleList();
 
-  // Note: objects in the topmost directory of each storage area have a parent
-  //       of MTP_PARENT_ROOT. So we need to filter on storage ID as well.
+  MutexAutoLock lock(mMutex);
 
-  DbArray::size_type numEntries = mDb.Length();
-  DbArray::index_type entryIndex;
+  ProtectedDbArray::size_type numEntries = mDb.Length();
+  ProtectedDbArray::index_type entryIndex;
   for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
     RefPtr<DbEntry> entry = mDb[entryIndex];
-    if (entry && entry->mStorageID == aStorageID && entry->mParent == aParent) {
+    if (entry &&
+        (aStorageID == 0xFFFFFFFF || entry->mStorageID == aStorageID) &&
+        (aFormat == 0 || entry->mObjectFormat == aFormat) &&
+        (aParent == 0 || entry->mParent == aParent)) {
       list->push(entry->mHandle);
     }
   }
+  MTP_LOG("  returning %d items", list->size());
   return list.forget();
 }
 
 //virtual
 int
 MozMtpDatabase::getNumObjects(MtpStorageID aStorageID,
                               MtpObjectFormat aFormat,
                               MtpObjectHandle aParent)
 {
   MTP_LOG("");
 
+  // aStorageID == 0xFFFFFFFF for all storage
+  // aFormat    == 0          for all formats
+  // aParent    == 0xFFFFFFFF for objects with no parents
+  // aParent    == 0          for all objects
+
   int count = 0;
 
-  DbArray::size_type numEntries = mDb.Length();
-  DbArray::index_type entryIndex;
+  MutexAutoLock lock(mMutex);
+
+  ProtectedDbArray::size_type numEntries = mDb.Length();
+  ProtectedDbArray::index_type entryIndex;
   for (entryIndex = 1; entryIndex < numEntries; entryIndex++) {
     RefPtr<DbEntry> entry = mDb[entryIndex];
-    if (entry && entry->mStorageID == aStorageID) {
+    if (entry &&
+        (aStorageID == 0xFFFFFFFF || entry->mStorageID == aStorageID) &&
+        (aFormat == 0 || entry->mObjectFormat == aFormat) &&
+        (aParent == 0 || entry->mParent == aParent)) {
       count++;
     }
   }
 
+  MTP_LOG("  returning %d items", count);
   return count;
 }
 
 //virtual
 MtpObjectFormatList*
 MozMtpDatabase::getSupportedPlaybackFormats()
 {
   static const uint16_t init_data[] = {MTP_FORMAT_UNDEFINED, MTP_FORMAT_ASSOCIATION, MTP_FORMAT_PNG};
@@ -518,20 +902,22 @@ MozMtpDatabase::resetDeviceProperty(MtpD
   MTP_LOG("(NOT SUPPORTED)");
   return MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
 }
 
 void
 MozMtpDatabase::QueryEntries(MozMtpDatabase::MatchType aMatchType,
                              uint32_t aMatchField1,
                              uint32_t aMatchField2,
-                             DbArray &result)
+                             UnprotectedDbArray &result)
 {
-  DbArray::size_type numEntries = mDb.Length();
-  DbArray::index_type entryIdx;
+  MutexAutoLock lock(mMutex);
+
+  ProtectedDbArray::size_type numEntries = mDb.Length();
+  ProtectedDbArray::index_type entryIdx;
   RefPtr<DbEntry> entry;
 
   result.Clear();
 
   switch (aMatchType) {
 
     case MatchAll:
       for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
@@ -653,17 +1039,17 @@ MozMtpDatabase::getObjectPropertyList(Mt
         // select objects whose handle is aHandle and format matches aFormat
         matchType = MatchHandleFormat;
         matchField1 = aHandle;
         matchField2 = aFormat;
       }
     }
   }
 
-  DbArray result;
+  UnprotectedDbArray result;
   QueryEntries(matchType, matchField1, matchField2, result);
 
   const MtpObjectProperty *objectPropertyList;
   size_t numObjectProperties = 0;
   MtpObjectProperty objectProperty;
 
   if (aProperty == 0xffffffff) {
     // return all supported properties
@@ -671,18 +1057,18 @@ MozMtpDatabase::getObjectPropertyList(Mt
     objectPropertyList = sSupportedObjectProperties;
   } else {
     // return property indicated by aProperty
     numObjectProperties = 1;
     objectProperty = aProperty;
     objectPropertyList = &objectProperty;
   }
 
-  DbArray::size_type numEntries = result.Length();
-  DbArray::index_type entryIdx;
+  UnprotectedDbArray::size_type numEntries = result.Length();
+  UnprotectedDbArray::index_type entryIdx;
 
   aPacket.putUInt32(numObjectProperties * numEntries);
   for (entryIdx = 0; entryIdx < numEntries; entryIdx++) {
     RefPtr<DbEntry> entry = result[entryIdx];
 
     for (size_t propertyIdx = 0; propertyIdx < numObjectProperties; propertyIdx++) {
       aPacket.putUInt32(entry->mHandle);
       MtpObjectProperty prop = objectPropertyList[propertyIdx];
@@ -839,24 +1225,23 @@ MozMtpDatabase::deleteFile(MtpObjectHand
   RefPtr<DbEntry> entry = GetEntry(aHandle);
   if (!entry) {
     MTP_ERR("Invalid Handle: 0x%08x", aHandle);
     return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
   }
 
   MTP_LOG("Handle: 0x%08x '%s'", aHandle, entry->mPath.get());
 
-  //TODO: MtpServer::doDeleteObject calls us, and then calls a private
-  //      method (deletePath) which recursively deletes the path.
-  // We need to tell device storage that these files are gone
-
   // File deletion will happen in lower level implementation.
   // The only thing we need to do is removing the entry from the db.
   RemoveEntry(aHandle);
 
+  // Tell Device Storage that the file is gone.
+  FileWatcherNotify(entry, "deleted");
+
   return MTP_RESPONSE_OK;
 }
 
 #if 0
 //virtual
 MtpResponseCode
 MozMtpDatabase::moveFile(MtpObjectHandle aHandle, MtpObjectHandle aNewParent)
 {
--- a/dom/system/gonk/MozMtpDatabase.h
+++ b/dom/system/gonk/MozMtpDatabase.h
@@ -4,27 +4,32 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_system_mozmtpdatabase_h__
 #define mozilla_system_mozmtpdatabase_h__
 
 #include "MozMtpCommon.h"
 
+#include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
-#include "nsISupportsImpl.h"
 #include "nsString.h"
+#include "nsIThread.h"
 #include "nsTArray.h"
 
+class DeviceStorageFile;
+
 BEGIN_MTP_NAMESPACE // mozilla::system::mtp
 
+class RefCountedMtpServer;
+
 using namespace android;
 
-class MozMtpDatabase : public MtpDatabase
+class MozMtpDatabase MOZ_FINAL : public MtpDatabase
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozMtpDatabase)
 
   MozMtpDatabase();
   virtual ~MozMtpDatabase();
 
   // called from SendObjectInfo to reserve a database entry for the incoming file
@@ -106,60 +111,156 @@ public:
 
   virtual void sessionStarted();
 
   virtual void sessionEnded();
 
   void AddStorage(MtpStorageID aStorageID, const char* aPath, const char *aName);
   void RemoveStorage(MtpStorageID aStorageID);
 
+  void FileWatcherUpdate(RefCountedMtpServer* aMtpServer,
+                         DeviceStorageFile* aFile,
+                         const nsACString& aEventType);
+
 private:
 
   struct DbEntry
   {
+    DbEntry()
+      : mHandle(0),
+        mStorageID(0),
+        mObjectFormat(MTP_FORMAT_DEFINED),
+        mParent(0),
+        mObjectSize(0),
+        mDateCreated(0),
+        mDateModified(0) {}
+
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DbEntry)
 
     MtpObjectHandle mHandle;        // uint32_t
     MtpStorageID    mStorageID;     // uint32_t
     nsCString       mObjectName;
     MtpObjectFormat mObjectFormat;  // uint16_t
     MtpObjectHandle mParent;        // uint32_t
     uint64_t        mObjectSize;
     nsCString       mDisplayName;
     nsCString       mPath;
     PRTime          mDateCreated;
     PRTime          mDateModified;
   };
-  typedef nsTArray<mozilla::RefPtr<DbEntry> > DbArray;
+
+  template<class T>
+  class ProtectedTArray : private nsTArray<T>
+  {
+  public:
+    typedef T elem_type;
+    typedef typename nsTArray<T>::size_type size_type;
+    typedef typename nsTArray<T>::index_type index_type;
+    typedef nsTArray<T> base_type;
+
+    static const index_type NoIndex = base_type::NoIndex;
+
+    ProtectedTArray(mozilla::Mutex& aMutex)
+      : mMutex(aMutex)
+    {}
+
+    size_type Length() const
+    {
+      // GRR - This assert prints to stderr and won't show up in logcat.
+      mMutex.AssertCurrentThreadOwns();
+      return base_type::Length();
+    }
+
+    template <class Item>
+    elem_type* AppendElement(const Item& aItem)
+    {
+      mMutex.AssertCurrentThreadOwns();
+      return base_type::AppendElement(aItem);
+    }
 
-  DbArray mDb;
+    void Clear()
+    {
+      mMutex.AssertCurrentThreadOwns();
+      base_type::Clear();
+    }
+
+    void RemoveElementAt(index_type aIndex)
+    {
+      mMutex.AssertCurrentThreadOwns();
+      base_type::RemoveElementAt(aIndex);
+    }
+
+    elem_type& operator[](index_type aIndex)
+    {
+      mMutex.AssertCurrentThreadOwns();
+      return base_type::ElementAt(aIndex);
+    }
+
+    const elem_type& operator[](index_type aIndex) const
+    {
+      mMutex.AssertCurrentThreadOwns();
+      return base_type::ElementAt(aIndex);
+    }
+
+  private:
+    mozilla::Mutex& mMutex;
+  };
+  typedef nsTArray<mozilla::RefPtr<DbEntry> > UnprotectedDbArray;
+  typedef ProtectedTArray<mozilla::RefPtr<DbEntry> > ProtectedDbArray;
+
+  struct StorageEntry
+  {
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StorageEntry)
+
+    MtpStorageID  mStorageID;
+    nsCString     mStoragePath;
+    nsCString     mStorageName;
+  };
+  typedef ProtectedTArray<mozilla::RefPtr<StorageEntry> > StorageArray;
 
   enum MatchType
   {
     MatchAll,
     MatchHandle,
     MatchParent,
     MatchFormat,
     MatchHandleFormat,
     MatchParentFormat,
   };
 
-
-  void AddEntry(DbEntry *aEntry);
+  void AddEntry(DbEntry* aEntry);
+  void DumpEntries(const char* aLabel);
+  MtpObjectHandle FindEntryByPath(const nsACString& aPath);
   mozilla::TemporaryRef<DbEntry> GetEntry(MtpObjectHandle aHandle);
   void RemoveEntry(MtpObjectHandle aHandle);
   void QueryEntries(MatchType aMatchType, uint32_t aMatchField1,
-                    uint32_t aMatchField2, DbArray& aResult);
+                    uint32_t aMatchField2, UnprotectedDbArray& aResult);
 
   nsCString BaseName(const nsCString& aPath);
 
 
   MtpObjectHandle GetNextHandle()
   {
     return mDb.Length();
   }
 
   void AddDirectory(MtpStorageID aStorageID, const char *aPath, MtpObjectHandle aParent);
+
+  MtpObjectHandle CreateEntryForFile(const nsACString& aPath, DeviceStorageFile* aFile);
+
+  StorageArray::index_type FindStorage(MtpStorageID aStorageID);
+  MtpStorageID FindStorageIDFor(const nsACString& aPath, nsCSubstring& aRemainder);
+  void FileWatcherNotify(DbEntry* aEntry, const char* aEventType);
+
+  // We need a mutex to protext mDb and mStorage. The MTP server runs on a
+  // dedicated thread, and it updates/accesses mDb. When files are updated
+  // through DeviceStorage, we need to update/access mDb and mStorage as well
+  // (from a non-MTP server thread).
+  mozilla::Mutex mMutex;
+  ProtectedDbArray mDb;
+  StorageArray mStorage;
+
+  bool mBeginSendObjectCalled;
 };
 
 END_MTP_NAMESPACE
 
 #endif // mozilla_system_mozmtpdatabase_h__
--- a/dom/system/gonk/MozMtpServer.cpp
+++ b/dom/system/gonk/MozMtpServer.cpp
@@ -14,46 +14,196 @@
 #include <errno.h>
 #include <stdio.h>
 #include <unistd.h>
 
 #include <cutils/properties.h>
 #include <private/android_filesystem_config.h>
 
 #include "base/message_loop.h"
+#include "DeviceStorage.h"
 #include "mozilla/FileUtils.h"
+#include "mozilla/LazyIdleThread.h"
 #include "mozilla/Scoped.h"
+#include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "nsAutoPtr.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsISupportsImpl.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 
 #include "Volume.h"
 
+#define DEFAULT_THREAD_TIMEOUT_MS 30000
+
 using namespace android;
 using namespace mozilla;
 BEGIN_MTP_NAMESPACE
 
+class FileWatcherUpdateRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  FileWatcherUpdateRunnable(MozMtpDatabase* aMozMtpDatabase,
+                            RefCountedMtpServer* aMtpServer,
+                            DeviceStorageFile* aFile,
+                            const nsACString& aEventType)
+    : mMozMtpDatabase(aMozMtpDatabase),
+      mMtpServer(aMtpServer),
+      mFile(aFile),
+      mEventType(aEventType)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    // Runs on the FileWatcherUpdate->mIOThread
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    mMozMtpDatabase->FileWatcherUpdate(mMtpServer, mFile, mEventType);
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<MozMtpDatabase> mMozMtpDatabase;
+  nsRefPtr<RefCountedMtpServer> mMtpServer;
+  nsRefPtr<DeviceStorageFile> mFile;
+  nsCString mEventType;
+};
+
+// The FileWatcherUpdate class listens for file-watcher-update events
+// and tells the MtpServer about the changes.
+class FileWatcherUpdate MOZ_FINAL : public nsIObserver
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  FileWatcherUpdate(MozMtpServer* aMozMtpServer)
+    : mMozMtpServer(aMozMtpServer)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    mIOThread = new LazyIdleThread(
+      DEFAULT_THREAD_TIMEOUT_MS,
+      NS_LITERAL_CSTRING("MTP FileWatcherUpdate"));
+
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    obs->AddObserver(this, "file-watcher-update", false);
+  }
+
+  ~FileWatcherUpdate()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    obs->RemoveObserver(this, "file-watcher-update");
+  }
+
+  NS_IMETHOD
+  Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (strcmp(aTopic, "file-watcher-update")) {
+      // We're only interested in file-watcher-update events
+      return NS_OK;
+    }
+
+    NS_ConvertUTF16toUTF8 eventType(aData);
+    if (!eventType.EqualsLiteral("created") && !eventType.EqualsLiteral("deleted")) {
+      // MTP doesn't have a modified notification.
+      return NS_OK;
+    }
+
+    DeviceStorageFile* file = static_cast<DeviceStorageFile*>(aSubject);
+    file->Dump("file-watcher-update");
+    MTP_LOG("file-watcher-update: file %s %s",
+            NS_LossyConvertUTF16toASCII(file->mPath).get(),
+            eventType.get());
+
+    nsRefPtr<MozMtpDatabase> mozMtpDatabase = mMozMtpServer->GetMozMtpDatabase();
+    nsRefPtr<RefCountedMtpServer> mtpServer = mMozMtpServer->GetMtpServer();
+
+    // We're not supposed to perform I/O on the main thread, so punt the
+    // notification (which will write to /dev/mtp_usb) to an I/O Thread.
+
+    nsRefPtr<FileWatcherUpdateRunnable> r =
+      new FileWatcherUpdateRunnable(mozMtpDatabase, mtpServer, file, eventType);
+    mIOThread->Dispatch(r, NS_DISPATCH_NORMAL);
+
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<MozMtpServer> mMozMtpServer;
+  nsCOMPtr<nsIThread> mIOThread;
+};
+NS_IMPL_ISUPPORTS(FileWatcherUpdate, nsIObserver)
+static StaticRefPtr<FileWatcherUpdate> sFileWatcherUpdate;
+
+class AllocFileWatcherUpdateRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  AllocFileWatcherUpdateRunnable(MozMtpServer* aMozMtpServer)
+    : mMozMtpServer(aMozMtpServer)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    sFileWatcherUpdate = new FileWatcherUpdate(mMozMtpServer);
+    return NS_OK;
+  }
+private:
+  nsRefPtr<MozMtpServer> mMozMtpServer;
+};
+
+class FreeFileWatcherUpdateRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  FreeFileWatcherUpdateRunnable(MozMtpServer* aMozMtpServer)
+    : mMozMtpServer(aMozMtpServer)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    sFileWatcherUpdate = nullptr;
+    return NS_OK;
+  }
+private:
+  nsRefPtr<MozMtpServer> mMozMtpServer;
+};
+
 class MtpServerRunnable : public nsRunnable
 {
 public:
   MtpServerRunnable(int aMtpUsbFd, MozMtpServer* aMozMtpServer)
     : mMozMtpServer(aMozMtpServer),
       mMtpUsbFd(aMtpUsbFd)
   {
   }
 
   nsresult Run()
   {
     nsRefPtr<RefCountedMtpServer> server = mMozMtpServer->GetMtpServer();
 
+    DebugOnly<nsresult> rv =
+      NS_DispatchToMainThread(new AllocFileWatcherUpdateRunnable(mMozMtpServer));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
     MTP_LOG("MozMtpServer started");
     server->run();
     MTP_LOG("MozMtpServer finished");
 
+    rv = NS_DispatchToMainThread(new FreeFileWatcherUpdateRunnable(mMozMtpServer));
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
     return NS_OK;
   }
 
 private:
   nsRefPtr<MozMtpServer> mMozMtpServer;
   ScopedClose mMtpUsbFd; // We want to hold this open while the server runs
 };