2025-05-26 23:55:23 +08:00
|
|
|
|
using Microsoft.Win32;
|
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
|
using Serein.Library;
|
2025-03-18 21:01:15 +08:00
|
|
|
|
using Serein.Library.Api;
|
2025-05-26 23:55:23 +08:00
|
|
|
|
using Serein.Workbench.Api;
|
2025-05-30 12:06:06 +08:00
|
|
|
|
using Serein.Workbench.ViewModels;
|
2025-03-18 21:01:15 +08:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2025-05-26 23:55:23 +08:00
|
|
|
|
using System.IO;
|
2025-03-18 21:01:15 +08:00
|
|
|
|
using System.Linq;
|
2025-05-27 18:32:40 +08:00
|
|
|
|
using System.Security.Cryptography;
|
2025-03-18 21:01:15 +08:00
|
|
|
|
using System.Security.Cryptography.X509Certificates;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2025-05-28 23:19:00 +08:00
|
|
|
|
using System.Windows.Input;
|
2025-03-18 21:01:15 +08:00
|
|
|
|
|
|
|
|
|
|
namespace Serein.Workbench.Services
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
#region 工作台事件
|
|
|
|
|
|
|
|
|
|
|
|
public delegate void PreviewlMethodInfoHandler(PreviewlMethodInfoEventArgs eventArgs);
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 工作台事件参数
|
|
|
|
|
|
public class PreviewlMethodInfoEventArgs(MethodDetailsInfo mdInfo) : EventArgs
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 方法信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public MethodDetailsInfo MethodDetailsInfo { get; } = mdInfo;
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 工作台事件管理
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
internal interface IWorkbenchEventService
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 预览了某个方法信息(待创建)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
event PreviewlMethodInfoHandler OnPreviewlMethodInfo;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 预览依赖方法信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
void PreviewLibraryMethodInfo(MethodDetailsInfo mdInfo);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 工作台事件的实现类
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
internal class WorkbenchEventService : IWorkbenchEventService
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
private readonly IFlowEnvironment flowEnvironment;
|
2025-05-26 23:55:23 +08:00
|
|
|
|
private readonly IFlowEEForwardingService flowEEForwardingService;
|
2025-05-28 23:19:00 +08:00
|
|
|
|
private readonly IKeyEventService keyEventService;
|
|
|
|
|
|
private readonly FlowNodeService flowNodeService;
|
2025-05-26 23:55:23 +08:00
|
|
|
|
|
2025-03-18 21:01:15 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 管理工作台的事件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="flowEnvironment"></param>
|
2025-05-26 23:55:23 +08:00
|
|
|
|
/// <param name="flowEEForwardingService"></param>
|
2025-05-28 23:19:00 +08:00
|
|
|
|
/// <param name="keyEventService"></param>
|
|
|
|
|
|
/// <param name="flowNodeService"></param>
|
|
|
|
|
|
public WorkbenchEventService(IFlowEnvironment flowEnvironment,
|
2025-05-30 11:53:33 +08:00
|
|
|
|
IFlowEEForwardingService flowEEForwardingService,
|
|
|
|
|
|
IKeyEventService keyEventService,
|
|
|
|
|
|
FlowNodeService flowNodeService)
|
2025-03-18 21:01:15 +08:00
|
|
|
|
{
|
|
|
|
|
|
this.flowEnvironment = flowEnvironment;
|
2025-05-26 23:55:23 +08:00
|
|
|
|
this.flowEEForwardingService = flowEEForwardingService;
|
2025-05-28 23:19:00 +08:00
|
|
|
|
this.keyEventService = keyEventService;
|
|
|
|
|
|
this.flowNodeService = flowNodeService;
|
2025-05-26 23:55:23 +08:00
|
|
|
|
InitEvents();
|
2025-03-18 21:01:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-26 23:55:23 +08:00
|
|
|
|
private void InitEvents()
|
2025-03-18 21:01:15 +08:00
|
|
|
|
{
|
2025-06-02 19:17:30 +08:00
|
|
|
|
flowEEForwardingService.ProjectLoaded += FlowEEForwardingService_OnProjectLoaded;
|
|
|
|
|
|
flowEEForwardingService.ProjectSaving += SaveProjectToLocalFile;
|
|
|
|
|
|
flowEEForwardingService.EnvOutput += FlowEEForwardingService_OnEnvOut;
|
2025-05-28 23:19:00 +08:00
|
|
|
|
keyEventService.OnKeyDown += KeyEventService_OnKeyDown; ;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-30 11:53:33 +08:00
|
|
|
|
private void FlowEEForwardingService_OnProjectLoaded(ProjectLoadedEventArgs eventArgs)
|
|
|
|
|
|
{
|
2025-05-30 12:06:06 +08:00
|
|
|
|
var edit = App.GetService<Locator>().FlowEditViewModel;
|
2025-05-30 11:53:33 +08:00
|
|
|
|
|
2025-05-30 12:06:06 +08:00
|
|
|
|
App.UIContextOperation.Invoke(async () => {
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var item in flowNodeService.FlowCanvass)
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Delay(50);
|
|
|
|
|
|
flowNodeService.CurrentSelectCanvas = item;
|
|
|
|
|
|
var tab = edit.CanvasTabs.First(tab => tab.Content == item);
|
|
|
|
|
|
edit.SelectedTab = tab;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-05-30 11:53:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-28 23:19:00 +08:00
|
|
|
|
private void KeyEventService_OnKeyDown(System.Windows.Input.Key key)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
2025-05-27 18:32:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void FlowEEForwardingService_OnEnvOut(InfoType type, string value)
|
|
|
|
|
|
{
|
|
|
|
|
|
LogWindow.Instance.AppendText($"{DateTime.Now} [{type}] : {value}{Environment.NewLine}");
|
2025-03-18 21:01:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-27 18:32:40 +08:00
|
|
|
|
|
2025-05-26 23:55:23 +08:00
|
|
|
|
|
2025-03-18 21:01:15 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 预览了某个方法信息(待创建)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public event PreviewlMethodInfoHandler? OnPreviewlMethodInfo;
|
2025-05-26 23:55:23 +08:00
|
|
|
|
|
2025-03-18 21:01:15 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 预览依赖方法信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void PreviewLibraryMethodInfo(MethodDetailsInfo mdInfo)
|
|
|
|
|
|
{
|
|
|
|
|
|
OnPreviewlMethodInfo?.Invoke(new PreviewlMethodInfoEventArgs(mdInfo));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 需要放置节点控件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void PlateNodeControl()
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2025-05-26 23:55:23 +08:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 保存项目数据到本地文件
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="e"></param>
|
|
|
|
|
|
private void SaveProjectToLocalFile(ProjectSavingEventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var project = e.ProjectData;
|
2025-05-27 18:32:40 +08:00
|
|
|
|
|
|
|
|
|
|
#region 获取保存路径
|
2025-05-26 23:55:23 +08:00
|
|
|
|
// 创建一个新的保存文件对话框
|
|
|
|
|
|
SaveFileDialog saveFileDialog = new()
|
|
|
|
|
|
{
|
|
|
|
|
|
Filter = "DynamicNodeFlow Files (*.dnf)|*.dnf",
|
|
|
|
|
|
DefaultExt = "dnf",
|
|
|
|
|
|
FileName = "project.dnf"
|
|
|
|
|
|
// FileName = System.IO.Path.GetFileName(App.FileDataPath)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 显示保存文件对话框
|
|
|
|
|
|
bool? result = saveFileDialog.ShowDialog();
|
|
|
|
|
|
// 如果用户选择了文件并点击了保存按钮
|
|
|
|
|
|
if (result == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
SereinEnv.WriteLine(InfoType.ERROR, "取消保存文件");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var savePath = saveFileDialog.FileName;
|
|
|
|
|
|
string? librarySavePath = System.IO.Path.GetDirectoryName(savePath);
|
|
|
|
|
|
if (string.IsNullOrEmpty(librarySavePath))
|
|
|
|
|
|
{
|
|
|
|
|
|
SereinEnv.WriteLine(InfoType.ERROR, "保存项目DLL时返回了意外的文件保存路径");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-05-27 18:32:40 +08:00
|
|
|
|
#endregion
|
2025-05-26 23:55:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-05-27 18:32:40 +08:00
|
|
|
|
#region 将Dll输出到指定路径
|
2025-05-26 23:55:23 +08:00
|
|
|
|
Uri saveProjectFileUri = new Uri(savePath);
|
|
|
|
|
|
SereinEnv.WriteLine(InfoType.INFO, "项目文件保存路径:" + savePath);
|
|
|
|
|
|
for (int index = 0; index < project.Librarys.Length; index++)
|
|
|
|
|
|
{
|
2025-07-28 12:16:29 +08:00
|
|
|
|
FlowLibraryInfo? library = project.Librarys[index];
|
2025-05-26 23:55:23 +08:00
|
|
|
|
string sourceFilePath = new Uri(library.FilePath).LocalPath; // 源文件夹
|
|
|
|
|
|
string targetDir = System.IO.Path.Combine(librarySavePath, library.AssemblyName); // 目标文件夹
|
|
|
|
|
|
if (!Path.Exists(targetDir))
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(targetDir);
|
|
|
|
|
|
}
|
|
|
|
|
|
string targetFilePath = System.IO.Path.Combine(targetDir, library.FileName); // 目标文件夹
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (File.Exists(sourceFilePath))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!File.Exists(targetFilePath))
|
|
|
|
|
|
{
|
|
|
|
|
|
SereinEnv.WriteLine(InfoType.INFO, $"源文件路径 : {sourceFilePath}");
|
|
|
|
|
|
SereinEnv.WriteLine(InfoType.INFO, $"目标路径 : {targetFilePath}");
|
|
|
|
|
|
File.Copy(sourceFilePath, targetFilePath, true);
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
SereinEnv.WriteLine(InfoType.WARN, $"目标路径已有类库文件: {targetFilePath}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
SereinEnv.WriteLine(InfoType.WARN, $"源文件不存在 : {targetFilePath}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (IOException ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
SereinEnv.WriteLine(InfoType.ERROR, ex.Message);
|
|
|
|
|
|
}
|
|
|
|
|
|
var dirName = System.IO.Path.GetDirectoryName(targetFilePath);
|
|
|
|
|
|
if (!string.IsNullOrEmpty(dirName))
|
|
|
|
|
|
{
|
|
|
|
|
|
var tmpUri2 = new Uri(targetFilePath);
|
|
|
|
|
|
var relativePath = saveProjectFileUri.MakeRelativeUri(tmpUri2).ToString(); // 转为类库的相对文件路径
|
|
|
|
|
|
|
|
|
|
|
|
//string relativePath = System.IO.Path.GetRelativePath(savePath, targetPath);
|
|
|
|
|
|
project.Librarys[index].FilePath = relativePath;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2025-05-27 18:32:40 +08:00
|
|
|
|
#endregion
|
2025-05-26 23:55:23 +08:00
|
|
|
|
|
2025-05-27 18:32:40 +08:00
|
|
|
|
#region 输出项目保存文件
|
2025-05-26 23:55:23 +08:00
|
|
|
|
JObject projectJsonData = JObject.FromObject(project);
|
|
|
|
|
|
File.WriteAllText(savePath, projectJsonData.ToString());
|
2025-05-27 18:32:40 +08:00
|
|
|
|
#endregion
|
2025-05-26 23:55:23 +08:00
|
|
|
|
|
2025-05-27 18:32:40 +08:00
|
|
|
|
|
2025-05-26 23:55:23 +08:00
|
|
|
|
}
|
2025-05-27 18:32:40 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-18 21:01:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-27 18:32:40 +08:00
|
|
|
|
|
|
|
|
|
|
#region 抽取重复文件(例如net运行时)
|
|
|
|
|
|
/*
|
|
|
|
|
|
|
|
|
|
|
|
1. 扫描目录并计算哈希
|
|
|
|
|
|
string[] directories = new[] { "path1", "path2" };
|
|
|
|
|
|
var fileHashMap = new Dictionary<string, List<string>>(); // hash -> List<full paths>
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var dir in directories)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var file in Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories))
|
|
|
|
|
|
{
|
|
|
|
|
|
using var stream = File.OpenRead(file);
|
|
|
|
|
|
using var sha = SHA256.Create();
|
|
|
|
|
|
var hash = Convert.ToHexString(sha.ComputeHash(stream));
|
|
|
|
|
|
|
|
|
|
|
|
if (!fileHashMap.ContainsKey(hash))
|
|
|
|
|
|
fileHashMap[hash] = new List<string>();
|
|
|
|
|
|
|
|
|
|
|
|
fileHashMap[hash].Add(file);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
2. 将重复文件压缩并保存
|
|
|
|
|
|
string archiveDir = "compressed_output";
|
|
|
|
|
|
Directory.CreateDirectory(archiveDir);
|
|
|
|
|
|
|
|
|
|
|
|
var manifest = new List<FileRecord>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var kvp in fileHashMap.Where(kvp => kvp.Value.Count > 1))
|
|
|
|
|
|
{
|
|
|
|
|
|
var hash = kvp.Key;
|
|
|
|
|
|
var originalFile = kvp.Value[0];
|
|
|
|
|
|
var archivePath = Path.Combine(archiveDir, $"{hash}.gz");
|
|
|
|
|
|
|
|
|
|
|
|
using (var input = File.OpenRead(originalFile))
|
|
|
|
|
|
using (var output = File.Create(archivePath))
|
|
|
|
|
|
using (var gzip = new GZipStream(output, CompressionLevel.Optimal))
|
|
|
|
|
|
{
|
|
|
|
|
|
input.CopyTo(gzip);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
manifest.Add(new FileRecord
|
|
|
|
|
|
{
|
|
|
|
|
|
Hash = hash,
|
|
|
|
|
|
ArchiveFile = $"{hash}.gz",
|
|
|
|
|
|
OriginalPaths = kvp.Value
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
3. 生成清单文件(JSON)
|
|
|
|
|
|
public class FileRecord
|
|
|
|
|
|
{
|
|
|
|
|
|
public string Hash { get; set; }
|
|
|
|
|
|
public string ArchiveFile { get; set; }
|
|
|
|
|
|
public List<string> OriginalPaths { get; set; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
File.WriteAllText("manifest.json", JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true }));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4. 根据清单还原原始文件结构
|
|
|
|
|
|
var manifestJson = File.ReadAllText("manifest.json");
|
|
|
|
|
|
var manifest = JsonSerializer.Deserialize<List<FileRecord>>(manifestJson);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var record in manifest)
|
|
|
|
|
|
{
|
|
|
|
|
|
var archivePath = Path.Combine("compressed_output", record.ArchiveFile);
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var path in record.OriginalPaths)
|
|
|
|
|
|
{
|
|
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
|
|
|
|
|
|
|
|
|
|
|
using var input = File.OpenRead(archivePath);
|
|
|
|
|
|
using var gzip = new GZipStream(input, CompressionMode.Decompress);
|
|
|
|
|
|
using var output = File.Create(path);
|
|
|
|
|
|
|
|
|
|
|
|
gzip.CopyTo(output);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|