Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
QuiteAFancyEmerald
GitHub Repository: QuiteAFancyEmerald/Holy-Unblocker
Path: blob/master/lib/rammerhead/src/classes/RammerheadSessionFileCache.js
5253 views
1
const fs = require('fs');
2
const path = require('path');
3
const RammerheadSessionAbstractStore = require('./RammerheadSessionAbstractStore');
4
const RammerheadSession = require('./RammerheadSession');
5
const RammerheadLogging = require('../classes/RammerheadLogging');
6
7
// rh = rammerhead. extra f to distinguish between rhsession (folder) and rhfsession (file)
8
const sessionFileExtension = '.rhfsession';
9
10
class RammerheadSessionFileCache extends RammerheadSessionAbstractStore {
11
/**
12
*
13
* @param {object} options
14
* @param {string} options.saveDirectory - all cacheTimeouted sessions will be saved in this folder
15
* to avoid storing all the sessions in the memory.
16
* @param {RammerheadLogging|undefined} options.logger
17
* @param {number} options.cacheTimeout - timeout before saving cache to disk and deleting it from the cache
18
* @param {number} options.cacheCheckInterval
19
* @param {boolean} options.deleteUnused - (default: true) if set to true, it deletes unused sessions when saving cache to disk
20
* @param {boolean} options.deleteCorruptedSessions - (default: true) if set to true, auto-deletes session files that
21
* give a parse error (happens when nodejs exits abruptly while serializing session to disk)
22
* @param {object|null} options.staleCleanupOptions - set to null to disable cleaning up stale sessions
23
* @param {number|null} options.staleCleanupOptions.staleTimeout - stale sessions that are inside saveDirectory that go over
24
* this timeout will be deleted. Set to null to disable.
25
* @param {number|null} options.staleCleanupOptions.maxToLive - any created sessions that are older than this will be deleted no matter the usage.
26
* Set to null to disable.
27
* @param {number} options.staleCleanupOptions.staleCheckInterval
28
*/
29
constructor({
30
saveDirectory = path.join(__dirname, '../../sessions'),
31
logger = new RammerheadLogging({ logLevel: 'disabled' }),
32
cacheTimeout = 1000 * 60 * 20, // 20 minutes
33
cacheCheckInterval = 1000 * 60 * 10, // 10 minutes,
34
deleteUnused = true,
35
deleteCorruptedSessions = true,
36
staleCleanupOptions = {
37
staleTimeout: 1000 * 60 * 60 * 24 * 1, // 1 day
38
maxToLive: 1000 * 60 * 60 * 24 * 4, // four days
39
staleCheckInterval: 1000 * 60 * 60 * 1 // 1 hour
40
}
41
} = {}) {
42
super();
43
this.saveDirectory = saveDirectory;
44
this.logger = logger;
45
this.deleteUnused = deleteUnused;
46
this.cacheTimeout = cacheTimeout;
47
this.deleteCorruptedSessions = deleteCorruptedSessions;
48
/**
49
* @type {Map.<string, RammerheadSession>}
50
*/
51
this.cachedSessions = new Map();
52
setInterval(() => this._saveCacheToDisk(), cacheCheckInterval).unref();
53
if (staleCleanupOptions) {
54
this._removeStaleSessions(staleCleanupOptions.staleTimeout, staleCleanupOptions.maxToLive);
55
setInterval(
56
() => this._removeStaleSessions(staleCleanupOptions.staleTimeout, staleCleanupOptions.maxToLive),
57
staleCleanupOptions.staleCheckInterval
58
).unref();
59
}
60
}
61
62
keysStore() {
63
return fs
64
.readdirSync(this.saveDirectory)
65
.filter((file) => file.endsWith(sessionFileExtension))
66
.map((file) => file.slice(0, -sessionFileExtension.length));
67
}
68
/**
69
* @returns {string[]} - list of session ids in store
70
*/
71
keys() {
72
let arr = this.keysStore();
73
for (const id of this.cachedSessions.keys()) {
74
if (!arr.includes(id)) arr.push(id);
75
}
76
return arr;
77
}
78
/**
79
* @param {string} id
80
* @returns {boolean}
81
*/
82
has(id) {
83
return this.cachedSessions.has(id) || fs.existsSync(this._getSessionFilePath(id));
84
}
85
/**
86
* @param {string} id
87
* @param {boolean} updateActiveTimestamp
88
* @returns {RammerheadSession|undefined}
89
*/
90
get(id, updateActiveTimestamp = true, cacheToMemory = true) {
91
if (!this.has(id)) {
92
this.logger.debug(`(FileCache.get) ${id} does not exist`);
93
return;
94
}
95
96
this.logger.debug(`(FileCache.get) ${id}`);
97
if (this.cachedSessions.has(id)) {
98
this.logger.debug(`(FileCache.get) returning memory cached session ${id}`);
99
return this.cachedSessions.get(id);
100
}
101
102
let session;
103
try {
104
session = RammerheadSession.DeserializeSession(id, fs.readFileSync(this._getSessionFilePath(id)));
105
} catch (e) {
106
if (e.name === 'SyntaxError' && e.message.includes('JSON')) {
107
this.logger.warn(`(FileCache.get) ${id} bad JSON`);
108
if (this.deleteCorruptedSessions) {
109
this.delete(id);
110
this.logger.warn(`(FileCache.get) ${id} deleted because of bad JSON`);
111
}
112
return;
113
}
114
}
115
116
if (updateActiveTimestamp) {
117
this.logger.debug(`(FileCache.get) ${id} update active timestamp`);
118
session.updateLastUsed();
119
}
120
121
if (cacheToMemory) {
122
this.cachedSessions.set(id, session);
123
this.logger.debug(`(FileCache.get) saved ${id} into cache memory`);
124
}
125
126
return session;
127
}
128
/**
129
* @param {string} id
130
* @returns {RammerheadSession}
131
*/
132
add(id) {
133
if (this.has(id)) throw new Error(`session ${id} already exists`);
134
135
fs.writeFileSync(this._getSessionFilePath(id), new RammerheadSession().serializeSession());
136
137
this.logger.debug(`FileCache.add ${id}`);
138
139
return this.get(id);
140
}
141
/**
142
* @param {string} id
143
* @returns {boolean} - returns true when a delete operation is performed
144
*/
145
delete(id) {
146
if (id === RammerheadSession.autocompleteId) return false;
147
this.logger.debug(`(FileCache.delete) deleting ${id}`);
148
if (this.has(id)) {
149
fs.unlinkSync(this._getSessionFilePath(id));
150
this.cachedSessions.delete(id);
151
this.logger.debug(`(FileCache.delete) deleted ${id}`);
152
return true;
153
}
154
this.logger.debug(`(FileCache.delete) ${id} does not exist`);
155
return false;
156
}
157
/**
158
* @param {string} id
159
* @param {string} serializedSession
160
*/
161
addSerializedSession(id, serializedSession) {
162
this.logger.debug(`(FileCache.addSerializedSession) adding serialized session id ${id} to store`);
163
const session = RammerheadSession.DeserializeSession(id, serializedSession);
164
fs.writeFileSync(this._getSessionFilePath(id), session.serializeSession());
165
this.logger.debug(`(FileCache.addSerializedSession) added ${id} to cache`);
166
}
167
close() {
168
this.logger.debug(`(FileCache.close) calling _saveCacheToDisk`);
169
this._saveCacheToDisk(true);
170
}
171
172
/**
173
* @private
174
* @param {string} id
175
* @returns {string} - generated file path to session
176
*/
177
_getSessionFilePath(id) {
178
return path.join(this.saveDirectory, id.replace(/\/|\\/g, '') + sessionFileExtension);
179
}
180
/**
181
* @private
182
* @param {number|null} staleTimeout
183
* @param {number|null} maxToLive
184
*/
185
_removeStaleSessions(staleTimeout, maxToLive) {
186
const sessionIds = this.keysStore();
187
let deleteCount = 0;
188
this.logger.debug(`(FileCache._removeStaleSessions) Need to go through ${sessionIds.length} sessions in store`);
189
190
const now = Date.now();
191
for (const id of sessionIds) {
192
const session = this.get(id, false, false);
193
if (!session) {
194
this.logger.debug(`(FileCache._removeStaleSessions) skipping ${id} as .get() returned undefined`);
195
continue;
196
}
197
if (
198
(staleTimeout && now - session.lastUsed > staleTimeout) ||
199
(maxToLive && now - session.createdAt > maxToLive)
200
) {
201
this.delete(id);
202
deleteCount++;
203
this.logger.debug(`(FileCache._removeStaleSessions) deleted ${id}`);
204
}
205
}
206
207
this.logger.debug(`(FileCache._removeStaleSessions) Deleted ${deleteCount} sessions from store`);
208
}
209
/**
210
* @private
211
*/
212
_saveCacheToDisk(forceSave) {
213
let deleteCount = 0;
214
this.logger.debug(`(FileCache._saveCacheToDisk) need to go through ${this.cachedSessions.size} sessions`);
215
216
const now = Date.now();
217
for (const [sessionId, session] of this.cachedSessions) {
218
if (forceSave || now - session.lastUsed > this.cacheTimeout) {
219
if (session.lastUsed === session.createdAt && this.deleteUnused) {
220
this.cachedSessions.delete(sessionId);
221
deleteCount++;
222
this.logger.debug(`(FileCache._saveCacheToDisk) deleted unused ${sessionId} from memory`);
223
} else {
224
fs.writeFileSync(this._getSessionFilePath(sessionId), session.serializeSession());
225
this.cachedSessions.delete(sessionId);
226
deleteCount++;
227
this.logger.debug(
228
`(FileCache._saveCacheToDisk) removed ${sessionId} from memory and saved to store`
229
);
230
}
231
}
232
}
233
234
this.logger.debug(`(FileCache._saveCacheToDisk) Removed ${deleteCount} sessions from memory`);
235
}
236
}
237
238
module.exports = RammerheadSessionFileCache;
239
240