Files
grungnet/src/workers/DatabaseWorker.js

217 lines
6.0 KiB
JavaScript
Raw Normal View History

import * as Comlink from "comlink";
import {
importInto,
exportDB,
peakImportFile,
} from "@mitchemmc/dexie-export-import";
import { encode, decode } from "@msgpack/msgpack";
import { getDatabase } from "../database";
2021-04-15 15:18:47 +10:00
import blobToBuffer from "../helpers/blobToBuffer";
// Worker to load large amounts of database data on a separate thread
let service = {
/**
* Load either a whole table or individual item from the DB
* @param {string} table Table to load from
* @param {string=} key Optional database key to load, if undefined whole table will be loaded
*/
async loadData(table, key) {
try {
let db = getDatabase({});
if (key) {
// Load specific item
const data = await db.table(table).get(key);
const packed = encode(data);
return Comlink.transfer(packed, [packed.buffer]);
} else {
// Load entire table
let items = [];
// Use a cursor instead of toArray to prevent IPC max size error
await db.table(table).each((item) => {
items.push(item);
});
// Pack data with msgpack so we can use transfer to avoid memory issues
const packed = encode(items);
return Comlink.transfer(packed, [packed.buffer]);
}
} catch {}
},
2021-01-27 11:57:23 +11:00
/**
* Put data into table encoded by msgpack
* @param {Uint8Array} data
* @param {string} table
*/
async putData(data, table) {
try {
let db = getDatabase({});
const decoded = decode(data);
await db.table(table).put(decoded);
return true;
} catch {
return false;
}
},
2021-01-27 11:57:23 +11:00
/**
* Export current database
* @param {function} progressCallback
2021-04-30 14:05:12 +10:00
* @param {string[]} mapIds An array of map ids to export
* @param {string[]} tokenIds An array of token ids to export
2021-01-27 11:57:23 +11:00
*/
2021-04-30 14:05:12 +10:00
async exportData(progressCallback, mapIds, tokenIds) {
let db = getDatabase({});
2021-04-30 14:05:12 +10:00
// Add assets for selected maps and tokens
const maps = await db.table("maps").where("id").anyOf(mapIds).toArray();
const tokens = await db
.table("tokens")
.where("id")
.anyOf(tokenIds)
.toArray();
const assetIds = [];
for (let map of maps) {
if (map.type === "file") {
assetIds.push(map.file);
assetIds.push(map.thumbnail);
for (let res of Object.values(map.resolutions)) {
assetIds.push(res);
}
2021-04-30 14:05:12 +10:00
}
}
for (let token of tokens) {
if (token.type === "file") {
assetIds.push(token.file);
assetIds.push(token.thumbnail);
}
2021-04-30 14:05:12 +10:00
}
const filter = (table, value) => {
if (table === "maps") {
2021-04-30 14:05:12 +10:00
return mapIds.includes(value.id);
}
if (table === "states") {
2021-04-30 14:05:12 +10:00
return mapIds.includes(value.mapId);
}
if (table === "tokens") {
2021-04-30 14:05:12 +10:00
return tokenIds.includes(value.id);
}
if (table === "assets") {
return assetIds.includes(value.id);
}
// Always include groups table
if (table === "groups") {
return true;
}
return false;
};
const data = await exportDB(db, {
progressCallback,
filter,
numRowsPerChunk: 1,
2021-02-14 10:11:52 +11:00
prettyJson: true,
});
2021-04-15 15:18:47 +10:00
const buffer = await blobToBuffer(data);
return Comlink.transfer(buffer, [buffer.buffer]);
2021-01-27 11:57:23 +11:00
},
/**
* Import into current database
* @param {Blob} data
* @param {string} databaseName The name of the database to import into
2021-01-27 11:57:23 +11:00
* @param {function} progressCallback
*/
async importData(data, databaseName, progressCallback) {
const importMeta = await peakImportFile(data);
2021-02-14 19:17:14 +11:00
if (!importMeta.data) {
throw new Error("Uanble to parse file");
}
let db = getDatabase({});
if (importMeta.data.databaseName !== db.name) {
throw new Error("Unable to import database, name mismatch");
}
if (importMeta.data.databaseVersion > db.verno) {
throw new Error(
`Database version differs. Current database is in version ${db.verno} but export is ${importMeta.data.databaseVersion}`
);
}
// Ensure import DB is cleared before importing new data
let importDB = getDatabase({ addons: [] }, databaseName, 0);
await importDB.delete();
importDB.close();
// Load import database up to it's desired version
importDB = getDatabase(
{ addons: [] },
databaseName,
importMeta.data.databaseVersion,
false
);
await importInto(importDB, data, {
progressCallback,
acceptNameDiff: true,
overwriteValues: true,
filter: (table, value) => {
// Ensure values are of the correct form
if (table === "maps" || table === "tokens") {
return "id" in value && "owner" in value;
}
if (table === "states") {
return "mapId" in value;
}
return true;
},
acceptVersionDiff: true,
});
importDB.close();
2021-01-27 11:57:23 +11:00
},
/**
* Ensure the asset cache doesn't go over `maxCacheSize` by removing cached assets
* Removes largest assets first
* @param {number} maxCacheSize Max size of cache in bytes
*/
async cleanAssetCache(maxCacheSize) {
try {
let db = getDatabase({});
const userId = (await db.table("user").get("userId")).value;
const cachedAssets = await db
.table("assets")
.where("owner")
.notEqual(userId)
.toArray();
const totalSize = cachedAssets.reduce(
(acc, cur) => acc + cur.file.byteLength,
0
);
if (totalSize > maxCacheSize) {
// Remove largest assets first
const largestAssets = cachedAssets.sort(
(a, b) => b.file.byteLength - a.file.byteLength
);
let assetsToDelete = [];
let deletedBytes = 0;
for (let asset of largestAssets) {
assetsToDelete.push(asset.id);
deletedBytes += asset.file.byteLength;
if (totalSize - deletedBytes < maxCacheSize) {
break;
}
}
await db.table("assets").bulkDelete(assetsToDelete);
}
} catch {}
},
};
Comlink.expose(service);