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
2 changes: 1 addition & 1 deletion dotnet/src/Devolutions.Pinget.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
using Devolutions.Pinget.Cli;
using Devolutions.Pinget.Core;

const string Version = "0.12.0";
const string Version = "0.13.0";
const string UpgradeUnsupportedWarning = "Upgrading packages is not supported on this platform; no changes were made.";

if (args.Length == 1 && (string.Equals(args[0], "--version", StringComparison.OrdinalIgnoreCase) || string.Equals(args[0], "-v", StringComparison.OrdinalIgnoreCase)))
Expand Down
93 changes: 93 additions & 0 deletions dotnet/src/Devolutions.Pinget.Core.Tests/CoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3658,6 +3658,99 @@ public void Show_PreindexedStaleIndex_RefreshesBeforeLatestSelection()
}
}

[Fact]
public void Show_Preindexed_CachesSourceFilesInsideTheConfiguredAppRoot()
{
const string packageId = "Test.AppRootScopedCachePackage";
var catalog = PreindexedCatalogFixture.Create(packageId, "1.0.0");

using var server = new TestPreindexedSourceServer(catalog.MsixBytes, catalog.Files);
var appRoot = TestPaths.CreateTempAppRoot();
try
{
using var repo = Repository.Open(new RepositoryOptions
{
AppRoot = appRoot,
PreIndexedSourceAutoUpdateInterval = TimeSpan.FromDays(1),
});
ReplaceSources(repo, ("test", server.Url, SourceKind.PreIndexed));
WritePreindexedIndex(appRoot, repo, "test", catalog.IndexBytes);

var result = repo.ShowManifest(new PackageQuery
{
Id = packageId,
Exact = true,
Source = "test",
});

Assert.Equal("1.0.0", result.PackageVersion);
Assert.NotEmpty(result.CachedFiles);
Assert.All(result.CachedFiles, file => Assert.StartsWith(
Path.GetFullPath(appRoot),
Path.GetFullPath(file),
StringComparison.OrdinalIgnoreCase));

var cacheRoot = Path.Combine(appRoot, "Microsoft", "Windows Package Manager");
Assert.True(Directory.Exists(cacheRoot));
var cached = Directory.GetFiles(cacheRoot, "*", SearchOption.AllDirectories);
Assert.Contains(cached, file =>
file.Contains(Path.Combine("V2_M", "test"), StringComparison.OrdinalIgnoreCase));
Assert.Contains(cached, file =>
file.Contains(Path.Combine("V2_PVD", "test"), StringComparison.OrdinalIgnoreCase));
}
finally
{
TestPaths.DeleteAppRoot(appRoot);
}
}

[Fact]
public void Show_PreindexedUnsafeIdentifier_SanitizesTheCacheSegmentInsideTheAppRoot()
{
const string packageId = "Test.UnsafeIdentifierCachePackage";
const string sourceName = "c:foo";
var catalog = PreindexedCatalogFixture.Create(packageId, "1.0.0");

using var server = new TestPreindexedSourceServer(catalog.MsixBytes, catalog.Files);
var appRoot = TestPaths.CreateTempAppRoot();
try
{
using var repo = Repository.Open(new RepositoryOptions
{
AppRoot = appRoot,
PreIndexedSourceAutoUpdateInterval = TimeSpan.FromDays(1),
});
ReplaceSources(repo, (sourceName, server.Url, SourceKind.PreIndexed));
WritePreindexedIndex(appRoot, repo, sourceName, catalog.IndexBytes);

var result = repo.ShowManifest(new PackageQuery
{
Id = packageId,
Exact = true,
Source = sourceName,
});

Assert.Equal("1.0.0", result.PackageVersion);
Assert.NotEmpty(result.CachedFiles);
Assert.All(result.CachedFiles, file => Assert.StartsWith(
Path.GetFullPath(appRoot),
Path.GetFullPath(file),
StringComparison.OrdinalIgnoreCase));

var cacheRoot = Path.Combine(appRoot, "Microsoft", "Windows Package Manager");
Assert.True(Directory.Exists(cacheRoot));
var cached = Directory.GetFiles(cacheRoot, "*", SearchOption.AllDirectories);
Assert.Contains(cached, file =>
file.Contains(Path.Combine("V2_M", "c_foo"), StringComparison.OrdinalIgnoreCase));
Assert.Contains(cached, file =>
file.Contains(Path.Combine("V2_PVD", "c_foo"), StringComparison.OrdinalIgnoreCase));
}
finally
{
TestPaths.DeleteAppRoot(appRoot);
}
}

