1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2026-02-04 02:43:32 +00:00

Merge branch 'master' into development

This commit is contained in:
Robert Beekman 2025-12-24 14:04:57 +01:00
commit 36761509e2
13 changed files with 656 additions and 640 deletions

View File

@ -22,9 +22,8 @@ jobs:
run: |
$MidnightUtc = [DateTime]::UtcNow.Date
$BranchName = "${{ github.ref_name }}".replace('/','-').replace('.','-')
$ApiVersion = (Select-Xml -Path 'src/Artemis.Core/Artemis.Core.csproj' -XPath '//PluginApiVersion').Node.InnerText
$NumberOfCommitsToday = (git log --after=$($MidnightUtc.ToString("o")) --oneline | Measure-Object -Line).Lines
$VersionNumber = "$ApiVersion.$($MidnightUtc.ToString("yyyy.MMdd")).$NumberOfCommitsToday"
$VersionNumber = "1.$($MidnightUtc.ToString("yyyy.MMdd")).$NumberOfCommitsToday"
# If we're not in master, add the branch name to the version so it counts as prerelease
if ($BranchName -ne "master") { $VersionNumber += "-$BranchName" }
"version-number=$VersionNumber" >> $Env:GITHUB_OUTPUT
@ -55,7 +54,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
dotnet-version: '10.0.x'
- name: Publish Artemis
run: dotnet publish --configuration Release -p:Version=${{ needs.version.outputs.version-number }} --runtime ${{ matrix.rid }} --output build/${{ matrix.rid }} --self-contained Artemis/src/Artemis.UI.${{ matrix.csproj }}/Artemis.UI.${{ matrix.csproj }}.csproj
- name: Upload Artifact

View File

@ -22,9 +22,8 @@ jobs:
run: |
$MidnightUtc = [DateTime]::UtcNow.Date
$BranchName = "${{ github.ref_name }}".replace('/','-').replace('.','-')
$ApiVersion = (Select-Xml -Path 'src/Artemis.Core/Artemis.Core.csproj' -XPath '//PluginApiVersion').Node.InnerText
$NumberOfCommitsToday = (git log --after=$($MidnightUtc.ToString("o")) --oneline | Measure-Object -Line).Lines
$VersionNumber = "$ApiVersion.$($MidnightUtc.ToString("yyyy.MMdd")).$NumberOfCommitsToday"
$VersionNumber = "1.$($MidnightUtc.ToString("yyyy.MMdd")).$NumberOfCommitsToday"
# If we're not in master, add the branch name to the version so it counts as prerelease
if ($BranchName -ne "master") { $VersionNumber += "-$BranchName" }
"version-number=$VersionNumber" >> $Env:GITHUB_OUTPUT
@ -37,7 +36,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
dotnet-version: '10.0.x'
- name: Checkout
uses: actions/checkout@v4
- name: Pack Artemis.Core

View File

@ -41,7 +41,7 @@ public static class Constants
/// The full path to the Artemis data folder
/// </summary>
#if DEBUG
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis-dev");
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
#else
public static readonly string DataFolder = Path.Combine(BaseFolder, "Artemis");
#endif

View File

@ -17,7 +17,7 @@ public static class ReleaseExtensions
if (release.MinimumVersion == null || Constants.CurrentVersion == "local")
return true;
return release.MinimumVersion <= Version.Parse(Constants.CurrentVersion).ArtemisVersionToLong();
return Version.Parse(release.MinimumVersion) <= Version.Parse(Constants.CurrentVersion);
}
}
}

View File

@ -1,45 +0,0 @@
using System;
namespace Artemis.UI.Extensions;
public static class VersionExtensions
{
/// <param name="version">The version to convert</param>
extension(Version version)
{
/// <summary>
/// Convert a Version to a long representation for easy comparison in PostgreSQL
/// <remarks>Assumes format: major.year.dayOfYear.revision (e.g., 1.2024.0225.2)</remarks>
/// </summary>
/// <returns>A long value that preserves version comparison order</returns>
public long ArtemisVersionToLong()
{
// Format: major.year.dayOfYear.revision
// Convert to: majorYYYYDDDRRRR (16 digits)
// Major: 1 digit (0-9)
// Year: 4 digits (e.g., 2024)
// Day: 3 digits (001-366, padded)
// Revision: 4 digits (0000-9999, padded)
long major = Math.Max(0, Math.Min(9, version.Major));
long year = Math.Max(1000, Math.Min(9999, version.Minor));
long day = Math.Max(1, Math.Min(366, version.Build));
long revision = Math.Max(0, Math.Min(9999, version.Revision >= 0 ? version.Revision : 0));
return major * 100000000000L +
year * 10000000L +
day * 10000L +
revision;
}
public static Version FromLong(long versionLong)
{
int major = (int)(versionLong / 100000000000L);
int year = (int)((versionLong / 10000000L) % 10000);
int day = (int)((versionLong / 10000L) % 1000);
int revision = (int)(versionLong % 10000L);
return new Version(major, year, day, revision);
}
}
}

View File

