Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
23e87c1
feat: Implement GitHub OAuth and Gist backup infrastructure
google-labs-jules[bot] Jul 7, 2025
6022db2
feat: Implement GitHub Gist backup and restore
google-labs-jules[bot] Jul 7, 2025
d91f5b1
Implement GitHub Gist backup feature with related UI and settings upd…
Jul 7, 2025
25a3334
feat(backup): Implement GitHub Gist backup and restore
Jul 8, 2025
da717c0
Merge branch 'marticliment:main' into feat/github-gist-backup
theguy000 Jul 8, 2025
7369b69
fix(build): Correct syntax and add GitHub secrets file
Jul 8, 2025
160385d
update related codebase
Jul 8, 2025
b905f44
Merge branch 'main' into feat/github-gist-backup
marticliment Jul 8, 2025
28cc0b5
Merge branch 'main' into pr/3826
marticliment Jul 8, 2025
3a021b0
Generate secrets file via source generators, instead of apply_version…
marticliment Jul 8, 2025
e0948db
simplify methods to import and export settings, to generate backup fi…
marticliment Jul 8, 2025
c7b7719
Simplify opening bundle from string
marticliment Jul 8, 2025
7fdbca2
Add code to dynamically identify the required gist, no need to store …
marticliment Jul 8, 2025
489894f
better id calculation
marticliment Jul 8, 2025
2a96d01
Add the ability to fetch all available packages
marticliment Jul 9, 2025
1954b1f
Implement asking for a backup and loading it into package bundles
marticliment Jul 9, 2025
bef5b4a
Update Backup.xaml.cs
marticliment Jul 9, 2025
b505369
Remove unused setting
marticliment Jul 9, 2025
56c42ae
Add a widget on the titlebar to login and logout
marticliment Jul 9, 2025
4c68014
Add pointer cursor to some buttons
marticliment Jul 9, 2025
e75754d
begin redistribution of backup UI
marticliment Jul 9, 2025
106a88c
Final touches to the Cloud Backup UI
marticliment Jul 9, 2025
bbc3f3c
Implement actual automated backup logic on launch
marticliment Jul 9, 2025
0bef749
comment code
marticliment Jul 9, 2025
44b2e16
Make non-async methods that were not truely asynchronous
marticliment Jul 9, 2025
a9d373d
Remove unused imports
marticliment Jul 9, 2025
047ea06
Improvements to login page
marticliment Jul 9, 2025
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ InstallerExtras/MsiCreator/UniGetUISetup.msi
src/global.json
UniGetUI.Installer.ms-store-test.exe
UniGetUI Installer_winget-fix-test.exe
InstallerExtras/uninst-*.e32
InstallerExtras/uninst-*.e32
1 change: 1 addition & 0 deletions scripts/apply_versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def fileReplaceLinesWith(filename: str, list: dict[str, str], encoding="utf-8"):
}, encoding="utf-8-sig")

print("done!")

except FileNotFoundError as e:
print(f"Error: {e.strerror}: {e.filename}")
os.system("pause")
Expand Down
73 changes: 73 additions & 0 deletions src/UniGetUI.Core.SecureSettings/SecureGHTokenManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Windows.Security.Credentials;
using UniGetUI.Core.Logging;

namespace UniGetUI.Core.SecureSettings
{
public static class SecureGHTokenManager
{
private const string GitHubResourceName = "UniGetUI/GitHubAccessToken";
private static readonly string UserName = Environment.UserName;

public static void StoreToken(string token)
{
if (string.IsNullOrEmpty(token))
{
Logger.Warn("Attempted to store a null or empty token. Operation cancelled.");
return;
}

var vault = new PasswordVault();
var newCredential = new PasswordCredential(GitHubResourceName, UserName, token);

try
{
if (GetToken() is not null)
{
DeleteToken();
}
}
catch
{
// ignore
}

vault.Add(newCredential);
Logger.Info("GitHub access token stored/updated securely.");
}

public static string? GetToken()
{
try
{
var vault = new PasswordVault();
var credential = vault.Retrieve(GitHubResourceName, UserName);
credential.RetrievePassword();
Logger.Debug("GitHub access token retrieved.");
return credential.Password;
}
catch (Exception ex)
{
Logger.Warn($"Could not retrieve token (it may not exist): {ex.Message}");
return null;
}
}

public static void DeleteToken()
{
var vault = new PasswordVault();
var credentials = vault.FindAllByResource(GitHubResourceName);
if (credentials.Count > 0)
{
foreach (var cred in credentials)
{
vault.Remove(cred);
}
Logger.Info("GitHub access token deleted.");
}
else
{
Logger.Info("No GitHub access token found to delete.");
}
}
}
}
37 changes: 23 additions & 14 deletions src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,12 @@ namespace UniGetUI.Core.SettingsEngine;

