Fix/chapter not getting deleted after being read (#532)

* Create permanent subscriptions

The subscriptions have to be permanently open, otherwise, the cache gets outdated.

E.g. in case a view is open, which does not subscribe to the download updates, finished downloads are never received and thus, data of existing chapters/mangas in the cache get outdated

* [Codegen][Tool] Get latest data from cache for automatic chapter deletion after reading

The manga chapters query is on "standby" to prevent the reader from rerender in case the underlying query data updates, which would cause the reader to jump back to the beginning of the last read page.
Thus, when checking if the chapter should be automatically deleted, the data from the query can't be used, since it is outdated.
This commit is contained in:
schroda
2024-01-01 22:40:39 +01:00
committed by GitHub
parent 7d3d82556a
commit 05fa3d0732
12 changed files with 93 additions and 32 deletions

View File

@@ -35,6 +35,7 @@ import { DownloadSettings } from '@/screens/settings/DownloadSettings.tsx';
import { ServerSettings } from '@/screens/settings/ServerSettings.tsx';
import { WebUISettings } from '@/screens/settings/WebUISettings.tsx';
import { ServerUpdateChecker } from '@/components/settings/ServerUpdateChecker.tsx';
import { requestManager } from '@/lib/requests/RequestManager.ts';
if (process.env.NODE_ENV !== 'production') {
// Adds messages only in a dev environment
@@ -52,10 +53,25 @@ const ScrollToTop = () => {
return null;
};
/**
* Creates permanent subscriptions to always have the latest data.
*
* E.g. in case a view is open, which does not subscribe to the download updates, finished downloads are never received
* and thus, data of existing chapters/mangas in the cache get outdated
*/
const BackgroundSubscriptions = () => {
requestManager.useDownloadSubscription();
requestManager.useUpdaterSubscription();
requestManager.useWebUIUpdateSubscription();
return null;
};
export const App: React.FC = () => (
<AppContext>
<ScrollToTop />
<ServerUpdateChecker />
<BackgroundSubscriptions />
<CssBaseline />
<DefaultNavBar />
<Container

View File

@@ -66,8 +66,8 @@ interface IProps {
export const ChapterList: React.FC<IProps> = ({ manga, isRefreshing }) => {
const { t } = useTranslation();
const { data: downloaderData } = requestManager.useDownloadSubscription();
const queue = (downloaderData?.downloadChanged.queue as DownloadType[]) ?? [];
const { data: downloaderData } = requestManager.useGetDownloadStatus();
const queue = (downloaderData?.downloadStatus.queue as DownloadType[]) ?? [];
const [options, dispatch] = useChapterOptions(manga.id);
const { data: chaptersData, loading: isLoading } = requestManager.useGetMangaChapters(manga.id);

View File

@@ -38,8 +38,8 @@ export function UpdateChecker({ handleFinishedUpdate }: { handleFinishedUpdate?:
const { data: lastUpdateTimestampData, refetch: reFetchLastTimestamp } =
requestManager.useGetLastGlobalUpdateTimestamp();
const lastUpdateTimestamp = lastUpdateTimestampData?.lastUpdateTimestamp.timestamp;
const { data: updaterData } = requestManager.useUpdaterSubscription();
const status = updaterData?.updateStatusChanged;
const { data: updaterData } = requestManager.useGetGlobalUpdateSummary();
const status = updaterData?.updateStatus;
const loading = !!status?.isRunning;
const progress = useMemo(

View File

@@ -1,8 +1,8 @@
import {FieldPolicy, FieldReadFunction, Reference, TypePolicies, TypePolicy} from '@apollo/client/cache';
import {
GetCategoryQueryVariables, GetChapterQueryVariables,
GetChaptersQuery, GetExtensionQueryVariables, GetGlobalMetadataQueryVariables,
GetMangaQueryVariables, GetSourceQueryVariables,
GetChaptersQuery, GetDownloadStatusQueryVariables, GetExtensionQueryVariables, GetGlobalMetadataQueryVariables,
GetMangaQueryVariables, GetSourceQueryVariables, GetUpdateStatusQueryVariables, GetWebuiUpdateStatusQueryVariables,
} from "@/lib/graphql/generated/graphql.ts";
import {FieldFunctionOptions} from "@apollo/client/cache/inmemory/policies";
export type AboutServerPayloadKeySpecifier = ('buildTime' | 'buildType' | 'discord' | 'github' | 'name' | 'revision' | 'version' | AboutServerPayloadKeySpecifier)[];
@@ -518,10 +518,10 @@ export type QueryFieldPolicy = {
chapters?: FieldPolicy<GetChaptersQuery['chapters']> | FieldReadFunction<GetChaptersQuery['chapters']>,
checkForServerUpdates?: FieldPolicy<any> | FieldReadFunction<any>,
checkForWebUIUpdate?: FieldPolicy<any> | FieldReadFunction<any>,
downloadStatus?: FieldPolicy<any> | FieldReadFunction<any>,
downloadStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetDownloadStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetDownloadStatusQueryVariables>>,
extension?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetExtensionQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetExtensionQueryVariables>>,
extensions?: FieldPolicy<any> | FieldReadFunction<any>,
getWebUIUpdateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
getWebUIUpdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>>,
lastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
manga?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetMangaQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetMangaQueryVariables>>,
mangas?: FieldPolicy<any> | FieldReadFunction<any>,
@@ -531,7 +531,7 @@ export type QueryFieldPolicy = {
settings?: FieldPolicy<any> | FieldReadFunction<any>,
source?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetSourceQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetSourceQueryVariables>>,
sources?: FieldPolicy<any> | FieldReadFunction<any>,
updateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
updateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetUpdateStatusQueryVariables>>,
validateBackup?: FieldPolicy<any> | FieldReadFunction<any>
};
export type ReorderChapterDownloadPayloadKeySpecifier = ('clientMutationId' | 'downloadStatus' | ReorderChapterDownloadPayloadKeySpecifier)[];

View File

@@ -2369,14 +2369,14 @@ export type UpdateExtensionMutationVariables = Exact<{
}>;
export type UpdateExtensionMutation = { __typename?: 'Mutation', updateExtension: { __typename?: 'UpdateExtensionPayload', clientMutationId?: string | null, extension: { __typename?: 'ExtensionType', apkName: string, versionName: string, versionCode: number, isInstalled: boolean, isObsolete: boolean, hasUpdate: boolean } } };
export type UpdateExtensionMutation = { __typename?: 'Mutation', updateExtension: { __typename?: 'UpdateExtensionPayload', clientMutationId?: string | null, extension: { __typename?: 'ExtensionType', pkgName: string, apkName: string, versionName: string, versionCode: number, isInstalled: boolean, isObsolete: boolean, hasUpdate: boolean } } };
export type UpdateExtensionsMutationVariables = Exact<{
input: UpdateExtensionsInput;
}>;
export type UpdateExtensionsMutation = { __typename?: 'Mutation', updateExtensions: { __typename?: 'UpdateExtensionsPayload', clientMutationId?: string | null, extensions: Array<{ __typename?: 'ExtensionType', apkName: string, versionName: string, versionCode: number, isInstalled: boolean, isObsolete: boolean, hasUpdate: boolean }> } };
export type UpdateExtensionsMutation = { __typename?: 'Mutation', updateExtensions: { __typename?: 'UpdateExtensionsPayload', clientMutationId?: string | null, extensions: Array<{ __typename?: 'ExtensionType', pkgName: string, apkName: string, versionName: string, versionCode: number, isInstalled: boolean, isObsolete: boolean, hasUpdate: boolean }> } };
export type InstallExternalExtensionMutationVariables = Exact<{
file: Scalars['Upload']['input'];

View File

@@ -170,10 +170,17 @@ import {
UpdateMangasCategoriesMutation,
UpdateMangasCategoriesMutationVariables,
UpdateMangaCategoriesPatchInput,
GetWebuiUpdateStatusQuery,
GetWebuiUpdateStatusQueryVariables,
} from '@/lib/graphql/generated/graphql.ts';
import { GET_GLOBAL_METADATAS } from '@/lib/graphql/queries/GlobalMetadataQuery.ts';
import { SET_GLOBAL_METADATA } from '@/lib/graphql/mutations/GlobalMetadataMutation.ts';
import { CHECK_FOR_SERVER_UPDATES, CHECK_FOR_WEBUI_UPDATE, GET_ABOUT } from '@/lib/graphql/queries/ServerInfoQuery.ts';
import {
CHECK_FOR_SERVER_UPDATES,
CHECK_FOR_WEBUI_UPDATE,
GET_ABOUT,
GET_WEBUI_UPDATE_STATUS,
} from '@/lib/graphql/queries/ServerInfoQuery.ts';
import { GET_EXTENSIONS } from '@/lib/graphql/queries/ExtensionQuery.ts';
import {
GET_EXTENSIONS_FETCH,
@@ -2104,8 +2111,8 @@ export class RequestManager {
}
public useGetDownloadStatus(
options?: SubscriptionHookOptions<GetDownloadStatusQuery, GetDownloadStatusQueryVariables>,
): SubscriptionResult<GetDownloadStatusQuery, GetDownloadStatusQueryVariables> {
options?: QueryHookOptions<GetDownloadStatusQuery, GetDownloadStatusQueryVariables>,
): AbortableApolloUseQueryResponse<GetDownloadStatusQuery, GetDownloadStatusQueryVariables> {
return this.doRequest(GQLMethod.USE_QUERY, GET_DOWNLOAD_STATUS, {}, options);
}
@@ -2163,6 +2170,12 @@ export class RequestManager {
): AbortableApolloMutationResponse<ResetWebuiUpdateStatusMutation> {
return this.doRequest(GQLMethod.MUTATION, RESET_WEBUI_UPDATE_STATUS, undefined, options);
}
public useGetWebUIUpdateStatus(
options?: QueryHookOptions<GetWebuiUpdateStatusQuery, GetWebuiUpdateStatusQueryVariables>,
): AbortableApolloUseQueryResponse<GetWebuiUpdateStatusQuery, GetWebuiUpdateStatusQueryVariables> {
return this.doRequest(GQLMethod.USE_QUERY, GET_WEBUI_UPDATE_STATUS, undefined, options);
}
}
export const requestManager = new RequestManager();

View File

@@ -26,6 +26,8 @@ const typePolicies: StrictTypedTypePolicies = {
SettingsType: { keyFields: [] },
DownloadStatus: { keyFields: [] },
DownloadType: { keyFields: ['chapter'] },
WebUIUpdateStatus: { keyFields: [] },
UpdateStatus: { keyFields: [] },
Query: {
fields: {
manga(_, { args, toReference }) {
@@ -58,6 +60,21 @@ const typePolicies: StrictTypedTypePolicies = {
key: args?.key,
});
},
downloadStatus(_, { toReference }) {
return toReference({
__typename: 'DownloadStatus',
key: {},
});
},
getWebUIUpdateStatus(_, { toReference }) {
return toReference({
__typename: 'WebUIUpdateStatus',
key: {},
});
},
updateStatus(_, { toReference }) {
return toReference({ __typename: 'UpdateStatus', key: {} });
},
chapters: {
keyArgs: ['condition', 'filter', 'orderBy', 'orderByType'],
merge(existing, incoming) {

View File

@@ -98,7 +98,6 @@ const DownloadChapterItem = ({
export const DownloadQueue: React.FC = () => {
const { t } = useTranslation();
requestManager.useDownloadSubscription();
const [reorderDownload, { reset: revertReorder }] = requestManager.useReorderChapterInDownloadQueue();
const { data: downloadStatusData, loading: isLoading } = requestManager.useGetDownloadStatus();

View File

@@ -31,6 +31,7 @@ import { useDebounce } from '@/util/useDebounce.ts';
import { UpdateChapterPatchInput } from '@/lib/graphql/generated/graphql.ts';
import { useMetadataServerSettings } from '@/util/metadataServerSettings.ts';
import { defaultPromiseErrorHandler } from '@/util/defaultPromiseErrorHandler.ts';
import { FULL_CHAPTER_FIELDS } from '@/lib/graphql/Fragments.ts';
const isDupChapter = async (chapterIndex: number, currentChapter: TChapter) => {
const nextChapter = await requestManager.getChapter(currentChapter.manga.id, chapterIndex).response;
@@ -191,9 +192,22 @@ export function Reader() {
(mangaChapter) => mangaChapter.sourceOrder === chapterToDeleteSourceOrder,
);
if (!chapterToDelete) {
return -1;
}
const chapterToDeleteUpToDateData = requestManager.graphQLClient.client.cache.readFragment<TChapter>({
id: requestManager.graphQLClient.client.cache.identify({
__typename: 'ChapterType',
id: chapterToDelete.id,
}),
fragment: FULL_CHAPTER_FIELDS,
fragmentName: 'FULL_CHAPTER_FIELDS',
});
const shouldDeleteChapter =
chapterToDelete?.isDownloaded &&
(!chapterToDelete?.isBookmarked || metadataSettings.deleteChaptersWithBookmark);
chapterToDeleteUpToDateData?.isDownloaded &&
(!chapterToDeleteUpToDateData?.isBookmarked || metadataSettings.deleteChaptersWithBookmark);
if (!shouldDeleteChapter) {
return -1;
}

View File

@@ -117,8 +117,8 @@ export const Updates: React.FC = () => {
const updateEntries = chapterUpdateData?.chapters.nodes ?? [];
const groupedUpdates = useMemo(() => groupByDate(updateEntries), [updateEntries]);
const groupCounts: number[] = useMemo(() => groupedUpdates.map((group) => group[1]), [groupedUpdates]);
const { data: downloaderData } = requestManager.useDownloadSubscription();
const queue = (downloaderData?.downloadChanged.queue as DownloadType[]) ?? [];
const { data: downloaderData } = requestManager.useGetDownloadStatus();
const queue = (downloaderData?.downloadStatus.queue as DownloadType[]) ?? [];
const lastUpdateTimestampCompRef = useRef<HTMLElement>(null);
const [lastUpdateTimestampCompHeight, setLastUpdateTimestampCompHeight] = useState(0);

View File

@@ -211,9 +211,11 @@ export function About() {
} = requestManager.useCheckForWebUIUpdate({ notifyOnNetworkStatusChange: true });
const webUIUpdateCheckError = orgWebUIUpdateCheckError || webUIUpdateData?.checkForWebUIUpdate.tag === '';
const { data: webUIUpdateStatusData } = requestManager.useWebUIUpdateSubscription();
const { state: webUIUpdateState, progress: webUIUpdateProgress } =
webUIUpdateStatusData?.webUIUpdateStatusChange ?? { state: UpdateState.Idle, progress: 0 };
const { data: webUIUpdateStatusData } = requestManager.useGetWebUIUpdateStatus();
const { state: webUIUpdateState, progress: webUIUpdateProgress } = webUIUpdateStatusData?.getWebUIUpdateStatus ?? {
state: UpdateState.Idle,
progress: 0,
};
useEffect(() => {
const isError = webUIUpdateState === UpdateState.Error;
@@ -236,7 +238,7 @@ export function About() {
makeToast(
t('settings.about.webui.label.update_success', {
version: webUIUpdateStatusData!.webUIUpdateStatusChange.info.tag,
version: webUIUpdateStatusData!.getWebUIUpdateStatus.info.tag,
}),
'success',
);
@@ -245,16 +247,16 @@ export function About() {
fragment: ABOUT_WEBUI,
data: {
__typename: 'AboutWebUI',
channel: webUIUpdateStatusData!.webUIUpdateStatusChange.info.channel,
tag: webUIUpdateStatusData!.webUIUpdateStatusChange.info.tag,
channel: webUIUpdateStatusData!.getWebUIUpdateStatus.info.channel,
tag: webUIUpdateStatusData!.getWebUIUpdateStatus.info.tag,
},
});
requestManager.graphQLClient.client.cache.writeFragment({
fragment: WEBUI_UPDATE_CHECK,
data: {
__typename: 'WebUIUpdateCheck',
channel: webUIUpdateStatusData!.webUIUpdateStatusChange.info.channel,
tag: webUIUpdateStatusData!.webUIUpdateStatusChange.info.tag,
channel: webUIUpdateStatusData!.getWebUIUpdateStatus.info.channel,
tag: webUIUpdateStatusData!.getWebUIUpdateStatus.info.tag,
updateAvailable: false,
},
});

View File

@@ -54,8 +54,8 @@ const addImports = format(
`import {FieldPolicy, FieldReadFunction, Reference, TypePolicies, TypePolicy} from '@apollo/client/cache';
import {
\tGetCategoryQueryVariables, GetChapterQueryVariables,
\tGetChaptersQuery, GetExtensionQueryVariables, GetGlobalMetadataQueryVariables,
\tGetMangaQueryVariables, GetSourceQueryVariables,
\tGetChaptersQuery, GetDownloadStatusQueryVariables, GetExtensionQueryVariables, GetGlobalMetadataQueryVariables,
\tGetMangaQueryVariables, GetSourceQueryVariables, GetUpdateStatusQueryVariables, GetWebuiUpdateStatusQueryVariables,
} from "@/lib/graphql/generated/graphql.ts";
import {FieldFunctionOptions} from "@apollo/client/cache/inmemory/policies";`,
);
@@ -95,10 +95,10 @@ const fixTypingOfQueryTypePolicies = format(
\tchapters?: FieldPolicy<GetChaptersQuery['chapters']> | FieldReadFunction<GetChaptersQuery['chapters']>,
\tcheckForServerUpdates?: FieldPolicy<any> | FieldReadFunction<any>,
\tcheckForWebUIUpdate?: FieldPolicy<any> | FieldReadFunction<any>,
\tdownloadStatus?: FieldPolicy<any> | FieldReadFunction<any>,
\tdownloadStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetDownloadStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetDownloadStatusQueryVariables>>,
\textension?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetExtensionQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetExtensionQueryVariables>>,
\textensions?: FieldPolicy<any> | FieldReadFunction<any>,
\tgetWebUIUpdateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
\tgetWebUIUpdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetWebuiUpdateStatusQueryVariables>>,
\tlastUpdateTimestamp?: FieldPolicy<any> | FieldReadFunction<any>,
\tmanga?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetMangaQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetMangaQueryVariables>>,
\tmangas?: FieldPolicy<any> | FieldReadFunction<any>,
@@ -108,7 +108,7 @@ const fixTypingOfQueryTypePolicies = format(
\tsettings?: FieldPolicy<any> | FieldReadFunction<any>,
\tsource?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetSourceQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetSourceQueryVariables>>,
\tsources?: FieldPolicy<any> | FieldReadFunction<any>,
\tupdateStatus?: FieldPolicy<any> | FieldReadFunction<any>,
\tupdateStatus?: FieldPolicy<Reference, Reference, Reference, FieldFunctionOptions<GetUpdateStatusQueryVariables>> | FieldReadFunction<Reference, Reference, FieldFunctionOptions<GetUpdateStatusQueryVariables>>,
\tvalidateBackup?: FieldPolicy<any> | FieldReadFunction<any>
};`,
);