Files
MoShiro/backend/src/MoShiro.Backend.Application/Tasks/EffectiveTasks.cs
T
justme 2e146d4da7
ci/woodpecker/push/main-amd64-build Pipeline was successful
refactor!: rename the project from Monitor to MoShiro [CI]
Namespaces, assemblies, solution, binaries, images, systemd units, config paths, environment variables, protocol methods and the Linux user are all renamed. Breaking: existing databases, node identities and installed units must be recreated.
2026-10-07 02:03:25 +08:00

115 lines
3.8 KiB
C#

using System.Text.Json;
using MoShiro.Protocol;
namespace MoShiro.Backend.Application;
public sealed record EffectiveTask(
TaskId Id,
string Scope,
string Kind,
string? Target,
IReadOnlyDictionary<string, JsonElement> Params,
string Schedule,
ParamValues Resolved);
/// <summary>
/// 有效任务集:线路任务模板 + 针对单个 Agent 的覆盖 + 该 Agent 的目标任务。
/// 参数经 ParamSpec 校验并补全默认值,Backend 和节点使用同一套规则。
/// </summary>
public static class EffectiveTasks
{
public const string LineScope = "line";
public const string TargetScope = "target";
public static IReadOnlyList<EffectiveTask> Compute(IReadOnlyList<TaskRecord> all, AgentRecord agent, bool filterByAgentKinds)
{
var result = new List<EffectiveTask>();
var overrides = new Dictionary<TaskId, TaskRecord>();
foreach (TaskRecord task in all)
{
if (task.AgentId == agent.Id && task.BaseTaskId is { } baseId)
{
overrides[baseId] = task;
}
}
foreach (TaskRecord task in all.OrderBy(t => t.Id.Value, StringComparer.Ordinal))
{
if (task.Scope == LineScope && task.AgentId is null)
{
// 模板:有覆盖时以覆盖为准(覆盖可以停用或改参数)
TaskRecord? over = overrides.GetValueOrDefault(task.Id);
if (over is not null)
{
if (over.Enabled)
{
Add(result, over.Id, task.Scope, over.Kind, null, Merge(task.Params, over.Params), over.Schedule, agent, filterByAgentKinds);
}
}
else if (task.Enabled)
{
Add(result, task.Id, task.Scope, task.Kind, null, task.Params, task.Schedule, agent, filterByAgentKinds);
}
}
else if (task.Scope == TargetScope && task.AgentId == agent.Id && task.Enabled)
{
Add(result, task.Id, task.Scope, task.Kind, task.Target, task.Params, task.Schedule, agent, filterByAgentKinds);
}
}
return result;
}
private static Dictionary<string, JsonElement> Merge(IReadOnlyDictionary<string, JsonElement> baseParams, IReadOnlyDictionary<string, JsonElement> over)
{
var merged = new Dictionary<string, JsonElement>(baseParams);
foreach ((string key, JsonElement value) in over)
{
merged[key] = value;
}
return merged;
}
private static void Add(
List<EffectiveTask> result,
TaskId id,
string scope,
string kind,
string? target,
IReadOnlyDictionary<string, JsonElement> parameters,
string schedule,
AgentRecord agent,
bool filterByAgentKinds)
{
KindSpec? spec = Kinds.Find(kind);
if (spec is null || (filterByAgentKinds && agent.Kinds.Count > 0 && !agent.Kinds.Contains(kind)))
{
return;
}
ParamResolution resolved = ParamResolver.Resolve(spec.Params, parameters);
if (!resolved.IsValid)
{
return;
}
result.Add(new EffectiveTask(id, scope, kind, target, resolved.Values!.Values, schedule, resolved.Values));
}
/// <summary>该 Agent 持续线路任务的总带宽(bit/s)。</summary>
public static long ContinuousLineBudget(IReadOnlyList<EffectiveTask> tasks)
{
long total = 0;
foreach (EffectiveTask task in tasks)
{
if (task.Scope == LineScope && task.Schedule == TaskSchedule.Continuous)
{
total += Kinds.Find(task.Kind)!.BudgetBitsPerSecond(task.Resolved);
}
}
return total;
}
}