use anyhow::{Result, anyhow};
use oxigdal_core::io::{DataSource, FileDataSource};
use std::sync::OnceLock;
static TOKIO_RUNTIME: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
fn get_runtime() -> Result<&'static tokio::runtime::Runtime> {
if let Some(rt) = TOKIO_RUNTIME.get() {
return Ok(rt);
}
let rt = tokio::runtime::Runtime::new()
.map_err(|e| anyhow!("failed to create tokio runtime for cloud I/O: {}", e))?;
let _ = TOKIO_RUNTIME.set(rt);
TOKIO_RUNTIME
.get()
.ok_or_else(|| anyhow!("tokio runtime unavailable after init"))
}
pub fn is_cloud_uri(uri: &str) -> bool {
uri.starts_with("s3://") || uri.starts_with("gs://") || uri.starts_with("az://")
}
pub fn error_for_cloud_write(uri: &str) -> anyhow::Error {
anyhow!("cloud write not yet supported: {uri}; please write locally then upload")
}
pub fn open_datasource(uri: &str) -> Result<Box<dyn DataSource>> {
if let Some(path) = uri.strip_prefix("file://") {
return Ok(Box::new(
FileDataSource::open(path).map_err(|e| anyhow!("{}", e))?,
));
}
if is_cloud_uri(uri) {
let rt = get_runtime()?;
let ds = rt.block_on(open_cloud_datasource(uri))?;
return Ok(ds);
}
Ok(Box::new(
FileDataSource::open(uri).map_err(|e| anyhow!("{}", e))?,
))
}
async fn open_cloud_datasource(uri: &str) -> Result<Box<dyn DataSource>> {
let (backend, bucket, key) = oxigdal_rs3gw::parse_url(uri).map_err(|e| anyhow!("{}", e))?;
let storage = backend
.create_storage()
.await
.map_err(|e| anyhow!("{}", e))?;
let ds = oxigdal_rs3gw::Rs3gwDataSource::new(storage, bucket, key)
.await
.map_err(|e| anyhow!("{}", e))?;
Ok(Box::new(ds))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_cloud_uri() {
assert!(is_cloud_uri("s3://bucket/key"));
assert!(is_cloud_uri("gs://bucket/obj"));
assert!(is_cloud_uri("az://container/blob"));
assert!(!is_cloud_uri("/local/path.tif"));
assert!(!is_cloud_uri("file:///local.tif"));
assert!(!is_cloud_uri("relative/path.tif"));
}
#[test]
fn test_error_for_cloud_write() {
let err = error_for_cloud_write("s3://my-bucket/output.tif");
let msg = err.to_string();
assert!(msg.contains("s3://my-bucket/output.tif"));
assert!(msg.contains("not yet supported"));
}
#[test]
fn test_open_datasource_file_path() {
let dir = std::env::temp_dir();
let path = dir.join("cloud_test_direct.bin");
std::fs::write(&path, b"test data").expect("write temp file");
let result = open_datasource(path.to_str().expect("valid path"));
assert!(result.is_ok(), "expected Ok, got: {:?}", result.err());
}
#[test]
fn test_open_datasource_file_uri() {
let dir = std::env::temp_dir();
let path = dir.join("cloud_test_uri.bin");
std::fs::write(&path, b"test data").expect("write temp file");
let uri = format!("file://{}", path.display());
let result = open_datasource(&uri);
assert!(result.is_ok(), "expected Ok, got: {:?}", result.err());
}
#[test]
fn test_cloud_uri_classification_comprehensive() {
assert!(is_cloud_uri("s3://bucket/path/to/file.tif"));
assert!(is_cloud_uri("gs://my-gcs-bucket/dir/file.tif"));
assert!(is_cloud_uri("az://mycontainer/blob/path.tif"));
assert!(!is_cloud_uri("file:///data/local.tif"));
assert!(!is_cloud_uri("/absolute/path.tif"));
assert!(!is_cloud_uri("relative/path.tif"));
assert!(!is_cloud_uri("http://example.com/file.tif"));
assert!(!is_cloud_uri("https://example.com/file.tif"));
assert!(!is_cloud_uri(""));
}
}