[Fact]
public void Show_PreindexedStaleIndex_NotModifiedKeepsLocalIndex()
{
Expand Down
26 changes: 10 additions & 16 deletions dotnet/src/Devolutions.Pinget.Core/PreIndexedSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ public static string ResolveV1RelativePath(SqliteConnection conn, long? pathPart

// V2 version data: fetch versionData.mszyml from CDN, decompress CK+deflate → YAML
public static (List<V2VersionDataEntry> Entries, string VersionDataFile) LoadV2VersionData(
HttpClient client, SqliteConnection conn, SourceRecord source, long packageRowid, string packageHash, string? appRoot = null)
HttpClient client, SqliteConnection conn, SourceRecord source, long packageRowid, string packageHash, string? appRoot)
{
// Resolve package id from packages table
using var idCmd = conn.CreateCommand();
Expand All @@ -390,13 +390,13 @@ public static (List<V2VersionDataEntry> Entries, string VersionDataFile) LoadV2V
var hashPrefix = packageHash.Length >= 8 ? packageHash[..8].ToLowerInvariant() : packageHash.ToLowerInvariant();
var relativePath = $"packages/{packageId}/{hashPrefix}/versionData.mszyml";

var bytes = GetCachedSourceFile(client, "V2_PVD", source, relativePath, packageHash);
var bytes = GetCachedSourceFile(client, "V2_PVD", source, relativePath, packageHash, appRoot);
var yaml = DecompressMszyml(bytes);

var doc = ParseV2VersionDataDocument(yaml)
?? throw new InvalidOperationException("Failed to parse versionData.mszyml");

var cacheDir = TempCachePath("V2_PVD", source.Identifier);
var cacheDir = TempCachePath(appRoot, "V2_PVD", source.Identifier);
return (doc.Versions, Path.Combine(cacheDir, relativePath.Replace('/', '\\')));
}

Expand Down Expand Up @@ -541,10 +541,10 @@ private static void SkipNode(IParser parser)
}

