use crate::client::Client;
use crate::error::{Error, Result};
use crate::events::lock;
use crate::frame::MAX_META_BYTES;
use crate::wire::*;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Mutex;
pub(crate) const MAX_BATCH_ITEMS: usize = 4096;
pub type StatResult = Result<FileInfo>;
pub(crate) fn item<T>(r: Result<T>) -> Result<Result<T>> {
match r {
Err(e @ Error::Server { .. }) => Ok(Err(e)),
other => other.map(Ok),
}
}
pub(crate) fn batch_errors(n: usize, errors: Vec<BatchItemError>) -> Result<Vec<Option<Error>>> {
let mut out: Vec<Option<Error>> = (0..n).map(|_| None).collect();
for e in errors {
let slot = out
.get_mut(e.index as usize)
.ok_or_else(|| Error::Protocol(format!("batch error index {} out of {n}", e.index)))?;
*slot = Some(Error::Server {
code: e.code,
message: e.message,
});
}
Ok(out)
}
pub(crate) fn par_map<T: Send>(n: usize, workers: usize, job: impl Fn(usize) -> Result<T> + Sync) -> Result<Vec<T>> {
if n <= 1 || workers <= 1 {
return (0..n).map(job).collect();
}
let next = AtomicUsize::new(0);
let failed = AtomicBool::new(false);
let slots: Mutex<Vec<Option<Result<T>>>> = Mutex::new((0..n).map(|_| None).collect());
std::thread::scope(|sc| {
for _ in 0..workers.min(n) {
sc.spawn(|| loop {
let i = next.fetch_add(1, Ordering::SeqCst);
if i >= n || failed.load(Ordering::SeqCst) {
break;
}
let r = job(i);
if r.is_err() {
failed.store(true, Ordering::SeqCst);
}
lock(&slots)[i] = Some(r);
});
}
});
let mut out = Vec::with_capacity(n);
for r in slots
.into_inner()
.unwrap_or_else(|e| e.into_inner())
.into_iter()
.flatten()
{
out.push(r?);
}
Ok(out)
}
impl Client {
pub fn stat_batch(&self, paths: &[String]) -> Result<Vec<StatResult>> {
let meta_budget = MAX_META_BYTES as usize / 2;
let mut parts = Vec::new();
let mut start = 0;
while start < paths.len() {
let (mut end, mut size) = (start, 0usize);
while end < paths.len()
&& end - start < MAX_BATCH_ITEMS
&& (end == start || size + paths[end].len() + 8 <= meta_budget)
{
size += paths[end].len() + 8;
end += 1;
}
parts.push(&paths[start..end]);
start = end;
}
let chunks = par_map(parts.len(), self.pool_size(), |i| {
let part = parts[i];
let r: StatBatchResponse = self.unary(method::STAT_BATCH, &StatBatchRequest { paths: part.to_vec() })?;
if r.infos.len() != part.len() {
return Err(Error::Protocol(format!(
"stat batch returned {} infos for {} paths",
r.infos.len(),
part.len()
)));
}
let mut errs = batch_errors(part.len(), r.errors)?;
Ok(
r.infos
.into_iter()
.enumerate()
.map(|(i, info)| match errs[i].take() {
Some(e) => Err(e),
None => Ok(info),
})
.collect::<Vec<_>>(),
)
})?;
Ok(chunks.into_iter().flatten().collect())
}
pub fn read_file(&self, path: &str) -> Result<Vec<u8>> {
let (data, limit) = self
.run_any(|c| {
let req = ReadFileRequest {
path: path.into(),
max_bytes: c.max_payload() as u64,
};
let (r, data) = c.call_msg::<ReadFileResponse>(method::READ_FILE, &req, &[])?;
if r.count as usize != data.len() {
return Err(Error::Protocol(format!(
"read file count {} payload {}",
r.count,
data.len()
)));
}
Ok((data, c.max_payload()))
})?
.0;
if data.len() < limit {
return Ok(data);
}
let f = self.open(path, open_flag::READ_ONLY, 0)?;
let mut out = Vec::new();
loop {
let part = f.read_at(out.len() as u64, limit)?;
let short = part.len() < limit;
out.extend_from_slice(&part);
if short {
return Ok(out);
}
}
}
pub fn write_file(&self, path: &str, data: &[u8], truncate: bool) -> Result<()> {
let sent = self
.run_any(|c| {
if data.len() > c.max_payload() {
return Ok(false);
}
let req = WriteFileRequest {
path: path.into(),
truncate,
};
let (r, _) = c.call_msg::<WriteFileResponse>(method::WRITE_FILE, &req, data)?;
if r.count as usize != data.len() {
return Err(Error::Protocol(format!(
"write file count {} for {} bytes",
r.count,
data.len()
)));
}
Ok(true)
})?
.0;
if sent {
return Ok(());
}
let mut flags = open_flag::WRITE_ONLY | open_flag::CREATE;
if truncate {
flags |= open_flag::TRUNC;
}
let f = self.open(path, flags, 0o644)?;
f.write_at(0, data)?;
f.close()
}
}