actix-fs/src/lib.rs
2020-06-19 12:38:02 -05:00

820 lines
24 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#![deny(missing_docs)]
//! # Actix FS
//! _Asyncronous filesystem operations for actix-based systems_
//!
//! ## Usage
//!
//! ```rust
//! use std::io::SeekFrom;
//!
//! #[actix_rt::main]
//! async fn main() -> Result<()> {
//! let file = actix_fs::file::open("tests/read.txt").await?;
//! let (file, position) = actix_fs::file::seek(file, SeekFrom::Start(7)).await?;
//! let bytes = actix_fs::file::read(file).await?;
//!
//! assert!(position == 7);
//! assert!(bytes.as_ref() == b"World!\n");
//! Ok(())
//! }
//! ```
//!
//! ### Contributing
//! Unless otherwise stated, all contributions to this project will be licensed under the CSL with
//! the exceptions listed in the License section of this file.
//!
//! ### License
//! This work is licensed under the Cooperative Software License. This is not a Free Software
//! License, but may be considered a "source-available License." For most hobbyists, self-employed
//! developers, worker-owned companies, and cooperatives, this software can be used in most
//! projects so long as this software is distributed under the terms of the CSL. For more
//! information, see the provided LICENSE file. If none exists, the license can be found online
//! [here](https://lynnesbian.space/csl/). If you are a free software project and wish to use this
//! software under the terms of the GNU Affero General Public License, please contact me at
//! [asonix@asonix.dog](mailto:asonix@asonix.dog) and we can sort that out. If you wish to use this
//! project under any other license, especially in proprietary software, the answer is likely no.
//!
//! Actix FS is currently licensed under the AGPL to the Lemmy project, found
//! at [github.com/LemmyNet/lemmy](https://github.com/LemmyNet/lemmy)
use actix_threadpool::run;
use bytes::Bytes;
use futures::stream::Stream;
use std::{
fs, io,
path::{Path, PathBuf},
};
pub mod file;
#[derive(Debug, thiserror::Error)]
/// Possible errors produced by Actix FS
pub enum Error {
/// IO Error
#[error("{0}")]
Io(#[from] io::Error),
/// Task canceled (due to panic)
#[error("Task canceled")]
Canceled,
}
/// Actix FS Result type
pub type Result<T> = std::result::Result<T, Error>;
impl Error {
/// Get the io::ErrorKind from the error if present
///
/// This method returns None if the IO operation panicked.
pub fn kind(&self) -> Option<io::ErrorKind> {
match self {
Error::Io(ref io) => Some(io.kind()),
_ => None,
}
}
}
impl From<std::str::Utf8Error> for Error {
fn from(e: std::str::Utf8Error) -> Self {
Error::Io(io::Error::new(io::ErrorKind::InvalidInput, e))
}
}
/// Returns the canonical, absolute form of a path with all intermediate
/// components normalized and symbolic links resolved.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `realpath` function on Unix
/// and the `CreateFile` and `GetFinalPathNameByHandle` functions on Windows.
/// Note that, this [may change in the future][changes].
///
/// On Windows, this converts the path to use [extended length path][path]
/// syntax, which allows your program to use longer path names, but means you
/// can only join backslash-delimited paths to it, and it may be incompatible
/// with other applications (if passed to the application on the command-line,
/// or written to a file another application may read).
///
/// [changes]: ../io/index.html#platform-specific-behavior
/// [path]: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * `path` does not exist.
/// * A non-final component in path is not a directory.
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// let path = actix_fs::canonicalize("../a/../foo.txt").await?;
/// Ok(())
/// }
/// ```
pub async fn canonicalize<P>(path: P) -> Result<PathBuf>
where
P: AsRef<Path> + Send + 'static,
{
let path = run(move || fs::canonicalize(path)).await?;
Ok(path)
}
/// Copies the contents of one file to another. This function will also
/// copy the permission bits of the original file to the destination file.
///
/// This function will **overwrite** the contents of `to`.
///
/// Note that if `from` and `to` both point to the same file, then the file
/// will likely get truncated by this operation.
///
/// On success, the total number of bytes copied is returned and it is equal to
/// the length of the `to` file as reported by `metadata`.
///
/// If youre wanting to copy the contents of one file to another and youre
/// working with [`File`]s, see the [`io::copy`] function.
///
/// [`io::copy`]: ../io/fn.copy.html
/// [`File`]: ./struct.File.html
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `open` function in Unix
/// with `O_RDONLY` for `from` and `O_WRONLY`, `O_CREAT`, and `O_TRUNC` for `to`.
/// `O_CLOEXEC` is set for returned file descriptors.
/// On Windows, this function currently corresponds to `CopyFileEx`. Alternate
/// NTFS streams are copied but only the size of the main stream is returned by
/// this function. On MacOS, this function corresponds to `fclonefileat` and
/// `fcopyfile`.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * The `from` path is not a file.
/// * The `from` file does not exist.
/// * The current process does not have the permission rights to access
/// `from` or write `to`.
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// fn main() -> actix_fs::Result<()> {
/// actix_fs::copy("foo.txt", "bar.txt").await?; // Copy foo.txt to bar.txt
/// Ok(())
/// }
/// ```
pub async fn copy<P, Q>(from: P, to: Q) -> Result<u64>
where
P: AsRef<Path> + Send + 'static,
Q: AsRef<Path> + Send + 'static,
{
let bytes = run(move || fs::copy(from, to)).await?;
Ok(bytes)
}
/// Creates a new, empty directory at the provided path
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `mkdir` function on Unix
/// and the `CreateDirectory` function on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// **NOTE**: If a parent of the given path doesn't exist, this function will
/// return an error. To create a directory and all its missing parents at the
/// same time, use the [`create_dir_all`] function.
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * User lacks permissions to create directory at `path`.
/// * A parent of the given path doesn't exist. (To create a directory and all
/// its missing parents at the same time, use the [`create_dir_all`]
/// function.)
/// * `path` already exists.
///
/// [`create_dir_all`]: fn.create_dir_all.html
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// actix_fs::create_dir("/some/dir").await?;
/// Ok(())
/// }
/// ```
pub async fn create_dir<P>(path: P) -> Result<()>
where
P: AsRef<Path> + Send + 'static,
{
run(move || fs::create_dir(path)).await?;
Ok(())
}
/// Recursively create a directory and all of its parent components if they
/// are missing.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `mkdir` function on Unix
/// and the `CreateDirectory` function on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * If any directory in the path specified by `path`
/// does not already exist and it could not be created otherwise. The specific
/// error conditions for when a directory is being created (after it is
/// determined to not exist) are outlined by [`fs::create_dir`].
///
/// Notable exception is made for situations where any of the directories
/// specified in the `path` could not be created as it was being created concurrently.
/// Such cases are considered to be successful. That is, calling `create_dir_all`
/// concurrently from multiple threads or processes is guaranteed not to fail
/// due to a race condition with itself.
///
/// [`fs::create_dir`]: fn.create_dir.html
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// actix_fs::create_dir_all("/some/dir").await?;
/// Ok(())
/// }
/// ```
pub async fn create_dir_all<P>(path: P) -> Result<()>
where
P: AsRef<Path> + Send + 'static,
{
run(move || fs::create_dir_all(path)).await?;
Ok(())
}
/// Creates a new hard link on the filesystem.
///
/// The `dst` path will be a link pointing to the `src` path. Note that systems
/// often require these two paths to both be located on the same filesystem.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `link` function on Unix
/// and the `CreateHardLink` function on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * The `src` path is not a file or doesn't exist.
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// actix_fs::hard_link("a.txt", "b.txt").await?; // Hard link a.txt to b.txt
/// Ok(())
/// }
/// ```
pub async fn hard_link<P, Q>(src: P, dst: Q) -> Result<()>
where
P: AsRef<Path> + Send + 'static,
Q: AsRef<Path> + Send + 'static,
{
run(move || fs::hard_link(src, dst)).await?;
Ok(())
}
/// Given a path, query the file system to get information about a file,
/// directory, etc.
///
/// This function will traverse symbolic links to query information about the
/// destination file.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `stat` function on Unix
/// and the `GetFileAttributesEx` function on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * The user lacks permissions to perform `metadata` call on `path`.
/// * `path` does not exist.
///
/// # Examples
///
/// ```rust,no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// let attr = actix_fs::metadata("/some/file/path.txt").await?;
/// // inspect attr ...
/// Ok(())
/// }
/// ```
pub async fn metadata<P>(path: P) -> Result<fs::Metadata>
where
P: AsRef<Path> + Send + 'static,
{
let metadata = run(move || fs::metadata(path)).await?;
Ok(metadata)
}
/// Read the entire contents of a file into a bytes vector.
///
/// This is a convenience function for using [`File::open`] and [`read_to_end`]
/// with fewer imports and without an intermediate variable. It pre-allocates a
/// buffer based on the file size when available, so it is generally faster than
/// reading into a vector created with `Vec::new()`.
///
/// [`file::open`]: struct.File.html#method.open
/// [`read_to_end`]: ../io/trait.Read.html#method.read_to_end
///
/// # Errors
///
/// This function will return an error if `path` does not already exist.
/// Other errors may also be returned according to [`OpenOptions::open`].
///
/// [`OpenOptions::open`]: struct.OpenOptions.html#method.open
///
/// It will also return an error if it encounters while reading an error
/// of a kind other than [`ErrorKind::Interrupted`].
///
/// [`ErrorKind::Interrupted`]: ../../std/io/enum.ErrorKind.html#variant.Interrupted
///
/// # Examples
///
/// ```no_run
/// use std::net::SocketAddr;
///
/// #[actix_rt::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
/// let foo: SocketAddr = String::from_utf8_lossy(&actix_fs::read("address.txt").await?).parse()?;
/// Ok(())
/// }
/// ```
pub async fn read<P>(path: P) -> Result<Bytes>
where
P: AsRef<Path> + Send + 'static,
{
let f = file::open(path).await?;
file::read(f).await
}
/// Reads a symbolic link, returning the file that the link points to.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `readlink` function on Unix
/// and the `CreateFile` function with `FILE_FLAG_OPEN_REPARSE_POINT` and
/// `FILE_FLAG_BACKUP_SEMANTICS` flags on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * `path` is not a symbolic link.
/// * `path` does not exist.
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// let path = actix_fs::read_link("a.txt").await?;
/// Ok(())
/// }
/// ```
pub async fn read_link<P>(path: P) -> Result<PathBuf>
where
P: AsRef<Path> + Send + 'static,
{
let path = run(move || fs::read_link(path)).await?;
Ok(path)
}
/// Read the contents of a file stream of bytes.
///
/// This is a convenience function for using [`file::open`] and [`read_to_stream`]
/// with fewer imports and without an intermediate variable.
///
/// [`file::open`]: struct.File.html#method.open
/// [`read_to_string`]: ../io/trait.Read.html#method.read_to_string
///
/// # Errors
///
/// This function will return an error if `path` does not already exist.
/// Other errors may also be returned according to [`OpenOptions::open`].
///
/// [`OpenOptions::open`]: struct.OpenOptions.html#method.open
///
/// # Examples
///
/// ```no_run
/// use std::net::SocketAddr;
///
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// let mut stream = actix_fs::read_to_stream("some.txt").await?;
///
/// while let Some(res) = stream.next().await {
/// println!("bytes: {:?}", res?);
/// }
/// Ok(())
/// }
/// ```
pub async fn read_to_stream<P>(path: P) -> Result<file::FileStream>
where
P: AsRef<Path> + Send + 'static,
{
let f = file::open(path).await?;
file::read_to_stream(f).await
}
/// Read the entire contents of a file into a string.
///
/// This is a convenience function for using [`File::open`] and [`read_to_string`]
/// with fewer imports and without an intermediate variable. It pre-allocates a
/// buffer based on the file size when available, so it is generally faster than
/// reading into a string created with `String::new()`.
///
/// [`file::open`]: struct.File.html#method.open
/// [`read_to_string`]: ../io/trait.Read.html#method.read_to_string
///
/// # Errors
///
/// This function will return an error if `path` does not already exist.
/// Other errors may also be returned according to [`OpenOptions::open`].
///
/// [`OpenOptions::open`]: struct.OpenOptions.html#method.open
///
/// It will also return an error if it encounters while reading an error
/// of a kind other than [`ErrorKind::Interrupted`],
/// or if the contents of the file are not valid UTF-8.
///
/// [`ErrorKind::Interrupted`]: ../../std/io/enum.ErrorKind.html#variant.Interrupted
///
/// # Examples
///
/// ```no_run
/// use std::net::SocketAddr;
///
/// #[actix_rt::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error + 'static>> {
/// let foo: SocketAddr = actix_fs::read_to_string("address.txt").await?.parse()?;
/// Ok(())
/// }
/// ```
pub async fn read_to_string<P>(path: P) -> Result<String>
where
P: AsRef<Path> + Send + 'static,
{
let f = file::open(path).await?;
file::read_to_string(f).await
}
/// Removes an existing, empty directory.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `rmdir` function on Unix
/// and the `RemoveDirectory` function on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * The user lacks permissions to remove the directory at the provided `path`.
/// * The directory isn't empty.
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// actix_fs::remove_dir("/some/dir").await?;
/// Ok(())
/// }
/// ```
pub async fn remove_dir<P>(path: P) -> Result<()>
where
P: AsRef<Path> + Send + 'static,
{
run(move || fs::remove_dir(path)).await?;
Ok(())
}
/// Removes a directory at this path, after removing all its contents. Use
/// carefully!
///
/// This function does **not** follow symbolic links and it will simply remove the
/// symbolic link itself.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to `opendir`, `lstat`, `rm` and `rmdir` functions on Unix
/// and the `FindFirstFile`, `GetFileAttributesEx`, `DeleteFile`, and `RemoveDirectory` functions
/// on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// See [`actix_fs::remove_file`] and [`actix_fs::remove_dir`].
///
/// [`actix_fs::remove_file`]: fn.remove_file.html
/// [`actix_fs::remove_dir`]: fn.remove_dir.html
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// actix_fs::remove_dir_all("/some/dir").await?;
/// Ok(())
/// }
/// ```
pub async fn remove_dir_all<P>(path: P) -> Result<()>
where
P: AsRef<Path> + Send + 'static,
{
run(move || fs::remove_dir_all(path)).await?;
Ok(())
}
/// Removes a file from the filesystem.
///
/// Note that there is no
/// guarantee that the file is immediately deleted (e.g., depending on
/// platform, other open file descriptors may prevent immediate removal).
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `unlink` function on Unix
/// and the `DeleteFile` function on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * `path` points to a directory.
/// * The user lacks permissions to remove the file.
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// actix_fs::remove_file("a.txt").await?;
/// Ok(())
/// }
/// ```
pub async fn remove_file<P>(path: P) -> Result<()>
where
P: AsRef<Path> + Send + 'static,
{
run(move || fs::remove_file(path)).await?;
Ok(())
}
/// Rename a file or directory to a new name, replacing the original file if
/// `to` already exists.
///
/// This will not work if the new name is on a different mount point.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `rename` function on Unix
/// and the `MoveFileEx` function with the `MOVEFILE_REPLACE_EXISTING` flag on Windows.
///
/// Because of this, the behavior when both `from` and `to` exist differs. On
/// Unix, if `from` is a directory, `to` must also be an (empty) directory. If
/// `from` is not a directory, `to` must also be not a directory. In contrast,
/// on Windows, `from` can be anything, but `to` must *not* be a directory.
///
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * `from` does not exist.
/// * The user lacks permissions to view contents.
/// * `from` and `to` are on separate filesystems.
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// actix_fs::rename("a.txt", "b.txt").await?; // Rename a.txt to b.txt
/// Ok(())
/// }
/// ```
pub async fn rename<P, Q>(from: P, to: Q) -> Result<()>
where
P: AsRef<Path> + Send + 'static,
Q: AsRef<Path> + Send + 'static,
{
run(move || fs::rename(from, to)).await?;
Ok(())
}
/// Changes the permissions found on a file or a directory.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `chmod` function on Unix
/// and the `SetFileAttributes` function on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * `path` does not exist.
/// * The user lacks the permission to change attributes of the file.
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// let mut perms = actix_fs::metadata("foo.txt").await?.permissions();
/// perms.set_readonly(true);
/// actix_fs::set_permissions("foo.txt", perms).await?;
/// Ok(())
/// }
/// ```
pub async fn set_permissions<P>(path: P, permissions: fs::Permissions) -> Result<()>
where
P: AsRef<Path> + Send + 'static,
{
run(move || fs::set_permissions(path, permissions)).await?;
Ok(())
}
/// Query the metadata about a file without following symlinks.
///
/// # Platform-specific behavior
///
/// This function currently corresponds to the `lstat` function on Unix
/// and the `GetFileAttributesEx` function on Windows.
/// Note that, this [may change in the future][changes].
///
/// [changes]: ../io/index.html#platform-specific-behavior
///
/// # Errors
///
/// This function will return an error in the following situations, but is not
/// limited to just these cases:
///
/// * The user lacks permissions to perform `metadata` call on `path`.
/// * `path` does not exist.
///
/// # Examples
///
/// ```rust,no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// let attr = actix_fs::symlink_metadata("/some/file/path.txt").await?;
/// // inspect attr ...
/// Ok(())
/// }
/// ```
pub async fn symlink_metadata<P>(path: P) -> Result<fs::Metadata>
where
P: AsRef<Path> + Send + 'static,
{
let metadata = run(move || fs::symlink_metadata(path)).await?;
Ok(metadata)
}
/// Write bytes as the entire contents of a file.
///
/// This function will create a file if it does not exist,
/// and will entirely replace its contents if it does.
///
/// This is a convenience function for using [`File::create`] and [`write_all`]
/// with fewer imports.
///
/// [`file::create`]: struct.File.html#method.create
/// [`write_all`]: ../io/trait.Write.html#method.write_all
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// actix_fs::write("foo.txt", b"Lorem ipsum").await?;
/// actix_fs::write("bar.txt", "dolor sit").await?;
/// Ok(())
/// }
/// ```
pub async fn write<P, B>(path: P, contents: B) -> Result<()>
where
P: AsRef<Path> + Send + 'static,
B: Into<Bytes>,
{
let f = file::create(path).await?;
file::write(f, contents.into()).await
}
/// Write the bytes stream as the entire contents of a file.
///
/// This function will create a file if it does not exist,
/// and will entirely replace its contents if it does.
///
/// This is a convenience function for using [`File::create`] and [`write_all`]
/// with fewer imports.
///
/// [`file::create`]: struct.File.html#method.create
/// [`write_all`]: ../io/trait.Write.html#method.write_all
///
/// # Examples
///
/// ```no_run
/// #[actix_rt::main]
/// async fn main() -> actix_fs::Result<()> {
/// let mut stream = actix_fs::read_to_stream("foo.txt").await?;
///
/// actix_fs::write_stream("bar.txt", stream).await?;
/// Ok(())
/// }
/// ```
pub async fn write_stream<P, S, E>(path: P, stream: S) -> Result<()>
where
P: AsRef<Path> + Send + 'static,
S: Stream<Item = Result<Bytes>> + Unpin,
E: From<Error> + Unpin,
{
let f = file::create(path).await?;
file::write_stream(f, stream).await
}