Use new "binTrackRecord" mutation during migration

Does not cause any requests to the trackers during the migration, which prevents getting rate limited

closes #1104
This commit is contained in:
schroda
2026-06-30 14:58:22 +02:00
parent e316668ba6
commit ef0dbef9cd
6 changed files with 107 additions and 35 deletions

View File

@@ -21,27 +21,10 @@ import type {
MigrationChapter, MigrationChapter,
} from '@/features/migration/Migration.types.ts'; } from '@/features/migration/Migration.types.ts';
import { getMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts'; import { getMetadataServerSettings } from '@/features/settings/services/ServerSettingsMetadata.ts';
import { Queue } from '@/lib/Queue.ts';
import type { TrackerIdInfo } from '@/features/tracker/Tracker.types.ts';
import { assertIsDefined } from '@/base/Asserts.ts';
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { makeToast } from '@/base/utils/Toast.ts'; import { makeToast } from '@/base/utils/Toast.ts';
export class MangaMigration { export class MangaMigration {
private static trackerQueue = new Map<TrackerIdInfo['id'], Queue>();
private static getOrCreateQueue(trackerId: TrackerIdInfo['id']): Queue {
if (!MangaMigration.trackerQueue.has(trackerId)) {
MangaMigration.trackerQueue.set(trackerId, new Queue(1));
}
const queue = MangaMigration.trackerQueue.get(trackerId);
assertIsDefined(queue);
return queue;
}
static async migrate( static async migrate(
mangaToMigrate: MangaToMigrate | null | undefined, mangaToMigrate: MangaToMigrate | null | undefined,
mangaToMigrateTo: MangaToMigrateTo | null | undefined, mangaToMigrateTo: MangaToMigrateTo | null | undefined,
@@ -285,24 +268,7 @@ export class MangaMigration {
return { return {
copy: () => copy: () =>
trackBindingsToAdd.map( trackBindingsToAdd.map(
(trackRecord) => (trackRecord) => requestManager.bindTrackRecord(mangaToMigrateTo.id, trackRecord.id).response,
MangaMigration.getOrCreateQueue(trackRecord.trackerId).enqueue(
String(mangaToMigrate.id),
async () => {
try {
await requestManager.bindTracker(
mangaToMigrateTo.id,
trackRecord.trackerId,
trackRecord.remoteId,
trackRecord.private,
).response;
} finally {
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
}
},
).promise,
), ),
cleanup: () => cleanup: () =>
mode === 'migrate' mode === 'migrate'

View File

@@ -40,6 +40,15 @@ export type BindTrackPayloadFieldPolicy = {
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>; clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>;
trackRecord?: FieldPolicy<any> | FieldReadFunction<any>; trackRecord?: FieldPolicy<any> | FieldReadFunction<any>;
}; };
export type BindTrackRecordPayloadKeySpecifier = (
| 'clientMutationId'
| 'trackRecord'
| BindTrackRecordPayloadKeySpecifier
)[];
export type BindTrackRecordPayloadFieldPolicy = {
clientMutationId?: FieldPolicy<any> | FieldReadFunction<any>;
trackRecord?: FieldPolicy<any> | FieldReadFunction<any>;
};
export type CategoryEdgeKeySpecifier = ('cursor' | 'node' | CategoryEdgeKeySpecifier)[]; export type CategoryEdgeKeySpecifier = ('cursor' | 'node' | CategoryEdgeKeySpecifier)[];
export type CategoryEdgeFieldPolicy = { export type CategoryEdgeFieldPolicy = {
cursor?: FieldPolicy<any> | FieldReadFunction<any>; cursor?: FieldPolicy<any> | FieldReadFunction<any>;
@@ -869,6 +878,7 @@ export type MultiSelectListPreferenceFieldPolicy = {
}; };
export type MutationKeySpecifier = ( export type MutationKeySpecifier = (
| 'bindTrack' | 'bindTrack'
| 'bindTrackRecord'
| 'clearCachedImages' | 'clearCachedImages'
| 'clearDownloader' | 'clearDownloader'
| 'connectKoSyncAccount' | 'connectKoSyncAccount'
@@ -948,6 +958,7 @@ export type MutationKeySpecifier = (
)[]; )[];
export type MutationFieldPolicy = { export type MutationFieldPolicy = {
bindTrack?: FieldPolicy<any> | FieldReadFunction<any>; bindTrack?: FieldPolicy<any> | FieldReadFunction<any>;
bindTrackRecord?: FieldPolicy<any> | FieldReadFunction<any>;
clearCachedImages?: FieldPolicy<any> | FieldReadFunction<any>; clearCachedImages?: FieldPolicy<any> | FieldReadFunction<any>;
clearDownloader?: FieldPolicy<any> | FieldReadFunction<any>; clearDownloader?: FieldPolicy<any> | FieldReadFunction<any>;
connectKoSyncAccount?: FieldPolicy<any> | FieldReadFunction<any>; connectKoSyncAccount?: FieldPolicy<any> | FieldReadFunction<any>;
@@ -2455,6 +2466,10 @@ export type StrictTypedTypePolicies = {
keyFields?: false | BindTrackPayloadKeySpecifier | (() => undefined | BindTrackPayloadKeySpecifier); keyFields?: false | BindTrackPayloadKeySpecifier | (() => undefined | BindTrackPayloadKeySpecifier);
fields?: BindTrackPayloadFieldPolicy; fields?: BindTrackPayloadFieldPolicy;
}; };
BindTrackRecordPayload?: Omit<TypePolicy, 'fields' | 'keyFields'> & {
keyFields?: false | BindTrackRecordPayloadKeySpecifier | (() => undefined | BindTrackRecordPayloadKeySpecifier);
fields?: BindTrackRecordPayloadFieldPolicy;
};
CategoryEdge?: Omit<TypePolicy, 'fields' | 'keyFields'> & { CategoryEdge?: Omit<TypePolicy, 'fields' | 'keyFields'> & {
keyFields?: false | CategoryEdgeKeySpecifier | (() => undefined | CategoryEdgeKeySpecifier); keyFields?: false | CategoryEdgeKeySpecifier | (() => undefined | CategoryEdgeKeySpecifier);
fields?: CategoryEdgeFieldPolicy; fields?: CategoryEdgeFieldPolicy;

View File

@@ -71,6 +71,18 @@ export type BindTrackPayload = {
trackRecord: TrackRecordType; trackRecord: TrackRecordType;
}; };
export type BindTrackRecordInput = {
clientMutationId?: InputMaybe<Scalars['String']['input']>;
mangaId: Scalars['Int']['input'];
trackRecordId: Scalars['Int']['input'];
};
export type BindTrackRecordPayload = {
__typename?: 'BindTrackRecordPayload';
clientMutationId?: Maybe<Scalars['String']['output']>;
trackRecord: TrackRecordType;
};
export type BooleanFilterInput = { export type BooleanFilterInput = {
distinctFrom?: InputMaybe<Scalars['Boolean']['input']>; distinctFrom?: InputMaybe<Scalars['Boolean']['input']>;
distinctFromAll?: InputMaybe<Array<Scalars['Boolean']['input']>>; distinctFromAll?: InputMaybe<Array<Scalars['Boolean']['input']>>;
@@ -1310,6 +1322,7 @@ export type MultiSelectListPreference = {
export type Mutation = { export type Mutation = {
__typename?: 'Mutation'; __typename?: 'Mutation';
bindTrack: BindTrackPayload; bindTrack: BindTrackPayload;
bindTrackRecord?: Maybe<BindTrackRecordPayload>;
clearCachedImages: ClearCachedImagesPayload; clearCachedImages: ClearCachedImagesPayload;
clearDownloader?: Maybe<ClearDownloaderPayload>; clearDownloader?: Maybe<ClearDownloaderPayload>;
connectKoSyncAccount: KoSyncConnectPayload; connectKoSyncAccount: KoSyncConnectPayload;
@@ -1391,6 +1404,10 @@ export type MutationBindTrackArgs = {
input: BindTrackInput; input: BindTrackInput;
}; };
export type MutationBindTrackRecordArgs = {
input: BindTrackRecordInput;
};
export type MutationClearCachedImagesArgs = { export type MutationClearCachedImagesArgs = {
input: ClearCachedImagesInput; input: ClearCachedImagesInput;
}; };

View File

@@ -3951,6 +3951,42 @@ export type TrackerBindMutation = {
}; };
}; };
export type TrackerBindTrackRecordMutationVariables = Exact<{
input: Types.BindTrackRecordInput;
}>;
export type TrackerBindTrackRecordMutation = {
__typename: 'Mutation';
bindTrackRecord: {
__typename: 'BindTrackRecordPayload';
trackRecord: {
__typename: 'TrackRecordType';
id: number;
remoteId: string;
trackerId: number;
remoteUrl: string;
title: string;
status: number;
lastChapterRead: number;
totalChapters: number;
score: number;
displayScore: string;
startDate: string;
finishDate: string;
private: boolean;
manga: {
__typename: 'MangaType';
id: number;
trackRecords: {
__typename: 'TrackRecordNodeList';
totalCount: number;
nodes: Array<{ __typename: 'TrackRecordType'; id: number; trackerId: number }>;
};
};
};
} | null;
};
export type TrackerUnbindMutationVariables = Exact<{ export type TrackerUnbindMutationVariables = Exact<{
input: Types.UnbindTrackInput; input: Types.UnbindTrackInput;
}>; }>;

View File

@@ -69,6 +69,28 @@ export const TRACKER_BIND = gql`
} }
`; `;
export const TRACK_BIND_TRACK_RECORD = gql`
${TRACK_RECORD_BIND_FIELDS}
mutation TRACKER_BIND_TRACK_RECORD($input: BindTrackRecordInput!) {
bindTrackRecord(input: $input) {
trackRecord {
...TRACK_RECORD_BIND_FIELDS
manga {
id
trackRecords {
totalCount
nodes {
id
trackerId
}
}
}
}
}
}
`;
export const TRACKER_UNBIND = gql` export const TRACKER_UNBIND = gql`
mutation TRACKER_UNBIND($input: UnbindTrackInput!) { mutation TRACKER_UNBIND($input: UnbindTrackInput!) {
unbindTrack(input: $input) { unbindTrack(input: $input) {

View File

@@ -126,6 +126,8 @@ import type {
StopUpdaterMutationVariables, StopUpdaterMutationVariables,
TrackerBindMutation, TrackerBindMutation,
TrackerBindMutationVariables, TrackerBindMutationVariables,
TrackerBindTrackRecordMutation,
TrackerBindTrackRecordMutationVariables,
TrackerFetchBindMutation, TrackerFetchBindMutation,
TrackerFetchBindMutationVariables, TrackerFetchBindMutationVariables,
TrackerLoginCredentialsMutation, TrackerLoginCredentialsMutation,
@@ -309,6 +311,7 @@ import type { QueuePriority } from '@/lib/Queue.ts';
import { SourceAwareQueue } from '@/lib/SourceAwareQueue.ts'; import { SourceAwareQueue } from '@/lib/SourceAwareQueue.ts';
import { TRACKER_SEARCH } from '@/lib/graphql/tracker/TrackerQuery.ts'; import { TRACKER_SEARCH } from '@/lib/graphql/tracker/TrackerQuery.ts';
import { import {
TRACK_BIND_TRACK_RECORD,
TRACKER_BIND, TRACKER_BIND,
TRACKER_FETCH_BIND, TRACKER_FETCH_BIND,
TRACKER_LOGIN_CREDENTIALS, TRACKER_LOGIN_CREDENTIALS,
@@ -3719,6 +3722,19 @@ export class RequestManager {
); );
} }
public bindTrackRecord(
mangaId: number,
trackRecordId: number,
options?: MutationOptions<TrackerBindTrackRecordMutation, TrackerBindTrackRecordMutationVariables>,
): AbortableApolloMutationResponse<TrackerBindTrackRecordMutation> {
return this.doRequest(
GQLMethod.MUTATION,
TRACK_BIND_TRACK_RECORD,
{ input: { mangaId, trackRecordId } },
options,
);
}
public unbindTracker( public unbindTracker(
recordId: number, recordId: number,
deleteRemoteTrack?: boolean, deleteRemoteTrack?: boolean,