#[cfg(windows)]
use std::path::PathBuf;
use anyhow::{bail, Context, Result};
use chrono::Datelike;
use super::types::{CreateDirectory, File, Files, MoveRequest};
use crate::commands::ignite::types::Deployment;
use crate::state::http::HttpClient;
use crate::state::State;
pub async fn get_files_for_path(
http: &HttpClient,
deployment: &str,
volume: &str,
path: &str,
) -> Result<Files> {
let path = path_into_uri_safe(path);
let files = http
.request::<Files>(
"GET",
&format!("/ignite/deployments/{deployment}/volumes/{volume}/files/{path}"),
None,
)
.await?
.context("Failed to get files for path")?;
Ok(files)
}
pub async fn delete_files_for_path(
http: &HttpClient,
deployment: &str,
volume: &str,
path: &str,
) -> Result<()> {
let path = path_into_uri_safe(path);
http.request::<()>(
"DELETE",
&format!("/ignite/deployments/{deployment}/volumes/{volume}/files/{path}"),
None,
)
.await?;
Ok(())
}
pub fn path_into_uri_safe(path: &str) -> String {
path.replace('/', "%2F")
}
fn permission_to_string(permission: u64) -> Result<String> {
let permission = u32::from_str_radix(&permission.to_string(), 8)?;
let mut perms = String::new();
match permission & 0o170000 {
0o140000 => perms.push('s'),
0o120000 => perms.push('l'),
0o100000 => perms.push('-'),
0o060000 => perms.push('b'),
0o040000 => perms.push('d'),
0o020000 => perms.push('c'),
0o010000 => perms.push('p'),
_ => bail!("Unknown file type"),
}
for i in 0..3 {
let shifted = permission >> (6 - (i * 3));
if shifted & 0o4 != 0 {
perms.push('r');
} else {
perms.push('-');
}
if shifted & 0o2 != 0 {
perms.push('w');
} else {
perms.push('-');
}
if shifted & 0o1 != 0 {
perms.push('x');
} else {
perms.push('-');
}
}
Ok(perms)
}
pub fn format_file(file: &File) -> Result<String> {
let date =
chrono::DateTime::parse_from_rfc3339(&file.updated_at).context("Failed to parse date")?;
let date = if date.year() == chrono::Local::now().year() {
date.format("%b %d %H:%M")
} else {
date.format("%b %d %Y")
};
let res = format!(
"{}\t{}\t{}\t{}",
permission_to_string(file.permissions)?,
file.size,
date,
file.name
);
Ok(res)
}
fn get_volume_from_deployment(deployment: &str) -> Result<String> {
let tail = deployment
.split('_')
.nth(1)
.context("Failed to get volume from deployment")?;
Ok(format!("volume_{tail}"))
}
pub async fn parse_target_from_path_like(
state: &State,
path_like: &str,
) -> Result<(Option<(Deployment, String)>, String)> {
let parts: Vec<&str> = path_like.split(':').collect();
#[cfg(windows)]
if PathBuf::from(path_like).is_absolute() {
return Ok((None, path_like.to_string()));
}
if parts.len() > 2 {
bail!("Invalid source or target: {path_like}");
}
if parts.len() == 1 {
return Ok((None, parts[0].to_string()));
}
let (deployment, path) = (parts[0], parts[1]);
let deployment = state.get_deployment_by_name_or_id(deployment).await?;
if !deployment.is_stateful() {
bail!("Deployment {} is not stateful", deployment.id);
}
let volume = get_volume_from_deployment(&deployment.id)?;
Ok((Some((deployment, volume)), path.to_string()))
}
pub async fn move_file(
http: &HttpClient,
deployment: &str,
volume: &str,
source: &str,
target: &str,
) -> Result<()> {
http.request::<()>(
"PATCH",
&format!("/ignite/deployments/{deployment}/volumes/{volume}/path"),
Some((
serde_json::to_vec(&MoveRequest {
source: source.to_owned(),
target: target.to_owned(),
})?
.into(),
"application/json",
)),
)
.await?;
Ok(())
}
pub async fn create_directory(
http: &HttpClient,
deployment: &str,
volume: &str,
path: &str,
recursive: bool,
) -> Result<()> {
http.request::<()>(
"POST",
&format!("/ignite/deployments/{deployment}/volumes/{volume}/folder"),
Some((
serde_json::to_vec(&CreateDirectory {
path: path.to_owned(),
recursive,
})?
.into(),
"application/json",
)),
)
.await?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_path_into_uri_safe() {
assert_eq!(path_into_uri_safe("/"), "%2F");
assert_eq!(path_into_uri_safe("/home"), "%2Fhome");
assert_eq!(path_into_uri_safe("/home/"), "%2Fhome%2F");
assert_eq!(path_into_uri_safe("/home/user"), "%2Fhome%2Fuser");
assert_eq!(path_into_uri_safe("/home/user/"), "%2Fhome%2Fuser%2F");
assert_eq!(
path_into_uri_safe("/home/user/file"),
"%2Fhome%2Fuser%2Ffile"
);
}
#[test]
fn test_permission_to_string() {
assert_eq!(permission_to_string(40755).unwrap(), "drwxr-xr-x");
assert_eq!(permission_to_string(100644).unwrap(), "-rw-r--r--");
assert_eq!(permission_to_string(100777).unwrap(), "-rwxrwxrwx");
}
#[tokio::test]
async fn test_parse_target_from_path_like() {
let state = State::new(Default::default()).await.unwrap();
let (deployment, path) = parse_target_from_path_like(&state, "/").await.unwrap();
assert_eq!(deployment, None);
assert_eq!(path, "/");
let (deployment, path) = parse_target_from_path_like(&state, "/home").await.unwrap();
assert_eq!(deployment, None);
assert_eq!(path, "/home");
#[cfg(windows)]
{
let (deployment, path) = parse_target_from_path_like(&state, "C:\\").await.unwrap();
assert_eq!(deployment, None);
assert_eq!(path, "C:\\");
let (deployment, path) = parse_target_from_path_like(&state, "C:\\home")
.await
.unwrap();
assert_eq!(deployment, None);
assert_eq!(path, "C:\\user");
}
}
}