Skip to content
Merged
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
21 changes: 9 additions & 12 deletions demo/OneWare.Demo.Desktop/DesktopDemoApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,15 @@ protected override async Task LoadContentAsync()

var arguments = Environment.GetCommandLineArgs();

Window? splashWindow = null;
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
{
splashWindow = new SplashWindow
{
DataContext = Container.Resolve<SplashWindowViewModel>()
};
splashWindow.Show();
}
// Window? splashWindow = null;
// if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
// {
// splashWindow = new SplashWindow
// {
// DataContext = Container.Resolve<SplashWindowViewModel>()
// };
// splashWindow.Show();
// }

if (arguments.Length > 1 && !arguments[1].StartsWith("--"))
{
Expand Down Expand Up @@ -92,9 +92,6 @@ protected override async Task LoadContentAsync()
}
}

await Task.Delay(1000);
splashWindow?.Close();

try
{
var settingsService = Container.Resolve<ISettingsService>();
Expand Down
48 changes: 46 additions & 2 deletions src/OneWare.Core/Services/ApplicationStateService.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using System.Collections.ObjectModel;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using OneWare.Core.Views.Windows;
using OneWare.Essentials.Enums;
using OneWare.Essentials.Helpers;
using OneWare.Essentials.Models;
using OneWare.Essentials.PackageManager;
using OneWare.Essentials.Services;
using Prism.Ioc;

Expand Down Expand Up @@ -117,6 +117,11 @@ public void ExecuteUrlLaunchActions(string key, string? value)
{
if(_urlLaunchActions.TryGetValue(key, out var action))
action.Invoke(value);
else
{
//Try to auto download extension if possible
_ = AttemptAutoDownloadExtensionAsync(key, value);
}
}

public void ExecuteShutdownActions()
Expand Down Expand Up @@ -258,4 +263,43 @@ private void PerformRestart()
ContainerLocator.Container.Resolve<ILogger>()?.Error($"Failed to restart application: {ex.Message}", ex);
}
}

private async Task AttemptAutoDownloadExtensionAsync(string autoLaunchId, string? value)
{
var state = AddState($"Running Autolaunch Action: {autoLaunchId}", AppState.Loading);

try
{
var packageService = ContainerLocator.Container.Resolve<IPackageService>();
await packageService.LoadPackagesAsync();
var package = packageService.Packages.Values
.Where(x => x.Status != PackageStatus.Installed)
.FirstOrDefault(x => x.Package.UrlLaunchIds != null && x.Package.UrlLaunchIds.Split(';').Select(y => y.Trim()).Contains(autoLaunchId));

var packageWindowService = ContainerLocator.Container.Resolve<IPackageWindowService>();
if (package is { Package: { Id: not null } } packageModel)
{
var result = await packageWindowService.QuickInstallPackageAsync(package.Package.Id);
if (result)
{
if(_urlLaunchActions.TryGetValue(autoLaunchId, out var action))
action.Invoke(value);
}
else
{
ContainerLocator.Container.Resolve<ILogger>().Warning($"Failed downloading package for ID: {autoLaunchId}");
}
}
else
{
ContainerLocator.Container.Resolve<ILogger>().Warning($"No package found for auto launch id: {autoLaunchId}");
}
}
catch (Exception e)
{
ContainerLocator.Container.Resolve<ILogger>().Error(e.Message, e);
}

RemoveState(state);
}
}
25 changes: 9 additions & 16 deletions src/OneWare.Essentials/Models/PackageModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@ public abstract class PackageModel : ObservableObject
private PackageVersion? _installedVersion;
private Package _package;

private bool _isIndeterminate;
private float _progress;

private PackageStatus _status;

private string? _installedVersionWarningText;

protected readonly IHttpService HttpService;

