diff --git a/KitX Dashboard Assets/KitX.Dashboard.Assets.csproj b/KitX Dashboard Assets/KitX.Dashboard.Assets.csproj index 8f66ea34..ab33cbab 100644 --- a/KitX Dashboard Assets/KitX.Dashboard.Assets.csproj +++ b/KitX Dashboard Assets/KitX.Dashboard.Assets.csproj @@ -1,7 +1,7 @@  Library - net7.0 + net8.0 enable copyused true diff --git a/KitX Dashboard Assets/KitX.svg b/KitX Dashboard Assets/KitX.svg new file mode 100644 index 00000000..a76fefd2 --- /dev/null +++ b/KitX Dashboard Assets/KitX.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/KitX Dashboard Core/KitX.Dashboard.Core.csproj b/KitX Dashboard Core/KitX.Dashboard.Core.csproj index d4398000..6f4623a8 100644 --- a/KitX Dashboard Core/KitX.Dashboard.Core.csproj +++ b/KitX Dashboard Core/KitX.Dashboard.Core.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/KitX Dashboard Fonts/KitX.Dashboard.Fonts.csproj b/KitX Dashboard Fonts/KitX.Dashboard.Fonts.csproj index da9f5006..7a826c12 100644 --- a/KitX Dashboard Fonts/KitX.Dashboard.Fonts.csproj +++ b/KitX Dashboard Fonts/KitX.Dashboard.Fonts.csproj @@ -1,7 +1,7 @@  Library - net7.0 + net8.0 enable copyused true diff --git a/KitX Dashboard Repair Tool/KitX.Dashboard.RepairTool.csproj b/KitX Dashboard Repair Tool/KitX.Dashboard.RepairTool.csproj index 53f71e75..bfca505b 100644 --- a/KitX Dashboard Repair Tool/KitX.Dashboard.RepairTool.csproj +++ b/KitX Dashboard Repair Tool/KitX.Dashboard.RepairTool.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable true diff --git a/KitX Dashboard Repair Tool/Program.cs b/KitX Dashboard Repair Tool/Program.cs index a19ac410..bdcfa745 100644 --- a/KitX Dashboard Repair Tool/Program.cs +++ b/KitX Dashboard Repair Tool/Program.cs @@ -6,7 +6,8 @@ KitX Repair Tool (C) Crequency Environment: {Environment.Version} OS Version: {Environment.OSVersion} - """); + """ + ); }; var log_exception = (Exception e) => @@ -15,13 +16,18 @@ KitX Repair Tool (C) Crequency Console.WriteLine(e.StackTrace); }; -T? ask(string tip = "Input: ", Func? parse = null) +static T? ask(string tip = "Input: ", Func? parse = null) { Console.Write(tip); - string? input = Console.ReadLine(); + + var input = Console.ReadLine(); + if (input is null) return default(T); - if (parse is not null) return parse(input); - else throw new Exception(input); + + if (parse is not null) + return parse(input); + else + throw new Exception(input); }; var menu = () => @@ -29,11 +35,16 @@ KitX Repair Tool (C) Crequency Console.WriteLine( """ 1. (root) Linux wayland repair (add `LC_ALL=C` to environment variables) - """); - return ask("Your select: ", x => int.TryParse(x, out int y) ? y : -1); + """ + ); + + return ask("You selected: ", x => int.TryParse(x, out int y) ? y : -1); }; + + tip_copyright(); + switch (menu()) { case 1: @@ -44,7 +55,12 @@ KitX Repair Tool (C) Crequency } try { - File.AppendAllLines("/etc/environment", new string[] { "LC_ALL=C" }); + await File.AppendAllLinesAsync( + "/etc/environment", + [ + "LC_ALL=C" + ] + ); } catch (Exception e) { diff --git a/KitX Dashboard Updater/KitX.Dashboard.Updater.csproj b/KitX Dashboard Updater/KitX.Dashboard.Updater.csproj index 73bc01b3..c2490c43 100644 --- a/KitX Dashboard Updater/KitX.Dashboard.Updater.csproj +++ b/KitX Dashboard Updater/KitX.Dashboard.Updater.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0 enable enable diff --git a/KitX Dashboard/App.axaml b/KitX Dashboard/App.axaml index 6576c546..a22c748f 100644 --- a/KitX Dashboard/App.axaml +++ b/KitX Dashboard/App.axaml @@ -12,8 +12,6 @@ - KitX - @@ -32,12 +30,20 @@ avares://KitX.Dashboard.Fonts/sarasa-mono-cl-regular.ttf#Sarasa Mono CL avares://KitX.Dashboard.Fonts/SourceHanSans-VF.ttf#Source Han Sans VF + + + + + + + + @@ -45,10 +51,13 @@ + ToolTipText="{Binding TrayIconText}"> + + + diff --git a/KitX Dashboard/App.axaml.cs b/KitX Dashboard/App.axaml.cs index 81f0b183..d6868ef8 100644 --- a/KitX Dashboard/App.axaml.cs +++ b/KitX Dashboard/App.axaml.cs @@ -1,189 +1,173 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Markup.Xaml; -using Avalonia.Media; -using Avalonia.Media.Imaging; -using Avalonia.Styling; -using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; -using KitX.Dashboard.Managers; -using KitX.Dashboard.Services; -using KitX.Dashboard.ViewModels; -using KitX.Dashboard.Views; -using LiveChartsCore; -using LiveChartsCore.SkiaSharpView; -using Serilog; -using System; -using System.IO; -using System.Linq; -using System.Threading; - -namespace KitX.Dashboard; - -public partial class App : Application -{ - public static readonly Bitmap DefaultIcon = new( - $"{GlobalInfo.AssetsPath}{ConfigManager.AppConfig.App.CoverIconFileName}".GetFullPath() - ); - - private AppViewModel? viewModel; - - /// - /// Override `Initialize` function - /// - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - - LoadLanguage(); - - CalculateThemeColor(); - - InitLiveCharts(); - - // Must construct after `LoadLanguage()` function. - viewModel = new(); - - DataContext = viewModel; - } - - /// - /// Load Language - /// - private void LoadLanguage() - { - var lang = ConfigManager.AppConfig.App.AppLanguage; - var backup_lang = ConfigManager.AppConfig.App.SurpportLanguages.Keys.First(); - var path = $"{GlobalInfo.LanguageFilePath}/{lang}.axaml".GetFullPath(); - var backup_langPath = $"{GlobalInfo.LanguageFilePath}/{backup_lang}.axaml"; - - backup_langPath = backup_langPath.GetFullPath(); - - try - { - Resources.MergedDictionaries.Clear(); - - Resources.MergedDictionaries.Add( - AvaloniaRuntimeXamlLoader.Load( - File.ReadAllText(path) - ) as ResourceDictionary ?? new() - ); - } - catch (Exception ex) - { - Log.Warning(ex, $"Language File {lang}.axaml not found."); - - Resources.MergedDictionaries.Clear(); - - try - { - Resources.MergedDictionaries.Add( - AvaloniaRuntimeXamlLoader.Load( - File.ReadAllText(backup_langPath) - ) as ResourceDictionary ?? new() - ); - ConfigManager.AppConfig.App.AppLanguage = backup_lang; - } - catch (Exception e) - { - Log.Warning(e, $"Suspected absence of language files on record."); - } - finally - { - Log.Warning($"No surpport language file loaded."); - } - } - - try - { - EventService.Invoke(nameof(EventService.LanguageChanged)); - } - catch (Exception e) - { - Log.Warning(e, $"Failed to invoke language changed event."); - } - } - - /// - /// Calculate theme color - /// - private static void CalculateThemeColor() - { - Color c = Color.Parse(ConfigManager.AppConfig.App.ThemeColor); - - if (Current is not null) - { - Current.Resources["ThemePrimaryAccent"] = - new SolidColorBrush(new Color(c.A, c.R, c.G, c.B)); - for (char i = 'A'; i <= 'E'; ++i) - { - Current.Resources[$"ThemePrimaryAccentTransparent{i}{i}"] = - new SolidColorBrush(new Color((byte)(170 + (i - 'A') * 17), c.R, c.G, c.B)); - } - for (int i = 1; i <= 9; ++i) - { - Current.Resources[$"ThemePrimaryAccentTransparent{i}{i}"] = - new SolidColorBrush(new Color((byte)(i * 10 + i), c.R, c.G, c.B)); - } - } - } - - /// - /// Init `LiveCharts` - /// - private static void InitLiveCharts() - { - { - var usingLightTheme = Current?.ActualThemeVariant == ThemeVariant.Light; - - LiveCharts.Configure( - config => - (usingLightTheme ? config.AddLightTheme() : config.AddDarkTheme()) - .AddSkiaSharp() - .AddDefaultMappers() - ); - } - - EventService.ThemeConfigChanged += () => - { - var usingLightTheme = Current?.ActualThemeVariant == ThemeVariant.Light; - - LiveCharts.Configure(config => - { - config = usingLightTheme ? config.AddLightTheme() : config.AddDarkTheme(); - }); - }; - } - - /// - /// Override `OnFrameworkInitializationCompleted` function - /// - public override void OnFrameworkInitializationCompleted() - { - var location = $"{nameof(App)}.{nameof(OnFrameworkInitializationCompleted)}"; - - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - { - desktop.MainWindow = new MainWindow - { - DataContext = new MainWindowViewModel(), - }; - } - - if (ConfigManager.AppConfig.App.ShowAnnouncementWhenStart) - new Thread(async () => - { - try - { - await AnouncementManager.CheckNewAnnouncements(); - } - catch (Exception ex) - { - Log.Error(ex, $"In {location}: {ex.Message}"); - } - }).Start(); - - base.OnFrameworkInitializationCompleted(); - } -} +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using Avalonia.Styling; +using Common.BasicHelper.Utils.Extensions; +using KitX.Dashboard.Managers; +using KitX.Dashboard.Services; +using KitX.Dashboard.ViewModels; +using KitX.Dashboard.Views; +using LiveChartsCore; +using LiveChartsCore.SkiaSharpView; +using Serilog; +using System; +using System.IO; +using System.Linq; +using System.Threading; + +namespace KitX.Dashboard; + +public partial class App : Application +{ + public static readonly Bitmap DefaultIcon = new( + $"{ConstantTable.AssetsPath}{Instances.ConfigManager.AppConfig.App.CoverIconFileName}".GetFullPath() + ); + + private AppViewModel? viewModel; + + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + + LoadLanguage(); + + CalculateThemeColor(); + + InitLiveCharts(); + + // Must construct after `LoadLanguage()` function. + viewModel = new(); + + DataContext = viewModel; + } + + private void LoadLanguage() + { + var config = Instances.ConfigManager.AppConfig; + var lang = config.App.AppLanguage; + var backup_lang = config.App.SurpportLanguages.Keys.First(); + var path = $"{ConstantTable.LanguageFilePath}/{lang}.axaml".GetFullPath(); + var backup_langPath = $"{ConstantTable.LanguageFilePath}/{backup_lang}.axaml".GetFullPath(); + + try + { + Resources.MergedDictionaries.Clear(); + + Resources.MergedDictionaries.Add( + AvaloniaRuntimeXamlLoader.Load( + File.ReadAllText(path) + ) as ResourceDictionary ?? [] + ); + } + catch (Exception ex) + { + Log.Warning(ex, $"Language File {lang}.axaml not found."); + + Resources.MergedDictionaries.Clear(); + + try + { + Resources.MergedDictionaries.Add( + AvaloniaRuntimeXamlLoader.Load( + File.ReadAllText(backup_langPath) + ) as ResourceDictionary ?? [] + ); + + config.App.AppLanguage = backup_lang; + } + catch (Exception e) + { + Log.Warning(e, $"Suspected absence of language files on record."); + } + finally + { + Log.Warning($"No surpport language file loaded."); + } + } + + try + { + EventService.Invoke(nameof(EventService.LanguageChanged)); + } + catch (Exception e) + { + Log.Warning(e, $"Failed to invoke language changed event."); + } + } + + private static void CalculateThemeColor() + { + Color c = Color.Parse(Instances.ConfigManager.AppConfig.App.ThemeColor); + + if (Current is not null) + { + Current.Resources["ThemePrimaryAccent"] = + new SolidColorBrush(new Color(c.A, c.R, c.G, c.B)); + for (char i = 'A'; i <= 'E'; ++i) + { + Current.Resources[$"ThemePrimaryAccentTransparent{i}{i}"] = + new SolidColorBrush(new Color((byte)(170 + (i - 'A') * 17), c.R, c.G, c.B)); + } + for (int i = 1; i <= 9; ++i) + { + Current.Resources[$"ThemePrimaryAccentTransparent{i}{i}"] = + new SolidColorBrush(new Color((byte)(i * 10 + i), c.R, c.G, c.B)); + } + } + } + + private static void InitLiveCharts() + { + { + var usingLightTheme = Current?.ActualThemeVariant == ThemeVariant.Light; + + LiveCharts.Configure( + config => + (usingLightTheme ? config.AddLightTheme() : config.AddDarkTheme()) + .AddSkiaSharp() + .AddDefaultMappers() + ); + } + + EventService.ThemeConfigChanged += () => + { + var usingLightTheme = Current?.ActualThemeVariant == ThemeVariant.Light; + + LiveCharts.Configure(config => + { + config = usingLightTheme ? config.AddLightTheme() : config.AddDarkTheme(); + }); + }; + } + + public override void OnFrameworkInitializationCompleted() + { + var location = $"{nameof(App)}.{nameof(OnFrameworkInitializationCompleted)}"; + + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow + { + DataContext = new MainWindowViewModel(), + }; + } + + if (Instances.ConfigManager.AppConfig.App.ShowAnnouncementWhenStart) + new Thread(async () => + { + try + { + await AnouncementManager.CheckNewAnnouncements(); + } + catch (Exception ex) + { + Log.Error(ex, $"In {location}: {ex.Message}"); + } + }).Start(); + + base.OnFrameworkInitializationCompleted(); + } +} diff --git a/KitX Dashboard/AppFramework.cs b/KitX Dashboard/AppFramework.cs new file mode 100644 index 00000000..c92fb6d4 --- /dev/null +++ b/KitX Dashboard/AppFramework.cs @@ -0,0 +1,263 @@ +using Avalonia.Threading; +using CommandLine; +using Common.BasicHelper.IO; +using Common.BasicHelper.Utils.Extensions; +using KitX.Dashboard.Managers; +using KitX.Dashboard.Names; +using KitX.Dashboard.Options; +using KitX.Dashboard.Services; +using KitX.Dashboard.Views; +using LiteDB; +using ReactiveUI; +using Serilog; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reactive; +using System.Threading; +using System.Threading.Tasks; + +namespace KitX.Dashboard; + +public static class AppFramework +{ + private readonly static Queue actionsInInitialization = []; + + public static void ProcessStartupArguments() + { + Parser.Default.ParseArguments(Environment.GetCommandLineArgs()) + .WithParsed(opt => + { + ConstantTable.IsSingleProcessStartMode = !opt.DisableSingleProcessCheck; + ConstantTable.EnabledConfigFileHotReload = !opt.DisableConfigHotReload; + ConstantTable.SkipNetworkSystemOnStartup = opt.DisableNetworkSystemOnStartup; + + TasksManager.RunTask(() => + { + if (opt.PluginPath is not null) + ImportPlugin(opt.PluginPath); + }, $"{nameof(ImportPlugin)}", catchException: true); + }); + } + + public static void RunFramework() + { + // If dump file exists, delete it. + if (File.Exists("./dump.log".GetFullPath())) + File.Delete("./dump.log".GetFullPath()); + + Instances.Initialize(); + + Instances.ConfigManager.AppConfig.App.RanTime++; + + var config = Instances.ConfigManager.AppConfig; + + ProcessStartupArguments(); + + if (ConstantTable.IsSingleProcessStartMode) + Process.GetProcesses().WhenCount( + count => count >= 2, + item => item.ProcessName.StartsWith("KitX.Dashboard"), + _ => Environment.Exit(ExitCodes.MultiProcessesStarted) + ); + + LoadResource(); + + #region Initialize log system + + var logdir = config.Log.LogFilePath.GetFullPath(); + + if (!Directory.Exists(logdir)) + Directory.CreateDirectory(logdir); + + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Information() + .WriteTo.File( + $"{logdir}Log_.log", + outputTemplate: config.Log.LogTemplate, + rollingInterval: RollingInterval.Hour, + fileSizeLimitBytes: config.Log.LogFileSingleMaxSize, + buffered: true, + flushToDiskInterval: new( + 0, + 0, + config.Log.LogFileFlushInterval + ), + restrictedToMinimumLevel: config.Log.LogLevel, + rollOnFileSizeLimit: true, + retainedFileCountLimit: config.Log.LogFileMaxCount + ) + .CreateLogger(); + + Log.Information("KitX Dashboard Started."); + + #endregion + + #region Initialize global exception catching + + AppDomain.CurrentDomain.UnhandledException += (sender, e) => + { + if (e.ExceptionObject is Exception ex) + Log.Error(ex, $"Au oh! Fatal: {ex.Message}"); + }; + + TaskScheduler.UnobservedTaskException += (sender, e) => + { + Log.Error(e.Exception, $"Au oh! Fatal: {e.Exception.Message}"); + }; + + RxApp.DefaultExceptionHandler = Observer.Create(ex => + { + Log.Error(ex, $"Au oh! Fatal: {ex.Message}"); + }); + + #endregion + + #region Initialize DataBase + + InitDataBase(); + + #endregion + + #region Initialize WebManager + + Instances.SignalTasksManager!.SignalRun(nameof(SignalsNames.MainWindowInitSignal), () => + { + new Thread(async () => + { + Thread.Sleep( + Convert.ToInt32(config.Web.DelayStartSeconds * 1000) + ); + + if (ConstantTable.SkipNetworkSystemOnStartup) + Instances.WebManager = new(); + else + Instances.WebManager = await new WebManager().RunAsync(new()); + }).Start(); + }); + + #endregion + + #region Initialize StatisticsManager + + StatisticsManager.Start(); + + #endregion + + #region Initialize persistent windows + + Instances.SignalTasksManager.SignalRun(nameof(SignalsNames.MainWindowInitSignal), () => + { + Dispatcher.UIThread.Post(() => + { + ViewInstances.PluginsLaunchWindow = new(); + }); + }); + + #endregion + + actionsInInitialization.ForEach(x => x.Invoke()); + } + + private static void InitDataBase() + { + var location = $"{nameof(AppFramework)}.{nameof(InitDataBase)}"; + + try + { + var dir = ConstantTable.DataPath.GetFullPath(); + + if (!Directory.Exists(dir)) _ = Directory.CreateDirectory(dir); + + var dbfile = ConstantTable.ActivitiesDataBaseFilePath.GetFullPath(); + + var db = new LiteDatabase(dbfile); + + Instances.ActivitiesDataBase = db; + + ActivityManager.RecordAppStart(); + } + catch (Exception ex) + { + Log.Error(ex, $"In {location}: {ex.Message}"); + } + } + + private static async void LoadResource() + { + var location = $"{nameof(AppFramework)}.{nameof(LoadResource)}"; + + try + { + ConstantTable.KitXIconBase64 = await FileHelper.ReadAllAsync( + $"{ConstantTable.AssetsPath}{ConstantTable.IconBase64FileName}".GetFullPath() + ); + } + catch (Exception ex) + { + Log.Error(ex, $"In {location}: {ex.Message}"); + } + } + + public static void AfterInitailization(Action action) => actionsInInitialization.Enqueue(action); + + private static void ImportPlugin(string kxpPath) + { + var location = $"{nameof(AppFramework)}.{nameof(ImportPlugin)}"; + + try + { + if (!File.Exists(kxpPath)) + { + Console.WriteLine($"No this file: {kxpPath}"); + + throw new Exception("Plugins Package Doesn't Exist."); + } + else + { + PluginsManager.ImportPlugin([kxpPath]); + } + } + catch (Exception ex) + { + Log.Error(ex, $"In {location}: {ex.Message}"); + } + } + + public static void EnsureExit() + { + var location = $"{nameof(AppFramework)}.{nameof(EnsureExit)}"; + + new Thread(async () => + { + try + { + ActivityManager.RecordAppExit(); + + Instances.FileWatcherManager?.Clear(); + + Instances.ConfigManager.SaveAll(); + + Log.CloseAndFlush(); + + if (Instances.WebManager is not null) + await Instances.WebManager.CloseAsync(new()); + Instances.WebManager?.Dispose(); + + Instances.ActivitiesDataBase?.Commit(); + Instances.ActivitiesDataBase?.Dispose(); + + ConstantTable.Running = false; + + Thread.Sleep(Instances.ConfigManager.AppConfig.App.LastBreakAfterExit); + + Environment.Exit(0); + } + catch (Exception ex) + { + Log.Warning(ex, $"In {location}: {ex.Message}"); + } + }).Start(); + } +} diff --git a/KitX Dashboard/Configuration/AnnouncementConfig.cs b/KitX Dashboard/Configuration/AnnouncementConfig.cs new file mode 100644 index 00000000..24865c04 --- /dev/null +++ b/KitX Dashboard/Configuration/AnnouncementConfig.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace KitX.Dashboard.Configuration; + +public class AnnouncementConfig : ConfigBase +{ + public List Accepted { get; set; } = []; +} diff --git a/KitX Dashboard/Data/AppConfig.cs b/KitX Dashboard/Configuration/AppConfig.cs similarity index 58% rename from KitX Dashboard/Data/AppConfig.cs rename to KitX Dashboard/Configuration/AppConfig.cs index a837c45f..2a51c05a 100644 --- a/KitX Dashboard/Data/AppConfig.cs +++ b/KitX Dashboard/Configuration/AppConfig.cs @@ -1,61 +1,42 @@ using Avalonia.Controls; +using Common.BasicHelper.Graphics.Screen; using FluentAvalonia.UI.Controls; +using KitX.Dashboard.Configuration.Interfaces; using Serilog.Events; using System.Collections.Generic; -using System.Text.Json.Serialization; -namespace KitX.Dashboard.Data; +namespace KitX.Dashboard.Configuration; -/// -/// 配置结构 -/// -public class AppConfig +public class AppConfig : ConfigBase { - [JsonInclude] public Config_App App { get; set; } = new(); - [JsonInclude] public Config_Windows Windows { get; set; } = new(); - [JsonInclude] public Config_Pages Pages { get; set; } = new(); - [JsonInclude] public Config_Web Web { get; set; } = new(); - [JsonInclude] public Config_Log Log { get; set; } = new(); - [JsonInclude] public Config_IO IO { get; set; } = new(); - [JsonInclude] public Config_Activity Activity { get; set; } = new(); - [JsonInclude] public Config_Loaders Loaders { get; set; } = new(); - /// - /// AppConfig - /// public class Config_App { - [JsonInclude] public string IconFileName { get; set; } = "KitX-Icon-1920x-margin-2x.png"; - [JsonInclude] public string CoverIconFileName { get; set; } = "KitX-Icon-Background.png"; - [JsonInclude] public string AppLanguage { get; set; } = "zh-cn"; - [JsonInclude] public string Theme { get; set; } = "Follow"; - [JsonInclude] public string ThemeColor { get; set; } = "#FF3873D9"; - [JsonInclude] public Dictionary SurpportLanguages { get; set; } = new() { { "zh-cn", "中文 (简体)" }, @@ -67,344 +48,211 @@ public class Config_App { "ko-kr", "한국어" }, }; - [JsonInclude] public string LocalPluginsFileFolder { get; set; } = "./Plugins/"; - [JsonInclude] public string LocalPluginsDataFolder { get; set; } = "./PluginsDatas/"; - [JsonInclude] public bool DeveloperSetting { get; set; } = false; - [JsonInclude] public bool ShowAnnouncementWhenStart { get; set; } = true; - [JsonInclude] public ulong RanTime { get; set; } = 0; + + public int LastBreakAfterExit { get; set; } = 2000; } - /// - /// WindowsConfig - /// public class Config_Windows { - - [JsonInclude] public Config_MainWindow MainWindow { get; set; } = new(); - [JsonInclude] public Config_AnnouncementWindow AnnouncementWindow { get; set; } = new(); - /// - /// MainWindowConfig - /// - public class Config_MainWindow + public class Config_MainWindow : IWindowConfig { - [JsonInclude] - public double Window_Width { get; set; } = 1280; - - [JsonInclude] - public double Window_Height { get; set; } = 720; - - [JsonInclude] - public int Window_Left { get; set; } = -1; + public Resolution Size { get; set; } = Resolution.Parse("1280x720"); - [JsonInclude] - public int Window_Top { get; set; } = -1; + public Distances Location { get; set; } = new(left: -1, top: -1); - [JsonInclude] public WindowState WindowState { get; set; } = WindowState.Normal; - [JsonInclude] public bool IsHidden { get; set; } = false; - [JsonInclude] public Dictionary Tags { get; set; } = new() { { "SelectedPage", "Page_Home" } }; - [JsonInclude] public bool EnabledMica { get; set; } = true; - [JsonInclude] - public double MicaOpacity { get; set; } = 0.15; - - [JsonInclude] public int GreetingTextCount_Morning { get; set; } = 5; - [JsonInclude] public int GreetingTextCount_Noon { get; set; } = 3; - [JsonInclude] public int GreetingTextCount_AfterNoon { get; set; } = 3; - [JsonInclude] public int GreetingTextCount_Evening { get; set; } = 2; - [JsonInclude] public int GreetingTextCount_Night { get; set; } = 4; - [JsonInclude] public int GreetingUpdateInterval { get; set; } = 10; } - /// - /// AnnouncementWindowConfig - /// - public class Config_AnnouncementWindow + public class Config_AnnouncementWindow : IWindowConfig { - [JsonInclude] - public double Window_Width { get; set; } = 1280; + public Resolution Size { get; set; } = Resolution.Parse("1280x720"); - [JsonInclude] - public double Window_Height { get; set; } = 720; - - [JsonInclude] - public int Window_Left { get; set; } = -1; - - [JsonInclude] - public int Window_Top { get; set; } = -1; + public Distances Location { get; set; } = new(left: -1, top: -1); } } - /// - /// ViewsConfig - /// public class Config_Pages { - [JsonInclude] public Config_HomePage Home { get; set; } = new(); - [JsonInclude] public Config_DevicePage Device { get; set; } = new(); - [JsonInclude] public Config_MarketPage Market { get; set; } = new(); - [JsonInclude] public Config_SettingsPage Settings { get; set; } = new(); - /// - /// HomePageConfig - /// public class Config_HomePage { - [JsonInclude] - public NavigationViewPaneDisplayMode NavigationViewPaneDisplayMode { get; set; } - = NavigationViewPaneDisplayMode.Auto; + public NavigationViewPaneDisplayMode NavigationViewPaneDisplayMode { get; set; } = NavigationViewPaneDisplayMode.Auto; - [JsonInclude] public string SelectedViewName { get; set; } = "View_Recent"; - [JsonInclude] public bool IsNavigationViewPaneOpened { get; set; } = true; - [JsonInclude] public bool UseAreaExpanded { get; set; } = true; } - /// - /// DevicePageConfig - /// public class Config_DevicePage { } - /// - /// MargetPageConfig - /// public class Config_MarketPage { } - /// - /// SettingsPageConfig - /// public class Config_SettingsPage { - [JsonInclude] - public NavigationViewPaneDisplayMode NavigationViewPaneDisplayMode { get; set; } - = NavigationViewPaneDisplayMode.Auto; + public NavigationViewPaneDisplayMode NavigationViewPaneDisplayMode { get; set; } = NavigationViewPaneDisplayMode.Auto; - [JsonInclude] public string SelectedViewName { get; set; } = "View_General"; - [JsonInclude] public bool MicaAreaExpanded { get; set; } = true; - [JsonInclude] public bool MicaToolTipIsOpen { get; set; } = true; - [JsonInclude] public bool PaletteAreaExpanded { get; set; } = false; - [JsonInclude] public bool WebRelatedAreaExpanded { get; set; } = true; - [JsonInclude] public bool WebRelatedAreaOfNetworkInterfacesExpanded { get; set; } = false; - [JsonInclude] public bool LogRelatedAreaExpanded { get; set; } = true; - [JsonInclude] public bool UpdateRelatedAreaExpanded { get; set; } = true; - [JsonInclude] public bool AboutAreaExpanded { get; set; } = false; - [JsonInclude] public bool AuthorsAreaExpanded { get; set; } = false; - [JsonInclude] public bool LinksAreaExpanded { get; set; } = false; - [JsonInclude] public bool ThirdPartyLicensesAreaExpanded { get; set; } = false; - [JsonInclude] public bool IsNavigationViewPaneOpened { get; set; } = true; } } - /// - /// WebConfig - /// public class Config_Web { - [JsonInclude] public double DelayStartSeconds { get; set; } = 0.5; - [JsonInclude] - public string APIServer { get; set; } = "api.catrol.cn"; + public string ApiServer { get; set; } = "api.catrol.cn"; - [JsonInclude] - public string APIPath { get; set; } = "/apps/kitx/"; + public string ApiPath { get; set; } = "/apps/kitx/"; - [JsonInclude] public int DevicesViewRefreshDelay { get; set; } = 1000; - [JsonInclude] public List? AcceptedNetworkInterfaces { get; set; } = null; - [JsonInclude] public int? UserSpecifiedPluginsServerPort { get; set; } = null; - [JsonInclude] - public int UDPPortSend { get; set; } = 23404; + public int UdpPortSend { get; set; } = 23404; - [JsonInclude] - public int UDPPortReceive { get; set; } = 24040; + public int UdpPortReceive { get; set; } = 24040; - [JsonInclude] - public int UDPSendFrequency { get; set; } = 1000; + public int UdpSendFrequency { get; set; } = 1000; - [JsonInclude] - public string UDPBroadcastAddress { get; set; } = "224.0.0.0"; + public string UdpBroadcastAddress { get; set; } = "224.0.0.0"; - [JsonInclude] public string IPFilter { get; set; } = "192.168"; - [JsonInclude] public int SocketBufferSize { get; set; } = 1024 * 100; - [JsonInclude] - public int DeviceInfoStructTTLSeconds { get; set; } = 7; + public int DeviceInfoTTLSeconds { get; set; } = 7; - [JsonInclude] public bool DisableRemovingOfflineDeviceCard { get; set; } = false; - [JsonInclude] public string UpdateServer { get; set; } = "api.catrol.cn"; - /// - /// %platform% - Windows, Linux, MacOS (win, linux, mac) - /// - [JsonInclude] public string UpdatePath { get; set; } = "/apps/kitx/%platform%/"; - [JsonInclude] public string UpdateDownloadPath { get; set; } = "/apps/kitx/update/%platform%/"; /// /// %channel% - Stable, Beta, Alpha (stable, beta, alpha) /// - [JsonInclude] + public string UpdateChannel { get; set; } = "stable"; - [JsonInclude] public string UpdateSource { get; set; } = "latest-components.json"; - [JsonInclude] public int DebugServicesServerPort { get; set; } = 7777; } - /// - /// LogConfig - /// public class Config_Log { - - [JsonInclude] public long LogFileSingleMaxSize { get; set; } = 1024 * 1024 * 10; // 10MB - [JsonInclude] public string LogFilePath { get; set; } = "./Log/"; - [JsonInclude] - public string LogTemplate { get; set; } = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] " + - "[{Level:u3}] {Message:lj}{NewLine}{Exception}"; + public string LogTemplate { get; set; } = "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{Level:u3}] {Message:lj}{NewLine}{Exception}"; - [JsonInclude] public int LogFileMaxCount { get; set; } = 50; - [JsonInclude] public int LogFileFlushInterval { get; set; } = 30; #if DEBUG - [JsonInclude] public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information; #else - [JsonInclude] public LogEventLevel LogLevel { get; set; } = LogEventLevel.Warning; #endif } - /// - /// IOConfig - /// public class Config_IO { - [JsonInclude] public int UpdatingCheckPerThreadFilesCount { get; set; } = 20; - [JsonInclude] public int OperatingSystemVersionUpdateInterval { get; set; } = 60; } - /// - /// ActivityConfig - /// public class Config_Activity { - [JsonInclude] public int TotalRecorded { get; set; } = 0; } - /// - /// LoadersConfig - /// public class Config_Loaders { - [JsonInclude] public string InstallPath { get; set; } = "./Loaders/"; } } diff --git a/KitX Dashboard/Configuration/ConfigBase.cs b/KitX Dashboard/Configuration/ConfigBase.cs new file mode 100644 index 00000000..95b56f0e --- /dev/null +++ b/KitX Dashboard/Configuration/ConfigBase.cs @@ -0,0 +1,83 @@ +using Common.BasicHelper.Utils.Extensions; +using System; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace KitX.Dashboard.Configuration; + +[JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(ConfigBase), typeDiscriminator: nameof(ConfigBase))] +[JsonDerivedType(typeof(AppConfig), typeDiscriminator: nameof(AppConfig))] +[JsonDerivedType(typeof(PluginsConfig), typeDiscriminator: nameof(PluginsConfig))] +[JsonDerivedType(typeof(MarketConfig), typeDiscriminator: nameof(MarketConfig))] +[JsonDerivedType(typeof(AnnouncementConfig), typeDiscriminator: nameof(AnnouncementConfig))] +public class ConfigBase +{ + public string? ConfigFileLocation { get; set; } + + public string? ConfigFileWatcherName { get; set; } + + public DateTime? ConfigGeneratedTime { get; set; } = DateTime.Now; +} + +public static class ConfigBaseExtensions +{ + private static readonly object _configReadWriteLock = new(); + + private static readonly JsonSerializerOptions serializationOptions = new() + { + WriteIndented = true, + IncludeFields = true, + PropertyNameCaseInsensitive = true, + }; + + public static T Load(this string path) where T : ConfigBase, new() + { + path = path.GetFullPath(); + + if (!File.Exists(path)) + { + var dir = Path.GetDirectoryName(path); + + if (!Directory.Exists(dir) && dir is not null) + Directory.CreateDirectory(dir); + + var conf = new T().Save(path); + + return conf; + } + + string text; + + lock (_configReadWriteLock) + { + text = File.ReadAllText(path); + } + + var result = JsonSerializer.Deserialize(text, serializationOptions); + + return result as T ?? throw new Exception("Can not deserialize config file."); + } + + public static T Save(this T config, string path) where T : ConfigBase + { + path = path.GetFullPath(); + + lock (_configReadWriteLock) + { + var text = JsonSerializer.Serialize(config, serializationOptions); + + File.WriteAllText(path, text); + } + + return config; + } + + public static T SetConfigFileLocation(this T config, string path) where T : ConfigBase + { + config.ConfigFileLocation = path; + + return config; + } +} diff --git a/KitX Dashboard/Configuration/ConfigFetcher.cs b/KitX Dashboard/Configuration/ConfigFetcher.cs new file mode 100644 index 00000000..13046a99 --- /dev/null +++ b/KitX Dashboard/Configuration/ConfigFetcher.cs @@ -0,0 +1,6 @@ +namespace KitX.Dashboard.Configuration; + +public class ConfigFetcher +{ + public static AppConfig AppConfig => Instances.ConfigManager.AppConfig; +} diff --git a/KitX Dashboard/Configuration/Interfaces/IWindowConfig.cs b/KitX Dashboard/Configuration/Interfaces/IWindowConfig.cs new file mode 100644 index 00000000..167adb19 --- /dev/null +++ b/KitX Dashboard/Configuration/Interfaces/IWindowConfig.cs @@ -0,0 +1,10 @@ +using Common.BasicHelper.Graphics.Screen; + +namespace KitX.Dashboard.Configuration.Interfaces; + +public interface IWindowConfig +{ + public Resolution Size { get; set; } + + public Distances Location { get; set; } +} diff --git a/KitX Dashboard/Data/MarketConfig.cs b/KitX Dashboard/Configuration/MarketConfig.cs similarity index 66% rename from KitX Dashboard/Data/MarketConfig.cs rename to KitX Dashboard/Configuration/MarketConfig.cs index 8166230c..cd1b786b 100644 --- a/KitX Dashboard/Data/MarketConfig.cs +++ b/KitX Dashboard/Configuration/MarketConfig.cs @@ -1,14 +1,10 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -namespace KitX.Dashboard.Data; +namespace KitX.Dashboard.Configuration; -/// -/// 市场配置文件 -/// -public class MarketConfig +public class MarketConfig : ConfigBase { - [JsonInclude] public Dictionary Sources { get; set; } = new() { { "KitX Official Market Source", "https://cget.catrol.cn/KitX/v1/index.json" } diff --git a/KitX Dashboard/Configuration/PluginsConfig.cs b/KitX Dashboard/Configuration/PluginsConfig.cs new file mode 100644 index 00000000..05bae6a5 --- /dev/null +++ b/KitX Dashboard/Configuration/PluginsConfig.cs @@ -0,0 +1,9 @@ +using KitX.Dashboard.Models; +using System.Collections.Generic; + +namespace KitX.Dashboard.Configuration; + +public class PluginsConfig : ConfigBase +{ + public List Plugins { get; set; } = []; +} diff --git a/KitX Dashboard/Data/GlobalInfo.cs b/KitX Dashboard/ConstantTable.cs similarity index 69% rename from KitX Dashboard/Data/GlobalInfo.cs rename to KitX Dashboard/ConstantTable.cs index ae14f985..878427b1 100644 --- a/KitX Dashboard/Data/GlobalInfo.cs +++ b/KitX Dashboard/ConstantTable.cs @@ -1,16 +1,14 @@ using Common.BasicHelper.Utils.Extensions; using System; -namespace KitX.Dashboard.Data; +namespace KitX.Dashboard; -internal static class GlobalInfo +internal static class ConstantTable { internal const string AppName = "KitX"; internal const string AppFullName = "KitX Dashboard"; - internal const string ConfigPath = "./Config/"; - internal const string DataPath = "./Data/"; internal const string LanguageFilePath = "./Languages/"; @@ -23,25 +21,15 @@ internal static class GlobalInfo internal const string IconBase64FileName = "KitX.Base64.txt"; - internal const int LastBreakAfterExit = 2000; - - private const string configFilePath = $"{ConfigPath}config.json"; - - private const string pluginsListConfigFilePath = $"{ConfigPath}plugins.json"; - private const string activitiesDataBaseFilePath = $"{DataPath}Activities.db"; private const string thirdPartLicenseFilePath = $"{AssetsPath}ThirdPartLicense.md"; - internal static string ConfigFilePath => configFilePath.GetFullPath(); - - internal static string PluginsListConfigFilePath => pluginsListConfigFilePath.GetFullPath(); - internal static string ActivitiesDataBaseFilePath => activitiesDataBaseFilePath.GetFullPath(); internal static string ThirdPartLicenseFilePath => thirdPartLicenseFilePath.GetFullPath(); - internal static int PluginServerPort = -1; + internal static int PluginsServerPort = -1; internal static bool Running = true; @@ -51,7 +39,7 @@ internal static class GlobalInfo internal static bool SkipNetworkSystemOnStartup = false; - internal static int DeviceServerPort = -1; + internal static int DevicesServerPort = -1; internal static DateTime ServerBuildTime = new(); @@ -59,8 +47,6 @@ internal static class GlobalInfo internal const string Api_Get_Announcement = "get-announcement.php"; - internal const string AnnouncementsJsonPath = $"{ConfigPath}announcements.json"; - internal static string MyMacAddress = string.Empty; internal static string KitXIconBase64 = string.Empty; diff --git a/KitX Dashboard/Converters/ActivityStatusConverters.cs b/KitX Dashboard/Converters/ActivityStatusConverters.cs new file mode 100644 index 00000000..7666c71c --- /dev/null +++ b/KitX Dashboard/Converters/ActivityStatusConverters.cs @@ -0,0 +1,106 @@ +using Avalonia.Data.Converters; +using Common.Activity; +using System; +using System.Globalization; +using Material.Icons; +using Avalonia.Media; + +namespace KitX.Dashboard.Converters; + +public class ActivityStatusIconConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is null) return null; + + if (value is ActivityStatus status) + switch (status) + { + case ActivityStatus.Unknown: + return MaterialIconKind.TimerSandEmpty; + case ActivityStatus.Opened: + return MaterialIconKind.TimerSand; + case ActivityStatus.Pending: + return MaterialIconKind.TimerSandPaused; + case ActivityStatus.Running: + return MaterialIconKind.TimerSandComplete; + case ActivityStatus.Closed: + return MaterialIconKind.TimerSandFull; + } + + return null; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is null) return null; + + if (value is MaterialIconKind kind) + switch (kind) + { + case MaterialIconKind.TimerSandEmpty: + return ActivityStatus.Unknown; + case MaterialIconKind.TimerSand: + return ActivityStatus.Opened; + case MaterialIconKind.TimerSandPaused: + return ActivityStatus.Pending; + case MaterialIconKind.TimerSandComplete: + return ActivityStatus.Running; + case MaterialIconKind.TimerSandFull: + return ActivityStatus.Closed; + } + + return null; + } +} + +public class ActivityTasksStatusIconConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is null) return null; + + if (value is ActivityTaskStatus status) + switch (status) + { + case ActivityTaskStatus.Unknown: + return MaterialIconKind.SourceFork; + case ActivityTaskStatus.Pending: + return MaterialIconKind.SourceBranchPlus; + case ActivityTaskStatus.Running: + return MaterialIconKind.SourceBranchSync; + case ActivityTaskStatus.Success: + return MaterialIconKind.SourceBranchCheck; + case ActivityTaskStatus.Warning: + return MaterialIconKind.SourceBranchMinus; + case ActivityTaskStatus.Errored: + return MaterialIconKind.SourceBranchRemove; + } + + return null; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is null) return null; + + if (value is MaterialIconKind kind) + switch (kind) + { + case MaterialIconKind.SourceFork: + return ActivityTaskStatus.Unknown; + case MaterialIconKind.SourceBranchPlus: + return ActivityTaskStatus.Pending; + case MaterialIconKind.SourceBranchSync: + return ActivityTaskStatus.Running; + case MaterialIconKind.SourceBranchCheck: + return ActivityTaskStatus.Success; + case MaterialIconKind.SourceBranchMinus: + return ActivityTaskStatus.Warning; + case MaterialIconKind.SourceBranchRemove: + return ActivityTaskStatus.Errored; + } + + return null; + } +} diff --git a/KitX Dashboard/Converters/AvaloniaEditDocumentConverters.cs b/KitX Dashboard/Converters/AvaloniaEditDocumentConverters.cs new file mode 100644 index 00000000..12931a0b --- /dev/null +++ b/KitX Dashboard/Converters/AvaloniaEditDocumentConverters.cs @@ -0,0 +1,27 @@ +using Avalonia.Data.Converters; +using AvaloniaEdit.Document; +using Common.Activity; +using System; +using System.Collections.ObjectModel; +using System.Globalization; + +namespace KitX.Dashboard.Converters; + +public class AvaloniaEditDocumentStringConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is null) return null; + + var document = new TextDocument(value as string); + + if (parameter is ObservableCollection lines) + { + + } + + return document; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotImplementedException(); +} diff --git a/KitX Dashboard/Converters/Base64Converters.cs b/KitX Dashboard/Converters/Base64Converters.cs new file mode 100644 index 00000000..d8a08215 --- /dev/null +++ b/KitX Dashboard/Converters/Base64Converters.cs @@ -0,0 +1,40 @@ +using Avalonia.Data.Converters; +using Avalonia.Media.Imaging; +using Serilog; +using System; +using System.Globalization; +using System.IO; + +namespace KitX.Dashboard.Converters; + +public class Base64ToIconConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var location = $"{nameof(Base64ToIconConverter)}.{nameof(Convert)}"; + + try + { + var base64 = value as string; + + ArgumentNullException.ThrowIfNull(base64, nameof(value)); + + var src = System.Convert.FromBase64String(base64); + + using var ms = new MemoryStream(src); + + return new Bitmap(ms); + } + catch (Exception e) + { + Log.Warning( + e, + $"In {location}: Failed to transform icon from base64 to byte[] or create bitmap from `MemoryStream`. {e.Message}" + ); + + return App.DefaultIcon; + } + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotImplementedException(); +} diff --git a/KitX Dashboard/Converters/OperatingSystem2Enum.cs b/KitX Dashboard/Converters/OperatingSystem2Enum.cs index dca0feef..504d860f 100644 --- a/KitX Dashboard/Converters/OperatingSystem2Enum.cs +++ b/KitX Dashboard/Converters/OperatingSystem2Enum.cs @@ -1,4 +1,4 @@ -using KitX.Web.Rules; +using KitX.Shared.Device; using System; namespace KitX.Dashboard.Converters; diff --git a/KitX Dashboard/Converters/PluginInfoConverters.cs b/KitX Dashboard/Converters/PluginInfoConverters.cs new file mode 100644 index 00000000..2f7d26d4 --- /dev/null +++ b/KitX Dashboard/Converters/PluginInfoConverters.cs @@ -0,0 +1,65 @@ +using Avalonia.Data.Converters; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace KitX.Dashboard.Converters; + +public class PluginMultiLanguagePropertyConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is null) return null; + + if (value is Dictionary dict) + { + var result = dict.TryGetValue( + Instances.ConfigManager.AppConfig.App.AppLanguage, + out var lang + ) ? lang : dict.Values.First(); + + return result; + } + + return string.Empty; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value; + } +} + +public class PluginInfoTagsFetchConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is null || parameter as string is null) return null; + + if (value is Dictionary dict) + if (dict.TryGetValue((parameter as string)!, out var tag)) + return tag; + + return string.Empty; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value; + } +} + +public class PluginInfoSelectedConverter : IMultiValueConverter +{ + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values.Count != 3) return false; + + if (values[0] is string id1) + if (values[1] is string id2) + return id1.Equals(id2) && (bool)values[2]!; + + return false; + } +} diff --git a/KitX Dashboard/Converters/WindowAttributesConverter.cs b/KitX Dashboard/Converters/WindowAttributesConverter.cs index 441c4ae5..1223e238 100644 --- a/KitX Dashboard/Converters/WindowAttributesConverter.cs +++ b/KitX Dashboard/Converters/WindowAttributesConverter.cs @@ -1,23 +1,27 @@ using Avalonia.Controls; +using Avalonia.Platform; using Common.BasicHelper.Graphics.Screen; namespace KitX.Dashboard.Converters; internal class WindowAttributesConverter { - /// - /// 坐标回正 - /// - /// 传入的坐标 - /// 是否是距左距离 - /// 回正的坐标 - internal static int PositionCameCenter(int input, bool isLeft, Screens screens, Resolution win) + internal static Distances PositionCameCenter(Distances location, Screen? screen, Resolution win) { - if (win.Width is null || win.Height is null) return 0; + if (location.Left == -1) + location.Left = ((screen?.WorkingArea.Width ?? 2560) - (int)win.Width!) / 2; - return isLeft - ? (input == -1 ? (screens.Primary?.WorkingArea.Width ?? 2560 - (int)win.Width) / 2 : input) - : (input == -1 ? (screens.Primary?.WorkingArea.Height ?? 1440 - (int)win.Height) / 2 : input) - ; + if (location.Top == -1) + location.Top = (screen?.WorkingArea.Height ?? 1440 - (int)win.Height!) / 2; + + return location; + } +} + +internal static class WindowAttributesConverterExtensions +{ + internal static Distances BringToCenter(this Distances location, Screen? screen, Resolution win) + { + return WindowAttributesConverter.PositionCameCenter(location, screen, win); } } diff --git a/KitX Dashboard/Data/ErrorCodes.cs b/KitX Dashboard/Data/ErrorCodes.cs deleted file mode 100644 index e6707b9a..00000000 --- a/KitX Dashboard/Data/ErrorCodes.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace KitX.Dashboard.Data; - -internal static class ErrorCodes -{ - internal const int StartUpArgumentsError = 1001; - - internal const int ConfigFileDidntExists = 1002; - - internal const int AlraedyStartedOneProcess = 1003; -} diff --git a/KitX Dashboard/ExitCodes.cs b/KitX Dashboard/ExitCodes.cs new file mode 100644 index 00000000..344a7a4a --- /dev/null +++ b/KitX Dashboard/ExitCodes.cs @@ -0,0 +1,6 @@ +namespace KitX.Dashboard; + +internal static class ExitCodes +{ + internal const int MultiProcessesStarted = 1000; +} diff --git a/KitX Dashboard/ExperimentalFlags.cs b/KitX Dashboard/ExperimentalFlags.cs new file mode 100644 index 00000000..f18fd6a8 --- /dev/null +++ b/KitX Dashboard/ExperimentalFlags.cs @@ -0,0 +1,6 @@ +namespace KitX.Dashboard; + +public static class ExperimentalFlags +{ + public static bool EnablePluginLaunchWindowWidthSnap { get; set; } = false; +} diff --git a/KitX Dashboard/Generators/GreetingTextGenerator.cs b/KitX Dashboard/Generators/GreetingTextGenerator.cs index f8558e63..82399fa0 100644 --- a/KitX Dashboard/Generators/GreetingTextGenerator.cs +++ b/KitX Dashboard/Generators/GreetingTextGenerator.cs @@ -1,5 +1,4 @@ -using KitX.Dashboard.Managers; -using System; +using System; namespace KitX.Dashboard.Generators; @@ -16,19 +15,30 @@ internal static string GetKey() var time = DateTime.Now.Hour; if (time >= 6 && time < 12) - key = key.Replace("%Step%", "Morning") - .Replace("%Index%", GenerateRandomIndex(Step.Morning).ToString()); + key = key.Replace("%Step%", "Morning").Replace( + "%Index%", + GenerateRandomIndex(Step.Morning).ToString() + ); else if (time >= 12 && time < 14) - key = key.Replace("%Step%", "Noon") - .Replace("%Index%", GenerateRandomIndex(Step.Noon).ToString()); + key = key.Replace("%Step%", "Noon").Replace( + "%Index%", + GenerateRandomIndex(Step.Noon).ToString() + ); else if (time >= 14 && time < 18) - key = key.Replace("%Step%", "AfterNoon") - .Replace("%Index%", GenerateRandomIndex(Step.AfterNoon).ToString()); + key = key.Replace("%Step%", "AfterNoon").Replace( + "%Index%", + GenerateRandomIndex(Step.AfterNoon).ToString() + ); else if (time >= 18 && time < 24) - key = key.Replace("%Step%", "Evening") - .Replace("%Index%", GenerateRandomIndex(Step.Evening).ToString()); - else key = key.Replace("%Step%", "Night") - .Replace("%Index%", GenerateRandomIndex(Step.Night).ToString()); + key = key.Replace("%Step%", "Evening").Replace( + "%Index%", + GenerateRandomIndex(Step.Evening).ToString() + ); + else + key = key.Replace("%Step%", "Night").Replace( + "%Index%", + GenerateRandomIndex(Step.Night).ToString() + ); return key; } @@ -49,31 +59,31 @@ internal static int GenerateRandomIndex(Step step) case Step.Morning: result = random.Next( 1, - ConfigManager.AppConfig.Windows.MainWindow.GreetingTextCount_Morning + 1 + Instances.ConfigManager.AppConfig.Windows.MainWindow.GreetingTextCount_Morning + 1 ); break; case Step.Noon: result = random.Next( 1, - ConfigManager.AppConfig.Windows.MainWindow.GreetingTextCount_Noon + 1 + Instances.ConfigManager.AppConfig.Windows.MainWindow.GreetingTextCount_Noon + 1 ); break; case Step.AfterNoon: result = random.Next( 1, - ConfigManager.AppConfig.Windows.MainWindow.GreetingTextCount_AfterNoon + 1 + Instances.ConfigManager.AppConfig.Windows.MainWindow.GreetingTextCount_AfterNoon + 1 ); break; case Step.Evening: result = random.Next( 1, - ConfigManager.AppConfig.Windows.MainWindow.GreetingTextCount_Evening + 1 + Instances.ConfigManager.AppConfig.Windows.MainWindow.GreetingTextCount_Evening + 1 ); break; case Step.Night: result = random.Next( 1, - ConfigManager.AppConfig.Windows.MainWindow.GreetingTextCount_Night + 1 + Instances.ConfigManager.AppConfig.Windows.MainWindow.GreetingTextCount_Night + 1 ); break; } diff --git a/KitX Dashboard/Helper.cs b/KitX Dashboard/Helper.cs deleted file mode 100644 index 352d182b..00000000 --- a/KitX Dashboard/Helper.cs +++ /dev/null @@ -1,345 +0,0 @@ -using Avalonia.Threading; -using Common.BasicHelper.IO; -using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; -using KitX.Dashboard.Managers; -using KitX.Dashboard.Names; -using LiteDB; -using ReactiveUI; -using Serilog; -using System; -using System.Diagnostics; -using System.IO; -using System.Reactive; -using System.Threading; -using System.Threading.Tasks; - -namespace KitX.Dashboard; - -public static class Helper -{ - /// - /// 处理启动参数 - /// - /// 参数列表 - public static void ProcessStartupArguments(string[] args) - { - try - { - for (int i = 0; i < args.Length; i++) - { - switch (args[i]) - { - case "--import-plugin": - if (i != args.Length - 1) - try - { - ImportPlugin(args[i + 1]); - } - catch (Exception e) - { - Console.WriteLine(e.Message); - } - else throw new Exception("No arguments for plugin location."); - break; - case "--disable-single-process-check": - GlobalInfo.IsSingleProcessStartMode = false; - break; - case "--disable-config-hot-reload": - GlobalInfo.EnabledConfigFileHotReload = false; - break; - case "--disable-network-system-on-startup": - GlobalInfo.SkipNetworkSystemOnStartup = true; - break; - } - } - } - catch (Exception e) - { - Console.WriteLine(e.Message); - - Environment.Exit(ErrorCodes.StartUpArgumentsError); - } - } - - /// - /// Run framework - /// - public static void RunFramework() - { - if (GlobalInfo.IsSingleProcessStartMode) - SingleProcessCheck(); - - if (GlobalInfo.EnabledConfigFileHotReload) - Instances.FileWatcherManager = new(); - - ConfigManager.Init(); - - LoadResource(); - - #region 初始化日志系统 - - var logdir = ConfigManager.AppConfig.Log.LogFilePath.GetFullPath(); - - if (!Directory.Exists(logdir)) - Directory.CreateDirectory(logdir); - - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Information() - .WriteTo.File( - $"{logdir}Log_.log", - outputTemplate: ConfigManager.AppConfig.Log.LogTemplate, - rollingInterval: RollingInterval.Hour, - fileSizeLimitBytes: ConfigManager.AppConfig.Log.LogFileSingleMaxSize, - buffered: true, - flushToDiskInterval: new( - 0, - 0, - ConfigManager.AppConfig.Log.LogFileFlushInterval - ), - restrictedToMinimumLevel: ConfigManager.AppConfig.Log.LogLevel, - rollOnFileSizeLimit: true, - retainedFileCountLimit: ConfigManager.AppConfig.Log.LogFileMaxCount - ) - .CreateLogger(); - - Log.Information("KitX Dashboard Started."); - - #endregion - - #region 初始化全局异常捕获 - - AppDomain.CurrentDomain.UnhandledException += (sender, e) => - { - if (e.ExceptionObject is Exception ex) - Log.Error(ex, $"Au oh! Fatal: {ex.Message}"); - }; - - TaskScheduler.UnobservedTaskException += (sender, e) => - { - Log.Error(e.Exception, $"Au oh! Fatal: {e.Exception.Message}"); - }; - - RxApp.DefaultExceptionHandler = Observer.Create(ex => - { - Log.Error(ex, $"Au oh! Fatal: {ex.Message}"); - }); - - #endregion - - #region 初始化环境 - - InitEnvironment(); - - #endregion - - #region 初始化缓存管理器 - - Instances.CacheManager = new(); - - #endregion - - #region 初始化数据库 - - InitDataBase(); - - #endregion - - #region 初始化 TasksManager - - Instances.SignalTasksManager = new(); - - #endregion - - #region 初始化 WebManager - - Instances.SignalTasksManager.SignalRun(nameof(SignalsNames.MainWindowInitSignal), () => - { - new Thread(async () => - { - Thread.Sleep( - Convert.ToInt32(ConfigManager.AppConfig.Web.DelayStartSeconds * 1000) - ); - - if (GlobalInfo.SkipNetworkSystemOnStartup) - Instances.WebManager = new(); - else - Instances.WebManager = await new WebManager().Start(); - }).Start(); - }); - - #endregion - - #region 初始化数据记录管理器 - - StatisticsManager.Start(); - - #endregion - - #region 初始化热键系统 - - Instances.HotKeyManager = new HotKeyManager().Hook(); - - #endregion - - #region 初始化持久的窗口 - - Instances.SignalTasksManager.SignalRun(nameof(SignalsNames.MainWindowInitSignal), () => - { - Dispatcher.UIThread.Post(() => - { - Instances.PluginsLaunchWindow = new(); - }); - }); - - #endregion - - } - - /// - /// 初始化环境 - /// - private static void InitEnvironment() - { - var location = $"{nameof(Helper)}.{nameof(InitEnvironment)}"; - - if (!Common.Algorithm.Interop.Environment.Check()) - new Thread(async () => - { - try - { - await Common.Algorithm.Interop.Environment.InstallAsync(); - } - catch (Exception ex) - { - Log.Error(ex, $"In {location}: {ex.Message}"); - } - }).Start(); - } - - /// - /// 初始化数据库 - /// - private static void InitDataBase() - { - var location = $"{nameof(Helper)}.{nameof(InitDataBase)}"; - - try - { - var dir = GlobalInfo.DataPath.GetFullPath(); - - if (!Directory.Exists(dir)) _ = Directory.CreateDirectory(dir); - - var dbfile = GlobalInfo.ActivitiesDataBaseFilePath.GetFullPath(); - - var db = new LiteDatabase(dbfile); - - Instances.ActivitiesDataBase = db; - - ActivityManager.RecordAppStart(); - } - catch (Exception ex) - { - Log.Error(ex, $"In {location}: {ex.Message}"); - } - } - - /// - /// 检查当前是否是单进程状态 - /// - private static void SingleProcessCheck() - { - var nowProcesses = Process.GetProcesses(); - var count = 0; - - foreach (var item in nowProcesses) - { - if (item.ProcessName.Replace(".exe", "").Equals("KitX Dashboard")) - ++count; - - if (count >= 2) Environment.Exit(ErrorCodes.AlraedyStartedOneProcess); - } - } - - /// - /// 读取资源 - /// - private static async void LoadResource() - { - var location = $"{nameof(Helper)}.{nameof(LoadResource)}"; - - try - { - GlobalInfo.KitXIconBase64 = await FileHelper.ReadAllAsync( - $"{GlobalInfo.AssetsPath}{GlobalInfo.IconBase64FileName}".GetFullPath() - ); - } - catch (Exception ex) - { - Log.Error(ex, $"In {location}: {ex.Message}"); - } - } - - /// - /// 导入插件 - /// - /// .kxp Path - private static void ImportPlugin(string kxpPath) - { - try - { - if (!File.Exists(kxpPath)) - { - Console.WriteLine($"No this file: {kxpPath}"); - - throw new Exception("Plugins Package Doesn't Exist."); - } - else - { - PluginsManager.ImportPlugin(new string[] { kxpPath }); - } - } - catch (Exception ex) - { - Log.Error(ex, "In Helper.ImportPlugin()"); - } - } - - /// - /// 退出 - /// - public static void Exit() - { - var location = $"{nameof(Helper)}.{nameof(Exit)}"; - - new Thread(() => - { - try - { - ActivityManager.RecordAppExit(); - - Instances.FileWatcherManager?.Clear(); - - ConfigManager.SaveConfigs(); - - Log.CloseAndFlush(); - - Instances.WebManager?.Stop(); - Instances.WebManager?.Dispose(); - - Instances.ActivitiesDataBase?.Commit(); - Instances.ActivitiesDataBase?.Dispose(); - - GlobalInfo.Running = false; - - Thread.Sleep(GlobalInfo.LastBreakAfterExit); - - Environment.Exit(0); - } - catch (Exception ex) - { - Log.Warning(ex, $"In {location}: {ex.Message}"); - } - }).Start(); - } -} diff --git a/KitX Dashboard/Instances.cs b/KitX Dashboard/Instances.cs index 9b5221c2..2c21e583 100644 --- a/KitX Dashboard/Instances.cs +++ b/KitX Dashboard/Instances.cs @@ -1,29 +1,56 @@ using Common.BasicHelper.Core.TaskSystem; +using Common.BasicHelper.Utils.Extensions; using KitX.Dashboard.Managers; -using KitX.Dashboard.Views; -using KitX.Dashboard.Views.Pages.Controls; using LiteDB; -using System.Collections.ObjectModel; +using System.Linq; -internal class Instances +namespace KitX.Dashboard; + +public static class Instances { - internal static SignalTasksManager? SignalTasksManager; + public static SignalTasksManager? SignalTasksManager { get; set; } + + public static WebManager? WebManager { get; set; } + + public static FileWatcherManager? FileWatcherManager { get; set; } - internal static WebManager? WebManager; + public static LiteDatabase? ActivitiesDataBase { get; set; } - internal static FileWatcherManager? FileWatcherManager; + public static CacheManager? CacheManager { get; set; } - internal static ObservableCollection PluginCards = new(); + public static KeyHookManager? KeyHookManager { get; set; } - internal static ObservableCollection DeviceCards = new(); + public static ConfigManager ConfigManager { get; set; } = new ConfigManager().SetLocation("./Config/").Load(); - internal static LiteDatabase? ActivitiesDataBase; + internal static void Initialize() + { + var location = $"{nameof(Instances)}.{nameof(Initialize)}"; - internal static CacheManager? CacheManager; + TasksManager.RunTask(() => + { + TasksManager.RunTask( + () => SignalTasksManager = new(), + location.Append("." + nameof(SignalTasksManager)), + catchException: true + ); - internal static HotKeyManager? HotKeyManager; + TasksManager.RunTask( + () => CacheManager = new(), + location.Append("." + nameof(CacheManager)), + catchException: true + ); - internal static MainWindow? MainWindow; + TasksManager.RunTask( + () => KeyHookManager = new KeyHookManager().Hook(), + location.Append("." + nameof(KeyHookManager)), + catchException: true + ); - internal static PluginsLaunchWindow? PluginsLaunchWindow; + TasksManager.RunTask(() => + { + if (ConstantTable.EnabledConfigFileHotReload) + FileWatcherManager = new(); + }, location.Append("." + nameof(FileWatcherManager)), catchException: true); + }, location, catchException: true); + } } diff --git a/KitX Dashboard/Interfaces/IModuleController.cs b/KitX Dashboard/Interfaces/IModuleController.cs deleted file mode 100644 index a08aeeff..00000000 --- a/KitX Dashboard/Interfaces/IModuleController.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace KitX.Dashboard.Interfaces; - -internal interface IModuleController : IDisposable -{ - Task Start(); - - Task Stop(); - - Task Restart(); -} diff --git a/KitX Dashboard/Interfaces/Network/IKitXClient.cs b/KitX Dashboard/Interfaces/Network/IKitXClient.cs deleted file mode 100644 index 10209185..00000000 --- a/KitX Dashboard/Interfaces/Network/IKitXClient.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace KitX.Dashboard.Interfaces.Network; - -internal interface IKitXClient : IModuleController -{ - Task Connect(); - - Task Disconnect(); - - Task Send(byte[] content); - - T OnReceive(Action action); -} diff --git a/KitX Dashboard/Interfaces/Network/IKitXServer.cs b/KitX Dashboard/Interfaces/Network/IKitXServer.cs deleted file mode 100644 index 0e208578..00000000 --- a/KitX Dashboard/Interfaces/Network/IKitXServer.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Net.Sockets; -using System.Threading.Tasks; - -namespace KitX.Dashboard.Interfaces.Network; - -internal interface IKitXServer : IModuleController -{ - Task Broadcast(byte[] content); - - Task BroadCast(byte[] content, Func? pattern); - - Task Send(byte[] content, string target); - - T OnReceive(Action action); -} diff --git a/KitX Dashboard/KitX.Dashboard.csproj b/KitX Dashboard/KitX.Dashboard.csproj index 6ddeac51..09ac20ab 100644 --- a/KitX Dashboard/KitX.Dashboard.csproj +++ b/KitX Dashboard/KitX.Dashboard.csproj @@ -1,7 +1,7 @@  WinExe - net7.0 + net8.0 enable true Assets\KitX-Icon-256x.ico @@ -13,7 +13,7 @@ $(Version) $(Version) - 3.23.04.$([System.DateTime]::UtcNow.Date.Subtract($([System.DateTime]::Parse("2005-06-06"))).TotalDays) + 3.24.10.$([System.DateTime]::UtcNow.Date.Subtract($([System.DateTime]::Parse("2020-10-01"))).TotalDays) @@ -63,44 +63,51 @@ PreserveNewest + - - - - - + + + + + + - - + + + + + - - + + + + - + - - + + - - - + + + + - - + + - - + - \ No newline at end of file + diff --git a/KitX Dashboard/Languages/en-us.axaml b/KitX Dashboard/Languages/en-us.axaml index 7cd7ab83..4b43082d 100644 --- a/KitX Dashboard/Languages/en-us.axaml +++ b/KitX Dashboard/Languages/en-us.axaml @@ -11,6 +11,8 @@ Export Exit Show Main Window + View Announcements + Show Launcher Window Millisecond s minute @@ -30,6 +32,8 @@ Reset to Auto Random Fixed + Name + Author Verbose Debug Infomation diff --git a/KitX Dashboard/Languages/fr-fr.axaml b/KitX Dashboard/Languages/fr-fr.axaml index 643f3fdd..bfdc0c68 100644 --- a/KitX Dashboard/Languages/fr-fr.axaml +++ b/KitX Dashboard/Languages/fr-fr.axaml @@ -11,6 +11,8 @@ exporter quitter Afficher la fenêtre principale + Voir l'annonce + Text_Public_ShowLauncherWindow milliseconde deuxième minute @@ -30,6 +32,8 @@ réinitialiser automatiquement Aléatoire écurie + Nom + Auteur détaillé débogage informations diff --git a/KitX Dashboard/Languages/ja-jp.axaml b/KitX Dashboard/Languages/ja-jp.axaml index ed9e4c49..63f2a3fb 100644 --- a/KitX Dashboard/Languages/ja-jp.axaml +++ b/KitX Dashboard/Languages/ja-jp.axaml @@ -11,6 +11,8 @@ 書き出す 終了する メインウィンドウを表示 + お知らせを見る + Text_Public_ShowLauncherWindow ミリ秒 2番目 @@ -30,6 +32,8 @@ 自動にリセット ランダム 安定 + 名前 + 著者 详细 调试 情報 diff --git a/KitX Dashboard/Languages/ko-kr.axaml b/KitX Dashboard/Languages/ko-kr.axaml index e3d706e5..170e3b29 100644 --- a/KitX Dashboard/Languages/ko-kr.axaml +++ b/KitX Dashboard/Languages/ko-kr.axaml @@ -11,6 +11,8 @@ 내보내다 그만두다 메인 창 표시 + 공지사항 보기 + Text_Public_ShowLauncherWindow 밀리초 @@ -30,6 +32,8 @@ 자동으로 재설정 무작위의 결정된 + 이름 + 작가 상세한 디버깅 정보 diff --git a/KitX Dashboard/Languages/ru-ru.axaml b/KitX Dashboard/Languages/ru-ru.axaml index 60feb2a9..34e9be8b 100644 --- a/KitX Dashboard/Languages/ru-ru.axaml +++ b/KitX Dashboard/Languages/ru-ru.axaml @@ -11,6 +11,8 @@ экспорт покидать Показать главное окно + Посмотреть объявление + Text_Public_ShowLauncherWindow миллисекунда второй минута @@ -30,6 +32,8 @@ сбросить на авто случайный стабильный + Имя + Автор подробный отладка Информация diff --git a/KitX Dashboard/Languages/zh-cn.axaml b/KitX Dashboard/Languages/zh-cn.axaml index 2cbd3b1a..a16fde5a 100644 --- a/KitX Dashboard/Languages/zh-cn.axaml +++ b/KitX Dashboard/Languages/zh-cn.axaml @@ -11,6 +11,8 @@ 导出 退出 显示主窗口 + 查看公告 + 显示启动器 毫秒 分钟 @@ -30,6 +32,8 @@ 重置到自动 随机 固定 + 名称 + 作者 详细 调试 信息 diff --git a/KitX Dashboard/Languages/zh-tw.axaml b/KitX Dashboard/Languages/zh-tw.axaml index bc2e7bfe..22ad0068 100644 --- a/KitX Dashboard/Languages/zh-tw.axaml +++ b/KitX Dashboard/Languages/zh-tw.axaml @@ -11,6 +11,8 @@ 導出 退出 顯示主窗口 + 查看公告 + Text_Public_ShowLauncherWindow 毫秒 分鐘 @@ -30,6 +32,8 @@ 重置到自動 隨機 固定 + 名稱 + 作者 詳細 調試 信息 diff --git a/KitX Dashboard/Managers/ActivityManager.cs b/KitX Dashboard/Managers/ActivityManager.cs index 26ea4b01..b1c990d6 100644 --- a/KitX Dashboard/Managers/ActivityManager.cs +++ b/KitX Dashboard/Managers/ActivityManager.cs @@ -1,9 +1,10 @@ using Common.Activity; using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; using KitX.Dashboard.Names; using LiteDB; using System; +using System.Collections.Generic; +using System.Linq; using System.Linq.Expressions; namespace KitX.Dashboard.Managers; @@ -16,11 +17,17 @@ internal class ActivityManager private static Activity? _appActivity; - /// - /// 记录活动 - /// - /// 活动 - /// 键选择器 + public static List ReadActivities() + { + if (Instances.ActivitiesDataBase is LiteDatabase db) + { + var col = db.GetCollection(CollectionName); + + return col.FindAll().ToList(); + } + else return []; + } + public static void Record(Activity activity, Expression> keySelector) { var location = $"{nameof(ActivityManager)}.{nameof(Record)}"; @@ -37,7 +44,7 @@ public static void Record(Activity activity, Expression> key col?.EnsureIndex(keySelector); - ConfigManager.AppConfig.Activity.TotalRecorded += col is null ? 0 : 1; + Instances.ConfigManager.AppConfig.Activity.TotalRecorded += col is null ? 0 : 1; db.Commit(); } @@ -45,10 +52,6 @@ public static void Record(Activity activity, Expression> key }, location, catchException: true); } - /// - /// 更新活动 - /// - /// 活动 public static void Update(Activity activity) { var location = $"{nameof(ActivityManager)}.{nameof(Update)}"; @@ -73,15 +76,13 @@ public static void RecordAppStart() { var activity = new Activity() { - Id = ConfigManager.AppConfig.Activity.TotalRecorded + 1, + Id = Instances.ConfigManager.AppConfig.Activity.TotalRecorded, Name = nameof(ActivityNames.AppLifetime), - Author = GlobalInfo.AppFullName, + Author = ConstantTable.AppFullName, Title = ActivityTitles.AppStart, Category = nameof(ActivitySortNames.DashboardEvent), - IconKind = Material.Icons.MaterialIconKind.RocketLaunch - } - .Open(GlobalInfo.AppFullName) - ; + IconKind = Material.Icons.MaterialIconKind.RocketLaunch, + }.Open(ConstantTable.AppFullName); _appActivity = activity; @@ -92,7 +93,7 @@ public static void RecordAppExit() { if (_appActivity is Activity activity) { - activity.Close(GlobalInfo.AppFullName); + activity.Close(ConstantTable.AppFullName); Update(activity); } diff --git a/KitX Dashboard/Managers/AnouncementManager.cs b/KitX Dashboard/Managers/AnouncementManager.cs index b31b9771..6709948b 100644 --- a/KitX Dashboard/Managers/AnouncementManager.cs +++ b/KitX Dashboard/Managers/AnouncementManager.cs @@ -1,12 +1,9 @@ using Avalonia.Threading; -using Common.BasicHelper.IO; -using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; using KitX.Dashboard.Views; using System; using System.Collections.Generic; -using System.IO; using System.Net.Http; +using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -14,83 +11,70 @@ namespace KitX.Dashboard.Managers; internal class AnouncementManager { - /// - /// 异步检查新公告 - /// - /// 异步任务 public static async Task CheckNewAnnouncements() { - var client = new HttpClient(); + using var client = new HttpClient(); client.DefaultRequestHeaders.Accept.Clear(); - // 链接头部 - var linkBase = $"http://" + - $"{ConfigManager.AppConfig.Web.APIServer}" + - $"{ConfigManager.AppConfig.Web.APIPath}"; + var appConfig = Instances.ConfigManager.AppConfig; - // 获取公告列表的api链接 - var link = $"{linkBase}{GlobalInfo.Api_Get_Announcements}"; + var linkBase = new StringBuilder() + .Append("https://") + .Append(appConfig.Web.ApiServer) + .Append(appConfig.Web.ApiPath) + .ToString() + ; + + var link = new StringBuilder() + .Append(linkBase) + .Append(ConstantTable.Api_Get_Announcements) + .ToString() + ; - // 公告列表 var msg = await client.GetStringAsync(link); var list = JsonSerializer.Deserialize>(msg); - // 本地已阅列表 - List? readed; + var accepted = Instances.ConfigManager.AnnouncementConfig.Accepted; - var confPath = GlobalInfo.AnnouncementsJsonPath.GetFullPath(); + var unreads = new List(); - if (File.Exists(confPath)) - readed = JsonSerializer.Deserialize>( - await FileHelper.ReadAllAsync(confPath) - ); - else readed = new(); + if (list is null || accepted is null) return; - // 未阅读列表 - var unreads = new List(); + foreach (var item in list) + if (!accepted.Contains(item)) + unreads.Add(DateTime.Parse(item)); + + var src = new Dictionary(); - // 添加没有阅读的公告到未阅读列表 - if (list is not null && readed is not null) + foreach (var item in unreads) { - foreach (var item in list) - if (!readed.Contains(item)) - unreads.Add(DateTime.Parse(item)); + var apiLink = new StringBuilder() + .Append($"{linkBase}{ConstantTable.Api_Get_Announcement}") + .Append('?') + .Append($"lang={Instances.ConfigManager.AppConfig.App.AppLanguage}") + .Append('&') + .Append($"date={item:yyyy-MM-dd HH-mm}") + .ToString() + ; + + var md = JsonSerializer.Deserialize( + await client.GetStringAsync(apiLink) + ); - // 公告列表<发布时间, 公告内容> - var src = new Dictionary(); + if (md is not null) + src.Add(item.ToString("yyyy-MM-dd HH:mm"), md); + } - foreach (var item in unreads) - { - // 获取单个公告的链接 - var apiLink = $"{linkBase}{GlobalInfo.Api_Get_Announcement}" + - $"?" + - $"lang={ConfigManager.AppConfig.App.AppLanguage}" + - $"&" + - $"date={item:yyyy-MM-dd HH-mm}"; - - var md = JsonSerializer.Deserialize( - await client.GetStringAsync(apiLink) - ); - - if (md is not null) - src.Add(item.ToString("yyyy-MM-dd HH:mm"), md); - } - - if (unreads.Count > 0) + if (unreads.Count > 0) + { + Dispatcher.UIThread.Post(() => { - Dispatcher.UIThread.Post(() => - { - var toast = new AnouncementsWindow(); - - toast.UpdateSource(src, readed); - toast.Show(); - }); - } - } + var toast = new AnouncementsWindow().UpdateSource(src); - // 结束Http客户端 - client.Dispose(); + ViewInstances.ShowWindow(toast); + }); + } } } diff --git a/KitX Dashboard/Managers/CacheManager.cs b/KitX Dashboard/Managers/CacheManager.cs index 4b1d655f..08f0a363 100644 --- a/KitX Dashboard/Managers/CacheManager.cs +++ b/KitX Dashboard/Managers/CacheManager.cs @@ -8,20 +8,14 @@ namespace KitX.Dashboard.Managers; -internal class CacheManager +public class CacheManager { internal CacheManager() { - FilesCache = new(); - ReceivingFilesCache = new(); + FilesCache = []; + ReceivingFilesCache = []; } - /// - /// 异步获取 MD5 值 - /// - /// 要计算的数据 - /// 是否转换格式 - /// MD5 值 private static async Task GetMD5(byte[] bytes, bool trans = false) { byte[]? result = null; @@ -42,12 +36,6 @@ await Task.Run(() => return sb.ToString(); } - /// - /// 异步加载本地文件到缓存 - /// - /// 文件路径 - /// 取消口令 - /// 缓存文件编号 (MD5), 如果 Hash 值已存在, 则替换原内容 public async Task LoadFileToCache(string fileLocation, CancellationToken token = default) { var fullPath = Path.GetFullPath(fileLocation); @@ -68,12 +56,6 @@ await Task.Run(() => return id; } - /// - /// 异步接收文件到缓存 - /// - /// 文件字节数组 - /// 取消口令 - /// 缓存文件编号 (MD5), 如果 Hash 值已存在, 则替换原内容 public async Task ReceiveFileToCache(byte[] bin, CancellationToken token = default) { var id = await GetMD5(bin, true); @@ -82,19 +64,13 @@ await Task.Run(() => if (ReceivingFilesCache is null || id is null) return null; - if (!ReceivingFilesCache.ContainsKey(id)) - ReceivingFilesCache.Add(id, bin); + _ = ReceivingFilesCache.TryAdd(id, bin); GC.Collect(); return id; } - /// - /// 从接收到的文件缓存中获取文件 - /// - /// 缓存 ID - /// 文件字节数组 public byte[]? GetReceivedFileFromCache(string id) { if (ReceivingFilesCache is null) return null; @@ -104,10 +80,6 @@ await Task.Run(() => else return null; } - /// - /// 清除文件缓存 - /// - /// 指定 ID 清除 public bool? DisposeFileCache(string id) { if (FilesCache is null) return null; diff --git a/KitX Dashboard/Managers/ConfigManager.cs b/KitX Dashboard/Managers/ConfigManager.cs index 85281ab5..c48ccc5d 100644 --- a/KitX Dashboard/Managers/ConfigManager.cs +++ b/KitX Dashboard/Managers/ConfigManager.cs @@ -1,218 +1,178 @@ -using Common.BasicHelper.IO; -using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; -using KitX.Dashboard.Models; +using Common.BasicHelper.Utils.Extensions; +using KitX.Dashboard.Configuration; using KitX.Dashboard.Names; using KitX.Dashboard.Services; using Serilog; using System; using System.Collections.Generic; using System.IO; -using System.Text.Json; namespace KitX.Dashboard.Managers; -internal class ConfigManager +public class ConfigManager { - private static readonly object _appConfigWriteLock = new(); + internal class ConfigManagerInfo + { + private string? _location; - private static readonly object _pluginsListConfigWriteLock = new(); + internal string? Location + { + get => _location; + set + { + ArgumentNullException.ThrowIfNull(value, nameof(Location)); - internal static AppConfig AppConfig = new(); + _location = Path.GetFullPath(value); - internal static void Init() + if (!Directory.Exists(_location)) + Directory.CreateDirectory(_location); + } + } + } + + private readonly Dictionary _configs; + + internal ConfigManagerInfo? Infos; + + public ConfigManager() { - InitConfigs(); + Infos = new(); - InitEvents(); + _configs = []; - InitFileWatcher(); + InitEvents(); } - internal static void InitConfigs() + private void InitEvents() { - var location = $"{nameof(ConfigManager)}.{nameof(InitConfigs)}"; + var location = $"{nameof(ConfigManager)}.{nameof(InitEvents)}"; TasksManager.RunTask(() => { - try + + EventService.AppConfigChanged += () => { - var configDir = GlobalInfo.ConfigPath.GetFullPath(); - var configFilePath = GlobalInfo.ConfigFilePath.GetFullPath(); - var pluginsListConfigFilePath = GlobalInfo.PluginsListConfigFilePath.GetFullPath(); - - if (!Directory.Exists(configDir)) - _ = Directory.CreateDirectory(configDir); - - if (!File.Exists(configFilePath)) - SaveAppConfig(); - else - LoadAppConfig(); - - if (!File.Exists(pluginsListConfigFilePath)) - SavePluginsListConfig(); - else - LoadPluginsListConfig(); - } - catch (Exception ex) + Instances.FileWatcherManager!.IncreaseExceptCount(AppConfig.ConfigFileWatcherName!); + + AppConfig.Save(AppConfig.ConfigFileLocation!); + }; + + EventService.PluginsConfigChanged += () => { - Log.Error(ex, $"In {location}: {ex.Message}"); - } + Instances.FileWatcherManager!.IncreaseExceptCount(PluginsConfig.ConfigFileWatcherName!); + + PluginsConfig.Save(PluginsConfig.ConfigFileLocation!); + }; + }, location); } - internal static void InitEvents() + public ConfigManager SetLocation(string location) { - var location = $"{nameof(ConfigManager)}.{nameof(InitEvents)}"; - - TasksManager.RunTask(() => - { - EventService.ConfigSettingsChanged += () => SaveAppConfig(); - - EventService.PluginsListChanged += () => SavePluginsListConfig(); + if (Infos is not null) + Infos.Location = location; - }, location); + return this; } - internal static void InitFileWatcher() + private void RegisterFileWatcher(T config) where T : ConfigBase, new() { - var location = $"{nameof(ConfigManager)}.{nameof(InitFileWatcher)}"; + var name = "ConfigFileWatcher".Append(typeof(T).Name); + + var path = config.ConfigFileLocation!; - var appConfigWatcher = nameof(FileWatcherNames.AppConfigFileWatcher); + config.ConfigFileWatcherName = name; - Instances.FileWatcherManager?.RegisterWatcher( - appConfigWatcher, - GlobalInfo.ConfigFilePath.GetFullPath(), - (x, y) => + config.Save(config.ConfigFileLocation!); + + Instances.FileWatcherManager!.RegisterWatcher( + name, + path, + (_, y) => { - Log.Information($"{appConfigWatcher}: {y.Name}, {y.ChangeType}"); + var location = $"{nameof(ConfigManager)}.{nameof(RegisterFileWatcher)}"; + + Log.Information($"FileChanged: {name} | {y.Name}, {y.ChangeType}"); try { - lock (_appConfigWriteLock) - { - AppConfig = JsonSerializer.Deserialize( - File.ReadAllText(GlobalInfo.ConfigFilePath) - ) ?? new(); - - EventService.Invoke(nameof(EventService.OnConfigHotReloaded)); - } + _configs[typeof(T).Name] = path.Load(); } - catch (Exception ex) + catch (Exception e) { - Log.Error(ex, $"In {location}: {ex.Message}"); + Log.Error(e, $"In {location}: {e.Message}"); } } ); } - internal static void SaveConfigs() + public ConfigManager LoadConfigFile() where T : ConfigBase, new() { - var location = $"{nameof(ConfigManager)}.{nameof(SaveConfigs)}"; + var name = typeof(T).Name; - TasksManager.RunTask(() => - { - try - { - SaveAppConfig(); - SavePluginsListConfig(); - } - catch (Exception ex) + ArgumentNullException.ThrowIfNull(name, nameof(name)); + + var path = $"{Infos?.Location}{name}.json".GetFullPath(); + + var config = path.Load().SetConfigFileLocation(path).Save(path); + + _configs.Add(name, config); + + if (ConstantTable.EnabledConfigFileHotReload) + AppFramework.AfterInitailization(() => { - Log.Error(ex, $"In {location}: {ex.Message}"); - } - }, location); + Instances.SignalTasksManager!.SignalRun( + nameof(SignalsNames.FileWatcherManagerInitializedSignal), + () => RegisterFileWatcher(config) + ); + }); + + return this; } - internal static async void LoadAppConfig() + public ConfigManager Load() { - var location = $"{nameof(ConfigManager)}.{nameof(LoadAppConfig)}"; + var location = $"{nameof(ConfigManager)}.{nameof(Load)}"; - await TasksManager.RunTaskAsync(async () => + TasksManager.RunTask(() => { - try - { - AppConfig = JsonSerializer.Deserialize( - await FileHelper.ReadAllAsync(GlobalInfo.ConfigFilePath) - ) ?? new(); - } - catch (Exception ex) - { - Log.Error(ex, $"In {location}: {ex.Message}"); + LoadConfigFile(); + LoadConfigFile(); + LoadConfigFile(); + LoadConfigFile(); + }, location, catchException: false); - AppConfig = new AppConfig(); - } - }, location); + return this; } - internal static void SaveAppConfig() + public ConfigManager SaveAll() { - var location = $"{nameof(ConfigManager)}.{nameof(SaveAppConfig)}"; - - var options = new JsonSerializerOptions() - { - WriteIndented = true, - IncludeFields = true, - }; + var location = $"{nameof(ConfigManager)}.{nameof(SaveAll)}"; TasksManager.RunTask(() => { - try - { - lock (_appConfigWriteLock) - { - File.WriteAllText( - GlobalInfo.ConfigFilePath.GetFullPath(), - JsonSerializer.Serialize(AppConfig, options) - ); - } - } - catch (Exception ex) - { - Log.Warning(ex, $"In {location}: {ex.Message}"); - } - }, location); + foreach (var config in _configs.Values) + config.Save(config.ConfigFileLocation ?? throw new InvalidOperationException( + $"Saving config requires `{nameof(ConfigBase.ConfigFileLocation)}` property not null." + )); + + }, location, catchException: true); + + return this; } - internal static async void LoadPluginsListConfig() + private T GetConfig() where T : ConfigBase { - var location = $"{nameof(ConfigManager)}.{nameof(LoadPluginsListConfig)}"; + var name = typeof(T).Name; - await TasksManager.RunTaskAsync(async () => - { - try - { - PluginsManager.Plugins = JsonSerializer.Deserialize>( - await FileHelper.ReadAllAsync(GlobalInfo.PluginsListConfigFilePath) - ) ?? new(); - } - catch (Exception ex) - { - Log.Error(ex, $"In {location}: {ex.Message}"); - PluginsManager.Plugins = new(); - } - }, location); + return _configs[name] as T ?? throw new Exception($"Can not find config: {name}"); } - internal static void SavePluginsListConfig() - { - var location = $"{nameof(ConfigManager)}.{nameof(SavePluginsListConfig)}"; + public AppConfig AppConfig => GetConfig(); - var options = new JsonSerializerOptions() - { - WriteIndented = true, - IncludeFields = true, - }; + public PluginsConfig PluginsConfig => GetConfig(); - TasksManager.RunTask(() => - { - lock (_pluginsListConfigWriteLock) - { - File.WriteAllText( - GlobalInfo.PluginsListConfigFilePath.GetFullPath(), - JsonSerializer.Serialize(PluginsManager.Plugins, options) - ); - } - }, location); - } + public MarketConfig MarketConfig => GetConfig(); + + public AnnouncementConfig AnnouncementConfig => GetConfig(); } diff --git a/KitX Dashboard/Network/DevicesNetwork.cs b/KitX Dashboard/Managers/DevicesManager.cs similarity index 62% rename from KitX Dashboard/Network/DevicesNetwork.cs rename to KitX Dashboard/Managers/DevicesManager.cs index 1eff5df0..eec24b0c 100644 --- a/KitX Dashboard/Network/DevicesNetwork.cs +++ b/KitX Dashboard/Managers/DevicesManager.cs @@ -1,28 +1,30 @@ using Avalonia.Threading; -using KitX.Dashboard.Data; -using KitX.Dashboard.Managers; +using KitX.Dashboard.Network; +using KitX.Dashboard.Network.DevicesNetwork; using KitX.Dashboard.Services; +using KitX.Dashboard.Views; using KitX.Dashboard.Views.Pages.Controls; -using KitX.Web.Rules; +using KitX.Shared.Device; using Serilog; using System; using System.Collections.Generic; +using System.Text; using System.Threading; using Timer = System.Timers.Timer; -namespace KitX.Dashboard.Network; +namespace KitX.Dashboard.Managers; -internal class DevicesNetwork +internal class DevicesManager { internal static DevicesServer? devicesServer = null; internal static DevicesClient? devicesClient = null; - private static readonly object _receivedDeviceInfoStruct4WatchLock = new(); + private static readonly object _receivedDeviceInfo4WatchLock = new(); - internal static List? receivedDeviceInfoStruct4Watch; + internal static List? receivedDeviceInfo4Watch; - internal static readonly Queue deviceInfoStructs = new(); + internal static readonly Queue deviceInfoStructs = new(); private static readonly object AddDeviceCard2ViewLock = new(); @@ -30,49 +32,40 @@ internal class DevicesNetwork private static void InitEvents() { - EventService.OnReceivingDeviceInfoStruct += dis => + EventService.OnReceivingDeviceInfo += dis => { - if (dis.IsMainDevice && dis.DeviceServerBuildTime < GlobalInfo.ServerBuildTime) + if (dis.IsMainDevice && dis.DevicesServerBuildTime < ConstantTable.ServerBuildTime) { Stop(); Watch4MainDevice(); - Log.Information($"In DevicesService: Watched earlier built server. " + - $"DeviceServerAddress: {dis.IPv4}:{dis.DeviceServerPort} " + - $"DeviceServerBuildTime: {dis.DeviceServerBuildTime}"); + Log.Information( + new StringBuilder() + .AppendLine("Watched earlier built server.") + .AppendLine($"DevicesServerAddress: {dis.Device.IPv4}:{dis.DevicesServerPort} ") + .AppendLine($"DevicesServerBuildTime: {dis.DevicesServerBuildTime}") + .ToString() + ); } }; } - /// - /// 判断设备是否应该标记为离线 - /// - /// 设备广播信息 - /// 是否离线 - private static bool CheckDeviceIsOffline(DeviceInfoStruct info) + private static bool CheckDeviceIsOffline(DeviceInfo info) => DateTime.UtcNow - info.SendTime.ToUniversalTime() > new TimeSpan( 0, 0, - ConfigManager.AppConfig.Web.DeviceInfoStructTTLSeconds + Instances.ConfigManager.AppConfig.Web.DeviceInfoTTLSeconds ); - /// - /// 判断是否是本机卡片 - /// - /// 设备信息 - /// 是否是本机卡片 - private static bool CheckIsCurrentMachine(DeviceInfoStruct info) + private static bool CheckIsCurrentMachine(DeviceInfo info) { - var self = DevicesDiscoveryServer.DefaultDeviceInfoStruct; + var self = DevicesDiscoveryServer.DefaultDeviceInfo; - return info.DeviceMacAddress.Equals(self.DeviceMacAddress) - && info.DeviceName.Equals(self.DeviceName); + return info.Device.MacAddress.Equals(self.Device.MacAddress) + && info.Device.DeviceName.Equals(self.Device.DeviceName); } - /// - /// 更新数据源中的设备信息并添加数据源中没有的设备卡片 - /// private static void UpdateSourceAndAddCards() { var needToAddDevicesCount = 0; @@ -89,9 +82,9 @@ private static void UpdateSourceAndAddCards() if (findThis) continue; - foreach (var item in Instances.DeviceCards) + foreach (var item in ViewInstances.DeviceCards) { - if (item.viewModel.DeviceInfo.DeviceName.Equals(deviceInfoStruct.DeviceName)) + if (item.viewModel.DeviceInfo.Device.DeviceName.Equals(deviceInfoStruct.Device.DeviceName)) { item.viewModel.DeviceInfo = deviceInfoStruct; findThis = true; @@ -112,7 +105,7 @@ private static void UpdateSourceAndAddCards() { lock (AddDeviceCard2ViewLock) { - Instances.DeviceCards.Add(new(deviceInfoStruct)); + ViewInstances.DeviceCards.Add(new(deviceInfoStruct)); --needToAddDevicesCount; } @@ -123,14 +116,11 @@ private static void UpdateSourceAndAddCards() while (needToAddDevicesCount != 0) ; } - /// - /// 移除离线设备 - /// private static void RemoveOfflineCards() { var devicesNeedToBeRemoved = new List(); - foreach (var item in Instances.DeviceCards) + foreach (var item in ViewInstances.DeviceCards) { var info = item.viewModel.DeviceInfo; @@ -144,7 +134,7 @@ private static void RemoveOfflineCards() lock (AddDeviceCard2ViewLock) { foreach (var item in devicesNeedToBeRemoved) - Instances.DeviceCards.Remove(item); + ViewInstances.DeviceCards.Remove(item); } removeDeviceTaskRunning = false; }); @@ -152,15 +142,12 @@ private static void RemoveOfflineCards() while (removeDeviceTaskRunning) ; } - /// - /// 移动本机设备卡片到第一个 - /// private static void MoveSelfCard2First() { var index = 0; var moveSelfCardTaskRunning = true; - foreach (var item in Instances.DeviceCards) + foreach (var item in ViewInstances.DeviceCards) { var info = item.viewModel.DeviceInfo; @@ -172,7 +159,7 @@ private static void MoveSelfCard2First() { try { - Instances.DeviceCards.Move(index, 0); + ViewInstances.DeviceCards.Move(index, 0); } catch (Exception e) { @@ -190,16 +177,13 @@ private static void MoveSelfCard2First() } } - /// - /// 持续检查并移除 - /// private static void KeepCheckAndRemove() { - var location = $"{nameof(DevicesNetwork)}.{nameof(KeepCheckAndRemove)}"; + var location = $"{nameof(DevicesManager)}.{nameof(KeepCheckAndRemove)}"; var timer = new Timer() { - Interval = ConfigManager.AppConfig.Web.DevicesViewRefreshDelay, + Interval = Instances.ConfigManager.AppConfig.Web.DevicesViewRefreshDelay, AutoReset = true }; @@ -215,7 +199,7 @@ private static void KeepCheckAndRemove() UpdateSourceAndAddCards(); - if (!ConfigManager.AppConfig.Web.DisableRemovingOfflineDeviceCard) + if (!Instances.ConfigManager.AppConfig.Web.DisableRemovingOfflineDeviceCard) RemoveOfflineCards(); MoveSelfCard2First(); @@ -231,41 +215,34 @@ private static void KeepCheckAndRemove() timer.Start(); - EventService.ConfigSettingsChanged += () => + EventService.AppConfigChanged += () => { - timer.Interval = ConfigManager.AppConfig.Web.DevicesViewRefreshDelay; + timer.Interval = Instances.ConfigManager.AppConfig.Web.DevicesViewRefreshDelay; }; } - /// - /// 更新收到的UDP包 - /// - /// 设备信息结构 - internal static void Update(DeviceInfoStruct deviceInfo) + internal static void Update(DeviceInfo deviceInfo) { deviceInfoStructs.Enqueue(deviceInfo); - if (receivedDeviceInfoStruct4Watch is not null) + if (receivedDeviceInfo4Watch is not null) { - lock (_receivedDeviceInfoStruct4WatchLock) + lock (_receivedDeviceInfo4WatchLock) { - receivedDeviceInfoStruct4Watch.Add(deviceInfo); + receivedDeviceInfo4Watch.Add(deviceInfo); } } - EventService.Invoke(nameof(EventService.OnReceivingDeviceInfoStruct), deviceInfo); + EventService.Invoke(nameof(EventService.OnReceivingDeviceInfo), [deviceInfo]); } - /// - /// 观察主控 - /// internal static void Watch4MainDevice() { - var location = $"{nameof(DevicesNetwork)}.{nameof(Watch4MainDevice)}"; + var location = $"{nameof(DevicesManager)}.{nameof(Watch4MainDevice)}"; new Thread(() => { - receivedDeviceInfoStruct4Watch = new(); + receivedDeviceInfo4Watch = []; var checkedTime = 0; var hadMainDevice = false; @@ -277,18 +254,18 @@ internal static void Watch4MainDevice() { try { - if (receivedDeviceInfoStruct4Watch is null) continue; + if (receivedDeviceInfo4Watch is null) continue; - lock (_receivedDeviceInfoStruct4WatchLock) + lock (_receivedDeviceInfo4WatchLock) { - foreach (var item in receivedDeviceInfoStruct4Watch) + foreach (var item in receivedDeviceInfo4Watch) { if (item.IsMainDevice) { - if (item.DeviceServerBuildTime.ToUniversalTime() < earliestBuiltServerTime) + if (item.DevicesServerBuildTime.ToUniversalTime() < earliestBuiltServerTime) { - serverPort = item.DeviceServerPort; - serverAddress = item.IPv4; + serverPort = item.DevicesServerPort; + serverAddress = item.Device.IPv4; } hadMainDevice = true; } @@ -301,8 +278,8 @@ internal static void Watch4MainDevice() if (checkedTime == 7) { - receivedDeviceInfoStruct4Watch?.Clear(); - receivedDeviceInfoStruct4Watch = null; + receivedDeviceInfo4Watch?.Clear(); + receivedDeviceInfo4Watch = null; WatchingOver(hadMainDevice, serverAddress, serverPort); } @@ -311,8 +288,8 @@ internal static void Watch4MainDevice() } catch (Exception e) { - receivedDeviceInfoStruct4Watch?.Clear(); - receivedDeviceInfoStruct4Watch = null; + receivedDeviceInfo4Watch?.Clear(); + receivedDeviceInfo4Watch = null; Log.Error(e, $"In {location}: {e.Message} Rewatch."); @@ -324,12 +301,9 @@ internal static void Watch4MainDevice() }).Start(); } - /// - /// 观察结束 - /// internal static async void WatchingOver(bool foundMainDevice, string serverAddress, int serverPort) { - var location = $"{nameof(DevicesNetwork)}.{nameof(WatchingOver)}"; + var location = $"{nameof(DevicesManager)}.{nameof(WatchingOver)}"; Log.Information($"In {location}: " + $"{nameof(foundMainDevice)} -> {foundMainDevice}, " + diff --git a/KitX Dashboard/Managers/FileWatcherManager.cs b/KitX Dashboard/Managers/FileWatcherManager.cs index a81d724f..982bfd25 100644 --- a/KitX Dashboard/Managers/FileWatcherManager.cs +++ b/KitX Dashboard/Managers/FileWatcherManager.cs @@ -1,21 +1,24 @@ using Common.BasicHelper.Utils.Extensions; +using KitX.Dashboard.Names; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; namespace KitX.Dashboard.Managers; -internal class FileWatcherManager +public class FileWatcherManager { - private readonly Dictionary Watchers = new(); - - /// - /// 注册文件监控 - /// - /// 监控名称 - /// 监控文件路径 - /// 文件变更时事件 - /// 试图注册已经注册的监控 + private readonly Dictionary Watchers = []; + + public FileWatcherManager() + { + AppFramework.AfterInitailization(() => + { + Instances.SignalTasksManager!.RaiseSignal(nameof(SignalsNames.FileWatcherManagerInitializedSignal)); + }); + } + public FileWatcherManager RegisterWatcher( string name, string filePath, @@ -34,10 +37,6 @@ public FileWatcherManager RegisterWatcher( return this; } - /// - /// 注销文件监控, 不存在则什么也不做 - /// - /// 监控名称 public FileWatcherManager UnregisterWatcher(string name) { if (Watchers.TryGetValue(name, out var watcher)) @@ -49,11 +48,6 @@ public FileWatcherManager UnregisterWatcher(string name) return this; } - /// - /// 增加例外次数 - /// - /// 要增加的监控 - /// 要增加的次数 public FileWatcherManager IncreaseExceptCount(string name, int count = 1) { if (Watchers.TryGetValue(name, out var watcher)) @@ -62,11 +56,6 @@ public FileWatcherManager IncreaseExceptCount(string name, int count = 1) return this; } - /// - /// 减少例外次数 - /// - /// 要减少的监控 - /// 要减少的例外次数 public FileWatcherManager DecreaseExceptCount(string name, int count = 1) { if (Watchers.TryGetValue(name, out var watcher)) @@ -75,9 +64,6 @@ public FileWatcherManager DecreaseExceptCount(string name, int count = 1) return this; } - /// - /// 清空所有监控 - /// public FileWatcherManager Clear() { foreach (KeyValuePair item in Watchers) @@ -102,36 +88,30 @@ public FileWatcher( { var location = $"{nameof(FileWatcherManager)}.{nameof(FileWatcher)}"; - watcher = new(); + var filepath = filename.GetFullPath(); + + var path = Path.GetDirectoryName(filepath) ?? throw new NullReferenceException($"In {location}._ctor: Failed in {nameof(Path.GetDirectoryName)}"); + + watcher = new() + { + NotifyFilter = notifyFilters ?? NotifyFilters.LastWrite, + Path = path, + Filter = Path.GetFileName(filepath.GetFullPath()) + }; watcher.Changed += (x, y) => { if (ExceptCounts > 0) --ExceptCounts; - else onchanged(x, y); + else + onchanged(x, y); }; - watcher.NotifyFilter = notifyFilters ?? NotifyFilters.LastWrite; - - var filepath = filename.GetFullPath(); - var path = Path.GetDirectoryName(filepath) - ?? throw new NullReferenceException("In FileWatcher(): Failed in GetDirectoryName()"); - - watcher.Path = path; - watcher.Filter = filepath.GetFullPath(); watcher.EnableRaisingEvents = true; } - /// - /// 增加例外次数 - /// - /// 次数 public void IncreaseExceptCount(int count) => ExceptCounts += count; - /// - /// 减少例外次数 - /// - /// 次数 public void DecreaseExceptCount(int count) => ExceptCounts -= count; public void Dispose() diff --git a/KitX Dashboard/Managers/HotKeyManager.cs b/KitX Dashboard/Managers/KeyHookManager.cs similarity index 82% rename from KitX Dashboard/Managers/HotKeyManager.cs rename to KitX Dashboard/Managers/KeyHookManager.cs index 86184efc..90ca31ae 100644 --- a/KitX Dashboard/Managers/HotKeyManager.cs +++ b/KitX Dashboard/Managers/KeyHookManager.cs @@ -6,7 +6,7 @@ namespace KitX.Dashboard.Managers; -internal class HotKeyManager +public class KeyHookManager { private const int keysLimitation = 5; @@ -14,14 +14,14 @@ internal class HotKeyManager private readonly Dictionary>? hotKeyHandlers; - public HotKeyManager() + public KeyHookManager() { keyPressed = new(); - hotKeyHandlers = new(); + hotKeyHandlers = []; } - public HotKeyManager Hook() + public KeyHookManager Hook() { var hook = new TaskPoolGlobalHook(); @@ -58,14 +58,14 @@ private void VerifyKeys() handler.Invoke(tmpList); } - public HotKeyManager RegisterHotKeyHandler(string name, Action handler) + public KeyHookManager RegisterHotKeyHandler(string name, Action handler) { hotKeyHandlers!.Add(name, handler); return this; } - public HotKeyManager UnregisterHotKeyHandler(string name) + public KeyHookManager UnregisterHotKeyHandler(string name) { if (hotKeyHandlers!.TryGetValue(name, out _)) { diff --git a/KitX Dashboard/Managers/PluginsManager.cs b/KitX Dashboard/Managers/PluginsManager.cs index c04a544a..6a68882d 100644 --- a/KitX Dashboard/Managers/PluginsManager.cs +++ b/KitX Dashboard/Managers/PluginsManager.cs @@ -1,77 +1,68 @@ using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; using KitX.Dashboard.Models; using KitX.Dashboard.Services; -using KitX.Formats.KXP; -using KitX.Web.Rules; +using KitX.Shared.Loader; +using KitX.Shared.Plugin; using Serilog; using System; using System.Collections.Generic; using System.IO; -using JsonSerializer = System.Text.Json.JsonSerializer; +using System.Text; +using System.Text.Json; namespace KitX.Dashboard.Managers; internal class PluginsManager { - - internal static List Plugins = new(); + internal static List Plugins => Instances.ConfigManager.PluginsConfig.Plugins; internal static void ImportPlugin(string[] kxpfiles, bool inGraphic = false) { var location = $"{nameof(PluginsManager)}.{nameof(ImportPlugin)}"; - var processPath = Environment.ProcessPath - ?? throw new Exception("Can not get path of `KitX Dashboard` process."); + var processPath = Environment.ProcessPath ?? throw new Exception("Can not get path of `KitX.Dashboard` process."); - var workbase = Path.GetDirectoryName(processPath) - ?? throw new Exception("Can not get work base of `KitX`."); + var workbase = Path.GetDirectoryName(processPath) ?? throw new Exception("Can not get work base of `KitX`."); foreach (var item in kxpfiles) { try { - var decoder = new Decoder(item); + var decoder = new FileFormats.ExtensionsPackage.Decoder(item); - var rst = decoder.GetLoaderAndPluginStruct(); + var rst = decoder.GetLoaderAndPluginInfo(); - var loaderStruct = JsonSerializer.Deserialize(rst.Item1); - var pluginStruct = JsonSerializer.Deserialize(rst.Item2); + var loaderInfo = JsonSerializer.Deserialize(rst.Item1); - var config = inGraphic ? - ConfigManager.AppConfig : - JsonSerializer.Deserialize( - File.ReadAllText(GlobalInfo.ConfigFilePath) - ); + var pluginInfo = JsonSerializer.Deserialize(rst.Item2); - if (config is null) - { - Console.WriteLine($"No config file found!"); - - if (!inGraphic) Environment.Exit(ErrorCodes.ConfigFileDidntExists); - } + var config = Instances.ConfigManager.AppConfig; var pluginsavedir = config?.App?.LocalPluginsFileFolder.GetFullPath(); - var thisplugindir = $"" + - $"{pluginsavedir}/" + - $"{pluginStruct.PublisherName}_{pluginStruct.AuthorName}/" + - $"{pluginStruct.Name}/" + - $"{pluginStruct.Version}/"; - - thisplugindir = thisplugindir.GetFullPath(); + var thisPluginDir = new StringBuilder() + .Append(pluginsavedir) + .Append('/') + .Append($"{pluginInfo.PublisherName}_{pluginInfo.AuthorName}") + .Append('/') + .Append(pluginInfo.Name) + .Append('/') + .Append(pluginInfo.Version) + .ToString() + .GetFullPath() + ; - if (Directory.Exists(thisplugindir)) - Directory.Delete(thisplugindir, true); + if (Directory.Exists(thisPluginDir)) + Directory.Delete(thisPluginDir, true); - _ = Directory.CreateDirectory(thisplugindir); + _ = Directory.CreateDirectory(thisPluginDir); - _ = decoder.Decode(thisplugindir); + _ = decoder.Decode(thisPluginDir); - if (!Plugins.Exists(x => x.InstallPath?.Equals(thisplugindir) ?? false)) + if (!Plugins.Exists(x => x.InstallPath?.Equals(thisPluginDir) ?? false)) Plugins.Add(new() { - InstallPath = thisplugindir + InstallPath = thisPluginDir }); } catch (Exception e) @@ -84,11 +75,11 @@ internal static void ImportPlugin(string[] kxpfiles, bool inGraphic = false) { Log.Error(e, msg); - throw; // 如果是图形界面调用, 则再次抛出便于给出图形化提示 + throw; // If called in graphic mode, throw again for better tip } } } - EventService.Invoke(nameof(EventService.PluginsListChanged)); + EventService.Invoke(nameof(EventService.PluginsConfigChanged)); } } diff --git a/KitX Dashboard/Managers/StatisticsManager.cs b/KitX Dashboard/Managers/StatisticsManager.cs index f9753560..c8339b20 100644 --- a/KitX Dashboard/Managers/StatisticsManager.cs +++ b/KitX Dashboard/Managers/StatisticsManager.cs @@ -1,5 +1,4 @@ using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; using KitX.Dashboard.Services; using Serilog; using System; @@ -13,7 +12,7 @@ namespace KitX.Dashboard.Managers; internal class StatisticsManager { - internal static Dictionary? UseStatistics = new(); + internal static Dictionary? UseStatistics = []; internal static void Start() { @@ -30,7 +29,7 @@ internal static void InitEvents() { try { - var dataDir = GlobalInfo.DataPath.GetFullPath(); + var dataDir = ConstantTable.DataPath.GetFullPath(); if (!Directory.Exists(dataDir)) Directory.CreateDirectory(dataDir); var useFile = "UseCount.json"; @@ -49,7 +48,7 @@ internal static void InitEvents() internal static async void RecoverPreviousStatistics() { - var dataDir = GlobalInfo.DataPath.GetFullPath(); + var dataDir = ConstantTable.DataPath.GetFullPath(); if (!Directory.Exists(dataDir)) Directory.CreateDirectory(dataDir); try @@ -106,15 +105,11 @@ internal static void BeginRecord() if (UseStatistics is null) return; - if (UseStatistics.ContainsKey(today)) + if (!UseStatistics.TryAdd(today, 0.01)) { UseStatistics[today] += 0.01; UseStatistics[today] = Math.Round(UseStatistics[today], 2); } - else - { - UseStatistics.Add(today, 0.01); - } EventService.Invoke(nameof(EventService.UseStatisticsChanged)); } diff --git a/KitX Dashboard/Managers/TasksManager.cs b/KitX Dashboard/Managers/TasksManager.cs index b4a97f4a..eb68559c 100644 --- a/KitX Dashboard/Managers/TasksManager.cs +++ b/KitX Dashboard/Managers/TasksManager.cs @@ -6,19 +6,16 @@ namespace KitX.Dashboard.Managers; internal class TasksManager { - /// - /// 执行任务, 并带有更好的日志显示 - /// - /// 要执行的动作 - /// 日志显示名称 - /// 日志提示 public static void RunTask( Action action, string name = nameof(Action), string prompt = ">>> ", - bool catchException = false) + bool catchException = false, + bool logIt = true + ) { - Log.Information($"{prompt}Task `{name}` began."); + if (logIt) + Log.Information($"{prompt}Task `{name}` began."); if (catchException) { @@ -28,27 +25,26 @@ public static void RunTask( } catch (Exception e) { - Log.Error(e, $"{prompt}Task `{name}` failed: {e.Message}"); + if (logIt) + Log.Error(e, $"{prompt}Task `{name}` failed: {e.Message}"); } } else action(); - Log.Information($"{prompt}Task `{name}` done."); + if (logIt) + Log.Information($"{prompt}Task `{name}` done."); } - /// - /// 异步执行任务, 并带有更好的日志显示 - /// - /// 要执行的动作 - /// 任务名称 - /// 日志提示 public static async Task RunTaskAsync( Action action, string name = nameof(Action), string prompt = ">>> ", - bool catchException = false) + bool catchException = false, + bool logIt = true + ) { - Log.Information($"{prompt}Task `{name}` began."); + if (logIt) + Log.Information($"{prompt}Task `{name}` began."); if (catchException) { @@ -58,11 +54,13 @@ public static async Task RunTaskAsync( } catch (Exception e) { - Log.Error(e, $"{prompt}Task `{name}` failed: {e.Message}"); + if (logIt) + Log.Error(e, $"{prompt}Task `{name}` failed: {e.Message}"); } } else await Task.Run(action); - Log.Information($"{prompt}Task `{name}` done."); + if (logIt) + Log.Information($"{prompt}Task `{name}` done."); } } diff --git a/KitX Dashboard/Managers/WebManager.cs b/KitX Dashboard/Managers/WebManager.cs index d3a57c1f..cd3c405b 100644 --- a/KitX Dashboard/Managers/WebManager.cs +++ b/KitX Dashboard/Managers/WebManager.cs @@ -1,105 +1,64 @@ -using KitX.Dashboard.Network; +using KitX.Dashboard.Models.Network; +using KitX.Dashboard.Network.DevicesNetwork; +using KitX.Dashboard.Network.PluginsNetwork; using Serilog; using System; using System.Collections.ObjectModel; +using System.Text.Json; using System.Threading.Tasks; namespace KitX.Dashboard.Managers; public class WebManager : IDisposable { - internal PluginsServer? pluginsServer; - internal DevicesDiscoveryServer? devicesDiscoveryServer; - internal ObservableCollection? NetworkInterfaceRegistered; public WebManager() { - NetworkInterfaceRegistered = new(); + NetworkInterfaceRegistered = []; } - /// - /// 开始执行网络相关服务 - /// - /// 是否启动全部 - /// 是否启动插件服务器 - /// 是否启动设备服务器 - /// 是否启动设备自发现服务器 - /// 网络管理器本身 - public async Task Start - ( - bool startAll = true, - - bool startPluginsNetwork = false, - bool startDevicesNetwork = false, - bool startDevicesDiscoveryServer = false - ) + public async Task RunAsync(WebManagerOperationInfo info) { - var location = $"{nameof(WebManager)}.{nameof(Start)}"; + var location = $"{nameof(WebManager)}.{nameof(RunAsync)}"; await TasksManager.RunTaskAsync(async () => { try { - if (startAll || startDevicesDiscoveryServer) - devicesDiscoveryServer = await new DevicesDiscoveryServer().Start(); - - if (startAll || startDevicesNetwork) - DevicesNetwork.Start(); + if (info.RunAll || info.RunPluginsServer) + PluginsServer.Instance.Run(); - if (startAll || startPluginsNetwork) - { - PluginsNetwork.KeepCheckAndRemove(); - PluginsNetwork.KeepCheckAndRemoveOrDelete(); + if (info.RunAll || info.RunDevicesDiscoveryServer) + await DevicesDiscoveryServer.Instance.RunAsync(); - pluginsServer = await new PluginsServer().Start(); - } + if (info.RunAll || info.RunDevicesServer) + DevicesManager.Start(); } catch (Exception ex) { - Log.Error(ex, $"In {location}: " + - $"{nameof(startPluginsNetwork)}: {startPluginsNetwork}," + - $"{nameof(startDevicesNetwork)}: {startDevicesNetwork}," + - $"{nameof(startDevicesDiscoveryServer)}: {startDevicesDiscoveryServer}"); + Log.Error(ex, $"In {location}: {JsonSerializer.Serialize(info)}"); } }, location); return this; } - /// - /// 停止执行网络相关服务 - /// - /// 是否停止插件服务器 - /// 是否停止设备服务器 - /// 网络管理器本身 - public WebManager Stop - ( - bool stopAll = true, - - bool stopPluginsServices = false, - bool stopDevicesServices = false, - bool stopDevicesDiscoveryServer = false - ) + public async Task CloseAsync(WebManagerOperationInfo info) { - var location = $"{nameof(WebManager)}.{nameof(Stop)}"; + var location = $"{nameof(WebManager)}.{nameof(CloseAsync)}"; try { - if (stopAll || stopPluginsServices) - pluginsServer?.Stop().ContinueWith( - server => server.Dispose() - ); + if (info.CloseAll || info.CloseDevicesServer) + DevicesManager.Stop(); - if (stopAll || stopDevicesServices) - DevicesNetwork.Stop(); - - if (stopAll || stopDevicesDiscoveryServer) + if (info.CloseAll || info.CloseDevicesDiscoveryServer) { - devicesDiscoveryServer?.Stop().ContinueWith( + await DevicesDiscoveryServer.Instance.CloseAsync().ContinueWith( async server => { - await Task.Delay(ConfigManager.AppConfig.Web.UDPSendFrequency + 500); + await Task.Delay(Instances.ConfigManager.AppConfig.Web.UdpSendFrequency + 500); server.Dispose(); } @@ -107,6 +66,9 @@ public WebManager Stop while (DevicesDiscoveryServer.CloseDevicesDiscoveryServerRequest) { } } + + if (info.CloseAll || info.ClosePluginsServer) + await PluginsServer.Instance.Close(); } catch (Exception ex) { @@ -116,51 +78,17 @@ public WebManager Stop return this; } - /// - /// 重启网络相关服务 - /// - /// 是否重启插件服务器 - /// 是否重启设备服务器 - /// 重新启动前要执行的操作 - /// 网络管理器本身 - public WebManager Restart - ( - bool restartAll = true, - - bool restartPluginsServices = false, - bool restartDevicesServices = false, - bool restartDevicesDiscoveryServer = false, - - Action? actionBeforeStarting = null - ) + public async Task RestartAsync(WebManagerOperationInfo info, Action? actionBeforeStarting = null) { - Stop( - restartAll, - restartPluginsServices, - restartDevicesServices, - restartDevicesDiscoveryServer - ); - - Task.Run(async () => - { - await Task.Delay(ConfigManager.AppConfig.Web.UDPSendFrequency + 100); + await CloseAsync(info); - actionBeforeStarting?.Invoke(); + actionBeforeStarting?.Invoke(); - await Start( - restartAll, - restartPluginsServices, - restartDevicesServices, - restartDevicesDiscoveryServer - ); - }); + await RunAsync(info); return this; } - /// - /// 释放资源 - /// public void Dispose() { GC.SuppressFinalize(this); diff --git a/KitX Dashboard/Models/Network/WebManagerOperationInfo.cs b/KitX Dashboard/Models/Network/WebManagerOperationInfo.cs new file mode 100644 index 00000000..f1f9b4cd --- /dev/null +++ b/KitX Dashboard/Models/Network/WebManagerOperationInfo.cs @@ -0,0 +1,43 @@ +namespace KitX.Dashboard.Models.Network; + +public struct WebManagerOperationInfo +{ + public bool RunPluginsServer = true; + + public bool RunDevicesServer = true; + + public bool RunDevicesDiscoveryServer = true; + + public bool RunAll + { + readonly get => RunPluginsServer && RunDevicesServer && RunDevicesDiscoveryServer; + set + { + RunPluginsServer = value; + RunDevicesServer = value; + RunDevicesDiscoveryServer = value; + } + } + + public bool ClosePluginsServer = true; + + public bool CloseDevicesServer = true; + + public bool CloseDevicesDiscoveryServer = true; + + public bool CloseAll + { + readonly get => ClosePluginsServer && CloseDevicesServer && CloseDevicesDiscoveryServer; + set + { + ClosePluginsServer = value; + CloseDevicesServer = value; + CloseDevicesDiscoveryServer = value; + } + } + + public WebManagerOperationInfo() + { + + } +} diff --git a/KitX Dashboard/Models/Plugin.cs b/KitX Dashboard/Models/Plugin.cs index 2a6c5b7c..57c98786 100644 --- a/KitX Dashboard/Models/Plugin.cs +++ b/KitX Dashboard/Models/Plugin.cs @@ -1,4 +1,6 @@ -using KitX.Web.Rules; +using KitX.Shared.Device; +using KitX.Shared.Loader; +using KitX.Shared.Plugin; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -6,27 +8,15 @@ namespace KitX.Dashboard.Models; public class Plugin { - /// - /// 插件安装路径 - /// [JsonInclude] public string? InstallPath { get; set; } - /// - /// 该插件的详细信息 - /// [JsonIgnore] - public PluginStruct PluginDetails { get; set; } + public PluginInfo PluginDetails { get; set; } - /// - /// 需要的加载器的详细信息 - /// [JsonIgnore] - public LoaderStruct RequiredLoaderStruct { get; set; } + public LoaderInfo RequiredLoaderInfo { get; set; } - /// - /// 已安装此插件的网络设备 - /// [JsonIgnore] public List? InstalledDevices { get; set; } } diff --git a/KitX Dashboard/Names/ActivityTitles.cs b/KitX Dashboard/Names/ActivityTitles.cs index 9cb3e93c..d271d621 100644 --- a/KitX Dashboard/Names/ActivityTitles.cs +++ b/KitX Dashboard/Names/ActivityTitles.cs @@ -1,9 +1,7 @@ -using KitX.Dashboard.Data; - -namespace KitX.Dashboard.Names; +namespace KitX.Dashboard.Names; internal class ActivityTitles { - internal static string? AppStart { get; } = $"{GlobalInfo.AppFullName} Start"; + internal static string? AppStart { get; } = $"{ConstantTable.AppFullName} Start"; } diff --git a/KitX Dashboard/Names/FileWatcherNames.cs b/KitX Dashboard/Names/FileWatcherNames.cs deleted file mode 100644 index 9a419415..00000000 --- a/KitX Dashboard/Names/FileWatcherNames.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace KitX.Dashboard.Names; - -internal class FileWatcherNames -{ - internal static string? AppConfigFileWatcher { get; } -} diff --git a/KitX Dashboard/Names/PluginTagsNames.cs b/KitX Dashboard/Names/PluginTagsNames.cs new file mode 100644 index 00000000..4253d72a --- /dev/null +++ b/KitX Dashboard/Names/PluginTagsNames.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace KitX.Dashboard.Names; + +internal static class PluginTagsNames +{ + internal static object? JoinTime => null; +} + diff --git a/KitX Dashboard/Names/SignalsNames.cs b/KitX Dashboard/Names/SignalsNames.cs index 525bd09f..c0403c36 100644 --- a/KitX Dashboard/Names/SignalsNames.cs +++ b/KitX Dashboard/Names/SignalsNames.cs @@ -7,4 +7,6 @@ internal class SignalsNames internal static string? MainWindowOpenedSignal { get; } internal static string? FinishedFindingNetworkInterfacesSignal { get; } + + internal static string? FileWatcherManagerInitializedSignal { get; } } diff --git a/KitX Dashboard/Network/DevicesClient.cs b/KitX Dashboard/Network/DevicesClient.cs index 91c45ec4..2ed06887 100644 --- a/KitX Dashboard/Network/DevicesClient.cs +++ b/KitX Dashboard/Network/DevicesClient.cs @@ -1,5 +1,4 @@ using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Interfaces.Network; using KitX.Dashboard.Managers; using Serilog; using System; @@ -9,7 +8,7 @@ namespace KitX.Dashboard.Network; -internal class DevicesClient : IKitXClient +internal class DevicesClient { private static TcpClient? client = null; @@ -29,7 +28,7 @@ public static ClientStatus Status status = value; if (status == ClientStatus.Errored) - DevicesNetwork.Restart(); + DevicesManager.Restart(); } } @@ -62,7 +61,7 @@ public async void Receive() if (stream is null) return; - var buffer = new byte[ConfigManager.AppConfig.Web.SocketBufferSize]; // Default 10 MB buffer + var buffer = new byte[Instances.ConfigManager.AppConfig.Web.SocketBufferSize]; // Default 10 MB buffer try { diff --git a/KitX Dashboard/Network/DevicesDiscoveryServer.cs b/KitX Dashboard/Network/DevicesNetwork/DevicesDiscoveryServer.cs similarity index 71% rename from KitX Dashboard/Network/DevicesDiscoveryServer.cs rename to KitX Dashboard/Network/DevicesNetwork/DevicesDiscoveryServer.cs index ec34df4f..ee084950 100644 --- a/KitX Dashboard/Network/DevicesDiscoveryServer.cs +++ b/KitX Dashboard/Network/DevicesNetwork/DevicesDiscoveryServer.cs @@ -1,9 +1,8 @@ using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; -using KitX.Dashboard.Interfaces.Network; using KitX.Dashboard.Managers; using KitX.Dashboard.Names; -using KitX.Web.Rules; +using KitX.Dashboard.Views; +using KitX.Shared.Device; using Serilog; using System; using System.Collections.Generic; @@ -15,24 +14,25 @@ using System.Threading; using System.Threading.Tasks; -namespace KitX.Dashboard.Network; +namespace KitX.Dashboard.Network.DevicesNetwork; -/// -/// 设备自发现网络服务器 -/// -internal class DevicesDiscoveryServer : IKitXServer +public class DevicesDiscoveryServer { + private static DevicesDiscoveryServer? _instance; + + public static DevicesDiscoveryServer Instance => _instance ??= new DevicesDiscoveryServer(); + private static UdpClient? UdpSender = null; private static UdpClient? UdpReceiver = null; private static System.Timers.Timer? UdpSendTimer = null; - private static int DeviceInfoStructUpdatedTimes = 0; + private static int DeviceInfoUpdatedTimes = 0; private static int LastTimeToOSVersionUpdated = 0; - private static readonly List SupportedNetworkInterfacesIndexes = new(); + private static readonly List SupportedNetworkInterfacesIndexes = []; private static bool disposed = false; @@ -42,7 +42,7 @@ internal class DevicesDiscoveryServer : IKitXServer internal static readonly Queue Messages2BroadCast = new(); - internal static DeviceInfoStruct DefaultDeviceInfoStruct = NetworkHelper.GetDeviceInfo(); + internal static DeviceInfo DefaultDeviceInfo = NetworkHelper.GetDeviceInfo(); private static ServerStatus status = ServerStatus.Pending; @@ -55,9 +55,6 @@ internal static ServerStatus Status } } - /// - /// 寻找受支持的网络适配器并把 UDP 客户端加入组播 - /// private static void FindSupportNetworkInterfaces(List clients, IPAddress multicastAddress) { var multicastGroupJoinedInterfacesCount = 0; @@ -65,25 +62,17 @@ private static void FindSupportNetworkInterfaces(List clients, IPAddr foreach (var adapter in NetworkInterface.GetAllNetworkInterfaces()) { var adapterProperties = adapter.GetIPProperties(); - if (adapterProperties is null) continue; - try - { - var logs = adapter.Dump2Lines(); - for (int i = 0; i < logs.Length; i++) - Log.Information(logs[i]); - } - catch (Exception ex) - { - Log.Warning(ex, "Logging network interface items."); - } + if (adapterProperties is null) continue; if (!NetworkHelper.CheckNetworkInterface(adapter, adapterProperties)) continue; var unicastIPAddresses = adapterProperties.UnicastAddresses; + if (unicastIPAddresses is null) continue; var p = adapterProperties.GetIPv4Properties(); + if (p is null) continue; // IPv4 is not configured on this adapter SupportedNetworkInterfacesIndexes.Add(IPAddress.HostToNetworkOrder(p.Index)); @@ -104,6 +93,7 @@ private static void FindSupportNetworkInterfaces(List clients, IPAddr catch (Exception ex) { var location = $"{nameof(DevicesServer)}.{nameof(FindSupportNetworkInterfaces)}"; + Log.Error(ex, $"In {location}: {ex.Message}"); } } @@ -111,49 +101,44 @@ private static void FindSupportNetworkInterfaces(List clients, IPAddr Instances.SignalTasksManager?.RaiseSignal(nameof(SignalsNames.FinishedFindingNetworkInterfacesSignal)); - Log.Information($"" + - $"Find {SupportedNetworkInterfacesIndexes.Count} supported network interfaces."); - Log.Information($"" + - $"Joined {multicastGroupJoinedInterfacesCount} multicast groups."); + Log.Information($"Find {SupportedNetworkInterfacesIndexes.Count} supported network interfaces."); + + Log.Information($"Joined {multicastGroupJoinedInterfacesCount} multicast groups."); } - /// - /// 更新默认设备信息结构 - /// - private static void UpdateDefaultDeviceInfoStruct() + private static void UpdateDefaultDeviceInfo() { - DefaultDeviceInfoStruct.IsMainDevice = GlobalInfo.IsMainMachine; - DefaultDeviceInfoStruct.SendTime = DateTime.UtcNow; - DefaultDeviceInfoStruct.IPv4 = NetworkHelper.GetInterNetworkIPv4(); - DefaultDeviceInfoStruct.IPv6 = NetworkHelper.GetInterNetworkIPv6(); - DefaultDeviceInfoStruct.PluginServerPort = GlobalInfo.PluginServerPort; - DefaultDeviceInfoStruct.PluginsCount = Instances.PluginCards.Count; - DefaultDeviceInfoStruct.IsMainDevice = GlobalInfo.IsMainMachine; - DefaultDeviceInfoStruct.DeviceServerPort = GlobalInfo.DeviceServerPort; - DefaultDeviceInfoStruct.DeviceServerBuildTime = GlobalInfo.ServerBuildTime; - - if (LastTimeToOSVersionUpdated > ConfigManager.AppConfig.IO.OperatingSystemVersionUpdateInterval) + DefaultDeviceInfo.IsMainDevice = ConstantTable.IsMainMachine; + DefaultDeviceInfo.SendTime = DateTime.UtcNow; + DefaultDeviceInfo.Device + .ResetIPv4(NetworkHelper.GetInterNetworkIPv4()) + .ResetIPv6(NetworkHelper.GetInterNetworkIPv6()) + ; + DefaultDeviceInfo.PluginsServerPort = ConstantTable.PluginsServerPort; + DefaultDeviceInfo.PluginsCount = ViewInstances.PluginInfos.Count; + DefaultDeviceInfo.IsMainDevice = ConstantTable.IsMainMachine; + DefaultDeviceInfo.DevicesServerPort = ConstantTable.DevicesServerPort; + DefaultDeviceInfo.DevicesServerBuildTime = ConstantTable.ServerBuildTime; + + if (LastTimeToOSVersionUpdated > Instances.ConfigManager.AppConfig.IO.OperatingSystemVersionUpdateInterval) { LastTimeToOSVersionUpdated = 0; - DefaultDeviceInfoStruct.DeviceOSVersion = NetworkHelper.TryGetOSVersionString(); + DefaultDeviceInfo.DeviceOSVersion = NetworkHelper.TryGetOSVersionString() ?? ""; } - ++DeviceInfoStructUpdatedTimes; + ++DeviceInfoUpdatedTimes; ++LastTimeToOSVersionUpdated; - if (DeviceInfoStructUpdatedTimes < 0) DeviceInfoStructUpdatedTimes = 0; + if (DeviceInfoUpdatedTimes < 0) DeviceInfoUpdatedTimes = 0; } - /// - /// 多设备广播发送方法 - /// private void MultiDevicesBroadCastSend() { var location = $"{nameof(DevicesDiscoveryServer)}.{nameof(MultiDevicesBroadCastSend)}"; IPEndPoint multicast = new( - IPAddress.Parse(ConfigManager.AppConfig.Web.UDPBroadcastAddress), - ConfigManager.AppConfig.Web.UDPPortReceive + IPAddress.Parse(Instances.ConfigManager.AppConfig.Web.UdpBroadcastAddress), + Instances.ConfigManager.AppConfig.Web.UdpPortReceive ); UdpSender?.Client.SetSocketOption( SocketOptionLevel.Socket, @@ -166,9 +151,10 @@ private void MultiDevicesBroadCastSend() UdpSendTimer = new() { - Interval = ConfigManager.AppConfig.Web.UDPSendFrequency, + Interval = Instances.ConfigManager.AppConfig.Web.UdpSendFrequency, AutoReset = true }; + UdpSendTimer.Elapsed += (_, _) => { var closingRequest = CloseDevicesDiscoveryServerRequest; @@ -181,17 +167,17 @@ private void MultiDevicesBroadCastSend() erroredInterfacesIndexes.Clear(); } - UpdateDefaultDeviceInfoStruct(); + UpdateDefaultDeviceInfo(); if (closingRequest) - DefaultDeviceInfoStruct.SendTime -= TimeSpan.FromSeconds(20); + DefaultDeviceInfo.SendTime -= TimeSpan.FromSeconds(20); - var sendText = JsonSerializer.Serialize(DefaultDeviceInfoStruct); + var sendText = JsonSerializer.Serialize(DefaultDeviceInfo); var sendBytes = sendText.FromUTF8(); foreach (var item in SupportedNetworkInterfacesIndexes) { - if (!GlobalInfo.Running) break; + if (!ConstantTable.Running) break; // 如果错误网络适配器中存在当前项的记录, 跳过 if (erroredInterfacesIndexes.Contains(item)) continue; @@ -240,14 +226,11 @@ private void MultiDevicesBroadCastSend() UdpSendTimer.Start(); } - /// - /// 多设备广播接收方法 - /// private void MultiDevicesBroadCastReceive() { var location = $"{nameof(DevicesDiscoveryServer)}.{nameof(MultiDevicesBroadCastReceive)}"; - IPEndPoint multicast = new(IPAddress.Any, 0); + var multicast = new IPEndPoint(IPAddress.Any, 0); UdpReceiver?.Client.SetSocketOption( SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, @@ -258,7 +241,7 @@ private void MultiDevicesBroadCastReceive() { try { - while (GlobalInfo.Running && !CloseDevicesDiscoveryServerRequest) + while (ConstantTable.Running && !CloseDevicesDiscoveryServerRequest) { var bytes = UdpReceiver?.Receive(ref multicast); var client = $"{multicast.Address}:{multicast.Port}"; @@ -273,8 +256,8 @@ private void MultiDevicesBroadCastReceive() try { - DevicesNetwork.Update( - JsonSerializer.Deserialize(result) + DevicesManager.Update( + JsonSerializer.Deserialize(result) ); } catch (Exception ex) @@ -290,7 +273,7 @@ private void MultiDevicesBroadCastReceive() Status = ServerStatus.Errored; } - await Stop(); + await CloseAsync(); }).Start(); } @@ -299,7 +282,7 @@ private static void Init() { disposed = false; - DeviceInfoStructUpdatedTimes = 0; + DeviceInfoUpdatedTimes = 0; LastTimeToOSVersionUpdated = 0; @@ -309,10 +292,10 @@ private static void Init() Messages2BroadCast.Clear(); - DefaultDeviceInfoStruct = NetworkHelper.GetDeviceInfo(); + DefaultDeviceInfo = NetworkHelper.GetDeviceInfo(); } - public async Task Start() + public async Task RunAsync() { if (Status != ServerStatus.Pending) return this; @@ -321,7 +304,7 @@ public async Task Start() Init(); UdpSender = new( - ConfigManager.AppConfig.Web.UDPPortSend, + Instances.ConfigManager.AppConfig.Web.UdpPortSend, AddressFamily.InterNetwork ) { @@ -332,7 +315,7 @@ public async Task Start() UdpReceiver = new( new IPEndPoint( IPAddress.Any, - ConfigManager.AppConfig.Web.UDPPortReceive + Instances.ConfigManager.AppConfig.Web.UdpPortReceive ) ); @@ -341,16 +324,13 @@ await TasksManager.RunTaskAsync(() => try { FindSupportNetworkInterfaces( - new() - { - UdpSender, UdpReceiver - }, - IPAddress.Parse(ConfigManager.AppConfig.Web.UDPBroadcastAddress) + [UdpSender, UdpReceiver], + IPAddress.Parse(Instances.ConfigManager.AppConfig.Web.UdpBroadcastAddress) ); // 寻找所有支持的网络适配器 } catch (Exception ex) { - var location = $"{nameof(DevicesServer)}.{nameof(Start)}"; + var location = $"{nameof(DevicesServer)}.{nameof(RunAsync)}"; Log.Warning(ex, $"In {location}: {ex.Message}"); } }, nameof(FindSupportNetworkInterfaces)); @@ -370,7 +350,7 @@ await TasksManager.RunTaskAsync( return this; } - public async Task Stop() + public async Task CloseAsync() { if (Status != ServerStatus.Running) return this; @@ -390,9 +370,9 @@ public async Task Restart() { await Task.Run(async () => { - await Stop(); + await CloseAsync(); - await Start(); + await RunAsync(); }); return this; @@ -419,11 +399,6 @@ public async Task BroadCast(byte[] content, Func - /// 设定当接收到数据时的处理代码 - /// - /// 处理代码, 参数一为接收到的数据 (byte[]), 参数二是数据发送者, ip:port - /// 设备自发现网络服务器本身 public DevicesDiscoveryServer OnReceive(Action action) { onReceive = action; @@ -442,6 +417,6 @@ public void Dispose() UdpSender?.Dispose(); UdpReceiver?.Dispose(); - GC.SuppressFinalize(this); + GC.Collect(); } } diff --git a/KitX Dashboard/Network/DevicesServer.cs b/KitX Dashboard/Network/DevicesServer.cs index a8e30f3b..557e7de8 100644 --- a/KitX Dashboard/Network/DevicesServer.cs +++ b/KitX Dashboard/Network/DevicesServer.cs @@ -1,6 +1,4 @@ using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; -using KitX.Dashboard.Interfaces.Network; using KitX.Dashboard.Managers; using KitX.Dashboard.Services; using Serilog; @@ -13,7 +11,7 @@ namespace KitX.Dashboard.Network; -internal class DevicesServer : IKitXServer +internal class DevicesServer { private static TcpListener? listener = null; @@ -21,7 +19,7 @@ internal class DevicesServer : IKitXServer private static bool disposed = false; - private static readonly Dictionary clients = new(); + private static readonly Dictionary clients = []; private static Action? onReceive = null; @@ -35,7 +33,7 @@ public static ServerStatus Status status = value; if (status == ServerStatus.Errored) - DevicesNetwork.Restart(); + DevicesManager.Restart(); } } @@ -50,9 +48,6 @@ private static void Init() keepListen = true; } - /// - /// 接收客户端 - /// private static void AcceptClient() { var location = $"{nameof(DevicesServer)}.{nameof(AcceptClient)}"; @@ -80,10 +75,6 @@ private static void AcceptClient() } } - /// - /// 接收客户端消息 - /// - /// TcpClient private static void ReceiveMessage(TcpClient client) { var location = $"{nameof(DevicesServer)}.{nameof(ReceiveMessage)}"; @@ -103,7 +94,7 @@ private static void ReceiveMessage(TcpClient client) while (keepListen) { - var buffer = new byte[ConfigManager.AppConfig.Web.SocketBufferSize]; + var buffer = new byte[Instances.ConfigManager.AppConfig.Web.SocketBufferSize]; var length = await stream.ReadAsync(buffer); @@ -205,9 +196,9 @@ await TasksManager.RunTaskAsync(() => var port = ((IPEndPoint)listener.LocalEndpoint).Port; // 取服务端口号 - GlobalInfo.DeviceServerPort = port; // 全局端口号标明 - GlobalInfo.ServerBuildTime = DateTime.UtcNow; - GlobalInfo.IsMainMachine = true; + ConstantTable.DevicesServerPort = port; // 全局端口号标明 + ConstantTable.ServerBuildTime = DateTime.UtcNow; + ConstantTable.IsMainMachine = true; Log.Information($"DevicesServer Port: {port}"); @@ -244,8 +235,8 @@ await TasksManager.RunTaskAsync(() => clients.Clear(); - GlobalInfo.IsMainMachine = false; - GlobalInfo.DeviceServerPort = -1; + ConstantTable.IsMainMachine = false; + ConstantTable.DevicesServerPort = -1; EventService.Invoke(nameof(EventService.DevicesServerPortChanged)); diff --git a/KitX Dashboard/Network/NetworkHelper.cs b/KitX Dashboard/Network/NetworkHelper.cs index 968d3bad..b72f131c 100644 --- a/KitX Dashboard/Network/NetworkHelper.cs +++ b/KitX Dashboard/Network/NetworkHelper.cs @@ -1,9 +1,8 @@ using Common.BasicHelper.Core.Shell; using Common.BasicHelper.Utils.Extensions; using KitX.Dashboard.Converters; -using KitX.Dashboard.Data; -using KitX.Dashboard.Managers; -using KitX.Web.Rules; +using KitX.Dashboard.Views; +using KitX.Shared.Device; using Serilog; using System; using System.IO; @@ -16,18 +15,13 @@ namespace KitX.Dashboard.Network; internal static class NetworkHelper { - /// - /// 检查网络适配器是否符合要求 - /// - /// 网络适配器 - /// 是否符合要求 internal static bool CheckNetworkInterface ( NetworkInterface adapter, IPInterfaceProperties adapterProperties ) { - var userPointed = ConfigManager.AppConfig.Web.AcceptedNetworkInterfaces; + var userPointed = Instances.ConfigManager.AppConfig.Web.AcceptedNetworkInterfaces; if (userPointed is not null) if (userPointed.Contains(adapter.Name)) @@ -50,11 +44,6 @@ IPInterfaceProperties adapterProperties return true; } - /// - /// 判断 IPv4 地址是否为内部网络地址 - /// - /// 网络地址 - /// 是否为内部网络地址 internal static bool IsInterNetworkAddressV4(IPAddress address) { var bytes = address.GetAddressBytes(); @@ -68,10 +57,6 @@ internal static bool IsInterNetworkAddressV4(IPAddress address) }; } - /// - /// 获取本机内网 IPv4 地址 - /// - /// 使用点分十进制表示法的本机内网IPv4地址 internal static string GetInterNetworkIPv4() { var location = $"{nameof(NetworkHelper)}.{nameof(GetInterNetworkIPv4)}"; @@ -83,7 +68,7 @@ from ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList where ip.AddressFamily == AddressFamily.InterNetwork && IsInterNetworkAddressV4(ip) && !ip.ToString().Equals("127.0.0.1") - && ip.ToString().StartsWith(ConfigManager.AppConfig.Web.IPFilter) + && ip.ToString().StartsWith(Instances.ConfigManager.AppConfig.Web.IPFilter) select ip; Log.Information($"IPv4 addresses: {search.Print(print: false)}"); @@ -100,10 +85,6 @@ from ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList } } - /// - /// 获取本机内网 IPv6 地址 - /// - /// IPv6 地址 internal static string GetInterNetworkIPv6() { var location = $"{nameof(NetworkHelper)}.{nameof(GetInterNetworkIPv6)}"; @@ -130,10 +111,6 @@ from ip in Dns.GetHostEntry(Dns.GetHostName()).AddressList } } - /// - /// 尝试获取设备 MAC 地址 - /// - /// MAC 地址 internal static string? TryGetDeviceMacAddress() { var location = $"{nameof(NetworkHelper)}.{nameof(TryGetDeviceMacAddress)}"; @@ -160,10 +137,6 @@ from nic in NetworkInterface.GetAllNetworkInterfaces() } } - /// - /// 尝试获取系统版本 - /// - /// 系统版本 internal static string? TryGetOSVersionString() { var location = $"{nameof(NetworkHelper)}.{nameof(TryGetOSVersionString)}"; @@ -232,23 +205,22 @@ from nic in NetworkInterface.GetAllNetworkInterfaces() return result; } - /// - /// 获取设备信息 - /// - /// 设备信息结构体 - internal static DeviceInfoStruct GetDeviceInfo() => new() + internal static DeviceInfo GetDeviceInfo() => new() { - DeviceName = Environment.MachineName, - DeviceMacAddress = TryGetDeviceMacAddress(), - IsMainDevice = GlobalInfo.IsMainMachine, + Device = new() + { + DeviceName = Environment.MachineName, + MacAddress = TryGetDeviceMacAddress() ?? "", + IPv4 = GetInterNetworkIPv4(), + IPv6 = GetInterNetworkIPv6(), + }, + IsMainDevice = ConstantTable.IsMainMachine, SendTime = DateTime.UtcNow, DeviceOSType = OperatingSystem2Enum.GetOSType(), - DeviceOSVersion = TryGetOSVersionString(), - IPv4 = GetInterNetworkIPv4(), - IPv6 = GetInterNetworkIPv6(), - PluginServerPort = GlobalInfo.PluginServerPort, - DeviceServerPort = GlobalInfo.DeviceServerPort, - DeviceServerBuildTime = new(), - PluginsCount = Instances.PluginCards.Count, + DeviceOSVersion = TryGetOSVersionString() ?? "", + PluginsServerPort = ConstantTable.PluginsServerPort, + DevicesServerPort = ConstantTable.DevicesServerPort, + DevicesServerBuildTime = new(), + PluginsCount = ViewInstances.PluginInfos.Count, }; } diff --git a/KitX Dashboard/Network/NetworkStatus.cs b/KitX Dashboard/Network/NetworkStatus.cs index 88786c0f..8ee7d775 100644 --- a/KitX Dashboard/Network/NetworkStatus.cs +++ b/KitX Dashboard/Network/NetworkStatus.cs @@ -1,73 +1,31 @@ namespace KitX.Dashboard.Network; -/// -/// 服务器状态 -/// -internal enum ServerStatus +public enum ServerStatus { - /// - /// 未知状态 - /// Unknown = 0, - /// - /// 启动中 - /// Starting = 1, - /// - /// 运行中 - /// Running = 2, - /// - /// 停止中 - /// Stopping = 3, - /// - /// 等待中 - /// Pending = 4, - /// - /// 发生错误 - /// Errored = 5, } -/// -/// 客户端状态 -/// -internal enum ClientStatus +public enum ClientStatus { - /// - /// 未知状态 - /// Unknown = 0, - /// - /// 连接中 - /// Connecting = 1, - /// - /// 运行中 - /// Running = 2, - /// - /// 断开连接中 - /// Disconnecting = 3, - /// - /// 等待中 - /// Pending = 4, - /// - /// 发生错误 - /// Errored = 5, } diff --git a/KitX Dashboard/Network/PluginsNetwork.cs b/KitX Dashboard/Network/PluginsNetwork.cs deleted file mode 100644 index ec6d058a..00000000 --- a/KitX Dashboard/Network/PluginsNetwork.cs +++ /dev/null @@ -1,249 +0,0 @@ -using Avalonia.Threading; -using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; -using KitX.Dashboard.Models; -using KitX.Dashboard.Services; -using KitX.Dashboard.Views.Pages.Controls; -using KitX.Web.Rules; -using Serilog; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Text.Json; - -namespace KitX.Dashboard.Managers; - -internal class PluginsNetwork -{ - /// - /// 执行 Socket 消息 - /// - /// 消息 - internal static void Execute(string msg, IPEndPoint endPoint) - { - var location = $"{nameof(PluginsNetwork)}.{nameof(Execute)}"; - - try - { - if (msg.StartsWith("PluginStruct: ")) - { - var json = msg[14..]; - - var pluginStruct = JsonSerializer.Deserialize(json); - - pluginStruct.Tags ??= new(); - - // 标注实例注册 ID - pluginStruct.Tags.Add("Authorized_ID", - $"{pluginStruct.PublisherName}" + - $"." + - $"{pluginStruct.Name}" + - $"." + - $"{pluginStruct.Version}" - ); - - // 标注 IPEndPoint - pluginStruct.Tags.Add("IPEndPoint", endPoint.ToString()); - - pluginsToAdd.Enqueue(pluginStruct); - - var workPath = ConfigManager.AppConfig.App.LocalPluginsDataFolder.GetFullPath(); - var sendtxt = $"WorkPath: {workPath}"; - var bytes = sendtxt.FromUTF8(); - - Instances.WebManager?.pluginsServer?.Send(bytes, endPoint.ToString()); - } - } - catch (Exception e) - { - Log.Error(e, $"In {location}: (msg) => msg: {msg}; {e.Message}"); - } - } - - internal static readonly Queue pluginsToRemove = new(); - - internal static readonly Queue pluginsToAdd = new(); - - internal static readonly Queue pluginsToRemoveFromDB = new(); - - internal static readonly Queue pluginsToDelete = new(); - - internal static readonly object PluginsListOperationLock = new(); - - /// - /// 持续检查并移除 - /// - internal static void KeepCheckAndRemove() - { - var location = $"{nameof(PluginsNetwork)}.{nameof(KeepCheckAndRemove)}"; - - bool timerElapsing = false; - - var timer = new System.Timers.Timer() - { - Interval = 10, - AutoReset = true - }; - - timer.Elapsed += (_, _) => - { - if (timerElapsing) - return; - else - timerElapsing = true; - - try - { - if (pluginsToAdd.Count > 0) - { - var pluginStruct = pluginsToAdd.Dequeue(); - - Dispatcher.UIThread.Post(() => - { - var card = new PluginCard(pluginStruct) - { - IPEndPoint = pluginStruct.Tags["IPEndPoint"] - }; - - Instances.PluginCards.Add(card); - }); - } - - if (pluginsToRemove.Count > 0) - { - var endPoint = pluginsToRemove.Dequeue().ToString(); - - var matched = Instances.PluginCards.FirstOrDefault( - x => x!.IPEndPoint?.Equals(endPoint) ?? false, - null - ); - - if (matched is not null) - Instances.PluginCards.Remove(matched); - } - - if (!GlobalInfo.Running) - { - timer.Stop(); - } - } - catch (Exception ex) - { - Log.Error(ex, $"In {location}: {ex.Message}"); - } - - timerElapsing = false; - }; - - timer.Start(); - } - - /// - /// 断开了连接 - /// - /// 插件 id - internal static void Disconnect(IPEndPoint endPoint) - { - pluginsToRemove.Enqueue(endPoint); - } - - /// - /// 请求移除插件 - /// - /// 插件的安装信息 - internal static void RequireRemovePlugin(Plugin plugin) => pluginsToRemoveFromDB.Enqueue(plugin); - - /// - /// 请求删除插件 - /// - /// 插件的安装信息 - internal static void RequireDeletePlugin(Plugin plugin) => pluginsToDelete.Enqueue(plugin); - - /// - /// 持续检查移除和删除队列 - /// - internal static void KeepCheckAndRemoveOrDelete() - { - var location = $"{nameof(PluginsNetwork)}.{nameof(KeepCheckAndRemoveOrDelete)}"; - - System.Timers.Timer timer = new() - { - Interval = 2000, - AutoReset = true - }; - - timer.Elapsed += (_, _) => - { - try - { - var isPluginsListUpdated = false; - - if (pluginsToRemoveFromDB.Count > 0) - { - isPluginsListUpdated = true; - - while (pluginsToRemoveFromDB.Count > 0) - { - var plugin = pluginsToRemoveFromDB.Dequeue(); - - lock (PluginsListOperationLock) - { - PluginsManager.Plugins.RemoveAt( -PluginsManager.Plugins.FindIndex( - x => - { - if (x.InstallPath is null) return false; - - return x.InstallPath.Equals(plugin.InstallPath); - } - ) - ); - } - } - } - - if (pluginsToDelete.Count > 0) - { - isPluginsListUpdated = true; - - while (pluginsToDelete.Count > 0) - { - var plugin = pluginsToDelete.Dequeue(); - - lock (PluginsListOperationLock) - { - PluginsManager.Plugins.RemoveAt( -PluginsManager.Plugins.FindIndex( - x => - { - if (x.InstallPath is not null) - return x.InstallPath.Equals(plugin.InstallPath); - else return false; - } - ) - ); - } - - var pgfiledir = Path.GetFullPath( - $"{ConfigManager.AppConfig.App.LocalPluginsFileFolder}/" + - $"{plugin.PluginDetails.PublisherName}_{plugin.PluginDetails.AuthorName}/" + - $"{plugin.PluginDetails.Name}/{plugin.PluginDetails.Version}/" - ); - - Directory.Delete(pgfiledir, true); - } - } - - if (isPluginsListUpdated) EventService.Invoke(nameof(EventService.PluginsListChanged)); - } - catch (Exception ex) - { - Log.Error(ex, $"In {location}: {ex.Message}"); - } - }; - - timer.Start(); - } -} diff --git a/KitX Dashboard/Network/PluginsNetwork/PluginConnector.cs b/KitX Dashboard/Network/PluginsNetwork/PluginConnector.cs new file mode 100644 index 00000000..23f849c3 --- /dev/null +++ b/KitX Dashboard/Network/PluginsNetwork/PluginConnector.cs @@ -0,0 +1,217 @@ +using Common.BasicHelper.Utils.Extensions; +using Fleck; +using KitX.Dashboard.Names; +using KitX.Dashboard.Views; +using KitX.Shared.Plugin; +using KitX.Shared.WebCommand; +using KitX.Shared.WebCommand.Details; +using KitX.Shared.WebCommand.Infos; +using Serilog; +using System; +using System.Text.Json; +using System.Threading.Tasks; + +namespace KitX.Dashboard.Network.PluginsNetwork; + +public class PluginConnector +{ + private readonly IWebSocketConnection? _connection; + + private readonly IWebSocketConnectionInfo? _connectionInfo; + + private string? _path; + + private bool _initialized = false; + + private PluginInfo? _pluginInfo; + + private ServerStatus connectorStatus = ServerStatus.Pending; + + private readonly JsonSerializerOptions serializerOptions = new() + { + WriteIndented = true, + IncludeFields = true, + PropertyNameCaseInsensitive = true, + }; + + public delegate void PluginStatusUpdatedHandler(); + + public event PluginStatusUpdatedHandler PluginStatusUpdated = new(() => { }); + + public PluginConnector() + { + + } + + public PluginConnector(IWebSocketConnection socket) + { + _connection = socket; + _connectionInfo = socket.ConnectionInfo; + } + + public string? Path + { + get => _path; + set + { + _path = value; + + PluginStatusUpdated.Invoke(); + } + } + + public string? ConnectionId => Path; + + public PluginInfo? PluginInfo + { + get => _pluginInfo; + set + { + _pluginInfo = value; + + PluginStatusUpdated.Invoke(); + } + } + + public bool PluginInfoAvailable => PluginInfo is null; + + public ServerStatus ConnectorStatus + { + get => connectorStatus; + set + { + connectorStatus = value; + + PluginStatusUpdated.Invoke(); + } + } + + public PluginConnector Initialize() + { + _initialized = true; + + Path = _connectionInfo!.Path.Trim('/'); + + return this; + } + + public PluginConnector Run() + { + if (_initialized == false) + Initialize(); + + var location = $"{nameof(PluginConnector)}.{nameof(Run)}"; + + _connection!.OnOpen = () => + { + }; + + _connection.OnClose = () => + { + try + { + if (PluginInfo is not null) + ViewInstances.PluginInfos.Remove(PluginInfo.Value); + } + catch (Exception e) + { + Log.Warning(e, $"In {location}: {e.Message}"); + } + + PluginsServer.Instance.PluginConnectors.Remove(this); + }; + + _connection.OnMessage = message => + { + var kwc = JsonSerializer.Deserialize(message, serializerOptions); + + var command = JsonSerializer.Deserialize(kwc.Content, serializerOptions); + + switch (command.Request) + { + case CommandRequestInfo.RegisterPlugin: + + var body = command.Body.ToUTF8(count: command.BodyLength); + + PluginInfo = JsonSerializer.Deserialize(body, serializerOptions); + + ArgumentNullException.ThrowIfNull(PluginInfo, nameof(PluginInfo)); + + ArgumentNullException.ThrowIfNull(PluginInfo.Value.Tags, nameof(PluginInfo.Value.Tags)); + + PluginInfo.Value.Tags.Add(nameof(ConnectionId), ConnectionId ?? string.Empty); + PluginInfo.Value.Tags.Add( + nameof(PluginTagsNames.JoinTime), + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss(FF)") + ); + + ViewInstances.PluginInfos.Add(PluginInfo.Value); + + Log.Information($"In {location}: New plugin registered with {body.Replace("\r", "").Replace("\n", "")}"); + + break; + case CommandRequestInfo.RequestWorkingDetail: + SendWorkingDetail(); + break; + case CommandRequestInfo.ReportStatus: + break; + case CommandRequestInfo.RequestCommand: + break; + } + + ; + }; + + _connection.OnError = ex => + { + try + { + if (PluginInfo is not null) + ViewInstances.PluginInfos.Remove(PluginInfo.Value); + } + catch (Exception e) + { + Log.Warning(e, $"In {location}: {e.Message}"); + } + + PluginsServer.Instance.PluginConnectors.Remove(this); + + Log.Error(ex, $"In {location}: {ex.Message}"); + }; + + return this; + } + + private void SendMessage(T content) => _connection!.Send( + JsonSerializer.Serialize(content, serializerOptions) + ); + + private void SendWorkingDetail() + { + if (_path.IsNullOrWhiteSpace()) + SendMessage(new PluginWorkingDetail() + { + PluginDataDirectory = null, + PluginSaveDirectory = null, + }); + else + { + + } + } + + internal async void Request(Request request) + { + await _connection!.Send(JsonSerializer.Serialize(request, serializerOptions)); + } + + public async Task CloseAsync() + { + await Task.Run(() => + { + _connection!.Close(); + }); + + return this; + } +} diff --git a/KitX Dashboard/Network/PluginsNetwork/PluginsServer.cs b/KitX Dashboard/Network/PluginsNetwork/PluginsServer.cs new file mode 100644 index 00000000..c1f3c5a5 --- /dev/null +++ b/KitX Dashboard/Network/PluginsNetwork/PluginsServer.cs @@ -0,0 +1,85 @@ +using Fleck; +using KitX.Dashboard.Configuration; +using KitX.Dashboard.Services; +using KitX.Shared.Plugin; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Threading.Tasks; + +namespace KitX.Dashboard.Network.PluginsNetwork; + +public class PluginsServer : ConfigFetcher +{ + private static PluginsServer? _pluginsServer; + + public static PluginsServer Instance => _pluginsServer ??= new PluginsServer(); + + private WebSocketServer? _server; + + private readonly List _connectors = []; + + public List PluginConnectors => _connectors; + + public PluginsServer() + { + InitializeServer(); + } + + private void InitializeServer() + { + var port = AppConfig.Web.UserSpecifiedPluginsServerPort ?? 0; + + port = port is >= 0 and <= 65535 ? port : 0; + + _server ??= new WebSocketServer($"ws://0.0.0.0:{port}"); + } + + public PluginsServer Run() + { + InitializeServer(); + + _server!.Start(socket => + { + var connector = new PluginConnector(socket).Run(); + + _connectors.Add(connector); + }); + + EventService.Invoke(nameof(EventService.PluginsServerPortChanged), [_server!.Port]); + + return this; + } + + public PluginConnector? FindConnector(PluginInfo info) + { + var query = PluginConnectors.Where( + x => x.PluginInfo.HasValue && x.PluginInfo.Value.Equals(info) + ); + + if (query.Any()) + return query.First(); + else + return null; + } + + public async Task Close() + { + await Task.Run(() => + { + Task.WaitAll( + _connectors.Select( + c => c.CloseAsync() + ).ToArray() + ); + + _connectors.Clear(); + + _server?.Dispose(); + + _server = null; + }); + + return this; + } +} diff --git a/KitX Dashboard/Network/PluginsServer.cs b/KitX Dashboard/Network/PluginsServer.cs deleted file mode 100644 index ede98d30..00000000 --- a/KitX Dashboard/Network/PluginsServer.cs +++ /dev/null @@ -1,288 +0,0 @@ -using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; -using KitX.Dashboard.Interfaces.Network; -using KitX.Dashboard.Managers; -using KitX.Dashboard.Services; -using Serilog; -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; - -namespace KitX.Dashboard.Network; - -internal class PluginsServer : IKitXServer -{ - private static TcpListener? listener = null; - - private static bool keepListen = true; - - private static bool disposed = false; - - private static readonly Dictionary clients = new(); - - private static Action? onReceive = null; - - private static ServerStatus status = ServerStatus.Pending; - - public static ServerStatus Status - { - get => status; - set - { - status = value; - } - } - - private static void Init() - { - disposed = false; - - clients.Clear(); - - var port = ConfigManager.AppConfig.Web.UserSpecifiedPluginsServerPort; - - if (port < 0 || port > 65535) port = null; - - listener = new(IPAddress.Any, port ?? 0); - - keepListen = true; - } - - /// - /// 接收客户端 - /// - private static void AcceptClient() - { - var location = $"{nameof(PluginsServer)}.{nameof(AcceptClient)}"; - - try - { - while (keepListen && listener is not null) - { - var client = listener.AcceptTcpClient(); - - if (client.Client.RemoteEndPoint is not IPEndPoint endpoint) continue; - - clients.Add(endpoint.ToString(), client); - - Log.Information($"New plugin connection: {endpoint}"); - - ReceiveMessage(client); - } - } - catch (Exception ex) - { - Log.Error(ex, $"In {location}: {ex.Message}"); - - Status = ServerStatus.Errored; - } - } - - /// - /// 接收消息 - /// - /// TcpClient - private static void ReceiveMessage(TcpClient client) - { - var location = $"{nameof(PluginsServer)}.{nameof(ReceiveMessage)}"; - - IPEndPoint? endpoint = null; - NetworkStream? stream = null; - - new Thread(async () => - { - try - { - endpoint = client.Client.RemoteEndPoint as IPEndPoint; - - stream = client.GetStream(); - - if (endpoint is null || stream is null) return; - - while (keepListen) - { - var buffer = new byte[ConfigManager.AppConfig.Web.SocketBufferSize]; - - var length = stream is null ? 0 : await stream.ReadAsync(buffer); - - if (length > 0) - { - onReceive?.Invoke(buffer, length, endpoint.ToString()); - - var msg = buffer.ToUTF8(0, length); - - Log.Information($"From: {endpoint}\tReceive: {msg}"); - - PluginsNetwork.Execute(msg, endpoint); - } - else break; //客户端断开连接 跳出循环 - } - } - catch (Exception ex) - { - Log.Error(ex, $"In {location}: {ex.Message}"); - Log.Information($"Connection broke from: {endpoint}"); - - Status = ServerStatus.Errored; - } - finally - { - if (endpoint is not null) - { - PluginsNetwork.Disconnect(endpoint); - - clients.Remove(endpoint.ToString()); - } - - stream?.CloseAndDispose(); - - client.CloseAndDispose(); - } - }).Start(); - } - - public async Task Broadcast(byte[] content) - { - await Task.Run(() => - { - foreach (var client in clients.Values) - { - var stream = client.GetStream(); - - stream.Write(content, 0, content.Length); - - stream.Flush(); - } - }); - - return this; - } - - public async Task BroadCast(byte[] content, Func? pattern) - { - await Task.Run(() => - { - foreach (var client in clients.Values) - if (pattern?.Invoke(client) ?? false) - { - var stream = client.GetStream(); - - stream.Write(content, 0, content.Length); - - stream.Flush(); - } - }); - - return this; - } - - public async Task Send(byte[] content, string target) - { - await Task.Run(() => - { - var client = clients[target]; - - var stream = client.GetStream(); - - stream.Write(content, 0, content.Length); - - stream.Flush(); - }); - - return this; - } - - public PluginsServer OnReceive(Action action) - { - onReceive = action; - - return this; - } - - public async Task Start() - { - var location = $"{nameof(PluginsServer)}.{nameof(Start)}"; - - await TasksManager.RunTaskAsync(() => - { - Status = ServerStatus.Starting; - - Init(); - - if (listener is null) return; - - listener.Start(); - - var port = ((IPEndPoint)listener.LocalEndpoint).Port; // 取服务端口号 - - GlobalInfo.PluginServerPort = port; // 全局端口号标明 - - EventService.Invoke(nameof(EventService.PluginsServerPortChanged)); - - Log.Information($"PluginsServer Port: {port}"); - - new Thread(AcceptClient).Start(); - - Status = ServerStatus.Running; - - }, location); - - return this; - } - - public async Task Stop() - { - var location = $"{nameof(PluginsServer)}.{nameof(Stop)}"; - - Status = ServerStatus.Stopping; - - keepListen = false; - - await TasksManager.RunTaskAsync(() => - { - foreach (KeyValuePair item in clients) - { - item.Value.Close(); - item.Value.Dispose(); - } - - clients.Clear(); - - listener?.Stop(); - - Status = ServerStatus.Pending; - - }, location, catchException: true); - - return this; - } - - public async Task Restart() - { - var location = $"{nameof(PluginsServer)}.{nameof(Restart)}"; - - await TasksManager.RunTaskAsync(async () => - { - await Stop(); - - await Start(); - - }, location); - - return this; - } - - public void Dispose() - { - if (disposed) return; - - disposed = true; - - listener = null; - - GC.SuppressFinalize(this); - } -} diff --git a/KitX Dashboard/Options/BaseOptions.cs b/KitX Dashboard/Options/BaseOptions.cs new file mode 100644 index 00000000..2fc07bc6 --- /dev/null +++ b/KitX Dashboard/Options/BaseOptions.cs @@ -0,0 +1,9 @@ +using CommandLine; + +namespace KitX.Dashboard.Options; + +public class BaseOptions +{ + [Option('v', "verbose", HelpText = "View detailed output")] + public bool Verbose { get; set; } +} diff --git a/KitX Dashboard/Options/StartupOptions.cs b/KitX Dashboard/Options/StartupOptions.cs new file mode 100644 index 00000000..85bb527a --- /dev/null +++ b/KitX Dashboard/Options/StartupOptions.cs @@ -0,0 +1,18 @@ +using CommandLine; + +namespace KitX.Dashboard.Options; + +public class StartupOptions : BaseOptions +{ + [Option("import-plugin", HelpText = "Import a plugin via CLI")] + public string? PluginPath { get; set; } + + [Option("disable-single-process-check", Default = false, HelpText = "Allow user run multiple `KitX.Dashboard.exe`")] + public bool DisableSingleProcessCheck { get; set; } + + [Option("disable-config-hot-reload", Default = false, HelpText = "Do not reload config when it is edited outside")] + public bool DisableConfigHotReload { get; set; } + + [Option("disable-network-system-on-startup", Default = false, HelpText = "Do not power network system at startup")] + public bool DisableNetworkSystemOnStartup { get; set; } +} diff --git a/KitX Dashboard/Program.cs b/KitX Dashboard/Program.cs index 38a8d072..75b83868 100644 --- a/KitX Dashboard/Program.cs +++ b/KitX Dashboard/Program.cs @@ -1,87 +1,71 @@ -using Avalonia; -using Avalonia.ReactiveUI; -using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Managers; -using KitX.Dashboard.Services; -using System; -using System.IO; - -namespace KitX.Dashboard; - -class Program -{ - /// - /// Main entry for program - /// - /// - /// Initialization code. Don't use any Avalonia, third-party APIs or any - /// SynchronizationContext-reliant code before AppMain is called: things aren't initialized - /// yet and stuff might break. - [STAThread] - public static void Main(string[] args) - { - try - { - // If dump file exists, delete it. - if (File.Exists("./dump.log".GetFullPath())) - File.Delete("./dump.log".GetFullPath()); - - // Init event service - EventService.Init(); - - // Process startup arguments - Helper.ProcessStartupArguments(args); - - // Run framework - Helper.RunFramework(); - - ConfigManager.AppConfig.App.RanTime++; - - // Run Avalonia - BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); - - // Make sure all threads exit - Helper.Exit(); - } - catch (Exception e) - { - // Any unhandled exception will be catched here! - File.AppendAllText( - "./dump.log".GetFullPath(), - $""" - [{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {e.Message} - {e.StackTrace} - """ - ); - -#if !DEBUG - Environment.Exit(1); -#endif - - } - } - - /// - /// Build Avalonia app - /// - /// Avalonia AppBuilder - /// Do not remove this, it also used by visual designer. - public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() - .UsePlatformDetect() - .WithInterFont() - .LogToTrace() - .UseReactiveUI() - .With( - new MacOSPlatformOptions - { - ShowInDock = true, - } - ) - .With( - new X11PlatformOptions - { - EnableMultiTouch = true, - } - ); -} +using Avalonia; +using Avalonia.ReactiveUI; +using Common.BasicHelper.Utils.Extensions; +using System; +using System.IO; + +namespace KitX.Dashboard; + +class Program +{ + /// + /// Main entry for program + /// + /// + /// Initialization code. Don't use any Avalonia, third-party APIs or any + /// SynchronizationContext-reliant code before AppMain is called: things aren't initialized + /// yet and stuff might break. + [STAThread] + public static void Main(string[] args) + { + try + { + // Run framework + AppFramework.RunFramework(); + + // Run Avalonia + BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); + + // Make sure all threads exit + AppFramework.EnsureExit(); + } + catch (Exception e) + { + // Any unhandled exception will be catched here! + File.AppendAllText( + "./dump.log".GetFullPath(), + $""" + [{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {e.Message} + {e.StackTrace} + """ + ); +#if !DEBUG + Environment.Exit(1); +#endif + } + } + + /// + /// Build Avalonia app + /// + /// Avalonia AppBuilder + /// Do not remove this, it also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .WithInterFont() + .LogToTrace() + .UseReactiveUI() + .With( + new MacOSPlatformOptions + { + ShowInDock = true, + } + ) + .With( + new X11PlatformOptions + { + EnableMultiTouch = true, + } + ); +} diff --git a/KitX Dashboard/Properties/launchSettings.json b/KitX Dashboard/Properties/launchSettings.json index eb3eb77d..f0450cd7 100644 --- a/KitX Dashboard/Properties/launchSettings.json +++ b/KitX Dashboard/Properties/launchSettings.json @@ -1,12 +1,12 @@ -{ - "profiles": { - "KitX Dashboard": { - "commandName": "Project" - }, - "WSL": { - "commandName": "WSL2", - "environmentVariables": {}, - "distributionName": "Ubuntu-20.04" - } +{ + "profiles": { + "KitX Dashboard": { + "commandName": "Project", + "nativeDebugging": false + }, + "WSL": { + "commandName": "WSL2", + "distributionName": "Ubuntu-20.04" } + } } \ No newline at end of file diff --git a/KitX Dashboard/Services/DebugService.cs b/KitX Dashboard/Services/DebugService.cs index 0253157d..b4f593b4 100644 --- a/KitX Dashboard/Services/DebugService.cs +++ b/KitX Dashboard/Services/DebugService.cs @@ -1,428 +1,66 @@ -using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Managers; -using KitX.Dashboard.Network; -using Serilog; +using Csharpell.Core; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Scripting; using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Security.Cryptography; using System.Text; +using System.Reflection; +using System.Threading.Tasks; +using System.Threading; +using System.Diagnostics; namespace KitX.Dashboard.Services; -internal class DebugService +public static class DebugService { - /// - /// 执行命令 - /// - /// 命令 - /// 执行结果 - internal static string? ExecuteCommand(string cmd) - { - var header = cmd.GetCommandHeader(); - if (header is null) return null; - var args = cmd.Trim()[header.Length..].GetCommandArgs(); - if (args is null) return null; - var name = header.ToFunctionName(); - if (name is null) return null; - var foo = typeof(DebugCommands).GetMethod(name); - if (foo is null) return null; - if (args.TryGetValue("--times", out var timesString)) - { - if (int.TryParse(timesString, out var times)) - for (var i = 0; i < times - 1; ++i) - _ = foo?.Invoke(null, new Dictionary[] { args }); - } - return foo?.Invoke(null, new Dictionary[] { args }) as string; - } -} + private static readonly CSharpScriptEngine Engine = new(); -internal class DebugCommands -{ - private static string SaveConfig() + public static async Task ExecuteCodesAsync(string code, CancellationToken cancellationToken = default) { - ConfigManager.SaveConfigs(); + var sw = new Stopwatch(); - return "AppConfig saved!"; - } + var begin = DateTime.Now; - public static string? Help(Dictionary args) - { - StringBuilder doc = new(); - doc.AppendLine("You can append `help` to any command to see documents of this command."); - doc.AppendLine("Or append command name to `help` command to see documents of this command."); - if (args.Count == 1 && args.Keys.ToArray()[0].Equals("")) return doc.ToString(); - foreach (var item in args.Keys) - doc.AppendLine(DebugService.ExecuteCommand($"{item} help") ?? $"No help doc for {item}."); - return doc.ToString(); - } + sw.Start(); - public static string? Version(Dictionary args) - { - if (args.TryGetValue("help", out _)) - return "Print version of KitX Dashboard."; - return Assembly.GetEntryAssembly()?.GetName()?.Version?.ToString(); - } - - public static string? Save(Dictionary args) - { - if (args.TryGetValue("help", out _)) - return "" + - "Save datas to disk.\n" + - "\t--type [config] | saveAction KitX Dashboard config file.\n" + - "\tconfig | saveAction KitX Dashboard config file.\n"; - if (args.TryGetValue("--type", out var type)) - return type switch - { - "config" => SaveConfig(), - _ => "Missing value of `--type`.", - }; - else if (args.TryGetValue("config", out _)) - { - return SaveConfig(); - } - else return "Missing arguments."; - } - - public static string? Config(Dictionary args) - { - if (args.TryGetValue("help", out _)) - { - return "" + - "Edit KitX Dashboard app config.\n" + - "\t--set developing...\n" + - "\t--get developing...\n"; - } - if (args.TryGetValue("--set", out _)) - { - return "Missing value of `--set`."; - } - else if (args.TryGetValue("--get", out _)) - { - return "Missing value of `--get`."; - } - else if (args.TryGetValue("saveAction", out _)) - { - return SaveConfig(); - } - else return "Missing arguments."; - } - - public static string? Send(Dictionary args) - { - if (args.TryGetValue("help", out _)) - { - return "" + - "Send data in network.\n" + - "\t--type [DeviceUdpPack, ClientMessage, HostMessage, HostBroadCast] |\n" + - "\t\tDeviceUdpPack | [--value] Send a device udp pack through string.\n" + - "\t\tClientMessage | [--value] Send client message to master device.\n" + - "\t\tHostMessage | [--value] [--to] Send message to clients as master device.\n" + - "\t\tHostBroadcast | [--value] Broadcast a message to every clients."; - } - if (args.TryGetValue("--type", out var type)) - { - switch (type.ToLower()) - { - // DeviceUdpPack - case "deviceudppack": - if (args.TryGetValue("--value", out var udpPackValue)) - { - DevicesDiscoveryServer.Messages2BroadCast.Enqueue(udpPackValue); - return "Appended value to broadcast list."; - } - else return "Missing value of `--value`."; - // ClientMessage - case "clientmessage": - if (args.TryGetValue("--value", out var clientMessageValue)) - { - DevicesNetwork.devicesClient?.Send(clientMessageValue.FromUTF8()); - return $"Sent msg: {clientMessageValue}"; - } - else return "Missing value of `--value`."; - // HostMessage - case "hostmessage": - if (args.TryGetValue("--value", out var hostMessageValue)) - { - if (args.TryGetValue("--to", out var hostMessageTo)) - { - DevicesNetwork.devicesServer?.Send( - hostMessageValue.FromUTF8(), - hostMessageTo - ); - return $"Sent msg: {hostMessageValue}, to: {hostMessageTo}"; - } - else return "Missing value of `--to`."; - } - else return "Missing value of `--value`."; - // HostBroadcast - case "hostbroadcast": - if (args.TryGetValue("--value", out var broadcastValue)) - { - DevicesNetwork.devicesServer?.BroadCast( - broadcastValue.FromUTF8(), - null - ); - return $"Broadcast msg: {broadcastValue}"; - } - else return "Missing value of `--value`"; - default: - return "Missing value of `--type`."; - } - } - else return "Missing arguments."; - } - - public static string? Cache(Dictionary args) - { try { - if (args.TryGetValue("help", out _)) - { - return "" + - "Load file to CacheManager.\n" + - "\t--file | with path of file, if contain space, quote it."; - } - if (args.TryGetValue("--file", out var file)) - { - var path = file; - - if (path.StartsWith('"') && path.EndsWith('"')) - path = path[1..^1]; - - Log.Information($"Debug Tool: Request CacheManager to load {path}"); - - path = Path.GetFullPath(path); - - if (!File.Exists(path)) return "File not found."; - - if (args.TryGetValue("--way", out var way)) + var result = (await Engine.ExecuteAsync( + code, + options => { - switch (way) - { - case "complete": break; - case "fragment": - //ToDo: Load file in fragments. - return ""; - } - } - - var info = new FileInfo(path); - - if (info.Length > 2.0 * 1024 * 1024 * 1024 - 500) - return "File larger than 2 GB, unsupported by this way"; - - var id = Instances.CacheManager?.LoadFileToCache(path); - - return $"File loaded, ID (MD5): {id?.Result ?? "null"}"; - } - else return "Missing arguments."; - } - catch (Exception ex) - { - return $"Error when executing, ex: {ex.Message}"; + options = options + .WithReferences(Assembly.GetExecutingAssembly()) + .WithImports("KitX", "KitX.Dashboard") + .WithLanguageVersion(LanguageVersion.Preview) + ; + + return options; + + }, + addDefaultImports: true, + runInReplMode: false, + cancellationToken: cancellationToken + ))?.ToString(); + + sw.Stop(); + + return new StringBuilder() + .AppendLine($"[{begin:yyyy-MM-dd HH:mm:ss}] [I] Posted.") + .AppendLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [I] Ended, took {sw.ElapsedMilliseconds} ms.") + .AppendLine(result) + .ToString() + ; } - } - - public static string? Dispose(Dictionary args) - { - - if (args.TryGetValue("--type", out var type)) + catch (Exception e) { - switch (type) - { - case "file": - if (args.TryGetValue("--id", out var fileId)) - { - var id = fileId; - var result = Instances.CacheManager?.DisposeFileCache(id); - if (result is null) return "Unknown error occursed."; - if (!(bool)result) return "Dispose failed."; - return "Disposed."; - } - else return "Missing ID for file."; - default: - return "Missing value of `--type`"; - } - } - else return "Missing arguments."; + sw.Stop(); + + return new StringBuilder() + .AppendLine($"[{begin:yyyy-MM-dd HH:mm:ss}] [I] Posted.") + .AppendLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [E] Exception caught after {sw.ElapsedMilliseconds} ms, Message: {e.Message}") + .AppendLine(e.StackTrace) + .ToString() + ; + }; } - - public static string? Start(Dictionary args) - { - if (args.TryGetValue("help", out _)) - return "" + - "- plugins-services\n" + - "- devices-services\n" + - "- devices-discovery-server\n" + - "- all\n"; - - if (args.TryGetValue("plugins-services", out _)) - Instances.WebManager?.Start(startAll: false, startPluginsNetwork: true); - if (args.TryGetValue("devices-services", out _)) - Instances.WebManager?.Start(startAll: false, startDevicesNetwork: true); - if (args.TryGetValue("devices-discovery-server", out _)) - Instances.WebManager?.Start(startAll: false, startDevicesDiscoveryServer: true); - if (args.TryGetValue("all", out _)) - Instances.WebManager?.Start(); - - return "Start action requested."; - } - - public static string? Stop(Dictionary args) - { - if (args.TryGetValue("help", out _)) - return "" + - "- plugins-services\n" + - "- devices-services\n" + - "- devices-discovery-server\n" + - "- all"; - - if (args.TryGetValue("plugins-services", out _)) - Instances.WebManager?.Stop(stopAll: false, stopPluginsServices: true); - if (args.TryGetValue("devices-services", out _)) - Instances.WebManager?.Stop(stopAll: false, stopDevicesServices: true); - if (args.TryGetValue("devices-discovery-server", out _)) - Instances.WebManager?.Stop(stopAll: false, stopDevicesDiscoveryServer: true); - if (args.TryGetValue("all", out _)) - Instances.WebManager?.Stop(); - - return "Stop action requested."; - } - - public static string? Hash(Dictionary args) - { - if (args.TryGetValue("help", out _)) - return "" + - "Return hashed value for parameters.\n" + - "\t--way [MD5,SHA1,Common.Algorithm] |\n" + - "\t\tMD5 | [--value] Use MD5 to hash parameters.\n" + - "\t\tSHA1 | [--value] Use SHA1 to hash parameters.\n" + - "\t\tCommon.Algorithm | [--value] Use Common.Algorithm to hash parameters."; - - if (args.TryGetValue("--way", out var way)) - { - switch (way.ToLower()) - { - case "md5": - { - var result = MD5.HashData(args["--value"].FromUTF8()).ToUTF8(); - return result; - } - case "sha1": - { - var result = SHA1.HashData(args["--value"].FromUTF8()).ToUTF8(); - return result; - } - case "common.algorithm": - { - return Common.Algorithm.Interop.Hash.FromString2Hex(args["--value"]); - } - default: - return "No this way."; - } - } - else return "Missing arguments."; - } -} - -internal static class DebugServiceTool -{ - /// - /// 获取命令头 - /// - /// 命令 - /// 命令头 - internal static string? GetCommandHeader(this string cmd) - { - var command = cmd.Trim(); - if (command is null) return null; - var header = command.Split(' ')[0]; - return header; - } - - /// - /// 获取命令参数 - /// - /// 命令 - /// 参数字典 - internal static Dictionary? GetCommandArgs(this string cmd) - { - var args = new Dictionary(); - var command = cmd.Trim(); - var quotes = command.ReplaceQuotes(); - command = quotes.Item1; - var quoteMap = quotes.Item2; - var pairs = command?.Split(' '); - if (pairs is null) return args; - for (var i = 0; i < pairs.Length; i++) - { - if (pairs[i].Trim().StartsWith("--")) - { - if (i == pairs.Length - 1) return null; - args.Add(pairs[i], pairs[i + 1]); - } - else args.Add(pairs[i], string.Empty); - } - foreach (var item in args) - { - if (quoteMap.TryGetValue(item.Value, out var value)) - { - args[item.Key] = value; - } - } - return args; - } - - /// - /// 替换文本中的引号内容 - /// - /// 文本 - /// 新的引号键以及对应的值 - internal static (string, Dictionary) ReplaceQuotes(this string text) - { - // 记录被替换的引号内容的键与内容 - var rst = new Dictionary(); - var count = 0; // 引号计数 - var lastPosition = -1; // 上一个开引号的位置, -1 表示上一个引号是闭引号 - var len = text.Length; // 源文本串长度 - for (var i = 0; i < len; i++) - { - if (text[i].Equals('"')) - { - if (lastPosition == -1) lastPosition = i; - else - { - var key = $"$$_Quotes_{++count}_$$"; // 按键数量替换 - var value = text[lastPosition..(i + 1)]; // 替换的文本 - var delta = key.Length - value.Length; // 长度差值 - len += delta; // 总长度设为替换后的总长度 - text = $"{text[0..lastPosition]}{key}{text[(i + 1)..]}"; // 替换 - rst.Add(key, value); // 添加键与替换的文本 - i += delta; // 指针归位 - lastPosition = -1; // 设为闭引号 - } - } - } - return (text, rst); - } - - /// - /// 获取字典中的值 - /// - /// 字典 - /// 键 - /// - internal static string? Value(this Dictionary src, string key) - => src.TryGetValue(key, out var value) ? value : null; - - /// - /// 将命令转为可能的函数名称 - /// - /// 命令 - /// 可能的函数名称 - internal static string ToFunctionName(this string cmd) - => $"{cmd[0].ToString().ToUpper()}{cmd[1..].ToLower()}"; } diff --git a/KitX Dashboard/Services/EventService.cs b/KitX Dashboard/Services/EventService.cs index c867f771..5fc2be6d 100644 --- a/KitX Dashboard/Services/EventService.cs +++ b/KitX Dashboard/Services/EventService.cs @@ -1,152 +1,98 @@ -using KitX.Web.Rules; +using KitX.Shared.Device; +using System.Reflection; +using System; namespace KitX.Dashboard.Services; -internal static class EventService +public static class EventService { + public static void Invoke(string eventName, object[]? objects = null) + { + var type = typeof(EventService); - internal delegate void LanguageChangedHandler(); + var eventField = type.GetField(eventName, BindingFlags.Static | BindingFlags.NonPublic); - internal delegate void GreetingTextIntervalUpdatedHandler(); + if (eventField is null || !typeof(Delegate).IsAssignableFrom(eventField.FieldType)) + { + throw new ArgumentException($"No event found with the name '{eventName}'.", nameof(eventName)); + } - internal delegate void ConfigSettingsChangedHandler(); + var @delegate = eventField.GetValue(null) as Delegate; - internal delegate void MicaOpacityChangedHandler(); + @delegate?.DynamicInvoke(objects); + } - internal delegate void PluginsListChangedHandler(); + public delegate void LanguageChangedHandler(); - internal delegate void DevelopSettingsChangedHandler(); + public static event LanguageChangedHandler LanguageChanged = new(() => { }); - internal delegate void LogConfigUpdatedHandler(); - internal delegate void ThemeConfigChangedHandler(); + public delegate void GreetingTextIntervalUpdatedHandler(); - internal delegate void UseStatisticsChangedHandler(); + public static event GreetingTextIntervalUpdatedHandler GreetingTextIntervalUpdated = new(() => { }); - internal delegate void OnExitingHandler(); - internal delegate void PluginsServerPortChangedHandler(); + public delegate void AppConfigChangedHandler(); - internal delegate void DevicesServerPortChangedHandler(); + public static event AppConfigChangedHandler AppConfigChanged = new(() => { }); - internal delegate void OnReceivingDeviceInfoStructHandler(DeviceInfoStruct dis); - internal delegate void OnConfigHotReloadedHandler(); + public delegate void PluginsConfigChangedHandler(); + public static event PluginsConfigChangedHandler PluginsConfigChanged = new(() => { }); - internal static event LanguageChangedHandler? LanguageChanged; + public delegate void MicaOpacityChangedHandler(); - internal static event GreetingTextIntervalUpdatedHandler? GreetingTextIntervalUpdated; + public static event MicaOpacityChangedHandler MicaOpacityChanged = new(() => { }); - internal static event ConfigSettingsChangedHandler? ConfigSettingsChanged; - internal static event MicaOpacityChangedHandler? MicaOpacityChanged; + public delegate void DevelopSettingsChangedHandler(); - internal static event PluginsListChangedHandler? PluginsListChanged; + public static event DevelopSettingsChangedHandler DevelopSettingsChanged = new(() => { }); - internal static event DevelopSettingsChangedHandler? DevelopSettingsChanged; - internal static event LogConfigUpdatedHandler? LogConfigUpdated; + public delegate void LogConfigUpdatedHandler(); - internal static event ThemeConfigChangedHandler? ThemeConfigChanged; + public static event LogConfigUpdatedHandler LogConfigUpdated = new(() => { }); - internal static event UseStatisticsChangedHandler? UseStatisticsChanged; - internal static event OnExitingHandler? OnExiting; + public delegate void ThemeConfigChangedHandler(); - internal static event PluginsServerPortChangedHandler? PluginsServerPortChanged; + public static event ThemeConfigChangedHandler ThemeConfigChanged = new(() => { }); - internal static event DevicesServerPortChangedHandler? DevicesServerPortChanged; - internal static event OnReceivingDeviceInfoStructHandler? OnReceivingDeviceInfoStruct; + public delegate void UseStatisticsChangedHandler(); - internal static event OnConfigHotReloadedHandler? OnConfigHotReloaded; + public static event UseStatisticsChangedHandler UseStatisticsChanged = new(() => { }); - /// - /// 必要的初始化 - /// - internal static void Init() - { - LanguageChanged += () => { }; - GreetingTextIntervalUpdated += () => { }; - ConfigSettingsChanged += () => { }; - MicaOpacityChanged += () => { }; - PluginsListChanged += () => { }; - DevelopSettingsChanged += () => { }; - LogConfigUpdated += () => { }; - ThemeConfigChanged += () => { }; - UseStatisticsChanged += () => { }; - OnExiting += () => { }; - DevicesServerPortChanged += () => { }; - OnReceivingDeviceInfoStruct += dis => { }; - OnConfigHotReloaded += () => { }; - PluginsServerPortChanged += () => { }; - } + public delegate void PluginsServerPortChangedHandler(int port); - /// - /// 执行全局事件 - /// - /// 事件名称 - internal static void Invoke(string eventName) - { - switch (eventName) - { - case nameof(LanguageChanged): - LanguageChanged?.Invoke(); - break; - case nameof(GreetingTextIntervalUpdated): - GreetingTextIntervalUpdated?.Invoke(); - break; - case nameof(ConfigSettingsChanged): - ConfigSettingsChanged?.Invoke(); - break; - case nameof(MicaOpacityChanged): - MicaOpacityChanged?.Invoke(); - break; - case nameof(PluginsListChanged): - PluginsListChanged?.Invoke(); - break; - case nameof(DevelopSettingsChanged): - DevelopSettingsChanged?.Invoke(); - break; - case nameof(LogConfigUpdated): - LogConfigUpdated?.Invoke(); - break; - case nameof(ThemeConfigChanged): - ThemeConfigChanged?.Invoke(); - break; - case nameof(UseStatisticsChanged): - UseStatisticsChanged?.Invoke(); - break; - case nameof(OnExiting): - OnExiting?.Invoke(); - break; - case nameof(DevicesServerPortChanged): - DevicesServerPortChanged?.Invoke(); - break; - case nameof(OnConfigHotReloaded): - OnConfigHotReloaded?.Invoke(); - break; - case nameof(PluginsServerPortChanged): - PluginsServerPortChanged?.Invoke(); - break; - } - } + public static event PluginsServerPortChangedHandler PluginsServerPortChanged = new(port => ConstantTable.PluginsServerPort = port); - /// - /// 执行全局事件 - /// - /// 事件名称 - /// 事件参数 - internal static void Invoke(string eventName, object arg) - { - switch (eventName) - { - case nameof(OnReceivingDeviceInfoStruct): - OnReceivingDeviceInfoStruct?.Invoke((DeviceInfoStruct)arg); - break; - } - } + + public delegate void DevicesServerPortChangedHandler(); + + public static event DevicesServerPortChangedHandler DevicesServerPortChanged = new(() => { }); + + + public delegate void OnActivitiesUpdatedHandler(); + + public static event OnActivitiesUpdatedHandler OnActivitiesUpdated = new(() => { }); + + + public delegate void OnExitingHandler(); + + public static event OnExitingHandler OnExiting = new(() => { }); + + + public delegate void OnReceivingDeviceInfoHandler(DeviceInfo dis); + + public static event OnReceivingDeviceInfoHandler OnReceivingDeviceInfo = new(_ => { }); + + + public delegate void OnConfigHotReloadedHandler(); + + public static event OnConfigHotReloadedHandler OnConfigHotReloaded = new(() => { }); } diff --git a/KitX Dashboard/Styles/FontsStyles.axaml b/KitX Dashboard/Styles/FontsStyles.axaml new file mode 100644 index 00000000..58693745 --- /dev/null +++ b/KitX Dashboard/Styles/FontsStyles.axaml @@ -0,0 +1,15 @@ + + + + + diff --git a/KitX Dashboard/Utils/WindowsUtils.cs b/KitX Dashboard/Utils/WindowsUtils.cs new file mode 100644 index 00000000..2353cae2 --- /dev/null +++ b/KitX Dashboard/Utils/WindowsUtils.cs @@ -0,0 +1,23 @@ +using Avalonia.Platform; +using Common.BasicHelper.Graphics.Screen; + +namespace KitX.Dashboard.Utils; + +internal static class WindowsUtils +{ + internal static Resolution SuggestResolution(this Resolution res, Screen? screen) + { + if (res.Width == 1280 && res.Height == 720 && screen is not null) + { + var suggest = Resolution.Suggest( + Resolution.Parse("2560x1440"), + Resolution.Parse("1280x720"), + Resolution.Parse($"{screen.WorkingArea.Width}x{screen.WorkingArea.Height}") + ).Integerization(); + + return suggest; + } + + return res; + } +} diff --git a/KitX Dashboard/ViewLocator.cs b/KitX Dashboard/ViewLocator.cs index a357355a..614f38c7 100644 --- a/KitX Dashboard/ViewLocator.cs +++ b/KitX Dashboard/ViewLocator.cs @@ -7,12 +7,14 @@ namespace KitX.Dashboard; public class ViewLocator : IDataTemplate { - public Control Build(object data) - { - var name = data.GetType().FullName!.Replace("ViewModel", "View"); + public Control? Build(object? data) + { + if (data is null) return null; + + var name = data.GetType().FullName!.Replace("ViewModel", "View"); var type = Type.GetType(name); - if (type != null) + if (type is not null) { return (Control)Activator.CreateInstance(type)!; } @@ -20,8 +22,8 @@ public Control Build(object data) return new TextBlock { Text = "Not Found: " + name }; } - public bool Match(object data) + public bool Match(object? data) { return data is ViewModelBase; } -} \ No newline at end of file +} diff --git a/KitX Dashboard/ViewModels/AnouncementsWindowViewModel.cs b/KitX Dashboard/ViewModels/AnouncementsWindowViewModel.cs index 2ed90dbe..94e3b690 100644 --- a/KitX Dashboard/ViewModels/AnouncementsWindowViewModel.cs +++ b/KitX Dashboard/ViewModels/AnouncementsWindowViewModel.cs @@ -1,17 +1,12 @@ -using Avalonia.Collections; -using Common.BasicHelper.Utils.Extensions; +using Avalonia; using FluentAvalonia.UI.Controls; -using KitX.Dashboard.Data; -using KitX.Dashboard.Managers; +using KitX.Dashboard.Configuration; using KitX.Dashboard.Views; using ReactiveUI; using System.Collections.Generic; using System.ComponentModel; -using System.IO; using System.Linq; using System.Reactive; -using System.Text.Json; -using System.Threading.Tasks; namespace KitX.Dashboard.ViewModels; @@ -24,31 +19,24 @@ public AnouncementsWindowViewModel() InitCommands(); } - private void InitCommands() + public override void InitCommands() { - ConfirmReceivedCommand = ReactiveCommand.Create(async () => + ConfirmReceivedCommand = ReactiveCommand.Create(() => { - if (SelectedMenuItem is null || Readed is null) return; + var config = Instances.ConfigManager.AnnouncementConfig; - var key = SelectedMenuItem.Content.ToString(); + var accepted = config.Accepted; - if (key is null) return; + if (SelectedMenuItem is null || accepted is null) return; - if (!Readed.Contains(key)) - Readed.Add(key); + var key = SelectedMenuItem.Content!.ToString(); - var ConfigFilePath = GlobalInfo.AnnouncementsJsonPath.GetFullPath(); + if (key is null) return; - var options = new JsonSerializerOptions() - { - WriteIndented = true, - IncludeFields = true, - }; + if (!accepted.Contains(key)) + accepted.Add(key); - await File.WriteAllTextAsync( - ConfigFilePath, - JsonSerializer.Serialize(Readed, options) - ); + config.Save(config.ConfigFileLocation!); var finded = false; @@ -70,9 +58,11 @@ await File.WriteAllTextAsync( } }); - ConfirmReceivedAllCommand = ReactiveCommand.Create(async () => + ConfirmReceivedAllCommand = ReactiveCommand.Create(() => { - if (Readed is null) return; + var config = Instances.ConfigManager.AnnouncementConfig; + + var accepted = config.Accepted; var navView = Window?.AnouncementsNavigationView; @@ -84,38 +74,32 @@ await File.WriteAllTextAsync( if (key is null) continue; - if (!Readed.Contains(key)) - Readed.Add(key); + if (!accepted.Contains(key)) + accepted.Add(key); } - var ConfigFilePath = GlobalInfo.AnnouncementsJsonPath.GetFullPath(); - - var options = new JsonSerializerOptions() - { - WriteIndented = true, - IncludeFields = true, - }; - - await File.WriteAllTextAsync( - ConfigFilePath, - JsonSerializer.Serialize(Readed, options) - ); + config.Save(config.ConfigFileLocation!); Window?.Close(); } }); } + public override void InitEvents() + { + + } + internal static double Window_Width { - get => ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Width; - set => ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Width = value; + get => AppConfig.Windows.AnnouncementWindow.Size.Width!.Value; + set => AppConfig.Windows.AnnouncementWindow.Size.Width = value; } internal static double Window_Height { - get => ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Height; - set => ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Height = value; + get => AppConfig.Windows.AnnouncementWindow.Size.Height!.Value; + set => AppConfig.Windows.AnnouncementWindow.Size.Height = value; } private NavigationViewItem? selectedMenuItem; @@ -157,7 +141,7 @@ internal string Markdown } } - private Dictionary sources = new(); + private Dictionary sources = []; internal Dictionary Sources { @@ -185,9 +169,7 @@ internal Dictionary Sources internal AnouncementsWindow? Window { get; set; } - internal List? Readed { get; set; } - - internal ReactiveCommand? ConfirmReceivedCommand { get; set; } + internal ReactiveCommand? ConfirmReceivedCommand { get; set; } - internal ReactiveCommand? ConfirmReceivedAllCommand { get; set; } + internal ReactiveCommand? ConfirmReceivedAllCommand { get; set; } } diff --git a/KitX Dashboard/ViewModels/AppViewModel.cs b/KitX Dashboard/ViewModels/AppViewModel.cs index fddcf1dd..b659e315 100644 --- a/KitX Dashboard/ViewModels/AppViewModel.cs +++ b/KitX Dashboard/ViewModels/AppViewModel.cs @@ -1,20 +1,17 @@ -using Avalonia; -using Avalonia.Controls; -using KitX.Dashboard.Data; +using Avalonia.Controls; using KitX.Dashboard.Managers; using KitX.Dashboard.Services; +using KitX.Dashboard.Views; using ReactiveUI; -using System.ComponentModel; using System.Reactive; using System.Reflection; using System.Text; +using System.Threading.Tasks; namespace KitX.Dashboard.ViewModels; internal class AppViewModel : ViewModelBase { - public new event PropertyChangedEventHandler? PropertyChanged; - public AppViewModel() { InitCommands(); @@ -24,11 +21,11 @@ public AppViewModel() UpdateTrayIconText(); } - private void InitCommands() + public override void InitCommands() { TrayIconClickedCommand = ReactiveCommand.Create(() => { - var win = Instances.MainWindow; + var win = ViewInstances.MainWindow; if (win?.WindowState == WindowState.Minimized) win.WindowState = WindowState.Normal; @@ -37,71 +34,100 @@ private void InitCommands() win?.Activate(); - ConfigManager.AppConfig.Windows.MainWindow.IsHidden = false; + Instances.ConfigManager.AppConfig.Windows.MainWindow.IsHidden = false; + + SaveAppConfigChanges(); + }); + + ViewLatestAnnouncementsCommand = ReactiveCommand.Create(async () => + { + await AnouncementManager.CheckNewAnnouncements(); + }); + + PluginLauncherCommand = ReactiveCommand.Create(() => + { + ViewInstances.PluginsLaunchWindow ??= new(); + + var win = ViewInstances.PluginsLaunchWindow; + + if (win.IsVisible) + { + win.Hide(); + + return; + } - EventService.Invoke(nameof(EventService.ConfigSettingsChanged)); + win.Show(); + + win.Activate(); }); ExitCommand = ReactiveCommand.Create(() => { - GlobalInfo.Exiting = true; + ViewInstances.DeviceCards.Clear(); + + ViewInstances.PluginInfos.Clear(); + + ConstantTable.Exiting = true; EventService.Invoke(nameof(EventService.OnExiting)); - var win = Instances.MainWindow; + var win = ViewInstances.MainWindow; win?.Close(); }); } - private static void InitEvents() + public override void InitEvents() { - Instances.DeviceCards.CollectionChanged += - (_, _) => UpdateTrayIconText(); + ViewInstances.DeviceCards.CollectionChanged += (_, _) => UpdateTrayIconText(); - Instances.PluginCards.CollectionChanged += - (_, _) => UpdateTrayIconText(); - } + ViewInstances.PluginInfos.CollectionChanged += (_, _) => UpdateTrayIconText(); - private static void UpdateTrayIconText() - { - if (Application.Current is not null) - Application.Current.Resources[nameof(TrayIconText)] = TrayIconText; + EventService.DevicesServerPortChanged += UpdateTrayIconText; + + EventService.DevicesServerPortChanged += UpdateTrayIconText; } - internal static string TrayIconText + private void UpdateTrayIconText() { - get - { - var sb = new StringBuilder(); - - sb.AppendLine( - FetchStringFromResource(Application.Current, "Text_MainWindow_Title") ?? "KitX" - ); - - sb.AppendLine($"v{Assembly.GetEntryAssembly()?.GetName().Version}"); - - sb.AppendLine(); - - sb.AppendLine( - $"{Instances.DeviceCards.Count} " + - $"{FetchStringFromResource(Application.Current, "Text_Device_Tip_Detected")}" - ); - - sb.AppendLine( - $"{Instances.PluginCards.Count} " + - $"{FetchStringFromResource(Application.Current, "Text_Lib_Tip_Connected")}" - ); - - sb.AppendLine(); + var sb = new StringBuilder() + .AppendLine(Translate("Text_MainWindow_Title") ?? "KitX") + .AppendLine($"v{Assembly.GetEntryAssembly()?.GetName().Version}") + .AppendLine() + .Append(Translate("Text_Settings_Performence_Web_DevicesServerPort")) + .AppendLine(": " + ConstantTable.DevicesServerPort) + .Append(Translate("Text_Settings_Performence_Web_PluginsServerPort")) + .AppendLine(": " + ConstantTable.PluginsServerPort) + .AppendLine() + .Append(ViewInstances.DeviceCards.Count + " ") + .AppendLine(Translate("Text_Device_Tip_Detected")) + .Append(ViewInstances.PluginInfos.Count + " ") + .AppendLine(Translate("Text_Lib_Tip_Connected")) + .AppendLine() + .Append("Hello, World!") + ; + + TrayIconText = sb.ToString(); + } - sb.Append("Hello, World!"); + internal string trayIconText = ""; - return sb.ToString(); - } + internal string TrayIconText + { + get => trayIconText; + set => this.RaiseAndSetIfChanged( + ref trayIconText, + value, + nameof(TrayIconText) + ); } internal ReactiveCommand? TrayIconClickedCommand { get; set; } + internal ReactiveCommand? ViewLatestAnnouncementsCommand { get; set; } + internal ReactiveCommand? ExitCommand { get; set; } + + internal ReactiveCommand? PluginLauncherCommand { get; set; } } diff --git a/KitX Dashboard/ViewModels/DebugWindowViewModel.cs b/KitX Dashboard/ViewModels/DebugWindowViewModel.cs new file mode 100644 index 00000000..482c867a --- /dev/null +++ b/KitX Dashboard/ViewModels/DebugWindowViewModel.cs @@ -0,0 +1,80 @@ +using ReactiveUI; +using System.Reactive; +using AvaloniaEdit.Document; +using KitX.Dashboard.Services; +using System.Threading.Tasks; +using Avalonia.Threading; +using System.Threading; + +namespace KitX.Dashboard.ViewModels; + +internal class DebugWindowViewModel : ViewModelBase +{ + private CancellationTokenSource? _cancellationTokenSource; + + public DebugWindowViewModel() + { + InitCommands(); + + InitEvents(); + } + + public override void InitCommands() + { + SubmitCodesCommand = ReactiveCommand.Create(SubmitCodes); + + CancelExecutionCommand = ReactiveCommand.Create(() => _cancellationTokenSource?.Cancel()); + } + + public override void InitEvents() + { + + } + + internal void SubmitCodes(IDocument doc) + { + IsExecuting = true; + + var code = doc.Text; + + var tokenSource = new CancellationTokenSource(); + + _cancellationTokenSource = tokenSource; + + Task.Run(async () => + { + var result = await DebugService.ExecuteCodesAsync(code, tokenSource.Token); + + tokenSource.Dispose(); + + _cancellationTokenSource = null; + + Dispatcher.UIThread.Invoke(() => + { + ExecutionResult = result ?? string.Empty; + + IsExecuting = false; + }); + }); + } + + private string _executionResult = string.Empty; + + public string ExecutionResult + { + get => _executionResult; + set => this.RaiseAndSetIfChanged(ref _executionResult, value); + } + + private bool _isExecuting; + + public bool IsExecuting + { + get => _isExecuting; + set => this.RaiseAndSetIfChanged(ref _isExecuting, value); + } + + internal ReactiveCommand? SubmitCodesCommand { get; set; } + + internal ReactiveCommand? CancelExecutionCommand { get; set; } +} diff --git a/KitX Dashboard/ViewModels/MainWindowViewModel.cs b/KitX Dashboard/ViewModels/MainWindowViewModel.cs index e481a321..8b3da49c 100644 --- a/KitX Dashboard/ViewModels/MainWindowViewModel.cs +++ b/KitX Dashboard/ViewModels/MainWindowViewModel.cs @@ -1,108 +1,45 @@ -using Avalonia; -using Avalonia.Controls; -using KitX.Dashboard.Data; -using KitX.Dashboard.Managers; -using KitX.Dashboard.Services; -using KitX.Dashboard.Views; -using ReactiveUI; -using System.ComponentModel; -using System.Reactive; -using System.Reflection; -using System.Text; - -namespace KitX.Dashboard.ViewModels; - -internal class MainWindowViewModel : ViewModelBase -{ - public new event PropertyChangedEventHandler? PropertyChanged; - - public MainWindowViewModel() - { - InitCommands(); - - InitEvents(); - } - - private void InitCommands() - { - TrayIconClickedCommand = ReactiveCommand.Create(mainWindow => - { - var win = mainWindow as MainWindow; - - if (win?.WindowState == WindowState.Minimized) - win.WindowState = WindowState.Normal; - - win?.Show(); - - win?.Activate(); - - ConfigManager.AppConfig.Windows.MainWindow.IsHidden = false; - - EventService.Invoke(nameof(EventService.ConfigSettingsChanged)); - }); - - ExitCommand = ReactiveCommand.Create(mainWindow => - { - GlobalInfo.Exiting = true; - - EventService.Invoke(nameof(EventService.OnExiting)); - - var win = mainWindow as MainWindow; - - win?.Close(); - }); - - RefreshGreetingCommand = ReactiveCommand.Create(mainWindow => - { - var win = mainWindow as MainWindow; - - win?.UpdateGreetingText(); - }); - } - - private void InitEvents() - { - Instances.DeviceCards.CollectionChanged += (_, _) => - { - PropertyChanged?.Invoke(this, new(nameof(TrayIconText))); - }; - } - - internal static string TrayIconText - { - get - { - var sb = new StringBuilder(); - - sb.AppendLine( - FetchStringFromResource(Application.Current, "Text_MainWindow_Title") ?? "KitX" - ); - - sb.AppendLine($"v{Assembly.GetEntryAssembly()?.GetName().Version}"); - - sb.AppendLine(); - - sb.AppendLine( - $"{Instances.DeviceCards.Count} " + - $"{FetchStringFromResource(Application.Current, "Text_Device_Tip_Detected")}" - ); - - sb.AppendLine( - $"{Instances.PluginCards.Count} " + - $"{FetchStringFromResource(Application.Current, "Text_Lib_Tip_Connected")}" - ); - - sb.AppendLine(); - - sb.Append("Hello, World!"); - - return sb.ToString(); - } - } - - internal ReactiveCommand? TrayIconClickedCommand { get; set; } - - internal ReactiveCommand? ExitCommand { get; set; } - - internal ReactiveCommand? RefreshGreetingCommand { get; set; } -} +using KitX.Dashboard.Configuration; +using KitX.Dashboard.Views; +using ReactiveUI; +using System.Reactive; + +namespace KitX.Dashboard.ViewModels; + +internal class MainWindowViewModel : ViewModelBase +{ + public MainWindowViewModel() + { + InitCommands(); + + InitEvents(); + } + + public override void InitCommands() + { + RefreshGreetingCommand = ReactiveCommand.Create(mainWindow => + { + var win = mainWindow as MainWindow; + + win?.UpdateGreetingText(); + }); + } + + public override void InitEvents() + { + + } + + internal static double Window_Width + { + get => AppConfig.Windows.MainWindow.Size.Width!.Value; + set => AppConfig.Windows.MainWindow.Size.Width = value; + } + + internal static double Window_Height + { + get => AppConfig.Windows.MainWindow.Size.Height!.Value; + set => AppConfig.Windows.MainWindow.Size.Height = value; + } + + internal ReactiveCommand? RefreshGreetingCommand { get; set; } +} diff --git a/KitX Dashboard/ViewModels/Pages/AccountPageViewModel.cs b/KitX Dashboard/ViewModels/Pages/AccountPageViewModel.cs index 8cad704a..40554fde 100644 --- a/KitX Dashboard/ViewModels/Pages/AccountPageViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/AccountPageViewModel.cs @@ -6,4 +6,8 @@ public AccountPageViewModel() { } + + public override void InitCommands() => throw new System.NotImplementedException(); + + public override void InitEvents() => throw new System.NotImplementedException(); } diff --git a/KitX Dashboard/ViewModels/Pages/Controls/DevelopingViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/DevelopingViewModel.cs index f2aecd9d..cc1e2329 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/DevelopingViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/DevelopingViewModel.cs @@ -6,4 +6,8 @@ public DevelopingViewModel() { } + + public override void InitCommands() => throw new System.NotImplementedException(); + + public override void InitEvents() => throw new System.NotImplementedException(); } diff --git a/KitX Dashboard/ViewModels/Pages/Controls/DeviceCardViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/DeviceCardViewModel.cs index 23430906..31ab3304 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/DeviceCardViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/DeviceCardViewModel.cs @@ -1,6 +1,6 @@ using Avalonia; using Common.BasicHelper.Utils.Extensions; -using KitX.Web.Rules; +using KitX.Shared.Device; using Material.Icons; using System.ComponentModel; @@ -15,20 +15,20 @@ public DeviceCardViewModel() } - private DeviceInfoStruct deviceInfo = new(); + private DeviceInfo deviceInfo = new(); - internal DeviceInfoStruct DeviceInfo + internal DeviceInfo DeviceInfo { get => deviceInfo; set { deviceInfo = value; - DeviceName = DeviceInfo.DeviceName; - DeviceMacAddress = DeviceInfo.DeviceMacAddress.IsNullOrWhiteSpace() + DeviceName = DeviceInfo.Device.DeviceName; + DeviceMacAddress = DeviceInfo.Device.MacAddress.IsNullOrWhiteSpace() ? - FetchStringFromResource(Application.Current, "Text_Device_NoMacAddress") + Translate("Text_Device_NoMacAddress") : - DeviceInfo.DeviceMacAddress + DeviceInfo.Device.MacAddress ; LastOnlineTime = DeviceInfo.SendTime.ToLocalTime().ToString("yyyy.MM.dd HH:mm:ss"); DeviceVersion = DeviceInfo.DeviceOSVersion; @@ -48,12 +48,12 @@ internal DeviceInfoStruct DeviceInfo OperatingSystems.IoT => MaterialIconKind.Chip, _ => MaterialIconKind.QuestionMarkCircle, }; - IPv4 = $"{DeviceInfo.IPv4}:{DeviceInfo.PluginServerPort}"; - IPv6 = DeviceInfo.IPv6; + IPv4 = $"{DeviceInfo.Device.IPv4}:{DeviceInfo.PluginsServerPort}"; + IPv6 = DeviceInfo.Device.IPv6; PluginsCount = DeviceInfo.PluginsCount.ToString(); DeviceControlStatus = DeviceInfo.IsMainDevice - ? FetchStringFromResource(Application.Current, "Text_Device_Type_Master") - : FetchStringFromResource(Application.Current, "Text_Device_Type_Slave"); + ? Translate("Text_Device_Type_Master") + : Translate("Text_Device_Type_Slave"); PropertyChanged?.Invoke( this, @@ -119,7 +119,11 @@ internal DeviceInfoStruct DeviceInfo internal string? DeviceServerAddress { get => deviceInfo.IsMainDevice - ? $"{deviceInfo.IPv4}:{deviceInfo.DeviceServerPort}" + ? $"{deviceInfo.Device.IPv4}:{deviceInfo.DevicesServerPort}" : null; } + + public override void InitCommands() => throw new System.NotImplementedException(); + + public override void InitEvents() => throw new System.NotImplementedException(); } diff --git a/KitX Dashboard/ViewModels/Pages/Controls/Home_ActivityLogViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/Home_ActivityLogViewModel.cs index 14a8c549..152fb331 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/Home_ActivityLogViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/Home_ActivityLogViewModel.cs @@ -1,6 +1,55 @@ -namespace KitX.Dashboard.ViewModels.Pages.Controls; +using Common.Activity; +using KitX.Dashboard.Managers; +using System.Collections.ObjectModel; +using System.ComponentModel; -internal class Home_ActivityLogViewModel : ViewModelBase +namespace KitX.Dashboard.ViewModels.Pages.Controls; + +internal class Home_ActivityLogViewModel : ViewModelBase, INotifyPropertyChanged { - internal double NoActivityLog_TipHeight { get; set; } = 200; + + public new event PropertyChangedEventHandler? PropertyChanged; + + internal static ObservableCollection Activities { get; set; } = []; + + private double noActivityLog_TipHeight = Activities.Count == 0 ? 200 : 0; + + internal double NoActivityLog_TipHeight + { + get => noActivityLog_TipHeight; + set + { + noActivityLog_TipHeight = value; + + PropertyChanged?.Invoke( + this, + new(nameof(NoActivityLog_TipHeight)) + ); + } + } + + public Home_ActivityLogViewModel() + { + InitCommands(); + + InitEvents(); + + Activities.Clear(); + + foreach (var item in ActivityManager.ReadActivities()) + Activities.Add(item); + } + + public override void InitCommands() + { + + } + + public override void InitEvents() + { + Activities.CollectionChanged += (_, _) => + { + NoActivityLog_TipHeight = Activities.Count == 0 ? 200 : 0; + }; + } } diff --git a/KitX Dashboard/ViewModels/Pages/Controls/Home_CountViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/Home_CountViewModel.cs index 0615c753..e4409651 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/Home_CountViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/Home_CountViewModel.cs @@ -14,30 +14,30 @@ internal class Home_CountViewModel : ViewModelBase, INotifyPropertyChanged private double noCount_TipHeight = 200; - private List use_xAxes = new() - { + private List use_xAxes = + [ new Axis { Labeler = Labelers.Default } - }; + ]; - private List use_yAxes = new() - { + private List use_yAxes = + [ new Axis { Labeler = (value) => $"{value} h" } - }; + ]; private ISeries[] useSeries = - { + [ new LineSeries { Values = new double[] { 2, 1, 3, 5, 3, 4, 6 }, Fill = null } - }; + ]; public Home_CountViewModel() { @@ -48,7 +48,9 @@ public Home_CountViewModel() NoCount_TipHeight = Use_Series.Length == 0 ? 200 : 0; } - private void InitEvents() + public override void InitCommands() => throw new System.NotImplementedException(); + + public override void InitEvents() { EventService.UseStatisticsChanged += RecoveryUseCount; } @@ -57,25 +59,23 @@ internal void RecoveryUseCount() { var use = StatisticsManager.UseStatistics; - Use_XAxes = new() - { + Use_XAxes = + [ new Axis { Labels = use?.Keys.ToList() } - }; + ]; - Use_Series = new ISeries[] - { + Use_Series = + [ new LineSeries { Values = use?.Values.ToArray(), Fill = null, - TooltipLabelFormatter = chartpoint => - $"{use?.Keys.ToArray()[(int)chartpoint.SecondaryValue]}: " + - $"{chartpoint.PrimaryValue} h" + XToolTipLabelFormatter = x => $"{use?.Keys.ToArray()[(int)x.Coordinate.SecondaryValue]}: {x.Coordinate.PrimaryValue} h" } - }; + ]; } internal double NoCount_TipHeight @@ -93,17 +93,17 @@ internal double NoCount_TipHeight internal bool UseAreaExpanded { - get => ConfigManager.AppConfig.Pages.Home.UseAreaExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Home.UseAreaExpanded; set { - ConfigManager.AppConfig.Pages.Home.UseAreaExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Home.UseAreaExpanded = value; PropertyChanged?.Invoke( this, new(nameof(UseAreaExpanded)) ); - EventService.Invoke(nameof(EventService.ConfigSettingsChanged)); + SaveAppConfigChanges(); } } diff --git a/KitX Dashboard/ViewModels/Pages/Controls/Home_RecentUseViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/Home_RecentUseViewModel.cs index 20c4951a..b8a8f483 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/Home_RecentUseViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/Home_RecentUseViewModel.cs @@ -1,15 +1,21 @@ -using KitX.Dashboard.Views.Pages.Controls; +using KitX.Dashboard.Models; +using KitX.Dashboard.Views; using System.Collections.ObjectModel; namespace KitX.Dashboard.ViewModels.Pages.Controls; -internal class Home_RecentUseViewModel : ViewModelBase +internal class Home_RecentUseViewModel : ViewModelBase, IView { + public Home_RecentUseViewModel() + { + + } public double NoRecent_TipHeight { get; set; } = 200; - /// - /// 插件卡片集合 - /// - public ObservableCollection RecentPluginCards { get; } = new(); + public ObservableCollection RecentPlugins { get; } = []; + + public override void InitCommands() => throw new System.NotImplementedException(); + + public override void InitEvents() => throw new System.NotImplementedException(); } diff --git a/KitX Dashboard/ViewModels/Pages/Controls/PluginBarViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/PluginBarViewModel.cs index 51c7ff27..50bae5c4 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/PluginBarViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/PluginBarViewModel.cs @@ -1,10 +1,9 @@ using Avalonia.Controls; using Avalonia.Media.Imaging; using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; -using KitX.Dashboard.Managers; using KitX.Dashboard.Models; using KitX.Dashboard.Network; +using KitX.Dashboard.Network.DevicesNetwork; using KitX.Dashboard.Services; using KitX.Dashboard.Views; using KitX.Dashboard.Views.Pages.Controls; @@ -30,17 +29,17 @@ public PluginBarViewModel() InitEvents(); } - internal void InitCommands() + public override void InitCommands() { ViewDetailsCommand = ReactiveCommand.Create(() => { - if (PluginDetail is not null && Instances.MainWindow is not null) + if (PluginDetail is not null && ViewInstances.MainWindow is not null) new PluginDetailWindow() { WindowStartupLocation = WindowStartupLocation.CenterOwner } - .SetPluginStruct(PluginDetail.PluginDetails) - .Show(Instances.MainWindow); + .SetPluginInfo(PluginDetail.PluginDetails) + .Show(ViewInstances.MainWindow); }); RemoveCommand = ReactiveCommand.Create(() => @@ -49,7 +48,7 @@ internal void InitCommands() { PluginBars?.Remove(PluginBar); - PluginsNetwork.RequireRemovePlugin(PluginDetail); + //PluginsNetwork.RequireRemovePlugin(PluginDetail); } }); @@ -58,7 +57,7 @@ internal void InitCommands() if (PluginDetail is not null && PluginBar is not null) { PluginBars?.Remove(PluginBar); - PluginsNetwork.RequireDeletePlugin(PluginDetail); + //PluginsNetwork.RequireDeletePlugin(PluginDetail); } }); @@ -70,24 +69,24 @@ internal void InitCommands() { try { - var loaderName = PluginDetail?.RequiredLoaderStruct.LoaderName; - var loaderVersion = PluginDetail?.RequiredLoaderStruct.LoaderVersion; + var loaderName = PluginDetail?.RequiredLoaderInfo.LoaderName; + var loaderVersion = PluginDetail?.RequiredLoaderInfo.LoaderVersion; var pd = PluginDetail?.PluginDetails; var pluginPath = $"{PluginDetail?.InstallPath}/{pd?.RootStartupFileName}"; var pluginFile = pluginPath.GetFullPath(); - var connectStr = "" + - $"{DevicesDiscoveryServer.DefaultDeviceInfoStruct.IPv4}" + + var connectStr = "ws://" + + $"{DevicesDiscoveryServer.DefaultDeviceInfo.Device.IPv4}" + $":" + - $"{GlobalInfo.PluginServerPort}"; + $"{ConstantTable.PluginsServerPort}/"; if (PluginDetail is null) return; - if (PluginDetail.RequiredLoaderStruct.SelfLoad) + if (PluginDetail.RequiredLoaderInfo.SelfLoad) Process.Start(pluginFile, $"--connect {connectStr}"); else { - var loaderFile = $"{ConfigManager.AppConfig.Loaders.InstallPath}/" + + var loaderFile = $"{Instances.ConfigManager.AppConfig.Loaders.InstallPath}/" + $"{loaderName}/{loaderVersion}/{loaderName}"; if (OperatingSystem.IsWindows()) @@ -115,7 +114,7 @@ internal void InitCommands() }); } - internal void InitEvents() + public override void InitEvents() { EventService.LanguageChanged += () => { @@ -134,7 +133,7 @@ internal string? DisplayName if (PluginDetail is null) return null; return PluginDetail.PluginDetails.DisplayName.TryGetValue( - ConfigManager.AppConfig.App.AppLanguage, out var lang + Instances.ConfigManager.AppConfig.App.AppLanguage, out var lang ) ? lang : PluginDetail.PluginDetails.DisplayName.Values.GetEnumerator().Current; } } diff --git a/KitX Dashboard/ViewModels/Pages/Controls/PluginCardViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/PluginCardViewModel.cs deleted file mode 100644 index 59497939..00000000 --- a/KitX Dashboard/ViewModels/Pages/Controls/PluginCardViewModel.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Media.Imaging; -using KitX.Dashboard.Data; -using KitX.Dashboard.Managers; -using KitX.Dashboard.Views; -using KitX.Web.Rules; -using ReactiveUI; -using Serilog; -using System; -using System.IO; -using System.Reactive; - -namespace KitX.Dashboard.ViewModels.Pages.Controls; - -internal class PluginCardViewModel -{ - internal PluginStruct pluginStruct = new(); - - public PluginCardViewModel() - { - pluginStruct.IconInBase64 = GlobalInfo.KitXIconBase64; - - InitCommands(); - } - - private void InitCommands() - { - ViewDetailsCommand = ReactiveCommand.Create(() => - { - if (Instances.MainWindow is not null) - new PluginDetailWindow() - { - WindowStartupLocation = WindowStartupLocation.CenterOwner - } - .SetPluginStruct(pluginStruct) - .Show(Instances.MainWindow); - }); - } - - internal string DisplayName => pluginStruct.DisplayName.TryGetValue( - ConfigManager.AppConfig.App.AppLanguage, out var lang - ) ? lang : pluginStruct.DisplayName.Values.GetEnumerator().Current; - - internal string Version => pluginStruct.Version; - - internal string SimpleDescription => pluginStruct.SimpleDescription.TryGetValue( - ConfigManager.AppConfig.App.AppLanguage, out var lang - ) ? lang : pluginStruct.SimpleDescription.GetEnumerator().Current.Value; - - internal string IconInBase64 => pluginStruct.IconInBase64; - - internal Bitmap IconDisplay - { - get - { - var location = $"{nameof(PluginCardViewModel)}.{nameof(IconDisplay)}.getter"; - - try - { - var src = Convert.FromBase64String(IconInBase64); - - using var ms = new MemoryStream(src); - - return new(ms); - } - catch (Exception e) - { - Log.Warning( - e, - $"In {location}: " + - $"Failed to transform icon from base64 to byte[] " + - $"or create bitmap from `MemoryStream`. {e.Message}" - ); - - return App.DefaultIcon; - } - } - } - - internal ReactiveCommand? ViewDetailsCommand { get; set; } -} diff --git a/KitX Dashboard/ViewModels/Pages/Controls/Settings_AboutViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/Settings_AboutViewModel.cs index 1490fb56..4dc1d19c 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/Settings_AboutViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/Settings_AboutViewModel.cs @@ -1,5 +1,6 @@ using Common.BasicHelper.IO; using KitX.Dashboard.Managers; +using KitX.Dashboard.Views.Pages.Controls; using ReactiveUI; using System.ComponentModel; using System.Reactive; @@ -12,43 +13,41 @@ internal class Settings_AboutViewModel : ViewModelBase, INotifyPropertyChanged { public new event PropertyChangedEventHandler? PropertyChanged; - internal int clickCount = 0; + internal AppLogo? AppLogo { get; set; } internal Settings_AboutViewModel() { InitCommands(); } - private void InitCommands() + public override void InitCommands() { - AppNameButtonClickedCommand = ReactiveCommand.Create(() => ++clickCount); + AppNameButtonClickedCommand = ReactiveCommand.Create( + () => + { + if (AppLogo is not null) + { + if (AppLogo.IsAnimating) + AppLogo.StopAnimations(); + else + AppLogo.InitAnimations(); + } + } + ); LoadThirdPartyLicenseCommand = ReactiveCommand.Create(async () => { var license = await FileHelper.ReadAllAsync( - Data.GlobalInfo.ThirdPartLicenseFilePath + ConstantTable.ThirdPartLicenseFilePath ); ThirdPartyLicenseString = license; }); } - internal static string VersionText => $"v{Assembly.GetEntryAssembly()?.GetName().Version}"; - - internal bool easterEggsFounded = false; + public override void InitEvents() => throw new System.NotImplementedException(); - internal bool EasterEggsFounded - { - get => easterEggsFounded; - set - { - easterEggsFounded = value; - PropertyChanged?.Invoke( - this, - new(nameof(EasterEggsFounded)) - ); - } - } + internal static string VersionText => $"v{Assembly.GetEntryAssembly()?.GetName().Version}"; private string thirdPartyLicenseString = string.Empty; @@ -67,45 +66,45 @@ internal string ThirdPartyLicenseString public static bool AboutAreaExpanded { - get => ConfigManager.AppConfig.Pages.Settings.AboutAreaExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Settings.AboutAreaExpanded; set { - ConfigManager.AppConfig.Pages.Settings.AboutAreaExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Settings.AboutAreaExpanded = value; SaveAppConfigChanges(); } } public static bool AuthorsAreaExpanded { - get => ConfigManager.AppConfig.Pages.Settings.AuthorsAreaExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Settings.AuthorsAreaExpanded; set { - ConfigManager.AppConfig.Pages.Settings.AuthorsAreaExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Settings.AuthorsAreaExpanded = value; SaveAppConfigChanges(); } } public static bool LinksAreaExpanded { - get => ConfigManager.AppConfig.Pages.Settings.LinksAreaExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Settings.LinksAreaExpanded; set { - ConfigManager.AppConfig.Pages.Settings.LinksAreaExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Settings.LinksAreaExpanded = value; SaveAppConfigChanges(); } } public static bool ThirdPartyLicensesAreaExpanded { - get => ConfigManager.AppConfig.Pages.Settings.ThirdPartyLicensesAreaExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Settings.ThirdPartyLicensesAreaExpanded; set { - ConfigManager.AppConfig.Pages.Settings.ThirdPartyLicensesAreaExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Settings.ThirdPartyLicensesAreaExpanded = value; SaveAppConfigChanges(); } } - internal ReactiveCommand? AppNameButtonClickedCommand { get; set; } + internal ReactiveCommand? AppNameButtonClickedCommand { get; set; } internal ReactiveCommand? LoadThirdPartyLicenseCommand { get; set; } } diff --git a/KitX Dashboard/ViewModels/Pages/Controls/Settings_GeneralViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/Settings_GeneralViewModel.cs index 68e6214b..7b05d2d5 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/Settings_GeneralViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/Settings_GeneralViewModel.cs @@ -1,16 +1,11 @@ -using Common.BasicHelper.Utils.Extensions; -using Common.ExternalConsole; -using KitX.Dashboard.Managers; +using KitX.Dashboard.Managers; using KitX.Dashboard.Services; +using KitX.Dashboard.Views; using ReactiveUI; using Serilog; using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Diagnostics; -using System.IO; using System.Reactive; -using System.Threading; using System.Threading.Tasks; namespace KitX.Dashboard.ViewModels.Pages.Controls; @@ -19,10 +14,6 @@ internal class Settings_GeneralViewModel : ViewModelBase, INotifyPropertyChanged { public new event PropertyChangedEventHandler? PropertyChanged; - private static ExternalConsolesManager _manager = new(); - - private static int _consolesCount = 0; - internal Settings_GeneralViewModel() { InitCommands(); @@ -30,7 +21,7 @@ internal Settings_GeneralViewModel() InitEvents(); } - private void InitCommands() + public override void InitCommands() { ShowAnnouncementsInstantlyCommand = ReactiveCommand.Create(() => { @@ -47,115 +38,13 @@ private void InitCommands() }); }); - OpenDebugToolCommand = ReactiveCommand.Create(async () => + OpenDebugToolCommand = ReactiveCommand.Create(() => { - ++_consolesCount; - - var port = ConfigManager.AppConfig.Web.DebugServicesServerPort; - - if (!_manager.ServerLaunched) _manager = await _manager.LaunchServer(port); - - var name = $"KitX_DebugTool_{_consolesCount}"; - var console = _manager.Register(name); - - new Thread(() => - { - try - { - ProcessStartInfo psi = new() - { - FileName = Path.GetFullPath($"./Common.ExternalConsole.ExternalConsole" + - $"{(OperatingSystem.IsWindows() ? ".exe" : "")}"), - Arguments = $"--port {port} --name {name}", - CreateNoWindow = false, - UseShellExecute = true, - }; - var process = new Process - { - StartInfo = psi - }; - process.Start(); - - var keepWorking = true; - var messages2Send = new Queue() - .Push(@"|^disable_debug|") - ; - - async void Reader(StreamReader reader) - { - try - { - while (keepWorking) - { - var message = await reader.ReadLineAsync(); - - switch (message) - { - case null: - continue; - case @"|^console_exit|": - keepWorking = false; - break; - case "": - continue; - default: - if (message.Equals(string.Empty)) continue; - - var result = DebugService.ExecuteCommand(message); - messages2Send.Enqueue(result ?? "No this command."); - break; - } - } - } - catch (Exception ex) - { - console.Dispose(); - - var location = $"{nameof(Settings_UpdateViewModel)}.{nameof(OpenDebugToolCommand)}"; - Log.Warning(ex, $"In {location}: {ex.Message}"); - } - } - - async void Writer(StreamWriter writer) - { - try - { - while (keepWorking) - { - if (messages2Send.Count > 0) - { - await writer.WriteLineAsync( - messages2Send.Dequeue() - .Replace("\r\n", "\n") - .Replace("\n", "|^new_line|") - ); - await writer.FlushAsync(); - } - } - } - catch (Exception ex) - { - await writer.DisposeAsync(); - console.Dispose(); - - var location = $"{nameof(Settings_GeneralViewModel)}.{nameof(OpenDebugToolCommand)}"; - Log.Warning(ex, $"In {location}: {ex.Message}"); - } - } - - console.HandleMessages(Reader, Writer); - } - catch (Exception ex) - { - console.Dispose(); - var location = $"{nameof(Settings_GeneralViewModel)}.{nameof(OpenDebugToolCommand)}"; - Log.Error(ex, $"In {location}: {ex.Message}"); - } - }).Start(); + ViewInstances.ShowWindow(new DebugWindow(), ViewInstances.MainWindow); }); } - private void InitEvents() + public override void InitEvents() { EventService.DevelopSettingsChanged += () => PropertyChanged?.Invoke( this, @@ -167,45 +56,45 @@ private void InitEvents() internal static string LocalPluginsFileDirectory { - get => ConfigManager.AppConfig.App.LocalPluginsFileFolder; + get => Instances.ConfigManager.AppConfig.App.LocalPluginsFileFolder; set { - ConfigManager.AppConfig.App.LocalPluginsFileFolder = value; + Instances.ConfigManager.AppConfig.App.LocalPluginsFileFolder = value; SaveAppConfigChanges(); } } internal static string LocalPluginsDataDirectory { - get => ConfigManager.AppConfig.App.LocalPluginsDataFolder; + get => Instances.ConfigManager.AppConfig.App.LocalPluginsDataFolder; set { - ConfigManager.AppConfig.App.LocalPluginsDataFolder = value; + Instances.ConfigManager.AppConfig.App.LocalPluginsDataFolder = value; SaveAppConfigChanges(); } } internal static int ShowAnnouncementsStatus { - get => ConfigManager.AppConfig.App.ShowAnnouncementWhenStart ? 0 : 1; + get => Instances.ConfigManager.AppConfig.App.ShowAnnouncementWhenStart ? 0 : 1; set { - ConfigManager.AppConfig.App.ShowAnnouncementWhenStart = value == 0; + Instances.ConfigManager.AppConfig.App.ShowAnnouncementWhenStart = value == 0; SaveAppConfigChanges(); } } internal static bool DeveloperSettingEnabled { - get => ConfigManager.AppConfig.App.DeveloperSetting; + get => Instances.ConfigManager.AppConfig.App.DeveloperSetting; } internal static int DeveloperSettingStatus { - get => ConfigManager.AppConfig.App.DeveloperSetting ? 0 : 1; + get => Instances.ConfigManager.AppConfig.App.DeveloperSetting ? 0 : 1; set { - ConfigManager.AppConfig.App.DeveloperSetting = value == 0; + Instances.ConfigManager.AppConfig.App.DeveloperSetting = value == 0; EventService.Invoke(nameof(EventService.DevelopSettingsChanged)); SaveAppConfigChanges(); } @@ -213,5 +102,5 @@ internal static int DeveloperSettingStatus internal ReactiveCommand? ShowAnnouncementsInstantlyCommand { get; set; } - internal ReactiveCommand? OpenDebugToolCommand { get; set; } + internal ReactiveCommand? OpenDebugToolCommand { get; set; } } diff --git a/KitX Dashboard/ViewModels/Pages/Controls/Settings_PerformenceViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/Settings_PerformenceViewModel.cs index aec470b2..af480f9e 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/Settings_PerformenceViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/Settings_PerformenceViewModel.cs @@ -1,7 +1,6 @@ using Avalonia; using Avalonia.Threading; using Common.BasicHelper.Utils.Extensions; -using KitX.Dashboard.Data; using KitX.Dashboard.Managers; using KitX.Dashboard.Models; using KitX.Dashboard.Names; @@ -13,7 +12,6 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; -using System.Linq; using System.Reactive; using System.Text; using System.Threading.Tasks; @@ -31,7 +29,7 @@ internal Settings_PerformenceViewModel() InitEvents(); } - private void InitCommands() + public override void InitCommands() { EmptyLogsCommand = ReactiveCommand.Create(() => { @@ -40,7 +38,7 @@ private void InitCommands() Task.Run(() => { var dir = new DirectoryInfo( - ConfigManager.AppConfig.Log.LogFilePath.GetFullPath() + Instances.ConfigManager.AppConfig.Log.LogFilePath.GetFullPath() ); foreach (var file in dir.GetFiles()) @@ -69,28 +67,28 @@ private void InitCommands() ); } - private void InitEvents() + public override void InitEvents() { EventService.LogConfigUpdated += () => { - var logdir = ConfigManager.AppConfig.Log.LogFilePath.GetFullPath(); + var logdir = Instances.ConfigManager.AppConfig.Log.LogFilePath.GetFullPath(); Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .WriteTo.File( $"{logdir}Log_.log", - outputTemplate: ConfigManager.AppConfig.Log.LogTemplate, + outputTemplate: Instances.ConfigManager.AppConfig.Log.LogTemplate, rollingInterval: RollingInterval.Hour, - fileSizeLimitBytes: ConfigManager.AppConfig.Log.LogFileSingleMaxSize, + fileSizeLimitBytes: Instances.ConfigManager.AppConfig.Log.LogFileSingleMaxSize, buffered: true, flushToDiskInterval: new( 0, 0, - ConfigManager.AppConfig.Log.LogFileFlushInterval + Instances.ConfigManager.AppConfig.Log.LogFileFlushInterval ), - restrictedToMinimumLevel: ConfigManager.AppConfig.Log.LogLevel, + restrictedToMinimumLevel: Instances.ConfigManager.AppConfig.Log.LogLevel, rollOnFileSizeLimit: true, - retainedFileCountLimit: ConfigManager.AppConfig.Log.LogFileMaxCount + retainedFileCountLimit: Instances.ConfigManager.AppConfig.Log.LogFileMaxCount ) .CreateLogger(); }; @@ -111,7 +109,7 @@ private void InitEvents() new(nameof(DevicesServerPort)) ); - EventService.PluginsServerPortChanged += () => PropertyChanged?.Invoke( + EventService.PluginsServerPortChanged += port => PropertyChanged?.Invoke( this, new(nameof(PluginsServerPort)) ); @@ -172,10 +170,10 @@ private void InitEvents() internal static double DelayedWebStartSeconds { - get => ConfigManager.AppConfig.Web.DelayStartSeconds; + get => Instances.ConfigManager.AppConfig.Web.DelayStartSeconds; set { - ConfigManager.AppConfig.Web.DelayStartSeconds = value; + Instances.ConfigManager.AppConfig.Web.DelayStartSeconds = value; SaveAppConfigChanges(); } } @@ -184,13 +182,13 @@ internal static double DelayedWebStartSeconds internal int PluginsServerPortType { - get => ConfigManager.AppConfig.Web.UserSpecifiedPluginsServerPort is null ? 0 : 1; + get => Instances.ConfigManager.AppConfig.Web.UserSpecifiedPluginsServerPort is null ? 0 : 1; set { if (value == 0) - ConfigManager.AppConfig.Web.UserSpecifiedPluginsServerPort = null; + Instances.ConfigManager.AppConfig.Web.UserSpecifiedPluginsServerPort = null; else - ConfigManager.AppConfig.Web.UserSpecifiedPluginsServerPort = PluginsServerPort; + Instances.ConfigManager.AppConfig.Web.UserSpecifiedPluginsServerPort = PluginsServerPort; PropertyChanged?.Invoke( this, @@ -203,22 +201,22 @@ internal int PluginsServerPortType internal static int PluginsServerPort { - get => GlobalInfo.PluginServerPort; + get => ConstantTable.PluginsServerPort; set { if (value >= 0 && value <= 65535) - ConfigManager.AppConfig.Web.UserSpecifiedPluginsServerPort = value; + Instances.ConfigManager.AppConfig.Web.UserSpecifiedPluginsServerPort = value; } } - internal static int DevicesServerPort => GlobalInfo.DeviceServerPort; + internal static int DevicesServerPort => ConstantTable.DevicesServerPort; internal static string LocalIPFilter { - get => ConfigManager.AppConfig.Web.IPFilter; + get => Instances.ConfigManager.AppConfig.Web.IPFilter; set { - ConfigManager.AppConfig.Web.IPFilter = value; + Instances.ConfigManager.AppConfig.Web.IPFilter = value; SaveAppConfigChanges(); } } @@ -227,7 +225,7 @@ internal static string AcceptedNetworkInterfacesNames { get { - var userPointed = ConfigManager.AppConfig.Web.AcceptedNetworkInterfaces; + var userPointed = Instances.ConfigManager.AppConfig.Web.AcceptedNetworkInterfaces; if (userPointed is null) return "Auto"; @@ -237,38 +235,36 @@ internal static string AcceptedNetworkInterfacesNames set { if (value.ToLower().Equals("auto")) - ConfigManager.AppConfig.Web.AcceptedNetworkInterfaces = null; + Instances.ConfigManager.AppConfig.Web.AcceptedNetworkInterfaces = null; else { var userInput = value.Split(';'); - ConfigManager.AppConfig.Web.AcceptedNetworkInterfaces = userInput.ToList(); + Instances.ConfigManager.AppConfig.Web.AcceptedNetworkInterfaces = [.. userInput]; } } } - internal static ObservableCollection? AvailableNetworkInterfaces - => Instances.WebManager?.NetworkInterfaceRegistered; + internal static ObservableCollection? AvailableNetworkInterfaces => Instances.WebManager?.NetworkInterfaceRegistered; - internal static ObservableCollection? SelectedNetworkInterfaces { get; } - = new(); + internal static ObservableCollection? SelectedNetworkInterfaces { get; } = []; internal static int DevicesListRefreshDelay { - get => ConfigManager.AppConfig.Web.DevicesViewRefreshDelay; + get => Instances.ConfigManager.AppConfig.Web.DevicesViewRefreshDelay; set { - ConfigManager.AppConfig.Web.DevicesViewRefreshDelay = value; + Instances.ConfigManager.AppConfig.Web.DevicesViewRefreshDelay = value; SaveAppConfigChanges(); } } internal static int GreetingTextUpdateInterval { - get => ConfigManager.AppConfig.Windows.MainWindow.GreetingUpdateInterval; + get => Instances.ConfigManager.AppConfig.Windows.MainWindow.GreetingUpdateInterval; set { - ConfigManager.AppConfig.Windows.MainWindow.GreetingUpdateInterval = value; + Instances.ConfigManager.AppConfig.Windows.MainWindow.GreetingUpdateInterval = value; EventService.Invoke(nameof(EventService.GreetingTextIntervalUpdated)); SaveAppConfigChanges(); } @@ -276,53 +272,53 @@ internal static int GreetingTextUpdateInterval internal static bool WebRelatedAreaExpanded { - get => ConfigManager.AppConfig.Pages.Settings.WebRelatedAreaExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Settings.WebRelatedAreaExpanded; set { - ConfigManager.AppConfig.Pages.Settings.WebRelatedAreaExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Settings.WebRelatedAreaExpanded = value; SaveAppConfigChanges(); } } internal static bool WebRelatedAreaOfNetworkInterfacesExpanded { - get => ConfigManager.AppConfig.Pages.Settings.WebRelatedAreaOfNetworkInterfacesExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Settings.WebRelatedAreaOfNetworkInterfacesExpanded; set { - ConfigManager.AppConfig.Pages.Settings.WebRelatedAreaOfNetworkInterfacesExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Settings.WebRelatedAreaOfNetworkInterfacesExpanded = value; SaveAppConfigChanges(); } } internal static bool LogRelatedAreaExpanded { - get => ConfigManager.AppConfig.Pages.Settings.LogRelatedAreaExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Settings.LogRelatedAreaExpanded; set { - ConfigManager.AppConfig.Pages.Settings.LogRelatedAreaExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Settings.LogRelatedAreaExpanded = value; SaveAppConfigChanges(); } } internal static bool UpdateRelatedAreaExpanded { - get => ConfigManager.AppConfig.Pages.Settings.UpdateRelatedAreaExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Settings.UpdateRelatedAreaExpanded; set { - ConfigManager.AppConfig.Pages.Settings.UpdateRelatedAreaExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Settings.UpdateRelatedAreaExpanded = value; SaveAppConfigChanges(); } } internal static int LogFileSizeUsage - => (int)(ConfigManager.AppConfig.Log.LogFilePath.GetTotalSize() / 1000 / 1024); + => (int)(Instances.ConfigManager.AppConfig.Log.LogFilePath.GetTotalSize() / 1000 / 1024); internal static int LogFileSizeLimit { - get => (int)(ConfigManager.AppConfig.Log.LogFileSingleMaxSize / 1024 / 1024); + get => (int)(Instances.ConfigManager.AppConfig.Log.LogFileSingleMaxSize / 1024 / 1024); set { - ConfigManager.AppConfig.Log.LogFileSingleMaxSize = value * 1024 * 1024; + Instances.ConfigManager.AppConfig.Log.LogFileSingleMaxSize = value * 1024 * 1024; EventService.Invoke(nameof(EventService.LogConfigUpdated)); SaveAppConfigChanges(); } @@ -330,10 +326,10 @@ internal static int LogFileSizeLimit internal static int LogFileMaxCount { - get => ConfigManager.AppConfig.Log.LogFileMaxCount; + get => Instances.ConfigManager.AppConfig.Log.LogFileMaxCount; set { - ConfigManager.AppConfig.Log.LogFileMaxCount = value; + Instances.ConfigManager.AppConfig.Log.LogFileMaxCount = value; EventService.Invoke(nameof(EventService.LogConfigUpdated)); SaveAppConfigChanges(); } @@ -341,10 +337,10 @@ internal static int LogFileMaxCount internal static int LogFileFlushInterval { - get => ConfigManager.AppConfig.Log.LogFileFlushInterval; + get => Instances.ConfigManager.AppConfig.Log.LogFileFlushInterval; set { - ConfigManager.AppConfig.Log.LogFileFlushInterval = value; + Instances.ConfigManager.AppConfig.Log.LogFileFlushInterval = value; EventService.Invoke(nameof(EventService.LogConfigUpdated)); SaveAppConfigChanges(); } @@ -352,22 +348,18 @@ internal static int LogFileFlushInterval internal static int CheckerPerThreadFilesCountLimit { - get => ConfigManager.AppConfig.IO.UpdatingCheckPerThreadFilesCount; + get => Instances.ConfigManager.AppConfig.IO.UpdatingCheckPerThreadFilesCount; set { - ConfigManager.AppConfig.IO.UpdatingCheckPerThreadFilesCount = value; + Instances.ConfigManager.AppConfig.IO.UpdatingCheckPerThreadFilesCount = value; SaveAppConfigChanges(); } } - private static string GetLogLevelDisplayText(string key) => FetchStringFromResource( - Application.Current, - key, - prefix: "Text_Log_" - ) ?? string.Empty; + private static string GetLogLevelDisplayText(string key) => Translate(key, prefix: "Text_Log_") ?? string.Empty; - internal static List SupportedLogLevels { get; } = new() - { + internal static List SupportedLogLevels { get; } = + [ new() { LogEventLevel = Serilog.Events.LogEventLevel.Verbose, @@ -404,10 +396,10 @@ private static string GetLogLevelDisplayText(string key) => FetchStringFromResou LogLevelName = "Fatal", LogLevelDisplayName = GetLogLevelDisplayText("Fatal") }, - }; + ]; private SupportedLogLevel? _currentLogLevel = SupportedLogLevels.Find( - x => x.LogEventLevel == ConfigManager.AppConfig.Log.LogLevel + x => x.LogEventLevel == Instances.ConfigManager.AppConfig.Log.LogLevel ); internal SupportedLogLevel? CurrentLogLevel @@ -419,7 +411,7 @@ internal SupportedLogLevel? CurrentLogLevel if (value is not null) { - ConfigManager.AppConfig.Log.LogLevel = value.LogEventLevel; + Instances.ConfigManager.AppConfig.Log.LogLevel = value.LogEventLevel; EventService.Invoke(nameof(EventService.LogConfigUpdated)); diff --git a/KitX Dashboard/ViewModels/Pages/Controls/Settings_PersonaliseViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/Settings_PersonaliseViewModel.cs index 028c1466..f8268171 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/Settings_PersonaliseViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/Settings_PersonaliseViewModel.cs @@ -6,7 +6,6 @@ using Avalonia.Threading; using FluentAvalonia.Styling; using FluentAvalonia.UI.Media; -using KitX.Dashboard.Data; using KitX.Dashboard.Managers; using KitX.Dashboard.Models; using KitX.Dashboard.Services; @@ -35,7 +34,7 @@ internal Settings_PersonaliseViewModel() InitData(); } - private void InitCommands() + public override void InitCommands() { ColorConfirmedCommand = ReactiveCommand.Create(async () => { @@ -62,7 +61,7 @@ await Dispatcher.UIThread.InvokeAsync(() => ); }); - ConfigManager.AppConfig.App.ThemeColor = themeColor.ToHexString(); + Instances.ConfigManager.AppConfig.App.ThemeColor = themeColor.ToHexString(); SaveAppConfigChanges(); }); @@ -72,11 +71,11 @@ await Dispatcher.UIThread.InvokeAsync(() => MicaToolTipClosedCommand = ReactiveCommand.Create(() => MicaToolTipIsOpen = false); } - private void InitEvents() + public override void InitEvents() { EventService.DevelopSettingsChanged += () => { - MicaOpacityConfirmButtonVisibility = ConfigManager.AppConfig.App.DeveloperSetting; + MicaOpacityConfirmButtonVisibility = Instances.ConfigManager.AppConfig.App.DeveloperSetting; }; EventService.LanguageChanged += () => @@ -85,7 +84,7 @@ private void InitEvents() item.ThemeDisplayName = GetThemeDisplayText(item.ThemeName); _currentAppTheme = SupportedThemes.Find( - x => x.ThemeName.Equals(ConfigManager.AppConfig.App.Theme) + x => x.ThemeName.Equals(Instances.ConfigManager.AppConfig.App.Theme) ); PropertyChanged?.Invoke( @@ -99,7 +98,7 @@ private void InitData() { SupportedLanguages.Clear(); - foreach (var item in ConfigManager.AppConfig.App.SurpportLanguages) + foreach (var item in Instances.ConfigManager.AppConfig.App.SurpportLanguages) SupportedLanguages.Add(new SupportedLanguage() { LanguageCode = item.Key, @@ -107,7 +106,7 @@ private void InitData() }); LanguageSelected = SupportedLanguages.FindIndex( - x => x.LanguageCode.Equals(ConfigManager.AppConfig.App.AppLanguage) + x => x.LanguageCode.Equals(Instances.ConfigManager.AppConfig.App.AppLanguage) ); } @@ -126,14 +125,10 @@ internal Color2 ThemeColor set => themeColor = value; } - private static string GetThemeDisplayText(string key) => FetchStringFromResource( - Application.Current, - key, - prefix: "Text_Settings_Personalise_Theme_" - ) ?? string.Empty; + private static string GetThemeDisplayText(string key) => Translate(key, prefix: "Text_Settings_Personalise_Theme_") ?? string.Empty; - internal static List SupportedThemes { get; } = new() - { + internal static List SupportedThemes { get; } = + [ new() { ThemeName = FluentAvaloniaTheme.LightModeString, @@ -145,19 +140,14 @@ private static string GetThemeDisplayText(string key) => FetchStringFromResource ThemeDisplayName = GetThemeDisplayText(FluentAvaloniaTheme.DarkModeString), }, new() - { - ThemeName = FluentAvaloniaTheme.HighContrastModeString, - ThemeDisplayName = GetThemeDisplayText(FluentAvaloniaTheme.HighContrastModeString), - }, - new() { ThemeName = "Follow", ThemeDisplayName = GetThemeDisplayText("Follow"), } - }; + ]; private SupportedTheme? _currentAppTheme = SupportedThemes.Find( - x => x.ThemeName.Equals(ConfigManager.AppConfig.App.Theme) + x => x.ThemeName.Equals(Instances.ConfigManager.AppConfig.App.Theme) ); internal SupportedTheme? CurrentAppTheme @@ -169,11 +159,11 @@ internal SupportedTheme? CurrentAppTheme if (value is null) return; - ConfigManager.AppConfig.App.Theme = value.ThemeName; + Instances.ConfigManager.AppConfig.App.Theme = value.ThemeName; if (Application.Current is null) return; - Application.Current.RequestedThemeVariant ??= value.ThemeName == "Follow" ? ThemeVariant.Default : value.ThemeName switch + Application.Current.RequestedThemeVariant = value.ThemeName switch { "Light" => ThemeVariant.Light, "Dark" => ThemeVariant.Dark, @@ -186,13 +176,13 @@ internal SupportedTheme? CurrentAppTheme } } - internal List SupportedLanguages { get; } = new(); + internal List SupportedLanguages { get; } = []; internal static void LoadLanguage() { var location = $"{nameof(Settings_PersonaliseViewModel)}.{nameof(LoadLanguage)}"; - var lang = ConfigManager.AppConfig.App.AppLanguage; + var lang = Instances.ConfigManager.AppConfig.App.AppLanguage; if (Application.Current is null) return; @@ -202,8 +192,8 @@ internal static void LoadLanguage() Application.Current.Resources.MergedDictionaries.Add( AvaloniaRuntimeXamlLoader.Load( - File.ReadAllText($"{GlobalInfo.LanguageFilePath}/{lang}.axaml") - ) as ResourceDictionary ?? new() + File.ReadAllText($"{ConstantTable.LanguageFilePath}/{lang}.axaml") + ) as ResourceDictionary ?? [] ); } catch (Exception ex) @@ -229,7 +219,7 @@ internal int LanguageSelected { try { - ConfigManager.AppConfig.App.AppLanguage = SupportedLanguages[value].LanguageCode; + Instances.ConfigManager.AppConfig.App.AppLanguage = SupportedLanguages[value].LanguageCode; if (languageSelected != -1) LoadLanguage(); @@ -246,47 +236,37 @@ internal int LanguageSelected internal static bool MicaAreaExpanded { - get => ConfigManager.AppConfig.Pages.Settings.MicaAreaExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Settings.MicaAreaExpanded; set { - ConfigManager.AppConfig.Pages.Settings.MicaAreaExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Settings.MicaAreaExpanded = value; SaveAppConfigChanges(); } } internal static int MicaStatus { - get => ConfigManager.AppConfig.Windows.MainWindow.EnabledMica ? 0 : 1; + get => Instances.ConfigManager.AppConfig.Windows.MainWindow.EnabledMica ? 0 : 1; set { - ConfigManager.AppConfig.Windows.MainWindow.EnabledMica = value != 1; + Instances.ConfigManager.AppConfig.Windows.MainWindow.EnabledMica = value != 1; SaveAppConfigChanges(); } } - internal static double MicaOpacity - { - get => ConfigManager.AppConfig.Windows.MainWindow.MicaOpacity; - set - { - ConfigManager.AppConfig.Windows.MainWindow.MicaOpacity = value; - EventService.Invoke(nameof(EventService.MicaOpacityChanged)); - } - } - internal static bool MicaToolTipIsOpen { - get => ConfigManager.AppConfig.Pages.Settings.MicaToolTipIsOpen; + get => Instances.ConfigManager.AppConfig.Pages.Settings.MicaToolTipIsOpen; set { - ConfigManager.AppConfig.Pages.Settings.MicaToolTipIsOpen = value; + Instances.ConfigManager.AppConfig.Pages.Settings.MicaToolTipIsOpen = value; SaveAppConfigChanges(); } } internal bool MicaOpacityConfirmButtonVisibility { - get => ConfigManager.AppConfig.App.DeveloperSetting; + get => Instances.ConfigManager.AppConfig.App.DeveloperSetting; set => PropertyChanged?.Invoke( this, new(nameof(MicaOpacityConfirmButtonVisibility)) @@ -295,10 +275,10 @@ internal bool MicaOpacityConfirmButtonVisibility internal static bool PaletteAreaExpanded { - get => ConfigManager.AppConfig.Pages.Settings.PaletteAreaExpanded; + get => Instances.ConfigManager.AppConfig.Pages.Settings.PaletteAreaExpanded; set { - ConfigManager.AppConfig.Pages.Settings.PaletteAreaExpanded = value; + Instances.ConfigManager.AppConfig.Pages.Settings.PaletteAreaExpanded = value; SaveAppConfigChanges(); } } diff --git a/KitX Dashboard/ViewModels/Pages/Controls/Settings_UpdateViewModel.cs b/KitX Dashboard/ViewModels/Pages/Controls/Settings_UpdateViewModel.cs index 44bbd5a1..03906caa 100644 --- a/KitX Dashboard/ViewModels/Pages/Controls/Settings_UpdateViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/Controls/Settings_UpdateViewModel.cs @@ -4,11 +4,9 @@ using Common.BasicHelper.Utils.Extensions; using Common.Update.Checker; using KitX.Dashboard.Converters; -using KitX.Dashboard.Data; -using KitX.Dashboard.Managers; -using KitX.Dashboard.Network; +using KitX.Dashboard.Network.DevicesNetwork; using KitX.Dashboard.Services; -using KitX.Web.Rules; +using KitX.Shared.Device; using MsBox.Avalonia; using MsBox.Avalonia.Enums; using ReactiveUI; @@ -42,14 +40,14 @@ internal Settings_UpdateViewModel() InitEvents(); } - internal void InitCommands() + public override void InitCommands() { CheckUpdateCommand = ReactiveCommand.Create(CheckUpdate); UpdateCommand = ReactiveCommand.Create(Update); } - internal void InitEvents() + public override void InitEvents() { Components.CollectionChanged += (_, _) => { @@ -79,7 +77,7 @@ internal int CanUpdateCount internal static int ComponentsCount { get => Components.Count; } - internal static ObservableCollection Components { get; } = new(); + internal static ObservableCollection Components { get; } = []; private string? tip = string.Empty; @@ -107,7 +105,7 @@ internal string DiskUseStatus public static int UpdateChannel { - get => ConfigManager.AppConfig.Web.UpdateChannel switch + get => Instances.ConfigManager.AppConfig.Web.UpdateChannel switch { "stable" => 0, "beta" => 1, @@ -116,22 +114,19 @@ public static int UpdateChannel }; set { - ConfigManager.AppConfig.Web.UpdateChannel = value switch + Instances.ConfigManager.AppConfig.Web.UpdateChannel = value switch { 0 => "stable", 1 => "beta", 2 => "alpha", _ => "stable" }; - EventService.Invoke(nameof(EventService.ConfigSettingsChanged)); + + SaveAppConfigChanges(); } } - private static string GetUpdateTip(string key) => FetchStringFromResource( - Application.Current, - key, - prefix: "Text_Settings_Update_Tip_" - ) ?? string.Empty; + private static string GetUpdateTip(string key) => Translate(key, prefix: "Text_Settings_Update_Tip_") ?? string.Empty; private static async void DownloadNewComponent(string url, string to, HttpClient client) { @@ -160,11 +155,11 @@ private static string GetDisplaySize(long size) private Checker ScanComponents(string workbase) { var wd = workbase; - var ld = Path.GetFullPath(GlobalInfo.LanguageFilePath); + var ld = Path.GetFullPath(ConstantTable.LanguageFilePath); var checker = new Checker() .SetRootDirectory(wd) - .SetPerThreadFilesCount(ConfigManager.AppConfig.IO.UpdatingCheckPerThreadFilesCount) + .SetPerThreadFilesCount(Instances.ConfigManager.AppConfig.IO.UpdatingCheckPerThreadFilesCount) .SetTransHash2String(true) .AppendIgnoreFolder("Config") .AppendIgnoreFolder("Core") @@ -174,10 +169,10 @@ private Checker ScanComponents(string workbase) .AppendIgnoreFolder("Update") .AppendIgnoreFolder("Loaders") .AppendIgnoreFolder("Plugins") - .AppendIgnoreFolder(ConfigManager.AppConfig.App.LocalPluginsFileFolder) - .AppendIgnoreFolder(ConfigManager.AppConfig.App.LocalPluginsDataFolder); + .AppendIgnoreFolder(Instances.ConfigManager.AppConfig.App.LocalPluginsFileFolder) + .AppendIgnoreFolder(Instances.ConfigManager.AppConfig.App.LocalPluginsDataFolder); - foreach (var item in ConfigManager.AppConfig.App.SurpportLanguages) + foreach (var item in Instances.ConfigManager.AppConfig.App.SurpportLanguages) _ = checker.AppendIncludeFile($"{ld}/{item.Key}.axaml"); Tip = GetUpdateTip("Scan"); @@ -223,6 +218,13 @@ private void CalculateComponentsHash(Checker checker) _calculateFinished = true; } + private static readonly JsonSerializerOptions JsonSerializerOptions = new() + { + WriteIndented = true, + IncludeFields = true, + PropertyNamingPolicy = new UpdateHashNamePolicy(), + }; + private static async Task?> GetLatestComponentsAsync ( HttpClient client @@ -231,10 +233,10 @@ HttpClient client client.DefaultRequestHeaders.Accept.Clear(); // 清除请求头部 var link = "https://" + - ConfigManager.AppConfig.Web.UpdateServer + - ConfigManager.AppConfig.Web.UpdatePath.Replace( + Instances.ConfigManager.AppConfig.Web.UpdateServer + + Instances.ConfigManager.AppConfig.Web.UpdatePath.Replace( "%platform%", - DevicesDiscoveryServer.DefaultDeviceInfoStruct.DeviceOSType switch + DevicesDiscoveryServer.DefaultDeviceInfo.DeviceOSType switch { OperatingSystems.Windows => "win", OperatingSystems.Linux => "linux", @@ -242,22 +244,15 @@ HttpClient client _ => "" } ) + - $"{ConfigManager.AppConfig.Web.UpdateChannel}/" + - ConfigManager.AppConfig.Web.UpdateSource; + $"{Instances.ConfigManager.AppConfig.Web.UpdateChannel}/" + + Instances.ConfigManager.AppConfig.Web.UpdateSource; var json = await client.GetStringAsync(link); - var option = new JsonSerializerOptions() - { - WriteIndented = true, - IncludeFields = true, - PropertyNamingPolicy = new UpdateHashNamePolicy(), - }; - var latestComponents = JsonSerializer .Deserialize>( json, - option + JsonSerializerOptions ); return latestComponents; @@ -367,12 +362,12 @@ private void UpdateFrontendViewAfterCompare if (updatedComponents.ContainsKey(item.Name)) { item.CanUpdate = true; - item.Task = FetchStringFromResource(Application.Current, "Text_Public_Replace"); + item.Task = Translate("Text_Public_Replace"); } else if (tdeleteComponents.ContainsKey(item.Name)) { item.CanUpdate = true; - item.Task = FetchStringFromResource(Application.Current, "Text_Public_Delete"); + item.Task = Translate("Text_Public_Delete"); } } @@ -386,7 +381,7 @@ private void UpdateFrontendViewAfterCompare CanUpdate = true, MD5 = latestComponents[item.Key].Item1, SHA1 = latestComponents[item.Key].Item2, - Task = FetchStringFromResource(Application.Current, "Text_Public_Add"), + Task = Translate("Text_Public_Add"), Size = GetDisplaySize(item.Value) }); } @@ -436,25 +431,25 @@ ref HttpClient client //TODO: 下载有变更的文件 var downloadLinkBase = "https://" + - ConfigManager.AppConfig.Web.UpdateServer + - ConfigManager.AppConfig.Web.UpdateDownloadPath.Replace( + Instances.ConfigManager.AppConfig.Web.UpdateServer + + Instances.ConfigManager.AppConfig.Web.UpdateDownloadPath.Replace( "%platform%", - DevicesDiscoveryServer.DefaultDeviceInfoStruct.DeviceOSType switch + DevicesDiscoveryServer.DefaultDeviceInfo.DeviceOSType switch { OperatingSystems.Windows => "win", OperatingSystems.Linux => "linux", OperatingSystems.MacOS => "mac", _ => "" }) + - $"{ConfigManager.AppConfig.Web.UpdateChannel}/"; + $"{Instances.ConfigManager.AppConfig.Web.UpdateChannel}/"; - if (!Directory.Exists(GlobalInfo.UpdateSavePath.GetFullPath())) - Directory.CreateDirectory(GlobalInfo.UpdateSavePath.GetFullPath()); + if (!Directory.Exists(ConstantTable.UpdateSavePath.GetFullPath())) + Directory.CreateDirectory(ConstantTable.UpdateSavePath.GetFullPath()); foreach (var item in updatedComponents) DownloadNewComponent( $"{downloadLinkBase}{item.Key.Replace(@"\", "/")}", - $"{GlobalInfo.UpdateSavePath}{item}".GetFullPath(), + $"{ConstantTable.UpdateSavePath}{item}".GetFullPath(), client ); } diff --git a/KitX Dashboard/ViewModels/Pages/DevicePageViewModel.cs b/KitX Dashboard/ViewModels/Pages/DevicePageViewModel.cs index ca1e81e7..60b88c4c 100644 --- a/KitX Dashboard/ViewModels/Pages/DevicePageViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/DevicePageViewModel.cs @@ -1,4 +1,4 @@ -using KitX.Dashboard.Managers; +using KitX.Dashboard.Views; using KitX.Dashboard.Views.Pages.Controls; using ReactiveUI; using System.Collections.ObjectModel; @@ -19,39 +19,43 @@ public DevicePageViewModel() InitEvents(); } - private void InitCommands() + public override void InitCommands() { - - RestartDevicesServerCommand = ReactiveCommand.Create(() => + RestartDevicesServerCommand = ReactiveCommand.Create(async () => { - Instances.WebManager?.Restart( - restartAll: false, - restartDevicesServices: true, - restartDevicesDiscoveryServer: true, - restartPluginsServices: false, + if (Instances.WebManager is null) + return; + + await Instances.WebManager.RestartAsync( + new() + { + ClosePluginsServer = false, + RunPluginsServer = false + }, actionBeforeStarting: () => DeviceCards.Clear() ); }); - StopDevicesServerCommand = ReactiveCommand.Create(() => + StopDevicesServerCommand = ReactiveCommand.Create(async () => { - Task.Run(async () => - { - Instances.WebManager?.Stop( - stopAll: false, - stopDevicesServices: true, - stopDevicesDiscoveryServer: true, - stopPluginsServices: false - ); - - await Task.Delay(ConfigManager.AppConfig.Web.UDPSendFrequency + 200); - - DeviceCards.Clear(); - }); + if (Instances.WebManager is null) + return; + + await Instances.WebManager.CloseAsync( + new() + { + ClosePluginsServer = false, + RunPluginsServer = false + } + ); + + await Task.Delay(Instances.ConfigManager.AppConfig.Web.UdpSendFrequency + 200); + + DeviceCards.Clear(); }); } - private void InitEvents() + public override void InitEvents() { DeviceCards.CollectionChanged += (_, _) => { @@ -94,9 +98,9 @@ internal double NoDevice_TipHeight } } - internal static ObservableCollection DeviceCards => Instances.DeviceCards; + internal static ObservableCollection DeviceCards => ViewInstances.DeviceCards; - internal ReactiveCommand? RestartDevicesServerCommand { get; set; } + internal ReactiveCommand? RestartDevicesServerCommand { get; set; } - internal ReactiveCommand? StopDevicesServerCommand { get; set; } + internal ReactiveCommand? StopDevicesServerCommand { get; set; } } diff --git a/KitX Dashboard/ViewModels/Pages/HomePageViewModel.cs b/KitX Dashboard/ViewModels/Pages/HomePageViewModel.cs index 536447e7..3c631de5 100644 --- a/KitX Dashboard/ViewModels/Pages/HomePageViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/HomePageViewModel.cs @@ -17,7 +17,7 @@ public HomePageViewModel() InitCommands(); } - internal void InitCommands() + public override void InitCommands() { ResetToAutoCommand = ReactiveCommand.Create(() => { @@ -35,14 +35,16 @@ internal void InitCommands() }); } + public override void InitEvents() => throw new System.NotImplementedException(); + internal static bool IsPaneOpen { - get => ConfigManager.AppConfig.Pages.Home.IsNavigationViewPaneOpened; + get => Instances.ConfigManager.AppConfig.Pages.Home.IsNavigationViewPaneOpened; set { - ConfigManager.AppConfig.Pages.Home.IsNavigationViewPaneOpened = value; + Instances.ConfigManager.AppConfig.Pages.Home.IsNavigationViewPaneOpened = value; - EventService.Invoke(nameof(EventService.ConfigSettingsChanged)); + SaveAppConfigChanges(); } } @@ -58,10 +60,10 @@ internal static bool IsPaneOpen internal NavigationViewPaneDisplayMode NavigationViewPaneDisplayMode { - get => ConfigManager.AppConfig.Pages.Home.NavigationViewPaneDisplayMode; + get => Instances.ConfigManager.AppConfig.Pages.Home.NavigationViewPaneDisplayMode; set { - ConfigManager.AppConfig.Pages.Home.NavigationViewPaneDisplayMode = value; + Instances.ConfigManager.AppConfig.Pages.Home.NavigationViewPaneDisplayMode = value; PropertyChanged?.Invoke( this, @@ -73,7 +75,7 @@ internal NavigationViewPaneDisplayMode NavigationViewPaneDisplayMode new(nameof(FirstItemMargin)) ); - EventService.Invoke(nameof(EventService.ConfigSettingsChanged)); + SaveAppConfigChanges(); } } diff --git a/KitX Dashboard/ViewModels/Pages/LibPageViewModel.cs b/KitX Dashboard/ViewModels/Pages/LibPageViewModel.cs index 09e39824..77cf9a37 100644 --- a/KitX Dashboard/ViewModels/Pages/LibPageViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/LibPageViewModel.cs @@ -1,59 +1,63 @@ -using KitX.Dashboard.Views.Pages.Controls; +using Avalonia.Controls; +using KitX.Dashboard.Views; +using KitX.Shared.Plugin; +using ReactiveUI; using System.Collections.ObjectModel; -using System.ComponentModel; +using System.Reactive; namespace KitX.Dashboard.ViewModels.Pages; -internal class LibPageViewModel : ViewModelBase, INotifyPropertyChanged +internal class LibPageViewModel : ViewModelBase { - public new event PropertyChangedEventHandler? PropertyChanged; - public LibPageViewModel() { - PluginCards.CollectionChanged += (_, _) => + InitCommands(); + + InitEvents(); + } + + public override void InitCommands() + { + ViewDetailsCommand = ReactiveCommand.Create(info => + { + if (ViewInstances.MainWindow is not null) + new PluginDetailWindow() + { + WindowStartupLocation = WindowStartupLocation.CenterOwner + } + .SetPluginInfo(info) + .Show(ViewInstances.MainWindow); + }); + } + + public override void InitEvents() + { + PluginInfos.CollectionChanged += (_, args) => { - NoPlugins_TipHeight = PluginCards.Count == 0 ? 300 : 0; - PluginsCount = $"{PluginCards.Count}"; + NoPlugins_TipHeight = PluginInfos.Count == 0 ? 300 : 0; + PluginsCount = $"{PluginInfos.Count}"; }; } - public string pluginsCount = $"{PluginCards.Count}"; + public string pluginsCount = $"{PluginInfos.Count}"; public string PluginsCount { get => pluginsCount; - set - { - pluginsCount = value; - PropertyChanged?.Invoke( - this, - new(nameof(PluginsCount)) - ); - } + set => this.RaiseAndSetIfChanged(ref pluginsCount, value); } - public double noPlugins_tipHeight = PluginCards.Count == 0 ? 300 : 0; + public double noPlugins_tipHeight = PluginInfos.Count == 0 ? 300 : 0; public double NoPlugins_TipHeight { get => noPlugins_tipHeight; - set - { - noPlugins_tipHeight = value; - PropertyChanged?.Invoke( - this, - new(nameof(NoPlugins_TipHeight)) - ); - } + set => this.RaiseAndSetIfChanged(ref noPlugins_tipHeight, value); } - /// - /// 插件卡片集合 - /// - public static ObservableCollection PluginCards => Instances.PluginCards; - - /// - /// 搜索框文字 - /// public string? SearchingText { get; set; } + + public static ObservableCollection PluginInfos => ViewInstances.PluginInfos; + + internal ReactiveCommand? ViewDetailsCommand { get; set; } } diff --git a/KitX Dashboard/ViewModels/Pages/MarketPageViewModel.cs b/KitX Dashboard/ViewModels/Pages/MarketPageViewModel.cs index b802eedf..03e275e9 100644 --- a/KitX Dashboard/ViewModels/Pages/MarketPageViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/MarketPageViewModel.cs @@ -6,4 +6,8 @@ public MarketPageViewModel() { } + + public override void InitCommands() => throw new System.NotImplementedException(); + + public override void InitEvents() => throw new System.NotImplementedException(); } diff --git a/KitX Dashboard/ViewModels/Pages/RepoPageViewModel.cs b/KitX Dashboard/ViewModels/Pages/RepoPageViewModel.cs index 2b8fc739..fc7c45e9 100644 --- a/KitX Dashboard/ViewModels/Pages/RepoPageViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/RepoPageViewModel.cs @@ -2,14 +2,17 @@ using KitX.Dashboard.Managers; using KitX.Dashboard.Models; using KitX.Dashboard.Services; +using KitX.Dashboard.Views.Pages; using KitX.Dashboard.Views.Pages.Controls; -using KitX.Web.Rules; +using KitX.Shared.Loader; +using KitX.Shared.Plugin; using ReactiveUI; using Serilog; using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; +using System.Linq; using System.Reactive; using System.Text.Json; using System.Threading; @@ -18,6 +21,8 @@ namespace KitX.Dashboard.ViewModels.Pages; internal class RepoPageViewModel : ViewModelBase, INotifyPropertyChanged { + private RepoPage? CurrentPage { get; set; } + public new event PropertyChangedEventHandler? PropertyChanged; public RepoPageViewModel() @@ -33,24 +38,21 @@ public RepoPageViewModel() RefreshPluginsCommand?.Execute(new()); } - private void InitCommands() + public override void InitCommands() { ImportPluginCommand = ReactiveCommand.Create(async win => { if (win is not Window window) return; - var ofd = new OpenFileDialog() - { - AllowMultiple = true, - }; + var topLevel = TopLevel.GetTopLevel(CurrentPage!); - ofd.Filters?.Add(new() - { - Name = "KitX Extensions Packages", - Extensions = { "kxp" } - }); + if (topLevel is null) return; - var files = await ofd.ShowAsync(window); + var files = (await topLevel.StorageProvider.OpenFilePickerAsync(new() + { + Title = "Open KitX Extensions Package File", + AllowMultiple = true, + })).Select(x => x.Path.LocalPath).ToList().ToArray(); if (files is not null && files?.Length > 0) { @@ -74,44 +76,52 @@ private void InitCommands() { PluginBars.Clear(); - lock (PluginsNetwork.PluginsListOperationLock) + //lock (PluginsNetwork.PluginsListOperationLock) + //{ + + //} + + foreach (var item in PluginsManager.Plugins) { - foreach (var item in PluginsManager.Plugins) + try { - try + var plugin = new Plugin() { - var plugin = new Plugin() - { - InstallPath = item.InstallPath, - PluginDetails = JsonSerializer.Deserialize( - File.ReadAllText( - Path.GetFullPath($"{item.InstallPath}/PluginStruct.json") - ) - ), - RequiredLoaderStruct = JsonSerializer.Deserialize( - File.ReadAllText( - Path.GetFullPath($"{item.InstallPath}/LoaderStruct.json") - ) - ), - InstalledDevices = new() - }; - - PluginBars.Add(new(plugin, ref pluginBars)); - } - catch (Exception ex) - { - Log.Error(ex, "In RefreshPlugins()"); - } + InstallPath = item.InstallPath, + PluginDetails = JsonSerializer.Deserialize( + File.ReadAllText( + Path.GetFullPath($"{item.InstallPath}/PluginInfo.json") + ) + ), + RequiredLoaderInfo = JsonSerializer.Deserialize( + File.ReadAllText( + Path.GetFullPath($"{item.InstallPath}/LoaderInfo.json") + ) + ), + InstalledDevices = [] + }; + + PluginBars.Add(new(plugin, ref pluginBars)); + } + catch (Exception ex) + { + Log.Error(ex, "In RefreshPlugins()"); } } }); } - private void InitEvents() + internal RepoPageViewModel SetControl(RepoPage control) + { + CurrentPage = control; + return this; + } + + public override void InitEvents() { - EventService.ConfigSettingsChanged += () => + EventService.AppConfigChanged += () => { - ImportButtonVisibility = ConfigManager.AppConfig.App.DeveloperSetting; + ImportButtonVisibility = Instances.ConfigManager.AppConfig.App.DeveloperSetting; }; PluginBars.CollectionChanged += (_, _) => @@ -157,10 +167,10 @@ internal double NoPlugins_TipHeight internal bool ImportButtonVisibility { - get => ConfigManager.AppConfig.App.DeveloperSetting; + get => Instances.ConfigManager.AppConfig.App.DeveloperSetting; set { - ConfigManager.AppConfig.App.DeveloperSetting = value; + Instances.ConfigManager.AppConfig.App.DeveloperSetting = value; PropertyChanged?.Invoke( this, @@ -169,7 +179,7 @@ internal bool ImportButtonVisibility } } - private ObservableCollection pluginBars = new(); + private ObservableCollection pluginBars = []; internal ObservableCollection PluginBars { diff --git a/KitX Dashboard/ViewModels/Pages/SettingsPageViewModel.cs b/KitX Dashboard/ViewModels/Pages/SettingsPageViewModel.cs index 280691f6..5b866f91 100644 --- a/KitX Dashboard/ViewModels/Pages/SettingsPageViewModel.cs +++ b/KitX Dashboard/ViewModels/Pages/SettingsPageViewModel.cs @@ -1,6 +1,5 @@ using Avalonia; using FluentAvalonia.UI.Controls; -using KitX.Dashboard.Managers; using KitX.Dashboard.Services; using ReactiveUI; using System.ComponentModel; @@ -17,7 +16,7 @@ internal SettingsPageViewModel() InitCommands(); } - internal void InitCommands() + public override void InitCommands() { ResetToAutoCommand = ReactiveCommand.Create(() => { @@ -35,14 +34,16 @@ internal void InitCommands() }); } + public override void InitEvents() => throw new System.NotImplementedException(); + internal static bool IsPaneOpen { - get => ConfigManager.AppConfig.Pages.Settings.IsNavigationViewPaneOpened; + get => Instances.ConfigManager.AppConfig.Pages.Settings.IsNavigationViewPaneOpened; set { - ConfigManager.AppConfig.Pages.Settings.IsNavigationViewPaneOpened = value; + Instances.ConfigManager.AppConfig.Pages.Settings.IsNavigationViewPaneOpened = value; - EventService.Invoke(nameof(EventService.ConfigSettingsChanged)); + SaveAppConfigChanges(); } } @@ -58,10 +59,10 @@ internal static bool IsPaneOpen internal NavigationViewPaneDisplayMode NavigationViewPaneDisplayMode { - get => ConfigManager.AppConfig.Pages.Settings.NavigationViewPaneDisplayMode; + get => Instances.ConfigManager.AppConfig.Pages.Settings.NavigationViewPaneDisplayMode; set { - ConfigManager.AppConfig.Pages.Settings.NavigationViewPaneDisplayMode = value; + Instances.ConfigManager.AppConfig.Pages.Settings.NavigationViewPaneDisplayMode = value; PropertyChanged?.Invoke( this, @@ -73,7 +74,7 @@ internal NavigationViewPaneDisplayMode NavigationViewPaneDisplayMode new(nameof(FirstItemMargin)) ); - EventService.Invoke(nameof(EventService.ConfigSettingsChanged)); + SaveAppConfigChanges(); } } diff --git a/KitX Dashboard/ViewModels/PluginDetailWindowViewModel.cs b/KitX Dashboard/ViewModels/PluginDetailWindowViewModel.cs index 3d7b8c6f..fed825e6 100644 --- a/KitX Dashboard/ViewModels/PluginDetailWindowViewModel.cs +++ b/KitX Dashboard/ViewModels/PluginDetailWindowViewModel.cs @@ -1,26 +1,19 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Media; -using Avalonia.Media.Imaging; using Avalonia.Styling; -using KitX.Dashboard.Managers; using KitX.Dashboard.Services; -using KitX.Web.Rules; +using KitX.Shared.Plugin; using ReactiveUI; -using Serilog; -using System; using System.Collections.ObjectModel; -using System.ComponentModel; -using System.IO; -using System.Linq; using System.Reactive; using System.Text; namespace KitX.Dashboard.ViewModels; -internal class PluginDetailWindowViewModel : ViewModelBase, INotifyPropertyChanged +internal class PluginDetailWindowViewModel : ViewModelBase { - public new event PropertyChangedEventHandler? PropertyChanged; + private PluginInfo? pluginDetail; public PluginDetailWindowViewModel() { @@ -29,149 +22,35 @@ public PluginDetailWindowViewModel() InitEvents(); } - internal void InitCommands() + public override void InitCommands() { FinishCommand = ReactiveCommand.Create( parent => (parent as Window)?.Close() ); } - internal void InitEvents() + public override void InitEvents() { - EventService.ThemeConfigChanged += () => PropertyChanged?.Invoke( - this, - new(nameof(TintColor)) - ); - } - - internal PluginStruct? PluginDetail { get; set; } - - internal string? DisplayName - { - get - { - if (PluginDetail is not null) - { - var key = ConfigManager.AppConfig.App.AppLanguage; - var exist = PluginDetail?.DisplayName.ContainsKey(key); - - if (exist is not null && (bool)exist) - return PluginDetail?.DisplayName[key]; - else return PluginDetail?.Name; - } - else return PluginDetail?.Name; - } - } - - internal string? Version => PluginDetail?.Version; - - internal string? AuthorName => PluginDetail?.AuthorName; - - internal string? PublisherName => PluginDetail?.PublisherName; - - internal string? AuthorLink => PluginDetail?.AuthorLink; - - internal string? PublisherLink => PluginDetail?.PublisherLink; - - internal string? SimpleDescription - { - get - { - if (PluginDetail is not null) - { - var key = ConfigManager.AppConfig.App.AppLanguage; - var exist = PluginDetail?.SimpleDescription.ContainsKey(key); - - if (exist is not null && (bool)exist) - return PluginDetail?.SimpleDescription[key]; - else return PluginDetail?.SimpleDescription.Values.ToArray()[0]; - } - else return PluginDetail?.SimpleDescription.Values.ToArray()[0]; - } - } - - internal string? ComplexDescription - { - get - { - if (PluginDetail is not null) - { - var key = ConfigManager.AppConfig.App.AppLanguage; - var exist = PluginDetail?.ComplexDescription.ContainsKey(key); - - if (exist is not null && (bool)exist) - return PluginDetail?.ComplexDescription[key]; - else return PluginDetail?.ComplexDescription.Values.ToArray()[0]; - } - else return PluginDetail?.ComplexDescription.Values.ToArray()[0]; - } + EventService.ThemeConfigChanged += () => this.RaisePropertyChanged(nameof(TintColor)); } - internal string? TotalDescriptionInMarkdown - { - get - { - if (PluginDetail is not null) - { - var key = ConfigManager.AppConfig.App.AppLanguage; - var exist = PluginDetail?.TotalDescriptionInMarkdown.ContainsKey(key); - - if (exist is not null && (bool)exist) - return PluginDetail?.TotalDescriptionInMarkdown[key]; - else return PluginDetail?.TotalDescriptionInMarkdown.Values.ToArray()[0]; - } - else return PluginDetail?.TotalDescriptionInMarkdown.Values.ToArray()[0]; - } - } + internal PluginInfo? PluginDetail { get => pluginDetail; set => pluginDetail = value; } - internal string? PublishDate => PluginDetail?.PublishDate - .ToLocalTime().ToString("yyyy.MM.dd"); + internal string? PublishDate => PluginDetail?.PublishDate.ToLocalTime().ToString("yyyy.MM.dd"); - internal string? LastUpdateDate => PluginDetail?.LastUpdateDate - .ToLocalTime().ToString("yyyy.MM.dd"); + internal string? LastUpdateDate => PluginDetail?.LastUpdateDate.ToLocalTime().ToString("yyyy.MM.dd"); - internal static Color TintColor => ConfigManager.AppConfig.App.Theme switch + internal static Color TintColor => Instances.ConfigManager.AppConfig.App.Theme switch { "Light" => Colors.WhiteSmoke, "Dark" => Colors.Black, - "Follow" => - Application.Current?.ActualThemeVariant == ThemeVariant.Light ? Colors.WhiteSmoke : Colors.Black, - _ => Color.Parse(ConfigManager.AppConfig.App.ThemeColor), + "Follow" => Application.Current?.ActualThemeVariant == ThemeVariant.Light ? Colors.WhiteSmoke : Colors.Black, + _ => Color.Parse(Instances.ConfigManager.AppConfig.App.ThemeColor), }; - private readonly ObservableCollection functions = new(); - - private readonly ObservableCollection tags = new(); + private readonly ObservableCollection functions = []; - internal Bitmap IconDisplay - { - get - { - var location = $"{nameof(PluginDetailWindowViewModel)}.{nameof(IconDisplay)}.getter"; - - try - { - if (PluginDetail is null) return App.DefaultIcon; - - var src = Convert.FromBase64String(PluginDetail.Value.IconInBase64); - - using var ms = new MemoryStream(src); - - return new(ms); - } - catch (Exception e) - { - Log.Warning( - e, - $"In {location}: " + - $"Failed to transform icon from base64 to byte[] " + - $"or create bitmap from `MemoryStream`. {e.Message}" - ); - - return App.DefaultIcon; - } - } - } + private readonly ObservableCollection tags = []; internal void InitFunctionsAndTags() { @@ -181,42 +60,28 @@ internal void InitFunctionsAndTags() if (PluginDetail?.Tags is null) return; - var langKey = ConfigManager.AppConfig.App.AppLanguage; - foreach (var func in PluginDetail.Value.Functions) { - var sb = new StringBuilder(); - - sb.Append(func.ReturnValueType); - sb.Append(' '); - - if (func.DisplayNames.TryGetValue(langKey, out var name)) - sb.Append(name); - else sb.Append(func.Name); - - sb.Append('('); - - if (func.Parameters.Count != func.ParametersType.Count) - throw new InvalidDataException( - "Parameters return type count didn't match parameters count." - ); + var sb = new StringBuilder() + .Append(func.ReturnValueType) + .Append(' ') + .Append(func.Name) + .Append('(') + ; var index = 0; foreach (var param in func.Parameters) { - sb.Append(func.ParametersType[index]); - - ++index; - - sb.Append(' '); + sb.Append(param.Type) + .Append(' ') + .Append(param.Name) + ; - if (param.Value.TryGetValue(langKey, out var paramName)) - sb.Append(paramName); - else sb.Append(param.Key); - - if (index != func.Parameters.Count) + if (index != func.Parameters.Count - 1) sb.Append(", "); + + ++index; } sb.Append(')'); @@ -225,7 +90,7 @@ internal void InitFunctionsAndTags() } foreach (var tag in PluginDetail.Value.Tags) - Tags.Add($"{{{tag.Key}: {tag.Value}}}"); + Tags.Add($"{{ {tag.Key}: {tag.Value} }}"); } internal ObservableCollection Functions => functions; diff --git a/KitX Dashboard/ViewModels/PluginsLaunchWindowViewModel.cs b/KitX Dashboard/ViewModels/PluginsLaunchWindowViewModel.cs index 3658c5c5..6d6773ce 100644 --- a/KitX Dashboard/ViewModels/PluginsLaunchWindowViewModel.cs +++ b/KitX Dashboard/ViewModels/PluginsLaunchWindowViewModel.cs @@ -1,5 +1,374 @@ -namespace KitX.Dashboard.ViewModels; +using Avalonia; +using Common.BasicHelper.Utils.Extensions; +using KitX.Dashboard.Network.PluginsNetwork; +using KitX.Dashboard.Views; +using KitX.Shared.Plugin; +using KitX.Shared.WebCommand; +using ReactiveUI; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text.Json; + +namespace KitX.Dashboard.ViewModels; internal class PluginsLaunchWindowViewModel : ViewModelBase { + public PluginsLaunchWindowViewModel() + { + InitCommands(); + + InitEvents(); + } + + public override void InitCommands() + { + + } + + public override void InitEvents() + { + PluginInfos.CollectionChanged += (_, _) => + { + PluginsCount = $"{PluginInfos.Count}"; + + CheckPluginIndex(); + + this.RaisePropertyChanged(nameof(SearchItems)); + this.RaisePropertyChanged(nameof(SelectedPluginInfo)); + this.RaisePropertyChanged(nameof(SelectedFunction)); + }; + } + + public string pluginsCount = "0"; + + public string PluginsCount + { + get => pluginsCount; + set + { + this.RaiseAndSetIfChanged(ref pluginsCount, value); + this.RaisePropertyChanged(nameof(NoPlugins_TipHeight)); + this.RaisePropertyChanged(nameof(SelectedPluginInfo)); + } + } + + public static double NoPlugins_TipHeight => PluginInfos.Count == 0 ? 40 : 0; + + private int selectedPluginIndex = 0; + + public int SelectedPluginIndex + { + get => selectedPluginIndex; + set + { + this.RaiseAndSetIfChanged(ref selectedPluginIndex, value); + this.RaisePropertyChanged(nameof(SelectedPluginInfo)); + } + } + + public PluginInfo? SelectedPluginInfo + { + get => PluginIndexInRange(SelectedPluginIndex) ? PluginInfos[SelectedPluginIndex] : null; + set + { + if (value is null) return; + + var index = PluginInfos.IndexOf(value.Value); + + this.RaiseAndSetIfChanged(ref selectedPluginIndex, index); + } + } + + private Function? selectedFunction; + + public Function? SelectedFunction + { + get + { + if (SelectedPluginInfo is null) selectedFunction = null; + + return selectedFunction; + } + + set + { + this.RaiseAndSetIfChanged(ref selectedFunction, value); + + this.RaisePropertyChanged(nameof(HavingParameters)); + } + } + + public static ObservableCollection PluginInfos => ViewInstances.PluginInfos; + + private bool isSelectingPlugin = true; + + public bool IsSelectingPlugin + { + get => isSelectingPlugin; + set + { + if (IsSelectingFunction) + IsSelectingFunction = false; + + this.RaiseAndSetIfChanged(ref isSelectingPlugin, value); + + if (value) + { + SearchingText = SelectedPluginInfo?.Name; + + this.RaisePropertyChanged(nameof(SearchItems)); + } + } + } + + private bool isSelectingFunction = false; + + public bool IsSelectingFunction + { + get => isSelectingFunction; + set + { + if (IsSelectingPlugin) + IsSelectingPlugin = false; + + this.RaiseAndSetIfChanged(ref isSelectingFunction, value); + + + if (value) + { + SearchingText = SelectedFunction?.Name; + + this.RaisePropertyChanged(nameof(SearchItems)); + } + } + } + + public bool HavingParameters + { + get + { + if (SelectedFunction is null) return false; + + return SelectedFunction.Value.Parameters.Count != 0; + } + } + + public IEnumerable SearchItems + { + get + { + if (IsSelectingPlugin) + { + return PluginInfos.Select(x => x.Name).Distinct(); + } + else if (IsSelectingFunction) + { + return SelectedPluginInfo?.Functions.Select(f => f.Name) ?? []; + } + else return []; + } + } + + private string? searchingText; + + public string? SearchingText + { + get => searchingText; + set + { + this.RaiseAndSetIfChanged(ref searchingText, value); + } + } + + private Vector scrollViewerOffset = new(0, 0); + + public Vector ScrollViewerOffset + { + get => scrollViewerOffset; + set => this.RaiseAndSetIfChanged(ref scrollViewerOffset, value); + } + + private bool isInDirectSelectingMode = false; + + public bool IsInDirectSelectingMode + { + get => isInDirectSelectingMode; + set => this.RaiseAndSetIfChanged(ref isInDirectSelectingMode, value); + } + + private void BringSelectedButtonIntoView(int perLineButtonsCount, double scrollviewerHeight, double scrollviewerOffsetY) + { + var viewerHeight = (int)Math.Floor(scrollviewerHeight); + + var viewerOffsetY = (int)Math.Floor(scrollviewerOffsetY); + + var lineIndex = SelectedPluginIndex / perLineButtonsCount; + + var up = lineIndex * 80; + + var down = up + 80; + + var targetY = lineIndex * 80; + + var condition = (up >= viewerOffsetY && down <= viewerOffsetY + viewerHeight); + + if (condition == false) + { + ScrollViewerOffset = new(0, targetY); + } + } + + private static bool PluginIndexInRange(int index) => index >= 0 && index < PluginInfos.Count; + + private void CheckPluginIndex() + { + if (PluginIndexInRange(SelectedPluginIndex) == false) + { + if (SelectedPluginIndex >= PluginInfos.Count) + SelectedPluginIndex = PluginInfos.Count - 1; + else if (SelectedPluginIndex < 0) + SelectedPluginIndex = 0; + } + } + + internal void SelectLeftOne(int perLineCount, double scrollviewerHeight, double scrollviewerOffsetY) + { + if (PluginIndexInRange(selectedPluginIndex - 1)) + { + SelectedPluginIndex--; + + BringSelectedButtonIntoView(perLineCount, scrollviewerHeight, scrollviewerOffsetY); + } + } + + internal void SelectRightOne(int perLineCount, double scrollviewerHeight, double scrollviewerOffsetY) + { + if (PluginIndexInRange(SelectedPluginIndex + 1)) + { + SelectedPluginIndex++; + + BringSelectedButtonIntoView(perLineCount, scrollviewerHeight, scrollviewerOffsetY); + } + } + + internal void SelectUpOne(int perLineCount, double scrollviewerHeight, double scrollviewerOffsetY) + { + var targetIndex = SelectedPluginIndex - perLineCount; + + if (PluginIndexInRange(targetIndex)) + { + SelectedPluginIndex = targetIndex; + + BringSelectedButtonIntoView(perLineCount, scrollviewerHeight, scrollviewerOffsetY); + } + } + + internal void SelectDownOne(int perLineCount, double scrollviewerHeight, double scrollviewerOffsetY) + { + var targetIndex = SelectedPluginIndex + perLineCount; + + if (PluginIndexInRange(targetIndex)) + { + SelectedPluginIndex = targetIndex; + + BringSelectedButtonIntoView(perLineCount, scrollviewerHeight, scrollviewerOffsetY); + } + else + { + targetIndex = PluginInfos.Count - 1; + + if ((targetIndex / perLineCount) - (SelectedPluginIndex / perLineCount) == 0) + return; + + if (PluginIndexInRange(targetIndex)) + { + SelectedPluginIndex = targetIndex; + + BringSelectedButtonIntoView(perLineCount, scrollviewerHeight, scrollviewerOffsetY); + } + } + } + + internal void SelectHomeOne(int perLineCount) + { + var currentLine = SelectedPluginIndex / perLineCount; + + var targetIndex = currentLine * perLineCount; + + if (PluginIndexInRange(targetIndex)) + SelectedPluginIndex = targetIndex; + } + + internal void SelectEndOne(int perLineCount) + { + var currentLine = SelectedPluginIndex / perLineCount; + + var targetIndex = ((currentLine + 1) * perLineCount) - 1; + + while (PluginIndexInRange(targetIndex) == false && targetIndex >= 0) + targetIndex--; + + if (PluginIndexInRange(targetIndex)) + SelectedPluginIndex = targetIndex; + } + + internal void SubmitSearchingText() + { + var text = SearchingText; + + if (IsSelectingPlugin) + { + var index = PluginInfos.IndexOf(x => x.Name.Equals(text)); + + if (index != -1) + { + SelectedPluginIndex = index; + + if (SelectedFunction is not null) SelectedFunction = null; + + IsSelectingFunction = true; + } + } + else if (IsSelectingFunction) + { + if (SelectedPluginInfo is not null) + foreach (var func in SelectedPluginInfo.Value.Functions) + if (func.Name.Equals(text)) + { + SelectedFunction = func; + + break; + } + + if (SelectedPluginInfo is not null && SelectedFunction is not null && (HavingParameters == false)) + { + var plugConnector = PluginsServer.Instance.FindConnector(SelectedPluginInfo.Value); + + if (plugConnector is not null) + { + var connector = new Connector() + .SetSerializer(x => JsonSerializer.Serialize(x)) + .SetSender(plugConnector.Request) + ; + + var request = connector.Request() + .ReceiveCommand() + .UpdateCommand(cmd => + { + cmd.FunctionName = SelectedFunction.Value.Name; + + return cmd; + }) + .Send() + ; + } + } + } + else + { + + } + } } diff --git a/KitX Dashboard/ViewModels/ViewModelBase.cs b/KitX Dashboard/ViewModels/ViewModelBase.cs index 6a0c4195..95a7582d 100644 --- a/KitX Dashboard/ViewModels/ViewModelBase.cs +++ b/KitX Dashboard/ViewModels/ViewModelBase.cs @@ -1,19 +1,24 @@ using Avalonia; -using Avalonia.Controls; +using Avalonia.Controls; +using KitX.Dashboard.Configuration; using KitX.Dashboard.Services; using ReactiveUI; namespace KitX.Dashboard.ViewModels; -public class ViewModelBase : ReactiveObject +public abstract class ViewModelBase : ReactiveObject { - protected static string? FetchStringFromResource( - Application? app, - string key, + protected static string? Translate + ( + string key = "", string prefix = "", string suffix = "", - string seperator = "") + string seperator = "", + Application? app = null + ) { + app ??= Application.Current; + if (app is null) return null; var res_key = $"{prefix}{seperator}{key}{seperator}{suffix}"; @@ -27,6 +32,12 @@ public class ViewModelBase : ReactiveObject } protected static void SaveAppConfigChanges() => EventService.Invoke( - nameof(EventService.ConfigSettingsChanged) - ); -} + nameof(EventService.AppConfigChanged) + ); + + public abstract void InitCommands(); + + public abstract void InitEvents(); + + internal static AppConfig AppConfig => Instances.ConfigManager.AppConfig; +} diff --git a/KitX Dashboard/Views/AnouncementsWindow.axaml b/KitX Dashboard/Views/AnouncementsWindow.axaml index b0b7951f..27f61363 100644 --- a/KitX Dashboard/Views/AnouncementsWindow.axaml +++ b/KitX Dashboard/Views/AnouncementsWindow.axaml @@ -14,7 +14,9 @@ MinHeight="600" d:DesignHeight="450" d:DesignWidth="800" + Background="Transparent" Icon="avares://KitX.Dashboard.Assets/KitX-Icon-32x32.png" + TransparencyLevelHint="Mica" mc:Ignorable="d"> - { - if (!closed) - Close(); - }; + var config = appConfig.Windows.AnnouncementWindow; -#if DEBUG - this.AttachDevTools(); -#endif + var screen = Screens.ScreenFromWindow(this); - } + var nowRes = config.Size.SuggestResolution(screen); - private void SuggestResolutionAndLocation() - { - if (ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Width == 1280 - && ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Height == 720) + var centerPos = config.Location.BringToCenter(screen, nowRes); + + ClientSize = new(nowRes.Width!.Value, nowRes.Height!.Value); + + Position = new((int)centerPos.Left, (int)centerPos.Top); + + ClientSizeProperty.Changed.Subscribe(size => { - var suggest = Resolution.Suggest( - Resolution.Parse("2560x1440"), - Resolution.Parse("1280x720"), - Resolution.Parse( - $"{Screens.Primary.Bounds.Width}x{Screens.Primary.Bounds.Height}" - ) - ).Integerization(); - - if (suggest.Width is not null && suggest.Height is not null) - { - ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Width = (double)suggest.Width; - ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Height = (double)suggest.Height; - } - } - } + if (WindowState != WindowState.Maximized) + config.Size = new Resolution(ClientSize.Width, ClientSize.Height); + }); - internal void UpdateSource(Dictionary src, List readed) - { - viewModel.Sources = src; - viewModel.Readed = readed; + PositionChanged += (_, args) => + { + if (WindowState == WindowState.Normal) + config.Location = new(left: Position.X, top: Position.Y); + }; } - private void SaveMetaData() + internal AnouncementsWindow UpdateSource(Dictionary src) { - if (WindowState != WindowState.Minimized) - { - ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Left = Position.X; - ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Top = Position.Y; - } + viewModel.Sources = src; - ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Width = Width; - ConfigManager.AppConfig.Windows.AnnouncementWindow.Window_Height = Height; + return this; } protected override void OnClosing(WindowClosingEventArgs e) { - base.OnClosed(e); - - SaveMetaData(); + IView.SaveAppConfigChanges(); - closed = true; + base.OnClosing(e); } } diff --git a/KitX Dashboard/Views/DebugWindow.axaml b/KitX Dashboard/Views/DebugWindow.axaml new file mode 100644 index 00000000..38fd1e67 --- /dev/null +++ b/KitX Dashboard/Views/DebugWindow.axaml @@ -0,0 +1,63 @@ + + + + + + + - - - diff --git a/KitX Dashboard/Views/Pages/Controls/PluginCard.axaml.cs b/KitX Dashboard/Views/Pages/Controls/PluginCard.axaml.cs deleted file mode 100644 index db4143c1..00000000 --- a/KitX Dashboard/Views/Pages/Controls/PluginCard.axaml.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Avalonia.Controls; -using KitX.Dashboard.ViewModels.Pages.Controls; -using KitX.Web.Rules; - -namespace KitX.Dashboard.Views.Pages.Controls; - -public partial class PluginCard : UserControl -{ - private readonly PluginCardViewModel viewModel = new(); - - internal string? IPEndPoint { get; set; } - - public PluginCard() - { - InitializeComponent(); - - DataContext = viewModel; - } - - public PluginCard(PluginStruct ps) - { - InitializeComponent(); - - viewModel.pluginStruct = ps; - - DataContext = viewModel; - } -} diff --git a/KitX Dashboard/Views/Pages/Controls/Settings_About.axaml b/KitX Dashboard/Views/Pages/Controls/Settings_About.axaml index 2d4a06cf..e375394e 100644 --- a/KitX Dashboard/Views/Pages/Controls/Settings_About.axaml +++ b/KitX Dashboard/Views/Pages/Controls/Settings_About.axaml @@ -1,6 +1,7 @@  - + + + + + + diff --git a/KitX Dashboard/Views/Pages/RepoPage.axaml b/KitX Dashboard/Views/Pages/RepoPage.axaml index eb85dcb4..21c30944 100644 --- a/KitX Dashboard/Views/Pages/RepoPage.axaml +++ b/KitX Dashboard/Views/Pages/RepoPage.axaml @@ -12,8 +12,6 @@ @@ -21,13 +19,8 @@ HorizontalAlignment="Left" VerticalAlignment="Center" Orientation="Horizontal"> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/KitX Dashboard/Views/PluginsLaunchWindow.axaml.cs b/KitX Dashboard/Views/PluginsLaunchWindow.axaml.cs index 56b433c2..4d0ada3b 100644 --- a/KitX Dashboard/Views/PluginsLaunchWindow.axaml.cs +++ b/KitX Dashboard/Views/PluginsLaunchWindow.axaml.cs @@ -1,10 +1,8 @@ using Avalonia.Controls; using Avalonia.Input; -using Avalonia.Media; using Avalonia.Threading; using KitX.Dashboard.Services; using KitX.Dashboard.ViewModels; -using Serilog; using SharpHook.Native; using System; @@ -14,86 +12,230 @@ public partial class PluginsLaunchWindow : Window { private readonly PluginsLaunchWindowViewModel viewModel = new(); - private Action? OnHideAction; + private readonly Action? OnHideAction; private bool pluginsLaunchWindowDisplayed = false; + private int? previousSelectedPluginIndex = null; + public PluginsLaunchWindow() { - var location = $"{nameof(PluginsLaunchWindow)}.ctor"; - InitializeComponent(); DataContext = viewModel; - OnHide(() => pluginsLaunchWindowDisplayed = false); + OnHideAction = () => pluginsLaunchWindowDisplayed = false; EventService.OnExiting += Close; - if (OperatingSystem.IsWindows() == false) + Initialize(); + } + + private void Initialize() + { + if (this.FindControl("MainAutoCompleteBox") is AutoCompleteBox box) { - try - { - Background = Resources["ThemePrimaryAccent"] as SolidColorBrush; - } - catch (Exception ex) + box.KeyDown += (_, e) => { - Log.Warning(ex, $"In {location}: {ex.Message}"); - } + if (e.Key == Key.Enter) + { + viewModel.SubmitSearchingText(); + + e.Handled = true; + } + }; + } + + if (this.FindControl("PluginsScrollViewer") is ScrollViewer viewer) + { + viewer.KeyDown += PluginsScrollViewer_KeyDown; } RegisterGlobalHotKey(); } + private void PluginsScrollViewer_KeyDown(object? sender, KeyEventArgs e) + { + if (sender is not ScrollViewer viewer) return; + + if (viewer.IsFocused == false) return; + + switch (e.Key) + { + case Key.Enter: + // ToDo: Select Plugin Info + return; + } + + var perLineCount = (int)Math.Floor((Width - 40) / 80); + + var viewerHeight = viewer.DesiredSize.Height; + + var viewerOffsetY = viewer.Offset.Y; + + switch (e.Key) + { + case Key.Left: + viewModel.SelectLeftOne( + perLineCount, + viewerHeight, + viewerOffsetY + ); + break; + case Key.Right: + viewModel.SelectRightOne( + perLineCount, + viewerHeight, + viewerOffsetY + ); + break; + case Key.Up: + viewModel.SelectUpOne( + perLineCount, + viewerHeight, + viewerOffsetY + ); + break; + case Key.Down: + viewModel.SelectDownOne( + perLineCount, + viewerHeight, + viewerOffsetY + ); + break; + case Key.Home: + viewModel.SelectHomeOne(perLineCount); + break; + case Key.End: + viewModel.SelectEndOne(perLineCount); + break; + } + } + private void RegisterGlobalHotKey() { - Instances.HotKeyManager?.RegisterHotKeyHandler("", codes => + Instances.KeyHookManager?.RegisterHotKeyHandler(nameof(PluginsLaunchWindow), codes => { var count = codes.Length; var tmpList = codes; - if (count >= 3 && - tmpList[count - 3] == KeyCode.VcLeftControl && - tmpList[count - 2] == KeyCode.VcLeftMeta && - tmpList[count - 1] == KeyCode.VcC) + if (count < 3) return; + + if (tmpList[count - 3] != KeyCode.VcLeftControl) return; + + if (tmpList[count - 2] != KeyCode.VcLeftMeta) return; + + if (tmpList[count - 1] != KeyCode.VcC) return; + + Dispatcher.UIThread.Post(() => { - Dispatcher.UIThread.Post(() => + if (pluginsLaunchWindowDisplayed) { - if (pluginsLaunchWindowDisplayed) - { - Activate(); + Activate(); + + Focus(); + } + else + { + Show(); + } + + pluginsLaunchWindowDisplayed = true; + }); + }); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + var box = this.FindControl("MainAutoCompleteBox"); - Focus(); + switch (e.Key) + { + case Key.Tab: + if (viewModel.IsInDirectSelectingMode == false) + { + if (e.KeyModifiers == KeyModifiers.Shift) + { + if (viewModel.IsSelectingFunction) + viewModel.IsSelectingPlugin = true; } else { - Show(); + if (viewModel.IsSelectingPlugin) + viewModel.IsSelectingFunction = true; } + } + e.Handled = true; + break; + case Key.Escape: + Hide(); + OnHideAction?.Invoke(); + break; + } - pluginsLaunchWindowDisplayed = true; - }); - } - }); + switch (e.PhysicalKey) + { + case PhysicalKey.Backquote: + if (viewModel.IsInDirectSelectingMode == false) + { + viewModel.IsInDirectSelectingMode = true; + + if (this.FindControl("PluginsScrollViewer") is ScrollViewer viewer) + viewer.Focus(); + } + else + { + previousSelectedPluginIndex = viewModel.SelectedPluginIndex; + + viewModel.IsInDirectSelectingMode = false; + + box?.Focus(); + } + e.Handled = true; + break; + } + + base.OnKeyDown(e); } - public PluginsLaunchWindow OnHide(Action onHideAction) + protected override void OnPointerPressed(PointerPressedEventArgs e) { - OnHideAction = onHideAction; + BeginMoveDrag(e); - return this; + base.OnPointerPressed(e); } - private void PluginsLaunchWindow_PointerPressed(object? sender, PointerPressedEventArgs e) - => BeginMoveDrag(e); + protected override void OnResized(WindowResizedEventArgs e) + { + if (ExperimentalFlags.EnablePluginLaunchWindowWidthSnap) + { + var basicWidth = 80; + var addonWidth = 40; + var windowWidth = (int)e.ClientSize.Width; + var oneLineCount = (windowWidth - addonWidth) / basicWidth; + var left = (windowWidth - addonWidth) % basicWidth; + + if (left < basicWidth / 2) + Width = oneLineCount * basicWidth + addonWidth; + else + Width = (oneLineCount + 1) * basicWidth + addonWidth; + } - private void PluginsLaunchWindow_KeyDown(object? sender, KeyEventArgs e) + base.OnResized(e); + } + + protected override void OnClosing(WindowClosingEventArgs e) { - if (e.Key == Key.Escape) + if (!ConstantTable.Exiting) { + e.Cancel = true; + Hide(); OnHideAction?.Invoke(); } + + base.OnClosing(e); } } diff --git a/KitX Dashboard/Views/ViewInstances.cs b/KitX Dashboard/Views/ViewInstances.cs new file mode 100644 index 00000000..ded5f924 --- /dev/null +++ b/KitX Dashboard/Views/ViewInstances.cs @@ -0,0 +1,35 @@ +using Avalonia.Controls; +using KitX.Dashboard.Services; +using KitX.Dashboard.Views.Pages.Controls; +using KitX.Shared.Plugin; +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace KitX.Dashboard.Views; + +public static class ViewInstances +{ + public static ObservableCollection PluginInfos { get; set; } = []; + + public static ObservableCollection DeviceCards { get; set; } = []; + + public static MainWindow? MainWindow { get; set; } + + public static PluginsLaunchWindow? PluginsLaunchWindow { get; set; } + + public static List Windows { get; set; } = []; + + public static void ShowWindow(T window, Window? owner = null, bool showDialog = false) where T : Window + { + EventService.OnExiting += window.Close; + + Windows.Add(window); + + if (showDialog && owner is not null) + window.ShowDialog(owner); + else if (owner is null) + window.Show(); + else + window.Show(owner); + } +} diff --git a/README.md b/README.md index 1d6e1819..5765d894 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # Architecture -Based on `.NET 7` platform. +Based on `.NET 8` platform. Using [Avalonia UI](https://avaloniaui.net) as UI framework, using `Avalonia 11.0` . @@ -22,18 +22,21 @@ git clone https://github.com/Crequency/KitX-Dashboard.git # Build -Make sure you have `dotnet 7` sdk installed on your machine and added to `PATH` environment variable firstly. +Make sure you have `dotnet 8` sdk installed on your machine firstly. + +> Execute command `dotnet --list-sdks` to check if you have installed `dotnet 8` sdk. Then, ```shell cd 'KitX Dashboard/KitX Dashboard' + dotnet build ``` this will only build the project. -output is in `./bin/Debug/net7.0/` folder. +output is in `./bin/Debug/net8.0/` folder. or @@ -43,7 +46,7 @@ dotnet run this will build and run in current folder. -suggest using `dotnet run --project ../../..` command in `KitX Dashboard/bin/Debug/net7.0` folder, in order to avoid workspace error. +suggest using `dotnet run --project ../../..` command in `KitX Dashboard/bin/Debug/net8.0` folder, in order to avoid workspace error. # Dependencies @@ -63,6 +66,7 @@ git submodule init # Update submodules # If you are developing on Windows OS, replace `start.sh` with `start.ps1` # which requires powershell installed +# WARN: We're developing new `cheese` program to replace `start.sh` and `start.ps1` ./ToolKits/start.sh dashboard ./ToolKits/start.sh reference ```