diff options
Diffstat (limited to 'b2g/components/DirectoryProvider.js')
-rw-r--r-- | b2g/components/DirectoryProvider.js | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/b2g/components/DirectoryProvider.js b/b2g/components/DirectoryProvider.js new file mode 100644 index 000000000..a7dccd0c9 --- /dev/null +++ b/b2g/components/DirectoryProvider.js @@ -0,0 +1,295 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/AppConstants.jsm"); + +const XRE_OS_UPDATE_APPLY_TO_DIR = "OSUpdApplyToD" +const UPDATE_ARCHIVE_DIR = "UpdArchD" +const LOCAL_DIR = "/data/local"; +const UPDATES_DIR = "updates/0"; +const FOTA_DIR = "updates/fota"; +const COREAPPSDIR_PREF = "b2g.coreappsdir" + +XPCOMUtils.defineLazyServiceGetter(Services, "env", + "@mozilla.org/process/environment;1", + "nsIEnvironment"); + +XPCOMUtils.defineLazyServiceGetter(Services, "um", + "@mozilla.org/updates/update-manager;1", + "nsIUpdateManager"); + +XPCOMUtils.defineLazyServiceGetter(Services, "volumeService", + "@mozilla.org/telephony/volume-service;1", + "nsIVolumeService"); + +XPCOMUtils.defineLazyServiceGetter(this, "cpmm", + "@mozilla.org/childprocessmessagemanager;1", + "nsISyncMessageSender"); + +XPCOMUtils.defineLazyGetter(this, "gExtStorage", function dp_gExtStorage() { + return Services.env.get("EXTERNAL_STORAGE"); +}); + +// This exists to mark the affected code for bug 828858. +const gUseSDCard = true; + +const VERBOSE = 1; +var log = + VERBOSE ? + function log_dump(msg) { dump("DirectoryProvider: " + msg + "\n"); } : + function log_noop(msg) { }; + +function DirectoryProvider() { +} + +DirectoryProvider.prototype = { + classID: Components.ID("{9181eb7c-6f87-11e1-90b1-4f59d80dd2e5}"), + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]), + _xpcom_factory: XPCOMUtils.generateSingletonFactory(DirectoryProvider), + + _profD: null, + + getFile: function(prop, persistent) { + if (AppConstants.platform === "gonk") { + return this.getFileOnGonk(prop, persistent); + } + return this.getFileNotGonk(prop, persistent); + }, + + getFileOnGonk: function(prop, persistent) { + let localProps = ["cachePDir", "webappsDir", "PrefD", "indexedDBPDir", + "permissionDBPDir", "UpdRootD"]; + if (localProps.indexOf(prop) != -1) { + let file = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile) + file.initWithPath(LOCAL_DIR); + persistent.value = true; + return file; + } + if (prop == "ProfD") { + let dir = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile); + dir.initWithPath(LOCAL_DIR+"/tests/profile"); + if (dir.exists()) { + persistent.value = true; + return dir; + } + } + if (prop == "coreAppsDir") { + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile) + file.initWithPath("/system/b2g"); + persistent.value = true; + return file; + } + if (prop == UPDATE_ARCHIVE_DIR) { + // getUpdateDir will set persistent to false since it may toggle between + // /data/local/ and /mnt/sdcard based on free space and/or availability + // of the sdcard. + // before download, check if free space is 2.1 times of update.mar + return this.getUpdateDir(persistent, UPDATES_DIR, 2.1); + } + if (prop == XRE_OS_UPDATE_APPLY_TO_DIR) { + // getUpdateDir will set persistent to false since it may toggle between + // /data/local/ and /mnt/sdcard based on free space and/or availability + // of the sdcard. + // before apply, check if free space is 1.1 times of update.mar + return this.getUpdateDir(persistent, FOTA_DIR, 1.1); + } + return null; + }, + + getFileNotGonk: function(prop, persistent) { + // In desktop builds, coreAppsDir is the same as the profile + // directory unless otherwise specified. We just need to get the + // path from the parent, and it is then used to build + // jar:remoteopenfile:// uris. + if (prop == "coreAppsDir") { + let coreAppsDirPref; + try { + coreAppsDirPref = Services.prefs.getCharPref(COREAPPSDIR_PREF); + } catch (e) { + // coreAppsDirPref may not exist if we're on an older version + // of gaia, so just fail silently. + } + let appsDir; + // If pref doesn't exist or isn't set, default to old value + if (!coreAppsDirPref || coreAppsDirPref == "") { + appsDir = Services.dirsvc.get("ProfD", Ci.nsIFile); + appsDir.append("webapps"); + } else { + appsDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile) + appsDir.initWithPath(coreAppsDirPref); + } + persistent.value = true; + return appsDir; + } else if (prop == "ProfD") { + let inParent = Cc["@mozilla.org/xre/app-info;1"] + .getService(Ci.nsIXULRuntime) + .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; + if (inParent) { + // Just bail out to use the default from toolkit. + return null; + } + if (!this._profD) { + this._profD = cpmm.sendSyncMessage("getProfD", {})[0]; + } + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath(this._profD); + persistent.value = true; + return file; + } + return null; + }, + + // The VolumeService only exists on the device, and not on desktop + volumeHasFreeSpace: function dp_volumeHasFreeSpace(volumePath, requiredSpace) { + if (!volumePath) { + return false; + } + if (!Services.volumeService) { + return false; + } + let volume = Services.volumeService.createOrGetVolumeByPath(volumePath); + if (!volume || volume.state !== Ci.nsIVolume.STATE_MOUNTED) { + return false; + } + let stat = volume.getStats(); + if (!stat) { + return false; + } + return requiredSpace <= stat.freeBytes; + }, + + findUpdateDirWithFreeSpace: function dp_findUpdateDirWithFreeSpace(requiredSpace, subdir) { + if (!Services.volumeService) { + return this.createUpdatesDir(LOCAL_DIR, subdir); + } + + let activeUpdate = Services.um.activeUpdate; + if (gUseSDCard) { + if (this.volumeHasFreeSpace(gExtStorage, requiredSpace)) { + let extUpdateDir = this.createUpdatesDir(gExtStorage, subdir); + if (extUpdateDir !== null) { + return extUpdateDir; + } + log("Warning: " + gExtStorage + " has enough free space for update " + + activeUpdate.name + ", but is not writable"); + } + } + + if (this.volumeHasFreeSpace(LOCAL_DIR, requiredSpace)) { + let localUpdateDir = this.createUpdatesDir(LOCAL_DIR, subdir); + if (localUpdateDir !== null) { + return localUpdateDir; + } + log("Warning: " + LOCAL_DIR + " has enough free space for update " + + activeUpdate.name + ", but is not writable"); + } + + return null; + }, + + getUpdateDir: function dp_getUpdateDir(persistent, subdir, multiple) { + let defaultUpdateDir = this.getDefaultUpdateDir(); + persistent.value = false; + + let activeUpdate = Services.um.activeUpdate; + if (!activeUpdate) { + log("Warning: No active update found, using default update dir: " + + defaultUpdateDir); + return defaultUpdateDir; + } + + let selectedPatch = activeUpdate.selectedPatch; + if (!selectedPatch) { + log("Warning: No selected patch, using default update dir: " + + defaultUpdateDir); + return defaultUpdateDir; + } + + let requiredSpace = selectedPatch.size * multiple; + let updateDir = this.findUpdateDirWithFreeSpace(requiredSpace, subdir); + if (updateDir) { + return updateDir; + } + + // If we've gotten this far, there isn't enough free space to download the patch + // on either external storage or /data/local. All we can do is report the + // error and let upstream code handle it more gracefully. + log("Error: No volume found with " + requiredSpace + " bytes for downloading"+ + " update " + activeUpdate.name); + activeUpdate.errorCode = Cr.NS_ERROR_FILE_TOO_BIG; + return null; + }, + + createUpdatesDir: function dp_createUpdatesDir(root, subdir) { + let dir = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile); + dir.initWithPath(root); + if (!dir.isWritable()) { + log("Error: " + dir.path + " isn't writable"); + return null; + } + dir.appendRelativePath(subdir); + if (dir.exists()) { + if (dir.isDirectory() && dir.isWritable()) { + return dir; + } + // subdir is either a file or isn't writable. In either case we + // can't use it. + log("Error: " + dir.path + " is a file or isn't writable"); + return null; + } + // subdir doesn't exist, and the parent is writable, so try to + // create it. This can fail if a file named updates exists. + try { + dir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0770', 8)); + } catch (e) { + // The create failed for some reason. We can't use it. + log("Error: " + dir.path + " unable to create directory"); + return null; + } + return dir; + }, + + getDefaultUpdateDir: function dp_getDefaultUpdateDir() { + let path = gExtStorage; + if (!path) { + path = LOCAL_DIR; + } + + if (Services.volumeService) { + let extVolume = Services.volumeService.createOrGetVolumeByPath(path); + if (!extVolume) { + path = LOCAL_DIR; + } + } + + let dir = Cc["@mozilla.org/file/local;1"] + .createInstance(Ci.nsILocalFile) + dir.initWithPath(path); + + if (!dir.exists() && path != LOCAL_DIR) { + // Fallback to LOCAL_DIR if we didn't fallback earlier + dir.initWithPath(LOCAL_DIR); + + if (!dir.exists()) { + throw Cr.NS_ERROR_FILE_NOT_FOUND; + } + } + + dir.appendRelativePath("updates"); + return dir; + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DirectoryProvider]); |