-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathword-counting.js
155 lines (117 loc) · 3.81 KB
/
word-counting.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import fs from "node:fs";
import { Module } from "node:module";
class WordCount {
constructor() {
const a = fs.readdirSync("./db");
//make sure dir exists
if (!a.some((b) => b === "count")) {
//if not, make the folder for it
fs.mkdirSync("./db/count");
}
const roomJSONlist = fs.readdirSync("./db/count");
//map to store everything in
this.perRoom = new Map();
this.writePromise = new Map();
this.writeQueued = new Map();
this.statsString = new Map();
//every stored room
for (const obj of roomJSONlist) {
//read and parse file
let roomFile;
try {
roomFile = fs.readFileSync(`./db/count/${obj}`);
} catch (e) {
console.log(`error reading file ${obj}, it may be a dir\n${e}`);
break;
}
const rm = JSON.parse(roomFile);
// Get room ID from the file name
const roomID = obj.substring(0, obj.length - 5);
// Create a submap for the room
const roomMap = new Map();
this.perRoom.set(roomID, roomMap);
// Iterate through each word and its stats
const wordsSaved = Object.keys(rm);
for (const word of wordsSaved) {
const stats = rm[word];
const users = Object.keys(stats);
// Create a map for each word to store each user's stats
const wordMap = new Map();
roomMap.set(word, wordMap);
// Process each user's stats
for (const user of users) {
const userUsage = stats[user];
// Check if userUsage is a number
if (!Number.isNaN(userUsage)) {
wordMap.set(user, userUsage);
} else {
console.warn(`Invalid data for user '${user}' in word '${word}'.`);
// Handle invalid data differently if needed
}
}
}
}
}
async addToUser(room, word, user, amount) {
if (!this.perRoom.has(room)) this.perRoom.set(room, new Map());
let stats = this.perRoom.get(room).get(word);
if (!stats) {
stats = new Map();
this.perRoom.get(room).set(word, stats);
}
let current = stats.get(user);
//if undefined set it to 0 just to make sure i dont footgun myself
if (!current) current = 0;
//set new value
stats.set(user, current + amount);
this.queueWrite(room);
}
async queueWrite(room) {
//the data is being global, if theres a scheduled write queued already
//the data will be written already so we can just toss any other queued
//for that room
if (this.writeQueued.get(room)) return;
//get the last write to await
const currentWritePromise = this.writePromise.get(room);
if (currentWritePromise) {
//say we already have one right after this
this.writeQueued.set(room, true);
//prevent racey writes
await currentWritePromise;
}
//save the promise to prevent racey writes
this.writePromise.set(room, this.write(room));
//no longer queueing
this.writeQueued.delete(room);
}
async write(room) {
// Regular expression to match any ASCII control character (bytes 0-31)
// biome-ignore lint/suspicious/noControlCharactersInRegex:
const controlCharsPattern = /[\x00-\x1F]/;
//dangerous characters, we simply will not store data about this room
if (
room.includes("\\") ||
room.includes("/") ||
room.includes("\0") ||
controlCharsPattern.test(room)
)
return;
const statsMap = this.perRoom.get(room);
const words = Array.from(statsMap.keys());
const statsObjMap = new Map();
for (const word of words) {
statsObjMap.set(word, Object.fromEntries(statsMap.get(word).entries()));
}
const statsObj = Object.fromEntries(statsObjMap.entries());
const statsString = JSON.stringify(statsObj, null, 2);
const oldStatsString = this.statsString.get(room);
//if its the same as what was already written no need to write
if (oldStatsString === statsString) return;
try {
fs.writeFileSync(`./db/count/${room}.json`, statsString);
} catch (e) {
console.log(`error writing file for ${room}\n${e}`);
}
}
}
export { WordCount };