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