Skip to content

Commit fea4ca7

Browse files
authored
Install specific Scoop versions and update pinned installs (#5619)
1 parent cc726cd commit fea4ca7

19 files changed

Lines changed: 533 additions & 52 deletions

File tree

‎media/supported-managers.svg‎

Lines changed: 2 additions & 2 deletions
Loading

‎src/Languages/lang_en.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2044,5 +2044,6 @@
20442044
"UniGetUI stops checking for updates and sending notifications until you open it again.": "UniGetUI stops checking for updates and sending notifications until you open it again.",
20452045
"UniGetUI is still running": "UniGetUI is still running",
20462046
"UniGetUI keeps running in the background and checking for updates, without a system tray icon. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu. You can change this in Settings > User interface preferences.": "UniGetUI keeps running in the background and checking for updates, without a system tray icon. To reopen it, launch UniGetUI again. To exit, use More > Quit UniGetUI in the navigation menu. You can change this in Settings > User interface preferences.",
2047-
"UniGetUI must keep running in the background when its window is closed for notifications to work": "UniGetUI must keep running in the background when its window is closed for notifications to work"
2047+
"UniGetUI must keep running in the background when its window is closed for notifications to work": "UniGetUI must keep running in the background when its window is closed for notifications to work",
2048+
"\"{0}\" is not a valid version.": "\"{0}\" is not a valid version."
20482049
}

‎src/UniGetUI.Avalonia/ViewModels/DialogPages/InstallOptionsViewModel.cs‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ partial void OnSelectedProfileChanged(string? value)
163163
[ObservableProperty] private bool _versionEnabled;
164164
public ObservableCollection<string> VersionOptions { get; } = [];
165165
[ObservableProperty] private string? _selectedVersion;
166+
[ObservableProperty] private bool _versionIsTyped;
167+
[ObservableProperty] private string _typedVersion = "";
168+
public string TypedVersionPlaceholder { get; } = CoreTools.Translate("Latest");
166169

167170
[ObservableProperty] private bool _skipMinorChecked;
168171
// Index 0 => keep first 1 number significant (level 2), 1 => first 2 (level 3), 2 => first 3 (level 4).
@@ -182,6 +185,7 @@ partial void OnSelectedProfileChanged(string? value)
182185
partial void OnInteractiveCheckedChanged(bool value) => Refresh();
183186
partial void OnSkipHashCheckedChanged(bool value) => Refresh();
184187
partial void OnSelectedVersionChanged(string? value) => Refresh();
188+
partial void OnTypedVersionChanged(string value) => Refresh();
185189

186190
// ── Architecture / Scope / Location tab ───────────────────────────────────
187191
[ObservableProperty] private bool _archEnabled;
@@ -326,6 +330,8 @@ public InstallOptionsViewModel(IPackage package, OperationType operation, Instal
326330
? CoreTools.Translate("PreRelease")
327331
: CoreTools.Translate("Latest");
328332
VersionEnabled = caps.SupportsCustomVersions || caps.SupportsPreRelease;
333+
VersionIsTyped = caps.SupportsCustomVersions && !caps.CanListVersions;
334+
TypedVersion = options.Version;
329335

330336
// Architecture
331337
string defaultLabel = CoreTools.Translate("Default");
@@ -386,7 +392,7 @@ public InstallOptionsViewModel(IPackage package, OperationType operation, Instal
386392
PackageIcon = BitmapLoader.TryLoadAsset(_fallbackIconUri);
387393
_ = LoadIconAsync();
388394

389-
if (caps.SupportsCustomVersions)
395+
if (caps.SupportsCustomVersions && caps.CanListVersions)
390396
_ = LoadVersionsAsync(options.Version);
391397

392398
_ = LoadIgnoredUpdatesAsync();
@@ -717,6 +723,8 @@ private InstallOptions SnapshotOptions()
717723
var ver = SelectedVersion ?? "";
718724
o.PreRelease = ver == CoreTools.Translate("PreRelease");
719725
o.Version = (ver != CoreTools.Translate("Latest") && ver != CoreTools.Translate("PreRelease") && ver.Length > 0) ? ver : "";
726+
if (VersionIsTyped)
727+
o.Version = TypedVersion.Trim();
720728

721729
string defaultLabel = CoreTools.Translate("Default");
722730
o.Architecture = (SelectedArch != defaultLabel && SelectedArch is not null) ? SelectedArch : "";

‎src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsControl.axaml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,17 @@
317317
ItemsSource="{Binding VersionOptions}"
318318
SelectedItem="{Binding SelectedVersion}"
319319
IsEnabled="{Binding VersionEnabled}"
320+
IsVisible="{Binding !VersionIsTyped}"
320321
automation:AutomationProperties.LabeledBy="{Binding #VersionLabelBlock}"
321322
HorizontalAlignment="Stretch"/>
323+
<TextBox Grid.Column="1"
324+
Classes="install-option-input search-field-standalone"
325+
Text="{Binding TypedVersion}"
326+
PlaceholderText="{Binding TypedVersionPlaceholder}"
327+
IsEnabled="{Binding VersionEnabled}"
328+
IsVisible="{Binding VersionIsTyped}"
329+
automation:AutomationProperties.LabeledBy="{Binding #VersionLabelBlock}"
330+
HorizontalAlignment="Stretch"/>
322331
</Grid>
323332

324333
<Border Height="1" Background="{DynamicResource AppBorderBrush}"

‎src/UniGetUI.Avalonia/Views/SoftwarePages/CatalogEditorPage.cs‎

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,15 +326,29 @@ private async Task PinVersionAsync(IPackage? package)
326326
MinWidth = 280,
327327
};
328328
AutomationProperties.SetName(picker, CoreTools.Translate("Version"));
329+
bool typed = !package.Manager.Capabilities.CanListVersions;
330+
var typedBox = new TextBox
331+
{
332+
Text = entry.Version,
333+
PlaceholderText = latest,
334+
HorizontalAlignment = HorizontalAlignment.Stretch,
335+
MinWidth = 280,
336+
};
337+
AutomationProperties.SetName(typedBox, CoreTools.Translate("Version"));
329338
var status = new TextBlock { Text = CoreTools.Translate("Loading versions"), TextWrapping = TextWrapping.Wrap };
330339
var body = new StackPanel { Spacing = 8 };
331340
body.Children.Add(new TextBlock
332341
{
333342
Text = CoreTools.Translate("Choose the version of {0} that this catalog installs.", entry.Name),
334343
TextWrapping = TextWrapping.Wrap,
335344
});
336-
body.Children.Add(picker);
337-
body.Children.Add(status);
345+
if (typed)
346+
body.Children.Add(typedBox);
347+
else
348+
{
349+
body.Children.Add(picker);
350+
body.Children.Add(status);
351+
}
338352
var dialog = new ImmersiveConfirmationDialog(CoreTools.Translate("Pin version"), body,
339353
CoreTools.Translate("Pin version"), CoreTools.Translate("Cancel"));
340354

@@ -357,11 +371,27 @@ async Task LoadVersionsAsync()
357371
picker.SelectedItem = entry.Version.Length > 0 ? entry.Version : latest;
358372
picker.IsEnabled = true;
359373
}
360-
_ = LoadVersionsAsync();
374+
if (!typed)
375+
_ = LoadVersionsAsync();
361376
await dialog.ShowDialog(window);
362377

