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 }) {
|
||||
const { userId } = useAuth();
|
||||
const { ownedTokens, tokens, updateToken } = useTokenData();
|
||||
const { ownedTokens, tokens } = useTokenData();
|
||||
const [fullScreen] = useSetting("map.fullScreen");
|
||||
|
||||
function handleProxyDragEnd(isOnMap, token) {
|
||||
@ -51,13 +51,6 @@ function Tokens({ onMapTokenStateCreate }) {
|
||||
tokenState.key = token.key;
|
||||
}
|
||||
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();
|
||||
|
||||
// 100 MB max cache size
|
||||
const maxCacheSize = 1e8;
|
||||
|
||||
export function AssetsProvider({ children }) {
|
||||
const { worker, database } = useDatabase();
|
||||
|
||||
useEffect(() => {
|
||||
worker.cleanAssetCache(maxCacheSize);
|
||||
}, [worker]);
|
||||
|
||||
const getAsset = useCallback(
|
||||
async (assetId) => {
|
||||
return await database.table("assets").get(assetId);
|
||||
@ -56,7 +63,7 @@ export function AssetsProvider({ children }) {
|
||||
|
||||
const addAssets = useCallback(
|
||||
async (assets) => {
|
||||
return database.table("assets").bulkAdd(assets);
|
||||
await database.table("assets").bulkAdd(assets);
|
||||
},
|
||||
[database]
|
||||
);
|
||||
|
@ -1,10 +1,4 @@
|
||||
import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
useContext,
|
||||
useCallback,
|
||||
useRef,
|
||||
} from "react";
|
||||
import React, { useEffect, useState, useContext, useCallback } from "react";
|
||||
import { decode } from "@msgpack/msgpack";
|
||||
|
||||
import { useAuth } from "./AuthContext";
|
||||
@ -14,9 +8,6 @@ import { maps as defaultMaps } from "../maps";
|
||||
|
||||
const MapDataContext = React.createContext();
|
||||
|
||||
// Maximum number of maps to keep in the cache
|
||||
const cachedMapMax = 15;
|
||||
|
||||
const defaultMapState = {
|
||||
tokens: {},
|
||||
drawShapes: {},
|
||||
@ -89,16 +80,7 @@ export function MapDataProvider({ children }) {
|
||||
loadMaps();
|
||||
}, [userId, database, databaseStatus, worker]);
|
||||
|
||||
const mapsRef = useRef(maps);
|
||||
useEffect(() => {
|
||||
mapsRef.current = maps;
|
||||
}, [maps]);
|
||||
|
||||
const getMap = useCallback((mapId) => {
|
||||
return mapsRef.current.find((map) => map.id === mapId);
|
||||
}, []);
|
||||
|
||||
const getMapFromDB = useCallback(
|
||||
const getMap = useCallback(
|
||||
async (mapId) => {
|
||||
let map = await database.table("maps").get(mapId);
|
||||
return map;
|
||||
@ -106,7 +88,7 @@ export function MapDataProvider({ children }) {
|
||||
[database]
|
||||
);
|
||||
|
||||
const getMapStateFromDB = useCallback(
|
||||
const getMapState = useCallback(
|
||||
async (mapId) => {
|
||||
let mapState = await database.table("states").get(mapId);
|
||||
return mapState;
|
||||
@ -114,25 +96,6 @@ export function MapDataProvider({ children }) {
|
||||
[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
|
||||
* @param {Object} map map to add
|
||||
@ -143,11 +106,8 @@ export function MapDataProvider({ children }) {
|
||||
const state = { ...defaultMapState, mapId: map.id };
|
||||
await database.table("maps").add(map);
|
||||
await database.table("states").add(state);
|
||||
if (map.owner !== userId) {
|
||||
await updateCache();
|
||||
}
|
||||
},
|
||||
[database, updateCache, userId]
|
||||
[database]
|
||||
);
|
||||
|
||||
const removeMaps = useCallback(
|
||||
@ -182,16 +142,9 @@ export function MapDataProvider({ children }) {
|
||||
|
||||
const updateMap = useCallback(
|
||||
async (id, update) => {
|
||||
// fake-indexeddb throws an error when updating maps in production.
|
||||
// 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(
|
||||
@ -210,21 +163,6 @@ export function MapDataProvider({ children }) {
|
||||
[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
|
||||
useEffect(() => {
|
||||
if (!database || databaseStatus === "loading") {
|
||||
@ -301,11 +239,9 @@ export function MapDataProvider({ children }) {
|
||||
updateMap,
|
||||
updateMaps,
|
||||
updateMapState,
|
||||
putMap,
|
||||
getMap,
|
||||
getMapFromDB,
|
||||
mapsLoading,
|
||||
getMapStateFromDB,
|
||||
getMapState,
|
||||
};
|
||||
return (
|
||||
<MapDataContext.Provider value={value}>{children}</MapDataContext.Provider>
|
||||
|
@ -1,10 +1,4 @@
|
||||
import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
useContext,
|
||||
useCallback,
|
||||
useRef,
|
||||
} from "react";
|
||||
import React, { useEffect, useState, useContext, useCallback } from "react";
|
||||
import { decode } from "@msgpack/msgpack";
|
||||
|
||||
import { useAuth } from "./AuthContext";
|
||||
@ -14,8 +8,6 @@ import { tokens as defaultTokens } from "../tokens";
|
||||
|
||||
const TokenDataContext = React.createContext();
|
||||
|
||||
const cachedTokenMax = 100;
|
||||
|
||||
export function TokenDataProvider({ children }) {
|
||||
const { database, databaseStatus, worker } = useDatabase();
|
||||
const { userId } = useAuth();
|
||||
@ -62,16 +54,7 @@ export function TokenDataProvider({ children }) {
|
||||
loadTokens();
|
||||
}, [userId, database, databaseStatus, worker]);
|
||||
|
||||
const tokensRef = useRef(tokens);
|
||||
useEffect(() => {
|
||||
tokensRef.current = tokens;
|
||||
}, [tokens]);
|
||||
|
||||
const getToken = useCallback((tokenId) => {
|
||||
return tokensRef.current.find((token) => token.id === tokenId);
|
||||
}, []);
|
||||
|
||||
const getTokenFromDB = useCallback(
|
||||
const getToken = useCallback(
|
||||
async (tokenId) => {
|
||||
let token = await database.table("tokens").get(tokenId);
|
||||
return token;
|
||||
@ -79,33 +62,11 @@ export function TokenDataProvider({ children }) {
|
||||
[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(
|
||||
async (token) => {
|
||||
await database.table("tokens").add(token);
|
||||
if (token.owner !== userId) {
|
||||
await updateCache();
|
||||
}
|
||||
},
|
||||
[database, updateCache, userId]
|
||||
[database]
|
||||
);
|
||||
|
||||
const removeTokens = useCallback(
|
||||
@ -142,16 +103,6 @@ export function TokenDataProvider({ children }) {
|
||||
[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
|
||||
useEffect(() => {
|
||||
if (!database || databaseStatus === "loading") {
|
||||
@ -209,11 +160,9 @@ export function TokenDataProvider({ children }) {
|
||||
removeTokens,
|
||||
updateToken,
|
||||
updateTokens,
|
||||
putToken,
|
||||
getToken,
|
||||
tokensById,
|
||||
tokensLoading,
|
||||
getTokenFromDB,
|
||||
getToken,
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -556,6 +556,7 @@ const versions = {
|
||||
delete map[res];
|
||||
}
|
||||
}
|
||||
delete map.lastUsed;
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -573,6 +574,7 @@ const versions = {
|
||||
} else {
|
||||
token.outline = "rect";
|
||||
}
|
||||
delete token.lastUsed;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
@ -13,14 +13,14 @@ import { isEmpty } from "../helpers/shared";
|
||||
import useResponsiveLayout from "../hooks/useResponsiveLayout";
|
||||
|
||||
function EditTokenModal({ isOpen, onDone, tokenId }) {
|
||||
const { updateToken, getTokenFromDB } = useTokenData();
|
||||
const { updateToken, getToken } = useTokenData();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [token, setToken] = useState();
|
||||
useEffect(() => {
|
||||
async function loadToken() {
|
||||
setIsLoading(true);
|
||||
setToken(await getTokenFromDB(tokenId));
|
||||
setToken(await getToken(tokenId));
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ function EditTokenModal({ isOpen, onDone, tokenId }) {
|
||||
} else {
|
||||
setToken();
|
||||
}
|
||||
}, [isOpen, tokenId, getTokenFromDB]);
|
||||
}, [isOpen, tokenId, getToken]);
|
||||
|
||||
function handleClose() {
|
||||
setTokenSettingChanges({});
|
||||
|
@ -67,11 +67,9 @@ function SelectMapModal({
|
||||
addMap,
|
||||
removeMaps,
|
||||
resetMap,
|
||||
updateMap,
|
||||
updateMaps,
|
||||
mapsLoading,
|
||||
getMapFromDB,
|
||||
getMapStateFromDB,
|
||||
getMapState,
|
||||
} = useMapData();
|
||||
const { addAssets } = useAssets();
|
||||
|
||||
@ -260,7 +258,11 @@ function SelectMapModal({
|
||||
}
|
||||
// Create thumbnail
|
||||
const thumbnailImage = await createThumbnail(image, file.type);
|
||||
const thumbnail = { ...thumbnailImage, id: uuid(), owner: userId };
|
||||
const thumbnail = {
|
||||
...thumbnailImage,
|
||||
id: uuid(),
|
||||
owner: userId,
|
||||
};
|
||||
assets.push(thumbnail);
|
||||
|
||||
const fileAsset = {
|
||||
@ -297,7 +299,6 @@ function SelectMapModal({
|
||||
id: uuid(),
|
||||
created: Date.now(),
|
||||
lastModified: Date.now(),
|
||||
lastUsed: Date.now(),
|
||||
owner: userId,
|
||||
...defaultMapProps,
|
||||
};
|
||||
@ -392,19 +393,11 @@ function SelectMapModal({
|
||||
return;
|
||||
}
|
||||
if (selectedMapIds.length === 1) {
|
||||
// Update last used for cache invalidation
|
||||
const lastUsed = Date.now();
|
||||
const map = selectedMaps[0];
|
||||
const mapState = await getMapStateFromDB(map.id);
|
||||
if (map.type === "file") {
|
||||
setIsLoading(true);
|
||||
await updateMap(map.id, { lastUsed });
|
||||
const updatedMap = await getMapFromDB(map.id);
|
||||
onMapChange(updatedMap, mapState);
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
const map = selectedMaps[0];
|
||||
const mapState = await getMapState(map.id);
|
||||
onMapChange(map, mapState);
|
||||
}
|
||||
setIsLoading(false);
|
||||
} else {
|
||||
onMapChange(null, null);
|
||||
}
|
||||
|
@ -196,7 +196,6 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
type: "file",
|
||||
created: Date.now(),
|
||||
lastModified: Date.now(),
|
||||
lastUsed: Date.now(),
|
||||
owner: userId,
|
||||
defaultSize: 1,
|
||||
defaultCategory: "character",
|
||||
|
@ -141,6 +141,43 @@ let service = {
|
||||
});
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user