Files
MilkyShots/Lactose/Jobs/Job.cs
REDCODE 9d9491253b refactor: clean up imports, simplify checks, and add SearchDropdown component
- Remove unused using directives across C# and Razor files
- Remove unused IServiceProvider from SettingsRepository
- Simplify null/empty string checks in StatsRepository
- Add null-safe navigation for Albums/Tags in stats queries
- Initialize Asset.Hash default to prevent null refs
- Deduplicate AssetIds in AssetPicker
- Add OnStartedWaiting/OnFinishedWaiting/OnProgressChanged to Job
- Add global SearchDropdown component with keyboard nav
- Fix XML doc param mismatches
2026-07-12 20:47:24 +02:00

168 lines
6.4 KiB
C#

using Butter.Types;
namespace Lactose.Jobs;
/// <summary>
/// Abstract class representing a job that can be executed asynchronously.
/// Provides events for job lifecycle (Started, ProgressChanged, Completed, Canceled, Failed).
/// Inherit from this class and implement the TaskJob method to define the job's functionality.
/// </summary>
public abstract class Job {
/// <summary>
/// Gets the unique identifier of the job.
/// </summary>
public virtual Guid Id { get; } = Guid.NewGuid();
/// <summary>
/// Gets or sets the parent job ID, if this is a sub-job.
/// </summary>
public virtual Guid? ParentJobId { get; set; }
/// <summary>
/// Gets the name of the job.
/// </summary>
public virtual string Name { get; } = "Unnamed Job";
/// <summary>
/// Gets the status tracker for this job.
/// </summary>
public virtual JobStatus JobStatus { get; }
/// <summary>
/// Gets or sets the DI scope created for this job's dependencies.
/// Disposed automatically when the job completes.
/// </summary>
public IServiceScope? Scope { get; set; }
Task? task;
CancellationTokenSource? cts;
CancellationToken? _parentToken;
/// <summary>
/// Initialises a new job instance.
/// </summary>
protected Job() => JobStatus = new JobStatus(this);
/// <summary>
/// Links this job's cancellation token to a parent token. When the parent token is cancelled,
/// this job's token is also cancelled automatically — even before <see cref="Start"/> is called.
/// </summary>
/// <param name="parentToken">The parent job's cancellation token.</param>
public void LinkParentToken(CancellationToken parentToken) {
_parentToken = parentToken;
}
/// <summary>
/// Sets up the task and the cancellation token, then starts the task (triggering the Started event)
/// Also sets up a continuation to handle completion, failure, or cancellation of the task.
/// </summary>
public virtual void Start() {
JobStatus.Start();
cts = _parentToken.HasValue
? CancellationTokenSource.CreateLinkedTokenSource(_parentToken.Value)
: new CancellationTokenSource();
task = Task.Run(() => TaskJob(cts.Token));
task.ContinueWith(t => {
if (t.IsCanceled && JobStatus.Status != EJobStatus.Canceled) {
JobStatus.Cancel();
}
if (t.IsFaulted && JobStatus.Status != EJobStatus.Canceled) {
JobStatus.Fail($"Unhandled exception: {t.Exception?.InnerException?.Message}");
}
switch (JobStatus.Status) {
case EJobStatus.Canceled:
Canceled?.Invoke(this, JobStatus);
break;
case EJobStatus.Failed:
Failed?.Invoke(this, JobStatus);
break;
case EJobStatus.Running:
case EJobStatus.Waiting:
case EJobStatus.Completed:
case EJobStatus.CompletedWithErrors:
Completed?.Invoke(this, JobStatus);
break;
case EJobStatus.Queued:
default:
Completed?.Invoke(this, JobStatus);
break;
}
cts?.Dispose();
Done?.Invoke(this, JobStatus);
}
);
Started?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Override this method to implement the job's functionality. The provided CancellationToken can be used to check for cancellation requests.
/// Invoke ProgressChanged event to report progress updates.
/// </summary>
/// <param name="token">Cancellation toke to retrieve when a task cancellation has been requested</param>
protected abstract Task TaskJob(CancellationToken token);
/// <summary>
/// Cancels the job. For running jobs, signals the cancellation token. For queued jobs,
/// marks the status as Canceled and fires completion events directly.
/// </summary>
public virtual void Cancel() {
if (cts is not null) {
cts.Cancel();
} else {
JobStatus.Cancel();
Canceled?.Invoke(this, JobStatus);
Done?.Invoke(this, JobStatus);
}
}
/// <summary>
/// Raised when the job starts running.
/// </summary>
public virtual event EventHandler? Started;
/// <summary>
/// Raised when the job makes progress. The JobStatus parameter contains the current progress (0 to 1).
/// </summary>
public virtual event EventHandler<JobStatus>? ProgressChanged;
/// <summary>
/// Raised when the job transitions from running to waiting (e.g., waiting for resources or sub jobs).
/// </summary>
public virtual event EventHandler<JobStatus>? StartedWaiting;
/// <summary>
/// Raised when the job transitions from waiting to running.
/// </summary>
public virtual event EventHandler<JobStatus>? FinishedWaiting;
/// <summary>
/// Raised when the job completes successfully.
/// </summary>
public virtual event EventHandler<JobStatus>? Completed;
/// <summary>
/// Raised when the job is canceled by the user or system.
/// </summary>
public virtual event EventHandler<JobStatus>? Canceled;
/// <summary>
/// Raised when the job fails due to an unhandled exception.
/// </summary>
public virtual event EventHandler<JobStatus>? Failed;
/// <summary>
/// Raised when the job is done, regardless of the final status (Completed, Canceled, or Failed).
/// </summary>
public virtual event EventHandler<JobStatus>? Done;
/// <summary>
/// Raises the StartedWaiting event.
/// </summary>
/// <param name="e">The job status.</param>
protected virtual void OnStartedWaiting(JobStatus e) { StartedWaiting?.Invoke(this, e); }
/// <summary>
/// Raises the FinishedWaiting event.
/// </summary>
/// <param name="e">The job status.</param>
protected virtual void OnFinishedWaiting(JobStatus e) { FinishedWaiting?.Invoke(this, e); }
/// <summary>
/// Raises the ProgressChanged event.
/// </summary>
/// <param name="e">The job status.</param>
protected virtual void OnProgressChanged(JobStatus e) { ProgressChanged?.Invoke(this, e); }
}