blob: 53311b5da51772759df6b5affcd79b31540c1bb4 [file] [log] [blame]
/**
* @license
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
// Date cutoff is one day:
const CLEANUP_MAX_AGE = 24 * 60 * 60 * 1000;
// Clean up old entries no more frequently than one day.
const CLEANUP_THROTTLE_INTERVAL = 24 * 60 * 60 * 1000;
const CLEANUP_PREFIXES = [
'draft:',
'editablecontent:',
];
/** @extends Polymer.Element */
class GrStorage extends Polymer.GestureEventListeners(
Polymer.LegacyElementMixin(
Polymer.Element)) {
static get is() { return 'gr-storage'; }
static get properties() {
return {
_lastCleanup: Number,
/** @type {?Storage} */
_storage: {
type: Object,
value() {
return window.localStorage;
},
},
_exceededQuota: {
type: Boolean,
value: false,
},
};
}
getDraftComment(location) {
this._cleanupItems();
return this._getObject(this._getDraftKey(location));
}
setDraftComment(location, message) {
const key = this._getDraftKey(location);
this._setObject(key, {message, updated: Date.now()});
}
eraseDraftComment(location) {
const key = this._getDraftKey(location);
this._storage.removeItem(key);
}
getEditableContentItem(key) {
this._cleanupItems();
return this._getObject(this._getEditableContentKey(key));
}
setEditableContentItem(key, message) {
this._setObject(this._getEditableContentKey(key),
{message, updated: Date.now()});
}
eraseEditableContentItem(key) {
this._storage.removeItem(this._getEditableContentKey(key));
}
_getDraftKey(location) {
const range = location.range ?
`${location.range.start_line}-${location.range.start_character}` +
`-${location.range.end_character}-${location.range.end_line}` :
null;
let key = ['draft', location.changeNum, location.patchNum, location.path,
location.line || ''].join(':');
if (range) {
key = key + ':' + range;
}
return key;
}
_getEditableContentKey(key) {
return `editablecontent:${key}`;
}
_cleanupItems() {
// Throttle cleanup to the throttle interval.
if (this._lastCleanup &&
Date.now() - this._lastCleanup < CLEANUP_THROTTLE_INTERVAL) {
return;
}
this._lastCleanup = Date.now();
let item;
for (const key in this._storage) {
if (!this._storage.hasOwnProperty(key)) { continue; }
for (const prefix of CLEANUP_PREFIXES) {
if (key.startsWith(prefix)) {
item = this._getObject(key);
if (Date.now() - item.updated > CLEANUP_MAX_AGE) {
this._storage.removeItem(key);
}
break;
}
}
}
}
_getObject(key) {
const serial = this._storage.getItem(key);
if (!serial) { return null; }
return JSON.parse(serial);
}
_setObject(key, obj) {
if (this._exceededQuota) { return; }
try {
this._storage.setItem(key, JSON.stringify(obj));
} catch (exc) {
// Catch for QuotaExceededError and disable writes on local storage the
// first time that it occurs.
if (exc.code === 22) {
this._exceededQuota = true;
console.warn('Local storage quota exceeded: disabling');
return;
} else {
throw exc;
}
}
}
}
customElements.define(GrStorage.is, GrStorage);
})();