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.8.1";
const string Version = "0.8.2";
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
36 changes: 36 additions & 0 deletions dotnet/src/Devolutions.Pinget.Core.Tests/CoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2947,6 +2947,42 @@ public void Show_PreindexedStaleIndex_RefreshesBeforeLatestSelection()
}
}

[Fact]
public void Show_PreindexedFreshIndexMtime_DoesNotRefreshForStaleSourceMetadata()
{
const string packageId = "Test.FreshIndexPackage";
var initialCatalog = PreindexedCatalogFixture.Create(packageId, "1.0.0");
var refreshedCatalog = PreindexedCatalogFixture.Create(packageId, "1.1.0", "1.0.0");

using var server = new TestPreindexedSourceServer(refreshedCatalog.MsixBytes, initialCatalog.Files.Concat(refreshedCatalog.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));
repo.ListSources().Single(source => source.Name == "test").LastUpdate = DateTime.UtcNow.AddDays(-2);
WritePreindexedIndex(appRoot, repo, "test", initialCatalog.IndexBytes);

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

Assert.Equal("1.0.0", result.PackageVersion);
Assert.Equal(0, server.SourceUpdateRequests);
}
finally
{
TestPaths.DeleteAppRoot(appRoot);
}
}

[Fact]
public void Show_PreindexedExplicitMissingVersion_DoesNotRefreshTwiceAfterStaleRefresh()
{
Expand Down
2 changes: 1 addition & 1 deletion dotnet/src/Devolutions.Pinget.Core/Models.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public record RepositoryOptions
/// Maximum age for an existing pre-indexed source index before Pinget attempts a background refresh.
/// Set to null to disable automatic freshness checks.
/// </summary>
public TimeSpan? PreIndexedSourceAutoUpdateInterval { get; init; } = TimeSpan.FromMinutes(5);
public TimeSpan? PreIndexedSourceAutoUpdateInterval { get; init; } = TimeSpan.FromMinutes(15);
}

public record RepositoryWarning
Expand Down
12 changes: 8 additions & 4 deletions dotnet/src/Devolutions.Pinget.Core/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1434,14 +1434,18 @@ private bool IsPreindexedIndexStale(SourceRecord source, string indexPath)
if (_preIndexedSourceAutoUpdateInterval is null)
return false;

var lastUpdate = source.LastUpdate;
if (lastUpdate is null && File.Exists(indexPath))
lastUpdate = File.GetLastWriteTimeUtc(indexPath);
var lastUpdate = source.LastUpdate?.ToUniversalTime();
if (File.Exists(indexPath))
{
var indexLastWrite = File.GetLastWriteTimeUtc(indexPath);
if (lastUpdate is null || indexLastWrite > lastUpdate.Value)
lastUpdate = indexLastWrite;
}

if (lastUpdate is null)
return true;

return DateTime.UtcNow - lastUpdate.Value.ToUniversalTime() >= _preIndexedSourceAutoUpdateInterval.Value;
return DateTime.UtcNow - lastUpdate.Value >= _preIndexedSourceAutoUpdateInterval.Value;
}