public partial class Settings
{
public static void ExportToJSON(string path)
public static void ExportToFile_JSON(string path)
{
Dictionary<string, string> settings = [];
foreach (string entry in Directory.EnumerateFiles(CoreData.UniGetUIUserConfigurationDirectory))
{
if(new[] {"OperationHistory", "WinGetAlreadyUpgradedPackages.json", "TelemetryClientToken", "CurrentSessionToken"}.Contains(entry.Split("\\")[^1]))
continue;

settings.Add(Path.GetFileName(entry), File.ReadAllText(entry));
}

File.WriteAllText(path, JsonSerializer.Serialize(settings, SerializationOptions));
File.WriteAllText(path, ExportToString_JSON());
}

public static void ImportFromJSON(string path)
public static void ImportFromFile_JSON(string path)
{
if (Path.GetDirectoryName(path) == CoreData.UniGetUIUserConfigurationDirectory)
{
Expand All @@ -29,16 +20,34 @@ public static void ImportFromJSON(string path)
File.Copy(path, newPath);
path = newPath;
}
ImportFromString_JSON(path);
}

public static string ExportToString_JSON()
{
Dictionary<string, string> settings = [];
foreach (string entry in Directory.EnumerateFiles(CoreData.UniGetUIUserConfigurationDirectory))
{
if (new[] { "OperationHistory", "WinGetAlreadyUpgradedPackages.json", "TelemetryClientToken", "CurrentSessionToken" }.Contains(Path.GetFileName(entry)))
continue;

settings.Add(Path.GetFileName(entry), File.ReadAllText(entry));
}
return JsonSerializer.Serialize(settings, SerializationOptions);
}

public static void ImportFromString_JSON(string jsonContent)
{
ResetSettings();
Dictionary<string, string> settings = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(path), SerializationOptions) ?? [];
Dictionary<string, string> settings = JsonSerializer.Deserialize<Dictionary<string, string>>(jsonContent, SerializationOptions) ?? [];
foreach (KeyValuePair<string, string> entry in settings)
{
if(new[] {"OperationHistory", "WinGetAlreadyUpgradedPackages.json", "TelemetryClientToken", "CurrentSessionToken"}.Contains(entry.Key))
if (new[] { "OperationHistory", "WinGetAlreadyUpgradedPackages.json", "TelemetryClientToken", "CurrentSessionToken" }.Contains(entry.Key))
continue;

File.WriteAllText(Path.Join(CoreData.UniGetUIUserConfigurationDirectory, entry.Key), entry.Value);
}
Logger.Info("Settings successfully imported from string content.");
}

public static void ResetSettings()
Expand Down
8 changes: 6 additions & 2 deletions src/UniGetUI.Core.Settings/SettingsEngine_Names.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public enum K
AlreadyWarnedAboutAdmin,
ShownTelemetryBanner,
CollapseNavMenuOnWideScreen,
EnablePackageBackup,
EnablePackageBackup_LOCAL,
EnablePackageBackup_CLOUD,
ChangeBackupOutputDirectory,
DisableWinGetMalfunctionDetector,
EnableBackupTimestamping,
Expand Down Expand Up @@ -76,6 +77,7 @@ public enum K
DisableProgressNotifications,
KillProcessesThatRefuseToDie,
ManagerPaths,
GitHubUserLogin,

