Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 37 additions & 2 deletions src/Beutl.Api/Services/InstalledPackageRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class InstalledPackageRepository : IBeutlApiResource
{
private readonly ILogger _logger = Log.CreateLogger<InstalledPackageRepository>();
private readonly HashSet<PackageIdentity> _packages = [];
private readonly Dictionary<string, string?> _resolvedBeutlVersions = new(StringComparer.OrdinalIgnoreCase);
private readonly Subject<(PackageIdentity Package, bool Exists)> _subject = new();
private const string FileName = "installedPackages.json";

Expand Down Expand Up @@ -44,6 +45,7 @@ public void UpgradePackages(PackageIdentity package)
}
_packages.RemoveWhere(x => StringComparer.OrdinalIgnoreCase.Equals(x.Id, package.Id));
_packages.Add(package);
_resolvedBeutlVersions[package.Id] = BeutlApplication.Version;
Save();

foreach (PackageIdentity removed in removedItems)
Expand All @@ -68,6 +70,7 @@ public void AddPackage(string name, string version)

if (_packages.Add(package))
{
_resolvedBeutlVersions[package.Id] = BeutlApplication.Version;
Save();
_subject.OnNext((package, true));
}
Expand All @@ -87,6 +90,7 @@ public void AddPackage(PackageIdentity package)

if (_packages.Add(package))
{
_resolvedBeutlVersions[package.Id] = BeutlApplication.Version;
Save();
_subject.OnNext((package, true));
}
Expand All @@ -101,6 +105,7 @@ public void RemovePackage(string name, string version)
x => StringComparer.OrdinalIgnoreCase.Equals(x.Id, name) && x.Version == nugetVersion);
if (package != null && _packages.Remove(package))
{
_resolvedBeutlVersions.Remove(name);
Save();
_subject.OnNext((package, false));
}
Expand All @@ -112,6 +117,7 @@ public void RemovePackage(PackageIdentity package)
_logger.LogInformation("Removing package: {PackageId} with version: {PackageVersion}", package.Id, package.Version);
if (_packages.Remove(package))
{
_resolvedBeutlVersions.Remove(package.Id);
Save();
_subject.OnNext((package, false));
}
Expand All @@ -127,6 +133,7 @@ public void RemovePackages(string name)
removed = GetLocalPackages(name).ToArray();
}
_packages.RemoveWhere(x => StringComparer.OrdinalIgnoreCase.Equals(x.Id, name));
_resolvedBeutlVersions.Remove(name);
Save();
foreach (PackageIdentity package in removed)
{
Expand Down Expand Up @@ -157,14 +164,33 @@ public IObservable<bool> GetObservable(string name, string? version = null)
return new _Observable(this, name, version);
}

public PackageIdentity[] GetPackagesNeedingDependencyReResolution()
{
string currentVersion = BeutlApplication.Version;
return [.. _packages.Where(p =>
{
_resolvedBeutlVersions.TryGetValue(p.Id, out string? ver);
return ver != currentVersion;
})];
}

public void SetResolvedBeutlVersion(string packageId, string beutlVersion)
{
_resolvedBeutlVersions[packageId] = beutlVersion;
Save();
}

private void Save()
{
_logger.LogInformation("Saving installed packages to file.");
string fileName = Path.Combine(Helper.AppRoot, FileName);
using (FileStream stream = File.Create(fileName))
{
JsonSerializer.Serialize(stream, _packages
.Select(x => new S_Package(x.Id, x.Version.ToString()))
.Select(x => new S_Package(
x.Id,
x.Version.ToString(),
_resolvedBeutlVersions.GetValueOrDefault(x.Id)))
.ToArray());
}
_logger.LogInformation("Saved {Count} packages to file.", _packages.Count);
Expand All @@ -183,8 +209,17 @@ private void Restore()
if (JsonSerializer.Deserialize<S_Package[]>(stream) is S_Package[] packages)
{
_packages.Clear();
_resolvedBeutlVersions.Clear();

_packages.AddRange(packages.Select(x => new PackageIdentity(x.Name, new NuGetVersion(x.Version))));

foreach (S_Package pkg in packages)
{
if (pkg.BeutlVersion is { } beutlVersion)
{
_resolvedBeutlVersions[pkg.Name] = beutlVersion;
}
}
}
}
catch (Exception ex)
Expand All @@ -202,7 +237,7 @@ private void Restore()
}

