• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 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 * Group of progress item in the progress center panels.
9 *
10 * This is responsible for generating the summarized item and managing lifetime
11 * of error items.
12 * @param {string} name Name of the group.
13 * @param {boolean} quiet Whether the group is for quiet items or not.
14 * @constructor
15 */
16function ProgressCenterItemGroup(name, quiet) {
17  /**
18   * Name of the group.
19   * @type {string}
20   */
21  this.name = name;
22
23  /**
24   * Whether the group is for quiet items or not.
25   * @type {boolean}
26   * @private
27   */
28  this.quiet_ = quiet;
29
30  /**
31   * State of the group.
32   * @type {ProgressCenterItemGroup.State}
33   * @private
34   */
35  this.state_ = ProgressCenterItemGroup.State.EMPTY;
36
37  /**
38   * Items that are progressing, or completed but still animated.
39   * Key is item ID.
40   * @type {Object.<string, ProgressCenterItem>}
41   * @private
42   */
43  this.items_ = {};
44
45  /**
46   * Set of animated state of items. Key is item ID and value is whether the
47   * item is animated or not.
48   * @type {Object.<string, boolean>}
49   * @private
50   */
51  this.animated_ = {};
52
53  /**
54   * Last summarized item.
55   * @type {ProgressCenterItem}
56   * @private
57   */
58  this.summarizedItem_ = null;
59
60  /**
61   * Whether the summarized item is animated or not.
62   * @type {boolean}
63   * @private
64   */
65  this.summarizedItemAnimated_ = false;
66
67  /**
68   * Total maximum progress value of items already completed and removed from
69   * this.items_.
70   * @type {number}
71   * @private
72   */
73  this.totalProgressMax_ = 0;
74
75  /**
76   * Total progress value of items already completed and removed from
77   * this.items_.
78   * @type {number}
79   * @private
80   */
81  this.totalProgressValue_ = 0;
82
83  Object.seal(this);
84}
85
86/**
87 * State of ProgressCenterItemGroup.
88 * @enum {string}
89 * @const
90 */
91ProgressCenterItemGroup.State = Object.freeze({
92  // Group has no items.
93  EMPTY: 'empty',
94  // Group has at least 1 progressing item.
95  ACTIVE: 'active',
96  // Group has no progressing items but still shows error items.
97  INACTIVE: 'inactive'
98});
99
100/**
101 * Makes the summarized item for the groups.
102 *
103 * When a group has only error items, getSummarizedItem of the item returns
104 * null. Basically the first result of the groups that the progress center panel
105 * contains is used as a summarized item. But If all the group returns null, the
106 * progress center panel generates the summarized item by using the method.
107 *
108 * @param {Array.<ProgressCenterItemGroup>} var_groups List of groups.
109 * @return {ProgressCenterItem} Summarized item.
110 */
111ProgressCenterItemGroup.getSummarizedErrorItem = function(var_groups) {
112  var groups = Array.prototype.slice.call(arguments);
113  var errorItems = [];
114  for (var i = 0; i < groups.length; i++) {
115    for (var id in groups[i].items_) {
116      var item = groups[i].items_[id];
117      if (item.state === ProgressItemState.ERROR)
118        errorItems.push(item);
119    }
120  }
121  if (errorItems.length === 0)
122    return null;
123
124  if (errorItems.length === 1)
125    return errorItems[0].clone();
126
127  var item = new ProgressCenterItem();
128  item.state = ProgressItemState.ERROR;
129  item.message = strf('ERROR_PROGRESS_SUMMARY_PLURAL',
130                      errorItems.length);
131  item.single = false;
132  return item;
133};
134
135/**
136 * Obtains Whether the item should be animated or not.
137 * @param {boolean} previousAnimated Whether the item is previously animated or
138 *     not.
139 * @param {ProgressCenterItem} previousItem Item before updating.
140 * @param {ProgressCenterItem} item New item.
141 * @return {boolean} Whether the item should be animated or not.
142 * @private
143 */
144ProgressCenterItemGroup.shouldAnimate_ = function(
145    previousAnimated, previousItem, item) {
146  if (!previousItem || !item || previousItem.quiet || item.quiet)
147    return false;
148  if (previousItem.progressRateInPercent < item.progressRateInPercent)
149    return true;
150  if (previousAnimated &&
151      previousItem.progressRateInPercent === item.progressRateInPercent)
152    return true;
153  return false;
154};
155
156ProgressCenterItemGroup.prototype = {
157  /**
158   * @return {ProgressCenterItemGroup.State} State of the group.
159   */
160  get state() {
161    return this.state_;
162  },
163
164  /**
165   * @return {number} Number of error items that the group contains.
166   */
167  get numErrors() {
168    var result = 0;
169    for (var id in this.items_) {
170      if (this.items_[id].state === ProgressItemState.ERROR)
171        result++;
172    }
173    return result;
174  }
175};
176
177/**
178 * Obtains the progressing (or completed but animated) item.
179 *
180 * @param {string} id Item ID.
181 * @return {ProgressCenterItem} Item having the ID.
182 */
183ProgressCenterItemGroup.prototype.getItem = function(id) {
184  return this.items_[id] || null;
185};
186
187/**
188 * Obtains whether the item should be animated or not.
189 * @param {string} id Item ID.
190 * @return {boolean} Whether the item should be animated or not.
191 */
192ProgressCenterItemGroup.prototype.isAnimated = function(id) {
193  return !!this.animated_[id];
194};
195
196/**
197 * Obtains whether the summarized item should be animated or not.
198 * @return {boolean} Whether the summarized item should be animated or not.
199 */
200ProgressCenterItemGroup.prototype.isSummarizedAnimated = function() {
201  return this.summarizedItemAnimated_;
202};
203
204/**
205 * Starts item update.
206 * Marks the given item as updating.
207 * @param {ProgressCenterItem} item Item containing updated information.
208 */
209ProgressCenterItemGroup.prototype.update = function(item) {
210  // If the group is inactive, go back to the empty state.
211  this.endInactive();
212
213  // Compares the current state and the new state to check if the update is
214  // valid or not.
215  var previousItem = this.items_[item.id];
216  switch (item.state) {
217    case ProgressItemState.ERROR:
218      if (previousItem && previousItem.state !== ProgressItemState.PROGRESSING)
219        return;
220      if (this.state_ === ProgressCenterItemGroup.State.EMPTY)
221        this.state_ = ProgressCenterItemGroup.State.INACTIVE;
222      this.items_[item.id] = item.clone();
223      this.animated_[item.id] = false;
224      this.summarizedItem_ = null;
225      break;
226
227    case ProgressItemState.PROGRESSING:
228    case ProgressItemState.COMPLETED:
229      if ((!previousItem && item.state === ProgressItemState.COMPLETED) ||
230          (previousItem &&
231           previousItem.state !== ProgressItemState.PROGRESSING))
232        return;
233      if (this.state_ === ProgressCenterItemGroup.State.EMPTY)
234        this.state_ = ProgressCenterItemGroup.State.ACTIVE;
235      this.items_[item.id] = item.clone();
236      this.animated_[item.id] = ProgressCenterItemGroup.shouldAnimate_(
237          !!this.animated_[item.id],
238          previousItem,
239          item);
240      if (!this.animated_[item.id])
241        this.completeItemAnimation(item.id);
242      break;
243
244    case ProgressItemState.CANCELED:
245      if (!previousItem ||
246          previousItem.state !== ProgressItemState.PROGRESSING)
247        return;
248      delete this.items_[item.id];
249      this.animated_[item.id] = false;
250      this.summarizedItem_ = null;
251  }
252
253  // Update the internal summarized item cache.
254  var previousSummarizedItem = this.summarizedItem_;
255  this.summarizedItem_ = this.getSummarizedItem(0);
256  this.summarizedItemAnimated_ = ProgressCenterItemGroup.shouldAnimate_(
257      !!this.summarizedItemAnimated_,
258      previousSummarizedItem,
259      this.summarizedItem_);
260  if (!this.summarizedItemAnimated_)
261    this.completeSummarizedItemAnimation();
262};
263
264/**
265 * Notifies the end of the item's animation to the group.
266 * If all the items except error items completes, the group enter the inactive
267 * state.
268 * @param {string} id Item ID.
269 */
270ProgressCenterItemGroup.prototype.completeItemAnimation = function(id) {
271  if (this.state_ !== ProgressCenterItemGroup.State.ACTIVE)
272    return;
273
274  this.animated_[id] = false;
275  if (this.items_[id].state === ProgressItemState.COMPLETED) {
276    this.totalProgressValue_ += (this.items_[id].progressValue || 0.0);
277    this.totalProgressMax_ += (this.items_[id].progressMax || 0.0);
278    delete this.items_[id];
279    this.tryEndActive_();
280  }
281};
282
283/**
284 * Notifies the end of the summarized item's animation.
285 * This may update summarized view. (1 progressing + 1 error -> 1 error)
286 */
287ProgressCenterItemGroup.prototype.completeSummarizedItemAnimation = function() {
288  this.summarizedItemAnimated_ = false;
289  this.tryEndActive_();
290};
291
292/**
293 * Obtains the summary of the set.
294 * @param {number} numOtherErrors Number of errors contained by other groups.
295 * @return {ProgressCenterItem} Item.
296 */
297ProgressCenterItemGroup.prototype.getSummarizedItem =
298    function(numOtherErrors) {
299  if (this.state_ === ProgressCenterItemGroup.State.EMPTY ||
300      this.state_ === ProgressCenterItemGroup.State.INACTIVE)
301    return null;
302
303  var summarizedItem = new ProgressCenterItem();
304  summarizedItem.quiet = this.quiet_;
305  summarizedItem.progressMax += this.totalProgressMax_;
306  summarizedItem.progressValue += this.totalProgressValue_;
307  var progressingItems = [];
308  var errorItems = [];
309  var numItems = 0;
310
311  for (var id in this.items_) {
312    var item = this.items_[id];
313    numItems++;
314
315    // Count states.
316    switch (item.state) {
317      case ProgressItemState.PROGRESSING:
318      case ProgressItemState.COMPLETED:
319        progressingItems.push(item);
320        break;
321      case ProgressItemState.ERROR:
322        errorItems.push(item);
323        continue;
324    }
325
326    // If all of the progressing items have the same type, then use
327    // it. Otherwise use TRANSFER, since it is the most generic.
328    if (summarizedItem.type === null)
329      summarizedItem.type = item.type;
330    else if (summarizedItem.type !== item.type)
331      summarizedItem.type = ProgressItemType.TRANSFER;
332
333    // Sum up the progress values.
334    summarizedItem.progressMax += item.progressMax;
335    summarizedItem.progressValue += item.progressValue;
336  }
337
338  // Returns 1 item.
339  if (progressingItems.length === 1 &&
340      errorItems.length + numOtherErrors === 0) {
341    summarizedItem.id = progressingItems[0].id;
342    summarizedItem.cancelCallback = progressingItems[0].cancelCallback;
343    summarizedItem.message = progressingItems[0].message;
344    summarizedItem.state = progressingItems[0].state;
345    return summarizedItem;
346  }
347
348  // Returns integrated items.
349  if (progressingItems.length > 0) {
350    var numErrors = errorItems.length + numOtherErrors;
351    var messages = [];
352    switch (summarizedItem.type) {
353      case ProgressItemType.COPY:
354        messages.push(str('COPY_PROGRESS_SUMMARY'));
355        break;
356      case ProgressItemType.MOVE:
357        messages.push(str('MOVE_PROGRESS_SUMMARY'));
358        break;
359      case ProgressItemType.DELETE:
360        messages.push(str('DELETE_PROGRESS_SUMMARY'));
361        break;
362      case ProgressItemType.ZIP:
363        messages.push(str('ZIP_PROGRESS_SUMMARY'));
364        break;
365      case ProgressItemType.SYNC:
366        messages.push(str('SYNC_PROGRESS_SUMMARY'));
367        break;
368      case ProgressItemType.TRANSFER:
369        messages.push(str('TRANSFER_PROGRESS_SUMMARY'));
370        break;
371    }
372    if (numErrors === 1)
373      messages.push(str('ERROR_PROGRESS_SUMMARY'));
374    else if (numErrors > 1)
375      messages.push(strf('ERROR_PROGRESS_SUMMARY_PLURAL', numErrors));
376    summarizedItem.single = false;
377    summarizedItem.message = messages.join(' ');
378    summarizedItem.state = ProgressItemState.PROGRESSING;
379    return summarizedItem;
380  }
381
382  // Returns complete items.
383  summarizedItem.state = ProgressItemState.COMPLETED;
384  return summarizedItem;
385};
386
387/**
388 * Goes back to the EMPTY state from the INACTIVE state. Removes all the items.
389 * If the current state is not the INACTIVE, nothing happens.
390 */
391ProgressCenterItemGroup.prototype.endInactive = function() {
392  if (this.state_ !== ProgressCenterItemGroup.State.INACTIVE)
393    return;
394  this.items_ = {};
395  this.animated_ = {};
396  this.summarizedItem_ = null;
397  this.summarizedItemAnimated_ = false;
398  this.totalProgressValue_ = 0.0;
399  this.totalProgressMax_ = 0.0;
400  this.state_ = ProgressCenterItemGroup.State.EMPTY;
401};
402
403/**
404 * Ends active state if there is no progressing and animated items.
405 * @private
406 */
407ProgressCenterItemGroup.prototype.tryEndActive_ = function() {
408  if (this.state_ !== ProgressCenterItemGroup.State.ACTIVE ||
409      this.summarizedItemAnimated_)
410    return;
411  var hasError = false;
412  for (var id in this.items_) {
413    // If there is non-error item (progressing, or completed but still
414    // animated), we should stay the active state.
415    if (this.items_[id].state !== ProgressItemState.ERROR)
416      return;
417    hasError = true;
418  }
419  this.state_ = ProgressCenterItemGroup.State.INACTIVE;
420  if (!hasError)
421    this.endInactive();
422};
423