blob: 8f5c48662b9020fe24a56a95bcc23aa1b5cb7b56 [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.
*/
import '../../../scripts/bundled-polymer.js';
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
const DURATION_DAY = 24 * 60 * 60 * 1000;
// Clean up old entries no more frequently than one day.
const CLEANUP_THROTTLE_INTERVAL = DURATION_DAY;
const CLEANUP_PREFIXES_MAX_AGE_MAP = {
// respectfultip has a 14-day expiration
'respectfultip:': 14 * DURATION_DAY,
'draft:': DURATION_DAY,
'editablecontent:': DURATION_DAY,
};
/** @extends Polymer.Element */
class GrStorage extends GestureEventListeners(
LegacyElementMixin(
PolymerElement)) {
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()});
}
getRespectfulTipVisibility() {
this._cleanupItems();
return this._getObject('respectfultip:visibility');
}
setRespectfulTipVisibility(delayDays = 0) {
this._cleanupItems();
this._setObject(
'respectfultip:visibility',
{updated: Date.now() + delayDays * DURATION_DAY}
);
}
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;
Object.keys(this._storage).forEach(key => {
Object.keys(CLEANUP_PREFIXES_MAX_AGE_MAP).forEach(prefix => {
if (key.startsWith(prefix)) {
item = this._getObject(key);
const expiration = CLEANUP_PREFIXES_MAX_AGE_MAP[prefix];
if (Date.now() - item.updated > expiration) {
this._storage.removeItem(key);
}
}
});
});
}
_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);