Optionally migrate track bindings

This commit is contained in:
schroda
2024-04-20 23:51:47 +02:00
parent 0d8bb789a0
commit 2066851793
9 changed files with 116 additions and 21 deletions

View File

@@ -35,7 +35,7 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
const mangaId = Number(mangaIdAsString); const mangaId = Number(mangaIdAsString);
const { const {
settings: { migrateChapters, migrateCategories, deleteChapters }, settings: { migrateChapters, migrateCategories, migrateTracking, deleteChapters },
} = useMetadataServerSettings(); } = useMetadataServerSettings();
const [isMigrationInProcess, setIsMigrationInProcess] = useState(false); const [isMigrationInProcess, setIsMigrationInProcess] = useState(false);
@@ -58,6 +58,7 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
mode, mode,
migrateChapters, migrateChapters,
migrateCategories, migrateCategories,
migrateTracking,
deleteChapters, deleteChapters,
}); });
@@ -84,6 +85,12 @@ export const MigrateDialog = ({ mangaIdToMigrateTo, onClose }: { mangaIdToMigrat
checked={migrateCategories} checked={migrateCategories}
onChange={(_, checked) => setMigrationFlag('migrateCategories', checked)} onChange={(_, checked) => setMigrationFlag('migrateCategories', checked)}
/> />
<CheckboxInput
disabled={isMigrationInProcess}
label={t('tracking.title')}
checked={migrateTracking}
onChange={(_, checked) => setMigrationFlag('migrateTracking', checked)}
/>
<CheckboxInput <CheckboxInput
disabled={isMigrationInProcess} disabled={isMigrationInProcess}
label={t('migrate.dialog.label.delete_downloaded')} label={t('migrate.dialog.label.delete_downloaded')}

View File

@@ -368,22 +368,66 @@ export class Mangas {
throw new Error('Categories are missing'); throw new Error('Categories are missing');
} }
requestManager.updateMangasCategories([mangaToMigrateTo.id], { await requestManager.updateMangasCategories([mangaToMigrateTo.id], {
addToCategories: mangaToMigrate.categories.nodes.map((category) => category.id), addToCategories: mangaToMigrate.categories.nodes.map((category) => category.id),
}); }).response;
}
private static async migrateTracking(
mode: MigrateMode,
mangaToMigrate: GetMangaToMigrateQuery['manga'],
mangaToMigrateTo: GetMangaToMigrateToFetchMutation['fetchManga']['manga'],
): Promise<void> {
if (!mangaToMigrate.trackRecords) {
throw new Error('TrackRecords of manga to migrate are missing');
}
if (!mangaToMigrateTo.trackRecords) {
throw new Error('TrackRecords of manga to migrate to are missing');
}
const trackBindingsToAdd = mangaToMigrate.trackRecords.nodes.filter((trackRecordToMigrate) =>
mangaToMigrateTo.trackRecords!.nodes.every(
(trackRecord) => trackRecordToMigrate.remoteId !== trackRecord.remoteId,
),
);
await Promise.all([
...(mode === 'migrate'
? mangaToMigrate.trackRecords.nodes.map(
(trackRecord) => requestManager.unbindTracker(trackRecord.id).response,
)
: []),
...trackBindingsToAdd.map((trackRecord) =>
requestManager.bindTracker(mangaToMigrateTo.id, trackRecord.trackerId, trackRecord.remoteId),
),
]);
} }
static async migrate( static async migrate(
mangaId: number, mangaId: number,
mangaIdToMigrateTo: number, mangaIdToMigrateTo: number,
{ mode, migrateChapters, migrateCategories, deleteChapters }: Omit<MigrateOptions, 'mangaIdToMigrateTo'>, {
mode,
migrateChapters,
migrateCategories,
migrateTracking,
deleteChapters,
}: Omit<MigrateOptions, 'mangaIdToMigrateTo'>,
): Promise<void> { ): Promise<void> {
return Mangas.executeAction('migrate', 1, async () => { return Mangas.executeAction('migrate', 1, async () => {
const [{ data: mangaToMigrateData }, { data: mangaToMigrateToData }] = await Promise.all([ const [{ data: mangaToMigrateData }, { data: mangaToMigrateToData }] = await Promise.all([
requestManager.getMangaToMigrate(mangaId, { migrateChapters, migrateCategories, deleteChapters }) requestManager.getMangaToMigrate(mangaId, {
.response, migrateChapters,
requestManager.getMangaToMigrateToFetch(mangaIdToMigrateTo, { migrateChapters, migrateCategories }) migrateCategories,
.response, migrateTracking,
deleteChapters,
}).response,
requestManager.getMangaToMigrateToFetch(mangaIdToMigrateTo, {
migrateChapters,
migrateCategories,
migrateTracking,
}).response,
]); ]);
if (!mangaToMigrateData.manga || !mangaToMigrateToData?.fetchManga.manga) { if (!mangaToMigrateData.manga || !mangaToMigrateToData?.fetchManga.manga) {
@@ -407,6 +451,9 @@ export class Mangas {
migrateCategories migrateCategories
? Mangas.migrateCategories(mangaToMigrateData.manga, mangaToMigrateToData.fetchManga.manga) ? Mangas.migrateCategories(mangaToMigrateData.manga, mangaToMigrateToData.fetchManga.manga)
: undefined, : undefined,
migrateTracking
? Mangas.migrateTracking(mode, mangaToMigrateData.manga, mangaToMigrateToData.fetchManga.manga)
: undefined,
!mangaToMigrateToData.fetchManga.manga.inLibrary !mangaToMigrateToData.fetchManga.manga.inLibrary
? requestManager.updateManga(mangaIdToMigrateTo, { updateManga: { inLibrary: true } }).response ? requestManager.updateManga(mangaIdToMigrateTo, { updateManga: { inLibrary: true } }).response
: undefined, : undefined,

View File

@@ -2890,10 +2890,11 @@ export type GetMangaToMigrateToFetchMutationVariables = Exact<{
id: Scalars['Int']['input']; id: Scalars['Int']['input'];
migrateChapters: Scalars['Boolean']['input']; migrateChapters: Scalars['Boolean']['input'];
migrateCategories: Scalars['Boolean']['input']; migrateCategories: Scalars['Boolean']['input'];
migrateTracking: Scalars['Boolean']['input'];
}>; }>;
export type GetMangaToMigrateToFetchMutation = { __typename?: 'Mutation', fetchManga: { __typename?: 'FetchMangaPayload', clientMutationId?: string | null, manga: { __typename?: 'MangaType', id: number, title: string, inLibrary: boolean, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> } } }, fetchChapters?: { __typename?: 'FetchChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> } }; export type GetMangaToMigrateToFetchMutation = { __typename?: 'Mutation', fetchManga: { __typename?: 'FetchMangaPayload', clientMutationId?: string | null, manga: { __typename?: 'MangaType', id: number, title: string, inLibrary: boolean, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> }, trackRecords?: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number }> } } }, fetchChapters?: { __typename?: 'FetchChaptersPayload', chapters: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> } };
export type SetMangaMetadataMutationVariables = Exact<{ export type SetMangaMetadataMutationVariables = Exact<{
input: SetMangaMetaInput; input: SetMangaMetaInput;
@@ -3181,10 +3182,11 @@ export type GetMangaToMigrateQueryVariables = Exact<{
id: Scalars['Int']['input']; id: Scalars['Int']['input'];
getChapterData: Scalars['Boolean']['input']; getChapterData: Scalars['Boolean']['input'];
migrateCategories: Scalars['Boolean']['input']; migrateCategories: Scalars['Boolean']['input'];
migrateTracking: Scalars['Boolean']['input'];
}>; }>;
export type GetMangaToMigrateQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', id: number, inLibrary: boolean, title: string, chapters?: { __typename?: 'ChapterNodeList', totalCount: number, nodes: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> }, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> } } }; export type GetMangaToMigrateQuery = { __typename?: 'Query', manga: { __typename?: 'MangaType', id: number, inLibrary: boolean, title: string, chapters?: { __typename?: 'ChapterNodeList', totalCount: number, nodes: Array<{ __typename?: 'ChapterType', id: number, chapterNumber: number, isRead: boolean, isDownloaded: boolean, isBookmarked: boolean, manga: { __typename?: 'MangaType', id: number } }> }, categories?: { __typename?: 'CategoryNodeList', nodes: Array<{ __typename?: 'CategoryType', id: number }> }, trackRecords?: { __typename?: 'TrackRecordNodeList', nodes: Array<{ __typename?: 'TrackRecordType', id: number, remoteId: string, trackerId: number }> } } };
export type GetMangasQueryVariables = Exact<{ export type GetMangasQueryVariables = Exact<{
after?: InputMaybe<Scalars['Cursor']['input']>; after?: InputMaybe<Scalars['Cursor']['input']>;

View File

@@ -50,7 +50,12 @@ export const GET_MANGA_FETCH = gql`
// makes the server fetch and return the manga // makes the server fetch and return the manga
export const GET_MANGA_TO_MIGRATE_TO_FETCH = gql` export const GET_MANGA_TO_MIGRATE_TO_FETCH = gql`
mutation GET_MANGA_TO_MIGRATE_TO_FETCH($id: Int!, $migrateChapters: Boolean!, $migrateCategories: Boolean!) { mutation GET_MANGA_TO_MIGRATE_TO_FETCH(
$id: Int!
$migrateChapters: Boolean!
$migrateCategories: Boolean!
$migrateTracking: Boolean!
) {
fetchManga(input: { id: $id }) { fetchManga(input: { id: $id }) {
clientMutationId clientMutationId
manga { manga {
@@ -62,6 +67,13 @@ export const GET_MANGA_TO_MIGRATE_TO_FETCH = gql`
id id
} }
} }
trackRecords @include(if: $migrateTracking) {
nodes {
id
remoteId
trackerId
}
}
} }
} }
fetchChapters(input: { mangaId: $id }) @include(if: $migrateChapters) { fetchChapters(input: { mangaId: $id }) @include(if: $migrateChapters) {

View File

@@ -22,7 +22,12 @@ export const GET_MANGA = gql`
// returns the current manga from the database // returns the current manga from the database
export const GET_MANGA_TO_MIGRATE = gql` export const GET_MANGA_TO_MIGRATE = gql`
query GET_MANGA_TO_MIGRATE($id: Int!, $getChapterData: Boolean!, $migrateCategories: Boolean!) { query GET_MANGA_TO_MIGRATE(
$id: Int!
$getChapterData: Boolean!
$migrateCategories: Boolean!
$migrateTracking: Boolean!
) {
manga(id: $id) { manga(id: $id) {
id id
inLibrary inLibrary
@@ -45,6 +50,13 @@ export const GET_MANGA_TO_MIGRATE = gql`
id id
} }
} }
trackRecords @include(if: $migrateTracking) {
nodes {
id
remoteId
trackerId
}
}
} }
} }
`; `;