Test1,
Test2,
Expand Down Expand Up @@ -126,7 +128,8 @@ public static string ResolveKey(K key)
K.AlreadyWarnedAboutAdmin => "AlreadyWarnedAboutAdmin",
K.ShownTelemetryBanner => "ShownTelemetryBanner",
K.CollapseNavMenuOnWideScreen => "CollapseNavMenuOnWideScreen",
K.EnablePackageBackup => "EnablePackageBackup",
K.EnablePackageBackup_LOCAL => "EnablePackageBackup",
K.EnablePackageBackup_CLOUD => "EnablePackageBackup_CLOUD",
K.ChangeBackupOutputDirectory => "ChangeBackupOutputDirectory",
K.DisableWinGetMalfunctionDetector => "DisableWinGetMalfunctionDetector",
K.EnableBackupTimestamping => "EnableBackupTimestamping",
Expand Down Expand Up @@ -163,6 +166,7 @@ public static string ResolveKey(K key)
K.DisableProgressNotifications => "DisableProgressNotifications",
K.KillProcessesThatRefuseToDie => "KillProcessesThatRefuseToDie",
K.ManagerPaths => "ManagerPaths",
K.GitHubUserLogin => "GitHubUserLogin",

K.Test1 => "TestSetting1",
K.Test2 => "TestSetting2",
Expand Down
2 changes: 1 addition & 1 deletion src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static class TelemetryHandler
Settings.K.DisableAutoCheckforUpdates,
Settings.K.AutomaticallyUpdatePackages,
Settings.K.AskToDeleteNewDesktopShortcuts,
Settings.K.EnablePackageBackup,
Settings.K.EnablePackageBackup_LOCAL,
Settings.K.DoCacheAdminRights,
Settings.K.DoCacheAdminRightsForBatches,
Settings.K.ForceLegacyBundledWinGet,
Expand Down
9 changes: 8 additions & 1 deletion src/UniGetUI/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,14 @@ public async Task ShowMainWindowFromRedirectAsync(AppActivationArguments rawArgs
Logger.Warn("REDIRECTOR ACTIVATOR: args.Kind is not Launch but rather " + kind);
}

MainWindow.DispatcherQueue.TryEnqueue(MainWindow.Activate);
/*if (kind == ExtendedActivationKind.Protocol)
{
if (rawArgs.Data is IProtocolActivatedEventArgs protocolArgs)
{
Logger.Info($"Protocol activation received: {protocolArgs.Uri}");
}
MainWindow.DispatcherQueue.TryEnqueue(MainWindow.Activate);
}*/
}

public async void DisposeAndQuit(int outputCode = 0)
Expand Down
1 change: 0 additions & 1 deletion src/UniGetUI/AppOperationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using UniGetUI.Interface.Telemetry;
using UniGetUI.PackageEngine.Enums;
using UniGetUI.PackageEngine.Interfaces;
using UniGetUI.PackageEngine.Managers.CargoManager;
using UniGetUI.PackageEngine.Managers.PowerShellManager;
using UniGetUI.PackageEngine.Operations;
using UniGetUI.PackageEngine.PackageClasses;
Expand Down
4 changes: 2 additions & 2 deletions src/UniGetUI/CLIHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static int ImportSettings()

