using System; using System.Threading; using System.Threading.Tasks; using FATrace.Com; using FATrace.Model; using NLog; namespace FATrace.OEMApp.Services { public sealed class TimeClearDataService { private readonly Logger _logger = LogManager.GetCurrentClassLogger(); private CancellationTokenSource? _cts; private Task? _loopTask; private TimeSpan _runAt = new TimeSpan(2, 0, 0); private int _retentionDays = 365; private bool _enabled = true; public event Action? Info; public void Start() { if (_cts != null) return; try { _enabled = ConfigHelper.GetBoolOrDefault("DataCleanupEnabled", true); var tod = ConfigHelper.GetStringOrDefault("DataCleanupTimeOfDay", "02:00:00"); if (!TimeSpan.TryParse(tod, out _runAt)) { _runAt = new TimeSpan(2, 0, 0); } _retentionDays = Math.Max(1, ConfigHelper.GetIntOrDefault("DataRetentionDays", 365)); } catch { } if (!_enabled) { _logger.Warn("[TimeClear] 已禁用,未启动"); return; } _cts = new CancellationTokenSource(); _loopTask = Task.Run(() => RunAsync(_cts.Token)); _logger.Info("[TimeClear] 服务已启动,时间点={RunAt}, 保留天数={Days}", _runAt, _retentionDays); SafeInfo($"定时清理服务启动,时间点={_runAt}, 保留天数={_retentionDays}"); } public void Stop() { try { _cts?.Cancel(); } catch { } _cts = null; _logger.Info("[TimeClear] 服务已停止"); } private async Task RunAsync(CancellationToken token) { while (!token.IsCancellationRequested) { try { var now = DateTime.Now; var next = GetNextRunTime(now); var delay = next - now; SafeInfo($"距离下一次清理还有 {delay:hh\\:mm\\:ss},计划时间 {next:yyyy-MM-dd HH:mm:ss}"); await Task.Delay(delay, token); await CleanupOnceAsync(token); } catch (OperationCanceledException) { break; } catch (Exception ex) { _logger.Error(ex, "[TimeClear] 后台循环异常"); try { await Task.Delay(TimeSpan.FromMinutes(1), token); } catch { } } } } private DateTime GetNextRunTime(DateTime now) { var next = now.Date + _runAt; if (next <= now.AddSeconds(1)) next = next.AddDays(1); return next; } private async Task CleanupOnceAsync(CancellationToken token) { var cutoff = DateTime.Now.AddDays(-_retentionDays); _logger.Info("[TimeClear] 开始清理,截止时间: {Cutoff}", cutoff); SafeInfo($"开始清理,截止时间: {cutoff:yyyy-MM-dd HH:mm:ss}"); long delJf = 0, delDl = 0, delRaw = 0, delAct = 0; try { delJf = FSqlContext.FDb.Delete().Where(a => a.CreateTime < cutoff).ExecuteAffrows(); } catch (Exception ex) { _logger.Warn(ex, "[TimeClear] 清理 JellyfinMonitorTask 失败"); } try { delDl = FSqlContext.FDb.Delete().Where(a => a.CreateTime < cutoff).ExecuteAffrows(); } catch (Exception ex) { _logger.Warn(ex, "[TimeClear] 清理 DownloadTask 失败"); } try { delRaw = FSqlContext.FDb.Delete().Where(a => a.CreateTime < cutoff).ExecuteAffrows(); } catch (Exception ex) { _logger.Warn(ex, "[TimeClear] 清理 OEMRawUse 失败"); } try { delAct = FSqlContext.FDb.Delete().Where(a => a.CreateTime < cutoff).ExecuteAffrows(); } catch (Exception ex) { _logger.Warn(ex, "[TimeClear] 清理 VideoAction 失败"); } _logger.Info("[TimeClear] 清理完成: JellyfinMonitorTask={Jf}, DownloadTask={Dl}, OEMRawUse={Raw}, VideoAction={Act}", delJf, delDl, delRaw, delAct); SafeInfo($"清理完成: Jf={delJf}, Dl={delDl}, Raw={delRaw}, Act={delAct}"); await Task.CompletedTask; } private void SafeInfo(string msg) { try { Info?.Invoke(msg); } catch { } } } }