Added better file error handling when creating files and directories on local disk in jar-store and remote-experiment-loader.
authorJono X <jono@mozilla.com>
Fri, 30 Jul 2010 00:01:42 +0900
changeset 477 9870d18d2cd523e0deb2095e2c1640004581f0d9
parent 476 be283513313f403e80a40560aac88576c135aeba
child 478 cc33363d4351287e1307538fe8807bfec9ace790
push id396
push userjdicarlo@mozilla.com
push dateThu, 29 Jul 2010 15:01:49 +0000
Added better file error handling when creating files and directories on local disk in jar-store and remote-experiment-loader.
extension/modules/jar-code-store.js
extension/modules/remote-experiment-loader.js
--- a/extension/modules/jar-code-store.js
+++ b/extension/modules/jar-code-store.js
@@ -125,48 +125,52 @@ JarStore.prototype = {
     // s now contains your hash in hex
 
     return (s == expectedHash);
   },
 
   saveJarFile: function( filename, rawData, expectedHash ) {
     console.info("Saving a JAR file as " + filename + " hash = " + expectedHash);
     // rawData is a string of binary data representing a jar file
+    let jarFile;
     try {
-    let jarFile = this._baseDir.clone();
+      jarFile = this._baseDir.clone();
       // filename may have directories in it; use just the last part
       jarFile.append(filename.split("/").pop());
 
       // If a file of that name already exists, remove it!
       if (jarFile.exists()) {
         jarFile.remove(false);
       }
-    // From https://developer.mozilla.org/en/Code_snippets/File_I%2f%2fO#Getting_special_files
-    jarFile.create( Ci.nsIFile.NORMAL_FILE_TYPE, 600);
-    let stream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
-                    createInstance(Ci.nsIFileOutputStream);
-    stream.init(jarFile, 0x04 | 0x08 | 0x20, 0600, 0); // readwrite, create, truncate
-
-    stream.write(rawData, rawData.length);
-    if (stream instanceof Ci.nsISafeOutputStream) {
-      stream.finish();
-    } else {
-      stream.close();
-    }
-    // Verify hash; if it's good, index and set last modified time.
-    // If not good, remove it.
-    if (this._verifyJar(jarFile, expectedHash)) {
-      this._indexJar(jarFile);
-      this._lastModified[jarFile.leafName] = jarFile.lastModifiedTime;
-    } else {
-      console.warn("Bad JAR file, doesn't match hash: " + expectedHash);
-      jarFile.remove(false);
-    }
+      // From https://developer.mozilla.org/en/Code_snippets/File_I%2f%2fO#Getting_special_files
+      jarFile.create( Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
+      let stream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
+                      createInstance(Ci.nsIFileOutputStream);
+      stream.init(jarFile, 0x04 | 0x08 | 0x20, 0600, 0); // readwrite, create, truncate
+      stream.write(rawData, rawData.length);
+      if (stream instanceof Ci.nsISafeOutputStream) {
+        stream.finish();
+      } else {
+        stream.close();
+      }
+      // Verify hash; if it's good, index and set last modified time.
+      // If not good, remove it.
+      if (this._verifyJar(jarFile, expectedHash)) {
+        this._indexJar(jarFile);
+        this._lastModified[jarFile.leafName] = jarFile.lastModifiedTime;
+      } else {
+        console.warn("Bad JAR file, doesn't match hash: " + expectedHash);
+        jarFile.remove(false);
+      }
     } catch(e) {
       console.warn("Error in saving jar file: " + e);
+      // Remove any partially saved file
+      if (jarFile.exists()) {
+        jarFile.remove(false);
+      }
     }
   },
 
   resolveModule: function(root, path) {
     // Root will be null if require() was done by absolute path.
     if (root != null) {
       // TODO I don't think we need to do anything special here.
     }
--- a/extension/modules/remote-experiment-loader.js
+++ b/extension/modules/remote-experiment-loader.js
@@ -317,76 +317,107 @@ exports.RemoteExperimentLoader.prototype
       let hash = j.hash;
       if (j.studyfile) {
         this._experimentFileNames.push(j.studyfile);
       }
     }
     return true;
   },
 
