use crate::prelude::*;
use serde_json::json;
use std::{
fs,
io::{prelude::*, BufRead, BufReader},
path::Path,
};
use tinytemplate::{format_unescaped, TinyTemplate};
pub struct Task {
is_template: bool,
local_path: String,
remote_path: String,
file_mode: i32,
}
impl Task {
pub fn new_template(local_path: String, remote_path: String, file_mode: i32) -> Self {
Self {
is_template: true,
local_path,
remote_path,
file_mode,
}
}
pub fn new_file(local_path: String, remote_path: String, file_mode: i32) -> Self {
Self {
is_template: false,
local_path,
remote_path,
file_mode,
}
}
}
pub enum TaskContext {
Template { content: String, file_size: u64 },
File { file_size: u64 },
}
impl GenericTask<TaskContext> for Task {
fn prepare(&self, host: Host) -> Result<TaskContext> {
let local_path = Path::new(self.local_path.as_str());
if !local_path.exists() {
return Err(Box::new(Error::FileNotFound(format!(
"No such file: {}",
self.local_path
))));
} else if local_path.is_dir() {
return Err(Box::new(Error::IsADirectory(format!(
"Path is a directory, not a file: {}",
self.local_path
))));
}
if self.is_template {
let template = fs::read_to_string(self.local_path.clone())?;
let mut tt = TinyTemplate::new();
tt.set_default_formatter(&format_unescaped);
tt.add_template("file", template.as_str())?;
let ctx = json!({ "host": host });
let content = tt.render("file", &ctx)?;
let file_size = u64::try_from(content.len())?;
Ok(TaskContext::Template { content, file_size })
} else {
let file_size = local_path.metadata()?.len();
Ok(TaskContext::File { file_size })
}
}
fn apply(&self, host: Host, context: TaskContext) -> TaskResult {
let file_size = match context {
TaskContext::Template {
file_size: size, ..
} => size,
TaskContext::File { file_size: size } => size,
};
let sess = host.get_session()?;
let mut channel = sess.scp_send(
Path::new(&self.remote_path),
self.file_mode,
file_size,
None,
)?;
match context {
TaskContext::Template { content, .. } => {
channel.write(content.as_bytes())?;
}
TaskContext::File { .. } => {
let file = fs::File::open(&self.local_path)?;
let block_size = 4 * 1024 * 1024; let mut reader = BufReader::with_capacity(block_size, file);
loop {
let buffer = reader.fill_buf()?;
let length = buffer.len();
if length > 0 {
channel.write(buffer)?;
} else {
break;
}
reader.consume(length);
}
}
}
channel.send_eof()?;
channel.wait_eof()?;
channel.close()?;
channel.wait_close()?;
Ok(json!({ "file_size": file_size }))
}
}