@ -69,11 +69,11 @@ public partial class EntryReleaseInfoViewModel : ActivatableViewModelBase
}).DisposeWith(d);
IsCurrentVersion = Release != null && _workshopService.GetInstalledEntry(Release.Entry.Id)?.ReleaseId == Release.Id;
IncompatibilityReason = Release != null && !Release.IsCompatible() ? $"Requires Artemis v{Version.FromLong(Release.MinimumVersion!.Value)} or later" : null;
IncompatibilityReason = Release != null && !Release.IsCompatible() ? $"Requires Artemis v{Release.MinimumVersion} or later" : null;
});
this.WhenAnyValue(vm => vm.Release).Subscribe(r => IsCurrentVersion = r != null && _workshopService.GetInstalledEntry(r.Entry.Id)?.ReleaseId == r.Id);
this.WhenAnyValue(vm => vm.Release).Subscribe(r => IncompatibilityReason = r != null && !r.IsCompatible() ? $"Requires Artemis v{Version.FromLong(r.MinimumVersion!.Value)} or later" : null);
this.WhenAnyValue(vm => vm.Release).Subscribe(r => IncompatibilityReason = r != null && !r.IsCompatible() ? $"Requires Artemis v{r.MinimumVersion} or later" : null);
InDetailsScreen = true;
}

View File

@ -37,7 +37,7 @@ public partial class EntryReleaseItemViewModel : ActivatableViewModelBase
}).DisposeWith(d);
IsCurrentVersion = _workshopService.GetInstalledEntry(_entry.Id)?.ReleaseId == Release.Id;
IncompatibilityReason = !Release.IsCompatible() ? $"Requires Artemis v{Version.FromLong(Release.MinimumVersion!.Value)} or later" : null;
IncompatibilityReason = !Release.IsCompatible() ? $"Requires Artemis v{Release.MinimumVersion} or later" : null;
});
}

View File

@ -20,15 +20,21 @@ public class WorkshopUpdateService : IWorkshopUpdateService
private readonly ILogger _logger;
private readonly IWorkshopClient _client;
private readonly IWorkshopService _workshopService;
private readonly IPluginManagementService _pluginManagementService;
private readonly Lazy<IUpdateNotificationProvider> _updateNotificationProvider;
private readonly PluginSetting<bool> _showNotifications;
public WorkshopUpdateService(ILogger logger, IWorkshopClient client, IWorkshopService workshopService, ISettingsService settingsService,
public WorkshopUpdateService(ILogger logger,
IWorkshopClient client,
IWorkshopService workshopService,
ISettingsService settingsService,
IPluginManagementService pluginManagementService,
Lazy<IUpdateNotificationProvider> updateNotificationProvider)
{
_logger = logger;
_client = client;
_workshopService = workshopService;
_pluginManagementService = pluginManagementService;
_updateNotificationProvider = updateNotificationProvider;
_showNotifications = settingsService.GetSetting("Workshop.ShowNotifications", true);
}
@ -88,6 +94,18 @@ public class WorkshopUpdateService : IWorkshopUpdateService
else
_logger.Warning("Auto-update failed for entry {Entry}: {Message}", entry, updateResult.Message);
if (!updateResult.IsSuccess || updateResult.Installed is not Plugin {IsEnabled: false} updatedPlugin)
return updateResult.IsSuccess;
try
{
_pluginManagementService.EnablePlugin(updatedPlugin, true, true);
}
catch (Exception e)
{
_logger.Warning(e, "Failed to auto-enable updated plugin {Plugin}", updatedPlugin);
}
return updateResult.IsSuccess;
}
catch (Exception e)

View File

@ -102,8 +102,6 @@ public class PluginEntryInstallationHandler : IEntryInstallationHandler
// ignored, will get cleaned up as an orphaned file
}
if (installedEntry.Entity.Id != Guid.Empty)
_workshopService.RemoveInstalledEntry(installedEntry);
return EntryInstallResult.FromException(e);
}

View File

@ -225,7 +225,6 @@ public class InstalledEntry : CorePropertyChanged
IsOfficial = Entity.IsOfficial;
Name = Entity.Name;
Summary = Entity.Summary;
EntryType = (EntryType) Entity.EntryType;
Downloads = Entity.Downloads;
CreatedAt = Entity.CreatedAt;
LatestReleaseId = Entity.LatestReleaseId;
@ -236,14 +235,21 @@ public class InstalledEntry : CorePropertyChanged
InstalledAt = Entity.InstalledAt;
AutoUpdate = Entity.AutoUpdate;
// Avoiding a cast here, the enum has shifted around before as it is generated from the GraphQL schema
EntryType = Entity.EntryType switch
{
0 => EntryType.Plugin,
1 => EntryType.Profile,
2 => EntryType.Layout,
_ => EntryType
};
_metadata = Entity.Metadata != null ? new Dictionary<string, JsonNode>(Entity.Metadata) : [];
}
internal void Save()
{
Entity.EntryId = Id;
Entity.EntryType = (int) EntryType;
Entity.Author = Author;
Entity.IsOfficial = IsOfficial;
Entity.Name = Name;
@ -258,6 +264,15 @@ public class InstalledEntry : CorePropertyChanged
Entity.InstalledAt = InstalledAt;
Entity.AutoUpdate = AutoUpdate;
// Avoiding a cast here, the enum has shifted around before as it is generated from the GraphQL schema
Entity.EntryType = EntryType switch
{
EntryType.Plugin => 0,
EntryType.Profile => 1,
EntryType.Layout => 2,
_ => Entity.EntryType
};
Entity.Metadata = new Dictionary<string, JsonNode>(_metadata);
}
}

