use std::io;
use std::path::{Path, PathBuf};
use crate::prelude::RepoId;
use super::{Repository, RepositoryError, UserInfo};
/// A [`Repository`] that is created for temporary operations, such as cloning.
///
/// When the `TempRepository` is no longer needed, then call one of destructors:
///
/// - [`TempRepository::cleanup`]: remove the repository directory
/// - [`TempRepository::mv`]: move the repository directory to a final
/// destination and remove the old directory
///
/// [`TempRepository`] implements [`AsRef`] so that the [`Repository`] can be
/// used in places where a [`Repository`] is needed.
pub struct TempRepository {
repo: Repository,
path: PathBuf,
}
impl TempRepository {
/// Extension used for the directory
pub(crate) const EXT: &str = "tmp";
const RANDOMNESS_LENGTH: usize = 6;
pub(super) fn new<P>(root: P, rid: RepoId, info: &UserInfo) -> Result<Self, RepositoryError>
where
P: AsRef<Path>,
{
let random: String = std::iter::repeat_with(fastrand::alphanumeric)
.take(Self::RANDOMNESS_LENGTH)
.collect();
let path = root
.as_ref()
.join(format!("{}.{random}", rid.canonical()))
.with_extension(Self::EXT);
let repo = Repository::create(&path, rid, info)?;
Ok(Self { repo, path })
}
/// Clean up the temporary directory of the repository.
///
/// Note that the repository is dropped first to ensure that there are no
/// handles to the repository, before removing the directory.
pub fn cleanup(self) {
let path = self.path.clone();
drop(self.repo);
Self::remove(&path)
}
/// Move the temporary directory of the repository to the new path.
///
/// If `to` already exists, then the temporary directory is removed, and the
/// repository is not moved.
///
/// Note that the repository is dropped first to ensure that there are no
/// handles to the repository, before removing the directory.
pub fn mv<P>(self, to: P) -> io::Result<()>
where
P: AsRef<Path>,
{
let to = to.as_ref();
let rid = self.repo.id;
let path = self.path.clone();
drop(self.repo);
if to.exists() {
log::warn!(target: "radicle", "Refusing to move from temporary directory '{}' because destination {rid} already exists. Removing the temporary directory.", self.path.display());
Self::remove(&path);
}
std::fs::rename(path, to)
}
fn remove(path: &PathBuf) {
if let Err(err) = std::fs::remove_dir_all(path) {
let path = path.display();
log::error!(target: "worker", "Failed to remove temporary directory '{path}': {err}");
}
}
}
impl AsRef<Repository> for TempRepository {
fn as_ref(&self) -> &Repository {
&self.repo
}
}