mirror of
https://github.com/mollersuite/monofile.git
synced 2024-11-21 21:36:26 -08:00
this is terrible. why did i do this
This commit is contained in:
parent
a160ea7146
commit
bdb57fc878
154
src/server/lib/dbfile.ts
Normal file
154
src/server/lib/dbfile.ts
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
import { readFile, writeFile, rename, readdir } from "fs/promises"
|
||||||
|
import path from "node:path"
|
||||||
|
|
||||||
|
const DATADIR = `./.data`
|
||||||
|
const TICK = 500
|
||||||
|
|
||||||
|
export type Write = ReturnType<typeof writeFile>
|
||||||
|
|
||||||
|
// this is fucking stupid why did i write this
|
||||||
|
|
||||||
|
class Activity {
|
||||||
|
|
||||||
|
_write: () => Promise<any>
|
||||||
|
destroy: () => void
|
||||||
|
|
||||||
|
goal: number = Date.now()
|
||||||
|
lastWrt: number = Date.now()
|
||||||
|
clock? : { type: "precise", id: NodeJS.Timeout } | { type: "tick", id: NodeJS.Timeout, lastGoal: number }
|
||||||
|
|
||||||
|
constructor(writeFunc: () => Promise<any>, destroyFunc: () => void) {
|
||||||
|
this._write = writeFunc
|
||||||
|
this.destroy = destroyFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
write() {
|
||||||
|
this.lastWrt = Date.now();
|
||||||
|
return this._write()
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
|
this.stopClock()
|
||||||
|
this.write()
|
||||||
|
this.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
tick() {
|
||||||
|
if (!this.clock || !("lastGoal" in this.clock)) return
|
||||||
|
if (Date.now() > this.goal) return this.finish();
|
||||||
|
|
||||||
|
if (this.goal == this.clock.lastGoal)
|
||||||
|
this.startPreciseClock()
|
||||||
|
else if (Date.now()-this.lastWrt < 15000)
|
||||||
|
this.write()
|
||||||
|
}
|
||||||
|
|
||||||
|
stopClock() {
|
||||||
|
if (!this.clock) return
|
||||||
|
else clearTimeout(this.clock.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
startTickClock() {
|
||||||
|
this.stopClock()
|
||||||
|
this.clock = {
|
||||||
|
type: "tick",
|
||||||
|
id: setInterval(this.tick.bind(this), TICK),
|
||||||
|
lastGoal: this.goal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startPreciseClock() {
|
||||||
|
this.stopClock()
|
||||||
|
this.clock = {
|
||||||
|
type: "precise",
|
||||||
|
id: setTimeout(this.finish.bind(this), this.goal-Date.now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set() {
|
||||||
|
this.goal = Date.now()+5000
|
||||||
|
if (!this.clock || this.clock.type != "precise")
|
||||||
|
this.startPreciseClock()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DbFile<Structure extends ({}|[])> {
|
||||||
|
|
||||||
|
name: string
|
||||||
|
data: Structure
|
||||||
|
activity?: Activity
|
||||||
|
|
||||||
|
private writeInProgress?: Promise<void>
|
||||||
|
private rewriteNeeded: boolean = false
|
||||||
|
private readonly files: string[]
|
||||||
|
|
||||||
|
constructor(name: string, defaultData: Structure) {
|
||||||
|
this.name = name
|
||||||
|
this.data = defaultData
|
||||||
|
this.files = [`${name}.json`, `${name}-b.json`].map(e => path.join(DATADIR, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async findAvailable() {
|
||||||
|
return (await readdir(DATADIR))
|
||||||
|
.filter(e => e.match(new RegExp(`^${this.name}(?:-b)?.json$`)))
|
||||||
|
.map(e => path.join(DATADIR, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Write files to disk; doesn't care about preventing corruption aside from the 2 copies
|
||||||
|
*/
|
||||||
|
private async write() {
|
||||||
|
|
||||||
|
let data = JSON.stringify(this.data)
|
||||||
|
for (let x in this.files)
|
||||||
|
await writeFile(x, data)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Write files to disk; checks if a write is in progress first
|
||||||
|
*/
|
||||||
|
private async queueWrite(): Promise<void> {
|
||||||
|
if (this.writeInProgress) { // if write in progress
|
||||||
|
this.rewriteNeeded = true // signify that a rewrite is needed
|
||||||
|
return this.writeInProgress
|
||||||
|
}
|
||||||
|
|
||||||
|
this.writeInProgress = this.write()
|
||||||
|
await this.writeInProgress; // wait for it to complete
|
||||||
|
delete this.writeInProgress; // then remove it
|
||||||
|
|
||||||
|
if (this.rewriteNeeded) return this.queueWrite() // queues up another write if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Starts saving data to disk
|
||||||
|
*/
|
||||||
|
async save() {
|
||||||
|
if (!this.activity)
|
||||||
|
this.activity =
|
||||||
|
new Activity(
|
||||||
|
this.queueWrite.bind(this),
|
||||||
|
() => delete this.activity
|
||||||
|
)
|
||||||
|
|
||||||
|
this.activity.set()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async tryRead(path: string) {
|
||||||
|
return JSON.parse(readFile(path).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
async read() {
|
||||||
|
let availFiles = await this.findAvailable()
|
||||||
|
for (let x in availFiles) {
|
||||||
|
let data = await this.tryRead(x).catch(e => null)
|
||||||
|
if (data !== null) {
|
||||||
|
this.data = data
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue