diff --git a/API/Controllers/ServerController.cs b/API/Controllers/ServerController.cs index 46c7dc9f2..ba4258531 100644 --- a/API/Controllers/ServerController.cs +++ b/API/Controllers/ServerController.cs @@ -291,4 +291,15 @@ public class ServerController : BaseApiController return Ok(await _emailService.GetVersion(emailServiceUrl)); } + /// + /// Checks for updates and pushes an event to the UI + /// + /// + [HttpGet("check-for-updates")] + public async Task CheckForAnnouncements() + { + await _taskScheduler.CheckForUpdate(); + return Ok(); + } + } diff --git a/API/Services/TaskScheduler.cs b/API/Services/TaskScheduler.cs index 09f1a155d..3e1b6cdb2 100644 --- a/API/Services/TaskScheduler.cs +++ b/API/Services/TaskScheduler.cs @@ -35,6 +35,7 @@ public interface ITaskScheduler void ScanSiteThemes(); void CovertAllCoversToEncoding(); Task CleanupDbEntries(); + Task CheckForUpdate(); } public class TaskScheduler : ITaskScheduler @@ -242,14 +243,14 @@ public class TaskScheduler : ITaskScheduler public void ScheduleUpdaterTasks() { _logger.LogInformation("Scheduling Auto-Update tasks"); - RecurringJob.AddOrUpdate(CheckForUpdateId, () => CheckForUpdate(), $"0 */{Rnd.Next(4, 6)} * * *", RecurringJobOptions); + RecurringJob.AddOrUpdate(CheckForUpdateId, () => CheckForUpdate(), $"0 */{Rnd.Next(1, 2)} * * *", RecurringJobOptions); BackgroundJob.Enqueue(() => CheckForUpdate()); } public void ScanFolder(string folderPath, TimeSpan delay) { var normalizedFolder = Tasks.Scanner.Parser.Parser.NormalizePath(folderPath); - if (HasAlreadyEnqueuedTask(ScannerService.Name, "ScanFolder", new object[] { normalizedFolder })) + if (HasAlreadyEnqueuedTask(ScannerService.Name, "ScanFolder", [normalizedFolder])) { _logger.LogInformation("Skipped scheduling ScanFolder for {Folder} as a job already queued", normalizedFolder); diff --git a/UI/Web/hash-localization.js b/UI/Web/hash-localization.js index 48d51761a..0d1621d90 100644 --- a/UI/Web/hash-localization.js +++ b/UI/Web/hash-localization.js @@ -15,13 +15,11 @@ function generateChecksum(str, algorithm, encoding) { const result = {}; glob.sync(`${jsonFilesDir}**/*.json`).forEach(path => { - console.log('Calculating hash for ', path); let tokens = path.split('dist\\browser\\assets\\langs\\'); if (tokens.length === 1) { tokens = path.split('dist/browser/assets/langs/'); } const lang = tokens[1]; - console.log('Language: ', lang); const content = fs.readFileSync(path, { encoding: 'utf-8' }); result[lang.replace('.json', '')] = generateChecksum(content); }); diff --git a/UI/Web/src/app/_services/server.service.ts b/UI/Web/src/app/_services/server.service.ts index 3f3a88a25..803857554 100644 --- a/UI/Web/src/app/_services/server.service.ts +++ b/UI/Web/src/app/_services/server.service.ts @@ -41,6 +41,10 @@ export class ServerService { return this.httpClient.get(this.baseUrl + 'server/check-update', {}); } + checkForUpdates() { + return this.httpClient.get(this.baseUrl + 'server/check-for-updates', {}); + } + getChangelog() { return this.httpClient.get(this.baseUrl + 'server/changelog', {}); } diff --git a/UI/Web/src/app/app.component.ts b/UI/Web/src/app/app.component.ts index 85a43eb6d..94c79b1a6 100644 --- a/UI/Web/src/app/app.component.ts +++ b/UI/Web/src/app/app.component.ts @@ -7,11 +7,12 @@ import { NavService } from './_services/nav.service'; import { filter } from 'rxjs/operators'; import {NgbModal, NgbModalConfig, NgbOffcanvas, NgbRatingConfig} from '@ng-bootstrap/ng-bootstrap'; import { DOCUMENT, NgClass, NgIf, AsyncPipe } from '@angular/common'; -import { Observable } from 'rxjs'; +import {interval, Observable, switchMap} from 'rxjs'; import {ThemeService} from "./_services/theme.service"; import { SideNavComponent } from './sidenav/_components/side-nav/side-nav.component'; import {NavHeaderComponent} from "./nav/_components/nav-header/nav-header.component"; import {takeUntilDestroyed} from "@angular/core/rxjs-interop"; +import {ServerService} from "./_services/server.service"; @Component({ selector: 'app-root', @@ -28,6 +29,7 @@ export class AppComponent implements OnInit { private readonly offcanvas = inject(NgbOffcanvas); public readonly navService = inject(NavService); public readonly cdRef = inject(ChangeDetectorRef); + public readonly serverService = inject(ServerService); constructor(private accountService: AccountService, private libraryService: LibraryService, @@ -95,6 +97,14 @@ export class AppComponent implements OnInit { this.libraryService.getLibraryNames().pipe(take(1), shareReplay({refCount: true, bufferSize: 1})).subscribe(); // On load, make an initial call for valid license this.accountService.hasValidLicense().subscribe(); + + interval(4 * 60 * 60 * 1000) // 4 hours in milliseconds + .pipe( + switchMap(() => this.accountService.currentUser$), + filter(u => this.accountService.hasAdminRole(u!)), + switchMap(_ => this.serverService.checkForUpdates()) + ) + .subscribe(); } } } diff --git a/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts b/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts index 379b68521..515d9dace 100644 --- a/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts +++ b/UI/Web/src/app/cards/_modals/edit-series-modal/edit-series-modal.component.ts @@ -440,10 +440,6 @@ export class EditSeriesModalComponent implements OnInit { return a.isoCode == b.isoCode; } - if (this.metadata.language === undefined || this.metadata.language === null || this.metadata.language === '') { - this.metadata.language = 'en'; - } - const l = this.validLanguages.find(l => l.isoCode === this.metadata.language); if (l !== undefined) { this.languageSettings.savedData = l; diff --git a/UI/Web/src/app/dashboard/_components/dashboard.component.ts b/UI/Web/src/app/dashboard/_components/dashboard.component.ts index 0475cf9fe..ce011ceb7 100644 --- a/UI/Web/src/app/dashboard/_components/dashboard.component.ts +++ b/UI/Web/src/app/dashboard/_components/dashboard.component.ts @@ -32,7 +32,7 @@ import {StreamType} from "../../_models/dashboard/stream-type.enum"; import {LoadingComponent} from "../../shared/loading/loading.component"; import {ScrobbleProvider, ScrobblingService} from "../../_services/scrobbling.service"; import {ToastrService} from "ngx-toastr"; - +import {ServerService} from "../../_services/server.service"; enum StreamId { OnDeck, @@ -41,6 +41,7 @@ enum StreamId { MoreInGenre, } + @Component({ selector: 'app-dashboard', templateUrl: './dashboard.component.html', @@ -67,6 +68,7 @@ export class DashboardComponent implements OnInit { private readonly dashboardService = inject(DashboardService); private readonly scrobblingService = inject(ScrobblingService); private readonly toastr = inject(ToastrService); + private readonly serverService = inject(ServerService); libraries$: Observable = this.libraryService.getLibraries().pipe(take(1), takeUntilDestroyed(this.destroyRef)) isLoadingDashboard = true; diff --git a/openapi.json b/openapi.json index d3396ce87..fb7dca492 100644 --- a/openapi.json +++ b/openapi.json @@ -7,7 +7,7 @@ "name": "GPL-3.0", "url": "https://github.com/Kareadita/Kavita/blob/develop/LICENSE" }, - "version": "0.7.12.5" + "version": "0.7.12.7" }, "servers": [ { @@ -9869,6 +9869,19 @@ } } }, + "/api/Server/check-for-updates": { + "get": { + "tags": [ + "Server" + ], + "summary": "Checks for updates and pushes an event to the UI", + "responses": { + "200": { + "description": "Success" + } + } + } + }, "/api/Settings/base-url": { "get": { "tags": [