Optionally ignore outdated matches during bulk migration

This commit is contained in:
schroda
2026-05-08 15:19:26 +02:00
parent 67bb2248c4
commit 3d514d357a
7 changed files with 87 additions and 3 deletions

View File

@@ -52,6 +52,7 @@ export const ENTRY_STATUS_TRANSLATION: Record<MigrationEntryStatus, MessageDescr
[MigrationEntryStatus.SEARCH_COMPLETE]: msg`Match found`, [MigrationEntryStatus.SEARCH_COMPLETE]: msg`Match found`,
[MigrationEntryStatus.SEARCH_FAILED]: msg`Search failed`, [MigrationEntryStatus.SEARCH_FAILED]: msg`Search failed`,
[MigrationEntryStatus.NO_MATCH]: msg`No match found`, [MigrationEntryStatus.NO_MATCH]: msg`No match found`,
[MigrationEntryStatus.OUTDATED]: msg`Only outdated matches found`,
[MigrationEntryStatus.MIGRATING]: msg`Migrating…`, [MigrationEntryStatus.MIGRATING]: msg`Migrating…`,
[MigrationEntryStatus.MIGRATION_COMPLETE]: msg`Successfully migrated`, [MigrationEntryStatus.MIGRATION_COMPLETE]: msg`Successfully migrated`,
[MigrationEntryStatus.MIGRATION_FAILED]: msg`Migration failed`, [MigrationEntryStatus.MIGRATION_FAILED]: msg`Migration failed`,
@@ -62,6 +63,7 @@ export const MIGRATE_SEARCH_ENTRY_GROUPS = [
MigrationEntryStatus.SEARCHING, MigrationEntryStatus.SEARCHING,
MigrationEntryStatus.SEARCH_FAILED, MigrationEntryStatus.SEARCH_FAILED,
MigrationEntryStatus.NO_MATCH, MigrationEntryStatus.NO_MATCH,
MigrationEntryStatus.OUTDATED,
MigrationEntryStatus.SEARCH_COMPLETE, MigrationEntryStatus.SEARCH_COMPLETE,
] as const satisfies readonly MigrationEntryStatus[]; ] as const satisfies readonly MigrationEntryStatus[];
@@ -72,6 +74,7 @@ export const MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE: Record<
[MigrationEntryStatus.SEARCHING]: true, [MigrationEntryStatus.SEARCHING]: true,
[MigrationEntryStatus.SEARCH_FAILED]: false, [MigrationEntryStatus.SEARCH_FAILED]: false,
[MigrationEntryStatus.NO_MATCH]: false, [MigrationEntryStatus.NO_MATCH]: false,
[MigrationEntryStatus.OUTDATED]: false,
[MigrationEntryStatus.SEARCH_COMPLETE]: false, [MigrationEntryStatus.SEARCH_COMPLETE]: false,
}; };
@@ -79,6 +82,7 @@ export const MIGRATE_EXECUTE_ENTRY_GROUPS = [
MigrationEntryStatus.MIGRATING, MigrationEntryStatus.MIGRATING,
MigrationEntryStatus.MIGRATION_FAILED, MigrationEntryStatus.MIGRATION_FAILED,
MigrationEntryStatus.NO_MATCH, MigrationEntryStatus.NO_MATCH,
MigrationEntryStatus.OUTDATED,
MigrationEntryStatus.EXCLUDED, MigrationEntryStatus.EXCLUDED,
MigrationEntryStatus.MIGRATION_COMPLETE, MigrationEntryStatus.MIGRATION_COMPLETE,
] as const satisfies readonly MigrationEntryStatus[]; ] as const satisfies readonly MigrationEntryStatus[];
@@ -90,6 +94,7 @@ export const MIGRATE_EXECUTE_ENTRY_GROUP_EXPAND_DEFAULT_STATE: Record<
[MigrationEntryStatus.MIGRATING]: true, [MigrationEntryStatus.MIGRATING]: true,
[MigrationEntryStatus.MIGRATION_FAILED]: false, [MigrationEntryStatus.MIGRATION_FAILED]: false,
[MigrationEntryStatus.NO_MATCH]: false, [MigrationEntryStatus.NO_MATCH]: false,
[MigrationEntryStatus.OUTDATED]: false,
[MigrationEntryStatus.EXCLUDED]: false, [MigrationEntryStatus.EXCLUDED]: false,
[MigrationEntryStatus.MIGRATION_COMPLETE]: false, [MigrationEntryStatus.MIGRATION_COMPLETE]: false,
}; };

