use std::env;
use std::str::FromStr;
use regex::Regex;
use s3::creds::Credentials;
use s3::{Bucket, Region};
use serde::{Deserialize, Serialize};
use xvc_core::R1NStore;
use xvc_core::{error, info, XvcOutputSender};
use xvc_core::{XvcCachePath, XvcRoot};
use crate::{Error, Result, XvcStorage, XvcStorageEvent};
use crate::{XvcStorageGuid, XvcStorageOperations};
use super::async_common::XvcS3StorageOperations;
use super::{XvcStorageListEvent, XvcStoragePath};
pub fn cmd_new_r2(
_input: std::io::StdinLock,
output_snd: &XvcOutputSender,
xvc_root: &XvcRoot,
name: String,
account_id: String,
bucket_name: String,
storage_prefix: String,
) -> Result<()> {
let mut storage = XvcR2Storage {
guid: XvcStorageGuid::new(),
name,
account_id,
bucket_name,
storage_prefix,
};
info!(output_snd, "R2 Storage: {:#?}", storage);
let init_event = storage.init(output_snd, xvc_root)?;
xvc_root.with_r1nstore_mut(|store: &mut R1NStore<XvcStorage, XvcStorageEvent>| {
let store_e = xvc_root.new_entity();
let event_e = xvc_root.new_entity();
store.insert(
store_e,
XvcStorage::R2(storage.clone()),
event_e,
XvcStorageEvent::Init(init_event.clone()),
);
Ok(())
})?;
Ok(())
}
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)]
pub struct XvcR2Storage {
pub guid: XvcStorageGuid,
pub name: String,
pub account_id: String,
pub bucket_name: String,
pub storage_prefix: String,
}
impl XvcS3StorageOperations for XvcR2Storage {
fn storage_prefix(&self) -> String {
self.storage_prefix.clone()
}
fn guid(&self) -> &XvcStorageGuid {
&self.guid
}
fn bucket_name(&self) -> String {
self.bucket_name.clone()
}
fn build_storage_path(&self, cache_path: &XvcCachePath) -> XvcStoragePath {
XvcStoragePath::from(format!(
"{}/{}/{}",
self.storage_prefix,
self.guid(),
cache_path
))
}
fn region(&self) -> String {
self.account_id.clone()
}
fn credentials(&self) -> Result<Credentials> {
let specific_access_key_var = format!("XVC_STORAGE_ACCESS_KEY_ID_{}", self.name);
let specific_secret_key_var = format!("XVC_STORAGE_SECRET_ACCESS_KEY_{}", self.name);
if let (Ok(access_key), Ok(secret_key)) = (
env::var(&specific_access_key_var),
env::var(&specific_secret_key_var),
) {
return Credentials::new(Some(&access_key), Some(&secret_key), None, None, None)
.map_err(|e| e.into());
}
let type_access_key_var = "R2_ACCESS_KEY_ID";
let type_secret_key_var = "R2_SECRET_ACCESS_KEY";
if let (Ok(access_key), Ok(secret_key)) =
(env::var(type_access_key_var), env::var(type_secret_key_var))
{
return Credentials::new(Some(&access_key), Some(&secret_key), None, None, None)
.map_err(|e| e.into());
}
Err(Error::CloudCredentialsNotFound {
storage_name: self.name.clone(),
var_pairs: vec![
(specific_access_key_var, specific_secret_key_var),
(
type_access_key_var.to_string(),
type_secret_key_var.to_string(),
),
],
})
}
fn get_bucket(&self) -> Result<Box<Bucket>> {
let credentials = self.credentials()?;
let region = Region::R2 {
account_id: self.account_id.clone(),
};
let bucket = Bucket::new(&self.bucket_name, region, credentials)?;
Ok(bucket)
}
async fn a_list(
&self,
output: &XvcOutputSender,
xvc_root: &xvc_core::XvcRoot,
) -> Result<XvcStorageListEvent> {
let bucket = self.get_bucket()?;
let xvc_guid = xvc_root.guid();
let prefix = self.storage_prefix.clone();
let res_list = bucket
.list(
format!("{}/{}", self.storage_prefix, xvc_guid),
Some("/".to_string()),
)
.await;
match res_list {
Ok(list_all) => {
let re = Regex::new(&format!(
"{prefix}/{xvc_guid}/{cp}/{d3}/{d3}/{d58}/0\\..*$",
cp = r#"[a-zA-Z][0-9]"#,
d3 = r#"[0-9A-Fa-f]{3}"#,
d58 = r#"[0-9A-Fa-f]{58}"#
))
.unwrap();
let paths = list_all
.iter()
.filter_map(|e| {
if re.is_match(e.name.as_ref()) {
Some(XvcStoragePath::from_str(&e.name).unwrap())
} else {
None
}
})
.collect();
Ok(XvcStorageListEvent {
guid: self.guid.clone(),
paths,
})
}
Err(err) => {
error!(output, "{}", err);
Err(Error::S3Error { source: err })
}
}
}
}