Let's add another case in our match expression in the handle_cmd() method:
#[async] fn handle_cmd(mut self, cmd: Command) -> Result<Self> { match cmd { Command::Cwd(directory) => self = await!(self.cwd(directory))?, // … } }
It simply calls the following method:
#[async] fn cwd(mut self, directory: PathBuf) -> Result<Self> { let path = self.cwd.join(&directory); let (new_self, res) = self.complete_path(path); self = new_self; if let Ok(dir) = res { let (new_self, res) = self.strip_prefix(dir); self = new_self; if let Ok(prefix) = res { self.cwd = prefix.to_path_buf(); self = await!(self.send(Answer::new(ResultCode::Ok, &format!("Directory changed to \"
{}\"", directory.display()))))?; return Ok(self) } } self = await!(self.send(Answer::new(ResultCode::FileNotFound, "No such file or directory")))?; Ok(self) }
This code uses the following two methods, which are similar to those in the previous chapter:
use std::path::StripPrefixError; fn complete_path(self, path: PathBuf) -> (Self, result::Result<PathBuf, io::Error>) { let directory = self.server_root.join(if path.has_root() { path.iter().skip(1).collect() } else { path }); let dir = directory.canonicalize(); if let Ok(ref dir) = dir { if !dir.starts_with(&self.server_root) { return (self,
Err(io::ErrorKind::PermissionDenied.into())); } } (self, dir) } fn strip_prefix(self, dir: PathBuf) -> (Self, result::Result<PathBuf, StripPrefixError>) { let res = dir.strip_prefix(&self.server_root).map(|p| p.to_path_buf()); (self, res) }
Since it uses a new attribute, let's add it to the Client structure:
struct Client { cwd: PathBuf, server_root: PathBuf, writer: Writer, }
We also add its constructor:
impl Client { fn new(writer: Writer, server_root: PathBuf) -> Client { Client { cwd: PathBuf::from("/"), server_root, writer, } } }
We also need to pass this value in a few places, first, in the client function and its wrapper:
#[async] fn client(stream: TcpStream, server_root: PathBuf) -> Result<()> { // … let mut client = Client::new(writer, server_root); // … } #[async] fn handle_client(stream: TcpStream, server_root: PathBuf) -> result::Result<(), ()> { await!(client(stream, server_root)) .map_err(|error| println!("Error handling client: {}",
error)) }
Then, we need to update the server function:
#[async] fn server(handle: Handle, server_root: PathBuf) -> io::Result<()> { // … #[async] for (stream, addr) in listener.incoming() { let address = format!("[address : {}]", addr); println!("New client: {}", address); handle.spawn(handle_client(stream, server_root.clone())); println!("Waiting another client..."); } Ok(()) }
To send the server root to the handle_client function call.
And finally, we'll update the main function to send it to the server function:
use std::env; fn main() { let mut core = Core::new().expect("Cannot create tokio Core"); let handle = core.handle(); match env::current_dir() { Ok(server_root) => { if let Err(error) = core.run(server(handle,
server_root)) { println!("Error running the server: {}", error); } } Err(e) => println!("Couldn't start server: {:?}", e), } }
Here, we send the current directory as the server root.