private string RefreshPreindexedSource(int sourceIndex, bool force, PreIndexedRefreshKind kind)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@{
RootModule = 'Devolutions.Pinget.Client.psm1'
ModuleVersion = '0.8.1'
ModuleVersion = '0.8.2'
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.8.1";
public const string Current = "0.8.2";
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="Pack">

<PropertyGroup>
<Version>0.8.1</Version>
<Version>0.8.2</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.8.1</Version>
<Version>0.8.2</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.8.1"
version = "0.8.2"
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.8.1", path = "../pinget-core" }
pinget-core = { version = "0.8.2", path = "../pinget-core" }
chrono = "0.4.44"
dirs = "6.0"
jsonschema = "0.30"
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.8.1"
version = "0.8.2"
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.8.1"
version = "0.8.2"
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
45 changes: 38 additions & 7 deletions rust/crates/pinget-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const DEFAULT_MAX_RESULTS: usize = 50;
const LIST_LOOKUP_MAX_RESULTS: usize = 500;
const PREINDEXED_CANDIDATES: &[&str] = &["source2.msix", "source.msix"];
const DEFAULT_USER_AGENT: &str = "pinget-rs/0.1";
const DEFAULT_PREINDEXED_AUTO_UPDATE_MINUTES: i64 = 5;
const DEFAULT_PREINDEXED_AUTO_UPDATE_MINUTES: i64 = 15;
const PREINDEXED_REFRESH_RETRY_MINUTES: i64 = 5;
#[cfg(windows)]
const PACKAGED_FAMILY_NAME: &str = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe";
Expand Down Expand Up @@ -2864,12 +2864,19 @@ impl Repository {
return false;
};

let last_update = source.last_update.or_else(|| {
let index_modified = || {
fs::metadata(index_path)
.ok()
.and_then(|metadata| metadata.modified().ok())
.map(DateTime::<Utc>::from)
});
};

let last_update = match (source.last_update, index_modified()) {
(Some(source_last_update), Some(index_last_write)) => Some(source_last_update.max(index_last_write)),
(Some(source_last_update), None) => Some(source_last_update),
(None, Some(index_last_write)) => Some(index_last_write),
(None, None) => None,
};

match last_update {
Some(last_update) => Utc::now() - last_update >= interval,
Expand Down Expand Up @@ -2982,7 +2989,10 @@ impl Repository {
Utc::now().timestamp_nanos_opt().unwrap_or_default()
));
fs::write(&temp_index_path, index_bytes).context("failed to persist temporary source index")?;
replace_file(&temp_index_path, &index_path).context("failed to persist source index")?;
if let Err(error) = replace_file(&temp_index_path, &index_path) {
let _ = fs::remove_file(&temp_index_path);
return Err(error).context("failed to persist source index");
}
// The new index.db is a fresh database; any WAL/SHM sidecars
// left from the previous one no longer match it and would make
// SQLite recover against the wrong database. Drop them so the
Expand Down Expand Up @@ -12865,7 +12875,7 @@ Installers:
let mut source = test_source_record(server.url());
source.last_update = Some(Utc::now() - Duration::days(2));
write_test_preindexed_index(&app_root, &source, &["1.0.0"]);
let mut repository = open_test_preindexed_repository(&app_root, &source, Some(Duration::minutes(1)));
let mut repository = open_test_preindexed_repository(&app_root, &source, Some(Duration::zero()));

let result = repository
.show(&show_test_package(None))
Expand All @@ -12876,6 +12886,27 @@ Installers:
let _ = fs::remove_dir_all(app_root);
}

#[test]
fn preindexed_fresh_index_mtime_skips_refresh_when_metadata_is_stale() {
let app_root = temp_app_root("preindexed_fresh_mtime");
let server = TestHttpServer::start();
server.set_status("/source2.msix", 404);
server.set_source_package(make_source_package(&["1.0.0", "2.0.0"]), "fresh");
server.set_bytes("/manifests/Test.Package.yaml", test_manifest_yaml("1.0.0"));
let mut source = test_source_record(server.url());
source.last_update = Some(Utc::now() - Duration::days(2));
write_test_preindexed_index(&app_root, &source, &["1.0.0"]);
let mut repository = open_test_preindexed_repository(&app_root, &source, Some(Duration::minutes(1)));

let result = repository
.show(&show_test_package(None))
.expect("show latest from fresh index mtime");

assert_eq!(result.manifest.version, "1.0.0");
assert_eq!(server.request_count("/source.msix"), 0);
let _ = fs::remove_dir_all(app_root);
}

#[test]
fn preindexed_stale_refresh_failure_falls_back_for_latest_requests() {
let app_root = temp_app_root("preindexed_ttl_fallback");
Expand All @@ -12886,7 +12917,7 @@ Installers:
let mut source = test_source_record(server.url());
source.last_update = Some(Utc::now() - Duration::days(2));
write_test_preindexed_index(&app_root, &source, &["1.0.0"]);
let mut repository = open_test_preindexed_repository(&app_root, &source, Some(Duration::minutes(1)));
let mut repository = open_test_preindexed_repository(&app_root, &source, Some(Duration::zero()));

let result = repository
.show(&show_test_package(None))
Expand All @@ -12905,7 +12936,7 @@ Installers:
let mut source = test_source_record(server.url());
source.last_update = Some(Utc::now() - Duration::days(2));
write_test_preindexed_index(&app_root, &source, &["1.0.0"]);
let mut repository = open_test_preindexed_repository(&app_root, &source, Some(Duration::minutes(1)));
let mut repository = open_test_preindexed_repository(&app_root, &source, Some(Duration::zero()));

let error = repository
.show(&show_test_package(Some("3.0.0")))
Expand Down
Loading