OLD | NEW |
| (Empty) |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 'use strict'; | |
6 | |
7 /** | |
8 * Progress center at the background page. | |
9 * @constructor | |
10 */ | |
11 var ProgressCenter = function() { | |
12 cr.EventTarget.call(this); | |
13 | |
14 /** | |
15 * Default container. | |
16 * @type {ProgressItemContainer} | |
17 * @private | |
18 */ | |
19 this.targetContainer_ = ProgressItemContainer.CLIENT; | |
20 | |
21 /** | |
22 * Current items managed by the progress center. | |
23 * @type {Array.<ProgressItem>} | |
24 * @private | |
25 */ | |
26 this.items_ = []; | |
27 | |
28 /** | |
29 * Timeout callback to remove items. | |
30 * @type {TimeoutManager} | |
31 * @private | |
32 */ | |
33 this.resetTimeout_ = new ProgressCenter.TimeoutManager( | |
34 this.reset_.bind(this)); | |
35 }; | |
36 | |
37 /** | |
38 * The default amount of milliseconds time, before a progress item will reset | |
39 * after the last complete. | |
40 * @type {number} | |
41 * @private | |
42 * @const | |
43 */ | |
44 ProgressCenter.RESET_DELAY_TIME_MS_ = 5000; | |
45 | |
46 /** | |
47 * Utility for timeout callback. | |
48 * | |
49 * @param {function(*):*} callback Callbakc function. | |
50 * @constructor | |
51 */ | |
52 ProgressCenter.TimeoutManager = function(callback) { | |
53 this.callback_ = callback; | |
54 this.id_ = null; | |
55 Object.seal(this); | |
56 }; | |
57 | |
58 /** | |
59 * Requests timeout. Previous request is canceled. | |
60 * @param {number} milliseconds Time to invoke the callback function. | |
61 */ | |
62 ProgressCenter.TimeoutManager.prototype.request = function(milliseconds) { | |
63 if (this.id_) | |
64 clearTimeout(this.id_); | |
65 this.id_ = setTimeout(function() { | |
66 this.id_ = null; | |
67 this.callback_(); | |
68 }.bind(this), milliseconds); | |
69 }; | |
70 | |
71 ProgressCenter.prototype = { | |
72 __proto__: cr.EventTarget.prototype, | |
73 | |
74 /** | |
75 * Obtains the items to be displayed in the application window. | |
76 * @private | |
77 */ | |
78 get applicationItems() { | |
79 return this.items_.filter(function(item) { | |
80 return item.container == ProgressItemContainer.CLIENT; | |
81 }); | |
82 } | |
83 }; | |
84 | |
85 /** | |
86 * Updates the item in the progress center. | |
87 * If the item has a new ID, the item is added to the item list. | |
88 * | |
89 * @param {ProgressCenterItem} item Updated item. | |
90 */ | |
91 ProgressCenter.prototype.updateItem = function(item) { | |
92 var index = this.getItemIndex_(item.id); | |
93 if (index === -1) { | |
94 item.container = this.targetContainer_; | |
95 this.items_.push(item); | |
96 } else { | |
97 this.items_[index] = item; | |
98 } | |
99 | |
100 if (item.status !== ProgressItemState.PROGRESSING) | |
101 this.resetTimeout_.request(ProgressCenter.RESET_DELAY_TIME_MS_); | |
102 | |
103 var event = new Event(ProgressCenterEvent.ITEM_UPDATED); | |
104 event.item = item; | |
105 this.dispatchEvent(event); | |
106 }; | |
107 | |
108 /** | |
109 * Requests to cancel the progress item. | |
110 * @param {string} id Progress ID to be requested to cancel. | |
111 */ | |
112 ProgressCenter.prototype.requestCancel = function(id) { | |
113 var item = this.getItemById(id); | |
114 if (item && item.cancelCallback) | |
115 item.cancelCallback(); | |
116 }; | |
117 | |
118 /** | |
119 * Switches the default container. | |
120 * @param {ProgressItemContainer} newContainer New value of the default | |
121 * container. | |
122 */ | |
123 ProgressCenter.prototype.switchContainer = function(newContainer) { | |
124 if (this.targetContainer_ === newContainer) | |
125 return; | |
126 | |
127 // Current items to be moved to the notification center. | |
128 if (newContainer == ProgressItemContainer.NOTIFICATION) { | |
129 var items = this.applicationItems; | |
130 for (var i = 0; i < items.length; i++) { | |
131 items[i].container = ProgressItemContainer.NOTIFICATION; | |
132 this.postItemToNotification_(items); | |
133 } | |
134 } | |
135 | |
136 // The items in the notification center does not come back to the Files.app | |
137 // clients. | |
138 | |
139 // Assign the new value. | |
140 this.targetContainer_ = newContainer; | |
141 }; | |
142 | |
143 /** | |
144 * Obtains the summarized item to be displayed in the closed progress center | |
145 * panel. | |
146 * @return {ProgressCenterItem} Summarized item. Returns null if there is no | |
147 * item. | |
148 */ | |
149 ProgressCenter.prototype.getSummarizedItem = function() { | |
150 var applicationItems = this.applicationItems; | |
151 if (applicationItems.length == 0) | |
152 return null; | |
153 if (applicationItems.length == 1) | |
154 return applicationItems[0]; | |
155 var summarizedItem = new ProgressCenterItem(); | |
156 summarizedItem.summarized = true; | |
157 var completeCount = 0; | |
158 var progressingCount = 0; | |
159 var canceledCount = 0; | |
160 var errorCount = 0; | |
161 for (var i = 0; i < applicationItems.length; i++) { | |
162 switch (applicationItems[i].state) { | |
163 case ProgressItemState.COMPLETE: | |
164 completeCount++; | |
165 break; | |
166 case ProgressItemState.PROGRESSING: | |
167 progressingCount++; | |
168 break; | |
169 case ProgressItemState.ERROR: | |
170 errorCount++; | |
171 continue; | |
172 case ProgressItemState.CANCELED: | |
173 canceledCount++; | |
174 continue; | |
175 } | |
176 summarizedItem.progressMax += applicationItems[i].progressMax; | |
177 summarizedItem.progressValue += applicationItems[i].progressValue; | |
178 } | |
179 var messages = []; | |
180 if (completeCount) | |
181 messages.push(completeCount + ' complete'); | |
182 if (progressingCount) | |
183 messages.push(progressingCount + ' active'); | |
184 if (canceledCount) | |
185 messages.push(canceledCount + ' canceled'); | |
186 if (errorCount) | |
187 messages.push(errorCount + ' error'); | |
188 summarizedItem.message = messages.join(', ') + '.'; | |
189 summarizedItem.state = | |
190 completeCount + progressingCount == 0 ? ProgressItemState.CANCELED : | |
191 progressingCount > 0 ? ProgressItemState.PROGRESSING : | |
192 ProgressItemState.COMPLETE; | |
193 return summarizedItem; | |
194 }; | |
195 | |
196 /** | |
197 * Obtains item by ID. | |
198 * @param {string} id ID of progress item. | |
199 * @return {ProgressCenterItem} Progress center item having the specified | |
200 * ID. Null if the item is not found. | |
201 */ | |
202 ProgressCenter.prototype.getItemById = function(id) { | |
203 return this.items_[this.getItemIndex_(id)]; | |
204 }; | |
205 | |
206 /** | |
207 * Obtains item index that have the specifying ID. | |
208 * @param {string} id Item ID. | |
209 * @return {number} Item index. Returns -1 If the item is not found. | |
210 * @private | |
211 */ | |
212 ProgressCenter.prototype.getItemIndex_ = function(id) { | |
213 for (var i = 0; i < this.items_.length; i++) { | |
214 if (this.items_[i].id === id) | |
215 return i; | |
216 } | |
217 return -1; | |
218 }; | |
219 | |
220 /** | |
221 * Passes the item to the ChromeOS's message center. | |
222 * | |
223 * TODO(hirono): Implement the method. | |
224 * | |
225 * @private | |
226 */ | |
227 ProgressCenter.prototype.passItemsToNotification_ = function() { | |
228 | |
229 }; | |
230 | |
231 /** | |
232 * Hides the progress center if there is no progressing items. | |
233 * @private | |
234 */ | |
235 ProgressCenter.prototype.reset_ = function() { | |
236 // If we have a progressing item, stop reset. | |
237 for (var i = 0; i < this.items_.length; i++) { | |
238 if (this.items_[i].state == ProgressItemState.PROGRESSING) | |
239 return; | |
240 } | |
241 | |
242 // Reset items. | |
243 this.items_.splice(0, this.items_.length); | |
244 | |
245 // Dispatch a event. | |
246 this.dispatchEvent(new Event(ProgressCenterEvent.RESET)); | |
247 }; | |
248 | |
249 /** | |
250 * An event handler for progress center. | |
251 * @param {FileOperationManager} fileOperationManager File operation manager. | |
252 * @param {ProgressCenter} progressCenter Progress center. | |
253 * @constructor | |
254 */ | |
255 var ProgressCenterHandler = function(fileOperationManager, progressCenter) { | |
256 /** | |
257 * Number of deleted files. | |
258 * @type {number} | |
259 * @private | |
260 */ | |
261 this.totalDeleted_ = 0; | |
262 | |
263 /** | |
264 * File operation manager. | |
265 * @type {FileOperationManager} | |
266 * @private | |
267 */ | |
268 this.fileOperationManager_ = fileOperationManager; | |
269 | |
270 /** | |
271 * Progress center. | |
272 * @type {progressCenter} | |
273 * @private | |
274 */ | |
275 this.progressCenter_ = progressCenter; | |
276 | |
277 // Seal the object. | |
278 Object.seal(this); | |
279 | |
280 // Register event. | |
281 fileOperationManager.addEventListener('copy-progress', | |
282 this.onCopyProgress_.bind(this)); | |
283 fileOperationManager.addEventListener('delete', | |
284 this.onDeleteProgress_.bind(this)); | |
285 }; | |
286 | |
287 /** | |
288 * Generate a progress message from the event. | |
289 * @param {Event} event Progress event. | |
290 * @return {string} message. | |
291 * @private | |
292 */ | |
293 ProgressCenterHandler.getMessage_ = function(event) { | |
294 if (event.reason === 'ERROR') { | |
295 switch (event.error.code) { | |
296 case util.FileOperationErrorType.TARGET_EXISTS: | |
297 var name = event.error.data.name; | |
298 if (event.error.data.isDirectory) | |
299 name += '/'; | |
300 switch (event.status.operationType) { | |
301 case 'COPY': return strf('COPY_TARGET_EXISTS_ERROR', name); | |
302 case 'MOVE': return strf('MOVE_TARGET_EXISTS_ERROR', name); | |
303 case 'ZIP': return strf('ZIP_TARGET_EXISTS_ERROR', name); | |
304 default: return strf('TRANSFER_TARGET_EXISTS_ERROR', name); | |
305 } | |
306 | |
307 case util.FileOperationErrorType.FILESYSTEM_ERROR: | |
308 var detail = util.getFileErrorString(event.error.data.code); | |
309 switch (event.status.operationType) { | |
310 case 'COPY': return strf('COPY_FILESYSTEM_ERROR', detail); | |
311 case 'MOVE': return strf('MOVE_FILESYSTEM_ERROR', detail); | |
312 case 'ZIP': return strf('ZIP_FILESYSTEM_ERROR', detail); | |
313 default: return strf('TRANSFER_FILESYSTEM_ERROR', detail); | |
314 } | |
315 | |
316 default: | |
317 switch (event.status.operationType) { | |
318 case 'COPY': return strf('COPY_UNEXPECTED_ERROR', event.error); | |
319 case 'MOVE': return strf('MOVE_UNEXPECTED_ERROR', event.error); | |
320 case 'ZIP': return strf('ZIP_UNEXPECTED_ERROR', event.error); | |
321 default: return strf('TRANSFER_UNEXPECTED_ERROR', event.error); | |
322 } | |
323 } | |
324 } else if (event.status.numRemainingItems === 1) { | |
325 var name = event.status.processingEntry.name; | |
326 switch (event.status.operationType) { | |
327 case 'COPY': return strf('COPY_FILE_NAME', name); | |
328 case 'MOVE': return strf('MOVE_FILE_NAME', name); | |
329 case 'ZIP': return strf('ZIP_FILE_NAME', name); | |
330 default: return strf('TRANSFER_FILE_NAME', name); | |
331 } | |
332 } else { | |
333 var remainNumber = event.status.numRemainingItems; | |
334 switch (event.status.operationType) { | |
335 case 'COPY': return strf('COPY_ITEMS_REMAINING', remainNumber); | |
336 case 'MOVE': return strf('MOVE_ITEMS_REMAINING', remainNumber); | |
337 case 'ZIP': return strf('ZIP_ITEMS_REMAINING', remainNumber); | |
338 default: return strf('TRANSFER_ITEMS_REMAINING', remainNumber); | |
339 } | |
340 } | |
341 }; | |
342 | |
343 /** | |
344 * Generate a delete message from the event. | |
345 * @param {Event} event Progress event. | |
346 * @param {number} totalDeleted Total number of deleted files. | |
347 * @return {string} message. | |
348 * @private | |
349 */ | |
350 ProgressCenterHandler.getDeleteMessage_ = function(event, totalDeleted) { | |
351 if (totalDeleted === 1) { | |
352 var fullPath = util.extractFilePath(event.urls[0]); | |
353 var fileName = PathUtil.split(fullPath).pop(); | |
354 return strf('DELETED_MESSAGE', fileName); | |
355 } else { | |
356 return strf('DELETED_MESSAGE_PLURAL', totalDeleted); | |
357 } | |
358 }; | |
359 | |
360 /** | |
361 * Handles the copy-progress event. | |
362 * @param {Event} event The copy-progress event. | |
363 * @private | |
364 */ | |
365 ProgressCenterHandler.prototype.onCopyProgress_ = function(event) { | |
366 var progressCenter = this.progressCenter_; | |
367 var item; | |
368 switch (event.reason) { | |
369 case 'BEGIN': | |
370 item = new ProgressCenterItem(); | |
371 item.id = event.taskId; | |
372 item.message = ProgressCenterHandler.getMessage_(event); | |
373 item.progressMax = event.status.totalBytes; | |
374 item.progressValue = event.status.processedBytes; | |
375 item.cancelCallback = this.fileOperationManager_.requestTaskCancel.bind( | |
376 this.fileOperationManager_, | |
377 event.taskId); | |
378 progressCenter.updateItem(item); | |
379 break; | |
380 | |
381 case 'PROGRESS': | |
382 item = progressCenter.getItemById(event.taskId); | |
383 if (!item) { | |
384 console.error('Cannot find copying item.'); | |
385 return; | |
386 } | |
387 item.message = ProgressCenterHandler.getMessage_(event); | |
388 item.progressValue = event.status.processedBytes; | |
389 progressCenter.updateItem(item); | |
390 break; | |
391 | |
392 case 'SUCCESS': | |
393 case 'ERROR': | |
394 item = progressCenter.getItemById(event.taskId); | |
395 if (!item) { | |
396 // ERROR events can be dispatched before BEGIN events. | |
397 item = new ProgressCenterItem(); | |
398 item.id = event.taskId; | |
399 item.progressMax = 1; | |
400 } | |
401 if (event.reason === 'SUCCESS') { | |
402 // TODO(hirono): Add a message for complete. | |
403 item.state = ProgressItemState.COMPLETE; | |
404 item.progressValue = item.progressMax; | |
405 } else if (event.error.data.code === FileError.ABORT_ERR) { | |
406 item.message = strf('COPY_CANCELLED'); | |
407 item.state = ProgressItemState.CANCELED; | |
408 } else { | |
409 item.message = ProgressCenterHandler.getMessage_(event); | |
410 item.state = ProgressItemState.ERROR; | |
411 } | |
412 progressCenter.updateItem(item); | |
413 break; | |
414 } | |
415 }; | |
416 | |
417 /** | |
418 * Handles the delete event. | |
419 * @param {Event} event The delete event. | |
420 * @private | |
421 */ | |
422 ProgressCenterHandler.prototype.onDeleteProgress_ = function(event) { | |
423 var progressCenter = this.progressCenter_; | |
424 var item; | |
425 switch (event.reason) { | |
426 case 'BEGIN': | |
427 this.totalDeleted_ = 0; | |
428 item = new ProgressCenterItem(); | |
429 item.id = event.taskId; | |
430 // TODO(hirono): Specifying the correct message. | |
431 item.message = | |
432 ProgressCenterHandler.getDeleteMessage_(event, this.totalDeleted_); | |
433 item.progressMax = 100; | |
434 progressCenter.updateItem(item); | |
435 break; | |
436 | |
437 case 'PROGRESS': | |
438 item = progressCenter.getItemById(event.taskId); | |
439 if (!item) { | |
440 console.error('Cannot find deleting item.'); | |
441 return; | |
442 } | |
443 this.totalDeleted_ += event.urls.length; | |
444 item.message = | |
445 ProgressCenterHandler.getDeleteMessage_(event, this.totalDeleted_); | |
446 progressCenter.updateItem(item); | |
447 break; | |
448 | |
449 case 'SUCCESS': | |
450 case 'ERROR': | |
451 item = progressCenter.getItemById(event.taskId); | |
452 if (!item) { | |
453 console.error('Cannot find deleting item.'); | |
454 return; | |
455 } | |
456 if (event.reason === 'SUCCESS') { | |
457 this.totalDeleted_ += event.urls.length; | |
458 item.message = | |
459 ProgressCenterHandler.getDeleteMessage_(event, this.totalDeleted_); | |
460 item.state = ProgressItemState.COMPLETE; | |
461 item.progressValue = item.progressMax; | |
462 } else { | |
463 item.message = str('DELETE_ERROR'); | |
464 item.state = ProgressItemState.ERROR; | |
465 } | |
466 progressCenter.updateItem(item); | |
467 break; | |
468 } | |
469 }; | |
OLD | NEW |