OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011 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 // require cr.js |
| 6 // require cr/event_target.js |
| 7 // require cr/ui.js |
| 8 // require cr/ui/tabs.js |
| 9 // require cr/ui/tree.js |
| 10 // require cr/util.js |
| 11 |
| 12 (function() { |
| 13 'use strict'; |
| 14 |
| 15 /** |
| 16 * Return true if |obj| is {}. |
| 17 * @param {Object} obj Object to be checked. |
| 18 */ |
| 19 function isEmptyObject(obj) { |
| 20 for (var i in obj) |
| 21 return false; |
| 22 return true; |
| 23 } |
| 24 |
| 25 /** |
| 26 * Copy properties from |src| to |dest|. |
| 27 * @param {Object} src Source of the copy. |
| 28 * @param {Object} dest Destination of the copy. |
| 29 * @return {Object} |dest|. |
| 30 */ |
| 31 var overwrite_ = function(src, dest) { |
| 32 for (var i in src) |
| 33 dest[i] = src[i]; |
| 34 return dest; |
| 35 }; |
| 36 |
| 37 /** |
| 38 * Apply localization to |element| with i18n_template.js if available. |
| 39 * @param {Element} element Element to be localized. |
| 40 */ |
| 41 var localize_ = function(element) { |
| 42 if (window.i18nTemplate && cr.quota.localizedStrings) |
| 43 i18nTemplate.process(element, cr.quota.localizedStrings); |
| 44 }; |
| 45 |
| 46 var normalizer_ = { |
| 47 /** |
| 48 * Returns 'N/A' (Not Available) text if |v| is undefined. |
| 49 * @param {Object} v Object to print. |
| 50 * @return {string} 'N/A' or ''. |
| 51 */ |
| 52 avail_: function(v) { |
| 53 return v === undefined ? |
| 54 '<span i18n-content="text-not_available">N/A</span>' : ''; |
| 55 }, |
| 56 |
| 57 /** |
| 58 * Returns 'true', 'false' or 'N/A' for boolean |v|. |
| 59 * @param {?boolean} v Boolean to print. |
| 60 * @return {string} 'N/A', 'true' or 'false'. |
| 61 */ |
| 62 bool: function(v) { |
| 63 return normalizer_.avail_(v) || |
| 64 (v ? '<span i18n-content="text-true">true</span>' : |
| 65 '<span i18n-content="text-false">false</span>'); |
| 66 }, |
| 67 |
| 68 /** |
| 69 * Returns |v| itself unless |v| is not undefined, |
| 70 * else returns 'N/A' text. |
| 71 * @param {?string} v String to print. |
| 72 * @return {string} 'N/A' or |v|. |
| 73 */ |
| 74 text: function(v) { |
| 75 return normalizer_.avail_(v) || v; |
| 76 }, |
| 77 |
| 78 /** |
| 79 * Returns formatted string from toString'ed integer. |
| 80 * e.g. numBytes(123456789) = "123.45 MB (123,456,789 B)" |
| 81 * If |v| is undefined, this function returns "N/A". |
| 82 * @param {?string} v Digits to print. |
| 83 * @return {string} 'N/A' or formatted |v|. |
| 84 */ |
| 85 numBytes: function(v) { |
| 86 return normalizer_.avail_(v) || (function() { |
| 87 var result = ' B'; |
| 88 var prefix_index = 0; |
| 89 while (v.length > 3) { |
| 90 result = ',' + v.slice(-3) + result; |
| 91 v = v.slice(0, -3); |
| 92 ++prefix_index; |
| 93 } |
| 94 |
| 95 if (prefix_index > 0) { |
| 96 var p = result.slice(1, 3); |
| 97 result = v + result; |
| 98 |
| 99 var PREFIX = [' ', ' K', ' M', ' G', ' T', ' P']; |
| 100 prefix_index = Math.min(prefix_index, PREFIX.length - 1); |
| 101 result = |
| 102 v + '.' + p + |
| 103 PREFIX[prefix_index] + 'B' + |
| 104 ' (' + result + ')'; |
| 105 } else { |
| 106 result = v + result; |
| 107 } |
| 108 |
| 109 return result; |
| 110 })(); |
| 111 }, |
| 112 |
| 113 /** |
| 114 * Return formatted date |v| unless |v| is not undefined. |
| 115 * @param {?number} v Number of milliseconds since |
| 116 * UNIX epock time (0:00, Jan 1, 1970, UTC). |
| 117 * @return {string} Formatted text of date or 'N/A'. |
| 118 */ |
| 119 date: function(v) { |
| 120 return normalizer_.avail_(v) || (function() { |
| 121 // TODO(tzik): show relative time |
| 122 return new Date(v).toString(); |
| 123 })(); |
| 124 }, |
| 125 |
| 126 /** |
| 127 * Return toString'ed number |v| unless |v| is not undefined. |
| 128 * @param {?number} v Number to print. |
| 129 * @return {string} toString'ed |v| or 'N/A'. |
| 130 */ |
| 131 num: function(v) { |
| 132 return normalizer_.avail_(v) || v.toString(); |
| 133 } |
| 134 }; |
| 135 |
| 136 /** |
| 137 * Available disk space |
| 138 * @type {number|undefined} |
| 139 */ |
| 140 var available_space = undefined; |
| 141 |
| 142 /** |
| 143 * Root of the quota data tree, |
| 144 * holding userdata as |tree_view_object.detail|. |
| 145 * @type {cr.ui.Tree} |
| 146 */ |
| 147 var tree_view_object = undefined; |
| 148 |
| 149 /** |
| 150 * Key-value styled statistics data. |
| 151 * This WebUI does not touch contents, just show. |
| 152 * The value is hold as |stats[key].detail| |
| 153 * @type {Object<string,Element>} |
| 154 */ |
| 155 var stats = {}; |
| 156 |
| 157 /** |
| 158 * Initialize and return |tree_view_object|. |
| 159 * @return {cr.ui.Tree} Initialized |tree_view_object|. |
| 160 */ |
| 161 function getTreeViewObject() { |
| 162 if (!tree_view_object) { |
| 163 tree_view_object = $('tree-view'); |
| 164 cr.ui.decorate(tree_view_object, cr.ui.Tree); |
| 165 tree_view_object.detail = {payload: {}, children: {}}; |
| 166 tree_view_object.addEventListener('change', updateDescription); |
| 167 } |
| 168 return tree_view_object; |
| 169 } |
| 170 |
| 171 /** |
| 172 * Initialize and return a tree item represents specified storage type. |
| 173 * @param {!string} type Storage type. |
| 174 * @return {cr.ui.TreeItem} Initialized |storage_object|. |
| 175 */ |
| 176 function getStorageObject(type) { |
| 177 var tree_view_object = getTreeViewObject(); |
| 178 var storage_object = tree_view_object.detail.children[type]; |
| 179 if (!storage_object) { |
| 180 storage_object = new cr.ui.TreeItem({ |
| 181 label: type, |
| 182 detail: {payload: {}, children: {}} |
| 183 }); |
| 184 storage_object.mayHaveChildren_ = true; |
| 185 tree_view_object.detail.children[type] = storage_object; |
| 186 tree_view_object.add(storage_object); |
| 187 } |
| 188 return storage_object; |
| 189 } |
| 190 |
| 191 /** |
| 192 * Initialize and return a tree item represents specified |
| 193 * storage type and hostname. |
| 194 * @param {!string} type Storage type. |
| 195 * @param {!string} host Hostname. |
| 196 * @return {cr.ui.TreeItem} Initialized |host_object|. |
| 197 */ |
| 198 function getHostObject(type, host) { |
| 199 var storage_object = getStorageObject(type); |
| 200 var host_object = storage_object.detail.children[host]; |
| 201 if (!host_object) { |
| 202 host_object = new cr.ui.TreeItem({ |
| 203 label: host, |
| 204 detail: {payload: {}, children: {}} |
| 205 }); |
| 206 host_object.mayHaveChildren_ = true; |
| 207 storage_object.detail.children[host] = host_object; |
| 208 storage_object.add(host_object); |
| 209 } |
| 210 return host_object; |
| 211 } |
| 212 |
| 213 /** |
| 214 * Iinitialize and return a tree item represents specified |
| 215 * storage type, hostname and origin url. |
| 216 * @param {!string} type Storage type. |
| 217 * @param {!string} host Hostname. |
| 218 * @param {!string} origin Origin URL. |
| 219 * @return {cr.ui.TreeItem} Initialized |origin_object|. |
| 220 */ |
| 221 function getOriginObject(type, host, origin) { |
| 222 var host_object = getHostObject(type, host); |
| 223 var origin_object = host_object.detail.children[origin]; |
| 224 if (!origin_object) { |
| 225 origin_object = new cr.ui.TreeItem({ |
| 226 label: origin, |
| 227 detail: {payload: {}, children: {}} |
| 228 }); |
| 229 origin_object.mayHaveChildren_ = false; |
| 230 host_object.detail.children[origin] = origin_object; |
| 231 host_object.add(origin_object); |
| 232 } |
| 233 return origin_object; |
| 234 } |
| 235 |
| 236 /** |
| 237 * Event Handler for |cr.quota.onAvailableSpaceUpdated|. |
| 238 * |event.detail| contains |available_space| size. |
| 239 * |available_space| is a (toString'ed) 64bit integer, |
| 240 * that represents total available disk space. |
| 241 * @param {CustomEvent} event AvailableSpaceUpdated event. |
| 242 */ |
| 243 function handleAvailableSpace(event) { |
| 244 /** |
| 245 * @type {string} |
| 246 */ |
| 247 available_space = event.detail; |
| 248 $('diskspace-entry').innerHTML = normalizer_.numBytes(available_space); |
| 249 }; |
| 250 |
| 251 /** |
| 252 * Event Handler for |cr.quota.onGlobalDataUpdated|. |
| 253 * |event.detail| contains a record which has: |
| 254 * |type|: |
| 255 * Storage type, that is either 'temporary' or 'persistent' |
| 256 * |usage|: |
| 257 * Total storage usage of all hosts, |
| 258 * toString'ed 64bit integer. |
| 259 * |unlimited_usage|: |
| 260 * Total storage usage of unlimited-quota origins, |
| 261 * toString'ed 64bit integer. |
| 262 * |quota|: |
| 263 * Total quota of the storage, toString'ed 64bit integer. |
| 264 * |
| 265 * |usage|, |unlimited_usage| and |quota| can be missing, |
| 266 * and some additional field can be included. |
| 267 * @param {CustomEvent} event GlobalDataUpdated event. |
| 268 */ |
| 269 function handleGlobalData(event) { |
| 270 /** |
| 271 * @type {{ |
| 272 * type: {!string}, |
| 273 * usage: {?string}, |
| 274 * unlimited_usage: {?string} |
| 275 * quota: {?string} |
| 276 * }} |
| 277 */ |
| 278 var data = event.detail; |
| 279 var storage_object = getStorageObject(data.type); |
| 280 overwrite_(data, storage_object.detail.payload); |
| 281 storage_object.reveal(); |
| 282 }; |
| 283 |
| 284 /** |
| 285 * Event Handler for |cr.quota.onHostDataUpdated|. |
| 286 * |event.detail| contains records which have: |
| 287 * |host|: |
| 288 * Hostname of the entry. (e.g. 'example.com') |
| 289 * |type|: |
| 290 * Storage type. 'temporary' or 'persistent' |
| 291 * |usage|: |
| 292 * Total storage usage of the host, toString'ed 64bit integer. |
| 293 * |quota|: |
| 294 * Per-host quota, toString'ed 64bit integer. |
| 295 * |
| 296 * |usage| and |quota| can be missing, |
| 297 * and some additional field can be included. |
| 298 * @param {CustomEvent} event HostDataUpdated event. |
| 299 */ |
| 300 function handleHostData(event) { |
| 301 /** |
| 302 * @type {Array<{ |
| 303 * host: {!string}, |
| 304 * type: {!string}, |
| 305 * usage: {?string}, |
| 306 * quota: {?string} |
| 307 * }} |
| 308 */ |
| 309 var data_array = event.detail; |
| 310 |
| 311 for (var i = 0; i < data_array.length; ++i) { |
| 312 var data = data_array[i]; |
| 313 var host_object = getHostObject(data.type, data.host); |
| 314 overwrite_(data, host_object.detail.payload); |
| 315 host_object.reveal(); |
| 316 } |
| 317 } |
| 318 |
| 319 /** |
| 320 * Event Handler for |cr.quota.onOriginDataUpdated|. |
| 321 * |event.detail| contains records which have: |
| 322 * |origin|: |
| 323 * Origin URL of the entry. |
| 324 * |type|: |
| 325 * Storage type of the entry. 'temporary' or 'persistent'. |
| 326 * |host|: |
| 327 * Hostname of the entry. |
| 328 * |in_use|: |
| 329 * Whether the origin is in use or not. |
| 330 * |used_count|: |
| 331 * Used count of the storage from the origin. |
| 332 * |last_access_time|: |
| 333 * Last storage access time from the origin. |
| 334 * Number of seconds since Jan 1, 1970. |
| 335 * |
| 336 * |in_use|, |used_count| and |last_access_time| can be missing, |
| 337 * and some additional field can be included. |
| 338 * @param {CustomEvent} event OriginDataUpdated event. |
| 339 */ |
| 340 function handleOriginData(event) { |
| 341 /** |
| 342 * @type {Array<{ |
| 343 * origin: {!string}, |
| 344 * type: {!string}, |
| 345 * host: {!string}, |
| 346 * in_use: {?boolean}, |
| 347 * used_count: {?number}, |
| 348 * last_access_time: {?number} |
| 349 * }>} |
| 350 */ |
| 351 var data_array = event.detail; |
| 352 |
| 353 for (var i = 0; i < data_array.length; ++i) { |
| 354 var data = data_array[i]; |
| 355 var origin_object = getOriginObject(data.type, data.host, data.origin); |
| 356 overwrite_(data, origin_object.detail.payload); |
| 357 origin_object.reveal(); |
| 358 } |
| 359 } |
| 360 |
| 361 /** |
| 362 * Event Handler for |cr.quota.onStatisticsUpdated|. |
| 363 * |event.detail| contains misc statistics data as dictionary. |
| 364 * @param {CustomEvent} event StatisticsUpdated event. |
| 365 */ |
| 366 function handleStatistics(event) { |
| 367 var data = event.detail; |
| 368 for (var key in data) { |
| 369 var entry = stats[key]; |
| 370 if (!entry) { |
| 371 entry = cr.doc.createElement('tr'); |
| 372 $('stat-entries').appendChild(entry); |
| 373 stats[key] = entry; |
| 374 } |
| 375 entry.detail = data[key]; |
| 376 entry.innerHTML = |
| 377 '<td>' + normalizer_.text(key) + '</td>' + |
| 378 '<td>' + normalizer_.text(entry.detail) + '</td>'; |
| 379 localize_(entry); |
| 380 } |
| 381 } |
| 382 |
| 383 /** |
| 384 * Update description on 'tree-item-description' field with |
| 385 * selected item in Tree view. |
| 386 */ |
| 387 function updateDescription() { |
| 388 var item = getTreeViewObject().selectedItem; |
| 389 var tbody = $('tree-item-description'); |
| 390 tbody.innerHTML = ''; |
| 391 |
| 392 if (item) { |
| 393 var keyAndLabel = [['type', 'Storage Type'], |
| 394 ['host', 'Host Name'], |
| 395 ['origin', 'Origin URL'], |
| 396 ['usage', 'Total Storage Usage', normalizer_.numBytes], |
| 397 ['unlimited_usage', 'Usage of Unlimited Origins', |
| 398 normalizer_.numBytes], |
| 399 ['quota', 'Quota', normalizer_.numBytes], |
| 400 ['in_use', 'Origin is in use?'], |
| 401 ['used_count', 'Used count'], |
| 402 ['last_access_time', 'Last Access Time', |
| 403 normalizer_.date] |
| 404 ]; |
| 405 for (var i = 0; i < keyAndLabel.length; ++i) { |
| 406 var key = keyAndLabel[i][0]; |
| 407 var label = keyAndLabel[i][1]; |
| 408 var entry = item.detail.payload[key]; |
| 409 if (entry === undefined) |
| 410 continue; |
| 411 |
| 412 var normalize = keyAndLabel[i][2] || normalizer_.text; |
| 413 |
| 414 var row = cr.doc.createElement('tr'); |
| 415 row.innerHTML = |
| 416 '<td>' + label + '</td>' + |
| 417 '<td>' + normalize(entry) + '</td>'; |
| 418 localize_(row); |
| 419 tbody.appendChild(row); |
| 420 } |
| 421 } |
| 422 } |
| 423 |
| 424 /** |
| 425 * Dump |tree_view_object| or subtree to a object. |
| 426 * @param {?{cr.ui.Tree|cr.ui.TreeItem}} opt_treeitem |
| 427 * @return {Object} Dump result object from |tree_view_object|. |
| 428 */ |
| 429 function dumpTreeToObj(opt_treeitem) { |
| 430 var treeitem = opt_treeitem || getTreeViewObject(); |
| 431 var res = {}; |
| 432 res.payload = treeitem.detail.payload; |
| 433 res.children = []; |
| 434 for (var i in treeitem.detail.children) { |
| 435 var child = treeitem.detail.children[i]; |
| 436 res.children.push(dumpTreeToObj(child)); |
| 437 } |
| 438 |
| 439 if (isEmptyObject(res.payload)) |
| 440 delete res.payload; |
| 441 |
| 442 if (res.children.length == 0) |
| 443 delete res.children; |
| 444 return res; |
| 445 } |
| 446 |
| 447 /** |
| 448 * Dump |stats| to a object. |
| 449 * @return {Object} Dump result object from |stats|. |
| 450 */ |
| 451 function dumpStatsToObj() { |
| 452 var result = {}; |
| 453 for (var key in stats) |
| 454 result[key] = stats[key].detail; |
| 455 return result; |
| 456 } |
| 457 |
| 458 /** |
| 459 * Event handler for 'dump-button' 'click'ed. |
| 460 * Dump and show all data from WebUI page to 'dump-field' element. |
| 461 */ |
| 462 function dump() { |
| 463 var separator = '========\n'; |
| 464 |
| 465 $('dump-field').textContent = |
| 466 separator + |
| 467 'Summary\n' + |
| 468 separator + |
| 469 JSON.stringify({available_space: available_space}, null, 2) + '\n' + |
| 470 separator + |
| 471 'Usage And Quota\n' + |
| 472 separator + |
| 473 JSON.stringify(dumpTreeToObj(), null, 2) + '\n' + |
| 474 separator + |
| 475 'Misc Statistics\n' + |
| 476 separator + |
| 477 JSON.stringify(dumpStatsToObj(), null, 2); |
| 478 } |
| 479 |
| 480 function onLoad() { |
| 481 cr.ui.decorate('tabbox', cr.ui.TabBox); |
| 482 localize_(document); |
| 483 |
| 484 cr.quota.onAvailableSpaceUpdated.addEventListener('update', |
| 485 handleAvailableSpace); |
| 486 cr.quota.onGlobalDataUpdated.addEventListener('update', handleGlobalData); |
| 487 cr.quota.onHostDataUpdated.addEventListener('update', handleHostData); |
| 488 cr.quota.onOriginDataUpdated.addEventListener('update', handleOriginData); |
| 489 cr.quota.onStatisticsUpdated.addEventListener('update', handleStatistics); |
| 490 cr.quota.requestData(); |
| 491 |
| 492 $('refresh-button').addEventListener('click', cr.quota.requestData, false); |
| 493 $('dump-button').addEventListener('click', dump, false); |
| 494 } |
| 495 |
| 496 cr.doc.addEventListener('DOMContentLoaded', onLoad, false); |
| 497 })(); |
OLD | NEW |