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
6 changes: 5 additions & 1 deletion src/Languages/lang_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1075,5 +1075,9 @@
"Could not import settings": "Could not import settings",
"The installer for {package} does not match the hash in its manifest": "The installer for {package} does not match the hash in its manifest",
"The package manifest is likely out of date. WinGet cannot skip this check while running as administrator.": "The package manifest is likely out of date. WinGet cannot skip this check while running as administrator.",
"The package manifest is likely out of date. Skipping this check requires WinGet's InstallerHashOverride administrator setting.": "The package manifest is likely out of date. Skipping this check requires WinGet's InstallerHashOverride administrator setting."
"The package manifest is likely out of date. Skipping this check requires WinGet's InstallerHashOverride administrator setting.": "The package manifest is likely out of date. Skipping this check requires WinGet's InstallerHashOverride administrator setting.",
"Subfolder for each package:": "Subfolder for each package:",
"%PACKAGE% is replaced with the package ID, and %NAME% with the package name.": "%PACKAGE% is replaced with the package ID, and %NAME% with the package name.",
"Package name": "Package name",
"No subfolder": "No subfolder"
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public partial class InstallOptionsViewModel : ObservableObject
public string ParamsUpdateLabel { get; } = CoreTools.Translate("Custom update arguments:");
public string ParamsUninstallLabel { get; } = CoreTools.Translate("Custom uninstall arguments:");
public string CliArgsHintLabel { get; } = CoreTools.Translate("These fields are independent: an argument set for Install won't apply to Update or Uninstall, and vice versa.");
public string LocationPlaceholderHintLabel { get; } = CoreTools.Translate("%PACKAGE% is replaced with the package ID, and %NAME% with the package name.");
public string EnvVarSyntaxHintLabel { get; } = Settings.Get(Settings.K.ExpandEnvVarsWithPercentSyntax)
? CoreTools.Translate("Environment variables use %VARIABLE% syntax.")
: CoreTools.Translate("Environment variables use <VARIABLE> syntax.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ private static void ShowPackageDetails(PackageOperation packageOp)
private static async Task ShowInstallOptionsAsync(PackageOperation packageOp)
{
if (GetMainWindow() is not { } mainWindow) return;
var opts = await InstallOptionsFactory.LoadApplicableAsync(packageOp.Package);
var opts = await InstallOptionsFactory.LoadForPackageAsync(packageOp.Package);
var win = new InstallOptionsWindow(packageOp.Package, OperationType.None, opts);
await win.ShowDialog(mainWindow);
await InstallOptionsFactory.SaveForPackageAsync(opts, packageOp.Package);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public partial class InstallOptionsPanelViewModel : ViewModelBase
{
private readonly IPackageManager _manager;
private readonly string _defaultLocationLabel;
private readonly string _subfolderIdLabel = CoreTools.Translate("Package ID");
private readonly string _subfolderNameLabel = CoreTools.Translate("Package name");
private readonly string _subfolderNoneLabel = CoreTools.Translate("No subfolder");

public event EventHandler? NavigateToAdministratorRequested;

Expand Down Expand Up @@ -55,6 +58,8 @@ public partial class InstallOptionsPanelViewModel : ViewModelBase
[ObservableProperty] private bool _locationSelectEnabled;
[ObservableProperty] private bool _locationResetEnabled;
[ObservableProperty] private string _locationText = "";
[ObservableProperty] private ObservableCollection<string> _subfolderItems = [];
[ObservableProperty] private string? _selectedSubfolder;

// ── CLI args ──────────────────────────────────────────────────────────────
[ObservableProperty] private bool _cliSectionEnabled;
Expand All @@ -74,6 +79,8 @@ public partial class InstallOptionsPanelViewModel : ViewModelBase
public string LocationLabel { get; } = CoreTools.Translate("Install location:");
public string SelectDirLabel { get; } = CoreTools.Translate("Select");
public string ResetDirLabel { get; } = CoreTools.Translate("Reset");
public string SubfolderLabel { get; } = CoreTools.Translate("Subfolder for each package:");
public string LocationPlaceholderHintLabel { get; } = CoreTools.Translate("%PACKAGE% is replaced with the package ID, and %NAME% with the package name.");
public string InstallArgsLabel { get; } = CoreTools.Translate("Custom install arguments:");
public string UpdateArgsLabel { get; } = CoreTools.Translate("Custom update arguments:");
public string UninstallArgsLabel { get; } = CoreTools.Translate("Custom uninstall arguments:");
Expand All @@ -92,11 +99,18 @@ public partial class InstallOptionsPanelViewModel : ViewModelBase
public double ArchOpacity => ArchitectureEnabled ? 1.0 : 0.5;
public double ScopeOpacity => ScopeEnabled ? 1.0 : 0.5;
public double LocationOpacity => LocationSelectEnabled ? 1.0 : 0.5;
public bool SubfolderEnabled => LocationSelectEnabled && LocationResetEnabled;

partial void OnCliSectionEnabledChanged(bool value) => OnPropertyChanged(nameof(CliOpacity));
partial void OnArchitectureEnabledChanged(bool value) => OnPropertyChanged(nameof(ArchOpacity));
partial void OnScopeEnabledChanged(bool value) => OnPropertyChanged(nameof(ScopeOpacity));
partial void OnLocationSelectEnabledChanged(bool value) => OnPropertyChanged(nameof(LocationOpacity));
partial void OnLocationResetEnabledChanged(bool value) => OnPropertyChanged(nameof(SubfolderEnabled));

partial void OnLocationSelectEnabledChanged(bool value)
{
OnPropertyChanged(nameof(LocationOpacity));
OnPropertyChanged(nameof(SubfolderEnabled));
}

// Mark HasChanges when user edits options (guards against firing during load)
partial void OnAdminCheckedChanged(bool value) => HasChanges = !IsLoading;
Expand All @@ -107,6 +121,13 @@ public partial class InstallOptionsPanelViewModel : ViewModelBase
partial void OnSelectedArchitectureChanged(string? value) => HasChanges = !IsLoading;
partial void OnSelectedScopeChanged(string? value) => HasChanges = !IsLoading;

partial void OnSelectedSubfolderChanged(string? value)
{
if (IsLoading || value is null || !LocationResetEnabled) return;
LocationText = _withSubfolder(LocationText, value);
HasChanges = true;
}

public InstallOptionsPanelViewModel(IPackageManager manager)
{
_manager = manager;
Expand All @@ -122,6 +143,11 @@ public InstallOptionsPanelViewModel(IPackageManager manager)
_scopeItems.Add(CoreTools.Translate(CommonTranslations.ScopeNames[PackageScope.Local]));
_scopeItems.Add(CoreTools.Translate(CommonTranslations.ScopeNames[PackageScope.Global]));

_subfolderItems.Add(_subfolderIdLabel);
_subfolderItems.Add(_subfolderNameLabel);
_subfolderItems.Add(_subfolderNoneLabel);
_selectedSubfolder = _subfolderIdLabel;

_ = DoLoadOptions();
}

Expand Down Expand Up @@ -236,13 +262,15 @@ private async Task DoLoadOptions()
{
LocationText = options.CustomInstallLocation;
LocationResetEnabled = true;
SelectedSubfolder = _subfolderLabelFor(LocationText);
}
else
{
LocationText = _manager.Capabilities.SupportsCustomLocations
? _defaultLocationLabel
: CoreTools.Translate("Install location can't be changed for {0} packages", _manager.DisplayName);
LocationResetEnabled = false;
SelectedSubfolder = _subfolderIdLabel;
}

// CLI
Expand All @@ -267,19 +295,82 @@ private async Task SelectLocation(Visual? visual)
if (folders is not [{ } folder]) return;
var path = folder.TryGetLocalPath();
if (string.IsNullOrEmpty(path)) return;
LocationText = path.TrimEnd('/').TrimEnd('\\') + "/%PACKAGE%";
LocationText = _withSubfolder(path, SelectedSubfolder);
LocationResetEnabled = true;
HasChanges = true;
}

[RelayCommand]
private void ResetLocation()
{
LocationText = _defaultLocationLabel;
LocationResetEnabled = false;
LocationText = _defaultLocationLabel;
SelectedSubfolder = _subfolderIdLabel;
HasChanges = true;
}

private string _subfolderLabelFor(string location)
{
string trimmed = location.TrimEnd('/', '\\');

if (trimmed.EndsWith(InstallOptionsFactory.PackageNamePlaceholder, StringComparison.OrdinalIgnoreCase))
return _subfolderNameLabel;

if (trimmed.EndsWith(InstallOptionsFactory.PackageIdPlaceholder, StringComparison.OrdinalIgnoreCase))
return _subfolderIdLabel;

return _subfolderNoneLabel;
}

private string _withSubfolder(string location, string? subfolderLabel)
{
string[] placeholders =
[
InstallOptionsFactory.PackageIdPlaceholder,
InstallOptionsFactory.PackageNamePlaceholder,
];

string basePath = location.TrimEnd('/', '\\');

foreach (var placeholder in placeholders)
{
if (basePath.EndsWith(placeholder, StringComparison.OrdinalIgnoreCase))
{
basePath = basePath[..^placeholder.Length];
break;
}
}

basePath = _asDirectoryPath(basePath);

string subfolder =
subfolderLabel == _subfolderNameLabel ? InstallOptionsFactory.PackageNamePlaceholder
: subfolderLabel == _subfolderIdLabel ? InstallOptionsFactory.PackageIdPlaceholder
: "";

if (basePath.Length is 0)
return subfolder;

if (subfolder.Length is 0)
return basePath;

return basePath[^1] is '/' or '\\'
? basePath + subfolder
: basePath + Path.DirectorySeparatorChar + subfolder;
}

private static string _asDirectoryPath(string path)
{
string trimmed = path.TrimEnd('/', '\\');

if (trimmed.Length is 0)
return path.Length is 0 ? path : path[..1];

return trimmed.Length is 2 && trimmed[1] is ':'
? trimmed + Path.DirectorySeparatorChar
: trimmed;
}

// ── Navigation ────────────────────────────────────────────────────────────

[RelayCommand]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,12 @@
FontSize="14"
HorizontalAlignment="Stretch"/>

<TextBlock Text="{Binding LocationPlaceholderHintLabel}"
IsVisible="{Binding LocationEnabled}"
TextWrapping="Wrap"
Opacity="0.7"
FontSize="12"/>

<Grid ColumnDefinitions="*,Auto" ColumnSpacing="8"
IsVisible="{Binding LocationEnabled}">
<TextBlock Text="{Binding EnvVarSyntaxHintLabel}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,27 @@
FontSize="14"
Opacity="0.6"/>

<Grid ColumnDefinitions="*,*">
<TextBlock x:Name="SubfolderLabelBlock"
Grid.Column="0"
Text="{Binding SubfolderLabel}"
VerticalAlignment="Center"
FontSize="14"
Opacity="{Binding LocationOpacity}"/>
<ComboBox Grid.Column="1"
ItemsSource="{Binding SubfolderItems}"
SelectedItem="{Binding SelectedSubfolder}"
IsEnabled="{Binding SubfolderEnabled}"
HorizontalAlignment="Stretch"
MaxWidth="200"
automation:AutomationProperties.LabeledBy="{Binding #SubfolderLabelBlock}"/>
</Grid>
<TextBlock Text="{Binding LocationPlaceholderHintLabel}"
IsVisible="{Binding LocationSelectEnabled}"
TextWrapping="Wrap"
Opacity="0.7"
FontSize="12"/>

<!-- CLI disabled warning -->
<TextBlock Text="{Binding CliDisabledLabel}"
IsVisible="{Binding CliDisabledWarningVisible}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,11 @@ private async Task CreateBatchScriptAsync()
IReadOnlyList<string> param;
try
{
var exported = pkg.installation_options.Copy();
exported.CustomInstallLocation = InstallOptionsFactory.ExpandPackagePlaceholders(
exported.CustomInstallLocation, pkg);
param = pkg.Manager.OperationHelper.GetStandaloneParameters(
pkg, pkg.installation_options, OperationType.Install);
pkg, exported, OperationType.Install);
}
catch (InvalidOperationException ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ namespace UniGetUI.PackageEngine.PackageClasses
/// </summary>
public static class InstallOptionsFactory
{
public const string PackageIdPlaceholder = "%PACKAGE%";
public const string PackageNamePlaceholder = "%NAME%";

public static bool IsIdentityScopedOptionsFile(string fileName) =>
StoragePath.IsIdentityScoped(fileName);

Expand Down Expand Up @@ -162,14 +165,12 @@ public static InstallOptions LoadApplicable(
$"Package {package.Id} does not override options, will use package manager's default..."
);
instance = LoadForManager(package.Manager);

var legalizedId = CoreTools.MakeValidFileName(package.Id);
instance.CustomInstallLocation = instance.CustomInstallLocation.Replace(
"%PACKAGE%",
legalizedId
);
}

instance.CustomInstallLocation = ExpandPackagePlaceholders(
instance.CustomInstallLocation,
package
);
instance.CustomInstallLocationIsExplicit = locationIsExplicit;

if (elevated is not null)
Expand Down Expand Up @@ -399,6 +400,25 @@ private static void _expandAndSanitizeCliArguments(List<string> parameters)
}
}

public static string ExpandPackagePlaceholders(string location, IPackage package)
{
if (!location.Contains('%'))
return location;

string legalizedId = _legalizeFolderName(package.Id);
string legalizedName = _legalizeFolderName(package.Name);

if (legalizedName.Length is 0)
legalizedName = legalizedId;

return location
.Replace(PackageIdPlaceholder, legalizedId, StringComparison.OrdinalIgnoreCase)
.Replace(PackageNamePlaceholder, legalizedName, StringComparison.OrdinalIgnoreCase);
Comment thread
GabrielDuf marked this conversation as resolved.
}

private static string _legalizeFolderName(string value) =>
CoreTools.MakeValidFileName(value.Replace("%", ""));

private static string _expandEnvironmentVariables(string value)
{
if (string.IsNullOrEmpty(value))
Expand Down
Loading
Loading