New ftp_get and ftp_get_in_memory commands

This commit is contained in:
sagie gur ari 2020-06-28 18:14:46 +00:00
parent 7142e94c4a
commit cb3b2d2cbf
11 changed files with 396 additions and 4 deletions

View file

@ -2,6 +2,8 @@
### v0.5.1
* New ftp_get command.
* New ftp_get_in_memory command.
* New ftp_list command.
* New ftp_nlst command.

View file

@ -111,6 +111,8 @@
* [std::net::Hostname (hostname)](#std__net__Hostname)
* [std::net::HttpClient (http_client)](#std__net__HttpClient)
* [std::net::WGet (wget)](#std__net__WGet)
* [std::net::ftp::Get (ftp_get)](#std__net__ftp__Get)
* [std::net::ftp::GetInMemory (ftp_get_in_memory)](#std__net__ftp__GetInMemory)
* [std::net::ftp::List (ftp_list)](#std__net__ftp__List)
* [std::net::ftp::NLst (ftp_nlst)](#std__net__ftp__NLst)
* [std::process::Execute (exec)](#std__process__Execute)
@ -4143,6 +4145,72 @@ http_client --method "${scope::wget::method}" --output-file "${scope::wget::file
#### Aliases:
wget
<a name="std__net__ftp__Get"></a>
## std::net::ftp::Get
```sh
result = ftp_get --host <hostname> [--port 21] [--username <user name>] [--password <password>] [--path <path>] [--type <A/I>] --remote-file <file name> --local-file <file name>
```
Invokes the FTP GET command from the given connection and file details.
#### Parameters
* --host - The host name or IP to connect to
* --port - Optional port number to use (by default 21)
* --username - Optional user name used to login (if not user or password provided, no login operation will be invoked)
* --password - Optional password used to login (if not user or password provided, no login operation will be invoked)
* --path - Optional path on the remote server to invoke operation on
* --type - Optional setting of the transfer type as A (ascii) I (image, binary)
* --remote-file - The remote file to download
* --local-file - The target local file name
#### Return Value
true if operation was completed.
#### Examples
```sh
ftp_get --host myhost --username someuser --password 12345 --remote-file README.md --local-file README.md
```
#### Aliases:
ftp_get
<a name="std__net__ftp__GetInMemory"></a>
## std::net::ftp::GetInMemory
```sh
handle = ftp_get_in_memory --host <hostname> [--port 21] [--username <user name>] [--password <password>] [--path <path>] [--type <A/I>] --remote-file <file name>
```
Invokes the FTP GET command from the given connection and file details.
#### Parameters
* --host - The host name or IP to connect to
* --port - Optional port number to use (by default 21)
* --username - Optional user name used to login (if not user or password provided, no login operation will be invoked)
* --password - Optional password used to login (if not user or password provided, no login operation will be invoked)
* --path - Optional path on the remote server to invoke operation on
* --type - Optional setting of the transfer type as A (ascii) I (image, binary)
* --remote-file - The remote file to download
#### Return Value
The binary data handle.
#### Examples
```sh
handle = ftp_get_in_memory --host myhost --username someuser --password 12345 --remote-file README.md
text = bytes_to_string ${handle}
```
#### Aliases:
ftp_get_in_memory
<a name="std__net__ftp__List"></a>
## std::net::ftp::List
```sh

View file

@ -0,0 +1,26 @@
```sh
result = ftp_get --host <hostname> [--port 21] [--username <user name>] [--password <password>] [--path <path>] [--type <A/I>] --remote-file <file name> --local-file <file name>
```
Invokes the FTP GET command from the given connection and file details.
#### Parameters
* --host - The host name or IP to connect to
* --port - Optional port number to use (by default 21)
* --username - Optional user name used to login (if not user or password provided, no login operation will be invoked)
* --password - Optional password used to login (if not user or password provided, no login operation will be invoked)
* --path - Optional path on the remote server to invoke operation on
* --type - Optional setting of the transfer type as A (ascii) I (image, binary)
* --remote-file - The remote file to download
* --local-file - The target local file name
#### Return Value
true if operation was completed.
#### Examples
```sh
ftp_get --host myhost --username someuser --password 12345 --remote-file README.md --local-file README.md
```

View file

@ -0,0 +1,97 @@
use crate::sdk::std::net::ftp::{validate_and_run_with_connection, Options};
use crate::utils::io::create_empty_file;
use crate::utils::pckg;
use duckscript::types::command::{Command, CommandResult};
use ftp::{FtpError, FtpStream};
use std::fs::OpenOptions;
use std::io::{BufWriter, Error, Read, Write};
#[cfg(test)]
#[path = "./mod_test.rs"]
mod mod_test;
fn write_file(reader: &mut dyn Read, target_file: &str) -> Result<(), Error> {
let mut file = OpenOptions::new().append(true).open(target_file)?;
{
let mut writer = BufWriter::new(&mut file);
let mut buffer = [0; 10240];
loop {
let read_size = reader.read(&mut buffer)?;
if read_size > 0 {
writer.write(&buffer[0..read_size])?;
} else {
break;
}
}
writer.flush()?;
}
file.sync_all()?;
Ok(())
}
#[derive(Clone)]
pub(crate) struct CommandImpl {
package: String,
}
impl Command for CommandImpl {
fn name(&self) -> String {
pckg::concat(&self.package, "Get")
}
fn aliases(&self) -> Vec<String> {
vec!["ftp_get".to_string()]
}
fn help(&self) -> String {
include_str!("help.md").to_string()
}
fn clone_and_box(&self) -> Box<dyn Command> {
Box::new((*self).clone())
}
fn run(&self, arguments: Vec<String>) -> CommandResult {
validate_and_run_with_connection(
&arguments,
&|options: &Options| -> Result<(), String> {
if options.remote_file.is_none() {
Err("Missing remote file name".to_string())
} else if options.local_file.is_none() {
Err("Missing local file name.".to_string())
} else {
Ok(())
}
},
&mut |options: &Options, ftp_stream: &mut FtpStream| -> CommandResult {
let options_clone = options.clone();
let remote_file = options_clone.remote_file.unwrap();
let local_file = options_clone.local_file.unwrap();
match create_empty_file(&local_file) {
Ok(_) => {
match ftp_stream.retr(&remote_file, |reader| {
match write_file(reader, &local_file) {
Ok(_) => Ok(()),
Err(error) => Err(FtpError::InvalidResponse(error.to_string())),
}
}) {
Ok(_) => CommandResult::Continue(Some(true.to_string())),
Err(error) => CommandResult::Error(error.to_string()),
}
}
Err(error) => CommandResult::Error(error),
}
},
)
}
}
pub(crate) fn create(package: &str) -> Box<dyn Command> {
Box::new(CommandImpl {
package: package.to_string(),
})
}

View file

@ -0,0 +1,12 @@
use super::*;
use crate::test;
#[test]
fn common_functions() {
test::test_common_command_functions(create(""));
}
#[test]
fn run_no_args() {
test::run_script_and_error(vec![create("")], "out = ftp_get", "out");
}

View file

@ -0,0 +1,26 @@
```sh
handle = ftp_get_in_memory --host <hostname> [--port 21] [--username <user name>] [--password <password>] [--path <path>] [--type <A/I>] --remote-file <file name>
```
Invokes the FTP GET command from the given connection and file details.
#### Parameters
* --host - The host name or IP to connect to
* --port - Optional port number to use (by default 21)
* --username - Optional user name used to login (if not user or password provided, no login operation will be invoked)
* --password - Optional password used to login (if not user or password provided, no login operation will be invoked)
* --path - Optional path on the remote server to invoke operation on
* --type - Optional setting of the transfer type as A (ascii) I (image, binary)
* --remote-file - The remote file to download
#### Return Value
The binary data handle.
#### Examples
```sh
handle = ftp_get_in_memory --host myhost --username someuser --password 12345 --remote-file README.md
text = bytes_to_string ${handle}
```

View file

@ -0,0 +1,80 @@
use crate::sdk::std::net::ftp::{validate_and_run_with_connection, Options};
use crate::utils::pckg;
use crate::utils::state::put_handle;
use duckscript::types::command::{Command, CommandResult, Commands};
use duckscript::types::instruction::Instruction;
use duckscript::types::runtime::StateValue;
use ftp::FtpStream;
use std::collections::HashMap;
#[cfg(test)]
#[path = "./mod_test.rs"]
mod mod_test;
#[derive(Clone)]
pub(crate) struct CommandImpl {
package: String,
}
impl Command for CommandImpl {
fn name(&self) -> String {
pckg::concat(&self.package, "GetInMemory")
}
fn aliases(&self) -> Vec<String> {
vec!["ftp_get_in_memory".to_string()]
}
fn help(&self) -> String {
include_str!("help.md").to_string()
}
fn clone_and_box(&self) -> Box<dyn Command> {
Box::new((*self).clone())
}
fn requires_context(&self) -> bool {
true
}
fn run_with_context(
&self,
arguments: Vec<String>,
state: &mut HashMap<String, StateValue>,
_variables: &mut HashMap<String, String>,
_output_variable: Option<String>,
_instructions: &Vec<Instruction>,
_commands: &mut Commands,
_line: usize,
) -> CommandResult {
validate_and_run_with_connection(
&arguments,
&|options: &Options| -> Result<(), String> {
if options.remote_file.is_none() {
Err("Missing remote file name".to_string())
} else {
Ok(())
}
},
&mut |options: &Options, ftp_stream: &mut FtpStream| -> CommandResult {
let options_clone = options.clone();
let remote_file = options_clone.remote_file.unwrap();
match ftp_stream.simple_retr(&remote_file) {
Ok(binary) => {
let key = put_handle(state, StateValue::ByteArray(binary.into_inner()));
CommandResult::Continue(Some(key))
}
Err(error) => CommandResult::Error(error.to_string()),
}
},
)
}
}
pub(crate) fn create(package: &str) -> Box<dyn Command> {
Box::new(CommandImpl {
package: package.to_string(),
})
}

View file

@ -0,0 +1,12 @@
use super::*;
use crate::test;
#[test]
fn common_functions() {
test::test_common_command_functions(create(""));
}
#[test]
fn run_no_args() {
test::run_script_and_error(vec![create("")], "out = ftp_get_in_memory", "out");
}

View file

@ -1,3 +1,5 @@
mod get;
mod get_in_memory;
mod list;
mod nlst;
@ -12,6 +14,8 @@ static PACKAGE: &str = "ftp";
pub(crate) fn load(commands: &mut Commands, parent: &str) -> Result<(), ScriptError> {
let package = pckg::concat(parent, PACKAGE);
commands.set(get::create(&package))?;
commands.set(get_in_memory::create(&package))?;
commands.set(list::create(&package))?;
commands.set(nlst::create(&package))?;
@ -34,6 +38,8 @@ pub(crate) struct Options {
password: Option<String>,
path: Option<String>,
transfer_type: Option<TransferType>,
remote_file: Option<String>,
local_file: Option<String>,
}
impl Options {
@ -45,6 +51,8 @@ impl Options {
password: None,
path: None,
transfer_type: None,
remote_file: None,
local_file: None,
}
}
}
@ -57,17 +65,34 @@ enum LookingFor {
Password,
Path,
TransferType,
RemoteFile,
LocalFile,
}
pub(crate) fn run_with_connection(
arguments: &Vec<String>,
func: &mut FnMut(&Options, &mut FtpStream) -> CommandResult,
) -> CommandResult {
validate_and_run_with_connection(
arguments,
&|_options: &Options| -> Result<(), String> { Ok(()) },
func,
)
}
pub(crate) fn validate_and_run_with_connection(
arguments: &Vec<String>,
validate_input: &Fn(&Options) -> Result<(), String>,
func: &mut FnMut(&Options, &mut FtpStream) -> CommandResult,
) -> CommandResult {
match parse_common_options(&arguments) {
Ok(options) => run_in_ftp_connection_context(
&options,
&mut |ftp_stream: &mut FtpStream| -> CommandResult { func(&options, ftp_stream) },
),
Ok(options) => match validate_input(&options) {
Ok(_) => run_in_ftp_connection_context(
&options,
&mut |ftp_stream: &mut FtpStream| -> CommandResult { func(&options, ftp_stream) },
),
Err(error) => CommandResult::Error(error),
},
Err(error) => CommandResult::Error(error),
}
}
@ -85,6 +110,8 @@ fn parse_common_options(arguments: &Vec<String>) -> Result<Options, String> {
"--password" => looking_for = LookingFor::Password,
"--path" => looking_for = LookingFor::Path,
"--type" => looking_for = LookingFor::TransferType,
"--remote-file" => looking_for = LookingFor::RemoteFile,
"--local-file" => looking_for = LookingFor::LocalFile,
_ => (),
},
LookingFor::Host => {
@ -135,6 +162,18 @@ fn parse_common_options(arguments: &Vec<String>) -> Result<Options, String> {
looking_for = LookingFor::Flag;
}
}
LookingFor::RemoteFile => {
if !argument.is_empty() {
options.remote_file = Some(argument.to_string());
}
looking_for = LookingFor::Flag;
}
LookingFor::LocalFile => {
if !argument.is_empty() {
options.local_file = Some(argument.to_string());
}
looking_for = LookingFor::Flag;
}
}
}

View file

@ -0,0 +1,14 @@
fn test_valid
handle = ftp_get_in_memory --host test.rebex.net --username demo --password password --remote-file readme.txt
text = bytes_to_string ${handle}
release ${handle}
empty = is_empty ${text}
assert_false ${empty}
found = contains ${text} Welcome
assert ${found}
end

View file

@ -0,0 +1,16 @@
fn test_valid
filename = set ./target/_duckscript_test/net/ftp/get/readme.txt
result = ftp_get --host test.rebex.net --username demo --password password --remote-file readme.txt --local-file ${filename}
assert ${result}
text = readfile ${filename}
empty = is_empty ${text}
assert_false ${empty}
found = contains ${text} Welcome
assert ${found}
end