From b4d004e402d6fc2d64e656ba96be557fbb217543 Mon Sep 17 00:00:00 2001 From: Joseph Milazzo Date: Sun, 8 Mar 2026 14:49:19 -0500 Subject: [PATCH] Ensure we deconstruct volumes when they consist of multiple items --- Kavita.Server/Controllers/PersonController.cs | 13 ------- UI/Web/src/app/_services/account.service.ts | 22 ++++++----- .../_components/dashboard.component.ts | 2 +- .../app/shared/_services/download.service.ts | 37 ++++++++++++++++++- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/Kavita.Server/Controllers/PersonController.cs b/Kavita.Server/Controllers/PersonController.cs index 4209be842..88245491a 100644 --- a/Kavita.Server/Controllers/PersonController.cs +++ b/Kavita.Server/Controllers/PersonController.cs @@ -221,19 +221,6 @@ public class PersonController( return Ok(await unitOfWork.PersonRepository.GetSeriesKnownFor(personId, UserId)); } - /// - /// Return external Series the person is an artist/author of. Requires Admin due to age rating restrictions. - /// - /// - /// - [PersonAccess] - [KPlus] - [Authorize(PolicyGroups.AdminPolicy)] - [HttpGet("external-series")] - public async Task>> GetExternalSeries(int personId) - { - return Ok(await unitOfWork.ExternalSeriesMetadataRepository.GetExternalSeriesForPerson(personId, UserId)); - } /// /// Returns all individual chapters by role. Limited to 20 results. diff --git a/UI/Web/src/app/_services/account.service.ts b/UI/Web/src/app/_services/account.service.ts index 49bcaded1..e4a7358da 100644 --- a/UI/Web/src/app/_services/account.service.ts +++ b/UI/Web/src/app/_services/account.service.ts @@ -272,15 +272,14 @@ export class AccountService { this.stopRefreshTokenTimer(); - if (user) { - if (!isSameUser) { - this.messageHub.stopHubConnection(); - this.messageHub.createHubConnection(user); - this.licenseService.checkForValidLicense().subscribe(); - } - if (user.token) { - this.startRefreshTokenTimer(); - } + if (user && !isSameUser) { + this.messageHub.stopHubConnection(); + this.messageHub.createHubConnection(user); + this.licenseService.checkForValidLicense().subscribe(); + } + + if (user?.token) { + this.startRefreshTokenTimer(); } } @@ -447,7 +446,10 @@ export class AccountService { refreshAccount(): Observable { if (!this._currentUser()) return of(null); return this.httpClient.get(this.baseUrl + 'account/refresh-account').pipe(map((user: User) => { - if (user) this.setCurrentUser({ ...user }); + if (user) { + this.setCurrentUser({...user}); + this.licenseService.checkForValidLicense().subscribe(); + } return user; })); } diff --git a/UI/Web/src/app/dashboard/_components/dashboard.component.ts b/UI/Web/src/app/dashboard/_components/dashboard.component.ts index 05e96a2f5..d0c58cfc9 100644 --- a/UI/Web/src/app/dashboard/_components/dashboard.component.ts +++ b/UI/Web/src/app/dashboard/_components/dashboard.component.ts @@ -134,7 +134,7 @@ export class DashboardComponent { } }); - this.licenseService.checkForValidLicense() + this.licenseService.hasAnyLicense() .pipe( filter((hasLic: boolean) => hasLic), switchMap(_ => this.scrobblingService.hasTokenExpired(ScrobbleProvider.AniList)), diff --git a/UI/Web/src/app/shared/_services/download.service.ts b/UI/Web/src/app/shared/_services/download.service.ts index 7cf480235..0457f2e34 100644 --- a/UI/Web/src/app/shared/_services/download.service.ts +++ b/UI/Web/src/app/shared/_services/download.service.ts @@ -249,7 +249,7 @@ export class DownloadService { this.downloadSeries(entity as Series); break; case 'volume': - this.enqueueSingle(entity as Volume, 'volume', '', libraryId, seriesId); + this.downloadVolume(entity as Volume, libraryId, seriesId); break; case 'chapter': this.enqueueSingle(entity as Chapter, 'chapter', '', libraryId, seriesId); @@ -489,6 +489,41 @@ export class DownloadService { return this.httpClient.post>(this.baseUrl + 'download/bulk-series-size', seriesIds); } + private downloadVolumeSize(volumeId: number) { + return this.httpClient.get(this.baseUrl + 'download/volume-size?volumeId=' + volumeId); + } + + private downloadChapterSize(chapterId: number) { + return this.httpClient.get(this.baseUrl + 'download/chapter-size?chapterId=' + chapterId); + } + + private downloadVolume(volume: Volume, libraryId: number, seriesId: number) { + this.debugLog('downloadVolume()', volume.minNumber); + + // Volumes can be either a bunch of chapters or just 1 + if (volume.chapters.length === 1) { + this.enqueueSingle(volume, 'volume', '', libraryId, seriesId); + return; + } + this.debugLog(`downloadVolume() decomposed into ${volume.chapters.length} items`); + + const items = volume.chapters.map(c => ({ entity: c as Chapter, entityType: 'chapter' as const })); + + const userPrefs = this.accountService.userPreferences(); + if (userPrefs?.promptForDownloadSize && items.length > 0) { + // Single size call for the whole series, single confirm dialog + this.downloadVolumeSize(volume.id).pipe( + switchMap(async size => this.confirmSize(size, 'volume')), + filter(confirmed => confirmed), + takeUntilDestroyed(this.destroyRef) + ).subscribe(() => this.enqueueItems(items, '', libraryId, seriesId)); + } else { + this.enqueueItems(items, '', libraryId, seriesId); + } + + + } + private downloadSeries(series: Series) { this.debugLog('downloadSeries()', series.name); this.seriesService.getSeriesDetail(series.id).pipe(