176 lines
4.4 KiB
JavaScript
176 lines
4.4 KiB
JavaScript
/* eslint-disable max-classes-per-file, no-underscore-dangle */
|
||
|
||
const fs = require('fs');
|
||
const log = require('./log');
|
||
|
||
const ESSENTIAL_ENTRY_POINTS = [
|
||
// Login page
|
||
'pages.sessions.new',
|
||
// Explore page
|
||
'pages.root',
|
||
];
|
||
|
||
// TODO: Find a way to keep this list up-to-date/relevant.
|
||
const COMMON_ENTRY_POINTS = [
|
||
...ESSENTIAL_ENTRY_POINTS,
|
||
'pages.admin',
|
||
'pages.admin.dashboard',
|
||
'pages.dashboard.groups.index',
|
||
'pages.dashboard.projects.index',
|
||
'pages.groups.new',
|
||
'pages.groups.show',
|
||
'pages.profiles.preferences.show',
|
||
'pages.projects.commit.show',
|
||
'pages.projects.edit',
|
||
'pages.projects.issues.index',
|
||
'pages.projects.issues.new',
|
||
'pages.projects.issues.show',
|
||
'pages.projects.jobs.show',
|
||
'pages.projects.merge_requests.index',
|
||
'pages.projects.merge_requests.show',
|
||
'pages.projects.milestones.index',
|
||
'pages.projects.new',
|
||
'pages.projects.pipelines.index',
|
||
'pages.projects.pipelines.show',
|
||
'pages.projects.settings.ci_cd.show',
|
||
'pages.projects.settings.repository.show',
|
||
'pages.projects.show',
|
||
'pages.users',
|
||
];
|
||
|
||
/**
|
||
* The History class is responsible for tracking which entry points have been
|
||
* requested, and persisting/loading the history to/from disk.
|
||
*/
|
||
class History {
|
||
constructor(historyFilePath) {
|
||
this._historyFilePath = historyFilePath;
|
||
this._history = {};
|
||
|
||
this._loadHistoryFile();
|
||
}
|
||
|
||
onRequestEntryPoint(entryPoint) {
|
||
const wasVisitedRecently = this.isRecentlyVisited(entryPoint);
|
||
|
||
this._addEntryPoint(entryPoint);
|
||
|
||
this._writeHistoryFile();
|
||
|
||
return wasVisitedRecently;
|
||
}
|
||
|
||
// eslint-disable-next-line class-methods-use-this
|
||
isRecentlyVisited() {
|
||
return true;
|
||
}
|
||
|
||
// eslint-disable-next-line class-methods-use-this
|
||
get size() {
|
||
return 0;
|
||
}
|
||
|
||
// Private methods
|
||
|
||
_addEntryPoint(entryPoint) {
|
||
if (!this._history[entryPoint]) {
|
||
this._history[entryPoint] = { lastVisit: null, count: 0 };
|
||
}
|
||
|
||
this._history[entryPoint].lastVisit = Date.now();
|
||
this._history[entryPoint].count += 1;
|
||
}
|
||
|
||
_writeHistoryFile() {
|
||
try {
|
||
fs.writeFileSync(this._historyFilePath, JSON.stringify(this._history), 'utf8');
|
||
} catch (error) {
|
||
log('Warning – Could not write to history', error.message);
|
||
}
|
||
}
|
||
|
||
_loadHistoryFile() {
|
||
try {
|
||
fs.accessSync(this._historyFilePath);
|
||
} catch (e) {
|
||
// History file doesn't exist; attempt to seed it, and return early
|
||
this._seedHistory();
|
||
return;
|
||
}
|
||
|
||
// History file already exists; attempt to load its contents into memory
|
||
try {
|
||
this._history = JSON.parse(fs.readFileSync(this._historyFilePath, 'utf8'));
|
||
const historySize = Object.keys(this._history).length;
|
||
log(`Successfully loaded history containing ${historySize} entry points`);
|
||
} catch (error) {
|
||
log('Could not load history', error.message);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Seeds a reasonable set of approximately the most common entry points to
|
||
* seed the history. This helps to avoid fresh GDK installs showing the
|
||
* compiling overlay too often.
|
||
*/
|
||
_seedHistory() {
|
||
log('Seeding history...');
|
||
COMMON_ENTRY_POINTS.forEach((entryPoint) => this._addEntryPoint(entryPoint));
|
||
this._writeHistoryFile();
|
||
}
|
||
}
|
||
|
||
const MS_PER_DAY = 1000 * 60 * 60 * 24;
|
||
|
||
/**
|
||
* The HistoryWithTTL class adds LRU-like behaviour onto the base History
|
||
* behaviour. Entry points visited within the last `ttl` days are considered
|
||
* "recent", and therefore should be eagerly compiled.
|
||
*/
|
||
class HistoryWithTTL extends History {
|
||
constructor(historyFilePath, ttl) {
|
||
super(historyFilePath);
|
||
this._ttl = ttl;
|
||
this._calculateRecentEntryPoints();
|
||
}
|
||
|
||
onRequestEntryPoint(entryPoint) {
|
||
const wasVisitedRecently = super.onRequestEntryPoint(entryPoint);
|
||
|
||
this._calculateRecentEntryPoints();
|
||
|
||
return wasVisitedRecently;
|
||
}
|
||
|
||
isRecentlyVisited(entryPoint) {
|
||
return this._recentEntryPoints.has(entryPoint);
|
||
}
|
||
|
||
get size() {
|
||
return this._recentEntryPoints.size;
|
||
}
|
||
|
||
// Private methods
|
||
|
||
_calculateRecentEntryPoints() {
|
||
const oldestVisitAllowed = Date.now() - MS_PER_DAY * this._ttl;
|
||
|
||
const recentEntryPoints = Object.entries(this._history).reduce(
|
||
(acc, [entryPoint, { lastVisit }]) => {
|
||
if (lastVisit > oldestVisitAllowed) {
|
||
acc.push(entryPoint);
|
||
}
|
||
|
||
return acc;
|
||
},
|
||
[],
|
||
);
|
||
|
||
this._recentEntryPoints = new Set([...ESSENTIAL_ENTRY_POINTS, ...recentEntryPoints]);
|
||
}
|
||
}
|
||
|
||
module.exports = {
|
||
History,
|
||
HistoryWithTTL,
|
||
};
|