chore(rust): update lockfile and format sources
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Generated
+209
-14
@@ -654,6 +654,12 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
@@ -799,11 +805,24 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"r-efi 5.3.0",
|
||||
"wasip2",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi 6.0.0",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hash32"
|
||||
version = "0.2.1"
|
||||
@@ -822,6 +841,15 @@ dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
@@ -1037,6 +1065,12 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
@@ -1076,6 +1110,8 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1137,6 +1173,12 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.185"
|
||||
@@ -1664,6 +1706,16 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.5.0"
|
||||
@@ -1781,6 +1833,12 @@ version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
@@ -1789,9 +1847,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
@@ -2047,9 +2105,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rtoolbox"
|
||||
version = "0.0.4"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "327b72899159dfae8060c51a1f6aebe955245bcd9cc4997eed0f623caea022e4"
|
||||
checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -2065,7 +2123,7 @@ dependencies = [
|
||||
"borsh",
|
||||
"bytes",
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"rand 0.8.6",
|
||||
"rkyv",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -2444,7 +2502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.4",
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
@@ -2536,9 +2594,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.0"
|
||||
version = "1.52.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a91135f59b1cbf38c91e73cf3386fca9bb77915c45ce2771460c9d92f0f3d776"
|
||||
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
@@ -2674,9 +2732,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
@@ -2705,6 +2763,12 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.9.0"
|
||||
@@ -2809,11 +2873,20 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
version = "1.0.3+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
"wit-bindgen 0.57.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.51.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2871,6 +2944,40 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.95"
|
||||
@@ -3173,6 +3280,94 @@ name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn 2.0.117",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
|
||||
+4
-16
@@ -10,10 +10,7 @@ struct FileListResponse {
|
||||
|
||||
impl MDRSConnection {
|
||||
/// List all files in a folder, following pagination automatically.
|
||||
pub async fn list_all_files(
|
||||
&self,
|
||||
folder_id: &str,
|
||||
) -> Result<Vec<File>, anyhow::Error> {
|
||||
pub async fn list_all_files(&self, folder_id: &str) -> Result<Vec<File>, anyhow::Error> {
|
||||
let mut all_files = Vec::new();
|
||||
let mut page: u32 = 1;
|
||||
loop {
|
||||
@@ -36,13 +33,9 @@ impl MDRSConnection {
|
||||
}
|
||||
|
||||
/// Upload a local file into the given remote folder.
|
||||
pub async fn upload_file(
|
||||
&self,
|
||||
folder_id: &str,
|
||||
file_path: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
pub async fn upload_file(&self, folder_id: &str, file_path: &str) -> Result<(), anyhow::Error> {
|
||||
use anyhow::{anyhow, bail};
|
||||
use reqwest::multipart;
|
||||
use anyhow::{anyhow, bail};
|
||||
let file_name: String = std::path::Path::new(file_path)
|
||||
.file_name()
|
||||
.ok_or_else(|| anyhow!("Invalid file path: `{}`", file_path))?
|
||||
@@ -62,11 +55,7 @@ use anyhow::{anyhow, bail};
|
||||
}
|
||||
|
||||
/// Download a file from `url` and write it to `dest`.
|
||||
pub async fn download_file(
|
||||
&self,
|
||||
url: &str,
|
||||
dest: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
pub async fn download_file(&self, url: &str, dest: &str) -> Result<(), anyhow::Error> {
|
||||
let resp = self
|
||||
.client
|
||||
.get(url)
|
||||
@@ -78,4 +67,3 @@ use anyhow::{anyhow, bail};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-7
@@ -1,6 +1,6 @@
|
||||
use crate::connection::MDRSConnection;
|
||||
use anyhow::{bail};
|
||||
pub use crate::models::folder::{FolderDetail, FolderSimple};
|
||||
use anyhow::bail;
|
||||
|
||||
impl MDRSConnection {
|
||||
/// List folders matching the given path under a laboratory (GET v3/folders/?path=...&laboratory_id=...)
|
||||
@@ -50,11 +50,7 @@ impl MDRSConnection {
|
||||
|
||||
/// Authenticate against a password-locked folder (POST v3/folders/{id}/auth/).
|
||||
/// Returns `Err` if the password is incorrect or the request fails.
|
||||
pub async fn folder_auth(
|
||||
&self,
|
||||
folder_id: &str,
|
||||
password: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
pub async fn folder_auth(&self, folder_id: &str, password: &str) -> Result<(), anyhow::Error> {
|
||||
let resp = self
|
||||
.client
|
||||
.post(self.build_url(&format!("v3/folders/{}/auth/", folder_id)))
|
||||
@@ -71,4 +67,3 @@ impl MDRSConnection {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,14 @@ impl MDRSConnection {
|
||||
let resp = self.get("v3/laboratories/").await?;
|
||||
// The API may return a paginated object or a direct array
|
||||
let text = resp.text().await?;
|
||||
let items: Vec<Laboratory> = if let Ok(list) = serde_json::from_str::<Vec<Laboratory>>(&text) {
|
||||
list
|
||||
} else if let Ok(paged) = serde_json::from_str::<LabListResponse>(&text) {
|
||||
paged.results.unwrap_or_default()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let items: Vec<Laboratory> =
|
||||
if let Ok(list) = serde_json::from_str::<Vec<Laboratory>>(&text) {
|
||||
list
|
||||
} else if let Ok(paged) = serde_json::from_str::<LabListResponse>(&text) {
|
||||
paged.results.unwrap_or_default()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
Ok(Laboratories { items })
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -1,7 +1,7 @@
|
||||
use crate::connection::MDRSConnection;
|
||||
use crate::models::user::User as ModelUser;
|
||||
use anyhow::bail;
|
||||
use serde::Deserialize;
|
||||
use anyhow::{bail};
|
||||
|
||||
/// Full API response shape from GET v3/users/current/
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -38,10 +38,7 @@ impl MDRSConnection {
|
||||
|
||||
/// Refresh the access token using the refresh token.
|
||||
/// POST v3/users/token/refresh/ {refresh: ...} -> {access: new_access}
|
||||
pub async fn token_refresh(
|
||||
&self,
|
||||
refresh_token: &str,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
pub async fn token_refresh(&self, refresh_token: &str) -> Result<String, anyhow::Error> {
|
||||
let body = serde_json::json!({ "refresh": refresh_token });
|
||||
let resp = self
|
||||
.client
|
||||
|
||||
Vendored
+21
-11
@@ -4,8 +4,8 @@ pub mod types;
|
||||
pub use digest::compute_digest;
|
||||
pub use types::{Cache, CacheLaboratory, CacheLabsWrapper, CacheUser};
|
||||
|
||||
use anyhow::{anyhow, bail};
|
||||
use crate::connection::MDRSConnection;
|
||||
use anyhow::{anyhow, bail};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::sync::{Arc, LazyLock, Mutex};
|
||||
@@ -43,10 +43,21 @@ fn cache_file_path(remote: &str) -> std::path::PathBuf {
|
||||
pub fn load_cache(remote: &str) -> Result<Cache, anyhow::Error> {
|
||||
let cache_path = cache_file_path(remote);
|
||||
if !cache_path.exists() {
|
||||
bail!("Not logged in to `{}`. Run `mdrs login {}` first.", remote, remote);
|
||||
bail!(
|
||||
"Not logged in to `{}`. Run `mdrs login {}` first.",
|
||||
remote,
|
||||
remote
|
||||
);
|
||||
}
|
||||
let data = fs::read_to_string(&cache_path)?;
|
||||
serde_json::from_str::<Cache>(&data).map_err(|e| anyhow!("Cache for `{}` is invalid or outdated ({}). Run `mdrs login {}` to refresh it.", remote, e, remote))
|
||||
serde_json::from_str::<Cache>(&data).map_err(|e| {
|
||||
anyhow!(
|
||||
"Cache for `{}` is invalid or outdated ({}). Run `mdrs login {}` to refresh it.",
|
||||
remote,
|
||||
e,
|
||||
remote
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -61,9 +72,7 @@ pub fn load_cache(remote: &str) -> Result<Cache, anyhow::Error> {
|
||||
/// - `flock(LOCK_EX)` on a dedicated `cache/{remote}.lock` file serializes
|
||||
/// the entire read-check-refresh-write cycle across separate processes on
|
||||
/// the same host.
|
||||
pub async fn load_cache_with_token_refresh(
|
||||
remote: &str,
|
||||
) -> Result<Cache, anyhow::Error> {
|
||||
pub async fn load_cache_with_token_refresh(remote: &str) -> Result<Cache, anyhow::Error> {
|
||||
let lock = get_remote_lock(remote);
|
||||
let _guard = lock.lock().await;
|
||||
|
||||
@@ -81,7 +90,11 @@ pub async fn load_cache_with_token_refresh(
|
||||
let mut cache = load_cache(remote)?;
|
||||
|
||||
if crate::token::is_expired(&cache.token.refresh) {
|
||||
bail!("Session for `{}` has expired. Please run `mdrs login {}` again.", remote, remote);
|
||||
bail!(
|
||||
"Session for `{}` has expired. Please run `mdrs login {}` again.",
|
||||
remote,
|
||||
remote
|
||||
);
|
||||
}
|
||||
|
||||
if crate::token::is_refresh_required(&cache.token.access, &cache.token.refresh) {
|
||||
@@ -99,10 +112,7 @@ pub async fn load_cache_with_token_refresh(
|
||||
|
||||
/// Call the token-refresh endpoint and write the new access token back to the
|
||||
/// cache file. The caller must already hold the per-remote async mutex.
|
||||
async fn refresh_and_persist(
|
||||
remote: &str,
|
||||
cache: &Cache,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
async fn refresh_and_persist(remote: &str, cache: &Cache) -> Result<String, anyhow::Error> {
|
||||
let url = crate::commands::config::get_remote_url(remote)?
|
||||
.ok_or_else(|| anyhow!("Remote `{}` is not configured.", remote))?;
|
||||
let conn = MDRSConnection::new(&url);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use crate::commands::shared::{
|
||||
find_folder, find_lab_in_cache, parse_remote_path,
|
||||
};
|
||||
use anyhow::{bail};
|
||||
use crate::commands::shared::{find_folder, find_lab_in_cache, parse_remote_path};
|
||||
use anyhow::bail;
|
||||
|
||||
pub async fn chacl(
|
||||
remote_path: &str,
|
||||
@@ -29,7 +27,10 @@ pub async fn chacl(
|
||||
let folder = find_folder(&conn, lab.id, &folder_path, None).await?;
|
||||
|
||||
let mut data = serde_json::Map::new();
|
||||
data.insert("access_level".to_string(), serde_json::json!(access_level_id));
|
||||
data.insert(
|
||||
"access_level".to_string(),
|
||||
serde_json::json!(access_level_id),
|
||||
);
|
||||
if recursive {
|
||||
data.insert("lower".to_string(), serde_json::json!(1));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::bail;
|
||||
use configparser::ini::Ini;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use anyhow::{bail};
|
||||
|
||||
fn config_path() -> PathBuf {
|
||||
crate::settings::SETTINGS.config_dirname.join("config.ini")
|
||||
|
||||
+19
-11
@@ -1,15 +1,11 @@
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use crate::commands::shared::{
|
||||
find_file_by_name, find_folder, find_lab_in_cache,
|
||||
find_subfolder_by_name, nfc, parse_remote_path,
|
||||
find_file_by_name, find_folder, find_lab_in_cache, find_subfolder_by_name, nfc,
|
||||
parse_remote_path,
|
||||
};
|
||||
use anyhow::{bail};
|
||||
use anyhow::bail;
|
||||
|
||||
pub async fn cp(
|
||||
src_path: &str,
|
||||
dest_path: &str,
|
||||
recursive: bool,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
pub async fn cp(src_path: &str, dest_path: &str, recursive: bool) -> Result<(), anyhow::Error> {
|
||||
let (s_remote, s_lab, s_path) = parse_remote_path(src_path)?;
|
||||
let dest_ends_with_slash = dest_path.ends_with('/');
|
||||
let (d_remote, d_lab, d_path) = parse_remote_path(dest_path)?;
|
||||
@@ -51,7 +47,11 @@ pub async fn cp(
|
||||
bail!("File `{}` already exists.", d_basename);
|
||||
}
|
||||
if find_subfolder_by_name(&d_parent_folder.sub_folders, &d_basename).is_some() {
|
||||
bail!("Cannot overwrite non-folder `{}` with folder `{}`.", d_basename, d_path);
|
||||
bail!(
|
||||
"Cannot overwrite non-folder `{}` with folder `{}`.",
|
||||
d_basename,
|
||||
d_path
|
||||
);
|
||||
}
|
||||
// No-op if source and destination are identical
|
||||
if s_parent_folder.id == d_parent_folder.id && d_basename == s_basename {
|
||||
@@ -81,13 +81,21 @@ pub async fn cp(
|
||||
}
|
||||
let src_folder_id = src_folder.id.clone();
|
||||
if find_file_by_name(&d_parent_files, &d_basename).is_some() {
|
||||
bail!("Cannot overwrite non-folder `{}` with folder `{}`.", d_basename, s_path);
|
||||
bail!(
|
||||
"Cannot overwrite non-folder `{}` with folder `{}`.",
|
||||
d_basename,
|
||||
s_path
|
||||
);
|
||||
}
|
||||
if let Some(d_folder) = find_subfolder_by_name(&d_parent_folder.sub_folders, &d_basename) {
|
||||
if d_folder.id == src_folder_id {
|
||||
bail!("`{}` and `{}` are the same folder.", s_path, s_path);
|
||||
}
|
||||
bail!("Cannot move `{}` to `{}`: Folder not empty.", s_path, d_path);
|
||||
bail!(
|
||||
"Cannot move `{}` to `{}`: Folder not empty.",
|
||||
s_path,
|
||||
d_path
|
||||
);
|
||||
}
|
||||
// No-op if source and destination are identical
|
||||
if s_parent_folder.id == d_parent_folder.id && s_basename == d_basename {
|
||||
|
||||
+16
-15
@@ -1,13 +1,12 @@
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use crate::commands::shared::{
|
||||
find_file_by_name, find_folder, find_lab_in_cache,
|
||||
find_subfolder_by_name, parse_remote_path,
|
||||
find_file_by_name, find_folder, find_lab_in_cache, find_subfolder_by_name, parse_remote_path,
|
||||
};
|
||||
use crate::connection::MDRSConnection;
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use anyhow::{anyhow, bail};
|
||||
|
||||
pub async fn download(
|
||||
remote_path: &str,
|
||||
@@ -93,8 +92,7 @@ pub async fn download(
|
||||
let dir_files = conn.list_all_files(&folder_id).await?;
|
||||
|
||||
// Download files in this folder (up to 10 concurrent).
|
||||
let mut futs: FuturesUnordered<tokio::task::JoinHandle<()>> =
|
||||
FuturesUnordered::new();
|
||||
let mut futs: FuturesUnordered<tokio::task::JoinHandle<()>> = FuturesUnordered::new();
|
||||
for f in &dir_files {
|
||||
if is_excluded(&excludes, &lab.name, &folder.path, Some(&f.name)) {
|
||||
continue;
|
||||
@@ -119,7 +117,10 @@ pub async fn download(
|
||||
// (connection pool) while supplying a fresh Bearer token.
|
||||
let task_conn = match load_cache_with_token_refresh(&remote).await {
|
||||
Ok(c) => conn.with_token(c.token.access),
|
||||
Err(e) => { eprintln!("Error: {}", e); return; }
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let dest_str = dest_path.to_string_lossy().to_string();
|
||||
match task_conn.download_file(&url, &dest_str).await {
|
||||
@@ -163,18 +164,18 @@ pub async fn download(
|
||||
/// Return true if the given lab/folder/file path matches any exclude pattern.
|
||||
/// Constructs: `/{lab_name}{folder_path}{file_name}` lowercased, trailing slash stripped.
|
||||
/// `folder_path` is expected to already start (and end) with "/".
|
||||
fn is_excluded(excludes: &[String], lab_name: &str, folder_path: &str, file_name: Option<&str>) -> bool {
|
||||
fn is_excluded(
|
||||
excludes: &[String],
|
||||
lab_name: &str,
|
||||
folder_path: &str,
|
||||
file_name: Option<&str>,
|
||||
) -> bool {
|
||||
if excludes.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let path = format!(
|
||||
"/{}{}{}",
|
||||
lab_name,
|
||||
folder_path,
|
||||
file_name.unwrap_or("")
|
||||
)
|
||||
.trim_end_matches('/')
|
||||
.to_lowercase();
|
||||
let path = format!("/{}{}{}", lab_name, folder_path, file_name.unwrap_or(""))
|
||||
.trim_end_matches('/')
|
||||
.to_lowercase();
|
||||
excludes.iter().any(|e| e == &path)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use anyhow::{anyhow};
|
||||
use crate::commands::shared::{
|
||||
find_file_by_name, find_folder, find_lab_in_cache,
|
||||
parse_remote_path,
|
||||
find_file_by_name, find_folder, find_lab_in_cache, parse_remote_path,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
|
||||
pub async fn file_metadata(remote_path: &str, password: Option<&str>) -> Result<(), anyhow::Error> {
|
||||
let (remote, labname, r_path) = parse_remote_path(remote_path)?;
|
||||
|
||||
+12
-5
@@ -18,8 +18,12 @@ pub async fn labs(remote: &str) -> Result<(), anyhow::Error> {
|
||||
|
||||
println!(
|
||||
"{:<w_name$} {:<w_pi$} {:<w_full$}",
|
||||
header.0, header.1, header.2,
|
||||
w_name = w_name, w_pi = w_pi, w_full = w_full,
|
||||
header.0,
|
||||
header.1,
|
||||
header.2,
|
||||
w_name = w_name,
|
||||
w_pi = w_pi,
|
||||
w_full = w_full,
|
||||
);
|
||||
let sep_len = w_name + 2 + w_pi + 2 + w_full;
|
||||
println!("{}", "-".repeat(sep_len));
|
||||
@@ -27,11 +31,14 @@ pub async fn labs(remote: &str) -> Result<(), anyhow::Error> {
|
||||
for lab in &labs.items {
|
||||
println!(
|
||||
"{:<w_name$} {:<w_pi$} {:<w_full$}",
|
||||
lab.name, lab.pi_name, lab.full_name,
|
||||
w_name = w_name, w_pi = w_pi, w_full = w_full,
|
||||
lab.name,
|
||||
lab.pi_name,
|
||||
lab.full_name,
|
||||
w_name = w_name,
|
||||
w_pi = w_pi,
|
||||
w_full = w_full,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use anyhow::{anyhow, bail};
|
||||
use crate::cache::{CacheLabsWrapper, CacheUser, compute_digest};
|
||||
use crate::connection::MDRSConnection;
|
||||
use crate::models::laboratory::Laboratories;
|
||||
use crate::models::user::User;
|
||||
use anyhow::{anyhow, bail};
|
||||
use reqwest::Client;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{Value, json};
|
||||
@@ -41,12 +41,7 @@ struct TokenResp {
|
||||
refresh: String,
|
||||
}
|
||||
|
||||
|
||||
pub async fn login(
|
||||
username: &str,
|
||||
password: &str,
|
||||
remote: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
pub async fn login(username: &str, password: &str, remote: &str) -> Result<(), anyhow::Error> {
|
||||
// resolve remote label to URL from config
|
||||
let url_opt = crate::commands::config::get_remote_url(remote)?;
|
||||
let base_url = url_opt.ok_or_else(|| anyhow!("Remote host `{}` is not configured", remote))?;
|
||||
@@ -141,4 +136,3 @@ pub async fn login(
|
||||
println!("Login Successful");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+5
-11
@@ -1,11 +1,9 @@
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use crate::commands::shared::{find_folder, find_lab_in_cache, fmt_datetime, parse_remote_path};
|
||||
use crate::connection::MDRSConnection;
|
||||
use crate::models::file::File;
|
||||
use crate::models::folder::{FolderDetail, FolderSimple};
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use crate::commands::shared::{
|
||||
find_folder, find_lab_in_cache, fmt_datetime, parse_remote_path,
|
||||
};
|
||||
use crate::connection::MDRSConnection;
|
||||
use serde_json::{json, Value};
|
||||
use serde_json::{Value, json};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
@@ -214,11 +212,7 @@ fn file_to_json(f: &File, base_url: &str) -> Value {
|
||||
let download_url = if f.download_url.starts_with("http") {
|
||||
f.download_url.clone()
|
||||
} else {
|
||||
format!(
|
||||
"{}{}",
|
||||
base_url.trim_end_matches('/'),
|
||||
f.download_url
|
||||
)
|
||||
format!("{}{}", base_url.trim_end_matches('/'), f.download_url)
|
||||
};
|
||||
json!({
|
||||
"id": f.id,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use crate::commands::shared::{
|
||||
find_folder, find_lab_in_cache, parse_remote_path,
|
||||
};
|
||||
use crate::commands::shared::{find_folder, find_lab_in_cache, parse_remote_path};
|
||||
|
||||
pub async fn metadata(remote_path: &str, password: Option<&str>) -> Result<(), anyhow::Error> {
|
||||
let (remote, labname, folder_path) = parse_remote_path(remote_path)?;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use crate::commands::shared::{
|
||||
find_file_by_name, find_folder, find_lab_in_cache,
|
||||
find_subfolder_by_name, nfc, parse_remote_path,
|
||||
find_file_by_name, find_folder, find_lab_in_cache, find_subfolder_by_name, nfc,
|
||||
parse_remote_path,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
|
||||
|
||||
+1
-1
@@ -14,8 +14,8 @@ pub mod metadata;
|
||||
pub mod mkdir;
|
||||
pub mod mv;
|
||||
pub mod rm;
|
||||
pub mod selfupdate;
|
||||
pub mod shared;
|
||||
pub mod upload;
|
||||
pub mod version;
|
||||
pub mod whoami;
|
||||
pub mod selfupdate;
|
||||
|
||||
+18
-6
@@ -1,9 +1,9 @@
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use crate::commands::shared::{
|
||||
find_file_by_name, find_folder, find_lab_in_cache,
|
||||
find_subfolder_by_name, nfc, parse_remote_path,
|
||||
find_file_by_name, find_folder, find_lab_in_cache, find_subfolder_by_name, nfc,
|
||||
parse_remote_path,
|
||||
};
|
||||
use anyhow::{bail};
|
||||
use anyhow::bail;
|
||||
|
||||
pub async fn mv(src_path: &str, dest_path: &str) -> Result<(), anyhow::Error> {
|
||||
let (s_remote, s_lab, s_path) = parse_remote_path(src_path)?;
|
||||
@@ -47,7 +47,11 @@ pub async fn mv(src_path: &str, dest_path: &str) -> Result<(), anyhow::Error> {
|
||||
bail!("File `{}` already exists.", d_basename);
|
||||
}
|
||||
if find_subfolder_by_name(&d_parent_folder.sub_folders, &d_basename).is_some() {
|
||||
bail!("Cannot overwrite non-folder `{}` with folder `{}`.", d_basename, d_path);
|
||||
bail!(
|
||||
"Cannot overwrite non-folder `{}` with folder `{}`.",
|
||||
d_basename,
|
||||
d_path
|
||||
);
|
||||
}
|
||||
// No-op if source and destination are identical
|
||||
if s_parent_folder.id == d_parent_folder.id && d_basename == s_basename {
|
||||
@@ -74,13 +78,21 @@ pub async fn mv(src_path: &str, dest_path: &str) -> Result<(), anyhow::Error> {
|
||||
};
|
||||
let src_folder_id = src_folder.id.clone();
|
||||
if find_file_by_name(&d_parent_files, &d_basename).is_some() {
|
||||
bail!("Cannot overwrite non-folder `{}` with folder `{}`.", d_basename, s_path);
|
||||
bail!(
|
||||
"Cannot overwrite non-folder `{}` with folder `{}`.",
|
||||
d_basename,
|
||||
s_path
|
||||
);
|
||||
}
|
||||
if let Some(d_folder) = find_subfolder_by_name(&d_parent_folder.sub_folders, &d_basename) {
|
||||
if d_folder.id == src_folder_id {
|
||||
bail!("`{}` and `{}` are the same folder.", s_path, s_path);
|
||||
}
|
||||
bail!("Cannot move `{}` to `{}`: Folder not empty.", s_path, d_path);
|
||||
bail!(
|
||||
"Cannot move `{}` to `{}`: Folder not empty.",
|
||||
s_path,
|
||||
d_path
|
||||
);
|
||||
}
|
||||
// No-op if source and destination are identical
|
||||
if s_parent_folder.id == d_parent_folder.id && s_basename == d_basename {
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use crate::commands::shared::{
|
||||
find_file_by_name, find_folder, find_lab_in_cache,
|
||||
find_subfolder_by_name, parse_remote_path,
|
||||
find_file_by_name, find_folder, find_lab_in_cache, find_subfolder_by_name, parse_remote_path,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
|
||||
|
||||
+10
-10
@@ -50,7 +50,11 @@ fn is_newer(current: &str, latest: &str) -> bool {
|
||||
|
||||
/// Extract the binary named `bin_name` from a `.tar.gz` archive at `archive_path`
|
||||
/// and write it to `dest_path`.
|
||||
fn extract_from_tar_gz(archive_path: &Path, bin_name: &str, dest_path: &Path) -> anyhow::Result<()> {
|
||||
fn extract_from_tar_gz(
|
||||
archive_path: &Path,
|
||||
bin_name: &str,
|
||||
dest_path: &Path,
|
||||
) -> anyhow::Result<()> {
|
||||
use flate2::read::GzDecoder;
|
||||
use tar::Archive;
|
||||
|
||||
@@ -98,12 +102,12 @@ fn extract_from_zip(archive_path: &Path, bin_name: &str, dest_path: &Path) -> an
|
||||
pub async fn selfupdate(yes: bool) -> anyhow::Result<()> {
|
||||
let current_version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
println!("Checking for updates (current version: {current_version}, target: {BUILD_TARGET})...");
|
||||
|
||||
let api_url = format!(
|
||||
"{GITEA_HOST}/api/v1/repos/{REPO_OWNER}/{REPO_NAME}/releases?limit=1"
|
||||
println!(
|
||||
"Checking for updates (current version: {current_version}, target: {BUILD_TARGET})..."
|
||||
);
|
||||
|
||||
let api_url = format!("{GITEA_HOST}/api/v1/repos/{REPO_OWNER}/{REPO_NAME}/releases?limit=1");
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let mut req = client
|
||||
.get(&api_url)
|
||||
@@ -178,10 +182,7 @@ pub async fn selfupdate(yes: bool) -> anyhow::Result<()> {
|
||||
|
||||
let download_resp = download_req.send().await?;
|
||||
if !download_resp.status().is_success() {
|
||||
bail!(
|
||||
"Failed to download asset: HTTP {}",
|
||||
download_resp.status()
|
||||
);
|
||||
bail!("Failed to download asset: HTTP {}", download_resp.status());
|
||||
}
|
||||
|
||||
let bytes = download_resp.bytes().await?;
|
||||
@@ -215,4 +216,3 @@ pub async fn selfupdate(yes: bool) -> anyhow::Result<()> {
|
||||
println!("Successfully updated to version {latest_version}.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+11
-6
@@ -2,17 +2,15 @@ use crate::cache::{Cache, CacheLaboratory};
|
||||
use crate::connection::MDRSConnection;
|
||||
use crate::models::file::File;
|
||||
use crate::models::folder::{FolderDetail, FolderSimple};
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
use anyhow::{anyhow, bail};
|
||||
use unicode_normalization::UnicodeNormalization;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Path helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Parse "remote:/labname/path/" into (remote, labname, folder_path).
|
||||
pub fn parse_remote_path(
|
||||
remote_path: &str,
|
||||
) -> Result<(String, String, String), anyhow::Error> {
|
||||
pub fn parse_remote_path(remote_path: &str) -> Result<(String, String, String), anyhow::Error> {
|
||||
let parts: Vec<&str> = remote_path.splitn(2, ':').collect();
|
||||
if parts.len() != 2 {
|
||||
bail!("remote_path must be in the form 'remote:/labname/path/'");
|
||||
@@ -75,13 +73,20 @@ pub async fn find_folder(
|
||||
bail!("Folder `{}` not found.", path);
|
||||
}
|
||||
if folders.len() != 1 {
|
||||
bail!("Ambiguous path `{}`: {} folders matched.", path, folders.len());
|
||||
bail!(
|
||||
"Ambiguous path `{}`: {} folders matched.",
|
||||
path,
|
||||
folders.len()
|
||||
);
|
||||
}
|
||||
let folder_simple = &folders[0];
|
||||
if folder_simple.lock {
|
||||
match password {
|
||||
None => {
|
||||
bail!("Folder `{}` is locked. Use -p/--password to provide a password.", path);
|
||||
bail!(
|
||||
"Folder `{}` is locked. Use -p/--password to provide a password.",
|
||||
path
|
||||
);
|
||||
}
|
||||
Some(pw) => conn.folder_auth(&folder_simple.id, pw).await?,
|
||||
}
|
||||
|
||||
+25
-13
@@ -1,14 +1,13 @@
|
||||
use crate::models::folder::FolderSimple;
|
||||
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
||||
use crate::commands::shared::{
|
||||
find_file_by_name, find_folder, find_lab_in_cache,
|
||||
nfc, parse_remote_path,
|
||||
find_file_by_name, find_folder, find_lab_in_cache, nfc, parse_remote_path,
|
||||
};
|
||||
use crate::models::folder::FolderSimple;
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::stream::{FuturesUnordered, StreamExt};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::fs;
|
||||
use anyhow::{anyhow, bail};
|
||||
|
||||
pub async fn upload(
|
||||
local_path: &str,
|
||||
@@ -40,7 +39,8 @@ pub async fn upload(
|
||||
}
|
||||
}
|
||||
}
|
||||
conn.upload_file(&dest_folder.id, &local.to_string_lossy()).await?;
|
||||
conn.upload_file(&dest_folder.id, &local.to_string_lossy())
|
||||
.await?;
|
||||
println!("{}{}", dest_folder.path, filename);
|
||||
} else if local.is_dir() {
|
||||
if !recursive {
|
||||
@@ -50,13 +50,18 @@ pub async fn upload(
|
||||
// remote_path. E.g. `upload ./mydir remote:/lab/path/` creates
|
||||
// `/lab/path/mydir/` on the remote and uploads into that folder.
|
||||
let local_basename = local.file_name().unwrap().to_string_lossy().to_string();
|
||||
let top_remote_id = find_or_create_folder(&conn, &dest_folder.id, &dest_folder.sub_folders, &local_basename).await?;
|
||||
let top_remote_id = find_or_create_folder(
|
||||
&conn,
|
||||
&dest_folder.id,
|
||||
&dest_folder.sub_folders,
|
||||
&local_basename,
|
||||
)
|
||||
.await?;
|
||||
let top_folder = conn.retrieve_folder(&top_remote_id).await?;
|
||||
println!("{}", top_folder.path.trim_end_matches('/'));
|
||||
|
||||
// Iterative depth-first walk: each entry is (local_dir, remote_folder_id)
|
||||
let mut stack: Vec<(PathBuf, String)> =
|
||||
vec![(local.to_path_buf(), top_remote_id)];
|
||||
let mut stack: Vec<(PathBuf, String)> = vec![(local.to_path_buf(), top_remote_id)];
|
||||
|
||||
while let Some((local_dir, remote_id)) = stack.pop() {
|
||||
let folder_detail = conn.retrieve_folder(&remote_id).await?;
|
||||
@@ -77,15 +82,16 @@ pub async fn upload(
|
||||
// Ensure each local sub-directory exists on the remote side
|
||||
for subdir in subdirs {
|
||||
let dirname = subdir.file_name().unwrap().to_string_lossy().to_string();
|
||||
let sub_remote_id = find_or_create_folder(&conn, &remote_id, &folder_detail.sub_folders, &dirname).await?;
|
||||
let sub_remote_id =
|
||||
find_or_create_folder(&conn, &remote_id, &folder_detail.sub_folders, &dirname)
|
||||
.await?;
|
||||
let sub_folder = conn.retrieve_folder(&sub_remote_id).await?;
|
||||
println!("{}", sub_folder.path.trim_end_matches('/'));
|
||||
stack.push((subdir, sub_remote_id));
|
||||
}
|
||||
|
||||
// Upload files in this directory (up to 10 concurrent)
|
||||
let mut futs: FuturesUnordered<tokio::task::JoinHandle<()>> =
|
||||
FuturesUnordered::new();
|
||||
let mut futs: FuturesUnordered<tokio::task::JoinHandle<()>> = FuturesUnordered::new();
|
||||
for file_path in files {
|
||||
let filename = file_path.file_name().unwrap().to_string_lossy().to_string();
|
||||
let file_path_str = file_path.to_string_lossy().to_string();
|
||||
@@ -111,7 +117,10 @@ pub async fn upload(
|
||||
// (connection pool) while supplying a fresh Bearer token.
|
||||
let task_conn = match load_cache_with_token_refresh(&remote).await {
|
||||
Ok(c) => conn.with_token(c.token.access),
|
||||
Err(e) => { eprintln!("Error: {}", e); return; }
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
match task_conn.upload_file(&folder_id, &file_path_str).await {
|
||||
Ok(_) => println!("{}{}", remote_path_prefix, fname),
|
||||
@@ -138,7 +147,10 @@ async fn find_or_create_folder(
|
||||
existing: &[FolderSimple],
|
||||
name: &str,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
if let Some(sf) = existing.iter().find(|f| nfc(&f.name).to_lowercase() == nfc(name).to_lowercase()) {
|
||||
if let Some(sf) = existing
|
||||
.iter()
|
||||
.find(|f| nfc(&f.name).to_lowercase() == nfc(name).to_lowercase())
|
||||
{
|
||||
return Ok(sf.id.clone());
|
||||
}
|
||||
let resp = conn.create_folder(parent_id, &nfc(name)).await?;
|
||||
|
||||
@@ -100,4 +100,3 @@ impl MDRSConnection {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-13
@@ -13,10 +13,7 @@ use cli::{Cli, Commands};
|
||||
use error::handle_error;
|
||||
|
||||
fn run(cli: Cli) {
|
||||
let build_rt = || {
|
||||
tokio::runtime::Runtime::new()
|
||||
.unwrap_or_else(|e| handle_error(e.into()))
|
||||
};
|
||||
let build_rt = || tokio::runtime::Runtime::new().unwrap_or_else(|e| handle_error(e.into()));
|
||||
|
||||
match cli.command {
|
||||
Commands::Config(subcmd) => {
|
||||
@@ -150,9 +147,10 @@ fn run(cli: Cli) {
|
||||
remote_path,
|
||||
password,
|
||||
} => {
|
||||
if let Err(e) = build_rt()
|
||||
.block_on(commands::metadata::metadata(&remote_path, password.as_deref()))
|
||||
{
|
||||
if let Err(e) = build_rt().block_on(commands::metadata::metadata(
|
||||
&remote_path,
|
||||
password.as_deref(),
|
||||
)) {
|
||||
handle_error(e);
|
||||
}
|
||||
}
|
||||
@@ -169,10 +167,11 @@ fn run(cli: Cli) {
|
||||
handle_error(e);
|
||||
}
|
||||
}
|
||||
Commands::Mv { src_path, dest_path } => {
|
||||
if let Err(e) =
|
||||
build_rt().block_on(commands::mv::mv(&src_path, &dest_path))
|
||||
{
|
||||
Commands::Mv {
|
||||
src_path,
|
||||
dest_path,
|
||||
} => {
|
||||
if let Err(e) = build_rt().block_on(commands::mv::mv(&src_path, &dest_path)) {
|
||||
handle_error(e);
|
||||
}
|
||||
}
|
||||
@@ -181,8 +180,7 @@ fn run(cli: Cli) {
|
||||
dest_path,
|
||||
recursive,
|
||||
} => {
|
||||
if let Err(e) =
|
||||
build_rt().block_on(commands::cp::cp(&src_path, &dest_path, recursive))
|
||||
if let Err(e) = build_rt().block_on(commands::cp::cp(&src_path, &dest_path, recursive))
|
||||
{
|
||||
handle_error(e);
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
// JWT utilities for token expiry checking (no signature verification required)
|
||||
|
||||
use anyhow::{anyhow, bail};
|
||||
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use anyhow::{anyhow, bail};
|
||||
|
||||
fn now_secs() -> i64 {
|
||||
SystemTime::now()
|
||||
|
||||
Reference in New Issue
Block a user