diff options
Diffstat (limited to 'src/Prisbeam.ts')
-rw-r--r-- | src/Prisbeam.ts | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/src/Prisbeam.ts b/src/Prisbeam.ts new file mode 100644 index 0000000..c380b6f --- /dev/null +++ b/src/Prisbeam.ts @@ -0,0 +1,150 @@ +import {PrisbeamFrontend} from "./PrisbeamFrontend"; +import {PrisbeamOptions} from "./PrisbeamOptions"; +import fs from "fs"; +import path from "path"; +import {PrisbeamUpdater, VERSION} from "../index"; +import {PrisbeamPropertyStore} from "./PrisbeamPropertyStore"; + +export class Prisbeam { + public version: string = VERSION; + public readonly verbose: boolean; + public readonly path: string; + readonly sensitiveImageProtocol: boolean; + public frontend: PrisbeamFrontend; + readonly cache?: string; + private sqlite: any; + private database: any; + private readonly readOnly: boolean; + public propertyStore: PrisbeamPropertyStore; + + constructor(options: PrisbeamOptions) { + this.verbose = options.verbose ?? true; + this.sqlite = require(options.sqlitePath ?? "sqlite3").verbose(); + + if (!fs.existsSync(path.resolve(options.database))) throw new Error("Invalid database folder specified: " + path.resolve(options.database)); + if (!fs.existsSync(path.resolve(options.database) + "/instance.pbmk")) throw new Error("Not a valid Prisbeam database: " + path.resolve(options.database)); + + this.path = path.resolve(options.database); + + if (options.cachePath) { + if (!fs.existsSync(path.resolve(options.cachePath))) throw new Error("Invalid cache folder specified: " + path.resolve(options.cachePath)); + this.cache = path.resolve(options.cachePath); + } + + this.readOnly = options.readOnly; + this.sensitiveImageProtocol = options.sensitiveImageProtocol ?? false; + } + + async clean() { + if (this.readOnly) throw new Error("The database is open is read-only mode."); + await this._sql("DROP TABLE IF EXISTS image_tags"); + await this._sql("DROP TABLE IF EXISTS image_intensities"); + await this._sql("DROP TABLE IF EXISTS image_representations"); + await this._sql("DROP TABLE IF EXISTS image_categories"); + await this._sql("DROP TABLE IF EXISTS images"); + await this._sql("DROP TABLE IF EXISTS uploaders"); + await this._sql("DROP TABLE IF EXISTS tags"); + await this._sql("DROP TABLE IF EXISTS tags_pre"); + await this._sql("DROP TABLE IF EXISTS compressed"); + await this._sql("CREATE TABLE images (id INT NOT NULL UNIQUE, source_id INT NOT NULL UNIQUE, source_name TEXT, source TEXT NOT NULL, animated BOOL, aspect_ratio FLOAT, comment_count INT, created_at TIMESTAMP, deletion_reason LONGTEXT, description LONGTEXT, downvotes INT, duplicate_of INT, duration FLOAT, faves INT, first_seen_at TIMESTAMP, format TEXT, height INT, hidden_from_users BOOL, mime_type TEXT, name LONGTEXT, orig_sha512_hash TEXT, processed BOOL, score INT, sha512_hash TEXT, size INT, source_url LONGTEXT, spoilered BOOL, tag_count INT, thumbnails_generated BOOL, updated_at TIMESTAMP, uploader INT, upvotes INT, width INT, wilson_score FLOAT, PRIMARY KEY (id), FOREIGN KEY (uploader) REFERENCES uploaders(id))"); + await this._sql("CREATE TABLE image_tags (image_id INT NOT NULL UNIQUE, tags LONGTEXT NOT NULL, PRIMARY KEY (image_id), FOREIGN KEY (image_id) REFERENCES images(id))"); + await this._sql("CREATE TABLE image_intensities (image_id INT NOT NULL UNIQUE, ne FLOAT NOT NULL, nw FLOAT NOT NULL, se FLOAT NOT NULL, sw FLOAT NOT NULL, PRIMARY KEY (image_id), FOREIGN KEY (image_id) REFERENCES images(id))"); + await this._sql("CREATE TABLE image_representations (image_id INT NOT NULL UNIQUE, view LONGTEXT NOT NULL, full TEXT, large TEXT, medium TEXT, small TEXT, tall TEXT, thumb TEXT, thumb_small TEXT, thumb_tiny TEXT, PRIMARY KEY (image_id), FOREIGN KEY (image_id) REFERENCES images(id))"); + await this._sql("CREATE TABLE tags (id INT NOT NULL UNIQUE, name TEXT NOT NULL UNIQUE, alias INT, implications LONGTEXT, category TEXT, description LONGTEXT, description_short LONGTEXT, slug TEXT UNIQUE, PRIMARY KEY (id))"); + await this._sql("CREATE TABLE uploaders (id INT NOT NULL UNIQUE, name TEXT, PRIMARY KEY (id))"); + await this._sql("CREATE TABLE IF NOT EXISTS metadata (key TEXT NOT NULL UNIQUE, value LONGTEXT NOT NULL, PRIMARY KEY (key))"); + } + + _sql(query: string) { + let verbose = this.verbose; + if (verbose) console.debug("=>", query); + + return new Promise<any>((res, rej) => { + this.database.all(query, function (err: Error | null, data?: any) { + if (err) { + if (verbose) console.debug("<=", data); + rej(err); + } else { + if (verbose) console.debug("<=", data); + res(data); + } + }); + }); + } + + async initialize(restoreBackup: boolean) { + if (restoreBackup) { + let backups = (await fs.promises.readdir(this.path)).filter(i => i.endsWith(".pbdb") && i !== "current.pbdb"); + + if (backups.length > 0 && !isNaN(parseInt(backups[0].split(".")[0]))) { + await fs.promises.copyFile(this.path + "/" + backups[0], this.path + "/current.pbdb"); + await fs.promises.unlink(this.path + "/" + backups[0]); + } + } + + if (this.cache) { + await fs.promises.copyFile(this.path + "/current.pbdb", this.cache + "/work.pbdb"); + + if (this.readOnly) { + this.database = new this.sqlite.Database(this.cache + "/work.pbdb", this.sqlite.OPEN_READONLY); + } else { + this.database = new this.sqlite.Database(this.cache + "/work.pbdb"); + } + } else { + if (this.readOnly) { + this.database = new this.sqlite.Database(this.path + "/current.pbdb", this.sqlite.OPEN_READONLY); + } else { + this.database = new this.sqlite.Database(this.path + "/current.pbdb"); + } + } + + await new Promise<void>((res) => { + this.database.serialize(() => { + res(); + }); + }); + + if (!this.readOnly) { + if ((await this._sql("SELECT COUNT(*) FROM metadata WHERE key='libprisbeam_timestamp'"))[0]["COUNT(*)"] === 0) { + await this._sql('INSERT INTO metadata(key, value) VALUES ("libprisbeam_timestamp", "' + new Date().toISOString() + '")'); + } else { + await this._sql('UPDATE metadata SET value="' + new Date().toISOString() + '" WHERE key="libprisbeam_timestamp"'); + } + } + + await this._sql("CREATE TABLE IF NOT EXISTS metadata (key TEXT NOT NULL UNIQUE, value LONGTEXT NOT NULL, PRIMARY KEY (key))"); + + this.frontend = new PrisbeamFrontend(this); + this.propertyStore = new PrisbeamPropertyStore(this); + this.propertyStore.initialize(); + + this.frontend.initialize(); + await this.defragment(); + } + + async defragment() { + await this._sql("VACUUM"); + } + + async close() { + await new Promise<void>((res) => { + this.database.wait(() => { + res(); + }); + }); + + this.database.close(); + + if (this.cache) { + await fs.promises.copyFile(this.cache + "/work.pbdb", this.path + "/current.pbdb"); + } + } + + /** + * @deprecated Use PrisbeamUpdater instead + */ + async updateFromPreprocessed(preprocessed: string, tags: string, statusUpdateHandler: Function) { + let updater = new PrisbeamUpdater(this); + await updater.updateFromPreprocessed(preprocessed, tags, statusUpdateHandler); + } +} |