use std::path::{Component, PathBuf};
use anyhow::{anyhow, Context as _};
use reedline_repl_rs::clap::ArgMatches;
use s_macro::s;
pub use wildland_cargo_lib::api::CargoLib;
use wildland_corex::dfs::interface::node_stat::NodeType;
use wildland_corex::dfs::interface::{
AbortFlag,
DfsFrontendError,
DummyIStream,
DummyProgressReporter,
};
use crate::plugins::dfs::stream::{DevShellIStream, DevShellOStream, DevShellProgressReporter};
use crate::sigint::Ctrlc;
use crate::Context;
#[tracing::instrument(level = "trace", ret, err(Debug))]
pub fn aux_resolve_path(path: &String, cwd: PathBuf) -> anyhow::Result<String> {
tracing::trace!("working with {}", path);
let path = PathBuf::from(path);
let mut components = path.components().peekable();
let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
components.next();
PathBuf::from(c.as_os_str())
} else {
PathBuf::new()
};
if path.is_relative() {
ret = cwd.join(ret);
}
for component in components {
match component {
Component::Prefix(..) => unreachable!(),
Component::RootDir => {
ret.push(component.as_os_str());
}
Component::CurDir => {}
Component::ParentDir => {
ret.pop();
}
Component::Normal(c) => {
ret.push(c);
}
}
}
Ok(ret.to_str().context("Failed to serialize path")?.to_owned())
}
#[tracing::instrument(level = "trace", err(Debug), ret, skip(ctx))]
pub fn aux_dfs_get_path_type(ctx: &mut Context, path: &String) -> anyhow::Result<NodeType> {
let path = aux_resolve_path(path, ctx.current_path.clone())?;
let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
let node = dfsapi
.metadata(path.clone())
.context(s!("path does not exist: {}", path))?;
Ok(node.node_type)
}
#[tracing::instrument(level = "trace", ret, err(Debug))]
pub fn aux_size_mod(size: usize) -> anyhow::Result<String> {
let mut size = size;
let mut modifier = " b";
if size > 1024 {
size /= 1024;
modifier = " Kib";
}
if size > 1024 {
size /= 1024;
modifier = " Mib";
}
if size > 1024 {
size /= 1024;
modifier = " Gib";
}
if size > 1024 {
size /= 1024;
modifier = " Tib";
}
Ok(format!("{size}{modifier}"))
}
#[tracing::instrument(level = "trace", ret, err(Debug))]
pub fn aux_resolve_local(path: &String, cwd: PathBuf) -> anyhow::Result<String> {
let homedir = std::env::var("HOME").context("can not find HOME environment variable")?;
let cwd = PathBuf::from("/");
if path.matches('~').count() > 1 {
return Err(anyhow!("`~` can only be used only once"));
}
if path.starts_with("~/") {
let mut path = path.clone();
path.replace_range(0..1, &homedir);
aux_resolve_path(&path, cwd)
} else if path.contains('~') {
Err(anyhow!("`~` can only be used at the beginning of the path"))
} else {
aux_resolve_path(path, cwd)
}
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_touch(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let path = args
.get_one::<String>("path")
.context("path not provided")?;
let path = aux_resolve_path(path, ctx.current_path.clone())?;
let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
dfsapi
.upload(
path.clone(),
DummyIStream::boxed(vec![]),
Box::new(DummyProgressReporter {}),
&AbortFlag::new(),
None,
)
.context("Failed to create file")?;
Ok(Some(s!("Ok: File Created: {}", path)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_mkdir(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let path = args
.get_one::<String>("path")
.context("path not provided")?;
let path = aux_resolve_path(path, ctx.current_path.clone())?;
tracing::debug!("creating dir with path {path}");
let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
dfsapi.create_dir(path).context("Failed to create file")?;
Ok(Some(s!("Ok: Directory Created")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_rmdir(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let path = args
.get_one::<String>("path")
.context("path not provided")?;
let is_recursive = args
.get_one::<bool>("recursive")
.unwrap_or(&false)
.to_owned();
let path = aux_resolve_path(path, ctx.current_path.clone())?;
let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
dfsapi
.remove_dir(path, is_recursive)
.context("Failed to remove directory")?;
Ok(Some(s!("Ok: Directory Removed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_rm(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let path = args
.get_one::<String>("path")
.context("path not provided")?;
let path = aux_resolve_path(path, ctx.current_path.clone())?;
let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
dfsapi.remove_file(path)?;
Ok(Some(s!("Ok: File Removed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_stat(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let path = args
.get_one::<String>("path")
.context("path not provided")?;
let path = aux_resolve_path(path, ctx.current_path.clone())?;
let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
let meta = dfsapi.metadata(path.clone())?;
println!(
"file:\t{},\nuuid:\t{}\nsize\t{}\n",
path, meta.wildland_object_id, meta.size
);
Ok(Some(s!("Ok: File statistics printed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_cd(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let path = args
.get_one::<String>("path")
.context("path not provided")?;
let path = aux_resolve_path(path, ctx.current_path.clone())?;
if aux_dfs_get_path_type(ctx, &path)? != NodeType::Dir {
return Err(anyhow::anyhow!("path is not a directory"));
}
ctx.current_path = std::path::PathBuf::from(path);
Ok(Some(s!("")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_ls(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let path = match args.get_one::<String>("path") {
Some(p) => p.to_owned(),
None => ctx.current_path.clone().to_string_lossy().into(),
};
let path = aux_resolve_path(&path, ctx.current_path.clone())?;
let dfs_api = ctx.get_dfs().context("can't get dfs api")?;
let entries = dfs_api.read_dir(path).context("Failed to read directory")?;
for entry in entries {
let meta = entry.stat;
let size = aux_size_mod(meta.size).unwrap_or(s!("???"));
let modifier = match meta.node_type {
NodeType::File => "-",
NodeType::Dir => "d",
NodeType::Other => "?",
NodeType::Symlink => "l",
};
let perms = match meta.permissions.is_readonly() {
true => "r-",
false => "rw",
};
let name = PathBuf::from(entry.item_name)
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned();
println!(" {modifier} {perms} \t{size: <9}\t{name}");
}
Ok(Some(s!("Ok: Directory listed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_get_path(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let uuid = args
.get_one::<String>("uuid")
.context("uuid not provided")?;
let dfs_api = ctx.get_dfs().context("can't get dfs api")?;
let entries = dfs_api.get_path(uuid.to_owned())?;
println!("entries: {:?}", entries);
Ok(Some(s!("Ok: uuid processed, paths printed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(_args, ctx))]
pub(crate) fn h_pwd(_args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
Ok(Some(s!(ctx
.current_path
.clone()
.into_os_string()
.into_string()
.map_err(|_| anyhow!(
"could not transform path to string"
))?)))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_rename(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let lpath = args
.get_one::<String>("source_path")
.context("source path not provided")?;
let lpath = aux_resolve_path(lpath, ctx.current_path.clone())?;
let rpath = args
.get_one::<String>("target_path")
.context("target path not provided")?;
let rpath = aux_resolve_path(rpath, ctx.current_path.clone())?;
let dfsapi = ctx.get_dfs().context("can't get dfs api")?;
dfsapi
.rename(lpath, rpath)
.context("Failed to rename directory")?;
Ok(Some(s!("Ok: Directory renamed")))
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_get(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let source_path = args
.get_one::<String>("source_path")
.context("path not provided")?;
let source_path = aux_resolve_path(source_path, ctx.current_path.clone())?;
let target_path = args
.get_one::<String>("target_path")
.context("remote path not provided")?;
let dfs_api = ctx.get_dfs().context("can't get dfs api")?;
let ostream = Box::new(DevShellOStream::try_new(target_path)?);
let abort_flag = AbortFlag::new();
let _ctrlc = Ctrlc::new({
let flag = abort_flag.clone();
move || flag.set()
});
match dfs_api.download(
source_path.clone(),
ostream,
Box::new(DevShellProgressReporter {}),
&abort_flag,
) {
Ok(_) => Ok(Some(format!(
"File {source_path} downloaded to {target_path}"
))),
Err(DfsFrontendError::Aborted) => Ok(Some("Downloading canceled".into())),
Err(e) => Err(e.into()),
}
}
#[tracing::instrument(level = "trace", err(Debug), skip(args, ctx))]
pub(crate) fn h_put(args: ArgMatches, ctx: &mut Context) -> anyhow::Result<Option<String>> {
let source_path = args
.get_one::<String>("source_path")
.context("local path not provided")?;
let target_path = args
.get_one::<String>("target_path")
.context("remote path not provided")?;
let target_path = aux_resolve_path(target_path, ctx.current_path.clone())?;
if !PathBuf::from(source_path.clone()).exists() {
return Err(anyhow::anyhow!("local path does not exist: {source_path}"));
}
let dfs_api = ctx.get_dfs().context("can't get dfs api")?;
let istream = Box::new(DevShellIStream::try_new(source_path)?);
let abort_flag = AbortFlag::new();
let _ctrlc = Ctrlc::new({
let flag = abort_flag.clone();
move || flag.set()
});
match dfs_api.upload(
target_path.clone(),
istream,
Box::new(DevShellProgressReporter {}),
&abort_flag,
None,
) {
Ok(_) => Ok(Some(format!(
"File {source_path} uploaded as {target_path}"
))),
Err(DfsFrontendError::Aborted) => Ok(Some("Uploading canceled".into())),
Err(e) => Err(e.into()),
}
}