summaryrefslogtreecommitdiff
path: root/rola-bucket/src/bucket
diff options
context:
space:
mode:
Diffstat (limited to 'rola-bucket/src/bucket')
-rw-r--r--rola-bucket/src/bucket/bind.rs210
-rw-r--r--rola-bucket/src/bucket/bind/test.rs231
-rw-r--r--rola-bucket/src/bucket/config.rs107
-rw-r--r--rola-bucket/src/bucket/config/test.rs116
-rw-r--r--rola-bucket/src/bucket/idmap.rs0
-rw-r--r--rola-bucket/src/bucket/init.rs29
-rw-r--r--rola-bucket/src/bucket/space.rs9
7 files changed, 679 insertions, 23 deletions
diff --git a/rola-bucket/src/bucket/bind.rs b/rola-bucket/src/bucket/bind.rs
new file mode 100644
index 0000000..87f3382
--- /dev/null
+++ b/rola-bucket/src/bucket/bind.rs
@@ -0,0 +1,210 @@
+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<String>) -> 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<Protocol: AsyncBucketTransferProtocol + Send + Sync>(
+ space: &Space<Bucket<Protocol>>,
+) -> Result<Vec<BucketBind>, 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::<u8>() {
+ // 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<Protocol: AsyncBucketTransferProtocol + Send + Sync>(
+ space: &Space<Bucket<Protocol>>,
+ 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<Protocol: AsyncBucketTransferProtocol + Send + Sync>(
+ space: &Space<Bucket<Protocol>>,
+ idx: u8,
+) -> Result<Option<BucketBind>, 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<Protocol: AsyncBucketTransferProtocol + Send + Sync>(
+ space: &Space<Bucket<Protocol>>,
+ idx: u8,
+) -> Result<bool, SpaceError> {
+ 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<Protocol: AsyncBucketTransferProtocol + Send + Sync>(
+ space: &Space<Bucket<Protocol>>,
+ 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<Ordering> {
+ 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<String> 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)
+ }
+}
diff --git a/rola-bucket/src/bucket/bind/test.rs b/rola-bucket/src/bucket/bind/test.rs
new file mode 100644
index 0000000..10126b1
--- /dev/null
+++ b/rola-bucket/src/bucket/bind/test.rs
@@ -0,0 +1,231 @@
+use std::{fs, path::Path};
+
+use shared_constants::bucket::FILE_BUCKET_BIND;
+use shared_functions::rola_test_sandbox;
+use space_system::Space;
+
+use crate::{
+ Bucket, NoProtocol,
+ bind::{
+ BucketBind, check_bucket_bind_exists, read_bucket_bind, read_bucket_binds,
+ remove_bucket_bind, write_bucket_bind,
+ },
+};
+
+fn init_bucket(path: &Path) -> Space<Bucket<NoProtocol>> {
+ let bucket = Bucket::<NoProtocol>::new_local();
+ let mut space = Space::new(bucket);
+ space.set_current_dir(&path).unwrap();
+ space.init(path).unwrap();
+ space
+}
+
+#[test]
+fn test_read_bucket_binds() {
+ let sandbox = rola_test_sandbox("bucket_bind_read");
+
+ let b = init_bucket(&sandbox.path);
+
+ let bind_1 = sandbox.join(FILE_BUCKET_BIND("1"));
+ let bind_2 = sandbox.join(FILE_BUCKET_BIND("2"));
+ let bind_3 = sandbox.join(FILE_BUCKET_BIND("3"));
+ let bind_fail = sandbox.join(FILE_BUCKET_BIND("@"));
+ let other = sandbox.join("ot");
+
+ fs::write(bind_1, "./bucket1").unwrap();
+ fs::write(bind_2, "\n./bucket2").unwrap();
+ fs::write(bind_3, "./bucket3\nbbb").unwrap();
+ fs::write(bind_fail, "omg").unwrap();
+ fs::write(other, "ok").unwrap();
+
+ let result = read_bucket_binds(&b).unwrap();
+ assert!(result.contains(&BucketBind::new(1, "./bucket1")));
+ assert!(result.contains(&BucketBind::new(2, "./bucket2")));
+ assert!(result.contains(&BucketBind::new(3, "./bucket3\nbbb")));
+
+ assert_eq!(result.len(), 3);
+}
+
+#[test]
+fn test_write_and_read_bucket_bind() {
+ let sandbox = rola_test_sandbox("bucket_bind_write_read");
+
+ let space = init_bucket(&sandbox.path);
+
+ // Write bucket bind records
+ write_bucket_bind(&space, 1, "./bucket1").unwrap();
+ write_bucket_bind(&space, 2, "./bucket2").unwrap();
+ write_bucket_bind(&space, 3, "./bucket3\nbbb").unwrap();
+
+ // Verify reads return the correct values
+ let bind1 = read_bucket_bind(&space, 1).unwrap().unwrap();
+ assert_eq!(bind1, BucketBind::new(1, "./bucket1"));
+
+ let bind2 = read_bucket_bind(&space, 2).unwrap().unwrap();
+ assert_eq!(bind2, BucketBind::new(2, "./bucket2"));
+
+ let bind3 = read_bucket_bind(&space, 3).unwrap().unwrap();
+ assert_eq!(bind3, BucketBind::new(3, "./bucket3\nbbb"));
+
+ // Read a non-existent bind should return None
+ let bind4 = read_bucket_bind(&space, 4).unwrap();
+ assert!(bind4.is_none());
+}
+
+#[test]
+fn test_write_bucket_bind_trims_whitespace() {
+ let sandbox = rola_test_sandbox("bucket_bind_trim");
+
+ let space = init_bucket(&sandbox.path);
+
+ // Write URL with surrounding whitespace
+ write_bucket_bind(&space, 1, " ./bucket1 ").unwrap();
+
+ // Verify it was trimmed on write
+ let bind = read_bucket_bind(&space, 1).unwrap().unwrap();
+ assert_eq!(bind, BucketBind::new(1, "./bucket1"));
+}
+
+#[test]
+fn test_write_bucket_bind_overwrites_existing() {
+ let sandbox = rola_test_sandbox("bucket_bind_overwrite");
+
+ let space = init_bucket(&sandbox.path);
+
+ write_bucket_bind(&space, 1, "./bucket_v1").unwrap();
+ write_bucket_bind(&space, 1, "./bucket_v2").unwrap();
+
+ let bind = read_bucket_bind(&space, 1).unwrap().unwrap();
+ assert_eq!(bind, BucketBind::new(1, "./bucket_v2"));
+}
+
+#[test]
+fn test_check_bucket_bind_exists() {
+ let sandbox = rola_test_sandbox("bucket_bind_check_exists");
+
+ let space = init_bucket(&sandbox.path);
+
+ // Initially, no bucket bind should exist
+ let exists = check_bucket_bind_exists(&space, 1).unwrap();
+ assert!(!exists);
+
+ // Write a bucket bind and verify it exists
+ write_bucket_bind(&space, 1, "./bucket1").unwrap();
+ let exists = check_bucket_bind_exists(&space, 1).unwrap();
+ assert!(exists);
+
+ // A different index should still not exist
+ let exists = check_bucket_bind_exists(&space, 2).unwrap();
+ assert!(!exists);
+
+ // Write another bind and check
+ write_bucket_bind(&space, 2, "./bucket2").unwrap();
+ let exists = check_bucket_bind_exists(&space, 2).unwrap();
+ assert!(exists);
+}
+
+#[test]
+fn test_check_bucket_bind_exists_after_delete() {
+ let sandbox = rola_test_sandbox("bucket_bind_check_exists_after_delete");
+
+ let space = init_bucket(&sandbox.path);
+
+ write_bucket_bind(&space, 1, "./bucket1").unwrap();
+ assert!(check_bucket_bind_exists(&space, 1).unwrap());
+
+ // Delete by removing the file directly
+ let bind_path = sandbox.path.join(format!(
+ "{}{:03}",
+ shared_constants::bucket::PREFIX_BUCKET_BIND,
+ 1
+ ));
+ std::fs::remove_file(bind_path).unwrap();
+
+ let exists = check_bucket_bind_exists(&space, 1).unwrap();
+ assert!(!exists);
+}
+
+#[test]
+fn test_remove_bucket_bind() {
+ let sandbox = rola_test_sandbox("bucket_bind_remove");
+
+ let space = init_bucket(&sandbox.path);
+
+ // Write a bucket bind
+ write_bucket_bind(&space, 1, "./bucket1").unwrap();
+ assert!(check_bucket_bind_exists(&space, 1).unwrap());
+
+ // Remove it
+ remove_bucket_bind(&space, 1).unwrap();
+ assert!(!check_bucket_bind_exists(&space, 1).unwrap());
+}
+
+#[test]
+fn test_remove_bucket_bind_nonexistent() {
+ let sandbox = rola_test_sandbox("bucket_bind_remove_nonexistent");
+
+ let space = init_bucket(&sandbox.path);
+
+ // Removing a non-existent bind should return an error
+ let result = remove_bucket_bind(&space, 99);
+ assert!(result.is_err());
+}
+
+#[test]
+fn test_remove_bucket_bind_does_not_affect_others() {
+ let sandbox = rola_test_sandbox("bucket_bind_remove_others");
+
+ let space = init_bucket(&sandbox.path);
+
+ // Write multiple bucket binds
+ write_bucket_bind(&space, 1, "./bucket1").unwrap();
+ write_bucket_bind(&space, 2, "./bucket2").unwrap();
+ write_bucket_bind(&space, 3, "./bucket3").unwrap();
+
+ // Remove bind 2
+ remove_bucket_bind(&space, 2).unwrap();
+
+ // Bind 1 and 3 should still exist
+ assert!(check_bucket_bind_exists(&space, 1).unwrap());
+ assert!(!check_bucket_bind_exists(&space, 2).unwrap());
+ assert!(check_bucket_bind_exists(&space, 3).unwrap());
+
+ // Values should be preserved for remaining binds
+ assert_eq!(
+ read_bucket_bind(&space, 1).unwrap().unwrap(),
+ BucketBind::new(1, "./bucket1")
+ );
+ assert_eq!(
+ read_bucket_bind(&space, 3).unwrap().unwrap(),
+ BucketBind::new(3, "./bucket3")
+ );
+}
+
+#[test]
+fn test_remove_bucket_bind_then_read_returns_none() {
+ let sandbox = rola_test_sandbox("bucket_bind_remove_then_read");
+
+ let space = init_bucket(&sandbox.path);
+
+ write_bucket_bind(&space, 1, "./bucket1").unwrap();
+ remove_bucket_bind(&space, 1).unwrap();
+
+ let bind = read_bucket_bind(&space, 1).unwrap();
+ assert!(bind.is_none());
+}
+
+#[test]
+fn test_remove_bucket_bind_then_write_again() {
+ let sandbox = rola_test_sandbox("bucket_bind_remove_then_write");
+
+ let space = init_bucket(&sandbox.path);
+
+ write_bucket_bind(&space, 1, "./bucket_v1").unwrap();
+ remove_bucket_bind(&space, 1).unwrap();
+
+ // Write the same index again
+ write_bucket_bind(&space, 1, "./bucket_v2").unwrap();
+
+ let bind = read_bucket_bind(&space, 1).unwrap().unwrap();
+ assert_eq!(bind, BucketBind::new(1, "./bucket_v2"));
+}
diff --git a/rola-bucket/src/bucket/config.rs b/rola-bucket/src/bucket/config.rs
new file mode 100644
index 0000000..2fd111c
--- /dev/null
+++ b/rola-bucket/src/bucket/config.rs
@@ -0,0 +1,107 @@
+use serde::{Deserialize, Serialize};
+use space_system::{Space, SpaceError};
+
+use crate::{AsyncBucketTransferProtocol, Bucket};
+
+#[cfg(test)]
+mod test;
+
+/// Configuration for a bucket.
+///
+/// This struct defines how a bucket should be configured, including its type.
+#[derive(Default, Serialize, Deserialize)]
+pub struct BucketConfig {
+ /// The type of the bucket, e.g., client bucket or normal bucket.
+ ///
+ /// When deserializing from TOML, this is expected to be under the key `"type"`.
+ #[serde(rename = "type")]
+ pub bucket_type: BucketType,
+}
+
+/// Enum for bucket types, used to distinguish different types of buckets.
+///
+/// When deserializing, field names are mapped to string values in TOML via `serde(rename)`.
+#[derive(Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
+pub enum BucketType {
+ /// Client bucket
+ /// Uses local ID, mapped to remote ID via IDMAP
+ #[serde(rename = "client")]
+ ClientBucket,
+
+ /// Normal bucket
+ /// Uses global ID
+ #[default]
+ #[serde(rename = "bucket")]
+ Bucket,
+}
+
+/// Reads the bucket configuration from a file path.
+///
+/// This function reads the bucket configuration file at the given `path`.
+/// If the file cannot be read or the content cannot be parsed as TOML,
+/// a default `BucketConfig` will be used instead of returning an error.
+///
+/// # Note
+///
+/// - If the configuration file cannot be read (e.g., file not found, permission denied),
+/// a default [`BucketConfig`] is returned.
+/// - If the file content is not valid TOML or does not match the expected structure,
+/// a default [`BucketConfig`] is returned as well.
+pub fn read_bucket_config_from_path(
+ path: impl AsRef<std::path::Path>,
+) -> Result<BucketConfig, SpaceError> {
+ let config_content = match std::fs::read_to_string(path) {
+ Ok(content) => content,
+ Err(_) => return Ok(BucketConfig::default()),
+ };
+ Ok(toml::from_str(&config_content).unwrap_or_default())
+}
+
+/// Writes the bucket configuration to a file path.
+///
+/// This function serializes the given bucket configuration as TOML and
+/// writes it to the specified `path`. If the serialization or write fails,
+/// the error is returned as a [`SpaceError`].
+pub fn write_bucket_config_to_path(
+ path: impl AsRef<std::path::Path>,
+ config: &BucketConfig,
+) -> Result<(), SpaceError> {
+ let config_content = toml::to_string(config).map_err(|e| {
+ SpaceError::Other(format!("Failed to serialize bucket config to TOML: {}", e))
+ })?;
+ std::fs::write(path, config_content.as_bytes())
+ .map_err(|e| SpaceError::Other(format!("Failed to write bucket config to file: {}", e)))?;
+ Ok(())
+}
+
+/// Reads the bucket configuration from the space, with shared with_path logic.
+///
+/// This function attempts to read the bucket configuration file (`FILE_BUCKET_ROOT_CONFIG`)
+/// from the given `space`. If the file cannot be read or the content cannot be parsed as TOML,
+/// a default `BucketConfig` will be used instead of returning an error.
+///
+/// # Note
+///
+/// - If the configuration file cannot be read (e.g., file not found, permission denied),
+/// a default [`BucketConfig`] is returned.
+/// - If the file content is not valid TOML or does not match the expected structure,
+/// a default [`BucketConfig`] is returned as well.
+pub fn read_bucket_config<Protocol: AsyncBucketTransferProtocol + Send + Sync>(
+ space: &Space<Bucket<Protocol>>,
+) -> Result<BucketConfig, SpaceError> {
+ let path = space.local_path(shared_constants::common::FILE_BUCKET_ROOT_CONFIG)?;
+ read_bucket_config_from_path(path)
+}
+
+/// Writes the bucket configuration to the space, with shared with_path logic.
+///
+/// This function attempts to write the bucket configuration file (`FILE_BUCKET_ROOT_CONFIG`)
+/// to the given `space` as a TOML string. If the serialization or write fails, the error
+/// is returned.
+pub fn write_bucket_config<Protocol: AsyncBucketTransferProtocol + Send + Sync>(
+ space: &Space<Bucket<Protocol>>,
+ config: &BucketConfig,
+) -> Result<(), SpaceError> {
+ let path = space.local_path(shared_constants::common::FILE_BUCKET_ROOT_CONFIG)?;
+ write_bucket_config_to_path(path, config)
+}
diff --git a/rola-bucket/src/bucket/config/test.rs b/rola-bucket/src/bucket/config/test.rs
new file mode 100644
index 0000000..d586045
--- /dev/null
+++ b/rola-bucket/src/bucket/config/test.rs
@@ -0,0 +1,116 @@
+use shared_functions::rola_test_sandbox;
+
+use crate::config::{
+ BucketConfig, BucketType, read_bucket_config_from_path, write_bucket_config_to_path,
+};
+
+#[test]
+fn test_read_bucket_config_from_path_file_not_found_returns_default() {
+ let sandbox = rola_test_sandbox("bucket_config_read_file_not_found");
+ let path = sandbox.path.join("nonexistent.toml");
+
+ let config = read_bucket_config_from_path(&path).unwrap();
+ assert_eq!(config.bucket_type, BucketType::Bucket);
+}
+
+#[test]
+fn test_read_bucket_config_from_path_invalid_toml_returns_default() {
+ let sandbox = rola_test_sandbox("bucket_config_read_invalid_toml");
+ let path = sandbox.path.join("config.toml");
+ std::fs::write(&path, "this is not valid TOML {{{").unwrap();
+
+ let config = read_bucket_config_from_path(&path).unwrap();
+ assert_eq!(config.bucket_type, BucketType::Bucket);
+}
+
+#[test]
+fn test_read_bucket_config_from_path_valid_client_bucket() {
+ let sandbox = rola_test_sandbox("bucket_config_read_valid_client");
+ let path = sandbox.path.join("config.toml");
+ std::fs::write(&path, r#"type = "client""#).unwrap();
+
+ let config = read_bucket_config_from_path(&path).unwrap();
+ assert_eq!(config.bucket_type, BucketType::ClientBucket);
+}
+
+#[test]
+fn test_read_bucket_config_from_path_valid_normal_bucket() {
+ let sandbox = rola_test_sandbox("bucket_config_read_valid_normal");
+ let path = sandbox.path.join("config.toml");
+ std::fs::write(&path, r#"type = "bucket""#).unwrap();
+
+ let config = read_bucket_config_from_path(&path).unwrap();
+ assert_eq!(config.bucket_type, BucketType::Bucket);
+}
+
+#[test]
+fn test_read_bucket_config_from_path_empty_file_returns_default() {
+ let sandbox = rola_test_sandbox("bucket_config_read_empty");
+ let path = sandbox.path.join("config.toml");
+ std::fs::write(&path, "").unwrap();
+
+ let config = read_bucket_config_from_path(&path).unwrap();
+ assert_eq!(config.bucket_type, BucketType::Bucket);
+}
+
+#[test]
+fn test_read_bucket_config_from_path_unknown_type_returns_default() {
+ let sandbox = rola_test_sandbox("bucket_config_read_unknown_type");
+ let path = sandbox.path.join("config.toml");
+ std::fs::write(&path, r#"type = "something_else""#).unwrap();
+
+ let config = read_bucket_config_from_path(&path).unwrap();
+ assert_eq!(config.bucket_type, BucketType::Bucket);
+}
+
+#[test]
+fn test_write_and_read_roundtrip_client_bucket() {
+ let sandbox = rola_test_sandbox("bucket_config_roundtrip_client");
+ let path = sandbox.path.join("config.toml");
+
+ let config = BucketConfig {
+ bucket_type: BucketType::ClientBucket,
+ };
+ write_bucket_config_to_path(&path, &config).unwrap();
+
+ let loaded = read_bucket_config_from_path(&path).unwrap();
+ assert_eq!(loaded.bucket_type, BucketType::ClientBucket);
+}
+
+#[test]
+fn test_write_and_read_roundtrip_normal_bucket() {
+ let sandbox = rola_test_sandbox("bucket_config_roundtrip_normal");
+ let path = sandbox.path.join("config.toml");
+
+ let config = BucketConfig {
+ bucket_type: BucketType::Bucket,
+ };
+ write_bucket_config_to_path(&path, &config).unwrap();
+
+ let loaded = read_bucket_config_from_path(&path).unwrap();
+ assert_eq!(loaded.bucket_type, BucketType::Bucket);
+}
+
+#[test]
+fn test_write_bucket_config_to_path_invalid_dir_fails() {
+ let path = "/nonexistent_directory_12345/config.toml";
+
+ let config = BucketConfig::default();
+ let result = write_bucket_config_to_path(&path, &config);
+ assert!(result.is_err());
+}
+
+#[test]
+fn test_write_bucket_config_overwrites_existing_file() {
+ let sandbox = rola_test_sandbox("bucket_config_overwrite");
+ let path = sandbox.path.join("config.toml");
+ std::fs::write(&path, r#"type = "client""#).unwrap();
+
+ let config = BucketConfig {
+ bucket_type: BucketType::Bucket,
+ };
+ write_bucket_config_to_path(&path, &config).unwrap();
+
+ let loaded = read_bucket_config_from_path(&path).unwrap();
+ assert_eq!(loaded.bucket_type, BucketType::Bucket);
+}
diff --git a/rola-bucket/src/bucket/idmap.rs b/rola-bucket/src/bucket/idmap.rs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/rola-bucket/src/bucket/idmap.rs
diff --git a/rola-bucket/src/bucket/init.rs b/rola-bucket/src/bucket/init.rs
index 30bf0f4..d2dda24 100644
--- a/rola-bucket/src/bucket/init.rs
+++ b/rola-bucket/src/bucket/init.rs
@@ -1,16 +1,20 @@
-use std::path::{Path, PathBuf};
+use std::{
+ fs,
+ path::{Path, PathBuf},
+};
use shared_constants::{
bucket::{
DIR_BUCKET_COMPRESSED_OBJ, DIR_BUCKET_DELTA, DIR_BUCKET_ID_REVS, DIR_BUCKET_ID_TAGS,
- DIR_BUCKET_OBJ,
+ DIR_BUCKET_IDMAP, DIR_BUCKET_OBJ,
},
common::FILE_BUCKET_ROOT_CONFIG,
};
use space_system::SpaceError;
-use tokio::fs;
-pub(crate) async fn init_bucket_at(path: PathBuf) -> Result<(), SpaceError> {
+use crate::config::{BucketConfig, write_bucket_config_to_path};
+
+pub(crate) fn init_bucket_at(path: PathBuf) -> Result<(), SpaceError> {
let bucket_config_file = path.join(FILE_BUCKET_ROOT_CONFIG);
// Check if directory is empty
@@ -19,32 +23,25 @@ pub(crate) async fn init_bucket_at(path: PathBuf) -> Result<(), SpaceError> {
return Err(SpaceError::RequireEmptyDirectory);
}
- write_config(&bucket_config_file).await?;
- create_dirs(&path).await?;
+ write_bucket_config_to_path(&bucket_config_file, &BucketConfig::default())?;
+ create_dirs(&path)?;
Ok(())
}
-async fn write_config(bucket_config_file: &Path) -> Result<(), SpaceError> {
- fs::write(bucket_config_file, include_str!("../../res/bucket.toml"))
- .await
- .map_err(SpaceError::Io)
-}
-
-async fn create_dirs(bucket_dir: &Path) -> Result<(), SpaceError> {
+fn create_dirs(bucket_dir: &Path) -> Result<(), SpaceError> {
let dirs = [
DIR_BUCKET_OBJ,
DIR_BUCKET_COMPRESSED_OBJ,
DIR_BUCKET_DELTA,
DIR_BUCKET_ID_REVS,
DIR_BUCKET_ID_TAGS,
+ DIR_BUCKET_IDMAP,
];
for dir in dirs {
let full_path = bucket_dir.join(dir);
- fs::create_dir_all(&full_path)
- .await
- .map_err(SpaceError::Io)?;
+ fs::create_dir_all(&full_path).map_err(SpaceError::Io)?;
}
Ok(())
diff --git a/rola-bucket/src/bucket/space.rs b/rola-bucket/src/bucket/space.rs
index ed1311c..1775914 100644
--- a/rola-bucket/src/bucket/space.rs
+++ b/rola-bucket/src/bucket/space.rs
@@ -1,6 +1,4 @@
-// use log::trace;
use shared_constants::common::FILE_BUCKET_ROOT_CONFIG;
-use shared_functions::trace;
use space_system::{SpaceRoot, SpaceRootFindPattern};
use crate::{AsyncBucketTransferProtocol, Bucket, bucket::init::init_bucket_at};
@@ -10,11 +8,8 @@ impl<Protocol: AsyncBucketTransferProtocol + Send + Sync> SpaceRoot for Bucket<P
SpaceRootFindPattern::IncludeFile(FILE_BUCKET_ROOT_CONFIG.into())
}
- async fn create_space(path: &std::path::Path) -> Result<(), space_system::SpaceError> {
- let path_str = path.display().to_string();
- trace!("Creating bucket at: {}", &path_str);
- init_bucket_at(path.into()).await?;
- trace!("Bucket created at: {}", &path_str);
+ fn create_space(path: &std::path::Path) -> Result<(), space_system::SpaceError> {
+ init_bucket_at(path.into())?;
Ok(())
}
}