pg_embedded_setup_unpriv/cache/operations/populate.rs
1//! Cache population after successful downloads.
2//!
3//! Provides functions to copy freshly downloaded binaries into the cache.
4
5use camino::Utf8Path;
6use color_eyre::eyre::Context;
7use std::fs;
8use tracing::{debug, warn};
9
10use super::copy::copy_dir_recursive;
11use crate::error::BootstrapResult;
12
13/// Marker file name indicating a complete cache entry.
14const COMPLETION_MARKER: &str = ".complete";
15
16/// Observability target for cache operations.
17const LOG_TARGET: &str = "pg_embed::cache";
18
19/// Populates the cache with binaries from the given source directory.
20///
21/// After a successful download, call this function to copy binaries to the
22/// cache and write the completion marker.
23///
24/// # Arguments
25///
26/// * `source` - Directory containing freshly downloaded/extracted binaries
27/// * `cache_dir` - Root directory of the binary cache
28/// * `version` - Version string for the cache entry
29///
30/// # Errors
31///
32/// Returns an error if:
33/// - The cache directory cannot be created
34/// - Copying binaries fails
35/// - Writing the completion marker fails
36///
37/// # Examples
38///
39/// ```no_run
40/// use camino::Utf8Path;
41/// use pg_embedded_setup_unpriv::cache::populate_cache;
42///
43/// let source = Utf8Path::new("/tmp/sandbox/install/17.4.0");
44/// let cache_dir = Utf8Path::new("/home/user/.cache/pg-embedded/binaries");
45/// populate_cache(source, cache_dir, "17.4.0")?;
46/// # Ok::<(), color_eyre::Report>(())
47/// ```
48pub fn populate_cache(
49 source: &Utf8Path,
50 cache_dir: &Utf8Path,
51 version: &str,
52) -> BootstrapResult<()> {
53 let version_dir = cache_dir.join(version);
54
55 log_populate_start(source, cache_dir, version);
56
57 fs::create_dir_all(&version_dir)
58 .with_context(|| format!("failed to create cache directory: {version_dir}"))?;
59
60 copy_dir_recursive(source.as_std_path(), version_dir.as_std_path())
61 .with_context(|| format!("failed to copy binaries to cache: {version_dir}"))?;
62
63 write_completion_marker(&version_dir)?;
64
65 log_populate_complete(version, &version_dir);
66 Ok(())
67}
68
69/// Logs the start of a cache population operation.
70fn log_populate_start(source: &Utf8Path, cache_dir: &Utf8Path, version: &str) {
71 debug!(
72 target: LOG_TARGET,
73 source = %source,
74 cache_dir = %cache_dir,
75 version = %version,
76 "populating cache"
77 );
78}
79
80/// Logs the completion of a cache population operation.
81fn log_populate_complete(version: &str, version_dir: &Utf8Path) {
82 debug!(
83 target: LOG_TARGET,
84 version = %version,
85 path = %version_dir,
86 "cache population completed"
87 );
88}
89
90/// Writes the completion marker to indicate a valid cache entry.
91fn write_completion_marker(cache_path: &Utf8Path) -> BootstrapResult<()> {
92 let marker = cache_path.join(COMPLETION_MARKER);
93 fs::write(&marker, "")
94 .with_context(|| format!("failed to write cache completion marker: {marker}"))?;
95 Ok(())
96}
97
98/// Attempts to populate the cache after a download, logging warnings on failure.
99///
100/// This is a convenience wrapper that does not propagate errors, allowing the
101/// main flow to continue even if caching fails.
102///
103/// # Arguments
104///
105/// * `source` - Directory containing freshly downloaded binaries
106/// * `cache_dir` - Root directory of the binary cache
107/// * `version` - Version string for the cache entry
108pub fn try_populate_cache(source: &Utf8Path, cache_dir: &Utf8Path, version: &str) {
109 if let Err(err) = populate_cache(source, cache_dir, version) {
110 warn!(
111 target: LOG_TARGET,
112 error = %err,
113 version = %version,
114 "failed to populate cache, future runs may re-download"
115 );
116 }
117}