Bug 981085 - Stop using OS.File in apps code. r=fabrice, a=lsblakk
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Tue, 25 Mar 2014 09:14:06 -0400
changeset 192239 ed09e2b74d14
parent 192234 1c4fdb3d772d
child 192240 9c41e6f07c3e
push id3539
push userryanvm@gmail.com
push date2014-05-09 14:29 +0000
treeherdermozilla-beta@bbac2a994298 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice, lsblakk
bugs981085
milestone30.0
Bug 981085 - Stop using OS.File in apps code. r=fabrice, a=lsblakk
dom/apps/src/AppsUtils.jsm
dom/apps/src/OperatorApps.jsm
dom/apps/src/Webapps.jsm
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -10,21 +10,24 @@ const Ci = Components.interfaces;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/WebappOSUtils.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
-  return Cc["@mozilla.org/network/util;1"]
-           .getService(Ci.nsINetUtil);
-});
+XPCOMUtils.defineLazyServiceGetter(this, "NetworkUtil",
+                                   "@mozilla.org/network/util;1",
+                                   "nsINetUtil");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+  "resource://gre/modules/NetUtil.jsm");
 
 // Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
 
 this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper", "isAbsoluteURI"];
 
 function debug(s) {
   //dump("-*- AppsUtils.jsm: " + s + "\n");
 }
