|
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/. */ |
|
4 |
|
5 const Cc = Components.classes; |
|
6 const Ci = Components.interfaces; |
|
7 const Cu = Components.utils; |
|
8 const Cr = Components.results; |
|
9 |
|
10 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
11 Cu.import("resource://gre/modules/Services.jsm"); |
|
12 |
|
13 const XRE_OS_UPDATE_APPLY_TO_DIR = "OSUpdApplyToD" |
|
14 const UPDATE_ARCHIVE_DIR = "UpdArchD" |
|
15 const LOCAL_DIR = "/data/local"; |
|
16 const UPDATES_DIR = "updates/0"; |
|
17 const FOTA_DIR = "updates/fota"; |
|
18 |
|
19 XPCOMUtils.defineLazyServiceGetter(Services, "env", |
|
20 "@mozilla.org/process/environment;1", |
|
21 "nsIEnvironment"); |
|
22 |
|
23 XPCOMUtils.defineLazyServiceGetter(Services, "um", |
|
24 "@mozilla.org/updates/update-manager;1", |
|
25 "nsIUpdateManager"); |
|
26 |
|
27 XPCOMUtils.defineLazyServiceGetter(Services, "volumeService", |
|
28 "@mozilla.org/telephony/volume-service;1", |
|
29 "nsIVolumeService"); |
|
30 |
|
31 XPCOMUtils.defineLazyServiceGetter(this, "cpmm", |
|
32 "@mozilla.org/childprocessmessagemanager;1", |
|
33 "nsISyncMessageSender"); |
|
34 |
|
35 XPCOMUtils.defineLazyGetter(this, "gExtStorage", function dp_gExtStorage() { |
|
36 return Services.env.get("EXTERNAL_STORAGE"); |
|
37 }); |
|
38 |
|
39 // This exists to mark the affected code for bug 828858. |
|
40 const gUseSDCard = true; |
|
41 |
|
42 const VERBOSE = 1; |
|
43 let log = |
|
44 VERBOSE ? |
|
45 function log_dump(msg) { dump("DirectoryProvider: " + msg + "\n"); } : |
|
46 function log_noop(msg) { }; |
|
47 |
|
48 function DirectoryProvider() { |
|
49 } |
|
50 |
|
51 DirectoryProvider.prototype = { |
|
52 classID: Components.ID("{9181eb7c-6f87-11e1-90b1-4f59d80dd2e5}"), |
|
53 |
|
54 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider]), |
|
55 _xpcom_factory: XPCOMUtils.generateSingletonFactory(DirectoryProvider), |
|
56 |
|
57 _profD: null, |
|
58 |
|
59 getFile: function dp_getFile(prop, persistent) { |
|
60 #ifdef MOZ_WIDGET_GONK |
|
61 let localProps = ["cachePDir", "webappsDir", "PrefD", "indexedDBPDir", |
|
62 "permissionDBPDir", "UpdRootD"]; |
|
63 if (localProps.indexOf(prop) != -1) { |
|
64 let file = Cc["@mozilla.org/file/local;1"] |
|
65 .createInstance(Ci.nsILocalFile) |
|
66 file.initWithPath(LOCAL_DIR); |
|
67 persistent.value = true; |
|
68 return file; |
|
69 } |
|
70 if (prop == "ProfD") { |
|
71 let dir = Cc["@mozilla.org/file/local;1"] |
|
72 .createInstance(Ci.nsILocalFile); |
|
73 dir.initWithPath(LOCAL_DIR+"/tests/profile"); |
|
74 if (dir.exists()) { |
|
75 persistent.value = true; |
|
76 return dir; |
|
77 } |
|
78 } |
|
79 if (prop == "coreAppsDir") { |
|
80 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile) |
|
81 file.initWithPath("/system/b2g"); |
|
82 persistent.value = true; |
|
83 return file; |
|
84 } |
|
85 if (prop == UPDATE_ARCHIVE_DIR) { |
|
86 // getUpdateDir will set persistent to false since it may toggle between |
|
87 // /data/local/ and /mnt/sdcard based on free space and/or availability |
|
88 // of the sdcard. |
|
89 // before download, check if free space is 2.1 times of update.mar |
|
90 return this.getUpdateDir(persistent, UPDATES_DIR, 2.1); |
|
91 } |
|
92 if (prop == XRE_OS_UPDATE_APPLY_TO_DIR) { |
|
93 // getUpdateDir will set persistent to false since it may toggle between |
|
94 // /data/local/ and /mnt/sdcard based on free space and/or availability |
|
95 // of the sdcard. |
|
96 // before apply, check if free space is 1.1 times of update.mar |
|
97 return this.getUpdateDir(persistent, FOTA_DIR, 1.1); |
|
98 } |
|
99 #else |
|
100 // In desktop builds, coreAppsDir is the same as the profile directory. |
|
101 // We just need to get the path from the parent, and it is then used to |
|
102 // build jar:remoteopenfile:// uris. |
|
103 if (prop == "coreAppsDir") { |
|
104 let appsDir = Services.dirsvc.get("ProfD", Ci.nsIFile); |
|
105 appsDir.append("webapps"); |
|
106 persistent.value = true; |
|
107 return appsDir; |
|
108 } else if (prop == "ProfD") { |
|
109 let inParent = Cc["@mozilla.org/xre/app-info;1"] |
|
110 .getService(Ci.nsIXULRuntime) |
|
111 .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; |
|
112 if (inParent) { |
|
113 // Just bail out to use the default from toolkit. |
|
114 return null; |
|
115 } |
|
116 if (!this._profD) { |
|
117 this._profD = cpmm.sendSyncMessage("getProfD", {})[0]; |
|
118 } |
|
119 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); |
|
120 file.initWithPath(this._profD); |
|
121 persistent.value = true; |
|
122 return file; |
|
123 } |
|
124 #endif |
|
125 return null; |
|
126 }, |
|
127 |
|
128 // The VolumeService only exists on the device, and not on desktop |
|
129 volumeHasFreeSpace: function dp_volumeHasFreeSpace(volumePath, requiredSpace) { |
|
130 if (!volumePath) { |
|
131 return false; |
|
132 } |
|
133 if (!Services.volumeService) { |
|
134 return false; |
|
135 } |
|
136 let volume = Services.volumeService.createOrGetVolumeByPath(volumePath); |
|
137 if (!volume || volume.state !== Ci.nsIVolume.STATE_MOUNTED) { |
|
138 return false; |
|
139 } |
|
140 let stat = volume.getStats(); |
|
141 if (!stat) { |
|
142 return false; |
|
143 } |
|
144 return requiredSpace <= stat.freeBytes; |
|
145 }, |
|
146 |
|
147 findUpdateDirWithFreeSpace: function dp_findUpdateDirWithFreeSpace(requiredSpace, subdir) { |
|
148 if (!Services.volumeService) { |
|
149 return this.createUpdatesDir(LOCAL_DIR, subdir); |
|
150 } |
|
151 |
|
152 let activeUpdate = Services.um.activeUpdate; |
|
153 if (gUseSDCard) { |
|
154 if (this.volumeHasFreeSpace(gExtStorage, requiredSpace)) { |
|
155 let extUpdateDir = this.createUpdatesDir(gExtStorage, subdir); |
|
156 if (extUpdateDir !== null) { |
|
157 return extUpdateDir; |
|
158 } |
|
159 log("Warning: " + gExtStorage + " has enough free space for update " + |
|
160 activeUpdate.name + ", but is not writable"); |
|
161 } |
|
162 } |
|
163 |
|
164 if (this.volumeHasFreeSpace(LOCAL_DIR, requiredSpace)) { |
|
165 let localUpdateDir = this.createUpdatesDir(LOCAL_DIR, subdir); |
|
166 if (localUpdateDir !== null) { |
|
167 return localUpdateDir; |
|
168 } |
|
169 log("Warning: " + LOCAL_DIR + " has enough free space for update " + |
|
170 activeUpdate.name + ", but is not writable"); |
|
171 } |
|
172 |
|
173 return null; |
|
174 }, |
|
175 |
|
176 getUpdateDir: function dp_getUpdateDir(persistent, subdir, multiple) { |
|
177 let defaultUpdateDir = this.getDefaultUpdateDir(); |
|
178 persistent.value = false; |
|
179 |
|
180 let activeUpdate = Services.um.activeUpdate; |
|
181 if (!activeUpdate) { |
|
182 log("Warning: No active update found, using default update dir: " + |
|
183 defaultUpdateDir); |
|
184 return defaultUpdateDir; |
|
185 } |
|
186 |
|
187 let selectedPatch = activeUpdate.selectedPatch; |
|
188 if (!selectedPatch) { |
|
189 log("Warning: No selected patch, using default update dir: " + |
|
190 defaultUpdateDir); |
|
191 return defaultUpdateDir; |
|
192 } |
|
193 |
|
194 let requiredSpace = selectedPatch.size * multiple; |
|
195 let updateDir = this.findUpdateDirWithFreeSpace(requiredSpace, subdir); |
|
196 if (updateDir) { |
|
197 return updateDir; |
|
198 } |
|
199 |
|
200 // If we've gotten this far, there isn't enough free space to download the patch |
|
201 // on either external storage or /data/local. All we can do is report the |
|
202 // error and let upstream code handle it more gracefully. |
|
203 log("Error: No volume found with " + requiredSpace + " bytes for downloading"+ |
|
204 " update " + activeUpdate.name); |
|
205 activeUpdate.errorCode = Cr.NS_ERROR_FILE_TOO_BIG; |
|
206 return null; |
|
207 }, |
|
208 |
|
209 createUpdatesDir: function dp_createUpdatesDir(root, subdir) { |
|
210 let dir = Cc["@mozilla.org/file/local;1"] |
|
211 .createInstance(Ci.nsILocalFile); |
|
212 dir.initWithPath(root); |
|
213 if (!dir.isWritable()) { |
|
214 log("Error: " + dir.path + " isn't writable"); |
|
215 return null; |
|
216 } |
|
217 dir.appendRelativePath(subdir); |
|
218 if (dir.exists()) { |
|
219 if (dir.isDirectory() && dir.isWritable()) { |
|
220 return dir; |
|
221 } |
|
222 // subdir is either a file or isn't writable. In either case we |
|
223 // can't use it. |
|
224 log("Error: " + dir.path + " is a file or isn't writable"); |
|
225 return null; |
|
226 } |
|
227 // subdir doesn't exist, and the parent is writable, so try to |
|
228 // create it. This can fail if a file named updates exists. |
|
229 try { |
|
230 dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0770); |
|
231 } catch (e) { |
|
232 // The create failed for some reason. We can't use it. |
|
233 log("Error: " + dir.path + " unable to create directory"); |
|
234 return null; |
|
235 } |
|
236 return dir; |
|
237 }, |
|
238 |
|
239 getDefaultUpdateDir: function dp_getDefaultUpdateDir() { |
|
240 let path = gExtStorage; |
|
241 if (!path) { |
|
242 path = LOCAL_DIR; |
|
243 } |
|
244 |
|
245 if (Services.volumeService) { |
|
246 let extVolume = Services.volumeService.createOrGetVolumeByPath(path); |
|
247 if (!extVolume) { |
|
248 path = LOCAL_DIR; |
|
249 } |
|
250 } |
|
251 |
|
252 let dir = Cc["@mozilla.org/file/local;1"] |
|
253 .createInstance(Ci.nsILocalFile) |
|
254 dir.initWithPath(path); |
|
255 |
|
256 if (!dir.exists() && path != LOCAL_DIR) { |
|
257 // Fallback to LOCAL_DIR if we didn't fallback earlier |
|
258 dir.initWithPath(LOCAL_DIR); |
|
259 |
|
260 if (!dir.exists()) { |
|
261 throw Cr.NS_ERROR_FILE_NOT_FOUND; |
|
262 } |
|
263 } |
|
264 |
|
265 dir.appendRelativePath("updates"); |
|
266 return dir; |
|
267 } |
|
268 }; |
|
269 |
|
270 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DirectoryProvider]); |