// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // require cr.js // require cr/event_target.js // require cr/ui.js // require cr/ui/tabs.js // require cr/ui/tree.js // require cr/util.js (function() { 'use strict'; /** * @param {Object} object Object to be checked. * @return {boolean} true if |object| is {}. * @private */ function isEmptyObject_(object) { for (var i in object) return false; return true; } /** * Copy properties from |source| to |destination|. * @param {Object} source Source of the copy. * @param {Object} destination Destination of the copy. * @return {Object} |destination|. * @private */ function copyAttributes_(source, destination) { for (var i in source) destination[i] = source[i]; return destination; }; /** * Apply localization to |element| with i18n_template.js if available. * @param {Element} element Element to be localized. * @private */ function localize_(element) { if (window.i18nTemplate && window.templateData) i18nTemplate.process(element, templateData); }; /** * Returns 'N/A' (Not Available) text if |value| is undefined. * @param {Object} value Object to print. * @return {string} 'N/A' or ''. * @private */ function checkIfAvailable_(value) { return value === undefined ? 'N/A' : ''; } /** * Returns |value| itself if |value| is not undefined, * else returns 'N/A' text. * @param {?string} value String to print. * @return {string} 'N/A' or |value|. * @private */ function stringToText_(value) { return checkIfAvailable_(value) || value; } /** * Separates |value| into segments. * The length of first segment is at most |maxLength|. * Length of other following segments are just |maxLength|. * e.g. separateBackward_('abcdefghijk', 4) == ['abc','defg','hijk']; * @param {string} value String to be separated. * @param {number} maxLength Max length of segments. * @return {Array.} Array of segments. * @private */ function separateBackward_(value, maxLength) { var result = []; while (value.length > maxLength) { result.unshift(value.slice(-3)); value = value.slice(0, -3); } result.unshift(value); return result; } /** * Returns formatted string from number as number of bytes. * e.g. numBytesToText(123456789) = '123.45 MB (123,456,789 B)'. * If |value| is undefined, this function returns 'N/A'. * @param {?number} value Number to print. * @return {string} 'N/A' or formatted |value|. * @private */ function numBytesToText_(value) { var result = checkIfAvailable_(value); if (result) return result; var segments = separateBackward_(value.toString(), 3); result = segments.join(',') + ' B'; if (segments.length > 1) { var UNIT = [' B', ' KB', ' MB', ' GB', ' TB', ' PB']; result = segments[0] + '.' + segments[1].slice(0, 2) + UNIT[Math.min(segments.length, UNIT.length) - 1] + ' (' + result + ')'; } return result; } /** * Return formatted date |value| if |value| is not undefined. * If |value| is undefined, this function returns 'N/A'. * @param {?number} value Number of milliseconds since * UNIX epoch time (0:00, Jan 1, 1970, UTC). * @return {string} Formatted text of date or 'N/A'. * @private */ function dateToText(value) { var result = checkIfAvailable_(value); if (result) return result; var time = new Date(value); var now = new Date(); var delta = Date.now() - value; var SECOND = 1000; var MINUTE = 60 * SECOND; var HOUR = 60 * MINUTE; var DAY = 23 * HOUR; var WEEK = 7 * DAY; var SHOW_SECOND = 5 * MINUTE; var SHOW_MINUTE = 5 * HOUR; var SHOW_HOUR = 3 * DAY; var SHOW_DAY = 2 * WEEK; var SHOW_WEEK = 3 * 30 * DAY; if (delta < 0) { result = 'access from future '; } else if (delta < SHOW_SECOND) { result = Math.ceil(delta / SECOND) + ' sec ago '; } else if (delta < SHOW_MINUTE) { result = Math.ceil(delta / MINUTE) + ' min ago '; } else if (delta < SHOW_HOUR) { result = Math.ceil(delta / HOUR) + ' hr ago '; } else if (delta < SHOW_WEEK) { result = Math.ceil(delta / DAY) + ' day ago '; } result += '(' + time.toString() + ')'; return result; } /** * Available disk space. * @type {number|undefined} */ var availableSpace = undefined; /** * Root of the quota data tree, * holding userdata as |treeViewObject.detail|. * @type {cr.ui.Tree} */ var treeViewObject = undefined; /** * Key-value styled statistics data. * This WebUI does not touch contents, just show. * The value is hold as |statistics[key].detail|. * @type {Object} */ var statistics = {}; /** * Initialize and return |treeViewObject|. * @return {cr.ui.Tree} Initialized |treeViewObject|. */ function getTreeViewObject() { if (!treeViewObject) { treeViewObject = $('tree-view'); cr.ui.decorate(treeViewObject, cr.ui.Tree); treeViewObject.detail = {payload: {}, children: {}}; treeViewObject.addEventListener('change', updateDescription); } return treeViewObject; } /** * Initialize and return a tree item, that represents specified storage type. * @param {!string} type Storage type. * @return {cr.ui.TreeItem} Initialized |storageObject|. */ function getStorageObject(type) { var treeViewObject = getTreeViewObject(); var storageObject = treeViewObject.detail.children[type]; if (!storageObject) { storageObject = new cr.ui.TreeItem({ label: type, detail: {payload: {}, children: {}} }); storageObject.mayHaveChildren_ = true; treeViewObject.detail.children[type] = storageObject; treeViewObject.add(storageObject); } return storageObject; } /** * Initialize and return a tree item, that represents specified * storage type and hostname. * @param {!string} type Storage type. * @param {!string} host Hostname. * @return {cr.ui.TreeItem} Initialized |hostObject|. */ function getHostObject(type, host) { var storageObject = getStorageObject(type); var hostObject = storageObject.detail.children[host]; if (!hostObject) { hostObject = new cr.ui.TreeItem({ label: host, detail: {payload: {}, children: {}} }); hostObject.mayHaveChildren_ = true; storageObject.detail.children[host] = hostObject; storageObject.add(hostObject); } return hostObject; } /** * Initialize and return a tree item, that represents specified * storage type, hostname and origin url. * @param {!string} type Storage type. * @param {!string} host Hostname. * @param {!string} origin Origin URL. * @return {cr.ui.TreeItem} Initialized |originObject|. */ function getOriginObject(type, host, origin) { var hostObject = getHostObject(type, host); var originObject = hostObject.detail.children[origin]; if (!originObject) { originObject = new cr.ui.TreeItem({ label: origin, detail: {payload: {}, children: {}} }); originObject.mayHaveChildren_ = false; hostObject.detail.children[origin] = originObject; hostObject.add(originObject); } return originObject; } /** * Event Handler for |cr.quota.onAvailableSpaceUpdated|. * |event.detail| contains |availableSpace|. * |availableSpace| represents total available disk space. * @param {CustomEvent} event AvailableSpaceUpdated event. */ function handleAvailableSpace(event) { /** * @type {string} */ availableSpace = event.detail; $('diskspace-entry').innerHTML = numBytesToText_(availableSpace); }; /** * Event Handler for |cr.quota.onGlobalInfoUpdated|. * |event.detail| contains a record which has: * |type|: * Storage type, that is either 'temporary' or 'persistent'. * |usage|: * Total storage usage of all hosts. * |unlimitedUsage|: * Total storage usage of unlimited-quota origins. * |quota|: * Total quota of the storage. * * |usage|, |unlimitedUsage| and |quota| can be missing, * and some additional fields can be included. * @param {CustomEvent} event GlobalInfoUpdated event. */ function handleGlobalInfo(event) { /** * @type {{ * type: {!string}, * usage: {?number}, * unlimitedUsage: {?number} * quota: {?string} * }} */ var data = event.detail; var storageObject = getStorageObject(data.type); copyAttributes_(data, storageObject.detail.payload); storageObject.reveal(); if (getTreeViewObject().selectedItem == storageObject) updateDescription(); }; /** * Event Handler for |cr.quota.onPerHostInfoUpdated|. * |event.detail| contains records which have: * |host|: * Hostname of the entry. (e.g. 'example.com') * |type|: * Storage type. 'temporary' or 'persistent' * |usage|: * Total storage usage of the host. * |quota|: * Per-host quota. * * |usage| and |quota| can be missing, * and some additional fields can be included. * @param {CustomEvent} event PerHostInfoUpdated event. */ function handlePerHostInfo(event) { /** * @type {Array<{ * host: {!string}, * type: {!string}, * usage: {?number}, * quota: {?number} * }} */ var dataArray = event.detail; for (var i = 0; i < dataArray.length; ++i) { var data = dataArray[i]; var hostObject = getHostObject(data.type, data.host); copyAttributes_(data, hostObject.detail.payload); hostObject.reveal(); if (getTreeViewObject().selectedItem == hostObject) updateDescription(); } } /** * Event Handler for |cr.quota.onPerOriginInfoUpdated|. * |event.detail| contains records which have: * |origin|: * Origin URL of the entry. * |type|: * Storage type of the entry. 'temporary' or 'persistent'. * |host|: * Hostname of the entry. * |inUse|: * true if the origin is in use. * |usedCount|: * Used count of the storage from the origin. * |lastAccessTime|: * Last storage access time from the origin. * Number of milliseconds since UNIX epoch (Jan 1, 1970, 0:00:00 UTC). * |lastModifiedTime|: * Last modified time of the storage from the origin. * Number of milliseconds since UNIX epoch. * * |inUse|, |usedCount|, |lastAccessTime| and |lastModifiedTime| can be missing, * and some additional fields can be included. * @param {CustomEvent} event PerOriginInfoUpdated event. */ function handlePerOriginInfo(event) { /** * @type {Array<{ * origin: {!string}, * type: {!string}, * host: {!string}, * inUse: {?boolean}, * usedCount: {?number}, * lastAccessTime: {?number} * lastModifiedTime: {?number} * }>} */ var dataArray = event.detail; for (var i = 0; i < dataArray.length; ++i) { var data = dataArray[i]; var originObject = getOriginObject(data.type, data.host, data.origin); copyAttributes_(data, originObject.detail.payload); originObject.reveal(); if (getTreeViewObject().selectedItem == originObject) updateDescription(); } } /** * Event Handler for |cr.quota.onStatisticsUpdated|. * |event.detail| contains misc statistics data as dictionary. * @param {CustomEvent} event StatisticsUpdated event. */ function handleStatistics(event) { /** * @type {Object.} */ var data = event.detail; for (var key in data) { var entry = statistics[key]; if (!entry) { entry = cr.doc.createElement('tr'); $('stat-entries').appendChild(entry); statistics[key] = entry; } entry.detail = data[key]; entry.innerHTML = '' + stringToText_(key) + '' + '' + stringToText_(entry.detail) + ''; localize_(entry); } } /** * Update description on 'tree-item-description' field with * selected item in tree view. */ function updateDescription() { var item = getTreeViewObject().selectedItem; var tbody = $('tree-item-description'); tbody.innerHTML = ''; if (item) { var keyAndLabel = [['type', 'Storage Type'], ['host', 'Host Name'], ['origin', 'Origin URL'], ['usage', 'Total Storage Usage', numBytesToText_], ['unlimitedUsage', 'Usage of Unlimited Origins', numBytesToText_], ['quota', 'Quota', numBytesToText_], ['inUse', 'Origin is in use?'], ['usedCount', 'Used count'], ['lastAccessTime', 'Last Access Time', dateToText], ['lastModifiedTime', 'Last Modified Time', dateToText] ]; for (var i = 0; i < keyAndLabel.length; ++i) { var key = keyAndLabel[i][0]; var label = keyAndLabel[i][1]; var entry = item.detail.payload[key]; if (entry === undefined) continue; var normalize = keyAndLabel[i][2] || stringToText_; var row = cr.doc.createElement('tr'); row.innerHTML = '' + label + '' + '' + normalize(entry) + ''; localize_(row); tbody.appendChild(row); } } } /** * Dump |treeViewObject| or subtree to a object. * @param {?{cr.ui.Tree|cr.ui.TreeItem}} opt_treeitem * @return {Object} Dump result object from |treeViewObject|. */ function dumpTreeToObj(opt_treeitem) { var treeitem = opt_treeitem || getTreeViewObject(); var res = {}; res.payload = treeitem.detail.payload; res.children = []; for (var i in treeitem.detail.children) { var child = treeitem.detail.children[i]; res.children.push(dumpTreeToObj(child)); } if (isEmptyObject_(res.payload)) delete res.payload; if (res.children.length == 0) delete res.children; return res; } /** * Dump |statistics| to a object. * @return {Object} Dump result object from |statistics|. */ function dumpStatisticsToObj() { var result = {}; for (var key in statistics) result[key] = statistics[key].detail; return result; } /** * Event handler for 'dump-button' 'click'ed. * Dump and show all data from WebUI page to 'dump-field' element. */ function dump() { var separator = '========\n'; $('dump-field').textContent = separator + 'Summary\n' + separator + JSON.stringify({availableSpace: availableSpace}, null, 2) + '\n' + separator + 'Usage And Quota\n' + separator + JSON.stringify(dumpTreeToObj(), null, 2) + '\n' + separator + 'Misc Statistics\n' + separator + JSON.stringify(dumpStatisticsToObj(), null, 2); } function onLoad() { cr.ui.decorate('tabbox', cr.ui.TabBox); localize_(document); cr.quota.onAvailableSpaceUpdated.addEventListener('update', handleAvailableSpace); cr.quota.onGlobalInfoUpdated.addEventListener('update', handleGlobalInfo); cr.quota.onPerHostInfoUpdated.addEventListener('update', handlePerHostInfo); cr.quota.onPerOriginInfoUpdated.addEventListener('update', handlePerOriginInfo); cr.quota.onStatisticsUpdated.addEventListener('update', handleStatistics); cr.quota.requestInfo(); $('refresh-button').addEventListener('click', cr.quota.requestInfo, false); $('dump-button').addEventListener('click', dump, false); } cr.doc.addEventListener('DOMContentLoaded', onLoad, false); })();