@@ -334,17 +337,17 @@ this.AppsUtils = {
     }
     return true;
   },
 
   checkManifestContentType: function
      checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) {
     let hadCharset = { };
     let charset = { };
-    let contentType = NetUtil.parseContentType(aContentType, charset, hadCharset);
+    let contentType = NetworkUtil.parseContentType(aContentType, charset, hadCharset);
     if (aInstallOrigin != aWebappOrigin &&
         contentType != "application/x-web-app-manifest+json") {
       return false;
     }
     return true;
   },
 
   /**
@@ -491,40 +494,67 @@ this.AppsUtils = {
         return false;
       }
     }
 
     // Nothing failed.
     return true;
   },
 
-  // Loads a JSON file using OS.file. aFile is a string representing the path
+  // Asynchronously loads a JSON file. aPath is a string representing the path
   // of the file to be read.
-  // Returns a Promise resolved with the json payload or rejected with
-  // OS.File.Error
-  loadJSONAsync: function(aFile) {
-    debug("_loadJSONAsync: " + aFile);
-    return Task.spawn(function() {
-      let file = yield OS.File.open(aFile, { read: true });
-      let rawData = yield file.read();
-      // Read json file into a string
-      let data;
-      try {
-        // Obtain a converter to read from a UTF-8 encoded input stream.
-        let converter = new TextDecoder();
-        data = JSON.parse(converter.decode(rawData));
-        file.close();
-      } catch (ex) {
-        debug("Error parsing JSON: " + aFile + ". Error: " + ex);
-        Cu.reportError("OperatorApps: Could not parse JSON: " +
-                       aFile + " " + ex + "\n" + ex.stack);
-        throw ex;
-      }
-      throw new Task.Result(data);
-    });
+  loadJSONAsync: function(aPath) {
+    let deferred = Promise.defer();
+
+    try {
+      let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+      file.initWithPath(aPath);
+
+      let channel = NetUtil.newChannel(file);
+      channel.contentType = "application/json";
+
+      NetUtil.asyncFetch(channel, function(aStream, aResult) {
+        if (!Components.isSuccessCode(aResult)) {
+          deferred.resolve(null);
+
+          if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
+            // We expect this under certain circumstances, like for webapps.json
+            // on firstrun, so we return early without reporting an error.
+            return;
+          }
+
+          Cu.reportError("AppsUtils: Could not read from json file " + aPath);
+          return;
+        }
+
+        try {
+          // 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";
+
+          // Read json file into a string
+          let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
+                                                            aStream.available()) || ""));
+          aStream.close();
+
+          deferred.resolve(data);
+        } catch (ex) {
+          Cu.reportError("AppsUtils: Could not parse JSON: " +
+                         aPath + " " + ex + "\n" + ex.stack);
+          deferred.resolve(null);
+        }
+      });
+    } catch (ex) {
+      Cu.reportError("AppsUtils: Could not read from " +
+                     aPath + " : " + ex + "\n" + ex.stack);
+      deferred.resolve(null);
+    }
+
+    return deferred.promise;
   },
 
   // Returns the MD5 hash of a string.
   computeHash: function(aString) {
     let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                       .createInstance(Ci.nsIScriptableUnicodeConverter);
     converter.charset = "UTF-8";
     let result = {};
--- a/dom/apps/src/OperatorApps.jsm
+++ b/dom/apps/src/OperatorApps.jsm
@@ -13,16 +13,18 @@ this.EXPORTED_SYMBOLS = ["OperatorAppsRe
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/Webapps.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
+let Path = OS.Path;
+
 #ifdef MOZ_B2G_RIL
 XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
                                    "@mozilla.org/ril/content-helper;1",
                                    "nsIIccProvider");
 #endif
 
 function debug(aMsg) {
   //dump("-*-*- OperatorApps.jsm : " + aMsg + "\n");
@@ -53,19 +55,16 @@ function isFirstRunWithSIM() {
     }
   } catch(e) {
     debug ("Error getting pref. " + e);
   }
   return true;
 }
 
 #ifdef MOZ_B2G_RIL
-let File = OS.File;
-let Path = OS.Path;
-
 let iccListener = {
   notifyStkCommand: function() {},
 
   notifyStkSessionEnd: function() {},
 
   notifyCardStateChanged: function() {},
 
   notifyIccInfoChanged: function() {
@@ -128,48 +127,47 @@ this.OperatorAppsRegistry = {
     }
 #endif
   },
 
   _copyDirectory: function(aOrg, aDst) {
     debug("copying " + aOrg + " to " + aDst);
     return aDst && Task.spawn(function() {
       try {
-        let orgInfo = yield File.stat(aOrg);
-        if (!orgInfo.isDir) {
+        let orgDir = Cc["@mozilla.org/file/local;1"]
+                       .createInstance(Ci.nsIFile);
+        orgDir.initWithPath(aOrg);
+        if (!orgDir.isDirectory()) {
           return;
         }
 
-        let dirDstExists = yield File.exists(aDst);
-        if (!dirDstExists) {
-          yield File.makeDir(aDst);
+        let dstDir = Cc["@mozilla.org/file/local;1"]
+                       .createInstance(Ci.nsIFile);
+        dstDir.initWithPath(aDst);
+        if (!dstDir.exists()) {
+          dstDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
         }
-        let iterator = new File.DirectoryIterator(aOrg);
-        if (!iterator) {
-          debug("No iterator over: " + aOrg);
-          return;
-        }
-        try {
-          while (true) {
-            let entry;
-            try {
-              entry = yield iterator.next();
-            } catch (ex if ex == StopIteration) {
-              break;
+
+        let entries = orgDir.directoryEntries;
+        while (entries.hasMoreElements()) {
+          let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+
+          if (!entry.isDirectory()) {
+            // Remove the file, because copyTo doesn't overwrite files.
+            let dstFile = dstDir.clone();
+            dstFile.append(entry.leafName);
+            if(dstFile.exists()) {
+              dstFile.remove(false);
             }
 
-            if (!entry.isDir) {
-              yield File.copy(entry.path, Path.join(aDst, entry.name));
-            } else {
-              yield this._copyDirectory(entry.path,
-                                        Path.join(aDst, entry.name));
-            }
+            entry.copyTo(dstDir, entry.leafName);
+          } else {
+            yield this._copyDirectory(entry.path,
+                                      Path.join(aDst, entry.name));
           }
-        } finally {
-          iterator.close();
         }
       } catch (e) {
         debug("Error copying " + aOrg + " to " + aDst + ". " + e);
       }
     }.bind(this));
   },
 
   _initializeSourceDir: function() {
@@ -183,33 +181,37 @@ this.OperatorAppsRegistry = {
                                          [SINGLE_VARIANT_SOURCE_DIR]).path;
         return;
       }
       // If SINGLE_VARIANT_CONF_FILE is in PREF_SINGLE_VARIANT_DIR return
       // PREF_SINGLE_VARIANT_DIR as sourceDir, else go to
       // DIRECTORY_NAME + SINGLE_VARIANT_SOURCE_DIR and move all apps (and
       // configuration file) to PREF_SINGLE_VARIANT_DIR and return
       // PREF_SINGLE_VARIANT_DIR as sourceDir.
-      let existsDir = yield File.exists(svFinalDirName);
-      if (!existsDir) {
-        yield File.makeDir(svFinalDirName, {ignoreExisting: true});
+      let svFinalDir = Cc["@mozilla.org/file/local;1"]
+                         .createInstance(Ci.nsIFile);
+      svFinalDir.initWithPath(svFinalDirName);
+      if (!svFinalDir.exists()) {
+        svFinalDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
       }
 
-      let existsSvIndex = yield File.exists(Path.join(svFinalDirName,
-                                            SINGLE_VARIANT_CONF_FILE));
-      if (!existsSvIndex) {
-        let svSourceDirName = FileUtils.getFile(DIRECTORY_NAME,
-                                              [SINGLE_VARIANT_SOURCE_DIR]).path;
-        yield this._copyDirectory(svSourceDirName, svFinalDirName);
-        debug("removing directory:" + svSourceDirName);
-        File.removeDir(svSourceDirName, {
-          ignoreAbsent: true,
-          ignorePermissions: true
-        });
+      let svIndex = svFinalDir.clone();
+      svIndex.append(SINGLE_VARIANT_CONF_FILE);
+      if (!svIndex.exists()) {
+        let svSourceDir = FileUtils.getFile(DIRECTORY_NAME,
+                                            [SINGLE_VARIANT_SOURCE_DIR]);
+
+        yield this._copyDirectory(svSourceDir.path, svFinalDirName);
+
+        debug("removing directory:" + svSourceDir.path);
+        try {
+          svSourceDir.remove(true);
+        } catch(ex) { }
       }
+
       this.appsDir = svFinalDirName;
     }.bind(this));
   },
 
   set appsDir(aDir) {
     debug("appsDir SET: " + aDir);
     if (aDir) {
       this._baseDirectory = Cc["@mozilla.org/file/local;1"]
@@ -271,28 +273,28 @@ this.OperatorAppsRegistry = {
       appId: undefined,
       isBrowser: false,
       isPackage: isPackage,
       forceSuccessAck: true
     };
 
     if (isPackage) {
       debug("aId:" + aId + ". Installing as packaged app.");
-      let installPack = OS.Path.join(this.appsDir.path, aId, APPLICATION_ZIP);
-      OS.File.exists(installPack).then(
-        function(aExists) {
-          if (!aExists) {
-            debug("SV " + installPack.path + " file do not exists for app " +
-                  aId);
-            return;
-          }
-          appData.app.localInstallPath = installPack;
-          appData.app.updateManifest = aManifest;
-          DOMApplicationRegistry.confirmInstall(appData);
-      });
+      let installPack = this.appsDir.clone();
+      installPack.append(aId);
+      installPack.append(APPLICATION_ZIP);
+
+      if (!installPack.exists()) {
+        debug("SV " + installPack.path + " file do not exists for app " + aId);
+        return;
+      }
+
+      appData.app.localInstallPath = installPack.path;
+      appData.app.updateManifest = aManifest;
+      DOMApplicationRegistry.confirmInstall(appData);
     } else {
       debug("aId:" + aId + ". Installing as hosted app.");
       appData.app.manifest = aManifest;
       DOMApplicationRegistry.confirmInstall(appData);
     }
   },
 
   _installOperatorApps: function(aMcc, aMnc) {
@@ -303,28 +305,34 @@ this.OperatorAppsRegistry = {
         return;
       }
 
       let aIdsApp = yield this._getSingleVariantApps(aMcc, aMnc);
       debug("installOperatorApps --> aIdsApp:" + JSON.stringify(aIdsApp));
       for (let i = 0; i < aIdsApp.length; i++) {
         let aId = aIdsApp[i];
         let aMetadata = yield AppsUtils.loadJSONAsync(
-                           OS.Path.join(this.appsDir.path, aId, METADATA));
+                           Path.join(this.appsDir.path, aId, METADATA));
+        if (!aMetadata) {
+          debug("Error reading metadata file");
+          return;
+        }
+
         debug("metadata:" + JSON.stringify(aMetadata));
         let isPackage = true;
         let manifest;
         let manifests = [UPDATEMANIFEST, MANIFEST];
         for (let j = 0; j < manifests.length; j++) {
-          try {
-            manifest = yield AppsUtils.loadJSONAsync(
-                          OS.Path.join(this.appsDir.path, aId, manifests[j]));
+          manifest = yield AppsUtils.loadJSONAsync(
+                        Path.join(this.appsDir.path, aId, manifests[j]));
+
+          if (!manifest) {
+            isPackage = false;
+          } else {
             break;
-          } catch (e) {
-            isPackage = false;
           }
         }
         if (manifest) {
           this._launchInstall(isPackage, aId, aMetadata, manifest);
         } else {
           debug ("Error. Neither " + UPDATEMANIFEST + " file nor " + MANIFEST +
                  " file for " + aId + " app.");
         }
@@ -344,17 +352,17 @@ this.OperatorAppsRegistry = {
       while (ncode.length < 3) {
         ncode = "0" + ncode;
       }
       return ncode;
     }
 
     return Task.spawn(function () {
       let key = normalizeCode(aMcc) + "-" + normalizeCode(aMnc);
-      let file = OS.Path.join(this.appsDir.path, SINGLE_VARIANT_CONF_FILE);
+      let file = Path.join(this.appsDir.path, SINGLE_VARIANT_CONF_FILE);
       let aData = yield AppsUtils.loadJSONAsync(file);
       if (!aData || !(key in aData)) {
         return;
       }
       throw new Task.Result(aData[key]);
     }.bind(this));
   }
 };
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -167,17 +167,17 @@ this.DOMApplicationRegistry = {
     this.appsFile = FileUtils.getFile(DIRECTORY_NAME,
                                       ["webapps", "webapps.json"], true).path;
 
     this.loadAndUpdateApps();
   },
 
   // loads the current registry, that could be empty on first run.
   loadCurrentRegistry: function() {
-    return this._loadJSONAsync(this.appsFile).then((aData) => {
+    return AppsUtils.loadJSONAsync(this.appsFile).then((aData) => {
       if (!aData) {
         return;
       }
 
       this.webapps = aData;
       let appDir = OS.Path.dirname(this.appsFile);
       for (let id in this.webapps) {
         let app = this.webapps[id];
@@ -502,17 +502,17 @@ this.DOMApplicationRegistry = {
         file = FileUtils.getFile("coreAppsDir", ["webapps", "webapps.json"], false);
       } catch(e) { }
 
       if (!file || !file.exists()) {
         return;
       }
 
       // a
-      let data = yield this._loadJSONAsync(file.path);
+      let data = yield AppsUtils.loadJSONAsync(file.path);
       if (!data) {
         return;
       }
 
       // b : core apps are not removable.
       for (let id in this.webapps) {
         if (id in data || this.webapps[id].removable)
           continue;
@@ -938,66 +938,16 @@ this.DOMApplicationRegistry = {
       cpmm = null;
       ppmm = null;
     } else if (aTopic == "memory-pressure") {
       // Clear the manifest cache on memory pressure.
       this._manifestCache = {};
     }
   },
 
-  _loadJSONAsync: function(aPath) {
-    let deferred = Promise.defer();
-
-    try {
-      let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-      file.initWithPath(aPath);
-      let channel = NetUtil.newChannel(file);
-      channel.contentType = "application/json";
-      NetUtil.asyncFetch(channel, function(aStream, aResult) {
-        if (!Components.isSuccessCode(aResult)) {
-          deferred.resolve(null);
-
-          if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
-            // We expect this under certain circumstances, like for webapps.json
-            // on firstrun, so we return early without reporting an error.
-            return;
-          }
-
-          Cu.reportError("DOMApplicationRegistry: Could not read from json file "
-                         + aPath);
-          return;
-        }
-
-        try {
-          // 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";
-
-          // Read json file into a string
-          let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
-                                                            aStream.available()) || ""));
-          aStream.close();
-
-          deferred.resolve(data);
-        } catch (ex) {
-          Cu.reportError("DOMApplicationRegistry: Could not parse JSON: " +
-                         aPath + " " + ex + "\n" + ex.stack);
-          deferred.resolve(null);
-        }
-      });
-    } catch (ex) {
-      Cu.reportError("DOMApplicationRegistry: Could not read from " +
-                     aPath + " : " + ex + "\n" + ex.stack);
-      deferred.resolve(null);
-    }
-
-    return deferred.promise;
-  },
-
   addMessageListener: function(aMsgNames, aApp, aMm) {
     aMsgNames.forEach(function (aMsgName) {
       let man = aApp && aApp.manifestURL;
       if (!(aMsgName in this.children)) {
         this.children[aMsgName] = [];
       }
 
       let mmFound = this.children[aMsgName].some(function(mmRef) {
@@ -1223,20 +1173,40 @@ this.DOMApplicationRegistry = {
 
   _getAppDir: function(aId) {
     return FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
   },
 
   _writeFile: function(aPath, aData) {
     debug("Saving " + aPath);
 
-    return OS.File.writeAtomic(aPath,
-                               new TextEncoder().encode(aData),
-                               { tmpPath: aPath + ".tmp" })
-                  .then(null, Cu.reportError);
+    let deferred = Promise.defer();
+
+    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+    file.initWithPath(aPath);
+
+    // Initialize the file output stream
+    let ostream = FileUtils.openSafeFileOutputStream(file);
+
+    // Obtain a converter to convert our data to a UTF-8 encoded input stream.
+    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                      .createInstance(Ci.nsIScriptableUnicodeConverter);
+    converter.charset = "UTF-8";
+
+    // Asynchronously copy the data to the file.
+    let istream = converter.convertToInputStream(aData);
+    NetUtil.asyncCopy(istream, ostream, function(aResult) {
+      if (!Components.isSuccessCode(aResult)) {
+        deferred.reject()
+      } else {
+        deferred.resolve();
+      }
+    });
+
+    return deferred.promise;
   },
 
   doLaunch: function (aData, aMm) {
     this.launch(
       aData.manifestURL,
       aData.startPoint,
       aData.timestamp,
       function onsuccess() {
@@ -1395,17 +1365,17 @@ this.DOMApplicationRegistry = {
             });
           });
         }
       });
 
       return;
     }
 
-    this._loadJSONAsync(file.path).then((aJSON) => {
+    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, {
@@ -1508,17 +1478,17 @@ this.DOMApplicationRegistry = {
         }
 
         delete app.retryingDownload;
 
         this._saveApps().then(() => {
           // Update the handlers and permissions for this app.
           this.updateAppHandlers(aOldManifest, aData, app);
 
-          this._loadJSONAsync(staged.path).then((aUpdateManifest) => {
+          AppsUtils.loadJSONAsync(staged.path).then((aUpdateManifest) => {
             let appObject = AppsUtils.cloneAppObject(app);
             appObject.updateManifest = aUpdateManifest;
             this.notifyUpdateHandlers(appObject, aData, appFile.path);
           });
 
           if (supportUseCurrentProfile()) {
             PermissionsInstaller.installPermissions(
               { manifest: aData,
@@ -2599,17 +2569,17 @@ onInstallSuccessAck: function onInstallS
           // the manifest file used to be named manifest.json, so fallback on this.
           let baseDir = this.webapps[id].basePath == this.getCoreAppsBasePath()
                           ? "coreAppsDir" : DIRECTORY_NAME;
 
           let dir = FileUtils.getDir(baseDir, ["webapps", id], false, true);
 
           let fileNames = ["manifest.webapp", "update.webapp", "manifest.json"];
           for (let fileName of fileNames) {
-            this._manifestCache[id] = yield this._loadJSONAsync(OS.Path.join(dir.path, fileName));
+            this._manifestCache[id] = yield AppsUtils.loadJSONAsync(OS.Path.join(dir.path, fileName));
             if (this._manifestCache[id]) {
               break;
             }
           }
         }
 
         elem.manifest = this._manifestCache[id];
       }
@@ -2890,58 +2860,51 @@ onInstallSuccessAck: function onInstallS
   /**
    * Compute the MD5 hash of a file, doing async IO off the main thread.
    *
    * @param   {String} aFilePath
    *                   the path of the file to hash
    * @returns {String} the MD5 hash of the file
    */
   _computeFileHash: function(aFilePath) {
-    return Task.spawn(function*() {
+    let deferred = Promise.defer();
+
+    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+    file.initWithPath(aFilePath);
+
+    NetUtil.asyncFetch(file, function(inputStream, status) {
+      if (!Components.isSuccessCode(status)) {
+        debug("Error reading " + aFilePath + ": " + e);
+        deferred.reject();
+        return;
+      }
+
       let hasher = Cc["@mozilla.org/security/hash;1"]
                      .createInstance(Ci.nsICryptoHash);
       // We want to use the MD5 algorithm.
       hasher.init(hasher.MD5);
 
-      const CHUNK_SIZE = 16384;
+      const PR_UINT32_MAX = 0xffffffff;
+      hasher.updateFromStream(inputStream, PR_UINT32_MAX);
 
       // Return the two-digit hexadecimal code for a byte.
       function toHexString(charCode) {
         return ("0" + charCode.toString(16)).slice(-2);
       }
 
-      let file;
-      try {
-        file = yield OS.File.open(aFilePath, { read: true });
-      } catch(e) {
-        debug("Error opening " + aFilePath + ": " + e);
-        return null;
-      }
-
-      try {
-        let array;
-        do {
-          array = yield file.read(CHUNK_SIZE);
-          hasher.update(array, array.length);
-        } while (array.length == CHUNK_SIZE);
-      } catch(e) {
-        debug("Error reading " + aFilePath + ": " + e);
-        return null;
-      }
-
-      yield file.close();
-
       // We're passing false to get the binary hash and not base64.
       let data = hasher.finish(false);
       // Convert the binary hash data to a hex string.
       let hash = [toHexString(data.charCodeAt(i)) for (i in data)].join("");
       debug("File hash computed: " + hash);
 
-      return hash;
+      deferred.resolve(hash);
     });
+
+    return deferred.promise;
   },
 
   /**
    * Send an "applied" event right away for the package being installed.
    *
    * XXX We use this to exit the app update process early when the downloaded
    * package is identical to the last one we installed.  Presumably we do
    * something similar after updating the app, and we could refactor both cases
@@ -2959,22 +2922,25 @@ onInstallSuccessAck: function onInstallS
     aOldApp.readyToApplyDownload = false;
     if (aOldApp.staged && aOldApp.staged.manifestHash) {
       // If we're here then the manifest has changed but the package
       // hasn't. Let's clear this, so we don't keep offering
       // a bogus update to the user
       aOldApp.manifestHash = aOldApp.staged.manifestHash;
       aOldApp.etag = aOldApp.staged.etag || aOldApp.etag;
       aOldApp.staged = {};
+
       // Move the staged update manifest to a non staged one.
-      let dirPath = this._getAppDir(aId).path;
-
-      // We don't really mind much if this fails.
-      OS.File.move(OS.Path.join(dirPath, "staged-update.webapp"),
-                   OS.Path.join(dirPath, "update.webapp"));
+      try {
+        let staged = this._getAppDir(aId);
+        staged.append("staged-update.webapp");
+        staged.moveTo(staged.parent, "update.webapp");
+      } catch (ex) {
+        // We don't really mind much if this fails.
+      }
     }
 
     // Save the updated registry, and cleanup the tmp directory.
     this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
         app: aOldApp,
         manifestURL: aNewApp.manifestURL
       });