Browse Source

Create EEMS Server UI

Zixuan 5 days ago
parent
commit
bc45a136d8

+ 1 - 0
Directory.Packages.props

@@ -4,6 +4,7 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+    <PackageVersion Include="Hardcodet.NotifyIcon.Wpf" Version="2.0.1" />
     <PackageVersion Include="Mapster" Version="7.4.0" />
     <PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.19" />
     <PackageVersion Include="Microsoft.AspNetCore.SignalR.Common" Version="8.0.19" />

+ 0 - 23
EEMSCenter/EEMSCenter.csproj

@@ -1,23 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-	<PropertyGroup>
-		<OutputType>Exe</OutputType>
-		<TargetFramework>net8.0-windows</TargetFramework>
-		<OutputPath>$(SolutionDir)Binary\Server</OutputPath>
-		<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
-		<ImplicitUsings>enable</ImplicitUsings>
-		<Nullable>enable</Nullable>
-		<UseWPF>true</UseWPF>
-		<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
-	</PropertyGroup>
-	<ItemGroup>
-		<FrameworkReference Include="Microsoft.AspNetCore.App" />
-	</ItemGroup>
-	<ItemGroup>
-	  <PackageReference Include="Microsoft.AspNetCore.SignalR.Common" />
-	</ItemGroup>
-	<ItemGroup>
-	  <ProjectReference Include="..\Data\Device\Device.csproj" />
-	  <ProjectReference Include="..\Universal\Universal.csproj" />
-	</ItemGroup>
-</Project>

+ 0 - 6
EEMSCenter/EEMSCenter.csproj.user

@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup>
-    <_LastSelectedProfileId>D:\Projects\EEMS\EEMSCenter\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
-  </PropertyGroup>
-</Project>

+ 0 - 63
EEMSCenter/Program.cs

@@ -1,63 +0,0 @@
-using EEMSCenter.Hubs;
-using EEMSCenter.HubSender;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.Extensions.DependencyInjection;
-using System.Windows;
-
-namespace EEMSCenter;
-
-internal class Program
-{
-    public static WebApplication? WebApplication { get; private set; }
-
-    static void Main(string[] args)
-    {
-        Mutex mutex = new(true, "7E862400-8020-BE75-5266-B2C4BEB54075", out bool flag);
-        if (!flag)
-            return;
-
-        AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
-        TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
-
-        WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
-        builder.Services.AddSignalR(
-            options =>
-            {
-                options.EnableDetailedErrors = true;
-                options.MaximumReceiveMessageSize = 262144;//256k
-                //options.ClientTimeoutInterval = TimeSpan.FromSeconds(5);
-                //options.KeepAliveInterval = TimeSpan.FromSeconds(10);
-                //options.MaximumParallelInvocationsPerClient = 5;
-            });
-
-        //Host Service
-        builder.Services.AddHostedService<HostLifetime>();
-        builder.Services.AddSingleton<DeviceManager>();
-        builder.Services.AddSingleton<ClientManager>();
-
-        builder.Services.AddSingleton<UISender>();
-
-        WebApplication = builder.Build();
-        WebApplication.MapHub<UIHub>("/UIHub");
-        WebApplication.MapHub<ClientsHub>("/ClientHub");
-
-
-        string? address = NetworkAddressHelper.GetAllIPAddressStr().Where(t => t.StartsWith("10.")).FirstOrDefault()?.ToString();
-        address ??= "127.0.0.1";
-
-        WebApplication.RunAsync($"http://{address}:50054").Wait();
-    }
-
-
-
-    private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
-    {
-        string? s = e.ExceptionObject?.ToString();
-        MessageBox.Show(s is null ? "Unknow UnhandledException" : s);
-    }
-
-    private static void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
-    {
-        //_expLog.Fatal(e.Exception!.ToString()!);
-    }
-}

+ 0 - 11
EEMSCenter/Properties/PublishProfiles/FolderProfile.pubxml

@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
-<Project>
-  <PropertyGroup>
-    <Configuration>Release</Configuration>
-    <Platform>Any CPU</Platform>
-    <PublishDir>E:\TestServer</PublishDir>
-    <PublishProtocol>FileSystem</PublishProtocol>
-    <_TargetId>Folder</_TargetId>
-  </PropertyGroup>
-</Project>

