Bug 982006 - Make GetDownloadDirectory return error for both SD card missing and SD card busy. r=bz
authorGhislain 'Aus' Lacroix <glacroix@mozilla.com>
Mon, 31 Mar 2014 12:10:44 -0700
changeset 176432 ae1fe345280677dd22355833b27848d34ece49c5
parent 176344 56dffd1a24f819e65c1b7aef0b9e2c9e0205b594
child 176433 cd8094ea37af9f658509720ec7747101d738a75b
push id26524
push userryanvm@gmail.com
push dateTue, 01 Apr 2014 20:44:18 +0000
treeherdermozilla-central@0ff6afce0133 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs982006
milestone31.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 982006 - Make GetDownloadDirectory return error for both SD card missing and SD card busy. r=bz
dom/downloads/src/DownloadsAPI.js
dom/downloads/src/DownloadsAPI.jsm
dom/locales/en-US/chrome/nsWebBrowserPersist.properties
uriloader/exthandler/nsExternalHelperAppService.cpp
uriloader/exthandler/nsExternalHelperAppService.h
--- a/dom/downloads/src/DownloadsAPI.js
+++ b/dom/downloads/src/DownloadsAPI.js
@@ -314,17 +314,18 @@ DOMDownloadImpl.prototype = {
     props.forEach((prop) => {
       if (aDownload[prop] && (aDownload[prop] != this[prop])) {
         this[prop] = aDownload[prop];
         changed = true;
       }
     });
 
     if (aDownload.error) {
-      this.error = new this._window.DOMError("DownloadError", aDownload.error);
+      this.error =
+        new this._window.DOMError("DownloadError", aDownload.error.result);
     } else {
       this.error = null;
     }
 
     // The visible state has not changed, so no need to fire an event.
     if (!changed) {
       return;
     }
--- a/dom/downloads/src/DownloadsAPI.jsm
+++ b/dom/downloads/src/DownloadsAPI.jsm
@@ -91,17 +91,17 @@ let DownloadsAPI = {
       currentBytes: aDownload.currentBytes,
       url: aDownload.source.url,
       path: aDownload.target.path,
       contentType: aDownload.contentType,
       startTime: aDownload.startTime.getTime()
     };
 
     if (aDownload.error) {
-      res.error = aDownload.error.name;
+      res.error = aDownload.error;
     }
 
     res.id = this.downloadId(aDownload);
 
     // The state of the download. Can be any of "downloading", "stopped",
     // "succeeded", finalized".
 
     // Default to "stopped"
--- a/dom/locales/en-US/chrome/nsWebBrowserPersist.properties
+++ b/dom/locales/en-US/chrome/nsWebBrowserPersist.properties
@@ -3,14 +3,15 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 readError=%S could not be saved, because the source file could not be read.\n\nTry again later, or contact the server administrator.
 writeError=%S could not be saved, because an unknown error occurred.\n\nTry saving to a different location.
 launchError=%S could not be opened, because an unknown error occurred.\n\nTry saving to disk first and then opening the file.
 diskFull=There is not enough room on the disk to save %S.\n\nRemove unnecessary files from the disk and try again, or try saving in a different location.
 readOnly=%S could not be saved, because the disk, folder, or file is write-protected.\n\nWrite-enable the disk and try again, or try saving in a different location.
 accessError=%S could not be saved, because you cannot change the contents of that folder.\n\nChange the folder properties and try again, or try saving in a different location.
-accessErrorSD=No SD card.\n\nAn SD card is required to download %S.
+SDAccessErrorCardReadOnly=Cannot download file because the SD card is in use.
+SDAccessErrorCardMissing=Cannot download file because the SD card is missing.
 helperAppNotFound=%S could not be opened, because the associated helper application does not exist. Change the association in your preferences.
 noMemory=There is not sufficient memory to complete the action you requested.\n\nQuit some applications and try again.
 title=Downloading %S
 fileAlreadyExistsError=%S could not be saved, because a file already exists with the same name as the '_files' directory.\n\nTry saving to a different location.
 fileNameTooLongError=%S could not be saved, because the file name was too long.\n\nTry saving with a shorter file name.
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -82,21 +82,17 @@
 #include "nsITextToSubURI.h" // to unescape the filename
 #include "nsIMIMEHeaderParam.h"
 
 #include "nsIWindowWatcher.h"
 
 #include "nsIDownloadHistory.h" // to mark downloads as visited
 #include "nsDocShellCID.h"
 
-#include "nsIDOMWindow.h"
-#include "nsIDocShell.h"
-
 #include "nsCRT.h"
-
 #include "nsLocalHandlerApp.h"
 
 #include "nsIRandomGenerator.h"
 
 #include "ContentChild.h"
 #include "nsXULAppAPI.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocShellTreeOwner.h"
@@ -287,33 +283,42 @@ static bool GetFilenameAndExtensionFromC
   return handleExternally;
 }
 
 /**
  * Obtains the directory to use.  This tends to vary per platform, and
  * needs to be consistent throughout our codepaths. For platforms where
  * helper apps use the downloads directory, this should be kept in
  * sync with nsDownloadManager.cpp
+ *
+ * Optionally skip availability of the directory and storage.
  */