View File

@@ -60,6 +60,7 @@ export type MetadataMigrationSettings = {
export interface MigrationBulkSearchSettings { export interface MigrationBulkSearchSettings {
selectHighestChapterNumberSource: boolean; selectHighestChapterNumberSource: boolean;
ignoreOutdatedMatches: boolean;
} }
export enum MigrationPhase { export enum MigrationPhase {
@@ -76,6 +77,7 @@ export enum MigrationEntryStatus {
SEARCHING = 'searching', SEARCHING = 'searching',
SEARCH_COMPLETE = 'search_complete', SEARCH_COMPLETE = 'search_complete',
SEARCH_FAILED = 'search_failed', SEARCH_FAILED = 'search_failed',
OUTDATED = 'outdated',
NO_MATCH = 'no_match', NO_MATCH = 'no_match',
MIGRATING = 'migrating', MIGRATING = 'migrating',
MIGRATION_COMPLETE = 'migration_complete', MIGRATION_COMPLETE = 'migration_complete',

View File

@@ -378,6 +378,7 @@ export class MigrationManager {
...MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE, ...MIGRATE_SEARCH_ENTRY_GROUP_EXPAND_DEFAULT_STATE,
[MigrationEntryStatus.SEARCHING]: false, [MigrationEntryStatus.SEARCHING]: false,
[MigrationEntryStatus.NO_MATCH]: !allSearchesFailed && !searchProgress.success, [MigrationEntryStatus.NO_MATCH]: !allSearchesFailed && !searchProgress.success,
[MigrationEntryStatus.OUTDATED]: !allSearchesFailed && !searchProgress.success,
[MigrationEntryStatus.SEARCH_FAILED]: allSearchesFailed, [MigrationEntryStatus.SEARCH_FAILED]: allSearchesFailed,
[MigrationEntryStatus.SEARCH_COMPLETE]: !!searchProgress.success, [MigrationEntryStatus.SEARCH_COMPLETE]: !!searchProgress.success,
}; };
@@ -730,7 +731,7 @@ export class MigrationManager {
mainSignal: AbortSignal, mainSignal: AbortSignal,
options: MigrationBulkSearchSettings, options: MigrationBulkSearchSettings,
): Promise<void> { ): Promise<void> {
const { selectHighestChapterNumberSource } = options; const { selectHighestChapterNumberSource, ignoreOutdatedMatches } = options;
const state = MigrationManager.getState(); const state = MigrationManager.getState();
const entry = state.entries[mangaId]; const entry = state.entries[mangaId];
@@ -837,6 +838,7 @@ export class MigrationManager {
destSourceId, destSourceId,
); );
const ignoreOutdatedMatch = ignoreOutdatedMatches && !hasNewerChapter;
const isPreferredSourcePriorityMatch = const isPreferredSourcePriorityMatch =
hasHigherSourcePriority && !selectHighestChapterNumberSource; hasHigherSourcePriority && !selectHighestChapterNumberSource;
const isPreferredChapterNumberMatch = const isPreferredChapterNumberMatch =
@@ -845,7 +847,8 @@ export class MigrationManager {
(hasHigherSourcePriority && hasSameLatestChapterAsSelectedMatch) || (hasHigherSourcePriority && hasSameLatestChapterAsSelectedMatch) ||
!draftMatchEntry); !draftMatchEntry);
const isPreferredMatch = isPreferredSourcePriorityMatch || isPreferredChapterNumberMatch; const isPreferredMatch =
!ignoreOutdatedMatch && (isPreferredSourcePriorityMatch || isPreferredChapterNumberMatch);
if (isPreferredMatch) { if (isPreferredMatch) {
draftEntry.selectedMatchMangaId = bestMatch.id; draftEntry.selectedMatchMangaId = bestMatch.id;
draftEntry.selectedMatchSourceId = destSourceId; draftEntry.selectedMatchSourceId = destSourceId;
@@ -873,7 +876,12 @@ export class MigrationManager {
if (draftEntry.searchMatches.length) { if (draftEntry.searchMatches.length) {
draft.searchProgress.success += 1; draft.searchProgress.success += 1;
if (draftEntry.selectedMatchMangaId != null) {
draftEntry.status = MigrationEntryStatus.SEARCH_COMPLETE; draftEntry.status = MigrationEntryStatus.SEARCH_COMPLETE;
} else {
draftEntry.status = MigrationEntryStatus.OUTDATED;
}
} else { } else {
draftEntry.status = MigrationEntryStatus.NO_MATCH; draftEntry.status = MigrationEntryStatus.NO_MATCH;
} }

View File

@@ -29,10 +29,34 @@ export const MigrationBulkSearchOptionsDialog = ({
const { t } = useLingui(); const { t } = useLingui();
const [selectHighestChapterNumberSource, setSelectHighestChapterNumberSource] = useState(false); const [selectHighestChapterNumberSource, setSelectHighestChapterNumberSource] = useState(false);
const [ignoreOutdatedMatches, setIgnoreOutdatesMatches] = useState(false);
return ( return (
<Dialog open={isVisible} fullWidth onClose={onDismiss} onTransitionExited={onExitComplete}> <Dialog open={isVisible} fullWidth onClose={onDismiss} onTransitionExited={onExitComplete}>
<DialogTitle>{t`Search options`}</DialogTitle> <DialogTitle>{t`Search options`}</DialogTitle>
<DialogContent>
<CheckboxInput
label={
<Stack
sx={{
// Padding comes from the MUI Checkbox component
pt: '9px',
}}
>
<Typography>{t`Ignore matches without newer chapters`}</Typography>
<Typography
variant="body2"
color="textSecondary"
>{t`Do not automatically select matches if they are behind the current source. They will still be shown in the found matches`}</Typography>
</Stack>
}
sx={{
alignItems: 'start',
}}
checked={ignoreOutdatedMatches}
onChange={(_, checked) => setIgnoreOutdatesMatches(checked)}
/>
</DialogContent>
<DialogContent dividers> <DialogContent dividers>
<Stack <Stack
direction="row" direction="row"
@@ -82,6 +106,7 @@ export const MigrationBulkSearchOptionsDialog = ({
onClick={() => onClick={() =>
onSubmit({ onSubmit({
selectHighestChapterNumberSource, selectHighestChapterNumberSource,
ignoreOutdatedMatches,
}) })
} }
> >

View File

@@ -60,6 +60,10 @@ export const MigrationExecute = () => {
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.NO_MATCH), () => entryList.filter((entry) => entry.status === MigrationEntryStatus.NO_MATCH),
[entryList], [entryList],
); );
const outdatedEntries = useMemo(
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.OUTDATED),
[entryList],
);
return ( return (
<> <>
@@ -102,6 +106,15 @@ export const MigrationExecute = () => {
color="warning" color="warning"
isMigrating isMigrating
/> />
<MigrationEntryGroup
status={MigrationEntryStatus.OUTDATED}
title={plural(outdatedEntries.length, {
one: '1 entry with only outdated matches',
other: '# entries with only outdated matches',
})}
entries={outdatedEntries}
color="warning"
/>
<MigrationEntryGroup <MigrationEntryGroup
status={MigrationEntryStatus.EXCLUDED} status={MigrationEntryStatus.EXCLUDED}
title={plural(excludedEntries.length, { title={plural(excludedEntries.length, {

View File

@@ -58,6 +58,10 @@ export const MigrationSearch = () => {
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.NO_MATCH), () => entryList.filter((entry) => entry.status === MigrationEntryStatus.NO_MATCH),
[entryList], [entryList],
); );
const outdatedEntries = useMemo(
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.OUTDATED),
[entryList],
);
const matchedEntries = useMemo( const matchedEntries = useMemo(
() => entryList.filter((entry) => entry.status === MigrationEntryStatus.SEARCH_COMPLETE), () => entryList.filter((entry) => entry.status === MigrationEntryStatus.SEARCH_COMPLETE),
[entryList], [entryList],
@@ -103,6 +107,15 @@ export const MigrationSearch = () => {
entries={noMatchEntries} entries={noMatchEntries}
color="warning" color="warning"
/> />
<MigrationEntryGroup
status={MigrationEntryStatus.OUTDATED}
title={plural(outdatedEntries.length, {
one: '1 entry with only outdated matches',
other: '# entries with only outdated matches',
})}
entries={outdatedEntries}
color="warning"
/>
<MigrationEntryGroup <MigrationEntryGroup
status={MigrationEntryStatus.SEARCH_COMPLETE} status={MigrationEntryStatus.SEARCH_COMPLETE}
title={plural(matchedEntries.length, { title={plural(matchedEntries.length, {

View File

@@ -63,6 +63,12 @@ msgstr "{0, plural, one {# Tracker} other {# Tracker}}"
msgid "{0, plural, one {1 entry with no match} other {# entries with no match}}" msgid "{0, plural, one {1 entry with no match} other {# entries with no match}}"
msgstr "{0, plural, one {1 entry with no match} other {# entries with no match}}" msgstr "{0, plural, one {1 entry with no match} other {# entries with no match}}"
#. placeholder {0}: outdatedEntries.length
#: src/features/migration/screens/MigrationExecute.tsx
#: src/features/migration/screens/MigrationSearch.tsx
msgid "{0, plural, one {1 entry with only outdated matches} other {# entries with only outdated matches}}"
msgstr "{0, plural, one {1 entry with only outdated matches} other {# entries with only outdated matches}}"
#. placeholder {0}: excludedEntries.length #. placeholder {0}: excludedEntries.length
#: src/features/migration/screens/MigrationExecute.tsx #: src/features/migration/screens/MigrationExecute.tsx
msgid "{0, plural, one {1 excluded entry} other {# excluded entries}}" msgid "{0, plural, one {1 excluded entry} other {# excluded entries}}"
@@ -1334,6 +1340,10 @@ msgstr "Display file sizes in binary (KiB, MiB, GiB) instead of decimal (KB, MB,
msgid "Display mode" msgid "Display mode"
msgstr "Display mode" msgstr "Display mode"
#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx
msgid "Do not automatically select matches if they are behind the current source. They will still be shown in the found matches"
msgstr "Do not automatically select matches if they are behind the current source. They will still be shown in the found matches"
#: src/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx #: src/features/settings/components/koreaderSync/KoreaderSyncSettings.tsx
msgid "Document matching method" msgid "Document matching method"
msgstr "Document matching method" msgstr "Document matching method"
@@ -1823,6 +1833,10 @@ msgstr "Ignore automatic chapter downloads for entries with unread chapters"
msgid "Ignore filters when searching" msgid "Ignore filters when searching"
msgstr "Ignore filters when searching" msgstr "Ignore filters when searching"
#: src/features/migration/components/MigrationBulkSearchOptionsDialog.tsx
msgid "Ignore matches without newer chapters"
msgstr "Ignore matches without newer chapters"
#: src/features/downloads/screens/DownloadSettings.tsx #: src/features/downloads/screens/DownloadSettings.tsx
msgid "Ignore re-uploaded chapters" msgid "Ignore re-uploaded chapters"
msgstr "Ignore re-uploaded chapters" msgstr "Ignore re-uploaded chapters"
@@ -2520,6 +2534,10 @@ msgstr "Oneshot"
msgid "Ongoing" msgid "Ongoing"
msgstr "Ongoing" msgstr "Ongoing"
#: src/features/migration/Migration.constants.ts
msgid "Only outdated matches found"
msgstr "Only outdated matches found"
#: src/features/downloads/components/DownloadAheadSetting.tsx #: src/features/downloads/components/DownloadAheadSetting.tsx
msgid "Only works if the current chapter plus the next chapter are already downloaded." msgid "Only works if the current chapter plus the next chapter are already downloaded."
msgstr "Only works if the current chapter plus the next chapter are already downloaded." msgstr "Only works if the current chapter plus the next chapter are already downloaded."