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:
16
src/App.tsx
16
src/App.tsx
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)[];
|
||||
|
||||
@@ -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'];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
};`,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user