vdsl_sync/infra/
location.rs1use std::path::{Path, PathBuf};
21use std::sync::Arc;
22
23use async_trait::async_trait;
24
25use crate::domain::location::LocationId;
26use crate::infra::error::InfraError;
27use crate::infra::location_scanner::LocationScanner;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
43pub enum LocationKind {
44 Local,
46 Remote,
48 Cloud,
50}
51
52#[async_trait]
63pub trait Location: Send + Sync {
64 fn id(&self) -> &LocationId;
66
67 fn kind(&self) -> LocationKind;
71
72 fn file_root(&self) -> &Path;
78
79 fn scanner(&self) -> Arc<dyn LocationScanner>;
83
84 async fn ensure(&self) -> Result<(), InfraError>;
93}
94
95use crate::infra::hasher::ContentHasher;
100use crate::infra::location_scanner::LocalScanner;
101
102pub struct LocalLocation {
106 id: LocationId,
107 root: PathBuf,
108 hasher: Arc<dyn ContentHasher>,
109}
110
111impl LocalLocation {
112 pub fn new(root: PathBuf, hasher: Arc<dyn ContentHasher>) -> Self {
113 Self {
114 id: LocationId::local(),
115 root,
116 hasher,
117 }
118 }
119}
120
121#[async_trait]
122impl Location for LocalLocation {
123 fn id(&self) -> &LocationId {
124 &self.id
125 }
126
127 fn kind(&self) -> LocationKind {
128 LocationKind::Local
129 }
130
131 fn file_root(&self) -> &Path {
132 &self.root
133 }
134
135 fn scanner(&self) -> Arc<dyn LocationScanner> {
136 Arc::new(LocalScanner::new(
137 self.id.clone(),
138 self.root.clone(),
139 self.hasher.clone(),
140 ))
141 }
142
143 async fn ensure(&self) -> Result<(), InfraError> {
144 if !self.root.exists() {
145 std::fs::create_dir_all(&self.root).map_err(|e| {
146 InfraError::Init(format!(
147 "local file_root '{}' does not exist and could not be created: {e}",
148 self.root.display()
149 ))
150 })?;
151 }
152 if !self.root.is_dir() {
153 return Err(InfraError::Init(format!(
154 "local file_root '{}' exists but is not a directory",
155 self.root.display()
156 )));
157 }
158 Ok(())
159 }
160}
161
162use crate::infra::location_scanner::SshScanner;
167use crate::infra::shell::RemoteShell;
168
169pub struct SshLocation {
173 id: LocationId,
174 root: PathBuf,
175 shell: Arc<dyn RemoteShell>,
176}
177
178impl SshLocation {
179 pub fn new(id: LocationId, root: PathBuf, shell: Arc<dyn RemoteShell>) -> Self {
180 Self { id, root, shell }
181 }
182}
183
184#[async_trait]
185impl Location for SshLocation {
186 fn id(&self) -> &LocationId {
187 &self.id
188 }
189
190 fn kind(&self) -> LocationKind {
191 LocationKind::Remote
192 }
193
194 fn file_root(&self) -> &Path {
195 &self.root
196 }
197
198 fn scanner(&self) -> Arc<dyn LocationScanner> {
199 Arc::new(SshScanner::new(
200 self.id.clone(),
201 self.root.clone(),
202 self.shell.clone(),
203 ))
204 }
205
206 async fn ensure(&self) -> Result<(), InfraError> {
207 let output = self.shell.exec(&["echo", "pong"], Some(30)).await?;
208 if !output.success {
209 return Err(InfraError::Init(format!(
210 "SSH location '{}' unreachable (exit {}): {}",
211 self.id,
212 output.exit_code.unwrap_or(-1),
213 output.stderr.trim()
214 )));
215 }
216 Ok(())
217 }
218}
219
220use crate::infra::backend::StorageBackend;
225use crate::infra::location_scanner::CloudScanner;
226
227pub struct CloudLocation {
232 id: LocationId,
233 root: PathBuf,
234 backend: Arc<dyn StorageBackend>,
235}
236
237impl CloudLocation {
238 pub fn new(id: LocationId, root: PathBuf, backend: Arc<dyn StorageBackend>) -> Self {
239 Self { id, root, backend }
240 }
241}
242
243#[async_trait]
244impl Location for CloudLocation {
245 fn id(&self) -> &LocationId {
246 &self.id
247 }
248
249 fn kind(&self) -> LocationKind {
250 LocationKind::Cloud
251 }
252
253 fn file_root(&self) -> &Path {
254 &self.root
255 }
256
257 fn scanner(&self) -> Arc<dyn LocationScanner> {
258 Arc::new(CloudScanner::new(
259 self.id.clone(),
260 self.root.clone(),
261 self.backend.clone(),
262 ))
263 }
264
265 async fn ensure(&self) -> Result<(), InfraError> {
266 self.backend.ensure().await.map_err(|e| {
267 InfraError::Init(format!("cloud location '{}' ensure failed: {e}", self.id))
268 })
269 }
270}