Throw a toastr on matched metadata page when there is a rate limit issue.

This commit is contained in:
Joseph Milazzo 2025-06-20 16:03:27 -05:00
parent 224e839dad
commit 3a01e9af3a
6 changed files with 55 additions and 11 deletions

View file

@ -386,6 +386,9 @@ public class ExternalMetadataService : IExternalMetadataService
{ {
// We can't rethrow because Fix match is done in a background thread and Hangfire will requeue multiple times // We can't rethrow because Fix match is done in a background thread and Hangfire will requeue multiple times
_logger.LogInformation(ex, "Rate limit hit for matching {SeriesName} with Kavita+", series.Name); _logger.LogInformation(ex, "Rate limit hit for matching {SeriesName} with Kavita+", series.Name);
// Fire SignalR event about this
await _eventHub.SendMessageAsync(MessageFactory.ExternalMatchRateLimitError,
MessageFactory.ExternalMatchRateLimitErrorEvent(series.Id, series.Name));
} }
} }

View file

@ -152,6 +152,10 @@ public static class MessageFactory
/// A Person merged has been merged into another /// A Person merged has been merged into another
/// </summary> /// </summary>
public const string PersonMerged = "PersonMerged"; public const string PersonMerged = "PersonMerged";
/// <summary>
/// A Rate limit error was hit when matching a series with Kavita+
/// </summary>
public const string ExternalMatchRateLimitError = "ExternalMatchRateLimitError";
public static SignalRMessage DashboardUpdateEvent(int userId) public static SignalRMessage DashboardUpdateEvent(int userId)
{ {
@ -679,4 +683,16 @@ public static class MessageFactory
}, },
}; };
} }
public static SignalRMessage ExternalMatchRateLimitErrorEvent(int seriesId, string seriesName)
{
return new SignalRMessage()
{
Name = ExternalMatchRateLimitError,
Body = new
{
seriesId = seriesId,
seriesName = seriesName,
},
};
}
} }

View file

@ -0,0 +1,4 @@
export interface ExternalMatchRateLimitErrorEvent {
seriesId: number;
seriesName: string;
}

View file

@ -10,6 +10,7 @@ import { User } from '../_models/user';
import {DashboardUpdateEvent} from "../_models/events/dashboard-update-event"; import {DashboardUpdateEvent} from "../_models/events/dashboard-update-event";
import {SideNavUpdateEvent} from "../_models/events/sidenav-update-event"; import {SideNavUpdateEvent} from "../_models/events/sidenav-update-event";
import {SiteThemeUpdatedEvent} from "../_models/events/site-theme-updated-event"; import {SiteThemeUpdatedEvent} from "../_models/events/site-theme-updated-event";
import {ExternalMatchRateLimitErrorEvent} from "../_models/events/external-match-rate-limit-error-event";
export enum EVENTS { export enum EVENTS {
UpdateAvailable = 'UpdateAvailable', UpdateAvailable = 'UpdateAvailable',
@ -114,6 +115,10 @@ export enum EVENTS {
* A Person merged has been merged into another * A Person merged has been merged into another
*/ */
PersonMerged = 'PersonMerged', PersonMerged = 'PersonMerged',
/**
* A Rate limit error was hit when matching a series with Kavita+
*/
ExternalMatchRateLimitError = 'ExternalMatchRateLimitError'
} }
export interface Message<T> { export interface Message<T> {
@ -236,6 +241,13 @@ export class MessageHubService {
}); });
}); });
this.hubConnection.on(EVENTS.ExternalMatchRateLimitError, resp => {
this.messagesSource.next({
event: EVENTS.ExternalMatchRateLimitError,
payload: resp.body as ExternalMatchRateLimitErrorEvent
});
});
this.hubConnection.on(EVENTS.NotificationProgress, (resp: NotificationProgressEvent) => { this.hubConnection.on(EVENTS.NotificationProgress, (resp: NotificationProgressEvent) => {
this.messagesSource.next({ this.messagesSource.next({
event: EVENTS.NotificationProgress, event: EVENTS.NotificationProgress,

View file

@ -1,7 +1,7 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core'; import {ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnInit} from '@angular/core';
import {LicenseService} from "../../_services/license.service"; import {LicenseService} from "../../_services/license.service";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {TranslocoDirective} from "@jsverse/transloco"; import {translate, TranslocoDirective} from "@jsverse/transloco";
import {ImageComponent} from "../../shared/image/image.component"; import {ImageComponent} from "../../shared/image/image.component";
import {ImageService} from "../../_services/image.service"; import {ImageService} from "../../_services/image.service";
import {Series} from "../../_models/series"; import {Series} from "../../_models/series";
@ -23,6 +23,8 @@ import {EVENTS, MessageHubService} from "../../_services/message-hub.service";
import {ScanSeriesEvent} from "../../_models/events/scan-series-event"; import {ScanSeriesEvent} from "../../_models/events/scan-series-event";
import {LibraryTypePipe} from "../../_pipes/library-type.pipe"; import {LibraryTypePipe} from "../../_pipes/library-type.pipe";
import {allKavitaPlusMetadataApplicableTypes} from "../../_models/library/library"; import {allKavitaPlusMetadataApplicableTypes} from "../../_models/library/library";
import {ExternalMatchRateLimitErrorEvent} from "../../_models/events/external-match-rate-limit-error-event";
import {ToastrService} from "ngx-toastr";
@Component({ @Component({
selector: 'app-manage-matched-metadata', selector: 'app-manage-matched-metadata',
@ -55,6 +57,7 @@ export class ManageMatchedMetadataComponent implements OnInit {
private readonly manageService = inject(ManageService); private readonly manageService = inject(ManageService);
private readonly messageHub = inject(MessageHubService); private readonly messageHub = inject(MessageHubService);
private readonly cdRef = inject(ChangeDetectorRef); private readonly cdRef = inject(ChangeDetectorRef);
private readonly toastr = inject(ToastrService);
protected readonly imageService = inject(ImageService); protected readonly imageService = inject(ImageService);
@ -81,6 +84,11 @@ export class ManageMatchedMetadataComponent implements OnInit {
} }
} }
if (message.event == EVENTS.ExternalMatchRateLimitError) {
const evt = message.payload as ExternalMatchRateLimitErrorEvent;
this.toastr.error(translate('toasts.external-match-rate-error', {seriesName: evt.seriesName}))
}
}); });

View file

@ -2743,7 +2743,8 @@
"webtoon-override": "Switching to Webtoon mode due to images representing a webtoon.", "webtoon-override": "Switching to Webtoon mode due to images representing a webtoon.",
"scrobble-gen-init": "Enqueued a job to generate scrobble events from past reading history and ratings, syncing them with connected services.", "scrobble-gen-init": "Enqueued a job to generate scrobble events from past reading history and ratings, syncing them with connected services.",
"series-bound-to-reading-profile": "Series bound to Reading Profile {{name}}", "series-bound-to-reading-profile": "Series bound to Reading Profile {{name}}",
"library-bound-to-reading-profile": "Library bound to Reading Profile {{name}}" "library-bound-to-reading-profile": "Library bound to Reading Profile {{name}}",
"external-match-rate-error": "Kavita ran out of rate looking up {{seriesName}}. Try again in 5 minutes."
}, },
"read-time-pipe": { "read-time-pipe": {