Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ jobs:
# Zip
Compress-Archive -Path "unigetui_bin/*" -DestinationPath "output/UniGetUI.$Platform.zip" -CompressionLevel Optimal
Compress-Archive -Path "InstallerExtras/ForceUniGetUIPortable" -DestinationPath "output/UniGetUI.$Platform.zip" -Update
# Installer is created in output during the previous step
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ UniGetUI is primarily built for Windows. The Microsoft Store is the recommended
![GitHub Release](https://img.shields.io/github/v/release/Devolutions/UniGetUI?style=for-the-badge)
Use the installer for the best Windows experience. `UniGetUI.Installer.exe` is the legacy/default x64 installer alias; use the explicit architecture downloads if needed.

The `.zip` is portable: it keeps settings and caches next to the executable rather than in your user profile. See [docs/PORTABLE.md](docs/PORTABLE.md).

| Architecture | Installer | Portable `.zip` |
|---|---|---|
| x64 | [UniGetUI.Installer.x64.exe](https://github.com/Devolutions/UniGetUI/releases/latest/download/UniGetUI.Installer.x64.exe) ([default x64 alias](https://github.com/Devolutions/UniGetUI/releases/latest/download/UniGetUI.Installer.exe)) | [UniGetUI.x64.zip](https://github.com/Devolutions/UniGetUI/releases/latest/download/UniGetUI.x64.zip) |
Expand Down
65 changes: 51 additions & 14 deletions docs/PORTABLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ outside the installation folder. Portable mode moves all of that next to the exe
whole application, settings included, can live on a removable drive or be copied between
machines. A few things deliberately stay outside that folder; see [What changes](#what-changes).

The Windows `.zip` release ships in portable mode. The installer, and the macOS and Linux
archives, do not.

## Enabling portable mode

Portable mode is controlled by a single marker file named `ForceUniGetUIPortable`, placed in
Expand Down Expand Up @@ -50,9 +53,11 @@ New-Item -ItemType File -Path "C:\Path\To\UniGetUI\ForceUniGetUIPortable"
touch /path/to/unigetui/ForceUniGetUIPortable
```

This is how you make the portable `.zip` and `.tar.gz` release archives actually portable.
They ship **without** the marker, so out of the box they still write to the per-user data
directory like a regular install.
The Windows `.zip` already ships with the marker, so it is portable out of the box; creating
the file by hand is only needed for the macOS and Linux `.tar.gz` archives, which do not carry
it. Deleting the marker is the supported way to turn portable mode back off in the `.zip`, and
it is deliberately excluded from `IntegrityTree.json` so removing it cannot fail the integrity
check.

### Where the marker goes

Expand All @@ -76,22 +81,54 @@ whenever the bundle is replaced by an update. Re-create it after upgrading.
| Cached language files | `<data dir>\CachedLanguageFiles` | `<install dir>\Settings\CachedLanguageFiles` |
| Stored secrets, macOS and Linux | `<data dir>/SecureStorage` | `<install dir>/Settings/SecureStorage` |
| Stored secrets, Windows | Credential Manager | Credential Manager (**not** relocated) |
| Default package-backup folder | `Documents\UniGetUI` | `Documents\UniGetUI` (**not** relocated) |

Package backups are one exception: their default location stays in the user's Documents
folder, and portable mode does not move it. Point it somewhere inside the portable folder from
the Backup settings page if you want backups to travel with the app.

The GitHub backup token is the other exception, and where it lives depends on the platform. On
macOS and Linux it is written to `SecureStorage` inside the data directory, so it travels with a
portable folder, as a plain file on disk. On Windows it is held in Credential Manager instead,
so a portable copy does not carry the login, and every portable copy on one machine shares
the same stored token unless `UNIGETUI_GITHUB_TOKEN_NAMESPACE` is set to separate them.
| Session log, WebView2 profile, update logs | `%TEMP%\UniGetUI` on Windows; `$TMPDIR/UniGetUI` elsewhere | `<install dir>\Settings\Temp` |
Comment thread
GabrielDuf marked this conversation as resolved.
| Default package-backup folder | `Documents\UniGetUI` | `<install dir>\Settings\Backups` |
| Elevated secure settings, Windows | `%ProgramFiles%\UniGetUI\SecureSettings` | `%ProgramFiles%\UniGetUI\SecureSettings` (**not** relocated) |

Package backups follow the portable folder, so they travel with the app. A path chosen on the
Backup settings page always wins over that default.

Two things deliberately stay put. Elevated secure settings — the toggles that permit CLI
arguments, custom manager paths and pre/post-operation commands — live under `%ProgramFiles%`
precisely because writing there needs administrator rights. Moving them into a user-writable
portable folder would let any process running as the user grant UniGetUI the right to execute
arbitrary commands, so they stay where they are.

The scratch directory holds files that are rebuilt on demand: the session log, the crash report
left behind for the next launch, the per-attempt auto-updater log, the WebView2 profile, and the
`%TEMP%` handed to package-manager subprocesses when UniGetUI runs elevated. Portable mode moves
it inside the portable folder, so a portable copy leaves nothing behind in the system temporary
Comment thread
GabrielDuf marked this conversation as resolved.
Outdated
directory. It is safe to delete while UniGetUI is not running.

The GitHub backup token is the second, and where it lives depends on the platform. On Windows it
is held in Credential Manager, which encrypts it per user and does not travel with the folder, so
a portable copy asks you to sign in on each machine. On macOS and Linux it is written to
`SecureStorage` inside the data directory **as a plain file**, so it does travel — treat a
portable folder carrying one as you would the token itself. Relocating the Windows token into the
portable folder would mean that same plaintext trade-off, on removable media, so it stays in
Credential Manager. Every portable copy on one machine shares the same stored token unless
`UNIGETUI_GITHUB_TOKEN_NAMESPACE` is set to separate them.

Portable mode also does not relocate anything owned by the package managers themselves. WinGet,
Scoop, Chocolatey, npm and the rest keep their own state in their usual per-user or system
locations, and the packages they install are installed normally.

## Importing settings from a per-user installation

A portable folder starts empty, so an existing installation's settings are not picked up
automatically — they stay in the per-user data directory, untouched.

The first time UniGetUI runs portable and finds settings there, it offers a one-time
**Import** action in a notification. Accepting copies `Configuration` and `InstallationOptions`
into the portable folder; caches are skipped because they are rebuilt on demand and are far
larger than the settings themselves. Nothing is overwritten and nothing is removed from the
source, so a per-user installation on the same machine keeps working. Restart UniGetUI
afterwards for the imported settings to take effect.

Dismissing the notification, or importing once, stops it from appearing again. This matters for
a portable copy carried between machines: it will never silently absorb the settings of a
machine it happens to be plugged into.

## What a portable install does not register

The Windows installer registers these only for a regular installation, so a portable install
Expand Down
24 changes: 24 additions & 0 deletions src/Shared/AutoUpdater.InstallerArguments.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace UniGetUI.Shared;

internal static class AutoUpdaterInstallerArguments
{
private const string CommonWindowsArguments =
"/SILENT /SUPPRESSMSGBOXES /NORESTART /SP- /NoVCRedist /NoEdgeWebView /NoWinGet /NoRedirectionGuard /NoDesktopShortcut";

/// <summary>
/// Arguments for the Windows installer when it is run to update an existing copy.
/// A portable copy is pinned to its own directory and re-selects the portable task,
/// because the installer would otherwise fall back to its default directory and
/// install a second, regular copy elsewhere.
/// </summary>
internal static string ForWindows(bool isPortable, string installationDirectory)
{
if (!isPortable || string.IsNullOrWhiteSpace(installationDirectory))
{
return CommonWindowsArguments;
}

string directory = installationDirectory.TrimEnd('\\', '/');
Comment thread
Copilot marked this conversation as resolved.
Outdated
return $"{CommonWindowsArguments} /TASKS=\"portableinstall\" /DIR=\"{directory}\"";
}
}
2 changes: 1 addition & 1 deletion src/UniGetUI.Avalonia/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ public static void ApplyTheme(string value)
}

public static string WebViewUserDataFolder { get; } =
Path.Join(Path.GetTempPath(), "UniGetUI", "WebView");
Path.Join(AppPaths.ScratchDirectory, "WebView");

private static void SetUpWebViewUserDataFolder()
{
Expand Down
3 changes: 2 additions & 1 deletion src/UniGetUI.Avalonia/CrashHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace UniGetUI.Avalonia;
public static class CrashHandler
{
public static readonly string PendingCrashFile =
Path.Combine(Path.GetTempPath(), "UniGetUI_pending_crash.txt");
Path.Combine(AppPaths.ScratchDirectory, "pending_crash.txt");

private const string NO_CORRUPT_DIALOG = "--no-corrupt-dialog";

Expand Down Expand Up @@ -223,6 +223,7 @@ Inner exception details (depth level: {{i}})
// Persist crash data so the next normal app launch can show the report.
try
{
Directory.CreateDirectory(Path.GetDirectoryName(PendingCrashFile)!);
File.WriteAllText(PendingCrashFile, Error_String, Encoding.UTF8);
}
catch
Expand Down
2 changes: 2 additions & 0 deletions src/UniGetUI.Avalonia/Infrastructure/AvaloniaAppHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ public static void Run(string[] args)
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
CrashHandler.ReportFatalException((Exception)e.ExceptionObject);

ProcessEnvironmentConfigurator.ConfigurePingetStorage();

if (ShouldPrepareCliConsole(args))
{
WindowsConsoleHost.PrepareCliIO();
Expand Down
15 changes: 12 additions & 3 deletions src/UniGetUI.Avalonia/Infrastructure/AvaloniaAutoUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using UniGetUI.Core.Logging;
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.Shared;

namespace UniGetUI.Avalonia.Infrastructure;

Expand Down Expand Up @@ -106,8 +107,7 @@ private static void RaiseStatus(
private static readonly Lock _updateLogLock = new();
private static StringBuilder? _updateLogBuilder;
private static readonly string _updateLogPath = Path.Combine(
Path.GetTempPath(),
"UniGetUI",
AppPaths.ScratchDirectory,
"last-update-attempt.log"
);

Expand Down Expand Up @@ -669,13 +669,22 @@ private static async Task LaunchInstallerAsync(string installerLocation)
return;
}

string installerArguments = AutoUpdaterInstallerArguments.ForWindows(
CoreData.IsPortable,
CoreData.UniGetUIExecutableDirectory);

if (CoreData.IsPortable)
{
LogUpdateInfo($"Portable install: updating in place at {CoreData.UniGetUIExecutableDirectory}");
}

LogUpdateInfo($"Launching installer: {installerLocation}");
using Process p = new()
{
StartInfo = new ProcessStartInfo
{
FileName = installerLocation,
Arguments = "/SILENT /SUPPRESSMSGBOXES /NORESTART /SP- /NoVCRedist /NoEdgeWebView /NoWinGet /NoRedirectionGuard /NoDesktopShortcut",
Arguments = installerArguments,
UseShellExecute = true,
CreateNoWindow = true,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Diagnostics;
using UniGetUI.Core.Data;
using UniGetUI.Core.Logging;
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
Expand All @@ -21,6 +22,42 @@ public static void PrepareForCurrentPlatform()
ApplyProxySettingsToProcess();
}

/// <summary>
/// Points Pinget at the portable folder so a portable copy leaves nothing in
/// %LOCALAPPDATA%\Devolutions\Pinget. The source mode has to be set alongside it:
/// an app-root override alone makes Pinget fall back to its own private source list
/// instead of the machine's real WinGet sources. An externally supplied value wins,
/// so an administrator can still place the store elsewhere.
/// </summary>
public static void ConfigurePingetStorage()
{
try
{
if (!CoreData.IsPortable)
return;

SetIfUnset("PINGET_APPROOT", Path.Join(CoreData.UniGetUIDataDirectory, "Pinget"));
SetIfUnset("PINGET_SOURCE_MODE", "auto");
}
catch (Exception ex)
{
Logger.Error("Could not point Pinget at the portable folder:");
Logger.Error(ex);
}
}

private static void SetIfUnset(string name, string value)
{
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable(name)))
{
Logger.Info($"{name} is already set; leaving it untouched");
return;
}

Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.Process);
Logger.Info($"{name} set to {value}");
Comment thread
GabrielDuf marked this conversation as resolved.
}

public static void ApplyProxySettingsToProcess()
{
try
Expand Down
3 changes: 2 additions & 1 deletion src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@
<PackageReference Include="Avalonia.Controls.WebView" Version="12.0.0" />
<PackageReference Include="Tmds.DBus.Protocol" Version="0.92.0" />
<PackageReference Include="Devolutions.UniGetUI.Elevator" Version="2.6.1.3" GeneratePathProperty="true" ExcludeAssets="build;buildTransitive;native" Condition="$([MSBuild]::IsOSPlatform('Windows'))" />
<PackageReference Include="Devolutions.Pinget.Cli.Rust" Version="0.10.0" GeneratePathProperty="true" ExcludeAssets="build;buildTransitive;native" />
<PackageReference Include="Devolutions.Pinget.Cli.Rust" Version="0.11.0" GeneratePathProperty="true" ExcludeAssets="build;buildTransitive;native" />
</ItemGroup>

<ItemGroup>
Expand All @@ -218,6 +218,7 @@
<Compile Include="..\SharedAssemblyInfo.cs" Link="SharedAssemblyInfo.cs" />
<Compile Include="..\Shared\SharedPreUiCommandDispatcher.cs" Link="Shared\SharedPreUiCommandDispatcher.cs" />
<Compile Include="..\Shared\StartupBundleArguments.cs" Link="Shared\StartupBundleArguments.cs" />
<Compile Include="..\Shared\AutoUpdater.InstallerArguments.cs" Link="Shared\AutoUpdater.InstallerArguments.cs" />
</ItemGroup>

<ItemGroup>
Expand Down
49 changes: 49 additions & 0 deletions src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using UniGetUI.Avalonia.Views.Pages.LogPages;
using UniGetUI.Avalonia.Views.Pages.SettingsPages;
using UniGetUI.Core.Data;
using UniGetUI.Core.Logging;
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.Interface.Enums;
Expand Down Expand Up @@ -160,16 +161,16 @@
private void ToggleOperationsPanel() => OperationsPanelExpanded = !OperationsPanelExpanded;

[RelayCommand]
private void RetryFailedOperations() => AvaloniaOperationRegistry.RetryFailed();

Check warning on line 164 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (Avalonia)

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 164 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (NativeAOT)

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 164 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (Avalonia)

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 164 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (Avalonia)

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 164 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (NativeAOT)

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 164 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 164 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 164 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'RetryFailedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

[RelayCommand]
private void ClearSuccessfulOperations() => AvaloniaOperationRegistry.ClearSuccessful();

Check warning on line 167 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (Avalonia)

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 167 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (NativeAOT)

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 167 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (Avalonia)

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 167 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (Avalonia)

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 167 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (NativeAOT)

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 167 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 167 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 167 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearSuccessfulOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

[RelayCommand]
private void ClearFinishedOperations() => AvaloniaOperationRegistry.ClearFinished();

Check warning on line 170 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (Avalonia)

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 170 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (NativeAOT)

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 170 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (Avalonia)

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 170 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (Avalonia)

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 170 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (NativeAOT)

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 170 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 170 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 170 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'ClearFinishedOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

[RelayCommand]
private void CancelAllOperations() => AvaloniaOperationRegistry.CancelAll();

Check warning on line 173 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (Avalonia)

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 173 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Linux (NativeAOT)

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 173 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (Avalonia)

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 173 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (Avalonia)

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 173 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / Windows (NativeAOT)

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 173 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 173 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

Check warning on line 173 in src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs

View workflow job for this annotation

GitHub Actions / test-codebase

Member 'CancelAllOperations' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)

// ─── Sidebar ─────────────────────────────────────────────────────────────
public SidebarViewModel Sidebar { get; } = new();
Expand Down Expand Up @@ -288,6 +289,7 @@
public InfoBarViewModel UpdatesBanner { get; } = new() { Severity = InfoBarSeverity.Success };
public InfoBarViewModel WinGetWarningBanner { get; } = new() { Severity = InfoBarSeverity.Warning };
public InfoBarViewModel TelemetryWarner { get; } = new() { Severity = InfoBarSeverity.Informational };
public InfoBarViewModel PortableImportBanner { get; } = new() { Severity = InfoBarSeverity.Informational };

// Oldest first (rendered bottom-up so the newest sits nearest the corner).
public ObservableCollection<InfoBarViewModel> Toasts { get; } = new();
Expand Down Expand Up @@ -415,6 +417,7 @@
RegisterBannerToast(UpdatesBanner);
RegisterBannerToast(WinGetWarningBanner);
RegisterBannerToast(TelemetryWarner);
RegisterBannerToast(PortableImportBanner);

DiscoverPage = new DiscoverSoftwarePage();
UpdatesPage = new SoftwareUpdatesPage();
Expand Down Expand Up @@ -538,6 +541,12 @@
TelemetryWarner.IsOpen = true;
}

if (!Settings.Get(Settings.K.ShownPortableImportBanner)
&& PortableDataImport.FindImportableSource() is { } importableSource)
{
ShowPortableImportBanner(importableSource);
}

if (WasUpdatedSinceLastRun() && !Settings.Get(Settings.K.DisableReleaseNotesOnUpdate))
{
NavigateTo(PageType.ReleaseNotes);
Expand All @@ -548,6 +557,46 @@
}
}

private void ShowPortableImportBanner(string importableSource)
{
PortableImportBanner.Title = CoreTools.Translate("Import your previous settings?");
PortableImportBanner.Message = CoreTools.Translate(
"UniGetUI is running in portable mode and started with empty settings. Settings from a previous installation were found at {0}.",
importableSource
Comment thread
GabrielDuf marked this conversation as resolved.
Outdated
);
PortableImportBanner.IsClosable = true;
PortableImportBanner.ActionButtonText = CoreTools.Translate("Import");
PortableImportBanner.ActionButtonCommand = new CommunityToolkit.Mvvm.Input.RelayCommand(() =>
{
Settings.Set(Settings.K.ShownPortableImportBanner, true);

try
{
int copied = PortableDataImport.Import(importableSource);
PortableImportBanner.Severity = InfoBarSeverity.Success;
Comment thread
Copilot marked this conversation as resolved.
Outdated
PortableImportBanner.Title = CoreTools.Translate("Settings imported");
PortableImportBanner.Message = CoreTools.Translate(
"{0} file(s) were copied. Restart UniGetUI to apply them.", copied);
PortableImportBanner.ActionButtonText = CoreTools.Translate("Restart");
PortableImportBanner.ActionButtonCommand = new CommunityToolkit.Mvvm.Input.RelayCommand(
AppRestartHelper.Restart);
}
catch (Exception ex)
{
Logger.Error("Could not import settings into the portable folder");
Logger.Error(ex);
PortableImportBanner.Severity = InfoBarSeverity.Error;
PortableImportBanner.Title = CoreTools.Translate("Could not import settings");
Comment thread
GabrielDuf marked this conversation as resolved.
PortableImportBanner.Message = ex.Message;
PortableImportBanner.ActionButtonText = CoreTools.Translate("View log");
PortableImportBanner.ActionButtonCommand = new CommunityToolkit.Mvvm.Input.RelayCommand(
() => NavigateTo(PageType.OwnLog));
}
});
PortableImportBanner.OnClosed = () => Settings.Set(Settings.K.ShownPortableImportBanner, true);
PortableImportBanner.IsOpen = true;
}

// Returns true the first time the app runs after being updated to a newer build
private static bool WasUpdatedSinceLastRun()
{
Expand Down
Loading
Loading