use derive_more::{Display, IsVariant};
use serde::{Deserialize, Serialize};
use std::{io, path::PathBuf};
use strum::AsRefStr;
pub type ByteVec = Vec<u8>;
#[cfg(feature = "structopt")]
fn parse_byte_vec(src: &str) -> ByteVec {
src.as_bytes().to_vec()
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct Request {
pub tenant: String,
pub id: usize,
pub payload: Vec<RequestData>,
}
impl Request {
pub fn new(tenant: impl Into<String>, payload: Vec<RequestData>) -> Self {
let id = rand::random();
Self {
tenant: tenant.into(),
id,
payload,
}
}
pub fn to_payload_type_string(&self) -> String {
self.payload
.iter()
.map(AsRef::as_ref)
.collect::<Vec<&str>>()
.join(",")
}
}
#[derive(Clone, Debug, PartialEq, Eq, AsRefStr, IsVariant, Serialize, Deserialize)]
#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))]
#[serde(
rename_all = "snake_case",
deny_unknown_fields,
tag = "type",
content = "data"
)]
#[strum(serialize_all = "snake_case")]
pub enum RequestData {
#[cfg_attr(feature = "structopt", structopt(visible_aliases = &["cat"]))]
FileRead {
path: PathBuf,
},
FileReadText {
path: PathBuf,
},
FileWrite {
path: PathBuf,
#[cfg_attr(feature = "structopt", structopt(parse(from_str = parse_byte_vec)))]
data: ByteVec,
},
FileWriteText {
path: PathBuf,
text: String,
},
FileAppend {
path: PathBuf,
#[cfg_attr(feature = "structopt", structopt(parse(from_str = parse_byte_vec)))]
data: ByteVec,
},
FileAppendText {
path: PathBuf,
text: String,
},
#[cfg_attr(feature = "structopt", structopt(visible_aliases = &["ls"]))]
DirRead {
path: PathBuf,
#[serde(default = "one")]
#[cfg_attr(feature = "structopt", structopt(short, long, default_value = "1"))]
depth: usize,
#[cfg_attr(feature = "structopt", structopt(short, long))]
absolute: bool,
#[cfg_attr(feature = "structopt", structopt(short, long))]
canonicalize: bool,
#[cfg_attr(feature = "structopt", structopt(long))]
include_root: bool,
},
#[cfg_attr(feature = "structopt", structopt(visible_aliases = &["mkdir"]))]
DirCreate {
path: PathBuf,
#[cfg_attr(feature = "structopt", structopt(short, long))]
all: bool,
},
#[cfg_attr(feature = "structopt", structopt(visible_aliases = &["rm"]))]
Remove {
path: PathBuf,
#[cfg_attr(feature = "structopt", structopt(short, long))]
force: bool,
},
#[cfg_attr(feature = "structopt", structopt(visible_aliases = &["cp"]))]
Copy {
src: PathBuf,
dst: PathBuf,
},
#[cfg_attr(feature = "structopt", structopt(visible_aliases = &["mv"]))]
Rename {
src: PathBuf,
dst: PathBuf,
},
Exists {
path: PathBuf,
},
Metadata {
path: PathBuf,
#[cfg_attr(feature = "structopt", structopt(short, long))]
canonicalize: bool,
#[cfg_attr(feature = "structopt", structopt(long))]
resolve_file_type: bool,
},
#[cfg_attr(feature = "structopt", structopt(visible_aliases = &["run"]))]
ProcRun {
cmd: String,
args: Vec<String>,
},
#[cfg_attr(feature = "structopt", structopt(visible_aliases = &["kill"]))]
ProcKill {
id: usize,
},
ProcStdin {
id: usize,
data: String,
},
ProcList {},
SystemInfo {},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct Response {
pub tenant: String,
pub id: usize,
pub origin_id: Option<usize>,
pub payload: Vec<ResponseData>,
}
impl Response {
pub fn new(
tenant: impl Into<String>,
origin_id: Option<usize>,
payload: Vec<ResponseData>,
) -> Self {
let id = rand::random();
Self {
tenant: tenant.into(),
id,
origin_id,
payload,
}
}
pub fn to_payload_type_string(&self) -> String {
self.payload
.iter()
.map(AsRef::as_ref)
.collect::<Vec<&str>>()
.join(",")
}
}
#[derive(Clone, Debug, PartialEq, Eq, AsRefStr, IsVariant, Serialize, Deserialize)]
#[serde(
rename_all = "snake_case",
deny_unknown_fields,
tag = "type",
content = "data"
)]
#[strum(serialize_all = "snake_case")]
pub enum ResponseData {
Ok,
Error(Error),
Blob {
data: Vec<u8>,
},
Text {
data: String,
},
DirEntries {
entries: Vec<DirEntry>,
errors: Vec<Error>,
},
Exists(bool),
Metadata {
canonicalized_path: Option<PathBuf>,
file_type: FileType,
len: u64,
readonly: bool,
accessed: Option<u128>,
created: Option<u128>,
modified: Option<u128>,
},
ProcStart {
id: usize,
},
ProcStdout {
id: usize,
data: String,
},
ProcStderr {
id: usize,
data: String,
},
ProcDone {
id: usize,
success: bool,
code: Option<i32>,
},
ProcEntries {
entries: Vec<RunningProcess>,
},
SystemInfo {
family: String,
os: String,
arch: String,
current_dir: PathBuf,
main_separator: char,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct DirEntry {
pub path: PathBuf,
pub file_type: FileType,
pub depth: usize,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, AsRefStr, IsVariant, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
#[strum(serialize_all = "snake_case")]
pub enum FileType {
Dir,
File,
Symlink,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct RunningProcess {
pub cmd: String,
pub args: Vec<String>,
pub id: usize,
}
impl From<io::Error> for ResponseData {
fn from(x: io::Error) -> Self {
Self::Error(Error::from(x))
}
}
impl From<walkdir::Error> for ResponseData {
fn from(x: walkdir::Error) -> Self {
Self::Error(Error::from(x))
}
}
impl From<tokio::task::JoinError> for ResponseData {
fn from(x: tokio::task::JoinError) -> Self {
Self::Error(Error::from(x))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct Error {
pub kind: ErrorKind,
pub description: String,
}
impl From<io::Error> for Error {
fn from(x: io::Error) -> Self {
Self {
kind: ErrorKind::from(x.kind()),
description: format!("{}", x),
}
}
}
impl From<walkdir::Error> for Error {
fn from(x: walkdir::Error) -> Self {
if x.io_error().is_some() {
x.into_io_error().map(Self::from).unwrap()
} else {
Self {
kind: ErrorKind::Loop,
description: format!("{}", x),
}
}
}
}
impl From<tokio::task::JoinError> for Error {
fn from(x: tokio::task::JoinError) -> Self {
Self {
kind: if x.is_cancelled() {
ErrorKind::TaskCancelled
} else if x.is_panic() {
ErrorKind::TaskPanicked
} else {
ErrorKind::Unknown
},
description: format!("{}", x),
}
}
}
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum ErrorKind {
NotFound,
PermissionDenied,
ConnectionRefused,
ConnectionReset,
ConnectionAborted,
NotConnected,
AddrInUse,
AddrNotAvailable,
BrokenPipe,
AlreadyExists,
WouldBlock,
InvalidInput,
InvalidData,
TimedOut,
WriteZero,
Interrupted,
Other,
UnexpectedEof,
Unsupported,
Loop,
TaskCancelled,
TaskPanicked,
Unknown,
}
impl From<io::ErrorKind> for ErrorKind {
fn from(kind: io::ErrorKind) -> Self {
match kind {
io::ErrorKind::NotFound => Self::NotFound,
io::ErrorKind::PermissionDenied => Self::PermissionDenied,
io::ErrorKind::ConnectionRefused => Self::ConnectionRefused,
io::ErrorKind::ConnectionReset => Self::ConnectionReset,
io::ErrorKind::ConnectionAborted => Self::ConnectionAborted,
io::ErrorKind::NotConnected => Self::NotConnected,
io::ErrorKind::AddrInUse => Self::AddrInUse,
io::ErrorKind::AddrNotAvailable => Self::AddrNotAvailable,
io::ErrorKind::BrokenPipe => Self::BrokenPipe,
io::ErrorKind::AlreadyExists => Self::AlreadyExists,
io::ErrorKind::WouldBlock => Self::WouldBlock,
io::ErrorKind::InvalidInput => Self::InvalidInput,
io::ErrorKind::InvalidData => Self::InvalidData,
io::ErrorKind::TimedOut => Self::TimedOut,
io::ErrorKind::WriteZero => Self::WriteZero,
io::ErrorKind::Interrupted => Self::Interrupted,
io::ErrorKind::Other => Self::Other,
io::ErrorKind::UnexpectedEof => Self::UnexpectedEof,
io::ErrorKind::Unsupported => Self::Unsupported,
_ => Self::Unknown,
}
}
}
const fn one() -> usize {
1
}