|
1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 "use strict"; |
|
7 |
|
8 let Cu = Components.utils; |
|
9 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
10 |
|
11 function dump(a) { |
|
12 Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a); |
|
13 } |
|
14 |
|
15 XPCOMUtils.defineLazyModuleGetter(this, "Notifications", |
|
16 "resource://gre/modules/Notifications.jsm"); |
|
17 |
|
18 const URI_GENERIC_ICON_DOWNLOAD = "drawable://alert_download"; |
|
19 const URI_PAUSE_ICON = "drawable://pause"; |
|
20 const URI_CANCEL_ICON = "drawable://close"; |
|
21 const URI_RESUME_ICON = "drawable://play"; |
|
22 |
|
23 |
|
24 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm"); |
|
25 |
|
26 var Downloads = { |
|
27 _initialized: false, |
|
28 _dlmgr: null, |
|
29 _progressAlert: null, |
|
30 _privateDownloads: [], |
|
31 _showingPrompt: false, |
|
32 _downloadsIdMap: {}, |
|
33 |
|
34 _getLocalFile: function dl__getLocalFile(aFileURI) { |
|
35 // if this is a URL, get the file from that |
|
36 // XXX it's possible that using a null char-set here is bad |
|
37 const fileUrl = Services.io.newURI(aFileURI, null, null).QueryInterface(Ci.nsIFileURL); |
|
38 return fileUrl.file.clone().QueryInterface(Ci.nsILocalFile); |
|
39 }, |
|
40 |
|
41 init: function dl_init() { |
|
42 if (this._initialized) |
|
43 return; |
|
44 this._initialized = true; |
|
45 |
|
46 // Monitor downloads and display alerts |
|
47 this._dlmgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); |
|
48 this._progressAlert = new AlertDownloadProgressListener(); |
|
49 this._dlmgr.addPrivacyAwareListener(this._progressAlert); |
|
50 Services.obs.addObserver(this, "last-pb-context-exited", true); |
|
51 }, |
|
52 |
|
53 openDownload: function dl_openDownload(aDownload) { |
|
54 let fileUri = aDownload.target.spec; |
|
55 let guid = aDownload.guid; |
|
56 let f = this._getLocalFile(fileUri); |
|
57 try { |
|
58 f.launch(); |
|
59 } catch (ex) { |
|
60 // in case we are not able to open the file (i.e. there is no app able to handle it) |
|
61 // we just open the browser tab showing it |
|
62 BrowserApp.addTab("about:downloads?id=" + guid); |
|
63 } |
|
64 }, |
|
65 |
|
66 cancelDownload: function dl_cancelDownload(aDownload) { |
|
67 aDownload.cancel(); |
|
68 let fileURI = aDownload.target.spec; |
|
69 let f = this._getLocalFile(fileURI); |
|
70 |
|
71 OS.File.remove(f.path); |
|
72 }, |
|
73 |
|
74 showCancelConfirmPrompt: function dl_showCancelConfirmPrompt(aDownload) { |
|
75 if (this._showingPrompt) |
|
76 return; |
|
77 this._showingPrompt = true; |
|
78 // Open a prompt that offers a choice to cancel the download |
|
79 let title = Strings.browser.GetStringFromName("downloadCancelPromptTitle"); |
|
80 let message = Strings.browser.GetStringFromName("downloadCancelPromptMessage"); |
|
81 let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_YES + |
|
82 Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_NO; |
|
83 let choice = Services.prompt.confirmEx(null, title, message, flags, |
|
84 null, null, null, null, {}); |
|
85 if (choice == 0) |
|
86 this.cancelDownload(aDownload); |
|
87 this._showingPrompt = false; |
|
88 }, |
|
89 |
|
90 handleClickEvent: function dl_handleClickEvent(aDownload) { |
|
91 // Only open the downloaded file if the download is complete |
|
92 if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) |
|
93 this.openDownload(aDownload); |
|
94 else if (aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_DOWNLOADING || |
|
95 aDownload.state == Ci.nsIDownloadManager.DOWNLOAD_PAUSED) |
|
96 this.showCancelConfirmPrompt(aDownload); |
|
97 }, |
|
98 |
|
99 clickCallback: function dl_clickCallback(aDownloadId) { |
|
100 this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { |
|
101 if (Components.isSuccessCode(status)) |
|
102 this.handleClickEvent(download); |
|
103 }).bind(this)); |
|
104 }, |
|
105 |
|
106 pauseClickCallback: function dl_buttonPauseCallback(aDownloadId) { |
|
107 this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { |
|
108 if (Components.isSuccessCode(status)) |
|
109 download.pause(); |
|
110 }).bind(this)); |
|
111 }, |
|
112 |
|
113 resumeClickCallback: function dl_buttonPauseCallback(aDownloadId) { |
|
114 this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { |
|
115 if (Components.isSuccessCode(status)) |
|
116 download.resume(); |
|
117 }).bind(this)); |
|
118 }, |
|
119 |
|
120 cancelClickCallback: function dl_buttonPauseCallback(aDownloadId) { |
|
121 this._dlmgr.getDownloadByGUID(aDownloadId, (function(status, download) { |
|
122 if (Components.isSuccessCode(status)) |
|
123 this.cancelDownload(download); |
|
124 }).bind(this)); |
|
125 }, |
|
126 |
|
127 notificationCanceledCallback: function dl_notifCancelCallback(aId, aDownloadId) { |
|
128 let notificationId = this._downloadsIdMap[aDownloadId]; |
|
129 if (notificationId && notificationId == aId) |
|
130 delete this._downloadsIdMap[aDownloadId]; |
|
131 }, |
|
132 |
|
133 createNotification: function dl_createNotif(aDownload, aOptions) { |
|
134 let notificationId = Notifications.create(aOptions); |
|
135 this._downloadsIdMap[aDownload.guid] = notificationId; |
|
136 }, |
|
137 |
|
138 updateNotification: function dl_updateNotif(aDownload, aOptions) { |
|
139 let notificationId = this._downloadsIdMap[aDownload.guid]; |
|
140 if (notificationId) |
|
141 Notifications.update(notificationId, aOptions); |
|
142 }, |
|
143 |
|
144 cancelNotification: function dl_cleanNotif(aDownload) { |
|
145 Notifications.cancel(this._downloadsIdMap[aDownload.guid]); |
|
146 delete this._downloadsIdMap[aDownload.guid]; |
|
147 }, |
|
148 |
|
149 // observer for last-pb-context-exited |
|
150 observe: function dl_observe(aSubject, aTopic, aData) { |
|
151 let download; |
|
152 while ((download = this._privateDownloads.pop())) { |
|
153 try { |
|
154 let notificationId = aDownload.guid; |
|
155 Notifications.clear(notificationId); |
|
156 Downloads.removeNotification(download); |
|
157 } catch (e) { |
|
158 dump("Error removing private download: " + e); |
|
159 } |
|
160 } |
|
161 }, |
|
162 |
|
163 QueryInterface: function (aIID) { |
|
164 if (!aIID.equals(Ci.nsISupports) && |
|
165 !aIID.equals(Ci.nsIObserver) && |
|
166 !aIID.equals(Ci.nsISupportsWeakReference)) |
|
167 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
168 return this; |
|
169 } |
|
170 }; |
|
171 |
|
172 const PAUSE_BUTTON = { |
|
173 buttonId: "pause", |
|
174 title : Strings.browser.GetStringFromName("alertDownloadsPause"), |
|
175 icon : URI_PAUSE_ICON, |
|
176 onClicked: function (aId, aCookie) { |
|
177 Downloads.pauseClickCallback(aCookie); |
|
178 } |
|
179 }; |
|
180 |
|
181 const CANCEL_BUTTON = { |
|
182 buttonId: "cancel", |
|
183 title : Strings.browser.GetStringFromName("alertDownloadsCancel"), |
|
184 icon : URI_CANCEL_ICON, |
|
185 onClicked: function (aId, aCookie) { |
|
186 Downloads.cancelClickCallback(aCookie); |
|
187 } |
|
188 }; |
|
189 |
|
190 const RESUME_BUTTON = { |
|
191 buttonId: "resume", |
|
192 title : Strings.browser.GetStringFromName("alertDownloadsResume"), |
|
193 icon: URI_RESUME_ICON, |
|
194 onClicked: function (aId, aCookie) { |
|
195 Downloads.resumeClickCallback(aCookie); |
|
196 } |
|
197 }; |
|
198 |
|
199 function DownloadNotifOptions (aDownload, aTitle, aMessage) { |
|
200 this.icon = URI_GENERIC_ICON_DOWNLOAD; |
|
201 this.onCancel = function (aId, aCookie) { |
|
202 Downloads.notificationCanceledCallback(aId, aCookie); |
|
203 } |
|
204 this.onClick = function (aId, aCookie) { |
|
205 Downloads.clickCallback(aCookie); |
|
206 } |
|
207 this.title = aTitle; |
|
208 this.message = aMessage; |
|
209 this.buttons = null; |
|
210 this.cookie = aDownload.guid; |
|
211 this.persistent = true; |
|
212 } |
|
213 |
|
214 function DownloadProgressNotifOptions (aDownload, aButtons) { |
|
215 DownloadNotifOptions.apply(this, [aDownload, aDownload.displayName, aDownload.percentComplete + "%"]); |
|
216 this.ongoing = true; |
|
217 this.progress = aDownload.percentComplete; |
|
218 this.buttons = aButtons; |
|
219 } |
|
220 |
|
221 // AlertDownloadProgressListener is used to display progress in the alert notifications. |
|
222 function AlertDownloadProgressListener() { } |
|
223 |
|
224 AlertDownloadProgressListener.prototype = { |
|
225 ////////////////////////////////////////////////////////////////////////////// |
|
226 //// nsIDownloadProgressListener |
|
227 onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress, aDownload) { |
|
228 let strings = Strings.browser; |
|
229 let availableSpace = -1; |
|
230 try { |
|
231 // diskSpaceAvailable is not implemented on all systems |
|
232 let availableSpace = aDownload.targetFile.diskSpaceAvailable; |
|
233 } catch(ex) { } |
|
234 let contentLength = aDownload.size; |
|
235 if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) { |
|
236 Downloads.updateNotification(aDownload, new DownloadNotifOptions(aDownload, |
|
237 strings.GetStringFromName("alertDownloadsNoSpace"), |
|
238 strings.GetStringFromName("alertDownloadsSize"))); |
|
239 aDownload.cancel(); |
|
240 } |
|
241 |
|
242 if (aDownload.percentComplete == -1) { |
|
243 // Undetermined progress is not supported yet |
|
244 return; |
|
245 } |
|
246 |
|
247 Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [PAUSE_BUTTON, CANCEL_BUTTON])); |
|
248 }, |
|
249 |
|
250 onDownloadStateChange: function(aState, aDownload) { |
|
251 let state = aDownload.state; |
|
252 switch (state) { |
|
253 case Ci.nsIDownloadManager.DOWNLOAD_QUEUED: { |
|
254 NativeWindow.toast.show(Strings.browser.GetStringFromName("alertDownloadsToast"), "long"); |
|
255 Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload, |
|
256 Strings.browser.GetStringFromName("alertDownloadsStart2"), |
|
257 aDownload.displayName)); |
|
258 break; |
|
259 } |
|
260 case Ci.nsIDownloadManager.DOWNLOAD_PAUSED: { |
|
261 Downloads.updateNotification(aDownload, new DownloadProgressNotifOptions(aDownload, [RESUME_BUTTON, CANCEL_BUTTON])); |
|
262 break; |
|
263 } |
|
264 case Ci.nsIDownloadManager.DOWNLOAD_FAILED: |
|
265 case Ci.nsIDownloadManager.DOWNLOAD_CANCELED: |
|
266 case Ci.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL: |
|
267 case Ci.nsIDownloadManager.DOWNLOAD_DIRTY: |
|
268 case Ci.nsIDownloadManager.DOWNLOAD_FINISHED: { |
|
269 Downloads.cancelNotification(aDownload); |
|
270 if (aDownload.isPrivate) { |
|
271 let index = Downloads._privateDownloads.indexOf(aDownload); |
|
272 if (index != -1) { |
|
273 Downloads._privateDownloads.splice(index, 1); |
|
274 } |
|
275 } |
|
276 |
|
277 if (state == Ci.nsIDownloadManager.DOWNLOAD_FINISHED) { |
|
278 Downloads.createNotification(aDownload, new DownloadNotifOptions(aDownload, |
|
279 Strings.browser.GetStringFromName("alertDownloadsDone2"), |
|
280 aDownload.displayName)); |
|
281 } |
|
282 break; |
|
283 } |
|
284 } |
|
285 }, |
|
286 |
|
287 onStateChange: function(aWebProgress, aRequest, aState, aStatus, aDownload) { }, |
|
288 onSecurityChange: function(aWebProgress, aRequest, aState, aDownload) { }, |
|
289 |
|
290 ////////////////////////////////////////////////////////////////////////////// |
|
291 //// nsISupports |
|
292 QueryInterface: function (aIID) { |
|
293 if (!aIID.equals(Ci.nsIDownloadProgressListener) && |
|
294 !aIID.equals(Ci.nsISupports)) |
|
295 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
296 return this; |
|
297 } |
|
298 }; |