Bug 982006 - Make GetDownloadDirectory return error for both SD card missing and SD card busy. r=bz, a=1.4+
authorGhislain 'Aus' Lacroix <glacroix@mozilla.com>
Mon, 31 Mar 2014 12:10:44 -0700
changeset 191501 15312c8b1528ade4431118b56bf55bd57fbeffba
parent 191500 73cbf021d9fed31561dfee457b42acf96ddf334f
child 191502 acdc21019fce9af33cbf9ff14466cfa6aeb90034
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz, 1
bugs982006
milestone30.0a2
Bug 982006 - Make GetDownloadDirectory return error for both SD card missing and SD card busy. r=bz, a=1.4+
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