Home | History | Annotate | Download | only in quota_internals
      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  * @param {Object} object Object to be checked.
     17  * @return {boolean} true if |object| is {}.
     18  * @private
     19  */
     20 function isEmptyObject_(object) {
     21   for (var i in object)
     22     return false;
     23   return true;
     24 }
     25 
     26 /**
     27  * Copy properties from |source| to |destination|.
     28  * @param {Object} source Source of the copy.
     29  * @param {Object} destination Destination of the copy.
     30  * @return {Object} |destination|.
     31  * @private
     32  */
     33 function copyAttributes_(source, destination) {
     34   for (var i in source)
     35     destination[i] = source[i];
     36   return destination;
     37 };
     38 
     39 /**
     40  * Apply localization to |element| with i18n_template.js if available.
     41  * @param {Element} element Element to be localized.
     42  * @private
     43  */
     44 function localize_(element) {
     45   if (window.i18nTemplate && window.templateData)
     46     i18nTemplate.process(element, templateData);
     47 };
     48 
     49 /**
     50  * Returns 'N/A' (Not Available) text if |value| is undefined.
     51  * @param {Object} value Object to print.
     52  * @return {string} 'N/A' or ''.
     53  * @private
     54  */
     55 function checkIfAvailable_(value) {
     56   return value === undefined ? 'N/A' : '';
     57 }
     58 
     59 /**
     60  * Returns |value| itself if |value| is not undefined,
     61  * else returns 'N/A' text.
     62  * @param {?string} value String to print.
     63  * @return {string} 'N/A' or |value|.
     64  * @private
     65  */
     66 function stringToText_(value) {
     67   return checkIfAvailable_(value) || value;
     68 }
     69 
     70 /**
     71  * Separates |value| into segments.
     72  * The length of first segment is at most |maxLength|.
     73  * Length of other following segments are just |maxLength|.
     74  * e.g. separateBackward_('abcdefghijk', 4) == ['abc','defg','hijk'];
     75  * @param {string} value String to be separated.
     76  * @param {number} maxLength Max length of segments.
     77  * @return {Array.<string>} Array of segments.
     78  * @private
     79  */
     80 function separateBackward_(value, maxLength) {
     81   var result = [];
     82   while (value.length > maxLength) {
     83     result.unshift(value.slice(-3));
     84     value = value.slice(0, -3);
     85   }
     86   result.unshift(value);
     87   return result;
     88 }
     89 
     90 /**
     91  * Returns formatted string from number as number of bytes.
     92  * e.g. numBytesToText(123456789) = '123.45 MB (123,456,789 B)'.
     93  * If |value| is undefined, this function returns 'N/A'.
     94  * @param {?number} value Number to print.
     95  * @return {string} 'N/A' or formatted |value|.
     96  * @private
     97  */
     98 function numBytesToText_(value) {
     99   var result = checkIfAvailable_(value);
    100   if (result)
    101     return result;
    102 
    103   var segments = separateBackward_(value.toString(), 3);
    104   result = segments.join(',') + ' B';
    105 
    106   if (segments.length > 1) {
    107     var UNIT = [' B', ' KB', ' MB', ' GB', ' TB', ' PB'];
    108     result = segments[0] + '.' + segments[1].slice(0, 2) +
    109         UNIT[Math.min(segments.length, UNIT.length) - 1] +
    110         ' (' + result + ')';
    111   }
    112 
    113   return result;
    114 }
    115 
    116 /**
    117  * Return formatted date |value| if |value| is not undefined.
    118  * If |value| is undefined, this function returns 'N/A'.
    119  * @param {?number} value Number of milliseconds since
    120  *   UNIX epoch time (0:00, Jan 1, 1970, UTC).
    121  * @return {string} Formatted text of date or 'N/A'.
    122  * @private
    123  */
    124 function dateToText(value) {
    125   var result = checkIfAvailable_(value);
    126   if (result)
    127     return result;
    128 
    129   var time = new Date(value);
    130   var now = new Date();
    131   var delta = Date.now() - value;
    132 
    133   var SECOND = 1000;
    134   var MINUTE = 60 * SECOND;
    135   var HOUR = 60 * MINUTE;
    136   var DAY = 23 * HOUR;
    137   var WEEK = 7 * DAY;
    138 
    139   var SHOW_SECOND = 5 * MINUTE;
    140   var SHOW_MINUTE = 5 * HOUR;
    141   var SHOW_HOUR = 3 * DAY;
    142   var SHOW_DAY = 2 * WEEK;
    143   var SHOW_WEEK = 3 * 30 * DAY;
    144 
    145   if (delta < 0) {
    146     result = 'access from future ';
    147   } else if (delta < SHOW_SECOND) {
    148     result = Math.ceil(delta / SECOND) + ' sec ago ';
    149   } else if (delta < SHOW_MINUTE) {
    150     result = Math.ceil(delta / MINUTE) + ' min ago ';
    151   } else if (delta < SHOW_HOUR) {
    152     result = Math.ceil(delta / HOUR) + ' hr ago ';
    153   } else if (delta < SHOW_WEEK) {
    154     result = Math.ceil(delta / DAY) + ' day ago ';
    155   }
    156 
    157   result += '(' + time.toString() + ')';
    158   return result;
    159 }
    160 
    161 /**
    162  * Available disk space.
    163  * @type {number|undefined}
    164  */
    165 var availableSpace = undefined;
    166 
    167 /**
    168  * Root of the quota data tree,
    169  * holding userdata as |treeViewObject.detail|.
    170  * @type {cr.ui.Tree}
    171  */
    172 var treeViewObject = undefined;
    173 
    174 /**
    175  * Key-value styled statistics data.
    176  * This WebUI does not touch contents, just show.
    177  * The value is hold as |statistics[key].detail|.
    178  * @type {Object<string,Element>}
    179  */
    180 var statistics = {};
    181 
    182 /**
    183  * Initialize and return |treeViewObject|.
    184  * @return {cr.ui.Tree} Initialized |treeViewObject|.
    185  */
    186 function getTreeViewObject() {
    187   if (!treeViewObject) {
    188     treeViewObject = $('tree-view');
    189     cr.ui.decorate(treeViewObject, cr.ui.Tree);
    190     treeViewObject.detail = {payload: {}, children: {}};
    191     treeViewObject.addEventListener('change', updateDescription);
    192   }
    193   return treeViewObject;
    194 }
    195 
    196 /**
    197  * Initialize and return a tree item, that represents specified storage type.
    198  * @param {!string} type Storage type.
    199  * @return {cr.ui.TreeItem} Initialized |storageObject|.
    200  */
    201 function getStorageObject(type) {
    202   var treeViewObject = getTreeViewObject();
    203   var storageObject = treeViewObject.detail.children[type];
    204   if (!storageObject) {
    205     storageObject = new cr.ui.TreeItem({
    206         label: type,
    207         detail: {payload: {}, children: {}}
    208     });
    209     storageObject.mayHaveChildren_ = true;
    210     treeViewObject.detail.children[type] = storageObject;
    211     treeViewObject.add(storageObject);
    212   }
    213   return storageObject;
    214 }
    215 
    216 /**
    217  * Initialize and return a tree item, that represents specified
    218  *  storage type and hostname.
    219  * @param {!string} type Storage type.
    220  * @param {!string} host Hostname.
    221  * @return {cr.ui.TreeItem} Initialized |hostObject|.
    222  */
    223 function getHostObject(type, host) {
    224   var storageObject = getStorageObject(type);
    225   var hostObject = storageObject.detail.children[host];
    226   if (!hostObject) {
    227     hostObject = new cr.ui.TreeItem({
    228         label: host,
    229         detail: {payload: {}, children: {}}
    230     });
    231     hostObject.mayHaveChildren_ = true;
    232     storageObject.detail.children[host] = hostObject;
    233     storageObject.add(hostObject);
    234   }
    235   return hostObject;
    236 }
    237 
    238 /**
    239  * Initialize and return a tree item, that represents specified
    240  * storage type, hostname and origin url.
    241  * @param {!string} type Storage type.
    242  * @param {!string} host Hostname.
    243  * @param {!string} origin Origin URL.
    244  * @return {cr.ui.TreeItem} Initialized |originObject|.
    245  */
    246 function getOriginObject(type, host, origin) {
    247   var hostObject = getHostObject(type, host);
    248   var originObject = hostObject.detail.children[origin];
    249   if (!originObject) {
    250     originObject = new cr.ui.TreeItem({
    251         label: origin,
    252         detail: {payload: {}, children: {}}
    253     });
    254     originObject.mayHaveChildren_ = false;
    255     hostObject.detail.children[origin] = originObject;
    256     hostObject.add(originObject);
    257   }
    258   return originObject;
    259 }
    260 
    261 /**
    262  * Event Handler for |cr.quota.onAvailableSpaceUpdated|.
    263  * |event.detail| contains |availableSpace|.
    264  * |availableSpace| represents total available disk space.
    265  * @param {CustomEvent} event AvailableSpaceUpdated event.
    266  */
    267 function handleAvailableSpace(event) {
    268   /**
    269    * @type {string}
    270    */
    271   availableSpace = event.detail;
    272   $('diskspace-entry').innerHTML = numBytesToText_(availableSpace);
    273 };
    274 
    275 /**
    276  * Event Handler for |cr.quota.onGlobalInfoUpdated|.
    277  * |event.detail| contains a record which has:
    278  *   |type|:
    279  *     Storage type, that is either 'temporary' or 'persistent'.
    280  *   |usage|:
    281  *     Total storage usage of all hosts.
    282  *   |unlimitedUsage|:
    283  *     Total storage usage of unlimited-quota origins.
    284  *   |quota|:
    285  *     Total quota of the storage.
    286  *
    287  *  |usage|, |unlimitedUsage| and |quota| can be missing,
    288  *  and some additional fields can be included.
    289  * @param {CustomEvent} event GlobalInfoUpdated event.
    290  */
    291 function handleGlobalInfo(event) {
    292   /**
    293    * @type {{
    294    *         type: {!string},
    295    *         usage: {?number},
    296    *         unlimitedUsage: {?number}
    297    *         quota: {?string}
    298    *       }}
    299    */
    300   var data = event.detail;
    301   var storageObject = getStorageObject(data.type);
    302   copyAttributes_(data, storageObject.detail.payload);
    303   storageObject.reveal();
    304   if (getTreeViewObject().selectedItem == storageObject)
    305     updateDescription();
    306 
    307 };
    308 
    309 /**
    310  * Event Handler for |cr.quota.onPerHostInfoUpdated|.
    311  * |event.detail| contains records which have:
    312  *   |host|:
    313  *     Hostname of the entry. (e.g. 'example.com')
    314  *   |type|:
    315  *     Storage type. 'temporary' or 'persistent'
    316  *   |usage|:
    317  *     Total storage usage of the host.
    318  *   |quota|:
    319  *     Per-host quota.
    320  *
    321  * |usage| and |quota| can be missing,
    322  * and some additional fields can be included.
    323  * @param {CustomEvent} event PerHostInfoUpdated event.
    324  */
    325 function handlePerHostInfo(event) {
    326   /**
    327    * @type {Array<{
    328    *         host: {!string},
    329    *         type: {!string},
    330    *         usage: {?number},
    331    *         quota: {?number}
    332    *       }}
    333    */
    334   var dataArray = event.detail;
    335 
    336   for (var i = 0; i < dataArray.length; ++i) {
    337     var data = dataArray[i];
    338     var hostObject = getHostObject(data.type, data.host);
    339     copyAttributes_(data, hostObject.detail.payload);
    340     hostObject.reveal();
    341     if (getTreeViewObject().selectedItem == hostObject)
    342       updateDescription();
    343 
    344   }
    345 }
    346 
    347 /**
    348  * Event Handler for |cr.quota.onPerOriginInfoUpdated|.
    349  * |event.detail| contains records which have:
    350  *   |origin|:
    351  *     Origin URL of the entry.
    352  *   |type|:
    353  *     Storage type of the entry. 'temporary' or 'persistent'.
    354  *   |host|:
    355  *     Hostname of the entry.
    356  *   |inUse|:
    357  *     true if the origin is in use.
    358  *   |usedCount|:
    359  *     Used count of the storage from the origin.
    360  *   |lastAccessTime|:
    361  *     Last storage access time from the origin.
    362  *     Number of milliseconds since UNIX epoch (Jan 1, 1970, 0:00:00 UTC).
    363  *   |lastModifiedTime|:
    364  *     Last modified time of the storage from the origin.
    365  *     Number of milliseconds since UNIX epoch.
    366  *
    367  * |inUse|, |usedCount|, |lastAccessTime| and |lastModifiedTime| can be missing,
    368  * and some additional fields can be included.
    369  * @param {CustomEvent} event PerOriginInfoUpdated event.
    370  */
    371 function handlePerOriginInfo(event) {
    372   /**
    373    * @type {Array<{
    374    *         origin: {!string},
    375    *         type: {!string},
    376    *         host: {!string},
    377    *         inUse: {?boolean},
    378    *         usedCount: {?number},
    379    *         lastAccessTime: {?number}
    380    *         lastModifiedTime: {?number}
    381    *       }>}
    382    */
    383   var dataArray = event.detail;
    384 
    385   for (var i = 0; i < dataArray.length; ++i) {
    386     var data = dataArray[i];
    387     var originObject = getOriginObject(data.type, data.host, data.origin);
    388     copyAttributes_(data, originObject.detail.payload);
    389     originObject.reveal();
    390     if (getTreeViewObject().selectedItem == originObject)
    391       updateDescription();
    392   }
    393 }
    394 
    395 /**
    396  * Event Handler for |cr.quota.onStatisticsUpdated|.
    397  * |event.detail| contains misc statistics data as dictionary.
    398  * @param {CustomEvent} event StatisticsUpdated event.
    399  */
    400 function handleStatistics(event) {
    401   /**
    402    * @type {Object.<string>}
    403    */
    404   var data = event.detail;
    405   for (var key in data) {
    406     var entry = statistics[key];
    407     if (!entry) {
    408       entry = cr.doc.createElement('tr');
    409       $('stat-entries').appendChild(entry);
    410       statistics[key] = entry;
    411     }
    412     entry.detail = data[key];
    413     entry.innerHTML =
    414         '<td>' + stringToText_(key) + '</td>' +
    415         '<td>' + stringToText_(entry.detail) + '</td>';
    416     localize_(entry);
    417   }
    418 }
    419 
    420 /**
    421  * Update description on 'tree-item-description' field with
    422  * selected item in tree view.
    423  */
    424 function updateDescription() {
    425   var item = getTreeViewObject().selectedItem;
    426   var tbody = $('tree-item-description');
    427   tbody.innerHTML = '';
    428 
    429   if (item) {
    430     var keyAndLabel = [['type', 'Storage Type'],
    431                        ['host', 'Host Name'],
    432                        ['origin', 'Origin URL'],
    433                        ['usage', 'Total Storage Usage', numBytesToText_],
    434                        ['unlimitedUsage', 'Usage of Unlimited Origins',
    435                         numBytesToText_],
    436                        ['quota', 'Quota', numBytesToText_],
    437                        ['inUse', 'Origin is in use?'],
    438                        ['usedCount', 'Used count'],
    439                        ['lastAccessTime', 'Last Access Time',
    440                         dateToText],
    441                        ['lastModifiedTime', 'Last Modified Time',
    442                         dateToText]
    443                       ];
    444     for (var i = 0; i < keyAndLabel.length; ++i) {
    445       var key = keyAndLabel[i][0];
    446       var label = keyAndLabel[i][1];
    447       var entry = item.detail.payload[key];
    448       if (entry === undefined)
    449         continue;
    450 
    451       var normalize = keyAndLabel[i][2] || stringToText_;
    452 
    453       var row = cr.doc.createElement('tr');
    454       row.innerHTML =
    455           '<td>' + label + '</td>' +
    456           '<td>' + normalize(entry) + '</td>';
    457       localize_(row);
    458       tbody.appendChild(row);
    459     }
    460   }
    461 }
    462 
    463 /**
    464  * Dump |treeViewObject| or subtree to a object.
    465  * @param {?{cr.ui.Tree|cr.ui.TreeItem}} opt_treeitem
    466  * @return {Object} Dump result object from |treeViewObject|.
    467  */
    468 function dumpTreeToObj(opt_treeitem) {
    469   var treeitem = opt_treeitem || getTreeViewObject();
    470   var res = {};
    471   res.payload = treeitem.detail.payload;
    472   res.children = [];
    473   for (var i in treeitem.detail.children) {
    474     var child = treeitem.detail.children[i];
    475     res.children.push(dumpTreeToObj(child));
    476   }
    477 
    478   if (isEmptyObject_(res.payload))
    479     delete res.payload;
    480 
    481   if (res.children.length == 0)
    482     delete res.children;
    483   return res;
    484 }
    485 
    486 /**
    487  * Dump |statistics| to a object.
    488  * @return {Object} Dump result object from |statistics|.
    489  */
    490 function dumpStatisticsToObj() {
    491   var result = {};
    492   for (var key in statistics)
    493     result[key] = statistics[key].detail;
    494   return result;
    495 }
    496 
    497 /**
    498  * Event handler for 'dump-button' 'click'ed.
    499  * Dump and show all data from WebUI page to 'dump-field' element.
    500  */
    501 function dump() {
    502   var separator = '========\n';
    503 
    504   $('dump-field').textContent =
    505       separator +
    506       'Summary\n' +
    507       separator +
    508       JSON.stringify({availableSpace: availableSpace}, null, 2) + '\n' +
    509       separator +
    510       'Usage And Quota\n' +
    511       separator +
    512       JSON.stringify(dumpTreeToObj(), null, 2) + '\n' +
    513       separator +
    514       'Misc Statistics\n' +
    515       separator +
    516       JSON.stringify(dumpStatisticsToObj(), null, 2);
    517 }
    518 
    519 function onLoad() {
    520   cr.ui.decorate('tabbox', cr.ui.TabBox);
    521   localize_(document);
    522 
    523   cr.quota.onAvailableSpaceUpdated.addEventListener('update',
    524                                                     handleAvailableSpace);
    525   cr.quota.onGlobalInfoUpdated.addEventListener('update', handleGlobalInfo);
    526   cr.quota.onPerHostInfoUpdated.addEventListener('update', handlePerHostInfo);
    527   cr.quota.onPerOriginInfoUpdated.addEventListener('update',
    528                                                    handlePerOriginInfo);
    529   cr.quota.onStatisticsUpdated.addEventListener('update', handleStatistics);
    530   cr.quota.requestInfo();
    531 
    532   $('refresh-button').addEventListener('click', cr.quota.requestInfo, false);
    533   $('dump-button').addEventListener('click', dump, false);
    534 }
    535 
    536 cr.doc.addEventListener('DOMContentLoaded', onLoad, false);
    537 })();
    538