Reduce source requests during bulk migration

During the search the destination entries are already freshly fetched. Thus, this is not necessary anymore during the actual migration
This commit is contained in:
schroda
2026-05-08 16:28:53 +02:00
parent 3ba5edefbd
commit 4c00d487fe
3 changed files with 103 additions and 49 deletions

View File

@@ -126,7 +126,7 @@ export const MangaCard = memo((props: MangaCardProps) => {
try { try {
try { try {
await MangaMigration.migrate(migrationSourceMangaId, id, options); await MangaMigration.migrateByIdWithFetch(migrationSourceMangaId, id, options);
} catch (e) { } catch (e) {
makeToast( makeToast(
t(MANGA_ACTION_TO_TRANSLATION['migrate'].error), t(MANGA_ACTION_TO_TRANSLATION['migrate'].error),

View File

@@ -14,13 +14,28 @@ import type {
} from '@/lib/graphql/generated/graphql.ts'; } from '@/lib/graphql/generated/graphql.ts';
import type { MangaIdInfo } from '@/features/manga/Manga.types.ts'; import type { MangaIdInfo } from '@/features/manga/Manga.types.ts';
import { Chapters } from '@/features/chapter/services/Chapters.ts'; import { Chapters } from '@/features/chapter/services/Chapters.ts';
import { getMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
import { ALL_APP_METADATA_KEY_PREFIXES } from '@/features/metadata/Metadata.constants.ts'; import { ALL_APP_METADATA_KEY_PREFIXES } from '@/features/metadata/Metadata.constants.ts';
import type { MigrateMode, MigrateOptions } from '@/features/migration/Migration.types.ts'; import type { MigrateMode, MigrateOptions } from '@/features/migration/Migration.types.ts';
import type {
ChapterBookmarkInfo,
ChapterDownloadInfo,
ChapterIdInfo,
ChapterNumberInfo,
ChapterReadInfo,
} from '@/features/chapter/Chapter.types.ts';
import type { GqlMetaHolder } from '@/features/metadata/Metadata.types.ts';
import { getMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
type MangaToMigrate = NonNullable<GetMangaToMigrateQuery['manga']>; type MangaToMigrate = NonNullable<GetMangaToMigrateQuery['manga']>;
type MangaToMigrateTo = NonNullable<GetMangaToMigrateToFetchMutation['fetchManga']>['manga']; type MangaToMigrateTo = NonNullable<GetMangaToMigrateToFetchMutation['fetchManga']>['manga'];
export type MigrationChapter = ChapterIdInfo &
ChapterReadInfo &
ChapterBookmarkInfo &
ChapterNumberInfo &
ChapterDownloadInfo &
GqlMetaHolder;
type MigrateAction = { copy: () => Promise<unknown>[]; cleanup: () => Promise<unknown>[] }; type MigrateAction = { copy: () => Promise<unknown>[]; cleanup: () => Promise<unknown>[] };
type MigrateActionCreator = () => MigrateAction; type MigrateActionCreator = () => MigrateAction;
@@ -29,8 +44,9 @@ const performMigrationAction = async (migrateAction: keyof MigrateAction, ...act
export class MangaMigration { export class MangaMigration {
static async migrate( static async migrate(
mangaId: MangaIdInfo['id'], mangaToMigrate: MangaToMigrate | null | undefined,
mangaIdToMigrateTo: number, mangaToMigrateTo: MangaToMigrateTo | null | undefined,
chaptersToMigrateTo: MigrationChapter[] | null | undefined,
{ {
mode, mode,
migrateChapters, migrateChapters,
@@ -39,37 +55,16 @@ export class MangaMigration {
deleteChapters, deleteChapters,
migrateMetadata, migrateMetadata,
}: Omit<MigrateOptions, 'mangaIdToMigrateTo'>, }: Omit<MigrateOptions, 'mangaIdToMigrateTo'>,
removeMangaFromCategories: boolean,
): Promise<void> { ): Promise<void> {
const [{ data: mangaToMigrateData }, { data: mangaToMigrateToData }, { removeMangaFromCategories }] = if (!mangaToMigrate || !mangaToMigrateTo) {
await Promise.all([
requestManager.getMangaToMigrate(mangaId, {
migrateChapters,
migrateCategories,
migrateTracking,
deleteChapters,
migrateMetadata,
}).response,
requestManager.getMangaToMigrateToFetch(mangaIdToMigrateTo, {
migrateChapters,
migrateCategories,
migrateTracking,
apolloOptions: { errorPolicy: 'all' },
}).response,
getMetadataServerSettings(),
]);
if (!mangaToMigrateData?.manga || !mangaToMigrateToData?.fetchManga?.manga) {
throw new Error('MangaMigration::migrate: missing manga data'); throw new Error('MangaMigration::migrate: missing manga data');
} }
if (migrateChapters && !mangaToMigrateData.manga.chapters) { if (migrateChapters && !mangaToMigrate.chapters) {
throw new Error('MangaMigration::migrate: missing chapters data'); throw new Error('MangaMigration::migrate: missing chapters data');
} }
if (!mangaToMigrateToData.fetchChapters?.chapters) {
mangaToMigrateToData.fetchChapters = { chapters: [] };
}
const performMigrationActions = async (...actionCreators: [boolean | undefined, MigrateActionCreator][]) => { const performMigrationActions = async (...actionCreators: [boolean | undefined, MigrateActionCreator][]) => {
const migrationActions: TupleUnion<keyof MigrateAction> = ['copy', 'cleanup']; const migrationActions: TupleUnion<keyof MigrateAction> = ['copy', 'cleanup'];
@@ -92,28 +87,20 @@ export class MangaMigration {
() => () =>
MangaMigration.migrateChapters( MangaMigration.migrateChapters(
mode, mode,
mangaToMigrateData.manga, mangaToMigrate.chapters?.nodes,
mangaToMigrateToData, chaptersToMigrateTo,
!!deleteChapters, !!deleteChapters,
!!migrateMetadata, !!migrateMetadata,
), ),
], ],
[ [migrateTracking, () => MangaMigration.migrateTracking(mode, mangaToMigrate, mangaToMigrateTo)],
migrateTracking,
() =>
MangaMigration.migrateTracking(
mode,
mangaToMigrateData.manga,
mangaToMigrateToData.fetchManga!.manga,
),
],
[ [
true, true,
() => () =>
MangaMigration.migrateManga( MangaMigration.migrateManga(
mode, mode,
mangaToMigrateData.manga, mangaToMigrate,
mangaToMigrateToData.fetchManga!.manga, mangaToMigrateTo,
!!migrateCategories, !!migrateCategories,
removeMangaFromCategories, removeMangaFromCategories,
!!migrateMetadata, !!migrateMetadata,
@@ -122,20 +109,86 @@ export class MangaMigration {
); );
} }
static async migrateByIdWithQuery(
mangaId: MangaIdInfo['id'],
mangaIdToMigrateTo: number,
options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>,
): Promise<void> {
const { migrateChapters, migrateCategories, migrateTracking, deleteChapters, migrateMetadata } = options;
const [{ data: mangaToMigrateData }, { data: mangaToMigrateToData }, { removeMangaFromCategories }] =
await Promise.all([
requestManager.getMangaToMigrate(mangaId, {
migrateChapters,
migrateCategories,
migrateTracking,
deleteChapters,
migrateMetadata,
}).response,
requestManager.getMangaToMigrate(mangaIdToMigrateTo, {
migrateChapters,
migrateCategories,
migrateTracking,
deleteChapters,
migrateMetadata,
}).response,
getMetadataServerSettings(),
]);
await MangaMigration.migrate(
mangaToMigrateData?.manga,
mangaToMigrateToData?.manga,
mangaToMigrateToData?.manga?.chapters?.nodes,
options,
removeMangaFromCategories,
);
}
static async migrateByIdWithFetch(
mangaId: MangaIdInfo['id'],
mangaIdToMigrateTo: number,
options: Omit<MigrateOptions, 'mangaIdToMigrateTo'>,
): Promise<void> {
const { migrateChapters, migrateCategories, migrateTracking, deleteChapters, migrateMetadata } = options;
const [{ data: mangaToMigrateData }, { data: mangaToMigrateToData }, { removeMangaFromCategories }] =
await Promise.all([
requestManager.getMangaToMigrate(mangaId, {
migrateChapters,
migrateCategories,
migrateTracking,
deleteChapters,
migrateMetadata,
}).response,
requestManager.getMangaToMigrateToFetch(mangaIdToMigrateTo, {
migrateChapters,
migrateCategories,
migrateTracking,
apolloOptions: { errorPolicy: 'all' },
}).response,
getMetadataServerSettings(),
]);
await MangaMigration.migrate(
mangaToMigrateData?.manga,
mangaToMigrateToData?.fetchManga?.manga,
mangaToMigrateToData?.fetchChapters?.chapters,
options,
removeMangaFromCategories,
);
}
private static migrateChapters( private static migrateChapters(
mode: MigrateMode, mode: MigrateMode,
mangaToMigrate: GetMangaToMigrateQuery['manga'], chaptersToMigrate: MigrationChapter[] | null | undefined,
mangaToMigrateToInfo: GetMangaToMigrateToFetchMutation, chaptersToMigrateTo: MigrationChapter[] | null | undefined,
deleteChapters: boolean, deleteChapters: boolean,
migrateMetadata: boolean, migrateMetadata: boolean,
): MigrateAction { ): MigrateAction {
if (!mangaToMigrate.chapters || !mangaToMigrateToInfo.fetchChapters?.chapters) { if (!chaptersToMigrate || !chaptersToMigrateTo) {
throw new Error('Chapters are missing'); throw new Error('Chapters are missing');
} }
const chaptersToMigrate = mangaToMigrate.chapters.nodes;
const chaptersToMigrateTo = mangaToMigrateToInfo.fetchChapters?.chapters;
const migratableChapters = Chapters.getMatchingChapterNumberChapters(chaptersToMigrate, chaptersToMigrateTo); const migratableChapters = Chapters.getMatchingChapterNumberChapters(chaptersToMigrate, chaptersToMigrateTo);
const highestReadChapterNumber = chaptersToMigrate.reduce((chapterNumber, chapterToMigrate) => { const highestReadChapterNumber = chaptersToMigrate.reduce((chapterNumber, chapterToMigrate) => {
@@ -197,7 +250,7 @@ export class MangaMigration {
? [ ? [
deleteChapters deleteChapters
? requestManager.deleteDownloadedChapters( ? requestManager.deleteDownloadedChapters(
Chapters.getIds(Chapters.getDownloaded(mangaToMigrate.chapters?.nodes ?? [])), Chapters.getIds(Chapters.getDownloaded(chaptersToMigrate)),
).response ).response
: Promise.resolve(), : Promise.resolve(),
] ]

View File

@@ -963,7 +963,8 @@ export class MigrationManager {
} }
assertIsDefined(entry.selectedMatchMangaId); assertIsDefined(entry.selectedMatchMangaId);
await MangaMigration.migrate(mangaId, entry.selectedMatchMangaId, options);
await MangaMigration.migrateByIdWithQuery(mangaId, entry.selectedMatchMangaId, options);
}); });
}); });