363-
if (dialog.Result is not true || picker.SelectedItem is not string chosen || !_document.CanEdit) return;
364-
string version = chosen == latest ? "" : chosen;
378+
if (dialog.Result is not true || !_document.CanEdit) return;
379+
string version;
380+
if (typed)
381+
{
382+
version = (typedBox.Text ?? "").Trim();
383+
if (version.Length > 0 && !CoreTools.IsValidPackageVersion(version))
384+
{
385+
window.ShowBanner(CoreTools.Translate("Pin version"),
386+
CoreTools.Translate("\"{0}\" is not a valid version.", version),
387+
MainWindow.RuntimeNotificationLevel.Error);
388+
return;
389+
}
390+
}
391+
else if (picker.SelectedItem is string chosen)
392+
version = chosen == latest ? "" : chosen;
393+
else
394+
return;
365395
int index = catalog.Packages.IndexOf(entry);
366396
if (version == entry.Version || index < 0) return;
367397
// Replace the entry rather than mutating it, so the list and the unsaved-changes state update.

‎src/UniGetUI.PackageEngine.Enums/ManagerCapabilities.cs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public struct ManagerCapabilities
4343
public bool CanListDependencies = false;
4444
public bool SupportsReleaseNotes = false;
4545
public bool SupportsCustomVersions = false;
46+
public bool CanListVersions = true;
47+
public bool SupportsCustomVersionsOnUpdate = true;
4648
public bool SupportsCustomArchitectures = false;
4749
public string[] SupportedCustomArchitectures = [];
4850
public bool SupportsCustomScopes = false;

‎src/UniGetUI.PackageEngine.Managers.Scoop/Helpers/ScoopPkgDetailsHelper.cs‎

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,7 @@ protected override void GetDetails_UnSafe(IPackageDetails details)
4141
}
4242
}
4343

44-
string packageId;
45-
// If source is ellipsed, a local path, or a URL manifest, omit source argument
46-
if (
47-
details.Package.Source.Name.Contains("...")
48-
|| details.Package.Source.Name.Contains(":\\")
49-
|| details.Package.Source.Name.StartsWith("http")
50-
)
51-
packageId = Scoop.RequireSafePackageSpec(details.Package.Id);
52-
else
53-
packageId = Scoop.RequireSafePackageSpec(
54-
$"{details.Package.Source.Name}/{details.Package.Id}"
55-
);
44+
string packageId = Scoop.GetPackageSpec(details.Package);
5645

5746
var startInfo = new ProcessStartInfo
5847
{
@@ -319,9 +308,6 @@ protected override IReadOnlyList<Uri> GetScreenshots_UnSafe(IPackage package)
319308
return null;
320309
}
321310

322-
protected override IReadOnlyList<string> GetInstallableVersions_UnSafe(IPackage package)
323-
{
324-
throw new InvalidOperationException("Scoop does not support custom package versions");
325-
}
311+
protected override IReadOnlyList<string> GetInstallableVersions_UnSafe(IPackage package) => [];
326312
}
327313
}

‎src/UniGetUI.PackageEngine.Managers.Scoop/Helpers/ScoopPkgOperationHelper.cs‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,13 @@ OperationType operation
2929
},
3030
];
3131

32-
// If source is ellipsed, a local path, or a URL manifest, omit source argument
33-
if (package.Source.Name.Contains("...") || package.Source.Name.Contains(":\\") || package.Source.Name.StartsWith("http"))
34-
parameters.Add(Scoop.RequireSafePackageSpec(package.Id));
35-
else
36-
parameters.Add(Scoop.RequireSafePackageSpec($"{package.Source.Name}/{package.Id}"));
32+
string spec = Scoop.GetPackageSpec(package);
33+
if (operation is OperationType.Install && options.Version.Length > 0)
34+
spec = Scoop.RequireSafePackageSpec($"{spec}@{options.Version}");
35+
parameters.Add(spec);
36+
37+
if (operation is OperationType.Update && ((Scoop)Manager).IsPinnedInstall(package))
38+
parameters.Add("--force");
3739

3840
if (
3941
package.OverridenOptions.Scope == PackageScope.Global

0 commit comments

Comments
 (0)