1use anyhow::{Result, anyhow};
4use oxigeo_core::io::{DataSource, FileDataSource};
5use std::sync::OnceLock;
6
7static TOKIO_RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
8
9fn get_runtime() -> Result<&'static tokio::runtime::Runtime> {
13 if let Some(rt) = TOKIO_RUNTIME.get() {
14 return Ok(rt);
15 }
16 let rt = tokio::runtime::Runtime::new()
17 .map_err(|e| anyhow!("failed to create tokio runtime for cloud I/O: {}", e))?;
18 let _ = TOKIO_RUNTIME.set(rt);
21 TOKIO_RUNTIME
22 .get()
23 .ok_or_else(|| anyhow!("tokio runtime unavailable after init"))
24}
25
26pub fn is_cloud_uri(uri: &str) -> bool {
28 uri.starts_with("s3://") || uri.starts_with("gs://") || uri.starts_with("az://")
29}
30
31pub fn error_for_cloud_write(uri: &str) -> anyhow::Error {
33 anyhow!("cloud write not yet supported: {uri}; please write locally then upload")
34}
35
36pub fn open_datasource(uri: &str) -> Result<Box<dyn DataSource>> {
45 if let Some(path) = uri.strip_prefix("file://") {
46 return Ok(Box::new(
47 FileDataSource::open(path).map_err(|e| anyhow!("{}", e))?,
48 ));
49 }
50
51 if is_cloud_uri(uri) {
52 let rt = get_runtime()?;
53 let ds = rt.block_on(open_cloud_datasource(uri))?;
54 return Ok(ds);
55 }
56
57 Ok(Box::new(
59 FileDataSource::open(uri).map_err(|e| anyhow!("{}", e))?,
60 ))
61}
62
63async fn open_cloud_datasource(uri: &str) -> Result<Box<dyn DataSource>> {
64 let (backend, bucket, key) = oxigeo_rs3gw::parse_url(uri).map_err(|e| anyhow!("{}", e))?;
65 let storage = backend
66 .create_storage()
67 .await
68 .map_err(|e| anyhow!("{}", e))?;
69 let ds = oxigeo_rs3gw::Rs3gwDataSource::new(storage, bucket, key)
70 .await
71 .map_err(|e| anyhow!("{}", e))?;
72 Ok(Box::new(ds))
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use std::sync::atomic::{AtomicU64, Ordering};
79
80 struct TempPath(std::path::PathBuf);
88
89 impl TempPath {
90 fn new(name: &str) -> Self {
91 static COUNTER: AtomicU64 = AtomicU64::new(0);
92 let seq = COUNTER.fetch_add(1, Ordering::Relaxed);
93 Self(std::env::temp_dir().join(format!(
94 "oxigeo_cli_cloud_{}_{seq}_{name}",
95 std::process::id()
96 )))
97 }
98 }
99
100 impl std::ops::Deref for TempPath {
101 type Target = std::path::Path;
102
103 fn deref(&self) -> &std::path::Path {
104 &self.0
105 }
106 }
107
108 impl AsRef<std::path::Path> for TempPath {
109 fn as_ref(&self) -> &std::path::Path {
110 &self.0
111 }
112 }
113
114 impl Drop for TempPath {
115 fn drop(&mut self) {
116 let _ = std::fs::remove_file(&self.0);
117 }
118 }
119
120 #[test]
121 fn test_is_cloud_uri() {
122 assert!(is_cloud_uri("s3://bucket/key"));
123 assert!(is_cloud_uri("gs://bucket/obj"));
124 assert!(is_cloud_uri("az://container/blob"));
125 assert!(!is_cloud_uri("/local/path.tif"));
126 assert!(!is_cloud_uri("file:///local.tif"));
127 assert!(!is_cloud_uri("relative/path.tif"));
128 }
129
130 #[test]
131 fn test_error_for_cloud_write() {
132 let err = error_for_cloud_write("s3://my-bucket/output.tif");
133 let msg = err.to_string();
134 assert!(msg.contains("s3://my-bucket/output.tif"));
135 assert!(msg.contains("not yet supported"));
136 }
137
138 #[test]
139 fn test_open_datasource_file_path() {
140 let path = TempPath::new("direct.bin");
141 std::fs::write(&path, b"test data").expect("write temp file");
142 let result = open_datasource(path.to_str().expect("valid path"));
143 assert!(result.is_ok(), "expected Ok, got: {:?}", result.err());
144 }
145
146 #[test]
147 fn test_open_datasource_file_uri() {
148 let path = TempPath::new("uri.bin");
149 std::fs::write(&path, b"test data").expect("write temp file");
150 let uri = format!("file://{}", path.display());
151 let result = open_datasource(&uri);
152 assert!(result.is_ok(), "expected Ok, got: {:?}", result.err());
153 }
154
155 #[test]
156 fn test_cloud_uri_classification_comprehensive() {
157 assert!(is_cloud_uri("s3://bucket/path/to/file.tif"));
159 assert!(is_cloud_uri("gs://my-gcs-bucket/dir/file.tif"));
160 assert!(is_cloud_uri("az://mycontainer/blob/path.tif"));
161
162 assert!(!is_cloud_uri("file:///data/local.tif"));
164 assert!(!is_cloud_uri("/absolute/path.tif"));
165 assert!(!is_cloud_uri("relative/path.tif"));
166 assert!(!is_cloud_uri("http://example.com/file.tif"));
167 assert!(!is_cloud_uri("https://example.com/file.tif"));
168 assert!(!is_cloud_uri(""));
169 }
170}