// Serializable
private record S_Package(string Name, string Version);
private record S_Package(string Name, string Version, string? BeutlVersion = null);

private sealed class _Observable : LightweightObservableBase<bool>
{
Expand Down
44 changes: 44 additions & 0 deletions src/Beutl.Api/Services/LoggerAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Microsoft.Extensions.Logging;
using NuGet.Common;
using LogLevel = NuGet.Common.LogLevel;

namespace Beutl.Api.Services;

public class LoggerAdapter(Microsoft.Extensions.Logging.ILogger logger) : LoggerBase
{
private readonly Microsoft.Extensions.Logging.ILogger _logger = logger;

public override void Log(ILogMessage message)
{
switch (message.Level)
{
case LogLevel.Debug:
_logger.LogDebug(message.ToString());
break;
case LogLevel.Information:
_logger.LogInformation(message.ToString());
break;
case LogLevel.Warning:
_logger.LogWarning(message.ToString());
break;
case LogLevel.Error:
_logger.LogError(message.ToString());
break;
case LogLevel.Verbose:
_logger.LogTrace(message.ToString());
break;
case LogLevel.Minimal:
_logger.LogTrace(message.ToString());
break;
default:
_logger.LogInformation(message.ToString());
break;
}
}

public override Task LogAsync(ILogMessage message)
{
Log(message);
return Task.CompletedTask;
}
}
16 changes: 11 additions & 5 deletions src/Beutl.Api/Services/PackageInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,16 @@ static string ByteArrayToString(byte[] bytes)
}
}

public async Task ReResolveDependencies(
PackageIdentity package,
ILogger? logger,
CancellationToken cancellationToken = default)
{
var context = PrepareForInstall(
package.Id, package.Version.ToString(), force: true, cancellationToken);
await ResolveDependencies(context, logger, cancellationToken);
}

