use bcode_session_models::SessionId;
use std::fs;
use std::path::Path;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum SessionStorageRecoveryError {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Lease(#[from] crate::lease::SessionLeaseError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SessionStorageRelocation {
Relocated,
BlockedByOwner,
DestinationConflict,
}
pub fn session_has_active_owner(
session_id: SessionId,
root: &Path,
) -> Result<bool, SessionStorageRecoveryError> {
crate::lease::active_session_owners(root, session_id)
.map(|owners| !owners.is_empty())
.map_err(Into::into)
}
pub fn relocate_session(
session_id: SessionId,
source_root: &Path,
destination_root: &Path,
) -> Result<SessionStorageRelocation, SessionStorageRecoveryError> {
let source = crate::db::session_dir_path(source_root, session_id);
let destination = crate::db::session_dir_path(destination_root, session_id);
if destination.exists() {
return Ok(SessionStorageRelocation::DestinationConflict);
}
let source_maintenance =
match crate::lease::acquire_session_maintenance_guard(source_root, session_id) {
Ok(guard) => guard,
Err(crate::lease::SessionLeaseError::OwnedByOtherDaemon { .. }) => {
return Ok(SessionStorageRelocation::BlockedByOwner);
}
Err(error) => return Err(error.into()),
};
let destination_maintenance =
crate::lease::acquire_session_maintenance_guard(destination_root, session_id)?;
if destination.exists() {
return Ok(SessionStorageRelocation::DestinationConflict);
}
fs::create_dir_all(destination_root)?;
fs::rename(source, destination)?;
drop(destination_maintenance);
drop(source_maintenance);
Ok(SessionStorageRelocation::Relocated)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn relocation_moves_once_without_overwriting_a_destination() {
let state = tempfile::tempdir().expect("state");
let source_root = state.path().join("source");
let destination_root = state.path().join("destination");
let session_id = SessionId::new();
let source = source_root.join(session_id.to_string());
let destination = destination_root.join(session_id.to_string());
fs::create_dir_all(&source).expect("source");
fs::write(source.join("session.db"), b"fixture").expect("fixture");
assert_eq!(
relocate_session(session_id, &source_root, &destination_root).expect("relocate"),
SessionStorageRelocation::Relocated
);
assert_eq!(
fs::read(destination.join("session.db")).expect("moved"),
b"fixture"
);
fs::create_dir_all(&source).expect("replacement source");
fs::write(source.join("other"), b"other").expect("other fixture");
assert_eq!(
relocate_session(session_id, &source_root, &destination_root).expect("conflict"),
SessionStorageRelocation::DestinationConflict
);
assert!(source.join("other").exists());
}
#[test]
fn relocation_reports_live_source_owner() {
let state = tempfile::tempdir().expect("state");
let source_root = state.path().join("source");
let destination_root = state.path().join("destination");
let session_id = SessionId::new();
let source = source_root.join(session_id.to_string());
fs::create_dir_all(&source).expect("source");
let _owner = crate::lease::acquire_session_lease(
&source_root,
session_id,
&crate::lease::SessionLeaseOwnerContext::default(),
)
.expect("owner");
assert_eq!(
relocate_session(session_id, &source_root, &destination_root).expect("blocked"),
SessionStorageRelocation::BlockedByOwner
);
assert!(source.exists());
}
}