use std::{fs::create_dir_all, path::PathBuf}; use mingling::{ Groupped, macros::{chain, dispatcher, pack, pack_err, r_println, renderer, route}, parser::AsPicker, res::ResExitCode, }; use rorolala::bucket::{Bucket, NoProtocol}; use serde::Serialize; use space_system::{Space, SpaceError, SpaceRoot, find_space_root_with}; use crate::{Next, error::ErrorIo, locale::I18nBucketManager, res::current_dir::ResCurrentDir}; pub const EC_BUCKET_CREATE_DIR_NOT_EMPTY: i32 = 2400; pub const EC_BUCKET_PATH_NOT_PROVIDED: i32 = 2401; pub const EC_BUCKET_PATH_NOT_DIRECTORY: i32 = 2402; pub const EC_BUCKET_NESTED: i32 = 2403; dispatcher!("bucket.init"); dispatcher!("bucket.create"); pack!(StateBucketCreationPrecheck = PathBuf); pack!(StateBucketCreation = PathBuf); #[derive(Debug, Groupped, Serialize)] pub struct ResultBucketCreated { bucket_path: PathBuf, } pack_err!(ErrorDirectoryNotEmpty = PathBuf); pack_err!(ErrorBucketPathNotProvided = ()); pack_err!(ErrorBucketPathNotDirectory = PathBuf); pack_err!(ErrorBucketPathNested = PathBuf); #[chain] pub fn handle_bucket_init(_args: EntryBucketInit, cwd: &mut ResCurrentDir) -> Next { // NOTE: It's a dirty operation :D // Directly extract the value of the cwd resource for use, reducing Clone // because it's guaranteed that `ResCurrentDir` won't be used after `handle_bucket_init` let cwd = std::mem::take(&mut cwd.cwd); StateBucketCreationPrecheck::new(cwd) } #[chain] pub fn handle_bucket_create(args: EntryBucketCreate) -> Next { let join = route! { args.pick_or_route::((), ErrorBucketPathNotProvided::new(()).to_render() ).unpack() }; StateBucketCreationPrecheck::new(join).to_chain() } #[chain] pub fn handle_state_bucket_creation_precheck(create: StateBucketCreationPrecheck) -> Next { let path = create.inner; // Reject if already inside a Bucket space if find_space_root_with(&path, &Bucket::::get_pattern()).is_ok() { return ErrorBucketPathNested::new(path).to_render(); } if path.exists() { if !path.is_dir() { return ErrorBucketPathNotDirectory::new(path).to_render(); } if path .read_dir() .map(|mut it| it.next().is_some()) .unwrap_or(false) { return ErrorDirectoryNotEmpty::new(path).to_render(); } } StateBucketCreation::new(path).to_chain() } #[chain] pub fn handle_state_bucket_creation(create: StateBucketCreation) -> Next { let path = create.inner; route! { create_dir_all(&path).map_err(|e| ErrorIo::from(e).to_render()) }; // Use a protocol-less Bucket as a temporary Space for initialization let bucket_space = Space::>::new(Bucket::::new_local()); // Initialize the Space and capture any SpaceError::Io let path_to_init = path.clone(); if let Err(SpaceError::Io(error)) = bucket_space.init(path_to_init) { return ErrorIo::from(error).to_render(); } ResultBucketCreated { bucket_path: path }.to_render() } #[renderer] pub fn render_result_bucket_created(result: ResultBucketCreated) { let path = result.bucket_path.to_string_lossy(); r_println!("{}", I18nBucketManager::created(path)); } #[renderer] pub fn render_error_directory_not_empty(_err: ErrorDirectoryNotEmpty, ec: &mut ResExitCode) { r_println!("{}", I18nBucketManager::error_directory_not_empty().trim()); ec.exit_code = EC_BUCKET_CREATE_DIR_NOT_EMPTY; } #[renderer] pub fn render_error_bucket_path_not_provided( _err: ErrorBucketPathNotProvided, ec: &mut ResExitCode, ) { r_println!( "{}", I18nBucketManager::error_bucket_path_not_provided().trim() ); ec.exit_code = EC_BUCKET_PATH_NOT_PROVIDED; } #[renderer] pub fn render_error_bucket_path_not_directory( _err: ErrorBucketPathNotDirectory, ec: &mut ResExitCode, ) { r_println!("{}", I18nBucketManager::error_directory_not_empty().trim()); ec.exit_code = EC_BUCKET_PATH_NOT_DIRECTORY; } #[renderer] pub fn render_error_bucket_path_nested(_err: ErrorBucketPathNested, ec: &mut ResExitCode) { r_println!("{}", I18nBucketManager::error_bucket_nested().trim()); ec.exit_code = EC_BUCKET_NESTED; }