public async Task ResolveDependencies(
PackageInstallContext context,
ILogger? logger,
Expand All @@ -304,11 +314,7 @@ public async Task ResolveDependencies(
NuGetFramework nuGetFramework = Helper.GetFrameworkName();
package = new PackageIdentity(packageId, NuGetVersion.Parse(version));

#if DEBUG
logger ??= ConsoleLogger.Instance;
#else
logger ??= NullLogger.Instance;
#endif
logger ??= new LoggerAdapter(_logger);

IEnumerable<SourceRepository> repositories = _sourceRepositoryProvider.GetRepositories();
var availablePackages = new HashSet<SourcePackageDependencyInfo>(PackageIdentityComparer.Default);
Expand Down
5 changes: 3 additions & 2 deletions src/Beutl.Api/Services/PluginDependencyResolver.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Reflection;
using System.Runtime.Loader;

using Beutl.Logging;
using NuGet.Common;
using NuGet.Frameworks;
using NuGet.Packaging;
Expand All @@ -11,6 +11,7 @@ namespace Beutl.Api.Services;
// https://github.com/dotnet/runtime/blob/9ec7fc21862f3446c6c6f7dcfff275942e3884d3/src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyDependencyResolver.cs
internal sealed class PluginDependencyResolver
{
private readonly ILogger _logger = new LoggerAdapter(Log.CreateLogger<PluginDependencyResolver>());
private const string NeutralCultureName = "neutral";
private const string ResourceAssemblyExtension = ".dll";

Expand All @@ -32,7 +33,7 @@ public PluginDependencyResolver(string mainDirectory, PackageFolderReader? reade
reader,
reader.GetIdentity(),
framework,
NullLogger.Instance,
_logger,
availablePackages);
}
else
Expand Down
22 changes: 21 additions & 1 deletion src/Beutl/Services/StartupTasks/LoadInstalledExtensionTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,24 @@ public sealed class LoadInstalledExtensionTask : StartupTask
private readonly ILogger<LoadInstalledExtensionTask> _logger = Log.CreateLogger<LoadInstalledExtensionTask>();
private readonly PackageManager _manager;

public LoadInstalledExtensionTask(PackageManager manager)
public LoadInstalledExtensionTask(PackageManager manager, Startup startup)
{
_manager = manager;

Task = Task.Run(async () =>
{
using (Activity? activity = Telemetry.StartActivity("LoadInstalledExtensionTask"))
{
// 依存関係の再復元完了を待機
ResolvePackageDependenciesTask resolveTask =
startup.GetTask<ResolvePackageDependenciesTask>();
await resolveTask.Task;

// 依存関係の再復元に失敗したパッケージIDを収集
HashSet<string> failedPackageIds = new(
resolveTask.Failures.Select(f => f.Package.Id),
StringComparer.OrdinalIgnoreCase);

// .beutl/packages/ 内のパッケージを読み込む
if (!await AsksRunInRestrictedMode())
{
Expand All @@ -33,6 +43,16 @@ public LoadInstalledExtensionTask(PackageManager manager)

Parallel.ForEach(packages, item =>
{
if (failedPackageIds.Contains(item.Name))
{
_logger.LogWarning(
"Skipping package {PackageName} due to dependency re-resolution failure.",
item.Name);
Failures.Add((item, new InvalidOperationException(
$"Dependency re-resolution failed for package '{item.Name}'.")));
return;
}

try
{
_manager.Load(item);
Expand Down
72 changes: 72 additions & 0 deletions src/Beutl/Services/StartupTasks/ResolvePackageDependenciesTask.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System.Collections.Concurrent;

using Beutl.Api.Services;
using Beutl.Logging;

using Microsoft.Extensions.Logging;

using NuGet.Common;
using NuGet.Packaging.Core;

namespace Beutl.Services.StartupTasks;

public sealed class ResolvePackageDependenciesTask : StartupTask
{
private readonly ILogger<ResolvePackageDependenciesTask> _logger =
Log.CreateLogger<ResolvePackageDependenciesTask>();

public ResolvePackageDependenciesTask(
InstalledPackageRepository repository,
PackageInstaller installer)
{
Task = Task.Run(async () =>
{
using (Activity? activity = Telemetry.StartActivity("ResolvePackageDependenciesTask"))
{
PackageIdentity[] packages = repository.GetPackagesNeedingDependencyReResolution();

if (packages.Length == 0)
{
_logger.LogInformation(
"All installed packages were resolved under the current Beutl version. No re-resolution needed.");
return;
}

_logger.LogInformation(
"Beutl version changed. Re-resolving dependencies for {Count} package(s).",
packages.Length);

foreach (PackageIdentity package in packages)
{
try
{
activity?.AddEvent(new ActivityEvent(
$"Re-resolving {package.Id} {package.Version}"));

await installer.ReResolveDependencies(
package, null, CancellationToken.None);

repository.SetResolvedBeutlVersion(
package.Id, BeutlApplication.Version);

_logger.LogInformation(
"Successfully re-resolved dependencies for {PackageId} {PackageVersion}.",
package.Id, package.Version);
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error);
_logger.LogError(ex,
"Failed to re-resolve dependencies for {PackageId} {PackageVersion}.",
package.Id, package.Version);
Failures.Add((package, ex));
}
}
}
});
}

public override Task Task { get; }

public ConcurrentBag<(PackageIdentity Package, Exception Error)> Failures { get; } = [];
}
6 changes: 5 additions & 1 deletion src/Beutl/Services/StartupTasks/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ public T GetTask<T>()
private void RegisterAll()
{
Register(() => new AuthenticationTask(_apiApp));
Register(() => new LoadInstalledExtensionTask(_apiApp.GetResource<PackageManager>()));
Register(() => new ResolvePackageDependenciesTask(
_apiApp.GetResource<InstalledPackageRepository>(),
_apiApp.GetResource<PackageInstaller>()));
Register(() => new LoadInstalledExtensionTask(
_apiApp.GetResource<PackageManager>(), this));
Register(() => new LoadPrimitiveExtensionTask(_apiApp.GetResource<PackageManager>()));
Register(() => new LoadSideloadExtensionTask(_apiApp.GetResource<PackageManager>()));
Register(() => new AfterLoadingExtensionsTask(this));
Expand Down
Loading