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 * @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 lastAccessTime = 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 += '(' + lastAccessTime.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.onGlobalDataUpdated|. | |
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 GlobalDataUpdated event. | |
290 */ | |
291 function handleGlobalData(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 }; | |
305 | |
306 /** | |
307 * Event Handler for |cr.quota.onHostDataUpdated|. | |
308 * |event.detail| contains records which have: | |
309 * |host|: | |
310 * Hostname of the entry. (e.g. 'example.com') | |
311 * |type|: | |
312 * Storage type. 'temporary' or 'persistent' | |
313 * |usage|: | |
314 * Total storage usage of the host. | |
315 * |quota|: | |
316 * Per-host quota. | |
317 * | |
318 * |usage| and |quota| can be missing, | |
319 * and some additional fields can be included. | |
320 * @param {CustomEvent} event HostDataUpdated event. | |
321 */ | |
322 function handleHostData(event) { | |
323 /** | |
324 * @type {Array<{ | |
325 * host: {!string}, | |
326 * type: {!string}, | |
327 * usage: {?number}, | |
328 * quota: {?number} | |
329 * }} | |
330 */ | |
331 var dataArray = event.detail; | |
332 | |
333 for (var i = 0; i < dataArray.length; ++i) { | |
334 var data = data_array[i]; | |
335 var hostObject = getHostObject(data.type, data.host); | |
336 copyAttributes_(data, hostObject.detail.payload); | |
337 hostObject.reveal(); | |
338 } | |
339 } | |
340 | |
341 /** | |
342 * Event Handler for |cr.quota.onOriginDataUpdated|. | |
343 * |event.detail| contains records which have: | |
344 * |origin|: | |
345 * Origin URL of the entry. | |
346 * |type|: | |
347 * Storage type of the entry. 'temporary' or 'persistent'. | |
348 * |host|: | |
349 * Hostname of the entry. | |
350 * |inUse|: | |
351 * true if the origin is in use. | |
352 * |usedCount|: | |
353 * Used count of the storage from the origin. | |
354 * |lastAccessTime|: | |
355 * Last storage access time from the origin. | |
356 * Number of milliseconds since UNIX epoch (Jan 1, 1970, 0:00:00 UTC). | |
357 * | |
358 * |inUse|, |usedCount| and |lastAccessTime| can be missing, | |
359 * and some additional fields can be included. | |
360 * @param {CustomEvent} event OriginDataUpdated event. | |
361 */ | |
362 function handleOriginData(event) { | |
363 /** | |
364 * @type {Array<{ | |
365 * origin: {!string}, | |
366 * type: {!string}, | |
367 * host: {!string}, | |
368 * inUse: {?boolean}, | |
369 * usedCount: {?number}, | |
370 * lastAccessTime: {?number} | |
371 * }>} | |
372 */ | |
373 var dataArray = event.detail; | |
374 | |
375 for (var i = 0; i < dataArray.length; ++i) { | |
376 var data = dataArray[i]; | |
377 var originObject = getOriginObject(data.type, data.host, data.origin); | |
378 copyAttributes_(data, originObject.detail.payload); | |
379 originObject.reveal(); | |
380 } | |
381 } | |
382 | |
383 /** | |
384 * Event Handler for |cr.quota.onStatisticsUpdated|. | |
385 * |event.detail| contains misc statistics data as dictionary. | |
386 * @param {CustomEvent} event StatisticsUpdated event. | |
387 */ | |
388 function handleStatistics(event) { | |
389 /** | |
390 * @type {Object.<string>} | |
391 */ | |
392 var data = event.detail; | |
393 for (var key in data) { | |
394 var entry = statistics[key]; | |
395 if (!entry) { | |
396 entry = cr.doc.createElement('tr'); | |
397 $('stat-entries').appendChild(entry); | |
398 statistics[key] = entry; | |
399 } | |
400 entry.detail = data[key]; | |
401 entry.innerHTML = | |
402 '<td>' + stringToText_(key) + '</td>' + | |
403 '<td>' + stringToText_(entry.detail) + '</td>'; | |
404 localize_(entry); | |
405 } | |
406 } | |
407 | |
408 /** | |
409 * Update description on 'tree-item-description' field with | |
410 * selected item in tree view. | |
411 */ | |
412 function updateDescription() { | |
413 var item = getTreeViewObject().selectedItem; | |
414 var tbody = $('tree-item-description'); | |
415 tbody.innerHTML = ''; | |
416 | |
417 if (item) { | |
418 var keyAndLabel = [['type', 'Storage Type'], | |
419 ['host', 'Host Name'], | |
420 ['origin', 'Origin URL'], | |
421 ['usage', 'Total Storage Usage', numBytesToText_], | |
422 ['unlimitedUsage', 'Usage of Unlimited Origins', | |
423 numBytesToText_], | |
424 ['quota', 'Quota', numBytesToText_], | |
425 ['inUse', 'Origin is in use?'], | |
426 ['usedCount', 'Used count'], | |
427 ['lastAccessTime', 'Last Access Time', | |
428 dateToText] | |
429 ]; | |
430 for (var i = 0; i < keyAndLabel.length; ++i) { | |
431 var key = keyAndLabel[i][0]; | |
432 var label = keyAndLabel[i][1]; | |
433 var entry = item.detail.payload[key]; | |
434 if (entry === undefined) | |
435 continue; | |
436 | |
437 var normalize = keyAndLabel[i][2] || stringToText_; | |
438 | |
439 var row = cr.doc.createElement('tr'); | |
440 row.innerHTML = | |
441 '<td>' + label + '</td>' + | |
442 '<td>' + normalize(entry) + '</td>'; | |
443 localize_(row); | |
444 tbody.appendChild(row); | |
445 } | |
446 } | |
447 } | |
448 | |
449 /** | |
450 * Dump |treeViewObject| or subtree to a object. | |
451 * @param {?{cr.ui.Tree|cr.ui.TreeItem}} opt_treeitem | |
452 * @return {Object} Dump result object from |treeViewObject|. | |
453 */ | |
454 function dumpTreeToObj(opt_treeitem) { | |
455 var treeitem = opt_treeitem || getTreeViewObject(); | |
456 var res = {}; | |
457 res.payload = treeitem.detail.payload; | |
458 res.children = []; | |
459 for (var i in treeitem.detail.children) { | |
460 var child = treeitem.detail.children[i]; | |
461 res.children.push(dumpTreeToObj(child)); | |
462 } | |
463 | |
464 if (isEmptyObject_(res.payload)) | |
465 delete res.payload; | |
466 | |
467 if (res.children.length == 0) | |
468 delete res.children; | |
469 return res; | |
470 } | |
471 | |
472 /** | |
473 * Dump |statistics| to a object. | |
474 * @return {Object} Dump result object from |statistics|. | |
475 */ | |
476 function dumpStatisticsToObj() { | |
477 var result = {}; | |
478 for (var key in statistics) | |
479 result[key] = statistics[key].detail; | |
480 return result; | |
481 } | |
482 | |
483 /** | |
484 * Event handler for 'dump-button' 'click'ed. | |
485 * Dump and show all data from WebUI page to 'dump-field' element. | |
486 */ | |
487 function dump() { | |
488 var separator = '========\n'; | |
489 | |
490 $('dump-field').textContent = | |
491 separator + | |
492 'Summary\n' + | |
493 separator + | |
494 JSON.stringify({availableSpace: availableSpace}, null, 2) + '\n' + | |
495 separator + | |
496 'Usage And Quota\n' + | |
497 separator + | |
498 JSON.stringify(dumpTreeToObj(), null, 2) + '\n' + | |
499 separator + | |
500 'Misc Statistics\n' + | |
501 separator + | |
502 JSON.stringify(dumpStatisticsToObj(), null, 2); | |
503 } | |
504 | |
505 function onLoad() { | |
506 cr.ui.decorate('tabbox', cr.ui.TabBox); | |
507 localize_(document); | |
508 | |
509 cr.quota.onAvailableSpaceUpdated.addEventListener('update', | |
510 handleAvailableSpace); | |
511 cr.quota.onGlobalDataUpdated.addEventListener('update', handleGlobalData); | |
512 cr.quota.onHostDataUpdated.addEventListener('update', handleHostData); | |
513 cr.quota.onOriginDataUpdated.addEventListener('update', handleOriginData); | |
514 cr.quota.onStatisticsUpdated.addEventListener('update', handleStatistics); | |
515 cr.quota.requestData(); | |
516 | |
517 $('refresh-button').addEventListener('click', cr.quota.requestData, false); | |
518 $('dump-button').addEventListener('click', dump, false); | |
519 } | |
520 | |
521 cr.doc.addEventListener('DOMContentLoaded', onLoad, false); | |
522 })(); | |
OLD | NEW |