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