try
{
Settings.ImportFromJSON(file);
Settings.ImportFromFile_JSON(file);
}
catch (Exception ex)
{
Expand All @@ -87,7 +87,7 @@ public static int ExportSettings()

try
{
Settings.ExportToJSON(file);
Settings.ExportToFile_JSON(file);
}
catch (Exception ex)
{
Expand Down
10 changes: 7 additions & 3 deletions src/UniGetUI/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UniGetUI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:services="using:UniGetUI.Services"
xmlns:widgets="using:UniGetUI.Interface.Widgets"
xmlns:winex="using:WinUIEx"
Title="UniGetUI"
Expand All @@ -32,18 +33,21 @@
x:Name="TitleBar"
Title="UniGetUI"
Grid.Row="0"
Margin="0,4"
Margin="0,0,0,-4"
BackRequested="TitleBar_OnBackRequested"
IsBackButtonVisible="False"
IsPaneToggleButtonVisible="True"
PaneToggleRequested="TitleBar_PaneToggleRequested"
IsBackButtonVisible="False"
BackRequested="TitleBar_OnBackRequested"
Visibility="Collapsed">
<winex:TitleBar.IconSource>
<ImageIconSource ImageSource="ms-appx:///Assets/Images/icon.png" />
</winex:TitleBar.IconSource>
<!--winex:TitleBar.Content>
<UserControl Height="10" />
</winex:TitleBar.Content-->
<winex:TitleBar.Footer>
<services:UserAvatar />
</winex:TitleBar.Footer>
</winex:TitleBar>
<StackPanel
Grid.Row="1"
Expand Down
2 changes: 1 addition & 1 deletion src/UniGetUI/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ public void ProcessCommandLineParameters()
{
// Handle potential JSON files
Logger.ImportantInfo("Begin attempt to open the package bundle " + param);
NavigationPage.LoadBundleFile(param);
NavigationPage.LoadBundleFromFile(param);
}
else if (param.EndsWith("UniGetUI.exe") || param.EndsWith("UniGetUI.dll"))
{
Expand Down
2 changes: 1 addition & 1 deletion src/UniGetUI/Package.appxmanifest
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>

<Package
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
Expand Down
42 changes: 40 additions & 2 deletions src/UniGetUI/Pages/DialogPages/DialogHelper_Generic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ namespace UniGetUI.Pages.DialogPages;

public static partial class DialogHelper
{
private static class DialogFactory
internal static class DialogFactory
{
public static ContentDialog Create()
{
Expand Down Expand Up @@ -61,7 +61,7 @@ public static ContentDialog Create_AsWindow(bool hasTitle, bool hasButtons = fal
}
}

public static MainWindow Window { private get; set; } = null!;
public static MainWindow Window { get; set; } = null!;

public static void ShowLoadingDialog(string text)
{
Expand Down Expand Up @@ -624,4 +624,42 @@ public static void ShowDismissableBalloon(string title, string message)
Window.DismissableNotification.Content = new TextBlock() { Text = message, TextWrapping = TextWrapping.Wrap };
Window.DismissableNotification.IsOpen = true;
}

public static async Task<string?> AskForBackupSelection(IEnumerable<string> availableBackups)
{
var dialog = DialogFactory.Create();
dialog.Title = CoreTools.Translate("Which backup do you want to open?");
dialog.PrimaryButtonText = CoreTools.Translate("Open");
dialog.SecondaryButtonText = CoreTools.Translate("Cancel");
dialog.DefaultButton = ContentDialogButton.Primary;
dialog.IsPrimaryButtonEnabled = false;

RadioButtons buttons = new RadioButtons();
foreach(var name in availableBackups) buttons.Items.Add(name);
buttons.SelectionChanged += (_, _) => dialog.IsPrimaryButtonEnabled = true;

dialog.Content = new StackPanel()
{
Orientation = Orientation.Vertical,
Spacing = 4,
Children =
{
new TextBlock() {
Text = CoreTools.Translate(
"Select the backup you want to open. Later, you will be able to review which packages you want to install."),
TextWrapping = TextWrapping.Wrap
},
new ScrollViewer()
{
Content = buttons,
HorizontalScrollMode = ScrollMode.Disabled
}
}
};

if(await Window.ShowDialogAsync(dialog) is ContentDialogResult.Primary)
return buttons.SelectedItem.ToString() ?? null;

return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using UniGetUI.Core.Language;
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.SettingsEngine.SecureSettings;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Enums;
Expand Down
1 change: 0 additions & 1 deletion src/UniGetUI/Pages/DialogPages/PackageDetailsPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.ObjectModel;
using System.Text;
using Windows.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
Expand Down
8 changes: 7 additions & 1 deletion src/UniGetUI/Pages/MainView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -466,12 +466,18 @@ private void MoreNavBtn_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRout
MoreNavButtonMenu.ShowAt(sender as FrameworkElement);
}

internal void LoadBundleFile(string param)
internal void LoadBundleFromFile(string param)
{
NavigateTo(PageType.Bundles);
BundlesPage?.OpenFromFile(param);
}

internal void LoadBundleFromString(string payload, BundleFormatType format, string source)
{
NavigateTo(PageType.Bundles);
BundlesPage?.OpenFromString(payload, format, source);
}

private void ClearAllFinished_OnClick(object sender, RoutedEventArgs e)
{
foreach (var widget in MainApp.Operations._operationList.ToArray())
Expand Down
Loading
Loading