diff --git a/ModManager/App.xaml b/ModManager/App.xaml index 56b342ec..3bf3b6ac 100644 --- a/ModManager/App.xaml +++ b/ModManager/App.xaml @@ -2,9 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Imya.UI" - xmlns:converters="clr-namespace:Imya.UI.ValueConverters" - - StartupUri="MainWindow.xaml"> + xmlns:converters="clr-namespace:Imya.UI.ValueConverters"> diff --git a/ModManager/App.xaml.cs b/ModManager/App.xaml.cs index dbfc263a..fb79696a 100644 --- a/ModManager/App.xaml.cs +++ b/ModManager/App.xaml.cs @@ -1,46 +1,213 @@ using System.Windows; -using Imya.Utils; -using Imya.Models; using Imya.UI.Properties; -using Imya.Enums; using Imya.UI.Utils; using System.Threading.Tasks; -using Imya.Models.Options; -using Imya.Models.Attributes; -using Imya.UI.Models; using Imya.Validation; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Imya.Services.Interfaces; +using Imya.Services; +using Imya.Models.Cache; +using Imya.GithubIntegration; +using System; +using Imya.Utils; +using Imya.Texts; +using Imya.UI.Views; +using Imya.GithubIntegration.StaticData; +using Imya.UI.Models; +using Imya.Models.Options; +using Imya.Models.GameLauncher; +using Imya.Models.Installation.Interfaces; +using Imya.Models.Installation; +using Imya.Models.Mods; +using System.Windows.Forms; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Attributes.Factories; +using Imya.Models.ModTweaker.DataModel.Storage; +using Imya.Models.ModTweaker.IO; +using Imya.UI.Components; +using Imya.Models.ModMetadata; +using Octokit; +using Imya.GithubIntegration.JsonData; +using Imya.GithubIntegration.RepositoryInformation; +using Imya.UI.ValueConverters; +using Anno.Utils; namespace Imya.UI { /// /// Interaction logic for App.xaml /// - public partial class App : Application + public partial class App : System.Windows.Application { - private ModCollectionHooks _hooks; + public static IHost AppHost { get; private set; } public App() { - // load localized text first - var text = TextManager.Instance; - text.LoadLanguageFile(Settings.Default.LanguageFilePath); + AppHost = Host.CreateDefaultBuilder() + .ConfigureServices((hostContext, services) => + { + //services + services.AddSingleton(); + services.AddSingleton(); + var gameSetup = services.BuildServiceProvider().GetRequiredService(); + gameSetup.SetGamePath(Settings.Default.GameRootPath, true); + gameSetup.SetModDirectoryName(Settings.Default.ModDirectoryName); + + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(serviceProvider => new ModCollectionHooks()); + + services.AddSingleton(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + //github integration + var githubClient = new GitHubClient(new ProductHeaderValue("iModYourAnno")); + services.AddSingleton(x => githubClient); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); + + //tweaks + + //game launcher + services.AddSingleton(); + services.AddSingleton(serviceProvider => new SteamGameLauncher( + serviceProvider.GetRequiredService())); + services.AddSingleton(serviceProvider => new StandardGameLauncher( + serviceProvider.GetRequiredService())); + //hooks + services.AddSingleton(serviceProvider => new CyclicDependencyValidator( + serviceProvider.GetRequiredService())); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + //caching + services.AddScoped, TimedCache>(); + + //installation + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddScoped(); + services.AddSingleton(serviceProvider => new GithubInstallationBuilder( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService())); + services.AddSingleton(serviceProvider => new ZipInstallationBuilder( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService())); + services.AddSingleton(); - var gameSetup = GameSetupManager.Instance; - //gameSetup.SetDownloadDirectory(Settings.Default.DownloadDir); + //application + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + + services.AddTransient(); + services.AddTransient(); + services.AddSingleton(); + }) + .Build(); + + + var gameSetup = AppHost.Services.GetRequiredService(); gameSetup.SetGamePath(Settings.Default.GameRootPath, true); gameSetup.SetModDirectoryName(Settings.Default.ModDirectoryName); - var appSettings = new AppSettings(); - GithubClientProvider.Authenticator = new DeviceFlowAuthenticator(); + var factory = AppHost.Services.GetRequiredService(); + var collection = factory.Get(gameSetup.GetModDirectory(), normalize: true, loadImages: true); + var imyaSetup = AppHost.Services.GetRequiredService(); + imyaSetup.GlobalModCollection = collection; + + //subscribe the global mod collection to the gamesetup + var textManager = AppHost.Services.GetRequiredService(); + textManager.LoadLanguageFile(Settings.Default.LanguageFilePath); + //check if this can be moved to OnStartup + var globalMods = AppHost.Services.GetRequiredService().GlobalModCollection; + globalMods.Hooks.AddHook(AppHost.Services.GetRequiredService()); + globalMods.Hooks.AddHook(AppHost.Services.GetRequiredService()); + globalMods.Hooks.AddHook(AppHost.Services.GetRequiredService()); + globalMods.Hooks.AddHook(AppHost.Services.GetRequiredService()); + globalMods.Hooks.AddHook(AppHost.Services.GetRequiredService()); + globalMods.Hooks.AddHook(AppHost.Services.GetRequiredService()); + globalMods.Hooks.AddHook(AppHost.Services.GetRequiredService()); + + + } + + protected override async void OnStartup(StartupEventArgs e) + { + var globalMods = AppHost.Services.GetRequiredService().GlobalModCollection; + await globalMods.LoadModsAsync(); + + var appSettings = AppHost.Services.GetRequiredService(); + appSettings.Initialize(); + var installationService = AppHost.Services.GetRequiredService(); + + if (appSettings.UseRateLimiting) + installationService.DownloadConfig.MaximumBytesPerSecond = appSettings.DownloadRateLimit; + + await AppHost.StartAsync(); - // init global mods - ModCollection.Global = new ModCollection(gameSetup.GetModDirectory(), normalize: true, loadImages: true); - _hooks = new(ModCollection.Global); - Task.Run(() => ModCollection.Global.LoadModsAsync()); + //hacky converters with dependencyinjection.... + Resources.Add("DlcTextConverter", AppHost.Services.GetRequiredService()); + Resources.Add("FilenameValidationConverter", AppHost.Services.GetRequiredService()); - if(AppSettings.Instance.UseRateLimiting) - InstallationManager.Instance.DownloadConfig.MaximumBytesPerSecond = AppSettings.Instance.DownloadRateLimit; + var startupForm = AppHost.Services.GetRequiredService(); + startupForm.Show(); + + base.OnStartup(e); + } + + protected override async void OnExit(ExitEventArgs e) + { + await AppHost.StopAsync(); + base.OnExit(e); } } } diff --git a/ModManager/Controls/FancyToggle.xaml.cs b/ModManager/Controls/FancyToggle.xaml.cs index 3d2eee5b..96116fdf 100644 --- a/ModManager/Controls/FancyToggle.xaml.cs +++ b/ModManager/Controls/FancyToggle.xaml.cs @@ -1,4 +1,5 @@ using Imya.Models; +using Imya.Texts; using Imya.Utils; using System; using System.Windows; @@ -51,23 +52,7 @@ public FancyToggle() InitializeComponent(); OnTextBlock.SizeChanged += OnTextBlock_SizeChanged; - OffTextBlock.SizeChanged += OnTextBlock_SizeChanged; - - Binding onBinding = new() - { - Source = TextManager.Instance.GetText("CONTROLS_TOGGLE_DEFAULT_ONTEXT") as LocalizedText, - Path = new PropertyPath("Text"), - UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged - }; - SetBinding(OnTextProperty, onBinding); - - Binding offBinding = new() - { - Source = TextManager.Instance.GetText("CONTROLS_TOGGLE_DEFAULT_OFFTEXT") as LocalizedText, - Path = new PropertyPath("Text"), - UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged - }; - SetBinding(OffTextProperty, offBinding); + OffTextBlock.SizeChanged += OnTextBlock_SizeChanged; } private void OnTextBlock_SizeChanged(object sender, SizeChangedEventArgs e) diff --git a/ModManager/IMainViewController.cs b/ModManager/IMainViewController.cs new file mode 100644 index 00000000..af878317 --- /dev/null +++ b/ModManager/IMainViewController.cs @@ -0,0 +1,14 @@ +using System.Windows.Controls; + +namespace Imya.UI +{ + public interface IMainViewController + { + public delegate void ViewChangedEventHandler(View view); + public event ViewChangedEventHandler ViewChanged; + + void SetView(View view); + void GoToLastView(); + View GetView(); + } +} diff --git a/ModManager/MainViewController.cs b/ModManager/MainViewController.cs index 5a6eaef5..d400050d 100644 --- a/ModManager/MainViewController.cs +++ b/ModManager/MainViewController.cs @@ -1,6 +1,9 @@ -using Imya.UI.Utils; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.UI.Utils; using Imya.UI.Views; -using Imya.Utils; +using Microsoft.Extensions.DependencyInjection; +using System; using System.ComponentModel; using System.Windows.Controls; @@ -10,36 +13,31 @@ public enum View { MOD_ACTIVATION, SETTINGS, TWEAKER, - GAME_SETUP, MODINFO_CREATOR, MOD_INSTALLATION, - GITHUB_BROWSER, + GITHUB_BROWSER } - public class MainViewController : INotifyPropertyChanged + public class MainViewController : INotifyPropertyChanged, IMainViewController { - private static readonly ModActivationView _modActivation = new(); - private static readonly SettingsView _settings = new(); - private static readonly ModTweakerView _tweaker = new(); - private static readonly GameSetupView _gameSetup = new(); - private static readonly ModinfoCreatorView _modinfoCreator = new(); - private static readonly InstallationView _modInstallation = new(); - private static readonly GithubBrowserView _githubBrowser = new(); + private readonly ModActivationView _modActivation; + private readonly SettingsView _settings; + private readonly ModTweakerView _tweaker; + private readonly ModinfoCreatorView _modinfoCreator; + private readonly InstallationView _modInstallation; + private readonly GithubBrowserView _githubBrowser; + private readonly PopupCreator _popupCreator; - public static MainViewController Instance { get; } = new(); + IServiceProvider _serviceProvider; - public delegate void ViewChangedEventHandler(View view); - public event ViewChangedEventHandler ViewChanged = delegate { }; + public event IMainViewController.ViewChangedEventHandler ViewChanged = delegate { }; - public static readonly View DefaultView = View.MOD_ACTIVATION; - public static readonly UserControl DefaultControl = _modInstallation; + public readonly View DefaultView = View.MOD_ACTIVATION; + public readonly UserControl DefaultControl; - private MainViewController() - { - _currentView = GetViewControl(DefaultView); - } + private ITweakService _tweakService; + private View _lastView; - private View _lastView = DefaultView; private UserControl _currentView; public UserControl CurrentView { @@ -51,6 +49,21 @@ public UserControl CurrentView } } + public MainViewController( + ITweakService tweakService, + IServiceProvider serviceProvider, + PopupCreator popupCreator) + { + _serviceProvider = serviceProvider; + _tweakService = tweakService; + _popupCreator = popupCreator; + + DefaultControl = _serviceProvider.GetRequiredService(); + _currentView = GetViewControl(DefaultView); + _lastView = DefaultView; + + } + #region INotifyPropertyChangedMembers public event PropertyChangedEventHandler? PropertyChanged; private void OnPropertyChanged(string propertyName) @@ -68,11 +81,11 @@ public void SetView(View view) if (currentView == View.TWEAKER && view != View.TWEAKER) { - if (TweakManager.Instance.HasUnsavedChanges) + if (_tweakService.HasUnsavedChanges) { - var dialog = PopupCreator.CreateSaveTweakPopup(); + var dialog = _popupCreator.CreateSaveTweakPopup(); if (dialog.ShowDialog() is true) - TweakManager.Instance.Save(); + _tweakService.Save(); } } CurrentView = GetViewControl(view); @@ -82,17 +95,16 @@ public void SetView(View view) ViewChanged(view); } - private static UserControl GetViewControl(View view) + private UserControl GetViewControl(View view) { return view switch { - View.MOD_ACTIVATION => _modActivation, - View.SETTINGS => _settings, - View.TWEAKER => _tweaker, - View.GAME_SETUP => _gameSetup, - View.MODINFO_CREATOR => _modinfoCreator, - View.GITHUB_BROWSER => _githubBrowser, - View.MOD_INSTALLATION => _modInstallation, + View.MOD_ACTIVATION => _serviceProvider.GetRequiredService(), + View.SETTINGS => _serviceProvider.GetRequiredService(), + View.TWEAKER => _serviceProvider.GetRequiredService(), + View.MODINFO_CREATOR => _serviceProvider.GetRequiredService(), + View.GITHUB_BROWSER => _serviceProvider.GetRequiredService(), + View.MOD_INSTALLATION => _serviceProvider.GetRequiredService(), _ => DefaultControl, }; } @@ -110,7 +122,6 @@ public View GetView() ModActivationView => View.MOD_ACTIVATION, SettingsView => View.SETTINGS, ModTweakerView => View.TWEAKER, - GameSetupView => View.GAME_SETUP, ModinfoCreatorView => View.MODINFO_CREATOR, GithubBrowserView => View.GITHUB_BROWSER, InstallationView => View.MOD_INSTALLATION, diff --git a/ModManager/MainWindow.xaml b/ModManager/MainWindow.xaml index ae3e68e4..b1d39bac 100644 --- a/ModManager/MainWindow.xaml +++ b/ModManager/MainWindow.xaml @@ -30,16 +30,14 @@ - - - - - + + await GithubClientProvider.Authenticator.StartAuthentication()); + Task.Run(async () => await authenticator.StartAuthentication()); } - if (GameSetupManager.Instance.NeedsModloaderRemoval()) + if (_gameSetupService.NeedsModloaderRemoval()) { - var result = PopupCreator.CreateModloaderPopup().ShowDialog(); + var result = _popupCreator.CreateModloaderPopup().ShowDialog(); if (result is true) - GameSetupManager.Instance.RemoveModloader(); + _gameSetupService.RemoveModloader(); } // initialize self-updater - SelfUpdater.CheckForUpdate(GithubClientProvider.Client, "anno-mods", "iModYourAnno"); + if(!Settings.DevMode) + selfUpdater.CheckForUpdate("anno-mods", "iModYourAnno"); } public void SetUpEmbeddedConsole() { - Console.SetOut(new EmbeddedConsole(ConsoleLogTextBox.ConsoleOut, this)); + Console.SetOut(new EmbeddedConsole(ConsoleLogTextBox.Console, this)); } } } diff --git a/ModManager/ModManager_Views.csproj b/ModManager/ModManager_Views.csproj index 5d9722df..4d846c29 100644 --- a/ModManager/ModManager_Views.csproj +++ b/ModManager/ModManager_Views.csproj @@ -63,6 +63,7 @@ + diff --git a/ModManager/Utils/AppSettings.cs b/ModManager/Models/AppSettings.cs similarity index 73% rename from ModManager/Utils/AppSettings.cs rename to ModManager/Models/AppSettings.cs index 5f07651d..c026454d 100644 --- a/ModManager/Utils/AppSettings.cs +++ b/ModManager/Models/AppSettings.cs @@ -1,8 +1,9 @@ using Imya.Models; using Imya.Models.NotifyPropertyChanged; using Imya.Models.Options; -using Imya.UI.Models; -using Imya.UI.Utils; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Texts; using Imya.Utils; using System; using System.Collections.Generic; @@ -10,63 +11,13 @@ using System.Windows; using System.Windows.Media; -namespace Imya.UI.Utils +namespace Imya.UI.Models { - public struct LanguageSetting - { - public string LanguageName { get; private init; } - public ApplicationLanguage Language { get; private init; } - - public LanguageSetting(string nameId, ApplicationLanguage lang) - { - var localizedName = TextManager.Instance.GetText(nameId); - localizedName.Update(lang); // this will globally update them, but it doesn't matter in this specific case - LanguageName = localizedName.Text; - Language = lang; - } - } - public struct ThemeSetting + public class AppSettings : PropertyChangedNotifier, IAppSettings { - public IText ThemeName { get; private set; } - public Uri ThemePath { get; private set; } - public String ThemeID { get; private set; } - public Brush ThemePrimaryColorBrush { get; private set; } - public Brush ThemePrimaryColorDarkBrush { get => new SolidColorBrush(Color.Multiply(_ThemePrimaryColor, 0.5f)); } - - private Color _ThemePrimaryColor; - - public ThemeSetting(IText name, String path, String id, Color c) - { - ThemeName = name; - ThemePath = new Uri(path, UriKind.RelativeOrAbsolute); - ThemeID = id; - - _ThemePrimaryColor = c; - ThemePrimaryColorBrush = new SolidColorBrush(_ThemePrimaryColor); - } - } - - public struct SortSetting - { - public IComparer Comparer { get; init; } - public IText SortingName { get; init; } - public String ID { get; init; } - - public SortSetting(IComparer comparer, IText sortingname, String id) - { - Comparer = comparer; - SortingName = sortingname; - ID = id; - } - } - - public class AppSettings : PropertyChangedNotifier - { - public static AppSettings Instance { get; set; } - - private TextManager TextManager = TextManager.Instance; - private GameSetupManager GameSetup = GameSetupManager.Instance; + private readonly ITextManager TextManager; + private readonly IGameSetupService GameSetup; public List Themes { get; } = new List(); public List Languages { get; } = new List(); @@ -75,7 +26,7 @@ public class AppSettings : PropertyChangedNotifier //painfully horrible tbh, this lookup should get better. private ResourceDictionary ThemeDictionary; - public ModInstallationOptions InstallationOptions { get; } = new(); + public IModInstallationOptions InstallationOptions { get; } public bool ShowConsole { @@ -110,7 +61,8 @@ public bool DevMode } } - public String ModDirectoryName { + public string ModDirectoryName + { get => Properties.Settings.Default.ModDirectoryName; set { @@ -121,7 +73,8 @@ public String ModDirectoryName { } } - public String GamePath { + public string GamePath + { get => Properties.Settings.Default.GameRootPath; set { @@ -132,11 +85,11 @@ public String GamePath { } } - public String ModindexLocation + public string ModindexLocation { get => Properties.Settings.Default.ModindexLocation; - set - { + set + { Properties.Settings.Default.ModindexLocation = value; Properties.Settings.Default.Save(); OnPropertyChanged(nameof(ModindexLocation)); @@ -184,7 +137,7 @@ public ThemeSetting Theme public SortSetting Sorting { - get => _sorting; + get => _sorting; set { if (!Sortings.Contains(value)) @@ -193,6 +146,7 @@ public SortSetting Sorting Properties.Settings.Default.Save(); _sorting = value; OnPropertyChanged(nameof(Sorting)); + SortSettingChanged.Invoke(value); } } private SortSetting _sorting; @@ -212,37 +166,40 @@ public long DownloadRateLimit public bool UseRateLimiting { get => Properties.Settings.Default.UseRatelimit; - set + set { - Properties.Settings.Default.UseRatelimit = value; + Properties.Settings.Default.UseRatelimit = value; Properties.Settings.Default.Save(); RateLimitChanged(value ? DownloadRateLimit : 0); OnPropertyChanged(nameof(UseRateLimiting)); } } - public delegate void RateLimitChangedEventHandler(long new_rate_limit); - public event RateLimitChangedEventHandler RateLimitChanged = delegate { }; - + public event IAppSettings.RateLimitChangedEventHandler RateLimitChanged = delegate { }; + public event IAppSettings.SortSettingChangedEventHandler SortSettingChanged = delegate { }; - public AppSettings() + public AppSettings( + IInstallationService installationService, + ITextManager textManager, + IGameSetupService gameSetupService) { + TextManager = textManager; + GameSetup = gameSetupService; + Themes.Add(new ThemeSetting(TextManager["THEME_GREEN"], "Styles/Themes/DarkGreen.xaml", "DarkGreen", Colors.DarkOliveGreen)); Themes.Add(new ThemeSetting(TextManager["THEME_CYAN"], "Styles/Themes/DarkCyan.xaml", "DarkCyan", Colors.DarkCyan)); Themes.Add(new ThemeSetting(TextManager["THEME_LIGHT"], "Styles/Themes/Light.xaml", "Light", Colors.LightGray)); Themes.Add(new ThemeSetting(TextManager["THEME_DARKVIOLET"], "Styles/Themes/DarkViolet.xaml", "DarkViolet", Colors.Purple)); - Languages.Add(new LanguageSetting("SETTINGS_LANG_ENGLISH", ApplicationLanguage.English)); - Languages.Add(new LanguageSetting("SETTINGS_LANG_GERMAN", ApplicationLanguage.German)); + Languages.Add(new LanguageSetting(TextManager["SETTINGS_LANG_ENGLISH"], ApplicationLanguage.English)); + Languages.Add(new LanguageSetting(TextManager["SETTINGS_LANG_GERMAN"], ApplicationLanguage.German)); Sortings.Add(new SortSetting(CompareByActiveCategoryName.Default, TextManager["SORTING_DEFAULT"], "Default")); Sortings.Add(new SortSetting(CompareByCategoryName.Default, TextManager["SORTING_ACTIVE_AGNOSTIC"], "ActiveAgnostic")); Sortings.Add(new SortSetting(CompareByFolder.Default, TextManager["SORTING_BYFOLDER"], "Folder")); Sortings.Add(new SortSetting(ComparebyLoadOrder.Default, TextManager["SORTING_LOADORDER"], "LoadOrder")); - RateLimitChanged += x => InstallationManager.Instance.DownloadConfig.MaximumBytesPerSecond = x; - - Instance ??= this; + RateLimitChanged += x => installationService.DownloadConfig.MaximumBytesPerSecond = x; } private void ChangeLanguage(LanguageSetting lang) @@ -283,7 +240,7 @@ private void LoadThemeSetting() /// We need to defer the loading of ThemeDictionary until the app has started. /// public void Initialize() - { + { ThemeDictionary = Application.Current.Resources.MergedDictionaries[0]; LoadLanguageSetting(); LoadThemeSetting(); diff --git a/ModManager/Models/AuthenticationController.cs b/ModManager/Models/AuthenticationController.cs index 65b0307a..68cf9fb5 100644 --- a/ModManager/Models/AuthenticationController.cs +++ b/ModManager/Models/AuthenticationController.cs @@ -3,6 +3,7 @@ using Imya.UI.Popup; using Imya.UI.Utils; using Imya.Utils; +using Octokit; using System; using System.Collections.Generic; using System.Linq; @@ -11,7 +12,7 @@ namespace Imya.UI.Models { - public class AuthenticationController : PropertyChangedNotifier + public class AuthenticationController : PropertyChangedNotifier, IAuthenticationController { private AuthCodePopup? AuthCodePopup; @@ -36,24 +37,35 @@ public Uri? AvatarUri } private Uri? _uri; - public AuthenticationController() + private IAuthenticator _authenticator; + private IGitHubClient _client; + private readonly PopupCreator _popupCreator; + + public AuthenticationController( + IGitHubClient client, + IAuthenticator authenticator, + PopupCreator popupCreator) { - GithubClientProvider.Authenticator.UserCodeReceived += OnAuthCodeReceived; - GithubClientProvider.Authenticator.AuthenticationSuccess += OnAuthSuccess; + _authenticator = authenticator; + _client = client; + _popupCreator = popupCreator; + + _authenticator.UserCodeReceived += OnAuthCodeReceived; + _authenticator.AuthenticationSuccess += OnAuthSuccess; } public void Authenticate() { - Task.Run(async () => await GithubClientProvider.Authenticator.StartAuthentication()); + Task.Run(async () => await _authenticator.StartAuthentication()); } public void Logout() { - GithubClientProvider.Client.Credentials = Octokit.Credentials.Anonymous; + _client.Connection.Credentials = Octokit.Credentials.Anonymous; IsAuthenticated = false; AvatarUri = null; AuthenticatedUser = null; - GithubClientProvider.Authenticator.RemoveAuthentication(); + _authenticator.RemoveAuthentication(); } private void OnAuthCodeReceived(string AuthCode) @@ -62,7 +74,7 @@ private void OnAuthCodeReceived(string AuthCode) { if (AuthCodePopup is AuthCodePopup) AuthCodePopup.Close(); - AuthCodePopup = new AuthCodePopup(AuthCode); + AuthCodePopup = _popupCreator.CreateAuthCodePopup(AuthCode); AuthCodePopup.Show(); }); } @@ -79,7 +91,7 @@ private void OnAuthSuccess() private async Task UpdateUserLogin() { - var user = await GithubClientProvider.Client.User.Current(); + var user = await _client.User.Current(); AuthenticatedUser = user.Login; Console.WriteLine($"Authenticated as {AuthenticatedUser}"); AvatarUri = new Uri(user.AvatarUrl); diff --git a/ModManager/Models/BindableMod.cs b/ModManager/Models/BindableMod.cs index 4e33563e..1049b9bb 100644 --- a/ModManager/Models/BindableMod.cs +++ b/ModManager/Models/BindableMod.cs @@ -1,5 +1,6 @@ using Imya.Models; using Imya.Models.Attributes; +using Imya.Models.Mods; using System.Windows.Threading; namespace Imya.UI.Models diff --git a/ModManager/Models/BindableModCollection.cs b/ModManager/Models/BindableModCollection.cs index 3ac5cadc..9039d679 100644 --- a/ModManager/Models/BindableModCollection.cs +++ b/ModManager/Models/BindableModCollection.cs @@ -1,4 +1,5 @@ using Imya.Models; +using Imya.Models.Mods; using System; using System.Collections.Generic; using System.Windows; diff --git a/ModManager/Models/IAppSettings.cs b/ModManager/Models/IAppSettings.cs new file mode 100644 index 00000000..e948769b --- /dev/null +++ b/ModManager/Models/IAppSettings.cs @@ -0,0 +1,94 @@ +using Imya.Models; +using Imya.Models.Mods; +using Imya.Models.Options; +using Imya.Utils; +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace Imya.UI.Models +{ + public struct LanguageSetting + { + public string LanguageName { get; private init; } + public ApplicationLanguage Language { get; private init; } + + public LanguageSetting(IText text, ApplicationLanguage lang) + { + var localizedName = text; + localizedName.Update(lang); // this will globally update them, but it doesn't matter in this specific case + LanguageName = localizedName.Text; + Language = lang; + } + } + + public struct ThemeSetting + { + public IText ThemeName { get; private set; } + public Uri ThemePath { get; private set; } + public string ThemeID { get; private set; } + public Brush ThemePrimaryColorBrush { get; private set; } + public Brush ThemePrimaryColorDarkBrush { get => new SolidColorBrush(Color.Multiply(_ThemePrimaryColor, 0.5f)); } + + private Color _ThemePrimaryColor; + + public ThemeSetting(IText name, string path, string id, Color c) + { + ThemeName = name; + ThemePath = new Uri(path, UriKind.RelativeOrAbsolute); + ThemeID = id; + + _ThemePrimaryColor = c; + ThemePrimaryColorBrush = new SolidColorBrush(_ThemePrimaryColor); + } + } + + public struct SortSetting + { + public IComparer Comparer { get; init; } + public IText SortingName { get; init; } + public string ID { get; init; } + + public SortSetting(IComparer comparer, IText sortingname, string id) + { + Comparer = comparer; + SortingName = sortingname; + ID = id; + } + } + + public interface IAppSettings + { + delegate void RateLimitChangedEventHandler(long new_rate_limit); + delegate void SortSettingChangedEventHandler(SortSetting sortSetting); + + List Themes { get; } + List Languages { get; } + List Sortings { get; } + + IModInstallationOptions InstallationOptions { get; } + bool ShowConsole { get; set; } + bool ModCreatorMode { get; set; } + bool DevMode { get; set; } + bool ModloaderEnabled { get; set; } + long DownloadRateLimit { get; set; } + bool UseRateLimiting { get; set; } + string ModDirectoryName { get; set; } + string GamePath { get; set; } + string ModindexLocation { get; set; } + + LanguageSetting Language { get; set; } + ThemeSetting Theme { get; set; } + SortSetting Sorting { get; set; } + + event RateLimitChangedEventHandler RateLimitChanged; + event SortSettingChangedEventHandler SortSettingChanged; + + void Initialize(); + } +} diff --git a/ModManager/Models/IAuthenticationController.cs b/ModManager/Models/IAuthenticationController.cs new file mode 100644 index 00000000..fec8d26d --- /dev/null +++ b/ModManager/Models/IAuthenticationController.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Security; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.UI.Models +{ + public interface IAuthenticationController + { + bool IsAuthenticated { get; } + string? AuthenticatedUser { get; } + Uri? AvatarUri { get; } + + void Authenticate(); + void Logout(); + } +} diff --git a/ModManager/Popup/AddDlcPopup.xaml.cs b/ModManager/Popup/AddDlcPopup.xaml.cs index 23c12dc4..09fe7fd5 100644 --- a/ModManager/Popup/AddDlcPopup.xaml.cs +++ b/ModManager/Popup/AddDlcPopup.xaml.cs @@ -11,6 +11,7 @@ using System.Windows.Controls; using System.Windows.Input; using System.Linq; +using Imya.Services; namespace Imya.UI.Popup { @@ -19,23 +20,13 @@ namespace Imya.UI.Popup /// public partial class AddDlcPopup : Window, INotifyPropertyChanged { - public ObservableCollection Dlcs { get; private set; } - - private static String ProfilesDirectoryPath = ImyaSetupManager.Instance.ProfilesDirectoryPath; - + public ObservableCollection Dlcs { get; init; } public DlcId[] SelectedIDs { get; private set; } = Array.Empty(); - public TextManager TextManager { get; private set; } = TextManager.Instance; - - public AddDlcPopup( IEnumerable dlcIds) + public AddDlcPopup() { - Dlcs = new ObservableCollection(dlcIds); - InitializeComponent(); DataContext = this; - - Title = TextManager.Instance["PROFILE_LOAD"].Text; - DlcSelection.SelectionChanged += UpdateSelected; } diff --git a/ModManager/Popup/AuthCodePopup.xaml.cs b/ModManager/Popup/AuthCodePopup.xaml.cs index 7c3fde2e..59864b5f 100644 --- a/ModManager/Popup/AuthCodePopup.xaml.cs +++ b/ModManager/Popup/AuthCodePopup.xaml.cs @@ -31,9 +31,6 @@ public AuthCodePopup(String authcode) { InitializeComponent(); DataContext = this; - - MESSAGE = TextManager.Instance.GetText("USERCODE_POPUP_MESSAGE"); - CANCEL_TEXT = new SimpleText("Cancel"); AuthCode = authcode; } diff --git a/ModManager/Popup/ProfilesLoadPopup.xaml b/ModManager/Popup/ProfilesLoadPopup.xaml index 38528e05..de140f8c 100644 --- a/ModManager/Popup/ProfilesLoadPopup.xaml +++ b/ModManager/Popup/ProfilesLoadPopup.xaml @@ -31,7 +31,7 @@ - @@ -74,7 +74,7 @@ Margin="15,0" Height="30" Foreground="{StaticResource TextColorBrush}" - Content="{Binding TextManager.Instance[DIALOG_CANCEL]}" + Content="{Binding CANCEL_TEXT.Text}" HorizontalContentAlignment="Center" Click="CancelButtonClick" /> diff --git a/ModManager/Popup/ProfilesLoadPopup.xaml.cs b/ModManager/Popup/ProfilesLoadPopup.xaml.cs index 3aa1f36f..bef6c6a9 100644 --- a/ModManager/Popup/ProfilesLoadPopup.xaml.cs +++ b/ModManager/Popup/ProfilesLoadPopup.xaml.cs @@ -1,4 +1,6 @@ using Imya.Models; +using Imya.Services; +using Imya.Services.Interfaces; using Imya.Utils; using System; using System.Collections.ObjectModel; @@ -14,16 +16,14 @@ namespace Imya.UI.Popup /// /// Interaktionslogik für ProfilesPopup.xaml /// - public partial class ProfilesLoadPopup : Window, INotifyPropertyChanged + public partial class ProfilesLoadPopup : Window, INotifyPropertyChanged { - public ObservableCollection Profiles { get; private set; } = new ObservableCollection(); - - private static String ProfilesDirectoryPath = ImyaSetupManager.Instance.ProfilesDirectoryPath; + public IText OK_TEXT { get; set; } + public IText CANCEL_TEXT { get; set; } + public ObservableCollection Profiles { get; private set; } public ModActivationProfile? SelectedProfile { get; private set; } - public TextManager TextManager { get; private set; } = TextManager.Instance; - public bool HasSelection { get => _hasSelection; @@ -35,17 +35,15 @@ public bool HasSelection } private bool _hasSelection = false; - public ProfilesLoadPopup() + private readonly IProfilesService _profilesService; + + public ProfilesLoadPopup(IProfilesService profilesService) { + _profilesService = profilesService; InitializeComponent(); DataContext = this; - - Title = TextManager.Instance["PROFILE_LOAD"].Text; - - ProfileSelection.SelectionChanged += UpdateSelection; Load(); - } private void UpdateSelection(object sender, SelectionChangedEventArgs e) @@ -53,33 +51,13 @@ private void UpdateSelection(object sender, SelectionChangedEventArgs e) HasSelection = ProfileSelection.SelectedIndex >= 0; } - public void Load() - { - String ProfilesDirectory = ProfilesDirectoryPath; - - if (!Directory.Exists(ProfilesDirectory)) - { - Directory.CreateDirectory(ProfilesDirectory); - } - - foreach (String file in Directory.EnumerateFiles(ProfilesDirectory, "*."+ModActivationProfile.ProfileExtension)) - { - AddProfile(file); - } - } - - public void AddProfile(string filePath) - { - var profile = ModActivationProfile.FromFile(filePath); - if (profile is not null) - Profiles.Add(profile); - } + public void Load() => Profiles = new(_profilesService.GetSavedKeys()); public void Accept() { if (ProfileSelection.SelectedIndex != -1) { - SelectedProfile = (ModActivationProfile)ProfileSelection.SelectedItem!; + SelectedProfile = _profilesService.LoadProfile((String)ProfileSelection.SelectedItem!); } } @@ -98,17 +76,12 @@ public void DeleteButtonClick(object sender, RoutedEventArgs e) { Console.WriteLine("Delete requested"); - Button? button = sender as Button; - ModActivationProfile? profile = button?.DataContext as ModActivationProfile; - if(profile is not null) DeleteActivationProfile(profile); - } - - public void DeleteActivationProfile(ModActivationProfile profile) - { - Profiles.Remove(profile); - if (profile.HasFilename) + var button = sender as Button; + var key = button?.DataContext as String; + if (key is not null) { - File.Delete(profile.Filename!); + _profilesService.DeleteActivationProfile(key); + Profiles.Remove(key); } } diff --git a/ModManager/Popup/ProfilesSavePopup.xaml b/ModManager/Popup/ProfilesSavePopup.xaml index 0a317082..398f756b 100644 --- a/ModManager/Popup/ProfilesSavePopup.xaml +++ b/ModManager/Popup/ProfilesSavePopup.xaml @@ -17,7 +17,6 @@ - @@ -45,7 +44,7 @@ x:Name="NameTextbox"> diff --git a/ModManager/Popup/ProfilesSavePopup.xaml.cs b/ModManager/Popup/ProfilesSavePopup.xaml.cs index a998d967..05bd1a00 100644 --- a/ModManager/Popup/ProfilesSavePopup.xaml.cs +++ b/ModManager/Popup/ProfilesSavePopup.xaml.cs @@ -16,6 +16,8 @@ using Imya.Utils; using System.ComponentModel; using System.Runtime.CompilerServices; +using Imya.Services; +using Imya.Services.Interfaces; namespace Imya.UI.Popup { @@ -31,8 +33,7 @@ public enum FilenameValidation public partial class ProfilesSavePopup : Window, INotifyPropertyChanged { public String ProfileFilename { get; set; } = "Profile1"; - - private static String ProfilesDirectoryPath = ImyaSetupManager.Instance.ProfilesDirectoryPath; + public String ProfilesDirectoryPath { get; init; } public bool IsValidFilename { @@ -55,32 +56,27 @@ private set } private FilenameValidation _filenameValidation; - - public String FullFilename { get => Path.Combine(ProfilesDirectoryPath, ProfileFilename + "." + ModActivationProfile.ProfileExtension); } - public IText OK_TEXT { get; set; } public IText CANCEL_TEXT { get; set; } - public ProfilesSavePopup() + private readonly IProfilesService _profilesService; + + public ProfilesSavePopup(IProfilesService profilesService) { + _profilesService = profilesService; InitializeComponent(); - - OK_TEXT = TextManager.Instance["DIALOG_OKAY"]; - CANCEL_TEXT = TextManager.Instance["DIALOG_CANCEL"]; - DataContext = this; - NameTextbox.TextChanged += FilenameChanged; - - Title = TextManager.Instance["PROFILE_SAVE"].Text; } private FilenameValidation ValidateFilename() { char[] Invalids = Path.GetInvalidFileNameChars(); - if (ProfileFilename.Any(x => Invalids.Contains(x))) return FilenameValidation.Invalid; - if (File.Exists(FullFilename)) return FilenameValidation.AlreadyExists; + if (ProfileFilename.Any(x => Invalids.Contains(x))) + return FilenameValidation.Invalid; + if (_profilesService.ProfileExists(ProfileFilename)) + return FilenameValidation.AlreadyExists; return FilenameValidation.Valid; } diff --git a/ModManager/Utils/DeviceFlowAuthenticator.cs b/ModManager/Utils/DeviceFlowAuthenticator.cs index 61408a5e..92731868 100644 --- a/ModManager/Utils/DeviceFlowAuthenticator.cs +++ b/ModManager/Utils/DeviceFlowAuthenticator.cs @@ -20,9 +20,16 @@ public class DeviceFlowAuthenticator : IAuthenticator public event IAuthenticator.PopupRequestedEventHandler UserCodeReceived = delegate { }; public event IAuthenticator.AuthenticatedEventHandler AuthenticationSuccess = delegate { }; + private IGitHubClient _client; + + public DeviceFlowAuthenticator(IGitHubClient client) + { + _client = client; + } + public async Task StartAuthentication() { - if (GithubClientProvider.Client.Credentials.AuthenticationType != AuthenticationType.Anonymous) { + if (_client.Connection.Credentials.AuthenticationType != AuthenticationType.Anonymous) { Console.WriteLine("Already logged in"); return; } @@ -39,7 +46,7 @@ public async Task StartAuthentication() SaveOauthToken(access_token); } var credentials = new Credentials(access_token); - GithubClientProvider.Client.Credentials = credentials; + _client.Connection.Credentials = credentials; AuthenticationSuccess?.Invoke(); } @@ -47,11 +54,11 @@ public async Task StartAuthentication() { var request = new OauthDeviceFlowRequest(ClientID); - var deviceFlowResponse = await GithubClientProvider.Client.Oauth.InitiateDeviceFlow(request); + var deviceFlowResponse = await _client.Oauth.InitiateDeviceFlow(request); UserCodeReceived?.Invoke(deviceFlowResponse.UserCode); OpenInBrowser(deviceFlowResponse.VerificationUri); - var token = await GithubClientProvider.Client.Oauth.CreateAccessTokenForDeviceFlow(ClientID, deviceFlowResponse); + var token = await _client.Oauth.CreateAccessTokenForDeviceFlow(ClientID, deviceFlowResponse); return token; } diff --git a/ModManager/Utils/PopupCreator.cs b/ModManager/Utils/PopupCreator.cs index 15a4af75..07db0e4f 100644 --- a/ModManager/Utils/PopupCreator.cs +++ b/ModManager/Utils/PopupCreator.cs @@ -1,77 +1,124 @@ -using Imya.GithubIntegration.Download; +using Imya.Enums; +using Imya.GithubIntegration.Download; using Imya.Models; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Texts; using Imya.UI.Popup; -using Imya.Utils; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.ObjectModel; namespace Imya.UI.Utils { - internal class PopupCreator + public class PopupCreator { - static TextManager TextManager = TextManager.Instance; - public static GenericOkayPopup CreateSaveTweakPopup() + private readonly ITextManager _textManager; + private readonly ITweakService _tweakService; + private readonly IImyaSetupService _imyaSetupService; + + private readonly IServiceProvider _serviceProvider; + + public PopupCreator( + ITextManager textManager, + ITweakService tweakService, + IImyaSetupService imyaSetupService, + IServiceProvider serviceProvider) + { + _textManager = textManager; + _tweakService = tweakService; + _imyaSetupService = imyaSetupService; + _serviceProvider = serviceProvider; + } + public GenericOkayPopup CreateSaveTweakPopup() { var dialog = new GenericOkayPopup() { - MESSAGE = TextManager.GetText("TWEAK_UNSAVED_CHANGES"), - OK_TEXT = TextManager.GetText("TWEAK_SAVE"), - CANCEL_TEXT = TextManager.GetText("TWEAK_DISCARD"), - OkayAction = async () => await TweakManager.Instance.SaveAsync(), - CancelAction = () => TweakManager.Instance.Unload() + MESSAGE = _textManager.GetText("TWEAK_UNSAVED_CHANGES"), + OK_TEXT = _textManager.GetText("TWEAK_SAVE"), + CANCEL_TEXT = _textManager.GetText("TWEAK_DISCARD"), + OkayAction = async () => await _tweakService.SaveAsync(), + CancelAction = () => _tweakService.Unload() }; return dialog; } - public static GenericOkayPopup CreateInvalidSetupPopup() + public GenericOkayPopup CreateInvalidSetupPopup() { GenericOkayPopup popup = new GenericOkayPopup() { - MESSAGE = TextManager.GetText("ATTRIBUTE_GAMESTART_WARNING"), - CANCEL_TEXT = TextManager.GetText("DIALOG_CANCEL"), - OK_TEXT = TextManager.GetText("DIALOG_OKAY"), + MESSAGE = _textManager.GetText("ATTRIBUTE_GAMESTART_WARNING"), + CANCEL_TEXT = _textManager.GetText("DIALOG_CANCEL"), + OK_TEXT = _textManager.GetText("DIALOG_OKAY"), }; return popup; } - public static GenericOkayPopup CreateInstallationAlreadyRunningPopup() => new() + public GenericOkayPopup CreateInstallationAlreadyRunningPopup() => new() { - MESSAGE = TextManager.GetText("POPUP_INSTALLATION_ALREADY_RUNNING"), - OK_TEXT = TextManager.GetText("DIALOG_OKAY"), + MESSAGE = _textManager.GetText("POPUP_INSTALLATION_ALREADY_RUNNING"), + OK_TEXT = _textManager.GetText("DIALOG_OKAY"), HasCancelButton = false }; - public static GenericOkayPopup CreateExceptionPopup(Exception e) => new() + public GenericOkayPopup CreateExceptionPopup(Exception e) => new() { MESSAGE = new SimpleText(e.Message), - OK_TEXT = TextManager.GetText("DIALOG_OKAY"), + OK_TEXT = _textManager.GetText("DIALOG_OKAY"), HasCancelButton = false }; - public static GenericOkayPopup CreateApiRateExceededPopup() => new() + public GenericOkayPopup CreateApiRateExceededPopup() => new() { - MESSAGE = TextManager.GetText("API_RATELIMIT_REACHED"), - OK_TEXT = TextManager.GetText("DIALOG_OKAY"), + MESSAGE = _textManager.GetText("API_RATELIMIT_REACHED"), + OK_TEXT = _textManager.GetText("DIALOG_OKAY"), HasCancelButton = false }; - public static GenericOkayPopup CreateLogoutPopup() => new() + public GenericOkayPopup CreateLogoutPopup() => new() { - MESSAGE = TextManager.GetText("DASHBOARD_LOGOUTCONFIRMATION"), - CANCEL_TEXT = TextManager.GetText("DIALOG_CANCEL"), - OK_TEXT = TextManager.GetText("DIALOG_OKAY"), + MESSAGE = _textManager.GetText("DASHBOARD_LOGOUTCONFIRMATION"), + CANCEL_TEXT = _textManager.GetText("DIALOG_CANCEL"), + OK_TEXT = _textManager.GetText("DIALOG_OKAY"), }; - public static GenericOkayPopup CreateModloaderPopup() => new() + public GenericOkayPopup CreateModloaderPopup() => new() { - MESSAGE = TextManager.GetText("STARTUP_REMOVECOMMUNITYMODLOADER"), - CANCEL_TEXT = TextManager.GetText("STARTUP_REMOVECOMMUNITYMODLOADER_NO"), - OK_TEXT = TextManager.GetText("STARTUP_REMOVECOMMUNITYMODLOADER_YES"), + MESSAGE = _textManager.GetText("STARTUP_REMOVECOMMUNITYMODLOADER"), + CANCEL_TEXT = _textManager.GetText("STARTUP_REMOVECOMMUNITYMODLOADER_NO"), + OK_TEXT = _textManager.GetText("STARTUP_REMOVECOMMUNITYMODLOADER_YES"), }; + public AuthCodePopup CreateAuthCodePopup(string AuthCode) => new AuthCodePopup(AuthCode) + { + MESSAGE = _textManager.GetText("USERCODE_POPUP_MESSAGE"), + CANCEL_TEXT = _textManager.GetText("DIALOG_CANCEL") + }; + + public AddDlcPopup CreateAddDlcPopup(IEnumerable dlcIds) => new() + { + Title = _textManager.GetText("PROFILE_LOAD").Text, + Dlcs = new ObservableCollection(dlcIds) + }; + + public ProfilesLoadPopup CreateProfilesLoadPopup() { + var popup = new ProfilesLoadPopup(_serviceProvider.GetRequiredService()) + { + OK_TEXT = _textManager.GetText("DIALOG_OKAY"), + CANCEL_TEXT = _textManager.GetText("DIALOG_CANCEL"), + Title = _textManager.GetText("PROFILE_LOAD").Text + }; + popup.Load(); + return popup; + } + + public ProfilesSavePopup CreateProfilesSavePopup() => new(_serviceProvider.GetRequiredService()) + { + OK_TEXT = _textManager.GetText("DIALOG_OKAY"), + CANCEL_TEXT = _textManager.GetText("DIALOG_CANCEL"), + Title = _textManager.GetText("PROFILE_SAVE").Text + }; } } diff --git a/ModManager/Utils/SelfUpdater.cs b/ModManager/Utils/SelfUpdater.cs index 16413c5f..cb1438dc 100644 --- a/ModManager/Utils/SelfUpdater.cs +++ b/ModManager/Utils/SelfUpdater.cs @@ -6,13 +6,20 @@ namespace Anno.Utils { - internal class SelfUpdater + public class SelfUpdater { - public static void CheckForUpdate(GitHubClient client, string owner, string repo) + private readonly IGitHubClient _client; + + public SelfUpdater(IGitHubClient client) + { + _client = client; + } + + public void CheckForUpdate(string owner, string repo) { Task.Run(async () => { - var availableUpdate = await IsUpdateAvailableAsync(client, owner, repo); + var availableUpdate = await IsUpdateAvailableAsync(owner, repo); if (availableUpdate is not null) { var answer = MessageBox.Show($"iModYourAnno {availableUpdate.TagName} is available.\n\nDo you want to download it now?", @@ -25,9 +32,9 @@ public static void CheckForUpdate(GitHubClient client, string owner, string repo }); } - public async static Task IsUpdateAvailableAsync(GitHubClient client, string owner, string repo) + public async Task IsUpdateAvailableAsync(string owner, string repo) { - var latest = await client.Repository.Release.GetLatest(owner, repo); + var latest = await _client.Repository.Release.GetLatest(owner, repo); if (latest is null) return null; Console.WriteLine( @@ -41,7 +48,7 @@ public static void CheckForUpdate(GitHubClient client, string owner, string repo return (latestVersion > currentVersion) ? latest : null; } - public static void OpenReleasePage(Release release) + public void OpenReleasePage(Release release) { var info = new ProcessStartInfo(release.HtmlUrl) { @@ -50,7 +57,7 @@ public static void OpenReleasePage(Release release) Process.Start(info); } - public static Version GetCurrentVersion() + public Version GetCurrentVersion() { System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); diff --git a/ModManager/ValueConverters/AttributeConverters.cs b/ModManager/ValueConverters/AttributeConverters.cs index 0c55986f..302fea57 100644 --- a/ModManager/ValueConverters/AttributeConverters.cs +++ b/ModManager/ValueConverters/AttributeConverters.cs @@ -1,4 +1,5 @@ using Imya.Models.Attributes; +using Imya.Models.Attributes.Factories; using System; using System.Globalization; using System.Windows; diff --git a/ModManager/ValueConverters/DlcTextConverter.cs b/ModManager/ValueConverters/DlcTextConverter.cs index bb6b7744..9ba8b481 100644 --- a/ModManager/ValueConverters/DlcTextConverter.cs +++ b/ModManager/ValueConverters/DlcTextConverter.cs @@ -1,11 +1,14 @@ using Imya.Enums; using Imya.Models; +using Imya.Texts; using Imya.Utils; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Windows.Data; +using System.Windows.Markup; namespace Imya.UI.ValueConverters { @@ -13,9 +16,14 @@ namespace Imya.UI.ValueConverters /// A converter that maps DLC IDs to LocalizedTexts /// [ValueConversion(typeof(DlcId), typeof(LocalizedText))] - internal class DlcTextConverter : IValueConverter + public class DlcTextConverter : IValueConverter { - private TextManager TextManager = TextManager.Instance; + private ITextManager TextManager; + + public DlcTextConverter(ITextManager textManager) + { + TextManager = textManager; + } private Dictionary DlcTextMapping = new Dictionary { @@ -62,5 +70,6 @@ public object ConvertBack(object value, Type TargetType, object parameter, Cultu { throw new NotImplementedException(); } + } } diff --git a/ModManager/ValueConverters/FilenameValidationConverter.cs b/ModManager/ValueConverters/FilenameValidationConverter.cs index 754d3de0..4bbd548c 100644 --- a/ModManager/ValueConverters/FilenameValidationConverter.cs +++ b/ModManager/ValueConverters/FilenameValidationConverter.cs @@ -1,6 +1,8 @@ using Imya.Models; +using Imya.Texts; using Imya.UI.Popup; using Imya.Utils; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Globalization; @@ -9,20 +11,27 @@ using System.Text; using System.Threading.Tasks; using System.Windows.Data; +using System.Windows.Markup; namespace Imya.UI.ValueConverters { [ValueConversion(typeof(FilenameValidation), typeof(LocalizedText))] public class FilenameValidationConverter : IValueConverter { + private readonly ITextManager _textManager; + public FilenameValidationConverter(ITextManager textManager) + { + _textManager = textManager; + } + public object Convert(object value, Type TargetType, object parameter, CultureInfo Culture) { var status = (FilenameValidation)value; switch (status) { - case FilenameValidation.Invalid: return TextManager.Instance["PROFILESAVE_INVALID"]; - case FilenameValidation.AlreadyExists: return TextManager.Instance["PROFILESAVE_ALREADYEXISTS"]; + case FilenameValidation.Invalid: return _textManager["PROFILESAVE_INVALID"]; + case FilenameValidation.AlreadyExists: return _textManager["PROFILESAVE_ALREADYEXISTS"]; } return IText.Empty; diff --git a/ModManager/Views/Components/AttributeStaticHelp.xaml.cs b/ModManager/Views/Components/AttributeStaticHelp.xaml.cs index 01396483..39e50be2 100644 --- a/ModManager/Views/Components/AttributeStaticHelp.xaml.cs +++ b/ModManager/Views/Components/AttributeStaticHelp.xaml.cs @@ -1,5 +1,6 @@ using Imya.Models; using Imya.Models.Attributes; +using Imya.Texts; using Imya.Utils; using System; using System.Collections.Generic; @@ -32,20 +33,14 @@ public partial class AttributeStaticHelp : UserControl { public AttributeText[] Attributes { get; } - private TextManager TextManager = TextManager.Instance; + private ITextManager TextManager; - public AttributeStaticHelp() + public AttributeStaticHelp(ITextManager textManager) { + TextManager = textManager; + Attributes = new AttributeText[] - { - new() { Attribute = ModStatusAttributeFactory.Get(ModStatus.Updated), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_UPDATEDMOD") }, - new() { Attribute = ModStatusAttributeFactory.Get(ModStatus.New), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_NEWMOD") }, - new() { Attribute = ModStatusAttributeFactory.Get(ModStatus.Obsolete), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_OBSOLETEMOD") }, - new() { Attribute = TweakedAttributeFactory.Get(), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_TWEAKEDMOD") }, - new() { Attribute = MissingModinfoAttributeFactory.Get(), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_NOMODINFO") }, - new() { Attribute = new GenericModContextAttribute(), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_COMPABILITY")}, - new() { Attribute = new ModDependencyIssueAttribute(), Text = TextManager.GetText("ATTRIBUTE_STATICHELP_DEPENDENCY")}, - }; + { }; InitializeComponent(); diff --git a/ModManager/Views/Components/ConsoleLog.xaml.cs b/ModManager/Views/Components/ConsoleLog.xaml.cs index 64b7b414..f2deb36b 100644 --- a/ModManager/Views/Components/ConsoleLog.xaml.cs +++ b/ModManager/Views/Components/ConsoleLog.xaml.cs @@ -7,6 +7,7 @@ namespace Imya.UI.Components /// public partial class ConsoleLog : UserControl { + public TextBox Console { get => ConsoleOut; } public ConsoleLog() { InitializeComponent(); diff --git a/ModManager/Views/Components/Dashboard.xaml.cs b/ModManager/Views/Components/Dashboard.xaml.cs index df2dbbd1..a7f1512e 100644 --- a/ModManager/Views/Components/Dashboard.xaml.cs +++ b/ModManager/Views/Components/Dashboard.xaml.cs @@ -1,5 +1,4 @@ -using Imya.Models; -using Imya.Utils; +using Imya.Utils; using System.Windows; using System.Windows.Controls; using Imya.Enums; @@ -15,6 +14,10 @@ using Imya.UI.Popup; using Imya.UI.Models; using Imya.Models.GameLauncher; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Texts; +using Imya.Models.Mods; namespace Imya.UI.Components { @@ -23,17 +26,20 @@ namespace Imya.UI.Components /// public partial class Dashboard : UserControl, INotifyPropertyChanged { - public TextManager TextManager { get; } = TextManager.Instance; + public ITextManager TextManager { get; } public Properties.Settings Settings { get; } = Properties.Settings.Default; - public GameSetupManager GameSetupManager { get; } = GameSetupManager.Instance; - public MainViewController MainViewController { get; } = MainViewController.Instance; - - public AppSettings AppSettings { get; } = AppSettings.Instance; - - public AuthenticationController AuthenticationController { get; } = new AuthenticationController(); - public IAuthenticator Authenticator { get; } = GithubClientProvider.Authenticator; + public IGameSetupService GameSetupManager { get; init; } + public IMainViewController MainViewController { get; } + public IAppSettings AppSettings { get; init; } + public IAuthenticationController AuthenticationController { get; init; } + + public IAuthenticator Authenticator { get; } + private ITweakService _tweakService; private IGameLauncherFactory _launcherFactory; + private ModCollection _globalMods; + + private PopupCreator _popupCreator; public bool CanStartGame { get => _canStartGame; @@ -44,14 +50,34 @@ private set { } private bool _canStartGame; - public Dashboard() + public Dashboard( + IAuthenticator authenticator, + ITweakService tweakService, + IAppSettings appSettings, + IMainViewController mainViewController, + IGameLauncherFactory gameLauncherFactory, + IGameSetupService gameSetupService, + ITextManager textManager, + PopupCreator popupCreator, + IAuthenticationController authController, + IImyaSetupService imyaSetupService) { + Authenticator = authenticator; + AppSettings = appSettings; + AuthenticationController = authController; + MainViewController = mainViewController; + TextManager = textManager; + GameSetupManager = gameSetupService; + + _launcherFactory = gameLauncherFactory; + _popupCreator = popupCreator; + _tweakService = tweakService; + _globalMods = imyaSetupService.GlobalModCollection; + InitializeComponent(); DataContext = this; - MainViewController.Instance.ViewChanged += UpdateSelection; - _launcherFactory = new GameLauncherFactory(); - + MainViewController.ViewChanged += UpdateSelection; CanStartGame = CheckCanStartGame(); } @@ -66,7 +92,7 @@ private bool CheckCanStartGame() public void BrowserClick(object sender, RoutedEventArgs e) => MainViewController.SetView(View.GITHUB_BROWSER); - public void GameSetupClick(object sender, RoutedEventArgs e) => MainViewController.SetView(View.GAME_SETUP); + public void GameSetupClick(object sender, RoutedEventArgs e) => MainViewController.SetView(View.GITHUB_BROWSER); public void ModTweakerClick(object sender, RoutedEventArgs e) => MainViewController.SetView(View.TWEAKER); @@ -76,18 +102,18 @@ private bool CheckCanStartGame() public void StartGameClick(object sender, RoutedEventArgs e) { - var withUnresolved = ModCollection.Global?.WithAttribute(AttributeType.UnresolvedDependencyIssue); - var withIncompatibleIssue = ModCollection.Global?.WithAttribute(AttributeType.ModCompabilityIssue); + var withUnresolved = _globalMods.WithAttribute(AttributeType.UnresolvedDependencyIssue); + var withIncompatibleIssue = _globalMods.WithAttribute(AttributeType.ModCompabilityIssue); if (withUnresolved?.Count() > 0 || withIncompatibleIssue?.Count() > 0) { - GenericOkayPopup popup = PopupCreator.CreateInvalidSetupPopup(); + GenericOkayPopup popup = _popupCreator.CreateInvalidSetupPopup(); if (popup.ShowDialog() is false) return; } - if (TweakManager.Instance.HasUnsavedChanges) + if (_tweakService.HasUnsavedChanges) { - var dialog = PopupCreator.CreateSaveTweakPopup(); + var dialog = _popupCreator.CreateSaveTweakPopup(); if (dialog.ShowDialog() is false) return; } @@ -118,7 +144,7 @@ private void UpdateSelection(View view) { case View.MOD_ACTIVATION: return ModManagementButton; - case View.GAME_SETUP: + case View.MOD_INSTALLATION: return ModInstallationButton; case View.TWEAKER: return ModTweakerButton; @@ -146,7 +172,7 @@ private void LoginButtonClick(object sender, RoutedEventArgs e) private void LogoutButtonClick(object sender, RoutedEventArgs e) { - var dialogresult = PopupCreator.CreateLogoutPopup().ShowDialog(); + var dialogresult = _popupCreator.CreateLogoutPopup().ShowDialog(); if (dialogresult is false) return; AuthenticationController.Logout(); diff --git a/ModManager/Views/Components/ModDescriptionDisplay.xaml b/ModManager/Views/Components/ModDescriptionDisplay.xaml index d165b2f8..3976a35d 100644 --- a/ModManager/Views/Components/ModDescriptionDisplay.xaml +++ b/ModManager/Views/Components/ModDescriptionDisplay.xaml @@ -19,7 +19,6 @@ - @@ -226,7 +225,7 @@ TextWrapping="Wrap" Margin="0,0,0,0" x:Name="DLC_Dependency_TextboxTemplate" - Text="{Binding Path=., Converter={StaticResource DlcText}, UpdateSourceTrigger=Explicit}" + Text="{Binding Path=., Converter={StaticResource DlcTextConverter}, UpdateSourceTrigger=Explicit}" MaxWidth="{Binding DataContext.KnownIssueTextWidth, ElementName=BaseGrid}" VerticalAlignment="Center" /> diff --git a/ModManager/Views/Components/ModDescriptionDisplay.xaml.cs b/ModManager/Views/Components/ModDescriptionDisplay.xaml.cs index d721b3de..0996ff1c 100644 --- a/ModManager/Views/Components/ModDescriptionDisplay.xaml.cs +++ b/ModManager/Views/Components/ModDescriptionDisplay.xaml.cs @@ -8,6 +8,8 @@ using Imya.Enums; using Imya.Models; using Imya.Models.ModMetadata; +using Imya.Models.Mods; +using Imya.Texts; using Imya.Utils; using Markdown.Xaml; @@ -111,15 +113,16 @@ public String? MarkdownDescription } private String? _markdownDescription; - public TextManager TextManager { get; } = TextManager.Instance; + public ITextManager TextManager { get; init; } public double WindowWidth { get; private set; } - public ModDescriptionDisplay() + public ModDescriptionDisplay(ITextManager textManager) { InitializeComponent(); DataContext = this; - TextManager.Instance.LanguageChanged += OnLanguageChanged; + TextManager = textManager; + TextManager.LanguageChanged += OnLanguageChanged; } public void SetDisplayedMod(Mod? mod) diff --git a/ModManager/Views/Components/ModList.xaml b/ModManager/Views/Components/ModList.xaml index 533f4c96..37ef6e86 100644 --- a/ModManager/Views/Components/ModList.xaml +++ b/ModManager/Views/Components/ModList.xaml @@ -6,7 +6,6 @@ xmlns:local="clr-namespace:Imya.UI.Components" xmlns:converters="clr-namespace:Imya.UI.ValueConverters" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" - d:DataContext="{d:DesignInstance Type=local:ModList}" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> diff --git a/ModManager/Views/Components/ModList.xaml.cs b/ModManager/Views/Components/ModList.xaml.cs index 2cdcbde9..62e4aec2 100644 --- a/ModManager/Views/Components/ModList.xaml.cs +++ b/ModManager/Views/Components/ModList.xaml.cs @@ -1,6 +1,8 @@ using Imya.Models; +using Imya.Models.Mods; +using Imya.Services.Interfaces; +using Imya.Texts; using Imya.UI.Models; -using Imya.UI.Utils; using Imya.Utils; using System.Collections.Generic; using System.ComponentModel; @@ -15,9 +17,6 @@ namespace Imya.UI.Components /// public partial class ModList : UserControl, INotifyPropertyChanged { - public IText ActivateText { get; } = TextManager.Instance.GetText("MODLIST_ACTIVATE"); - public IText DeactivateText { get; } = TextManager.Instance.GetText("MODLIST_DEACTIVATE"); - /// /// Either the only or the first mod in the current selection /// @@ -26,28 +25,27 @@ public partial class ModList : UserControl, INotifyPropertyChanged public BindableModCollection Mods { get; init; } - public TextManager TextManager { get; } = TextManager.Instance; - - public AppSettings Settings { get; } = AppSettings.Instance; + public ITextManager TextManager { get; init; } + public IAppSettings Settings { get; init; } - public ModList() + public ModList( + ITextManager textManager, + IAppSettings settings, + IImyaSetupService imyaSetupService) { - Mods = new BindableModCollection(ModCollection.Global ?? ModCollection.Empty, this); + TextManager = textManager; + Settings = settings; + + Mods = new BindableModCollection(imyaSetupService.GlobalModCollection, this); - InitializeComponent(); DataContext = this; + InitializeComponent(); OnSelectionChanged(); - Settings.PropertyChanged += Settings_PropertyChanged; + Settings.SortSettingChanged += OnSortSettingChanged; } - private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == nameof(Settings.Sorting)) - { - Mods.Order = Settings.Sorting.Comparer; - } - } + private void OnSortSettingChanged(SortSetting e) => Mods.Order = e.Comparer; public bool ShowAttributes { get => _showAttributes; @@ -55,8 +53,6 @@ public bool ShowAttributes { } private bool _showAttributes = true; - - private void SelectionChanged(object sender, SelectionChangedEventArgs e) { OnSelectionChanged(); diff --git a/ModManager/Views/Components/ModTweaker.xaml.cs b/ModManager/Views/Components/ModTweaker.xaml.cs index 56b85acd..3b544233 100644 --- a/ModManager/Views/Components/ModTweaker.xaml.cs +++ b/ModManager/Views/Components/ModTweaker.xaml.cs @@ -16,8 +16,11 @@ using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; -using Imya.Models; -using Imya.Models.ModTweaker; +using Imya.Models.Mods; +using Imya.Models.ModTweaker.DataModel.Tweaking; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Texts; using Imya.UI.Popup; using Imya.UI.Utils; using Imya.Utils; @@ -30,10 +33,10 @@ namespace Imya.UI.Components /// public partial class ModTweaker : UserControl, INotifyPropertyChanged { - public TextManager TextManager { get; } = TextManager.Instance; - public TweakManager TweakManager { get; } = TweakManager.Instance; - - public GameSetupManager GameSetup { get; } = GameSetupManager.Instance; + public ITextManager TextManager { get; init; } + public ITweakService TweakManager { get; init; } + public IGameSetupService GameSetup { get; init; } + private readonly PopupCreator _popupCreator; public Mod? CurrentMod { @@ -46,8 +49,17 @@ public Mod? CurrentMod } private Mod? _currentMod; - public ModTweaker() + public ModTweaker( + ITextManager textManager, + ITweakService tweakService, + IGameSetupService gameSetupService, + PopupCreator popupCreator) { + GameSetup = gameSetupService; + TweakManager = tweakService; + TextManager = textManager; + _popupCreator = popupCreator; + InitializeComponent(); DataContext = this; IsVisibleChanged += OnVisibleChanged; @@ -81,7 +93,7 @@ private void LoadTweaks(Mod mod) { if (TweakManager.HasUnsavedChanges) { - var dialog = PopupCreator.CreateSaveTweakPopup(); + var dialog = _popupCreator.CreateSaveTweakPopup(); dialog.ShowDialog(); } TweakManager.Load(mod); diff --git a/ModManager/Views/GameSetupView.xaml b/ModManager/Views/GameSetupView.xaml deleted file mode 100644 index 83e26e26..00000000 --- a/ModManager/Views/GameSetupView.xaml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - diff --git a/ModManager/Views/GameSetupView.xaml.cs b/ModManager/Views/GameSetupView.xaml.cs deleted file mode 100644 index 97c65536..00000000 --- a/ModManager/Views/GameSetupView.xaml.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Windows.Controls; - -namespace Imya.UI.Views -{ - /// - /// Interaktionslogik für GameSetupView.xaml - /// - public partial class GameSetupView : UserControl - { - public GameSetupView() - { - InitializeComponent(); - } - } -} diff --git a/ModManager/Views/GithubBrowserView.xaml.cs b/ModManager/Views/GithubBrowserView.xaml.cs index b1c3feb7..6ab5ad33 100644 --- a/ModManager/Views/GithubBrowserView.xaml.cs +++ b/ModManager/Views/GithubBrowserView.xaml.cs @@ -1,13 +1,13 @@ using Imya.GithubIntegration; using Imya.GithubIntegration.Download; using Imya.GithubIntegration.JsonData; -using Imya.GithubIntegration.StaticData; -using Imya.GithubIntegration.RepositoryInformation; -using Imya.Models; using Imya.Models.Installation; -using Imya.UI.Popup; +using Imya.Models.Installation.Interfaces; +using Imya.Models.Mods; +using Imya.Services.Interfaces; +using Imya.Texts; +using Imya.UI.Models; using Imya.UI.Utils; -using Imya.Utils; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -15,20 +15,13 @@ using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; -using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; namespace Imya.UI.Views { public partial class GithubBrowserView : UserControl, INotifyPropertyChanged, IViewPage { - public IText MESSAGE { get; set; } - public IText OK_TEXT { get; set; } - public IText CANCEL_TEXT { get; set; } - #region Notifying public ObservableCollection DisplayedRepositories { @@ -50,6 +43,12 @@ public GithubRepoInfo? SelectedRepo { } private GithubRepoInfo? _selectedRepo; + public bool HasRepoSelection { + get => _hasRepoSelection; + set => SetProperty(ref _hasRepoSelection, value); + } + private bool _hasRepoSelection = false; + public bool CanAddToDownloads { get => _canAddToDownloads; @@ -59,24 +58,46 @@ public bool CanAddToDownloads #endregion public ObservableCollection AllRepositories; - private readonly IReadmeProvider ReadmeProvider = new StaticReadmeProvider(); - - public TextManager TextManager { get;} = TextManager.Instance; - public InstallationManager InstallationManager { get; } = InstallationManager.Instance; - public GithubBrowserView() + public ITextManager TextManager { get;} + public IInstallationService InstallationManager { get; init; } + + private readonly IMainViewController _mainViewController; + private readonly IReadmeStrategy _readmeProvider; + private readonly PopupCreator _popupCreator; + private readonly IGithubInstallationBuilderFactory _githubInstallationBuilderFactory; + private readonly IZipInstallationBuilderFactory _zipInstallationBuilderFactory; + private readonly IAppSettings _appSettings; + + public GithubBrowserView( + IMainViewController mainViewController, + IInstallationService installationService, + ITextManager textManager, + IReadmeStrategy readmeProvider, + IGithubInstallationBuilderFactory githubInstallationBuilderFactory, + IZipInstallationBuilderFactory zipInstallationBuilderFactory, + PopupCreator popupCreator, + IAppSettings appSettings) { + InstallationManager = installationService; + TextManager = textManager; + _readmeProvider = readmeProvider; + _popupCreator = popupCreator; + _githubInstallationBuilderFactory = githubInstallationBuilderFactory; + _zipInstallationBuilderFactory = zipInstallationBuilderFactory; + _appSettings = appSettings; + _mainViewController = mainViewController; + DataContext = this; InitializeComponent(); - OK_TEXT = new SimpleText("Download"); - CANCEL_TEXT = new SimpleText("Cancel"); InstallationManager.InstallationCompleted += ValidateCanAddToDownloads; + } public void OnLoad() { - var repoInfoProvider = new AutoRepoInfoSource(AppSettings.Instance.ModindexLocation); + var repoInfoProvider = new AutoRepoInfoSource(_appSettings.ModindexLocation); AllRepositories = new ObservableCollection(repoInfoProvider.GetAll()); DisplayedRepositories = AllRepositories; } @@ -89,7 +110,7 @@ private async void OkayButtonClick(object sender, RoutedEventArgs e) try { - var install = await GithubInstallationBuilder + var install = await _githubInstallationBuilderFactory .Create() .WithRepoInfo(SelectedRepo) .BuildAsync(); @@ -99,25 +120,23 @@ private async void OkayButtonClick(object sender, RoutedEventArgs e) } catch (Octokit.RateLimitExceededException ex) { - PopupCreator.CreateApiRateExceededPopup().ShowDialog(); + _popupCreator.CreateApiRateExceededPopup().ShowDialog(); } catch (InstallationException ex) { - PopupCreator.CreateExceptionPopup(ex).ShowDialog(); + _popupCreator.CreateExceptionPopup(ex).ShowDialog(); } } - private async void OnInstallFromZipAsync(object sender, RoutedEventArgs e) + private void OnInstallFromZipAsync(object sender, RoutedEventArgs e) { - if (ModCollection.Global is null) return; - var dialog = CreateOpenFileDialog(); if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) return; foreach (String filename in dialog.FileNames) { - var install = ZipInstallationBuilder + var install = _zipInstallationBuilderFactory .Create() .WithSource(filename) .Build(); @@ -140,35 +159,40 @@ private System.Windows.Forms.OpenFileDialog CreateOpenFileDialog() private void CancelButtonClick(object sender, RoutedEventArgs e) { - MainViewController.Instance.GoToLastView(); + _mainViewController.GoToLastView(); } private async void OnRepoSelectionChanged(object sender, SelectionChangedEventArgs e) { var repoInfo = RepoSelection.SelectedItem as GithubRepoInfo; - if (repoInfo is null) return; + if (repoInfo is null) + { + HasRepoSelection = false; + return; + } SelectedRepo = repoInfo; + HasRepoSelection = true; ValidateCanAddToDownloads(); try { - ReadmeText = await ReadmeProvider.GetReadmeAsync(SelectedRepo); + ReadmeText = await _readmeProvider.GetReadmeAsync(SelectedRepo); } catch (Octokit.RateLimitExceededException ex) { - PopupCreator.CreateApiRateExceededPopup().ShowDialog(); + _popupCreator.CreateApiRateExceededPopup().ShowDialog(); } catch (Octokit.ApiException ex) { - PopupCreator.CreateExceptionPopup(ex).ShowDialog(); + _popupCreator.CreateExceptionPopup(ex).ShowDialog(); } } private void ValidateCanAddToDownloads() { - CanAddToDownloads = RepoSelection is not null && !InstallationManager.Instance.IsProcessingInstallWithID(SelectedRepo.GetID()); + CanAddToDownloads = RepoSelection is not null && !InstallationManager.IsProcessingInstallWithID(SelectedRepo.GetID()); } public void Filter(IEnumerable keywords) diff --git a/ModManager/Views/InstallationView.xaml b/ModManager/Views/InstallationView.xaml index 7540a325..95634334 100644 --- a/ModManager/Views/InstallationView.xaml +++ b/ModManager/Views/InstallationView.xaml @@ -5,7 +5,6 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Imya.UI.Views" xmlns:controls="clr-namespace:Imya.UI.Controls" - d:DataContext="{d:DesignInstance Type=local:InstallationView}" xmlns:conv="clr-namespace:Imya.UI.ValueConverters" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" @@ -141,7 +140,7 @@ VerticalAlignment="Center" Orientation="Horizontal"> + Text="{Binding Status, UpdateSourceTrigger=PropertyChanged}" /> diff --git a/ModManager/Views/InstallationView.xaml.cs b/ModManager/Views/InstallationView.xaml.cs index 42e08342..f4919c83 100644 --- a/ModManager/Views/InstallationView.xaml.cs +++ b/ModManager/Views/InstallationView.xaml.cs @@ -14,11 +14,15 @@ using Imya.Models.Installation; using Imya.UI.Popup; using Imya.GithubIntegration; -using Imya.UI.Utils; using Imya.Models.Options; using Imya.GithubIntegration.Download; using Imya.GithubIntegration.StaticData; using Downloader; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Texts; +using Imya.UI.Models; +using Imya.Models.Installation.Interfaces; namespace Imya.UI.Views { @@ -30,12 +34,10 @@ public partial class InstallationView : UserControl, INotifyPropertyChanged { public static InstallationView? Instance { get; private set; } - public TextManager TextManager { get; } = TextManager.Instance; - public GameSetupManager GameSetup { get; } = GameSetupManager.Instance; - - public AppSettings Settings { get; } = AppSettings.Instance; - - public InstallationManager InstallationManager { get; } = InstallationManager.Instance; + public ITextManager TextManager { get; init; } + public IGameSetupService GameSetup { get; init; } + public IAppSettings Settings { get; init; } + public IInstallationService InstallationManager { get; init; } public ObservableCollection PendingDownloads { get; } @@ -56,8 +58,16 @@ private set #endregion - public InstallationView() + public InstallationView(ITextManager textManager, + IGameSetupService gameSetupService, + IAppSettings appSettings, + IInstallationService installationService) { + TextManager= textManager; + GameSetup= gameSetupService; + Settings = appSettings; + InstallationManager= installationService; + Instance = this; InitializeComponent(); @@ -73,7 +83,7 @@ public InstallationView() public async void OnInstallModLoader(object sender, RoutedEventArgs e) { - Console.WriteLine("This does fucking nothing right now"); + Console.WriteLine("This will do fucking nothing forever"); } private void OnLanguageChanged(ApplicationLanguage language) @@ -145,6 +155,6 @@ private ModLoaderStatus(string value) _value = value; } - public IText Localized => TextManager.Instance[_value]; + public IText Localized => new SimpleText("lolololol"); } } diff --git a/ModManager/Views/ModActivationView.xaml b/ModManager/Views/ModActivationView.xaml index feb4f247..6ee5696a 100644 --- a/ModManager/Views/ModActivationView.xaml +++ b/ModManager/Views/ModActivationView.xaml @@ -7,7 +7,6 @@ xmlns:Components="clr-namespace:Imya.UI.Components" xmlns:Converters="clr-namespace:Imya.UI.ValueConverters" xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" - d:DataContext="{d:DesignInstance Type=local:ModActivationView}" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -52,8 +51,15 @@ - - + + + - - @@ -104,12 +110,12 @@ - - @@ -133,7 +139,7 @@ - public partial class ModActivationView : UserControl, INotifyPropertyChanged { - public TextManager TextManager { get; } = TextManager.Instance; - public ModCollection? Mods { get; private set; } = ModCollection.Global; - public GameSetupManager GameSetupManager { get; } = GameSetupManager.Instance; + public ITextManager TextManager { get; init; } + public ModCollection? Mods { get; private set; } + public IGameSetupService GameSetupManager { get; } + + public ModList ModList { get; init; } + public ModDescriptionDisplay ModDescription { get; init; } + + private PopupCreator _popupCreator; + private IProfilesService _profilesService; #region notifyable properties @@ -53,10 +63,26 @@ public bool HasSelection #endregion - public ModActivationView() + public ModActivationView( + IGameSetupService gameSetup, + ITextManager textManager, + ModList modList, + ModDescriptionDisplay modDescriptionDisplay, + IImyaSetupService imyaSetupService, + PopupCreator popupCreator, + IProfilesService profilesService) { - InitializeComponent(); + GameSetupManager = gameSetup; + TextManager = textManager; + Mods = imyaSetupService.GlobalModCollection; + ModList = modList; + ModDescription = modDescriptionDisplay; + _popupCreator = popupCreator; + _profilesService = profilesService; + DataContext = this; + InitializeComponent(); + ModList.ModList_SelectionChanged += ModDescription.SetDisplayedMod; ModList.ModList_SelectionChanged += OnUpdateSelection; } @@ -97,8 +123,7 @@ private async void LoadProfileClick(object sender, RoutedEventArgs e) { if (Mods is null) return; - var Dialog = new ProfilesLoadPopup(); - + var Dialog = _popupCreator.CreateProfilesLoadPopup(); var dialogResult = Dialog.ShowDialog(); if (dialogResult is true && Dialog.SelectedProfile is not null) @@ -111,23 +136,14 @@ private void SaveProfileClick(object sender, RoutedEventArgs e) { if (Mods is null) return; - var dialog = new ProfilesSavePopup(); + var dialog = _popupCreator.CreateProfilesSavePopup(); dialog.ShowDialog(); + if (dialog.DialogResult is not true) + return; - if (dialog.DialogResult is true) - { - var profile = ModActivationProfile.FromModCollection(Mods, x => x.IsActive); - - if (profile.SaveToFile(dialog.FullFilename)) - { - Console.WriteLine($"Saved Profile to {dialog.ProfileFilename}."); - } - else - { - Console.WriteLine($"Failed to save profile {dialog.ProfileFilename}."); - } - } + var profile = _profilesService.CreateFromModCollection(Mods); + _profilesService.SaveProfile(profile, dialog.ProfileFilename); } Mod? _previousSelection = null; @@ -135,23 +151,15 @@ private void OnUpdateSelection(Mod? m) { if (_previousSelection != m) { - if (_previousSelection is not null) - _previousSelection.PropertyChanged -= OnSelectionPropertyChanged; - if (m is not null) - m.PropertyChanged += OnSelectionPropertyChanged; + HasSelection = ModList.CurrentlySelectedMod is not null; + AnyActiveSelected = ModList.CurrentlySelectedMods?.Any(x => x.IsActive) ?? false; + AnyInactiveSelected = ModList.CurrentlySelectedMods?.Any(x => !x.IsActive) ?? false; + OnlyRemovedSelected = ModList.CurrentlySelectedMods?.Where(x => x.IsRemoved).Count() == ModList.CurrentlySelectedMods?.Count(); + _previousSelection = m; } } - private void OnSelectionPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - HasSelection = ModList.CurrentlySelectedMod is not null; - AnyActiveSelected = ModList.CurrentlySelectedMods?.Any(x => x.IsActive) ?? false; - AnyInactiveSelected = ModList.CurrentlySelectedMods?.Any(x => !x.IsActive) ?? false; - OnlyRemovedSelected = ModList.CurrentlySelectedMods?.Where(x => x.IsRemoved).Count() == ModList.CurrentlySelectedMods?.Count(); - } - - #region INotifyPropertyChangedMembers public event PropertyChangedEventHandler? PropertyChanged = delegate { }; private void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); diff --git a/ModManager/Views/ModTweakerView.xaml b/ModManager/Views/ModTweakerView.xaml index 5f0cb13c..b83d3180 100644 --- a/ModManager/Views/ModTweakerView.xaml +++ b/ModManager/Views/ModTweakerView.xaml @@ -18,12 +18,12 @@ - - + + diff --git a/ModManager/Views/ModTweakerView.xaml.cs b/ModManager/Views/ModTweakerView.xaml.cs index b9c77fb6..e8fc9381 100644 --- a/ModManager/Views/ModTweakerView.xaml.cs +++ b/ModManager/Views/ModTweakerView.xaml.cs @@ -1,5 +1,6 @@ using Imya.Models; using Imya.Models.ModTweaker; +using Imya.UI.Components; using Imya.Utils; using System; using System.Collections.Generic; @@ -23,9 +24,18 @@ namespace Imya.UI.Views /// public partial class ModTweakerView : UserControl { - public ModTweakerView() + public ModList ModSelection { get; init; } + public ModTweaker TweakerFileView { get; init; } + + public ModTweakerView( + ModList modSelection, + ModTweaker modTweaker) { + ModSelection = modSelection; + TweakerFileView = modTweaker; + InitializeComponent(); + DataContext = this; ModSelection.ModList_SelectionChanged += TweakerFileView.UpdateCurrentDisplay; } diff --git a/ModManager/Views/ModinfoCreatorView.xaml b/ModManager/Views/ModinfoCreatorView.xaml index 12ff99f0..d99f63aa 100644 --- a/ModManager/Views/ModinfoCreatorView.xaml +++ b/ModManager/Views/ModinfoCreatorView.xaml @@ -22,7 +22,6 @@ - - diff --git a/ModManager/Views/ModinfoCreatorView.xaml.cs b/ModManager/Views/ModinfoCreatorView.xaml.cs index ff646dd3..235b25a5 100644 --- a/ModManager/Views/ModinfoCreatorView.xaml.cs +++ b/ModManager/Views/ModinfoCreatorView.xaml.cs @@ -15,7 +15,9 @@ using System.Windows.Shapes; using Imya.Enums; using Imya.Models.ModMetadata; +using Imya.Texts; using Imya.UI.Popup; +using Imya.UI.Utils; using Imya.Utils; namespace Imya.UI.Views @@ -25,7 +27,7 @@ namespace Imya.UI.Views /// public partial class ModinfoCreatorView : UserControl, INotifyPropertyChanged { - public TextManager TextManager { get; set; } = TextManager.Instance; + public ITextManager TextManager { get; init; } public ModinfoFactory ModinfoFactory { @@ -38,9 +40,14 @@ public ModinfoFactory ModinfoFactory } private ModinfoFactory _factory; + private PopupCreator _popupCreator; - public ModinfoCreatorView() + public ModinfoCreatorView( + ITextManager textManager, + PopupCreator popupCreator) { + TextManager = textManager; + _popupCreator = popupCreator; DataContext = this; InitializeComponent(); @@ -104,7 +111,7 @@ private void OnPropertyChanged(string propertyName) private void OnDlcAddClick(object sender, RoutedEventArgs e) { var remaining = ModinfoFactory.GetRemainingDlcIds(); - AddDlcPopup popup = new AddDlcPopup(remaining); + AddDlcPopup popup = _popupCreator.CreateAddDlcPopup(remaining); popup.ShowDialog(); if (popup.DialogResult is false) return; diff --git a/ModManager/Views/SettingsView.xaml.cs b/ModManager/Views/SettingsView.xaml.cs index 10f4acc9..79086029 100644 --- a/ModManager/Views/SettingsView.xaml.cs +++ b/ModManager/Views/SettingsView.xaml.cs @@ -8,8 +8,11 @@ using Imya.Utils; using Imya.Models; using System.Threading.Tasks; -using Imya.UI.Utils; using Imya.UI.Popup; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Texts; +using Imya.UI.Models; namespace Imya.UI.Views { @@ -18,10 +21,10 @@ namespace Imya.UI.Views /// public partial class SettingsView : UserControl, INotifyPropertyChanged { - public TextManager TextManager { get; } = TextManager.Instance; - public GameSetupManager GameSetup { get; } = GameSetupManager.Instance; + public ITextManager TextManager { get; init; } + public IGameSetupService GameSetup { get; init; } - public AppSettings AppSettings { get; set; } = AppSettings.Instance; + public IAppSettings AppSettings { get; init; } public long Max { get; } = 100 * 1024 * 1024; public long Min { get; } = 256 * 1024; @@ -43,12 +46,16 @@ private set private ModLoaderStatus _installStatus = ModLoaderStatus.NotInstalled; #endregion - - - public SettingsView() + public SettingsView( + IAppSettings appSettings, + ITextManager textManager, + IGameSetupService gameSetupService) { + AppSettings = appSettings; + TextManager = textManager; + GameSetup = gameSetupService; + InitializeComponent(); - AppSettings.Initialize(); LanguageSelection.SelectedItem = AppSettings.Language; ThemeSelection.SelectedItem = AppSettings.Theme; diff --git a/ModManager_Classes/GithubIntegration/Download/GithubDownloader.cs b/ModManager_Classes/GithubIntegration/Download/GithubDownloader.cs deleted file mode 100644 index b9838744..00000000 --- a/ModManager_Classes/GithubIntegration/Download/GithubDownloader.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using Imya.GithubIntegration.RepositoryInformation; -using Imya.Models.Options; -using Imya.Utils; -using Octokit; - -using Downloader; - - -namespace Imya.GithubIntegration.Download -{ - public struct DownloadResult - { - public DownloadResult() { } - - public bool DownloadSuccessful = false; - public String DownloadDestination = String.Empty; - } - - [Obsolete] - public class GithubDownloader - { - public GithubDownloaderOptions Options = new GithubDownloaderOptions(); - - public GithubDownloader(GithubDownloaderOptions _options) - { - Options = _options; - if (!Directory.Exists(Options.DownloadDirectory)) - { - Directory.CreateDirectory(Options.DownloadDirectory); - } - } - - /// - /// Downloads a Release into the download folder. - /// - /// the release to be downloaded - /// the name of the asset in the release to fetch - /// the Filepath where the release has been downloaded to. - private async Task DownloadReleaseAssetAsync(ReleaseAsset releaseAsset, IProgress? progress = null) - { - if (releaseAsset.BrowserDownloadUrl is null) throw new InstallationException("No matching release found"); - - String TargetFilename = Path.Combine(Options.DownloadDirectory, releaseAsset.Name); - - try - { - //add configuration options like download limit later - var downloadOpt = new DownloadConfiguration() - { - ChunkCount = 4, - ParallelDownload = true - }; - - IDownload download = DownloadBuilder.New() - .WithUrl(releaseAsset.BrowserDownloadUrl) - .WithFileLocation(TargetFilename) - .WithConfiguration(downloadOpt) - .Build(); - - if (progress is not null) - { - download.DownloadProgressChanged += (sender, e) => { - progress.Report((float)e.ProgressPercentage / 100); - }; - } - - await download.StartAsync(); - return new DownloadResult { DownloadSuccessful = true, DownloadDestination = TargetFilename }; - } - catch (Exception e) - { - throw new InstallationException($"Download failed: {e.Message}"); - } - } - - public async Task DownloadRepoInfoAsync(GithubRepoInfo repoInfo, IProgress? progress = null) - { - var releaseAsset = await repoInfo.GetReleaseAssetAsync(); - - if (releaseAsset is null) - { - //return new DownloadResult { DownloadSuccessful = false }; - throw new InstallationException($"Could not fetch any Release for {repoInfo}"); - } - - return await DownloadReleaseAssetAsync(releaseAsset, progress); - } - } -} \ No newline at end of file diff --git a/ModManager_Classes/GithubIntegration/GithubRepoInfo.cs b/ModManager_Classes/GithubIntegration/GithubRepoInfo.cs index 9bd25173..9248103b 100644 --- a/ModManager_Classes/GithubIntegration/GithubRepoInfo.cs +++ b/ModManager_Classes/GithubIntegration/GithubRepoInfo.cs @@ -15,27 +15,11 @@ public class GithubRepoInfo public String Owner { get; init; } public String ReleaseID { get; init; } - public String ReadmeMarkdownFilepath { get => GetMarkdownReadmeFilepath(); } - - private IReleaseAssetStrategy _releaseAssetStrategy; - private IReadmeFilepathStrategy _readmeFilepathStrategy; - private IModImageStrategy _imageStrategy; - - public GithubRepoInfo( - IReleaseAssetStrategy releaseAssetStrategy, - IReadmeFilepathStrategy readmeFilepathStrategy, - IModImageStrategy modImageStrategy, - String owner, - String repoName, - String releaseID) + public GithubRepoInfo(string name, string owner, string releaseid) { - Name = repoName; + Name = name; Owner = owner; - ReleaseID = releaseID; - - _releaseAssetStrategy = releaseAssetStrategy; - _readmeFilepathStrategy = readmeFilepathStrategy; - _imageStrategy = modImageStrategy; + ReleaseID = releaseid; } public override bool Equals([NotNullWhen(true)] object? obj) @@ -44,11 +28,7 @@ public override bool Equals([NotNullWhen(true)] object? obj) return Name == other.Name && Owner == other.Owner && ReleaseID == other.ReleaseID; } - public async Task GetReleaseAssetAsync() => await _releaseAssetStrategy.GetReleaseAssetAsync(this); - - public String GetMarkdownReadmeFilepath() => _readmeFilepathStrategy.GetMarkdownReadmeFilepath(); - - public async Task GetImageUrlAsync() => await _imageStrategy.GetImageUrlAsync(this); + public override int GetHashCode() => base.GetHashCode(); public override String ToString() { diff --git a/ModManager_Classes/GithubIntegration/IReadmeFilepathStrategy.cs b/ModManager_Classes/GithubIntegration/IReadmeFilepathStrategy.cs index 4704ad85..6117b855 100644 --- a/ModManager_Classes/GithubIntegration/IReadmeFilepathStrategy.cs +++ b/ModManager_Classes/GithubIntegration/IReadmeFilepathStrategy.cs @@ -6,6 +6,7 @@ namespace Imya.GithubIntegration { + [Obsolete] public interface IReadmeFilepathStrategy { String GetMarkdownReadmeFilepath(); diff --git a/ModManager_Classes/GithubIntegration/IReadmeStrategy.cs b/ModManager_Classes/GithubIntegration/IReadmeStrategy.cs new file mode 100644 index 00000000..3b266e40 --- /dev/null +++ b/ModManager_Classes/GithubIntegration/IReadmeStrategy.cs @@ -0,0 +1,9 @@ +using Octokit; + +namespace Imya.GithubIntegration +{ + public interface IReadmeStrategy + { + public Task GetReadmeAsync(GithubRepoInfo repoInfo); + } +} diff --git a/ModManager_Classes/GithubIntegration/JsonData/JsonRepoInfoSource.cs b/ModManager_Classes/GithubIntegration/JsonData/JsonRepoInfoSource.cs index c4d3d9aa..b43f9a8f 100644 --- a/ModManager_Classes/GithubIntegration/JsonData/JsonRepoInfoSource.cs +++ b/ModManager_Classes/GithubIntegration/JsonData/JsonRepoInfoSource.cs @@ -63,8 +63,7 @@ protected bool Parse(string json) // TODO static is hardcoded now, but shouldn't as soon as we support the others var packages = index.packages.Where(x => x.repo is not null && x.owner is not null && x.download is not null); - repositories = packages.Select(x => - StaticNameGithubRepoInfoFactory.CreateWithStaticName(x.repo!, x.owner!, x.download!)); + repositories = packages.Select(x => new GithubRepoInfo(x.repo!, x.owner!, x.download!)); return true; } diff --git a/ModManager_Classes/GithubIntegration/RepositoryInformation/IReadmeProvider.cs b/ModManager_Classes/GithubIntegration/RepositoryInformation/IReadmeProvider.cs deleted file mode 100644 index a56764b0..00000000 --- a/ModManager_Classes/GithubIntegration/RepositoryInformation/IReadmeProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Octokit; - -namespace Imya.GithubIntegration.RepositoryInformation -{ - public interface IReadmeProvider - { - public Task GetReadmeAsync(GithubRepoInfo repoInfo); - } -} diff --git a/ModManager_Classes/GithubIntegration/RepositoryInformation/IRepositoryProvider.cs b/ModManager_Classes/GithubIntegration/RepositoryInformation/IRepositoryProvider.cs index 2c3baad7..7dcf1606 100644 --- a/ModManager_Classes/GithubIntegration/RepositoryInformation/IRepositoryProvider.cs +++ b/ModManager_Classes/GithubIntegration/RepositoryInformation/IRepositoryProvider.cs @@ -7,7 +7,7 @@ namespace Imya.GithubIntegration.RepositoryInformation { - internal interface IRepositoryProvider + public interface IRepositoryProvider { Task FetchLatestReleaseAsync(GithubRepoInfo repoInfo); diff --git a/ModManager_Classes/GithubIntegration/RepositoryInformation/RepositoryProvider.cs b/ModManager_Classes/GithubIntegration/RepositoryInformation/RepositoryProvider.cs index e57037f4..4918f275 100644 --- a/ModManager_Classes/GithubIntegration/RepositoryInformation/RepositoryProvider.cs +++ b/ModManager_Classes/GithubIntegration/RepositoryInformation/RepositoryProvider.cs @@ -3,13 +3,13 @@ namespace Imya.GithubIntegration.RepositoryInformation { - internal class RepositoryProvider : IRepositoryProvider + public class RepositoryProvider : IRepositoryProvider { - private GitHubClient _githubClient = GithubClientProvider.Client; + private IGitHubClient _githubClient; - public RepositoryProvider() + public RepositoryProvider(IGitHubClient client) { - + _githubClient = client; } public async Task FetchLatestReleaseAsync(GithubRepoInfo repository) diff --git a/ModManager_Classes/GithubIntegration/StaticData/StaticReadmeProvider.cs b/ModManager_Classes/GithubIntegration/StaticData/StaticFilenameReadmeStrategy.cs similarity index 51% rename from ModManager_Classes/GithubIntegration/StaticData/StaticReadmeProvider.cs rename to ModManager_Classes/GithubIntegration/StaticData/StaticFilenameReadmeStrategy.cs index 48c271bc..8221f687 100644 --- a/ModManager_Classes/GithubIntegration/StaticData/StaticReadmeProvider.cs +++ b/ModManager_Classes/GithubIntegration/StaticData/StaticFilenameReadmeStrategy.cs @@ -1,21 +1,30 @@ -using Imya.GithubIntegration.RepositoryInformation; -using Imya.Models.Cache; +using Imya.Models.Cache; using Imya.Utils; using Microsoft.Extensions.Caching.Memory; using Octokit; namespace Imya.GithubIntegration.StaticData { - public class StaticReadmeProvider : IReadmeProvider + public class StaticFilenameReadmeStrategy : IReadmeStrategy { - public GitHubClient GitHubClient = GithubClientProvider.Client; - private ICache cache = new TimedCache(); + private static String _desiredFilename = "imya.md"; + + private IGitHubClient _client; + private ICache _cache; + + public StaticFilenameReadmeStrategy( + IGitHubClient client, + ICache cache) + { + _client = client; + _cache = cache; + } public async Task GetReadmeAsync(GithubRepoInfo repoInfo) { try { - return await cache.GetOrCreateAsync(repoInfo, _ => ReadmeFunc(repoInfo)); + return await _cache.GetOrCreateAsync(repoInfo, _ => ReadmeFunc(repoInfo)); } catch (RateLimitExceededException e) { @@ -30,7 +39,7 @@ public class StaticReadmeProvider : IReadmeProvider private async Task ReadmeFunc(GithubRepoInfo repoInfo) { - var readme = await GitHubClient.Repository.Content.GetAllContents(repoInfo.Owner, repoInfo.Name, repoInfo.GetMarkdownReadmeFilepath()); + var readme = await _client.Repository.Content.GetAllContents(repoInfo.Owner, repoInfo.Name, _desiredFilename); var content = readme.FirstOrDefault(); if (content is null) return String.Empty; return content!.Content; diff --git a/ModManager_Classes/GithubIntegration/StaticData/ImageStrategy.cs b/ModManager_Classes/GithubIntegration/StaticData/StaticFilepathImageStrategy.cs similarity index 66% rename from ModManager_Classes/GithubIntegration/StaticData/ImageStrategy.cs rename to ModManager_Classes/GithubIntegration/StaticData/StaticFilepathImageStrategy.cs index 27c1c5bc..52c2e3e7 100644 --- a/ModManager_Classes/GithubIntegration/StaticData/ImageStrategy.cs +++ b/ModManager_Classes/GithubIntegration/StaticData/StaticFilepathImageStrategy.cs @@ -10,17 +10,24 @@ namespace Imya.GithubIntegration.StaticData { - public class ImageStrategy : IModImageStrategy + public class StaticFilepathImageStrategy : IModImageStrategy { static string img_filename = "imya_icon.png"; + + IGitHubClient _client; + + public StaticFilepathImageStrategy(IGitHubClient client) + { + _client = client; + } + public async Task GetImageUrlAsync(GithubRepoInfo repoInfo) { - var client = GithubClientProvider.Client; - if (client.IsAuthenticated()) + if (_client.IsAuthenticated()) { try { - var image_content = await client.Repository.Content.GetAllContents(repoInfo.Owner, repoInfo.Name, img_filename); + var image_content = await _client.Repository.Content.GetAllContents(repoInfo.Owner, repoInfo.Name, img_filename); var image_url = image_content.FirstOrDefault()?.DownloadUrl; return image_url; } diff --git a/ModManager_Classes/GithubIntegration/StaticData/StaticNameGithubRepoInfoFactory.cs b/ModManager_Classes/GithubIntegration/StaticData/StaticNameGithubRepoInfoFactory.cs deleted file mode 100644 index ce5186b9..00000000 --- a/ModManager_Classes/GithubIntegration/StaticData/StaticNameGithubRepoInfoFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Imya.GithubIntegration.StaticData -{ - public class StaticNameGithubRepoInfoFactory - { - public static GithubRepoInfo CreateWithStaticName(String RepositoryName, String Owner, String AssetName) - { - IReleaseAssetStrategy release_asset_strat = new StaticNameReleaseAssetStrategy(AssetName); - IReadmeFilepathStrategy filepath_strat = new StaticReadmeFilepathStrategy(); - IModImageStrategy modimage_strat = new ImageStrategy(); - - return new GithubRepoInfo(release_asset_strat, filepath_strat, modimage_strat, Owner, RepositoryName, AssetName); - } - } -} diff --git a/ModManager_Classes/GithubIntegration/StaticData/StaticNameReleaseAssetStrategy.cs b/ModManager_Classes/GithubIntegration/StaticData/StaticNameReleaseAssetStrategy.cs index 482c8c19..14cc76d7 100644 --- a/ModManager_Classes/GithubIntegration/StaticData/StaticNameReleaseAssetStrategy.cs +++ b/ModManager_Classes/GithubIntegration/StaticData/StaticNameReleaseAssetStrategy.cs @@ -6,24 +6,21 @@ namespace Imya.GithubIntegration.StaticData { public class StaticNameReleaseAssetStrategy : IReleaseAssetStrategy { - private string DownloadPattern { get; init; } + static IRepositoryProvider? _releaseProvider; - static IRepositoryProvider? releaseProvider; - - public StaticNameReleaseAssetStrategy(string downloadPattern) + public StaticNameReleaseAssetStrategy(IRepositoryProvider releaseProvider) { - releaseProvider ??= new RepositoryProvider(); - DownloadPattern = downloadPattern; + _releaseProvider = releaseProvider; } public async Task GetReleaseAssetAsync(GithubRepoInfo repoInfo) { - var release = await releaseProvider!.FetchLatestReleaseAsync(repoInfo); + var release = await _releaseProvider!.FetchLatestReleaseAsync(repoInfo); if (release is null) return null; Matcher matcher = new(); - matcher.AddIncludePatterns(new string[] { DownloadPattern }); + matcher.AddIncludePatterns(new string[] { repoInfo.ReleaseID }); return release?.Assets.FirstOrDefault(x => matcher.Match(x.Name).HasMatches); } diff --git a/ModManager_Classes/GithubIntegration/StaticData/StaticReadmeFilepathStrategy.cs b/ModManager_Classes/GithubIntegration/StaticData/StaticReadmeFilepathStrategy.cs deleted file mode 100644 index 6c1584cb..00000000 --- a/ModManager_Classes/GithubIntegration/StaticData/StaticReadmeFilepathStrategy.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Imya.GithubIntegration.StaticData -{ - internal class StaticReadmeFilepathStrategy : IReadmeFilepathStrategy - { - public string GetMarkdownReadmeFilepath() - { - return "imya.md"; - } - } -} diff --git a/ModManager_Classes/Models/Attributes/AttributeCollection.cs b/ModManager_Classes/Models/Attributes/AttributeCollection.cs index 0754a013..4f49ba49 100644 --- a/ModManager_Classes/Models/Attributes/AttributeCollection.cs +++ b/ModManager_Classes/Models/Attributes/AttributeCollection.cs @@ -6,11 +6,18 @@ namespace Imya.Models.Attributes { public class AttributeCollection : ObservableCollection { + private object _lock = new Object(); + public void AddAttribute(IAttribute attrib) { - if (!attrib.MultipleAllowed && this.Any(x => x.AttributeType == attrib.AttributeType)) + if (attrib is null) return; - Add(attrib); + lock (_lock) + { + if (!attrib.MultipleAllowed && this.Any(x => x.AttributeType == attrib.AttributeType)) + return; + Add(attrib); + } } public IAttribute? GetByType(AttributeType type) diff --git a/ModManager_Classes/Models/Attributes/ConcreteAttributes/GenericModContextAttribute.cs b/ModManager_Classes/Models/Attributes/ConcreteAttributes/GenericModContextAttribute.cs index 036b9188..bdb57fc2 100644 --- a/ModManager_Classes/Models/Attributes/ConcreteAttributes/GenericModContextAttribute.cs +++ b/ModManager_Classes/Models/Attributes/ConcreteAttributes/GenericModContextAttribute.cs @@ -1,4 +1,5 @@ -using Imya.Utils; +using Imya.Models.Mods; +using Imya.Utils; namespace Imya.Models.Attributes { diff --git a/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModDependencyIssueAttribute.cs b/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModDependencyIssueAttribute.cs index 84063c1c..aee2ca89 100644 --- a/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModDependencyIssueAttribute.cs +++ b/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModDependencyIssueAttribute.cs @@ -9,12 +9,12 @@ namespace Imya.Models.Attributes { public class ModDependencyIssueAttribute : IAttribute { - public AttributeType AttributeType { get; } = AttributeType.UnresolvedDependencyIssue; - public IText Description { get => new SimpleText(String.Format(TextManager.Instance.GetText("ATTRIBUTE_MISSINGDEPENDENCY").Text, String.Join(',', UnresolvedDependencies))); } + public AttributeType AttributeType { get; init; } = AttributeType.UnresolvedDependencyIssue; + public IText Description { get; init; } bool IAttribute.MultipleAllowed => true; - public IEnumerable UnresolvedDependencies { get; } + public IEnumerable UnresolvedDependencies { get; init; } public ModDependencyIssueAttribute(IEnumerable issues) { diff --git a/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModReplacedByIssueAttribute.cs b/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModReplacedByIssueAttribute.cs index a6b8fc89..16335a59 100644 --- a/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModReplacedByIssueAttribute.cs +++ b/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModReplacedByIssueAttribute.cs @@ -1,11 +1,12 @@ -using Imya.Utils; +using Imya.Models.Mods; +using Imya.Utils; namespace Imya.Models.Attributes { public class ModReplacedByIssue : IAttribute { public AttributeType AttributeType { get; } = AttributeType.ModReplacedByIssue; - public IText Description { get => new SimpleText(string.Format(TextManager.Instance.GetText("ATTRIBUTE_REPLACEDBY").Text, _replacedBy.FolderName)); } + public IText Description { get; init; } bool IAttribute.MultipleAllowed => true; diff --git a/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModStatusAttribute.cs b/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModStatusAttribute.cs index c297f8a5..1f82f73e 100644 --- a/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModStatusAttribute.cs +++ b/ModManager_Classes/Models/Attributes/ConcreteAttributes/ModStatusAttribute.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Imya.Models.Attributes.Factories; namespace Imya.Models.Attributes { diff --git a/ModManager_Classes/Models/Attributes/CyclicDependencyAttributeFactory.cs b/ModManager_Classes/Models/Attributes/CyclicDependencyAttributeFactory.cs deleted file mode 100644 index f1cea32c..00000000 --- a/ModManager_Classes/Models/Attributes/CyclicDependencyAttributeFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Imya.Utils; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Imya.Models.Attributes -{ - public class CyclicDependencyAttributeFactory - { - public static IAttribute Get(IEnumerable context) - { - return new GenericModContextAttribute() - { - AttributeType = AttributeType.CyclicDependency, - Description = new SimpleText( - String.Format(TextManager.Instance.GetText("ATTRIBUTE_CYCLIC_DEPENDENCY").Text, - String.Join(',', context.Select(x => $"[{x.Category}] {x.Name}")))), - Context = context - }; - } - } -} diff --git a/ModManager_Classes/Models/Attributes/Factories/ContentInSubfolderAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Factories/ContentInSubfolderAttributeFactory.cs new file mode 100644 index 00000000..cac47831 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Factories/ContentInSubfolderAttributeFactory.cs @@ -0,0 +1,29 @@ +using Imya.Models.Attributes.Interfaces; +using Imya.Texts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Attributes.Factories +{ + public class ContentInSubfolderAttributeFactory : IContentInSubfolderAttributeFactory + { + private readonly ITextManager _textManager; + private GenericAttribute Subfolder; + + public ContentInSubfolderAttributeFactory(ITextManager textManager) + { + _textManager = textManager; + + Subfolder = new GenericAttribute() + { + AttributeType = AttributeType.ModContentInSubfolder, + Description = _textManager.GetText("ATTRIBUTE_MODCONTENTSUBFOLDER") + }; + } + + public IAttribute Get() => Subfolder; + } +} diff --git a/ModManager_Classes/Models/Attributes/Factories/CyclicDependencyAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Factories/CyclicDependencyAttributeFactory.cs new file mode 100644 index 00000000..eb11865f --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Factories/CyclicDependencyAttributeFactory.cs @@ -0,0 +1,33 @@ +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Mods; +using Imya.Texts; +using Imya.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Attributes.Factories +{ + public class CyclicDependencyAttributeFactory : ICyclicDependencyAttributeFactory + { + private ITextManager _textManager; + public CyclicDependencyAttributeFactory(ITextManager textManager) + { + _textManager = textManager; + } + + public IAttribute Get(IEnumerable context) + { + return new GenericModContextAttribute() + { + AttributeType = AttributeType.CyclicDependency, + Description = new SimpleText( + string.Format(_textManager.GetText("ATTRIBUTE_CYCLIC_DEPENDENCY").Text, + string.Join(',', context.Select(x => $"[{x.Category}] {x.Name}")))), + Context = context + }; + } + } +} diff --git a/ModManager_Classes/Models/Attributes/Factories/MissingModinfoAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Factories/MissingModinfoAttributeFactory.cs new file mode 100644 index 00000000..f288d374 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Factories/MissingModinfoAttributeFactory.cs @@ -0,0 +1,32 @@ +using Imya.Models.Attributes.Interfaces; +using Imya.Texts; +using Imya.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Attributes.Factories +{ + public class MissingModinfoAttributeFactory : IMissingModinfoAttributeFactory + { + private readonly ITextManager _textManager; + private GenericAttribute MissingModinfoAttribute; + + public MissingModinfoAttributeFactory(ITextManager textManager) + { + _textManager = textManager; + MissingModinfoAttribute = new GenericAttribute() + { + AttributeType = AttributeType.MissingModinfo, + Description = _textManager.GetText("ATTRIBUTE_NOMODINFO") + }; + } + + public IAttribute Get() + { + return MissingModinfoAttribute; + } + } +} diff --git a/ModManager_Classes/Models/Attributes/ModAccessIssueAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Factories/ModAccessIssueAttributeFactory.cs similarity index 57% rename from ModManager_Classes/Models/Attributes/ModAccessIssueAttributeFactory.cs rename to ModManager_Classes/Models/Attributes/Factories/ModAccessIssueAttributeFactory.cs index 3140eef4..cd8a6743 100644 --- a/ModManager_Classes/Models/Attributes/ModAccessIssueAttributeFactory.cs +++ b/ModManager_Classes/Models/Attributes/Factories/ModAccessIssueAttributeFactory.cs @@ -1,10 +1,18 @@ -using Imya.Utils; +using Imya.Models.Attributes.Interfaces; +using Imya.Texts; +using Imya.Utils; -namespace Imya.Models.Attributes +namespace Imya.Models.Attributes.Factories { - internal class ModAccessIssueAttributeFactory + public class ModAccessIssueAttributeFactory : IModAccessIssueAttributeFactory { - static GenericAttribute ModAccessIssueAttribute = + private readonly ITextManager _textManager; + public ModAccessIssueAttributeFactory(ITextManager textManager) + { + _textManager = textManager; + } + + GenericAttribute ModAccessIssueAttribute = new GenericAttribute() { AttributeType = AttributeType.IssueModAccess, @@ -12,7 +20,7 @@ internal class ModAccessIssueAttributeFactory Description = new SimpleText("Access to the Folder is denied. Please close all programs accessing this folder and retry.") }; - static GenericAttribute ModAccessIssue_NoDeleteAttribute = + GenericAttribute ModAccessIssue_NoDeleteAttribute = new GenericAttribute() { AttributeType = AttributeType.IssueModAccess, @@ -20,12 +28,12 @@ internal class ModAccessIssueAttributeFactory Description = new SimpleText("Could not delete this mod.") }; - public static IAttribute Get() + public IAttribute Get() { return ModAccessIssueAttribute; } - public static IAttribute GetNoDelete() + public IAttribute GetNoDelete() { return ModAccessIssue_NoDeleteAttribute; } diff --git a/ModManager_Classes/Models/Attributes/Factories/ModCompabilityAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Factories/ModCompabilityAttributeFactory.cs new file mode 100644 index 00000000..70d14e89 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Factories/ModCompabilityAttributeFactory.cs @@ -0,0 +1,33 @@ +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Mods; +using Imya.Texts; +using Imya.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Attributes.Factories +{ + public class ModCompabilityAttributeFactory : IModCompabilityAttributeFactory + { + private readonly ITextManager _textManager; + public ModCompabilityAttributeFactory(ITextManager textManager) + { + _textManager = textManager; + } + + public IAttribute Get(IEnumerable context) + { + return new GenericModContextAttribute() + { + AttributeType = AttributeType.ModCompabilityIssue, + Description = new SimpleText( + string.Format(_textManager.GetText("ATTRIBUTE_COMPABILITYERROR").Text, + string.Join(',', context.Select(x => $"[{x.Category}] {x.Name}")))), + Context = context + }; + } + } +} diff --git a/ModManager_Classes/Models/Attributes/Factories/ModDependencyIssueAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Factories/ModDependencyIssueAttributeFactory.cs new file mode 100644 index 00000000..725e8396 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Factories/ModDependencyIssueAttributeFactory.cs @@ -0,0 +1,31 @@ +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Mods; +using Imya.Texts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Attributes.Factories +{ + public class ModDependencyIssueAttributeFactory : IModDependencyIssueAttributeFactory + { + private ITextManager _textManager; + public ModDependencyIssueAttributeFactory(ITextManager textManager) + { + _textManager = textManager; + } + + public IAttribute Get(IEnumerable context) + { + String text = _textManager.GetText("ATTRIBUTE_MISSINGDEPENDENCY")?.Text ?? ""; + return new ModDependencyIssueAttribute() + { + AttributeType = AttributeType.UnresolvedDependencyIssue, + Description = new SimpleText(string.Format(text, string.Join(',', context))), + UnresolvedDependencies = context + }; + } + } +} diff --git a/ModManager_Classes/Models/Attributes/Factories/ModReplacedByAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Factories/ModReplacedByAttributeFactory.cs new file mode 100644 index 00000000..f160b58f --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Factories/ModReplacedByAttributeFactory.cs @@ -0,0 +1,28 @@ +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Mods; +using Imya.Texts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Attributes.Factories +{ + public class ModReplacedByAttributeFactory : IModReplacedByAttributeFactory + { + private ITextManager _textManager; + public ModReplacedByAttributeFactory(ITextManager textManager) + { + _textManager = textManager; + } + + public IAttribute Get(Mod replacedBy) + { + return new ModReplacedByIssue(replacedBy) + { + Description = new SimpleText(string.Format(_textManager.GetText("ATTRIBUTE_REPLACEDBY").Text, replacedBy.FolderName)), + }; + } + } +} diff --git a/ModManager_Classes/Models/Attributes/Factories/ModStatusAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Factories/ModStatusAttributeFactory.cs new file mode 100644 index 00000000..6625a89c --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Factories/ModStatusAttributeFactory.cs @@ -0,0 +1,76 @@ +using Imya.Models.Attributes.Interfaces; +using Imya.Texts; +using Imya.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Attributes.Factories +{ + // TODO obsolete may be a different kind of state more similar to other compatibility issues. + // Obsolete may also be detected on startup, not only after zip installation. + public enum ModStatus + { + Default, + New, + Updated, + Obsolete + } + + public class ModStatusAttributeFactory : IModStatusAttributeFactory + { + private readonly ITextManager _textManager; + private Dictionary> static_attribs; + public ModStatusAttributeFactory(ITextManager textManager) + { + _textManager = textManager; + + static_attribs = new() + { + { + ModStatus.Default, + () => new ModStatusAttribute() + { + AttributeType = AttributeType.ModStatus, + Description = new SimpleText("Mod Status Attribute"), + Status = ModStatus.Default + } + }, + { + ModStatus.New, + () => new ModStatusAttribute() + { + AttributeType = AttributeType.ModStatus, + Description = _textManager.GetText("ATTRIBUTE_STATUS_NEW"), + Status = ModStatus.New + } + }, + { + ModStatus.Updated, + () => new ModStatusAttribute() + { + AttributeType = AttributeType.ModStatus, + Description = _textManager.GetText("ATTRIBUTE_STATUS_UPDATE"), + Status = ModStatus.Updated + } + }, + { + ModStatus.Obsolete, + () => new ModStatusAttribute() + { + AttributeType = AttributeType.ModStatus, + Description = _textManager.GetText("ATTRIBUTE_STATUS_OBSOLETE"), + Status = ModStatus.Obsolete + } + } + }; + } + + public IAttribute Get(ModStatus status) + { + return static_attribs[status].Invoke(); + } + } +} diff --git a/ModManager_Classes/Models/Attributes/Factories/RemovedFolderAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Factories/RemovedFolderAttributeFactory.cs new file mode 100644 index 00000000..056ce08f --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Factories/RemovedFolderAttributeFactory.cs @@ -0,0 +1,28 @@ +using Imya.Models.Attributes.Interfaces; +using Imya.Texts; +using Imya.Utils; + +namespace Imya.Models.Attributes.Factories +{ + public class RemovedFolderAttributeFactory : IRemovedFolderAttributeFactory + { + private readonly ITextManager _textManager; + GenericAttribute RemovedFolderAttribute; + public RemovedFolderAttributeFactory(ITextManager textManager) + { + _textManager = textManager; + + RemovedFolderAttribute = new GenericAttribute() + { + AttributeType = AttributeType.IssueModRemoved, + //Description = TextManager.Instance.GetText("ATTRIBUTE_TWEAKED") + Description = new SimpleText("This mod has been removed by another program") + }; + } + + public IAttribute Get() + { + return RemovedFolderAttribute; + } + } +} diff --git a/ModManager_Classes/Models/Attributes/Factories/TweakedAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Factories/TweakedAttributeFactory.cs new file mode 100644 index 00000000..fe65a78e --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Factories/TweakedAttributeFactory.cs @@ -0,0 +1,28 @@ +using Imya.Models.Attributes.Interfaces; +using Imya.Texts; +using Imya.Utils; + +namespace Imya.Models.Attributes.Factories +{ + public class TweakedAttributeFactory : ITweakedAttributeFactory + { + private readonly ITextManager _textManager; + private GenericAttribute TweakedAttribute; + + public TweakedAttributeFactory(ITextManager textManager) + { + _textManager = textManager; + + TweakedAttribute = new GenericAttribute() + { + AttributeType = AttributeType.TweakedMod, + Description = _textManager.GetText("ATTRIBUTE_TWEAKED") + }; + } + + public IAttribute Get() + { + return TweakedAttribute; + } + } +} diff --git a/ModManager_Classes/Models/Attributes/IAttribute.cs b/ModManager_Classes/Models/Attributes/IAttribute.cs index 84f99218..fb77a4bc 100644 --- a/ModManager_Classes/Models/Attributes/IAttribute.cs +++ b/ModManager_Classes/Models/Attributes/IAttribute.cs @@ -16,6 +16,7 @@ public enum AttributeType ModContentInSubfolder, IssueModRemoved, IssueModAccess, + IssueNoDelete, ModReplacedByIssue, CyclicDependency } diff --git a/ModManager_Classes/Models/Attributes/Interfaces/IContentInSubfolderAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Interfaces/IContentInSubfolderAttributeFactory.cs new file mode 100644 index 00000000..575b1c71 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Interfaces/IContentInSubfolderAttributeFactory.cs @@ -0,0 +1,7 @@ +namespace Imya.Models.Attributes.Interfaces +{ + public interface IContentInSubfolderAttributeFactory + { + IAttribute Get(); + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/Attributes/Interfaces/ICyclicDependencyAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Interfaces/ICyclicDependencyAttributeFactory.cs new file mode 100644 index 00000000..8199a2f0 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Interfaces/ICyclicDependencyAttributeFactory.cs @@ -0,0 +1,9 @@ +using Imya.Models.Mods; + +namespace Imya.Models.Attributes.Interfaces +{ + public interface ICyclicDependencyAttributeFactory + { + IAttribute Get(IEnumerable context); + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/Attributes/Interfaces/IMissingModinfoAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Interfaces/IMissingModinfoAttributeFactory.cs new file mode 100644 index 00000000..8f7d9fcc --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Interfaces/IMissingModinfoAttributeFactory.cs @@ -0,0 +1,7 @@ +namespace Imya.Models.Attributes.Interfaces +{ + public interface IMissingModinfoAttributeFactory + { + IAttribute Get(); + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/Attributes/Interfaces/IModAccessIssueAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Interfaces/IModAccessIssueAttributeFactory.cs new file mode 100644 index 00000000..d7de9023 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Interfaces/IModAccessIssueAttributeFactory.cs @@ -0,0 +1,8 @@ +namespace Imya.Models.Attributes.Interfaces +{ + public interface IModAccessIssueAttributeFactory + { + IAttribute Get(); + IAttribute GetNoDelete(); + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/Attributes/Interfaces/IModCompabilityAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Interfaces/IModCompabilityAttributeFactory.cs new file mode 100644 index 00000000..fc1e88c7 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Interfaces/IModCompabilityAttributeFactory.cs @@ -0,0 +1,9 @@ +using Imya.Models.Mods; + +namespace Imya.Models.Attributes.Interfaces +{ + public interface IModCompabilityAttributeFactory + { + IAttribute Get(IEnumerable context); + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/Attributes/Interfaces/IModDependencyIssueAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Interfaces/IModDependencyIssueAttributeFactory.cs new file mode 100644 index 00000000..b232cb2a --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Interfaces/IModDependencyIssueAttributeFactory.cs @@ -0,0 +1,9 @@ +using Imya.Models.Mods; + +namespace Imya.Models.Attributes.Interfaces +{ + public interface IModDependencyIssueAttributeFactory + { + IAttribute Get(IEnumerable context); + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/Attributes/Interfaces/IModReplacedByAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Interfaces/IModReplacedByAttributeFactory.cs new file mode 100644 index 00000000..aa994ab6 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Interfaces/IModReplacedByAttributeFactory.cs @@ -0,0 +1,9 @@ +using Imya.Models.Mods; + +namespace Imya.Models.Attributes.Interfaces +{ + public interface IModReplacedByAttributeFactory + { + IAttribute Get(Mod replacedBy); + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/Attributes/Interfaces/IModStatusAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Interfaces/IModStatusAttributeFactory.cs new file mode 100644 index 00000000..4d036811 --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Interfaces/IModStatusAttributeFactory.cs @@ -0,0 +1,9 @@ +using Imya.Models.Attributes.Factories; + +namespace Imya.Models.Attributes.Interfaces +{ + public interface IModStatusAttributeFactory + { + IAttribute Get(ModStatus status); + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/Attributes/Interfaces/IRemovedFolderAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Interfaces/IRemovedFolderAttributeFactory.cs new file mode 100644 index 00000000..27498a3b --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Interfaces/IRemovedFolderAttributeFactory.cs @@ -0,0 +1,7 @@ +namespace Imya.Models.Attributes.Interfaces +{ + public interface IRemovedFolderAttributeFactory + { + IAttribute Get(); + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/Attributes/Interfaces/ITweakedAttributeFactory.cs b/ModManager_Classes/Models/Attributes/Interfaces/ITweakedAttributeFactory.cs new file mode 100644 index 00000000..d9fb3f2c --- /dev/null +++ b/ModManager_Classes/Models/Attributes/Interfaces/ITweakedAttributeFactory.cs @@ -0,0 +1,7 @@ +namespace Imya.Models.Attributes.Interfaces +{ + public interface ITweakedAttributeFactory + { + IAttribute Get(); + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/Attributes/MissingModinfoAttributeFactory.cs b/ModManager_Classes/Models/Attributes/MissingModinfoAttributeFactory.cs deleted file mode 100644 index 4d9ad53b..00000000 --- a/ModManager_Classes/Models/Attributes/MissingModinfoAttributeFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Imya.Utils; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Imya.Models.Attributes -{ - public class MissingModinfoAttributeFactory - { - static GenericAttribute MissingModinfoAttribute = - new GenericAttribute() - { - AttributeType = AttributeType.MissingModinfo, - Description = TextManager.Instance.GetText("ATTRIBUTE_NOMODINFO") - }; - - public static IAttribute Get() - { - return MissingModinfoAttribute; - } - } -} diff --git a/ModManager_Classes/Models/Attributes/ModCompabilityAttributeFactory.cs b/ModManager_Classes/Models/Attributes/ModCompabilityAttributeFactory.cs deleted file mode 100644 index 36b81d60..00000000 --- a/ModManager_Classes/Models/Attributes/ModCompabilityAttributeFactory.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Imya.Utils; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Imya.Models.Attributes -{ - public class ModCompabilityAttributeFactory - { - public static IAttribute Get(IEnumerable context) - { - return new GenericModContextAttribute() - { - AttributeType = AttributeType.ModCompabilityIssue, - Description = new SimpleText( - String.Format(TextManager.Instance.GetText("ATTRIBUTE_COMPABILITYERROR").Text, - String.Join(',', context.Select(x => $"[{x.Category}] {x.Name}")))), - Context = context - }; - } - } -} diff --git a/ModManager_Classes/Models/Attributes/ModStatusAttributeFactory.cs b/ModManager_Classes/Models/Attributes/ModStatusAttributeFactory.cs deleted file mode 100644 index ba8de1c2..00000000 --- a/ModManager_Classes/Models/Attributes/ModStatusAttributeFactory.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Imya.Utils; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Imya.Models.Attributes -{ - // TODO obsolete may be a different kind of state more similar to other compatibility issues. - // Obsolete may also be detected on startup, not only after zip installation. - public enum ModStatus - { - Default, - New, - Updated, - Obsolete - } - - public class ModStatusAttributeFactory - { - private static Dictionary static_attribs = new() - { - { - ModStatus.Default, - new ModStatusAttribute() - { - AttributeType = AttributeType.ModStatus, - Description = new SimpleText("Mod Status Attribute"), - Status = ModStatus.Default - } - }, - { - ModStatus.New, - new ModStatusAttribute() - { - AttributeType = AttributeType.ModStatus, - Description = TextManager.Instance.GetText("ATTRIBUTE_STATUS_NEW"), - Status = ModStatus.New - } - }, - { - ModStatus.Updated, - new ModStatusAttribute() - { - AttributeType = AttributeType.ModStatus, - Description = TextManager.Instance.GetText("ATTRIBUTE_STATUS_UPDATE"), - Status = ModStatus.Updated - } - }, - { - ModStatus.Obsolete, - new ModStatusAttribute() - { - AttributeType = AttributeType.ModStatus, - Description = TextManager.Instance.GetText("ATTRIBUTE_STATUS_OBSOLETE"), - Status = ModStatus.Obsolete - } - } - }; - - public static IAttribute Get(ModStatus status) - { - return static_attribs[status]; - } - } -} diff --git a/ModManager_Classes/Models/Attributes/RemovedFolderAttributeFactory.cs b/ModManager_Classes/Models/Attributes/RemovedFolderAttributeFactory.cs deleted file mode 100644 index b03132cd..00000000 --- a/ModManager_Classes/Models/Attributes/RemovedFolderAttributeFactory.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Imya.Utils; - -namespace Imya.Models.Attributes -{ - internal class RemovedFolderAttributeFactory - { - static GenericAttribute TweakedAttribute = - new GenericAttribute() - { - AttributeType = AttributeType.IssueModRemoved, - //Description = TextManager.Instance.GetText("ATTRIBUTE_TWEAKED") - Description = new SimpleText("This mod has been removed by another program") - }; - - public static IAttribute Get() - { - return TweakedAttribute; - } - } -} diff --git a/ModManager_Classes/Models/Attributes/TweakedAttributeFactory.cs b/ModManager_Classes/Models/Attributes/TweakedAttributeFactory.cs deleted file mode 100644 index 24d187bf..00000000 --- a/ModManager_Classes/Models/Attributes/TweakedAttributeFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Imya.Utils; - -namespace Imya.Models.Attributes -{ - public class TweakedAttributeFactory - { - static GenericAttribute TweakedAttribute = - new GenericAttribute() - { - AttributeType = AttributeType.TweakedMod, - Description = TextManager.Instance.GetText("ATTRIBUTE_TWEAKED") - }; - - public static IAttribute Get() - { - return TweakedAttribute; - } - } -} diff --git a/ModManager_Classes/Models/Cache/TimedCache.cs b/ModManager_Classes/Models/Cache/TimedCache.cs index 301cf465..154b1690 100644 --- a/ModManager_Classes/Models/Cache/TimedCache.cs +++ b/ModManager_Classes/Models/Cache/TimedCache.cs @@ -7,7 +7,7 @@ namespace Imya.Models.Cache { - internal class TimedCache : ITimedCache where TKey : notnull + public class TimedCache : ITimedCache where TKey : notnull { public TimeSpan ExpirationTime { get; set; } = TimeSpan.FromSeconds(60); diff --git a/ModManager_Classes/Models/GameLauncher/GameLauncherFactory.cs b/ModManager_Classes/Models/GameLauncher/GameLauncherFactory.cs index 4bfd20c1..61a8aa64 100644 --- a/ModManager_Classes/Models/GameLauncher/GameLauncherFactory.cs +++ b/ModManager_Classes/Models/GameLauncher/GameLauncherFactory.cs @@ -1,4 +1,6 @@ -using Imya.Utils; +using Imya.Services; +using Imya.Services.Interfaces; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; @@ -9,16 +11,26 @@ namespace Imya.Models.GameLauncher { public class GameLauncherFactory : IGameLauncherFactory { - GameSetupManager _gameSetup; + IGameSetupService _gameSetupService; + StandardGameLauncher _standardGameLauncher; + SteamGameLauncher _steamGameLauncher; - public GameLauncherFactory() + public GameLauncherFactory( + IGameSetupService gameSetupService, + StandardGameLauncher stdGameLauncher, + SteamGameLauncher steamGameLauncher) { - _gameSetup = GameSetupManager.Instance; + _standardGameLauncher = stdGameLauncher; + _steamGameLauncher = steamGameLauncher; + _gameSetupService = gameSetupService; } public IGameLauncher GetLauncher() { - return _gameSetup.GamePlatform == GamePlatform.Steam ? new SteamGameLauncher() : new StandardGameLauncher(); + return _gameSetupService.GamePlatform == + GamePlatform.Steam ? + _steamGameLauncher + : _standardGameLauncher; } } } diff --git a/ModManager_Classes/Models/GameLauncher/StandardGameLauncher.cs b/ModManager_Classes/Models/GameLauncher/StandardGameLauncher.cs index 3a921a50..872f6145 100644 --- a/ModManager_Classes/Models/GameLauncher/StandardGameLauncher.cs +++ b/ModManager_Classes/Models/GameLauncher/StandardGameLauncher.cs @@ -1,4 +1,6 @@ -using Imya.Utils; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Utils; using System; using System.Collections.Generic; using System.Diagnostics; @@ -16,12 +18,12 @@ namespace Imya.Models.GameLauncher */ public class StandardGameLauncher : GameLauncherBase, IGameLauncher { - private GameSetupManager _gameSetup; + private IGameSetupService _gameSetup; - public StandardGameLauncher() + public StandardGameLauncher(IGameSetupService gameSetup) { _scanner = new GameScanner(); - _gameSetup = GameSetupManager.Instance; + _gameSetup = gameSetup; RunningGame = null; } diff --git a/ModManager_Classes/Models/GameLauncher/SteamGameLauncher.cs b/ModManager_Classes/Models/GameLauncher/SteamGameLauncher.cs index a44fe5b5..b0447290 100644 --- a/ModManager_Classes/Models/GameLauncher/SteamGameLauncher.cs +++ b/ModManager_Classes/Models/GameLauncher/SteamGameLauncher.cs @@ -1,4 +1,6 @@ -using Imya.Utils; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Utils; using System; using System.Collections.Generic; using System.Diagnostics; @@ -19,12 +21,12 @@ namespace Imya.Models.GameLauncher */ public class SteamGameLauncher : GameLauncherBase, IGameLauncher { - private GameSetupManager _gameSetup; + private IGameSetupService _gameSetup; - public SteamGameLauncher() + public SteamGameLauncher(IGameSetupService gameSetupService) { _scanner = new GameScanner(); - _gameSetup = GameSetupManager.Instance; + _gameSetup = gameSetupService; RunningGame = null; } diff --git a/ModManager_Classes/Models/Installation/GithubInstallation.cs b/ModManager_Classes/Models/Installation/GithubInstallation.cs index 571cbfea..e612b673 100644 --- a/ModManager_Classes/Models/Installation/GithubInstallation.cs +++ b/ModManager_Classes/Models/Installation/GithubInstallation.cs @@ -3,7 +3,7 @@ using Imya.GithubIntegration; using Imya.GithubIntegration.Download; using Imya.Models; -using Imya.Models.Installation; +using Imya.Models.Installation.Interfaces; using Imya.Models.Options; using Imya.Utils; using Octokit; @@ -34,19 +34,13 @@ public bool IsPaused { } private bool _isPaused; - public bool IsBeingDownloaded { get => _isBeingDownloaded; set => SetProperty(ref _isBeingDownloaded, value); } private bool _isBeingDownloaded = false; - - - public GithubInstallation() - { - HeaderText = TextManager.Instance.GetText("INSTALLATION_HEADER_LOADER"); - } + { } } } diff --git a/ModManager_Classes/Models/Installation/GithubInstallationBuilder.cs b/ModManager_Classes/Models/Installation/GithubInstallationBuilder.cs index f981d71f..68e49bbd 100644 --- a/ModManager_Classes/Models/Installation/GithubInstallationBuilder.cs +++ b/ModManager_Classes/Models/Installation/GithubInstallationBuilder.cs @@ -1,9 +1,24 @@ using Imya.GithubIntegration; using Imya.GithubIntegration.Download; -using Imya.Utils; +using Imya.Models.Installation.Interfaces; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Texts; +using Microsoft.Extensions.DependencyInjection; namespace Imya.Models.Installation { + public class GithubInstallationBuilderFactory : IGithubInstallationBuilderFactory + { + public IServiceProvider _serviceProvider; + public GithubInstallationBuilderFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public GithubInstallationBuilder Create() => _serviceProvider.GetRequiredService(); + } + public class GithubInstallationBuilder { private GithubInstallation _installation; @@ -14,9 +29,25 @@ public class GithubInstallationBuilder private long? _download_size; - private GithubInstallationBuilder() { } - - public static GithubInstallationBuilder Create() => new GithubInstallationBuilder(); + private readonly IGameSetupService _gameSetupService; + private readonly IImyaSetupService _imyaSetupService; + private readonly ITextManager _textManager; + private readonly IReleaseAssetStrategy _releaseAssetStrategy; + private readonly IModImageStrategy _imageStrategy; + + public GithubInstallationBuilder( + IGameSetupService gameSetupService, + IImyaSetupService imyaSetupService, + ITextManager textManager, + IReleaseAssetStrategy releaseAssetStrategy, + IModImageStrategy imageStragegy) + { + _gameSetupService = gameSetupService; + _imyaSetupService = imyaSetupService; + _textManager = textManager; + _releaseAssetStrategy = releaseAssetStrategy; + _imageStrategy = imageStragegy; + } public GithubInstallationBuilder WithRepoInfo(GithubRepoInfo repoInfo) { @@ -48,7 +79,7 @@ public GithubInstallationBuilder UseModloaderInstallFlow(bool use) public async Task BuildAsync() { - if (GameSetupManager.Instance.GameRootPath is null) + if (_gameSetupService.GameRootPath is null) throw new Exception("No Game Path set!"); if (_repoInfoToInstall is null) @@ -56,7 +87,7 @@ public async Task BuildAsync() if (!_ignoreRepoInfoForUrl) { - var releaseasset = await _repoInfoToInstall.GetReleaseAssetAsync(); + var releaseasset = await _releaseAssetStrategy.GetReleaseAssetAsync(_repoInfoToInstall); if (releaseasset is null) throw new InstallationException($"Could not fetch any release for {_repoInfoToInstall}"); _url = releaseasset.BrowserDownloadUrl; @@ -64,29 +95,29 @@ public async Task BuildAsync() } var installationGuid = Guid.NewGuid().ToString(); - var sourceFilepath = Path.Combine(ImyaSetupManager.Instance.DownloadDirectoryPath, installationGuid + ".zip"); + var sourceFilepath = Path.Combine(_imyaSetupService.DownloadDirectoryPath, installationGuid + ".zip"); var header = $"{_repoInfoToInstall.Owner}/{_repoInfoToInstall.Name}"; var additional = $"{_repoInfoToInstall.ReleaseID}"; var id = _repoInfoToInstall.GetID(); - var url = await _repoInfoToInstall.GetImageUrlAsync(); + var url = await _imageStrategy.GetImageUrlAsync(_repoInfoToInstall); _installation = new GithubInstallation() { RepositoryToInstall = _repoInfoToInstall!, SourceFilepath = sourceFilepath, DownloadTargetFilename = sourceFilepath, - UnpackTargetPath = Path.Combine(ImyaSetupManager.Instance.UnpackDirectoryPath, installationGuid), + UnpackTargetPath = Path.Combine(_imyaSetupService.UnpackDirectoryPath, installationGuid), DownloadUrl = _url, DownloadSize = _download_size, - HeaderText = new SimpleText(header), AdditionalText = new SimpleText(additional), ID = id, Status = InstallationStatus.NotStarted, ImageUrl = url, - CancellationTokenSource = new CancellationTokenSource() - }; + CancellationTokenSource = new CancellationTokenSource(), + HeaderText = _textManager.GetText("INSTALLATION_HEADER_LOADER") + }; return _installation; } } diff --git a/ModManager_Classes/Models/Installation/IInstallation.cs b/ModManager_Classes/Models/Installation/IInstallation.cs index 306784e0..16a8674f 100644 --- a/ModManager_Classes/Models/Installation/IInstallation.cs +++ b/ModManager_Classes/Models/Installation/IInstallation.cs @@ -16,7 +16,7 @@ public interface IInstallation : IProgress IText? HeaderText { get; } IText? AdditionalText { get; } bool HasAdditionalText { get; } - IInstallationStatus? Status { get; set; } + InstallationStatus Status { get; set; } CancellationTokenSource CancellationTokenSource { get; } CancellationToken CancellationToken { get; } diff --git a/ModManager_Classes/Models/Installation/Installation.cs b/ModManager_Classes/Models/Installation/Installation.cs index c2a405dd..a697fe5a 100644 --- a/ModManager_Classes/Models/Installation/Installation.cs +++ b/ModManager_Classes/Models/Installation/Installation.cs @@ -36,12 +36,12 @@ public bool IsAbortable public String ID { get; init; } - public IInstallationStatus? Status + public InstallationStatus Status { get => _status; set => SetProperty(ref _status, value); } - private IInstallationStatus? _status; + private InstallationStatus _status; public IText? HeaderText { diff --git a/ModManager_Classes/Models/Installation/InstallationStatus.cs b/ModManager_Classes/Models/Installation/InstallationStatus.cs index 696b9692..6039bb98 100644 --- a/ModManager_Classes/Models/Installation/InstallationStatus.cs +++ b/ModManager_Classes/Models/Installation/InstallationStatus.cs @@ -7,19 +7,11 @@ namespace Imya.Models.Installation { - internal class InstallationStatus : IInstallationStatus - { - public static readonly InstallationStatus NotStarted = new("ZIP_NOTSTARTED"); - public static readonly InstallationStatus Unpacking = new("ZIP_UNPACKING"); - public static readonly InstallationStatus MovingFiles = new("ZIP_MOVING"); - public static readonly InstallationStatus Downloading = new("INSTALL_DOWNLOAD"); - - private readonly string _value; - private InstallationStatus(string value) - { - _value = value; - } - - public IText Localized => TextManager.Instance[_value]; + public enum InstallationStatus + { + NotStarted, + Unpacking, + MovingFiles, + Downloading } } diff --git a/ModManager_Classes/Models/Installation/CombinedInterfaces.cs b/ModManager_Classes/Models/Installation/Interfaces/CombinedInterfaces.cs similarity index 91% rename from ModManager_Classes/Models/Installation/CombinedInterfaces.cs rename to ModManager_Classes/Models/Installation/Interfaces/CombinedInterfaces.cs index 9a838b8e..e9b54d1c 100644 --- a/ModManager_Classes/Models/Installation/CombinedInterfaces.cs +++ b/ModManager_Classes/Models/Installation/Interfaces/CombinedInterfaces.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace Imya.Models.Installation +namespace Imya.Models.Installation.Interfaces { public interface IDownloadableUnpackable : IDownloadable, IUnpackable { } diff --git a/ModManager_Classes/Models/Installation/Interfaces/IGithubInstallationBuilderFactory.cs b/ModManager_Classes/Models/Installation/Interfaces/IGithubInstallationBuilderFactory.cs new file mode 100644 index 00000000..94e0a99a --- /dev/null +++ b/ModManager_Classes/Models/Installation/Interfaces/IGithubInstallationBuilderFactory.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Installation.Interfaces +{ + public interface IGithubInstallationBuilderFactory + { + GithubInstallationBuilder Create(); + } +} diff --git a/ModManager_Classes/Models/Installation/Interfaces/IZipInstallationBuilderFactory.cs b/ModManager_Classes/Models/Installation/Interfaces/IZipInstallationBuilderFactory.cs new file mode 100644 index 00000000..16a5a6b1 --- /dev/null +++ b/ModManager_Classes/Models/Installation/Interfaces/IZipInstallationBuilderFactory.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Installation.Interfaces +{ + public interface IZipInstallationBuilderFactory + { + ZipInstallationBuilder Create(); + } +} diff --git a/ModManager_Classes/Models/Installation/ZipInstallation.cs b/ModManager_Classes/Models/Installation/ZipInstallation.cs index e256f80a..92222da7 100644 --- a/ModManager_Classes/Models/Installation/ZipInstallation.cs +++ b/ModManager_Classes/Models/Installation/ZipInstallation.cs @@ -1,4 +1,5 @@ -using Imya.Models.Options; +using Imya.Models.Installation.Interfaces; +using Imya.Models.Options; using Imya.Utils; namespace Imya.Models.Installation @@ -8,11 +9,8 @@ public class ZipInstallation : Installation, IUnpackableInstallation public String SourceFilepath { get; init; } public String UnpackTargetPath { get; init; } - public ZipInstallation() - { - HeaderText = TextManager.Instance.GetText("INSTALLATION_HEADER_MOD"); - AdditionalText = new SimpleText(SourceFilepath); - } + public ZipInstallation() { } + public override string ToString() => $"InstallationTask of {SourceFilepath}"; } diff --git a/ModManager_Classes/Models/Installation/ZipInstallationBuilder.cs b/ModManager_Classes/Models/Installation/ZipInstallationBuilder.cs index 307bee98..d3bcff08 100644 --- a/ModManager_Classes/Models/Installation/ZipInstallationBuilder.cs +++ b/ModManager_Classes/Models/Installation/ZipInstallationBuilder.cs @@ -1,4 +1,8 @@ -using Imya.Utils; +using Imya.Models.Installation.Interfaces; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Texts; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Linq; @@ -7,13 +11,33 @@ namespace Imya.Models.Installation { + public class ZipInstallationBuilderFactory : IZipInstallationBuilderFactory + { + private readonly IServiceProvider _serviceProvider; + + public ZipInstallationBuilderFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + public ZipInstallationBuilder Create() => _serviceProvider.GetRequiredService(); + } + public class ZipInstallationBuilder { private String? _source; - private ZipInstallationBuilder() { } + private readonly IGameSetupService _gameSetupService; + private readonly IImyaSetupService _imyaSetupService; + private readonly ITextManager _textManager; - public static ZipInstallationBuilder Create() => new ZipInstallationBuilder(); + public ZipInstallationBuilder( + IGameSetupService gameSetupService, + IImyaSetupService imyaSetupService, + ITextManager _textManager) + { + _gameSetupService = gameSetupService; + _imyaSetupService = imyaSetupService; + } public ZipInstallationBuilder WithSource(String source_path) { @@ -23,7 +47,7 @@ public ZipInstallationBuilder WithSource(String source_path) public ZipInstallation Build() { - if (GameSetupManager.Instance.GameRootPath is null) + if (_gameSetupService.GameRootPath is null) throw new Exception("No Game Path set!"); if(_source is null) @@ -35,10 +59,11 @@ public ZipInstallation Build() var installation = new ZipInstallation() { SourceFilepath = _source, - UnpackTargetPath = Path.Combine(ImyaSetupManager.Instance.UnpackDirectoryPath, guid), - HeaderText = new SimpleText(header), + UnpackTargetPath = Path.Combine(_imyaSetupService.UnpackDirectoryPath, guid), Status = InstallationStatus.NotStarted, - CancellationTokenSource = new CancellationTokenSource() + CancellationTokenSource = new CancellationTokenSource(), + HeaderText = _textManager.GetText("INSTALLATION_HEADER_MOD"), + AdditionalText = new SimpleText(_source) }; return installation; } diff --git a/ModManager_Classes/Models/ModActivationProfile.cs b/ModManager_Classes/Models/ModActivationProfile.cs index 66ad696e..de5ba2ef 100644 --- a/ModManager_Classes/Models/ModActivationProfile.cs +++ b/ModManager_Classes/Models/ModActivationProfile.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Imya.Models.Mods; namespace Imya.Models { @@ -11,75 +12,16 @@ public class ModActivationProfile : IEnumerable { public static string ProfileExtension { get; } = "imyaprofile"; - private List ModFolderNames = new(); + public IEnumerable ModFolderNames = new List(); public string Title { get; set; } = "DummyTitle"; - /// - /// Filename the profile was loaded from. This is only set if the profile was loaded from a file. - /// - public string? Filename { get; set; } - public bool HasFilename => Filename is not null; - - public static ModActivationProfile FromModCollection(ModCollection collection, Func SelectFunction) - { - ModActivationProfile profile = new(); - profile.ModFolderNames = collection.Mods - .Where(SelectFunction) - .Select(x => x.FolderName) - .ToList(); - return profile; - } - - public static ModActivationProfile? FromFile(string _filename) - { - ModActivationProfile profile = new(); - try - { - using StreamReader reader = new(File.OpenRead(_filename)); - while (!reader.EndOfStream) - { - var line = reader.ReadLine(); - - //validation prettyplease? - - if (line is not null) - profile.ModFolderNames.Add(line); - } - - profile.Title = Path.GetFileNameWithoutExtension(_filename); - profile.Filename = _filename; - return profile; - } - catch (IOException) - { - Console.WriteLine($"Could not access File: {_filename}"); - return null; - } - } - - public bool SaveToFile(string _filename) - { - try - { - FileStream fs = File.Create(_filename); - SaveToStream(fs); - return true; - } - catch (IOException) - { - Console.WriteLine($"Could not create File: {_filename}"); - return false; - } + private ModActivationProfile() { + } - public void SaveToStream(Stream s) + public ModActivationProfile(IEnumerable folderNames) { - using StreamWriter writer = new(s); - foreach (string dir_name in ModFolderNames) - { - writer.WriteLine(dir_name); - writer.Flush(); - } + ModFolderNames = folderNames; } public bool IsEmpty() diff --git a/ModManager_Classes/Models/ModComparer.cs b/ModManager_Classes/Models/ModComparer.cs index 19a5c7d5..f523fb7e 100644 --- a/ModManager_Classes/Models/ModComparer.cs +++ b/ModManager_Classes/Models/ModComparer.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Imya.Models.Mods; namespace Imya.Models { diff --git a/ModManager_Classes/Models/ModMetadata/LocalizedModinfo.cs b/ModManager_Classes/Models/ModMetadata/LocalizedModinfo.cs new file mode 100644 index 00000000..2afb4c48 --- /dev/null +++ b/ModManager_Classes/Models/ModMetadata/LocalizedModinfo.cs @@ -0,0 +1,29 @@ +using Imya.Models.ModMetadata.ModinfoModel; +using Imya.Texts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static System.Net.Mime.MediaTypeNames; + +namespace Imya.Models.ModMetadata +{ + public class LocalizedModinfo : Modinfo + { + public LocalizedModinfo() + { } + + /// + /// Localized mod name or folder name as default. + /// + public new IText ModName { get; init; } + + /// + /// Localized category or "NoCategory" as default. + /// + public new IText Category { get; init; } + public new IText? Description { get; init; } + public new IText[]? KnownIssues { get; init; } + } +} diff --git a/ModManager_Classes/Models/ModMetadata/LocalizedModinfoFactory.cs b/ModManager_Classes/Models/ModMetadata/LocalizedModinfoFactory.cs new file mode 100644 index 00000000..3e9d4969 --- /dev/null +++ b/ModManager_Classes/Models/ModMetadata/LocalizedModinfoFactory.cs @@ -0,0 +1,72 @@ +using Imya.Models.ModMetadata.ModinfoModel; +using Imya.Texts; +using Octokit; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using static System.Net.Mime.MediaTypeNames; + +namespace Imya.Models.ModMetadata +{ + public class LocalizedModinfoFactory + { + private readonly ITextManager _textManager; + + public LocalizedModinfoFactory( + ITextManager textManager) + { + _textManager = textManager; + } + + public LocalizedModinfo GetLocalizedModinfo(Modinfo m) + { + var localized = new LocalizedModinfo() + { + Category = m.Category is not null ? _textManager.CreateLocalizedText(m.Category) : new SimpleText(""), + ModName = m.ModName is not null ? _textManager.CreateLocalizedText(m.ModName) : new SimpleText(""), + Description = m?.Description is not null ? _textManager.CreateLocalizedText(m.Description) : null, + KnownIssues = m?.KnownIssues is not null ? m.KnownIssues?.Where(x => x is not null).Select(x => _textManager.CreateLocalizedText(x)).ToArray() : null + }; + + localized.Version = m?.Version; + localized.ModID = m?.ModID; + localized.IncompatibleIds = m?.IncompatibleIds; + localized.DeprecateIds = m?.DeprecateIds; + localized.ModDependencies = m?.ModDependencies; + localized.DLCDependencies = m?.DLCDependencies; + localized.CreatorName = m?.CreatorName; + localized.CreatorContact = m?.CreatorContact; + localized.Image = m?.Image; + localized.LoadAfterIds = m?.LoadAfterIds; + + return localized; + } + + public LocalizedModinfo GetDummyModinfo(string foldername) + { + bool matches = MatchNameCategory(foldername, out var category, out var name); + var modName = new SimpleText(matches ? name : foldername); + var modCategory = matches ? new SimpleText(category) : _textManager.GetText("MODLIST_NOCATEGORY"); + + return new LocalizedModinfo() + { + Category = modCategory, + ModName = modName + }; + } + + private bool MatchNameCategory(string folderName, out string category, out string name) + { + string CategoryPattern = @"[[][a-z]+[]]"; + category = Regex.Match(folderName, CategoryPattern, RegexOptions.IgnoreCase).Value.TrimStart('[').TrimEnd(']'); + + string NamePattern = @"[^]]*"; + name = Regex.Match(folderName, NamePattern, RegexOptions.RightToLeft).Value.TrimStart(' '); + + return !name.Equals("") && !category.Equals(""); + } + } +} diff --git a/ModManager_Classes/Models/ModMetadata/ModIdActiveTouple.cs b/ModManager_Classes/Models/ModMetadata/ModIdActiveTouple.cs deleted file mode 100644 index 75bbea25..00000000 --- a/ModManager_Classes/Models/ModMetadata/ModIdActiveTouple.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Imya.Models.ModMetadata -{ - public class ModIdActiveTouple - { - public ModIdActiveTouple() { } - public String? ModID { get; set; } - public bool? Active { get; set; } - public String? Version { get; set; } - } -} diff --git a/ModManager_Classes/Models/ModMetadata/Modinfo.cs b/ModManager_Classes/Models/ModMetadata/Modinfo.cs deleted file mode 100644 index bc7c18c1..00000000 --- a/ModManager_Classes/Models/ModMetadata/Modinfo.cs +++ /dev/null @@ -1,65 +0,0 @@ -using Imya.Utils; -using Newtonsoft.Json; - -namespace Imya.Models.ModMetadata -{ - public class Modinfo - { - public Modinfo() { } - public string? Version { get; set; } - public string? ModID { get; set; } - public string[]? IncompatibleIds { get; set; } - public string[]? DeprecateIds { get; set; } - public string[]? ModDependencies { get; set; } - public Localized? Category { get; set; } - public Localized? ModName { get; set; } - public Localized? Description { get; set; } - public Localized[]? KnownIssues { get; set; } - public Dlc[]? DLCDependencies { get; set; } - public string? CreatorName { get; set; } - public string? CreatorContact { get; set; } - public string? Image { get; set; } - public string[]? LoadAfterIds { get; set; } - - public LocalizedModinfo GetLocalized(string name) => new (name, this); - } - - /// - /// Localized version of Modinfo. Localized properties are readonly. - /// - public class LocalizedModinfo : Modinfo - { - public LocalizedModinfo(string name, Modinfo? modinfo) - { - Version = modinfo?.Version; - ModID = modinfo?.ModID; - IncompatibleIds = modinfo?.IncompatibleIds; - DeprecateIds = modinfo?.DeprecateIds; - ModDependencies = modinfo?.ModDependencies; - DLCDependencies = modinfo?.DLCDependencies; - CreatorName = modinfo?.CreatorName; - CreatorContact = modinfo?.CreatorContact; - Image = modinfo?.Image; - LoadAfterIds = modinfo?.LoadAfterIds; - - // localize - Category = (modinfo?.Category is not null) ? TextManager.CreateLocalizedText(modinfo.Category) : TextManager.Instance["MODLIST_NOCATEGORY"]; - ModName = (modinfo?.ModName is not null) ? TextManager.CreateLocalizedText(modinfo.ModName) : new SimpleText(name); - Description = (modinfo?.Description is not null) ? TextManager.CreateLocalizedText(modinfo.Description) : null; - KnownIssues = (modinfo?.KnownIssues is not null) ? modinfo.KnownIssues.Where(x => x is not null).Select(x => TextManager.CreateLocalizedText(x)).ToArray() : null; - } - - /// - /// Localized mod name or folder name as default. - /// - public new IText ModName { get; init; } - - /// - /// Localized category or "NoCategory" as default. - /// - public new IText Category { get; init; } - - public new IText? Description { get; init; } - public new IText[]? KnownIssues { get; init; } - } -} diff --git a/ModManager_Classes/Models/ModMetadata/DLC.cs b/ModManager_Classes/Models/ModMetadata/ModinfoModel/DLC.cs similarity index 87% rename from ModManager_Classes/Models/ModMetadata/DLC.cs rename to ModManager_Classes/Models/ModMetadata/ModinfoModel/DLC.cs index 98fa7740..1eaa9767 100644 --- a/ModManager_Classes/Models/ModMetadata/DLC.cs +++ b/ModManager_Classes/Models/ModMetadata/ModinfoModel/DLC.cs @@ -2,7 +2,7 @@ using Newtonsoft.Json.Converters; using Imya.Enums; -namespace Imya.Models.ModMetadata +namespace Imya.Models.ModMetadata.ModinfoModel { public class Dlc { diff --git a/ModManager_Classes/Models/ModMetadata/Localized.cs b/ModManager_Classes/Models/ModMetadata/ModinfoModel/Localized.cs similarity index 53% rename from ModManager_Classes/Models/ModMetadata/Localized.cs rename to ModManager_Classes/Models/ModMetadata/ModinfoModel/Localized.cs index a2d35c9b..e23dede9 100644 --- a/ModManager_Classes/Models/ModMetadata/Localized.cs +++ b/ModManager_Classes/Models/ModMetadata/ModinfoModel/Localized.cs @@ -1,27 +1,27 @@ using Imya.Utils; -namespace Imya.Models.ModMetadata +namespace Imya.Models.ModMetadata.ModinfoModel { public class Localized { - public String? Chinese { get; set; } - public String? English { get; set; } - public String? French { get; set; } - public String? German { get; set; } - public String? Italian { get; set; } - public String? Japanese { get; set; } - public String? Korean { get; set; } - public String? Polish { get; set; } - public String? Russian { get; set; } - public String? Spanish { get; set; } - public String? Taiwanese { get; set; } + public string? Chinese { get; set; } + public string? English { get; set; } + public string? French { get; set; } + public string? German { get; set; } + public string? Italian { get; set; } + public string? Japanese { get; set; } + public string? Korean { get; set; } + public string? Polish { get; set; } + public string? Russian { get; set; } + public string? Spanish { get; set; } + public string? Taiwanese { get; set; } public Localized() { } // keep most common languages on top public bool HasAny() => - English is not null || German is not null || - French is not null || Italian is not null || Polish is not null || Russian is not null || Spanish is not null || + English is not null || German is not null || + French is not null || Italian is not null || Polish is not null || Russian is not null || Spanish is not null || Japanese is not null || Korean is not null || Taiwanese is not null; } diff --git a/ModManager_Classes/Models/ModMetadata/ModinfoModel/ModIdActiveTouple.cs b/ModManager_Classes/Models/ModMetadata/ModinfoModel/ModIdActiveTouple.cs new file mode 100644 index 00000000..58b22573 --- /dev/null +++ b/ModManager_Classes/Models/ModMetadata/ModinfoModel/ModIdActiveTouple.cs @@ -0,0 +1,10 @@ +namespace Imya.Models.ModMetadata.ModinfoModel +{ + public class ModIdActiveTouple + { + public ModIdActiveTouple() { } + public string? ModID { get; set; } + public bool? Active { get; set; } + public string? Version { get; set; } + } +} diff --git a/ModManager_Classes/Models/ModMetadata/ModinfoModel/Modinfo.cs b/ModManager_Classes/Models/ModMetadata/ModinfoModel/Modinfo.cs new file mode 100644 index 00000000..dc33a0a0 --- /dev/null +++ b/ModManager_Classes/Models/ModMetadata/ModinfoModel/Modinfo.cs @@ -0,0 +1,28 @@ +using Imya.Utils; +using Newtonsoft.Json; + +namespace Imya.Models.ModMetadata.ModinfoModel +{ + public class Modinfo + { + public Modinfo() { } + public string? Version { get; set; } + public string? ModID { get; set; } + public string[]? IncompatibleIds { get; set; } + public string[]? DeprecateIds { get; set; } + public string[]? ModDependencies { get; set; } + public Localized? Category { get; set; } + public Localized? ModName { get; set; } + public Localized? Description { get; set; } + public Localized[]? KnownIssues { get; set; } + public Dlc[]? DLCDependencies { get; set; } + public string? CreatorName { get; set; } + public string? CreatorContact { get; set; } + public string? Image { get; set; } + public string[]? LoadAfterIds { get; set; } + } + + /// + /// Localized version of Modinfo. Localized properties are readonly. + /// +} diff --git a/ModManager_Classes/Models/ModTweaker/DataModel/Storage/IModTweaksStorageModel.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Storage/IModTweaksStorageModel.cs new file mode 100644 index 00000000..a7576176 --- /dev/null +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Storage/IModTweaksStorageModel.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.ModTweaker.DataModel.Storage +{ + public interface IModTweaksStorageModel + { + public void SetTweakValue(string Filename, string ExposeID, string NewValue); + public bool TryGetTweakValue(string Filename, string ExposeID, out string? Value); + + public TweakerFileStorageModel GetTweak(string Filename); + } +} diff --git a/ModManager_Classes/Models/ModTweaker/DataModel/Storage/ITweakRepository.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Storage/ITweakRepository.cs new file mode 100644 index 00000000..2395afc5 --- /dev/null +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Storage/ITweakRepository.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.ModTweaker.DataModel.Storage +{ + public interface ITweakRepository + { + ModTweaksStorageModel Get(string ID); + bool IsStored(string ID); + void UpdateStorage(ModTweaksStorageModel storageModel, string modBaseName); + } +} diff --git a/ModManager_Classes/Models/ModTweaker/DataModel/Storage/ModTweaksStorageModel.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Storage/ModTweaksStorageModel.cs new file mode 100644 index 00000000..c84beea6 --- /dev/null +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Storage/ModTweaksStorageModel.cs @@ -0,0 +1,42 @@ +using Imya.Services; +using Imya.Utils; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.ModTweaker.DataModel.Storage +{ + public class ModTweaksStorageModel : IModTweaksStorageModel + { + public Dictionary Tweaks { get; set; } = new(); + + public ModTweaksStorageModel() + { + + } + + public void SetTweakValue(string Filename, string ExposeID, string NewValue) + { + AddOrGetTweak(Filename).SetTweakValue(ExposeID, NewValue); + } + + public bool TryGetTweakValue(string Filename, string ExposeID, out string? Value) + { + Value = GetTweak(Filename)?.GetTweakValue(ExposeID); + return Value is not null; + } + + public TweakerFileStorageModel? GetTweak(string Filename) + { + return Tweaks.SafeGet(Filename); + } + + public TweakerFileStorageModel AddOrGetTweak(string Filename) + { + return Tweaks.SafeAddOrGet(Filename); + } + } +} \ No newline at end of file diff --git a/ModManager_Classes/Models/ModTweaker/DataModel/Storage/TweakRepository.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Storage/TweakRepository.cs new file mode 100644 index 00000000..7252aedd --- /dev/null +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Storage/TweakRepository.cs @@ -0,0 +1,58 @@ +using Imya.Models.ModTweaker.IO; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Utils; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.ModTweaker.DataModel.Storage +{ + /// + /// A quick and dirty way to store the tweaks we did. + /// + public class TweakRepository : ITweakRepository + { + private readonly IImyaSetupService _imyaSetupService; + private readonly ModTweaksStorageModelLoader _storageModelLoader; + + public TweakRepository( + IImyaSetupService imyaSetupService, + ModTweaksStorageModelLoader storageModelLoader) + { + _imyaSetupService = imyaSetupService; + _storageModelLoader = storageModelLoader; + } + + private Dictionary loadedStorages = new(); + + /// + /// Gets the Tweak Storage for an ID. If it does not exist, it creates a new one. + /// + /// + /// + public ModTweaksStorageModel Get(string ID) + { + var filename = GetFilepath(ID); + if (!File.Exists(filename)) + return new ModTweaksStorageModel(); + return _storageModelLoader.Load(GetFilepath(ID)) ?? new ModTweaksStorageModel(); + } + + public bool IsStored(string ID) + { + return File.Exists(GetFilepath(ID)); + } + + public void UpdateStorage(ModTweaksStorageModel storageModel, string modBaseName) + { + _storageModelLoader.Save(storageModel, GetFilepath(modBaseName)); + } + + private String GetFilepath(String baseName) => Path.Combine(_imyaSetupService.TweakDirectoryPath, baseName + ".json"); + } + +} diff --git a/ModManager_Classes/Models/ModTweaker/DataModel/Storage/TweakerFileStorageModel.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Storage/TweakerFileStorageModel.cs new file mode 100644 index 00000000..7cdb8aa3 --- /dev/null +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Storage/TweakerFileStorageModel.cs @@ -0,0 +1,26 @@ +using Imya.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.ModTweaker.DataModel.Storage +{ + public class TweakerFileStorageModel + { + /// + /// Expose ID <-> Stored Value + /// + public Dictionary Values { get; set; } = new(); + + public string? GetTweakValue(string ExposeID) + { + return Values.SafeGet(ExposeID); + } + + public bool HasStoredValue(string ExposeID) => Values.ContainsKey(ExposeID); + + public void SetTweakValue(string ExposeID, string Value) => Values[ExposeID] = Value; + } +} diff --git a/ModManager_Classes/Models/ModTweaker/ExposedModValue.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedModValue.cs similarity index 73% rename from ModManager_Classes/Models/ModTweaker/ExposedModValue.cs rename to ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedModValue.cs index 2c26ba3b..31c97721 100644 --- a/ModManager_Classes/Models/ModTweaker/ExposedModValue.cs +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedModValue.cs @@ -3,30 +3,29 @@ using System.Text.RegularExpressions; using System.Xml; -namespace Imya.Models.ModTweaker +namespace Imya.Models.ModTweaker.DataModel.Tweaking { public class ExposedModValue : PropertyChangedNotifier, IExposedModValue { - public String Path { get; init; } - public String ModOpID { get; init; } - public String ExposeID { get; init; } - public String? Description { get; init; } - public String? Tooltip { get; init; } + public string Path { get; init; } + public string ModOpID { get; init; } + public string ExposeID { get; init; } + public string? Description { get; init; } + public string? Tooltip { get; init; } public ExposedModValueType ExposedModValueType { get; init; } public ExposedModValueReplaceType ReplaceType { get; init; } public TweakerFile Parent { get; init; } - public String Value + public string Value { get => _value; set { SetProperty(ref _value, value); - Parent.TweakStorage.SetTweakValue(Parent.FilePath, ExposeID, Value); } } - private String _value; + private string _value; public ExposedModValue() { @@ -38,7 +37,7 @@ public ExposedModValue() public bool IsSimpleValue { get => ExposedModValueType == ExposedModValueType.SimpleValue; } public bool IsSliderType { get => ExposedModValueType == ExposedModValueType.Slider; } public bool IsToggleType { get => ExposedModValueType == ExposedModValueType.Toggle || ExposedModValueType == ExposedModValueType.SkipToggle; } - + } } diff --git a/ModManager_Classes/Models/ModTweaker/ExposedModValueFactory.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedModValueFactory.cs similarity index 94% rename from ModManager_Classes/Models/ModTweaker/ExposedModValueFactory.cs rename to ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedModValueFactory.cs index 197cd19c..dee863ba 100644 --- a/ModManager_Classes/Models/ModTweaker/ExposedModValueFactory.cs +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedModValueFactory.cs @@ -6,23 +6,22 @@ using System.Threading.Tasks; using System.Xml; -namespace Imya.Models.ModTweaker +namespace Imya.Models.ModTweaker.DataModel.Tweaking { public class ExposedModValueFactory { - public static IExposedModValue? FromXmlNode(XmlNode Expose, TweakerFile parent) { - if (Expose.TryGetAttribute(TweakerConstants.EXPOSE_PATH, out String? Path) - && Expose.TryGetAttribute(TweakerConstants.MODOP_ID, out String? ModOpID) - && Expose.TryGetAttribute(TweakerConstants.EXPOSE_ATTR, out String? ExposeID)) + if (Expose.TryGetAttribute(TweakerConstants.EXPOSE_PATH, out string? Path) + && Expose.TryGetAttribute(TweakerConstants.MODOP_ID, out string? ModOpID) + && Expose.TryGetAttribute(TweakerConstants.EXPOSE_ATTR, out string? ExposeID)) { - Expose.TryGetAttribute(TweakerConstants.DESCRIPTION, out String? description); - Expose.TryGetAttribute(TweakerConstants.TOOLTIP, out String? tooltip); + Expose.TryGetAttribute(TweakerConstants.DESCRIPTION, out string? description); + Expose.TryGetAttribute(TweakerConstants.TOOLTIP, out string? tooltip); ExposedModValueType type = ExposedModValueType.SimpleValue; - Expose.TryGetAttribute(TweakerConstants.KIND, out String? Kind); - if (Kind is String valid_kind && Enum.TryParse(valid_kind, out var _val)) + Expose.TryGetAttribute(TweakerConstants.KIND, out string? Kind); + if (Kind is string valid_kind && Enum.TryParse(valid_kind, out var _val)) { type = _val; } @@ -96,7 +95,7 @@ public class ExposedModValueFactory IsInverted = IsInverted }; val.InitTrueValue(); - return val; + return val; } } else if (type == ExposedModValueType.SkipToggle) diff --git a/ModManager_Classes/Models/ModTweaker/ExposedPredefinedModValue.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedPredefinedModValue.cs similarity index 61% rename from ModManager_Classes/Models/ModTweaker/ExposedPredefinedModValue.cs rename to ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedPredefinedModValue.cs index 211019af..76e286ac 100644 --- a/ModManager_Classes/Models/ModTweaker/ExposedPredefinedModValue.cs +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedPredefinedModValue.cs @@ -4,13 +4,14 @@ using System.Text; using System.Threading.Tasks; -namespace Imya.Models.ModTweaker +namespace Imya.Models.ModTweaker.DataModel.Tweaking { public class ExposedPredefinedModValue : ExposedModValue { - public String[]? PredefinedValues { get; init; } + public string[]? PredefinedValues { get; init; } - public ExposedPredefinedModValue() : base() { + public ExposedPredefinedModValue() : base() + { ExposedModValueType = ExposedModValueType.Enum; } } diff --git a/ModManager_Classes/Models/ModTweaker/ExposedRangedModValue.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedRangedModValue.cs similarity index 77% rename from ModManager_Classes/Models/ModTweaker/ExposedRangedModValue.cs rename to ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedRangedModValue.cs index bec72baa..1e3c1f3e 100644 --- a/ModManager_Classes/Models/ModTweaker/ExposedRangedModValue.cs +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedRangedModValue.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace Imya.Models.ModTweaker +namespace Imya.Models.ModTweaker.DataModel.Tweaking { public class ExposedRangedModValue : ExposedModValue { @@ -12,9 +12,10 @@ public class ExposedRangedModValue : ExposedModValue public float Max { get; init; } public float Stepping { get; init; } - public ExposedRangedModValue() : base() { + public ExposedRangedModValue() : base() + { ExposedModValueType = ExposedModValueType.Slider; } } - + } diff --git a/ModManager_Classes/Models/ModTweaker/ExposedToggleModValue.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedToggleModValue.cs similarity index 67% rename from ModManager_Classes/Models/ModTweaker/ExposedToggleModValue.cs rename to ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedToggleModValue.cs index b1f7de90..9e9d5233 100644 --- a/ModManager_Classes/Models/ModTweaker/ExposedToggleModValue.cs +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ExposedToggleModValue.cs @@ -5,27 +5,30 @@ using System.Threading.Tasks; using System.Xml; -namespace Imya.Models.ModTweaker +namespace Imya.Models.ModTweaker.DataModel.Tweaking { public class ExposedToggleModValue : ExposedModValue { - public String TrueValue { get; set; } - public String FalseValue { get; set; } + public string TrueValue { get; set; } + public string FalseValue { get; set; } - public bool IsTrue { + public bool IsTrue + { get => _isTrue; - set { + set + { SetProperty(ref _isTrue, value); Value = _isTrue ? - (IsInverted ? FalseValue : TrueValue) : - (IsInverted ? TrueValue : FalseValue); + IsInverted ? FalseValue : TrueValue : + IsInverted ? TrueValue : FalseValue; } } private bool _isTrue; public bool IsInverted { get; init; } = true; - public ExposedToggleModValue() : base() { + public ExposedToggleModValue() : base() + { ExposedModValueType = ExposedModValueType.Toggle; ReplaceType = ExposedModValueReplaceType.Xml; } @@ -42,9 +45,9 @@ public void InitTrueValue() .Select(x => x.SelectSingleNode(Path)) .FirstOrDefault()? .OuterXml; - TrueValue = vals is not null ? String.Join("", vals) : String.Empty; + TrueValue = vals is not null ? string.Join("", vals) : string.Empty; }); - + } } } diff --git a/ModManager_Classes/Models/ModTweaker/IExposedModValue.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/IExposedModValue.cs similarity index 70% rename from ModManager_Classes/Models/ModTweaker/IExposedModValue.cs rename to ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/IExposedModValue.cs index a37413c0..3f8dd6d0 100644 --- a/ModManager_Classes/Models/ModTweaker/IExposedModValue.cs +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/IExposedModValue.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace Imya.Models.ModTweaker +namespace Imya.Models.ModTweaker.DataModel.Tweaking { public enum ExposedModValueType { SimpleValue, Enum, Slider, Toggle, SkipToggle } @@ -12,16 +12,16 @@ public enum ExposedModValueReplaceType { Text, Xml } public interface IExposedModValue { - public String Path { get; init; } - public String ModOpID { get; init; } - public String ExposeID { get; init; } + public string Path { get; init; } + public string ModOpID { get; init; } + public string ExposeID { get; init; } public ExposedModValueType ExposedModValueType { get; init; } - public ExposedModValueReplaceType ReplaceType { get; init;} + public ExposedModValueReplaceType ReplaceType { get; init; } public TweakerFile Parent { get; init; } - public String? Description { get; init; } + public string? Description { get; init; } - public String Value { get; set; } + public string Value { get; set; } public bool IsEnumType { get; } public bool IsSimpleValue { get; } diff --git a/ModManager_Classes/Models/ModTweaker/ModOp.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ModOp.cs similarity index 71% rename from ModManager_Classes/Models/ModTweaker/ModOp.cs rename to ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ModOp.cs index 4e848463..39e927ce 100644 --- a/ModManager_Classes/Models/ModTweaker/ModOp.cs +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ModOp.cs @@ -6,21 +6,21 @@ using System.Threading.Tasks; using System.Xml; -namespace Imya.Models.ModTweaker +namespace Imya.Models.ModTweaker.DataModel.Tweaking { public class ModOp { - public String? ID; + public string? ID; public IEnumerable Code; public string? Skip; //Mod Op related things - public String Type; - public String? GUID; - public String? Path; + public string Type; + public string? GUID; + public string? Path; - public bool IsValid => GUID is String || Path is String; - public bool HasID => ID is String; + public bool IsValid => GUID is string || Path is string; + public bool HasID => ID is string; public static ModOp? FromXmlNode(XmlNode ModOp) { @@ -32,9 +32,9 @@ public class ModOp if (type is not null) { - ModOp.TryGetAttribute(TweakerConstants.GUID, out String? Guid); - ModOp.TryGetAttribute(TweakerConstants.PATH, out String? Path); - ModOp.TryGetAttribute(TweakerConstants.MODOP_ID, out String? ID); + ModOp.TryGetAttribute(TweakerConstants.GUID, out string? Guid); + ModOp.TryGetAttribute(TweakerConstants.PATH, out string? Path); + ModOp.TryGetAttribute(TweakerConstants.MODOP_ID, out string? ID); return new ModOp { ID = ID!, @@ -52,12 +52,12 @@ public ModOp Clone() { return new ModOp() { - ID = this.ID, - Code = this.Code.Select(x => x.CloneNode(true)).ToList(), - Type = this.Type, - GUID = this.GUID, - Path = this.Path - }; + ID = ID, + Code = Code.Select(x => x.CloneNode(true)).ToList(), + Type = Type, + GUID = GUID, + Path = Path + }; } } diff --git a/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ModTweaks.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ModTweaks.cs new file mode 100644 index 00000000..35f1ac1c --- /dev/null +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/ModTweaks.cs @@ -0,0 +1,45 @@ +using Imya.Models.Attributes; +using Imya.Models.Attributes.Factories; +using Imya.Models.Mods; +using Imya.Models.NotifyPropertyChanged; +using Imya.Services; +using System.Collections.ObjectModel; + +namespace Imya.Models.ModTweaker.DataModel.Tweaking +{ + /// + /// Represents all Tweaker Files in a Mod at once + /// + public class ModTweaks : PropertyChangedNotifier + { + public bool IsEmpty => _tweakerFiles == null || _tweakerFiles.Count() == 0; + + public IEnumerable? TweakerFiles + { + get => _tweakerFiles ?? Enumerable.Empty(); + set + { + _tweakerFiles = value; + OnPropertyChanged(nameof(TweakerFiles)); + OnPropertyChanged(nameof(IsEmpty)); + } + } + private IEnumerable? _tweakerFiles = null; + + private Mod? _mod = null; + + public string ModBaseName { get => _mod.FolderName; } + public string ModBasePath { get => _mod.FullModPath; } + + public ModTweaks(Mod mod, IEnumerable tweakerFiles) + { + _mod = mod; + TweakerFiles = new ObservableCollection(tweakerFiles); + } + + public ModTweaks() + { + + } + } +} diff --git a/ModManager_Classes/Models/ModTweaker/TweakerFile.cs b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/TweakerFile.cs similarity index 85% rename from ModManager_Classes/Models/ModTweaker/TweakerFile.cs rename to ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/TweakerFile.cs index cd8a0473..d734d283 100644 --- a/ModManager_Classes/Models/ModTweaker/TweakerFile.cs +++ b/ModManager_Classes/Models/ModTweaker/DataModel/Tweaking/TweakerFile.cs @@ -1,4 +1,6 @@ -using Imya.Models.NotifyPropertyChanged; +using Imya.Models.ModTweaker.DataModel.Storage; +using Imya.Models.ModTweaker.IO; +using Imya.Models.NotifyPropertyChanged; using Imya.Utils; using System; using System.Collections.Generic; @@ -8,25 +10,25 @@ using System.Threading.Tasks; using System.Xml; -namespace Imya.Models.ModTweaker +namespace Imya.Models.ModTweaker.DataModel.Tweaking { public struct TweakerConstants { - public static readonly String EXPOSE_STRING = "ImyaExpose"; - public static readonly String EXPOSE_ATTR = "ExposeID"; - public static readonly String EXPOSE_PATH = "Path"; - public static readonly String MODOP_ID = "ModOpID"; - public static readonly String KIND = "Kind"; - public static readonly String DESCRIPTION = "Description"; - public static readonly String TOOLTIP = "Tooltip"; - public static readonly String ENUM_HEADER = "FixedValues"; - public static readonly String ENUM_ENTRY = "Value"; - public static readonly String ALT_TOGGLE_VAL = "AltValue"; - public static readonly String INVERTED = "Invert"; - - public static readonly String PATH = "Path"; - public static readonly String TYPE = "Type"; - public static readonly String GUID = "GUID"; + public static readonly string EXPOSE_STRING = "ImyaExpose"; + public static readonly string EXPOSE_ATTR = "ExposeID"; + public static readonly string EXPOSE_PATH = "Path"; + public static readonly string MODOP_ID = "ModOpID"; + public static readonly string KIND = "Kind"; + public static readonly string DESCRIPTION = "Description"; + public static readonly string TOOLTIP = "Tooltip"; + public static readonly string ENUM_HEADER = "FixedValues"; + public static readonly string ENUM_ENTRY = "Value"; + public static readonly string ALT_TOGGLE_VAL = "AltValue"; + public static readonly string INVERTED = "Invert"; + + public static readonly string PATH = "Path"; + public static readonly string TYPE = "Type"; + public static readonly string GUID = "GUID"; } /// @@ -36,11 +38,11 @@ public struct TweakerConstants /// public class TweakerFile : PropertyChangedNotifier { - public String BasePath { get; private set; } - public String FilePath { get; private set; } - public String BaseFilePath => Path.GetDirectoryName(FilePath); - public String SourceFilename { get; private set; } - public String EditFilename => Path.GetFileNameWithoutExtension(SourceFilename) + ".imyatweak.include.xml"; + public string BasePath { get; private set; } + public string FilePath { get; private set; } + public string BaseFilePath => Path.GetDirectoryName(FilePath); + public string SourceFilename { get; private set; } + public string EditFilename => Path.GetFileNameWithoutExtension(SourceFilename) + ".imyatweak.include.xml"; public string Title { get; private set; } @@ -71,9 +73,9 @@ public ObservableCollection ModOps private XmlDocument TargetDocument { get; set; } = new XmlDocument(); private XmlNode TargetRoot { get; set; } - public ITweakStorage TweakStorage; + public IModTweaksStorageModel TweakStorage; - private TweakerFile(String _filepath, String _basepath) + private TweakerFile(string _filepath, string _basepath) { FilePath = _filepath; Title = Path.GetFileName(FilePath); @@ -90,12 +92,12 @@ private void InitTargetDocument() TargetRoot = node; } - public IExposedModValue? GetExpose(String ExposeID) + public IExposedModValue? GetExpose(string ExposeID) { return Exposes.First(x => x.ExposeID.Equals(ExposeID)); } - public bool ExposeExists(String ExposeID) + public bool ExposeExists(string ExposeID) { return Exposes.Any(x => x.ExposeID.Equals(ExposeID)); } @@ -110,7 +112,7 @@ public bool ExposeExists(String ExposeID) public string GetDefaultNodeValue(IExposedModValue expose) { var op = ModOps.Where(x => x.HasID && x.ID!.Equals(expose.ModOpID)).FirstOrDefault(); - if (op is null) + if (op is null) return string.Empty; foreach (XmlNode x in op.Code) { @@ -207,7 +209,7 @@ public void EnsureSkipFlag(XmlNode? modop, bool value) public void EnsureInclude() { - String Filepath = $"./{EditFilename}"; + string Filepath = $"./{EditFilename}"; if (OriginalDocument.SelectSingleNode($"/ModOps/Include[@File = '{Filepath}']") is not null) return; @@ -227,7 +229,7 @@ public void EnsureInclude() /// /// /// Whether the expose needed to be executed. - + #endregion @@ -288,7 +290,7 @@ public void Save(string basePath) } } - public static bool TryInit(string basePath, string filePath, ITweakStorage tweakStorage, out TweakerFile tweakerFile) + public static bool TryInit(string basePath, string filePath, out TweakerFile tweakerFile) { tweakerFile = new(filePath, basePath); @@ -296,8 +298,6 @@ public static bool TryInit(string basePath, string filePath, ITweakStorage tweak { tweakerFile.OriginalDocument = doc; tweakerFile.BasePath = Path.GetFileName(basePath); - - tweakerFile.TweakStorage = tweakStorage; var parser = new XmlPatchParser(doc); var editables = parser.FetchExposes(tweakerFile); diff --git a/ModManager_Classes/Models/ModTweaker/IO/ModTweaksExporter.cs b/ModManager_Classes/Models/ModTweaker/IO/ModTweaksExporter.cs new file mode 100644 index 00000000..f60f8410 --- /dev/null +++ b/ModManager_Classes/Models/ModTweaker/IO/ModTweaksExporter.cs @@ -0,0 +1,53 @@ +using Imya.Models.Mods; +using Imya.Models.ModTweaker.DataModel.Storage; +using Imya.Models.ModTweaker.DataModel.Tweaking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.ModTweaker.IO +{ + public class ModTweaksExporter + { + private readonly ITweakRepository _tweakRepository; + public ModTweaksExporter(ITweakRepository tweakRepository) + { + _tweakRepository = tweakRepository; + } + + public void Save(ModTweaks tweaks) + { + if (tweaks.IsEmpty) + return; + var storageModel = ConvertToStorage(tweaks); + _tweakRepository.UpdateStorage(storageModel, tweaks.ModBaseName); + foreach (var tweakerFile in tweaks.TweakerFiles) + { + tweakerFile.Save(tweaks.ModBasePath); + } + } + + private ModTweaksStorageModel ConvertToStorage(ModTweaks tweaks) + { + ModTweaksStorageModel storage = new ModTweaksStorageModel(); + if (tweaks.TweakerFiles is null) + return new ModTweaksStorageModel(); + foreach (var file in tweaks.TweakerFiles!) + { + var tweak = GetTweak(file); + storage.Tweaks.Add(file.FilePath, tweak); + } + return storage; + } + + private TweakerFileStorageModel GetTweak(TweakerFile tweakerFile) + { + var tweak = new TweakerFileStorageModel(); + foreach (IExposedModValue expose in tweakerFile.Exposes) + tweak.SetTweakValue(expose.ExposeID, expose.Value); + return tweak; + } + } +} diff --git a/ModManager_Classes/Models/ModTweaker/IO/ModTweaksLoader.cs b/ModManager_Classes/Models/ModTweaker/IO/ModTweaksLoader.cs new file mode 100644 index 00000000..147e4f5b --- /dev/null +++ b/ModManager_Classes/Models/ModTweaker/IO/ModTweaksLoader.cs @@ -0,0 +1,57 @@ +using Imya.Models.Mods; +using Imya.Models.ModTweaker.DataModel.Storage; +using Imya.Models.ModTweaker.DataModel.Tweaking; + +namespace Imya.Models.ModTweaker.IO +{ + public class ModTweaksLoader + { + private readonly ITweakRepository _tweakRepository; + + public ModTweaksLoader(ITweakRepository tweakRepository) + { + _tweakRepository = tweakRepository; + } + + public ModTweaks? Load(Mod mod) + { + + var files = mod.GetFilesWithExtension("xml") + .Where(x => !x.EndsWith("imyatweak.include.xml")) + .Select(x => Path.GetRelativePath(mod.FullModPath, x)) + .ToArray(); + + var list = new List(); + foreach (string file in files) + { + if (TweakerFile.TryInit(mod.FullModPath, file, out var tweakerFile)) + list.Add(tweakerFile); + } + + var tweaks = new ModTweaks(mod, list); + var storedtweaks = _tweakRepository.Get(tweaks.ModBaseName); + + ApplyStoredValues(tweaks, storedtweaks); + return tweaks; + } + + private void ApplyStoredValues(ModTweaks tweaks, ModTweaksStorageModel stored) + { + foreach (TweakerFile file in tweaks.TweakerFiles!) + { + var tweak = stored.GetTweak(file.FilePath); + if (tweak is not null) + ApplyToTweakerFile(file, tweak); + } + } + + private void ApplyToTweakerFile(TweakerFile file, TweakerFileStorageModel tweak) + { + foreach (IExposedModValue expose in file.Exposes) + { + if (tweak.HasStoredValue(expose.ExposeID)) + expose.Value = tweak.GetTweakValue(expose.ExposeID)!; + } + } + } +} diff --git a/ModManager_Classes/Models/ModTweaker/IO/ModTweaksStorageModelLoader.cs b/ModManager_Classes/Models/ModTweaker/IO/ModTweaksStorageModelLoader.cs new file mode 100644 index 00000000..327cb6bb --- /dev/null +++ b/ModManager_Classes/Models/ModTweaker/IO/ModTweaksStorageModelLoader.cs @@ -0,0 +1,41 @@ +using Imya.Models.ModTweaker.DataModel.Storage; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.ModTweaker.IO +{ + public class ModTweaksStorageModelLoader + { + public void Save(ModTweaksStorageModel model, String filename) + { + string s = JsonConvert.SerializeObject(model.Tweaks, Formatting.Indented); + using (StreamWriter writer = new StreamWriter(File.Create(filename))) + { + writer.Write(s); + } + } + + public ModTweaksStorageModel? Load(String filename) + { + var jsonString = File.ReadAllText(filename); + try + { + var storageModel = new ModTweaksStorageModel(); + var tweaks = JsonConvert.DeserializeObject>(jsonString); + if (tweaks is null) return null; + storageModel.Tweaks = tweaks; + + return storageModel; + } + catch (JsonSerializationException e) + { + Console.WriteLine("Could not load Tweak Collection"); + return null; + } + } + } +} diff --git a/ModManager_Classes/Models/ModTweaker/TweakExporter.cs b/ModManager_Classes/Models/ModTweaker/IO/TweakExporter.cs similarity index 87% rename from ModManager_Classes/Models/ModTweaker/TweakExporter.cs rename to ModManager_Classes/Models/ModTweaker/IO/TweakExporter.cs index 3133da34..dfae73b0 100644 --- a/ModManager_Classes/Models/ModTweaker/TweakExporter.cs +++ b/ModManager_Classes/Models/ModTweaker/IO/TweakExporter.cs @@ -4,23 +4,25 @@ using System.Text; using System.Threading.Tasks; using System.Xml; +using Imya.Models.ModTweaker.DataModel.Tweaking; -namespace Imya.Models.ModTweaker +namespace Imya.Models.ModTweaker.IO { public class TweakExporter { IEnumerable ModOps; IEnumerable ExposedModValues; - public TweakExporter(IEnumerable modops, IEnumerable exposes) { + public TweakExporter(IEnumerable modops, IEnumerable exposes) + { ModOps = modops is not null ? modops.Select(x => x.Clone()).ToList() : Enumerable.Empty(); ExposedModValues = exposes is not null ? exposes : Enumerable.Empty(); } - public IEnumerable GetExported() + public IEnumerable GetExported() { foreach (IExposedModValue val in ExposedModValues) - { + { ExecuteExpose(val); } foreach (ModOp op in ModOps) @@ -48,13 +50,13 @@ private void ExecuteExpose(IExposedModValue expose) foreach (XmlNode x in n.Code) { var edited_node = ExecuteExpose(x, expose); - if(edited_node is not null) + if (edited_node is not null) newNodes.Add(edited_node); } - if (n.Code.Count() == 0 && !String.IsNullOrEmpty(expose.Value) && String.IsNullOrEmpty(expose.Path)) + if (n.Code.Count() == 0 && !string.IsNullOrEmpty(expose.Value) && string.IsNullOrEmpty(expose.Path)) { var loadedNode = LoadXmlNodeFrom(expose.Value); - if(loadedNode is not null) + if (loadedNode is not null) newNodes.Add(loadedNode); } n.Code = newNodes; @@ -68,7 +70,8 @@ private void ExecuteExpose(IExposedModValue expose) if (expose.ReplaceType == ExposedModValueReplaceType.Text) { - foreach (XmlNode n in nodesToEdit) { + foreach (XmlNode n in nodesToEdit) + { n.InnerText = expose.Value; } } @@ -90,7 +93,7 @@ private void ExecuteExpose(IExposedModValue expose) return node; } - private XmlNode? LoadXmlNodeFrom(String xml) + private XmlNode? LoadXmlNodeFrom(string xml) { XmlDocument doc = new XmlDocument(); try diff --git a/ModManager_Classes/Models/ModTweaker/XmlPatchParser.cs b/ModManager_Classes/Models/ModTweaker/IO/XmlPatchParser.cs similarity index 86% rename from ModManager_Classes/Models/ModTweaker/XmlPatchParser.cs rename to ModManager_Classes/Models/ModTweaker/IO/XmlPatchParser.cs index d69a2346..9404d1f7 100644 --- a/ModManager_Classes/Models/ModTweaker/XmlPatchParser.cs +++ b/ModManager_Classes/Models/ModTweaker/IO/XmlPatchParser.cs @@ -1,4 +1,5 @@ -using Imya.Utils; +using Imya.Models.ModTweaker.DataModel.Tweaking; +using Imya.Utils; using System; using System.Collections.Generic; using System.Linq; @@ -6,7 +7,7 @@ using System.Threading.Tasks; using System.Xml; -namespace Imya.Models.ModTweaker +namespace Imya.Models.ModTweaker.IO { /// /// Object that scans XML documents for Imya Expose Instructions and ModOps that are affected by it. @@ -42,21 +43,12 @@ internal IEnumerable FetchExposes(TweakerFile parent) IExposedModValue? expose = ExposedModValueFactory.FromXmlNode(ExposeInstruction, parent); if (expose is null) return null; - - if (parent.TweakStorage.TryGetTweakValue(parent.FilePath, expose.ExposeID, out var value)) - { - expose.Value = value!; - } - else - { - expose.Value = parent.GetDefaultNodeValue(expose); - } - + expose.Value = parent.GetDefaultNodeValue(expose); return expose; } internal IEnumerable FetchModOps(XmlDocument ImportDocument) - { + { var modOps = Document.SelectNodes("/ModOps/ModOp | /ModOps/Include"); if (modOps is null) yield break; diff --git a/ModManager_Classes/Models/ModTweaker/ModTweaks.cs b/ModManager_Classes/Models/ModTweaker/ModTweaks.cs deleted file mode 100644 index 94e2d038..00000000 --- a/ModManager_Classes/Models/ModTweaker/ModTweaks.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Imya.Models.Attributes; -using Imya.Models.NotifyPropertyChanged; -using Imya.Utils; -using System.Collections.ObjectModel; - -namespace Imya.Models.ModTweaker -{ - /// - /// Represents all Tweaker Files in a Mod at once - /// - public class ModTweaks : PropertyChangedNotifier - { - public bool IsEmpty => _tweakerFiles == null || _tweakerFiles.Count == 0; - - public ObservableCollection? TweakerFiles - { - get => _tweakerFiles; - set - { - _tweakerFiles = value; - OnPropertyChanged(nameof(TweakerFiles)); - OnPropertyChanged(nameof(IsEmpty)); - } - } - private ObservableCollection? _tweakerFiles = null; - - private Mod? _mod = null; - - private String? ModBaseName => _mod?.FolderName; - - public ITweakStorage TweakStorage; - public void Load(Mod mod) - { - if (mod is null) return; - - _mod = mod; - var files = mod.GetFilesWithExtension("xml").Where(x => !x.EndsWith("imyatweak.include.xml")).Select(x => Path.GetRelativePath(mod.FullModPath, x)).ToArray(); - var list = new ObservableCollection(); - - TweakStorage = TweakFileStorage.LoadOrCreate(ModBaseName!); - foreach (string filename in files) - { - if (TweakerFile.TryInit(mod.FullModPath, filename, TweakStorage, out var file)) - { - list.Add(file); - } - } - TweakerFiles = list; - } - - public void Save() - { - if (TweakerFiles != null && TweakerFiles.Count > 0 && _mod != null) - { - foreach (var f in TweakerFiles) - { - f.Save(_mod.FullModPath); - } - TweakStorage.Save(ModBaseName); - - if (_mod is not null && !_mod.Attributes.HasAttribute(AttributeType.TweakedMod)) - { - _mod.Attributes.AddAttribute(TweakedAttributeFactory.Get()); - } - - GameSetupManager.Instance.DeleteCache(); - } - - - } - } -} diff --git a/ModManager_Classes/Models/ModTweaker/Storage/ITweakStorage.cs b/ModManager_Classes/Models/ModTweaker/Storage/ITweakStorage.cs deleted file mode 100644 index ff7e30b0..00000000 --- a/ModManager_Classes/Models/ModTweaker/Storage/ITweakStorage.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Imya.Models.ModTweaker -{ - public interface ITweakStorage - { - public void SetTweakValue(String Filename, String ExposeID, String NewValue); - public bool TryGetTweakValue(String Filename, String ExposeID, out String? Value); - - public void Save(String FilenameBase); - } -} diff --git a/ModManager_Classes/Models/ModTweaker/Storage/TweakFileStorage.cs b/ModManager_Classes/Models/ModTweaker/Storage/TweakFileStorage.cs deleted file mode 100644 index e9bbf2bd..00000000 --- a/ModManager_Classes/Models/ModTweaker/Storage/TweakFileStorage.cs +++ /dev/null @@ -1,100 +0,0 @@ -using Imya.Utils; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Imya.Models.ModTweaker -{ - - public class TweakFileStorage : ITweakStorage - { - public Dictionary Tweaks { get; set; } = new(); - - public static String BaseDirectory => ImyaSetupManager.Instance.TweakDirectoryPath; - - public TweakFileStorage() - { - if(!Directory.Exists(BaseDirectory)) - Directory.CreateDirectory(BaseDirectory); - } - - public void SetTweakValue(string Filename, string ExposeID, string NewValue) - { - AddOrGetTweak(Filename).SetTweakValue(ExposeID, NewValue); - } - - public bool TryGetTweakValue(string Filename, string ExposeID, out string? Value) - { - Value = GetTweak(Filename)?.GetTweakValue(ExposeID); - return Value is not null; - } - - public Tweak? GetTweak(String Filename) - { - return Tweaks.SafeGet(Filename); - } - - public Tweak AddOrGetTweak(String Filename) - { - return Tweaks.SafeAddOrGet(Filename); - } - - public void Save(String FilenameBase) - { - //filter name to be on the safe side. - FilenameBase = Path.GetFileName(FilenameBase); - - String s = JsonConvert.SerializeObject(Tweaks, Formatting.Indented); - using (StreamWriter writer = new StreamWriter(File.Create($"{Path.Combine(BaseDirectory, FilenameBase)}.json"))) - { - writer.Write(s); - } - } - - public static TweakFileStorage? LoadFromFile(String FilenameBase) - { - try - { - return LoadFromJson(File.ReadAllText($"{Path.Combine(BaseDirectory, FilenameBase)}.json")); - } - catch (Exception) - { - return null; - } - } - - /// - /// Loads a Tweak Collection From the file with the specified ID. If there is no stored collection, it creates a new one. - /// - /// - /// The tweak collection - public static TweakFileStorage LoadOrCreate(String FilenameBase) - { - var coll = LoadFromFile(FilenameBase); - if (coll is not null) return coll; - - return new TweakFileStorage(); - } - - public static TweakFileStorage? LoadFromJson(String JsonString) - { - try - { - var coll = new TweakFileStorage(); - var tweaks = JsonConvert.DeserializeObject>(JsonString); - if (tweaks is null) return null; - coll.Tweaks = tweaks; - - return coll; - } - catch (JsonSerializationException e) - { - Console.WriteLine("Could not load Tweak Collection"); - return null; - } - } - } -} \ No newline at end of file diff --git a/ModManager_Classes/Models/ModTweaker/Storage/TweakStorageShelf.cs b/ModManager_Classes/Models/ModTweaker/Storage/TweakStorageShelf.cs deleted file mode 100644 index f2a47fa2..00000000 --- a/ModManager_Classes/Models/ModTweaker/Storage/TweakStorageShelf.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Imya.Utils; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Imya.Models.ModTweaker -{ - /// - /// A quick and dirty way to store the tweaks we did. - /// - public class TweakStorageShelf - { - public static TweakStorageShelf Global { get; } = new TweakStorageShelf(); - - public TweakStorageShelf() { } - - private Dictionary Tweaks = new(); - - /// - /// Gets the Tweak Storage for an ID. If it does not exist, it creates a new one. - /// - /// - /// - public ITweakStorage Get(String ID) - { - return Tweaks.SafeAddOrGet(ID); - } - - public bool IsStored(String ID) - { - return File.Exists(Path.Combine(ImyaSetupManager.Instance.TweakDirectoryPath, ID + ".json")); - } - - public IEnumerable GetAllStorages() - { - return Tweaks.Values.ToList(); - } - - public void SaveAll() - { - foreach (var (key, value) in Tweaks) - { - value.Save(key); - } - } - } - - public class Tweak - { - /// - /// Expose ID <-> Stored Value - /// - public Dictionary Values { get; set; } = new(); - - public String? GetTweakValue(String ExposeID) - { - return Values.SafeGet(ExposeID); - } - public void SetTweakValue(String ExposeID, String Value) => Values[ExposeID] = Value; - } -} diff --git a/ModManager_Classes/Models/Mods/IModCollectionFactory.cs b/ModManager_Classes/Models/Mods/IModCollectionFactory.cs new file mode 100644 index 00000000..84a5757a --- /dev/null +++ b/ModManager_Classes/Models/Mods/IModCollectionFactory.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Mods +{ + public interface IModCollectionFactory + { + ModCollection Get(string Filepath, + bool normalize = false, + bool loadImages = false, + bool autofixSubfolder = false); + } +} diff --git a/ModManager_Classes/Models/Mods/IModFactory.cs b/ModManager_Classes/Models/Mods/IModFactory.cs new file mode 100644 index 00000000..5c4887f7 --- /dev/null +++ b/ModManager_Classes/Models/Mods/IModFactory.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Mods +{ + public interface IModFactory + { + Mod? GetFromFolder(string modFolderPath); + } +} diff --git a/ModManager_Classes/Models/Mod.cs b/ModManager_Classes/Models/Mods/Mod.cs similarity index 75% rename from ModManager_Classes/Models/Mod.cs rename to ModManager_Classes/Models/Mods/Mod.cs index e6acaee7..b9c2c943 100644 --- a/ModManager_Classes/Models/Mod.cs +++ b/ModManager_Classes/Models/Mods/Mod.cs @@ -1,12 +1,15 @@ -using Imya.Models.ModMetadata; -using System.Collections.ObjectModel; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Text.RegularExpressions; using Imya.Utils; using Imya.Models.NotifyPropertyChanged; using Imya.Models.Attributes; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Attributes.Factories; +using Imya.Models.ModMetadata.ModinfoModel; +using Imya.Models.ModMetadata; -namespace Imya.Models +namespace Imya.Models.Mods { public class Mod : PropertyChangedNotifier { @@ -41,16 +44,23 @@ public bool IsActive /// public bool IsRemoved { - get => Attributes.HasAttribute(AttributeType.IssueModRemoved); + get => _isRemoved; + set => SetProperty(ref _isRemoved, value); + } + private bool _isRemoved; + + public bool IsObsolete + { + get => _isObsolete; set { - if (IsRemoved == value) + if (_isObsolete == value) return; - Attributes.Clear(); - Attributes.AddAttribute(RemovedFolderAttributeFactory.Get()); - OnPropertyChanged(nameof(IsRemoved)); + _isObsolete = value; + OnPropertyChanged(nameof(IsObsolete)); } } + private bool _isObsolete; /// /// Full path to mod folder. @@ -70,7 +80,7 @@ public bool IsRemoved /// public IText Category => Modinfo.Category; - public String ModID => Modinfo.ModID ?? FolderName; + public string ModID => Modinfo.ModID ?? FolderName; #endregion #region Optional Mod Manager info @@ -93,39 +103,20 @@ public bool IsRemoved public Attributes.AttributeCollection Attributes { get; } = new(); - #region loading - public static Mod? TryFromFolder(string modFolderPath) - { - var basePath = Path.GetDirectoryName(modFolderPath); - if (basePath is null || !Directory.Exists(modFolderPath)) return null; - - ModinfoLoader.TryLoadFromFile(Path.Combine(modFolderPath, "modinfo.json"), out var modinfo); - return new Mod(Path.GetFileName(modFolderPath), modinfo, basePath); - } - /// i.e. "[Gameplay] AI Shipyard" /// absolute path without folderName - public Mod (string folderName, Modinfo? modinfo, string basePath) + public Mod( + bool isActive, + string folderName, + LocalizedModinfo modinfo, + string basePath) { - IsActive = !folderName.StartsWith("-"); - FolderName = IsActive ? folderName : folderName[1..]; + IsActive = isActive; + FolderName = folderName; BasePath = basePath; + Modinfo = modinfo; - // create metadata if needed - if (modinfo is null) - { - modinfo = new(); - Attributes.AddAttribute(MissingModinfoAttributeFactory.Get()); - } - - if (modinfo.ModName is null || !modinfo.ModName.HasAny()) - { - bool matches = MatchNameCategory(FolderName, out var _category, out var _name); - modinfo.ModName = new FakeLocalized(matches ? _name : FolderName); - if (matches) modinfo.Category = new FakeLocalized(_category); - } - - Modinfo = modinfo.GetLocalized(FolderName); + SubMods = new List(); // Just construct as base64 for now. // TODO move to separate async function @@ -142,33 +133,13 @@ public Mod (string folderName, Modinfo? modinfo, string basePath) // TODO move to separate async? var info = new DirectoryInfo(FullModPath); SizeInMB = (float)Math.Round((decimal)info.EnumerateFiles("*", SearchOption.AllDirectories).Sum(x => x.Length) / 1024 / 1024, 1); - - string[] modinfos = Directory.GetFiles(Path.Combine(basePath, folderName), "modinfo.json", SearchOption.AllDirectories); - if (modinfos.Length > 1) - { - SubMods = new List(); - foreach (var submodinfo in modinfos) - { - if (submodinfo == Path.Combine(basePath, folderName, "modinfo.json")) - { - continue; - } - - Mod? submod = TryFromFolder(Path.GetDirectoryName(submodinfo) ?? ""); - if (submod is not null) - { - SubMods.Add(submod); - } - } - } } - public void InitImageAsFilepath(String ImagePath) + public void InitImageAsFilepath(string ImagePath) { Image = new ImyaImageSource(); Image.ConstructAsFilepathImage(ImagePath); } - #endregion #region modifying actions /// @@ -211,7 +182,7 @@ internal void AdaptToActiveStatus(bool active) { string sourcePath = Path.Combine(BasePath, FullFolderName); string targetPath = Path.Combine(BasePath, (active ? "" : "-") + FolderName); - + try { DirectoryEx.CleanMove(sourcePath, targetPath); @@ -259,16 +230,6 @@ public IEnumerable GetFilesWithExtension(string extension) return Directory.EnumerateFiles(FullModPath, $"*.{extension}", SearchOption.AllDirectories); } - private static bool MatchNameCategory(string folderName, out string category, out string name) - { - string CategoryPattern = @"[[][a-z]+[]]"; - category = Regex.Match(folderName, CategoryPattern, RegexOptions.IgnoreCase).Value.TrimStart('[').TrimEnd(']'); - - string NamePattern = @"[^]]*"; - name = Regex.Match(folderName, NamePattern, RegexOptions.RightToLeft).Value.TrimStart(' '); - - return !name.Equals("") && !category.Equals(""); - } #endregion #region comparisons @@ -330,11 +291,13 @@ public bool IsUpdateOf(Mod? target) #endregion #region Sub mods - public List? SubMods { get; private set; } + public List SubMods { get; private set; } + + public bool HasSubmods { get => SubMods.Count() > 0; } #endregion public ModStatus GetStatus() - { + { return (Attributes.GetByType(AttributeType.ModStatus) as ModStatusAttribute)?.Status ?? ModStatus.Default; } } diff --git a/ModManager_Classes/Models/ModCollection.cs b/ModManager_Classes/Models/Mods/ModCollection.cs similarity index 81% rename from ModManager_Classes/Models/ModCollection.cs rename to ModManager_Classes/Models/Mods/ModCollection.cs index 5f75d936..7eac2bb4 100644 --- a/ModManager_Classes/Models/ModCollection.cs +++ b/ModManager_Classes/Models/Mods/ModCollection.cs @@ -1,38 +1,19 @@ using Imya.Models.Attributes; +using Imya.Models.Attributes.Factories; +using Imya.Models.Attributes.Interfaces; using Imya.Models.NotifyPropertyChanged; +using Imya.Services; +using Imya.Services.Interfaces; using Imya.Utils; +using Imya.Validation; +using Microsoft.Extensions.DependencyInjection; using System.Collections; using System.Collections.Specialized; -namespace Imya.Models +namespace Imya.Models.Mods { public class ModCollection : PropertyChangedNotifier, IReadOnlyCollection, INotifyCollectionChanged { - public static readonly ModCollection Empty = new(""); - - #region global active collection - public static ModCollection? Global - { - get => _active; - set - { - if (_active == value) return; - if (_active is not null) - { - GameSetupManager.Instance.GameRootPathChanged -= _active.OnModPathChanged; - GameSetupManager.Instance.ModDirectoryNameChanged -= _active.OnModPathChanged; - } - if (value is not null) - { - GameSetupManager.Instance.GameRootPathChanged += value.OnModPathChanged; - GameSetupManager.Instance.ModDirectoryNameChanged += value.OnModPathChanged; - } - _active = value; - } - } - private static ModCollection? _active; - #endregion - #region UI related public int ActiveMods { @@ -62,45 +43,73 @@ public int InstalledSizeInMBs /// public event NotifyCollectionChangedEventHandler? CollectionChanged; - public string ModsPath { get; private set; } + public string ModsPath { get; set; } public IReadOnlyList Mods => _mods; private List _mods = new(); - public IEnumerable ModIDs { get => _modids; } - private List _modids = new(); + public IEnumerable ModIDs { get => _modids; } + private List _modids = new(); + + public bool Normalize { get; init; } + public bool LoadImages { get; init; } + public bool AutofixSubfolder { get; init; } + + private IGameSetupService _gameSetupService; + private IModFactory _modFactory; + private IModStatusAttributeFactory _modStatusAttributeFactory; + private IModAccessIssueAttributeFactory _modAccessIssueAttributeFactory; + private IRemovedFolderAttributeFactory _removedFolderAttributeFactory; - private readonly bool _normalize; - private readonly bool _loadImages; - private readonly bool _autofixSubfolder; + public ModCollectionHooks Hooks { get; } /// - /// Open mod collection from folder. + /// This constructor is internal. + /// To create a ModCollection, use /// /// Path to mods. /// Remove duplicate "-" /// Load image files into memory /// find data/ in subfolder and move up - public ModCollection(string path, bool normalize = false, bool loadImages = false, bool autofixSubfolder = false) + internal ModCollection( + IGameSetupService gameSetupService, + ModCollectionHooks hooks, + IModFactory modFactory, + IModStatusAttributeFactory modStatusAttributeFactory, + IModAccessIssueAttributeFactory modAccessIssueAttributeFactory, + IRemovedFolderAttributeFactory removedFolderAttributeFactory) { - ModsPath = path; - _normalize = normalize; - _loadImages = loadImages; - _autofixSubfolder = autofixSubfolder; + _gameSetupService = gameSetupService; + _modFactory = modFactory; + _modStatusAttributeFactory = modStatusAttributeFactory; + _modAccessIssueAttributeFactory = modAccessIssueAttributeFactory; + _removedFolderAttributeFactory = removedFolderAttributeFactory; + + ModsPath = ""; + Normalize = false; + LoadImages = false; + AutofixSubfolder = false; + + Hooks = hooks; + Hooks.HookTo(this); } /// /// Load all mods. + /// + /// TODO: All the loading should be done inside /// public async Task LoadModsAsync() { if (Directory.Exists(ModsPath)) { - if (_autofixSubfolder) + if (AutofixSubfolder) AutofixSubfolders(ModsPath); _mods = await LoadModsAsync(Directory.EnumerateDirectories(ModsPath) .Where(x => !Path.GetFileName(x).StartsWith("."))); + + int i = 0; } else { @@ -151,13 +160,13 @@ private static void AutofixSubfolders(string modsPath) private async Task> LoadModsAsync(IEnumerable folders, bool invokeEvents = true) { - var mods = folders.SelectNoNull(x => Mod.TryFromFolder(x)).ToList(); - if (_normalize) + var mods = folders.SelectNoNull(x => _modFactory.GetFromFolder(x)).ToList(); + if (Normalize) { foreach (var mod in mods) await mod.NormalizeAsync(); } - if (_loadImages) + if (LoadImages) { foreach (var mod in mods) { @@ -177,14 +186,14 @@ private async Task> LoadModsAsync(IEnumerable folders, bool in { CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, mods)); } - + return mods; } #region MemberFunctions - private async void OnModPathChanged(string _) + public async void OnModPathChanged(string _) { - ModsPath = GameSetupManager.Instance.GetModDirectory(); + ModsPath = _gameSetupService.GetModDirectory(); await LoadModsAsync(); } @@ -193,7 +202,7 @@ private async void OnModPathChanged(string _) private void OnActivationChanged(Mod? sender) { // remove mods with IssueModRemoved attribute - int removedModCount = _mods.Count(x => x.Attributes.HasAttribute(AttributeType.IssueModRemoved)); + int removedModCount = _mods.Count(x => x.IsRemoved); int newActiveCount = _mods.Count(x => x.IsActive); if (removedModCount > 0 || ActiveMods != newActiveCount) @@ -225,7 +234,7 @@ public async Task MoveIntoAsync(ModCollection source, bool allowOldToOverwrite = { foreach (var sourceMod in source.Mods) { - await Task.Run( + await Task.Run( async () => await MoveSingleModIntoAsync(sourceMod, source.ModsPath, allowOldToOverwrite), ct ); @@ -255,7 +264,7 @@ private async Task MoveSingleModIntoAsync(Mod sourceMod, string sourceModsPath, // do it! var status = Directory.Exists(targetModPath) ? ModStatus.Updated : ModStatus.New; - sourceMod.Attributes.AddAttribute(ModStatusAttributeFactory.Get(status)); + sourceMod.Attributes.AddAttribute(_modStatusAttributeFactory.Get(status)); DirectoryEx.CleanMove(Path.Combine(sourceModsPath, sourceMod.FullFolderName), targetModPath); Console.WriteLine($"{sourceMod.Attributes.GetByType(AttributeType.ModStatus)}: {sourceMod.FolderName}"); @@ -285,7 +294,7 @@ private async Task MoveSingleModIntoAsync(Mod sourceMod, string sourceModsPath, _mods.Remove(targetMod); } var reparsed = (await LoadModsAsync(new string[] { targetModPath }, false)).First(); - reparsed.Attributes.AddAttribute(ModStatusAttributeFactory.Get(status)); + reparsed.Attributes.AddAttribute(_modStatusAttributeFactory.Get(status)); _mods.Add(reparsed); } @@ -316,17 +325,23 @@ public async Task ChangeActivationAsync(IEnumerable mods, bool activation_s { throw new InvalidOperationException("Collection cannot change mods that are not in it."); } - var tasks = mods.Select(x => Task.Run(async () => await ChangeActivationAsync(x, activation_status))).ToList(); + var tasks = mods.Select(x => Task.Run(async () => await ChangeActivationNoEventsAsync(x, activation_status))).ToList(); await Task.WhenAll(tasks); OnActivationChanged(null); } public async Task ChangeActivationAsync(Mod mod, bool active) + { + await ChangeActivationNoEventsAsync(mod, active); + OnActivationChanged(mod); + } + + private async Task ChangeActivationNoEventsAsync(Mod mod, bool active) { if (mod.IsActive == active || mod.IsRemoved) return; - + var verb = active ? "activate" : "deactivate"; await Task.Run(() => @@ -339,8 +354,7 @@ await Task.Run(() => catch (InvalidOperationException e) { Console.WriteLine(e.Message); - if (!mod.IsRemoved) - mod.Attributes.AddAttribute(ModAccessIssueAttributeFactory.Get()); + mod.IsRemoved = true; } }); } @@ -385,7 +399,7 @@ await Task.Run(() => } catch (Exception e) { - mod.Attributes.Add(ModAccessIssueAttributeFactory.GetNoDelete()); + mod.Attributes.AddAttribute(_modAccessIssueAttributeFactory.GetNoDelete()); Console.WriteLine($"Failed to delete Mod: {mod.Category} {mod.Name}. Cause: {e.Message}"); } }); @@ -394,8 +408,8 @@ await Task.Run(() => public async Task MakeObsoleteAsync(Mod mod, string path) { - await ChangeActivationAsync(mod, false); - mod.Attributes.AddAttribute(ModStatusAttributeFactory.Get(ModStatus.Obsolete)); + await ChangeActivationNoEventsAsync(mod, false); + mod.Attributes.AddAttribute(_modStatusAttributeFactory.Get(ModStatus.Obsolete)); Console.WriteLine($"{ModStatus.Obsolete}: {mod.FolderName}"); } @@ -428,7 +442,7 @@ public async Task LoadProfileAsync(ModActivationProfile profile) { bool active = activationSet.Contains(mod.FolderName); if (active != mod.IsActive) - await ChangeActivationAsync(mod, active); + await ChangeActivationNoEventsAsync(mod, active); } CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); diff --git a/ModManager_Classes/Models/Mods/ModCollectionFactory.cs b/ModManager_Classes/Models/Mods/ModCollectionFactory.cs new file mode 100644 index 00000000..9cafe75f --- /dev/null +++ b/ModManager_Classes/Models/Mods/ModCollectionFactory.cs @@ -0,0 +1,44 @@ +using System; +using System.IO.Compression; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Installation; +using Imya.Services.Interfaces; +using Imya.Validation; +using Microsoft.Extensions.DependencyInjection; + +namespace Imya.Models.Mods +{ + /// + /// Install mods from zip file - might depracate this honestly + /// + public class ModCollectionFactory : IModCollectionFactory + { + private readonly IServiceProvider _serviceProvider; + public ModCollectionFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public ModCollection Get( + string Filepath, + bool normalize = false, + bool loadImages = false, + bool autofixSubfolder = false) + { + var collection = new ModCollection( + _serviceProvider.GetRequiredService(), + _serviceProvider.GetRequiredService(), + _serviceProvider.GetRequiredService(), + _serviceProvider.GetRequiredService(), + _serviceProvider.GetRequiredService(), + _serviceProvider.GetRequiredService()) + { + ModsPath = Filepath, + Normalize = normalize, + LoadImages = loadImages, + AutofixSubfolder = autofixSubfolder + }; + return collection; + } + } +} diff --git a/ModManager_Classes/Models/Mods/ModFactory.cs b/ModManager_Classes/Models/Mods/ModFactory.cs new file mode 100644 index 00000000..31920560 --- /dev/null +++ b/ModManager_Classes/Models/Mods/ModFactory.cs @@ -0,0 +1,73 @@ +using Imya.Models.Attributes; +using Imya.Models.Attributes.Factories; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.ModMetadata; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Models.Mods +{ + public class ModFactory : IModFactory + { + private readonly IMissingModinfoAttributeFactory _missingModinfoAttributeFactory; + private readonly LocalizedModinfoFactory _localizedModinfoFactory; + + public ModFactory( + IMissingModinfoAttributeFactory missingModinfoAttributeFactory, + LocalizedModinfoFactory localizedModinfoFactory) + { + _missingModinfoAttributeFactory = missingModinfoAttributeFactory; + _localizedModinfoFactory = localizedModinfoFactory; + } + + public Mod? GetFromFolder(string modFolderPath) + { + var basePath = Path.GetDirectoryName(modFolderPath); + if (basePath is null || !Directory.Exists(modFolderPath)) + return null; + + var folder = Path.GetFileName(modFolderPath); + var isActive = !folder.StartsWith("-"); + var folderName = isActive ? folder : folder[1..]; + + bool hasModinfo = ModinfoLoader.TryLoadFromFile(Path.Combine(modFolderPath, "modinfo.json"), out var modinfo); + + var localizedModinfo = hasModinfo ? + _localizedModinfoFactory.GetLocalizedModinfo(modinfo!) + : _localizedModinfoFactory.GetDummyModinfo(folderName); + + var mod = new Mod( + isActive, + folderName, + localizedModinfo, + basePath); + + if (!hasModinfo) + mod.Attributes.AddAttribute(_missingModinfoAttributeFactory.Get()); + + string[] modinfos = Directory.GetFiles(Path.Combine(basePath, folder), "modinfo.json", SearchOption.AllDirectories); + if (modinfos.Length > 1) + { + foreach (var submodinfo in modinfos) + { + if (submodinfo == Path.Combine(basePath, folder, "modinfo.json")) + { + continue; + } + + Mod? submod = GetFromFolder(Path.GetDirectoryName(submodinfo) ?? ""); + if (submod is not null) + { + mod.SubMods.Add(submod); + } + } + } + + return mod; + } + } +} diff --git a/ModManager_Classes/Models/Options/Options.cs b/ModManager_Classes/Models/Options/Options.cs index 57b2a1d9..d002870e 100644 --- a/ModManager_Classes/Models/Options/Options.cs +++ b/ModManager_Classes/Models/Options/Options.cs @@ -1,20 +1,43 @@ using Imya.Models.NotifyPropertyChanged; -using Imya.Utils; +using Imya.Services; +using Imya.Services.Interfaces; using System.ComponentModel; namespace Imya.Models.Options { - public class ModloaderInstallationOptions + public interface IModloaderInstallationOptions + { + String UnpackDirectory { get; set; } + } + + public class ModloaderInstallationOptions : IModloaderInstallationOptions + { + public String UnpackDirectory { get; set; } + public ModloaderInstallationOptions(IImyaSetupService imyaSetupService) { + UnpackDirectory = imyaSetupService.UnpackDirectoryPath; + } + } + + public interface IGithubDownloaderOptions { - public String UnpackDirectory { get; set; } = ImyaSetupManager.Instance.UnpackDirectoryPath; + String DownloadDirectory { get; set; } } - public class GithubDownloaderOptions + public class GithubDownloaderOptions : IGithubDownloaderOptions { - public String DownloadDirectory { get; set; } = ImyaSetupManager.Instance.DownloadDirectoryPath; + public String DownloadDirectory { get; set; } + public GithubDownloaderOptions(IImyaSetupService imyaSetupService) + { + DownloadDirectory = imyaSetupService.DownloadDirectoryPath; + } + } + + public interface IModInstallationOptions + { + String UnpackDirectory { get; set; } } - public class ModInstallationOptions : PropertyChangedNotifier + public class ModInstallationOptions : PropertyChangedNotifier, IModInstallationOptions { public bool AllowOldToOverwrite { get => _allowOldToOverwrite; @@ -25,7 +48,12 @@ public bool AllowOldToOverwrite { } } private bool _allowOldToOverwrite = false; - public String UnpackDirectory { get; set; } = ImyaSetupManager.Instance.UnpackDirectoryPath; + public String UnpackDirectory { get; set; } + + public ModInstallationOptions(IImyaSetupService imyaSetupService) + { + UnpackDirectory = imyaSetupService.UnpackDirectoryPath; + } } public class ModCollectionOptions diff --git a/ModManager_Classes/Utils/GameSetupManager.cs b/ModManager_Classes/Services/GameSetupService.cs similarity index 79% rename from ModManager_Classes/Utils/GameSetupManager.cs rename to ModManager_Classes/Services/GameSetupService.cs index 58765f0f..ea7072e4 100644 --- a/ModManager_Classes/Utils/GameSetupManager.cs +++ b/ModManager_Classes/Services/GameSetupService.cs @@ -1,13 +1,13 @@ using Imya.Models; using Imya.Models.GameLauncher; using Imya.Models.NotifyPropertyChanged; +using Imya.Services.Interfaces; +using Imya.Utils; using System; using System.Diagnostics; - using System.Timers; - -namespace Imya.Utils +namespace Imya.Services { /// /// Validates and setups the basics of a modded game installation. @@ -17,33 +17,23 @@ namespace Imya.Utils /// - /// /// - /// - - public enum GamePlatform { Steam, Agnostic } - public enum ModloaderInstallationState { Installed, Uninstalled, Deactivated } - - public class GameSetupManager : PropertyChangedNotifier + public class GameSetupService : PropertyChangedNotifier, IGameSetupService { - public static GameSetupManager Instance { get; } = new GameSetupManager(); - - private InstallationValidator Validator; - private GameScanner Scanner; + private InstallationValidator _validator; + private GameScanner _scanner; #region EVENTS - public delegate void GameRootPathChangedEventHandler(String newPath); - public event GameRootPathChangedEventHandler GameRootPathChanged = delegate { }; - - public delegate void ModDirectoryNameChangedEventHandler(String newName); - public event ModDirectoryNameChangedEventHandler ModDirectoryNameChanged = delegate { }; + public event IGameSetupService.GameRootPathChangedEventHandler GameRootPathChanged = delegate { }; + public event IGameSetupService.ModDirectoryNameChangedEventHandler ModDirectoryNameChanged = delegate { }; #endregion // File System Watchers - #pragma warning disable IDE0052 // Never used, but we want to keep them until GameSetupManager dies +#pragma warning disable IDE0052 // Never used, but we want to keep them until GameSetupManager dies private FileSystemWatcher? ModDirectoryWatcher; // private FileSystemWatcher ModLoaderWatcher; // This is TODO - #pragma warning restore IDE0052 +#pragma warning restore IDE0052 public bool IsGameRunning { @@ -56,19 +46,22 @@ public bool IsGameRunning } private bool _isGameRunning; - public String GameRootPath { get => _gameRootPath; + public string GameRootPath + { + get => _gameRootPath; private set { _gameRootPath = value; OnPropertyChanged(nameof(GameRootPath)); } } - private String _gameRootPath; + private string _gameRootPath; - public String? ExecutablePath { get; private protected set; } - public String? ExecutableDir { get; private protected set; } + public string? ExecutablePath { get; private protected set; } + public string? ExecutableDir { get; private protected set; } - public String ModDirectoryName { + public string ModDirectoryName + { get => _modDirectoryName; private set { @@ -76,7 +69,7 @@ private set OnPropertyChanged(nameof(ModDirectoryName)); } } - private String _modDirectoryName; + private string _modDirectoryName; public bool IsValidSetup { @@ -87,7 +80,8 @@ public bool IsValidSetup public GamePlatform GamePlatform { - get { + get + { if (ExecutableDir is null) return GamePlatform.Agnostic; @@ -98,7 +92,7 @@ public GamePlatform GamePlatform public bool IsModloaderInstalled => ModloaderState == ModloaderInstallationState.Installed; - public ModloaderInstallationState ModloaderState + public ModloaderInstallationState ModloaderState { get => _modloaderState; set @@ -109,31 +103,31 @@ public ModloaderInstallationState ModloaderState } private ModloaderInstallationState _modloaderState = ModloaderInstallationState.Uninstalled; - public GameSetupManager() + public GameSetupService() { - Scanner = new GameScanner(); + _scanner = new GameScanner(); } #region DIRECTORY_RELATED - public String GetModDirectory() => Path.Combine(GameRootPath, ModDirectoryName); + public string GetModDirectory() => Path.Combine(GameRootPath, ModDirectoryName); /// /// Set download directory. Relative to executable. /// - public void SetGamePath(String gamePath, bool autoSearchIfInvalid = false) + public void SetGamePath(string gamePath, bool autoSearchIfInvalid = false) { - String executablePath = Path.Combine(gamePath, "Bin", "Win64", "Anno1800.exe"); + string executablePath = Path.Combine(gamePath, "Bin", "Win64", "Anno1800.exe"); if (!File.Exists(executablePath) && autoSearchIfInvalid) { - var foundGamePath = Scanner.GetInstallDirFromRegistry() ?? ""; + var foundGamePath = _scanner.GetInstallDirFromRegistry() ?? ""; executablePath = Path.Combine(foundGamePath, "Bin", "Win64", "Anno1800.exe"); if (File.Exists(executablePath)) gamePath = foundGamePath; // only replace if found, otherwise keep "wrong" path in the settings. } GameRootPath = gamePath; - Validator = new InstallationValidator(GameRootPath); + _validator = new InstallationValidator(GameRootPath); if (File.Exists(executablePath)) { @@ -147,13 +141,13 @@ public void SetGamePath(String gamePath, bool autoSearchIfInvalid = false) ExecutableDir = null; IsValidSetup = false; } - + GameRootPathChanged(gamePath); CreateWatchers(); } - public void SetModDirectoryName(String ModDirectoryName) - { + public void SetModDirectoryName(string ModDirectoryName) + { this.ModDirectoryName = ModDirectoryName; ModDirectoryNameChanged(ModDirectoryName); @@ -165,7 +159,7 @@ public void SetModDirectoryName(String ModDirectoryName) private void CreateWatchers() => ModDirectoryWatcher = CreateWatcher(Path.Combine(GameRootPath)); [Obsolete] - public void UpdateModloaderInstallStatus() => ModloaderState = Validator.CheckModloaderInstallState(); + public void UpdateModloaderInstallStatus() => ModloaderState = _validator.CheckModloaderInstallState(); [Obsolete] public void EnsureModloaderActivation(bool desired) { @@ -173,10 +167,10 @@ public void EnsureModloaderActivation(bool desired) if (desired && ModloaderState == ModloaderInstallationState.Deactivated) { - ActivateModloader(); + ActivateModloader(); return; } - if (!desired && ModloaderState == ModloaderInstallationState.Installed) + if (!desired && ModloaderState == ModloaderInstallationState.Installed) DeactivateModloader(); } @@ -220,7 +214,7 @@ private void DeactivateModloader() { Console.WriteLine("Modloader Deactivation unsuccessful"); return; - } + } } /// @@ -258,7 +252,7 @@ public void RemoveModloader() // There are many other things that can go wrong besides a non-existant path, but let's take this for now return null; } - + return new FileSystemWatcher { Path = pathToWatch, diff --git a/ModManager_Classes/Services/ImyaSetupService.cs b/ModManager_Classes/Services/ImyaSetupService.cs new file mode 100644 index 00000000..f863283e --- /dev/null +++ b/ModManager_Classes/Services/ImyaSetupService.cs @@ -0,0 +1,58 @@ +using Imya.Models.Mods; +using Imya.Models.NotifyPropertyChanged; +using Imya.Services.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Services +{ + public class ImyaSetupService : PropertyChangedNotifier, IImyaSetupService + { + private static string WorkingDirectory = ".imya"; + private static string ProfilesDirectory = "profiles"; + private static string DownloadDirectory = "download"; + private static string TweakDirectory = "tweaks"; + private static string UnpackDirectory = ".unpack"; + + public IGameSetupService _gameSetup; + + public string WorkingDirectoryPath { get => Path.Combine(_gameSetup.GameRootPath, WorkingDirectory); } + public string ProfilesDirectoryPath { get => Path.Combine(_gameSetup.GameRootPath, WorkingDirectory, ProfilesDirectory); } + public string DownloadDirectoryPath { get => Path.Combine(_gameSetup.GameRootPath, WorkingDirectory, DownloadDirectory); } + //the fallback currently stores the tweaks elsewhere. I have no idea why the gamesetup isn't injected properly here. + public string TweakDirectoryPath { get => Path.Combine(_gameSetup.GameRootPath, WorkingDirectory, TweakDirectory); } + public string UnpackDirectoryPath { get => Path.Combine(_gameSetup.GameRootPath, WorkingDirectory, UnpackDirectory); } + + public ModCollection GlobalModCollection { get; set; } + + public ImyaSetupService(IGameSetupService gameSetup) + { + _gameSetup = gameSetup; + if (Directory.Exists(_gameSetup.GameRootPath)) + { + Init(); + } + _gameSetup.GameRootPathChanged += OnGameRootPathChanged; + } + + private void OnGameRootPathChanged(string GameRootPath) + { + if (Directory.Exists(GameRootPath)) + { + Init(); + } + } + + public void Init() + { + Directory.CreateDirectory(WorkingDirectoryPath); + Directory.CreateDirectory(ProfilesDirectoryPath); + Directory.CreateDirectory(DownloadDirectoryPath); + Directory.CreateDirectory(TweakDirectoryPath); + Directory.CreateDirectory(UnpackDirectoryPath); + } + } +} diff --git a/ModManager_Classes/Utils/InstallationManager.cs b/ModManager_Classes/Services/InstallationService.cs similarity index 84% rename from ModManager_Classes/Utils/InstallationManager.cs rename to ModManager_Classes/Services/InstallationService.cs index 03c3a6d2..70b984c1 100644 --- a/ModManager_Classes/Utils/InstallationManager.cs +++ b/ModManager_Classes/Services/InstallationService.cs @@ -1,17 +1,18 @@ -using Imya.Models; -using Imya.Models.Collections; +using Imya.Models.Collections; using Imya.Models.Installation; using Downloader; using Imya.Models.NotifyPropertyChanged; using System.Collections.ObjectModel; +using Imya.Services.Interfaces; +using Imya.Utils; +using Imya.Models.Installation.Interfaces; +using Imya.Models.Mods; -namespace Imya.Utils +namespace Imya.Services { - public class InstallationManager : PropertyChangedNotifier + public class InstallationService : PropertyChangedNotifier, IInstallationService { - public static InstallationManager Instance { get; private set; } = new InstallationManager(); - public List Unpacks { get; private set; } //we want a special queue that we can manipulate later on. @@ -19,11 +20,12 @@ public class InstallationManager : PropertyChangedNotifier public IDownloadableInstallation? CurrentDownload { get; private set; } public ObservableCollection ActiveInstallations { get; private set; } - public List CurrentGithubInstallsIDs { get; private set; } + public List CurrentGithubInstallsIDs { get; private set; } public DownloadConfiguration DownloadConfig { get; private set; } - public DownloadService DownloadService { + public DownloadService DownloadService + { get => _downloadService; set => SetProperty(ref _downloadService, value); } @@ -32,46 +34,44 @@ public DownloadService DownloadService { private Semaphore _moveIntoSem; private Semaphore _downloadSem; + private delegate void GithubInstallAddedEventHandler(GithubInstallation githubInstallation); private event GithubInstallAddedEventHandler GithubInstallAdded = delegate { }; - private event ZipInstallAddedEventHandler ZipInstallAdded = delegate { }; - public event InstallationCompletedEventHandler InstallationCompleted = delegate { }; - private delegate void GithubInstallAddedEventHandler(GithubInstallation githubInstallation); private delegate void ZipInstallAddedEventHandler(ZipInstallation zipInstallation); - public delegate void InstallationCompletedEventHandler(); + private event ZipInstallAddedEventHandler ZipInstallAdded = delegate { }; - public event InstallFailedEventHandler InstallFailedWithException = delegate { }; - public delegate void InstallFailedEventHandler(Exception exception_context); + public event IInstallationService.InstallationCompletedEventHandler InstallationCompleted = delegate { }; + public event IInstallationService.InstallFailedEventHandler InstallFailedWithException = delegate { }; - public double BytesPerSecondSpeed + public double BytesPerSecondSpeed { get => _bytesPerSecondSpeed; - set => SetProperty(ref _bytesPerSecondSpeed, value); + private set => SetProperty(ref _bytesPerSecondSpeed, value); } private double _bytesPerSecondSpeed; public double ProgressPercentage { get => _progressPercentage; - set => SetProperty(ref _progressPercentage, value); + private set => SetProperty(ref _progressPercentage, value); } private double _progressPercentage; public bool IsInstalling { get => _isInstalling; - set => SetProperty(ref _isInstalling, value); + private set => SetProperty(ref _isInstalling, value); } private bool _isInstalling = false; public int TotalInstallationCount { - get =>_totalInstallationCount; - set + get => _totalInstallationCount; + private set { IsInstalling = value > 0; SetProperty(ref _totalInstallationCount, value); - } + } } private int _totalInstallationCount; @@ -102,8 +102,15 @@ public double CurrentDownloadSpeedPerSecond private float max_progress = 1; #endregion - public InstallationManager() + private readonly IModCollectionFactory _modCollectionFactory; + private readonly IImyaSetupService _imyaSetupService; + + public InstallationService( + IModCollectionFactory factory, + IImyaSetupService imyaSetupService) { + _modCollectionFactory = factory; + _imyaSetupService = imyaSetupService; _moveIntoSem = new Semaphore(1, 1); _downloadSem = new Semaphore(1, 1); @@ -116,7 +123,7 @@ public InstallationManager() //TODO add options DownloadService = new(DownloadConfig); - DownloadService.DownloadProgressChanged += OnDownloadProgressChanged; + DownloadService.DownloadProgressChanged += OnDownloadProgressChanged; //when an install gets added, we invoke process with next download, semaphore does the rest for us. GithubInstallAdded += async (x) => await ProceedWithNextDownloadAsync(); @@ -146,18 +153,18 @@ public void EnqueueZipInstallation(ZipInstallation zipInstallation) ZipInstallAdded?.Invoke(zipInstallation); } - public bool IsProcessingInstallWithID(String id) + public bool IsProcessingInstallWithID(string id) { return CurrentGithubInstallsIDs.Any(x => x == id); } public void Pause() { - if (CurrentDownload is null) + if (CurrentDownload is null) return; DownloadService.Pause(); CurrentDownload.IsPaused = true; - BytesPerSecondSpeed = 0; + BytesPerSecondSpeed = 0; } public void Resume() @@ -243,7 +250,7 @@ private async Task ProceedWithNextDownloadAsync() private async Task UnpackAsync(IUnpackableInstallation zipInstallation) { if (zipInstallation.CancellationToken.IsCancellationRequested) - return; + return; zipInstallation.Status = InstallationStatus.Unpacking; await Task.Run(() => @@ -252,8 +259,8 @@ await Task.Run(() => if (Directory.Exists(zipInstallation.UnpackTargetPath)) Directory.Delete(zipInstallation.UnpackTargetPath, true); using (FileStream fs = File.OpenRead(zipInstallation.SourceFilepath)) - fs.ExtractZipFile(zipInstallation.UnpackTargetPath, overwrite: true, progress : zipInstallation); - }, zipInstallation.CancellationToken); + fs.ExtractZipFile(zipInstallation.UnpackTargetPath, overwrite: true, progress: zipInstallation); + }, zipInstallation.CancellationToken); } private async Task MoveModsAsync(IUnpackableInstallation unpackable) @@ -261,14 +268,13 @@ private async Task MoveModsAsync(IUnpackableInstallation unpackable) if (unpackable.CancellationToken.IsCancellationRequested) return; unpackable.Status = InstallationStatus.MovingFiles; - - var newCollection = new ModCollection(unpackable.UnpackTargetPath, autofixSubfolder: true); + var newCollection = _modCollectionFactory.Get(unpackable.UnpackTargetPath, autofixSubfolder : true); await newCollection.LoadModsAsync(); //async waiting await Task.Run(() => _moveIntoSem.WaitOne(), unpackable.CancellationToken); if (!unpackable.CancellationToken.IsCancellationRequested) { - await ModCollection.Global!.MoveIntoAsync(newCollection); + await _imyaSetupService.GlobalModCollection.MoveIntoAsync(newCollection); } _moveIntoSem.Release(); Unpacks.Remove(unpackable); @@ -277,13 +283,13 @@ private async Task MoveModsAsync(IUnpackableInstallation unpackable) private async Task DownloadAsync(IDownloadableInstallation downloadable) { if (downloadable.CancellationToken.IsCancellationRequested) - return; + return; CurrentDownload = downloadable; //register progress tracking and update status downloadable.Status = InstallationStatus.Downloading; downloadable.SetProgressRange(min_progress, max_dl_progress); - EventHandler eventHandler = (object? sender, DownloadProgressChangedEventArgs e) => + EventHandler eventHandler = (sender, e) => { downloadable.Report((float)e.ProgressPercentage / 100); }; @@ -297,7 +303,7 @@ private async Task DownloadAsync(IDownloadableInstallation downloadable) private void CleanUpUnpackable(IUnpackable unpackable) { - if(Directory.Exists(unpackable.UnpackTargetPath)) + if (Directory.Exists(unpackable.UnpackTargetPath)) Directory.Delete(unpackable.UnpackTargetPath); } @@ -310,7 +316,7 @@ private void CleanUpDownloadable(IDownloadable downloadable) private void OnDownloadProgressChanged(object? sender, DownloadProgressChangedEventArgs e) { if (DownloadService.IsPaused) - return; + return; BytesPerSecondSpeed = e.BytesPerSecondSpeed; ProgressPercentage = e.ProgressPercentage; } diff --git a/ModManager_Classes/Services/Interfaces/IGameSetupService.cs b/ModManager_Classes/Services/Interfaces/IGameSetupService.cs new file mode 100644 index 00000000..53e4d0d4 --- /dev/null +++ b/ModManager_Classes/Services/Interfaces/IGameSetupService.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Services.Interfaces +{ + /// + /// Validates and setups the basics of a modded game installation. + /// + /// - Mods folder + /// - Modloader + /// - + /// + + public enum GamePlatform { Steam, Agnostic } + + public enum ModloaderInstallationState { Installed, Uninstalled, Deactivated } + + public interface IGameSetupService + { + public delegate void GameRootPathChangedEventHandler(string newPath); + public event GameRootPathChangedEventHandler GameRootPathChanged; + + public delegate void ModDirectoryNameChangedEventHandler(string newName); + public event ModDirectoryNameChangedEventHandler ModDirectoryNameChanged; + + bool IsGameRunning { get; set; } + bool IsModloaderInstalled { get; } + bool IsValidSetup { get; } + string GameRootPath { get; } + string? ExecutablePath { get; } + string ModDirectoryName { get; } + + GamePlatform GamePlatform { get; } + ModloaderInstallationState ModloaderState { get; } + + string GetModDirectory(); + void SetGamePath(string gamePath, bool autoSearchifInvalid = false); + void SetModDirectoryName(string modDirectoryName); + void DeleteCache(); + + bool NeedsModloaderRemoval(); + void RemoveModloader(); + } +} diff --git a/ModManager_Classes/Services/Interfaces/IImyaSetupService.cs b/ModManager_Classes/Services/Interfaces/IImyaSetupService.cs new file mode 100644 index 00000000..ab840335 --- /dev/null +++ b/ModManager_Classes/Services/Interfaces/IImyaSetupService.cs @@ -0,0 +1,22 @@ +using Imya.Models.Mods; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Services.Interfaces +{ + public interface IImyaSetupService + { + public String WorkingDirectoryPath { get; } + public String ProfilesDirectoryPath { get; } + public String DownloadDirectoryPath { get; } + public String TweakDirectoryPath { get; } + public String UnpackDirectoryPath { get; } + + public ModCollection GlobalModCollection { get; set; } + + void Init(); + } +} diff --git a/ModManager_Classes/Services/Interfaces/IInstallationService.cs b/ModManager_Classes/Services/Interfaces/IInstallationService.cs new file mode 100644 index 00000000..71f140a9 --- /dev/null +++ b/ModManager_Classes/Services/Interfaces/IInstallationService.cs @@ -0,0 +1,48 @@ +using Downloader; +using Imya.Models.Collections; +using Imya.Models.Installation; +using Imya.Models.Installation.Interfaces; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Services.Interfaces +{ + public interface IInstallationService + { + public delegate void InstallationCompletedEventHandler(); + public event InstallationCompletedEventHandler InstallationCompleted; + + public delegate void InstallFailedEventHandler(Exception exception_context); + public event InstallFailedEventHandler InstallFailedWithException; + + public List Unpacks { get; } + public IQueue PendingDownloads { get; } + public IDownloadableInstallation? CurrentDownload { get; } + + public ObservableCollection ActiveInstallations { get; } + public List CurrentGithubInstallsIDs { get; } + public DownloadConfiguration DownloadConfig { get; } + public DownloadService DownloadService { get; set; } + + public double BytesPerSecondSpeed { get; } + public double ProgressPercentage { get; } + public bool IsInstalling { get; } + public int TotalInstallationCount { get; } + public int PendingInstallationsCount { get; } + public int RunningInstallationsCount { get; } + + public double CurrentDownloadSpeedPerSecond { get; } + + void EnqueueGithubInstallation(GithubInstallation githubInstallation); + void EnqueueZipInstallation(ZipInstallation zipInstallation); + bool IsProcessingInstallWithID(String id); + void Pause(); + void Resume(); + Task CancelAsync(IInstallation installation); + void RemovePending(IDownloadableUnpackableInstallation installation); + } +} diff --git a/ModManager_Classes/Services/Interfaces/IProfilesService.cs b/ModManager_Classes/Services/Interfaces/IProfilesService.cs new file mode 100644 index 00000000..9fe766ba --- /dev/null +++ b/ModManager_Classes/Services/Interfaces/IProfilesService.cs @@ -0,0 +1,20 @@ +using Imya.Models; +using Imya.Models.Mods; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Services.Interfaces +{ + public interface IProfilesService + { + IEnumerable GetSavedKeys(); + ModActivationProfile CreateFromModCollection(ModCollection collection); + ModActivationProfile? LoadProfile(String key); + void DeleteActivationProfile(String key); + void SaveProfile(ModActivationProfile profile, String key); + bool ProfileExists(String key); + } +} diff --git a/ModManager_Classes/Services/Interfaces/ITweakService.cs b/ModManager_Classes/Services/Interfaces/ITweakService.cs new file mode 100644 index 00000000..b14d6fa4 --- /dev/null +++ b/ModManager_Classes/Services/Interfaces/ITweakService.cs @@ -0,0 +1,23 @@ +using Imya.Models.Mods; +using Imya.Models.ModTweaker.DataModel.Tweaking; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Services.Interfaces +{ + public interface ITweakService + { + ModTweaks? Tweaks { get; } + bool HasUnsavedChanges { get; set; } + bool IsLoading { get; } + bool IsSaving { get; } + + void Save(); + Task SaveAsync(); + void Unload(); + void Load(Mod mod, bool ClearCurrentWhileLoading = true); + } +} diff --git a/ModManager_Classes/Services/ProfilesService.cs b/ModManager_Classes/Services/ProfilesService.cs new file mode 100644 index 00000000..832f5ac2 --- /dev/null +++ b/ModManager_Classes/Services/ProfilesService.cs @@ -0,0 +1,105 @@ +using Imya.Models; +using Imya.Models.Attributes.Factories; +using Imya.Models.Mods; +using Imya.Services.Interfaces; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Services +{ + public class ProfilesService : IProfilesService + { + private static readonly string _profileFileExtension = ".imyaprofile"; + + private readonly IImyaSetupService _imyaSetupService; + + public ProfilesService(IImyaSetupService imyaSetupService) + { + _imyaSetupService = imyaSetupService; + } + + private IEnumerable GetSavedProfiles() + { + var files = Directory.EnumerateFiles(_imyaSetupService.ProfilesDirectoryPath, "*" + _profileFileExtension); + return files?.Select(file => LoadProfile(file))?.Where(x => x is not null)?.ToArray() ?? Enumerable.Empty(); + } + + public IEnumerable GetSavedKeys() + { + var filepaths = Directory.EnumerateFiles(_imyaSetupService.ProfilesDirectoryPath, "*" + _profileFileExtension); + return filepaths.Select(x => Path.GetFileNameWithoutExtension(x)).ToArray(); + } + + public ModActivationProfile CreateFromModCollection(ModCollection collection) + { + var foldernames = collection.Mods + .Where(x => x.IsActive) + .Select(x => x.FolderName) + .ToList(); + return new ModActivationProfile(foldernames); + } + + public void DeleteActivationProfile(String key) + { + var filepath = GetFilepath(key); + if(File.Exists(filepath)) + File.Delete(GetFilepath(key)); + } + + public void SaveProfile(ModActivationProfile profile, String key) + { + string filepath = GetFilepath(key); + try + { + using StreamWriter writer = new(File.Create(filepath)); + foreach (string dir_name in profile.ModFolderNames) + { + writer.WriteLine(dir_name); + writer.Flush(); + } + } + catch (IOException e) + { + Console.WriteLine($"Could not create File: {filepath}"); + return; + } + } + + public bool ProfileExists(String key) + { + return File.Exists(GetFilepath(key)); + } + + public ModActivationProfile? LoadProfile(String key) + { + List parsedNames = new(); + var filename = GetFilepath(key); + + try + { + using StreamReader reader = new(File.OpenRead(filename)); + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + + if (line is not null) + parsedNames.Add(line); + } + return new ModActivationProfile(parsedNames) + { + Title = key + }; + } + catch (IOException) + { + Console.WriteLine($"Could not access File: {filename}"); + return null; + } + } + + private String GetFilepath(String key) => Path.Combine(_imyaSetupService.ProfilesDirectoryPath, key + _profileFileExtension); + } +} diff --git a/ModManager_Classes/Utils/TweakManager.cs b/ModManager_Classes/Services/TweakService.cs similarity index 62% rename from ModManager_Classes/Utils/TweakManager.cs rename to ModManager_Classes/Services/TweakService.cs index a708f6d1..62dc00df 100644 --- a/ModManager_Classes/Utils/TweakManager.cs +++ b/ModManager_Classes/Services/TweakService.cs @@ -1,22 +1,31 @@ -using Imya.Models; -using Imya.Models.ModTweaker; +using Imya.Models.Mods; +using Imya.Models.ModTweaker.DataModel.Tweaking; +using Imya.Models.ModTweaker.IO; using Imya.Models.NotifyPropertyChanged; +using Imya.Services.Interfaces; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Imya.Utils +namespace Imya.Services { - public class TweakManager : PropertyChangedNotifier + public class TweakService : PropertyChangedNotifier, ITweakService { - public static TweakManager Instance { get; } = new TweakManager(); + private readonly ModTweaksLoader _loader; + private readonly ModTweaksExporter _exporter; + + public TweakService(ModTweaksLoader loader, ModTweaksExporter exporter) + { + _loader = loader; + _exporter = exporter; + } public ModTweaks? Tweaks { get => _tweaks; - set + private set { _tweaks = value; OnPropertyChanged(nameof(Tweaks)); @@ -35,13 +44,19 @@ public bool HasUnsavedChanges } private bool _hasUnsavedChanges; - private bool IsSaving; + public bool IsSaving + { + get => _isSaving; + private set => SetProperty(ref _isSaving, value); + } + private bool _isSaving; - public bool IsLoading { + public bool IsLoading + { get => _isLoading; - set => SetProperty(ref _isLoading, value); + private set => SetProperty(ref _isLoading, value); } - private bool _isLoading = false; + private bool _isLoading = false; public void Save() { @@ -50,7 +65,7 @@ public void Save() IsSaving = true; ThreadPool.QueueUserWorkItem(o => { - tweaks?.Save(); + _exporter.Save(tweaks); IsSaving = false; }); } @@ -68,7 +83,7 @@ public async Task SaveAsync() IsSaving = true; HasUnsavedChanges = false; var tweaks = Tweaks; - await Task.Run(() => tweaks?.Save()); + await Task.Run(() => _exporter.Save(tweaks)); IsSaving = false; } @@ -78,13 +93,13 @@ public void Load(Mod mod, bool ClearCurrentWhileLoading = true) HasUnsavedChanges = false; // make sure everything is secure from access from other threads var currentTweaks = Tweaks; - if(ClearCurrentWhileLoading) + if (ClearCurrentWhileLoading) Tweaks = null; ThreadPool.QueueUserWorkItem(o => { - ModTweaks tweaks = new(); - if (mod is not null) - tweaks.Load(mod); + var tweaks = mod is not null ? + _loader.Load(mod) + : new ModTweaks(); Tweaks = tweaks; IsLoading = false; }); diff --git a/ModManager_Classes/Texts/ITextManager.cs b/ModManager_Classes/Texts/ITextManager.cs new file mode 100644 index 00000000..2761f60b --- /dev/null +++ b/ModManager_Classes/Texts/ITextManager.cs @@ -0,0 +1,29 @@ +using Imya.Models; +using Imya.Models.ModMetadata; +using Imya.Models.ModMetadata.ModinfoModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Texts +{ + public interface ITextManager + { + ApplicationLanguage ApplicationLanguage { get; } + + delegate void LanguageChangedEventHandler(ApplicationLanguage language); + event LanguageChangedEventHandler LanguageChanged; + + public IText this[String Key] { get; } + + void AddText(String Key, IText t); + void AddAnonymousText(IText t); + IText GetText(String Key); + void ChangeLanguage(ApplicationLanguage lang); + void UpdateTexts(); + void LoadLanguageFile(String Sourcefile); + LocalizedText CreateLocalizedText(Localized localized); + } +} diff --git a/ModManager_Classes/Texts/TextManager.cs b/ModManager_Classes/Texts/TextManager.cs index 8448cc79..1cfe1dea 100644 --- a/ModManager_Classes/Texts/TextManager.cs +++ b/ModManager_Classes/Texts/TextManager.cs @@ -1,27 +1,22 @@ -using System.Runtime.Serialization; -using Imya.Models; +using Imya.Models; +using Imya.Models.ModMetadata.ModinfoModel; using Newtonsoft.Json; -using Imya.Enums; -using Imya.Models.ModMetadata; // TODO move all text/language related code under Imya.Text or Imya.Language -namespace Imya.Utils +namespace Imya.Texts { - public class TextManager + public class TextManager : ITextManager { - public static TextManager Instance { get; } = new TextManager(); - public ApplicationLanguage ApplicationLanguage { get; private set; } = ApplicationLanguage.English; private readonly Dictionary KeyedTexts = new(); private readonly List UnkeyedTexts = new(); - public delegate void LanguageChangedEventHandler(ApplicationLanguage language); - public event LanguageChangedEventHandler LanguageChanged = delegate { }; + public event ITextManager.LanguageChangedEventHandler LanguageChanged = delegate { }; public IText this[String Key] { - get { return Instance.GetText(Key); } + get { return GetText(Key); } } public TextManager() @@ -54,7 +49,7 @@ public IText GetText(String Key) { return KeyedTexts[Key]; } - catch + catch (Exception e) { Console.WriteLine($"Could not find Text: {Key}"); return IText.Empty; @@ -107,7 +102,7 @@ public void LoadLanguageFile(String Sourcefile) } - public static LocalizedText CreateLocalizedText(Localized localized) + public LocalizedText CreateLocalizedText(Localized localized) { var newText = new LocalizedText(); @@ -123,11 +118,9 @@ public static LocalizedText CreateLocalizedText(Localized localized) if (localized.Spanish is String) newText.Spanish = localized.Spanish; if (localized.Taiwanese is String) newText.Taiwanese = localized.Taiwanese; - if (Instance is not null) - { - newText.Update(Instance.ApplicationLanguage); - Instance.AddAnonymousText(newText); - } + newText.Update(ApplicationLanguage); + AddAnonymousText(newText); + return newText; } } diff --git a/ModManager_Classes/Utils/FrameworkExtensions.cs b/ModManager_Classes/Utils/FrameworkExtensions.cs index 30c8ea81..3b78972a 100644 --- a/ModManager_Classes/Utils/FrameworkExtensions.cs +++ b/ModManager_Classes/Utils/FrameworkExtensions.cs @@ -1,5 +1,3 @@ - -using Imya.Models.ModTweaker; using Imya.Models.Installation; using System; using System.Collections.Generic; @@ -9,14 +7,15 @@ using System.Threading.Tasks; using System.Xml; using Octokit; +using Imya.Models.ModTweaker.DataModel.Tweaking; namespace Imya.Utils { public static class GithubClientExtensions { - public static bool IsAuthenticated(this GitHubClient client) + public static bool IsAuthenticated(this IGitHubClient client) { - return client.Credentials.AuthenticationType == AuthenticationType.Oauth; + return client.Connection.Credentials.AuthenticationType == AuthenticationType.Oauth; } } public static class CollectionExtension diff --git a/ModManager_Classes/Utils/GithubClientProvider.cs b/ModManager_Classes/Utils/GithubClientProvider.cs deleted file mode 100644 index 1db65310..00000000 --- a/ModManager_Classes/Utils/GithubClientProvider.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Octokit; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net; -using System.Security; -using System.Text; -using System.Threading.Tasks; - - -namespace Imya.Utils -{ - public class GithubClientProvider - { - public static GitHubClient Client { get; } = new GitHubClient(new ProductHeaderValue("iModYourAnno")); - - public static IAuthenticator Authenticator { get; set; } - } -} diff --git a/ModManager_Classes/Utils/IAuthenticator.cs b/ModManager_Classes/Utils/IAuthenticator.cs index 6b12b37a..2579723f 100644 --- a/ModManager_Classes/Utils/IAuthenticator.cs +++ b/ModManager_Classes/Utils/IAuthenticator.cs @@ -8,6 +8,7 @@ namespace Imya.Utils { + //todo move this to Imya.UI.Models public interface IAuthenticator { public event PopupRequestedEventHandler UserCodeReceived; diff --git a/ModManager_Classes/Utils/ImyaSetupManager.cs b/ModManager_Classes/Utils/ImyaSetupManager.cs deleted file mode 100644 index 58cc7bd3..00000000 --- a/ModManager_Classes/Utils/ImyaSetupManager.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Imya.Models.NotifyPropertyChanged; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Imya.Utils -{ - public class ImyaSetupManager : PropertyChangedNotifier - { - public static String WorkingDirectory = ".imya"; - public static String ProfilesDirectory = "profiles"; - public static String DownloadDirectory = "download"; - public static String TweakDirectory = "tweaks"; - public static String UnpackDirectory = ".unpack"; - - public static ImyaSetupManager Instance = new ImyaSetupManager(); - - public GameSetupManager GameSetup = GameSetupManager.Instance; - - public String WorkingDirectoryPath { get => Path.Combine(GameSetup.GameRootPath, WorkingDirectory); } - public String ProfilesDirectoryPath { get => Path.Combine(GameSetup.GameRootPath, WorkingDirectory, ProfilesDirectory); } - public String DownloadDirectoryPath { get => Path.Combine(GameSetup.GameRootPath, WorkingDirectory, DownloadDirectory); } - public String TweakDirectoryPath { get => Path.Combine(GameSetup.GameRootPath, WorkingDirectory, TweakDirectory); } - public String UnpackDirectoryPath { get => Path.Combine(GameSetup.GameRootPath, WorkingDirectory, UnpackDirectory); } - - public ImyaSetupManager() - { - if (Directory.Exists(GameSetup.GameRootPath)) - { - Init(); - } - GameSetup.GameRootPathChanged += OnGameRootPathChanged; - } - - private void OnGameRootPathChanged(String GameRootPath) - { - if (Directory.Exists(GameRootPath)) - { - Init(); - } - } - - public void Init() - { - Directory.CreateDirectory(WorkingDirectoryPath); - Directory.CreateDirectory(ProfilesDirectoryPath); - Directory.CreateDirectory(DownloadDirectoryPath); - Directory.CreateDirectory(TweakDirectoryPath); - Directory.CreateDirectory(UnpackDirectoryPath); - } - } -} diff --git a/ModManager_Classes/Utils/InstallationValidator.cs b/ModManager_Classes/Utils/InstallationValidator.cs index d3f41d4f..c8b06aad 100644 --- a/ModManager_Classes/Utils/InstallationValidator.cs +++ b/ModManager_Classes/Utils/InstallationValidator.cs @@ -1,4 +1,5 @@ -using System; +using Imya.Services.Interfaces; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/ModManager_Classes/Utils/ModinfoCreationManager.cs b/ModManager_Classes/Utils/ModinfoCreationManager.cs index 5aefa762..a661237a 100644 --- a/ModManager_Classes/Utils/ModinfoCreationManager.cs +++ b/ModManager_Classes/Utils/ModinfoCreationManager.cs @@ -1,5 +1,5 @@ using Imya.Enums; -using Imya.Models.ModMetadata; +using Imya.Models.ModMetadata.ModinfoModel; using Imya.Models.NotifyPropertyChanged; using System; using System.Collections.Generic; diff --git a/ModManager_Classes/Utils/ModinfoLoader.cs b/ModManager_Classes/Utils/ModinfoLoader.cs index d6d5c814..aa89074e 100644 --- a/ModManager_Classes/Utils/ModinfoLoader.cs +++ b/ModManager_Classes/Utils/ModinfoLoader.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using Imya.Models.ModMetadata.ModinfoModel; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Imya.Models.ModMetadata diff --git a/ModManager_Classes/Validation/CyclicDependencyValidator.cs b/ModManager_Classes/Validation/CyclicDependencyValidator.cs index df137b58..f6b26d40 100644 --- a/ModManager_Classes/Validation/CyclicDependencyValidator.cs +++ b/ModManager_Classes/Validation/CyclicDependencyValidator.cs @@ -1,7 +1,9 @@ -using Imya.Models; -using Imya.Models.Attributes; +using Imya.Models.Attributes; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Mods; using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Runtime.CompilerServices; using System.Text; @@ -9,9 +11,16 @@ namespace Imya.Validation { - internal class CyclicDependencyValidator : IModValidator + public class CyclicDependencyValidator : IModValidator { - public void Validate(IEnumerable changed, IReadOnlyCollection all) + private ICyclicDependencyAttributeFactory _attributeFactory; + + public CyclicDependencyValidator(ICyclicDependencyAttributeFactory attributeFactory) + { + _attributeFactory = attributeFactory; + } + + public void Validate(IEnumerable changed, IReadOnlyCollection all, NotifyCollectionChangedAction changedAction) { foreach (Mod x in all) x.Attributes.RemoveAttributesByType(AttributeType.CyclicDependency); @@ -20,7 +29,7 @@ public void Validate(IEnumerable changed, IReadOnlyCollection all) var cyclics = CyclicDependencies(x, all); if (cyclics.Count() > 0) { - x.Attributes.Add(CyclicDependencyAttributeFactory.Get(cyclics)); + x.Attributes.AddAttribute(_attributeFactory.Get(cyclics)); } } } diff --git a/ModManager_Classes/Validation/IModValidator.cs b/ModManager_Classes/Validation/IModValidator.cs index 39a2d85c..4913a69f 100644 --- a/ModManager_Classes/Validation/IModValidator.cs +++ b/ModManager_Classes/Validation/IModValidator.cs @@ -1,9 +1,10 @@ -using Imya.Models; +using Imya.Models.Mods; +using System.Collections.Specialized; namespace Imya.Validation { public interface IModValidator { - void Validate(IEnumerable changed, IReadOnlyCollection all); + void Validate(IEnumerable changed, IReadOnlyCollection all, NotifyCollectionChangedAction changedAction); } } diff --git a/ModManager_Classes/Validation/ModCollectionHooks.cs b/ModManager_Classes/Validation/ModCollectionHooks.cs index c0d8e00e..2717f548 100644 --- a/ModManager_Classes/Validation/ModCollectionHooks.cs +++ b/ModManager_Classes/Validation/ModCollectionHooks.cs @@ -1,27 +1,34 @@ -using Imya.Models; -using Imya.Models.Attributes; +using Imya.Models.Attributes; +using Imya.Models.Mods; using Imya.Models.ModTweaker; +using Imya.Models.ModTweaker.DataModel.Storage; +using Imya.Models.ModTweaker.DataModel.Tweaking; +using Imya.Models.ModTweaker.IO; using System.Collections.Specialized; using System.Threading; namespace Imya.Validation { + //todo rework the hook system public class ModCollectionHooks { - private SemaphoreSlim _tweaksave_sem; - private readonly IModValidator[] validators = new IModValidator[] - { - new ModContentValidator(), - new ModCompatibilityValidator(), - new CyclicDependencyValidator() - }; + private List validators = new(); - public ModCollectionHooks(ModCollection mods) + public ModCollectionHooks() + { + + } + + public void HookTo(ModCollection mods) { - _tweaksave_sem = new SemaphoreSlim(1); mods.CollectionChanged += ValidateOnChange; } + public void AddHook(IModValidator validator) + { + validators.Add(validator); + } + private void ValidateOnChange(object? sender, NotifyCollectionChangedEventArgs e) { if (sender is not ModCollection collection) @@ -29,32 +36,8 @@ private void ValidateOnChange(object? sender, NotifyCollectionChangedEventArgs e IEnumerable changed = e.NewItems?.OfType() ?? collection.Mods; foreach (var validator in validators) - validator.Validate(changed, collection.Mods); - - if (e.Action == NotifyCollectionChangedAction.Reset - || e.Action == NotifyCollectionChangedAction.Add) - { - foreach (var mod in changed) - UpdateWithTweak(mod); - } + validator.Validate(changed, collection.Mods, e.Action); } - private void UpdateWithTweak(Mod mod) - { - mod.Attributes.RemoveAttributesByType(AttributeType.TweakedMod); - if (!TweakStorageShelf.Global.IsStored(mod.FolderName)) - return; - - // TODO double access is unprotected - // TODO all validation should be offloaded to async, not tweaks individually - Task.Run(() => - { - ModTweaks tweaks = new(); - tweaks.Load(mod); - _tweaksave_sem.Wait(); - tweaks.Save(); - _tweaksave_sem.Release(); - }); - } } } diff --git a/ModManager_Classes/Validation/ModCompatibilityValidator.cs b/ModManager_Classes/Validation/ModCompatibilityValidator.cs index dd117aa0..b863df2b 100644 --- a/ModManager_Classes/Validation/ModCompatibilityValidator.cs +++ b/ModManager_Classes/Validation/ModCompatibilityValidator.cs @@ -1,54 +1,40 @@ -using Imya.Models; -using Imya.Models.Attributes; -using Imya.Models.ModMetadata; +using Imya.Models.Attributes; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.ModMetadata.ModinfoModel; +using Imya.Models.Mods; using System.Collections.Immutable; +using System.Collections.Specialized; namespace Imya.Validation { public class ModCompatibilityValidator : IModValidator { - public void Validate(IEnumerable changed, IReadOnlyCollection all) + private readonly IModCompabilityAttributeFactory _compabilityAttributeFactory; + + public ModCompatibilityValidator(IModCompabilityAttributeFactory factory) + { + _compabilityAttributeFactory = factory; + } + + public void Validate(IEnumerable changed, IReadOnlyCollection all, NotifyCollectionChangedAction changedAction) { foreach (var mod in all) ValidateSingle(mod, all); } - private static void ValidateSingle(Mod mod, IReadOnlyCollection collection) + private void ValidateSingle(Mod mod, IReadOnlyCollection collection) { - mod.Attributes.RemoveAttributesByType(AttributeType.UnresolvedDependencyIssue); mod.Attributes.RemoveAttributesByType(AttributeType.ModCompabilityIssue); - mod.Attributes.RemoveAttributesByType(AttributeType.ModReplacedByIssue); // skip dependency check if inactive or standalone if (!mod.IsActiveAndValid || collection is null) return; - var unresolvedDeps = GetUnresolvedDependencies(mod.Modinfo, collection); - if (unresolvedDeps.Any()) - mod.Attributes.AddAttribute(new ModDependencyIssueAttribute(unresolvedDeps)); - var incompatibles = GetIncompatibleMods(mod.Modinfo, collection); if (incompatibles.Any()) - mod.Attributes.AddAttribute(ModCompabilityAttributeFactory.Get(incompatibles)); - - Mod? newReplacementMod = HasBeenDeprecated(mod.Modinfo, collection) ?? IsNewestOfID(mod, collection); - if (newReplacementMod is not null && newReplacementMod != mod) - mod.Attributes.AddAttribute(new ModReplacedByIssue(newReplacementMod)); + mod.Attributes.AddAttribute(_compabilityAttributeFactory.Get(incompatibles)); } - private static IEnumerable GetUnresolvedDependencies(Modinfo modinfo, IReadOnlyCollection collection) - { - if (modinfo.ModDependencies is null) - yield break; - - foreach (var dep in modinfo.ModDependencies) - { - if (!collection.Any(x => x.Modinfo.ModID is not null - && (x.Modinfo.ModID.Equals(dep) || x.SubMods?.Find(submod => submod.ModID.Equals(dep)) is not null) - && x.IsActiveAndValid)) - yield return dep; - } - } private static IEnumerable GetIncompatibleMods(Modinfo modinfo, IReadOnlyCollection collection) { @@ -62,21 +48,5 @@ private static IEnumerable GetIncompatibleMods(Modinfo modinfo, IReadOnlyCo yield return result; } } - - private static Mod? HasBeenDeprecated(Modinfo modinfo, IReadOnlyCollection collection) - { - if (collection is null || modinfo.ModID is null) - return null; - - return collection.FirstOrDefault(x => x.Modinfo?.DeprecateIds?.Contains(modinfo.ModID) ?? false); - } - - private static Mod? IsNewestOfID(Mod mod, IReadOnlyCollection collection) - { - if (collection is null || mod.Modinfo.ModID is null) - return null; - - return collection.Where(x => x.Modinfo.ModID == mod.Modinfo.ModID).OrderBy(x => x.Version).LastOrDefault(); - } } } diff --git a/ModManager_Classes/Validation/ModContentValidator.cs b/ModManager_Classes/Validation/ModContentValidator.cs index ee082d32..8f934972 100644 --- a/ModManager_Classes/Validation/ModContentValidator.cs +++ b/ModManager_Classes/Validation/ModContentValidator.cs @@ -1,6 +1,8 @@ -using Imya.Models; -using Imya.Models.Attributes; +using Imya.Models.Attributes; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Mods; using Imya.Utils; +using System.Collections.Specialized; namespace Imya.Validation { @@ -10,19 +12,36 @@ namespace Imya.Validation /// public class ModContentValidator : IModValidator { - public void Validate(IEnumerable changed, IReadOnlyCollection all) + private readonly IContentInSubfolderAttributeFactory _factory; + public ModContentValidator(IContentInSubfolderAttributeFactory factory) + { + _factory = factory; + } + + public void Validate(IEnumerable changed, IReadOnlyCollection all, NotifyCollectionChangedAction changedAction) { foreach (var mod in changed) ValidateSingle(mod); } - private static void ValidateSingle(Mod mod) + private void ValidateSingle(Mod mod) { if (mod.IsRemoved || !Directory.Exists(mod.FullModPath)) { mod.IsRemoved = true; return; } + + string dataPath = Path.Combine(mod.FullModPath, "data"); + if (Directory.Exists(dataPath) || File.Exists(Path.Combine(mod.FullModPath, "modinfo.json"))) + return; + // data/ doesn't exist, that's odd + var foundFolders = Directory.GetDirectories(mod.FullModPath, "data", SearchOption.AllDirectories); + if (foundFolders.Length > 0) + { + // there's a data/ somewhere deeper, probably a mistake then + mod.Attributes.AddAttribute(_factory.Get()); + } } } } diff --git a/ModManager_Classes/Validation/ModDependencyValidator.cs b/ModManager_Classes/Validation/ModDependencyValidator.cs new file mode 100644 index 00000000..5a5cf82b --- /dev/null +++ b/ModManager_Classes/Validation/ModDependencyValidator.cs @@ -0,0 +1,55 @@ +using Imya.Models.Attributes; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.ModMetadata.ModinfoModel; +using Imya.Models.Mods; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Validation +{ + public class ModDependencyValidator : IModValidator + { + private readonly IModDependencyIssueAttributeFactory _dependencyAttributeFactory; + + public ModDependencyValidator(IModDependencyIssueAttributeFactory factory) + { + _dependencyAttributeFactory = factory; + } + + public void Validate(IEnumerable changed, IReadOnlyCollection all, NotifyCollectionChangedAction changedAction) + { + foreach (var mod in all) + ValidateSingle(mod, all); + } + + private void ValidateSingle(Mod mod, IReadOnlyCollection collection) + { + mod.Attributes.RemoveAttributesByType(AttributeType.UnresolvedDependencyIssue); + // skip dependency check if inactive or standalone + if (!mod.IsActiveAndValid || collection is null) + return; + + var unresolvedDeps = GetUnresolvedDependencies(mod.Modinfo, collection).ToArray(); + if (unresolvedDeps.Any()) + mod.Attributes.AddAttribute(_dependencyAttributeFactory.Get(unresolvedDeps)); + } + + private IEnumerable GetUnresolvedDependencies(Modinfo modinfo, IReadOnlyCollection collection) + { + if (modinfo.ModDependencies is null) + yield break; + + foreach (var dep in modinfo.ModDependencies) + { + if (!collection.Any(x => x.Modinfo.ModID is not null + && (x.Modinfo.ModID.Equals(dep) || x.SubMods?.Find(submod => submod.ModID.Equals(dep)) is not null) + && x.IsActiveAndValid)) + yield return dep; + } + } + } +} diff --git a/ModManager_Classes/Validation/ModReplacementValidator.cs b/ModManager_Classes/Validation/ModReplacementValidator.cs new file mode 100644 index 00000000..0e2ef2d6 --- /dev/null +++ b/ModManager_Classes/Validation/ModReplacementValidator.cs @@ -0,0 +1,59 @@ +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Attributes; +using Imya.Models.Mods; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Imya.Models.ModMetadata.ModinfoModel; +using System.Collections.Specialized; + +namespace Imya.Validation +{ + public class ModReplacementValidator : IModValidator + { + private readonly IModReplacedByAttributeFactory _modReplacedByAttributeFactory; + + public ModReplacementValidator(IModReplacedByAttributeFactory factory) + { + _modReplacedByAttributeFactory = factory; + } + + public void Validate(IEnumerable changed, IReadOnlyCollection all, NotifyCollectionChangedAction changedAction) + { + foreach (var mod in all) + ValidateSingle(mod, all); + } + + private void ValidateSingle(Mod mod, IReadOnlyCollection collection) + { + mod.Attributes.RemoveAttributesByType(AttributeType.ModReplacedByIssue); + // skip dependency check if inactive or standalone + if (!mod.IsActiveAndValid || collection is null) + return; + + Mod? newReplacementMod = HasBeenDeprecated(mod.Modinfo, collection) ?? IsNewestOfID(mod, collection); + if (newReplacementMod is not null && newReplacementMod != mod) + mod.Attributes.AddAttribute(_modReplacedByAttributeFactory.Get(newReplacementMod)); + } + + + private static Mod? HasBeenDeprecated(Modinfo modinfo, IReadOnlyCollection collection) + { + if (collection is null || modinfo.ModID is null) + return null; + + return collection.FirstOrDefault(x => x.Modinfo?.DeprecateIds?.Contains(modinfo.ModID) ?? false); + } + + private static Mod? IsNewestOfID(Mod mod, IReadOnlyCollection collection) + { + if (collection is null || mod.Modinfo.ModID is null) + return null; + + return collection.Where(x => x.Modinfo.ModID == mod.Modinfo.ModID).OrderBy(x => x.Version).LastOrDefault(); + } + + } +} diff --git a/ModManager_Classes/Validation/RemovedModValidator.cs b/ModManager_Classes/Validation/RemovedModValidator.cs new file mode 100644 index 00000000..25895342 --- /dev/null +++ b/ModManager_Classes/Validation/RemovedModValidator.cs @@ -0,0 +1,41 @@ +using Imya.Models.Attributes; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Mods; +using Imya.Texts; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace Imya.Validation +{ + public class RemovedModValidator : IModValidator + { + private readonly IRemovedFolderAttributeFactory _attributeFactory; + public RemovedModValidator(IRemovedFolderAttributeFactory attributeFactory) + { + _attributeFactory = attributeFactory; + } + + public void Validate(IEnumerable changed, IReadOnlyCollection all, NotifyCollectionChangedAction changedAction) + { + foreach (var mod in changed) + ValidateSingle(mod); + } + + + private void ValidateSingle(Mod mod) + { + if (!mod.IsRemoved) + { + mod.Attributes.RemoveAttributesByType(AttributeType.IssueModRemoved); + return; + } + mod.Attributes.Clear(); + mod.Attributes.AddAttribute(_attributeFactory.Get()); + } + } +} diff --git a/ModManager_Classes/Validation/TweakValidator.cs b/ModManager_Classes/Validation/TweakValidator.cs new file mode 100644 index 00000000..c0064a3c --- /dev/null +++ b/ModManager_Classes/Validation/TweakValidator.cs @@ -0,0 +1,71 @@ +using Imya.Models.Attributes; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.Mods; +using Imya.Models.ModTweaker.DataModel.Storage; +using Imya.Models.ModTweaker.IO; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Imya.Validation +{ + public class TweakValidator : IModValidator + { + private ITweakRepository _tweakRepository; + private SemaphoreSlim _tweaksave_sem; + private ModTweaksLoader _tweaksLoader; + private ModTweaksExporter _tweaksExporter; + private ITweakedAttributeFactory _tweakedAttributeFactory; + + public TweakValidator( + ITweakRepository tweakRepository, + ModTweaksLoader tweaksLoader, + ModTweaksExporter tweaksExporter, + ITweakedAttributeFactory tweakedAttributeFactory) + { + _tweakRepository = tweakRepository; + _tweaksave_sem = new SemaphoreSlim(1); + _tweaksLoader = tweaksLoader; + _tweaksExporter = tweaksExporter; + _tweakedAttributeFactory = tweakedAttributeFactory; + } + + public void Validate(IEnumerable changed, IReadOnlyCollection all, NotifyCollectionChangedAction changedAction) + { + + if (changedAction == NotifyCollectionChangedAction.Reset + || changedAction == NotifyCollectionChangedAction.Add) + { + foreach (var mod in changed) + { + UpdateWithTweak(mod); + } + } + + } + + private void UpdateWithTweak(Mod mod) + { + mod.Attributes.RemoveAttributesByType(AttributeType.TweakedMod); + if (!_tweakRepository.IsStored(mod.FolderName)) + return; + + // TODO double access is unprotected + // TODO all validation should be offloaded to async, not tweaks individually + Task.Run(() => + { + var tweaks = _tweaksLoader.Load(mod); + if (tweaks is not null && !tweaks.IsEmpty) + { + mod.Attributes.AddAttribute(_tweakedAttributeFactory.Get()); + _tweaksave_sem.Wait(); + _tweaksExporter.Save(tweaks); + _tweaksave_sem.Release(); + } + }); + } + } +} diff --git a/ModManager_Devplayground/Program.cs b/ModManager_Devplayground/Program.cs index 7b8c122e..12e650c7 100644 --- a/ModManager_Devplayground/Program.cs +++ b/ModManager_Devplayground/Program.cs @@ -1,13 +1,12 @@ using Imya; using Imya.GithubIntegration; -using Imya.Utils; +using Imya.Services; using ModManager_Devplayground; public class Program { public static async Task Main(String[] args) { - GameSetupManager gsm = GameSetupManager.Instance; - gsm.SetGamePath(@"F:\Spiele\Anno 1800"); + } } \ No newline at end of file diff --git a/tests/Imya.UnitTests/ExternalAccessTests.cs b/tests/Imya.UnitTests/ExternalAccessTests.cs index 6388d31f..95ba9097 100644 --- a/tests/Imya.UnitTests/ExternalAccessTests.cs +++ b/tests/Imya.UnitTests/ExternalAccessTests.cs @@ -1,6 +1,16 @@ using Imya.Models; +using Imya.Models.Attributes.Factories; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.ModMetadata; +using Imya.Models.Mods; +using Imya.Services.Interfaces; +using Imya.Texts; using Imya.Utils; using Imya.Validation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Moq; +using System; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -10,6 +20,35 @@ namespace Imya.UnitTests { public class ExternalAccessTests { + IModCollectionFactory _collectionFactory; + + IServiceProvider serviceProvider; + + public ExternalAccessTests() + { + var builder = Host.CreateDefaultBuilder(); + + serviceProvider = builder.ConfigureServices(services => + { + services.AddSingleton(Mock.Of()); + services.AddSingleton(x => new ModCollectionHooks()); + services.AddSingleton(Mock.Of()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + }).Build() + .Services; + + _collectionFactory = serviceProvider.GetRequiredService(); + } + [Fact] public async Task ChangeActivationOnDeletedMod() { @@ -19,8 +58,9 @@ public async Task ChangeActivationOnDeletedMod() // prepare a mod const string folder = $@"{test}\mods\[a] mod1"; Directory.CreateDirectory(folder); - var mods = new ModCollection($@"{test}\mods"); + var mods = _collectionFactory.Get($@"{test}\mods"); await mods.LoadModsAsync(); + mods.Hooks.AddHook(serviceProvider.GetRequiredService()); // check Assert.Single(mods); @@ -34,6 +74,7 @@ public async Task ChangeActivationOnDeletedMod() // mod should be still in the list, but marked as removed Assert.Single(mods); Assert.True(mods.Mods[0].IsRemoved); + //todo these should be seperate tests Assert.True(mods.Mods[0].Attributes.HasAttribute(Models.Attributes.AttributeType.IssueModRemoved)); Assert.Single(mods.Mods[0].Attributes); @@ -56,7 +97,7 @@ public async Task ValidateOnDeletedMod() File.WriteAllText($@"{folderB}\modinfo.json", @"{""ModID"": ""modB"", ""ModDependencies"": [ ""modA"" ]}"); const string folderC = $@"{test}\mods\[a] mod C"; Directory.CreateDirectory(folderC); - var mods = new ModCollection($@"{test}\mods"); + var mods = _collectionFactory.Get($@"{test}\mods"); await mods.LoadModsAsync(); // check @@ -66,16 +107,17 @@ public async Task ValidateOnDeletedMod() DirectoryEx.EnsureDeleted(folderA); // deactivate deleted mod to trigger IsRemoved=true - Mod modA = mods.Mods.Where(x => x.Name.Text == "mod A").First(); + Mod modA = mods.Mods.Where(x => x.ModID == "modA").First(); modA.IsRemoved = true; // trigger validation by deleting mod3 - ModCollectionHooks hooks = new(mods); - await mods.DeleteAsync(mods.Mods.Where(x => x.Name.Text == "mod C").ToArray()); + mods.Hooks.AddHook(serviceProvider.GetRequiredService()); + await mods.DeleteAsync(mods.Mods.Where(x => x.ModID == "[a] mod C").ToArray()); + Assert.Equal(2, mods.Count); // mod B should have dependency issue - Mod modB = mods.Mods.Where(x => x.Name.Text == "mod B").First(); + Mod modB = mods.Mods.Where(x => x.ModID == "modB").First(); Assert.True(modB.Attributes.HasAttribute(Models.Attributes.AttributeType.UnresolvedDependencyIssue)); } @@ -88,13 +130,13 @@ public async Task InstallDeletedMod() // prepare a mod const string folderA = $@"{test}\mods\[a] mod1"; Directory.CreateDirectory(folderA); - var mods = new ModCollection($@"{test}\mods"); + var mods = _collectionFactory.Get($@"{test}\mods"); await mods.LoadModsAsync(); // prepare reinstall mod const string folderB = $@"{test}\mods2\[a] mod1"; Directory.CreateDirectory(folderB); - var modsInstall = new ModCollection($@"{test}\mods2"); + var modsInstall = _collectionFactory.Get($@"{test}\mods2"); await modsInstall.LoadModsAsync(); // check @@ -125,7 +167,7 @@ public async Task DeleteDeletedMod() // prepare a mod const string folderA = $@"{test}\mods\[a] mod1"; Directory.CreateDirectory(folderA); - var mods = new ModCollection($@"{test}\mods"); + var mods = _collectionFactory.Get($@"{test}\mods"); await mods.LoadModsAsync(); // check @@ -151,7 +193,7 @@ public async Task DeleteDeletedModNoTrigger() // prepare a mod const string folderA = $@"{test}\mods\[a] mod1"; Directory.CreateDirectory(folderA); - var mods = new ModCollection($@"{test}\mods"); + var mods = _collectionFactory.Get($@"{test}\mods"); await mods.LoadModsAsync(); // check diff --git a/tests/Imya.UnitTests/Imya.UnitTests.csproj b/tests/Imya.UnitTests/Imya.UnitTests.csproj index d84e4e26..99c30ffc 100644 --- a/tests/Imya.UnitTests/Imya.UnitTests.csproj +++ b/tests/Imya.UnitTests/Imya.UnitTests.csproj @@ -8,7 +8,10 @@ + + + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Imya.UnitTests/ModCollectionTests.cs b/tests/Imya.UnitTests/ModCollectionTests.cs index 848d7f22..26b406dc 100644 --- a/tests/Imya.UnitTests/ModCollectionTests.cs +++ b/tests/Imya.UnitTests/ModCollectionTests.cs @@ -5,14 +5,47 @@ using Imya.Models; using Imya.Utils; using System.Linq; -using Imya.Models.Attributes; using System.Collections.Generic; +using Imya.Models.Attributes.Factories; +using Imya.Models.Mods; +using Moq; +using Imya.Services.Interfaces; +using Imya.Validation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Imya.Models.Attributes.Interfaces; +using Imya.Models.ModMetadata; +using Imya.Texts; namespace Imya.UnitTests { // Note: don't use await, debug doesn't work well with it public class ModCollectionTests { + ModCollectionFactory _collectionFactory; + + public ModCollectionTests() + { + var builder = Host.CreateDefaultBuilder(); + + var services = builder.ConfigureServices(services => + { + services.AddSingleton(Mock.Of()); + services.AddSingleton(Mock.Of()); + services.AddSingleton(Mock.Of()); + services.AddSingleton(Mock.Of()); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(Mock.Of()); + services.AddSingleton(); + }).Build() + .Services; + + _collectionFactory = services.GetRequiredService(); + } + [Fact] public void Single() { @@ -21,7 +54,7 @@ public void Single() const string folder = "tmp\\install1\\target\\[a] mod1"; Directory.CreateDirectory(folder); - var target = new ModCollection("tmp\\install1\\target"); + var target = _collectionFactory.Get("tmp\\install1\\target"); target.LoadModsAsync().Wait(); Assert.Single(target.Mods); @@ -37,7 +70,7 @@ public void LoadMods_InvalidPath() DirectoryEx.EnsureDeleted("tmp"); Assert.False(Directory.Exists("tmp\\asdf")); - var col = new ModCollection("tmp\\asdf"); + var col = _collectionFactory.Get("tmp\\asdf"); col.LoadModsAsync().Wait(); Assert.Empty(col.Mods); } @@ -56,7 +89,7 @@ public void LoadMods_AutofixSubfolder(bool autofixSubfolder) const string secondLevel = @"tmp\source\collection\deep\-[a] mod2\data"; Directory.CreateDirectory(secondLevel); File.WriteAllText($@"{secondLevel}\random.txt", ""); - var source = new ModCollection(@"tmp\source", autofixSubfolder: autofixSubfolder); + var source = _collectionFactory.Get(@"tmp\source", autofixSubfolder: autofixSubfolder); source.LoadModsAsync().Wait(); if (autofixSubfolder) @@ -87,18 +120,18 @@ public void MoveInto_CreateModFolder() DirectoryEx.EnsureDeleted("tmp"); Assert.False(Directory.Exists("tmp\\mods")); - var col = new ModCollection("tmp\\mods"); + var col = _collectionFactory.Get("tmp\\mods"); col.LoadModsAsync().Wait(); Assert.Empty(col.Mods); Directory.CreateDirectory("tmp\\source"); - var empty = new ModCollection("tmp\\source"); + var empty = _collectionFactory.Get("tmp\\source"); empty.LoadModsAsync().Wait(); col.MoveIntoAsync(empty).Wait(); // and another time to ensure double creation isn't a problem Directory.CreateDirectory("tmp\\source"); - empty = new ModCollection("tmp\\source"); + empty = _collectionFactory.Get("tmp\\source"); empty.LoadModsAsync().Wait(); col.MoveIntoAsync(empty).Wait(); } @@ -113,7 +146,7 @@ public void MoveInto_NewMods() // create target Directory.CreateDirectory("tmp\\target"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // verify target @@ -129,7 +162,7 @@ public void MoveInto_NewMods() const string inactiveSourceMod = "tmp\\source\\-[a] mod2"; Directory.CreateDirectory(inactiveSourceMod); File.WriteAllText($"{inactiveSourceMod}\\add.txt", ""); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should be overwritten @@ -153,7 +186,7 @@ public void MoveInto_OverwriteActiveMod() Directory.CreateDirectory(targetMod); File.WriteAllText($"{targetMod}\\remove.txt", ""); File.WriteAllText($"{targetMod}\\update.txt", "old text"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // verify target @@ -166,7 +199,7 @@ public void MoveInto_OverwriteActiveMod() Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\add.txt", ""); File.WriteAllText($"{sourceMod}\\update.txt", "new text"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should be overwritten @@ -190,7 +223,7 @@ public void MoveInto_OverwriteInactive() Directory.CreateDirectory(targetMod); File.WriteAllText($"{targetMod}\\remove.txt", ""); File.WriteAllText($"{targetMod}\\update.txt", "old text"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // verify target @@ -203,7 +236,7 @@ public void MoveInto_OverwriteInactive() Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\add.txt", ""); File.WriteAllText($"{sourceMod}\\update.txt", "new text"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should be overwritten @@ -228,7 +261,7 @@ public void MoveInto_SameModIDDifferentFolder() File.WriteAllText($"{obsoleteMod}\\remove.txt", ""); File.WriteAllText($"{obsoleteMod}\\update.txt", "old text"); File.WriteAllText($"{obsoleteMod}\\modinfo.json", "{\"ModID\": \"mod1\"}"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // verify obsolete target @@ -241,7 +274,7 @@ public void MoveInto_SameModIDDifferentFolder() File.WriteAllText($"{sourceMod}\\add.txt", ""); File.WriteAllText($"{sourceMod}\\update.txt", "new text"); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"ModID\": \"mod1\"}"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // verify results @@ -273,7 +306,7 @@ public void MoveInto_DeprecateOtherMod() File.WriteAllText($"{obsoleteMod}\\remove.txt", ""); File.WriteAllText($"{obsoleteMod}\\update.txt", "old text"); File.WriteAllText($"{obsoleteMod}\\modinfo.json", "{\"ModID\": \"mod1\"}"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // verify obsolete target @@ -286,7 +319,7 @@ public void MoveInto_DeprecateOtherMod() File.WriteAllText($"{sourceMod}\\add.txt", ""); File.WriteAllText($"{sourceMod}\\update.txt", "new text"); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"ModID\":\"mod2\", \"DeprecateIds\": [ \"mod1\" ]}"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // verify results @@ -317,7 +350,7 @@ public void MoveInto_OverwriteActiveModFromInactive() Directory.CreateDirectory(targetMod); File.WriteAllText($"{targetMod}\\remove.txt", ""); File.WriteAllText($"{targetMod}\\update.txt", "old text"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // verify target @@ -330,7 +363,7 @@ public void MoveInto_OverwriteActiveModFromInactive() Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\add.txt", ""); File.WriteAllText($"{sourceMod}\\update.txt", "new text"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should be overwritten @@ -353,14 +386,14 @@ public void MoveInto_SameFolderDifferentModID() const string targetMod = "tmp\\target\\[a] mod1"; Directory.CreateDirectory(targetMod); File.WriteAllText($"{targetMod}\\modinfo.json", "{\"ModID\": \"mod1\"}"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // create source const string sourceMod = "tmp\\source\\[a] mod1"; Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"ModID\": \"mod2\"}"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should be same, and new mod added with a different name @@ -383,14 +416,14 @@ public void MoveInto_SkipVersionPatterns() const string targetMod = "tmp\\target\\mod1"; Directory.CreateDirectory(targetMod); File.WriteAllText($"{targetMod}\\modinfo.json", "{\"Version\": \"1.0.1\"}"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // create source const string sourceMod = "tmp\\source\\mod1"; Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"Version\": \"1\"}"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should not be overwritten @@ -401,7 +434,7 @@ public void MoveInto_SkipVersionPatterns() // create source without version Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"Version\": null}"); - source = new ModCollection("tmp\\source"); + source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should not be overwritten @@ -421,14 +454,14 @@ public void MoveInto_UpdateVersionPatterns() // create target const string targetMod = "tmp\\target\\mod1"; Directory.CreateDirectory(targetMod); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // create source const string sourceMod = "tmp\\source\\mod1"; Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"Version\": \"1\"}"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should be overwritten @@ -439,7 +472,7 @@ public void MoveInto_UpdateVersionPatterns() // create source Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"Version\": \"1.0.1\"}"); - source = new ModCollection("tmp\\source"); + source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should be overwritten @@ -460,14 +493,14 @@ public void MoveInto_ForceVersionPatterns() const string targetMod = "tmp\\target\\mod1"; Directory.CreateDirectory(targetMod); File.WriteAllText($"{targetMod}\\modinfo.json", "{\"Version\": \"1.0.1\"}"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // create source const string sourceMod = "tmp\\source\\mod1"; Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"Version\": \"1\"}"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should not be overwritten @@ -478,7 +511,7 @@ public void MoveInto_ForceVersionPatterns() // create source without version Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"Version\": null}"); - source = new ModCollection("tmp\\source"); + source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should not be overwritten @@ -500,7 +533,7 @@ public void MoveInto_SameVersionDifferentContent() Directory.CreateDirectory(targetMod); File.WriteAllText($"{targetMod}\\modinfo.json", "{\"Version\": \"1\"}"); File.WriteAllText($"{targetMod}\\changed.txt", "old"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // create source @@ -508,7 +541,7 @@ public void MoveInto_SameVersionDifferentContent() Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"Version\": \"1\"}"); File.WriteAllText($"{sourceMod}\\changed.txt", "new"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should be overwritten @@ -531,7 +564,7 @@ public void MoveInto_SameVersionSameContent() Directory.CreateDirectory(targetMod); File.WriteAllText($"{targetMod}\\modinfo.json", "{\"Version\": \"1\"}"); File.WriteAllText($"{targetMod}\\unchanged.txt", "unchanged"); - var target = new ModCollection("tmp\\target"); + var target = _collectionFactory.Get("tmp\\target"); target.LoadModsAsync().Wait(); // create source @@ -539,7 +572,7 @@ public void MoveInto_SameVersionSameContent() Directory.CreateDirectory(sourceMod); File.WriteAllText($"{sourceMod}\\modinfo.json", "{\"Version\": \"1\"}"); File.WriteAllText($"{sourceMod}\\unchanged.txt", "unchanged"); - var source = new ModCollection("tmp\\source"); + var source = _collectionFactory.Get("tmp\\source"); source.LoadModsAsync().Wait(); // target should not be overwritten diff --git a/tests/Imya.UnitTests/SortOrderTests.cs b/tests/Imya.UnitTests/SortOrderTests.cs index 7fc8d2e1..6b9f9fd9 100644 --- a/tests/Imya.UnitTests/SortOrderTests.cs +++ b/tests/Imya.UnitTests/SortOrderTests.cs @@ -13,6 +13,7 @@ namespace Imya.UnitTests { public class SortOrderTests { + /* [Fact] public void LoadOrder_NormalDependency() { @@ -89,5 +90,6 @@ public void LoadOrder_ThreeMods() Assert.True(sorted[1].ModID == "Mod3"); Assert.True(sorted[2].ModID == "Mod1"); } + */ } } diff --git a/tests/Imya.UnitTests/TweakLogicTests.cs b/tests/Imya.UnitTests/TweakLogicTests.cs index b9330802..d48e8726 100644 --- a/tests/Imya.UnitTests/TweakLogicTests.cs +++ b/tests/Imya.UnitTests/TweakLogicTests.cs @@ -9,17 +9,17 @@ using System.IO; using Imya.Utils; using System.Xml; +using Imya.Services; +using Imya.Services.Interfaces; +using Imya.Models.ModTweaker.DataModel.Tweaking; +using Imya.Models.ModTweaker.DataModel.Storage; namespace Imya.UnitTests { public class TweakLogicTests { public TweakLogicTests() - { - //we need to register our gamepath, else we get exceptions thrown all over the place. - GameSetupManager gameSetupManager = GameSetupManager.Instance; - gameSetupManager.SetGamePath(""); - } + { } [Fact] public void CorrectlyEnsuresSkip() @@ -37,7 +37,7 @@ public void CorrectlyEnsuresSkip() InitWorkingDirectory(); LoadAssets(AssetsXML); - TweakerFile.TryInit("tweak_tmp", "assets.xml", TweakStorageShelf.Global.Get("TestStorage"), out var tweakerFile); + TweakerFile.TryInit("tweak_tmp", "assets.xml", out var tweakerFile); //assets.xml only has one value tweakerFile.Save("tweak_tmp"); @@ -74,7 +74,7 @@ public void CorrectlyEnsuresNoSkip() InitWorkingDirectory(); LoadAssets(assetsXML); - TweakerFile.TryInit("tweak_tmp", "assets.xml", TweakStorageShelf.Global.Get("TestStorage"), out var tweakerFile); + TweakerFile.TryInit("tweak_tmp", "assets.xml", out var tweakerFile); var toggleVal = tweakerFile.Exposes.First() as ExposedToggleModValue; toggleVal!.IsTrue = true; // dont skip on true tweakerFile.Save("tweak_tmp"); @@ -105,7 +105,7 @@ public void CorrectlyEnsuresSkipInverted() InitWorkingDirectory(); LoadAssets(assetsXML); - TweakerFile.TryInit("tweak_tmp", "assets.xml", TweakStorageShelf.Global.Get("TestStorage"), out var tweakerFile); + TweakerFile.TryInit("tweak_tmp", "assets.xml", out var tweakerFile); var toggleVal = tweakerFile.Exposes.First() as ExposedToggleModValue; toggleVal!.IsTrue = true; // skip on true (inverted) tweakerFile.Save("tweak_tmp"); @@ -138,7 +138,7 @@ public void CorrectlyEnsuresInclude() InitWorkingDirectory(); LoadAssets(AssetsXML); - TweakerFile.TryInit("tweak_tmp", "assets.xml", TweakStorageShelf.Global.Get("TestStorage"), out var tweakerFile); + TweakerFile.TryInit("tweak_tmp", "assets.xml", out var tweakerFile); //assets.xml only has one value tweakerFile.Save("tweak_tmp"); @@ -173,7 +173,7 @@ public void InnerXmlValueChanges() LoadAssets(AssetsXML_XmlReplace); - TweakerFile.TryInit("tweak_tmp", "assets.xml", TweakStorageShelf.Global.Get("TestStorage"), out var tweakerFile); + TweakerFile.TryInit("tweak_tmp", "assets.xml", out var tweakerFile); //assets.xml only has one value var toggleVal = tweakerFile.Exposes.First() as ExposedToggleModValue; toggleVal!.IsTrue = false; @@ -202,7 +202,7 @@ public void InnerTextValueChanges() String NewValue = "Some other Text"; LoadAssets(AssetsXML_TextReplace); - TweakerFile.TryInit("tweak_tmp", "assets.xml", TweakStorageShelf.Global.Get("TestStorage"), out var tweakerFile); + TweakerFile.TryInit("tweak_tmp", "assets.xml", out var tweakerFile); //assets.xml only has one value tweakerFile.Exposes.First().Value = NewValue; tweakerFile.Save("tweak_tmp"); @@ -236,7 +236,7 @@ public void DoesNothingOnWrongPath() LoadAssets(AssetsXML_TextReplace); - TweakerFile.TryInit("tweak_tmp", "assets.xml", TweakStorageShelf.Global.Get("TestStorage"), out var tweakerFile); + TweakerFile.TryInit("tweak_tmp", "assets.xml", out var tweakerFile); //assets.xml only has one value tweakerFile.Save("tweak_tmp");