View File

@ -26,9 +26,8 @@ public class WorkshopService : IWorkshopService
private readonly IWorkshopClient _workshopClient;
private readonly PluginSetting<bool> _migratedBuiltInPlugins;
private bool _initialized;
private bool _mutating;
public WorkshopService(ILogger logger,
IHttpClientFactory httpClientFactory,
@ -173,27 +172,45 @@ public class WorkshopService : IWorkshopService
/// <inheritdoc />
public async Task<EntryInstallResult> InstallEntry(IEntrySummary entry, IRelease release, Progress<StreamProgress> progress, CancellationToken cancellationToken)
{
IEntryInstallationHandler handler = _factory.CreateHandler(entry.EntryType);
EntryInstallResult result = await handler.InstallAsync(entry, release, progress, cancellationToken);
if (result.IsSuccess && result.Entry != null)
OnEntryInstalled?.Invoke(this, result.Entry);
else
_logger.Warning("Failed to install entry {Entry}: {Message}", entry, result.Message);
_mutating = true;
return result;
try
{
IEntryInstallationHandler handler = _factory.CreateHandler(entry.EntryType);
EntryInstallResult result = await handler.InstallAsync(entry, release, progress, cancellationToken);
if (result.IsSuccess && result.Entry != null)
OnEntryInstalled?.Invoke(this, result.Entry);
else
_logger.Warning("Failed to install entry {Entry}: {Message}", entry, result.Message);
return result;
}
finally
{
_mutating = false;
}
}
/// <inheritdoc />
public async Task<EntryUninstallResult> UninstallEntry(InstalledEntry installedEntry, CancellationToken cancellationToken)
{
IEntryInstallationHandler handler = _factory.CreateHandler(installedEntry.EntryType);
EntryUninstallResult result = await handler.UninstallAsync(installedEntry, cancellationToken);
if (result.IsSuccess)
OnEntryUninstalled?.Invoke(this, installedEntry);
else
_logger.Warning("Failed to uninstall entry {EntryId}: {Message}", installedEntry.Id, result.Message);
_mutating = true;
return result;
try
{
IEntryInstallationHandler handler = _factory.CreateHandler(installedEntry.EntryType);
EntryUninstallResult result = await handler.UninstallAsync(installedEntry, cancellationToken);
if (result.IsSuccess)
OnEntryUninstalled?.Invoke(this, installedEntry);
else
_logger.Warning("Failed to uninstall entry {EntryId}: {Message}", installedEntry.Id, result.Message);
return result;
}
finally
{
_mutating = false;
}
}
/// <inheritdoc />
@ -317,15 +334,27 @@ public class WorkshopService : IWorkshopService
if (_migratedBuiltInPlugins.Value)
return;
MigratingBuildInPlugins?.Invoke(this, EventArgs.Empty);
_mutating = true;
bool migrated = await BuiltInPluginsMigrator.Migrate(this, _workshopClient, _logger, _pluginRepository);
_migratedBuiltInPlugins.Value = migrated;
_migratedBuiltInPlugins.Save();
try
{
MigratingBuildInPlugins?.Invoke(this, EventArgs.Empty);
bool migrated = await BuiltInPluginsMigrator.Migrate(this, _workshopClient, _logger, _pluginRepository);
_migratedBuiltInPlugins.Value = migrated;
_migratedBuiltInPlugins.Save();
}
finally
{
_mutating = false;
}
}
private void ProfileServiceOnProfileRemoved(object? sender, ProfileConfigurationEventArgs e)
{
if (_mutating)
return;
InstalledEntry? entry = GetInstalledEntryByProfile(e.ProfileConfiguration);
if (entry == null)
return;
@ -336,6 +365,9 @@ public class WorkshopService : IWorkshopService
private void PluginManagementServiceOnPluginRemoved(object? sender, PluginEventArgs e)
{
if (_mutating)
return;
InstalledEntry? entry = GetInstalledEntryByPlugin(e.Plugin);
if (entry == null)
return;

File diff suppressed because it is too large Load Diff

View File

@ -59,8 +59,8 @@
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
<PackageVersion Include="Serilog.Sinks.Debug" Version="3.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="SkiaSharp" Version="3.119.1" />
<PackageVersion Include="SkiaSharp.Vulkan.SharpVk" Version="3.119.1" />
<PackageVersion Include="SkiaSharp" Version="2.88.9" />
<PackageVersion Include="SkiaSharp.Vulkan.SharpVk" Version="2.88.9" />
<PackageVersion Include="Splat.DryIoc" Version="17.1.1" />
<PackageVersion Include="StrawberryShake.Server" Version="15.1.11" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.15.0" />