diff --git a/src/features/migration/Migration.types.ts b/src/features/migration/Migration.types.ts index ecc8e1a1..266a749b 100644 --- a/src/features/migration/Migration.types.ts +++ b/src/features/migration/Migration.types.ts @@ -61,6 +61,7 @@ export type MetadataMigrationSettings = { export interface MigrationBulkSearchSettings { selectHighestChapterNumberSource: boolean; ignoreOutdatedMatches: boolean; + performAdvancedSearch: boolean; } export enum MigrationPhase { diff --git a/src/features/migration/MigrationManager.ts b/src/features/migration/MigrationManager.ts index 515e2c86..c1a695de 100644 --- a/src/features/migration/MigrationManager.ts +++ b/src/features/migration/MigrationManager.ts @@ -56,6 +56,7 @@ import { MANGA_MIGRATION_FIELDS } from '@/lib/graphql/manga/MangaFragments.ts'; import { ZustandUtil } from '@/lib/zustand/ZustandUtil.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import isEqual from 'lodash/fp/isEqual'; +import uniqBy from 'lodash/fp/uniqBy'; const RESUMABLE_PHASES: readonly MigrationPhase[] = [MigrationPhase.SEARCHING, MigrationPhase.MIGRATING]; @@ -663,66 +664,91 @@ export class MigrationManager { mangaTitle: string, sourceId: SourceIdInfo['id'], signal: AbortSignal, - { selectHighestChapterNumberSource }: MigrationBulkSearchSettings, + { selectHighestChapterNumberSource, performAdvancedSearch }: MigrationBulkSearchSettings, ): Promise { if (signal.aborted) { throw new Error(signal.reason); } - return MigrationManager.getOrCreateSourceQueue(sourceId)(async () => { + if (signal.aborted) { + throw new Error(signal.reason); + } + + if (!selectHighestChapterNumberSource && MigrationManager.hasHigherSourcePriorityMatch(mangaId, sourceId)) { + throw new Error('Entry already has a selected match from a higher priority source'); + } + + const searchQueries = performAdvancedSearch + ? [ + mangaTitle, + ...enhancedCleanup(mangaTitle) + .split(' ') + .filter((query) => query.length >= 3), + ] + : [mangaTitle]; + + const searchRequests = searchQueries.map((query) => + MigrationManager.getOrCreateSourceQueue(sourceId)(() => + requestManager.graphQLClient.client.mutate< + GetMigrationSourceMangasFetchMutation, + GetMigrationSourceMangasFetchMutationVariables + >({ + mutation: GET_MIGRATION_SOURCE_MANGAS_FETCH, + variables: { + input: { + source: sourceId, + query, + page: 1, + type: FetchSourceMangaType.Search, + }, + }, + context: { fetchOptions: { signal } }, + }), + ), + ); + + const searchResponses = await Promise.allSettled(searchRequests); + const successfulSearchResponses = searchResponses + .filter((response) => response.status === 'fulfilled') + .map((response) => response.value); + + if (!successfulSearchResponses.length) { + const failureReasons = searchResponses.map((response) => (response as PromiseRejectedResult).reason); + + throw new Error(`Search failed due to:${failureReasons.join('\n\n')}`); + } + + const searchResults = successfulSearchResponses.flatMap( + (response) => response?.data?.fetchSourceManga?.mangas ?? [], + ); + const uniqueSearchResults = uniqBy('id', searchResults); + const matches = uniqueSearchResults.filter( + (searchMatch) => enhancedCleanup(searchMatch.title) === enhancedCleanup(mangaTitle), + ); + + const matchUpdatePromises = matches.map(async (match) => { if (signal.aborted) { throw new Error(signal.reason); } - if (!selectHighestChapterNumberSource && MigrationManager.hasHigherSourcePriorityMatch(mangaId, sourceId)) { - throw new Error('Entry already has a selected match from a higher priority source'); - } + return (async () => { + try { + const updatedMatch = await requestManager.refreshManga(match.id, { + awaitRefetchQueries: true, + }).response; - const searchResponse = await requestManager.graphQLClient.client.mutate< - GetMigrationSourceMangasFetchMutation, - GetMigrationSourceMangasFetchMutationVariables - >({ - mutation: GET_MIGRATION_SOURCE_MANGAS_FETCH, - variables: { - input: { - source: sourceId, - query: mangaTitle, - page: 1, - type: FetchSourceMangaType.Search, - }, - }, - context: { fetchOptions: { signal } }, - }); - - const searchMatches = searchResponse?.data?.fetchSourceManga?.mangas ?? []; - const matches = searchMatches.filter( - (searchMatch) => enhancedCleanup(searchMatch.title) === enhancedCleanup(mangaTitle), - ); - - const matchUpdatePromises = matches.map(async (match) => { - if (signal.aborted) { - throw new Error(signal.reason); + return updatedMatch.data?.fetchManga?.manga ?? match; + } catch (e) { + // ignore } - return (async () => { - try { - const updatedMatch = await requestManager.refreshManga(match.id, { - awaitRefetchQueries: true, - }).response; - - return updatedMatch.data?.fetchManga?.manga ?? match; - } catch (e) { - // ignore - } - - return match; - })(); - }); - - const updatedMatches = await Promise.all(matchUpdatePromises); - - return updatedMatches; + return match; + })(); }); + + const updatedMatches = await Promise.all(matchUpdatePromises); + + return updatedMatches; } private static async searchForManga( diff --git a/src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx b/src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx index cdd6e097..eb45c36d 100644 --- a/src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx +++ b/src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx @@ -30,6 +30,7 @@ export const MigrationBulkSearchOptionsDialog = ({ const [selectHighestChapterNumberSource, setSelectHighestChapterNumberSource] = useState(false); const [ignoreOutdatedMatches, setIgnoreOutdatesMatches] = useState(false); + const [performAdvancedSearch, setPerformAdvancedSearch] = useState(false); return ( @@ -77,6 +78,27 @@ export const MigrationBulkSearchOptionsDialog = ({ {t`These options are slow and dangerous and may lead to restrictions from sources`} + + {t`Advanced search mode`} + {t`Breaks down the title into keywords for a wider search`} + + } + sx={{ + alignItems: 'start', + }} + checked={performAdvancedSearch} + onChange={(_, checked) => setPerformAdvancedSearch(checked)} + /> diff --git a/src/i18n/locales/en.po b/src/i18n/locales/en.po index 0e6c2d6b..244145d2 100644 --- a/src/i18n/locales/en.po +++ b/src/i18n/locales/en.po @@ -380,6 +380,10 @@ msgstr "Added manga to library!" msgid "Advanced" msgstr "Advanced" +#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx +msgid "Advanced search mode" +msgstr "Advanced search mode" + #: src/features/settings/Settings.constants.ts msgid "After changing this setting go to the \"About\" page and check for a webUI update and install it" msgstr "After changing this setting go to the \"About\" page and check for a webUI update and install it" @@ -601,6 +605,10 @@ msgstr "Both" msgid "Bottom" msgstr "Bottom" +#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx +msgid "Breaks down the title into keywords for a wider search" +msgstr "Breaks down the title into keywords for a wider search" + #: src/features/reader/settings/behaviour/ReaderBehaviourSettings.tsx msgid "Briefly show current mode when reader is opened or mode got changed" msgstr "Briefly show current mode when reader is opened or mode got changed"