use super::LockState;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StatInfo {
pub mode: u32,
pub size: u64,
pub mtime: i64,
pub ctime: i64,
pub dev: u64,
pub ino: u64,
}
impl StatInfo {
pub fn is_file(&self) -> bool {
self.mode & 0o170000 == 0o100000
}
pub fn is_dir(&self) -> bool {
self.mode & 0o170000 == 0o040000
}
pub fn is_symlink(&self) -> bool {
self.mode & 0o170000 == 0o120000
}
}
#[derive(Debug, Clone)]
pub struct DirEntryInfo {
pub basename: Vec<u8>,
pub kind: Option<crate::osutils::Kind>,
pub stat: StatInfo,
pub abspath: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransportError {
NotFound(String),
LockContention(String),
NotLocked,
AlreadyLocked,
Io {
kind: std::io::ErrorKind,
message: String,
},
Other(String),
}
impl From<std::io::Error> for TransportError {
fn from(e: std::io::Error) -> Self {
if e.kind() == std::io::ErrorKind::NotFound {
TransportError::NotFound(e.to_string())
} else {
TransportError::Io {
kind: e.kind(),
message: e.to_string(),
}
}
}
}
impl std::fmt::Display for TransportError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TransportError::NotFound(p) => write!(f, "No such file: {}", p),
TransportError::LockContention(p) => write!(f, "Lock contention: {}", p),
TransportError::NotLocked => write!(f, "Transport is not locked"),
TransportError::AlreadyLocked => write!(f, "Transport is already locked"),
TransportError::Io { kind, message } => {
write!(f, "I/O error ({:?}): {}", kind, message)
}
TransportError::Other(s) => write!(f, "Transport error: {}", s),
}
}
}
impl std::error::Error for TransportError {}
#[cfg(unix)]
pub fn bytes_to_path(b: &[u8]) -> Result<std::path::PathBuf, TransportError> {
use std::os::unix::ffi::OsStrExt;
Ok(std::path::PathBuf::from(std::ffi::OsStr::from_bytes(b)))
}
#[cfg(not(unix))]
pub fn bytes_to_path(b: &[u8]) -> Result<std::path::PathBuf, TransportError> {
String::from_utf8(b.to_vec())
.map(std::path::PathBuf::from)
.map_err(|e| TransportError::Other(e.to_string()))
}
#[cfg(unix)]
pub fn path_to_bytes(p: &std::path::Path) -> Vec<u8> {
use std::os::unix::ffi::OsStrExt;
p.as_os_str().as_bytes().to_vec()
}
#[cfg(not(unix))]
pub fn path_to_bytes(p: &std::path::Path) -> Vec<u8> {
p.to_string_lossy().into_owned().into_bytes()
}
pub fn stat_info_from_metadata(m: &std::fs::Metadata) -> Result<StatInfo, TransportError> {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
Ok(StatInfo {
mode: m.mode(),
size: m.size(),
mtime: m.mtime(),
ctime: m.ctime(),
dev: m.dev(),
ino: m.ino(),
})
}
#[cfg(not(unix))]
{
let _ = m;
Err(TransportError::Other(
"lstat unsupported on this platform".to_string(),
))
}
}
fn kind_from_stat(stat: &StatInfo) -> Option<crate::osutils::Kind> {
if stat.is_file() {
Some(crate::osutils::Kind::File)
} else if stat.is_dir() {
Some(crate::osutils::Kind::Directory)
} else if stat.is_symlink() {
Some(crate::osutils::Kind::Symlink)
} else {
None
}
}
pub fn lstat_path(abspath: &[u8]) -> Result<StatInfo, TransportError> {
let path = bytes_to_path(abspath)?;
let metadata = std::fs::symlink_metadata(&path)?;
stat_info_from_metadata(&metadata)
}
pub fn read_link_path(abspath: &[u8]) -> Result<Vec<u8>, TransportError> {
let path = bytes_to_path(abspath)?;
let target = std::fs::read_link(&path)?;
Ok(path_to_bytes(&target))
}
pub fn is_tree_reference_dir_path(abspath: &[u8]) -> Result<bool, TransportError> {
if abspath.is_empty() {
return Ok(false);
}
let path = bytes_to_path(abspath)?;
Ok(path.join(".bzr").is_dir())
}
pub fn list_dir_path(abspath: &[u8]) -> Result<Vec<DirEntryInfo>, TransportError> {
let path = bytes_to_path(abspath)?;
let entries = std::fs::read_dir(&path)?;
let mut out = Vec::new();
for entry in entries {
let entry = entry?;
let name = entry.file_name();
let basename = path_to_bytes(std::path::Path::new(&name));
let metadata = entry.metadata()?;
let stat = stat_info_from_metadata(&metadata)?;
let kind = kind_from_stat(&stat);
out.push(DirEntryInfo {
basename,
kind,
stat,
abspath: path_to_bytes(&entry.path()),
});
}
Ok(out)
}
pub trait Transport {
fn exists(&self) -> Result<bool, TransportError>;
fn lock_read(&mut self) -> Result<(), TransportError>;
fn lock_write(&mut self) -> Result<(), TransportError>;
fn unlock(&mut self) -> Result<(), TransportError>;
fn lock_state(&self) -> Option<LockState>;
fn read_all(&mut self) -> Result<Vec<u8>, TransportError>;
#[allow(clippy::len_without_is_empty)]
fn len(&mut self) -> Result<u64, TransportError> {
Ok(self.read_all()?.len() as u64)
}
fn read_at(&mut self, offset: u64, len: usize) -> Result<Vec<u8>, TransportError> {
let all = self.read_all()?;
let start = offset as usize;
if start >= all.len() {
return Ok(Vec::new());
}
let end = std::cmp::min(start + len, all.len());
Ok(all[start..end].to_vec())
}
fn write_all(&mut self, bytes: &[u8]) -> Result<(), TransportError>;
fn upgrade_to_write_lock(&mut self) -> Result<bool, TransportError> {
match self.lock_state() {
None => Err(TransportError::NotLocked),
Some(LockState::Write) => Err(TransportError::AlreadyLocked),
Some(LockState::Read) => Ok(false),
}
}
fn downgrade_to_read_lock(&mut self) -> Result<(), TransportError> {
match self.lock_state() {
None => Err(TransportError::NotLocked),
Some(LockState::Read) => Ok(()),
Some(LockState::Write) => Err(TransportError::Other(
"downgrade_to_read_lock not supported by this transport".into(),
)),
}
}
fn fdatasync(&mut self) -> Result<(), TransportError>;
fn lstat(&self, abspath: &[u8]) -> Result<StatInfo, TransportError>;
fn read_link(&self, abspath: &[u8]) -> Result<Vec<u8>, TransportError>;
fn is_tree_reference_dir(&self, abspath: &[u8]) -> Result<bool, TransportError>;
fn list_dir(&self, abspath: &[u8]) -> Result<Vec<DirEntryInfo>, TransportError>;
}