Remove cache functions from map and tokens and add to assets
This commit is contained in:
parent
fa94275ac8
commit
a91d559ba6
@ -19,7 +19,7 @@ const listTokenClassName = "list-token";
|
|||||||
|
|
||||||
function Tokens({ onMapTokenStateCreate }) {
|
function Tokens({ onMapTokenStateCreate }) {
|
||||||
const { userId } = useAuth();
|
const { userId } = useAuth();
|
||||||
const { ownedTokens, tokens, updateToken } = useTokenData();
|
const { ownedTokens, tokens } = useTokenData();
|
||||||
const [fullScreen] = useSetting("map.fullScreen");
|
const [fullScreen] = useSetting("map.fullScreen");
|
||||||
|
|
||||||
function handleProxyDragEnd(isOnMap, token) {
|
function handleProxyDragEnd(isOnMap, token) {
|
||||||
@ -51,13 +51,6 @@ function Tokens({ onMapTokenStateCreate }) {
|
|||||||
tokenState.key = token.key;
|
tokenState.key = token.key;
|
||||||
}
|
}
|
||||||
onMapTokenStateCreate(tokenState);
|
onMapTokenStateCreate(tokenState);
|
||||||
// TODO: Remove when cache is moved to assets
|
|
||||||
// Update last used for cache invalidation
|
|
||||||
// Keep last modified the same
|
|
||||||
updateToken(token.id, {
|
|
||||||
lastUsed: Date.now(),
|
|
||||||
lastModified: token.lastModified,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,9 +44,16 @@ import { omit } from "../helpers/shared";
|
|||||||
*/
|
*/
|
||||||
const AssetsContext = React.createContext();
|
const AssetsContext = React.createContext();
|
||||||
|
|
||||||
|
// 100 MB max cache size
|
||||||
|
const maxCacheSize = 1e8;
|
||||||
|
|
||||||
export function AssetsProvider({ children }) {
|
export function AssetsProvider({ children }) {
|
||||||
const { worker, database } = useDatabase();
|
const { worker, database } = useDatabase();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
worker.cleanAssetCache(maxCacheSize);
|
||||||
|
}, [worker]);
|
||||||
|
|
||||||
const getAsset = useCallback(
|
const getAsset = useCallback(
|
||||||
async (assetId) => {
|
async (assetId) => {
|
||||||
return await database.table("assets").get(assetId);
|
return await database.table("assets").get(assetId);
|
||||||
@ -56,7 +63,7 @@ export function AssetsProvider({ children }) {
|
|||||||
|
|
||||||
const addAssets = useCallback(
|
const addAssets = useCallback(
|
||||||
async (assets) => {
|
async (assets) => {
|
||||||
return database.table("assets").bulkAdd(assets);
|
await database.table("assets").bulkAdd(assets);
|
||||||
},
|
},
|
||||||
[database]
|
[database]
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
import React, {
|
import React, { useEffect, useState, useContext, useCallback } from "react";
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
useContext,
|
|
||||||
useCallback,
|
|
||||||
useRef,
|
|
||||||
} from "react";
|
|
||||||
import { decode } from "@msgpack/msgpack";
|
import { decode } from "@msgpack/msgpack";
|
||||||
|
|
||||||
import { useAuth } from "./AuthContext";
|
import { useAuth } from "./AuthContext";
|
||||||
@ -14,9 +8,6 @@ import { maps as defaultMaps } from "../maps";
|
|||||||
|
|
||||||
const MapDataContext = React.createContext();
|
const MapDataContext = React.createContext();
|
||||||
|
|
||||||
// Maximum number of maps to keep in the cache
|
|
||||||
const cachedMapMax = 15;
|
|
||||||
|
|
||||||
const defaultMapState = {
|
const defaultMapState = {
|
||||||
tokens: {},
|
tokens: {},
|
||||||
drawShapes: {},
|
drawShapes: {},
|
||||||
@ -89,16 +80,7 @@ export function MapDataProvider({ children }) {
|
|||||||
loadMaps();
|
loadMaps();
|
||||||
}, [userId, database, databaseStatus, worker]);
|
}, [userId, database, databaseStatus, worker]);
|
||||||
|
|
||||||
const mapsRef = useRef(maps);
|
const getMap = useCallback(
|
||||||
useEffect(() => {
|
|
||||||
mapsRef.current = maps;
|
|
||||||
}, [maps]);
|
|
||||||
|
|
||||||
const getMap = useCallback((mapId) => {
|
|
||||||
return mapsRef.current.find((map) => map.id === mapId);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getMapFromDB = useCallback(
|
|
||||||
async (mapId) => {
|
async (mapId) => {
|
||||||
let map = await database.table("maps").get(mapId);
|
let map = await database.table("maps").get(mapId);
|
||||||
return map;
|
return map;
|
||||||
@ -106,7 +88,7 @@ export function MapDataProvider({ children }) {
|
|||||||
[database]
|
[database]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getMapStateFromDB = useCallback(
|
const getMapState = useCallback(
|
||||||
async (mapId) => {
|
async (mapId) => {
|
||||||
let mapState = await database.table("states").get(mapId);
|
let mapState = await database.table("states").get(mapId);
|
||||||
return mapState;
|
return mapState;
|
||||||
@ -114,25 +96,6 @@ export function MapDataProvider({ children }) {
|
|||||||
[database]
|
[database]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Keep up to cachedMapMax amount of maps that you don't own
|
|
||||||
* Sorted by when they we're last used
|
|
||||||
*/
|
|
||||||
const updateCache = useCallback(async () => {
|
|
||||||
const cachedMaps = await database
|
|
||||||
.table("maps")
|
|
||||||
.where("owner")
|
|
||||||
.notEqual(userId)
|
|
||||||
.sortBy("lastUsed");
|
|
||||||
if (cachedMaps.length > cachedMapMax) {
|
|
||||||
const cacheDeleteCount = cachedMaps.length - cachedMapMax;
|
|
||||||
const idsToDelete = cachedMaps
|
|
||||||
.slice(0, cacheDeleteCount)
|
|
||||||
.map((map) => map.id);
|
|
||||||
database.table("maps").where("id").anyOf(idsToDelete).delete();
|
|
||||||
}
|
|
||||||
}, [database, userId]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a map to the database, also adds an assosiated state for that map
|
* Adds a map to the database, also adds an assosiated state for that map
|
||||||
* @param {Object} map map to add
|
* @param {Object} map map to add
|
||||||
@ -143,11 +106,8 @@ export function MapDataProvider({ children }) {
|
|||||||
const state = { ...defaultMapState, mapId: map.id };
|
const state = { ...defaultMapState, mapId: map.id };
|
||||||
await database.table("maps").add(map);
|
await database.table("maps").add(map);
|
||||||
await database.table("states").add(state);
|
await database.table("states").add(state);
|
||||||
if (map.owner !== userId) {
|
|
||||||
await updateCache();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[database, updateCache, userId]
|
[database]
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeMaps = useCallback(
|
const removeMaps = useCallback(
|
||||||
@ -182,16 +142,9 @@ export function MapDataProvider({ children }) {
|
|||||||
|
|
||||||
const updateMap = useCallback(
|
const updateMap = useCallback(
|
||||||
async (id, update) => {
|
async (id, update) => {
|
||||||
// fake-indexeddb throws an error when updating maps in production.
|
await database.table("maps").update(id, update);
|
||||||
// Catch that error and use put when it fails
|
|
||||||
try {
|
|
||||||
await database.table("maps").update(id, update);
|
|
||||||
} catch (error) {
|
|
||||||
const map = (await getMapFromDB(id)) || {};
|
|
||||||
await database.table("maps").put({ ...map, id, ...update });
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[database, getMapFromDB]
|
[database]
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateMaps = useCallback(
|
const updateMaps = useCallback(
|
||||||
@ -210,21 +163,6 @@ export function MapDataProvider({ children }) {
|
|||||||
[database]
|
[database]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a map to the database if none exists or replaces a map if it already exists
|
|
||||||
* Note: this does not add a map state to do that use AddMap
|
|
||||||
* @param {Object} map the map to put
|
|
||||||
*/
|
|
||||||
const putMap = useCallback(
|
|
||||||
async (map) => {
|
|
||||||
await database.table("maps").put(map);
|
|
||||||
if (map.owner !== userId) {
|
|
||||||
await updateCache();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[database, updateCache, userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create DB observable to sync creating and deleting
|
// Create DB observable to sync creating and deleting
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!database || databaseStatus === "loading") {
|
if (!database || databaseStatus === "loading") {
|
||||||
@ -301,11 +239,9 @@ export function MapDataProvider({ children }) {
|
|||||||
updateMap,
|
updateMap,
|
||||||
updateMaps,
|
updateMaps,
|
||||||
updateMapState,
|
updateMapState,
|
||||||
putMap,
|
|
||||||
getMap,
|
getMap,
|
||||||
getMapFromDB,
|
|
||||||
mapsLoading,
|
mapsLoading,
|
||||||
getMapStateFromDB,
|
getMapState,
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<MapDataContext.Provider value={value}>{children}</MapDataContext.Provider>
|
<MapDataContext.Provider value={value}>{children}</MapDataContext.Provider>
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
import React, {
|
import React, { useEffect, useState, useContext, useCallback } from "react";
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
useContext,
|
|
||||||
useCallback,
|
|
||||||
useRef,
|
|
||||||
} from "react";
|
|
||||||
import { decode } from "@msgpack/msgpack";
|
import { decode } from "@msgpack/msgpack";
|
||||||
|
|
||||||
import { useAuth } from "./AuthContext";
|
import { useAuth } from "./AuthContext";
|
||||||
@ -14,8 +8,6 @@ import { tokens as defaultTokens } from "../tokens";
|
|||||||
|
|
||||||
const TokenDataContext = React.createContext();
|
const TokenDataContext = React.createContext();
|
||||||
|
|
||||||
const cachedTokenMax = 100;
|
|
||||||
|
|
||||||
export function TokenDataProvider({ children }) {
|
export function TokenDataProvider({ children }) {
|
||||||
const { database, databaseStatus, worker } = useDatabase();
|
const { database, databaseStatus, worker } = useDatabase();
|
||||||
const { userId } = useAuth();
|
const { userId } = useAuth();
|
||||||
@ -62,16 +54,7 @@ export function TokenDataProvider({ children }) {
|
|||||||
loadTokens();
|
loadTokens();
|
||||||
}, [userId, database, databaseStatus, worker]);
|
}, [userId, database, databaseStatus, worker]);
|
||||||
|
|
||||||
const tokensRef = useRef(tokens);
|
const getToken = useCallback(
|
||||||
useEffect(() => {
|
|
||||||
tokensRef.current = tokens;
|
|
||||||
}, [tokens]);
|
|
||||||
|
|
||||||
const getToken = useCallback((tokenId) => {
|
|
||||||
return tokensRef.current.find((token) => token.id === tokenId);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getTokenFromDB = useCallback(
|
|
||||||
async (tokenId) => {
|
async (tokenId) => {
|
||||||
let token = await database.table("tokens").get(tokenId);
|
let token = await database.table("tokens").get(tokenId);
|
||||||
return token;
|
return token;
|
||||||
@ -79,33 +62,11 @@ export function TokenDataProvider({ children }) {
|
|||||||
[database]
|
[database]
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Keep up to cachedTokenMax amount of tokens that you don't own
|
|
||||||
* Sorted by when they we're last used
|
|
||||||
*/
|
|
||||||
const updateCache = useCallback(async () => {
|
|
||||||
const cachedTokens = await database
|
|
||||||
.table("tokens")
|
|
||||||
.where("owner")
|
|
||||||
.notEqual(userId)
|
|
||||||
.sortBy("lastUsed");
|
|
||||||
if (cachedTokens.length > cachedTokenMax) {
|
|
||||||
const cacheDeleteCount = cachedTokens.length - cachedTokenMax;
|
|
||||||
const idsToDelete = cachedTokens
|
|
||||||
.slice(0, cacheDeleteCount)
|
|
||||||
.map((token) => token.id);
|
|
||||||
database.table("tokens").where("id").anyOf(idsToDelete).delete();
|
|
||||||
}
|
|
||||||
}, [database, userId]);
|
|
||||||
|
|
||||||
const addToken = useCallback(
|
const addToken = useCallback(
|
||||||
async (token) => {
|
async (token) => {
|
||||||
await database.table("tokens").add(token);
|
await database.table("tokens").add(token);
|
||||||
if (token.owner !== userId) {
|
|
||||||
await updateCache();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[database, updateCache, userId]
|
[database]
|
||||||
);
|
);
|
||||||
|
|
||||||
const removeTokens = useCallback(
|
const removeTokens = useCallback(
|
||||||
@ -142,16 +103,6 @@ export function TokenDataProvider({ children }) {
|
|||||||
[database]
|
[database]
|
||||||
);
|
);
|
||||||
|
|
||||||
const putToken = useCallback(
|
|
||||||
async (token) => {
|
|
||||||
await database.table("tokens").put(token);
|
|
||||||
if (token.owner !== userId) {
|
|
||||||
await updateCache();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[database, updateCache, userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
// Create DB observable to sync creating and deleting
|
// Create DB observable to sync creating and deleting
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!database || databaseStatus === "loading") {
|
if (!database || databaseStatus === "loading") {
|
||||||
@ -209,11 +160,9 @@ export function TokenDataProvider({ children }) {
|
|||||||
removeTokens,
|
removeTokens,
|
||||||
updateToken,
|
updateToken,
|
||||||
updateTokens,
|
updateTokens,
|
||||||
putToken,
|
|
||||||
getToken,
|
|
||||||
tokensById,
|
tokensById,
|
||||||
tokensLoading,
|
tokensLoading,
|
||||||
getTokenFromDB,
|
getToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -556,6 +556,7 @@ const versions = {
|
|||||||
delete map[res];
|
delete map[res];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
delete map.lastUsed;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -573,6 +574,7 @@ const versions = {
|
|||||||
} else {
|
} else {
|
||||||
token.outline = "rect";
|
token.outline = "rect";
|
||||||
}
|
}
|
||||||
|
delete token.lastUsed;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -13,14 +13,14 @@ import { isEmpty } from "../helpers/shared";
|
|||||||
import useResponsiveLayout from "../hooks/useResponsiveLayout";
|
import useResponsiveLayout from "../hooks/useResponsiveLayout";
|
||||||
|
|
||||||
function EditTokenModal({ isOpen, onDone, tokenId }) {
|
function EditTokenModal({ isOpen, onDone, tokenId }) {
|
||||||
const { updateToken, getTokenFromDB } = useTokenData();
|
const { updateToken, getToken } = useTokenData();
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [token, setToken] = useState();
|
const [token, setToken] = useState();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function loadToken() {
|
async function loadToken() {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setToken(await getTokenFromDB(tokenId));
|
setToken(await getToken(tokenId));
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ function EditTokenModal({ isOpen, onDone, tokenId }) {
|
|||||||
} else {
|
} else {
|
||||||
setToken();
|
setToken();
|
||||||
}
|
}
|
||||||
}, [isOpen, tokenId, getTokenFromDB]);
|
}, [isOpen, tokenId, getToken]);
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
setTokenSettingChanges({});
|
setTokenSettingChanges({});
|
||||||
|
@ -67,11 +67,9 @@ function SelectMapModal({
|
|||||||
addMap,
|
addMap,
|
||||||
removeMaps,
|
removeMaps,
|
||||||
resetMap,
|
resetMap,
|
||||||
updateMap,
|
|
||||||
updateMaps,
|
updateMaps,
|
||||||
mapsLoading,
|
mapsLoading,
|
||||||
getMapFromDB,
|
getMapState,
|
||||||
getMapStateFromDB,
|
|
||||||
} = useMapData();
|
} = useMapData();
|
||||||
const { addAssets } = useAssets();
|
const { addAssets } = useAssets();
|
||||||
|
|
||||||
@ -260,7 +258,11 @@ function SelectMapModal({
|
|||||||
}
|
}
|
||||||
// Create thumbnail
|
// Create thumbnail
|
||||||
const thumbnailImage = await createThumbnail(image, file.type);
|
const thumbnailImage = await createThumbnail(image, file.type);
|
||||||
const thumbnail = { ...thumbnailImage, id: uuid(), owner: userId };
|
const thumbnail = {
|
||||||
|
...thumbnailImage,
|
||||||
|
id: uuid(),
|
||||||
|
owner: userId,
|
||||||
|
};
|
||||||
assets.push(thumbnail);
|
assets.push(thumbnail);
|
||||||
|
|
||||||
const fileAsset = {
|
const fileAsset = {
|
||||||
@ -297,7 +299,6 @@ function SelectMapModal({
|
|||||||
id: uuid(),
|
id: uuid(),
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
lastModified: Date.now(),
|
lastModified: Date.now(),
|
||||||
lastUsed: Date.now(),
|
|
||||||
owner: userId,
|
owner: userId,
|
||||||
...defaultMapProps,
|
...defaultMapProps,
|
||||||
};
|
};
|
||||||
@ -392,19 +393,11 @@ function SelectMapModal({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (selectedMapIds.length === 1) {
|
if (selectedMapIds.length === 1) {
|
||||||
// Update last used for cache invalidation
|
setIsLoading(true);
|
||||||
const lastUsed = Date.now();
|
|
||||||
const map = selectedMaps[0];
|
const map = selectedMaps[0];
|
||||||
const mapState = await getMapStateFromDB(map.id);
|
const mapState = await getMapState(map.id);
|
||||||
if (map.type === "file") {
|
onMapChange(map, mapState);
|
||||||
setIsLoading(true);
|
setIsLoading(false);
|
||||||
await updateMap(map.id, { lastUsed });
|
|
||||||
const updatedMap = await getMapFromDB(map.id);
|
|
||||||
onMapChange(updatedMap, mapState);
|
|
||||||
setIsLoading(false);
|
|
||||||
} else {
|
|
||||||
onMapChange(map, mapState);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
onMapChange(null, null);
|
onMapChange(null, null);
|
||||||
}
|
}
|
||||||
|
@ -196,7 +196,6 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
|||||||
type: "file",
|
type: "file",
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
lastModified: Date.now(),
|
lastModified: Date.now(),
|
||||||
lastUsed: Date.now(),
|
|
||||||
owner: userId,
|
owner: userId,
|
||||||
defaultSize: 1,
|
defaultSize: 1,
|
||||||
defaultCategory: "character",
|
defaultCategory: "character",
|
||||||
|
@ -141,6 +141,43 @@ let service = {
|
|||||||
});
|
});
|
||||||
importDB.close();
|
importDB.close();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
Comlink.expose(service);
|
||||||
|
Loading…
Reference in New Issue
Block a user