Files
uv/crates/uv-fs/src/lib.rs
T
Zanie Blue 2586f655bb Rename to uv (#1302)
First, replace all usages in files in-place. I used my editor for this.
If someone wants to add a one-liner that'd be fun.

Then, update directory and file names:

```
# Run twice for nested directories
find . -type d -print0 | xargs -0 rename s/puffin/uv/g
find . -type d -print0 | xargs -0 rename s/puffin/uv/g

# Update files
find . -type f -print0 | xargs -0 rename s/puffin/uv/g
```

Then add all the files again

```
# Add all the files again
git add crates
git add python/uv

# This one needs a force-add
git add -f crates/uv-trampoline
```
2024-02-15 11:19:46 -06:00

264 lines
7.8 KiB
Rust

use std::fmt::Display;
use std::path::{Path, PathBuf};
use fs2::FileExt;
use fs_err as fs;
use tempfile::NamedTempFile;
use tracing::{error, warn};
use uv_warnings::warn_user;
pub use crate::path::*;
mod path;
/// Reads the contents of the file path given into memory.
///
/// If the file path is `-`, then contents are read from stdin instead.
pub fn read(path: impl AsRef<Path>) -> std::io::Result<Vec<u8>> {
use std::io::Read;
let path = path.as_ref();
if path == Path::new("-") {
let mut buf = Vec::with_capacity(1024);
std::io::stdin().read_to_end(&mut buf)?;
Ok(buf)
} else {
fs::read(path)
}
}
/// Reads the contents of the file path given into memory as a `String`.
///
/// If the file path is `-`, then contents are read from stdin instead.
pub fn read_to_string(path: impl AsRef<Path>) -> std::io::Result<String> {
use std::io::Read;
let path = path.as_ref();
if path == Path::new("-") {
let mut buf = String::with_capacity(1024);
std::io::stdin().read_to_string(&mut buf)?;
Ok(buf)
} else {
fs::read_to_string(path)
}
}
/// Create a symlink from `src` to `dst`, replacing any existing symlink.
///
/// On Windows, this uses the `junction` crate to create a junction point.
#[cfg(windows)]
pub fn replace_symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
// Remove the existing symlink, if any.
match junction::delete(dunce::simplified(dst.as_ref())) {
Ok(()) => match fs_err::remove_dir_all(dst.as_ref()) {
Ok(()) => {}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
Err(err) => return Err(err),
},
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
Err(err) => return Err(err),
};
// Replace it with a new symlink.
junction::create(
dunce::simplified(src.as_ref()),
dunce::simplified(dst.as_ref()),
)
}
/// Create a symlink from `src` to `dst`, replacing any existing symlink.
#[cfg(unix)]
pub fn replace_symlink(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
// Create a symlink to the directory store.
let temp_dir =
tempfile::tempdir_in(dst.as_ref().parent().expect("Cache entry to have parent"))?;
let temp_file = temp_dir.path().join("link");
std::os::unix::fs::symlink(src, &temp_file)?;
// Move the symlink into the wheel cache.
fs_err::rename(&temp_file, dst.as_ref())?;
Ok(())
}
/// Write `data` to `path` atomically using a temporary file and atomic rename.
#[cfg(feature = "tokio")]
pub async fn write_atomic(path: impl AsRef<Path>, data: impl AsRef<[u8]>) -> std::io::Result<()> {
let temp_file = NamedTempFile::new_in(
path.as_ref()
.parent()
.expect("Write path must have a parent"),
)?;
fs_err::tokio::write(&temp_file, &data).await?;
temp_file.persist(&path).map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"Failed to persist temporary file to {}: {}",
path.normalized_display(),
err.error
),
)
})?;
Ok(())
}
/// Write `data` to `path` atomically using a temporary file and atomic rename.
pub fn write_atomic_sync(path: impl AsRef<Path>, data: impl AsRef<[u8]>) -> std::io::Result<()> {
let temp_file = NamedTempFile::new_in(
path.as_ref()
.parent()
.expect("Write path must have a parent"),
)?;
fs_err::write(&temp_file, &data)?;
temp_file.persist(&path).map_err(|err| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"Failed to persist temporary file to {}: {}",
path.normalized_display(),
err.error
),
)
})?;
Ok(())
}
/// Remove the file or directory at `path`, if it exists.
///
/// Returns `true` if the file or directory was removed, and `false` if the path did not exist.
pub fn force_remove_all(path: impl AsRef<Path>) -> Result<bool, std::io::Error> {
let path = path.as_ref();
let Some(metadata) = metadata_if_exists(path)? else {
return Ok(false);
};
if metadata.is_dir() {
fs::remove_dir_all(path)?;
} else {
fs::remove_file(path)?;
}
Ok(true)
}
/// Iterate over the subdirectories of a directory.
///
/// If the directory does not exist, returns an empty iterator.
pub fn directories(path: impl AsRef<Path>) -> impl Iterator<Item = PathBuf> {
path.as_ref()
.read_dir()
.ok()
.into_iter()
.flatten()
.filter_map(|entry| match entry {
Ok(entry) => Some(entry),
Err(err) => {
warn!("Failed to read entry: {}", err);
None
}
})
.filter(|entry| {
entry
.file_type()
.map_or(false, |file_type| file_type.is_dir())
})
.map(|entry| entry.path())
}
/// Iterate over the symlinks in a directory.
///
/// If the directory does not exist, returns an empty iterator.
pub fn symlinks(path: impl AsRef<Path>) -> impl Iterator<Item = PathBuf> {
path.as_ref()
.read_dir()
.ok()
.into_iter()
.flatten()
.filter_map(|entry| match entry {
Ok(entry) => Some(entry),
Err(err) => {
warn!("Failed to read entry: {}", err);
None
}
})
.filter(|entry| {
entry
.file_type()
.map_or(false, |file_type| file_type.is_symlink())
})
.map(|entry| entry.path())
}
/// Iterate over the files in a directory.
///
/// If the directory does not exist, returns an empty iterator.
pub fn files(path: impl AsRef<Path>) -> impl Iterator<Item = PathBuf> {
path.as_ref()
.read_dir()
.ok()
.into_iter()
.flatten()
.filter_map(|entry| match entry {
Ok(entry) => Some(entry),
Err(err) => {
warn!("Failed to read entry: {}", err);
None
}
})
.filter(|entry| {
entry
.file_type()
.map_or(false, |file_type| file_type.is_file())
})
.map(|entry| entry.path())
}
/// A file lock that is automatically released when dropped.
#[derive(Debug)]
pub struct LockedFile(fs_err::File);
impl LockedFile {
pub fn acquire(path: impl AsRef<Path>, resource: impl Display) -> Result<Self, std::io::Error> {
let file = fs_err::File::create(path.as_ref())?;
match file.file().try_lock_exclusive() {
Ok(()) => Ok(Self(file)),
Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {
warn_user!(
"Waiting to acquire lock for {} (lockfile: {})",
resource,
path.normalized_display(),
);
file.file().lock_exclusive()?;
Ok(Self(file))
}
Err(err) => Err(err),
}
}
}
impl Drop for LockedFile {
fn drop(&mut self) {
if let Err(err) = self.0.file().unlock() {
error!(
"Failed to unlock {}; program may be stuck: {}",
self.0.path().display(),
err
);
}
}
}
/// Given a path, return its metadata if the file exists, or `None` if it does not.
///
/// If the file exists but cannot be read, returns an error.
pub fn metadata_if_exists(path: impl AsRef<Path>) -> std::io::Result<Option<std::fs::Metadata>> {
match fs::metadata(path) {
Ok(metadata) => Ok(Some(metadata)),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
}