Bug 764172. Preserve profile when reinstalling webapp over existing installation. r=felipe
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Wed, 27 Jun 2012 17:14:55 -0700
changeset 97823 ea8be9af91839f698906962a224abafeb004598d
parent 97822 c408dd243e7aa8e0e8dcd419fe35eea3f04a6899
child 97824 fcb5936df6a8137577683b597ff1e729b52197d3
push id22999
push useremorley@mozilla.com
push dateThu, 28 Jun 2012 08:03:15 +0000
treeherdermozilla-central@bf8f2961d0cc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfelipe
bugs764172
milestone16.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 764172. Preserve profile when reinstalling webapp over existing installation. r=felipe
browser/modules/WebappsInstaller.jsm
--- a/browser/modules/WebappsInstaller.jsm
+++ b/browser/modules/WebappsInstaller.jsm
@@ -161,27 +161,27 @@ function WinNativeApp(aData) {
 
 WinNativeApp.prototype = {
   /**
    * Install the app in the system by creating the folder structure,
    *
    */
   install: function() {
     // Remove previously installed app (for update purposes)
-    this._removeInstallation();
+    this._removeInstallation(true);
 
     try {
       this._createDirectoryStructure();
       this._copyPrebuiltFiles();
       this._createConfigFiles();
       this._createShortcutFiles();
       this._writeSystemKeys();
       this._createAppProfile();
     } catch (ex) {
-      this._removeInstallation();
+      this._removeInstallation(false);
       throw(ex);
     }
 
     getIconForApp(this, function() {});
   },
 
   /**
    * Initializes properties that will be used during the installation process,
@@ -203,44 +203,45 @@ WinNativeApp.prototype = {
     let installDirLeaf = this.launchURI.scheme
                        + ";"
                        + this.launchURI.host;
     if (this.launchURI.port != -1) {
       installDirLeaf += ";" + this.launchURI.port;
     }
     this.installDir.append(installDirLeaf);
 
+    this.webapprt = this.installDir.clone();
+    this.webapprt.append(this.appNameAsFilename + ".exe");
+
+    this.configJson = this.installDir.clone();
+    this.configJson.append("webapp.json");
+
+    this.webappINI = this.installDir.clone();
+    this.webappINI.append("webapp.ini");
+
     this.uninstallDir = this.installDir.clone();
     this.uninstallDir.append("uninstall");
 
     this.uninstallerFile = this.uninstallDir.clone();
     this.uninstallerFile.append("webapp-uninstaller.exe");
 
     this.iconFile = this.installDir.clone();
     this.iconFile.append("chrome");
     this.iconFile.append("icons");
     this.iconFile.append("default");
     this.iconFile.append("default.ico");
 
-    this.desktopShortcut = Services.dirsvc.get("Desk", Ci.nsILocalFile);
-    this.desktopShortcut.append(this.appNameAsFilename + ".lnk");
-    this.desktopShortcut.followLinks = false;
-
-    this.startMenuShortcut = Services.dirsvc.get("Progs", Ci.nsILocalFile);
-    this.startMenuShortcut.append(this.appNameAsFilename + ".lnk");
-    this.startMenuShortcut.followLinks = false;
-
     this.uninstallSubkeyStr = this.launchURI.scheme + "://" +
                               this.launchURI.hostPort;
   },
 
   /**
    * Remove the current installation
    */
-  _removeInstallation : function() {
+  _removeInstallation : function(keepProfile) {
     let uninstallKey;
     try {
       uninstallKey = Cc["@mozilla.org/windows-registry-key;1"]
                      .createInstance(Ci.nsIWindowsRegKey);
       uninstallKey.open(uninstallKey.ROOT_KEY_CURRENT_USER,
                         "SOFTWARE\\Microsoft\\Windows\\" +
                         "CurrentVersion\\Uninstall",
                         uninstallKey.ACCESS_WRITE);
@@ -248,111 +249,103 @@ WinNativeApp.prototype = {
         uninstallKey.removeChild(this.uninstallSubkeyStr);
       }
     } catch (e) {
     } finally {
       if(uninstallKey)
         uninstallKey.close();
     }
 
-    try {
-      if(this.installDir.exists()) {
-        let dir = this.installDir.QueryInterface(Ci.nsILocalFile);
-        // We need to set followLinks to false so that the shortcut
-        // files can be removed even after the .exe it was pointing
-        // to was removed.
-        dir.followLinks = false;
-        dir.remove(true);
-      }
-    } catch(ex) {
+    let desktopShortcut = Services.dirsvc.get("Desk", Ci.nsILocalFile);
+    desktopShortcut.append(this.appNameAsFilename + ".lnk");
+
+    let startMenuShortcut = Services.dirsvc.get("Progs", Ci.nsILocalFile);
+    startMenuShortcut.append(this.appNameAsFilename + ".lnk");
+
+    let filesToRemove = [desktopShortcut, startMenuShortcut];
+
+    if (keepProfile) {
+      filesToRemove.push(this.iconFile);
+      filesToRemove.push(this.webapprt);
+      filesToRemove.push(this.configJson);
+      filesToRemove.push(this.webappINI);
+      filesToRemove.push(this.uninstallDir);
+    } else {
+      filesToRemove.push(this.installDir);
     }
 
-    try {
-      if(this.desktopShortcut && this.desktopShortcut.exists()) {
-        this.desktopShortcut.remove(false);
-      }
-
-      if(this.startMenuShortcut && this.startMenuShortcut.exists()) {
-        this.startMenuShortcut.remove(false);
-      }
-    } catch(ex) {
-    }
+    removeFiles(filesToRemove);
   },
 
   /**
    * Creates the main directory structure.
    */
   _createDirectoryStructure: function() {
-    this.installDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+    if (!this.installDir.exists())
+      this.installDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
     this.uninstallDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
   },
 
   /**
    * Creates the profile to be used for this app.
    */
   _createAppProfile: function() {
     if (!this.appcacheDefined)
       return;
 
     let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"]
                     .getService(Ci.nsIToolkitProfileService);
 
-    this.appProfile = profSvc.createDefaultProfileForApp(this.installDir.leafName,
-                                                         null, null);
+    try {
+      this.appProfile = profSvc.createDefaultProfileForApp(this.installDir.leafName,
+                                                           null, null);
+    } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {}
   },
 
   /**
    * Copy the pre-built files into their destination folders.
    */
   _copyPrebuiltFiles: function() {
-    let webapprt = this.runtimeFolder.clone();
-    webapprt.append("webapprt-stub.exe");
-    webapprt.copyTo(this.installDir, this.appNameAsFilename + ".exe");
+    let webapprtPre = this.runtimeFolder.clone();
+    webapprtPre.append("webapprt-stub.exe");
+    webapprtPre.copyTo(this.installDir, this.webapprt.leafName);
 
     let uninstaller = this.runtimeFolder.clone();
     uninstaller.append("webapp-uninstaller.exe");
     uninstaller.copyTo(this.uninstallDir, this.uninstallerFile.leafName);
   },
 
   /**
    * Creates the configuration files into their destination folders.
    */
   _createConfigFiles: function() {
     // ${InstallDir}/webapp.json
-    let configJson = this.installDir.clone();
-    configJson.append("webapp.json");
-    writeToFile(configJson, JSON.stringify(this.webappJson), function() {});
-
-    // ${InstallDir}/webapp.ini
-    let webappINI = this.installDir.clone().QueryInterface(Ci.nsILocalFile);
-    webappINI.append("webapp.ini");
+    writeToFile(this.configJson, JSON.stringify(this.webappJson), function() {});
 
     let factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]
                     .getService(Ci.nsIINIParserFactory);
 
-    let writer = factory.createINIParser(webappINI).QueryInterface(Ci.nsIINIParserWriter);
+    // ${InstallDir}/webapp.ini
+    let writer = factory.createINIParser(this.webappINI).QueryInterface(Ci.nsIINIParserWriter);
     writer.setString("Webapp", "Name", this.appName);
     writer.setString("Webapp", "Profile", this.installDir.leafName);
     writer.setString("Webapp", "Executable", this.appNameAsFilename);
     writer.setString("WebappRT", "InstallDir", this.runtimeFolder.path);
     writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
 
     // ${UninstallDir}/shortcuts_log.ini
     let shortcutLogsINI = this.uninstallDir.clone().QueryInterface(Ci.nsILocalFile);
     shortcutLogsINI.append("shortcuts_log.ini");
 
     writer = factory.createINIParser(shortcutLogsINI).QueryInterface(Ci.nsIINIParserWriter);
     writer.setString("STARTMENU", "Shortcut0", this.appNameAsFilename + ".lnk");
     writer.setString("DESKTOP", "Shortcut0", this.appNameAsFilename + ".lnk");
     writer.setString("TASKBAR", "Migrated", "true");
     writer.writeFile(null, Ci.nsIINIParserWriter.WRITE_UTF16);
 
-    writer = null;
-    factory = null;
-
     // ${UninstallDir}/uninstall.log
     let uninstallContent = 
       "File: \\webapp.ini\r\n" +
       "File: \\webapp.json\r\n" +
       "File: \\webapprt.old\r\n" +
       "File: \\chrome\\icons\\default\\default.ico";
     let uninstallLog = this.uninstallDir.clone();
     uninstallLog.append("uninstall.log");
@@ -402,17 +395,17 @@ WinNativeApp.prototype = {
    * Creates a shortcut file inside the app installation folder and makes
    * two copies of it: one into the desktop and one into the start menu.
    */
   _createShortcutFiles: function() {
     let shortcut = this.installDir.clone().QueryInterface(Ci.nsILocalFileWin);
     shortcut.append(this.appNameAsFilename + ".lnk");
 
     let target = this.installDir.clone();
-    target.append(this.appNameAsFilename + ".exe");
+    target.append(this.webapprt.leafName);
 
     /* function nsILocalFileWin.setShortcut(targetFile, workingDir, args,
                                             description, iconFile, iconIndex) */
 
     shortcut.setShortcut(target, this.installDir.clone(), null,
                          this.shortDescription, this.iconFile, 0);
 
     let desktop = Services.dirsvc.get("Desk", Ci.nsILocalFile);
@@ -453,17 +446,18 @@ WinNativeApp.prototype = {
       imgTools.decodeImageData(aImageStream, aMimeType, imgContainer);
       iconStream = imgTools.encodeImage(imgContainer.value,
                                         "image/vnd.microsoft.icon",
                                         "format=bmp;bpp=32");
     } catch (e) {
       throw("processIcon - Failure converting icon (" + e + ")");
     }
 
-    this.iconFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+    if (!this.iconFile.parent.exists())
+      this.iconFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
     let outputStream = FileUtils.openSafeFileOutputStream(this.iconFile);
     NetUtil.asyncCopy(iconStream, outputStream);
   }
 }
 
 #elifdef XP_MACOSX
 
 function MacNativeApp(aData) {
@@ -519,34 +513,23 @@ MacNativeApp.prototype = {
       this._removeInstallation(false);
       throw(ex);
     }
 
     getIconForApp(this, this._moveToApplicationsFolder);
   },
 
   _removeInstallation: function(keepProfile) {
-    try {
-      if(this.installDir.exists()) {
-        this.installDir.followLinks = false;
-        this.installDir.remove(true);
-      }
-    } catch(ex) {
+    let filesToRemove = [this.installDir];
+
+    if (!keepProfile) {
+      filesToRemove.push(this.appProfileDir);
     }
 
-   if (keepProfile)
-     return;
-
-   try {
-      if(this.appProfileDir.exists()) {
-        this.appProfileDir.followLinks = false;
-        this.appProfileDir.remove(true);
-      }
-    } catch(ex) {
-    }
+    removeFiles(filesToRemove);
   },
 
   _createDirectoryStructure: function() {
     if (!this.appProfileDir.exists())
       this.appProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
 
     this.contentsDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
     this.macOSDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
@@ -555,18 +538,20 @@ MacNativeApp.prototype = {
 
   _createAppProfile: function() {
     if (!this.appcacheDefined)
       return;
 
     let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"]
                     .getService(Ci.nsIToolkitProfileService);
 
-    this.appProfile = profSvc.createDefaultProfileForApp(this.appProfileDir.leafName,
-                                                         null, null);
+    try {
+      this.appProfile = profSvc.createDefaultProfileForApp(this.appProfileDir.leafName,
+                                                           null, null);
+    } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {}
   },
 
   _copyPrebuiltFiles: function() {
     let webapprt = this.runtimeFolder.clone();
     webapprt.append("webapprt-stub");
     webapprt.copyTo(this.macOSDir, "webapprt");
   },
 
@@ -696,16 +681,22 @@ LinuxNativeApp.prototype = {
     this.installDir.append("." + this.uniqueName);
 
     this.iconFile = this.installDir.clone();
     this.iconFile.append(this.uniqueName + ".png");
 
     this.webapprt = this.installDir.clone();
     this.webapprt.append("webapprt-stub");
 
+    this.configJson = this.installDir.clone();
+    this.configJson.append("webapp.json");
+
+    this.webappINI = this.installDir.clone();
+    this.webappINI.append("webapp.ini");
+
     let env = Cc["@mozilla.org/process/environment;1"]
                 .getService(Ci.nsIEnvironment);
     let xdg_data_home_env = env.get("XDG_DATA_HOME");
     if (xdg_data_home_env != "") {
       this.desktopINI = Cc["@mozilla.org/file/local;1"]
                           .createInstance(Ci.nsILocalFile);
       this.desktopINI.initWithPath(xdg_data_home_env);
     }
@@ -715,77 +706,79 @@ LinuxNativeApp.prototype = {
       this.desktopINI.append("share");
     }
 
     this.desktopINI.append("applications");
     this.desktopINI.append("owa-" + this.uniqueName + ".desktop");
   },
 
   install: function() {
-    this._removeInstallation();
+    this._removeInstallation(true);
 
     try {
       this._createDirectoryStructure();
       this._copyPrebuiltFiles();
       this._createConfigFiles();
       this._createAppProfile();
     } catch (ex) {
-      this._removeInstallation();
+      this._removeInstallation(false);
       throw(ex);
     }
 
     getIconForApp(this, function() {});
   },
 
-  _removeInstallation: function() {
-    try {
-      if (this.installDir.exists())
-        this.installDir.remove(true);
+  _removeInstallation: function(keepProfile) {
+    let filesToRemove = [this.desktopINI];
 
-      if (this.desktopINI.exists())
-        this.desktopINI.remove(false);
-    } catch(ex) {
+    if (keepProfile) {
+      filesToRemove.push(this.iconFile);
+      filesToRemove.push(this.webapprt);
+      filesToRemove.push(this.configJson);
+      filesToRemove.push(this.webappINI);
+    } else {
+      filesToRemove.push(this.installDir);
     }
+
+    removeFiles(filesToRemove);
   },
 
   _createDirectoryStructure: function() {
-    this.installDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+    if (!this.installDir.exists())
+      this.installDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
   },
 
   _copyPrebuiltFiles: function() {
     let webapprtPre = this.runtimeFolder.clone();
     webapprtPre.append(this.webapprt.leafName);
     webapprtPre.copyTo(this.installDir, this.webapprt.leafName);
   },
 
   _createAppProfile: function() {
     if (!this.appcacheDefined)
       return;
 
     let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"]
                     .getService(Ci.nsIToolkitProfileService);
 
-    return profSvc.createDefaultProfileForApp(this.installDir.leafName,
-                                              null, null);
+    try {
+      this.appProfile = profSvc.createDefaultProfileForApp(this.installDir.leafName,
+                                                           null, null);
+    } catch (ex if ex.result == Cr.NS_ERROR_ALREADY_INITIALIZED) {}
   },
 
   _createConfigFiles: function() {
     // ${InstallDir}/webapp.json
-    let configJson = this.installDir.clone();
-    configJson.append("webapp.json");
-    writeToFile(configJson, JSON.stringify(this.webappJson), function() {});
-
-    // ${InstallDir}/webapp.ini
-    let webappINI = this.installDir.clone();
-    webappINI.append("webapp.ini");
+    writeToFile(this.configJson, JSON.stringify(this.webappJson), function() {});
 
     let factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]
                     .getService(Ci.nsIINIParserFactory);
 
-    let writer = factory.createINIParser(webappINI).QueryInterface(Ci.nsIINIParserWriter);
+    // ${InstallDir}/webapp.ini
+    let writer = factory.createINIParser(this.webappINI).QueryInterface(Ci.nsIINIParserWriter);
     writer.setString("Webapp", "Name", this.appName);
     writer.setString("Webapp", "Profile", this.uniqueName);
     writer.setString("WebappRT", "InstallDir", this.runtimeFolder.path);
     writer.writeFile();
 
     // $XDG_DATA_HOME/applications/owa-<webappuniquename>.desktop
     this.desktopINI.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0755);
 
@@ -912,16 +905,32 @@ function getAvailableFile(aFolder, aName
     if (!file.exists()) {
       return file;
     }
   }
 
   return null;
 }
 
+/**
+ * Attempts to remove files or directories.
+ *
+ * @param aFiles An array with nsIFile objects to be removed
+ */
+function removeFiles(aFiles) {
+  for (let file of aFiles) {
+    try {
+      if (file.exists()) {
+        file.followLinks = false;
+        file.remove(true);
+      }
+    } catch(ex) {}
+  }
+}
+
 function escapeXML(aStr) {
   return aStr.toString()
              .replace(/&/g, "&amp;")
              .replace(/"/g, "&quot;")
              .replace(/'/g, "&apos;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;");
 }