public static byte[] GetCachedSourceFile(
HttpClient client, string bucket, SourceRecord source, string relativePath, string? expectedHash)
HttpClient client, string bucket, SourceRecord source, string relativePath, string? expectedHash, string? appRoot)
{
var normalizedRelative = relativePath.Replace('\\', '/');
var cacheDir = TempCachePath(bucket, source.Identifier);
var cacheDir = TempCachePath(appRoot, bucket, source.Identifier);
var cachePath = Path.Combine(cacheDir, normalizedRelative.Replace('/', '\\'));

// Check cache first
Expand Down Expand Up @@ -596,18 +596,12 @@ public static byte[] GetCachedSourceFileFromMsix(SourceRecord source, string rel
throw new InvalidOperationException($"Source file '{relativePath}' not found in MSIX for {source.Name}");
}

private static string TempCachePath(string bucket, string sourceIdentifier)
private static string TempCachePath(string? appRoot, string bucket, string sourceIdentifier)
{
if (OperatingSystem.IsWindows())
{
return Path.Combine(
SourceStoreManager.GetPackagedFileCacheRoot(null),
bucket,
sourceIdentifier);
}

var tempDir = Path.GetTempPath();
return Path.Combine(tempDir, "pinget", bucket, sourceIdentifier);
return Path.Combine(
SourceStoreManager.GetPackagedFileCacheRoot(appRoot),
bucket,
SourceStoreManager.SanitizePathSegment(sourceIdentifier));
Comment thread
GabrielDuf marked this conversation as resolved.
}

private static bool HashMatches(string? expected, byte[] data)
Expand Down
6 changes: 3 additions & 3 deletions dotnet/src/Devolutions.Pinget.Core/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1944,7 +1944,7 @@ private List<VersionKey> VersionsFromV2(int sourceIndex, long packageRowid, stri
var versions = PreIndexedSource.QueryV1Versions(conn, packageRowid);
var selected = SelectV1Version(versions, query.Version, query.Channel, display.Id, source.Name);
var relativePath = PreIndexedSource.ResolveV1RelativePath(conn, selected.PathPart);
var bytes = PreIndexedSource.GetCachedSourceFile(_client, "V1_M", source, relativePath, selected.ManifestHash);
var bytes = PreIndexedSource.GetCachedSourceFile(_client, "V1_M", source, relativePath, selected.ManifestHash, _appRoot);
var manifest = ParseYamlManifest(bytes);
var structuredDocument = ParseYamlManifestDocuments(bytes);
manifest = manifest with { Version = selected.Version, Channel = selected.Channel };
Expand Down Expand Up @@ -1993,7 +1993,7 @@ private List<VersionKey> VersionsFromV2(int sourceIndex, long packageRowid, stri
var source = _store.Sources[sourceIndex];
var (entries, vdFile) = PreIndexedSource.LoadV2VersionData(_client, conn, source, packageRowid, packageHash, _appRoot);
var selected = SelectV2Version(entries, query.Version, query.Channel, display.Id, source.Name);
var bytes = PreIndexedSource.GetCachedSourceFile(_client, "V2_M", source, selected.ManifestRelativePath, selected.ManifestHash);
var bytes = PreIndexedSource.GetCachedSourceFile(_client, "V2_M", source, selected.ManifestRelativePath, selected.ManifestHash, _appRoot);
var manifest = ParseYamlManifest(bytes);
var structuredDocument = ParseYamlManifestDocuments(bytes);
manifest = manifest with { Version = selected.Version };
Expand Down Expand Up @@ -3524,7 +3524,7 @@ private List<string> EnrichCorrelatedViaIndex(List<InstalledPackage> installed,
try
{
var bytes = PreIndexedSource.GetCachedSourceFile(
_client, "V2_M", source, latest.ManifestRelativePath, latest.ManifestHash);
_client, "V2_M", source, latest.ManifestRelativePath, latest.ManifestHash, _appRoot);
var manifest = ParseYamlManifest(bytes);
installed[idx].CorrelatedRequiresExplicitUpgrade = manifest.RequireExplicitUpgrade;
installed[idx].CorrelatedLacksCompatibleInstaller = !ManifestHasCompatibleInstaller(manifest);
Expand Down
2 changes: 1 addition & 1 deletion dotnet/src/Devolutions.Pinget.Core/SourceStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,6 @@ private static void AddYamlKeyValue(Dictionary<string, string?> yamlSource, stri
return value;
}

private static string SanitizePathSegment(string value) => string.Concat(value.Select(c =>
internal static string SanitizePathSegment(string value) => string.Concat(value.Select(c =>
@"\/:*?""<>|".Contains(c) ? '_' : c));
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@{
RootModule = 'Devolutions.Pinget.Client.psm1'
ModuleVersion = '0.12.0'
ModuleVersion = '0.13.0'
CompatiblePSEditions = @('Desktop', 'Core')
GUID = 'c6d1b5f2-5ccd-4771-9480-25caad7c58bd'
Author = 'Devolutions'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ namespace Devolutions.Pinget.PowerShell.Engine;

public static class PowerShellEngineVersion
{
public const string Current = "0.12.0";
public const string Current = "0.13.0";
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="Pack">

<PropertyGroup>
<Version>0.12.0</Version>
<Version>0.13.0</Version>
<Company>Devolutions Inc.</Company>
<Authors>Devolutions</Authors>
<PackageId>Devolutions.Pinget.Cli.DotNet</PackageId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="Pack">

<PropertyGroup>
<Version>0.12.0</Version>
<Version>0.13.0</Version>
<Company>Devolutions Inc.</Company>
<Authors>Devolutions</Authors>
<PackageId>Devolutions.Pinget.Cli.Rust</PackageId>
Expand Down
4 changes: 2 additions & 2 deletions rust/crates/pinget-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pinget-cli"
version = "0.12.0"
version = "0.13.0"
edition = "2024"

[lints]
Expand All @@ -13,7 +13,7 @@ path = "src/main.rs"
[dependencies]
anyhow = "1.0.102"
clap = { version = "4.6.1", features = ["derive"] }
pinget-core = { version = "0.12.0", path = "../pinget-core" }
pinget-core = { version = "0.13.0", path = "../pinget-core" }
chrono = "0.4.44"
dirs = "6.0"
jsonschema = { version = "0.30", default-features = false, features = ["resolve-file"] }
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/pinget-com/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pinget-com"
version = "0.12.0"
version = "0.13.0"
edition = "2024"
description = "Windows-only native COM bridge for Pinget backed by pinget-core."
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion rust/crates/pinget-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pinget-core"
version = "0.12.0"
version = "0.13.0"
edition = "2024"
description = "Pure Rust Pinget core library that works directly with source caches, REST endpoints, and installed package state without COM."
license = "MIT"
Expand Down
18 changes: 9 additions & 9 deletions rust/crates/pinget-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6331,15 +6331,7 @@ fn packaged_file_cache_root(app_root: &Path) -> PathBuf {
}

fn cache_root(app_root: &Path) -> PathBuf {
if uses_packaged_layout(app_root) {
packaged_file_cache_root(app_root)
} else {
default_cache_root_fallback()
}
}

fn default_cache_root_fallback() -> PathBuf {
std::env::temp_dir().join("cache")
packaged_file_cache_root(app_root)
}

fn packaged_source_type(kind: SourceKind) -> &'static str {
Expand Down Expand Up @@ -11321,6 +11313,14 @@ mod tests {
app_root.join("sources").join("winget_test")
);
assert_eq!(pins_db_path(&app_root), app_root.join("pins.db"));
assert_eq!(
temp_cache_path(&app_root, "V1_M", &source.identifier),
app_root
.join("Microsoft")
.join("Windows Package Manager")
.join("V1_M")
.join("Test.Source")
);
}

#[cfg(windows)]
Expand Down
Loading