-static nsresult GetDownloadDirectory(nsIFile **_directory)
+static nsresult GetDownloadDirectory(nsIFile **_directory,
+                                     bool aSkipChecks = false)
 {
   nsCOMPtr<nsIFile> dir;
 #ifdef XP_MACOSX
   // On OS X, we first try to get the users download location, if it's set.
   switch (Preferences::GetInt(NS_PREF_DOWNLOAD_FOLDERLIST, -1)) {
     case NS_FOLDER_VALUE_DESKTOP:
       (void) NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(dir));
       break;
     case NS_FOLDER_VALUE_CUSTOM:
       {
         Preferences::GetComplex(NS_PREF_DOWNLOAD_DIR,
                                 NS_GET_IID(nsIFile),
                                 getter_AddRefs(dir));
         if (!dir) break;
 
+        // If we're not checking for availability we're done.
+        if (aSkipChecks) {
+          dir.forget(_directory);
+          return NS_OK;
+        }
+
         // We have the directory, and now we need to make sure it exists
         bool dirExists = false;
         (void) dir->Exists(&dirExists);
         if (dirExists) break;
 
         nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0755);
         if (NS_FAILED(rv)) {
           dir = nullptr;
@@ -337,23 +342,45 @@ static nsresult GetDownloadDirectory(nsI
   // We need to check with the volume manager which storage point is
   // available.
 
   // Pick the default storage in case multiple (internal and external) ones
   // are available.
   nsString storageName;
   nsDOMDeviceStorage::GetDefaultStorageName(NS_LITERAL_STRING("sdcard"),
                                             storageName);
-  NS_ENSURE_TRUE(!storageName.IsEmpty(), NS_ERROR_FAILURE);
 
   DeviceStorageFile dsf(NS_LITERAL_STRING("sdcard"),
                         storageName,
                         NS_LITERAL_STRING("downloads"));
   NS_ENSURE_TRUE(dsf.mFile, NS_ERROR_FILE_ACCESS_DENIED);
-  NS_ENSURE_TRUE(dsf.IsAvailable(), NS_ERROR_FILE_ACCESS_DENIED);
+
+  // If we're not checking for availability we're done.
+  if (aSkipChecks) {
+    dsf.mFile.forget(_directory);
+    return NS_OK;
+  }
+
+  // Check device storage status before continuing.
+  nsString storageStatus;
+  dsf.GetStatus(storageStatus);
+
+  // If we get an "unavailable" status, it means the sd card is not present.
+  // We'll also catch internal errors by looking for an empty string and assume
+  // the SD card isn't present when this occurs.
+  if (storageStatus.EqualsLiteral("unavailable") ||
+      storageStatus.IsEmpty()) {
+    return NS_ERROR_FILE_NOT_FOUND;
+  }
+
+  // If we get a status other than 'available' here it means the card is busy
+  // because it's mounted via USB or it is being formatted.
+  if (!storageStatus.EqualsLiteral("available")) {
+    return NS_ERROR_FILE_ACCESS_DENIED;
+  }
 
   bool alreadyThere;
   nsresult rv = dsf.mFile->Exists(&alreadyThere);
   NS_ENSURE_SUCCESS(rv, rv);
   if (!alreadyThere) {
     rv = dsf.mFile->Create(nsIFile::DIRECTORY_TYPE, 0770);
     NS_ENSURE_SUCCESS(rv, rv);
   }
@@ -361,21 +388,27 @@ static nsresult GetDownloadDirectory(nsI
 #elif defined(ANDROID)
   // On mobile devices, we are avoiding exposing users to the file
   // system, and don't save downloads to temp directories
 
   // On Android we only return something if we have and SD-card
   char* downloadDir = getenv("DOWNLOADS_DIRECTORY");
   nsresult rv;
   if (downloadDir) {
-    nsCOMPtr<nsIFile> ldir; 
+    nsCOMPtr<nsIFile> ldir;
     rv = NS_NewNativeLocalFile(nsDependentCString(downloadDir),
                                true, getter_AddRefs(ldir));
     NS_ENSURE_SUCCESS(rv, rv);
     dir = do_QueryInterface(ldir);
+
+    // If we're not checking for availability we're done.
+    if (aSkipChecks) {
+      dir.forget(_directory);
+      return NS_OK;
+    }
   }
   else {
     return NS_ERROR_FAILURE;
   }
 #elif defined(XP_WIN)
   // On metro we want to be able to search opened files and the temp directory
   // is exlcuded in searches.
   nsresult rv;
@@ -1616,22 +1649,35 @@ NS_IMETHODIMP nsExternalAppHandler::OnSt
 
   // At this point, the child process has done everything it can usefully do
   // for OnStartRequest.
   if (XRE_GetProcessType() == GeckoProcessType_Content)
      return NS_OK;
 
   rv = SetUpTempFile(aChannel);
   if (NS_FAILED(rv)) {
+    nsresult transferError = rv;
+
+    rv = CreateFailedTransfer(aChannel && NS_UsePrivateBrowsing(aChannel));
+#ifdef PR_LOGGING
+    if (NS_FAILED(rv)) {
+      LOG(("Failed to create transfer to report failure."
+           "Will fallback to prompter!"));
+    }
+#endif
+
     mCanceled = true;
-    request->Cancel(rv);
+    request->Cancel(transferError);
+
     nsAutoString path;
     if (mTempFile)
       mTempFile->GetPath(path);
-    SendStatusChange(kWriteError, rv, request, path);
+
+    SendStatusChange(kWriteError, transferError, request, path);
+
     return NS_OK;
   }
 
   // Inform channel it is open on behalf of a download to prevent caching.
   nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(aChannel);
   if (httpInternal) {
     httpInternal->SetChannelIsForDownload(true);
   }
@@ -1781,18 +1827,19 @@ void nsExternalAppHandler::SendStatusCha
         // Attempt to write to read/only file.
         msgId.AssignLiteral("readOnly");
         break;
 
     case NS_ERROR_FILE_ACCESS_DENIED:
         if (type == kWriteError) {
           // Attempt to write without sufficient permissions.
 #if defined(ANDROID)
-          // On Android, assume the SD card is missing or read-only
-          msgId.AssignLiteral("accessErrorSD");
+          // On Android (and Gonk), this means the SD card is present but
+          // unavailable (read-only).
+          msgId.AssignLiteral("SDAccessErrorCardReadOnly");
 #else
           msgId.AssignLiteral("accessError");
 #endif
         }
         else
         {
           msgId.AssignLiteral("launchError");
         }
@@ -1801,16 +1848,24 @@ void nsExternalAppHandler::SendStatusCha
     case NS_ERROR_FILE_NOT_FOUND:
     case NS_ERROR_FILE_TARGET_DOES_NOT_EXIST:
     case NS_ERROR_FILE_UNRECOGNIZED_PATH:
         // Helper app not found, let's verify this happened on launch
         if (type == kLaunchError) {
           msgId.AssignLiteral("helperAppNotFound");
           break;
         }
+#if defined(ANDROID)
+        else if (type == kWriteError) {
+          // On Android (and Gonk), this means the SD card is missing (not in
+          // SD slot).
+          msgId.AssignLiteral("SDAccessErrorCardMissing");
+          break;
+        }
+#endif
         // fall through
 
     default:
         // Generic read/write/launch error message.
         switch(type)
         {
         case kReadError:
           msgId.AssignLiteral("readError");
@@ -2132,16 +2187,49 @@ nsresult nsExternalAppHandler::CreateTra
   // true and OnSaveComplete has been called.
   if (mStopRequestIssued && !mSaver && mTransfer) {
     NotifyTransfer(NS_OK);
   }
 
   return rv;
 }
 
+nsresult nsExternalAppHandler::CreateFailedTransfer(bool aIsPrivateBrowsing)
+{
+  nsresult rv;
+  nsCOMPtr<nsITransfer> transfer =
+    do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // If we don't have a download directory we're kinda screwed but it's OK
+  // we'll still report the error via the prompter.
+  nsCOMPtr<nsIFile> pseudoFile;
+  rv = GetDownloadDirectory(getter_AddRefs(pseudoFile), true);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Append the default suggested filename. If the user restarts the transfer
+  // we will re-trigger a filename check anyway to ensure that it is unique.
+  rv = pseudoFile->Append(mSuggestedFileName);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURI> pseudoTarget;
+  rv = NS_NewFileURI(getter_AddRefs(pseudoTarget), pseudoFile);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = transfer->Init(mSourceUrl, pseudoTarget, EmptyString(),
+                      mMimeInfo, mTimeDownloadStarted, nullptr, this,
+                      aIsPrivateBrowsing);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Our failed transfer is ready.
+  mTransfer = transfer.forget();
+
+  return NS_OK;
+}
+
 nsresult nsExternalAppHandler::SaveDestinationAvailable(nsIFile * aFile)
 {
   if (aFile)
     ContinueSave(aFile);
   else
     Cancel(NS_BINDING_ABORTED);
 
   return NS_OK;
--- a/uriloader/exthandler/nsExternalHelperAppService.h
+++ b/uriloader/exthandler/nsExternalHelperAppService.h
@@ -351,16 +351,22 @@ protected:
   void RetargetLoadNotifications(nsIRequest *request);
   /**
    * Once the user tells us how they want to dispose of the content
    * create an nsITransfer so they know what's going on. If this fails, the
    * caller MUST call Cancel.
    */
   nsresult CreateTransfer();
 
+  /**
+   * If we fail to create the necessary temporary file to initiate a transfer
+   * we will report the failure by creating a failed nsITransfer.
+   */
+  nsresult CreateFailedTransfer(bool aIsPrivateBrowsing);
+
   /*
    * The following two functions are part of the split of SaveToDisk
    * to make it async, and works as following:
    *
    *    SaveToDisk    ------->   RequestSaveDestination
    *                                     .
    *                                     .
    *                                     v