1use std::path::Path;
9use std::sync::Arc;
10
11use boatramp_core::Storage;
12
13use crate::backends::BlobBackend;
14use crate::error::{Error, Result};
15
16#[cfg(feature = "fs")]
17use boatramp_storage::FsStorage;
18
19#[derive(Debug, Clone)]
22pub struct BlobArgs {
23 pub blobs: BlobBackend,
24 pub s3_bucket: Option<String>,
25 pub s3_endpoint: Option<String>,
26 pub s3_region: Option<String>,
27 pub s3_path_style: bool,
28 pub gcs_bucket: Option<String>,
29 pub gcs_endpoint: Option<String>,
30 pub gcs_anonymous: bool,
31 pub azure_account: Option<String>,
32 pub azure_container: Option<String>,
33 pub azure_access_key: Option<String>,
34 pub azure_emulator: bool,
35}
36
37pub struct BuiltBlobs {
42 pub storage: Arc<dyn Storage>,
43 #[cfg_attr(not(feature = "handlers"), allow(dead_code))]
44 pub watch_provider: Option<Arc<dyn boatramp_core::blob_provision::WatchProvider>>,
45 #[cfg_attr(not(feature = "handlers"), allow(dead_code))]
46 pub provision_tier: boatramp_core::blob_notify::ProvisionTier,
47}
48
49#[cfg_attr(not(feature = "fs"), allow(unused_variables))]
52pub async fn build_blobs(
53 args: &BlobArgs,
54 data_dir: &Path,
55 notify_tier: Option<boatramp_core::blob_notify::ProvisionTier>,
56 notify_account: Option<String>,
57) -> Result<BuiltBlobs> {
58 match args.blobs {
59 #[cfg(feature = "fs")]
60 BlobBackend::Fs => Ok(BuiltBlobs {
61 storage: Arc::new(FsStorage::new(data_dir.join("blobs"))),
62 watch_provider: None,
63 provision_tier: boatramp_core::blob_notify::ProvisionTier::default(),
64 }),
65 #[cfg(not(feature = "fs"))]
66 BlobBackend::Fs => Err(Error::NoFsSupport),
67 BlobBackend::S3 => build_s3(args, notify_tier, notify_account).await,
68 BlobBackend::Gcs => build_gcs(args, notify_tier, notify_account).await,
69 BlobBackend::Azure => build_azure(args, notify_tier, notify_account).await,
70 }
71}
72
73#[cfg(feature = "azure")]
78async fn build_azure(
79 args: &BlobArgs,
80 notify_tier: Option<boatramp_core::blob_notify::ProvisionTier>,
81 _notify_account: Option<String>,
82) -> Result<BuiltBlobs> {
83 let (Some(account), Some(container)) =
84 (args.azure_account.clone(), args.azure_container.clone())
85 else {
86 return Err(Error::AzureConfigRequired);
87 };
88 let opts = boatramp_storage::AzureOptions {
89 account,
90 container,
91 access_key: args.azure_access_key.clone(),
92 emulator: args.azure_emulator,
93 };
94 match notify_tier {
95 Some(tier) => {
96 let (storage, provider) = boatramp_storage::AzureStorage::connect_with_notify(opts)
97 .map_err(|err| Error::AzureConnect(err.to_string()))?;
98 Ok(BuiltBlobs {
99 storage: Arc::new(storage),
100 watch_provider: Some(Arc::new(provider)),
101 provision_tier: tier,
102 })
103 }
104 None => {
105 let storage = boatramp_storage::AzureStorage::connect(opts)
106 .map_err(|err| Error::AzureConnect(err.to_string()))?;
107 Ok(BuiltBlobs {
108 storage: Arc::new(storage),
109 watch_provider: None,
110 provision_tier: boatramp_core::blob_notify::ProvisionTier::default(),
111 })
112 }
113 }
114}
115
116#[cfg(not(feature = "azure"))]
117async fn build_azure(
118 _args: &BlobArgs,
119 _notify_tier: Option<boatramp_core::blob_notify::ProvisionTier>,
120 _notify_account: Option<String>,
121) -> Result<BuiltBlobs> {
122 Err(Error::NoAzureSupport)
123}
124
125#[cfg(feature = "gcs")]
129async fn build_gcs(
130 args: &BlobArgs,
131 notify_tier: Option<boatramp_core::blob_notify::ProvisionTier>,
132 notify_account: Option<String>,
133) -> Result<BuiltBlobs> {
134 let bucket = args.gcs_bucket.clone().ok_or(Error::GcsBucketRequired)?;
135 let opts = boatramp_storage::GcsOptions {
136 bucket,
137 endpoint: args.gcs_endpoint.clone(),
138 anonymous: args.gcs_anonymous,
139 };
140 match notify_tier {
141 Some(tier) => {
142 let project = notify_account.unwrap_or_default();
143 let (storage, provider) =
144 boatramp_storage::GcsStorage::connect_with_notify(opts, project)
145 .await
146 .map_err(|err| Error::GcsConnect(err.to_string()))?;
147 Ok(BuiltBlobs {
148 storage: Arc::new(storage),
149 watch_provider: Some(Arc::new(provider)),
150 provision_tier: tier,
151 })
152 }
153 None => {
154 let storage = boatramp_storage::GcsStorage::connect(opts)
155 .await
156 .map_err(|err| Error::GcsConnect(err.to_string()))?;
157 Ok(BuiltBlobs {
158 storage: Arc::new(storage),
159 watch_provider: None,
160 provision_tier: boatramp_core::blob_notify::ProvisionTier::default(),
161 })
162 }
163 }
164}
165
166#[cfg(not(feature = "gcs"))]
167async fn build_gcs(
168 _args: &BlobArgs,
169 _notify_tier: Option<boatramp_core::blob_notify::ProvisionTier>,
170 _notify_account: Option<String>,
171) -> Result<BuiltBlobs> {
172 Err(Error::NoGcsSupport)
173}
174
175#[cfg(feature = "s3")]
176async fn build_s3(
177 args: &BlobArgs,
178 notify_tier: Option<boatramp_core::blob_notify::ProvisionTier>,
179 notify_account: Option<String>,
180) -> Result<BuiltBlobs> {
181 let bucket = args.s3_bucket.clone().ok_or(Error::S3BucketRequired)?;
182 let opts = boatramp_storage::S3Options {
183 bucket,
184 endpoint: args.s3_endpoint.clone(),
185 region: args.s3_region.clone(),
186 force_path_style: args.s3_path_style,
187 };
188 match notify_tier {
189 Some(tier) => {
192 let account = notify_account.unwrap_or_default();
193 let (storage, provider) =
194 boatramp_storage::S3Storage::connect_with_notify(opts, account).await;
195 Ok(BuiltBlobs {
196 storage: Arc::new(storage),
197 watch_provider: Some(Arc::new(provider)),
198 provision_tier: tier,
199 })
200 }
201 None => Ok(BuiltBlobs {
203 storage: Arc::new(boatramp_storage::S3Storage::connect(opts).await),
204 watch_provider: None,
205 provision_tier: boatramp_core::blob_notify::ProvisionTier::default(),
206 }),
207 }
208}
209
210#[cfg(not(feature = "s3"))]
211async fn build_s3(
212 _args: &BlobArgs,
213 _notify_tier: Option<boatramp_core::blob_notify::ProvisionTier>,
214 _notify_account: Option<String>,
215) -> Result<BuiltBlobs> {
216 Err(Error::NoS3Support)
217}