use serde::{Deserialize, Serialize};
use std::{
borrow::Cow,
ffi::{CStr, CString, OsStr, OsString},
path::{Path, PathBuf},
};
#[cfg(unix)]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use crate::{Error, Result, Type};
#[derive(Type, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Clone, Ord, PartialOrd)]
#[zbus(signature = "ay")]
pub struct FilePath<'f>(Cow<'f, CStr>);
impl<'f> FilePath<'f> {
pub fn new(cow: Cow<'f, CStr>) -> Self {
Self(cow)
}
pub fn as_c_str(&self) -> &CStr {
&self.0
}
pub fn to_string_lossy(&self) -> Cow<'_, str> {
self.0.to_string_lossy()
}
}
impl From<CString> for FilePath<'_> {
fn from(value: CString) -> Self {
FilePath(Cow::Owned(value))
}
}
impl<'f> From<&'f CString> for FilePath<'f> {
fn from(value: &'f CString) -> Self {
FilePath(Cow::Borrowed(value.as_c_str()))
}
}
impl<'f> From<&'f OsStr> for FilePath<'f> {
fn from(value: &'f OsStr) -> FilePath<'f> {
FilePath(bytes_with_null(value.as_encoded_bytes()))
}
}
impl<'f> From<&'f OsString> for FilePath<'f> {
fn from(value: &'f OsString) -> FilePath<'f> {
FilePath(bytes_with_null(value.as_encoded_bytes()))
}
}
impl From<OsString> for FilePath<'_> {
fn from(value: OsString) -> Self {
FilePath(vec_to_cstr(value.as_encoded_bytes().to_vec()))
}
}
impl<'f> From<&'f PathBuf> for FilePath<'f> {
fn from(value: &'f PathBuf) -> FilePath<'f> {
FilePath::from(value.as_os_str())
}
}
impl From<PathBuf> for FilePath<'_> {
fn from(value: PathBuf) -> FilePath<'static> {
FilePath::from(OsString::from(value))
}
}
impl<'f> From<&'f Path> for FilePath<'f> {
fn from(value: &'f Path) -> Self {
Self::from(value.as_os_str())
}
}
impl<'f> From<&'f CStr> for FilePath<'f> {
fn from(value: &'f CStr) -> Self {
Self(Cow::Borrowed(value))
}
}
impl<'f> From<&'f str> for FilePath<'f> {
fn from(value: &'f str) -> Self {
Self::from(OsStr::new(value))
}
}
impl<'f> AsRef<FilePath<'f>> for FilePath<'f> {
fn as_ref(&self) -> &FilePath<'f> {
self
}
}
impl AsRef<CStr> for FilePath<'_> {
fn as_ref(&self) -> &CStr {
self.as_c_str()
}
}
impl<'f> TryFrom<&'f FilePath<'f>> for &'f OsStr {
type Error = Error;
fn try_from(value: &'f FilePath<'f>) -> Result<Self> {
#[cfg(unix)]
{
Ok(OsStr::from_bytes(value.0.to_bytes()))
}
#[cfg(not(unix))]
{
value.0.to_str().map(OsStr::new).map_err(Error::Utf8)
}
}
}
impl<'f> TryFrom<&'f FilePath<'f>> for &'f Path {
type Error = Error;
fn try_from(value: &'f FilePath<'f>) -> Result<Self> {
<&OsStr>::try_from(value).map(Path::new)
}
}
impl TryFrom<FilePath<'_>> for OsString {
type Error = Error;
fn try_from(value: FilePath<'_>) -> Result<Self> {
#[cfg(unix)]
{
Ok(OsString::from_vec(value.0.into_owned().into_bytes()))
}
#[cfg(not(unix))]
{
<&OsStr>::try_from(&value).map(OsString::from)
}
}
}
impl TryFrom<FilePath<'_>> for PathBuf {
type Error = Error;
fn try_from(value: FilePath<'_>) -> Result<Self> {
OsString::try_from(value).map(PathBuf::from)
}
}
fn vec_to_cstr(mut bytes: Vec<u8>) -> Cow<'static, CStr> {
if let Some(pos) = bytes.iter().position(|&b| b == 0) {
bytes.truncate(pos + 1);
} else {
bytes.push(0);
}
Cow::Owned(CString::from_vec_with_nul(bytes).unwrap())
}
fn bytes_with_null(bytes: &[u8]) -> Cow<'_, CStr> {
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
return Cow::Borrowed(cstr);
}
Cow::Owned(CString::new(bytes).unwrap())
}
#[cfg(test)]
mod file_path_test {
use super::*;
use crate::Signature;
use std::path::{Path, PathBuf};
#[test]
fn from_test() {
let path = Path::new("/hello/world");
let path_buf = PathBuf::from(path);
let osstr = OsStr::new("/hello/world");
let os_string = OsString::from("/hello/world");
let cstr = CStr::from_bytes_until_nul("/hello/world\0".as_bytes()).unwrap_or_default();
let cstring = CString::new("/hello/world").unwrap_or_default();
let p1 = FilePath::from(path);
let p2 = FilePath::from(path_buf);
let p3 = FilePath::from(osstr);
let p4 = FilePath::from(os_string);
let p5 = FilePath::from(cstr);
let p6 = FilePath::from(cstring);
let p7 = FilePath::from("/hello/world");
assert_eq!(p1, p2);
assert_eq!(p2, p3);
assert_eq!(p3, p4);
assert_eq!(p4, p5);
assert_eq!(p5, p6);
assert_eq!(p5, p7);
}
#[test]
fn as_c_str_test() {
let bytes = c"/hello/\xff\xfe/world";
let file_path = FilePath::from(bytes);
assert_eq!(file_path.as_c_str(), bytes);
assert_eq!(<FilePath<'_> as AsRef<CStr>>::as_ref(&file_path), bytes);
}
#[test]
fn filepath_signature() {
assert_eq!(
&Signature::static_array(&Signature::U8),
FilePath::SIGNATURE
);
}
#[test]
fn try_into_test() {
let path = Path::new("/hello/world");
let file_path = FilePath::from(path);
assert_eq!(<&OsStr>::try_from(&file_path).unwrap(), path.as_os_str());
assert_eq!(<&Path>::try_from(&file_path).unwrap(), path);
assert_eq!(
OsString::try_from(file_path.clone()).unwrap(),
path.as_os_str()
);
assert_eq!(PathBuf::try_from(file_path).unwrap(), path);
}
#[cfg(unix)]
#[test]
fn non_utf8_try_into_test() {
let os_str = OsStr::from_bytes(b"/hello/\xff\xfe/world");
let file_path = FilePath::from(os_str);
assert_eq!(<&OsStr>::try_from(&file_path).unwrap(), os_str);
assert_eq!(<&Path>::try_from(&file_path).unwrap(), Path::new(os_str));
assert_eq!(OsString::try_from(file_path.clone()).unwrap(), os_str);
assert_eq!(PathBuf::try_from(file_path).unwrap(), Path::new(os_str));
}
#[cfg(not(unix))]
#[test]
fn non_utf8_try_into_test() {
let file_path = FilePath::from(c"/hello/\xff\xfe/world");
assert!(matches!(
<&OsStr>::try_from(&file_path),
Err(Error::Utf8(_))
));
assert!(matches!(<&Path>::try_from(&file_path), Err(Error::Utf8(_))));
assert!(matches!(
OsString::try_from(file_path.clone()),
Err(Error::Utf8(_))
));
assert!(matches!(PathBuf::try_from(file_path), Err(Error::Utf8(_))));
}
#[test]
fn vec_nul_termination() {
fn call_vec_to_cstr(v: Vec<u8>) -> Cow<'static, CStr> {
vec_to_cstr(v)
}
let v1 = vec![];
let v2 = vec![0x0];
let v3 = vec![0x1, 0x2, 0x0];
let v4 = vec![0x0, 0x0];
let v5 = vec![0x1, 0x0, 0x2, 0x0];
assert_eq!(
Cow::Borrowed(CStr::from_bytes_with_nul(&[0x0]).unwrap()),
call_vec_to_cstr(v1)
);
assert_eq!(
Cow::Borrowed(CStr::from_bytes_with_nul(&[0x0]).unwrap()),
call_vec_to_cstr(v2)
);
assert_eq!(
Cow::Borrowed(CStr::from_bytes_with_nul(&[0x1, 0x2, 0x0]).unwrap()),
call_vec_to_cstr(v3)
);
assert_eq!(
Cow::Borrowed(CStr::from_bytes_with_nul(&[0x0]).unwrap()),
call_vec_to_cstr(v4)
);
assert_eq!(
Cow::Borrowed(CStr::from_bytes_with_nul(&[0x1, 0x0]).unwrap()),
call_vec_to_cstr(v5)
);
}
}