+ 0 - 8
EEMSCenter/Properties/PublishProfiles/FolderProfile.pubxml.user

@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
-<Project>
-  <PropertyGroup>
-    <History>True|2025-09-01T05:47:14.3768845Z||;True|2025-09-01T13:46:18.1542477+08:00||;True|2025-09-01T13:43:54.7660119+08:00||;</History>
-    <LastFailureDetails />
-  </PropertyGroup>
-</Project>

+ 0 - 6
EEMSCenter/Properties/_global.cs

@@ -1,6 +0,0 @@
-global using Microsoft.AspNetCore.SignalR;
-global using System.Collections.Concurrent;
-global using System.IO;
-global using Universal;
-global using Device;
-global using EEMSCenter.Managers;

+ 32 - 0
EEMSCenterUI/App.xaml

@@ -0,0 +1,32 @@
+<Prism:PrismApplication x:Class="EEMSCenterUI.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:EEMSCenterUI"
+             xmlns:tb="http://www.hardcodet.net/taskbar"
+         xmlns:Prism="http://prismlibrary.com/">
+    <Prism:PrismApplication.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="/UICommon;component/Brush.xaml"/>
+                <ResourceDictionary Source="/UICommon;component/Styles/ButtonStyle.xaml"/>
+                <ResourceDictionary Source="/UICommon;component/Styles/CheckBoxStyle.xaml"/>
+                <ResourceDictionary Source="/UICommon;component/Styles/ComboBoxStyle.xaml"/>
+                <ResourceDictionary Source="/UICommon;component/DrawImage.xaml"/>
+                <ResourceDictionary Source="/UICommon;component/Styles/OtherStyle.xaml"/>
+                <ResourceDictionary Source="/UICommon;component/Styles/RadioButtonStyle.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+
+            <tb:TaskbarIcon x:Key="Taskbar"
+                        ToolTipText="EEMS Server"
+                        IconSource="Logo.ico"
+                        DoubleClickCommand="{Binding HideShowCommand}">
+                <tb:TaskbarIcon.ContextMenu>
+                    <ContextMenu >
+                        <MenuItem Header="显示/隐藏" Command="{Binding HideShowCommand}"/>
+                        <MenuItem Header="退出 EEMS Server"  Command="{Binding ExitCommand}"/>
+                    </ContextMenu>
+                </tb:TaskbarIcon.ContextMenu>
+            </tb:TaskbarIcon>
+        </ResourceDictionary>
+    </Prism:PrismApplication.Resources>
+</Prism:PrismApplication>

+ 42 - 0
EEMSCenterUI/App.xaml.cs

@@ -0,0 +1,42 @@
+using EEMSCenterUI.ViewModels;
+using EEMSCenterUI.Views;
+using Hardcodet.Wpf.TaskbarNotification;
+using System.Configuration;
+using System.Data;
+using System.Diagnostics.Contracts;
+using System.Windows;
+using System.Windows.Media.Animation;
+using System.Windows.Navigation;
+using UICommon.CommonContainer;
+
+namespace EEMSCenterUI;
+
+/// <summary>
+/// Interaction logic for App.xaml
+/// </summary>
+public partial class App : PrismApplication
+{
+    protected override Window CreateShell()
+    {
+        return Container.Resolve<MainWindow>();
+    }
+
+    protected override void RegisterTypes(IContainerRegistry containerRegistry)
+    {
+        containerRegistry.RegisterSingleton<TaskBarViewModel>();
+        _taskbar = (TaskbarIcon)FindResource("Taskbar");
+        _taskbar.DataContext = Container.Resolve<TaskBarViewModel>();
+    }
+
+
+    private static TaskbarIcon? _taskbar;
+    protected override void OnStartup(StartupEventArgs e)
+    {
+        base.OnStartup(e);
+    }
+
+    public static void  ShowBalloonTip(string title, string message, BalloonIcon symbol)
+    {
+        _taskbar?.ShowBalloonTip(title, message, symbol);
+    }
+}

+ 10 - 0
EEMSCenterUI/AssemblyInfo.cs

@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+    ResourceDictionaryLocation.None,            //where theme specific resource dictionaries are located
+                                                //(used if a resource is not found in the page,
+                                                // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly   //where the generic resource dictionary is located
+                                                //(used if a resource is not found in the page,
+                                                // app, or any theme specific resource dictionaries)
+)]

