Fix/pagination of sources which require pages to be fetched in order (#451)
* Handle sources that do not support revalidation At least e-hentai is not able to handle out of order pagination requests. E.g. in case the last request was for page 1 and the next one is for page 3, the result will be for page 2, because e-hentai just ignores the request page number and just returns the next page after the last fetched one. * Optionally clear source browse cache when opening component At least e-hentai is not able to handle out of order pagination requests. The revalidation logic will cause exactly this. Due to this, the revalidation for these sources is disabled. To prevent an out of date cache, it gets cleared when routing to the source component
This commit is contained in:
@@ -55,7 +55,11 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
||||
margin: '10px',
|
||||
}}
|
||||
>
|
||||
<CardActionArea component={Link} to={`/sources/${id}`} state={{ contentType: SourceContentType.POPULAR }}>
|
||||
<CardActionArea
|
||||
component={Link}
|
||||
to={`/sources/${id}`}
|
||||
state={{ contentType: SourceContentType.POPULAR, clearCache: true }}
|
||||
>
|
||||
<CardContent
|
||||
sx={{
|
||||
display: 'flex',
|
||||
@@ -105,7 +109,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
||||
variant="outlined"
|
||||
component={Link}
|
||||
to={`/sources/${id}`}
|
||||
state={{ contentType: SourceContentType.LATEST }}
|
||||
state={{ contentType: SourceContentType.LATEST, clearCache: true }}
|
||||
>
|
||||
{t('global.button.latest')}
|
||||
</Button>
|
||||
@@ -117,7 +121,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
||||
variant="outlined"
|
||||
component={Link}
|
||||
to={`/sources/${id}`}
|
||||
state={{ contentType: SourceContentType.LATEST }}
|
||||
state={{ contentType: SourceContentType.LATEST, clearCache: true }}
|
||||
>
|
||||
{t('global.button.latest')}
|
||||
</Button>
|
||||
@@ -126,7 +130,7 @@ export const SourceCard: React.FC<IProps> = (props: IProps) => {
|
||||
variant="outlined"
|
||||
component={Link}
|
||||
to={`/sources/${id}`}
|
||||
state={{ contentType: SourceContentType.POPULAR }}
|
||||
state={{ contentType: SourceContentType.POPULAR, clearCache: true }}
|
||||
>
|
||||
{t('global.button.popular')}
|
||||
</Button>
|
||||
|
||||
@@ -43,6 +43,21 @@ export class CustomCache {
|
||||
return this.keyToResponseMap.get(key) as Response;
|
||||
}
|
||||
|
||||
public getAllKeys(): string[] {
|
||||
return [...this.keyToResponseMap.keys()];
|
||||
}
|
||||
|
||||
public getMatchingKeys(regex: RegExp): string[] {
|
||||
return this.getAllKeys().filter((key) => !!regex.exec(key));
|
||||
}
|
||||
|
||||
public clearFor(...keys: string[]) {
|
||||
keys.forEach((key) => {
|
||||
this.keyToResponseMap.delete(key);
|
||||
this.keyToFetchTimestampMap.delete(key);
|
||||
});
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.keyToResponseMap.clear();
|
||||
this.keyToFetchTimestampMap.clear();
|
||||
|
||||
@@ -295,6 +295,16 @@ export type AbortableApolloMutationResponse<Data = any> = { response: Promise<Fe
|
||||
|
||||
const EXTENSION_LIST_CACHE_KEY = 'useExtensionListFetch';
|
||||
|
||||
const CACHE_INITIAL_PAGES_FETCHING_KEY = 'GET_SOURCE_MANGAS_FETCH_FETCHING_INITIAL_PAGES';
|
||||
const CACHE_PAGES_KEY = 'GET_SOURCE_MANGAS_FETCH_PAGES';
|
||||
const CACHE_RESULTS_KEY = 'GET_SOURCE_MANGAS_FETCH';
|
||||
|
||||
export const SPECIAL_ED_SOURCES = {
|
||||
REVALIDATION: [
|
||||
'57122881048805941', // e-hentai
|
||||
],
|
||||
};
|
||||
|
||||
// TODO - correctly update cache after all mutations instead of refetching queries
|
||||
export class RequestManager {
|
||||
public static readonly API_VERSION = '/api/v1/';
|
||||
@@ -322,6 +332,14 @@ export class RequestManager {
|
||||
return `${this.getBaseUrl()}${apiVersion}${endpoint}`;
|
||||
}
|
||||
|
||||
public clearBrowseCacheFor(sourceId: string) {
|
||||
const cacheKeys = this.cache.getMatchingKeys(
|
||||
new RegExp(`${CACHE_INITIAL_PAGES_FETCHING_KEY}|${CACHE_PAGES_KEY}|${CACHE_RESULTS_KEY}.*${sourceId}`),
|
||||
);
|
||||
|
||||
this.cache.clearFor(...cacheKeys);
|
||||
}
|
||||
|
||||
private createAbortController(): { signal: AbortSignal } & AbortableRequest {
|
||||
const abortController = new AbortController();
|
||||
const abortRequest = (reason?: any): void => {
|
||||
@@ -356,6 +374,7 @@ export class RequestManager {
|
||||
}
|
||||
|
||||
private async revalidatePage<Data = any, Variables extends OperationVariables = OperationVariables>(
|
||||
sourceId: string,
|
||||
cacheResultsKey: string,
|
||||
cachePagesKey: string,
|
||||
getVariablesFor: (page: number) => Variables,
|
||||
@@ -369,6 +388,10 @@ export class RequestManager {
|
||||
maxPage: number,
|
||||
signal: AbortSignal,
|
||||
): Promise<void> {
|
||||
if (SPECIAL_ED_SOURCES.REVALIDATION.includes(sourceId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { response: revalidationRequest } = this.doRequest(
|
||||
GQLMethod.MUTATION,
|
||||
GET_SOURCE_MANGAS_FETCH,
|
||||
@@ -406,6 +429,7 @@ export class RequestManager {
|
||||
|
||||
if (isCachedPageInvalid && pageToRevalidate < maxPage) {
|
||||
await this.revalidatePage(
|
||||
sourceId,
|
||||
cacheResultsKey,
|
||||
cachePagesKey,
|
||||
getVariablesFor,
|
||||
@@ -965,10 +989,6 @@ export class RequestManager {
|
||||
},
|
||||
});
|
||||
|
||||
const CACHE_INITIAL_PAGES_FETCHING_KEY = 'GET_SOURCE_MANGAS_FETCH_FETCHING_INITIAL_PAGES';
|
||||
const CACHE_PAGES_KEY = 'GET_SOURCE_MANGAS_FETCH_PAGES';
|
||||
const CACHE_RESULTS_KEY = 'GET_SOURCE_MANGAS_FETCH';
|
||||
|
||||
const isRevalidationDoneRef = useRef(false);
|
||||
const activeRevalidationRef = useRef<
|
||||
| [
|
||||
@@ -1019,6 +1039,7 @@ export class RequestManager {
|
||||
|
||||
const revalidatePage = async (pageToRevalidate: number, maxPage: number, signal: AbortSignal) =>
|
||||
this.revalidatePage(
|
||||
input.source,
|
||||
CACHE_RESULTS_KEY,
|
||||
CACHE_PAGES_KEY,
|
||||
getVariablesFor,
|
||||
|
||||
@@ -18,7 +18,11 @@ import FavoriteIcon from '@mui/icons-material/Favorite';
|
||||
import NewReleasesIcon from '@mui/icons-material/NewReleases';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
import { TPartialManga, TranslationKey } from '@/typings';
|
||||
import { requestManager, AbortableApolloUseMutationPaginatedResponse } from '@/lib/requests/RequestManager.ts';
|
||||
import {
|
||||
requestManager,
|
||||
AbortableApolloUseMutationPaginatedResponse,
|
||||
SPECIAL_ED_SOURCES,
|
||||
} from '@/lib/requests/RequestManager.ts';
|
||||
import { useDebounce } from '@/components/manga/hooks';
|
||||
import { useLibraryOptionsContext } from '@/components/context/LibraryOptionsContext';
|
||||
import { SourceGridLayout } from '@/components/source/GridLayouts';
|
||||
@@ -203,11 +207,12 @@ export function SourceMangas() {
|
||||
const {
|
||||
contentType: currentLocationContentType = SourceContentType.POPULAR,
|
||||
filtersToApply: currentLocationFiltersToApply = [],
|
||||
} =
|
||||
useLocation<{
|
||||
contentType: SourceContentType;
|
||||
filtersToApply: IPos[];
|
||||
}>().state ?? {};
|
||||
clearCache = false,
|
||||
} = useLocation<{
|
||||
contentType: SourceContentType;
|
||||
filtersToApply: IPos[];
|
||||
clearCache: boolean;
|
||||
}>().state ?? {};
|
||||
|
||||
const { options } = useLibraryOptionsContext();
|
||||
const [query] = useQueryParam('query', StringParam);
|
||||
@@ -288,6 +293,23 @@ export function SourceMangas() {
|
||||
setResetScrollPosition(true);
|
||||
}, [sourceId, contentType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!clearCache) {
|
||||
return;
|
||||
}
|
||||
|
||||
const requiresClear = SPECIAL_ED_SOURCES.REVALIDATION.includes(sourceId);
|
||||
if (!requiresClear) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestManager.clearBrowseCacheFor(sourceId);
|
||||
navigate('', {
|
||||
replace: true,
|
||||
state: { contentType: currentLocationContentType, filters: currentLocationFiltersToApply },
|
||||
});
|
||||
}, [clearCache]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (contentType !== SourceContentType.SEARCH) {
|
||||
|
||||
Reference in New Issue
Block a user