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 */ 16 function 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 */ 91 ProgressCenterItemGroup.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 */ 111 ProgressCenterItemGroup.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 */ 144 ProgressCenterItemGroup.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 156 ProgressCenterItemGroup.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 */ 183 ProgressCenterItemGroup.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 */ 192 ProgressCenterItemGroup.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 */ 200 ProgressCenterItemGroup.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 */ 209 ProgressCenterItemGroup.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 */ 270 ProgressCenterItemGroup.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 */ 287 ProgressCenterItemGroup.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 */ 297 ProgressCenterItemGroup.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 */ 391 ProgressCenterItemGroup.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 */ 407 ProgressCenterItemGroup.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