diff --git a/src/Artemis.Core/Models/Profile/Folder.cs b/src/Artemis.Core/Models/Profile/Folder.cs index a69f1d372..366d146b9 100644 --- a/src/Artemis.Core/Models/Profile/Folder.cs +++ b/src/Artemis.Core/Models/Profile/Folder.cs @@ -96,7 +96,7 @@ namespace Artemis.Core public override void Reset() { DisplayConditionMet = false; - Timeline.JumpToStart(); + Timeline.JumpToEnd(); foreach (ProfileElement child in Children) child.Reset(); diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 4485405d5..5d7bcdb99 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -183,6 +183,8 @@ namespace Artemis.Core General.ShapeType.CurrentValueSet += ShapeTypeOnCurrentValueSet; ApplyShapeType(); ActivateLayerBrush(); + + Reset(); } #region Storage @@ -272,7 +274,7 @@ namespace Artemis.Core public override void Reset() { DisplayConditionMet = false; - Timeline.JumpToStart(); + Timeline.JumpToEnd(); } /// diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index fbcb9a22d..86c8cb571 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -168,8 +168,28 @@ namespace Artemis.Core.Services private void ApplyLoggingLevel() { - _logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value); - LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value; + string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging")); + if (argument != null) + { + // Parse the provided log level + string[] parts = argument.Split('='); + if (parts.Length == 2 && Enum.TryParse(typeof(LogEventLevel), parts[1], true, out object? logLevelArgument)) + { + _logger.Information("Setting logging level to {loggingLevel} from startup argument", (LogEventLevel) logLevelArgument!); + LoggerProvider.LoggingLevelSwitch.MinimumLevel = (LogEventLevel) logLevelArgument; + } + else + { + _logger.Warning("Failed to set log level from startup argument {argument}", argument); + _logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value); + LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value; + } + } + else + { + _logger.Information("Setting logging level to {loggingLevel}", _loggingLevel.Value); + LoggerProvider.LoggingLevelSwitch.MinimumLevel = _loggingLevel.Value; + } } private void SurfaceOnUpdating(UpdatingEventArgs args) diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs index 0aa3a66a3..83bab9211 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs @@ -63,6 +63,13 @@ namespace Artemis.Core.Services /// Whether or not to save the new enabled state void DisablePlugin(Plugin plugin, bool saveState); + /// + /// Imports the plugin contained in the provided ZIP file + /// + /// The full path to the ZIP file that contains the plugin + /// The resulting plugin + Plugin ImportPlugin(string fileName); + /// /// Enables the provided plugin feature /// diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 30e085d6e..633b6d45b 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -273,7 +273,7 @@ namespace Artemis.Core.Services FileInfo[] fileInfos = directory.GetFiles(); if (!fileInfos.Any(f => string.Equals(f.Name, plugin.Info.Main, StringComparison.InvariantCulture))) throw new ArtemisPluginException(plugin, "Plugin main entry casing mismatch at " + plugin.Info.Main); - + // Load the plugin, all types implementing Plugin and register them with DI plugin.PluginLoader = PluginLoader.CreateFromAssemblyFile(mainFile!, configure => { @@ -437,6 +437,55 @@ namespace Artemis.Core.Services OnPluginDisabled(new PluginEventArgs(plugin)); } + /// + public Plugin ImportPlugin(string fileName) + { + DirectoryInfo pluginDirectory = new(Path.Combine(Constants.DataFolder, "plugins")); + + // Find the metadata file in the zip + using ZipArchive archive = ZipFile.OpenRead(fileName); + ZipArchiveEntry? metaDataFileEntry = archive.Entries.FirstOrDefault(e => e.Name == "plugin.json"); + if (metaDataFileEntry == null) + throw new ArtemisPluginException("Couldn't find a plugin.json in " + fileName); + + + using StreamReader reader = new(metaDataFileEntry.Open()); + PluginInfo pluginInfo = CoreJson.DeserializeObject(reader.ReadToEnd())!; + if (!pluginInfo.Main.EndsWith(".dll")) + throw new ArtemisPluginException("Main entry in plugin.json must point to a .dll file" + fileName); + + Plugin? existing = _plugins.FirstOrDefault(p => p.Guid == pluginInfo.Guid); + if (existing != null) + throw new ArtemisPluginException($"A plugin with the same GUID is already loaded: {existing.Info}"); + + string targetDirectory = pluginInfo.Main.Split(".dll")[0].Replace("/", "").Replace("\\", ""); + string uniqueTargetDirectory = targetDirectory; + int attempt = 2; + + // Find a unique folder + while (pluginDirectory.EnumerateDirectories().Any(d => d.Name == uniqueTargetDirectory)) + { + uniqueTargetDirectory = targetDirectory + "-" + attempt; + attempt++; + } + + // Extract everything in the same archive directory to the unique plugin directory + DirectoryInfo directoryInfo = new(Path.Combine(pluginDirectory.FullName, uniqueTargetDirectory)); + Directory.CreateDirectory(directoryInfo.FullName); + string metaDataDirectory = metaDataFileEntry.FullName.Replace(metaDataFileEntry.Name, ""); + foreach (ZipArchiveEntry zipArchiveEntry in archive.Entries) + { + if (zipArchiveEntry.FullName.StartsWith(metaDataDirectory)) + { + string target = Path.Combine(directoryInfo.FullName, zipArchiveEntry.FullName.Remove(0, metaDataDirectory.Length)); + zipArchiveEntry.ExtractToFile(target); + } + } + + // Load the newly extracted plugin and return the result + return LoadPlugin(directoryInfo); + } + #endregion #region Features diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index d185a83fb..a6d91aa1c 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -142,6 +142,7 @@ + diff --git a/src/Artemis.UI/Properties/launchSettings.json b/src/Artemis.UI/Properties/launchSettings.json index be8d62f45..e8d4f8dfb 100644 --- a/src/Artemis.UI/Properties/launchSettings.json +++ b/src/Artemis.UI/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Artemis.UI": { "commandName": "Project", - "commandLineArgs": "--force-elevation" + "commandLineArgs": "--force-elevation --logging=debug" } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml index 13e5b8fc1..7a031426c 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabView.xaml @@ -14,25 +14,35 @@ - + - + + - The list below shows all loaded plugins. If you're missing something, view your logs folder. + The list below shows all loaded plugins. + If you're missing something, view your logs folder. - + Margin="5 0" + Text="{Binding SearchPluginInput, Delay=300, UpdateSourceTrigger=PropertyChanged}" /> + + - + diff --git a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs index ffb530cd3..57f54847c 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs +++ b/src/Artemis.UI/Screens/Settings/Tabs/Plugins/PluginSettingsTabViewModel.cs @@ -2,8 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Ninject.Factories; +using Artemis.UI.Shared.Services; +using Ookii.Dialogs.Wpf; using Stylet; namespace Artemis.UI.Screens.Settings.Tabs.Plugins @@ -11,15 +14,17 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins public class PluginSettingsTabViewModel : Conductor.Collection.AllActive { private readonly IPluginManagementService _pluginManagementService; + private readonly IMessageService _messageService; private readonly ISettingsVmFactory _settingsVmFactory; private string _searchPluginInput; private List _instances; - public PluginSettingsTabViewModel(IPluginManagementService pluginManagementService, ISettingsVmFactory settingsVmFactory) + public PluginSettingsTabViewModel(IPluginManagementService pluginManagementService, IMessageService messageService, ISettingsVmFactory settingsVmFactory) { DisplayName = "PLUGINS"; _pluginManagementService = pluginManagementService; + _messageService = messageService; _settingsVmFactory = settingsVmFactory; } @@ -63,5 +68,25 @@ namespace Artemis.UI.Screens.Settings.Tabs.Plugins base.OnActivate(); } + + public void ImportPlugin() + { + VistaOpenFileDialog dialog = new(); + dialog.Filter = "ZIP files (*.zip)|*.zip"; + dialog.Title = "Import Artemis plugin"; + bool? result = dialog.ShowDialog(); + if (result == true) + { + Plugin plugin = _pluginManagementService.ImportPlugin(dialog.FileName); + + _instances = _pluginManagementService.GetAllPlugins() + .Select(p => _settingsVmFactory.CreatePluginSettingsViewModel(p)) + .OrderBy(i => i.Plugin.Info.Name) + .ToList(); + SearchPluginInput = plugin.Info.Name; + + _messageService.ShowMessage($"Imported plugin: {plugin.Info.Name}"); + } + } } } \ No newline at end of file diff --git a/src/Artemis.UI/packages.lock.json b/src/Artemis.UI/packages.lock.json index 8a022ea4a..2688602e6 100644 --- a/src/Artemis.UI/packages.lock.json +++ b/src/Artemis.UI/packages.lock.json @@ -80,6 +80,12 @@ "Ninject.Extensions.Factory": "3.3.2" } }, + "Ookii.Dialogs.Wpf": { + "type": "Direct", + "requested": "[3.1.0, )", + "resolved": "3.1.0", + "contentHash": "EpUWoSLLO1aVkUmo2dPE4xO+zQZRxbp13agbbQzGBL1ACALgCD69cTwdLdPdg2LIsPcZ4uA3iID+YaazdZxyww==" + }, "RawInput.Sharp": { "type": "Direct", "requested": "[0.0.3, )",