+ 26 - 0
EEMSCenterUI/EEMSCenterUI.csproj

@@ -0,0 +1,26 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+	<PropertyGroup>
+		<OutputType>WinExe</OutputType>
+		<TargetFramework>net8.0-windows</TargetFramework>
+		<Nullable>enable</Nullable>
+		<ImplicitUsings>enable</ImplicitUsings>
+		<UseWPF>true</UseWPF>
+		<UseWindowsForms>false</UseWindowsForms>
+	</PropertyGroup>
+
+	<ItemGroup>
+		<PackageReference Include="CommunityToolkit.Mvvm" />
+		<PackageReference Include="Hardcodet.NotifyIcon.Wpf" />
+		<PackageReference Include="Prism.DryIoc" />
+	</ItemGroup>
+	<ItemGroup>
+		<FrameworkReference Include="Microsoft.AspNetCore.App" />
+	</ItemGroup>
+	<ItemGroup>
+	  <ProjectReference Include="..\UICommon\UICommon.csproj" />
+	</ItemGroup>
+	<ItemGroup>
+	  <Resource Include="Logo.ico" />
+	</ItemGroup>
+</Project>

+ 4 - 0
EEMSCenterUI/EEMSCenterUI.csproj.user

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup />
+</Project>

+ 59 - 0
EEMSCenterUI/Helper/WindowHelper.cs

@@ -0,0 +1,59 @@
+using Hardcodet.Wpf.TaskbarNotification;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement;
+
+namespace EEMSCenterUI.Helper;
+
+internal class WindowHelper
+{
+    private static bool _isShow = true;
+    public static bool ShowWindow()
+    {
+        Application.Current.MainWindow.Show();
+        _isShow = true;
+        return true;
+    }
+
+    public static void AutoHideShow()
+    {
+        _ = _isShow switch
+        {
+            true => HideWindow(),
+            false => ShowWindow(),
+        };
+    }
+
+
+    public static bool HideWindow()
+    {
+        App.ShowBalloonTip("EEMS Server", "服务正在后台运行...", BalloonIcon.Info);
+        Application.Current.MainWindow.Hide();
+        _isShow = false;
+        return true;
+    }
+
+    public static bool ExitWindow()
+    {
+        MessageBoxResult result = MessageBox.Show(Application.Current.MainWindow, "确认退出 EEMS 服务?", "退出提示", MessageBoxButton.YesNo, MessageBoxImage.Question);
+
+        switch (result)
+        {
+            case MessageBoxResult.Yes:
+                App.Current.Shutdown();
+                break;
+            case MessageBoxResult.None:
+            case MessageBoxResult.OK:
+            case MessageBoxResult.Cancel:
+            case MessageBoxResult.No:
+            default:
+                break;
+        }
+        return true;
+    }
+
+}

BIN
EEMSCenterUI/Logo.ico


+ 16 - 0
EEMSCenterUI/ViewModels/MainWindowViewModel.cs

@@ -0,0 +1,16 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EEMSCenterUI.ViewModels;
+
+internal class MainWindowViewModel : ObservableObject
+{
+    public MainWindowViewModel()
+    {
+        
+    }
+}

+ 32 - 0
EEMSCenterUI/ViewModels/TaskBarViewModel.cs

@@ -0,0 +1,32 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using EEMSCenterUI.Helper;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EEMSCenterUI.ViewModels;
+
+public partial class TaskBarViewModel : ObservableObject
+{
+    public TaskBarViewModel()
+    {
+
+    }
+
+
+
+    [RelayCommand]
+    private void HideShow()
+    {
+        WindowHelper.AutoHideShow();
+    }
+
+    [RelayCommand]
+    private void Exit()
+    {
+        WindowHelper.ExitWindow();
+    }
+}

+ 46 - 0
EEMSCenterUI/Views/MainWindow.xaml