protected PackageModel(Package package,
Expand Down Expand Up @@ -52,26 +45,26 @@ public PackageVersion? InstalledVersion

public string? InstalledVersionWarningText
{
get => _installedVersionWarningText;
set => SetProperty(ref _installedVersionWarningText, value);
get;
set => SetProperty(ref field, value);
}

public PackageStatus Status
{
get => _status;
protected set => SetProperty(ref _status, value);
get;
protected set => SetProperty(ref field, value);
}

public bool IsIndeterminate
{
get => _isIndeterminate;
private set => SetProperty(ref _isIndeterminate, value);

get;
private set => SetProperty(ref field, value);
}

public float Progress
{
get => _progress;
private set => SetProperty(ref _progress, value);
get;
private set => SetProperty(ref field, value);
}

protected string ExtractionFolder { get; }
Expand Down
2 changes: 2 additions & 0 deletions src/OneWare.Essentials/PackageManager/Package.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public class Package
public string? License { get; init; }

public bool AcceptLicenseBeforeDownload { get; init; }

public string? UrlLaunchIds { get; init; }

public string? IconUrl { get; init; }

Expand Down
6 changes: 6 additions & 0 deletions src/OneWare.Essentials/Services/IPackageService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.ComponentModel;
using Avalonia.Controls;
using Avalonia.Media;
using OneWare.Essentials.Models;
using OneWare.Essentials.PackageManager;

Expand All @@ -21,4 +23,8 @@ public interface IPackageService : INotifyPropertyChanged
public Task<bool> LoadPackagesAsync();

public Task<bool> InstallAsync(Package package);

public Task<string?> DownloadLicenseAsync(Package package);

public Task<IImage?> DownloadPackageIconAsync(Package package);
}
3 changes: 2 additions & 1 deletion src/OneWare.Essentials/Services/IPackageWindowService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public interface IPackageWindowService
Task RefreshPackagesAsync();
Control ShowExtensionManager();
Control? ShowExtensionManager(string category, string? subcategory);
Task<bool> ShowExtensionManagerAndTryInstallAsync(string category, string packageId);
Task<bool> ShowExtensionManagerAndTryInstallAsync(string packageId);
Task<bool> QuickInstallPackageAsync(string packageId);
}
121 changes: 83 additions & 38 deletions src/OneWare.PackageManager/Services/PackageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reactive.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using Avalonia;
using Avalonia.Media;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
Expand All @@ -12,6 +13,7 @@
using OneWare.Essentials.PackageManager;
using OneWare.Essentials.Services;
using OneWare.PackageManager.Models;
using OpenCvSharp;
using Prism.Ioc;

namespace OneWare.PackageManager.Services;
Expand All @@ -33,9 +35,10 @@ public partial class PackageService : ObservableObject, IPackageService
private readonly ISettingsService _settingsService;

private readonly Lock _installLock = new();
private readonly SemaphoreSlim _loadSemaphore = new(1, 1);

private readonly ILogger _logger;
private CancellationTokenSource? _cancellationTokenSource;
private Task<bool>? _currentLoadTask;

