blob: d946253e5baafbda8d8a29bb96b7ba8a187ea4a4 [file] [log] [blame]
// Copyright (C) 2021 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.
/**
* This file deals with caching traces in the browser's Cache storage. The
* traces are cached so that the UI can gracefully reload a trace when the tab
* containing it is discarded by Chrome(e.g. because the tab was not used for a
* long time) or when the user accidentally hits reload.
*/
import {assertExists} from '../base/logging';
import {TraceArrayBufferSource, TraceSource} from './state';
const TRACE_CACHE_NAME = 'cached_traces';
const TRACE_CACHE_SIZE = 10;
export async function cacheTrace(
traceSource: TraceSource, traceUuid: string): Promise<boolean> {
let trace, title = '', fileName = '', url = '', contentLength = 0,
localOnly = false;
switch (traceSource.type) {
case 'ARRAY_BUFFER':
trace = traceSource.buffer;
title = traceSource.title;
fileName = traceSource.fileName || '';
url = traceSource.url || '';
contentLength = traceSource.buffer.byteLength;
localOnly = traceSource.localOnly || false;
break;
case 'FILE':
trace = await traceSource.file.arrayBuffer();
title = traceSource.file.name;
contentLength = traceSource.file.size;
break;
default:
return false;
}
assertExists(trace);
const headers = new Headers([
['x-trace-title', title],
['x-trace-url', url],
['x-trace-filename', fileName],
['x-trace-local-only', `${localOnly}`],
['content-type', 'application/octet-stream'],
['content-length', `${contentLength}`],
[
'expires',
// Expires in a week from now (now = upload time)
(new Date((new Date()).getTime() + (1000 * 60 * 60 * 24 * 7)))
.toUTCString()
]
]);
const traceCache = await caches.open(TRACE_CACHE_NAME);
await deleteStaleEntries(traceCache);
await traceCache.put(
`/_${TRACE_CACHE_NAME}/${traceUuid}`, new Response(trace, {headers}));
return true;
}
export async function tryGetTrace(traceUuid: string):
Promise<TraceArrayBufferSource|undefined> {
await deleteStaleEntries(await caches.open(TRACE_CACHE_NAME));
const response = await caches.match(
`/_${TRACE_CACHE_NAME}/${traceUuid}`, {cacheName: TRACE_CACHE_NAME});
if (!response) return undefined;
return {
type: 'ARRAY_BUFFER',
buffer: await response.arrayBuffer(),
title: response.headers.get('x-trace-title') || '',
fileName: response.headers.get('x-trace-filename') || undefined,
url: response.headers.get('x-trace-url') || undefined,
uuid: traceUuid,
localOnly: response.headers.get('x-trace-local-only') === 'true'
};
}
async function deleteStaleEntries(traceCache: Cache) {
/*
* Loop through stored caches and invalidate all but the most recent 10.
*/
const keys = await traceCache.keys();
const storedTraces: Array<{key: Request, date: Date}> = [];
for (const key of keys) {
const existingTrace = assertExists(await traceCache.match(key));
const expiryDate =
new Date(assertExists(existingTrace.headers.get('expires')));
if (expiryDate < new Date()) {
await traceCache.delete(key);
} else {
storedTraces.push({key, date: expiryDate});
}
}
if (storedTraces.length <= TRACE_CACHE_SIZE) return;
/*
* Sort the traces descending by time, such that most recent ones are placed
* at the beginning. Then, take traces from TRACE_CACHE_SIZE onwards and
* delete them from cache.
*/
const oldTraces =
storedTraces.sort((a, b) => b.date.getTime() - a.date.getTime())
.slice(TRACE_CACHE_SIZE);
for (const oldTrace of oldTraces) {
await traceCache.delete(oldTrace.key);
}
}