Skip to main content

xet_data/processing/
configurations.rs

1use std::path::{Path, PathBuf};
2use std::sync::Arc;
3
4use http::HeaderMap;
5use tracing::info;
6use xet_client::cas_client::auth::AuthConfig;
7use xet_runtime::core::XetContext;
8#[cfg(not(target_family = "wasm"))]
9use xet_runtime::core::xet_cache_root;
10
11use crate::error::Result;
12
13/// Session-specific configuration that varies per upload/download session.
14/// These are runtime values that cannot be configured via environment variables.
15#[derive(Debug, Clone)]
16pub struct SessionContext {
17    /// The endpoint URL. Use the `local://` prefix (configurable via `HF_XET_DATA_LOCAL_CAS_SCHEME`)
18    /// to specify a local filesystem path, or `memory://` for in-memory storage.
19    pub endpoint: String,
20    pub auth: Option<AuthConfig>,
21    pub custom_headers: Option<Arc<HeaderMap>>,
22    pub repo_paths: Vec<String>,
23    pub session_id: Option<String>,
24}
25
26impl SessionContext {
27    /// Returns true if this endpoint points to a local filesystem path.
28    pub fn is_local(&self, ctx: &XetContext) -> bool {
29        self.endpoint.starts_with(ctx.config.data.local_cas_scheme.as_str())
30    }
31
32    /// Returns the local filesystem path if this is a local endpoint.
33    pub fn local_path(&self, ctx: &XetContext) -> Option<PathBuf> {
34        let path = self.endpoint.strip_prefix(ctx.config.data.local_cas_scheme.as_str())?;
35        Some(PathBuf::from(path))
36    }
37
38    /// Returns true if this endpoint uses in-memory storage.
39    pub fn is_memory(&self) -> bool {
40        self.endpoint == "memory://"
41    }
42
43    /// Creates a SessionContext for local filesystem-based operations.
44    pub fn for_local_path(ctx: &XetContext, base_dir: impl AsRef<Path>) -> Self {
45        let path = base_dir.as_ref().to_path_buf();
46        let endpoint = format!("{}{}", ctx.config.data.local_cas_scheme, path.display());
47        Self {
48            endpoint,
49            auth: None,
50            custom_headers: None,
51            repo_paths: vec!["".into()],
52            session_id: None,
53        }
54    }
55
56    /// Creates a SessionContext for in-memory storage.
57    pub fn for_memory() -> Self {
58        Self {
59            endpoint: "memory://".into(),
60            auth: None,
61            custom_headers: None,
62            repo_paths: vec!["".into()],
63            session_id: None,
64        }
65    }
66}
67
68/// Main configuration for file upload/download operations.
69/// Combines session-specific values with runtime-computed paths derived from the endpoint.
70#[derive(Debug, Clone)]
71pub struct TranslatorConfig {
72    pub ctx: XetContext,
73    pub session: SessionContext,
74
75    /// Directory for caching shard files.
76    pub shard_cache_directory: PathBuf,
77
78    /// Directory for session-specific shard files.
79    pub shard_session_directory: PathBuf,
80
81    /// Per-session override: when true, progress aggregation is disabled
82    /// regardless of the global `HF_XET_DATA_AGGREGATE_PROGRESS` config value.
83    pub force_disable_progress_aggregation: bool,
84}
85
86impl TranslatorConfig {
87    fn create_base_xet_dir(base_dir: impl AsRef<Path>) -> Result<PathBuf> {
88        let base_path = base_dir.as_ref().join("xet");
89        std::fs::create_dir_all(&base_path)?;
90        Ok(base_path)
91    }
92
93    /// Creates a new TranslatorConfig from a SessionContext, computing all derived paths.
94    ///
95    /// On WASM, no filesystem directories are created. Shard cache and session
96    /// directories are set to empty paths since the WASM build does not exercise
97    /// disk-backed shard staging or shard-cache paths.
98    pub fn new(ctx: &XetContext, session: SessionContext) -> Result<Self> {
99        #[cfg(target_family = "wasm")]
100        let (shard_cache_directory, shard_session_directory) = (PathBuf::new(), PathBuf::new());
101
102        #[cfg(not(target_family = "wasm"))]
103        let (shard_cache_directory, shard_session_directory) = {
104            let config = ctx.config.as_ref();
105
106            if let Some(local_path) = session.local_path(ctx) {
107                let base_path = local_path.join("xet");
108                std::fs::create_dir_all(&base_path)?;
109
110                (base_path.join(&config.shard.cache_subdir), base_path.join(&config.session.dir_name))
111            } else if session.is_memory() {
112                let cache_path = xet_cache_root().join("memory");
113                std::fs::create_dir_all(&cache_path)?;
114
115                (cache_path.join(&config.shard.cache_subdir), cache_path.join(&config.session.dir_name))
116            } else {
117                let cache_path = compute_cache_path(&session.endpoint);
118                std::fs::create_dir_all(&cache_path)?;
119
120                let staging_directory = cache_path.join(&config.data.staging_subdir);
121                std::fs::create_dir_all(&staging_directory)?;
122
123                (cache_path.join(&config.shard.cache_subdir), staging_directory.join(&config.session.dir_name))
124            }
125        };
126
127        info!(
128            endpoint = %session.endpoint,
129            session_id = ?session.session_id,
130            shard_cache = %shard_cache_directory.display(),
131            shard_session = %shard_session_directory.display(),
132            "TranslatorConfig initialized"
133        );
134
135        Ok(Self {
136            ctx: ctx.clone(),
137            session,
138            shard_cache_directory,
139            shard_session_directory,
140            force_disable_progress_aggregation: false,
141        })
142    }
143
144    /// Creates a TranslatorConfig for local filesystem-based storage.
145    pub fn local_config(ctx: &XetContext, base_dir: impl AsRef<Path>) -> Result<Self> {
146        Self::new(ctx, SessionContext::for_local_path(ctx, base_dir))
147    }
148
149    /// Creates a TranslatorConfig that uses in-memory storage for XORBs.
150    /// Shard data still uses file-based storage in the provided base directory.
151    pub fn memory_config(ctx: &XetContext, base_dir: impl AsRef<Path>) -> Result<Self> {
152        let session = SessionContext::for_memory();
153        let config = ctx.config.as_ref();
154        let base_path = Self::create_base_xet_dir(base_dir)?;
155
156        Ok(Self {
157            ctx: ctx.clone(),
158            session,
159            shard_cache_directory: base_path.join(&config.shard.cache_subdir),
160            shard_session_directory: base_path.join(&config.session.dir_name),
161            force_disable_progress_aggregation: false,
162        })
163    }
164
165    /// Creates a TranslatorConfig that connects to a CAS server at the given endpoint.
166    /// Shard cache and session directories are created under the provided base directory.
167    /// Useful for tests that use LocalTestServer.
168    pub fn test_server_config(ctx: &XetContext, endpoint: impl AsRef<str>, base_dir: impl AsRef<Path>) -> Result<Self> {
169        let session = SessionContext {
170            endpoint: endpoint.as_ref().to_string(),
171            auth: None,
172            custom_headers: None,
173            repo_paths: vec!["".into()],
174            session_id: None,
175        };
176        let config = ctx.config.as_ref();
177        let base_path = Self::create_base_xet_dir(base_dir)?;
178
179        Ok(Self {
180            ctx: ctx.clone(),
181            session,
182            shard_cache_directory: base_path.join(&config.shard.cache_subdir),
183            shard_session_directory: base_path.join(&config.session.dir_name),
184            force_disable_progress_aggregation: false,
185        })
186    }
187
188    pub fn disable_progress_aggregation(mut self) -> Self {
189        self.force_disable_progress_aggregation = true;
190        self
191    }
192}
193
194/// Computes a cache-safe path from an endpoint URL.
195#[cfg(not(target_family = "wasm"))]
196fn compute_cache_path(endpoint: &str) -> PathBuf {
197    let cache_root = xet_cache_root();
198
199    let endpoint_prefix = endpoint
200        .chars()
201        .take(16)
202        .map(|c| if c.is_alphanumeric() { c } else { '_' })
203        .collect::<String>();
204
205    let endpoint_hash = xet_core_structures::merklehash::compute_data_hash(endpoint.as_bytes()).base64();
206    let endpoint_tag = format!("{endpoint_prefix}-{}", &endpoint_hash[..16]);
207
208    cache_root.join(endpoint_tag)
209}
210
211#[cfg(test)]
212mod tests {
213    use tempfile::tempdir;
214    use xet_runtime::core::XetContext;
215
216    use super::{SessionContext, TranslatorConfig};
217
218    #[test]
219    fn test_session_context_mode_detection() {
220        let ctx = XetContext::default().unwrap();
221        let temp_dir = tempdir().unwrap();
222        let local_session = SessionContext::for_local_path(&ctx, temp_dir.path());
223        assert!(local_session.is_local(&ctx));
224        assert!(!local_session.is_memory());
225        assert_eq!(local_session.local_path(&ctx).unwrap(), temp_dir.path().to_path_buf());
226
227        let memory_session = SessionContext::for_memory();
228        assert!(memory_session.is_memory());
229        assert!(!memory_session.is_local(&ctx));
230        assert!(memory_session.local_path(&ctx).is_none());
231
232        let remote_session = SessionContext {
233            endpoint: "http://localhost:8080".into(),
234            auth: None,
235            custom_headers: None,
236            repo_paths: Vec::new(),
237            session_id: None,
238        };
239        assert!(!remote_session.is_local(&ctx));
240        assert!(!remote_session.is_memory());
241        assert!(remote_session.local_path(&ctx).is_none());
242    }
243
244    #[test]
245    fn test_memory_and_server_configs_use_base_xet_layout() {
246        let ctx = XetContext::default().unwrap();
247        let temp_dir = tempdir().unwrap();
248
249        let memory_config = TranslatorConfig::memory_config(&ctx, temp_dir.path()).unwrap();
250        assert!(memory_config.shard_cache_directory.starts_with(temp_dir.path().join("xet")));
251        assert!(memory_config.shard_session_directory.starts_with(temp_dir.path().join("xet")));
252
253        let server_config =
254            TranslatorConfig::test_server_config(&ctx, "http://localhost:8080", temp_dir.path()).unwrap();
255        assert!(server_config.shard_cache_directory.starts_with(temp_dir.path().join("xet")));
256        assert!(server_config.shard_session_directory.starts_with(temp_dir.path().join("xet")));
257    }
258}