use std::path::Path;
use tailtalk_packets::afp::AfpError;
use tracing::error;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct IconKey {
pub creator: [u8; 4],
pub file_type: [u8; 4],
pub icon_type: u8,
}
impl IconKey {
pub fn to_bytes(&self) -> [u8; 9] {
let mut bytes = [0u8; 9];
bytes[0..4].copy_from_slice(&self.creator);
bytes[4..8].copy_from_slice(&self.file_type);
bytes[8] = self.icon_type;
bytes
}
}
pub struct DesktopDatabase {
pub dt_ref_num: u16,
db: sled::Db,
}
impl DesktopDatabase {
pub fn new(volume_root: &Path, dt_ref_num: u16) -> Result<Self, AfpError> {
let db_path = volume_root.join(".tailtalk").join("desktop.db");
if let Some(parent) = db_path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
error!(
"Failed to create Desktop Database directory at {:?}: {}",
parent, e
);
AfpError::AccessDenied
})?;
}
let db = sled::open(&db_path).map_err(|e| {
error!("Failed to open Desktop Database at {:?}: {}", db_path, e);
AfpError::AccessDenied
})?;
Ok(Self { dt_ref_num, db })
}
pub fn add_icon(
&self,
creator: [u8; 4],
file_type: [u8; 4],
icon_type: u8,
icon_data: &[u8],
) -> Result<(), AfpError> {
let key = IconKey {
creator,
file_type,
icon_type,
};
let tree = self.db.open_tree(b"icons").map_err(|e| {
error!("Failed to open 'icons' tree: {}", e);
AfpError::AccessDenied
})?;
tree.insert(key.to_bytes(), icon_data).map_err(|e| {
error!("Failed to insert icon: {}", e);
AfpError::AccessDenied
})?;
Ok(())
}
pub fn get_icon(
&self,
creator: [u8; 4],
file_type: [u8; 4],
icon_type: u8,
_size: u16,
) -> Result<Vec<u8>, AfpError> {
let key = IconKey {
creator,
file_type,
icon_type,
};
let tree = self.db.open_tree(b"icons").map_err(|e| {
error!("Failed to open 'icons' tree: {}", e);
AfpError::AccessDenied
})?;
if let Some(data) = tree.get(key.to_bytes()).map_err(|e| {
error!("Failed to get icon: {}", e);
AfpError::AccessDenied
})? {
Ok(data.to_vec())
} else {
Err(AfpError::ItemNotFound)
}
}
pub fn get_icon_info(
&self,
creator: [u8; 4],
_icon_type: u16,
) -> Result<(u32, u32, u16), AfpError> {
let tree = self.db.open_tree(b"icons").map_err(|e| {
error!("Failed to open 'icons' tree: {}", e);
AfpError::AccessDenied
})?;
for result in tree.iter() {
if let Ok((key, value)) = result
&& key.len() == 9
{
let mut key_creator = [0u8; 4];
key_creator.copy_from_slice(&key[0..4]);
if key_creator == creator {
let mut file_type = [0u8; 4];
file_type.copy_from_slice(&key[4..8]);
let icon_tag = 0; let file_type_u32 = u32::from_be_bytes(file_type);
let size = value.len() as u16;
return Ok((icon_tag, file_type_u32, size));
}
}
}
Err(AfpError::ItemNotFound)
}
pub fn set_comment(&self, node_id: u32, comment: &[u8]) -> Result<(), AfpError> {
let tree = self.db.open_tree(b"comments").map_err(|e| {
error!("Failed to open 'comments' tree: {}", e);
AfpError::AccessDenied
})?;
tree.insert(node_id.to_be_bytes(), comment).map_err(|e| {
error!("Failed to insert comment: {}", e);
AfpError::AccessDenied
})?;
tracing::info!(
"Set comment of length {} for node {}",
comment.len(),
node_id
);
Ok(())
}
pub fn get_comment(&self, node_id: u32) -> Result<Vec<u8>, AfpError> {
let tree = self.db.open_tree(b"comments").map_err(|e| {
error!("Failed to open 'comments' tree: {}", e);
AfpError::AccessDenied
})?;
tracing::info!("Get comment for node {}", node_id);
if let Some(data) = tree.get(node_id.to_be_bytes()).map_err(|e| {
error!("Failed to get comment: {}", e);
AfpError::AccessDenied
})? {
Ok(data.to_vec())
} else {
Err(AfpError::ItemNotFound)
}
}
pub fn remove_comment(&self, node_id: u32) -> Result<(), AfpError> {
let tree = self.db.open_tree(b"comments").map_err(|e| {
error!("Failed to open 'comments' tree: {}", e);
AfpError::AccessDenied
})?;
tree.remove(node_id.to_be_bytes()).map_err(|e| {
error!("Failed to remove comment: {}", e);
AfpError::AccessDenied
})?;
Ok(())
}
pub fn add_appl(
&self,
creator: [u8; 4],
tag: u32,
directory_id: u32,
path: &str,
) -> Result<(), AfpError> {
let tree = self.db.open_tree(b"appls").map_err(|e| {
error!("Failed to open 'appls' tree: {}", e);
AfpError::AccessDenied
})?;
let mut key = [0u8; 8];
key[0..4].copy_from_slice(&creator);
key[4..8].copy_from_slice(&tag.to_be_bytes());
let path_bytes = path.as_bytes();
let mut value = Vec::with_capacity(5 + path_bytes.len());
value.extend_from_slice(&directory_id.to_be_bytes());
value.push(path_bytes.len() as u8);
value.extend_from_slice(path_bytes);
tree.insert(key, value).map_err(|e| {
error!("Failed to insert APPL: {}", e);
AfpError::AccessDenied
})?;
Ok(())
}
pub fn remove_appl(
&self,
creator: [u8; 4],
directory_id: u32,
path: &str,
) -> Result<(), AfpError> {
let tree = self.db.open_tree(b"appls").map_err(|e| {
error!("Failed to open 'appls' tree: {}", e);
AfpError::AccessDenied
})?;
let dir_bytes = directory_id.to_be_bytes();
let path_bytes = path.as_bytes();
for result in tree.scan_prefix(creator) {
let (key, value) = result.map_err(|e| {
error!("Failed to scan APPLs: {}", e);
AfpError::AccessDenied
})?;
if value.len() >= 5 {
let path_len = value[4] as usize;
if value[0..4] == dir_bytes
&& value.len() >= 5 + path_len
&& &value[5..5 + path_len] == path_bytes
{
tree.remove(&key).map_err(|e| {
error!("Failed to remove APPL: {}", e);
AfpError::AccessDenied
})?;
return Ok(());
}
}
}
Err(AfpError::ItemNotFound)
}
pub fn get_appl(
&self,
creator: [u8; 4],
index: u16,
) -> Result<(u32, u32, String), AfpError> {
let tree = self.db.open_tree(b"appls").map_err(|e| {
error!("Failed to open 'appls' tree: {}", e);
AfpError::AccessDenied
})?;
let target = index.saturating_sub(1) as usize;
for (i, result) in tree.scan_prefix(creator).enumerate() {
let (key, value) = result.map_err(|e| {
error!("Failed to scan APPLs: {}", e);
AfpError::AccessDenied
})?;
if i == target {
if key.len() < 8 || value.len() < 5 {
return Err(AfpError::ItemNotFound);
}
let tag = u32::from_be_bytes(key[4..8].try_into().unwrap());
let directory_id = u32::from_be_bytes(value[0..4].try_into().unwrap());
let path_len = value[4] as usize;
let path = if value.len() >= 5 + path_len {
String::from_utf8_lossy(&value[5..5 + path_len]).into_owned()
} else {
String::new()
};
return Ok((tag, directory_id, path));
}
}
Err(AfpError::ItemNotFound)
}
}