Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/dotnet-build.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Build Project

on:
workflow_dispatch:
push:
branches: [ main ]
pull_request:
Expand Down
177 changes: 177 additions & 0 deletions Actions/ToggleWorkflowAction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using ClassIsland.Core.Abstractions.Automation;
using ClassIsland.Core.Abstractions.Services;
using ClassIsland.Core.Attributes;
using ClassIsland.Shared;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using SystemTools.Settings;
using Workflow = ClassIsland.Core.Models.Automation.Workflow;

namespace SystemTools.Actions;

[ActionInfo("SystemTools.ToggleWorkflow", "开关自动化", "\uE9A8", false)]
public class ToggleWorkflowAction(ILogger<ToggleWorkflowAction> logger) : ActionBase<ToggleWorkflowSettings>
{
private readonly ILogger<ToggleWorkflowAction> _logger = logger;

private static readonly ConcurrentDictionary<Guid, OriginalStateSnapshot> PreviousSnapshots = new();

protected override async Task OnInvoke()
{
try
{
_logger.LogDebug("ToggleWorkflowAction OnInvoke 开始");

if (Settings == null)
{
_logger.LogWarning("设置为空,无法执行");
return;
}

var automationService = IAppHost.TryGetService<IAutomationService>();
if (automationService?.Workflows == null)
{
_logger.LogError("无法获取自动化服务");
throw new InvalidOperationException("无法获取自动化服务,请确保 ClassIsland 已正确加载。");
}

var targetWorkflow = FindTargetWorkflow(automationService);
if (targetWorkflow == null)
{
_logger.LogWarning("未找到目标自动化: Index={Index}, Name={Name}",
Settings.TargetWorkflowIndex, Settings.TargetWorkflowName);
throw new InvalidOperationException($"未找到指定的自动化方案: {Settings.TargetWorkflowName}");
}

var actionSet = targetWorkflow.ActionSet;
var currentStatus = actionSet.IsEnabled;

if (IsRevertable)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor the revert opt-out

If the user clears “触发器恢复时自动还原原状态”, Settings.RevertToOriginal is still ignored here: any revertable trigger records a snapshot and OnRevert will restore it anyway. In revertable trigger scenarios this makes the new checkbox ineffective and prevents users from keeping the toggled workflow state after the trigger ends.

Useful? React with 👍 / 👎.

{
PreviousSnapshots[ActionSet.Guid] = new OriginalStateSnapshot(
actionSet.Name,
automationService.Workflows.IndexOf(targetWorkflow),
currentStatus);
_logger.LogDebug("已保存自动化 \"{WorkflowName}\" 的原始状态快照", actionSet.Name);
}

var (targetStatus, operationDescription) = Settings.EnableMode switch
{
true => (true, "启用"),
false => (false, "禁用"),
_ => (!currentStatus, !currentStatus ? "启用" : "禁用")
};

if (currentStatus == targetStatus)
{
_logger.LogInformation("自动化 \"{WorkflowName}\" 已经是{Operation}状态,无需操作",
actionSet.Name, operationDescription);
}
else
{
_logger.LogInformation("正在{Operation}自动化 \"{WorkflowName}\" (原始: {OriginalStatus} -> 目标: {TargetStatus})",
operationDescription, actionSet.Name, currentStatus, targetStatus);

actionSet.IsEnabled = targetStatus;
automationService.SaveConfig($"通过行动{operationDescription}自动化 \"{actionSet.Name}\"");

_logger.LogInformation("自动化 \"{WorkflowName}\" 已成功{Operation}",
actionSet.Name, operationDescription);
}

await base.OnInvoke();
_logger.LogDebug("ToggleWorkflowAction OnInvoke 完成");
}
catch (Exception ex)
{
_logger.LogError(ex, "ToggleWorkflowAction 执行失败");
throw;
}
}

protected override async Task OnRevert()
{
try
{
await base.OnRevert();

if (!PreviousSnapshots.TryRemove(ActionSet.Guid, out var snapshot))
{
_logger.LogWarning("未找到触发前状态,跳过恢复。ActionSet={ActionSetGuid}", ActionSet.Guid);
return;
}

var automationService = IAppHost.TryGetService<IAutomationService>();
if (automationService?.Workflows == null)
{
_logger.LogError("无法获取自动化服务,恢复失败");
return;
}

var targetWorkflow = FindTargetWorkflow(automationService);
if (targetWorkflow == null)
{
_logger.LogWarning("恢复时未找到目标自动化: {Name}", snapshot.WorkflowName);
return;
}

var actionSet = targetWorkflow.ActionSet;
actionSet.IsEnabled = snapshot.IsEnabled;
automationService.SaveConfig($"通过行动恢复自动化 \"{actionSet.Name}\" 到原始状态({snapshot.IsEnabled})");

_logger.LogInformation("已恢复自动化 \"{WorkflowName}\" 为触发前状态。ActionSet={ActionSetGuid}",
actionSet.Name, ActionSet.Guid);
}
catch (Exception ex)
{
_logger.LogError(ex, "ToggleWorkflowAction 恢复失败");
throw;
}
}

private Workflow? FindTargetWorkflow(IAutomationService automationService)
{
Workflow? targetWorkflow = null;

// 1. 尝试通过索引查找
if (Settings.TargetWorkflowIndex >= 0 && Settings.TargetWorkflowIndex < automationService.Workflows.Count)
{
targetWorkflow = automationService.Workflows[Settings.TargetWorkflowIndex];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate saved index before using it

When the workflow list has been reordered or a new workflow is inserted before the saved target, this path accepts any in-range TargetWorkflowIndex and never checks it against TargetWorkflowName, so the action can toggle a different automation than the one the user selected. Falling back to the saved name when the indexed workflow's name does not match would avoid silently changing the wrong workflow.

Useful? React with 👍 / 👎.

_logger.LogDebug("通过索引 {Index} 找到自动化: {Name}",
Settings.TargetWorkflowIndex, targetWorkflow.ActionSet.Name);
}

// 2. 如果索引找不到,尝试通过名称查找
if (targetWorkflow == null && !string.IsNullOrEmpty(Settings.TargetWorkflowName))
{
targetWorkflow = automationService.Workflows
.FirstOrDefault(w => w.ActionSet.Name == Settings.TargetWorkflowName);

if (targetWorkflow != null)
{
_logger.LogDebug("通过名称 \"{Name}\" 找到自动化", Settings.TargetWorkflowName);
Settings.TargetWorkflowIndex = automationService.Workflows.IndexOf(targetWorkflow);
}
else
{
_logger.LogWarning("通过名称 \"{Name}\" 未找到自动化", Settings.TargetWorkflowName);
}
}

if (targetWorkflow == null)
{
_logger.LogWarning("未能找到任何匹配的目标自动化 (Index={Index}, Name={Name})",
Settings.TargetWorkflowIndex, Settings.TargetWorkflowName);
}

return targetWorkflow;
}

private readonly record struct OriginalStateSnapshot(
string WorkflowName,
int WorkflowIndex,
bool IsEnabled);
}
Loading