use serde::{Deserialize, Serialize}; use shared_constants::bucket::PREFIX_BUCKET_BIND; use space_system::{Space, SpaceError}; use std::borrow::Borrow; use std::cmp::Ordering; use std::fmt; use std::fs::ReadDir; use std::io::ErrorKind::NotFound; use std::ops::{Deref, DerefMut}; use crate::{AsyncBucketTransferProtocol, Bucket}; #[cfg(test)] mod test; /// Represents a binding between a bucket and a URL. /// /// `BucketBind` is a newtype wrapper around a `String` that stores a URL /// associated with a bucket. It provides convenient access to the underlying /// URL string through `Deref`, `DerefMut`, `Borrow`, and `Display` trait /// implementations. #[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone)] pub struct BucketBind { /// The index of the bucket bind index: u8, /// The URL associated with the bucket bind. url: String, } impl BucketBind { /// Creates a new `BucketBind` with the given URL. fn new(index: u8, url: impl Into) -> Self { Self { index, url: url.into(), } } /// Returns the index of the bucket bind. pub fn get_index(&self) -> u8 { self.index } /// Returns a reference to the URL of the bucket bind. pub fn get_url(&self) -> &str { &self.url } } /// Reads all bucket bind records from the space. /// /// This function traverses the root directory of the specified space, filters files /// that start with a specific prefix (`PREFIX_BUCKET_BIND`), parses the index and URL /// from each binding record, and returns them as `BucketBind` objects. pub fn read_bucket_binds( space: &Space>, ) -> Result, SpaceError> { // Fixed prefix for bucket bind filenames const PREFIX: &str = PREFIX_BUCKET_BIND; // Open a read stream for the space root directory let reader: ReadDir = space.read_dir(".")?; let mut binds = Vec::new(); // Loop through each entry in the directory for entry in reader { let entry = entry?; let file_name = entry.file_name(); let name = file_name.to_string_lossy().to_string(); // Only process files starting with the bind prefix if let Some(suffix) = name.strip_prefix(PREFIX) { // Extract the part after the prefix as the index string // Attempt to parse the suffix as a u8 index value if let Ok(index) = suffix.parse::() { // Read the file content as the URL let content = space.read_to_string(&name)?; let url = content.trim().to_string(); // Add the parsed binding record to the list binds.push(BucketBind::new(index, url)); } } } // Sort by index before returning binds.sort(); Ok(binds) } /// Writes a bucket bind record to the space. /// /// This function creates or updates a binding between a bucket and a URL /// at the specified index. It writes the URL content to a file named /// with the prefix `PREFIX_BUCKET_BIND` followed by the zero-padded index. pub fn write_bucket_bind( space: &Space>, idx: u8, url: &str, ) -> Result<(), SpaceError> { const PREFIX: &str = PREFIX_BUCKET_BIND; let file_name = format!("{}{:03}", PREFIX, idx); space.write(&file_name, url.trim()) } /// Reads a single bucket bind record from the space by index. /// /// This function looks for a file named with the prefix `PREFIX_BUCKET_BIND` /// followed by the zero-padded index, reads its content as a URL, and returns /// the corresponding `BucketBind`. Returns `None` if the file does not exist. pub fn read_bucket_bind( space: &Space>, idx: u8, ) -> Result, SpaceError> { const PREFIX: &str = PREFIX_BUCKET_BIND; let file_name = format!("{}{:03}", PREFIX, idx); match space.read_to_string(&file_name) { Ok(content) => { let url = content.trim().to_string(); Ok(Some(BucketBind::new(idx, url))) } Err(SpaceError::Io(err)) => { if err.kind() == NotFound { Ok(None) } else { Err(SpaceError::Io(err)) } } Err(e) => Err(e), } } /// Checks whether a bucket bind record exists at the given index. /// /// Returns `true` if a file named with the prefix `PREFIX_BUCKET_BIND` followed /// by the zero-padded index exists in the space, `false` otherwise. pub fn check_bucket_bind_exists( space: &Space>, idx: u8, ) -> Result { const PREFIX: &str = PREFIX_BUCKET_BIND; let file_name = format!("{}{:03}", PREFIX, idx); match space.read_to_string(&file_name) { Ok(_) => Ok(true), Err(SpaceError::Io(err)) => { if err.kind() == NotFound { Ok(false) } else { Err(SpaceError::Io(err)) } } Err(e) => Err(e), } } /// Removes a bucket bind record from the space by index. /// /// This function deletes the file named with the prefix `PREFIX_BUCKET_BIND` /// followed by the zero-padded index from the space. Returns `Ok(())` if the /// deletion succeeds, or an error if the operation fails (including if the /// file does not exist). pub fn remove_bucket_bind( space: &Space>, idx: u8, ) -> Result<(), SpaceError> { const PREFIX: &str = PREFIX_BUCKET_BIND; let file_name = format!("{}{:03}", PREFIX, idx); space.remove_file(&file_name) } impl Ord for BucketBind { fn cmp(&self, other: &Self) -> Ordering { self.index.cmp(&other.index) } } impl PartialOrd for BucketBind { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Deref for BucketBind { type Target = String; fn deref(&self) -> &Self::Target { &self.url } } impl DerefMut for BucketBind { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.url } } impl Borrow for BucketBind { fn borrow(&self) -> &String { &self.url } } impl fmt::Display for BucketBind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.url) } }