Back to the LIST command

Now that we can handle a data connection, let's implement the LIST! For now, let's implement it without parameters (once again, we'll see in later chapters how to handle the LIST parameter). As usual, I'll let you add everything where it's needed and we'll just focus on the command handling:

Command::List => {
    if let Some(ref mut data_writer) = self.data_writer {
        let mut tmp = PathBuf::from(".");
        send_cmd(&mut self.stream, ResultCode::DataConnectionAlreadyOpen,
                 "Starting to list directory...");
        let mut out = String::new();
        for entry in read_dir(tmp).unwrap() {
            for entry in dir {
                if let Ok(entry) = entry {
                    add_file_info(entry.path(), &mut out);
                }
            }
            send_data(data_writer, &out)
        }
    } else {
        send_cmd(&mut self.stream, ResultCode::ConnectionClosed, 
"No opened data connection"); } if self.data_writer.is_some() { self.data_writer = None; send_cmd(&mut self.stream, ResultCode::ClosingDataConnection, "Transfer
done"); } }

There's nothing complicated here either too. Once the transfer is over, we close the client socket and move on. What remains to be added are the send_data and the add_file_info functions. Let's start with the first one:

fn send_data(stream: &mut TcpStream, s: &str) {
    write!(stream, "{}", s).unwrap();
}

Easy, there's no error handling, so it just stands on one line. Now let's see the add_file_info function:

fn add_file_info(path: PathBuf, out: &mut String) {
    let extra = if path.is_dir() { "/" } else { "" };
    let is_dir = if path.is_dir() { "d" } else { "-" };

    let meta = match ::std::fs::metadata(&path) {
        Ok(meta) => meta,
        _ => return,
    };
    let (time, file_size) = get_file_info(&meta);
    let path = match path.to_str() {
        Some(path) => match path.split("/").last() {
            Some(path) => path,
            _ => return,
        },
        _ => return,
    };
    let rights = if meta.permissions().readonly() {
        "r--r--r--"
    } else {
        "rw-rw-rw-"
    };
    let file_str = format!("{is_dir}{rights} {links} {owner} {group} {size} {month} 
{day} {hour}:{min} {path}{extra}\r\n", is_dir=is_dir, rights=rights, links=1, // number of links owner="anonymous", // owner name group="anonymous", // group name size=file_size, month=MONTHS[time.tm_mon as usize], day=time.tm_mday, hour=time.tm_hour, min=time.tm_min, path=path, extra=extra); out.push_str(&file_str); println!("==> {:?}", &file_str); }

To make this code work, you'll also need the following:

#[macro_use]
extern crate cfg_if;

cfg_if! {
    if #[cfg(windows)] {
        fn get_file_info(meta: &Metadata) -> (time::Tm, u64) {
            use std::os::windows::prelude::*;
            (time::at(time::Timespec::new(meta.last_write_time())), 
meta.file_size()) } } else { fn get_file_info(meta: &Metadata) -> (time::Tm, u64) { use std::os::unix::prelude::*; (time::at(time::Timespec::new(meta.mtime(), 0)),
meta.size()) } } }

Don't forget to add cfg_if in your Cargo.toml:

cfg-if = "0.1.2"

cfg-if is really good at help you do conditional compilation in a more easily readable way. A point to note about the get_file_info function now—this is one of the rare things that can't be performed in the same way on all systems.

Here, Windows has its own version and Unix has another. However, the two functions take the same argument (the import), and one function call changes. Let's go back to the add_file_info function now:

I suppose you recognized the output of the ls command, right? Apparently, the non-official RFC is working as follows:

dr--r--r-- 1 me me 1024 Jan 7 12:42 foo/
-rw-rw-rw- 1 me me 4 Mar 3 23:42 some_file

First, d if it's a directory or - if it isn't. Then, the rights (just like on Unix platforms):

[rwx][rwx][rwx]

The first rwx is for the owner, the second is about the group, and the last one is about everyone. Here, r stands for read access, w stands for write access, and x stands for execution access.

The rest seems explicit enough on its own, so there's no need to explain it.