Bug 915879 - Wait for _writeFile to finish before continuing. r=fabrice, a=lsblakk
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Fri, 09 May 2014 08:34:00 -0400
changeset 192249 c81fd101b5a2
parent 192248 eb84c3250ae3
child 192250 afcec252f3b7
push id3544
push userryanvm@gmail.com
push date2014-05-12 19:29 +0000
treeherdermozilla-beta@afcec252f3b7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice, lsblakk
bugs915879
milestone30.0
Bug 915879 - Wait for _writeFile to finish before continuing. r=fabrice, a=lsblakk
dom/apps/src/Webapps.jsm
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1288,18 +1288,19 @@ this.DOMApplicationRegistry = {
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
     });
     AppDownloadManager.remove(aManifestURL);
   },
 
-  startDownload: function startDownload(aManifestURL) {
+  startDownload: Task.async(function*(aManifestURL) {
     debug("startDownload for " + aManifestURL);
+
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
     if (!app) {
       debug("startDownload: No app found for " + aManifestURL);
       return;
     }
 
     if (app.downloading) {
@@ -1336,89 +1337,90 @@ this.DOMApplicationRegistry = {
                                  ["webapps", id,
                                   isUpdate ? "staged-update.webapp"
                                            : "update.webapp"],
                                  true);
 
     if (!file.exists()) {
       // This is a hosted app, let's check if it has an appcache
       // and download it.
-      this._readManifests([{ id: id }]).then((aResults) => {
-        let jsonManifest = aResults[0].manifest;
-        let manifest = new ManifestHelper(jsonManifest, app.origin);
-
-        if (manifest.appcache_path) {
-          debug("appcache found");
-          this.startOfflineCacheDownload(manifest, app, null, isUpdate);
-        } else {
-          // Hosted app with no appcache, nothing to do, but we fire a
-          // downloaded event.
-          debug("No appcache found, sending 'downloaded' for " + aManifestURL);
-          app.downloadAvailable = false;
-          this._saveApps().then(() => {
-            this.broadcastMessage("Webapps:UpdateState", {
-              app: app,
-              manifest: jsonManifest,
-              manifestURL: aManifestURL
-            });
-            this.broadcastMessage("Webapps:FireEvent", {
-              eventType: "downloadsuccess",
-              manifestURL: aManifestURL
-            });
-          });
-        }
-      });
+      let results = yield this._readManifests([{ id: id }]);
+
+      let jsonManifest = results[0].manifest;
+      let manifest = new ManifestHelper(jsonManifest, app.origin);
+
+      if (manifest.appcache_path) {
+        debug("appcache found");
+        this.startOfflineCacheDownload(manifest, app, null, isUpdate);
+      } else {
+        // Hosted app with no appcache, nothing to do, but we fire a
+        // downloaded event.
+        debug("No appcache found, sending 'downloaded' for " + aManifestURL);
+        app.downloadAvailable = false;
+
+        yield this._saveApps();
+
+        this.broadcastMessage("Webapps:UpdateState", {
+          app: app,
+          manifest: jsonManifest,
+          manifestURL: aManifestURL
+        });
+        this.broadcastMessage("Webapps:FireEvent", {
+          eventType: "downloadsuccess",
+          manifestURL: aManifestURL
+        });
+      }
 
       return;
     }
 
-    AppsUtils.loadJSONAsync(file.path).then((aJSON) => {
-      if (!aJSON) {
-        debug("startDownload: No update manifest found at " + file.path + " " +
-              aManifestURL);
-        return;
-      }
-
-      let manifest = new ManifestHelper(aJSON, app.installOrigin);
-      this.downloadPackage(manifest, {
-          manifestURL: aManifestURL,
-          origin: app.origin,
-          installOrigin: app.installOrigin,
-          downloadSize: app.downloadSize
-        }, isUpdate).then(function([aId, aManifest]) {
-          // Success! Keep the zip in of TmpD, we'll move it out when
-          // applyDownload() will be called.
-          // Save the manifest in TmpD also
-          let manFile = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", aId,
-                                     "manifest.webapp");
-          DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest));
-
-          app = DOMApplicationRegistry.webapps[aId];
-          // Set state and fire events.
-          app.downloading = false;
-          app.downloadAvailable = false;
-          app.readyToApplyDownload = true;
-          app.updateTime = Date.now();
-          DOMApplicationRegistry._saveApps().then(() => {
-            DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
-              app: app,
-              manifestURL: aManifestURL
-            });
-            DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
-              eventType: "downloadsuccess",
-              manifestURL: aManifestURL
-            });
-            if (app.installState == "pending") {
-              // We restarted a failed download, apply it automatically.
-              DOMApplicationRegistry.applyDownload(aManifestURL);
-            }
-          });
-        });
+    let json = yield AppsUtils.loadJSONAsync(file.path);
+    if (!json) {
+      debug("startDownload: No update manifest found at " + file.path + " " +
+            aManifestURL);
+      return;
+    }
+
+    let manifest = new ManifestHelper(json, app.installOrigin);
+    let [aId, aManifest] = yield this.downloadPackage(manifest, {
+        manifestURL: aManifestURL,
+        origin: app.origin,
+        installOrigin: app.installOrigin,
+        downloadSize: app.downloadSize
+      }, isUpdate);
+
+    // Success! Keep the zip in of TmpD, we'll move it out when
+    // applyDownload() will be called.
+    // Save the manifest in TmpD also
+    let manFile = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", aId,
+                               "manifest.webapp");
+    yield this._writeFile(manFile, JSON.stringify(aManifest));
+
+    app = this.webapps[aId];
+    // Set state and fire events.
+    app.downloading = false;
+    app.downloadAvailable = false;
+    app.readyToApplyDownload = true;
+    app.updateTime = Date.now();
+
+    yield this._saveApps();
+
+    this.broadcastMessage("Webapps:UpdateState", {
+      app: app,
+      manifestURL: aManifestURL
     });
