use crate::{error::Error, io::Stream};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiffKeyStatus {
Both,
A,
B,
}
impl From<subversion_sys::svn_hash_diff_key_status> for DiffKeyStatus {
fn from(status: subversion_sys::svn_hash_diff_key_status) -> Self {
match status {
subversion_sys::svn_hash_diff_key_status_svn_hash_diff_key_both => Self::Both,
subversion_sys::svn_hash_diff_key_status_svn_hash_diff_key_a => Self::A,
subversion_sys::svn_hash_diff_key_status_svn_hash_diff_key_b => Self::B,
_ => unreachable!("Invalid svn_hash_diff_key_status"),
}
}
}
pub fn read(
stream: &mut Stream,
terminator: Option<&str>,
) -> Result<HashMap<String, String>, Error<'static>> {
let pool = apr::Pool::new();
let raw_hash = unsafe { apr_sys::apr_hash_make(pool.as_mut_ptr()) };
let terminator_cstr = terminator
.map(std::ffi::CString::new)
.transpose()
.map_err(|_| Error::from_message("Invalid terminator string"))?;
unsafe {
let err = subversion_sys::svn_hash_read2(
raw_hash,
stream.as_mut_ptr(),
terminator_cstr
.as_ref()
.map_or(std::ptr::null(), |s| s.as_ptr()),
pool.as_mut_ptr(),
);
Error::from_raw(err)?;
}
let mut result = HashMap::new();
unsafe {
let mut hi = apr_sys::apr_hash_first(pool.as_mut_ptr(), raw_hash);
while !hi.is_null() {
let mut key_ptr: *const std::ffi::c_void = std::ptr::null();
let mut key_len: apr_sys::apr_ssize_t = 0;
let mut val_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
apr_sys::apr_hash_this(hi, &mut key_ptr, &mut key_len, &mut val_ptr);
let key_bytes = if key_len < 0 {
std::ffi::CStr::from_ptr(key_ptr as *const i8).to_bytes()
} else {
std::slice::from_raw_parts(key_ptr as *const u8, key_len as usize)
};
let key_str = String::from_utf8_lossy(key_bytes).to_string();
let value_ptr = val_ptr as *mut subversion_sys::svn_string_t;
let value_str = if value_ptr.is_null() {
String::new()
} else {
let svn_str = &*value_ptr;
if svn_str.data.is_null() || svn_str.len == 0 {
String::new()
} else {
let data_slice =
std::slice::from_raw_parts(svn_str.data as *const u8, svn_str.len as usize);
String::from_utf8_lossy(data_slice).into_owned()
}
};
result.insert(key_str, value_str);
hi = apr_sys::apr_hash_next(hi);
}
}
Ok(result)
}
pub fn write(
hash: &HashMap<String, String>,
stream: &mut Stream,
terminator: Option<&str>,
) -> Result<(), Error<'static>> {
let pool = apr::Pool::new();
let mut apr_hash = apr::hash::Hash::new(&pool);
let mut key_cstrings = Vec::new();
for (key, value) in hash.iter() {
let key_cstr = std::ffi::CString::new(key.as_str()).unwrap();
let value_bytes = value.as_bytes();
let svn_string = unsafe {
subversion_sys::svn_string_ncreate(
value_bytes.as_ptr() as *const i8,
value_bytes.len(),
pool.as_mut_ptr(),
)
};
unsafe {
apr_sys::apr_hash_set(
apr_hash.as_mut_ptr(),
key_cstr.as_ptr() as *const std::ffi::c_void,
apr_sys::APR_HASH_KEY_STRING as apr_sys::apr_ssize_t,
svn_string as *const std::ffi::c_void,
);
}
key_cstrings.push(key_cstr); }
let terminator_cstr = terminator
.map(std::ffi::CString::new)
.transpose()
.map_err(|_| Error::from_message("Invalid terminator string"))?;
unsafe {
let err = subversion_sys::svn_hash_write2(
apr_hash.as_mut_ptr(),
stream.as_mut_ptr(),
terminator_cstr
.as_ref()
.map_or(std::ptr::null(), |s| s.as_ptr()),
pool.as_mut_ptr(),
);
Error::from_raw(err)?;
}
Ok(())
}
pub fn read_incremental(
stream: &mut Stream,
terminator: Option<&str>,
) -> Result<HashMap<String, String>, Error<'static>> {
let pool = apr::Pool::new();
let raw_hash = unsafe { apr_sys::apr_hash_make(pool.as_mut_ptr()) };
let terminator_cstr = terminator
.map(std::ffi::CString::new)
.transpose()
.map_err(|_| Error::from_message("Invalid terminator string"))?;
unsafe {
let err = subversion_sys::svn_hash_read_incremental(
raw_hash,
stream.as_mut_ptr(),
terminator_cstr
.as_ref()
.map_or(std::ptr::null(), |s| s.as_ptr()),
pool.as_mut_ptr(),
);
Error::from_raw(err)?;
}
let mut result = HashMap::new();
unsafe {
let mut hi = apr_sys::apr_hash_first(pool.as_mut_ptr(), raw_hash);
while !hi.is_null() {
let mut key_ptr: *const std::ffi::c_void = std::ptr::null();
let mut key_len: apr_sys::apr_ssize_t = 0;
let mut val_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
apr_sys::apr_hash_this(hi, &mut key_ptr, &mut key_len, &mut val_ptr);
let key_bytes = if key_len < 0 {
std::ffi::CStr::from_ptr(key_ptr as *const i8).to_bytes()
} else {
std::slice::from_raw_parts(key_ptr as *const u8, key_len as usize)
};
let key_str = String::from_utf8_lossy(key_bytes).to_string();
let value_ptr = val_ptr as *mut subversion_sys::svn_string_t;
let value_str = if value_ptr.is_null() {
String::new()
} else {
let svn_str = &*value_ptr;
if svn_str.data.is_null() || svn_str.len == 0 {
String::new()
} else {
let data_slice =
std::slice::from_raw_parts(svn_str.data as *const u8, svn_str.len as usize);
String::from_utf8_lossy(data_slice).into_owned()
}
};
result.insert(key_str, value_str);
hi = apr_sys::apr_hash_next(hi);
}
}
Ok(result)
}
pub fn write_incremental(
hash: &HashMap<String, String>,
oldhash: &HashMap<String, String>,
stream: &mut Stream,
terminator: Option<&str>,
) -> Result<(), Error<'static>> {
let pool = apr::Pool::new();
let mut apr_hash = apr::hash::Hash::new(&pool);
let mut key_cstrings = Vec::new();
for (key, value) in hash.iter() {
let key_cstr = std::ffi::CString::new(key.as_str()).unwrap();
let value_bytes = value.as_bytes();
let svn_string = unsafe {
subversion_sys::svn_string_ncreate(
value_bytes.as_ptr() as *const i8,
value_bytes.len(),
pool.as_mut_ptr(),
)
};
unsafe {
apr_sys::apr_hash_set(
apr_hash.as_mut_ptr(),
key_cstr.as_ptr() as *const std::ffi::c_void,
apr_sys::APR_HASH_KEY_STRING as apr_sys::apr_ssize_t,
svn_string as *const std::ffi::c_void,
);
}
key_cstrings.push(key_cstr);
}
let mut apr_oldhash = apr::hash::Hash::new(&pool);
let mut old_key_cstrings = Vec::new();
for (key, value) in oldhash.iter() {
let key_cstr = std::ffi::CString::new(key.as_str()).unwrap();
let value_bytes = value.as_bytes();
let svn_string = unsafe {
subversion_sys::svn_string_ncreate(
value_bytes.as_ptr() as *const i8,
value_bytes.len(),
pool.as_mut_ptr(),
)
};
unsafe {
apr_sys::apr_hash_set(
apr_oldhash.as_mut_ptr(),
key_cstr.as_ptr() as *const std::ffi::c_void,
apr_sys::APR_HASH_KEY_STRING as apr_sys::apr_ssize_t,
svn_string as *const std::ffi::c_void,
);
}
old_key_cstrings.push(key_cstr);
}
let terminator_cstr = terminator
.map(std::ffi::CString::new)
.transpose()
.map_err(|_| Error::from_message("Invalid terminator string"))?;
unsafe {
let err = subversion_sys::svn_hash_write_incremental(
apr_hash.as_mut_ptr(),
apr_oldhash.as_mut_ptr(),
stream.as_mut_ptr(),
terminator_cstr
.as_ref()
.map_or(std::ptr::null(), |s| s.as_ptr()),
pool.as_mut_ptr(),
);
Error::from_raw(err)?;
}
Ok(())
}
pub fn diff<F>(
hash_a: &HashMap<String, String>,
hash_b: &HashMap<String, String>,
diff_func: F,
) -> Result<(), Error<'static>>
where
F: FnMut(&str, DiffKeyStatus) -> Result<(), Error<'static>>,
{
let pool = apr::Pool::new();
let mut apr_hash_a = apr::hash::Hash::new(&pool);
let mut keys_a_cstrings = Vec::new();
for (key, value) in hash_a.iter() {
let key_cstr = std::ffi::CString::new(key.as_str()).unwrap();
let value_bytes = value.as_bytes();
let svn_string = unsafe {
subversion_sys::svn_string_ncreate(
value_bytes.as_ptr() as *const i8,
value_bytes.len(),
pool.as_mut_ptr(),
)
};
unsafe {
apr_sys::apr_hash_set(
apr_hash_a.as_mut_ptr(),
key_cstr.as_ptr() as *const std::ffi::c_void,
apr_sys::APR_HASH_KEY_STRING as apr_sys::apr_ssize_t,
svn_string as *const std::ffi::c_void,
);
}
keys_a_cstrings.push(key_cstr);
}
let mut apr_hash_b = apr::hash::Hash::new(&pool);
let mut keys_b_cstrings = Vec::new();
for (key, value) in hash_b.iter() {
let key_cstr = std::ffi::CString::new(key.as_str()).unwrap();
let value_bytes = value.as_bytes();
let svn_string = unsafe {
subversion_sys::svn_string_ncreate(
value_bytes.as_ptr() as *const i8,
value_bytes.len(),
pool.as_mut_ptr(),
)
};
unsafe {
apr_sys::apr_hash_set(
apr_hash_b.as_mut_ptr(),
key_cstr.as_ptr() as *const std::ffi::c_void,
apr_sys::APR_HASH_KEY_STRING as apr_sys::apr_ssize_t,
svn_string as *const std::ffi::c_void,
);
}
keys_b_cstrings.push(key_cstr);
}
extern "C" fn diff_callback(
key: *const std::ffi::c_void,
klen: apr_sys::apr_ssize_t,
status: subversion_sys::svn_hash_diff_key_status,
baton: *mut std::ffi::c_void,
) -> *mut subversion_sys::svn_error_t {
let callback = unsafe {
&mut **(baton as *mut Box<dyn FnMut(&str, DiffKeyStatus) -> Result<(), Error<'static>>>)
};
let key_str = if klen < 0 {
let key_cstr = unsafe { std::ffi::CStr::from_ptr(key as *const i8) };
key_cstr.to_string_lossy().into_owned()
} else {
let key_slice = unsafe { std::slice::from_raw_parts(key as *const u8, klen as usize) };
String::from_utf8_lossy(key_slice).into_owned()
};
match callback(&key_str, DiffKeyStatus::from(status)) {
Ok(()) => std::ptr::null_mut(),
Err(e) => unsafe { e.into_raw() },
}
}
let boxed_callback: Box<Box<dyn FnMut(&str, DiffKeyStatus) -> Result<(), Error<'static>>>> =
Box::new(Box::new(diff_func));
let baton = Box::into_raw(boxed_callback) as *mut std::ffi::c_void;
unsafe {
let err = subversion_sys::svn_hash_diff(
apr_hash_a.as_mut_ptr(),
apr_hash_b.as_mut_ptr(),
Some(diff_callback),
baton,
pool.as_mut_ptr(),
);
drop(Box::from_raw(
baton as *mut Box<dyn FnMut(&str, DiffKeyStatus) -> Result<(), Error<'static>>>,
));
Error::from_raw(err)?;
}
Ok(())
}
pub fn from_cstring_keys(keys: &[&str]) -> Result<HashMap<String, String>, Error<'static>> {
let pool = apr::Pool::new();
let mut keys_array = apr::tables::TypedArray::<*const i8>::new(&pool, keys.len() as i32);
let key_cstrings: Vec<std::ffi::CString> = keys
.iter()
.map(|&s| std::ffi::CString::new(s))
.collect::<Result<Vec<_>, _>>()
.map_err(|_| Error::from_message("Invalid key string"))?;
for cstring in key_cstrings.iter() {
keys_array.push(cstring.as_ptr());
}
let mut hash_ptr: *mut apr_sys::apr_hash_t = std::ptr::null_mut();
unsafe {
let err = subversion_sys::svn_hash_from_cstring_keys(
&mut hash_ptr,
keys_array.as_ptr(),
pool.as_mut_ptr(),
);
Error::from_raw(err)?;
}
let mut result = HashMap::new();
for &key in keys {
result.insert(key.to_string(), String::new());
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_cstring_keys() {
let keys = ["key1", "key2", "key3"];
let hash = from_cstring_keys(&keys).unwrap();
assert_eq!(hash.len(), 3);
assert!(hash.contains_key("key1"));
assert!(hash.contains_key("key2"));
assert!(hash.contains_key("key3"));
assert_eq!(hash.get("key1"), Some(&String::new()));
}
#[test]
fn test_write_read_roundtrip() {
let mut original_hash = HashMap::new();
original_hash.insert("key1".to_string(), "value1".to_string());
original_hash.insert("key2".to_string(), "value2".to_string());
original_hash.insert("key3".to_string(), "value3".to_string());
let mut stringbuf = crate::io::StringBuf::new();
{
let mut write_stream = Stream::from_stringbuf(&mut stringbuf);
write(&original_hash, &mut write_stream, Some("END")).unwrap();
}
let contents_string = stringbuf.to_string();
let mut read_stream = Stream::from(contents_string);
let read_hash = read(&mut read_stream, Some("END")).unwrap();
assert_eq!(original_hash.len(), read_hash.len());
for (key, value) in &original_hash {
assert_eq!(read_hash.get(key), Some(value));
}
}
#[test]
fn test_hash_diff() {
let mut hash_a = HashMap::new();
hash_a.insert("common".to_string(), "value1".to_string());
hash_a.insert("only_a".to_string(), "value2".to_string());
let mut hash_b = HashMap::new();
hash_b.insert("common".to_string(), "value1".to_string());
hash_b.insert("only_b".to_string(), "value3".to_string());
let mut diffs = Vec::new();
diff(&hash_a, &hash_b, |key, status| {
diffs.push((key.to_string(), status));
Ok(())
})
.unwrap();
assert_eq!(diffs.len(), 3);
let keys: Vec<String> = diffs.iter().map(|(k, _)| k.clone()).collect();
assert!(keys.contains(&"common".to_string()));
assert!(keys.contains(&"only_a".to_string()));
assert!(keys.contains(&"only_b".to_string()));
}
#[test]
fn test_incremental_write_read() {
let mut old_hash = HashMap::new();
old_hash.insert("key1".to_string(), "old_value1".to_string());
old_hash.insert("key2".to_string(), "old_value2".to_string());
old_hash.insert("to_delete".to_string(), "delete_me".to_string());
let mut new_hash = HashMap::new();
new_hash.insert("key1".to_string(), "new_value1".to_string()); new_hash.insert("key2".to_string(), "old_value2".to_string()); new_hash.insert("new_key".to_string(), "new_value".to_string());
let mut stringbuf = crate::io::StringBuf::new();
{
let mut write_stream = Stream::from_stringbuf(&mut stringbuf);
write_incremental(&new_hash, &old_hash, &mut write_stream, Some("END")).unwrap();
}
let contents_string = stringbuf.to_string();
let mut read_stream = Stream::from(contents_string);
let result_hash = read_incremental(&mut read_stream, Some("END")).unwrap();
assert!(!result_hash.is_empty());
}
}
pub struct DirentHash<'a> {
inner: apr::hash::TypedHash<'a, subversion_sys::svn_dirent_t>,
}
impl<'a> DirentHash<'a> {
pub unsafe fn from_ptr(ptr: *mut apr_sys::apr_hash_t) -> Self {
Self {
inner: apr::hash::TypedHash::<subversion_sys::svn_dirent_t>::from_ptr(ptr),
}
}
pub fn to_hashmap(&self) -> HashMap<String, crate::DirEntry> {
self.inner
.iter()
.map(|(k, v)| {
let key = String::from_utf8_lossy(k).into_owned();
let dirent = crate::DirEntry::from_raw(v as *const _ as *mut _);
(key, dirent)
})
.collect()
}
pub fn iter(&self) -> impl Iterator<Item = (&str, crate::DirEntry)> + '_ {
self.inner.iter().map(|(k, v)| {
let path = std::str::from_utf8(k).unwrap_or("");
let dirent = crate::DirEntry::from_raw(v as *const _ as *mut _);
(path, dirent)
})
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn len(&self) -> usize {
self.inner.len()
}
}
pub struct PathChangeHash<'a> {
inner: apr::hash::Hash<'a>,
}
impl<'a> PathChangeHash<'a> {
pub unsafe fn from_ptr(ptr: *mut apr_sys::apr_hash_t) -> Self {
Self {
inner: apr::hash::Hash::from_ptr(ptr),
}
}
pub fn to_hashmap(&self) -> HashMap<String, crate::fs::FsPathChange> {
let mut result = HashMap::new();
for (k, v) in self.inner.iter() {
let path = String::from_utf8_lossy(k).into_owned();
let change =
crate::fs::FsPathChange::from_raw(v as *mut subversion_sys::svn_fs_path_change2_t);
result.insert(path, change);
}
result
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn len(&self) -> usize {
self.inner.len()
}
}
pub struct FsDirentHash<'a> {
inner: apr::hash::Hash<'a>,
}
impl<'a> FsDirentHash<'a> {
pub unsafe fn from_ptr(ptr: *mut apr_sys::apr_hash_t) -> Self {
Self {
inner: apr::hash::Hash::from_ptr(ptr),
}
}
pub fn to_hashmap(
&self,
pool: apr::SharedPool<'static>,
) -> HashMap<String, crate::fs::FsDirEntry> {
let mut result = HashMap::new();
for (k, v) in self.inner.iter() {
let name = String::from_utf8_lossy(k).into_owned();
let entry = crate::fs::FsDirEntry::from_raw(
v as *mut subversion_sys::svn_fs_dirent_t,
pool.clone(),
);
result.insert(name, entry);
}
result
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn len(&self) -> usize {
self.inner.len()
}
}