add resources and update resources

This commit is contained in:
PhilReact 2025-03-14 02:51:41 +02:00
parent ae5111618f
commit e8866f1585
3 changed files with 269 additions and 192 deletions

View File

@ -1,10 +1,12 @@
import React, { createContext, useContext, useMemo } from "react"; import React, { createContext, useContext, useMemo } from "react";
import { useAuth, UseAuthProps } from "../hooks/useAuth"; import { useAuth, UseAuthProps } from "../hooks/useAuth";
import { useResources } from "../hooks/useResources";
// ✅ Define Global Context Type // ✅ Define Global Context Type
interface GlobalContextType { interface GlobalContextType {
auth: ReturnType<typeof useAuth>; auth: ReturnType<typeof useAuth>;
resources: ReturnType<typeof useResources>;
} }
// ✅ Define Config Type for Hook Options // ✅ Define Config Type for Hook Options
@ -23,10 +25,10 @@ const GlobalContext = createContext<GlobalContextType | null>(null);
export const GlobalProvider = ({ children, config }: GlobalProviderProps) => { export const GlobalProvider = ({ children, config }: GlobalProviderProps) => {
// ✅ Call hooks and pass in options dynamically // ✅ Call hooks and pass in options dynamically
const auth = useAuth(config?.auth || {}); const auth = useAuth(config?.auth || {});
const resources = useResources()
// ✅ Merge all hooks into a single `contextValue` // ✅ Merge all hooks into a single `contextValue`
const contextValue = useMemo(() => ({ auth }), [auth]); const contextValue = useMemo(() => ({ auth, resources }), [auth, resources]);
return ( return (
<GlobalContext.Provider value={contextValue}> <GlobalContext.Provider value={contextValue}>

View File

@ -1,21 +1,36 @@
import React, { useCallback } from 'react' import React, { useCallback } from "react";
import { QortalMetadata, QortalSearchParams } from '../types/interfaces/resources'; import {
import { useCacheStore } from '../state/cache'; QortalMetadata,
import { RequestQueueWithPromise } from '../utils/queue'; QortalSearchParams,
import { base64ToUint8Array, uint8ArrayToObject } from '../utils/base64'; } from "../types/interfaces/resources";
import { useCacheStore } from "../state/cache";
import { RequestQueueWithPromise } from "../utils/queue";
import { base64ToUint8Array, uint8ArrayToObject } from "../utils/base64";
export const requestQueueProductPublishes = new RequestQueueWithPromise(20); export const requestQueueProductPublishes = new RequestQueueWithPromise(20);
export const requestQueueProductPublishesBackup = new RequestQueueWithPromise(5); export const requestQueueProductPublishesBackup = new RequestQueueWithPromise(
5
);
interface TemporaryResource { interface TemporaryResource {
qortalMetadata: QortalMetadata qortalMetadata: QortalMetadata;
data: any data: any;
} }
export const useResources = () => { export const useResources = () => {
const { setSearchCache, getSearchCache, getResourceCache, setResourceCache, addTemporaryResource, getTemporaryResources } = useCacheStore(); const {
setSearchCache,
getSearchCache,
getResourceCache,
setResourceCache,
addTemporaryResource
} = useCacheStore();
const requestControllers = new Map<string, AbortController>(); const requestControllers = new Map<string, AbortController>();
const getArbitraryResource = async (url: string, key: string): Promise<string> => { const getArbitraryResource = async (
url: string,
key: string
): Promise<string> => {
// ✅ Create or reuse an existing controller // ✅ Create or reuse an existing controller
let controller = requestControllers.get(key); let controller = requestControllers.get(key);
if (!controller) { if (!controller) {
@ -46,52 +61,74 @@ export const useResources = () => {
requestControllers.clear(); requestControllers.clear();
}; };
const fetchIndividualPublish = useCallback( const fetchIndividualPublish = useCallback(
async (item: QortalMetadata) => { async (item: QortalMetadata) => {
try { try {
const key = `${item?.service}-${item?.name}-${item?.identifier}`; const key = `${item?.service}-${item?.name}-${item?.identifier}`;
const cachedProduct = getResourceCache(`${item?.service}-${item?.name}-${item?.identifier}`); const cachedProduct = getResourceCache(
`${item?.service}-${item?.name}-${item?.identifier}`
);
if (cachedProduct) return; if (cachedProduct) return;
setResourceCache(`${item?.service}-${item?.name}-${item?.identifier}`, null); setResourceCache(
let hasFailedToDownload = false `${item?.service}-${item?.name}-${item?.identifier}`,
let res: string | undefined = undefined null
);
let hasFailedToDownload = false;
let res: string | undefined = undefined;
try { try {
res = await requestQueueProductPublishes.enqueue((): Promise<string> => { res = await requestQueueProductPublishes.enqueue(
return getArbitraryResource(`/arbitrary/${item?.service}/${item?.name}/${item?.identifier}?encoding=base64`, key) (): Promise<string> => {
}); return getArbitraryResource(
} catch (error) { `/arbitrary/${item?.service}/${item?.name}/${item?.identifier}?encoding=base64`,
hasFailedToDownload = true key
);
} }
if(res === 'canceled') return false );
} catch (error) {
hasFailedToDownload = true;
}
if (res === "canceled") return false;
if (hasFailedToDownload) { if (hasFailedToDownload) {
await new Promise((res) => { await new Promise((res) => {
setTimeout(() => { setTimeout(() => {
res(null) res(null);
}, 15000) }, 15000);
}) });
try { try {
res = await requestQueueProductPublishesBackup.enqueue((): Promise<string> => { res = await requestQueueProductPublishesBackup.enqueue(
return getArbitraryResource(`/arbitrary/${item?.service}/${item?.name}/${item?.identifier}?encoding=base64`, key) (): Promise<string> => {
}); return getArbitraryResource(
`/arbitrary/${item?.service}/${item?.name}/${item?.identifier}?encoding=base64`,
key
);
}
);
} catch (error) { } catch (error) {
setResourceCache(`${item?.service}-${item?.name}-${item?.identifier}`, false); setResourceCache(
return false `${item?.service}-${item?.name}-${item?.identifier}`,
false
);
return false;
} }
} }
if (res) { if (res) {
const toUint = base64ToUint8Array(res); const toUint = base64ToUint8Array(res);
const toObject = uint8ArrayToObject(toUint); const toObject = uint8ArrayToObject(toUint);
const fullDataObject = { data: {...toObject}, qortalMetadata: item }; const fullDataObject = {
setResourceCache(`${item?.service}-${item?.name}-${item?.identifier}`, fullDataObject); data: { ...toObject },
return fullDataObject qortalMetadata: item,
};
setResourceCache(
`${item?.service}-${item?.name}-${item?.identifier}`,
fullDataObject
);
return fullDataObject;
} }
} catch (error) { } catch (error) {
return false return false;
} }
}, },
[getResourceCache, setResourceCache] [getResourceCache, setResourceCache]
@ -107,14 +144,18 @@ export const useResources = () => {
); );
const fetchResources = useCallback( const fetchResources = useCallback(
async (params: QortalSearchParams, listName: string, cancelRequests?: boolean): Promise<QortalMetadata[]> => { async (
params: QortalSearchParams,
listName: string,
cancelRequests?: boolean
): Promise<QortalMetadata[]> => {
if (cancelRequests) { if (cancelRequests) {
cancelAllRequests() cancelAllRequests();
await new Promise((res) => { await new Promise((res) => {
setTimeout(() => { setTimeout(() => {
res(null) res(null);
}, 250); }, 250);
}) });
} }
const cacheKey = generateCacheKey(params); const cacheKey = generateCacheKey(params);
const searchCache = getSearchCache(listName, cacheKey); const searchCache = getSearchCache(listName, cacheKey);
@ -125,12 +166,12 @@ export const useResources = () => {
} else { } else {
const response = await qortalRequest({ const response = await qortalRequest({
action: "SEARCH_QDN_RESOURCES", action: "SEARCH_QDN_RESOURCES",
mode: 'ALL', mode: "ALL",
limit: 20, limit: 20,
...params, ...params,
}); });
if (!response) throw new Error("Unable to fetch resources"); if (!response) throw new Error("Unable to fetch resources");
responseData = response responseData = response;
} }
setSearchCache(listName, cacheKey, responseData); setSearchCache(listName, cacheKey, responseData);
fetchDataFromResults(responseData); fetchDataFromResults(responseData);
@ -140,18 +181,41 @@ export const useResources = () => {
[getSearchCache, setSearchCache, fetchDataFromResults] [getSearchCache, setSearchCache, fetchDataFromResults]
); );
const addNewResources = useCallback(
(listName: string, resources: TemporaryResource[]) => {
addTemporaryResource(
listName,
resources.map((item) => item.qortalMetadata)
);
resources.forEach((temporaryResource) => {
setResourceCache(
`${temporaryResource?.qortalMetadata?.service}-${temporaryResource?.qortalMetadata?.name}-${temporaryResource?.qortalMetadata?.identifier}`,
temporaryResource.data
);
});
},
[]
);
const updateNewResources = useCallback(
(resources: TemporaryResource[]) => {
const addNewResources = useCallback((listName:string, temporaryResources: TemporaryResource[])=> { resources.forEach((temporaryResource) => {
addTemporaryResource(listName, temporaryResources.map((item)=> item.qortalMetadata)) setResourceCache(
}, []) `${temporaryResource?.qortalMetadata?.service}-${temporaryResource?.qortalMetadata?.name}-${temporaryResource?.qortalMetadata?.identifier}`,
temporaryResource.data
);
});
},
[]
);
return { return {
fetchResources, fetchResources,
fetchIndividualPublish, fetchIndividualPublish,
addNewResources addNewResources,
} updateNewResources
} };
};
export const generateCacheKey = (params: QortalSearchParams): string => { export const generateCacheKey = (params: QortalSearchParams): string => {
const { const {
@ -174,7 +238,7 @@ export const generateCacheKey = (params: QortalSearchParams): string => {
limit, limit,
offset, offset,
reverse, reverse,
mode mode,
} = params; } = params;
const keyParts = [ const keyParts = [
@ -204,4 +268,3 @@ export const generateCacheKey = (params: QortalSearchParams): string => {
return keyParts; return keyParts;
}; };

View File

@ -64,23 +64,35 @@ export class RequestQueueWithPromise<T = any> {
} }
export async function retryTransaction(fn, args, throwError, retries) { export async function retryTransaction<T>(
fn: (...args: any[]) => Promise<T>,
args: any[],
throwError: boolean,
retries: number
): Promise<T | null> {
let attempt = 0; let attempt = 0;
while (attempt < retries) { while (attempt < retries) {
try { try {
return await fn(...args); return await fn(...args);
} catch (error) { } catch (error: unknown) {
if (error instanceof Error) {
console.error(`Attempt ${attempt + 1} failed: ${error.message}`); console.error(`Attempt ${attempt + 1} failed: ${error.message}`);
} else {
console.error(`Attempt ${attempt + 1} failed: Unknown error`);
}
attempt++; attempt++;
if (attempt === retries) { if (attempt === retries) {
console.error("Max retries reached. Skipping transaction."); console.error("Max retries reached. Skipping transaction.");
if (throwError) { if (throwError) {
throw new Error(error?.message || "Unable to process transaction") throw new Error(error instanceof Error ? error.message : "Unable to process transaction");
} else { } else {
return null return null;
} }
} }
await new Promise(res => setTimeout(res, 10000)); await new Promise((res) => setTimeout(res, 10000));
} }
} }
return null;
} }