@@ -0,0 +1,46 @@
+<Window x:Class="EEMSCenterUI.Views.MainWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:EEMSCenterUI"
+        xmlns:prism="http://prismlibrary.com/"
+        prism:ViewModelLocator.AutoWireViewModel="True"
+        mc:Ignorable="d"
+        WindowStyle="None" 
+        WindowChrome.WindowChrome="{DynamicResource WindowChromeKey}"
+        Title="MainWindow" Height="450" Width="800">
+    <Window.Resources>
+        <WindowChrome x:Key="WindowChromeKey">
+            <WindowChrome.CaptionHeight>0</WindowChrome.CaptionHeight>
+        </WindowChrome>
+    </Window.Resources>
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="auto"/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+        <Border x:Name="Bor" Background="{StaticResource LightThemeColor}" MouseLeftButtonDown="Grid_MouseLeftButtonDown">
+            <Grid >
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="auto"/>
+                    <ColumnDefinition Width="4"/>
+                    <ColumnDefinition Width="auto"/>
+                    <ColumnDefinition/>
+                    <ColumnDefinition Width="auto"/>
+                    <ColumnDefinition Width="4"/>
+                    <ColumnDefinition Width="auto"/>
+                </Grid.ColumnDefinitions>
+                <Image Source="/Logo.ico" Margin="4" Height="20"></Image>
+                <TextBlock Grid.Column="2" VerticalAlignment="Center" HorizontalAlignment="Center">EEMS Server</TextBlock>
+
+                <Button Grid.Column="4" Background="Transparent" BorderThickness="0" Width="40" Click="Hide_Click">
+                    <Image Source="{StaticResource Icon_Lock}" Height="20"/>
+                </Button>
+                <Button Grid.Column="6" Background="Transparent" BorderThickness="0" Width="40" Click="Exit_Click">
+                    <Image Source="{StaticResource Icon_Exit}" Height="20"/>
+                </Button>
+            </Grid>
+        </Border>
+    </Grid>
+</Window>

+ 39 - 0
EEMSCenterUI/Views/MainWindow.xaml.cs

@@ -0,0 +1,39 @@
+using EEMSCenterUI.Helper;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace EEMSCenterUI.Views;
+
+/// <summary>
+/// Interaction logic for MainWindow.xaml
+/// </summary>
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+    }
+
+    private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+    {
+        DragMove();
+    }
+
+    private void Hide_Click(object sender, RoutedEventArgs e)
+    {
+        WindowHelper.HideWindow();
+    }
+
+    private void Exit_Click(object sender, RoutedEventArgs e)
+    {
+        WindowHelper.ExitWindow();
+    }
+}

+ 15 - 6
EEMSMain.sln

