Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 12 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@ unreachable_pub = "warn"

[workspace.dependencies]
# crates
filemanager = { path = "crates/filemanager", version = "0.1.3", default-features = false }
timsrust = { path = "crates/timsrust", version = "0.5.3", default-features = false }
timsrust-centroid = { path = "crates/timsrust-centroid", version = "0.1.3", default-features = false }
timsrust-core = { path = "crates/timsrust-core", version = "0.1.3", default-features = false }
timsrust-mgf = { path = "crates/timsrust-mgf", version = "0.1.3", default-features = false }
timsrust-minitdf = { path = "crates/timsrust-minitdf", version = "0.1.3", default-features = false }
timsrust-parquet-spectra = { path = "crates/timsrust-parquet-spectra", version = "0.1.3", default-features = false }
timsrust-sdk = { path = "crates/timsrust-sdk", version = "0.1.3", default-features = false }
timsrust-tdf = { path = "crates/timsrust-tdf", version = "0.1.3", default-features = false }
timsrust-tsf = { path = "crates/timsrust-tsf", version = "0.1.3", default-features = false }
timsrust-utils = { path = "crates/timsrust-utils", version = "0.1.3", default-features = false }
filemanager = { path = "crates/filemanager", version = "0.1.4", default-features = false }
timsrust = { path = "crates/timsrust", version = "0.5.4", default-features = false }
timsrust-centroid = { path = "crates/timsrust-centroid", version = "0.1.4", default-features = false }
timsrust-core = { path = "crates/timsrust-core", version = "0.1.4", default-features = false }
timsrust-mgf = { path = "crates/timsrust-mgf", version = "0.1.4", default-features = false }
timsrust-minitdf = { path = "crates/timsrust-minitdf", version = "0.1.4", default-features = false }
timsrust-parquet-spectra = { path = "crates/timsrust-parquet-spectra", version = "0.1.4", default-features = false }
timsrust-sdk = { path = "crates/timsrust-sdk", version = "0.1.4", default-features = false }
timsrust-tdf = { path = "crates/timsrust-tdf", version = "0.1.4", default-features = false }
timsrust-tsf = { path = "crates/timsrust-tsf", version = "0.1.4", default-features = false }
timsrust-utils = { path = "crates/timsrust-utils", version = "0.1.4", default-features = false }
timsrust-patched = { version = "0.1.1", default-features = false }
# clis
timsrust-cli-core = { path = "clis/timsrust-cli-core", version = "0.1.3", default-features = false }
timsrust-cli-core = { path = "clis/timsrust-cli-core", version = "0.1.4", default-features = false }
# external
arrow = { version = "57", default-features = false }
bytemuck = { version = "1", default-features = false }
Expand Down
2 changes: 1 addition & 1 deletion clis/timsrust-centroid-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "timsrust-centroid-cli"
version = "0.1.3"
version = "0.1.4"
edition = "2024"
publish = false

Expand Down
2 changes: 1 addition & 1 deletion clis/timsrust-cli-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "timsrust-cli-core"
version = "0.1.3"
version = "0.1.4"
edition = "2024"
publish = false

Expand Down
2 changes: 1 addition & 1 deletion clis/timsrust-mgf-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "timsrust-mgf-cli"
version = "0.1.3"
version = "0.1.4"
edition = "2024"
publish = false

Expand Down
2 changes: 1 addition & 1 deletion clis/timsrust-parquet-spectra-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "timsrust-parquet-spectra-cli"
version = "0.1.3"
version = "0.1.4"
edition = "2024"
publish = false

Expand Down
2 changes: 1 addition & 1 deletion crates/filemanager/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "filemanager"
version = "0.1.3"
version = "0.1.4"
edition = "2021"
description = "Unified file abstraction for local and cloud files with caching options and support for multiple formats."
license = "Apache-2.0"
Expand Down
35 changes: 35 additions & 0 deletions crates/filemanager/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,23 @@ impl FileCache {
}
}

/// Returns the local cache URI for `uri` without checking existence.
///
/// Like [`cached_local`](Self::cached_local) but never touches the
/// filesystem. Local URIs pass through unchanged. For cloud URIs,
/// returns the path the file *would* live at inside the cache
/// directory (whether or not it has been downloaded yet), or `None`
/// when the URI has no key or no cache directory is configured.
pub fn local_path(&self, uri: impl Into<Uri>) -> Option<Uri> {
let uri = uri.into();
if uri.is_local() {
return Some(uri);
}
let dir = self.dir.as_ref()?;
let key = uri.key()?;
Some(Uri::from(dir.join(key)))
}

