|
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 |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const Cc = Components.classes; |
|
8 const Ci = Components.interfaces; |
|
9 const Cu = Components.utils; |
|
10 |
|
11 this.EXPORTED_SYMBOLS = []; |
|
12 |
|
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
14 Cu.import("resource://gre/modules/Downloads.jsm"); |
|
15 Cu.import("resource://gre/modules/Task.jsm"); |
|
16 |
|
17 XPCOMUtils.defineLazyServiceGetter(this, "ppmm", |
|
18 "@mozilla.org/parentprocessmessagemanager;1", |
|
19 "nsIMessageBroadcaster"); |
|
20 |
|
21 function debug(aStr) { |
|
22 #ifdef MOZ_DEBUG |
|
23 dump("-*- DownloadsAPI.jsm : " + aStr + "\n"); |
|
24 #endif |
|
25 } |
|
26 |
|
27 function sendPromiseMessage(aMm, aMessageName, aData, aError) { |
|
28 debug("sendPromiseMessage " + aMessageName); |
|
29 let msg = { |
|
30 id: aData.id, |
|
31 promiseId: aData.promiseId |
|
32 }; |
|
33 |
|
34 if (aError) { |
|
35 msg.error = aError; |
|
36 } |
|
37 |
|
38 aMm.sendAsyncMessage(aMessageName, msg); |
|
39 } |
|
40 |
|
41 let DownloadsAPI = { |
|
42 init: function() { |
|
43 debug("init"); |
|
44 |
|
45 this._ids = new WeakMap(); // Maps toolkit download objects to ids. |
|
46 this._index = {}; // Maps ids to downloads. |
|
47 |
|
48 ["Downloads:GetList", |
|
49 "Downloads:ClearAllDone", |
|
50 "Downloads:Remove", |
|
51 "Downloads:Pause", |
|
52 "Downloads:Resume"].forEach((msgName) => { |
|
53 ppmm.addMessageListener(msgName, this); |
|
54 }); |
|
55 |
|
56 let self = this; |
|
57 Task.spawn(function () { |
|
58 let list = yield Downloads.getList(Downloads.ALL); |
|
59 yield list.addView(self); |
|
60 |
|
61 debug("view added to download list."); |
|
62 }).then(null, Components.utils.reportError); |
|
63 |
|
64 this._currentId = 0; |
|
65 }, |
|
66 |
|
67 /** |
|
68 * Returns a unique id for each download, hashing the url and the path. |
|
69 */ |
|
70 downloadId: function(aDownload) { |
|
71 let id = this._ids.get(aDownload, null); |
|
72 if (!id) { |
|
73 id = "download-" + this._currentId++; |
|
74 this._ids.set(aDownload, id); |
|
75 this._index[id] = aDownload; |
|
76 } |
|
77 return id; |
|
78 }, |
|
79 |
|
80 getDownloadById: function(aId) { |
|
81 return this._index[aId]; |
|
82 }, |
|
83 |
|
84 /** |
|
85 * Converts a download object into a plain json object that we'll |
|
86 * send to the DOM side. |
|
87 */ |
|
88 jsonDownload: function(aDownload) { |
|
89 let res = { |
|
90 totalBytes: aDownload.totalBytes, |
|
91 currentBytes: aDownload.currentBytes, |
|
92 url: aDownload.source.url, |
|
93 path: aDownload.target.path, |
|
94 contentType: aDownload.contentType, |
|
95 startTime: aDownload.startTime.getTime() |
|
96 }; |
|
97 |
|
98 if (aDownload.error) { |
|
99 res.error = aDownload.error; |
|
100 } |
|
101 |
|
102 res.id = this.downloadId(aDownload); |
|
103 |
|
104 // The state of the download. Can be any of "downloading", "stopped", |
|
105 // "succeeded", finalized". |
|
106 |
|
107 // Default to "stopped" |
|
108 res.state = "stopped"; |
|
109 if (!aDownload.stopped && |
|
110 !aDownload.canceled && |
|
111 !aDownload.succeeded && |
|
112 !aDownload.DownloadError) { |
|
113 res.state = "downloading"; |
|
114 } else if (aDownload.succeeded) { |
|
115 res.state = "succeeded"; |
|
116 } |
|
117 return res; |
|
118 }, |
|
119 |
|
120 /** |
|
121 * download view methods. |
|
122 */ |
|
123 onDownloadAdded: function(aDownload) { |
|
124 let download = this.jsonDownload(aDownload); |
|
125 debug("onDownloadAdded " + uneval(download)); |
|
126 ppmm.broadcastAsyncMessage("Downloads:Added", download); |
|
127 }, |
|
128 |
|
129 onDownloadRemoved: function(aDownload) { |
|
130 let download = this.jsonDownload(aDownload); |
|
131 download.state = "finalized"; |
|
132 debug("onDownloadRemoved " + uneval(download)); |
|
133 ppmm.broadcastAsyncMessage("Downloads:Removed", download); |
|
134 this._index[this._ids.get(aDownload)] = null; |
|
135 this._ids.delete(aDownload); |
|
136 }, |
|
137 |
|
138 onDownloadChanged: function(aDownload) { |
|
139 let download = this.jsonDownload(aDownload); |
|
140 debug("onDownloadChanged " + uneval(download)); |
|
141 ppmm.broadcastAsyncMessage("Downloads:Changed", download); |
|
142 }, |
|
143 |
|
144 receiveMessage: function(aMessage) { |
|
145 if (!aMessage.target.assertPermission("downloads")) { |
|
146 debug("No 'downloads' permission!"); |
|
147 return; |
|
148 } |
|
149 |
|
150 debug("message: " + aMessage.name); |
|
151 |
|
152 switch (aMessage.name) { |
|
153 case "Downloads:GetList": |
|
154 this.getList(aMessage.data, aMessage.target); |
|
155 break; |
|
156 case "Downloads:ClearAllDone": |
|
157 this.clearAllDone(aMessage.data, aMessage.target); |
|
158 break; |
|
159 case "Downloads:Remove": |
|
160 this.remove(aMessage.data, aMessage.target); |
|
161 break; |
|
162 case "Downloads:Pause": |
|
163 this.pause(aMessage.data, aMessage.target); |
|
164 break; |
|
165 case "Downloads:Resume": |
|
166 this.resume(aMessage.data, aMessage.target); |
|
167 break; |
|
168 default: |
|
169 debug("Invalid message: " + aMessage.name); |
|
170 } |
|
171 }, |
|
172 |
|
173 getList: function(aData, aMm) { |
|
174 debug("getList called!"); |
|
175 let self = this; |
|
176 Task.spawn(function () { |
|
177 let list = yield Downloads.getList(Downloads.ALL); |
|
178 let downloads = yield list.getAll(); |
|
179 let res = []; |
|
180 downloads.forEach((aDownload) => { |
|
181 res.push(self.jsonDownload(aDownload)); |
|
182 }); |
|
183 aMm.sendAsyncMessage("Downloads:GetList:Return", res); |
|
184 }).then(null, Components.utils.reportError); |
|
185 }, |
|
186 |
|
187 clearAllDone: function(aData, aMm) { |
|
188 debug("clearAllDone called!"); |
|
189 let self = this; |
|
190 Task.spawn(function () { |
|
191 let list = yield Downloads.getList(Downloads.ALL); |
|
192 yield list.removeFinished(); |
|
193 list = yield Downloads.getList(Downloads.ALL); |
|
194 let downloads = yield list.getAll(); |
|
195 let res = []; |
|
196 downloads.forEach((aDownload) => { |
|
197 res.push(self.jsonDownload(aDownload)); |
|
198 }); |
|
199 aMm.sendAsyncMessage("Downloads:ClearAllDone:Return", res); |
|
200 }).then(null, Components.utils.reportError); |
|
201 }, |
|
202 |
|
203 remove: function(aData, aMm) { |
|
204 debug("remove id " + aData.id); |
|
205 let download = this.getDownloadById(aData.id); |
|
206 if (!download) { |
|
207 sendPromiseMessage(aMm, "Downloads:Remove:Return", |
|
208 aData, "NoSuchDownload"); |
|
209 return; |
|
210 } |
|
211 |
|
212 Task.spawn(function() { |
|
213 yield download.finalize(true); |
|
214 let list = yield Downloads.getList(Downloads.ALL); |
|
215 yield list.remove(download); |
|
216 }).then( |
|
217 function() { |
|
218 sendPromiseMessage(aMm, "Downloads:Remove:Return", aData); |
|
219 }, |
|
220 function() { |
|
221 sendPromiseMessage(aMm, "Downloads:Remove:Return", |
|
222 aData, "RemoveError"); |
|
223 } |
|
224 ); |
|
225 }, |
|
226 |
|
227 pause: function(aData, aMm) { |
|
228 debug("pause id " + aData.id); |
|
229 let download = this.getDownloadById(aData.id); |
|
230 if (!download) { |
|
231 sendPromiseMessage(aMm, "Downloads:Pause:Return", |
|
232 aData, "NoSuchDownload"); |
|
233 return; |
|
234 } |
|
235 |
|
236 download.cancel().then( |
|
237 function() { |
|
238 sendPromiseMessage(aMm, "Downloads:Pause:Return", aData); |
|
239 }, |
|
240 function() { |
|
241 sendPromiseMessage(aMm, "Downloads:Pause:Return", |
|
242 aData, "PauseError"); |
|
243 } |
|
244 ); |
|
245 }, |
|
246 |
|
247 resume: function(aData, aMm) { |
|
248 debug("resume id " + aData.id); |
|
249 let download = this.getDownloadById(aData.id); |
|
250 if (!download) { |
|
251 sendPromiseMessage(aMm, "Downloads:Resume:Return", |
|
252 aData, "NoSuchDownload"); |
|
253 return; |
|
254 } |
|
255 |
|
256 download.start().then( |
|
257 function() { |
|
258 sendPromiseMessage(aMm, "Downloads:Resume:Return", aData); |
|
259 }, |
|
260 function() { |
|
261 sendPromiseMessage(aMm, "Downloads:Resume:Return", |
|
262 aData, "ResumeError"); |
|
263 } |
|
264 ); |
|
265 } |
|
266 }; |
|
267 |
|
268 DownloadsAPI.init(); |