Optionally perform advanced search during bulk migration

This commit is contained in:
schroda
2026-05-08 15:41:58 +02:00
parent 3d514d357a
commit 55a620c63a
4 changed files with 105 additions and 47 deletions

View File

@@ -61,6 +61,7 @@ export type MetadataMigrationSettings = {
export interface MigrationBulkSearchSettings { export interface MigrationBulkSearchSettings {
selectHighestChapterNumberSource: boolean; selectHighestChapterNumberSource: boolean;
ignoreOutdatedMatches: boolean; ignoreOutdatedMatches: boolean;
performAdvancedSearch: boolean;
} }
export enum MigrationPhase { export enum MigrationPhase {

View File

@@ -56,6 +56,7 @@ import { MANGA_MIGRATION_FIELDS } from '@/lib/graphql/manga/MangaFragments.ts';
import { ZustandUtil } from '@/lib/zustand/ZustandUtil.ts'; import { ZustandUtil } from '@/lib/zustand/ZustandUtil.ts';
import { getErrorMessage } from '@/lib/HelperFunctions.ts'; import { getErrorMessage } from '@/lib/HelperFunctions.ts';
import isEqual from 'lodash/fp/isEqual'; import isEqual from 'lodash/fp/isEqual';
import uniqBy from 'lodash/fp/uniqBy';
const RESUMABLE_PHASES: readonly MigrationPhase[] = [MigrationPhase.SEARCHING, MigrationPhase.MIGRATING]; const RESUMABLE_PHASES: readonly MigrationPhase[] = [MigrationPhase.SEARCHING, MigrationPhase.MIGRATING];
@@ -663,66 +664,91 @@ export class MigrationManager {
mangaTitle: string, mangaTitle: string,
sourceId: SourceIdInfo['id'], sourceId: SourceIdInfo['id'],
signal: AbortSignal, signal: AbortSignal,
{ selectHighestChapterNumberSource }: MigrationBulkSearchSettings, { selectHighestChapterNumberSource, performAdvancedSearch }: MigrationBulkSearchSettings,
): Promise<MangaMigrationFieldsFragment[]> { ): Promise<MangaMigrationFieldsFragment[]> {
if (signal.aborted) { if (signal.aborted) {
throw new Error(signal.reason); 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) { if (signal.aborted) {
throw new Error(signal.reason); throw new Error(signal.reason);
} }
if (!selectHighestChapterNumberSource && MigrationManager.hasHigherSourcePriorityMatch(mangaId, sourceId)) { return (async () => {
throw new Error('Entry already has a selected match from a higher priority source'); try {
} const updatedMatch = await requestManager.refreshManga(match.id, {
awaitRefetchQueries: true,
}).response;
const searchResponse = await requestManager.graphQLClient.client.mutate< return updatedMatch.data?.fetchManga?.manga ?? match;
GetMigrationSourceMangasFetchMutation, } catch (e) {
GetMigrationSourceMangasFetchMutationVariables // ignore
>({
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 (async () => { return match;
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;
}); });
const updatedMatches = await Promise.all(matchUpdatePromises);
return updatedMatches;
} }
private static async searchForManga( private static async searchForManga(

View File

@@ -30,6 +30,7 @@ export const MigrationBulkSearchOptionsDialog = ({
const [selectHighestChapterNumberSource, setSelectHighestChapterNumberSource] = useState(false); const [selectHighestChapterNumberSource, setSelectHighestChapterNumberSource] = useState(false);
const [ignoreOutdatedMatches, setIgnoreOutdatesMatches] = useState(false); const [ignoreOutdatedMatches, setIgnoreOutdatesMatches] = useState(false);
const [performAdvancedSearch, setPerformAdvancedSearch] = useState(false);
return ( return (
<Dialog open={isVisible} fullWidth onClose={onDismiss} onTransitionExited={onExitComplete}> <Dialog open={isVisible} fullWidth onClose={onDismiss} onTransitionExited={onExitComplete}>
@@ -77,6 +78,27 @@ export const MigrationBulkSearchOptionsDialog = ({
{t`These options are slow and dangerous and may lead to restrictions from sources`} {t`These options are slow and dangerous and may lead to restrictions from sources`}
</Typography> </Typography>
</Stack> </Stack>
<CheckboxInput
label={
<Stack
sx={{
// Padding comes from the MUI Checkbox component
pt: '9px',
}}
>
<Typography>{t`Advanced search mode`}</Typography>
<Typography
variant="body2"
color="textSecondary"
>{t`Breaks down the title into keywords for a wider search`}</Typography>
</Stack>
}
sx={{
alignItems: 'start',
}}
checked={performAdvancedSearch}
onChange={(_, checked) => setPerformAdvancedSearch(checked)}
/>
<CheckboxInput <CheckboxInput
label={ label={
<Stack <Stack
@@ -107,6 +129,7 @@ export const MigrationBulkSearchOptionsDialog = ({
onSubmit({ onSubmit({
selectHighestChapterNumberSource, selectHighestChapterNumberSource,
ignoreOutdatedMatches, ignoreOutdatedMatches,
performAdvancedSearch,
}) })
} }
> >

View File

@@ -380,6 +380,10 @@ msgstr "Added manga to library!"
msgid "Advanced" msgid "Advanced"
msgstr "Advanced" msgstr "Advanced"
#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx
msgid "Advanced search mode"
msgstr "Advanced search mode"
#: src/features/settings/Settings.constants.ts #: 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" 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" 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" msgid "Bottom"
msgstr "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 #: src/features/reader/settings/behaviour/ReaderBehaviourSettings.tsx
msgid "Briefly show current mode when reader is opened or mode got changed" 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" msgstr "Briefly show current mode when reader is opened or mode got changed"