pub fn invalidate(&self, uri: impl Into<Uri>) -> Result<(), CacheError> {
match &self.dir {
Some(dir) => {
Expand Down Expand Up @@ -186,4 +203,22 @@ impl Uri {
pub fn cached_local(&self) -> Option<Uri> {
crate::global_cache().cached_local(self.clone())
}

/// Returns the local-filesystem representation of this URI, without
/// performing any network or existence checks.
///
/// Local URIs are returned unchanged. Cloud URIs are mapped to the
/// path they would occupy inside the configured cache directory
/// (regardless of whether the file has been downloaded). If no cache
/// directory is configured or the URI has no key, the URI is returned
/// unchanged.
///
/// Useful as a fast pre-check: callers can probe the returned URI
/// with local filesystem operations and only fall back to network
/// access if those probes fail.
pub fn local_representation(&self) -> Uri {
crate::global_cache()
.local_path(self.clone())
.unwrap_or_else(|| self.clone())
}
}
78 changes: 69 additions & 9 deletions crates/filemanager/src/cloud_store/cloud/authenticate.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::sync::Arc;
use std::collections::HashMap;
use std::sync::{Arc, OnceLock, RwLock};

use object_store::{path::Path as ObjectPath, ObjectStore};
use url::Url;
Expand All @@ -7,6 +8,69 @@ use crate::{
cloud_store::CloudProvider, uri::URI_SCHEME_SEPARATOR, CloudError,
};

/// Process-wide cache of authenticated [`ObjectStore`] instances.
///
/// Building an `ObjectStore` involves env-var lookups and constructing an
/// HTTP client, both of which are needlessly repeated when many URIs under
/// the same bucket/container are accessed. The cache is keyed by the parts
/// of the URI that determine store identity (scheme, host, and — for
/// virtual-hosted Azure HTTPS URLs — the container path segment).
fn store_cache() -> &'static RwLock<HashMap<String, Arc<dyn ObjectStore>>> {
static CACHE: OnceLock<RwLock<HashMap<String, Arc<dyn ObjectStore>>>> =
OnceLock::new();
CACHE.get_or_init(|| RwLock::new(HashMap::new()))
}

/// Computes the cache key for a parsed URL + provider.
///
/// For Azure HTTPS URLs the container is encoded into the bound store, so
/// it must be part of the key; for every other case `scheme://host` is
/// sufficient.
fn store_cache_key(url: &Url, provider: CloudProvider) -> String {
let scheme = url.scheme();
let host = url.host_str().unwrap_or("");
if provider == CloudProvider::Azure && scheme == "https" {
let container =
url.path_segments().and_then(|mut s| s.next()).unwrap_or("");
format!("{scheme}://{host}/{container}")
} else {
format!("{scheme}://{host}")
}
}

fn build_store(
url: &Url,
host: &str,
provider: CloudProvider,
) -> Result<Arc<dyn ObjectStore>, CloudError> {
match provider {
CloudProvider::S3 => build_s3(url, host),
CloudProvider::Azure => build_azure(url, host),
CloudProvider::Gcs => build_gcs(url, host),
}
}

fn cached_or_build_store(
url: &Url,
host: &str,
provider: CloudProvider,
) -> Result<Arc<dyn ObjectStore>, CloudError> {
let key = store_cache_key(url, provider);
let cache = store_cache();
if let Some(store) = cache
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.get(&key)
{
return Ok(store.clone());
}
let store = build_store(url, host, provider)?;
let mut guard = cache
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner());
Ok(guard.entry(key).or_insert(store).clone())
}

// --- credential detection ---

fn env_any(vars: &[&str]) -> bool {
Expand Down Expand Up @@ -169,18 +233,14 @@ impl CloudProvider {
let url =
Url::parse(raw).map_err(|e| CloudError::ThirdParty(Box::new(e)))?;
let host = url.host_str().unwrap_or("");
let provider = CloudProvider::parse(&url);
let store = match provider {
Some(CloudProvider::S3) => build_s3(&url, host),
Some(CloudProvider::Azure) => build_azure(&url, host),
Some(CloudProvider::Gcs) => build_gcs(&url, host),
None => Err(CloudError::NotACloudUri(url.to_string())),
}?;
let provider = CloudProvider::parse(&url)
.ok_or_else(|| CloudError::NotACloudUri(url.to_string()))?;
let store = cached_or_build_store(&url, host, provider)?;
// For HTTPS Azure URLs the first path segment is the container, already baked into the
// ObjectStore. Strip it so the resulting path is relative to the container.
let path_str = url.path().strip_prefix('/').unwrap_or("");
let path_str = if url.scheme() == "https"
&& matches!(provider, Some(CloudProvider::Azure))
&& matches!(provider, CloudProvider::Azure)
{
path_str.split_once('/').map(|(_, rest)| rest).unwrap_or("")
} else {
Expand Down
2 changes: 1 addition & 1 deletion crates/timsrust-centroid/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "timsrust-centroid"
version = "0.1.3"
version = "0.1.4"
edition = "2024"
description = "Centroiding algorithms for timsTOF ion mobility spectra"
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion crates/timsrust-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "timsrust-core"
version = "0.1.3"
version = "0.1.4"
edition = "2024"
description = "Core data types and abstractions for timsTOF data"
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion crates/timsrust-mgf/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "timsrust-mgf"
version = "0.1.3"
version = "0.1.4"
edition = "2024"
description = "MGF (Mascot Generic Format) writer for timsTOF spectra"
license = "Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion crates/timsrust-minitdf/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "timsrust-minitdf"
version = "0.1.3"
version = "0.1.4"
edition = "2024"
description = "Reader for the ProteoScape miniTDF timsTOF file format"
license = "Apache-2.0"
Expand Down
Loading
Loading