diff --git a/src/App.tsx b/src/App.tsx index 7e56579875..badfbd45d1 100644 --- a/src/App.tsx +++ b/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 = () => ( + = ({ 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); diff --git a/src/components/library/UpdateChecker.tsx b/src/components/library/UpdateChecker.tsx index a6955d1cc0..8fc718c6e2 100644 --- a/src/components/library/UpdateChecker.tsx +++ b/src/components/library/UpdateChecker.tsx @@ -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( diff --git a/src/lib/graphql/generated/apollo-helpers.ts b/src/lib/graphql/generated/apollo-helpers.ts index 9a0849678f..7ab312c21c 100644 --- a/src/lib/graphql/generated/apollo-helpers.ts +++ b/src/lib/graphql/generated/apollo-helpers.ts @@ -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 | FieldReadFunction, checkForServerUpdates?: FieldPolicy | FieldReadFunction, checkForWebUIUpdate?: FieldPolicy | FieldReadFunction, - downloadStatus?: FieldPolicy | FieldReadFunction, + downloadStatus?: FieldPolicy> | FieldReadFunction>, extension?: FieldPolicy> | FieldReadFunction>, extensions?: FieldPolicy | FieldReadFunction, - getWebUIUpdateStatus?: FieldPolicy | FieldReadFunction, + getWebUIUpdateStatus?: FieldPolicy> | FieldReadFunction>, lastUpdateTimestamp?: FieldPolicy | FieldReadFunction, manga?: FieldPolicy> | FieldReadFunction>, mangas?: FieldPolicy | FieldReadFunction, @@ -531,7 +531,7 @@ export type QueryFieldPolicy = { settings?: FieldPolicy | FieldReadFunction, source?: FieldPolicy> | FieldReadFunction>, sources?: FieldPolicy | FieldReadFunction, - updateStatus?: FieldPolicy | FieldReadFunction, + updateStatus?: FieldPolicy> | FieldReadFunction>, validateBackup?: FieldPolicy | FieldReadFunction }; export type ReorderChapterDownloadPayloadKeySpecifier = ('clientMutationId' | 'downloadStatus' | ReorderChapterDownloadPayloadKeySpecifier)[]; diff --git a/src/lib/graphql/generated/graphql.ts b/src/lib/graphql/generated/graphql.ts index e6ea6ad3d0..aacede82d8 100644 --- a/src/lib/graphql/generated/graphql.ts +++ b/src/lib/graphql/generated/graphql.ts @@ -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']; diff --git a/src/lib/requests/RequestManager.ts b/src/lib/requests/RequestManager.ts index ed7fc7f206..2a59c97dd4 100644 --- a/src/lib/requests/RequestManager.ts +++ b/src/lib/requests/RequestManager.ts @@ -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, - ): SubscriptionResult { + options?: QueryHookOptions, + ): AbortableApolloUseQueryResponse { return this.doRequest(GQLMethod.USE_QUERY, GET_DOWNLOAD_STATUS, {}, options); } @@ -2163,6 +2170,12 @@ export class RequestManager { ): AbortableApolloMutationResponse { return this.doRequest(GQLMethod.MUTATION, RESET_WEBUI_UPDATE_STATUS, undefined, options); } + + public useGetWebUIUpdateStatus( + options?: QueryHookOptions, + ): AbortableApolloUseQueryResponse { + return this.doRequest(GQLMethod.USE_QUERY, GET_WEBUI_UPDATE_STATUS, undefined, options); + } } export const requestManager = new RequestManager(); diff --git a/src/lib/requests/client/GraphQLClient.ts b/src/lib/requests/client/GraphQLClient.ts index d5b42fd03f..62c3a6bdf6 100644 --- a/src/lib/requests/client/GraphQLClient.ts +++ b/src/lib/requests/client/GraphQLClient.ts @@ -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) { diff --git a/src/screens/DownloadQueue.tsx b/src/screens/DownloadQueue.tsx index fbc07787bf..f19dfb0e4d 100644 --- a/src/screens/DownloadQueue.tsx +++ b/src/screens/DownloadQueue.tsx @@ -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(); diff --git a/src/screens/Reader.tsx b/src/screens/Reader.tsx index 184cd18e3e..fb4a11929c 100644 --- a/src/screens/Reader.tsx +++ b/src/screens/Reader.tsx @@ -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({ + 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; } diff --git a/src/screens/Updates.tsx b/src/screens/Updates.tsx index 15f998ce1e..3fbe58aa3f 100644 --- a/src/screens/Updates.tsx +++ b/src/screens/Updates.tsx @@ -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(null); const [lastUpdateTimestampCompHeight, setLastUpdateTimestampCompHeight] = useState(0); diff --git a/src/screens/settings/About.tsx b/src/screens/settings/About.tsx index 1ad1684271..8ce173b7f0 100644 --- a/src/screens/settings/About.tsx +++ b/src/screens/settings/About.tsx @@ -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, }, }); diff --git a/tools/scripts/codegenFormatter.ts b/tools/scripts/codegenFormatter.ts index e56e27528b..7f749ac6a7 100644 --- a/tools/scripts/codegenFormatter.ts +++ b/tools/scripts/codegenFormatter.ts @@ -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 | FieldReadFunction, \tcheckForServerUpdates?: FieldPolicy | FieldReadFunction, \tcheckForWebUIUpdate?: FieldPolicy | FieldReadFunction, -\tdownloadStatus?: FieldPolicy | FieldReadFunction, +\tdownloadStatus?: FieldPolicy> | FieldReadFunction>, \textension?: FieldPolicy> | FieldReadFunction>, \textensions?: FieldPolicy | FieldReadFunction, -\tgetWebUIUpdateStatus?: FieldPolicy | FieldReadFunction, +\tgetWebUIUpdateStatus?: FieldPolicy> | FieldReadFunction>, \tlastUpdateTimestamp?: FieldPolicy | FieldReadFunction, \tmanga?: FieldPolicy> | FieldReadFunction>, \tmangas?: FieldPolicy | FieldReadFunction, @@ -108,7 +108,7 @@ const fixTypingOfQueryTypePolicies = format( \tsettings?: FieldPolicy | FieldReadFunction, \tsource?: FieldPolicy> | FieldReadFunction>, \tsources?: FieldPolicy | FieldReadFunction, -\tupdateStatus?: FieldPolicy | FieldReadFunction, +\tupdateStatus?: FieldPolicy> | FieldReadFunction>, \tvalidateBackup?: FieldPolicy | FieldReadFunction };`, );