2017-07-06 15:33:09 +00:00
|
|
|
"use strict";
|
|
|
|
|
2018-06-15 20:31:06 +00:00
|
|
|
const log = require("../log");
|
2017-07-06 15:33:09 +00:00
|
|
|
const fs = require("fs");
|
|
|
|
const path = require("path");
|
|
|
|
const crypto = require("crypto");
|
2022-05-01 19:12:39 +00:00
|
|
|
const Config = require("../config");
|
2017-07-06 15:33:09 +00:00
|
|
|
|
|
|
|
class Storage {
|
|
|
|
constructor() {
|
|
|
|
this.references = new Map();
|
2017-12-18 14:58:43 +00:00
|
|
|
}
|
2017-07-06 15:33:09 +00:00
|
|
|
|
2017-12-18 14:58:43 +00:00
|
|
|
emptyDir() {
|
2017-07-06 15:33:09 +00:00
|
|
|
// Ensures that a directory is empty.
|
|
|
|
// Deletes directory contents if the directory is not empty.
|
|
|
|
// If the directory does not exist, it is created.
|
2020-03-16 11:53:48 +00:00
|
|
|
|
2022-05-01 19:12:39 +00:00
|
|
|
const dir = Config.getStoragePath();
|
2020-03-16 11:53:48 +00:00
|
|
|
let items;
|
|
|
|
|
|
|
|
try {
|
|
|
|
items = fs.readdirSync(dir);
|
|
|
|
} catch (e) {
|
|
|
|
fs.mkdirSync(dir, {recursive: true});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Use `fs.rmdirSync(dir, {recursive: true});` when it's stable (node 13+)
|
|
|
|
items.forEach((item) => deleteFolder(path.join(dir, item)));
|
2017-07-06 15:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dereference(url) {
|
|
|
|
const references = (this.references.get(url) || 0) - 1;
|
|
|
|
|
|
|
|
if (references < 0) {
|
|
|
|
return log.warn("Tried to dereference a file that has no references", url);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (references > 0) {
|
|
|
|
return this.references.set(url, references);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.references.delete(url);
|
|
|
|
|
|
|
|
// Drop "storage/" from url and join it with full storage path
|
2022-05-01 19:12:39 +00:00
|
|
|
const filePath = path.join(Config.getStoragePath(), url.substring(8));
|
2017-07-06 15:33:09 +00:00
|
|
|
|
|
|
|
fs.unlink(filePath, (err) => {
|
|
|
|
if (err) {
|
|
|
|
log.error("Failed to delete stored file", err);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
store(data, extension, callback) {
|
2020-03-21 20:55:36 +00:00
|
|
|
const hash = crypto.createHash("sha256").update(data).digest("hex");
|
2017-07-06 15:33:09 +00:00
|
|
|
const a = hash.substring(0, 2);
|
|
|
|
const b = hash.substring(2, 4);
|
2022-05-01 19:12:39 +00:00
|
|
|
const folder = path.join(Config.getStoragePath(), a, b);
|
2017-07-06 15:33:09 +00:00
|
|
|
const filePath = path.join(folder, `${hash.substring(4)}.${extension}`);
|
|
|
|
const url = `storage/${a}/${b}/${hash.substring(4)}.${extension}`;
|
|
|
|
|
|
|
|
this.references.set(url, 1 + (this.references.get(url) || 0));
|
|
|
|
|
|
|
|
// If file with this name already exists, we don't need to write it again
|
|
|
|
if (fs.existsSync(filePath)) {
|
|
|
|
return callback(url);
|
|
|
|
}
|
|
|
|
|
2020-03-16 11:53:48 +00:00
|
|
|
fs.mkdir(folder, {recursive: true}, (mkdirErr) => {
|
|
|
|
if (mkdirErr) {
|
|
|
|
log.error("Failed to create storage folder", mkdirErr);
|
2017-07-06 15:33:09 +00:00
|
|
|
|
2020-03-16 11:53:48 +00:00
|
|
|
return callback("");
|
|
|
|
}
|
2017-07-06 15:33:09 +00:00
|
|
|
|
2020-03-16 11:53:48 +00:00
|
|
|
fs.writeFile(filePath, data, (err) => {
|
|
|
|
if (err) {
|
|
|
|
log.error("Failed to store a file", err);
|
2017-07-06 15:33:09 +00:00
|
|
|
|
2020-03-16 11:53:48 +00:00
|
|
|
return callback("");
|
|
|
|
}
|
|
|
|
|
|
|
|
callback(url);
|
2019-07-17 09:33:59 +00:00
|
|
|
});
|
2020-03-16 11:53:48 +00:00
|
|
|
});
|
2017-07-06 15:33:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = new Storage();
|
2020-03-16 11:53:48 +00:00
|
|
|
|
|
|
|
function deleteFolder(dir) {
|
|
|
|
fs.readdirSync(dir).forEach((item) => {
|
|
|
|
item = path.join(dir, item);
|
|
|
|
|
|
|
|
if (fs.lstatSync(item).isDirectory()) {
|
|
|
|
deleteFolder(item);
|
|
|
|
} else {
|
|
|
|
fs.unlinkSync(item);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
fs.rmdirSync(dir);
|
|
|
|
}
|