Added ability for import to automatically upgrade older db versions

This commit is contained in:
Mitchell McCaffrey 2021-02-14 13:36:00 +11:00
parent 631d232a09
commit 650084ac84
3 changed files with 136 additions and 91 deletions

View File

@ -1,22 +1,48 @@
import Dexie from "dexie";
// eslint-disable-next-line no-unused-vars
import Dexie, { Version, DexieOptions } from "dexie";
import blobToBuffer from "./helpers/blobToBuffer";
import { getGridDefaultInset } from "./helpers/grid";
import { convertOldActionsToShapes } from "./actions";
import { createThumbnail } from "./helpers/image";
function loadVersions(db) {
// Helper to create a thumbnail for a file in a db
async function createDataThumbnail(data) {
const url = URL.createObjectURL(new Blob([data.file]));
return await Dexie.waitFor(
new Promise((resolve) => {
let image = new Image();
image.onload = async () => {
const thumbnail = await createThumbnail(image);
resolve(thumbnail);
};
image.src = url;
})
);
}
/**
* @callback VersionCallback
* @param {Version} version
*/
/**
* Mapping of version number to their upgrade function
* @type {Object.<number, VersionCallback>}
*/
const versions = {
// v1.2.0
db.version(1).stores({
maps: "id, owner",
states: "mapId",
tokens: "id, owner",
user: "key",
});
1(v) {
v.stores({
maps: "id, owner",
states: "mapId",
tokens: "id, owner",
user: "key",
});
},
// v1.2.1 - Move from blob files to array buffers
db.version(2)
.stores({})
.upgrade(async (tx) => {
2(v) {
v.stores({}).upgrade(async (tx) => {
const maps = await Dexie.waitFor(tx.table("maps").toArray());
let mapBuffers = {};
for (let map of maps) {
@ -29,10 +55,10 @@ function loadVersions(db) {
map.file = mapBuffers[map.id];
});
});
},
// v1.3.0 - Added new default tokens
db.version(3)
.stores({})
.upgrade((tx) => {
3(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("states")
.toCollection()
@ -92,10 +118,10 @@ function loadVersions(db) {
}
});
});
},
// v1.3.1 - Added show grid option
db.version(4)
.stores({})
.upgrade((tx) => {
4(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("maps")
.toCollection()
@ -103,10 +129,10 @@ function loadVersions(db) {
map.showGrid = false;
});
});
},
// v1.4.0 - Added fog subtraction
db.version(5)
.stores({})
.upgrade((tx) => {
5(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("states")
.toCollection()
@ -120,10 +146,10 @@ function loadVersions(db) {
}
});
});
},
// v1.4.2 - Added map resolutions
db.version(6)
.stores({})
.upgrade((tx) => {
6(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("maps")
.toCollection()
@ -132,10 +158,10 @@ function loadVersions(db) {
map.quality = "original";
});
});
},
// v1.5.0 - Fixed default token rogue spelling
db.version(7)
.stores({})
.upgrade((tx) => {
7(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("states")
.toCollection()
@ -147,10 +173,10 @@ function loadVersions(db) {
}
});
});
},
// v1.5.0 - Added map snap to grid option
db.version(8)
.stores({})
.upgrade((tx) => {
8(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("maps")
.toCollection()
@ -158,10 +184,10 @@ function loadVersions(db) {
map.snapToGrid = true;
});
});
},
// v1.5.1 - Added lock, visibility and modified to tokens
db.version(9)
.stores({})
.upgrade((tx) => {
9(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("states")
.toCollection()
@ -175,10 +201,10 @@ function loadVersions(db) {
}
});
});
},
// v1.5.1 - Added token prop category and remove isVehicle bool
db.version(10)
.stores({})
.upgrade((tx) => {
10(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("tokens")
.toCollection()
@ -187,10 +213,10 @@ function loadVersions(db) {
delete token.isVehicle;
});
});
},
// v1.5.2 - Added automatic cache invalidation to maps
db.version(11)
.stores({})
.upgrade((tx) => {
11(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("maps")
.toCollection()
@ -198,10 +224,10 @@ function loadVersions(db) {
map.lastUsed = map.lastModified;
});
});
},
// v1.5.2 - Added automatic cache invalidation to tokens
db.version(12)
.stores({})
.upgrade((tx) => {
12(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("tokens")
.toCollection()
@ -209,10 +235,10 @@ function loadVersions(db) {
token.lastUsed = token.lastModified;
});
});
},
// v1.6.0 - Added map grouping and grid scale and offset
db.version(13)
.stores({})
.upgrade((tx) => {
13(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("maps")
.toCollection()
@ -232,10 +258,10 @@ function loadVersions(db) {
delete map.gridType;
});
});
},
// v1.6.0 - Added token grouping
db.version(14)
.stores({})
.upgrade((tx) => {
14(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("tokens")
.toCollection()
@ -243,10 +269,10 @@ function loadVersions(db) {
token.group = "";
});
});
},
// v1.6.1 - Added width and height to tokens
db.version(15)
.stores({})
.upgrade(async (tx) => {
15(v) {
v.stores({}).upgrade(async (tx) => {
const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
let tokenSizes = {};
for (let token of tokens) {
@ -269,10 +295,10 @@ function loadVersions(db) {
token.height = tokenSizes[token.id].height;
});
});
},
// v1.7.0 - Added note tool
db.version(16)
.stores({})
.upgrade((tx) => {
16(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("states")
.toCollection()
@ -281,11 +307,10 @@ function loadVersions(db) {
state.editFlags = [...state.editFlags, "notes"];
});
});
},
// 1.7.0 (hotfix) - Optimized fog shape edits to only include needed data
db.version(17)
.stores({})
.upgrade((tx) => {
17(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("states")
.toCollection()
@ -305,11 +330,10 @@ function loadVersions(db) {
}
});
});
},
// 1.8.0 - Added note text only mode, converted draw and fog representations
db.version(18)
.stores({})
.upgrade((tx) => {
18(v) {
v.stores({}).upgrade((tx) => {
return tx
.table("states")
.toCollection()
@ -333,25 +357,10 @@ function loadVersions(db) {
delete state.fogDrawActionIndex;
});
});
async function createDataThumbnail(data) {
const url = URL.createObjectURL(new Blob([data.file]));
return await Dexie.waitFor(
new Promise((resolve) => {
let image = new Image();
image.onload = async () => {
const thumbnail = await createThumbnail(image);
resolve(thumbnail);
};
image.src = url;
})
);
}
},
// 1.8.0 - Add thumbnail to maps and add measurement to grid
db.version(19)
.stores({})
.upgrade(async (tx) => {
19(v) {
v.stores({}).upgrade(async (tx) => {
const maps = await Dexie.waitFor(tx.table("maps").toArray());
const thumbnails = {};
for (let map of maps) {
@ -365,11 +374,10 @@ function loadVersions(db) {
map.grid.measurement = { type: "chebyshev", scale: "5ft" };
});
});
},
// 1.8.0 - Add thumbnail to tokens
db.version(20)
.stores({})
.upgrade(async (tx) => {
20(v) {
v.stores({}).upgrade(async (tx) => {
const tokens = await Dexie.waitFor(tx.table("tokens").toArray());
const thumbnails = {};
for (let token of tokens) {
@ -382,11 +390,35 @@ function loadVersions(db) {
token.thumbnail = thumbnails[token.id];
});
});
},
};
const latestVersion = 20;
/**
* Load versions onto a database up to a specific version number
* @param {Dexie} db
* @param {number=} upTo version number to load up to, latest version if undefined
*/
export function loadVersions(db, upTo = latestVersion) {
for (let versionNumber = 1; versionNumber <= upTo; versionNumber++) {
versions[versionNumber](db.version(versionNumber));
}
}
// Get the dexie database used in DatabaseContext
export function getDatabase(options, name="OwlbearRodeoDB") {
/**
* Get a Dexie database with a name and versions applied
* @param {DexieOptions} options
* @param {string=} name
* @param {number=} versionNumber
* @returns {Dexie}
*/
export function getDatabase(
options,
name = "OwlbearRodeoDB",
versionNumber = latestVersion
) {
let db = new Dexie(name, options);
loadVersions(db);
loadVersions(db, versionNumber);
return db;
}

View File

@ -48,14 +48,12 @@ function ImportExportModal({ isOpen, onRequestClose }) {
setIsLoading(true);
backgroundTaskRunningRef.current = true;
try {
// Ensure import DB is cleared before importing new data
let importDB = getDatabase({}, importDBName);
importDB.delete();
await worker.importData(
file,
importDBName,
Comlink.proxy(handleDBProgress)
);
setIsLoading(false);
setShowImportSelector(true);
backgroundTaskRunningRef.current = false;
@ -106,6 +104,7 @@ function ImportExportModal({ isOpen, onRequestClose }) {
async function handleImportSelectorClose() {
const importDB = getDatabase({}, importDBName);
await importDB.delete();
importDB.close();
setShowImportSelector(false);
}
@ -157,6 +156,8 @@ function ImportExportModal({ isOpen, onRequestClose }) {
}
await importDB.delete();
importDB.close();
db.close();
setIsLoading(false);
backgroundTaskRunningRef.current = false;
window.location.reload();

View File

@ -91,8 +91,19 @@ let service = {
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}`
);
}
let importDB = getDatabase({}, databaseName);
// Ensure import DB is cleared before importing new data
let importDB = getDatabase({}, databaseName, 0);
await importDB.delete();
importDB.close();
// Load import database up to it's desired version
importDB = getDatabase({}, databaseName, importMeta.data.databaseVersion);
await importInto(importDB, data, {
progressCallback,
acceptNameDiff: true,
@ -107,9 +118,10 @@ let service = {
}
return true;
},
acceptVersionDiff: true,
});
db.close();
importDB.close();
db.close();
},
};