Added automatic cache invalidation for maps and tokens
This commit is contained in:
parent
529fd2caae
commit
e92c561a3a
@ -18,7 +18,7 @@ const listTokenClassName = "list-token";
|
||||
|
||||
function Tokens({ onMapTokenStateCreate }) {
|
||||
const { userId } = useContext(AuthContext);
|
||||
const { ownedTokens, tokens } = useContext(TokenDataContext);
|
||||
const { ownedTokens, tokens, updateToken } = useContext(TokenDataContext);
|
||||
const [fullScreen] = useSetting("map.fullScreen");
|
||||
|
||||
function handleProxyDragEnd(isOnMap, token) {
|
||||
@ -39,6 +39,8 @@ function Tokens({ onMapTokenStateCreate }) {
|
||||
locked: false,
|
||||
visible: true,
|
||||
});
|
||||
// Update last used for cache invalidation
|
||||
updateToken(token.id, { lastUsed: Date.now() });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,9 @@ 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: {},
|
||||
// An index into the draw actions array to which only actions before the
|
||||
@ -70,12 +73,19 @@ export function MapDataProvider({ children }) {
|
||||
loadMaps();
|
||||
}, [userId, database]);
|
||||
|
||||
/**
|
||||
* Adds a map to the database, also adds an assosiated state for that map
|
||||
* @param {Object} map map to add
|
||||
*/
|
||||
async function addMap(map) {
|
||||
await database.table("maps").add(map);
|
||||
const state = { ...defaultMapState, mapId: map.id };
|
||||
await database.table("states").add(state);
|
||||
setMaps((prevMaps) => [map, ...prevMaps]);
|
||||
setMapStates((prevStates) => [state, ...prevStates]);
|
||||
if (map.owner !== userId) {
|
||||
await updateCache();
|
||||
}
|
||||
}
|
||||
|
||||
async function removeMap(id) {
|
||||
@ -129,6 +139,11 @@ export function MapDataProvider({ children }) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
async function putMap(map) {
|
||||
await database.table("maps").put(map);
|
||||
setMaps((prevMaps) => {
|
||||
@ -141,6 +156,31 @@ export function MapDataProvider({ children }) {
|
||||
}
|
||||
return newMaps;
|
||||
});
|
||||
if (map.owner !== userId) {
|
||||
await updateCache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep up to cachedMapMax amount of maps that you don't own
|
||||
* Sorted by when they we're last used
|
||||
*/
|
||||
async function updateCache() {
|
||||
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();
|
||||
setMaps((prevMaps) => {
|
||||
return prevMaps.filter((map) => !idsToDelete.includes(map.id));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getMap(mapId) {
|
||||
|
@ -7,6 +7,8 @@ import { tokens as defaultTokens } from "../tokens";
|
||||
|
||||
const TokenDataContext = React.createContext();
|
||||
|
||||
const cachedTokenMax = 100;
|
||||
|
||||
export function TokenDataProvider({ children }) {
|
||||
const { database } = useContext(DatabaseContext);
|
||||
const { userId } = useContext(AuthContext);
|
||||
@ -45,6 +47,9 @@ export function TokenDataProvider({ children }) {
|
||||
async function addToken(token) {
|
||||
await database.table("tokens").add(token);
|
||||
setTokens((prevTokens) => [token, ...prevTokens]);
|
||||
if (token.owner !== userId) {
|
||||
await updateCache();
|
||||
}
|
||||
}
|
||||
|
||||
async function removeToken(id) {
|
||||
@ -80,6 +85,31 @@ export function TokenDataProvider({ children }) {
|
||||
}
|
||||
return newTokens;
|
||||
});
|
||||
if (token.owner !== userId) {
|
||||
await updateCache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep up to cachedTokenMax amount of tokens that you don't own
|
||||
* Sorted by when they we're last used
|
||||
*/
|
||||
async function updateCache() {
|
||||
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();
|
||||
setTokens((prevTokens) => {
|
||||
return prevTokens.filter((token) => !idsToDelete.includes(token.id));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getToken(tokenId) {
|
||||
|
@ -184,6 +184,28 @@ function loadVersions(db) {
|
||||
delete token.isVehicle;
|
||||
});
|
||||
});
|
||||
// v1.5.2 - Added automatic cache invalidation to maps
|
||||
db.version(11)
|
||||
.stores({})
|
||||
.upgrade(async (tx) => {
|
||||
return tx
|
||||
.table("maps")
|
||||
.toCollection()
|
||||
.modify((map) => {
|
||||
map.lastUsed = map.lastModified;
|
||||
});
|
||||
});
|
||||
// v1.5.2 - Added automatic cache invalidation to tokens
|
||||
db.version(12)
|
||||
.stores({})
|
||||
.upgrade(async (tx) => {
|
||||
return tx
|
||||
.table("tokens")
|
||||
.toCollection()
|
||||
.modify((token) => {
|
||||
token.lastUsed = token.lastModified;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Get the dexie database used in DatabaseContext
|
||||
|
@ -149,6 +149,7 @@ function SelectMapModal({
|
||||
id: shortid.generate(),
|
||||
created: Date.now(),
|
||||
lastModified: Date.now(),
|
||||
lastUsed: Date.now(),
|
||||
owner: userId,
|
||||
...defaultMapProps,
|
||||
});
|
||||
@ -213,7 +214,13 @@ function SelectMapModal({
|
||||
}
|
||||
if (selectedMapId) {
|
||||
await applyMapChanges();
|
||||
onMapChange(selectedMapWithChanges, selectedMapStateWithChanges);
|
||||
// Update last used for cache invalidation
|
||||
const lastUsed = Date.now();
|
||||
await updateMap(selectedMapId, { lastUsed });
|
||||
onMapChange(
|
||||
{ ...selectedMapWithChanges, lastUsed },
|
||||
selectedMapStateWithChanges
|
||||
);
|
||||
} else {
|
||||
onMapChange(null, null);
|
||||
}
|
||||
|
@ -75,6 +75,7 @@ function SelectTokensModal({ isOpen, onRequestClose }) {
|
||||
type: "file",
|
||||
created: Date.now(),
|
||||
lastModified: Date.now(),
|
||||
lastUsed: Date.now(),
|
||||
owner: userId,
|
||||
defaultSize: 1,
|
||||
category: "character",
|
||||
|
@ -32,7 +32,7 @@ function NetworkedMapAndTokens({ session }) {
|
||||
isLoading,
|
||||
} = useContext(MapLoadingContext);
|
||||
|
||||
const { putToken, getToken } = useContext(TokenDataContext);
|
||||
const { putToken, getToken, updateToken } = useContext(TokenDataContext);
|
||||
const { putMap, updateMap, getMapFromDB } = useContext(MapDataContext);
|
||||
|
||||
const [currentMap, setCurrentMap] = useState(null);
|
||||
@ -245,11 +245,15 @@ function NetworkedMapAndTokens({ session }) {
|
||||
if (newMap && newMap.type === "file") {
|
||||
const cachedMap = await getMapFromDB(newMap.id);
|
||||
if (cachedMap && cachedMap.lastModified >= newMap.lastModified) {
|
||||
setCurrentMap(cachedMap);
|
||||
// Update last used for cache invalidation
|
||||
const lastUsed = Date.now();
|
||||
await updateMap(cachedMap.id, { lastUsed });
|
||||
setCurrentMap({ ...cachedMap, lastUsed });
|
||||
} else {
|
||||
// Save map data but remove last modified so if there is an error
|
||||
// during the map request the cache is invalid
|
||||
await putMap({ ...newMap, lastModified: 0 });
|
||||
// during the map request the cache is invalid. Also add last used
|
||||
// for cache invalidation
|
||||
await putMap({ ...newMap, lastModified: 0, lastUsed: Date.now() });
|
||||
reply("mapRequest", newMap.id, "map");
|
||||
}
|
||||
} else {
|
||||
@ -326,16 +330,21 @@ function NetworkedMapAndTokens({ session }) {
|
||||
if (newToken && newToken.type === "file") {
|
||||
const cachedToken = getToken(newToken.id);
|
||||
if (
|
||||
!cachedToken ||
|
||||
cachedToken.lastModified !== newToken.lastModified
|
||||
cachedToken &&
|
||||
cachedToken.lastModified >= newToken.lastModified
|
||||
) {
|
||||
// Update last used for cache invalidation
|
||||
const lastUsed = Date.now();
|
||||
await updateToken(cachedToken.id, { lastUsed });
|
||||
} else {
|
||||
reply("tokenRequest", newToken.id, "token");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (id === "tokenRequest") {
|
||||
const token = getToken(data);
|
||||
reply("tokenResponse", token, "token");
|
||||
// Add a last used property for cache invalidation
|
||||
reply("tokenResponse", { ...token, lastUsed: Date.now() }, "token");
|
||||
}
|
||||
if (id === "tokenResponse") {
|
||||
const newToken = data;
|
||||
|
Loading…
Reference in New Issue
Block a user