|
|
|
@ -24,14 +24,17 @@ use std::{
|
|
|
|
|
fs::OpenOptions,
|
|
|
|
|
io::{Read, Write},
|
|
|
|
|
os::unix::fs::PermissionsExt,
|
|
|
|
|
path::PathBuf,
|
|
|
|
|
path::{Path, PathBuf},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
use melib::uuid::Uuid;
|
|
|
|
|
use melib::{error::*, uuid::Uuid};
|
|
|
|
|
|
|
|
|
|
/// Temporary file that can optionally cleaned up when it is dropped.
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct File {
|
|
|
|
|
pub path: PathBuf,
|
|
|
|
|
/// File's path.
|
|
|
|
|
path: PathBuf,
|
|
|
|
|
/// Delete file when it is dropped.
|
|
|
|
|
delete_on_drop: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -44,69 +47,121 @@ impl Drop for File {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl File {
|
|
|
|
|
pub fn file(&self) -> std::fs::File {
|
|
|
|
|
/// Open as a standard library file type.
|
|
|
|
|
pub fn as_std_file(&self) -> Result<std::fs::File> {
|
|
|
|
|
OpenOptions::new()
|
|
|
|
|
.read(true)
|
|
|
|
|
.write(true)
|
|
|
|
|
.create(true)
|
|
|
|
|
.open(&self.path)
|
|
|
|
|
.unwrap()
|
|
|
|
|
.chain_err_summary(|| format!("Could not create/open path {}", self.path.display()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn path(&self) -> &PathBuf {
|
|
|
|
|
/// The file's path.
|
|
|
|
|
pub fn path(&self) -> &Path {
|
|
|
|
|
&self.path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn read_to_string(&self) -> String {
|
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
|
let mut f = fs::File::open(&self.path)
|
|
|
|
|
.unwrap_or_else(|_| panic!("Can't open {}", &self.path.display()));
|
|
|
|
|
f.read_to_end(&mut buf)
|
|
|
|
|
.unwrap_or_else(|_| panic!("Can't read {}", &self.path.display()));
|
|
|
|
|
String::from_utf8(buf).unwrap()
|
|
|
|
|
/// Convenience method to read `File` to `String`.
|
|
|
|
|
pub fn read_to_string(&self) -> Result<String> {
|
|
|
|
|
fn inner(path: &Path) -> Result<String> {
|
|
|
|
|
let mut buf = Vec::new();
|
|
|
|
|
let mut f = fs::File::open(path)?;
|
|
|
|
|
f.read_to_end(&mut buf)?;
|
|
|
|
|
Ok(String::from_utf8(buf)?)
|
|
|
|
|
}
|
|
|
|
|
inner(&self.path).chain_err_summary(|| format!("Can't read {}", self.path.display()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returned `File` will be deleted when dropped if delete_on_drop is set, so
|
|
|
|
|
/// make sure to add it on `context.temp_files` to reap it later.
|
|
|
|
|
pub fn create_temp_file(
|
|
|
|
|
bytes: &[u8],
|
|
|
|
|
filename: Option<&str>,
|
|
|
|
|
path: Option<&mut PathBuf>,
|
|
|
|
|
extension: Option<&str>,
|
|
|
|
|
delete_on_drop: bool,
|
|
|
|
|
) -> File {
|
|
|
|
|
let mut dir = std::env::temp_dir();
|
|
|
|
|
|
|
|
|
|
let path = path.unwrap_or_else(|| {
|
|
|
|
|
dir.push("meli");
|
|
|
|
|
std::fs::DirBuilder::new()
|
|
|
|
|
.recursive(true)
|
|
|
|
|
.create(&dir)
|
|
|
|
|
.unwrap();
|
|
|
|
|
if let Some(filename) = filename {
|
|
|
|
|
dir.push(filename)
|
|
|
|
|
/// Returned `File` will be deleted when dropped if delete_on_drop is set,
|
|
|
|
|
/// so make sure to add it on `context.temp_files` to reap it later.
|
|
|
|
|
pub fn create_temp_file(
|
|
|
|
|
bytes: &[u8],
|
|
|
|
|
filename: Option<&str>,
|
|
|
|
|
path: Option<&mut PathBuf>,
|
|
|
|
|
extension: Option<&str>,
|
|
|
|
|
delete_on_drop: bool,
|
|
|
|
|
) -> Result<Self> {
|
|
|
|
|
let mut dir = std::env::temp_dir();
|
|
|
|
|
|
|
|
|
|
let path = if let Some(p) = path {
|
|
|
|
|
p
|
|
|
|
|
} else {
|
|
|
|
|
let u = Uuid::new_v4();
|
|
|
|
|
dir.push(u.as_simple().to_string());
|
|
|
|
|
dir.push("meli");
|
|
|
|
|
std::fs::DirBuilder::new().recursive(true).create(&dir)?;
|
|
|
|
|
if let Some(filename) = filename {
|
|
|
|
|
dir.push(filename)
|
|
|
|
|
} else {
|
|
|
|
|
let u = Uuid::new_v4();
|
|
|
|
|
dir.push(u.as_simple().to_string());
|
|
|
|
|
}
|
|
|
|
|
&mut dir
|
|
|
|
|
};
|
|
|
|
|
if let Some(ext) = extension {
|
|
|
|
|
path.set_extension(ext);
|
|
|
|
|
}
|
|
|
|
|
fn inner(path: &Path, bytes: &[u8], delete_on_drop: bool) -> Result<File> {
|
|
|
|
|
let mut f = std::fs::File::create(path)?;
|
|
|
|
|
let metadata = f.metadata()?;
|
|
|
|
|
let mut permissions = metadata.permissions();
|
|
|
|
|
|
|
|
|
|
permissions.set_mode(0o600); // Read/write for owner only.
|
|
|
|
|
f.set_permissions(permissions)?;
|
|
|
|
|
|
|
|
|
|
f.write_all(bytes)?;
|
|
|
|
|
f.flush()?;
|
|
|
|
|
Ok(File {
|
|
|
|
|
path: path.to_path_buf(),
|
|
|
|
|
delete_on_drop,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
&mut dir
|
|
|
|
|
});
|
|
|
|
|
if let Some(ext) = extension {
|
|
|
|
|
path.set_extension(ext);
|
|
|
|
|
inner(path, bytes, delete_on_drop)
|
|
|
|
|
.chain_err_summary(|| format!("Could not create file at path {}", path.display()))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_file_invalid_path() {
|
|
|
|
|
let f = File {
|
|
|
|
|
path: PathBuf::from("//////"),
|
|
|
|
|
delete_on_drop: true,
|
|
|
|
|
};
|
|
|
|
|
f.as_std_file().unwrap_err();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut f = std::fs::File::create(&path).unwrap();
|
|
|
|
|
let metadata = f.metadata().unwrap();
|
|
|
|
|
let mut permissions = metadata.permissions();
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_file_delete_on_drop() {
|
|
|
|
|
const S: &str = "hello world";
|
|
|
|
|
let tempdir = tempfile::tempdir().unwrap();
|
|
|
|
|
|
|
|
|
|
let delete_on_drop = File::create_temp_file(
|
|
|
|
|
S.as_bytes(),
|
|
|
|
|
None,
|
|
|
|
|
Some(&mut tempdir.path().join("test")),
|
|
|
|
|
None,
|
|
|
|
|
true,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert_eq!(&delete_on_drop.read_to_string().unwrap(), S);
|
|
|
|
|
drop(delete_on_drop);
|
|
|
|
|
assert!(!tempdir.path().join("test").try_exists().unwrap());
|
|
|
|
|
|
|
|
|
|
permissions.set_mode(0o600); // Read/write for owner only.
|
|
|
|
|
f.set_permissions(permissions).unwrap();
|
|
|
|
|
let persist = File::create_temp_file(
|
|
|
|
|
S.as_bytes(),
|
|
|
|
|
None,
|
|
|
|
|
Some(&mut tempdir.path().join("test")),
|
|
|
|
|
None,
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
.unwrap();
|
|
|
|
|
assert_eq!(&persist.read_to_string().unwrap(), S);
|
|
|
|
|
drop(persist);
|
|
|
|
|
assert!(tempdir.path().join("test").try_exists().unwrap());
|
|
|
|
|
|
|
|
|
|
f.write_all(bytes).unwrap();
|
|
|
|
|
f.flush().unwrap();
|
|
|
|
|
File {
|
|
|
|
|
path: path.clone(),
|
|
|
|
|
delete_on_drop,
|
|
|
|
|
_ = tempdir.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|