@@ -66,10 +66,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CAD2XMAL", "CAD2XMAL\CAD2XMAL.csproj", "{8E249AE0-505B-4481-8C83-42C71561370E}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EEMSCenter", "EEMSCenter\EEMSCenter.csproj", "{9C18B022-F837-4736-B298-480AC29F8362}"
-EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestSignalRClient", "TestSignalRClient\TestSignalRClient.csproj", "{969746CD-C070-4F36-B6ED-9ADB9606E15E}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EEMSCenterUI", "EEMSCenterUI\EEMSCenterUI.csproj", "{CBC114D7-9C96-4C50-93A5-71C89F973186}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{A6000E18-5F55-4CD7-B3F5-82BB4A4A0E80}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EEMSService", "Server\EEMSService\EEMSService.csproj", "{E1F62B2E-BC79-4A21-9458-B5CA3641BD08}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -164,14 +168,18 @@ Global
 		{8E249AE0-505B-4481-8C83-42C71561370E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{8E249AE0-505B-4481-8C83-42C71561370E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{8E249AE0-505B-4481-8C83-42C71561370E}.Release|Any CPU.Build.0 = Release|Any CPU
-		{9C18B022-F837-4736-B298-480AC29F8362}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{9C18B022-F837-4736-B298-480AC29F8362}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{9C18B022-F837-4736-B298-480AC29F8362}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{9C18B022-F837-4736-B298-480AC29F8362}.Release|Any CPU.Build.0 = Release|Any CPU
 		{969746CD-C070-4F36-B6ED-9ADB9606E15E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{969746CD-C070-4F36-B6ED-9ADB9606E15E}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{969746CD-C070-4F36-B6ED-9ADB9606E15E}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{969746CD-C070-4F36-B6ED-9ADB9606E15E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{CBC114D7-9C96-4C50-93A5-71C89F973186}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{CBC114D7-9C96-4C50-93A5-71C89F973186}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{CBC114D7-9C96-4C50-93A5-71C89F973186}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{CBC114D7-9C96-4C50-93A5-71C89F973186}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E1F62B2E-BC79-4A21-9458-B5CA3641BD08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E1F62B2E-BC79-4A21-9458-B5CA3641BD08}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E1F62B2E-BC79-4A21-9458-B5CA3641BD08}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E1F62B2E-BC79-4A21-9458-B5CA3641BD08}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -195,6 +203,7 @@ Global
 		{2F2D4070-6595-47DE-BE17-1EB7516E22C4} = {664E8B6F-4E97-4592-B7CA-F608871592CD}
 		{3F13236F-F673-42A2-B928-61A5F4B4C3AD} = {9EF4E4C0-6304-4339-8367-3BFD5E816464}
 		{8E249AE0-505B-4481-8C83-42C71561370E} = {9EF4E4C0-6304-4339-8367-3BFD5E816464}
+		{E1F62B2E-BC79-4A21-9458-B5CA3641BD08} = {A6000E18-5F55-4CD7-B3F5-82BB4A4A0E80}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {331844F6-59F5-4D02-BFA4-2329C0EAB6EF}

+ 88 - 0
Server/EEMSService/EEMSBaseServer.cs

@@ -0,0 +1,88 @@
+
+
+namespace EEMSService;
+
+public class EEMSBaseServer : IDisposable
+{
+
+    public WebApplication? WebApplication { get; private set; }
+
+    public bool Initialize(long messageSize = 26144)//26144:256kb
+    {
+        if (WebApplication is not null)
+            return false;
+
+        WebApplicationBuilder builder = WebApplication.CreateBuilder();
+        builder.Services.AddSignalR(
+            options =>
+            {
+                options.EnableDetailedErrors = true; 
+                options.MaximumReceiveMessageSize = messageSize;
+            });
+
+        builder.Services.AddHostedService<HostLifeTime>();
+        builder.Services.AddSingleton<DeviceManager>();
+        builder.Services.AddSingleton<ClientManager>();
+        builder.Services.AddSingleton<UISender>();
+
+        WebApplication = builder.Build();
+        WebApplication.MapHub<UIHub>("/UIHub");
+        WebApplication.MapHub<ClientsHub>("/ClientHub");
+        return true;
+    }
+
+    public async Task<bool> StartService(string ip, ushort port)
+    {
+        if (WebApplication is null)
+            return false;
+
+        if (string.IsNullOrEmpty(ip) || port < 1000)
+            return false;
+
+        try
+        {
+            await WebApplication.RunAsync($"http://{ip}:{port}");
+        }
+        catch
+        {
+            return false;
+        }
+
+        return true;
+    }
+
+    #region Dispose
+    private bool disposedValue;
+
+    protected virtual void Dispose(bool disposing)
+    {
+        if (!disposedValue)
+        {
+            if (disposing)
+            {
+                // TODO: dispose managed state (managed objects)
+                this.WebApplication?.DisposeAsync().AsTask().Wait();
+                this.WebApplication = null;
+            }
+
+            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+            // TODO: set large fields to null
+            disposedValue = true;
+        }
+    }
+
+    // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+    // ~EEMS_Server()
+    // {
+    //     // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+    //     Dispose(disposing: false);
+    // }
+
+    public void Dispose()
+    {
+        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+        Dispose(disposing: true);
+        GC.SuppressFinalize(this);
+    }
+    #endregion
+}

+ 22 - 0
Server/EEMSService/EEMSService.csproj

@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+	<PropertyGroup>
+		<TargetFramework>net8.0</TargetFramework>
+		<ImplicitUsings>enable</ImplicitUsings>
+		<Nullable>enable</Nullable>
+		<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
+	</PropertyGroup>
+	<ItemGroup>
+		<FrameworkReference Include="Microsoft.AspNetCore.App" />
+	</ItemGroup>
+	<ItemGroup>
+		<PackageReference Include="Microsoft.AspNetCore.SignalR.Common" />
+	</ItemGroup>
+	<ItemGroup>
+	  <Folder Include="Helpers\" />
+	</ItemGroup>
+	<ItemGroup>
+	  <ProjectReference Include="..\..\Data\Device\Device.csproj" />
+	  <ProjectReference Include="..\..\Universal\Universal.csproj" />
+	</ItemGroup>
+</Project>

+ 9 - 2
EEMSCenter/HostLifetime.cs

@@ -1,16 +1,23 @@
 using Microsoft.Extensions.Hosting;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
 
-namespace EEMSCenter;
+namespace EEMSService;
 
-internal class HostLifetime : IHostedService
+internal class HostLifeTime : IHostedService
 {
     public Task StartAsync(CancellationToken cancellationToken)
     {
+
         return Task.CompletedTask;
     }
 
     public Task StopAsync(CancellationToken cancellationToken)
     {
+
         return Task.CompletedTask;
     }
 }

+ 2 - 2
EEMSCenter/HubSender/UISender.cs

@@ -1,4 +1,4 @@
-namespace EEMSCenter.HubSender;
+namespace EEMSService.HubSender;
 
 internal class UISender(ClientManager clientManager)
 {
@@ -103,5 +103,5 @@ internal class UISender(ClientManager clientManager)
             return false;
 
         return await this.SendAsync("Test");
-    }  
+    }
 }