public PackageService(IHttpService httpService, ISettingsService settingsService, ILogger logger, IPaths paths)
{
Expand Down Expand Up @@ -92,56 +95,81 @@ public void RegisterPackage(Package package)

public async Task<bool> LoadPackagesAsync()
{
if (_cancellationTokenSource is not null) await _cancellationTokenSource.CancelAsync();
_cancellationTokenSource = new CancellationTokenSource();
// Check if there's already a load in progress
await _loadSemaphore.WaitAsync();
try
{
// If there's already a load task running, wait for it instead of starting a new one
if (_currentLoadTask != null && !_currentLoadTask.IsCompleted)
{
_loadSemaphore.Release();
return await _currentLoadTask;
}

// Start a new load task
_currentLoadTask = LoadPackagesInternalAsync();
}
finally
{
if (_loadSemaphore.CurrentCount == 0)
_loadSemaphore.Release();
}

return await _currentLoadTask;
}

private async Task<bool> LoadPackagesInternalAsync()
{
await WaitForInstallsAsync();

IsUpdating = true;

var result = true;

var customRepositories =
_settingsService.GetSettingValue<ObservableCollection<string>>("PackageManager_Sources");

var allRepos = PackageRepositories.Concat(customRepositories);
var newPackages = new List<Package>();

foreach (var repository in allRepos)
try
{
var loadedPackages = await LoadPackageRepositoryAsync(repository, _cancellationTokenSource.Token);
var customRepositories =
_settingsService.GetSettingValue<ObservableCollection<string>>("PackageManager_Sources");

if (_cancellationTokenSource.IsCancellationRequested) break;
var allRepos = PackageRepositories.Concat(customRepositories);
var newPackages = new List<Package>();

if (loadedPackages != null)
foreach (var package in loadedPackages)
{
newPackages.Add(package);

if (package.Id != null && Packages.TryGetValue(package.Id, out var pkg))
{
pkg.Package = package;
}
else
foreach (var repository in allRepos)
{
var loadedPackages = await LoadPackageRepositoryAsync(repository);

if (loadedPackages != null)
foreach (var package in loadedPackages)
{
AddPackage(package);
newPackages.Add(package);

if (package.Id != null && Packages.TryGetValue(package.Id, out var pkg))
{
pkg.Package = package;
}
else
{
AddPackage(package);
}
}
}

result = result && loadedPackages != null;
}
result = result && loadedPackages != null;
}

foreach (var removedPackage in Packages.Select(x => x.Value.Package)
.Where(x => !newPackages.Contains(x) && !StandalonePackages.Contains(x))
.ToArray())
foreach (var removedPackage in Packages.Select(x => x.Value.Package)
.Where(x => !newPackages.Contains(x) && !StandalonePackages.Contains(x))
.ToArray())
{
Packages.Remove(removedPackage.Id!);
}

PackagesUpdated?.Invoke(this, EventArgs.Empty);
}
finally
{
Packages.Remove(removedPackage.Id!);
IsUpdating = false;
}

PackagesUpdated?.Invoke(this, EventArgs.Empty);

IsUpdating = false;

return result;
}

Expand Down Expand Up @@ -177,6 +205,24 @@ public Task<bool> InstallAsync(Package package)
return Task.FromResult(false);
}

public async Task<string?> DownloadLicenseAsync(Package package)
{
var url = package.Tabs?.FirstOrDefault(x => x.Title == "License")?.ContentUrl;

if (url == null) return null;

return await _httpService.DownloadTextAsync(url);
}

public async Task<IImage?> DownloadPackageIconAsync(Package package)
{
var icon = package.IconUrl != null
? await _httpService.DownloadImageAsync(package.IconUrl)
: null;

return icon;
}

private void AddPackage(Package package, string? installedVersion = null)
{
if (package.Id == null) throw new Exception("Package ID cannot be empty");
Expand Down Expand Up @@ -221,9 +267,9 @@ private async Task WaitForInstallsAsync()
if (activeInstallTasks.Length != 0) await Task.WhenAll(activeInstallTasks.ToArray());
}

private async Task<Package[]?> LoadPackageRepositoryAsync(string url, CancellationToken cancellationToken)
private async Task<Package[]?> LoadPackageRepositoryAsync(string url)
{
var repositoryString = await _httpService.DownloadTextAsync(url, TimeSpan.FromSeconds(10), cancellationToken);
var repositoryString = await _httpService.DownloadTextAsync(url, TimeSpan.FromSeconds(10));
if (repositoryString == null) return null;

var trimmed = MyRegex().Replace(repositoryString, "");
Expand All @@ -244,8 +290,7 @@ private async Task WaitForInstallsAsync()
if (manifest.ManifestUrl == null) continue;

var downloadManifest =
await _httpService.DownloadTextAsync(manifest.ManifestUrl,
cancellationToken: cancellationToken);
await _httpService.DownloadTextAsync(manifest.ManifestUrl);

var package = JsonSerializer.Deserialize<Package>(downloadManifest!, SerializerOptions);

Expand Down
Loading
Loading