Skip to content
This repository has been archived by the owner on Sep 16, 2024. It is now read-only.

Commit

Permalink
Add machine id in filename
Browse files Browse the repository at this point in the history
  • Loading branch information
gwendalF committed Oct 16, 2023
1 parent a70a0b1 commit 6b7ac09
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 19 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ tokio = { version = "1", features = ["full"] }
itertools = "0.10.5"
once_cell = "1.16.0"
thiserror = "1"

machine-uid = "0.5.1"
fastrand = "2"
[dev-dependencies]
tempdir = "0.3.7"
Expand Down
65 changes: 47 additions & 18 deletions src/atomic_file/atomic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use std::io::{Error, ErrorKind, Read, Result};
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};

const MAX_VERSION_FILES: usize = 10;

pub struct TmpFile {
file: File,
path: PathBuf,
Expand Down Expand Up @@ -92,37 +94,32 @@ pub struct AtomicFile {
prefix: String,
}

fn parse_version(filename: &std::ffi::OsStr, prefix: &str) -> Option<usize> {
fn parse_version(filename: &std::ffi::OsStr) -> Option<usize> {
let filename = filename.to_str()?;
if !filename.starts_with(prefix) {
return None;
}
filename[prefix.len()..].parse().ok()
let (_, version) = filename.rsplit_once('.')?;
version.parse().ok()
}

impl AtomicFile {
pub fn new(path: impl Into<PathBuf>) -> Result<Self> {
pub fn new(path: impl Into<PathBuf>) -> crate::Result<Self> {
let directory = path.into();
let machine_id = machine_uid::get()?;
std::fs::create_dir_all(&directory)?;
let filename: &str = match directory.file_name() {
Some(name) => name.to_str().unwrap(),
None => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"`path` must specify a directory name",
));
}
None => Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"`path` must specify a directory name",
))?,
};
let prefix = format!("{}.", filename);
let prefix = format!("{}_{}.", filename, machine_id);
Ok(Self { directory, prefix })
}

fn latest_version(&self) -> Result<usize> {
let mut max_version = 0;
for entry in fs::read_dir(&self.directory)? {
if let Some(version) =
parse_version(&entry?.file_name(), &self.prefix)
{
if let Some(version) = parse_version(&entry?.file_name()) {
max_version = std::cmp::max(max_version, version);
}
}
Expand Down Expand Up @@ -152,13 +149,22 @@ impl AtomicFile {
/// version was not the same as `current` and the operation must be retried
/// with a fresher version of the file. Any other I/O error is forwarded as
/// well.
/// Return the number of old file deleted after swapping
pub fn compare_and_swap(
&self,
current: &ReadOnlyFile,
new: TmpFile,
) -> Result<()> {
) -> Result<usize> {
let new_path = self.path(current.version + 1);
(new.file).sync_data()?;
// Just to check if current.version is still the latest_version
let latest_version = self.latest_version()?;
if latest_version > current.version {
return Err(std::io::Error::new(
std::io::ErrorKind::Interrupted,
"the `current` file is not the latest version",
));
}
// May return `EEXIST`.
let res = std::fs::hard_link(&new.path, new_path);
if let Err(err) = res {
Expand All @@ -177,6 +183,29 @@ impl AtomicFile {
#[cfg(not(target_os = "unix"))]
Err(err)?;
}
Ok(())
Ok(self.prune_old_versions(latest_version))
}

/// Return the number of files deleted
fn prune_old_versions(&self, version: usize) -> usize {
let mut deleted = 0;
if let Ok(iterator) = fs::read_dir(&self.directory) {
for entry in iterator {
match entry {
Ok(entry) => match parse_version(&entry.file_name()) {
Some(file_version) => {
if file_version + MAX_VERSION_FILES < version {
if let Ok(_) = fs::remove_file(entry.path()) {
deleted += 1;
}
}
}
None => (),
},
Err(_) => (),
}
}
}
deleted
}
}
6 changes: 6 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,9 @@ impl From<url::ParseError> for ArklibError {
Self::Parse
}
}

impl From<Box<dyn std::error::Error>> for ArklibError {
fn from(e: Box<dyn std::error::Error>) -> Self {
Self::Other(anyhow::anyhow!(e.to_string()))
}
}

0 comments on commit 6b7ac09

Please sign in to comment.