-  },
+    this.broadcastMessage("Webapps:FireEvent", {
+      eventType: "downloadsuccess",
+      manifestURL: aManifestURL
+    });
+    if (app.installState == "pending") {
+      // We restarted a failed download, apply it automatically.
+      this.applyDownload(aManifestURL);
+    }
+  }),
 
   applyDownload: function applyDownload(aManifestURL) {
     debug("applyDownload for " + aManifestURL);
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
     if (!app || (app && !app.readyToApplyDownload)) {
       return;
     }
@@ -1847,50 +1849,50 @@ this.DOMApplicationRegistry = {
        getInterface: function(iid) {
          if (iid.equals(Ci.nsILoadContext))
            return this;
          throw Cr.NS_ERROR_NO_INTERFACE;
        }
      }
   },
 
-  updatePackagedApp: function(aData, aId, aApp, aNewManifest) {
+  updatePackagedApp: Task.async(function*(aData, aId, aApp, aNewManifest) {
     debug("updatePackagedApp");
 
     // Store the new update manifest.
     let dir = this._getAppDir(aId).path;
     let manFile = OS.Path.join(dir, "staged-update.webapp");
-    this._writeFile(manFile, JSON.stringify(aNewManifest));
+    yield this._writeFile(manFile, JSON.stringify(aNewManifest));
 
     let manifest = new ManifestHelper(aNewManifest, aApp.manifestURL);
     // A package is available: set downloadAvailable to fire the matching
     // event.
     aApp.downloadAvailable = true;
     aApp.downloadSize = manifest.size;
     aApp.updateManifest = aNewManifest;
-    this._saveApps().then(() => {
-      this.broadcastMessage("Webapps:UpdateState", {
-        app: aApp,
-        manifestURL: aApp.manifestURL
-      });
-      this.broadcastMessage("Webapps:FireEvent", {
-        eventType: "downloadavailable",
-        manifestURL: aApp.manifestURL,
-        requestID: aData.requestID
-      });
+    yield this._saveApps();
+
+    this.broadcastMessage("Webapps:UpdateState", {
+      app: aApp,
+      manifestURL: aApp.manifestURL
     });
-  },
+    this.broadcastMessage("Webapps:FireEvent", {
+      eventType: "downloadavailable",
+      manifestURL: aApp.manifestURL,
+      requestID: aData.requestID
+    });
+  }),
 
   // A hosted app is updated if the app manifest or the appcache needs
   // updating. Even if the app manifest has not changed, we still check
   // for changes in the app cache.
   // 'aNewManifest' would contain the updated app manifest if
   // it has actually been updated, while 'aOldManifest' contains the
   // stored app manifest.
-  updateHostedApp: function(aData, aId, aApp, aOldManifest, aNewManifest) {
+  updateHostedApp: Task.async(function*(aData, aId, aApp, aOldManifest, aNewManifest) {
     debug("updateHostedApp " + aData.manifestURL);
 
     // Clean up the deprecated manifest cache if needed.
     if (aId in this._manifestCache) {
       delete this._manifestCache[aId];
     }
 
     aApp.manifest = aNewManifest || aOldManifest;
@@ -1899,17 +1901,18 @@ this.DOMApplicationRegistry = {
     if (aNewManifest) {
       this.updateAppHandlers(aOldManifest, aNewManifest, aApp);
 
       this.notifyUpdateHandlers(AppsUtils.cloneAppObject(aApp), aNewManifest);
 
       // Store the new manifest.
       let dir = this._getAppDir(aId).path;
       let manFile = OS.Path.join(dir, "manifest.webapp");
-      this._writeFile(manFile, JSON.stringify(aNewManifest));
+      yield this._writeFile(manFile, JSON.stringify(aNewManifest));
+
       manifest = new ManifestHelper(aNewManifest, aApp.origin);
 
       if (supportUseCurrentProfile()) {
         // Update the permissions for this app.
         PermissionsInstaller.installPermissions({
           manifest: aApp.manifest,
           origin: aApp.origin,
           manifestURL: aData.manifestURL
@@ -1924,62 +1927,67 @@ this.DOMApplicationRegistry = {
       aApp.role = manifest.role || "";
       aApp.updateTime = Date.now();
     } else {
       manifest = new ManifestHelper(aOldManifest, aApp.origin);
     }
 
     // Update the registry.
     this.webapps[aId] = aApp;
-    this._saveApps().then(() => {
-      let reg = DOMApplicationRegistry;
-      if (!manifest.appcache_path) {
-        reg.broadcastMessage("Webapps:UpdateState", {
-          app: aApp,
-          manifest: aApp.manifest,
-          manifestURL: aApp.manifestURL
-        });
-        reg.broadcastMessage("Webapps:FireEvent", {
-          eventType: "downloadapplied",
-          manifestURL: aApp.manifestURL,
-          requestID: aData.requestID
-        });
-      } else {
-        // Check if the appcache is updatable, and send "downloadavailable" or
-        // "downloadapplied".
-        let updateObserver = {
-          observe: function(aSubject, aTopic, aObsData) {
-            debug("updateHostedApp: updateSvc.checkForUpdate return for " +
-                  aApp.manifestURL + " - event is " + aTopic);
-            let eventType =
-              aTopic == "offline-cache-update-available" ? "downloadavailable"
-                                                         : "downloadapplied";
-            aApp.downloadAvailable = (eventType == "downloadavailable");
-            reg._saveApps().then(() => {
-              reg.broadcastMessage("Webapps:UpdateState", {
-                app: aApp,
-                manifest: aApp.manifest,
-                manifestURL: aApp.manifestURL
-              });
-              reg.broadcastMessage("Webapps:FireEvent", {
-                eventType: eventType,
-                manifestURL: aApp.manifestURL,
-                requestID: aData.requestID
-              });
-            });
-          }
-        };
-        debug("updateHostedApp: updateSvc.checkForUpdate for " +
-              manifest.fullAppcachePath());
-        updateSvc.checkForUpdate(Services.io.newURI(manifest.fullAppcachePath(), null, null),
-                                 aApp.localId, false, updateObserver);
-      }
-      delete aApp.manifest;
-    });
-  },
+    yield this._saveApps();
+
+    if (!manifest.appcache_path) {
+      this.broadcastMessage("Webapps:UpdateState", {
+        app: aApp,
+        manifest: aApp.manifest,
+        manifestURL: aApp.manifestURL
+      });
+      this.broadcastMessage("Webapps:FireEvent", {
+        eventType: "downloadapplied",
+        manifestURL: aApp.manifestURL,
+        requestID: aData.requestID
+      });
+    } else {
+      // Check if the appcache is updatable, and send "downloadavailable" or
+      // "downloadapplied".
+      debug("updateHostedApp: updateSvc.checkForUpdate for " +
+            manifest.fullAppcachePath());
+
+      let updateDeferred = Promise.defer();
+
+      updateSvc.checkForUpdate(Services.io.newURI(manifest.fullAppcachePath(), null, null),
+                               aApp.localId, false,
+                               (aSubject, aTopic, aData) => updateDeferred.resolve(aTopic));
+
+      let topic = yield updateDeferred.promise;
+
+      debug("updateHostedApp: updateSvc.checkForUpdate return for " +
+            aApp.manifestURL + " - event is " + topic);
+
+      let eventType =
+        topic == "offline-cache-update-available" ? "downloadavailable"
+                                                  : "downloadapplied";
+
+      aApp.downloadAvailable = (eventType == "downloadavailable");
+      yield this._saveApps();
+
+      this.broadcastMessage("Webapps:UpdateState", {
+        app: aApp,
+        manifest: aApp.manifest,
+        manifestURL: aApp.manifestURL
+      });
+      this.broadcastMessage("Webapps:FireEvent", {
+        eventType: eventType,
+        manifestURL: aApp.manifestURL,
+        requestID: aData.requestID
+      });
+    }
+
+    delete aApp.manifest;
+  }),
 
   // Downloads the manifest and run checks, then eventually triggers the
   // installation UI.
   doInstall: function doInstall(aData, aMm) {
     let app = aData.app;
 
     let sendError = function sendError(aError) {
       aData.error = aError;
@@ -2318,20 +2326,20 @@ onInstallSuccessAck: function onInstallS
   _writeManifestFile: function(aId, aIsPackage, aJsonManifest) {
     debug("_writeManifestFile");
 
     // For packaged apps, keep the update manifest distinct from the app manifest.
     let manifestName = aIsPackage ? "update.webapp" : "manifest.webapp";
 
     let dir = this._getAppDir(aId).path;
     let manFile = OS.Path.join(dir, manifestName);
-    this._writeFile(manFile, JSON.stringify(aJsonManifest));
+    return this._writeFile(manFile, JSON.stringify(aJsonManifest));
   },
 
-  confirmInstall: function(aData, aProfileDir, aInstallSuccessCallback) {
+  confirmInstall: Task.async(function*(aData, aProfileDir, aInstallSuccessCallback) {
     debug("confirmInstall");
 
     let origin = Services.io.newURI(aData.app.origin, null, null);
     let id = this._appIdForManifestURL(aData.app.manifestURL);
     let manifestURL = origin.resolve(aData.app.manifestURL);
     let localId = this.getAppLocalIdByManifestURL(manifestURL);
 
     let isReinstall = false;
@@ -2380,52 +2388,25 @@ onInstallSuccessAck: function onInstallS
                            this.webapps[id].appStatus);
     }
 
     for each (let prop in ["installState", "downloadAvailable", "downloading",
                            "downloadSize", "readyToApplyDownload"]) {
       aData.app[prop] = appObject[prop];
     }
 
+    let dontNeedNetwork = false;
+
     if (manifest.appcache_path) {
       this.queuedDownload[app.manifestURL] = {
         manifest: manifest,
         app: appObject,
         profileDir: aProfileDir
       }
-    }
-
-    // We notify about the successful installation via mgmt.oninstall and the
-    // corresponging DOMRequest.onsuccess event as soon as the app is properly
-    // saved in the registry.
-    this._saveApps().then(() => {
-      this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
-      if (aData.isPackage && aData.autoInstall) {
-        // Skip directly to onInstallSuccessAck, since there isn't
-        // a WebappsRegistry to receive Webapps:Install:Return:OK and respond
-        // Webapps:Install:Return:Ack when an app is being auto-installed.
-        this.onInstallSuccessAck(app.manifestURL);
-      } else {
-        // Broadcast Webapps:Install:Return:OK so the WebappsRegistry can notify
-        // the installing page about the successful install, after which it'll
-        // respond Webapps:Install:Return:Ack, which calls onInstallSuccessAck.
-        this.broadcastMessage("Webapps:Install:Return:OK", aData);
-      }
-      if (!aData.isPackage) {
-        this.updateAppHandlers(null, app.manifest, app);
-        if (aInstallSuccessCallback) {
-          aInstallSuccessCallback(app.manifest);
-        }
-      }
-      Services.obs.notifyObservers(null, "webapps-installed",
-        JSON.stringify({ manifestURL: app.manifestURL }));
-    });
-
-    let dontNeedNetwork = false;
-    if (manifest.package_path) {
+    } else if (manifest.package_path) {
       // If it is a local app then it must been installed from a local file
       // instead of web.
 #ifdef MOZ_ANDROID_SYNTHAPKS
       // In that case, we would already have the manifest, not just the update
       // manifest.
       dontNeedNetwork = !!aData.app.manifest;
 #else
       if (aData.app.localInstallPath) {
@@ -2440,95 +2421,124 @@ onInstallSuccessAck: function onInstallS
 
       this.queuedPackageDownload[app.manifestURL] = {
         manifest: manifest,
         app: appObject,
         callback: aInstallSuccessCallback
       };
     }
 
+    // We notify about the successful installation via mgmt.oninstall and the
+    // corresponging DOMRequest.onsuccess event as soon as the app is properly
+    // saved in the registry.
+    yield this._saveApps();
+
+    this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
+    if (aData.isPackage && aData.autoInstall) {
+      // Skip directly to onInstallSuccessAck, since there isn't
+      // a WebappsRegistry to receive Webapps:Install:Return:OK and respond
+      // Webapps:Install:Return:Ack when an app is being auto-installed.
+      this.onInstallSuccessAck(app.manifestURL);
+    } else {
+      // Broadcast Webapps:Install:Return:OK so the WebappsRegistry can notify
+      // the installing page about the successful install, after which it'll
+      // respond Webapps:Install:Return:Ack, which calls onInstallSuccessAck.
+      this.broadcastMessage("Webapps:Install:Return:OK", aData);
+    }
+
+    if (!aData.isPackage) {
+      this.updateAppHandlers(null, app.manifest, app);
+      if (aInstallSuccessCallback) {
+        aInstallSuccessCallback(app.manifest);
+      }
+    }
+
+    Services.obs.notifyObservers(null, "webapps-installed",
+      JSON.stringify({ manifestURL: app.manifestURL }));
+
     if (aData.forceSuccessAck) {
       // If it's a local install, there's no content process so just
       // ack the install.
       this.onInstallSuccessAck(app.manifestURL, dontNeedNetwork);
     }
-  },
+  }),
 
 /**
    * Install the package after successfully downloading it
    *
    * Bound params:
    *
    * @param aNewApp {Object} the new app data
    * @param aInstallSuccessCallback {Function}
    *        the callback to call on install success
    *
    * Passed params:
    *
    * @param aId {Integer} the unique ID of the application
    * @param aManifest {Object} The manifest of the application
    */
-  _onDownloadPackage: function(aNewApp, aInstallSuccessCallback,
-                               [aId, aManifest]) {
+  _onDownloadPackage: Task.async(function*(aNewApp, aInstallSuccessCallback,
+                                           [aId, aManifest]) {
     debug("_onDownloadPackage");
     // Success! Move the zip out of TmpD.
     let app = this.webapps[aId];
     let zipFile =
       FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true);
     let dir = this._getAppDir(aId);
     zipFile.moveTo(dir, "application.zip");
     let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
     try {
       tmpDir.remove(true);
     } catch(e) { }
 
     // Save the manifest
     let manFile = OS.Path.join(dir.path, "manifest.webapp");
-    this._writeFile(manFile, JSON.stringify(aManifest));
+    yield this._writeFile(manFile, JSON.stringify(aManifest));
     // Set state and fire events.
     app.installState = "installed";
     app.downloading = false;
     app.downloadAvailable = false;
-    this._saveApps().then(() => {
-      this.updateAppHandlers(null, aManifest, aNewApp);
-      // Clear the manifest cache in case it holds the update manifest.
-      if (aId in this._manifestCache) {
-        delete this._manifestCache[aId];
-      }
-
-      this.broadcastMessage("Webapps:AddApp", { id: aId, app: aNewApp });
-      Services.obs.notifyObservers(null, "webapps-installed",
-        JSON.stringify({ manifestURL: aNewApp.manifestURL }));
-
-      if (supportUseCurrentProfile()) {
-        // Update the permissions for this app.
-        PermissionsInstaller.installPermissions({
-          manifest: aManifest,
-          origin: aNewApp.origin,
-          manifestURL: aNewApp.manifestURL
-        }, true);
-      }
-
-      this.updateDataStore(this.webapps[aId].localId, aNewApp.origin,
-                           aNewApp.manifestURL, aManifest, aNewApp.appStatus);
-
-      this.broadcastMessage("Webapps:UpdateState", {
-        app: app,
+
+    yield this._saveApps();
+
+    this.updateAppHandlers(null, aManifest, aNewApp);
+    // Clear the manifest cache in case it holds the update manifest.
+    if (aId in this._manifestCache) {
+      delete this._manifestCache[aId];
+    }
+
+    this.broadcastMessage("Webapps:AddApp", { id: aId, app: aNewApp });
+    Services.obs.notifyObservers(null, "webapps-installed",
+      JSON.stringify({ manifestURL: aNewApp.manifestURL }));
+
+    if (supportUseCurrentProfile()) {
+      // Update the permissions for this app.
+      PermissionsInstaller.installPermissions({
         manifest: aManifest,
+        origin: aNewApp.origin,
         manifestURL: aNewApp.manifestURL
-      });
-      this.broadcastMessage("Webapps:FireEvent", {
-        eventType: ["downloadsuccess", "downloadapplied"],
-        manifestURL: aNewApp.manifestURL
-      });
-      if (aInstallSuccessCallback) {
-        aInstallSuccessCallback(aManifest, zipFile.path);
-      }
+      }, true);
+    }
+
+    this.updateDataStore(this.webapps[aId].localId, aNewApp.origin,
+                         aNewApp.manifestURL, aManifest, aNewApp.appStatus);
+
+    this.broadcastMessage("Webapps:UpdateState", {
+      app: app,
+      manifest: aManifest,
+      manifestURL: aNewApp.manifestURL
     });
-  },
+    this.broadcastMessage("Webapps:FireEvent", {
+      eventType: ["downloadsuccess", "downloadapplied"],
+      manifestURL: aNewApp.manifestURL
+    });
+    if (aInstallSuccessCallback) {
+      aInstallSuccessCallback(aManifest, zipFile.path);
+    }
+  }),
 
   _nextLocalId: function() {
     let id = Services.prefs.getIntPref("dom.mozApps.maxLocalId") + 1;
 
     while (this.getManifestURLByLocalId(id)) {
       id++;
     }