View File

@@ -41,6 +41,7 @@ const GLOBAL_METADATA_KEYS: AppMetadataKeys[] = [
// migration // migration
'migrateChapters', 'migrateChapters',
'migrateCategories', 'migrateCategories',
'migrateTracking',
'deleteChapters', 'deleteChapters',
// browse // browse

View File

@@ -37,6 +37,7 @@ export const getDefaultSettings = (): MetadataServerSettings => ({
// migration // migration
migrateChapters: true, migrateChapters: true,
migrateCategories: true, migrateCategories: true,
migrateTracking: true,
deleteChapters: true, deleteChapters: true,
// browse // browse

View File

@@ -287,7 +287,7 @@ import {
TRACKER_UPDATE_BIND, TRACKER_UPDATE_BIND,
} from '@/lib/graphql/mutations/TrackerMutation.ts'; } from '@/lib/graphql/mutations/TrackerMutation.ts';
import { ControlledPromise } from '@/lib/ControlledPromise.ts'; import { ControlledPromise } from '@/lib/ControlledPromise.ts';
import { TManga } from '@/typings.ts'; import { MetadataMigrationSettings, TManga } from '@/typings.ts';
enum GQLMethod { enum GQLMethod {
QUERY = 'QUERY', QUERY = 'QUERY',
@@ -1549,19 +1549,22 @@ export class RequestManager {
{ {
migrateChapters = false, migrateChapters = false,
migrateCategories = false, migrateCategories = false,
migrateTracking = false,
deleteChapters = false, deleteChapters = false,
apolloOptions: options, apolloOptions: options,
}: { }: Partial<MetadataMigrationSettings> & {
migrateChapters?: boolean;
migrateCategories?: boolean;
deleteChapters?: boolean;
apolloOptions?: QueryOptions<GetMangaToMigrateQueryVariables, GetMangaToMigrateQuery>; apolloOptions?: QueryOptions<GetMangaToMigrateQueryVariables, GetMangaToMigrateQuery>;
} = {}, } = {},
): AbortabaleApolloQueryResponse<GetMangaToMigrateQuery> { ): AbortabaleApolloQueryResponse<GetMangaToMigrateQuery> {
return this.doRequest( return this.doRequest(
GQLMethod.QUERY, GQLMethod.QUERY,
GET_MANGA_TO_MIGRATE, GET_MANGA_TO_MIGRATE,
{ id: Number(mangaId), getChapterData: migrateChapters || deleteChapters, migrateCategories }, {
id: Number(mangaId),
getChapterData: migrateChapters || deleteChapters,
migrateCategories,
migrateTracking,
},
options, options,
); );
} }
@@ -1587,10 +1590,9 @@ export class RequestManager {
{ {
migrateChapters = false, migrateChapters = false,
migrateCategories = false, migrateCategories = false,
migrateTracking = false,
apolloOptions: options, apolloOptions: options,
}: { }: Partial<Omit<MetadataMigrationSettings, 'deleteChapters'>> & {
migrateChapters?: boolean;
migrateCategories?: boolean;
apolloOptions?: MutationOptions< apolloOptions?: MutationOptions<
GetMangaToMigrateToFetchMutation, GetMangaToMigrateToFetchMutation,
GetMangaToMigrateToFetchMutationVariables GetMangaToMigrateToFetchMutationVariables
@@ -1604,6 +1606,7 @@ export class RequestManager {
id: Number(mangaId), id: Number(mangaId),
migrateChapters, migrateChapters,
migrateCategories, migrateCategories,
migrateTracking,
}, },
options, options,
); );
@@ -2490,6 +2493,15 @@ export class RequestManager {
return this.doRequest(GQLMethod.USE_MUTATION, TRACKER_BIND, undefined, options); return this.doRequest(GQLMethod.USE_MUTATION, TRACKER_BIND, undefined, options);
} }
public bindTracker(
mangaId: number,
trackerId: number,
remoteId: string,
options?: MutationOptions<TrackerBindMutation, TrackerBindMutationVariables>,
): AbortableApolloMutationResponse<TrackerBindMutation> {
return this.doRequest(GQLMethod.MUTATION, TRACKER_BIND, { mangaId, remoteId, trackerId }, options);
}
public unbindTracker( public unbindTracker(
recordId: number, recordId: number,
deleteRemoteTrack?: boolean, deleteRemoteTrack?: boolean,

View File

@@ -242,6 +242,7 @@ export type MetadataClientSettings = {
export type MetadataMigrationSettings = { export type MetadataMigrationSettings = {
migrateChapters: boolean; migrateChapters: boolean;
migrateCategories: boolean; migrateCategories: boolean;
migrateTracking: boolean;
deleteChapters: boolean; deleteChapters: boolean;
}; };