polars_io/file_cache/
utils.rs1use std::sync::{Arc, LazyLock};
2use std::time::UNIX_EPOCH;
3
4use polars_error::{PolarsError, PolarsResult};
5use polars_utils::aliases::PlHashMap;
6use polars_utils::pl_path::{CloudScheme, PlRefPath};
7
8use super::cache::{FILE_CACHE, get_env_file_cache_ttl};
9use super::entry::FileCacheEntry;
10use super::file_fetcher::{CloudFileFetcher, LocalFileFetcher};
11use crate::cloud::{
12 CloudLocation, CloudOptions, PolarsObjectStore, build_object_store, object_path_from_str,
13};
14use crate::path_utils::{POLARS_TEMP_DIR_BASE_PATH, ensure_directory_init};
15
16pub static FILE_CACHE_PREFIX: LazyLock<PlRefPath> = LazyLock::new(|| {
17 let path = PlRefPath::try_from_path(&POLARS_TEMP_DIR_BASE_PATH.join("file-cache/")).unwrap();
18
19 if let Err(err) = ensure_directory_init(path.as_ref()) {
20 panic!(
21 "failed to create file cache directory: path = {}, err = {}",
22 path, err
23 );
24 }
25
26 path
27});
28
29pub(super) fn last_modified_u64(metadata: &std::fs::Metadata) -> u64 {
30 u64::try_from(
31 metadata
32 .modified()
33 .unwrap()
34 .duration_since(UNIX_EPOCH)
35 .unwrap()
36 .as_millis(),
37 )
38 .unwrap()
39}
40
41pub(super) fn update_last_accessed(file: &std::fs::File) {
42 let file_metadata = file.metadata().unwrap();
43
44 if let Err(e) = file.set_times(
45 std::fs::FileTimes::new()
46 .set_modified(file_metadata.modified().unwrap())
47 .set_accessed(std::time::SystemTime::now()),
48 ) {
49 panic!("failed to update file last accessed time: {e}");
50 }
51}
52
53pub async fn init_entries_from_uri_list(
54 uri_list: impl ExactSizeIterator<Item = PlRefPath> + Send + 'static,
55 cloud_options: Option<&CloudOptions>,
56) -> PolarsResult<Vec<Arc<FileCacheEntry>>> {
57 init_entries_from_uri_list_impl(Box::new(uri_list), cloud_options).await
58}
59
60async fn init_entries_from_uri_list_impl(
61 uri_list: Box<dyn ExactSizeIterator<Item = PlRefPath> + Send + 'static>,
62 cloud_options: Option<&CloudOptions>,
63) -> PolarsResult<Vec<Arc<FileCacheEntry>>> {
64 #[allow(clippy::len_zero)]
65 if uri_list.len() == 0 {
66 return Ok(Default::default());
67 }
68
69 let mut uri_list = uri_list.peekable();
70
71 let first_uri = uri_list.peek().unwrap().clone();
72
73 let file_cache_ttl = cloud_options
74 .map(|x| x.file_cache_ttl)
75 .unwrap_or_else(get_env_file_cache_ttl);
76
77 if first_uri.has_scheme() {
78 let uri_list: Vec<PlRefPath> = uri_list.collect();
79
80 let is_http = matches!(
82 first_uri.scheme(),
83 Some(CloudScheme::Http | CloudScheme::Https)
84 );
85
86 let authorities: Vec<&str> = if is_http {
87 Vec::new()
88 } else {
89 uri_list
90 .iter()
91 .map(|uri| &uri.as_str()[..uri.authority_end_position()])
92 .collect()
93 };
94
95 let mut representatives: PlHashMap<&str, &PlRefPath> = PlHashMap::default();
98 for (uri, authority) in uri_list.iter().zip(&authorities) {
99 representatives.entry(authority).or_insert(uri);
100 }
101
102 let shared_object_stores: PlHashMap<&str, PolarsObjectStore> =
103 futures::future::try_join_all(representatives.into_iter().map(
104 |(authority, uri)| async move {
105 let (_, object_store) =
106 build_object_store(uri.clone(), cloud_options, false).await?;
107 PolarsResult::Ok((authority, object_store))
108 },
109 ))
110 .await?
111 .into_iter()
112 .collect();
113
114 futures::future::try_join_all(uri_list.iter().enumerate().map(|(i, uri)| {
115 let uri = uri.clone();
116 let shared_object_store = authorities
117 .get(i)
118 .and_then(|authority| shared_object_stores.get(authority))
119 .cloned();
120
121 async move {
122 let object_store = if let Some(shared_object_store) = shared_object_store {
123 shared_object_store
124 } else {
125 let (_, object_store) =
126 build_object_store(uri.clone(), cloud_options, false).await?;
127 object_store
128 };
129
130 FILE_CACHE.init_entry(
131 uri.clone(),
132 &|| {
133 let CloudLocation { prefix, .. } =
134 CloudLocation::new(uri.clone(), false).unwrap();
135 let cloud_path = object_path_from_str(&prefix)?;
136 let object_store = object_store.clone();
137
138 Ok(Arc::new(CloudFileFetcher {
139 uri: uri.clone(),
140 object_store,
141 cloud_path,
142 }))
143 },
144 file_cache_ttl,
145 )
146 }
147 }))
148 .await
149 } else {
150 let mut out = Vec::with_capacity(uri_list.len());
151 for uri in uri_list {
152 let uri = tokio::fs::canonicalize(uri.as_str()).await.map_err(|err| {
153 let msg = Some(format!("{}: {}", err, uri).into());
154 PolarsError::IO {
155 error: err.into(),
156 msg,
157 }
158 })?;
159 let uri = PlRefPath::try_from_pathbuf(uri)?;
160
161 out.push(FILE_CACHE.init_entry(
162 uri.clone(),
163 &|| Ok(Arc::new(LocalFileFetcher::from_uri(uri.clone()))),
164 file_cache_ttl,
165 )?)
166 }
167 Ok(out)
168 }
169}