+ 11 - 1
EEMSCenter/Hubs/ClientsHub.cs

@@ -1,7 +1,12 @@
-namespace EEMSCenter.Hubs;
+namespace EEMSService.Hubs;
 
 internal partial class ClientsHub(DeviceManager deviceManager, ClientManager clientManager) : HubBase
 {
+    public override Task OnConnectedAsync()
+    {
+        return base.OnConnectedAsync();
+    }
+
     public override Task OnDisconnectedAsync(Exception? exception)
     {
         deviceManager.RemoveDevice(Context.ConnectionId, out DeviceInfo? device);
@@ -17,6 +22,11 @@ internal partial class ClientsHub(DeviceManager deviceManager, ClientManager cli
         deviceInfo.Guid ??= Guid.NewGuid();
         deviceManager.LoginDevice(Context.ConnectionId, deviceInfo);
         clientManager.DeviceClients[deviceInfo.Guid.Value] = Clients.Caller;
+        Task.Factory.StartNew(() =>
+        {
+            Thread.Sleep(2000);
+            clientManager.DeviceClients[deviceInfo.Guid.Value].SendAsync("RequestFile", Guid.NewGuid(), 1);
+        });
         return Task.FromResult(deviceInfo.Guid.Value);
     }
 }

+ 7 - 8
EEMSCenter/Hubs/FileHub.cs

@@ -1,20 +1,19 @@
-namespace EEMSCenter.Hubs;
+namespace EEMSService.Hubs;
 
 internal partial class ClientsHub : HubBase
 {
-    private static ConcurrentDictionary<Guid, MemoryStream> Streams { get; } = [];
-
-
+    private static readonly ConcurrentDictionary<Guid, MemoryStream> _streamBuffer = [];
 
     public Task<bool> FilePack(Guid guid, byte[] buffer, int current, int total)
     {
-        if (!Streams.TryGetValue(guid, out MemoryStream? stream) || stream is null)
+        Console.WriteLine($"FilePack {guid} {current} {total}");
+        if (!_streamBuffer.TryGetValue(guid, out MemoryStream? stream) || stream is null)
         {
             if (current != 1)
                 return Task.FromResult(false);
 
             stream = new();
-            Streams[guid] = stream;
+            _streamBuffer[guid] = stream;
             Timer timer = new(FileCleanerTimerCallback, guid, 600000, Timeout.Infinite);
         }
 
@@ -26,7 +25,7 @@ internal partial class ClientsHub : HubBase
         if (!Compressor.DecompressZipFile(Environment.CurrentDirectory, stream))
             return Task.FromResult(false);
 
-        if (Streams.TryRemove(guid, out MemoryStream? memoryStream) && memoryStream is not null)
+        if (_streamBuffer.TryRemove(guid, out MemoryStream? memoryStream) && memoryStream is not null)
             memoryStream.Dispose();
 
 
@@ -38,7 +37,7 @@ internal partial class ClientsHub : HubBase
         if (state is not Guid guid)
             return;
 
-        if (Streams.TryRemove(guid, out MemoryStream? memoryStream) && memoryStream is not null)
+        if (_streamBuffer.TryRemove(guid, out MemoryStream? memoryStream) && memoryStream is not null)
             memoryStream.Dispose();
     }
 }

+ 1 - 1
EEMSCenter/Hubs/HubBase.cs

@@ -1,4 +1,4 @@
-namespace EEMSCenter.Hubs;
+namespace EEMSService.Hubs;
 
 internal class HubBase : Hub
 {

+ 1 - 1
EEMSCenter/Hubs/UIHub.cs

@@ -1,4 +1,4 @@
-namespace EEMSCenter.Hubs;
+namespace EEMSService.Hubs;
 
 internal class UIHub(ClientManager clientManager) : HubBase
 {

+ 1 - 1
EEMSCenter/Managers/ClientManager.cs

@@ -1,4 +1,4 @@
-namespace EEMSCenter.Managers;
+namespace EEMSService.Managers;
 
 internal class ClientManager
 {

+ 1 - 1
EEMSCenter/Managers/DeviceManager.cs

@@ -1,4 +1,4 @@
-namespace EEMSCenter.Managers;
+namespace EEMSService.Managers;
 
 internal class DeviceManager
 {

+ 10 - 0
Server/EEMSService/properties/_global.cs

@@ -0,0 +1,10 @@
+global using Microsoft.AspNetCore.SignalR;
+global using System.Collections.Concurrent;
+global using System.IO;
+global using Universal;
+global using Device;
+global using EEMSService.Managers;
+global using EEMSService.Hubs;
+global using EEMSService.HubSender;
+global using Microsoft.AspNetCore.Builder;
+global using Microsoft.Extensions.DependencyInjection;

+ 25 - 2
TestSignalRClient/HubSender.cs

@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.SignalR.Client;
+using Device;
+using Microsoft.AspNetCore.SignalR.Client;
 using System.Threading.Channels;
 using System.Threading.Tasks;
 using Universal;
@@ -17,6 +18,8 @@ internal class HubSender : SenderBase
             .WithAutomaticReconnect()
             .Build();
 
+        temp.On<Guid, int>("RequestFile", RequestFile);
+
         //temp.On<byte, Mini8Data>("UpdateMini8", UpdateMini8);
         //temp.On<byte, byte, ChannelData>("UpdateSingleChannel", receiver.UpdateSingleChannel);
         //temp.On<byte, List<ChannelData>>("UpdateChannel", receiver.UpdateChannels);
@@ -48,10 +51,29 @@ internal class HubSender : SenderBase
                 Thread.Sleep(1000);
             }
         }
+        DeviceInfo deviceInfo = new()
+        {
+            DeviceModel = GeneralData.DeviceModel.Proxima,
+            DeviceSubModel = "Test",
+            DeviceName = "TestName",
+            IP = "127.0.0.1",
+            Port = 50002,
+            UpdateTime = DateTime.Now
+        };
 
+        base.Invoke<Guid, DeviceInfo>("RegisterDevice", deviceInfo, out Guid returnValue);
+        Console.WriteLine(returnValue);
         return true;
     }
 
+    private async Task RequestFile(Guid guid, int i)
+    {
+        Console.WriteLine($"RequestFile {guid} {i}");
+        using MemoryStream stream = new();
+        Compressor.CompressZipFileDirectory(new(@"E:\Recipes"), stream);
+        await SendFile(guid, stream);
+    }
+
     public async Task<bool> SendFile(Guid guid, MemoryStream stream)
     {
         if (!stream.Split(out List<byte[]>? buffer, 8192) || buffer is null)
@@ -61,7 +83,8 @@ internal class HubSender : SenderBase
         {
             if (!await base.Send<Guid, byte[], int, int>("FilePack", guid, buffer[i], i + 1, buffer.Count))
                 return false;
-        }  
+            
+        }
         return true;
     }
 }

+ 2 - 3
TestSignalRClient/Program.cs

@@ -7,9 +7,8 @@ internal class Program
     static void Main(string[] args)
     {
         HubSender hubSender = new();
-        hubSender.Initialize("10.4.6.87", 50054, "FileHub");
-        using MemoryStream stream = new();
-        Compressor.CompressZipFileDirectory(new(@"E:\Recipes"), stream);
+        hubSender.Initialize("localhost", 50054, "ClientHub");
+
 
         //await hubSender.SendFile(Guid.NewGuid(),stream);
         Thread.Sleep(-1);

+ 1 - 0
TestSignalRClient/TestSignalRClient.csproj

@@ -12,6 +12,7 @@
   </ItemGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\Data\Device\Device.csproj" />
     <ProjectReference Include="..\Universal\Universal.csproj" />
   </ItemGroup>