+  // TODO a bad thing that can go wrong: If we have a net connection but the index file
+  // has not changed, we currently don't try to download anything...
+
+  // Another bad thing: If there's a jar download that's corrupt or unreadable or has
+    // the wrong permissions or something, we need to kill it and download a new one.
+
+  // WTF every jar file I'm downloading appears as 0 bytes with __x__x___ permissions!
+
+  _cachedIndexNsiFile: null,
   get cachedIndexNsiFile() {
-    let file = Cc["@mozilla.org/file/directory_service;1"].
-                     getService(Ci.nsIProperties).
-                     get("ProfD", Ci.nsIFile);
-    file.append("TestPilotExperimentFiles"); // TODO this name should go in pref?
-    if (!file.exists() || !file.isDirectory()) {
-      file.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
+    if (!this._cachedIndexNsiFile) {
+      try {
+        let file = Cc["@mozilla.org/file/directory_service;1"].
+                         getService(Ci.nsIProperties).
+                         get("ProfD", Ci.nsIFile);
+        file.append("TestPilotExperimentFiles"); // TODO this name should go in pref?
+        // Make sure there's a directory with this name; delete any non-directory
+        // file that's in the way.
+        if (file.exists() && !file.isDirectory()) {
+          file.remove(false);
+        }
+        if (!file.exists()) {
+          file.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
+        }
+        file.append("index.json");
+        this._cachedIndexNsiFile = file;
+      } catch(e) {
+        console.warn("Error creating directory for cached index file: " + e);
+      }
     }
-    file.append("index.json");
-    return file;
+    return this._cachedIndexNsiFile;
   },
 
   _cacheIndexFile: function(data) {
     // write data to disk as basedir/index.json
-    let file = this.cachedIndexNsiFile;
-    if (file.exists()) {
-      file.remove(false);
-    }
-    file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
-    // file is nsIFile, data is a string
-    let foStream = Cc["@mozilla.org/network/file-output-stream;1"].
-                             createInstance(Ci.nsIFileOutputStream);
+    try {
+      let file = this.cachedIndexNsiFile;
+      if (file == null) {
+        console.warn("Can't cache index file because directory does not exist.");
+        return;
+      }
+      if (file.exists()) {
+        file.remove(false);
+      }
+      file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
+      // file is nsIFile, data is a string
+      let foStream = Cc["@mozilla.org/network/file-output-stream;1"].
+                               createInstance(Ci.nsIFileOutputStream);
 
-    // use 0x02 | 0x10 to open file for appending.
-    foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
-    // write, create, truncate
-    // In a c file operation, we have no need to set file mode with or operation,
-    // directly using "r" or "w" usually.
-
-    // if you are sure there will never ever be any non-ascii text in data you can
-    // also call foStream.writeData directly
-    let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
-                              createInstance(Ci.nsIConverterOutputStream);
-    converter.init(foStream, "UTF-8", 0, 0);
-    converter.writeString(data);
-    converter.close(); // this closes foStream
+      foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
+      // write, create, truncate
+      let converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
+                                createInstance(Ci.nsIConverterOutputStream);
+      converter.init(foStream, "UTF-8", 0, 0);
+      converter.writeString(data);
+      converter.close(); // this closes foStream too
+    } catch(e) {
+      console.warn("Error cacheing index file: " + e);
+    }
   },
 
+  // https://developer.mozilla.org/en/Table_Of_Errors
   _loadCachedIndexFile: function() {
     // If basedir/index.json exists, read it and return its data
     // Otherwise, return false
     let file = this.cachedIndexNsiFile;
+    if (file == null) {
+      console.warn("Can't load cached index file because directory does not exist.");
+      return false;
+    }
     if (file.exists()) {
-      let data = "";
-      let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
-                        createInstance(Ci.nsIFileInputStream);
-      let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
-                        createInstance(Ci.nsIConverterInputStream);
-      fstream.init(file, -1, 0, 0);
-      cstream.init(fstream, "UTF-8", 0, 0); // you can use another encoding here if you wish
-
-      let str = {};
-      while (cstream.readString(4096, str) != 0) {
-        data += str.value;
+      try {
+        let data = "";
+        let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+                          createInstance(Ci.nsIFileInputStream);
+        let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
+                          createInstance(Ci.nsIConverterInputStream);
+        fstream.init(file, -1, 0, 0);
+        cstream.init(fstream, "UTF-8", 0, 0);
+        let str = {};
+        while (cstream.readString(4096, str) != 0) {
+          data += str.value;
+        }
+        cstream.close(); // this closes fstream too
+        return data;
+      } catch(e) {
+        console.warn("Error occured in reading cached index file: " + e);
+        return false;
       }
-
-      cstream.close(); // this closes fstream
-
-      return data;
     } else {
+      console.warn("Trying to load cached index file but it does not exist.");
       return false;
     }
   },
 
   checkForUpdates: function(callback) {
     // Check for surveys and studies.  Entry point for all download and execution of
     // remote code.
     /* Callback will be called with true or false
@@ -396,17 +427,22 @@ exports.RemoteExperimentLoader.prototype
     let indexFileName = prefs.get("extensions.testpilot.indexFileName",
                                   "index.json");
     let self = this;
     // Unload everything before checking for updates, to be sure we
     // get the newest stuff.
     this._logger.info("Unloading everything to prepare to check for updates.");
     this._refreshLoader();
 
-    let modDate = this.cachedIndexNsiFile.lastModifiedTime;
+    let modDate = 0;
+    if (this.cachedIndexNsiFile) {
+      if (this.cachedIndexNsiFile.exists()) {
+        modDate = this.cachedIndexNsiFile.lastModifiedTime;
+      }
+    }
     let url = resolveUrl(self._baseUrl, indexFileName);
     self._fileGetter(url, function onDone(data) {
       if (data) {
         self._executeFreshIndexFile(data, callback);
         // cache index file contents so we can read them later if we can't get online.
         self._cacheIndexFile(data);
       } else {
         self._logger.info("Could not download index.json, using cached version.");