From 25d61569c2a83722138daa7dae22a4a12323090b Mon Sep 17 00:00:00 2001 From: Habib Rehman <13956188+habibrehmansg@users.noreply.github.com> Date: Sat, 21 Dec 2024 13:35:53 +0800 Subject: [PATCH] Cleanup plugin loading locations --- InfoPanel.Extras/DriveInfoPlugin.cs | 2 +- InfoPanel.Extras/InfoPanel.Extras.csproj | 5 - InfoPanel.Extras/IpifyPlugin.cs | 2 +- InfoPanel.Extras/VolumePlugin.cs | 58 ++------- InfoPanel.Extras/WeatherPlugin.cs | 2 +- InfoPanel.Plugins.Loader/PluginLoader.cs | 35 +---- InfoPanel.Plugins.Loader/PluginWrapper.cs | 5 + InfoPanel/App.xaml.cs | 9 -- InfoPanel/InfoPanel.csproj | 50 ++++--- InfoPanel/Monitors/PluginMonitor.cs | 151 +++++++++++----------- InfoPanel/Resources/Images/logo.ico | Bin 0 -> 15086 bytes InfoPanel/Utils/FpsCounter.cs | 4 - InfoPanel/Views/Pages/DesignPage.xaml | 2 +- 13 files changed, 128 insertions(+), 197 deletions(-) create mode 100644 InfoPanel/Resources/Images/logo.ico diff --git a/InfoPanel.Extras/DriveInfoPlugin.cs b/InfoPanel.Extras/DriveInfoPlugin.cs index 093be21..f19bb26 100644 --- a/InfoPanel.Extras/DriveInfoPlugin.cs +++ b/InfoPanel.Extras/DriveInfoPlugin.cs @@ -8,7 +8,7 @@ public class DriveInfoPlugin : BasePlugin private readonly List _containers = []; - public DriveInfoPlugin() : base("Drive Info Plugin") + public DriveInfoPlugin() : base("drive-info-plugin", "Drive Info") { } diff --git a/InfoPanel.Extras/InfoPanel.Extras.csproj b/InfoPanel.Extras/InfoPanel.Extras.csproj index 3916edc..2fa6b05 100644 --- a/InfoPanel.Extras/InfoPanel.Extras.csproj +++ b/InfoPanel.Extras/InfoPanel.Extras.csproj @@ -19,9 +19,4 @@ runtime - - - - - diff --git a/InfoPanel.Extras/IpifyPlugin.cs b/InfoPanel.Extras/IpifyPlugin.cs index da9cbd2..36e6cbd 100644 --- a/InfoPanel.Extras/IpifyPlugin.cs +++ b/InfoPanel.Extras/IpifyPlugin.cs @@ -12,7 +12,7 @@ public class IpifyPlugin : BasePlugin public override TimeSpan UpdateInterval => TimeSpan.FromMinutes(5); - public IpifyPlugin() : base("Ipify Plugin") + public IpifyPlugin() : base("ipify-plugin", "Public IP - Ipify") { } diff --git a/InfoPanel.Extras/VolumePlugin.cs b/InfoPanel.Extras/VolumePlugin.cs index 23dbedc..ddbee04 100644 --- a/InfoPanel.Extras/VolumePlugin.cs +++ b/InfoPanel.Extras/VolumePlugin.cs @@ -8,7 +8,9 @@ public class VolumePlugin : BasePlugin { private readonly List _containers = []; - public VolumePlugin() : base("Volume Plugin") + private MMDeviceEnumerator? _deviceEnumerator; + + public VolumePlugin() : base("volume-plugin","Volume Info") { } @@ -17,18 +19,15 @@ public VolumePlugin() : base("Volume Plugin") public override void Initialize() { //add default first - using MMDeviceEnumerator deviceEnumerator = new(); - using var defaultDevice = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + _deviceEnumerator = new(); + using var defaultDevice = _deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); PluginContainer container = new("Default"); - container.Entries.Add(new PluginText("device_friendly_name", "Device Name", defaultDevice.DeviceFriendlyName)); - container.Entries.Add(new PluginText("friendly_name", "Name", defaultDevice.FriendlyName)); - container.Entries.Add(new PluginText("short_name", "Short Name", defaultDevice.FriendlyName.Replace($"({defaultDevice.DeviceFriendlyName})", ""))); container.Entries.Add(new PluginSensor("volume", "Volume", (float)Math.Round(defaultDevice.AudioEndpointVolume.MasterVolumeLevelScalar * 100), "%")); container.Entries.Add(new PluginText("mute", "Mute", defaultDevice.AudioEndpointVolume.Mute.ToString())); _containers.Add(container); - var devices = deviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active); + var devices = _deviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active); foreach (var device in devices) { container = new(device.ID, device.FriendlyName); @@ -44,6 +43,7 @@ public override void Initialize() public override void Close() { + _deviceEnumerator?.Dispose(); } public override void Load(List containers) @@ -59,8 +59,6 @@ public override Task UpdateAsync(CancellationToken cancellationToken) public override void Update() { - using MMDeviceEnumerator deviceEnumerator = new(); - foreach(var container in _containers) { MMDevice? device = null; @@ -69,11 +67,11 @@ public override void Update() { if (container.Name == "Default") { - device = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + device = _deviceEnumerator?.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); } else { - device = deviceEnumerator.GetDevice(container.Id); + device = _deviceEnumerator?.GetDevice(container.Id); } if (device != null) @@ -82,30 +80,6 @@ public override void Update() { switch (entry.Id) { - case "device_friendly_name": - { - if (entry is PluginText text) - { - text.Value = device.DeviceFriendlyName; - } - } - break; - case "friendly_name": - { - if (entry is PluginText text) - { - text.Value = device.FriendlyName; - } - } - break; - case "short_name": - { - if (entry is PluginText text) - { - text.Value = device.FriendlyName.Replace($"({device.DeviceFriendlyName})", ""); - } - } - break; case "volume": { if (entry is PluginSensor sensor) @@ -131,20 +105,6 @@ public override void Update() device?.Dispose(); } } - - //var devices = deviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active); - //foreach (var device in devices) - //{ - // container = new(device.DeviceFriendlyName); - // container.Entries.Add(new PluginText("name", "Name", device.DeviceFriendlyName)); - // container.Entries.Add(new PluginSensor("volume", "Volume", (float)Math.Round(device.AudioEndpointVolume.MasterVolumeLevelScalar * 100), "%")); - // container.Entries.Add(new PluginText("mute", "Mute", device.AudioEndpointVolume.Mute.ToString())); - // _containers.Add(container); - // device.Dispose(); - //} - - //using var defaultDevice = _deviceEnumerator?.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - //_volumeSensor.Value = (float)Math.Round((defaultDevice?.AudioEndpointVolume.MasterVolumeLevelScalar ?? 0) * 100); } } } diff --git a/InfoPanel.Extras/WeatherPlugin.cs b/InfoPanel.Extras/WeatherPlugin.cs index 27bf35c..5a2e25a 100644 --- a/InfoPanel.Extras/WeatherPlugin.cs +++ b/InfoPanel.Extras/WeatherPlugin.cs @@ -38,7 +38,7 @@ public class WeatherPlugin : BasePlugin private readonly PluginSensor _rain = new("rain", "Rain", 0, "mm/h"); private readonly PluginSensor _snow = new("snow", "Snow", 0, "mm/h"); - public WeatherPlugin() : base("Weather Plugin") + public WeatherPlugin() : base("weather-plugin","Weather Info - OpenWeatherMap") { } diff --git a/InfoPanel.Plugins.Loader/PluginLoader.cs b/InfoPanel.Plugins.Loader/PluginLoader.cs index c8b447e..802a83f 100644 --- a/InfoPanel.Plugins.Loader/PluginLoader.cs +++ b/InfoPanel.Plugins.Loader/PluginLoader.cs @@ -1,40 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; +using System.Reflection; namespace InfoPanel.Plugins.Loader { public class PluginLoader { - public void test(string folder) - { - //var plugins= Directory.GetFiles(folder, "InfoPanel.*.dll"); - //IEnumerable commands = plugins.SelectMany(pluginPath => - //{ - // Assembly pluginAssembly = LoadPlugin(pluginPath); - // return CreateCommands(pluginAssembly); - //}).ToList(); - - //foreach (var command in commands) - //{ - // Trace.WriteLine(command); - // var panelDatas = command.GetData(); - // foreach(var panelData in panelDatas) - // { - // Trace.WriteLine(panelData.CollectionName); - // foreach(var item in panelData.EntryList) - // { - // Trace.WriteLine($"{item.Name}: {item.Value} {item.Unit}"); - // } - // } - //} - } - public IEnumerable InitializePlugin(string pluginPath) { Assembly pluginAssembly = LoadPlugin(pluginPath); @@ -72,7 +41,5 @@ static IEnumerable CreateCommands(Assembly assembly) $"Available types: {availableTypes}"); } } - - } } diff --git a/InfoPanel.Plugins.Loader/PluginWrapper.cs b/InfoPanel.Plugins.Loader/PluginWrapper.cs index 983acaf..6468ac4 100644 --- a/InfoPanel.Plugins.Loader/PluginWrapper.cs +++ b/InfoPanel.Plugins.Loader/PluginWrapper.cs @@ -126,6 +126,11 @@ private void DisposeResources() _task?.Dispose(); _cts = null; _task = null; + + try + { + Plugin.Close(); + }catch { } } } } diff --git a/InfoPanel/App.xaml.cs b/InfoPanel/App.xaml.cs index b155946..106b47d 100644 --- a/InfoPanel/App.xaml.cs +++ b/InfoPanel/App.xaml.cs @@ -203,9 +203,6 @@ protected override async void OnStartup(StartupEventArgs e) Exit += App_Exit; await StartPanels(); - - - testPlugin(); } private void OnSessionEnding(object sender, SessionEndingEventArgs e) @@ -335,11 +332,5 @@ private void DisplayWindow_Closed(object? sender, EventArgs e) DisplayWindows.Remove(displayWindow.Profile.Guid); } } - - public void testPlugin() - { - var pluginLoader = new PluginLoader(); - pluginLoader.test("plugins"); - } } } diff --git a/InfoPanel/InfoPanel.csproj b/InfoPanel/InfoPanel.csproj index 0f48d1f..99b9d73 100644 --- a/InfoPanel/InfoPanel.csproj +++ b/InfoPanel/InfoPanel.csproj @@ -14,6 +14,36 @@ False en Resources\app.manifest + ..\InfoPanel.Extras\bin\$(Platform)\$(Configuration)\$(TargetFramework) + bin\$(Platform)\$(Configuration)\$(TargetFramework)\plugins + + + + + + + + + + + + + + + + + + + + + + + + full + + + + none @@ -24,28 +54,16 @@ Lib\d2dwinform.dll - Lib\LibreHardwareMonitorLib.dll + Lib\LibreHardwareMonitorLib.dll - Lib\TuringSmartScreenLib.dll + Lib\TuringSmartScreenLib.dll - Lib\TuringSmartScreenLib.Helpers.GDI.dll + Lib\TuringSmartScreenLib.Helpers.GDI.dll - - - - - - full - - - - full - - @@ -97,6 +115,7 @@ + @@ -143,6 +162,7 @@ + diff --git a/InfoPanel/Monitors/PluginMonitor.cs b/InfoPanel/Monitors/PluginMonitor.cs index 8087664..196dbba 100644 --- a/InfoPanel/Monitors/PluginMonitor.cs +++ b/InfoPanel/Monitors/PluginMonitor.cs @@ -11,119 +11,116 @@ namespace InfoPanel.Monitors { - internal class PluginMonitor : BackgroundTask + internal class PluginMonitor: BackgroundTask { private static readonly Lazy _instance = new(() => new PluginMonitor()); public static PluginMonitor Instance => _instance.Value; - private static readonly ConcurrentDictionary HEADER_DICT = new(); public static readonly ConcurrentDictionary SENSORHASH = new(); - private PluginMonitor() { } - + private readonly Dictionary _loadedPlugins = []; + private PluginMonitor() { } - Dictionary loadedPlugins = []; - - private async Task load() + protected override async Task DoWorkAsync(CancellationToken token) { - PluginLoader pluginLoader = new(); - - var currentDirectory = Directory.GetCurrentDirectory(); - var pluginPath = Path.Combine(currentDirectory, "..\\..\\..\\..\\..\\InfoPanel.Extras\\bin\\Debug\\net8.0-windows", "InfoPanel.Extras.dll"); - - var plugins = pluginLoader.InitializePlugin(pluginPath); - + await Task.Delay(300, token); - foreach (var plugin in plugins) + try { - PluginWrapper pluginWrapper = new(plugin); - if (loadedPlugins.TryAdd(pluginWrapper.Name, pluginWrapper)) + var stopwatch = Stopwatch.StartNew(); + await LoadPluginsAsync(); + stopwatch.Stop(); + Trace.WriteLine($"Plugins loaded: {stopwatch.ElapsedMilliseconds}ms"); + + while (!token.IsCancellationRequested) { - try - { - await pluginWrapper.Initialize(); - Console.WriteLine($"Plugin {pluginWrapper.Name} loaded successfully"); - } - catch (Exception ex) + stopwatch.Restart(); + foreach (var plugin in _loadedPlugins.Values) { - Console.WriteLine($"Plugin {pluginWrapper.Name} failed to load: {ex.Message}"); + try + { + plugin.Update(); + } + catch { } } + stopwatch.Stop(); + //Trace.WriteLine($"Plugins updated: {stopwatch.ElapsedMilliseconds}ms"); + await Task.Delay(100, token); } - else + } + catch (TaskCanceledException) + { + Trace.WriteLine("Task cancelled"); + } + catch (Exception ex) + { + Trace.WriteLine($"Exception during work: {ex.Message}"); + } + finally + { + foreach (var loadedPluginWrapper in _loadedPlugins.Values) { - Console.WriteLine($"Plugin {pluginWrapper.Name} already loaded or duplicate plugin/name"); + await loadedPluginWrapper.StopAsync(); } - //break; + _loadedPlugins.Clear(); } - } - protected override async Task DoWorkAsync(CancellationToken token) + private async Task LoadPluginsAsync() { - await Task.Delay(300, token); - try - { - var stopwatch = new Stopwatch(); - stopwatch.Start(); + PluginLoader pluginLoader = new(); - await load(); + var pluginDirectory = Path.Combine(Directory.GetCurrentDirectory(), "plugins"); - stopwatch.Stop(); - Trace.WriteLine($"Plugins loaded: {stopwatch.ElapsedMilliseconds}ms"); - - try + foreach (var directory in Directory.GetDirectories(pluginDirectory)) + { + foreach (var pluginFile in Directory.GetFiles(directory, "InfoPanel.*.dll")) { + var plugins = pluginLoader.InitializePlugin(pluginFile); - while (!token.IsCancellationRequested) + foreach (var plugin in plugins) { - int indexOrder = 0; - foreach (var wrapper in loadedPlugins.Values) + PluginWrapper wrapper = new(plugin); + if (_loadedPlugins.TryAdd(wrapper.Name, wrapper)) { - wrapper.Update(); - - foreach (var container in wrapper.PluginContainers) + try { - foreach (var entry in container.Entries) + await wrapper.Initialize(); + Console.WriteLine($"Plugin {wrapper.Name} loaded successfully"); + + int indexOrder = 0; + foreach (var container in wrapper.PluginContainers) { - var id = $"/{wrapper.Id}/{container.Id}/{entry.Id}"; - SENSORHASH[id] = new() + foreach (var entry in container.Entries) { - Id = id, - Name = entry.Name, - ContainerId = container.Id, - ContainerName = container.Name, - PluginId = wrapper.Id, - PluginName = wrapper.Name, - Data = entry, - IndexOrder = indexOrder++ - }; + var id = $"/{wrapper.Id}/{container.Id}/{entry.Id}"; + SENSORHASH[id] = new() + { + Id = id, + Name = entry.Name, + ContainerId = container.Id, + ContainerName = container.Name, + PluginId = wrapper.Id, + PluginName = wrapper.Name, + Data = entry, + IndexOrder = indexOrder++ + }; + } } } + catch (Exception ex) + { + Console.WriteLine($"Plugin {wrapper.Name} failed to load: {ex.Message}"); + } + } + else + { + Console.WriteLine($"Plugin {wrapper.Name} already loaded or duplicate plugin/name"); } - - await Task.Delay(100, token); - } } - catch (TaskCanceledException) - { - Trace.WriteLine("Task cancelled"); - } - catch (Exception ex) - { - Trace.WriteLine($"Exception during work: {ex.Message}"); - } - finally - { - HEADER_DICT.Clear(); - SENSORHASH.Clear(); - } - } - catch (Exception e) - { - Trace.WriteLine($"LibreMonitor: Init error: {e.Message}"); } } diff --git a/InfoPanel/Resources/Images/logo.ico b/InfoPanel/Resources/Images/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..56a88ff19430a4ec8b6ef11aef97f901d8153f16 GIT binary patch literal 15086 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 + Source="/InfoPanel;component/Resources/Images/logo.ico" />