Add project files.
This commit is contained in:
25
ModVersionChecker.sln
Normal file
25
ModVersionChecker.sln
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36401.2 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModVersionChecker", "ModVersionChecker\ModVersionChecker.csproj", "{AF2DC7D5-9B7D-42B6-B9AA-092669626033}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{AF2DC7D5-9B7D-42B6-B9AA-092669626033}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{AF2DC7D5-9B7D-42B6-B9AA-092669626033}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{AF2DC7D5-9B7D-42B6-B9AA-092669626033}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{AF2DC7D5-9B7D-42B6-B9AA-092669626033}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {12AE20EE-3AC6-4E00-BF52-F5FA5EA2AF25}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
144
ModVersionChecker/Main.cs
Normal file
144
ModVersionChecker/Main.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModVersionChecker.forms;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using ModVersionChecker.managers.filesystem;
|
||||
using ModVersionChecker.managers.litedb;
|
||||
|
||||
namespace ModVersionChecker
|
||||
{
|
||||
class Program
|
||||
{
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
var builder = Host.CreateDefaultBuilder();
|
||||
var program = new Program();
|
||||
|
||||
builder.ConfigureServices(services =>
|
||||
{
|
||||
services.AddSingleton<IConfigManager, ConfigLiteDb>();
|
||||
services.AddSingleton<IAppsManager, AppConfigLiteDb>();
|
||||
services.AddSingleton<ISourcesDefManager, SourcesLiteDb>();
|
||||
services.AddSingleton<ICheckerTypesDefManager, CheckerTypesDefManager>();
|
||||
services.AddSingleton<IFlightSimsManager, FlightSimsLiteDb>();
|
||||
services.AddSingleton<IFormFactory, FormFactory>();
|
||||
services.AddSingleton<IAppStatusManager, AppStatusManager>();
|
||||
services.AddSingleton<INotifyIconService, NotifyIconService>();
|
||||
|
||||
services.AddTransient<MainForm>();
|
||||
services.AddTransient<AppDetailsForm>();
|
||||
services.AddTransient<VersionChecker>();
|
||||
});
|
||||
|
||||
using var host = builder.Build();
|
||||
|
||||
var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
|
||||
lifetime.ApplicationStarted.Register(() =>
|
||||
{
|
||||
Console.WriteLine("Application is shutting down...");
|
||||
});
|
||||
|
||||
Application.SetHighDpiMode(HighDpiMode.SystemAware);
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
if (!SystemTray.IsSupported())
|
||||
{
|
||||
MessageBox.Show("System tray not supported", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var serviceProvider = host.Services;
|
||||
var configForm = serviceProvider.GetService<MainForm>();
|
||||
var versionChecker = serviceProvider.GetService<VersionChecker>();
|
||||
var notifyIconService = serviceProvider.GetRequiredService<INotifyIconService>();
|
||||
var configManager = serviceProvider.GetRequiredService<IConfigManager>();
|
||||
var config = configManager.GetConfig();
|
||||
|
||||
EventHandler openFormHandler = (s, e) =>
|
||||
{
|
||||
if (configForm == null) return;
|
||||
|
||||
configForm.UpdateListView();
|
||||
if (configForm.Visible)
|
||||
{
|
||||
configForm.BringToFront();
|
||||
return;
|
||||
}
|
||||
configForm.ShowDialog();
|
||||
};
|
||||
|
||||
using (var notifyIcon = new NotifyIcon())
|
||||
{
|
||||
notifyIcon.Icon = new Icon("Resources/MVC-Icon.ico"); // Place MVC-Icon.ico in Resources
|
||||
notifyIcon.Text = "Update Checker";
|
||||
notifyIcon.Visible = true;
|
||||
notifyIconService.SetNotifyIcon(notifyIcon);
|
||||
|
||||
var contextMenu = new ContextMenuStrip();
|
||||
contextMenu.Items.Add("Configure", null, openFormHandler);
|
||||
contextMenu.Items.Add("Exit", null, (s, e) => Application.Exit());
|
||||
notifyIcon.ContextMenuStrip = contextMenu;
|
||||
|
||||
notifyIcon.DoubleClick += openFormHandler;
|
||||
|
||||
bool checkOnInitialStart = config.CheckOnStartup;
|
||||
if (checkOnInitialStart && versionChecker != null)
|
||||
{
|
||||
versionChecker.StartVersionChecking(notifyIcon);
|
||||
versionChecker.OnFinished += (s, e) => {
|
||||
if (configForm != null)
|
||||
{
|
||||
if (configForm.InvokeRequired)
|
||||
{
|
||||
configForm.Invoke(() => configForm.UpdateListView());
|
||||
}
|
||||
else
|
||||
{
|
||||
configForm.UpdateListView();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (versionChecker != null)
|
||||
{
|
||||
|
||||
if (configForm != null)
|
||||
{
|
||||
configForm.OnRecheck += (s, e) =>
|
||||
{
|
||||
if (versionChecker != null)
|
||||
{
|
||||
versionChecker.CheckAsync();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add to startup
|
||||
// AddToStartup();
|
||||
|
||||
Application.Run(); // Keep app running for tray icon
|
||||
}
|
||||
|
||||
host.RunAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
static void AddToStartup()
|
||||
{
|
||||
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true))
|
||||
{
|
||||
key?.SetValue("XintanalabsUpdateChecker", $"\"{Application.ExecutablePath}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class SystemTray
|
||||
{
|
||||
public static bool IsSupported() => System.Windows.Forms.SystemInformation.TerminalServerSession == false;
|
||||
}
|
||||
}
|
58
ModVersionChecker/ModVersionChecker.csproj
Normal file
58
ModVersionChecker/ModVersionChecker.csproj
Normal file
@@ -0,0 +1,58 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="HtmlAgilityPack.CssSelectors" Version="1.0.2" />
|
||||
<PackageReference Include="LiteDB" Version="5.0.21" />
|
||||
<PackageReference Include="LiteDB.Async" Version="0.1.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
|
||||
<PackageReference Include="NuGet.Versioning" Version="6.14.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="4.35.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="139.0.7258.6800" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Text.Json" Version="9.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="data\apps - Copy.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\apps.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\checkerTypesDef.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\config.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\sourcesDef.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\error-icon.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\MVC-Icon.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\ok-icon.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Resources\up-icon.ico">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="database\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
BIN
ModVersionChecker/Resources/MVC-Icon.ico
Normal file
BIN
ModVersionChecker/Resources/MVC-Icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
ModVersionChecker/Resources/error-icon.ico
Normal file
BIN
ModVersionChecker/Resources/error-icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 104 KiB |
BIN
ModVersionChecker/Resources/ok-icon.ico
Normal file
BIN
ModVersionChecker/Resources/ok-icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
BIN
ModVersionChecker/Resources/up-icon.ico
Normal file
BIN
ModVersionChecker/Resources/up-icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 102 KiB |
145
ModVersionChecker/VersionChecker.cs
Normal file
145
ModVersionChecker/VersionChecker.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.forms;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using ModVersionChecker.utils;
|
||||
using NuGet.Versioning;
|
||||
using OpenQA.Selenium.BiDi.Script;
|
||||
using System.Windows.Forms;
|
||||
|
||||
|
||||
namespace ModVersionChecker
|
||||
{
|
||||
public class VersionChecker
|
||||
{
|
||||
private readonly IConfigManager _configManager;
|
||||
private readonly IAppsManager _appsManager;
|
||||
private readonly ISourcesDefManager _sourcesDefManager;
|
||||
private readonly INotifyIconService _notifyIconService;
|
||||
private readonly IFlightSimsManager _fsManager;
|
||||
private List<string> errorMessages = new List<string>();
|
||||
private List<string> updateMessages = new List<string>();
|
||||
private NotifyIcon? _notifyIcon;
|
||||
|
||||
public event EventHandler<string>? OnFinished;
|
||||
|
||||
public VersionChecker(
|
||||
IConfigManager configManager,
|
||||
IAppsManager appsManager,
|
||||
ISourcesDefManager sourcesDefManager,
|
||||
INotifyIconService notifyIconService,
|
||||
IFlightSimsManager fsManager)
|
||||
{
|
||||
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
|
||||
_appsManager = appsManager ?? throw new ArgumentNullException(nameof(appsManager));
|
||||
_sourcesDefManager = sourcesDefManager ?? throw new ArgumentNullException(nameof(sourcesDefManager));
|
||||
_notifyIconService = notifyIconService ?? throw new ArgumentNullException(nameof(notifyIconService));
|
||||
_fsManager = fsManager ?? throw new ArgumentNullException(nameof(fsManager));
|
||||
}
|
||||
|
||||
private void HandleAppError(string message, AppConfig app)
|
||||
{
|
||||
errorMessages.Add(message);
|
||||
_appsManager.UpdateStatus(app, AppStatus.Error);
|
||||
}
|
||||
|
||||
public void StartVersionChecking(NotifyIcon notifyIcon)
|
||||
{
|
||||
var config = _configManager.Load() ?? new GlobalConfig();
|
||||
_notifyIcon = notifyIcon ?? throw new ArgumentNullException(nameof(notifyIcon));
|
||||
// Run version checks in a background thread
|
||||
new Thread(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await CheckAsync();
|
||||
|
||||
Thread.Sleep(config.IntervalMinutes * 60 * 1000);
|
||||
}
|
||||
|
||||
})
|
||||
{ IsBackground = true }.Start();
|
||||
}
|
||||
|
||||
public async Task CheckAsync()
|
||||
{
|
||||
var config = _configManager.Load() ?? new GlobalConfig();
|
||||
var apps = _appsManager.Load() ?? new List<AppConfig>();
|
||||
var sources = _sourcesDefManager.List() ?? new List<SourceDef>();
|
||||
var fsMods = _fsManager.Load() ?? new List<FsModPathConfig>();
|
||||
|
||||
updateMessages = new List<string>();
|
||||
errorMessages = new List<string>();
|
||||
|
||||
foreach (AppConfig app in apps)
|
||||
{
|
||||
if (app.Status != AppStatus.Error && app.LastCheckedAt != 0 && app.LastCheckedAt < TimeUtils.GetUnixTimeMillis(DateTime.Now.AddMinutes(-60)))
|
||||
continue;
|
||||
|
||||
var status = AppStatus.None;
|
||||
var sourceId = app.Source;
|
||||
if (string.IsNullOrWhiteSpace(sourceId))
|
||||
{
|
||||
HandleAppError($"{app.Name} has no source configured.", app);
|
||||
continue;
|
||||
}
|
||||
var source = sources.FirstOrDefault(s => s.Id == sourceId);
|
||||
if (source == null)
|
||||
{
|
||||
HandleAppError($"{app.Name} has an invalid source: {sourceId}", app);
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
foreach (var fsVersion in app.MsfsVersions)
|
||||
{
|
||||
var fsConfig = _fsManager.GetByShortName(fsVersion);
|
||||
if (fsConfig == null)
|
||||
{
|
||||
HandleAppError($"{app.Name} has no FS mod path configured for version {fsVersion}.", app);
|
||||
continue;
|
||||
}
|
||||
var checker = CheckerFactory.CreateChecker(source.Type);
|
||||
var current = NuGetVersion.Parse(VersionUtils.GetCurrentVersion(app, fsConfig));
|
||||
var latest = NuGetVersion.Parse(await checker.GetLatestVersion(app.Params, source));
|
||||
|
||||
app.CurrentVersion = current.ToString();
|
||||
app.LatestVersion = latest.ToString();
|
||||
|
||||
if (latest.CompareTo(current) == 1)
|
||||
{
|
||||
updateMessages.Add($"{app.Name}: New version {latest} (current: {current})");
|
||||
status = AppStatus.UpdateAvailable;
|
||||
}
|
||||
}
|
||||
_appsManager.UpdateStatus(app, status);
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
HandleAppError($"Failed for {app.Name}: {ex.Message}", app);
|
||||
}
|
||||
}
|
||||
|
||||
if (updateMessages.Count > 0)
|
||||
{
|
||||
_notifyIconService.ShowBalloonTip(
|
||||
10000,
|
||||
"Updates Available",
|
||||
string.Join("\n", updateMessages),
|
||||
ToolTipIcon.Info
|
||||
);
|
||||
}
|
||||
if (errorMessages.Count > 0)
|
||||
{
|
||||
_notifyIconService.ShowBalloonTip(
|
||||
10000,
|
||||
"Errors",
|
||||
string.Join("\n", errorMessages),
|
||||
ToolTipIcon.Error
|
||||
);
|
||||
}
|
||||
OnFinished?.Invoke(this, "Version check completed.");
|
||||
}
|
||||
}
|
||||
}
|
56
ModVersionChecker/checkers/ApiChecker.cs
Normal file
56
ModVersionChecker/checkers/ApiChecker.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker
|
||||
{
|
||||
public class ApiChecker : IVersionChecker
|
||||
{
|
||||
private static readonly HttpClient _httpClient = new HttpClient();
|
||||
|
||||
public async Task<string> GetLatestVersion(Dictionary<string, string> paramsDict, SourceDef source)
|
||||
{
|
||||
if (!paramsDict.TryGetValue("url", out var url) || string.IsNullOrEmpty(url))
|
||||
{
|
||||
throw new ArgumentException("API URL required");
|
||||
}
|
||||
if (!paramsDict.TryGetValue("jsonPath", out var jsonPath) || string.IsNullOrEmpty(jsonPath))
|
||||
{
|
||||
throw new ArgumentException("jsonPath required");
|
||||
}
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw new Exception($"API error: {(int)response.StatusCode} {response.ReasonPhrase}");
|
||||
}
|
||||
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
if (string.IsNullOrEmpty(body))
|
||||
{
|
||||
throw new Exception("Empty API response");
|
||||
}
|
||||
|
||||
using var jsonDoc = JsonDocument.Parse(body);
|
||||
var element = jsonDoc.RootElement;
|
||||
|
||||
foreach (var key in jsonPath.Split('.'))
|
||||
{
|
||||
if (!element.TryGetProperty(key, out var nextElement))
|
||||
{
|
||||
throw new Exception($"JSON key '{key}' not found in response");
|
||||
}
|
||||
element = nextElement;
|
||||
}
|
||||
|
||||
if (element.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
throw new Exception($"JSON value for '{jsonPath}' is not a string");
|
||||
}
|
||||
|
||||
return element.GetString()!.Trim();
|
||||
}
|
||||
}
|
||||
}
|
17
ModVersionChecker/checkers/CheckerFactory.cs
Normal file
17
ModVersionChecker/checkers/CheckerFactory.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace ModVersionChecker
|
||||
{
|
||||
public static class CheckerFactory
|
||||
{
|
||||
public static IVersionChecker CreateChecker(string type)
|
||||
{
|
||||
string[] parts = type.Split(':');
|
||||
|
||||
return parts[0].ToLower() switch
|
||||
{
|
||||
"scrape" => new ScrapeChecker(),
|
||||
"api" => new ApiChecker(),
|
||||
_ => throw new ArgumentException($"Unknown checker type: {type}")
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
94
ModVersionChecker/checkers/ScrapeChecker.cs
Normal file
94
ModVersionChecker/checkers/ScrapeChecker.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using ModVersionChecker.data.model;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
|
||||
namespace ModVersionChecker
|
||||
{
|
||||
public class ScrapeChecker : IVersionChecker
|
||||
{
|
||||
public async Task<string> GetLatestVersion(Dictionary<string, string> paramsDict, SourceDef source)
|
||||
{
|
||||
if (!paramsDict.TryGetValue("url", out var url) || string.IsNullOrEmpty(url))
|
||||
{
|
||||
throw new ArgumentException("URL required");
|
||||
}
|
||||
|
||||
var mode = GetValueOrDefault(paramsDict, "mode", source);
|
||||
var response = "";
|
||||
if (mode == "selenium")
|
||||
{
|
||||
response = await SeleniumFetch(url);
|
||||
}
|
||||
else
|
||||
{
|
||||
response = await DefaultFetch(url); ;
|
||||
}
|
||||
|
||||
|
||||
string pattern = @">\s+<";
|
||||
response = Regex.Replace(response, pattern, "><");
|
||||
var regex = GetValueOrDefault(paramsDict, "regex", source);
|
||||
|
||||
var match = System.Text.RegularExpressions.Regex.Match(response, regex);
|
||||
if (!match.Success || match.Groups.Count < 2)
|
||||
{
|
||||
throw new Exception($"No match with regex in response");
|
||||
}
|
||||
return match.Groups[1].Value;
|
||||
|
||||
}
|
||||
|
||||
private string GetValueOrDefault(Dictionary<string, string> dict, string key, SourceDef source)
|
||||
{
|
||||
var value = "";
|
||||
if (dict.ContainsKey(key) && !string.IsNullOrEmpty(dict[key]))
|
||||
{
|
||||
value = dict[key];
|
||||
}
|
||||
else if (source.Defaults != null && source.Defaults.ContainsKey(key))
|
||||
{
|
||||
value = source.Defaults[key];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private Task<string> DefaultFetch(string url)
|
||||
{
|
||||
var httpClient = new HttpClient();
|
||||
httpClient.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
|
||||
httpClient.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||||
httpClient.DefaultRequestHeaders.Add("Accept-Language", "en-US,en;q=0.5");
|
||||
|
||||
return httpClient.GetStringAsync(url);
|
||||
}
|
||||
|
||||
private async Task<string> SeleniumFetch(string url)
|
||||
{
|
||||
var service = ChromeDriverService.CreateDefaultService();
|
||||
service.HideCommandPromptWindow = true;
|
||||
|
||||
var options = new ChromeOptions();
|
||||
options.AddArgument("--headless"); // Run in headless mode
|
||||
options.AddArgument("--disable-gpu");
|
||||
options.AddArgument("--no-sandbox");
|
||||
options.AddArgument("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/91.0.4472.124");
|
||||
|
||||
using var driver = new ChromeDriver(service, options);
|
||||
try
|
||||
{
|
||||
driver.Navigate().GoToUrl(url);
|
||||
// Wait for the page to load
|
||||
await Task.Delay(2000); // Adjust as necessary
|
||||
// Example: Get the page source
|
||||
var pageSource = driver.PageSource;
|
||||
// Close the driver
|
||||
return pageSource;
|
||||
}
|
||||
finally
|
||||
{
|
||||
driver.Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
ModVersionChecker/checkers/VersionChecker.cs
Normal file
9
ModVersionChecker/checkers/VersionChecker.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using ModVersionChecker.data.model;
|
||||
|
||||
namespace ModVersionChecker
|
||||
{
|
||||
public interface IVersionChecker
|
||||
{
|
||||
Task<string> GetLatestVersion(Dictionary<string, string> paramsDict, SourceDef source);
|
||||
}
|
||||
}
|
55
ModVersionChecker/data/apps - Copy.json
Normal file
55
ModVersionChecker/data/apps - Copy.json
Normal file
@@ -0,0 +1,55 @@
|
||||
[
|
||||
{
|
||||
"name": "PMS50 GTN750",
|
||||
"msfsVersions": [ "msfs2024" ],
|
||||
"source": "pms50_gtn750",
|
||||
"params": {
|
||||
"url": "https://pms50.com/msfs/"
|
||||
},
|
||||
"currentVersionConfig": {
|
||||
"package": "pms50-instrument-gtn750"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Teikof SKMZ",
|
||||
"msfsVersions": [ "msfs2024" ],
|
||||
"source": "sim_market",
|
||||
"params": {
|
||||
"url": "https://secure.simmarket.com/teikof-studio-skmz-la-nubia-airport-msfs.phtml"
|
||||
},
|
||||
"currentVersionConfig": {
|
||||
"package": "teikofstudio-airport-skmz-manizales"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "SWS",
|
||||
"msfsVersions": [
|
||||
"msfs2024"
|
||||
],
|
||||
"source": "sws",
|
||||
"params": {
|
||||
"url": "https://simworksstudios.com/product/kodiak-100-series-ii/",
|
||||
"regex": "Current Version: (\\d\u002B\\.\\d\u002B\\.\\d\u002B)"
|
||||
},
|
||||
"currentVersionConfig": {
|
||||
"package": "sws-aircraft-kodiak-wheels",
|
||||
"version": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GSX Pro",
|
||||
"msfsVersions": [
|
||||
"msfs2024"
|
||||
],
|
||||
"source": "custom",
|
||||
"params": {
|
||||
"url": "https://www.fsdreamteam.com/couatl_liveupdate_notes.html",
|
||||
"regex": "<p>Version (\\d\u002B\\.\\d\u002B\\.\\d\u002B) –"
|
||||
},
|
||||
"currentVersionConfig": {
|
||||
"package": "fsdreamteam-gsx-pro",
|
||||
"version": ""
|
||||
}
|
||||
}
|
||||
|
||||
]
|
86
ModVersionChecker/data/apps.json
Normal file
86
ModVersionChecker/data/apps.json
Normal file
@@ -0,0 +1,86 @@
|
||||
[
|
||||
{
|
||||
"id": "8",
|
||||
"name": "PMS50 GTN750",
|
||||
"msfsVersions": [
|
||||
"msfs2024"
|
||||
],
|
||||
"source": "pms50_gtn750",
|
||||
"params": {
|
||||
"url": "https://pms50.com/msfs/",
|
||||
"regex": "Current version: (\\d\u002B\\.\\d\u002B\\.\\d\u002B)"
|
||||
},
|
||||
"fsFields": {
|
||||
"msfs2024": {
|
||||
"package": "pms50-instrument-gtn750"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"name": "Teikof SKMZ",
|
||||
"msfsVersions": [
|
||||
"msfs2024"
|
||||
],
|
||||
"source": "sim_market",
|
||||
"params": {
|
||||
"url": "https://secure.simmarket.com/teikof-studio-skmz-la-nubia-airport-msfs.phtml"
|
||||
},
|
||||
"fsFields": {
|
||||
"msfs2024": {
|
||||
"package": "teikofstudio-airport-skmz-manizales"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"name": "SWS",
|
||||
"msfsVersions": [
|
||||
"msfs2024"
|
||||
],
|
||||
"source": "sws",
|
||||
"params": {
|
||||
"url": "https://simworksstudios.com/product/kodiak-100-series-ii/",
|
||||
"regex": "Current Version: (\\d\u002B\\.\\d\u002B\\.\\d\u002B)"
|
||||
},
|
||||
"fsFields": {
|
||||
"msfs2024": {
|
||||
"package": "sws-aircraft-kodiak-wheels"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4",
|
||||
"name": "GSX Pro",
|
||||
"msfsVersions": [
|
||||
"msfs2024"
|
||||
],
|
||||
"source": "custom",
|
||||
"params": {
|
||||
"url": "https://www.fsdreamteam.com/couatl_liveupdate_notes.html",
|
||||
"regex": "\u003Cp\u003EVersion (\\d\u002B\\.\\d\u002B\\.\\d\u002B) \u2013"
|
||||
},
|
||||
"fsFields": {
|
||||
"msfs2024": {
|
||||
"package": "fsdreamteam-gsx-pro"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5",
|
||||
"name": "Aerostar 600",
|
||||
"msfsVersions": [
|
||||
"msfs2024"
|
||||
],
|
||||
"source": "a2a",
|
||||
"params": {
|
||||
"url": "https://a2asimulations.com/forum/viewforum.php?f=153",
|
||||
"regex": "Accu-Sim Aerostar 600 \u2013 v(\\d\u002B\\.\\d\u002B\\.\\d\u002B)"
|
||||
},
|
||||
"fsFields": {
|
||||
"msfs2024": {
|
||||
"package": "a2a-aircraft-aerostar600"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
33
ModVersionChecker/data/checkerTypesDef.json
Normal file
33
ModVersionChecker/data/checkerTypesDef.json
Normal file
@@ -0,0 +1,33 @@
|
||||
[
|
||||
{
|
||||
"name": "scrape",
|
||||
"params": [
|
||||
{
|
||||
"name": "url",
|
||||
"label": "Url",
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"label": "Regex",
|
||||
"name": "regex",
|
||||
"type": "string"
|
||||
}, {
|
||||
"label": "Mode",
|
||||
"name": "mode",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "api",
|
||||
"params": [
|
||||
{
|
||||
"label": "Url",
|
||||
"name": "url",
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
21
ModVersionChecker/data/config.json
Normal file
21
ModVersionChecker/data/config.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"intervalMinutes": 60,
|
||||
"checkOnStartup": false,
|
||||
"fsModPaths": {
|
||||
"msfs2024": {
|
||||
"path": "I:/Microsoft Flight Simulator 2024/Packages/Community/",
|
||||
"file": "manifest.json",
|
||||
"fileType": "json",
|
||||
"key": "package_version",
|
||||
"fields": [
|
||||
{
|
||||
"name": "package",
|
||||
"label": "Package Name",
|
||||
"type": "string",
|
||||
"control": "directory",
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
51
ModVersionChecker/data/model/AppConfig.cs
Normal file
51
ModVersionChecker/data/model/AppConfig.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class AppConfig
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("msfsVersions")]
|
||||
public List<string> MsfsVersions { get; set; } = new List<string> { "msfs2024" }; // Default to msfs2024
|
||||
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public string Source { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("params")]
|
||||
public Dictionary<string, string> Params { get; set; } = new Dictionary<string, string>();
|
||||
|
||||
[JsonPropertyName("fsFields")]
|
||||
public Dictionary<string, Dictionary<string, string>> FsFields { get; set; } = new Dictionary<string, Dictionary<string, string>>();
|
||||
|
||||
[JsonPropertyName("downloadUrl")]
|
||||
public string DownloadUrl { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("currentVersion")]
|
||||
public string CurrentVersion { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("latestVersion")]
|
||||
public string LatestVersion { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public AppStatus Status { get; set; } = AppStatus.None;
|
||||
|
||||
[JsonPropertyName("createdAt")]
|
||||
public long CreatedAt { get; set; } = 0;
|
||||
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public long UpdatedAt { get; set; } = 0;
|
||||
|
||||
[JsonPropertyName("lastCheckedAt")]
|
||||
public long LastCheckedAt { get; set; } = 0;
|
||||
|
||||
}
|
15
ModVersionChecker/data/model/AppStatus.cs
Normal file
15
ModVersionChecker/data/model/AppStatus.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.data.model
|
||||
{
|
||||
public enum AppStatus
|
||||
{
|
||||
None,
|
||||
UpdateAvailable,
|
||||
Error,
|
||||
}
|
||||
}
|
18
ModVersionChecker/data/model/CheckerTypeDef.cs
Normal file
18
ModVersionChecker/data/model/CheckerTypeDef.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.data.model
|
||||
{
|
||||
public class CheckerTypeDef
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("params")]
|
||||
public List<FieldDef> Params { get; set; } = new List<FieldDef>();
|
||||
}
|
||||
}
|
27
ModVersionChecker/data/model/FieldDef.cs
Normal file
27
ModVersionChecker/data/model/FieldDef.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.data.model
|
||||
{
|
||||
public class FieldDef
|
||||
{
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("label")]
|
||||
public string Label { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("required")]
|
||||
public bool Required { get; set; } = false;
|
||||
|
||||
[JsonPropertyName("control")]
|
||||
public string Control { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
36
ModVersionChecker/data/model/FsModPathConfig.cs
Normal file
36
ModVersionChecker/data/model/FsModPathConfig.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.data.model
|
||||
{
|
||||
public class FsModPathConfig
|
||||
{
|
||||
|
||||
public string Id { get; set; } = String.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("shortName")]
|
||||
public string ShortName { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("file")]
|
||||
public string File { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("fileType")]
|
||||
public string FileType { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("key")]
|
||||
public string Key { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("fields")]
|
||||
public List<FieldDef> Fields { get; set; } = new List<FieldDef>();
|
||||
}
|
||||
}
|
21
ModVersionChecker/data/model/GlobalConfig.cs
Normal file
21
ModVersionChecker/data/model/GlobalConfig.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using LiteDB;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.data.model
|
||||
{
|
||||
public class GlobalConfig
|
||||
{
|
||||
public string Id { get; set; } = String.Empty;
|
||||
|
||||
[JsonPropertyName("intervalMinutes")]
|
||||
public int IntervalMinutes { get; set; } = 60;
|
||||
|
||||
[JsonPropertyName("checkOnStartup")]
|
||||
public bool CheckOnStartup { get; set; } = true;
|
||||
}
|
||||
}
|
24
ModVersionChecker/data/model/SourceDef.cs
Normal file
24
ModVersionChecker/data/model/SourceDef.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.data.model
|
||||
{
|
||||
public class SourceDef
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("defaults")]
|
||||
public Dictionary<string, string> Defaults { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
47
ModVersionChecker/data/sourcesDef.json
Normal file
47
ModVersionChecker/data/sourcesDef.json
Normal file
@@ -0,0 +1,47 @@
|
||||
[
|
||||
{
|
||||
"id": "custom",
|
||||
"name": "Custom Source",
|
||||
"type": "scrape",
|
||||
"defaults": {
|
||||
"regex": "",
|
||||
"url": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sim_market",
|
||||
"name": "Sim Market",
|
||||
"type": "scrape",
|
||||
"defaults": {
|
||||
"regex": "<span class=\"details-card__item-text\">(\\d+\\.\\d+\\.\\d+)<\\/span>",
|
||||
"url": "https://secure.simmarket.com/"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pms50_gtn750",
|
||||
"name": "PMS50 GTN750",
|
||||
"type": "scrape",
|
||||
"defaults": {
|
||||
"url": "https://pms50.com/msfs/",
|
||||
"regex": "Current version: (\\d+\\.\\d+\\.\\d+)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "sws",
|
||||
"name": "SWS",
|
||||
"type": "scrape",
|
||||
"defaults": {
|
||||
"url": "https://simworksstudios.com/product",
|
||||
"regex": "Current Version: (\\d+\\.\\d+\\.\\d+)"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "a2a",
|
||||
"name": "A2A",
|
||||
"type": "scrape",
|
||||
"defaults": {
|
||||
"url": "https://a2asimulations.com/forum/viewtopic.php?f=153",
|
||||
"mode": "selenium"
|
||||
}
|
||||
}
|
||||
]
|
477
ModVersionChecker/forms/AppDetailsForm.cs
Normal file
477
ModVersionChecker/forms/AppDetailsForm.cs
Normal file
@@ -0,0 +1,477 @@
|
||||
using Microsoft.VisualBasic.FileIO;
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window;
|
||||
|
||||
namespace ModVersionChecker.forms
|
||||
{
|
||||
public class AppDetailsForm : Form
|
||||
{
|
||||
private readonly IConfigManager _configManager;
|
||||
private readonly IAppsManager _appsManager;
|
||||
private readonly ISourcesDefManager _sourcesDefManager;
|
||||
private readonly ICheckerTypesDefManager _checkerTypesDefManager;
|
||||
private readonly IFlightSimsManager _flightSimsManager;
|
||||
private readonly GlobalConfig _globalConfig;
|
||||
private int _currentRow;
|
||||
//private string? _appId;
|
||||
private bool _isEditable;
|
||||
//private List<AppConfig> _apps;
|
||||
private List<SourceDef> _sourcesDef;
|
||||
private List<CheckerTypeDef> _checkerTypesDef;
|
||||
|
||||
private TextBox _nameField, _downloadUrlField;
|
||||
private Label _nameLabel, _msfsVersionsLabel, _sourceLabel, _paramsSubtitle, _downloadUrlLabel;
|
||||
private ComboBox _sourceField;
|
||||
private Button _saveButton, _closeButton;
|
||||
private TableLayoutPanel _mainLayout, _paramsPanel, _fsFieldsPanel;
|
||||
private FlowLayoutPanel _buttonsPanel, _fsPanel;
|
||||
private readonly Dictionary<string, TextBox> _paramFields = new Dictionary<string, TextBox>();
|
||||
private readonly Dictionary<string, Dictionary<string, TextBox>> _fsFields = new Dictionary<string, Dictionary<string, TextBox>>();
|
||||
private List<string> _selectedFs = new List<string>();
|
||||
private List<CheckBox> _fsCheckBoxes = new List<CheckBox>();
|
||||
private AppConfig? _currentApp;
|
||||
|
||||
private List<FsModPathConfig> _flightSims;
|
||||
|
||||
public event EventHandler<string> OnAppChanged;
|
||||
|
||||
public AppDetailsForm(
|
||||
IConfigManager configManager,
|
||||
IAppsManager appsManager,
|
||||
ISourcesDefManager sourcesDefManager,
|
||||
ICheckerTypesDefManager checkerTypesDefManager,
|
||||
IFlightSimsManager flightSimsManager
|
||||
)
|
||||
{
|
||||
|
||||
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
|
||||
_appsManager = appsManager ?? throw new ArgumentNullException(nameof(appsManager));
|
||||
_sourcesDefManager = sourcesDefManager ?? throw new ArgumentNullException(nameof(sourcesDefManager));
|
||||
_checkerTypesDefManager = checkerTypesDefManager ?? throw new ArgumentNullException(nameof(checkerTypesDefManager));
|
||||
_flightSimsManager = flightSimsManager ?? throw new ArgumentNullException(nameof(flightSimsManager));
|
||||
|
||||
_flightSims = _flightSimsManager.Load() ?? new List<FsModPathConfig>();
|
||||
|
||||
_globalConfig = _configManager.Load() ?? new GlobalConfig();
|
||||
|
||||
_sourcesDef = _sourcesDefManager.List() ?? new List<SourceDef>();
|
||||
_checkerTypesDef = _checkerTypesDefManager.Load() ?? new List<CheckerTypeDef>();
|
||||
|
||||
|
||||
_selectedFs = _flightSims.Select(sim => sim.ShortName).ToList();
|
||||
|
||||
_mainLayout = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 2,
|
||||
RowCount = 14,
|
||||
ColumnStyles = { new ColumnStyle(SizeType.Absolute, 150), new ColumnStyle(SizeType.Percent, 100) }
|
||||
};
|
||||
|
||||
// App Name
|
||||
_nameLabel = new Label { Text = "Name:" };
|
||||
_nameField = new TextBox { Text = "", Enabled = _isEditable, Width = 300 };
|
||||
// FS Versions
|
||||
_msfsVersionsLabel = new Label { Text = "FS:" };
|
||||
_fsPanel = new FlowLayoutPanel
|
||||
{
|
||||
FlowDirection = FlowDirection.LeftToRight,
|
||||
AutoSize = true,
|
||||
Dock = DockStyle.Fill
|
||||
};
|
||||
//_msfs2020CheckBox = new CheckBox { Text = "MSFS 2020", Enabled = _isEditable };
|
||||
//_msfs2024CheckBox = new CheckBox { Text = "MSFS 2024", Enabled = _isEditable };
|
||||
// Source
|
||||
_sourceLabel = new Label { Text = "Source:" };
|
||||
_sourceField = new ComboBox { Enabled = _isEditable, Width = 300, DropDownStyle = ComboBoxStyle.DropDownList };
|
||||
_sourceField.Items.AddRange(_sourcesDef.Select(sd => sd.Id).ToArray());
|
||||
_sourceField.SelectedIndexChanged += OnSourceFieldIndexChanged;
|
||||
// Parameters
|
||||
_paramsSubtitle = new Label { Text = "SourceParameters:", Font = new System.Drawing.Font(Font, System.Drawing.FontStyle.Bold) };
|
||||
_paramsPanel = new TableLayoutPanel
|
||||
{
|
||||
AutoSize = true,
|
||||
BackColor = Color.White,
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 2,
|
||||
RowCount = 2,
|
||||
ColumnStyles = { new ColumnStyle(SizeType.Absolute, 150), new ColumnStyle(SizeType.Percent, 100) }
|
||||
};
|
||||
// Fs Fields Panel
|
||||
_fsFieldsPanel = new TableLayoutPanel
|
||||
{
|
||||
AutoSize = true,
|
||||
BackColor = Color.White,
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 2,
|
||||
RowCount = 2,
|
||||
ColumnStyles = { new ColumnStyle(SizeType.Absolute, 150), new ColumnStyle(SizeType.Percent, 100) }
|
||||
};
|
||||
|
||||
|
||||
// App Name
|
||||
_downloadUrlLabel = new Label { Text = "Download Url:" };
|
||||
_downloadUrlField = new TextBox { Text = "", Enabled = _isEditable, Width = 300 };
|
||||
|
||||
_buttonsPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, AutoSize = true, Dock = DockStyle.Fill };
|
||||
_saveButton = new Button { Text = "Save", Width = 100 };
|
||||
_closeButton = new Button { Text = "Close", Width = 100 };
|
||||
_saveButton.Click += OnSaveButtonClicked;
|
||||
_closeButton.Click += (s, e) => Close();
|
||||
Controls.Add(_mainLayout);
|
||||
|
||||
Size = new System.Drawing.Size(500, 500);
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
|
||||
InitializeForm();
|
||||
|
||||
}
|
||||
|
||||
public void SetApp(AppConfig? app, bool update = true)
|
||||
{
|
||||
_currentApp = app;
|
||||
_selectedFs = _currentApp?.MsfsVersions ?? new List<string>();
|
||||
|
||||
if (update)
|
||||
{
|
||||
UpdateForm();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetEditable(bool isEditable, bool update = true)
|
||||
{
|
||||
_isEditable = isEditable;
|
||||
if (update)
|
||||
{
|
||||
UpdateForm();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateForm()
|
||||
{
|
||||
|
||||
Text = _currentApp == null ? "Add App" : (_isEditable ? "Edit App" : "App Details");
|
||||
|
||||
_nameField.Text = _currentApp != null ? _currentApp.Name : "";
|
||||
_downloadUrlField.Enabled = _nameField.Enabled = _sourceField.Enabled = _isEditable;
|
||||
_downloadUrlField.Text = _currentApp != null ? _currentApp.DownloadUrl : "";
|
||||
|
||||
_flightSims.ForEach(fs =>
|
||||
{
|
||||
if (_currentApp != null && _currentApp.MsfsVersions.Contains(fs.ShortName))
|
||||
{
|
||||
if (!_selectedFs.Contains(fs.ShortName))
|
||||
{
|
||||
_selectedFs.Add(fs.ShortName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (int i = 0; i < _fsCheckBoxes.Count; i++)
|
||||
{
|
||||
var fsKey = _flightSims.FirstOrDefault(f => f.ShortName == _fsCheckBoxes[i].Text)?.ShortName;
|
||||
if (fsKey != null)
|
||||
{
|
||||
_fsCheckBoxes[i].Checked = _currentApp != null && _currentApp.MsfsVersions.Contains(fsKey);
|
||||
}
|
||||
}
|
||||
|
||||
_sourceField.SelectedIndex = _sourceField.Items.IndexOf(_currentApp != null ? _currentApp.Source : "");
|
||||
|
||||
UpdateFsFields();
|
||||
|
||||
UpdateParamFields();
|
||||
}
|
||||
|
||||
private bool isFsSelected(FsModPathConfig fs)
|
||||
{
|
||||
return _selectedFs.Contains(fs.ShortName);
|
||||
}
|
||||
private void UpdateFsFields()
|
||||
{
|
||||
_fsFields.Clear();
|
||||
_fsFieldsPanel.Controls.Clear();
|
||||
|
||||
foreach (var fs in _flightSims)
|
||||
{
|
||||
|
||||
if (fs == null || !isFsSelected(fs))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var fsKey = fs.ShortName;
|
||||
var fieldsDict = new Dictionary<string, TextBox>();
|
||||
_fsFields[fsKey] = fieldsDict;
|
||||
int currentRow = 0;
|
||||
|
||||
Label horizontalSeparator = new Label
|
||||
{
|
||||
Height = 50,
|
||||
Padding = new Padding(10, 0, 0, 0),
|
||||
BackColor = Color.GhostWhite, // Line-like separator
|
||||
Dock = DockStyle.Fill,
|
||||
TextAlign = ContentAlignment.MiddleLeft,
|
||||
Text = fsKey, // Optional: Add text to the separator
|
||||
ForeColor = Color.FromArgb(50, 50, 50) // Text color contrasts with background
|
||||
};
|
||||
|
||||
_fsFieldsPanel.Controls.Add(horizontalSeparator, 0, currentRow);
|
||||
_fsFieldsPanel.SetColumnSpan(horizontalSeparator, 2);
|
||||
currentRow++;
|
||||
|
||||
|
||||
foreach (var field in fs.Fields)
|
||||
{
|
||||
Control control;
|
||||
var value = GetFsFieldValue(fsKey, field.Name);
|
||||
var label = new Label { Text = $"{field.Label} ({(field.Required ? "Required" : "Optional")}):", Width = 100, AutoSize = true };
|
||||
var textBox = new TextBox
|
||||
{
|
||||
Width = 300,
|
||||
Enabled = _isEditable,
|
||||
Text = value
|
||||
};
|
||||
|
||||
switch (field.Control.ToLower())
|
||||
{
|
||||
case "directory":
|
||||
textBox.ReadOnly = true;
|
||||
control = new TableLayoutPanel
|
||||
{
|
||||
AutoSize = true,
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 2,
|
||||
RowCount = 1,
|
||||
ColumnStyles = { new ColumnStyle(SizeType.Percent, 80), new ColumnStyle(SizeType.Percent, 20) }
|
||||
};
|
||||
(control as TableLayoutPanel).Controls.Add(textBox, 0, 0);
|
||||
var browseButton = new Button { Text = "Browse", Width = 80, Enabled = _isEditable };
|
||||
browseButton.Click += (s, e) =>
|
||||
{
|
||||
using (var folderDialog = new FolderBrowserDialog())
|
||||
{
|
||||
folderDialog.Description = $"Select directory for {field.Label}";
|
||||
folderDialog.SelectedPath = textBox.Text == "" ? Path.Combine(fs.Path) : textBox.Text;
|
||||
|
||||
if (folderDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
string selectedDirectory = folderDialog.SelectedPath;
|
||||
string folderName = Path.GetFileName(selectedDirectory);
|
||||
textBox.Text = folderName;
|
||||
}
|
||||
}
|
||||
};
|
||||
(control as TableLayoutPanel).Controls.Add(browseButton, 1, 0);
|
||||
break;
|
||||
default:
|
||||
control = textBox;
|
||||
break;
|
||||
}
|
||||
|
||||
fieldsDict[field.Name] = textBox;
|
||||
|
||||
_fsFieldsPanel.Controls.Add(label, 0, currentRow);
|
||||
_fsFieldsPanel.Controls.Add(control, 1, currentRow);
|
||||
currentRow++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFsFieldValue(string fsKey, string fieldName)
|
||||
{
|
||||
if (_currentApp == null) return "";
|
||||
|
||||
var fsFields = _currentApp.FsFields.ContainsKey(fsKey) ? _currentApp.FsFields[fsKey] : new Dictionary<string, string>();
|
||||
if (fsFields.ContainsKey(fieldName))
|
||||
{
|
||||
return fsFields[fieldName];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private void UpdateParamFields()
|
||||
{
|
||||
if (_sourceField?.SelectedItem == null) return;
|
||||
|
||||
var selectedSource = _sourcesDef.FirstOrDefault(sd => sd.Id == _sourceField.SelectedItem.ToString());
|
||||
if (selectedSource == null) return;
|
||||
|
||||
var checkerType = _checkerTypesDef.FirstOrDefault(ct => ct.Name == selectedSource.Type);
|
||||
if (checkerType == null) return;
|
||||
|
||||
_paramFields.Clear();
|
||||
_paramsPanel.Controls.Clear();
|
||||
|
||||
int currentRow = 0;
|
||||
foreach (var paramDef in checkerType.Params)
|
||||
{
|
||||
var label = new Label { Text = $"{paramDef.Label} ({(paramDef.Required ? "Required" : "Optional")}):", Width = 100, AutoSize = true };
|
||||
var textBox = new TextBox
|
||||
{
|
||||
Width = 300,
|
||||
Enabled = _isEditable,
|
||||
Text = GetParamValue(paramDef.Name, selectedSource)
|
||||
};
|
||||
_paramFields[paramDef.Name] = textBox;
|
||||
|
||||
_paramsPanel.Controls.Add(label, 0, currentRow);
|
||||
_paramsPanel.Controls.Add(textBox, 1, currentRow);
|
||||
currentRow++;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetParamValue(string paramName, SourceDef source)
|
||||
{
|
||||
var valueFromSource = source.Defaults != null && source.Defaults.ContainsKey(paramName) ? source.Defaults[paramName] : "";
|
||||
|
||||
|
||||
if (_currentApp == null || _currentApp.Params == null || !_currentApp.Params.ContainsKey(paramName))
|
||||
return valueFromSource;
|
||||
return _currentApp.Params[paramName];
|
||||
}
|
||||
|
||||
|
||||
private void InitializeForm()
|
||||
{
|
||||
|
||||
|
||||
_currentRow = 0;
|
||||
_mainLayout.Controls.Add(_nameLabel, 0, _currentRow);
|
||||
_mainLayout.Controls.Add(_nameField, 1, _currentRow++);
|
||||
_mainLayout.Controls.Add(_msfsVersionsLabel, 0, _currentRow++);
|
||||
_mainLayout.Controls.Add(_fsPanel, 1, _currentRow);
|
||||
_mainLayout.SetColumnSpan(_fsPanel, 2);
|
||||
_currentRow++;
|
||||
_mainLayout.Controls.Add(_fsFieldsPanel, 0, _currentRow);
|
||||
_mainLayout.SetColumnSpan(_fsFieldsPanel, 2);
|
||||
_currentRow++;
|
||||
|
||||
_mainLayout.Controls.Add(_sourceLabel, 0, _currentRow);
|
||||
_mainLayout.Controls.Add(_sourceField, 1, _currentRow++);
|
||||
_mainLayout.Controls.Add(_paramsSubtitle, 0, _currentRow);
|
||||
_mainLayout.SetColumnSpan(_paramsSubtitle, 2);
|
||||
_currentRow++;
|
||||
_mainLayout.Controls.Add(_paramsPanel, 0, _currentRow);
|
||||
_mainLayout.SetColumnSpan(_paramsPanel, 2);
|
||||
_currentRow++;
|
||||
_mainLayout.Controls.Add(_downloadUrlLabel, 0, _currentRow);
|
||||
_mainLayout.Controls.Add(_downloadUrlField, 1, _currentRow++);
|
||||
_currentRow++;
|
||||
|
||||
|
||||
_mainLayout.Controls.Add(_buttonsPanel, 0, _currentRow++);
|
||||
|
||||
AddFsCheckboxes();
|
||||
AddButtons();
|
||||
|
||||
// UpdateForm();
|
||||
}
|
||||
|
||||
private void AddFsCheckboxes()
|
||||
{
|
||||
foreach (var fs in _flightSims)
|
||||
{
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
Text = fs.ShortName,
|
||||
Checked = _currentApp != null && _currentApp.MsfsVersions.Contains(fs.ShortName),
|
||||
};
|
||||
checkBox.CheckedChanged += (s, e) =>
|
||||
{
|
||||
if (checkBox.Checked)
|
||||
{
|
||||
if (!_selectedFs.Contains(fs.ShortName))
|
||||
{
|
||||
_selectedFs.Add(fs.ShortName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedFs.Remove(fs.ShortName);
|
||||
}
|
||||
UpdateFsFields();
|
||||
};
|
||||
_fsPanel.Controls.Add(checkBox);
|
||||
_fsCheckBoxes.Add(checkBox);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnSourceFieldIndexChanged(object? sender, EventArgs e)
|
||||
{
|
||||
if (_isEditable && _sourceField.SelectedItem != null)
|
||||
{
|
||||
UpdateParamFields();
|
||||
}
|
||||
}
|
||||
|
||||
private void AddButtons()
|
||||
{
|
||||
_buttonsPanel.Controls.Clear();
|
||||
_buttonsPanel.Controls.Add(_saveButton);
|
||||
_buttonsPanel.Controls.Add(_closeButton);
|
||||
}
|
||||
|
||||
private void OnSaveButtonClicked(object? sender, EventArgs e)
|
||||
{
|
||||
try {
|
||||
var paramsDict = _paramFields.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Text.Trim());
|
||||
var fsFieldsDict = _fsFields.ToDictionary(
|
||||
kvp => kvp.Key,
|
||||
kvp => kvp.Value.ToDictionary(fkvp => fkvp.Key, fkvp => fkvp.Value.Text.Trim())
|
||||
);
|
||||
var requiredParams = _checkerTypesDef
|
||||
.First(ct => ct.Name == _sourcesDef.FirstOrDefault(sd => sd.Id == _sourceField.SelectedItem?.ToString())?.Type)
|
||||
.Params.Where(p => p.Required)
|
||||
.Select(p => p.Name);
|
||||
if (requiredParams.Any(rp => string.IsNullOrWhiteSpace(paramsDict[rp])))
|
||||
{
|
||||
throw new Exception("All required parameters must be filled.");
|
||||
}
|
||||
var msfsVersions = _selectedFs;
|
||||
var isNewApp = (_currentApp == null || string.IsNullOrEmpty(_currentApp.Id));
|
||||
var app = new AppConfig
|
||||
{
|
||||
Id = isNewApp ? GetUuid() : _currentApp.Id,
|
||||
Name = _nameField.Text.Trim(),
|
||||
MsfsVersions = msfsVersions,
|
||||
Source = _sourceField.SelectedItem?.ToString() ?? "",
|
||||
Params = paramsDict,
|
||||
FsFields = fsFieldsDict,
|
||||
DownloadUrl = _downloadUrlField.Text.Trim(),
|
||||
CurrentVersion = _currentApp?.CurrentVersion ?? "",
|
||||
LatestVersion = _currentApp?.LatestVersion ?? "",
|
||||
Status = _currentApp?.Status ?? AppStatus.None
|
||||
};
|
||||
|
||||
if (isNewApp)
|
||||
{
|
||||
_appsManager.Insert(app);
|
||||
} else
|
||||
{
|
||||
_appsManager.Update(app);
|
||||
}
|
||||
|
||||
_currentApp = app;
|
||||
OnAppChanged?.Invoke(this, "App saved");
|
||||
Close();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Error: {ex.Message}", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetUuid()
|
||||
{
|
||||
Guid uuid = Guid.NewGuid();
|
||||
return uuid.ToString();
|
||||
}
|
||||
}
|
||||
}
|
120
ModVersionChecker/forms/AppDetailsForm.resx
Normal file
120
ModVersionChecker/forms/AppDetailsForm.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
68
ModVersionChecker/forms/FormFactory.cs
Normal file
68
ModVersionChecker/forms/FormFactory.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
|
||||
namespace ModVersionChecker.forms
|
||||
{
|
||||
public class FormFactory : IFormFactory
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public FormFactory(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
}
|
||||
|
||||
public AppDetailsForm CreateAppDetailsForm(AppConfig? app, bool isEditable, EventHandler<string>? onAppChanged)
|
||||
{
|
||||
var configManager = _serviceProvider.GetRequiredService<IConfigManager>();
|
||||
var appsManager = _serviceProvider.GetRequiredService<IAppsManager>();
|
||||
var sourcesDefManager = _serviceProvider.GetRequiredService<ISourcesDefManager>();
|
||||
var checkerTypesDefManager = _serviceProvider.GetRequiredService<ICheckerTypesDefManager>();
|
||||
var flightSimsManager = _serviceProvider.GetRequiredService<IFlightSimsManager>();
|
||||
var form = new AppDetailsForm(configManager, appsManager, sourcesDefManager, checkerTypesDefManager, flightSimsManager);
|
||||
form.SetApp(app, false);
|
||||
form.SetEditable(isEditable);
|
||||
if (onAppChanged != null)
|
||||
{
|
||||
form.OnAppChanged += onAppChanged;
|
||||
}
|
||||
return form;
|
||||
}
|
||||
|
||||
public GlobalConfigForm CreateGlobalConfigForm()
|
||||
{
|
||||
var configManager = _serviceProvider.GetRequiredService<IConfigManager>();
|
||||
var form = new GlobalConfigForm(configManager);
|
||||
return form;
|
||||
}
|
||||
|
||||
public SourcesConfigForm CreateSourcesConfigForm(EventHandler<string>? onSourcesChanged)
|
||||
{
|
||||
var sourcesDefManager = _serviceProvider.GetRequiredService<ISourcesDefManager>();
|
||||
var formFactory = _serviceProvider.GetRequiredService<IFormFactory>();
|
||||
var form = new SourcesConfigForm(formFactory, sourcesDefManager);
|
||||
if (onSourcesChanged != null)
|
||||
{
|
||||
form.OnSourcesChanged += onSourcesChanged;
|
||||
}
|
||||
return form;
|
||||
}
|
||||
|
||||
public SourceDetailForm CreateSourceDetailForm(SourceDef? sourceDef, EventHandler<string>? onSourceChanged)
|
||||
{
|
||||
var sourcesDefManager = _serviceProvider.GetRequiredService<ISourcesDefManager>();
|
||||
var checkerTypesDefManager = _serviceProvider.GetRequiredService<ICheckerTypesDefManager>();
|
||||
var formFactory = _serviceProvider.GetRequiredService<IFormFactory>();
|
||||
var form = new SourceDetailForm(formFactory, sourcesDefManager);
|
||||
form.SourceDef = sourceDef;
|
||||
|
||||
if (onSourceChanged != null)
|
||||
{
|
||||
form.UpdateFields();
|
||||
form.OnSourceChanged += onSourceChanged;
|
||||
}
|
||||
return form;
|
||||
}
|
||||
}
|
||||
}
|
122
ModVersionChecker/forms/GlobalConfigForm.cs
Normal file
122
ModVersionChecker/forms/GlobalConfigForm.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
|
||||
namespace ModVersionChecker.forms
|
||||
{
|
||||
public class GlobalConfigForm : Form
|
||||
{
|
||||
private IConfigManager _configManager;
|
||||
private GlobalConfig _config;
|
||||
|
||||
private Label _millislabel, _checkStartupLabel;
|
||||
private TrackBar _millisField;
|
||||
private CheckBox _checkStartupField;
|
||||
private Button _saveButton, _cancelButton;
|
||||
private TableLayoutPanel _mainLayout, _configsPanel;
|
||||
private FlowLayoutPanel _buttonPanel;
|
||||
|
||||
public GlobalConfigForm(IConfigManager configManager)
|
||||
{
|
||||
_configManager = configManager;
|
||||
_config = _configManager.GetConfig();
|
||||
InitializeComponent();
|
||||
}
|
||||
private void InitializeComponent()
|
||||
{
|
||||
SuspendLayout();
|
||||
|
||||
ClientSize = new System.Drawing.Size(600, 250);
|
||||
Name = "GlobalConfigForm";
|
||||
Text = "Global Configuration";
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
Padding = new Padding(10, 20, 10, 20 );
|
||||
_mainLayout = GetMainLayout();
|
||||
_configsPanel = GetConfigsPanel();
|
||||
|
||||
_buttonPanel = GetButtonsPanel();
|
||||
|
||||
_mainLayout.Controls.Add(_configsPanel, 0, 0);
|
||||
_mainLayout.Controls.Add(_buttonPanel, 0, 1);
|
||||
|
||||
Controls.Add(_mainLayout);
|
||||
|
||||
|
||||
ResumeLayout(false);
|
||||
|
||||
|
||||
}
|
||||
|
||||
private FlowLayoutPanel GetButtonsPanel()
|
||||
{
|
||||
var buttonsPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, AutoSize = true, Dock = DockStyle.Fill };
|
||||
_saveButton = new Button { Text = "Save", AutoSize = true };
|
||||
_saveButton.Click += (sender, e) =>
|
||||
{
|
||||
_config.IntervalMinutes = _millisField.Value;
|
||||
_config.CheckOnStartup = _checkStartupField.Checked;
|
||||
_configManager.Save(_config);
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
};
|
||||
_cancelButton = new Button { Text = "Cancel", AutoSize = true };
|
||||
_cancelButton.Click += (sender, e) =>
|
||||
{
|
||||
DialogResult = DialogResult.Cancel;
|
||||
Close();
|
||||
};
|
||||
buttonsPanel.Controls.Add(_saveButton);
|
||||
buttonsPanel.Controls.Add(_cancelButton);
|
||||
return buttonsPanel;
|
||||
}
|
||||
|
||||
private TableLayoutPanel GetConfigsPanel()
|
||||
{
|
||||
// Initialize the configurations panel
|
||||
var configsPanel = new TableLayoutPanel
|
||||
{
|
||||
AutoSize = true,
|
||||
Dock = DockStyle.Fill,
|
||||
ColumnCount = 2,
|
||||
RowCount = 2,
|
||||
ColumnStyles = { new ColumnStyle(SizeType.Absolute, 150), new ColumnStyle(SizeType.Percent, 100) }
|
||||
};
|
||||
|
||||
_millislabel = new Label { Text = "Millis", Width = 150};
|
||||
_millisField = new TrackBar { Minimum = 0, Maximum = 120, Value= _config.IntervalMinutes, Width = 300, TickStyle = TickStyle.None };
|
||||
FlowLayoutPanel millisPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.LeftToRight, AutoSize = true };
|
||||
Label millisValue = new Label { Text = _millisField.Value.ToString() + " minutes", AutoSize = true, Padding = new Padding(10, 10, 0, 0) };
|
||||
millisPanel.Controls.Add(_millisField);
|
||||
millisPanel.Controls.Add(millisValue);
|
||||
|
||||
_millisField.Scroll += (sender, e) => { millisValue.Text = _millisField.Value.ToString() + " minutes"; };
|
||||
|
||||
_checkStartupLabel = new Label { Text = "Check on Startup:", Width = 150 };
|
||||
_checkStartupField = new CheckBox { Checked = _config.CheckOnStartup };
|
||||
|
||||
configsPanel.Controls.Add(_millislabel, 0, 0);
|
||||
configsPanel.Controls.Add(millisPanel, 1, 0);
|
||||
configsPanel.Controls.Add(_checkStartupLabel, 0, 1);
|
||||
configsPanel.Controls.Add(_checkStartupField, 1, 1);
|
||||
|
||||
return configsPanel;
|
||||
}
|
||||
|
||||
private TableLayoutPanel GetMainLayout()
|
||||
{
|
||||
// Initialize the main layout panel
|
||||
var mainLayout = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
RowCount = 2,
|
||||
ColumnCount = 1
|
||||
};
|
||||
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 150)); // Paths panel height
|
||||
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 50)); // Button panel height
|
||||
Controls.Add(mainLayout);
|
||||
return mainLayout;
|
||||
}
|
||||
|
||||
// Add methods and properties for global configuration management here
|
||||
|
||||
}
|
||||
}
|
14
ModVersionChecker/forms/IFormFactory.cs
Normal file
14
ModVersionChecker/forms/IFormFactory.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using ModVersionChecker.data.model;
|
||||
|
||||
namespace ModVersionChecker.forms
|
||||
{
|
||||
public interface IFormFactory
|
||||
{
|
||||
AppDetailsForm CreateAppDetailsForm(AppConfig? app, bool isEditable, EventHandler<string>? onAppChanged);
|
||||
GlobalConfigForm CreateGlobalConfigForm();
|
||||
|
||||
SourcesConfigForm CreateSourcesConfigForm(EventHandler<string>? onSourcesChanged);
|
||||
|
||||
SourceDetailForm CreateSourceDetailForm(SourceDef? sourceDef, EventHandler<string>? onSourceChanged);
|
||||
}
|
||||
}
|
310
ModVersionChecker/forms/MainForm.cs
Normal file
310
ModVersionChecker/forms/MainForm.cs
Normal file
@@ -0,0 +1,310 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using ModVersionChecker.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Intrinsics.Arm;
|
||||
using System.Windows.Forms;
|
||||
|
||||
|
||||
namespace ModVersionChecker.forms
|
||||
{
|
||||
public class MainForm : Form
|
||||
{
|
||||
private readonly IConfigManager _configManager;
|
||||
private readonly IAppsManager _appsManager;
|
||||
private readonly IFormFactory _formFactory;
|
||||
private readonly IAppStatusManager _appStatusManager;
|
||||
private readonly IFlightSimsManager _fsManager;
|
||||
private readonly TableLayoutPanel _mainLayout;
|
||||
|
||||
private readonly GlobalConfig _globalConfig;
|
||||
private List<AppConfig> _apps = new List<AppConfig>();
|
||||
private ListView _listView;
|
||||
private ImageList _statusImageList = new ImageList();
|
||||
|
||||
public event EventHandler<EventArgs> OnConfigChanged;
|
||||
public event EventHandler<string> OnRecheck;
|
||||
private EventHandler<string> onAppChangedHandler;
|
||||
private MenuStrip _menuStrip;
|
||||
private List<FsModPathConfig> _fsMods;
|
||||
private readonly Dictionary<string, TextBox> _fsModPathTextBoxes = new Dictionary<string, TextBox>();
|
||||
|
||||
public MainForm(IConfigManager configManager, IAppsManager appsManager, IFormFactory formFactory, IAppStatusManager appStatusManager, IFlightSimsManager fsManager)
|
||||
{
|
||||
_configManager = configManager ?? throw new ArgumentNullException(nameof(configManager));
|
||||
_appsManager = appsManager ?? throw new ArgumentNullException(nameof(appsManager));
|
||||
_formFactory = formFactory ?? throw new ArgumentNullException(nameof(formFactory));
|
||||
_appStatusManager = appStatusManager ?? throw new ArgumentNullException(nameof(appStatusManager));
|
||||
_fsManager = fsManager ?? throw new ArgumentNullException(nameof(fsManager));
|
||||
_fsMods = _fsManager.Load();
|
||||
|
||||
_statusImageList.Images.Add("none", new Icon("Resources/ok-icon.ico"));
|
||||
_statusImageList.Images.Add("update", new Icon("Resources/up-icon.ico"));
|
||||
_statusImageList.Images.Add("error", new Icon("Resources/error-icon.ico"));
|
||||
|
||||
Text = "Update Checker Configuration";
|
||||
Size = new Size(600, 800);
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
|
||||
_globalConfig = configManager.Load() ?? new GlobalConfig();
|
||||
|
||||
_mainLayout = GetMainLayout();
|
||||
|
||||
_mainLayout.Controls.Add(GetPathsPanel(), 0, 0);
|
||||
|
||||
_listView = GetListView();
|
||||
_listView.SmallImageList = _statusImageList;
|
||||
|
||||
_mainLayout.Controls.Add(_listView , 0, 1);
|
||||
|
||||
_mainLayout.Controls.Add(GetButtonsPanel(), 0, 2);
|
||||
|
||||
onAppChangedHandler = (s2, e) =>
|
||||
{
|
||||
UpdateListView();
|
||||
OnConfigChanged?.Invoke(this, EventArgs.Empty);
|
||||
};
|
||||
|
||||
InitializeMenu();
|
||||
}
|
||||
|
||||
private TableLayoutPanel GetMainLayout()
|
||||
{
|
||||
// Initialize the main layout panel
|
||||
var mainLayout = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
RowCount = 3,
|
||||
ColumnCount = 1
|
||||
};
|
||||
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 150)); // Paths panel height
|
||||
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 70)); // ListView takes remaining space
|
||||
mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 50)); // Button panel height
|
||||
Controls.Add(mainLayout);
|
||||
return mainLayout;
|
||||
}
|
||||
|
||||
private FlowLayoutPanel GetPathsPanel()
|
||||
{
|
||||
var pathPanel = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.TopDown };
|
||||
|
||||
foreach (var fsMod in _fsMods)
|
||||
{
|
||||
var singlePathPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.LeftToRight, AutoSize = true };
|
||||
singlePathPanel.Controls.Add(new Label { Text = $"{fsMod.Name} Path:", Width = 100 });
|
||||
var pathField = new TextBox { Text = fsMod.Path ?? "", Width = 300 };
|
||||
singlePathPanel.Controls.Add(pathField);
|
||||
_fsModPathTextBoxes.Add(fsMod.ShortName, pathField);
|
||||
pathPanel.Controls.Add(singlePathPanel);
|
||||
}
|
||||
|
||||
//var pathPanel2024 = new FlowLayoutPanel { FlowDirection = FlowDirection.LeftToRight, AutoSize = true };
|
||||
//pathPanel2024.Controls.Add(new Label { Text = "MSFS 2024 Path:", Width = 100 });
|
||||
//var msfs2024PathField = new TextBox { Text = _globalConfig.FsModPaths.ContainsKey("msfs2024") ? _globalConfig.FsModPaths["msfs2024"].Path : "", Width = 300 };
|
||||
//pathPanel2024.Controls.Add(msfs2024PathField);
|
||||
//pathPanel.Controls.Add(pathPanel2024);
|
||||
|
||||
var savePathsButton = new Button { Text = "Save Paths" };
|
||||
savePathsButton.Click += (s, e) =>
|
||||
{
|
||||
foreach (var fsMod in _fsMods)
|
||||
{
|
||||
fsMod.Path = _fsModPathTextBoxes[fsMod.ShortName].Text;
|
||||
_fsManager.Save(fsMod);
|
||||
}
|
||||
_fsMods = _fsManager.Load();
|
||||
};
|
||||
pathPanel.Controls.Add(savePathsButton);
|
||||
return pathPanel;
|
||||
}
|
||||
|
||||
private ListView GetListView()
|
||||
{
|
||||
var listView = new ListView
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
View = View.Details,
|
||||
FullRowSelect = true,
|
||||
MultiSelect = false,
|
||||
Visible = true,
|
||||
Sorting = SortOrder.Ascending
|
||||
};
|
||||
|
||||
listView.Columns.Add("Name", 150);
|
||||
listView.Columns.Add("MSFS Versions", 100);
|
||||
listView.Columns.Add("Current", 80);
|
||||
listView.Columns.Add("Latest", 80);
|
||||
listView.Columns.Add("Last Checked", 150);
|
||||
|
||||
listView.DoubleClick += (s, e) =>
|
||||
{
|
||||
if (listView.SelectedItems.Count > 0)
|
||||
{
|
||||
ListViewItem selectedItem = listView.SelectedItems[0];
|
||||
AppConfig? app = selectedItem.Tag as AppConfig;
|
||||
if (app == null) return;
|
||||
if (_appStatusManager.GetAppStatus(app.Id) == AppStatus.UpdateAvailable)
|
||||
{
|
||||
if (string.IsNullOrEmpty(app.DownloadUrl))
|
||||
{
|
||||
MessageBox.Show("No download URL specified for this app.");
|
||||
return;
|
||||
}
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = app.DownloadUrl,
|
||||
UseShellExecute = true
|
||||
});
|
||||
} else
|
||||
{
|
||||
var form = _formFactory.CreateAppDetailsForm(app, true, onAppChangedHandler);
|
||||
form.FormClosed += (s2, e) =>
|
||||
{
|
||||
UpdateListView();
|
||||
};
|
||||
UpdateListView();
|
||||
form.ShowDialog();
|
||||
form.BringToFront();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return listView;
|
||||
}
|
||||
|
||||
private FlowLayoutPanel GetButtonsPanel() {
|
||||
var buttonPanel = new FlowLayoutPanel { Dock = DockStyle.Fill };
|
||||
var addButton = new Button { Text = "Add App" };
|
||||
var editButton = new Button { Text = "Edit App", Enabled = false };
|
||||
var deleteButton = new Button { Text = "Delete App", Enabled = false };
|
||||
var recheckButton = new Button { Text = "Recheck Versions" };
|
||||
|
||||
addButton.Click += (s, e) =>
|
||||
{
|
||||
var form = _formFactory.CreateAppDetailsForm(null, true, onAppChangedHandler); // Use factory
|
||||
form.ShowDialog();
|
||||
};
|
||||
editButton.Click += (s, e) =>
|
||||
{
|
||||
if (_listView.SelectedItems.Count > 0)
|
||||
{
|
||||
ListViewItem selectedItem = _listView.SelectedItems[0];
|
||||
AppConfig app = selectedItem.Tag as AppConfig;
|
||||
var form = _formFactory.CreateAppDetailsForm(app, true, onAppChangedHandler); // Use factory
|
||||
form.ShowDialog();
|
||||
|
||||
}
|
||||
};
|
||||
deleteButton.Click += (s, e) =>
|
||||
{
|
||||
if (_listView.SelectedItems.Count > 0 && _listView.SelectedItems[0].Tag != null)
|
||||
{
|
||||
_appsManager.Delete((_listView.SelectedItems[0].Tag as AppConfig).Id);
|
||||
UpdateListView();
|
||||
OnConfigChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
};
|
||||
_listView.SelectedIndexChanged += (s, e) =>
|
||||
{
|
||||
editButton.Enabled = deleteButton.Enabled = _listView.SelectedItems.Count > 0;
|
||||
};
|
||||
|
||||
// Add recheck logic here
|
||||
recheckButton.Click += async (s, e) =>
|
||||
{
|
||||
recheckButton.Enabled = false;
|
||||
OnRecheck.Invoke(this, "User initiated recheck from ConfigForm");
|
||||
recheckButton.Enabled = true;
|
||||
};
|
||||
|
||||
buttonPanel.Controls.AddRange(new[] { addButton, editButton, deleteButton, recheckButton });
|
||||
return buttonPanel;
|
||||
}
|
||||
|
||||
public void UpdateListView()
|
||||
{
|
||||
_apps = _appsManager.Load();
|
||||
_listView.Items.Clear();
|
||||
foreach (var app in _apps)
|
||||
{
|
||||
var item = new ListViewItem(app.Name);
|
||||
item.Tag = app;
|
||||
item.SubItems.Add(string.Join(", ", app.MsfsVersions));
|
||||
try
|
||||
{
|
||||
var fsMod = _fsMods.FirstOrDefault(fs => fs.ShortName == "msfs2024");
|
||||
// Pass the FsModPathConfig object directly, not its Path property
|
||||
var currentVersion = app.CurrentVersion;
|
||||
var latestVersion = app.LatestVersion;
|
||||
var lastChecked = TimeUtils.ToFriendlyTime(app.LastCheckedAt);
|
||||
item.SubItems.Add(currentVersion);
|
||||
item.SubItems.Add(latestVersion);
|
||||
item.SubItems.Add(lastChecked);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
item.SubItems.Add($"Error: {ex.Message}");
|
||||
}
|
||||
switch (_appStatusManager.GetAppStatus(app.Id))
|
||||
{
|
||||
case AppStatus.UpdateAvailable:
|
||||
item.ImageKey = "update";
|
||||
break;
|
||||
case AppStatus.Error:
|
||||
item.ImageKey = "error";
|
||||
break;
|
||||
default:
|
||||
item.ImageKey = "none";
|
||||
break;
|
||||
}
|
||||
_listView.Items.Add(item);
|
||||
}
|
||||
Console.WriteLine($"UpdateListView item count: {_listView.Items.Count}");
|
||||
}
|
||||
|
||||
private void InitializeMenu()
|
||||
{
|
||||
_menuStrip = new MenuStrip();
|
||||
|
||||
// Create top-level menu
|
||||
var configMenu = new ToolStripMenuItem("Configuration");
|
||||
|
||||
// Add sub-menu items
|
||||
var globalConfigItem = new ToolStripMenuItem("Global Settings");
|
||||
globalConfigItem.Click += (s, e) => ShowGlobalConfigDialog();
|
||||
|
||||
var sourcesConfigItem = new ToolStripMenuItem("Sources");
|
||||
sourcesConfigItem.Click += (s, e) => ShowSourcesConfigDialog();
|
||||
|
||||
var FlightSimsConfigItem = new ToolStripMenuItem("Flight Sims");
|
||||
FlightSimsConfigItem.Click += (s, e) => MessageBox.Show("Flight Sims configuration dialog would open here.");
|
||||
|
||||
configMenu.DropDownItems.Add(globalConfigItem);
|
||||
configMenu.DropDownItems.Add(sourcesConfigItem);
|
||||
configMenu.DropDownItems.Add(FlightSimsConfigItem);
|
||||
|
||||
_menuStrip.Items.Add(configMenu);
|
||||
|
||||
// Add the menu to the form
|
||||
Controls.Add(_menuStrip);
|
||||
MainMenuStrip = _menuStrip;
|
||||
}
|
||||
|
||||
private void ShowGlobalConfigDialog()
|
||||
{
|
||||
// Show your global config form/dialog here
|
||||
var globalConfigForm = _formFactory.CreateGlobalConfigForm();
|
||||
globalConfigForm.ShowDialog();
|
||||
}
|
||||
|
||||
private void ShowSourcesConfigDialog()
|
||||
{
|
||||
EventHandler<string> onSourcesChanged = (s, e) => MessageBox.Show("Sources Changed");
|
||||
var form = _formFactory.CreateSourcesConfigForm(onSourcesChanged);
|
||||
form.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
120
ModVersionChecker/forms/MainForm.resx
Normal file
120
ModVersionChecker/forms/MainForm.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
110
ModVersionChecker/forms/SourceDetailForm.cs
Normal file
110
ModVersionChecker/forms/SourceDetailForm.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.forms
|
||||
{
|
||||
// Simple editor form for SourceDef
|
||||
public class SourceDetailForm : Form
|
||||
{
|
||||
private readonly IFormFactory _formFactory;
|
||||
private readonly ISourcesDefManager _sourceManager;
|
||||
public SourceDef SourceDef { get; set; }
|
||||
public Boolean IsEditable => !string.IsNullOrWhiteSpace(SourceDef?.Id);
|
||||
private TextBox _idField, _nameField, _typeField, _defaultsField;
|
||||
private Button _okButton, _cancelButton;
|
||||
|
||||
public event EventHandler<string>? OnSourceChanged;
|
||||
|
||||
public SourceDetailForm(IFormFactory formFactory, ISourcesDefManager sourceManager)
|
||||
{
|
||||
_formFactory = formFactory ?? throw new ArgumentNullException(nameof(formFactory));
|
||||
_sourceManager = sourceManager ?? throw new ArgumentNullException(nameof(sourceManager));
|
||||
|
||||
InitializeComponent();
|
||||
_formFactory = formFactory;
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
Text = "Edit SourceDef";
|
||||
Size = new Size(400, 300);
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
Padding = new Padding(10);
|
||||
|
||||
var layout = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
RowCount = 5,
|
||||
ColumnCount = 2,
|
||||
Padding = new Padding(10)
|
||||
};
|
||||
|
||||
layout.Controls.Add(new Label { Text = "Id:", Width = 80 }, 0, 0);
|
||||
_idField = new TextBox { Text = "", Width = 200 };
|
||||
layout.Controls.Add(_idField, 1, 0);
|
||||
|
||||
layout.Controls.Add(new Label { Text = "Name:", Width = 80 }, 0, 1);
|
||||
_nameField = new TextBox { Text = "", Width = 200 };
|
||||
layout.Controls.Add(_nameField, 1, 1);
|
||||
|
||||
layout.Controls.Add(new Label { Text = "Type:", Width = 80 }, 0, 2);
|
||||
_typeField = new TextBox { Text = "", Width = 200 };
|
||||
layout.Controls.Add(_typeField, 1, 2);
|
||||
|
||||
layout.Controls.Add(new Label { Text = "Defaults (key=value, comma separated):", Width = 80 }, 0, 3);
|
||||
_defaultsField = new TextBox { Text = "", Width = 200 };
|
||||
layout.Controls.Add(_defaultsField, 1, 3);
|
||||
|
||||
var buttonPanel = new FlowLayoutPanel { FlowDirection = FlowDirection.RightToLeft, Dock = DockStyle.Fill };
|
||||
_okButton = new Button { Text = "OK", DialogResult = DialogResult.OK };
|
||||
_cancelButton = new Button { Text = "Cancel", DialogResult = DialogResult.Cancel };
|
||||
buttonPanel.Controls.Add(_okButton);
|
||||
buttonPanel.Controls.Add(_cancelButton);
|
||||
|
||||
layout.Controls.Add(buttonPanel, 0, 4);
|
||||
layout.SetColumnSpan(buttonPanel, 2);
|
||||
|
||||
Controls.Add(layout);
|
||||
|
||||
_okButton.Click += (s, e) =>
|
||||
{
|
||||
SourceDef.Id = _idField.Text.Trim();
|
||||
SourceDef.Name = _nameField.Text.Trim();
|
||||
SourceDef.Type = _typeField.Text.Trim();
|
||||
SourceDef.Defaults = ParseDefaults(_defaultsField.Text);
|
||||
DialogResult = DialogResult.OK;
|
||||
Close();
|
||||
};
|
||||
_cancelButton.Click += (s, e) => { DialogResult = DialogResult.Cancel; Close(); };
|
||||
}
|
||||
|
||||
public void UpdateFields()
|
||||
{
|
||||
if (SourceDef != null)
|
||||
{
|
||||
_idField.Text = SourceDef.Id;
|
||||
_nameField.Text = SourceDef.Name;
|
||||
_typeField.Text = SourceDef.Type;
|
||||
_defaultsField.Text = string.Join(", ", SourceDef.Defaults.Select(d => $"{d.Key}={d.Value}"));
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ParseDefaults(string text)
|
||||
{
|
||||
var dict = new Dictionary<string, string>();
|
||||
var pairs = text.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
var kv = pair.Split(new[] { '=' }, 2);
|
||||
if (kv.Length == 2)
|
||||
dict[kv[0].Trim()] = kv[1].Trim();
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
}
|
||||
}
|
136
ModVersionChecker/forms/SourcesConfigForm.cs
Normal file
136
ModVersionChecker/forms/SourcesConfigForm.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ModVersionChecker.forms
|
||||
{
|
||||
public class SourcesConfigForm : Form
|
||||
{
|
||||
private List<SourceDef> _sourceDefs;
|
||||
private ListView _listView;
|
||||
private Button _addButton, _editButton, _deleteButton, _closeButton;
|
||||
private TableLayoutPanel _mainLayout;
|
||||
private readonly ISourcesDefManager _sourcesManager;
|
||||
private readonly IFormFactory _formFactory;
|
||||
public event EventHandler<string>? OnSourcesChanged;
|
||||
public List<SourceDef> SourceDefs => _sourceDefs;
|
||||
|
||||
public SourcesConfigForm(IFormFactory formFactory, ISourcesDefManager sourcesManager)
|
||||
{
|
||||
_sourcesManager = sourcesManager ?? throw new ArgumentNullException(nameof(sourcesManager));
|
||||
_formFactory = formFactory ?? throw new ArgumentNullException(nameof(formFactory));
|
||||
_sourceDefs = _sourcesManager.List() ?? new List<SourceDef>();
|
||||
Padding = new Padding(20);
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
Text = "Source Definitions";
|
||||
Size = new Size(800, 400);
|
||||
StartPosition = FormStartPosition.CenterParent;
|
||||
|
||||
_mainLayout = new TableLayoutPanel
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
RowCount = 2,
|
||||
ColumnCount = 1,
|
||||
Padding = new Padding(10)
|
||||
};
|
||||
_mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 80));
|
||||
_mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 20));
|
||||
|
||||
_listView = new ListView
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
View = View.Details,
|
||||
FullRowSelect = true,
|
||||
MultiSelect = false,
|
||||
GridLines = true
|
||||
};
|
||||
_listView.Columns.Add("Id", 100);
|
||||
_listView.Columns.Add("Name", 150);
|
||||
_listView.Columns.Add("Type", 100);
|
||||
_listView.Columns.Add("Defaults", -2);
|
||||
|
||||
UpdateListView();
|
||||
|
||||
_mainLayout.Controls.Add(_listView, 0, 0);
|
||||
|
||||
var buttonPanel = new FlowLayoutPanel { Dock = DockStyle.Fill, FlowDirection = FlowDirection.LeftToRight };
|
||||
_addButton = new Button { Text = "Add" };
|
||||
_editButton = new Button { Text = "Edit", Enabled = false };
|
||||
_deleteButton = new Button { Text = "Delete", Enabled = false };
|
||||
_closeButton = new Button { Text = "Close", DialogResult = DialogResult.OK };
|
||||
|
||||
_addButton.Click += (s, e) => AddSourceDef();
|
||||
_editButton.Click += (s, e) => EditSourceDef();
|
||||
_deleteButton.Click += (s, e) => DeleteSourceDef();
|
||||
_closeButton.Click += (s, e) => Close();
|
||||
|
||||
_listView.SelectedIndexChanged += (s, e) =>
|
||||
{
|
||||
bool hasSelection = _listView.SelectedItems.Count > 0;
|
||||
_editButton.Enabled = hasSelection;
|
||||
_deleteButton.Enabled = hasSelection;
|
||||
};
|
||||
|
||||
buttonPanel.Controls.AddRange(new Control[] { _addButton, _editButton, _deleteButton, _closeButton });
|
||||
_mainLayout.Controls.Add(buttonPanel, 0, 1);
|
||||
|
||||
Controls.Add(_mainLayout);
|
||||
}
|
||||
|
||||
private void UpdateListView()
|
||||
{
|
||||
_listView.Items.Clear();
|
||||
foreach (var src in _sourceDefs)
|
||||
{
|
||||
var item = new ListViewItem(src.Id);
|
||||
item.SubItems.Add(src.Name);
|
||||
item.SubItems.Add(src.Type);
|
||||
item.SubItems.Add(string.Join(", ", src.Defaults.Select(d => $"{d.Key}={d.Value}")));
|
||||
item.Tag = src;
|
||||
_listView.Items.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSourceDef()
|
||||
{
|
||||
EventHandler<string>? handler = (s, e) => MessageBox.Show("Source Changed");
|
||||
var editor = _formFactory.CreateSourceDetailForm(null, handler);
|
||||
if (editor.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
_sourceDefs.Add(editor.SourceDef);
|
||||
UpdateListView();
|
||||
}
|
||||
}
|
||||
|
||||
private void EditSourceDef()
|
||||
{
|
||||
if (_listView.SelectedItems.Count == 0) return;
|
||||
var src = _listView.SelectedItems[0].Tag as SourceDef;
|
||||
EventHandler<string>? handler = (s, e) => MessageBox.Show("Source Changed");
|
||||
var editor = _formFactory.CreateSourceDetailForm(src, handler);
|
||||
|
||||
if (editor.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
//int idx = _sourceDefs.IndexOf(src);
|
||||
//_sourceDefs[idx] = editor.SourceDef;
|
||||
//UpdateListView();
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteSourceDef()
|
||||
{
|
||||
if (_listView.SelectedItems.Count == 0) return;
|
||||
var src = _listView.SelectedItems[0].Tag as SourceDef;
|
||||
_sourceDefs.Remove(src);
|
||||
UpdateListView();
|
||||
}
|
||||
}
|
||||
}
|
52
ModVersionChecker/managers/filesystem/AppStatusManager.cs
Normal file
52
ModVersionChecker/managers/filesystem/AppStatusManager.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.filesystem
|
||||
{
|
||||
|
||||
public class AppStatusManager : IAppStatusManager
|
||||
{
|
||||
private Dictionary<string, AppStatus> _statuses = new Dictionary<string, AppStatus>();
|
||||
|
||||
public AppStatusManager() { }
|
||||
public AppStatus? GetAppStatus(string appId)
|
||||
{
|
||||
if (!_statuses.ContainsKey(appId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return _statuses[appId];
|
||||
}
|
||||
public List<AppStatus> Load()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public void Save(List<AppStatus> appStatuses)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public void UpdateAppStatus(string appId, AppStatus appStatus)
|
||||
{
|
||||
if (_statuses.ContainsKey(appId))
|
||||
{
|
||||
_statuses[appId] = appStatus;
|
||||
} else
|
||||
{
|
||||
_statuses.Add(appId, appStatus);
|
||||
}
|
||||
}
|
||||
public bool DeleteAppStatus(string appId) {
|
||||
return _statuses.Remove(appId);
|
||||
}
|
||||
public void ClearAll()
|
||||
{
|
||||
_statuses.Clear();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
42
ModVersionChecker/managers/filesystem/AppsManager.cs
Normal file
42
ModVersionChecker/managers/filesystem/AppsManager.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModVersionChecker.managers.filesystem
|
||||
{
|
||||
public class AppsManager
|
||||
{
|
||||
private readonly string FilePath = Path.Combine(AppContext.BaseDirectory, "data", "apps.json");
|
||||
|
||||
public List<AppConfig> Load()
|
||||
{
|
||||
if (!File.Exists(FilePath))
|
||||
return new List<AppConfig>();
|
||||
var json = File.ReadAllText(FilePath);
|
||||
return JsonSerializer.Deserialize<List<AppConfig>>(json) ?? new();
|
||||
}
|
||||
|
||||
public void Save(List<AppConfig> apps)
|
||||
{
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
var json = JsonSerializer.Serialize(apps, options);
|
||||
File.WriteAllText(FilePath, json);
|
||||
}
|
||||
|
||||
public void Upsert(AppConfig app)
|
||||
{
|
||||
var apps = Load();
|
||||
var index = apps.FindIndex(a => a.Id == app.Id);
|
||||
if (index >= 0)
|
||||
{
|
||||
apps[index] = app;
|
||||
}
|
||||
else
|
||||
{
|
||||
apps.Add(app);
|
||||
}
|
||||
Save(apps);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModVersionChecker.managers.filesystem
|
||||
{
|
||||
public class CheckerTypesDefManager : ICheckerTypesDefManager
|
||||
{
|
||||
private readonly string FilePath = Path.Combine(AppContext.BaseDirectory, "data", "checkerTypesDef.json");
|
||||
|
||||
public List<CheckerTypeDef> Load()
|
||||
{
|
||||
if (!File.Exists(FilePath))
|
||||
return new List<CheckerTypeDef>();
|
||||
var json = File.ReadAllText(FilePath);
|
||||
return JsonSerializer.Deserialize<List<CheckerTypeDef>>(json) ?? new();
|
||||
}
|
||||
|
||||
public void Save(List<CheckerTypeDef> checkerTypesDef)
|
||||
{
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
var json = JsonSerializer.Serialize(checkerTypesDef, options);
|
||||
File.WriteAllText(FilePath, json);
|
||||
}
|
||||
}
|
||||
}
|
40
ModVersionChecker/managers/filesystem/ConfigManager.cs
Normal file
40
ModVersionChecker/managers/filesystem/ConfigManager.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModVersionChecker.managers.filesystem
|
||||
{
|
||||
public class ConfigManager : IConfigManager
|
||||
{
|
||||
|
||||
private static readonly string _filePath = Path.Combine(AppContext.BaseDirectory, "data", "config.json");
|
||||
private GlobalConfig _config;
|
||||
|
||||
public ConfigManager()
|
||||
{
|
||||
_config = Load();
|
||||
}
|
||||
|
||||
public GlobalConfig Load()
|
||||
{
|
||||
if (!File.Exists(_filePath))
|
||||
return new GlobalConfig();
|
||||
var json = File.ReadAllText(_filePath);
|
||||
return JsonSerializer.Deserialize<GlobalConfig>(json) ?? new GlobalConfig();
|
||||
}
|
||||
|
||||
public GlobalConfig GetConfig()
|
||||
{
|
||||
return _config;
|
||||
}
|
||||
|
||||
public void Save(GlobalConfig config)
|
||||
{
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
var json = JsonSerializer.Serialize(config, options);
|
||||
File.WriteAllText(_filePath, json);
|
||||
}
|
||||
}
|
||||
}
|
22
ModVersionChecker/managers/filesystem/NotifyIconService.cs
Normal file
22
ModVersionChecker/managers/filesystem/NotifyIconService.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.filesystem
|
||||
{
|
||||
public class NotifyIconService : INotifyIconService
|
||||
{
|
||||
private NotifyIcon? _notifyIcon;
|
||||
public void SetNotifyIcon(NotifyIcon icon)
|
||||
{
|
||||
_notifyIcon = icon;
|
||||
}
|
||||
public void ShowBalloonTip(int millis, string title, string message, ToolTipIcon icon)
|
||||
{
|
||||
_notifyIcon?.ShowBalloonTip(millis, title, message, icon);
|
||||
}
|
||||
}
|
||||
}
|
52
ModVersionChecker/managers/filesystem/SourcesDefManager.cs
Normal file
52
ModVersionChecker/managers/filesystem/SourcesDefManager.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModVersionChecker.managers.filesystem
|
||||
{
|
||||
public class SourcesDefManager
|
||||
{
|
||||
private readonly string _filePath = Path.Combine(AppContext.BaseDirectory, "data", "sourcesDef.json");
|
||||
private List<SourceDef> _sourcesDef = new List<SourceDef>();
|
||||
|
||||
public SourcesDefManager()
|
||||
{
|
||||
_sourcesDef = Load();
|
||||
}
|
||||
|
||||
private List<SourceDef> Load()
|
||||
{
|
||||
if (!File.Exists(_filePath))
|
||||
return new List<SourceDef>();
|
||||
var json = File.ReadAllText(_filePath);
|
||||
return JsonSerializer.Deserialize<List<SourceDef>>(json) ?? new();
|
||||
}
|
||||
|
||||
public List<SourceDef> GetSourcesDef()
|
||||
{
|
||||
return _sourcesDef;
|
||||
}
|
||||
|
||||
public void AddSourceDef(SourceDef sourceDef)
|
||||
{
|
||||
_sourcesDef.Add(sourceDef);
|
||||
Save(_sourcesDef);
|
||||
}
|
||||
|
||||
public void RemoveSourceDef(string id)
|
||||
{
|
||||
_sourcesDef.RemoveAll(s => s.Id == id);
|
||||
Save(_sourcesDef);
|
||||
}
|
||||
|
||||
|
||||
public void Save(List<SourceDef> sourcesDef)
|
||||
{
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
var json = JsonSerializer.Serialize(sourcesDef, options);
|
||||
File.WriteAllText(_filePath, json);
|
||||
}
|
||||
}
|
||||
}
|
23
ModVersionChecker/managers/interfaces/IAppStatusManager.cs
Normal file
23
ModVersionChecker/managers/interfaces/IAppStatusManager.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.interfaces
|
||||
{
|
||||
public interface IAppStatusManager
|
||||
{
|
||||
List<AppStatus> Load();
|
||||
void Save(List<AppStatus> appStatuses);
|
||||
AppStatus? GetAppStatus(string appId);
|
||||
|
||||
void UpdateAppStatus(string appId, AppStatus appStatus);
|
||||
|
||||
bool DeleteAppStatus(string appId);
|
||||
|
||||
void ClearAll();
|
||||
|
||||
}
|
||||
}
|
26
ModVersionChecker/managers/interfaces/IAppsManager.cs
Normal file
26
ModVersionChecker/managers/interfaces/IAppsManager.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.interfaces
|
||||
{
|
||||
public interface IAppsManager
|
||||
{
|
||||
|
||||
List<AppConfig> Load();
|
||||
|
||||
void Save(List<AppConfig> apps);
|
||||
|
||||
public void Insert(AppConfig app);
|
||||
|
||||
public void Update(AppConfig app);
|
||||
|
||||
void Delete(string id);
|
||||
|
||||
void UpdateStatus(AppConfig app, AppStatus status);
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.interfaces
|
||||
{
|
||||
public interface ICheckerTypesDefManager
|
||||
{
|
||||
List<CheckerTypeDef> Load();
|
||||
void Save(List<CheckerTypeDef> checkerTypesDef);
|
||||
}
|
||||
}
|
16
ModVersionChecker/managers/interfaces/IConfigManager.cs
Normal file
16
ModVersionChecker/managers/interfaces/IConfigManager.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.interfaces
|
||||
{
|
||||
public interface IConfigManager
|
||||
{
|
||||
GlobalConfig Load();
|
||||
void Save(GlobalConfig config);
|
||||
GlobalConfig GetConfig();
|
||||
}
|
||||
}
|
17
ModVersionChecker/managers/interfaces/IFlightSimsManager.cs
Normal file
17
ModVersionChecker/managers/interfaces/IFlightSimsManager.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.interfaces
|
||||
{
|
||||
public interface IFlightSimsManager
|
||||
{
|
||||
List<FsModPathConfig> Load();
|
||||
void Save(FsModPathConfig config);
|
||||
|
||||
FsModPathConfig? GetByShortName(string id);
|
||||
}
|
||||
}
|
14
ModVersionChecker/managers/interfaces/INotifyIconService.cs
Normal file
14
ModVersionChecker/managers/interfaces/INotifyIconService.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.interfaces
|
||||
{
|
||||
public interface INotifyIconService
|
||||
{
|
||||
void SetNotifyIcon(NotifyIcon icon);
|
||||
void ShowBalloonTip(int millis, string title, string message, ToolTipIcon icon);
|
||||
}
|
||||
}
|
19
ModVersionChecker/managers/interfaces/ISourcesDefManager.cs
Normal file
19
ModVersionChecker/managers/interfaces/ISourcesDefManager.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.interfaces
|
||||
{
|
||||
public interface ISourcesDefManager
|
||||
{
|
||||
List<SourceDef> List();
|
||||
|
||||
SourceDef? GetById(string id);
|
||||
void AddSourceDef(SourceDef sourceDef);
|
||||
void RemoveSourceDef(string id);
|
||||
void Save(SourceDef sourceDef);
|
||||
}
|
||||
}
|
70
ModVersionChecker/managers/litedb/AppConfigLiteDb.cs
Normal file
70
ModVersionChecker/managers/litedb/AppConfigLiteDb.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using ModVersionChecker.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.litedb
|
||||
{
|
||||
public class AppConfigLiteDb : LiteDb, IAppsManager
|
||||
{
|
||||
private string collection = LiteDb.APPS_COLLECTION;
|
||||
public List<AppConfig> Load()
|
||||
{
|
||||
var col = _db.GetCollection<AppConfig>(collection);
|
||||
return col.FindAll().ToList();
|
||||
}
|
||||
|
||||
public void Insert(AppConfig app)
|
||||
{
|
||||
var now = TimeUtils.GetUnixTimeMillis(null);
|
||||
app.CreatedAt = now;
|
||||
app.UpdatedAt = now;
|
||||
var col = _db.GetCollection<AppConfig>(collection);
|
||||
col.Insert(app);
|
||||
}
|
||||
|
||||
public void Update(AppConfig app)
|
||||
{
|
||||
var now = TimeUtils.GetUnixTimeMillis(null);
|
||||
app.UpdatedAt = now;
|
||||
var col = _db.GetCollection<AppConfig>(collection);
|
||||
col.Update(app);
|
||||
}
|
||||
|
||||
//public void Upsert(AppConfig app)
|
||||
//{
|
||||
// var now = TimeUtils.GetUnixTimeMillis(null);
|
||||
// app.UpdatedAt = now;
|
||||
// var col = _db.GetCollection<AppConfig>(collection);
|
||||
// if (string.IsNullOrEmpty(app.Id))
|
||||
// {
|
||||
// app.CreatedAt = now;
|
||||
// col.Insert(app);
|
||||
// }
|
||||
// col.Update(app);
|
||||
//}
|
||||
|
||||
public void Delete(string id)
|
||||
{
|
||||
var col = _db.GetCollection<AppConfig>(collection);
|
||||
col.Delete(id);
|
||||
}
|
||||
|
||||
public void Save(List<AppConfig> apps)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void UpdateStatus(AppConfig app, AppStatus status)
|
||||
{
|
||||
app.LastCheckedAt = TimeUtils.GetUnixTimeMillis(null);
|
||||
app.Status = status;
|
||||
var col = _db.GetCollection<AppConfig>(collection);
|
||||
col.Update(app);
|
||||
}
|
||||
}
|
||||
}
|
24
ModVersionChecker/managers/litedb/ConfigLiteDb.cs
Normal file
24
ModVersionChecker/managers/litedb/ConfigLiteDb.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
|
||||
namespace ModVersionChecker.managers.litedb
|
||||
{
|
||||
public class ConfigLiteDb : LiteDb, IConfigManager
|
||||
{
|
||||
private string collection = LiteDb.CONFIG_COLLECTION;
|
||||
public GlobalConfig Load()
|
||||
{
|
||||
var col = _db.GetCollection<GlobalConfig>(collection);
|
||||
return col.FindAll().FirstOrDefault() ?? new GlobalConfig();
|
||||
}
|
||||
public void Save(GlobalConfig config)
|
||||
{
|
||||
var col = _db.GetCollection<GlobalConfig>(collection);
|
||||
col.Upsert(config);
|
||||
}
|
||||
public GlobalConfig GetConfig()
|
||||
{
|
||||
return Load();
|
||||
}
|
||||
}
|
||||
}
|
32
ModVersionChecker/managers/litedb/FlightSimsLiteDb.cs
Normal file
32
ModVersionChecker/managers/litedb/FlightSimsLiteDb.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.litedb
|
||||
{
|
||||
internal class FlightSimsLiteDb : LiteDb, IFlightSimsManager
|
||||
{
|
||||
private string collection = FLIGHT_SIMS_COLLECTION;
|
||||
public List<FsModPathConfig> Load()
|
||||
{
|
||||
var col = _db.GetCollection<FsModPathConfig>(collection);
|
||||
return col.FindAll().ToList();
|
||||
}
|
||||
|
||||
public void Save(FsModPathConfig config)
|
||||
{
|
||||
var col = _db.GetCollection<FsModPathConfig>(collection);
|
||||
col.Upsert(config);
|
||||
}
|
||||
|
||||
public FsModPathConfig? GetByShortName(string id)
|
||||
{
|
||||
var col = _db.GetCollection<FsModPathConfig>(collection);
|
||||
return col.FindOne(x => x.ShortName == id);
|
||||
}
|
||||
}
|
||||
}
|
16
ModVersionChecker/managers/litedb/LiteDb.cs
Normal file
16
ModVersionChecker/managers/litedb/LiteDb.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using LiteDB;
|
||||
|
||||
namespace ModVersionChecker.managers.litedb
|
||||
{
|
||||
public class LiteDb
|
||||
{
|
||||
public static string DB_PATH = "ModVersionChecker.db";
|
||||
public static string APPS_COLLECTION = "apps";
|
||||
public static string CHECKER_TYPES_DEF_COLLECTION = "checker_types_def";
|
||||
public static string SOURCES_DEF_COLLECTION = "sources_def";
|
||||
public static string CONFIG_COLLECTION = "config";
|
||||
public static string FLIGHT_SIMS_COLLECTION = "flight_sims";
|
||||
|
||||
protected LiteDatabase _db = LiteDbSingleton.Instance;
|
||||
}
|
||||
}
|
12
ModVersionChecker/managers/litedb/LiteDbSingleton.cs
Normal file
12
ModVersionChecker/managers/litedb/LiteDbSingleton.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using LiteDB;
|
||||
|
||||
public static class LiteDbSingleton
|
||||
{
|
||||
private static readonly LiteDatabase _db = new LiteDatabase(new ConnectionString
|
||||
{
|
||||
Filename = "ModVersionChecker.db",
|
||||
Connection = ConnectionType.Shared
|
||||
});
|
||||
|
||||
public static LiteDatabase Instance => _db;
|
||||
}
|
43
ModVersionChecker/managers/litedb/SourcesLiteDb.cs
Normal file
43
ModVersionChecker/managers/litedb/SourcesLiteDb.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using ModVersionChecker.managers.interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.managers.litedb
|
||||
{
|
||||
public class SourcesLiteDb : LiteDb, ISourcesDefManager
|
||||
{
|
||||
private string collection = SOURCES_DEF_COLLECTION;
|
||||
public List<SourceDef> List()
|
||||
{
|
||||
var col = _db.GetCollection<SourceDef>(collection);
|
||||
return col.FindAll().ToList();
|
||||
}
|
||||
|
||||
public SourceDef? GetById(string id)
|
||||
{
|
||||
var col = _db.GetCollection<SourceDef>(collection);
|
||||
return col.FindOne(x => x.Id == id);
|
||||
}
|
||||
|
||||
public void AddSourceDef(SourceDef sourceDef)
|
||||
{
|
||||
var col = _db.GetCollection<SourceDef>(collection);
|
||||
col.Insert(sourceDef);
|
||||
}
|
||||
public void RemoveSourceDef(string id)
|
||||
{
|
||||
var col = _db.GetCollection<SourceDef>(collection);
|
||||
col.Delete(id);
|
||||
}
|
||||
|
||||
public void Save(SourceDef sourceDef)
|
||||
{
|
||||
var col = _db.GetCollection<SourceDef>(collection);
|
||||
col.Upsert(sourceDef);
|
||||
}
|
||||
}
|
||||
}
|
54
ModVersionChecker/utils/TimeUtils.cs
Normal file
54
ModVersionChecker/utils/TimeUtils.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ModVersionChecker.utils
|
||||
{
|
||||
public class TimeUtils
|
||||
{
|
||||
public static long GetUnixTimeMillis(DateTime? dateTime)
|
||||
{
|
||||
DateTime dt = dateTime ?? DateTime.UtcNow;
|
||||
return (long)(dt - new DateTime(1970, 1, 1)).TotalMilliseconds;
|
||||
}
|
||||
|
||||
public static DateTime FromUnixTimeMillis(long unixTimeMillis)
|
||||
{
|
||||
return new DateTime(1970, 1, 1).AddMilliseconds(unixTimeMillis);
|
||||
}
|
||||
|
||||
public static string ToFriendlyTime(long millisecods)
|
||||
{
|
||||
DateTime dateTime = FromUnixTimeMillis(millisecods);
|
||||
return ToFriendlyTime(dateTime);
|
||||
}
|
||||
|
||||
public static string ToFriendlyTime(DateTime dateTime)
|
||||
{
|
||||
// Use UTC for consistency with LiteDB if needed
|
||||
DateTime now = DateTime.UtcNow;
|
||||
|
||||
|
||||
TimeSpan span = now - dateTime;
|
||||
|
||||
// Handle future dates (optional)
|
||||
if (span.TotalSeconds < 0)
|
||||
return "In the future";
|
||||
|
||||
if (span.TotalSeconds < 60)
|
||||
return $"{(int)span.TotalSeconds} seconds ago";
|
||||
if (span.TotalMinutes < 60)
|
||||
return $"{(int)span.TotalMinutes} minute{(span.TotalMinutes < 2 ? "" : "s")} ago";
|
||||
if (span.TotalHours < 24)
|
||||
return $"{(int)span.TotalHours} hour{(span.TotalHours < 2 ? "" : "s")} ago";
|
||||
if (span.TotalDays < 30)
|
||||
return $"{(int)span.TotalDays} day{(span.TotalDays < 2 ? "" : "s")} ago";
|
||||
if (span.TotalDays < 365)
|
||||
return $"{(int)(span.TotalDays / 30)} month{(span.TotalDays / 30 < 2 ? "" : "s")} ago";
|
||||
|
||||
return $"{(int)(span.TotalDays / 365)} year{(span.TotalDays / 365 < 2 ? "" : "s")} ago";
|
||||
}
|
||||
}
|
||||
}
|
58
ModVersionChecker/utils/VersionUtils.cs
Normal file
58
ModVersionChecker/utils/VersionUtils.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using ModVersionChecker.data.model;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ModVersionChecker.utils
|
||||
{
|
||||
public static class VersionUtils
|
||||
{
|
||||
public static string GetCurrentVersion(AppConfig app, FsModPathConfig config)
|
||||
{
|
||||
var versionConfig = app.FsFields;
|
||||
var packageName = versionConfig["msfs2024"]["package"];
|
||||
var fsPath = config.Path;
|
||||
var fsFile = config.File;
|
||||
var fsFileType = config.FileType;
|
||||
var fsKey = config.Key;
|
||||
|
||||
var filePath = Path.GetFullPath(Path.Combine(fsPath, packageName, fsFile));
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return ""; // Fallback
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var content = File.ReadAllText(filePath).Trim();
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
throw new Exception($"Empty file: {filePath}");
|
||||
}
|
||||
|
||||
using var jsonDoc = JsonDocument.Parse(content);
|
||||
var element = jsonDoc.RootElement;
|
||||
foreach (var key in fsKey.Split('.'))
|
||||
{
|
||||
if (!element.TryGetProperty(key, out var nextElement))
|
||||
{
|
||||
throw new Exception($"JSON key '{key}' not found in {filePath}");
|
||||
}
|
||||
element = nextElement;
|
||||
}
|
||||
if (element.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
throw new Exception($"JSON value for '{fsKey}' is not a string in {filePath}");
|
||||
}
|
||||
|
||||
var version = element.GetString()!;
|
||||
|
||||
return version;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"Error reading or processing file '{filePath}': {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user