Automatically update to newest data when receiving cached data

This commit is contained in:
Aurora Lahtela 2022-09-06 14:23:39 +03:00
parent 0bd0bbef24
commit ef0ba83412
6 changed files with 106 additions and 13 deletions

View File

@ -24,6 +24,7 @@ import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DisplaySettings;
import com.djrapitops.plan.settings.config.paths.ProxySettings;
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
import com.djrapitops.plan.settings.theme.Theme;
import com.djrapitops.plan.settings.theme.ThemeVal;
import com.djrapitops.plan.utilities.java.Maps;
@ -85,6 +86,7 @@ public class MetadataJSONResolver implements NoAuthResolver {
.put("serverUUID", serverInfo.getServer().getUuid().toString())
.put("networkName", serverInfo.getServer().isProxy() ? config.get(ProxySettings.NETWORK_NAME) : null)
.put("mainCommand", mainCommand)
.put("refreshBarrierMs", config.get(WebserverSettings.REDUCED_REFRESH_BARRIER))
.build())
.build();
}

View File

@ -36,7 +36,7 @@ const Header = ({page, tab}) => {
const {toggleColorChooser} = useTheme();
const {t} = useTranslation();
const {requestUpdate, updating, lastUpdate, toggleSidebar} = useNavigation();
const {requestUpdate, lastUpdate, updating, toggleSidebar} = useNavigation();
const {getPlayerHeadImageUrl} = useMetadata();
const headImageUrl = user ? getPlayerHeadImageUrl(user.playerName, user.linkedToUuid) : undefined

View File

@ -1,23 +1,64 @@
import {useEffect, useState} from "react";
import {useNavigation} from "./navigationHook";
import {useDataStore} from "./datastoreHook";
import {useMetadata} from "./metadataHook";
export const useDataRequest = (fetchMethod, parameters) => {
const [data, setData] = useState(undefined);
const [loadingError, setLoadingError] = useState(undefined);
const {updateRequested, finishUpdate} = useNavigation();
const {refreshBarrierMs} = useMetadata();
const datastore = useDataStore();
/*eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
fetchMethod(updateRequested, ...parameters).then(({data: json, error}) => {
datastore.setAsUpdating(fetchMethod);
const handleResponse = (json, error, skipOldData, timeout) => {
if (json) {
setData(json);
finishUpdate(json.timestamp, json.timestamp_f);
const timestamp = json.timestamp;
if (timestamp) {
// Data has timestamp, the data may come from cache
const acceptedTimestamp = timestamp + (refreshBarrierMs ? refreshBarrierMs : 15000);
if (acceptedTimestamp < updateRequested) {
// Request again, received data was too old
setTimeout(() => {
fetchMethod(new Date().getTime(), ...parameters)
.then(({data: json, error}) => {
handleResponse(json, error, true, timeout >= 12000 ? timeout : timeout * 2);
});
}, timeout);
} else {
// Received data was new enough to be shown
setData(json);
datastore.storeData(fetchMethod, json);
datastore.finishUpdate(fetchMethod)
finishUpdate(json.timestamp, json.timestamp_f, datastore.isSomethingUpdating());
}
if (!skipOldData) {
// Old data is shown on first pass, further passes skip old data.
setData(json);
datastore.storeData(fetchMethod, json);
finishUpdate(json.timestamp, json.timestamp_f, datastore.isSomethingUpdating());
}
} else {
// Response data is not cached, no timestamp, show it immediately
setData(json);
datastore.finishUpdate(fetchMethod);
finishUpdate(json.timestamp, json.timestamp_f, datastore.isSomethingUpdating());
}
} else if (error) {
console.warn(error);
datastore.finishUpdate(fetchMethod)
setLoadingError(error);
finishUpdate(new Date().getTime(), "Error", datastore.isSomethingUpdating());
}
};
fetchMethod(updateRequested, ...parameters).then(({data: json, error}) => {
handleResponse(json, error, false, 1000);
});
}, [fetchMethod, ...parameters, updateRequested])
}, [fetchMethod, ...parameters, updateRequested, refreshBarrierMs])
/* eslint-enable react-hooks/exhaustive-deps */
return {data, loadingError};

View File

@ -0,0 +1,45 @@
import {useCallback} from "react";
import {useMetadata} from "./metadataHook";
export const useDataStore = () => {
const {datastore} = useMetadata();
if (datastore && !datastore.dataByMethod) datastore.dataByMethod = {};
if (datastore && !datastore.lastUpdateByMethod) datastore.lastUpdateByMethod = {};
if (datastore && !datastore.currentlyUpdatingMethods) datastore.currentlyUpdatingMethods = {};
const storeData = useCallback((method, data) => {
const hadPrevious = Boolean(datastore.dataByMethod[method]);
if (data) {
datastore.lastUpdateByMethod[method] = data.timestamp;
datastore.dataByMethod[method] = data;
}
return hadPrevious;
}, [datastore]);
const getLastUpdate = useCallback((method) => {
return datastore?.lastUpdateByMethod[method];
}, [datastore]);
const getData = useCallback((method) => {
return datastore?.dataByMethod[method];
}, [datastore]);
const isCurrentlyUpdating = useCallback((method) => {
return datastore && Boolean(datastore.currentlyUpdatingMethods[method]);
}, [datastore]);
const setAsUpdating = useCallback((method) => {
datastore.currentlyUpdatingMethods[method] = true;
}, [datastore]);
const finishUpdate = useCallback((method) => {
delete datastore.currentlyUpdatingMethods[method];
}, [datastore])
const isSomethingUpdating = useCallback(() => {
return datastore && Boolean(Object.values(datastore.currentlyUpdatingMethods).filter(value => Boolean(value)).length);
}, [datastore]);
return {storeData, getLastUpdate, getData, isCurrentlyUpdating, isSomethingUpdating, setAsUpdating, finishUpdate};
}

View File

@ -6,6 +6,7 @@ import terminal from '../Terminal-icon.png'
const MetadataContext = createContext({});
export const MetadataContextProvider = ({children}) => {
const [datastore] = useState({});
const [metadata, setMetadata] = useState({});
const updateMetadata = useCallback(async () => {
@ -34,7 +35,7 @@ export const MetadataContextProvider = ({children}) => {
updateMetadata();
}, [updateMetadata]);
const sharedState = {...metadata, getPlayerHeadImageUrl}
const sharedState = {...metadata, getPlayerHeadImageUrl, datastore}
return (<MetadataContext.Provider value={sharedState}>
{children}
</MetadataContext.Provider>

View File

@ -5,8 +5,8 @@ const NavigationContext = createContext({});
export const NavigationContextProvider = ({children}) => {
const [currentTab, setCurrentTab] = useState(undefined);
const [updateRequested, setUpdateRequested] = useState(Date.now());
const [updating, setUpdating] = useState(false);
const [lastUpdate, setLastUpdate] = useState({date: 0, formatted: ""});
const [updating, setUpdating] = useState({});
const [lastUpdate, setLastUpdate] = useState({});
const [items, setItems] = useState([]);
const [sidebarExpanded, setSidebarExpanded] = useState(window.innerWidth > 1350);
@ -31,13 +31,17 @@ export const NavigationContextProvider = ({children}) => {
}
}, [updating, setUpdateRequested, setUpdating]);
const finishUpdate = useCallback((date, formatted) => {
// TODO Logic to retry if received data is too old
// TODO currently not possible due to extensionData getting updated off-tab
// useEffect(requestUpdate, [currentTab]); // Force data to update when changing tab
const finishUpdate = useCallback((date, formatted, isStillUpdating) => {
if (date) {
setLastUpdate({date, formatted});
setUpdating(false);
if (!lastUpdate.date || date > lastUpdate.date) {
setLastUpdate({date, formatted});
}
setUpdating(isStillUpdating);
}
}, [setLastUpdate, setUpdating]);
}, [setLastUpdate, setUpdating, lastUpdate]);
const toggleSidebar = useCallback(() => {
setSidebarExpanded(!sidebarExpanded);