Created
February 21, 2017 22:47
-
-
Save GuillaumeGomez/455cdda98a055c21581170402efa2662 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![deny(missing_docs)] | |
#![deny(warnings)] | |
//! A crate to provide help to perform async I/O on files using `tokio`. | |
extern crate libc; | |
use std::fs::{File, OpenOptions, create_dir_all, remove_file}; | |
use std::io; | |
use std::path::{Path, PathBuf}; | |
use std::os::unix::io::AsRawFd; | |
use libc::c_int; | |
#[cfg(not(target_os = "macos"))] | |
fn sendfile(in_fd: c_int, out_fd: c_int, size: u64) -> bool { | |
let mut remaining = size as usize; | |
while remaining > 0 { | |
let wrote = unsafe { | |
libc::sendfile(out_fd, in_fd, ::std::ptr::null_mut(), remaining) | |
}; | |
if wrote == -1 { | |
unsafe { libc::perror(b"sendfile\0".as_ptr() as *const i8) }; | |
return false; | |
} | |
remaining -= wrote as usize; | |
} | |
true | |
} | |
#[cfg(target_os = "macos")] | |
fn sendfile(in_fd: c_int, out_fd: c_int, mut size: u64) -> bool { | |
let mut buf = [0u8; 512*1024]; | |
while size > 0 { | |
unsafe { | |
let ret = libc::read(in_fd, buf.as_mut_ptr() as *mut libc::c_void, | |
if size > buf.len() as u64 { buf.len() } else { size as usize }); | |
if ret == -1 { | |
libc::perror(b"read\0".as_ptr() as *const i8); | |
return false; | |
} | |
size -= ret as u64; | |
let mut pos = 0; | |
while pos < ret { | |
let res = libc::write(out_fd, buf.as_ptr().offset(pos) as *const libc::c_void, | |
ret as usize - pos as usize); | |
if res == -1 { | |
libc::perror(b"write\0".as_ptr() as *const i8); | |
return false; | |
} | |
pos += res; | |
} | |
} | |
} | |
true | |
} | |
fn send_data<F: AsRawFd, G: AsRawFd>(in_fd: &F, out_fd: &G, size: u64) -> bool { | |
sendfile(in_fd.as_raw_fd(), out_fd.as_raw_fd(), size) | |
} | |
/// This is mostly a wrapper around the `fs::File` struct to perform conversions | |
/// to tokio types. | |
#[derive(Clone)] | |
pub struct ReaderWriter { | |
folder: PathBuf, | |
} | |
impl ReaderWriter { | |
/// Open a folder or create it if it doesn't exist. It doesn't truncate. | |
pub fn new<P: AsRef<Path>>(path: &P) -> io::Result<ReaderWriter> { | |
if path.as_ref().is_dir() || create_dir_all(path).is_ok() { | |
Ok(ReaderWriter { | |
folder: path.as_ref().to_path_buf(), | |
}) | |
} else { | |
Err(io::Error::new(io::ErrorKind::Other, "Path isn't a directory")) | |
} | |
} | |
/// Write `size` bytes from `socket` into the corresponding `file_id`. | |
pub fn write_to_file<P: AsRef<Path>, F: AsRawFd>(&self, file_id: &P, socket: &F, | |
size: u64) -> Option<bool> { | |
if let Ok(file) = OpenOptions::new() | |
.write(true) | |
.create(true) | |
.truncate(true) | |
.open(self.folder.join(file_id)) { | |
Some(self.write(socket, &file, size)) | |
} else { | |
None | |
} | |
} | |
/// Write `size` bytes from `in_fd` into `out_fd`. | |
pub fn write<F: AsRawFd, G: AsRawFd>(&self, in_fd: &F, out_fd: &G, size: u64) -> bool { | |
send_data(in_fd, out_fd, size) | |
} | |
/// Returns a `File` with read/write access and creates it if it doesn't exist. | |
pub fn get_file<P: AsRef<Path>>(&self, file_id: &P, truncate: bool) -> Option<File> { | |
OpenOptions::new() | |
.write(true) | |
.create(true) | |
.read(true) | |
.truncate(truncate) | |
.open(self.folder.join(file_id)) | |
.ok() | |
} | |
/// Remove the corresponding file. | |
pub fn remove<P: AsRef<Path>>(&self, file_id: &P) -> io::Result<()> { | |
remove_file(self.folder.join(file_id)) | |
} | |
/// Checks if the corresponding file exists | |
pub fn exists<P: AsRef<Path>>(&self, file_id: &P) -> bool { | |
let path = self.folder.join(file_id); | |
path.exists() && path.is_file() | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
extern crate tempdir; | |
use self::tempdir::TempDir; | |
use ::ReaderWriter; | |
use std::fs::{File, OpenOptions}; | |
use std::io::{Read, Write}; | |
use std::path::Path; | |
fn write_in_file<P: AsRef<Path>>(p: &P, data: &str) { | |
let mut tmp_file = File::create(p).expect("create temp file"); | |
write!(tmp_file, "{}", data).expect("write temp file"); | |
} | |
#[test] | |
fn basic() { | |
let test_name = "basic"; | |
let tmp_dir = TempDir::new(&test_name).expect(&format!("{}: create temp dir", &test_name)); | |
let path = tmp_dir.path(); | |
let rw = ReaderWriter::new(&path).expect(&format!("{}: create ReaderWriter", &test_name)); | |
let x = "asjdfkjasdbfkasdbfkasbdkjfbasdbf"; | |
write_in_file(&tmp_dir.path().join("in"), x); | |
let tmp_file = File::open(tmp_dir.path().join("in")).expect("Open failed"); | |
assert_eq!(rw.write_to_file(&"out", &tmp_file, x.len() as u64), Some(true)); | |
let file_path = tmp_dir.path().join("out"); | |
let mut f = File::open(file_path).expect("file open"); | |
let mut buffer = String::new(); | |
f.read_to_string(&mut buffer).expect("read_to_string"); | |
assert_eq!(&buffer, x); | |
} | |
#[test] | |
fn basic2() { | |
let test_name = "basic2"; | |
let tmp_dir = TempDir::new(&test_name).expect(&format!("{}: create temp dir", &test_name)); | |
let path = tmp_dir.path(); | |
let rw = ReaderWriter::new(&path).expect(&format!("{}: create ReaderWriter", &test_name)); | |
let x = "asjdfkjasdbfkasdbfkasbdkjfbasdbf"; | |
write_in_file(&tmp_dir.path().join("in"), x); | |
let tmp_file = File::open(tmp_dir.path().join("in")).expect("Open failed"); | |
let file_path = tmp_dir.path().join("out"); | |
{ | |
let out_file = OpenOptions::new() | |
.write(true) | |
.create(true) | |
.truncate(true) | |
.open(&file_path) | |
.expect("OpenOptions failed"); | |
assert_eq!(rw.write(&tmp_file, &out_file, x.len() as u64), true); | |
} | |
let mut f = File::open(file_path).expect("file open"); | |
let mut buffer = String::new(); | |
f.read_to_string(&mut buffer).expect("read_to_string"); | |
assert_eq!(&buffer, x); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment