Prevent library duplicates logic from blocking UI

This commit is contained in:
schroda
2024-10-16 19:43:04 +02:00
parent 6d9740e994
commit 0e8ddd4717
4 changed files with 164 additions and 107 deletions

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { MangaType } from '@/lib/graphql/generated/graphql.ts';
import { enhancedCleanup } from '@/util/Strings.ts';
import { TMangaDuplicate, TMangaDuplicateResult, TMangaDuplicates } from '@/modules/library/Library.types.ts';
export const findDuplicatesByTitle = <Manga extends Pick<MangaType, 'title'>>(
libraryMangas: Manga[],
): TMangaDuplicates<Manga> => {
const titleToMangas = Object.groupBy(libraryMangas, ({ title }) => enhancedCleanup(title));
return Object.fromEntries(
Object.entries(titleToMangas)
.filter((titleToMangaMap): titleToMangaMap is [string, Manga[]] => (titleToMangaMap[1]?.length ?? 0) > 1)
.map(([, mangas]) => [mangas[0].title, mangas]),
);
};
const findDuplicatesByTitleAndAlternativeTitlesSingleManga = <Manga extends TMangaDuplicate>(
manga: Manga,
mangas: Manga[],
): TMangaDuplicateResult<Manga> => {
const titleToCheck = enhancedCleanup(manga.title);
const result: ReturnType<typeof findDuplicatesByTitleAndAlternativeTitlesSingleManga<Manga>> = {
byTitle: [manga],
byAlternativeTitle: [manga],
};
mangas.forEach((libraryManga) => {
const isDifferentManga = manga.id !== libraryManga.id;
if (!isDifferentManga) {
return;
}
const doesTitleMatch = enhancedCleanup(libraryManga.title) === titleToCheck;
const doesAlternativeTitleMatch = enhancedCleanup(libraryManga?.description ?? '').includes(titleToCheck);
const isDuplicate = doesTitleMatch || doesAlternativeTitleMatch;
if (!isDuplicate) {
return;
}
if (doesTitleMatch) {
result.byTitle.push(libraryManga);
}
if (doesAlternativeTitleMatch) {
result.byAlternativeTitle.push(libraryManga);
}
});
return result;
};
export const findDuplicatesByTitleAndAlternativeTitles = <Manga extends TMangaDuplicate>(
mangas: Manga[],
): Promise<TMangaDuplicates<Manga>> => {
const titleToMangas: TMangaDuplicates<Manga> = {};
const titleToAlternativeTitleMatches: TMangaDuplicates<Manga> = {};
mangas.forEach((mangaToCheck) => {
const titleToCheck = enhancedCleanup(mangaToCheck.title);
titleToMangas[titleToCheck] ??= [];
titleToAlternativeTitleMatches[titleToCheck] ??= [];
const { byTitle, byAlternativeTitle } = findDuplicatesByTitleAndAlternativeTitlesSingleManga(
mangaToCheck,
mangas,
);
titleToMangas[titleToCheck].push(...byTitle);
titleToAlternativeTitleMatches[titleToCheck].push(...byAlternativeTitle);
});
const titleToDuplicatesEntries = Object.entries(titleToMangas)
.map(([title, titleMatches]) => {
const uniqueTitleMatches = new Set(titleMatches);
const uniqueAlternativeTitleMatches = new Set(titleToAlternativeTitleMatches[title] ?? []);
const originalTitle = [...uniqueTitleMatches][0].title;
const combinedDuplicates = [...uniqueTitleMatches, ...uniqueAlternativeTitleMatches];
const duplicates = [...new Set([...combinedDuplicates])];
const noDuplicatesFound = duplicates.length === 1;
if (noDuplicatesFound) {
return null;
}
return [originalTitle, duplicates];
})
.filter((entry) => !!entry);
return Object.fromEntries(titleToDuplicatesEntries);
};