Bug 748199. Cannot install an webapp with the same name of a previously installed app on Mac. r=mstange
authorFelipe Gomes <felipc@gmail.com>
Thu, 26 Apr 2012 07:37:34 -0700
changeset 92487 8180e2442cd20eadbb11edfba07936b448fe0b24
parent 92486 2b118a11328b3ca67541660f4c6b612c161c8c34
child 92488 b952bb042f129b3ac3083209ecc447b080c3ed6d
push id22535
push userfelipc@gmail.com
push dateThu, 26 Apr 2012 14:43:16 +0000
treeherdermozilla-central@b952bb042f12 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs748199
milestone15.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 748199. Cannot install an webapp with the same name of a previously installed app on Mac. r=mstange
browser/modules/WebappsInstaller.jsm
--- a/browser/modules/WebappsInstaller.jsm
+++ b/browser/modules/WebappsInstaller.jsm
@@ -462,18 +462,19 @@ MacNativeApp.prototype = {
     //  host of the app origin + ";" +
     //  protocol + ";" +
     //  port (-1 for default port)
     this.appProfileDir = this.appSupportDir.clone();
     this.appProfileDir.append(this.launchURI.host + ";" +
                               this.launchURI.scheme + ";" +
                               this.launchURI.port);
 
-    this.installDir = Services.dirsvc.get("LocApp", Ci.nsILocalFile);
+    this.installDir = Services.dirsvc.get("TmpD", Ci.nsILocalFile);
     this.installDir.append(this.appNameAsFilename + ".app");
+    this.installDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0755);
 
     this.contentsDir = this.installDir.clone();
     this.contentsDir.append("Contents");
 
     this.macOSDir = this.contentsDir.clone();
     this.macOSDir.append("MacOS");
 
     this.resourcesDir = this.contentsDir.clone();
@@ -491,17 +492,17 @@ MacNativeApp.prototype = {
       this._createDirectoryStructure();
       this._copyPrebuiltFiles();
       this._createConfigFiles();
     } catch (ex) {
       this._removeInstallation(false);
       throw(ex);
     }
 
-    getIconForApp(this, this._createPListFile);
+    getIconForApp(this, this._moveToApplicationsFolder);
   },
 
   _removeInstallation: function(keepProfile) {
     try {
       if(this.installDir.exists()) {
         this.installDir.followLinks = false;
         this.installDir.remove(true);
       }
@@ -519,17 +520,16 @@ MacNativeApp.prototype = {
     } catch(ex) {
     }
   },
 
   _createDirectoryStructure: function() {
     if (!this.appProfileDir.exists())
       this.appProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
 
-    this.installDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
     this.contentsDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
     this.macOSDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
     this.resourcesDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
   },
 
   _copyPrebuiltFiles: function() {
     let webapprt = this.processFolder.clone();
     webapprt.append("webapprt-stub");
@@ -557,19 +557,17 @@ MacNativeApp.prototype = {
 
     let factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]
                     .getService(Ci.nsIINIParserFactory);
 
     let writer = factory.createINIParser(applicationINI).QueryInterface(Ci.nsIINIParserWriter);
     writer.setString("Webapp", "Name", this.appName);
     writer.setString("Webapp", "Profile", this.appProfileDir.leafName);
     writer.writeFile();
-  },
 
-  _createPListFile: function() {
     // ${InstallDir}/Contents/Info.plist
     let infoPListContent = '<?xml version="1.0" encoding="UTF-8"?>\n\
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n\
 <plist version="1.0">\n\
   <dict>\n\
     <key>CFBundleDevelopmentRegion</key>\n\
     <string>English</string>\n\
     <key>CFBundleDisplayName</key>\n\
@@ -597,16 +595,27 @@ MacNativeApp.prototype = {
   </dict>\n\
 </plist>';
 
     let infoPListFile = this.contentsDir.clone();
     infoPListFile.append("Info.plist");
     writeToFile(infoPListFile, infoPListContent, function() {});
   },
 
+  _moveToApplicationsFolder: function() {
+    let appDir = Services.dirsvc.get("LocApp", Ci.nsILocalFile);
+    let destination = getAvailableFile(appDir,
+                                       this.appNameAsFilename,
+                                       ".app");
+    if (!destination) {
+      return false;
+    }
+    this.installDir.moveTo(destination.parent, destination.leafName);
+  },
+
   /**
    * This variable specifies if the icon retrieval process should
    * use a temporary file in the system or a binary stream. This
    * is accessed by a common function in WebappsIconHelpers.js and
    * is different for each platform.
    */
   useTmpForIcon: true,
 
@@ -681,16 +690,57 @@ function stripStringForFilename(aPossibl
   let stripFrontRE = new RegExp("^\\W*","gi");
   let stripBackRE = new RegExp("\\W*$","gi");
 
   let stripped = aPossiblyBadFilenameString.replace(stripFrontRE, "");
   stripped = stripped.replace(stripBackRE, "");
   return stripped;
 }
 
+/**
+ * Finds a unique name available in a folder (i.e., non-existent file)
+ *
+ * @param aFolder nsIFile that represents the directory where we want to write
+ * @param aName   string with the filename (minus the extension) desired
+ * @param aExtension string with the file extension, including the dot
+
+ * @return nsILocalFile or null if folder is unwritable or unique name
+ *         was not available
+ */
+function getAvailableFile(aFolder, aName, aExtension) {
+  let folder = aFolder.QueryInterface(Ci.nsILocalFile);
+  folder.followLinks = false;
+  if (!folder.isDirectory() || !folder.isWritable()) {
+    return null;
+  }
+
+  let file = folder.clone();
+  file.append(aName + aExtension);
+
+  if (!file.exists()) {
+    return file;
+  }
+
+  for (let i = 2; i < 10; i++) {
+    file.leafName = aName + " (" + i + ")" + aExtension;
+    if (!file.exists()) {
+      return file;
+    }
+  }
+
+  for (let i = 10; i < 100; i++) {
+    file.leafName = aName + "-" + i + aExtension;
+    if (!file.exists()) {
+      return file;
+    }
+  }
+
+  return null;
+}
+
 function escapeXML(aStr) {
   return aStr.toString()
              .replace(/&/g, "&amp;")
              .replace(/"/g, "&quot;")
              .replace(/'/g, "&apos;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;");
 }