Commit 9694c3fa authored by 段廷瑞's avatar 段廷瑞

feat: 拆分独立的Agent.Host.exe

parent e6773c00
......@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UpdateExecuter", "UpdateExe
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tools.Agent", "Tools.Agent\Tools.Agent.csproj", "{D2D9C734-0C5B-4B06-9E4E-1AE6C4DB3F24}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tools.Agent.Host", "Tools.Agent.Host\Tools.Agent.Host.csproj", "{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tools", "Tools\Tools.csproj", "{C5AB3CE9-C46A-467D-96E4-D80DD3BBB239}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ftt.PatchPack", "Ftt.PatchPack\Ftt.PatchPack.csproj", "{42E384C1-CC13-494D-90E2-33C399E13F07}"
......@@ -61,6 +63,18 @@ Global
{D2D9C734-0C5B-4B06-9E4E-1AE6C4DB3F24}.Release|x86.ActiveCfg = Release|Any CPU
{D2D9C734-0C5B-4B06-9E4E-1AE6C4DB3F24}.Release|x64.Build.0 = Release|Any CPU
{D2D9C734-0C5B-4B06-9E4E-1AE6C4DB3F24}.Release|x86.Build.0 = Release|Any CPU
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Debug|x64.ActiveCfg = Debug|Any CPU
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Debug|x64.Build.0 = Debug|Any CPU
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Debug|x86.ActiveCfg = Debug|x86
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Debug|x86.Build.0 = Debug|x86
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Release|Any CPU.Build.0 = Release|Any CPU
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Release|x64.ActiveCfg = Release|Any CPU
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Release|x64.Build.0 = Release|Any CPU
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Release|x86.ActiveCfg = Release|x86
{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}.Release|x86.Build.0 = Release|x86
{C5AB3CE9-C46A-467D-96E4-D80DD3BBB239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5AB3CE9-C46A-467D-96E4-D80DD3BBB239}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5AB3CE9-C46A-467D-96E4-D80DD3BBB239}.Debug|x64.ActiveCfg = Debug|Any CPU
......
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<loadFromRemoteSources enabled="true" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="ICSharpCode.SharpZipLib" publicKeyToken="1b03e6acf1164f73" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-1.3.2.10" newVersion="1.3.2.10" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using Tools.MyAgent;
namespace Tools.MyAgent.Host
{
internal static class Program
{
private const int ProtocolVersion = 1;
private static AgentRuntime _runtime;
private static Mutex _instanceMutex;
[STAThread]
private static int Main(string[] args)
{
LaunchOptions options;
string error;
if (!LaunchOptions.TryParse(args, out options, out error))
return Fail(error, null);
if (options.ShowHelp)
{
MessageBox.Show(LaunchOptions.Usage, "Tools.Agent.Host", MessageBoxButtons.OK, MessageBoxIcon.Information);
return 0;
}
if (!TryAcquireSingleInstance(options.Product, options.DataRoot))
return 0;
try
{
var product = ProductContextCatalog.Resolve(options.Product);
product.ProjectRoot = options.ProjectRoot;
product.KnowledgeBaseRoot = options.KnowledgeBaseRoot;
product.KnowledgeBaseScope = options.KnowledgeBaseScope;
var runtimeOptions = new AgentRuntimeOptions
{
DataRoot = options.DataRoot,
CurrentUser = options.User,
ProductContext = product,
};
_runtime = new AgentRuntime(runtimeOptions);
if (options.ValidateOnly)
return 0;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.ThreadException += (sender, e) => Fail(e.Exception.Message, options.DataRoot);
AppDomain.CurrentDomain.UnhandledException += (sender, e) =>
WriteLog(options.DataRoot, e.ExceptionObject as Exception);
var form = _runtime.CreateChatForm();
form.Text = product.ProductName + " 智能助手";
ParentMonitor parentMonitor = ParentMonitor.Start(form, options.ParentPid);
form.FormClosed += (sender, e) => parentMonitor.Dispose();
Application.Run(form);
return 0;
}
catch (Exception ex)
{
return Fail(ex.Message, options.DataRoot);
}
finally
{
ReleaseSingleInstance();
}
}
private static bool TryAcquireSingleInstance(string product, string dataRoot)
{
string name = "Local\\Ftt.Tools.Agent.Host." + product + "." + HashForMutex(dataRoot);
try
{
bool created;
var mutex = new Mutex(true, name, out created);
if (created)
{
_instanceMutex = mutex;
return true;
}
mutex.Dispose();
ActivateExistingInstance(product);
return false;
}
catch
{
return true;
}
}
private static void ReleaseSingleInstance()
{
if (_instanceMutex == null) return;
try { _instanceMutex.ReleaseMutex(); } catch { }
try { _instanceMutex.Dispose(); } catch { }
_instanceMutex = null;
}
private static void ActivateExistingInstance(string product)
{
string title = GetProductTitle(product);
try
{
foreach (Process process in Process.GetProcessesByName("Tools.Agent.Host"))
{
try
{
process.Refresh();
if (process.MainWindowHandle == IntPtr.Zero
|| process.MainWindowTitle.IndexOf(title, StringComparison.OrdinalIgnoreCase) < 0)
continue;
ShowWindow(process.MainWindowHandle, SW_RESTORE);
SetForegroundWindow(process.MainWindowHandle);
break;
}
finally
{
process.Dispose();
}
}
}
catch { }
}
private static string GetProductTitle(string product)
{
if (string.Equals(product, ProductContextCatalog.PdmId, StringComparison.OrdinalIgnoreCase)) return "PDM";
if (string.Equals(product, ProductContextCatalog.MesId, StringComparison.OrdinalIgnoreCase)) return "MES";
return "ERP";
}
private static string HashForMutex(string value)
{
try
{
using (var sha = SHA256.Create())
{
byte[] bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(value ?? ""));
var builder = new StringBuilder(bytes.Length * 2);
foreach (byte item in bytes) builder.Append(item.ToString("x2"));
return builder.ToString();
}
}
catch
{
return "default";
}
}
private static int Fail(string message, string dataRoot)
{
WriteLog(dataRoot, new InvalidOperationException(message ?? "Agent 启动失败。"));
try
{
MessageBox.Show(message ?? "Agent 启动失败。", "Tools.Agent.Host", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
catch { }
return 1;
}
private static void WriteLog(string dataRoot, Exception error)
{
try
{
string root = string.IsNullOrWhiteSpace(dataRoot)
? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Agent")
: Path.GetFullPath(dataRoot);
string directory = Path.Combine(root, "logs");
Directory.CreateDirectory(directory);
string path = Path.Combine(directory, "host.log");
string text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " "
+ (error == null ? "unknown error" : error.ToString()) + Environment.NewLine;
File.AppendAllText(path, text);
}
catch { }
}
private sealed class LaunchOptions
{
public string Product;
public string User = "访客";
public string ProjectRoot = "";
public string KnowledgeBaseRoot = "";
public string KnowledgeBaseScope = "";
public string DataRoot;
public int ParentPid;
public bool ValidateOnly;
public bool ShowHelp;
public static readonly string Usage =
"Tools.Agent.Host.exe --protocol 1 --product <pdm|erp|mes> --user <user> "
+ "--data-root <path> [--project-root <path>] [--kb-root <path>] "
+ "[--kb-scope <path>] [--parent-pid <pid>]";
public static bool TryParse(string[] args, out LaunchOptions options, out string error)
{
options = new LaunchOptions();
error = null;
if (args == null) args = new string[0];
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < args.Length; i++)
{
string token = args[i] ?? "";
if (token.Equals("--help", StringComparison.OrdinalIgnoreCase)
|| token.Equals("-h", StringComparison.OrdinalIgnoreCase))
{
options.ShowHelp = true;
continue;
}
if (token.Equals("--validate-only", StringComparison.OrdinalIgnoreCase))
{
options.ValidateOnly = true;
continue;
}
if (!token.StartsWith("--", StringComparison.Ordinal) || token.Length <= 2)
{
error = "无法识别的启动参数。";
return false;
}
if (i + 1 >= args.Length || (args[i + 1] ?? "").StartsWith("--", StringComparison.Ordinal))
{
error = "启动参数缺少值:" + token;
return false;
}
values[token.Substring(2)] = args[++i];
}
if (options.ShowHelp) return true;
string protocolText;
if (!values.TryGetValue("protocol", out protocolText)
|| protocolText != ProtocolVersion.ToString())
{
error = "不支持的启动协议版本。";
return false;
}
if (!values.TryGetValue("product", out options.Product)
|| !ProductContextCatalog.IsRegistered(options.Product))
{
error = "product 必须是 pdm、erp 或 mes。";
return false;
}
string value;
if (values.TryGetValue("user", out value) && !string.IsNullOrWhiteSpace(value))
options.User = value.Trim();
if (!values.TryGetValue("data-root", out options.DataRoot)
|| string.IsNullOrWhiteSpace(options.DataRoot))
{
error = "data-root 不能为空。";
return false;
}
options.DataRoot = Path.GetFullPath(options.DataRoot);
values.TryGetValue("project-root", out options.ProjectRoot);
values.TryGetValue("kb-root", out options.KnowledgeBaseRoot);
values.TryGetValue("kb-scope", out options.KnowledgeBaseScope);
if (values.TryGetValue("parent-pid", out value) && !int.TryParse(value, out options.ParentPid))
{
error = "parent-pid 不是有效整数。";
return false;
}
return true;
}
}
private sealed class ParentMonitor : IDisposable
{
private readonly Form _form;
private readonly System.Threading.Timer _timer;
private readonly int _parentPid;
private int _disposed;
private ParentMonitor(Form form, int parentPid)
{
_form = form;
_parentPid = parentPid;
_timer = new System.Threading.Timer(CheckParent, null, 1000, 1000);
}
public static ParentMonitor Start(Form form, int parentPid)
{
return parentPid > 0 ? new ParentMonitor(form, parentPid) : new ParentMonitor(form, true);
}
private ParentMonitor(Form form, bool disabled)
{
_form = form;
_timer = null;
}
private void CheckParent(object state)
{
if (_timer == null || Volatile.Read(ref _disposed) != 0) return;
bool alive;
try
{
using (var process = Process.GetProcessById(_parentPid))
alive = !process.HasExited;
}
catch
{
alive = false;
}
if (!alive)
{
try
{
if (_form.IsHandleCreated && !_form.IsDisposed)
_form.BeginInvoke((MethodInvoker)delegate { if (!_form.IsDisposed) _form.Close(); });
}
catch { }
}
}
public void Dispose()
{
if (Interlocked.Exchange(ref _disposed, 1) != 0) return;
if (_timer != null) _timer.Dispose();
}
}
private const int SW_RESTORE = 9;
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr handle, int command);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr handle);
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{C6D2B1C8-2C9F-4A8B-9B1D-6C9A6F6D7F61}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>Tools.MyAgent.Host</RootNamespace>
<AssemblyName>Tools.Agent.Host</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkProfile />
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
<LangVersion>7.3</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="ICSharpCode.SharpZipLib, Version=1.3.2.10, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\packages\SharpZipLib.1.3.2\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="MySql.Data, Version=6.9.12.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d, processorArchitecture=MSIL">
<HintPath>..\packages\MySql.Data.6.9.12\lib\net45\MySql.Data.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NPOI, Version=2.5.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
<HintPath>..\packages\NPOI.2.5.1\lib\net45\NPOI.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NPOI.OOXML, Version=2.5.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
<HintPath>..\packages\NPOI.2.5.1\lib\net45\NPOI.OOXML.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NPOI.OpenXml4Net, Version=2.5.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
<HintPath>..\packages\NPOI.2.5.1\lib\net45\NPOI.OpenXml4Net.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NPOI.OpenXmlFormats, Version=2.5.1.0, Culture=neutral, PublicKeyToken=0df73ec7942b34e1, processorArchitecture=MSIL">
<HintPath>..\packages\NPOI.2.5.1\lib\net45\NPOI.OpenXmlFormats.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Npgsql, Version=3.2.7.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7, processorArchitecture=MSIL">
<HintPath>..\packages\Npgsql.3.2.7\lib\net45\Npgsql.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.ValueTuple">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Security" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Web" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.SQLite, Version=1.0.115.5, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
<HintPath>..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.115.5\lib\net45\System.Data.SQLite.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="UglyToad.PdfPig">
<HintPath>..\packages\PdfPig.0.0.9\lib\net45\UglyToad.PdfPig.dll</HintPath>
<Private>True</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<None Include="App.config" />
<ProjectReference Include="..\Tools.Agent\Tools.Agent.csproj">
<Project>{D2D9C734-0C5B-4B06-9E4E-1AE6C4DB3F24}</Project>
<Name>Tools.Agent</Name>
<GlobalPropertiesToRemove>Platform</GlobalPropertiesToRemove>
</ProjectReference>
<Content Include="..\Tools.Agent\Defaults\Agent\prompts\**\*">
<Link>Agent\prompts\%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\Tools.Agent\Defaults\Agent\dialect\**\*">
<Link>Agent\dialect\%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="..\Tools.Agent\Defaults\Agent\industry\**\*">
<Link>Agent\industry\%(RecursiveDir)%(Filename)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.115.5\build\net45\Stub.System.Data.SQLite.Core.NetFramework.targets" Condition="Exists('..\packages\Stub.System.Data.SQLite.Core.NetFramework.1.0.115.5\build\net45\Stub.System.Data.SQLite.Core.NetFramework.targets')" />
</Project>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment