toolkit/webapps/LinuxNativeApp.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 /**
     6  * Constructor for the Linux native app shell
     7  *
     8  * @param aApp {Object} the app object provided to the install function
     9  * @param aManifest {Object} the manifest data provided by the web app
    10  * @param aCategories {Array} array of app categories
    11  * @param aRegistryDir {String} (optional) path to the registry
    12  */
    13 function NativeApp(aApp, aManifest, aCategories, aRegistryDir) {
    14   CommonNativeApp.call(this, aApp, aManifest, aCategories, aRegistryDir);
    16   this.iconFile = "icon.png";
    17   this.webapprt = "webapprt-stub";
    18   this.configJson = "webapp.json";
    19   this.webappINI = "webapp.ini";
    20   this.zipFile = "application.zip";
    22   this.backupFiles = [ this.iconFile, this.configJson, this.webappINI ];
    23   if (this.isPackaged) {
    24     this.backupFiles.push(this.zipFile);
    25   }
    27   let xdg_data_home = Cc["@mozilla.org/process/environment;1"].
    28                       getService(Ci.nsIEnvironment).
    29                       get("XDG_DATA_HOME");
    30   if (!xdg_data_home) {
    31     xdg_data_home = OS.Path.join(HOME_DIR, ".local", "share");
    32   }
    34   // The desktop file name is: "owa-" + sanitized app name +
    35   // "-" + manifest url hash.
    36   this.desktopINI = OS.Path.join(xdg_data_home, "applications",
    37                                  "owa-" + this.uniqueName + ".desktop");
    38 }
    40 NativeApp.prototype = {
    41   __proto__: CommonNativeApp.prototype,
    43   /**
    44    * Creates a native installation of the web app in the OS
    45    *
    46    * @param aManifest {Object} the manifest data provided by the web app
    47    * @param aZipPath {String} path to the zip file for packaged apps (undefined
    48    *                          for hosted apps)
    49    */
    50   install: Task.async(function*(aManifest, aZipPath) {
    51     if (this._dryRun) {
    52       return;
    53     }
    55     // If the application is already installed, this is a reinstallation.
    56     if (WebappOSUtils.getInstallPath(this.app)) {
    57       return yield this.prepareUpdate(aManifest, aZipPath);
    58     }
    60     this._setData(aManifest);
    62     // The installation directory name is: sanitized app name +
    63     // "-" + manifest url hash.
    64     let installDir = OS.Path.join(HOME_DIR, "." + this.uniqueName);
    66     let dir = getFile(TMP_DIR, this.uniqueName);
    67     dir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
    68     let tmpDir = dir.path;
    70     // Create the installation in a temporary directory.
    71     try {
    72       this._copyPrebuiltFiles(tmpDir);
    73       yield this._createConfigFiles(tmpDir);
    75       if (aZipPath) {
    76         yield OS.File.move(aZipPath, OS.Path.join(tmpDir, this.zipFile));
    77       }
    79       yield this._getIcon(tmpDir);
    80     } catch (ex) {
    81       yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
    82       throw ex;
    83     }
    85     // Apply the installation.
    86     this._removeInstallation(true, installDir);
    88     try {
    89       yield this._applyTempInstallation(tmpDir, installDir);
    90     } catch (ex) {
    91       this._removeInstallation(false, installDir);
    92       yield OS.File.removeDir(tmpDir, { ignoreAbsent: true });
    93       throw ex;
    94     }
    95   }),
    97   /**
    98    * Creates an update in a temporary directory to be applied later.
    99    *
   100    * @param aManifest {Object} the manifest data provided by the web app
   101    * @param aZipPath {String} path to the zip file for packaged apps (undefined
   102    *                          for hosted apps)
   103    */
   104   prepareUpdate: Task.async(function*(aManifest, aZipPath) {
   105     if (this._dryRun) {
   106       return;
   107     }
   109     this._setData(aManifest);
   111     let installDir = WebappOSUtils.getInstallPath(this.app);
   112     if (!installDir) {
   113       throw ERR_NOT_INSTALLED;
   114     }
   116     let baseName = OS.Path.basename(installDir)
   117     let oldUniqueName = baseName.substring(1, baseName.length);
   118     if (this.uniqueName != oldUniqueName) {
   119       // Bug 919799: If the app is still in the registry, migrate its data to
   120       // the new format.
   121       throw ERR_UPDATES_UNSUPPORTED_OLD_NAMING_SCHEME;
   122     }
   124     let updateDir = OS.Path.join(installDir, "update");
   125     yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
   126     yield OS.File.makeDir(updateDir);
   128     try {
   129       yield this._createConfigFiles(updateDir);
   131       if (aZipPath) {
   132         yield OS.File.move(aZipPath, OS.Path.join(updateDir, this.zipFile));
   133       }
   135       yield this._getIcon(updateDir);
   136     } catch (ex) {
   137       yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
   138       throw ex;
   139     }
   140   }),
   142   /**
   143    * Applies an update.
   144    */
   145   applyUpdate: Task.async(function*() {
   146     if (this._dryRun) {
   147       return;
   148     }
   150     let installDir = WebappOSUtils.getInstallPath(this.app);
   151     let updateDir = OS.Path.join(installDir, "update");
   153     let backupDir = yield this._backupInstallation(installDir);
   155     try {
   156       yield this._applyTempInstallation(updateDir, installDir);
   157     } catch (ex) {
   158       yield this._restoreInstallation(backupDir, installDir);
   159       throw ex;
   160     } finally {
   161       yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
   162       yield OS.File.removeDir(updateDir, { ignoreAbsent: true });
   163     }
   164   }),
   166   _applyTempInstallation: Task.async(function*(aTmpDir, aInstallDir) {
   167     yield moveDirectory(aTmpDir, aInstallDir);
   169     this._createSystemFiles(aInstallDir);
   170   }),
   172   _removeInstallation: function(keepProfile, aInstallDir) {
   173     let filesToRemove = [this.desktopINI];
   175     if (keepProfile) {
   176       for (let filePath of this.backupFiles) {
   177         filesToRemove.push(OS.Path.join(aInstallDir, filePath));
   178       }
   180       filesToRemove.push(OS.Path.join(aInstallDir, this.webapprt));
   181     } else {
   182       filesToRemove.push(aInstallDir);
   183     }
   185     removeFiles(filesToRemove);
   186   },
   188   _backupInstallation: Task.async(function*(aInstallDir) {
   189     let backupDir = OS.Path.join(aInstallDir, "backup");
   190     yield OS.File.removeDir(backupDir, { ignoreAbsent: true });
   191     yield OS.File.makeDir(backupDir);
   193     for (let filePath of this.backupFiles) {
   194       yield OS.File.move(OS.Path.join(aInstallDir, filePath),
   195                          OS.Path.join(backupDir, filePath));
   196     }
   198     return backupDir;
   199   }),
   201   _restoreInstallation: function(aBackupDir, aInstallDir) {
   202     return moveDirectory(aBackupDir, aInstallDir);
   203   },
   205   _copyPrebuiltFiles: function(aDir) {
   206     let destDir = getFile(aDir);
   207     let stub = getFile(this.runtimeFolder, this.webapprt);
   208     stub.copyTo(destDir, null);
   209   },
   211   /**
   212    * Translate marketplace categories to freedesktop.org categories.
   213    *
   214    * @link http://standards.freedesktop.org/menu-spec/menu-spec-latest.html#category-registry
   215    *
   216    * @return an array of categories
   217    */
   218   _translateCategories: function() {
   219     let translations = {
   220       "books": "Education;Literature",
   221       "business": "Finance",
   222       "education": "Education",
   223       "entertainment": "Amusement",
   224       "sports": "Sports",
   225       "games": "Game",
   226       "health-fitness": "MedicalSoftware",
   227       "lifestyle": "Amusement",
   228       "music": "Audio;Music",
   229       "news-weather": "News",
   230       "photo-video": "Video;AudioVideo;Photography",
   231       "productivity": "Office",
   232       "shopping": "Amusement",
   233       "social": "Chat",
   234       "travel": "Amusement",
   235       "reference": "Science;Education;Documentation",
   236       "maps-navigation": "Maps",
   237       "utilities": "Utility"
   238     };
   240     // The trailing semicolon is needed as written in the freedesktop specification
   241     let categories = "";
   242     for (let category of this.categories) {
   243       let catLower = category.toLowerCase();
   244       if (catLower in translations) {
   245         categories += translations[catLower] + ";";
   246       }
   247     }
   249     return categories;
   250   },
   252   _createConfigFiles: function(aDir) {
   253     // ${InstallDir}/webapp.json
   254     yield writeToFile(OS.Path.join(aDir, this.configJson),
   255                       JSON.stringify(this.webappJson));
   257     let webappsBundle = Services.strings.createBundle("chrome://global/locale/webapps.properties");
   259     // ${InstallDir}/webapp.ini
   260     let webappINIfile = getFile(aDir, this.webappINI);
   262     let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
   263                  getService(Ci.nsIINIParserFactory).
   264                  createINIParser(webappINIfile).
   265                  QueryInterface(Ci.nsIINIParserWriter);
   266     writer.setString("Webapp", "Name", this.appName);
   267     writer.setString("Webapp", "Profile", this.uniqueName);
   268     writer.setString("Webapp", "UninstallMsg", webappsBundle.formatStringFromName("uninstall.notification", [this.appName], 1));
   269     writer.setString("WebappRT", "InstallDir", this.runtimeFolder);
   270     writer.writeFile();
   271   },
   273   _createSystemFiles: function(aInstallDir) {
   274     let webappsBundle = Services.strings.createBundle("chrome://global/locale/webapps.properties");
   276     let webapprtPath = OS.Path.join(aInstallDir, this.webapprt);
   278     // $XDG_DATA_HOME/applications/owa-<webappuniquename>.desktop
   279     let desktopINIfile = getFile(this.desktopINI);
   280     if (desktopINIfile.parent && !desktopINIfile.parent.exists()) {
   281       desktopINIfile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
   282     }
   284     let writer = Cc["@mozilla.org/xpcom/ini-processor-factory;1"].
   285                  getService(Ci.nsIINIParserFactory).
   286                  createINIParser(desktopINIfile).
   287                  QueryInterface(Ci.nsIINIParserWriter);
   288     writer.setString("Desktop Entry", "Name", this.appName);
   289     writer.setString("Desktop Entry", "Comment", this.shortDescription);
   290     writer.setString("Desktop Entry", "Exec", '"' + webapprtPath + '"');
   291     writer.setString("Desktop Entry", "Icon", OS.Path.join(aInstallDir,
   292                                                            this.iconFile));
   293     writer.setString("Desktop Entry", "Type", "Application");
   294     writer.setString("Desktop Entry", "Terminal", "false");
   296     let categories = this._translateCategories();
   297     if (categories)
   298       writer.setString("Desktop Entry", "Categories", categories);
   300     writer.setString("Desktop Entry", "Actions", "Uninstall;");
   301     writer.setString("Desktop Action Uninstall", "Name", webappsBundle.GetStringFromName("uninstall.label"));
   302     writer.setString("Desktop Action Uninstall", "Exec", webapprtPath + " -remove");
   304     writer.writeFile();
   306     desktopINIfile.permissions = PERMS_FILE | OS.Constants.libc.S_IXUSR;
   307   },
   309   /**
   310    * Process the icon from the imageStream as retrieved from
   311    * the URL by getIconForApp().
   312    *
   313    * @param aMimeType     ahe icon mimetype
   314    * @param aImageStream  the stream for the image data
   315    * @param aDir          the directory where the icon should be stored
   316    */
   317   _processIcon: function(aMimeType, aImageStream, aDir) {
   318     let deferred = Promise.defer();
   320     let imgTools = Cc["@mozilla.org/image/tools;1"].
   321                    createInstance(Ci.imgITools);
   323     let imgContainer = imgTools.decodeImage(aImageStream, aMimeType);
   324     let iconStream = imgTools.encodeImage(imgContainer, "image/png");
   326     let iconFile = getFile(aDir, this.iconFile);
   327     let outputStream = FileUtils.openSafeFileOutputStream(iconFile);
   328     NetUtil.asyncCopy(iconStream, outputStream, function(aResult) {
   329       if (Components.isSuccessCode(aResult)) {
   330         deferred.resolve();
   331       } else {
   332         deferred.reject("Failure copying icon: " + aResult);
   333       }
   334     });
   336     return deferred.promise;
   337   }
   338 }

mercurial