meli/meli/src/types/helpers.rs

183 lines
5.2 KiB
Rust

/*
* meli
*
* Copyright 2017-2018 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::{
fs,
fs::OpenOptions,
io::{Read, Write},
os::{
fd::{FromRawFd, OwnedFd},
unix::fs::PermissionsExt,
},
path::{Path, PathBuf},
};
use melib::{error::*, uuid::Uuid};
/// Temporary file that can optionally cleaned up when it is dropped.
#[derive(Debug)]
pub struct File {
/// File's path.
path: PathBuf,
/// Delete file when it is dropped.
delete_on_drop: bool,
}
impl Drop for File {
fn drop(&mut self) {
if self.delete_on_drop {
let _ = std::fs::remove_file(self.path());
}
}
}
impl 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)
.chain_err_summary(|| format!("Could not create/open path {}", self.path.display()))
}
/// The file's path.
pub fn path(&self) -> &Path {
&self.path
}
/// 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,
) -> Result<Self> {
let mut dir = std::env::temp_dir();
let path = if let Some(p) = path {
p
} else {
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,
})
}
inner(path, bytes, delete_on_drop)
.chain_err_summary(|| format!("Could not create file at path {}", path.display()))
}
}
pub fn pipe() -> Result<(OwnedFd, OwnedFd)> {
nix::unistd::pipe()
.map(|(fd1, fd2)| unsafe { (OwnedFd::from_raw_fd(fd1), OwnedFd::from_raw_fd(fd2)) })
.map_err(|err| {
Error::new("Could not create pipe")
.set_source(Some(
(Box::new(err) as Box<dyn std::error::Error + Send + Sync + 'static>).into(),
))
.set_err_kind(ErrorKind::OSError)
})
}
#[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();
}
#[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());
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());
_ = tempdir.close();
}
}