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
6 changes: 5 additions & 1 deletion src/Languages/lang_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1743,5 +1743,9 @@
"{0} packages cannot finish until your computer restarts.": "{0} packages cannot finish until your computer restarts.",
"Your computer will restart now. Save your work and close any open applications first.": "Your computer will restart now. Save your work and close any open applications first.",
"The computer could not be restarted": "The computer could not be restarted",
"UniGetUI could not start the restart. Please restart your computer manually.": "UniGetUI could not start the restart. Please restart your computer manually."
"UniGetUI could not start the restart. Please restart your computer manually.": "UniGetUI could not start the restart. Please restart your computer manually.",
"No installer is available for {0}": "No installer is available for {0}",
"{0} did not report a download address for this package. It may not be offered for your system, or it may no longer exist on {1}.": "{0} did not report a download address for this package. It may not be offered for your system, or it may no longer exist on {1}.",
"Some packages cannot be downloaded": "Some packages cannot be downloaded",
"{0} of the {1} selected packages were skipped, because their package manager cannot download installers or could not be found.": "{0} of the {1} selected packages were skipped, because their package manager cannot download installers or could not be found."
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ public static async Task AskLocationAndDownloadAsync(IPackage? package, TEL_Inst
if (package.Details.InstallerUrl is null)
{
Logger.Warn($"No installer URL found for {package.Id}");
win.ShowBanner(
CoreTools.Translate("No installer is available for {0}", package.Name),
CoreTools.Translate(
"{0} did not report a download address for this package. It may not be "
+ "offered for your system, or it may no longer exist on {1}.",
package.Manager.DisplayName,
package.Source.AsString_DisplayName
),
MainWindow.RuntimeNotificationLevel.Error
);
return;
}

Expand Down Expand Up @@ -160,10 +170,25 @@ public static async Task DownloadSelectedAsync(IEnumerable<IPackage> packages, T
{
if (MainWindow.Instance is not { } win) return;

var eligible = packages
var selected = packages.ToList();
var eligible = selected
.Where(p => !p.Source.IsVirtualManager && p.Manager.Capabilities.CanDownloadInstaller)
.ToList();

if (eligible.Count < selected.Count)
{
win.ShowBanner(
CoreTools.Translate("Some packages cannot be downloaded"),
CoreTools.Translate(
"{0} of the {1} selected packages were skipped, because their package "
+ "manager cannot download installers or could not be found.",
selected.Count - eligible.Count,
selected.Count
),
MainWindow.RuntimeNotificationLevel.Error
);
}

if (eligible.Count == 0) return;

var folders = await win.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
Expand Down
6 changes: 6 additions & 0 deletions src/UniGetUI.PackageEngine.Interfaces/IPackageDetails.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ public interface IPackageDetails
/// </summary>
public Uri? LicenseUrl { get; set; }

/// <summary>
/// The version of the package the installer URL points at, as reported by the
/// loaded manifest. Null when the manager does not report it.
/// </summary>
public string? Version { get; set; }

/// <summary>
/// A URL pointing to the installer of the package
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ protected override void GetDetails_UnSafe(IPackageDetails details)
logger.AddToStdOut(strContents);
JsonObject? contents = JsonNode.Parse(strContents) as JsonObject;

details.Version = contents?["version"]?.ToString();
details.License = contents?["license"]?.ToString();
details.Description = contents?["description"]?.ToString();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,14 @@ protected override void GetDetails_UnSafe(IPackageDetails details)
var categories = manifest.categories?.Select(c => c.category) ?? [];
details.Tags = [.. keywords, .. categories];

var versionData = manifest.versions.FirstOrDefault(v =>
v.num == details.Package.VersionString
);
var versionData = details.Package.HasConcreteVersion
? manifest.versions.FirstOrDefault(v => v.num == details.Package.VersionString)
: manifest.versions.FirstOrDefault(v => v.num == manifest.crate.max_stable_version)
?? manifest.versions.FirstOrDefault(v => v.num == manifest.crate.newest_version);

if (versionData is not null)
{
details.Version = versionData.num;
details.Author = versionData.published_by?.name;
details.License = versionData.license;
details.InstallerUrl = new Uri(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,25 @@ protected override void GetDetails_UnSafe(IPackageDetails details)
var logger = Manager.TaskLogger.CreateNew(LoggableTaskType.LoadPackageDetails);
try
{
string version = ResolveDetailsVersion(details.Package);
details.Version = version;

if (NuGetLocalFeed.TryGetDirectory(details.Package.Source, out string directory))
{
logger.Close(GetDetailsLocal(details, directory, logger) ? 0 : 1);
logger.Close(GetDetailsLocal(details, directory, version, logger) ? 0 : 1);
return;
}

if (NuGetV3ServiceIndex.IsV3Source(details.Package.Source))
{
logger.Close(GetDetailsV3(details, logger) ? 0 : 1);
logger.Close(GetDetailsV3(details, version, logger) ? 0 : 1);
return;
}

details.ManifestUrl = NuGetManifestLoader.GetManifestUrl(details.Package);
details.ManifestUrl = NuGetManifestLoader.GetManifestUrl(details.Package, version);
string? PackageManifestContents = NuGetManifestLoader.GetManifestContent(
details.Package
details.Package,
version
);
logger.Log(PackageManifestContents);

Expand Down Expand Up @@ -240,20 +244,17 @@ Match match in Regex.Matches(
private static bool GetDetailsLocal(
IPackageDetails details,
string directory,
string version,
INativeTaskLogger logger
)
{
IPackage package = details.Package;
LocalNuGetPackage? local = NuGetLocalFeed.Find(
directory,
package.Id,
package.VersionString
);
LocalNuGetPackage? local = NuGetLocalFeed.Find(directory, package.Id, version);

if (local is null)
{
logger.Error(
$"No package file for {package.Id} version {package.VersionString} was found "
$"No package file for {package.Id} version {version} was found "
+ $"on source {package.Source.Name} at Directory={directory}"
);
return false;
Expand Down Expand Up @@ -303,7 +304,11 @@ INativeTaskLogger logger
return true;
}

private static bool GetDetailsV3(IPackageDetails details, INativeTaskLogger logger)
private static bool GetDetailsV3(
IPackageDetails details,
string version,
INativeTaskLogger logger
)
{
IPackage package = details.Package;
NuGetV3ServiceIndex? index = NuGetV3ServiceIndex.Resolve(package.Source);
Expand All @@ -317,10 +322,10 @@ private static bool GetDetailsV3(IPackageDetails details, INativeTaskLogger logg
}

details.ManifestUrl =
NuGetV3Client.GetRegistrationLeafUrl(index, package.Id, package.VersionString)
?? NuGetV3Client.GetNuspecUrl(index, package.Id, package.VersionString);
NuGetV3Client.GetRegistrationLeafUrl(index, package.Id, version)
?? NuGetV3Client.GetNuspecUrl(index, package.Id, version);

V3CatalogEntry? entry = GetOrFetchCatalogEntry(package, index);
V3CatalogEntry? entry = GetOrFetchCatalogEntry(package, index, version);
if (entry is null)
{
logger.Error(
Expand Down Expand Up @@ -356,7 +361,7 @@ private static bool GetDetailsV3(IPackageDetails details, INativeTaskLogger logg
Uri? installerUrl =
Uri.TryCreate(entry.PackageContent, UriKind.Absolute, out Uri? packageContent)
? packageContent
: NuGetV3Client.GetPackageContentUrl(index, package.Id, package.VersionString);
: NuGetV3Client.GetPackageContentUrl(index, package.Id, version);
details.InstallerUrl = installerUrl;

if (entry.PackageSize > 0)
Expand Down Expand Up @@ -392,10 +397,12 @@ private static bool GetDetailsV3(IPackageDetails details, INativeTaskLogger logg

private static V3CatalogEntry? GetOrFetchCatalogEntry(
IPackage package,
NuGetV3ServiceIndex index
NuGetV3ServiceIndex index,
string? version = null
)
{
long hash = package.GetVersionedHash();
version ??= package.VersionString;
long hash = NuGetManifestLoader.GetCacheKey(package, version);
if (BaseNuGet.V3Entries.TryGetValue(hash, out V3CatalogEntry? cached))
{
Logger.Debug(
Expand All @@ -404,18 +411,37 @@ NuGetV3ServiceIndex index
return cached;
}

V3CatalogEntry? entry = NuGetV3Client.GetCatalogEntry(
index,
package.Id,
package.VersionString
);
V3CatalogEntry? entry = NuGetV3Client.GetCatalogEntry(index, package.Id, version);

if (entry is not null)
BaseNuGet.V3Entries[hash] = entry;

return entry;
}

private string ResolveDetailsVersion(IPackage package)
{
if (package.HasConcreteVersion)
return package.VersionString;

try
{
IReadOnlyList<string> versions = GetInstallableVersions_UnSafe(package);
return NuGetV3Client.SelectHighestVersion(versions, includePreRelease: false)
?? NuGetV3Client.SelectHighestVersion(versions, includePreRelease: true)
?? package.VersionString;
}
catch (Exception ex)
{
Logger.Warn(
$"Could not resolve the newest version of package {package.Id} on manager "
+ $"{package.Manager.Name}, the listed version will be used instead"
);
Logger.Warn(ex);
return package.VersionString;
}
}

private static string? FirstNonEmpty(params string?[] values)
{
foreach (string? value in values)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,32 @@ namespace UniGetUI.PackageEngine.Managers.Generic.NuGet.Internal
{
internal static class NuGetManifestLoader
{
/// <summary>
/// Cache key for the manifest and catalog caches. Reproduces the shape of
/// IPackage.GetVersionedHash() so that an entry cached for a concrete package is
/// reused when another package resolves to that same version, and so that a package
/// whose listed version is a placeholder does not share one slot across versions.
/// </summary>
Comment thread
GabrielDuf marked this conversation as resolved.
/// <param name="package">A valid Package object</param>
/// <param name="version">The version to key on; the package's own when omitted</param>
/// <returns>A cache key</returns>
public static long GetCacheKey(IPackage package, string? version = null)
{
return CoreTools.HashStringAsLong(
$"{package.Manager.Name}\\{package.Source.AsString_DisplayName}\\{package.Id}\\{version ?? package.VersionString}"
);
}

/// <summary>
/// Returns the URL to the manifest of a NuGet-based package
/// </summary>
/// <param name="package">A valid Package object</param>
/// <param name="version">The version to address; the package's own when omitted</param>
/// <returns>A Uri object</returns>
public static Uri GetManifestUrl(IPackage package)
public static Uri GetManifestUrl(IPackage package, string? version = null)
{
return new Uri(
$"{package.Source.Url}/Packages(Id='{package.Id}',Version='{package.VersionString}')"
$"{package.Source.Url}/Packages(Id='{package.Id}',Version='{version ?? package.VersionString}')"
);
}

Expand Down Expand Up @@ -53,18 +70,21 @@ public static Uri GetNuPkgUrl(IPackage package)
/// Returns the contents of the manifest of a NuGet-based package
/// </summary>
/// <param name="package">The package for which to obtain the manifest</param>
/// <param name="version">The version to fetch; the package's own when omitted</param>
/// <returns>A string containing the contents of the manifest</returns>
public static string? GetManifestContent(IPackage package)
public static string? GetManifestContent(IPackage package, string? version = null)
{
if (BaseNuGet.Manifests.TryGetValue(package.GetVersionedHash(), out string? manifest))
version ??= package.VersionString;
long cacheKey = GetCacheKey(package, version);
if (BaseNuGet.Manifests.TryGetValue(cacheKey, out string? manifest))
{
Logger.Debug(
$"Loading cached NuGet manifest for package {package.Id} on manager {package.Manager.Name}"
);
return manifest;
}

string PackageManifestUrl = GetManifestUrl(package).ToString();
string PackageManifestUrl = GetManifestUrl(package, version).ToString();

try
{
Expand All @@ -77,17 +97,17 @@ public static Uri GetNuPkgUrl(IPackage package)
);
using HttpResponseMessage initialResponse = client.Send(initialRequest);

if (!initialResponse.IsSuccessStatusCode && package.VersionString.EndsWith(".0"))
if (!initialResponse.IsSuccessStatusCode && version.EndsWith(".0"))
{
using var fallbackRequest = new HttpRequestMessage(
HttpMethod.Get,
new Uri(PackageManifestUrl.ToString().Replace(".0')", "')"))
);
using HttpResponseMessage fallbackResponse = client.Send(fallbackRequest);
return CacheManifestContent(package, PackageManifestUrl, fallbackResponse);
return CacheManifestContent(cacheKey, package, PackageManifestUrl, fallbackResponse);
}

return CacheManifestContent(package, PackageManifestUrl, initialResponse);
return CacheManifestContent(cacheKey, package, PackageManifestUrl, initialResponse);
}
}
catch (Exception e)
Expand All @@ -101,6 +121,7 @@ public static Uri GetNuPkgUrl(IPackage package)
}

private static string? CacheManifestContent(
long cacheKey,
IPackage package,
string packageManifestUrl,
HttpResponseMessage response
Expand All @@ -115,7 +136,7 @@ HttpResponseMessage response
}

string packageManifestContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
BaseNuGet.Manifests[package.GetVersionedHash()] = packageManifestContent;
BaseNuGet.Manifests[cacheKey] = packageManifestContent;
return packageManifestContent;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ protected override void GetDetails_UnSafe(IPackageDetails details)
logger.AddToStdOut(strContents);
JsonObject? contents = JsonNode.Parse(strContents) as JsonObject;

details.Version = contents?["version"]?.ToString();
details.License = contents?["license"]?.ToString();
details.Description = contents?["description"]?.ToString();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ protected override void GetDetails_UnSafe(IPackageDetails details)

if (contents?["info"] is JsonObject info)
{
details.Version = info["version"]?.ToString();
details.Description = info["summary"]?.ToString();
details.Author = info["author"]?.ToString();
details.Publisher = info["maintainer"]?.ToString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ protected override void GetDetails_UnSafe(IPackageDetails details)
details.Description = contents["description"]?.ToString();
}

details.Version = contents["version"]?.ToString();

// Load installer type
if (contents["innsetup"]?.ToString() == "true")
details.InstallerType = "Inno Setup (" + CoreTools.Translate("extracted") + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ internal static void ApplyShowResult(
Manifest manifest = result.Manifest;
Installer? installer = result.SelectedInstaller ?? manifest.Installers.FirstOrDefault();

SetIfPresent(value => details.Version = value, manifest.Version);
SetIfMissing(value => details.Author = value, details.Author, manifest.Author);
SetIfMissing(
value => details.Description = value,
Expand Down
Loading
Loading