blob: 1427af0499cca17f307fba9d13d1ef48c6a0365e [file] [log] [blame]
use std::fs::{self, File};
use std::io::{self, Read, Write, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use url::percent_encoding::percent_decode;
use uuid::Uuid;
use error::Error;
/// Converts a URL string (without scheme) into an OS specific path.
pub fn url_path_to_os_path(url_path: &str) -> Result<PathBuf, Error> {
let url_path = if cfg!(os = "windows") {
url_path.replace("/", r"\")
} else {
url_path.to_string()
};
let url_path = percent_decode(url_path.as_bytes())
.decode_utf8()
.map_err(|e| Error::Generic(format!("{}", e)))?
.into_owned();
Ok(Path::new(&url_path).to_path_buf())
}
pub fn url_path_to_path_components(url_path: &str) -> Result<Vec<String>, Error> {
let mut out = Vec::new();
for component in url_path.split("/") {
let component = percent_decode(component.as_bytes())
.decode_utf8()
.map_err(|e| Error::Generic(format!("Path component not utf-8: {:?}", e)))?
.into_owned();
out.push(component);
}
Ok(out)
}
#[derive(Debug)]
struct TempFileInner {
path: PathBuf,
file: File,
}
#[derive(Debug)]
pub struct TempFile(Option<TempFileInner>);
impl TempFile {
pub fn new(prefix: PathBuf) -> Result<Self, io::Error> {
let path = prefix.join(Uuid::new_v4().hyphenated().to_string());
Ok(TempFile(Some(TempFileInner {
path: path.clone(),
file: File::create(path)?,
})))
}
pub fn from_existing(path: PathBuf) -> Result<Self, io::Error> {
Ok(TempFile(Some( TempFileInner {
path: path.clone(),
file: File::open(path)?,
})))
}
pub fn file_mut(&mut self) -> Result<&mut File, io::Error> {
match self.0 {
Some(ref mut inner) => Ok(&mut inner.file),
None => Err(io::Error::new(io::ErrorKind::Other, "invalid TempFile reference"))
}
}
pub fn persist(mut self, dest: &Path) -> Result<(), io::Error> {
match self.0.take() {
Some(inner) => fs::rename(inner.path, dest),
None => Err(io::Error::new(io::ErrorKind::Other, "invalid TempFile reference")),
}
}
}
impl Write for TempFile {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
self.file_mut()?.write(buf)
}
fn flush(&mut self) -> Result<(), io::Error> {
self.file_mut()?.flush()
}
}
impl Read for TempFile {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
self.file_mut()?.read(buf)
}
}
impl Seek for TempFile {
fn seek(&mut self, pos: SeekFrom) -> Result<u64, io::Error> {
self.file_mut()?.seek(pos)
}
}
impl Drop for TempFile {
fn drop(&mut self) {
match self.0.take() {
Some(inner) => {
drop(inner.file);
match fs::remove_file(inner.path) {
Ok(()) => (),
Err(e) => warn!("Failed to delete tempfile: {:?}", e),
}
},
None => (),
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
#[cfg(not(target_os = "windows"))]
fn test_url_path_to_os_path_nix() {
let path = "/tmp/test";
assert_eq!(url_path_to_os_path(path), Ok(PathBuf::from("/tmp/test")));
}
#[test]
#[cfg(not(target_os = "windows"))]
fn test_url_path_to_os_path_percent_nix() {
let path = "/tmp/test%20stuff";
assert_eq!(url_path_to_os_path(path),
Ok(PathBuf::from("/tmp/test stuff")));
}
#[test]
#[cfg(target_os = "windows")]
fn test_url_path_to_os_path_win() {
let path = r"C:/tmp/test";
assert_eq!(url_path_to_os_path(path), Ok(PathBuf::from(r"C:\tmp\test")));
}
#[test]
#[cfg(target_os = "windows")]
fn test_url_path_to_os_path_spaces_win() {
let path = r"C:/tmp/test%20stuff";
assert_eq!(url_path_to_os_path(path),
Ok(PathBuf::from(r"C:\tmp\test stuff")));
}
#[test]
fn test_url_path_to_path_components() {
let path = "test/foo";
assert_eq!(url_path_to_path_components(path),
Ok(vec!["test".into(), "foo".into()]));
let path = "test/foo%20bar";
assert_eq!(url_path_to_path_components(path),
Ok(vec!["test".into(), "foo bar".into()]));
}
}