Bug 809948 - [Webapps] Check for enough device storage before starting app download; r=fabrice
authorFernando Jiménez <ferjmoreno@gmail.com>
Thu, 15 Nov 2012 10:35:37 +0100
changeset 113371 d95d4e953df5cec58abbf8116fe0b4be1f9acf60
parent 113370 c6188b322a68fc4e6bb3e8bd2ed0cecd3c6c1b7b
child 113372 cbe5fc3e1ce5dd9f2039fd1827bbdcc6885b2906
push id23869
push useremorley@mozilla.com
push dateThu, 15 Nov 2012 16:18:16 +0000
treeherdermozilla-central@a37525d304d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs809948
milestone19.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 809948 - [Webapps] Check for enough device storage before starting app download; r=fabrice
dom/apps/src/Webapps.js
dom/apps/src/Webapps.jsm
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -574,17 +574,17 @@ WebappsApplication.prototype = {
             case "installed":
               let app = msg.app;
               this.progress = app.progress || 0;
               this.downloadAvailable = app.downloadAvailable;
               this.downloading = app.downloading;
               this.readyToApplyDownload = app.readyToApplyDownload;
               this.downloadSize = app.downloadSize || 0;
               this.installState = app.installState;
-              this.manifest = app.manifest;
+              this._manifest = msg.manifest;
               this._fireEvent("downloadsuccess", this._ondownloadsuccess);
               this._fireEvent("downloadapplied", this._ondownloadapplied);
               break;
             case "canceled":
               app = msg.app;
               this.progress = app.progress || 0;
               this.downloadAvailable = app.downloadAvailable;
               this.downloading = app.downloading;
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1295,27 +1295,28 @@ this.DOMApplicationRegistry = {
     // - add the new app to the registry.
     // If we fail at any step, we backout the previous ones and return an error.
 
     debug(JSON.stringify(aApp));
 
     let id = this._appIdForManifestURL(aApp.manifestURL);
     let app = this.webapps[id];
 
+    let self = this;
     // Removes the directory we created, and sends an error to the DOM side.
     function cleanup(aError) {
       debug("Cleanup: " + aError);
       let dir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
       try {
         dir.remove(true);
       } catch (e) { }
-        this.broadcastMessage("Webapps:PackageEvent",
+        self.broadcastMessage("Webapps:PackageEvent",
                               { type: "error",
                                 manifestURL:  aApp.manifestURL,
-                                error: aError});
+                                error: aError });
     }
 
     function getInferedStatus() {
       // XXX Update once we have digital signatures (bug 772365)
       return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
     }
 
     function getAppStatus(aManifest) {
@@ -1330,104 +1331,129 @@ this.DOMApplicationRegistry = {
     function checkAppStatus(aManifest) {
       if (Services.prefs.getBoolPref("dom.mozApps.dev_mode")) {
         return true;
       }
 
       return (AppsUtils.getAppManifestStatus(aManifest) <= getInferedStatus());
     }
 
-    debug("About to download " + aManifest.fullPackagePath());
+    function download() {
+      debug("About to download " + aManifest.fullPackagePath());
+
+      let requestChannel = NetUtil.newChannel(aManifest.fullPackagePath())
+                                  .QueryInterface(Ci.nsIHttpChannel);
+      self.downloads[aApp.manifestURL] =
+        { channel:requestChannel,
+          appId: id,
+          previousState: aIsUpdate ? "installed" : "pending"
+        };
+      requestChannel.notificationCallbacks = {
+        QueryInterface: function notifQI(aIID) {
+          if (aIID.equals(Ci.nsISupports)          ||
+              aIID.equals(Ci.nsIProgressEventSink))
+            return this;
 
-    let requestChannel = NetUtil.newChannel(aManifest.fullPackagePath())
-                                .QueryInterface(Ci.nsIHttpChannel);
-    this.downloads[aApp.manifestURL] =
-      { channel:requestChannel,
-        appId: id,
-        previousState: aIsUpdate ? "installed" : "pending"
-      };
-    requestChannel.notificationCallbacks = {
-      QueryInterface: function notifQI(aIID) {
-        if (aIID.equals(Ci.nsISupports)          ||
-            aIID.equals(Ci.nsIProgressEventSink))
-          return this;
+          throw Cr.NS_ERROR_NO_INTERFACE;
+        },
+        getInterface: function notifGI(aIID) {
+          return this.QueryInterface(aIID);
+        },
+        onProgress: function notifProgress(aRequest, aContext,
+                                           aProgress, aProgressMax) {
+          debug("onProgress: " + aProgress + "/" + aProgressMax);
+          app.progress = aProgress;
+          self.broadcastMessage("Webapps:PackageEvent",
+                                { type: "progress",
+                                  manifestURL: aApp.manifestURL,
+                                  progress: aProgress });
+        },
+        onStatus: function notifStatus(aRequest, aContext, aStatus, aStatusArg) { }
+      }
+      NetUtil.asyncFetch(requestChannel,
+      function(aInput, aResult, aRequest) {
+        if (!Components.isSuccessCode(aResult)) {
+          // We failed to fetch the zip.
+          cleanup("NETWORK_ERROR");
+          return;
+        }
+        // Copy the zip on disk.
+        let zipFile = FileUtils.getFile("TmpD",
+                                        ["webapps", id, "application.zip"], true);
+        let ostream = FileUtils.openSafeFileOutputStream(zipFile);
+        NetUtil.asyncCopy(aInput, ostream, function (aResult) {
+          if (!Components.isSuccessCode(aResult)) {
+            // We failed to save the zip.
+            cleanup("DOWNLOAD_ERROR");
+            return;
+          }
 
-        throw Cr.NS_ERROR_NO_INTERFACE;
-      },
-      getInterface: function notifGI(aIID) {
-        return this.QueryInterface(aIID);
-      },
-      onProgress: function notifProgress(aRequest, aContext,
-                                         aProgress, aProgressMax) {
-        debug("onProgress: " + aProgress + "/" + aProgressMax);
-        app.progress = aProgress;
-        DOMApplicationRegistry.broadcastMessage("Webapps:PackageEvent",
-                                                { type: "progress",
-                                                  manifestURL: aApp.manifestURL,
-                                                  progress: aProgress });
-      },
-      onStatus: function notifStatus(aRequest, aContext, aStatus, aStatusArg) { }
-    }
+          let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
+                          .createInstance(Ci.nsIZipReader);
+          try {
+            zipReader.open(zipFile);
+            if (!zipReader.hasEntry("manifest.webapp")) {
+              throw "No manifest.webapp found.";
+            }
+
+            let istream = zipReader.getInputStream("manifest.webapp");
+
+            // Obtain a converter to read from a UTF-8 encoded input stream.
+            let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                            .createInstance(Ci.nsIScriptableUnicodeConverter);
+            converter.charset = "UTF-8";
+
+            let manifest = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(istream,
+                                                                 istream.available()) || ""));
+
+            if (!AppsUtils.checkManifest(manifest, aApp.installOrigin)) {
+              throw "INVALID_MANIFEST";
+            }
 
-    NetUtil.asyncFetch(requestChannel,
-    function(aInput, aResult, aRequest) {
-      if (!Components.isSuccessCode(aResult)) {
-        // We failed to fetch the zip.
-        cleanup("NETWORK_ERROR");
+            if (!checkAppStatus(manifest)) {
+              throw "INVALID_SECURITY_LEVEL";
+            }
+
+            if (aOnSuccess) {
+              aOnSuccess(id, manifest);
+            }
+            delete self.downloads[aApp.manifestURL];
+          } catch (e) {
+            // XXX we may need new error messages.
+            cleanup(e);
+          } finally {
+            zipReader.close();
+          }
+        });
+      });
+    };
+
+    let browser = Services.wm.getMostRecentWindow("navigator:browser");
+    let deviceStorage = browser.getContentWindow().navigator
+                               .getDeviceStorage("apps");
+    let req = deviceStorage.stat();
+    req.onsuccess = req.onerror = function statResult(e) {
+      // Even if we could not retrieve the device storage free space, we try
+      // to download the package.
+      if (!e.target.result) {
+        download();
         return;
       }
-      // Copy the zip on disk.
-      let zipFile = FileUtils.getFile("TmpD",
-                                      ["webapps", id, "application.zip"], true);
-      let ostream = FileUtils.openSafeFileOutputStream(zipFile);
-      NetUtil.asyncCopy(aInput, ostream, function (aResult) {
-        if (!Components.isSuccessCode(aResult)) {
-          // We failed to save the zip.
-          cleanup("DOWNLOAD_ERROR");
+
+      let freeBytes = e.target.result.freeBytes;
+      if (freeBytes) {
+        debug("Free storage: " + freeBytes + ". Download size: " +
+              aApp.downloadSize);
+        if (freeBytes <= aApp.downloadSize) {
+          cleanup("INSUFFICIENT_STORAGE");
           return;
         }
-
-        let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
-                        .createInstance(Ci.nsIZipReader);
-        try {
-          zipReader.open(zipFile);
-          if (!zipReader.hasEntry("manifest.webapp")) {
-            throw "No manifest.webapp found.";
-          }
-
-          let istream = zipReader.getInputStream("manifest.webapp");
-
-          // Obtain a converter to read from a UTF-8 encoded input stream.
-          let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
-                          .createInstance(Ci.nsIScriptableUnicodeConverter);
-          converter.charset = "UTF-8";
-
-          let manifest = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(istream,
-                                                               istream.available()) || ""));
-
-          if (!AppsUtils.checkManifest(manifest, aApp.installOrigin)) {
-            throw "INVALID_MANIFEST";
-          }
-
-          if (!checkAppStatus(manifest)) {
-            throw "INVALID_SECURITY_LEVEL";
-          }
-
-          if (aOnSuccess) {
-            aOnSuccess(id, manifest);
-          }
-          delete DOMApplicationRegistry.downloads[aApp.manifestURL];
-        } catch (e) {
-          // XXX we may need new error messages.
-          cleanup(e);
-        } finally {
-          zipReader.close();
-        }
-      });
-    });
+      }
+      download();
+    }
   },
 
   uninstall: function(aData, aMm) {
     for (let id in this.webapps) {
       let app = this.webapps[id];
       if (app.origin != aData.origin) {
         continue;
       }