diff --git a/mediarepo-api/Cargo.toml b/mediarepo-api/Cargo.toml index 7d6ed34..9cc15f6 100644 --- a/mediarepo-api/Cargo.toml +++ b/mediarepo-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mediarepo-api" -version = "0.32.0" +version = "0.32.1" edition = "2018" license = "gpl-3" diff --git a/mediarepo-api/src/client_api/job.rs b/mediarepo-api/src/client_api/job.rs index cfbe8d1..918b18b 100644 --- a/mediarepo-api/src/client_api/job.rs +++ b/mediarepo-api/src/client_api/job.rs @@ -34,4 +34,10 @@ impl JobApi { Ok(()) } + + /// Checks if a particular job is already running + #[tracing::instrument(level = "debug", skip(self))] + pub async fn is_job_running(&self, job_type: JobType) -> ApiResult { + self.emit_and_get("is_job_running", job_type, None).await + } } diff --git a/mediarepo-api/src/tauri_plugin/commands/job.rs b/mediarepo-api/src/tauri_plugin/commands/job.rs index 313e613..d4fa84a 100644 --- a/mediarepo-api/src/tauri_plugin/commands/job.rs +++ b/mediarepo-api/src/tauri_plugin/commands/job.rs @@ -9,3 +9,11 @@ pub async fn run_job(api_state: ApiAccess<'_>, job_type: JobType, sync: bool) -> Ok(()) } + +#[tauri::command] +pub async fn is_job_running(api_state: ApiAccess<'_>, job_type: JobType) -> PluginResult { + let api = api_state.api().await?; + let running = api.job.is_job_running(job_type).await?; + + Ok(running) +} diff --git a/mediarepo-api/src/tauri_plugin/mod.rs b/mediarepo-api/src/tauri_plugin/mod.rs index 85e581a..e32264a 100644 --- a/mediarepo-api/src/tauri_plugin/mod.rs +++ b/mediarepo-api/src/tauri_plugin/mod.rs @@ -75,7 +75,8 @@ impl MediarepoPlugin { get_file_tag_map, all_sorting_presets, add_sorting_preset, - delete_sorting_preset + delete_sorting_preset, + is_job_running ]), } } diff --git a/mediarepo-daemon/mediarepo-socket/src/namespaces/jobs.rs b/mediarepo-daemon/mediarepo-socket/src/namespaces/jobs.rs index d86b3f1..e1e208d 100644 --- a/mediarepo-daemon/mediarepo-socket/src/namespaces/jobs.rs +++ b/mediarepo-daemon/mediarepo-socket/src/namespaces/jobs.rs @@ -20,8 +20,9 @@ impl NamespaceProvider for JobsNamespace { fn register(handler: &mut EventHandler) { events!(handler, - "run_job" => Self::run_job - ) + "run_job" => Self::run_job, + "is_job_running" => Self::is_job_running + ); } } @@ -59,6 +60,26 @@ impl JobsNamespace { Ok(Response::empty()) } + + #[tracing::instrument(skip_all)] + pub async fn is_job_running(ctx: &Context, event: Event) -> IPCResult { + let job_type = event.payload::()?; + let dispatcher = get_job_dispatcher_from_context(ctx).await; + + let running = match job_type { + JobType::MigrateContentDescriptors => { + is_job_running::(&dispatcher).await + } + JobType::CalculateSizes => is_job_running::(&dispatcher).await, + JobType::GenerateThumbnails => { + is_job_running::(&dispatcher).await + } + JobType::CheckIntegrity => is_job_running::(&dispatcher).await, + JobType::Vacuum => is_job_running::(&dispatcher).await, + }; + + Response::payload(&ctx, running) + } } async fn dispatch_job( @@ -107,3 +128,12 @@ async fn calculate_all_sizes(ctx: &Context) -> RepoResult<()> { Ok(()) } + +async fn is_job_running(dispatcher: &JobDispatcher) -> bool { + if let Some(handle) = dispatcher.get_handle::().await { + let state = handle.state().await; + state == JobState::Running + } else { + false + } +} diff --git a/mediarepo-ui/src/api/Api.ts b/mediarepo-ui/src/api/Api.ts index 27e1d58..7c14413 100644 --- a/mediarepo-ui/src/api/Api.ts +++ b/mediarepo-ui/src/api/Api.ts @@ -19,6 +19,7 @@ import { GetSizeRequest, GetTagsForFilesRequest, InitRepositoryRequest, + IsJobRunningRequest, ReadFileRequest, RemoveRepositoryRequest, ResolvePathsToFilesRequest, @@ -187,6 +188,10 @@ export class MediarepoApi { return this.invokePlugin(ApiFunction.RunJob, request); } + public static async isJobRunning(request: IsJobRunningRequest): Promise { + return this.invokePlugin(ApiFunction.IsJobRunning, request); + } + public static async getAllSortingPresets(): Promise { return ShortCache.cached("sorting-presets", () => this.invokePlugin(ApiFunction.GetAllSortingPresets), 1000); } diff --git a/mediarepo-ui/src/api/api-types/functions.ts b/mediarepo-ui/src/api/api-types/functions.ts index 44f8041..201a8aa 100644 --- a/mediarepo-ui/src/api/api-types/functions.ts +++ b/mediarepo-ui/src/api/api-types/functions.ts @@ -40,6 +40,7 @@ export enum ApiFunction { SetFrontendState = "set_frontend_state", // jobs RunJob = "run_job", + IsJobRunning = "is_job_running", // presets GetAllSortingPresets = "all_sorting_presets", AddSortingPreset = "add_sorting_preset", diff --git a/mediarepo-ui/src/api/api-types/requests.ts b/mediarepo-ui/src/api/api-types/requests.ts index 2a813b6..d87acdf 100644 --- a/mediarepo-ui/src/api/api-types/requests.ts +++ b/mediarepo-ui/src/api/api-types/requests.ts @@ -117,3 +117,7 @@ export type AddSortingPresetRequest = { export type DeleteSortingPresetRequest = { id: number }; + +export type IsJobRunningRequest = { + jobType: JobType, +} diff --git a/mediarepo-ui/src/app/components/core/core.module.ts b/mediarepo-ui/src/app/components/core/core.module.ts index 37432e7..19658f5 100644 --- a/mediarepo-ui/src/app/components/core/core.module.ts +++ b/mediarepo-ui/src/app/components/core/core.module.ts @@ -17,7 +17,7 @@ import {MatSelectModule} from "@angular/material/select"; import {MatCheckboxModule} from "@angular/material/checkbox"; import {MatDividerModule} from "@angular/material/divider"; import {NgIconsModule} from "@ng-icons/core"; -import {MatPlus} from "@ng-icons/material-icons/baseline"; +import {MatMoreVert, MatPlus} from "@ng-icons/material-icons/baseline"; import {MatMenuModule} from "@angular/material/menu"; import {InputModule} from "../shared/input/input.module"; import {SidebarModule} from "../shared/sidebar/sidebar.module"; @@ -72,7 +72,7 @@ import {AboutDialogComponent} from "./repositories-tab/repository-overview/about MatProgressBarModule, MatCheckboxModule, ScrollingModule, - NgIconsModule.withIcons({ MatPlus }), + NgIconsModule.withIcons({ MatPlus, MatMoreVert }), FlexModule, MatButtonModule, MatMenuModule, diff --git a/mediarepo-ui/src/app/components/shared/repository/repository-maintenance/repository-maintenance.component.ts b/mediarepo-ui/src/app/components/shared/repository/repository-maintenance/repository-maintenance.component.ts index 00296fc..177655e 100644 --- a/mediarepo-ui/src/app/components/shared/repository/repository-maintenance/repository-maintenance.component.ts +++ b/mediarepo-ui/src/app/components/shared/repository/repository-maintenance/repository-maintenance.component.ts @@ -1,4 +1,4 @@ -import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from "@angular/core"; +import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnInit} from "@angular/core"; import {JobService} from "../../../../services/job/job.service"; import {JobType} from "../../../../../api/api-types/job"; import {MatDialog} from "@angular/material/dialog"; @@ -13,12 +13,12 @@ import {BehaviorSubject} from "rxjs"; styleUrls: ["./repository-maintenance.component.scss"], changeDetection: ChangeDetectionStrategy.OnPush }) -export class RepositoryMaintenanceComponent { - +export class RepositoryMaintenanceComponent implements OnInit, OnDestroy { public jobState: { [Property in JobType]?: boolean } = { CalculateSizes: false, GenerateThumbnails: false, }; + private jobStatusInterval: any; constructor( private changeDetector: ChangeDetectorRef, @@ -28,14 +28,23 @@ export class RepositoryMaintenanceComponent { ) { } + public ngOnDestroy(): void { + clearInterval(this.jobStatusInterval); + } + + public async ngOnInit() { + await this.updateJobStatus(); + this.jobStatusInterval = setInterval(() => this.updateJobStatus(), 10000); + } + public async runJob(jobType: JobType, runAsync: boolean) { if (runAsync) { this.jobState[jobType] = true; this.jobService.runJob(jobType).then(() => this.delay(1000)).catch(this.logger.error).finally(() => { this.jobState[jobType] = false; - this.changeDetector.detectChanges(); + this.changeDetector.markForCheck(); }); - this.changeDetector.detectChanges(); + this.changeDetector.markForCheck(); } else { const dialog = this.dialog.open(BusyDialogComponent, { disableClose: true, @@ -48,13 +57,13 @@ export class RepositoryMaintenanceComponent { } }); try { - this.changeDetector.detectChanges(); + this.changeDetector.markForCheck(); await this.jobService.runJob(jobType); } catch (err: any) { this.logger.error(err); } finally { dialog.close(); - this.changeDetector.detectChanges(); + this.changeDetector.markForCheck(); } } } @@ -65,4 +74,12 @@ export class RepositoryMaintenanceComponent { ms )); } + + private async updateJobStatus() { + const indexedTypes: JobType[] = ["CalculateSizes", "GenerateThumbnails"]; + for (const jobType of indexedTypes) { + this.jobState[jobType] = await this.jobService.isJobRunning(jobType); + } + this.changeDetector.markForCheck(); + } } diff --git a/mediarepo-ui/src/app/services/job/job.service.ts b/mediarepo-ui/src/app/services/job/job.service.ts index 44057c3..d90c6e9 100644 --- a/mediarepo-ui/src/app/services/job/job.service.ts +++ b/mediarepo-ui/src/app/services/job/job.service.ts @@ -13,4 +13,8 @@ export class JobService { public async runJob(jobType: JobType, sync: boolean = true): Promise { return MediarepoApi.runJob({ jobType, sync }); } + + public async isJobRunning(jobType: JobType): Promise { + return MediarepoApi.isJobRunning({ jobType }); + } }