mirror of
https://github.com/Crunchy-DL/Crunchy-Downloader.git
synced 2026-05-16 23:12:19 +00:00
- Added **fallback for sync failures** [#407](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/407) - Added **history setting** to remove non-existent series/episodes on refresh [#420](https://github.com/Crunchy-DL/Crunchy-Downloader/issues/420) - Added **movies to history** - Added **queue persistence** - Changed **download item state handling** - Changed **download item removal processing** - Made small changes to **font detection** - Changed **rate limit error handling** - Fixed issue where **files were not always cleaned up** for removed downloads - Fixed **crash when the queue list was modified** - Fixed **changelog parsing** not handling versions like `vX.X.X.X`, which caused changes to be re-added on every restart
83 lines
No EOL
2.3 KiB
C#
83 lines
No EOL
2.3 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CRD.Utils;
|
|
|
|
public class PeriodicWorkRunner(Func<CancellationToken, Task> work) : IDisposable{
|
|
private CancellationTokenSource? cts;
|
|
private Task? loopTask;
|
|
|
|
private TimeSpan currentInterval;
|
|
|
|
public DateTime LastRunTime = DateTime.MinValue;
|
|
|
|
public void StartOrRestart(TimeSpan interval, bool runImmediately = false, bool force = false){
|
|
if (interval <= TimeSpan.Zero){
|
|
Stop();
|
|
currentInterval = Timeout.InfiniteTimeSpan;
|
|
return;
|
|
}
|
|
|
|
if (!force && interval == currentInterval){
|
|
return;
|
|
}
|
|
|
|
currentInterval = interval;
|
|
|
|
Stop();
|
|
|
|
cts = new CancellationTokenSource();
|
|
loopTask = RunLoopAsync(interval, runImmediately, cts.Token);
|
|
}
|
|
|
|
public void StartOrRestartMinutes(int minutes, bool runImmediately = false, bool force = false)
|
|
=> StartOrRestart(TimeSpan.FromMinutes(minutes), runImmediately);
|
|
|
|
public void Stop(){
|
|
if (cts is null) return;
|
|
|
|
try{
|
|
cts.Cancel();
|
|
} finally{
|
|
cts.Dispose();
|
|
cts = null;
|
|
}
|
|
}
|
|
|
|
private async Task RunLoopAsync(TimeSpan interval, bool runImmediately, CancellationToken token){
|
|
if (runImmediately){
|
|
await SafeRunWork(token).ConfigureAwait(false);
|
|
}
|
|
|
|
using var timer = new PeriodicTimer(interval);
|
|
|
|
try{
|
|
while (await timer.WaitForNextTickAsync(token).ConfigureAwait(false)){
|
|
await SafeRunWork(token).ConfigureAwait(false);
|
|
}
|
|
} catch (OperationCanceledException){
|
|
}
|
|
}
|
|
|
|
private int running;
|
|
|
|
private async Task SafeRunWork(CancellationToken token){
|
|
if (Interlocked.Exchange(ref running, 1) == 1){
|
|
Console.Error.WriteLine("Task is already running!");
|
|
return;
|
|
}
|
|
|
|
try{
|
|
await work(token).ConfigureAwait(false);
|
|
LastRunTime = DateTime.Now;
|
|
} catch (OperationCanceledException) when (token.IsCancellationRequested){
|
|
} catch (Exception ex){
|
|
Console.Error.WriteLine(ex);
|
|
} finally{
|
|
Interlocked.Exchange(ref running, 0);
|
|
}
|
|
}
|
|
|
|
public void Dispose() => Stop();
|
|
} |