Skip to main content

quilt_rs/
local_domain.rs

1use std::marker::Unpin;
2use std::path::Path;
3use std::path::PathBuf;
4
5use tracing::{debug, info, warn};
6
7use crate::Res;
8use crate::flow;
9use crate::installed_package::InstalledPackage;
10use crate::io::manifest::RowsStream;
11use crate::io::manifest::build_manifest_from_rows_stream;
12use crate::io::remote::Remote;
13use crate::io::remote::RemoteS3;
14use crate::io::storage::LocalStorage;
15use crate::io::storage::Storage;
16use crate::lineage;
17use crate::lineage::DomainLineage;
18use crate::lineage::Home;
19use crate::manifest::Manifest;
20use crate::manifest::ManifestHeader;
21use crate::paths;
22use quilt_uri::ManifestUri;
23use quilt_uri::Namespace;
24
25/// This is the entrypoint for the lib.
26/// All the work you can do with packages is done through calling `LocalDomain` methods.
27#[derive(Debug)]
28pub struct LocalDomain<S: Storage = LocalStorage, R: Remote = RemoteS3> {
29    paths: paths::DomainPaths,
30    lineage: lineage::DomainLineageIo,
31    storage: S,
32    remote: R,
33}
34
35impl LocalDomain {
36    pub fn get_remote(&self) -> &RemoteS3 {
37        &self.remote
38    }
39
40    pub fn new(root_dir: impl AsRef<Path>) -> Self {
41        let paths = paths::DomainPaths::new(root_dir.as_ref().to_path_buf());
42        let lineage = lineage::DomainLineageIo::new(paths.lineage());
43        let storage = LocalStorage::new();
44        let remote = RemoteS3::new(paths.clone(), storage.clone());
45        Self {
46            paths,
47            lineage,
48            storage,
49            remote,
50        }
51    }
52
53    pub async fn get_home(&self) -> Res<Home> {
54        let lineage: DomainLineage = self.lineage.read(&self.storage).await?;
55        Ok(lineage.home)
56    }
57
58    pub async fn set_home(&self, dir: impl AsRef<Path>) -> Res<Home> {
59        info!("Setting home directory to {}", dir.as_ref().display());
60        Ok(self.lineage.set_home(&self.storage, dir).await?.home)
61    }
62
63    pub async fn scaffold_paths_for_installing(&self, namespace: &Namespace) -> Res {
64        debug!(
65            "Scaffolding installation paths for namespace: {}",
66            namespace
67        );
68        let home = self.get_home().await?;
69        self.paths
70            .scaffold_for_installing(&self.storage, &home, namespace)
71            .await
72    }
73
74    pub async fn scaffold_paths_for_caching(&self, bucket: &str) -> Res {
75        debug!("Scaffolding cache paths for bucket: {}", bucket);
76        self.paths.scaffold_for_caching(&self.storage, bucket).await
77    }
78
79    pub async fn browse_remote_manifest(&self, uri: &ManifestUri) -> Res<Manifest> {
80        self.scaffold_paths_for_caching(&uri.bucket).await?;
81
82        debug!("Initiating browse flow for manifest {}", uri.hash);
83        flow::browse(&self.paths, &self.storage, &self.remote, uri)
84            .await
85            .inspect(|_| debug!("Successfully browsed manifest {}", uri.hash))
86            .inspect_err(|e| warn!("Failed to browse manifest {}: {}", uri.hash, e))
87    }
88
89    pub fn create_installed_package(&self, namespace: Namespace) -> Res<InstalledPackage> {
90        // TODO: seems like you can use PackageLineage as an argument instead of namespace
91        Ok(InstalledPackage {
92            lineage: self.lineage.create_package_lineage(namespace.clone()),
93            namespace,
94            paths: self.paths.clone(),
95            remote: self.remote.try_clone()?,
96            storage: self.storage.clone(),
97        })
98    }
99
100    pub async fn install_package(&self, manifest_uri: &ManifestUri) -> Res<InstalledPackage> {
101        info!("Installing package: {}", manifest_uri.namespace);
102        debug!("Installing from manifest: {}", manifest_uri.display());
103
104        debug!("Preparing paths for installation");
105        self.scaffold_paths_for_caching(&manifest_uri.bucket)
106            .await?;
107        self.scaffold_paths_for_installing(&manifest_uri.namespace)
108            .await?;
109
110        debug!("Reading domain lineage");
111        let lineage: DomainLineage = self.lineage.read(&self.storage).await?;
112
113        debug!("Executing package installation flow");
114        let lineage = flow::install_package(
115            lineage,
116            &self.paths,
117            &self.storage,
118            &self.remote,
119            manifest_uri,
120        )
121        .await?;
122
123        debug!("Updating domain lineage");
124        self.lineage.write(&self.storage, lineage).await?;
125
126        info!("Successfully installed package: {}", manifest_uri.namespace);
127        self.create_installed_package(manifest_uri.namespace.clone())
128    }
129
130    pub async fn create_package(
131        &self,
132        namespace: Namespace,
133        source: Option<PathBuf>,
134        message: Option<String>,
135    ) -> Res<InstalledPackage> {
136        info!("Creating package: {}", namespace);
137
138        let lineage: DomainLineage = self.lineage.read(&self.storage).await?;
139
140        let lineage = flow::create(
141            lineage,
142            &self.paths,
143            &self.storage,
144            namespace.clone(),
145            source,
146            message,
147        )
148        .await?;
149
150        self.lineage.write(&self.storage, lineage).await?;
151
152        info!("Successfully created package: {}", namespace);
153        self.create_installed_package(namespace)
154    }
155
156    pub async fn uninstall_package(&self, namespace: Namespace) -> Res<()> {
157        info!("Uninstalling package: {}", namespace);
158
159        debug!("Preparing paths for uninstallation");
160        self.scaffold_paths_for_installing(&namespace).await?;
161
162        debug!("Reading current lineage state");
163        let lineage = self.lineage.read(&self.storage).await?;
164
165        debug!("Executing package uninstallation flow");
166        let lineage =
167            flow::uninstall_package(lineage, &self.paths, &self.storage, namespace.clone()).await?;
168
169        debug!("Updating domain lineage after uninstallation");
170        self.lineage.write(&self.storage, lineage).await?;
171
172        info!("Successfully uninstalled package: {}", namespace);
173        Ok(())
174    }
175
176    pub async fn list_installed_packages(&self) -> Res<Vec<InstalledPackage>> {
177        debug!("Listing installed packages");
178        let lineage = self.lineage.read(&self.storage).await?;
179        let namespaces = lineage.namespaces();
180
181        debug!("Found {} installed packages", namespaces.len());
182        let mut packages = Vec::with_capacity(namespaces.len());
183        for namespace in namespaces {
184            packages.push(self.create_installed_package(namespace)?);
185        }
186        Ok(packages)
187    }
188
189    pub async fn get_installed_package(
190        &self,
191        namespace: &Namespace,
192    ) -> Res<Option<InstalledPackage>> {
193        debug!("Looking up installed package: {}", namespace);
194        let lineage = self.lineage.read(&self.storage).await?;
195        if lineage.packages.contains_key(namespace) {
196            debug!("Found installed package: {}", namespace);
197            Ok(Some(self.create_installed_package(namespace.to_owned())?))
198        } else {
199            debug!("Package not found: {}", namespace);
200            Ok(None)
201        }
202    }
203
204    pub async fn build_manifest(
205        &self,
206        dest_path: PathBuf,
207        stream: impl RowsStream + Unpin,
208    ) -> Res<(PathBuf, String)> {
209        info!("Building manifest at: {}", dest_path.display());
210        let dest_dir = dest_path.parent().unwrap_or(&dest_path).to_path_buf();
211        build_manifest_from_rows_stream(&self.storage, dest_dir, ManifestHeader::default(), stream)
212            .await
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219
220    use tempfile::TempDir;
221    use test_log::test;
222
223    #[test(tokio::test)]
224    async fn test_list_installed_packages() -> Res<()> {
225        // Create a temporary directory for testing
226        let temp_dir = TempDir::new()?;
227        let local_domain = super::LocalDomain::new(temp_dir.path());
228
229        // Set home directory
230        local_domain.set_home(&temp_dir.path()).await?;
231
232        // Initially there should be no packages
233        let packages = local_domain.list_installed_packages().await?;
234        assert!(packages.is_empty());
235
236        // Add some packages to the lineage
237        let mut lineage = local_domain.lineage.read(&local_domain.storage).await?;
238
239        let namespaces = vec![
240            Namespace::from(("foo", "bar")),
241            Namespace::from(("test", "package")),
242            Namespace::from(("abc", "xyz")),
243        ];
244
245        for namespace in &namespaces {
246            lineage.packages.insert(
247                namespace.clone(),
248                crate::lineage::PackageLineage {
249                    commit: None,
250                    remote_uri: Some(ManifestUri {
251                        bucket: "test-bucket".to_string(),
252                        namespace: namespace.clone(),
253                        hash: "abcdef".to_string(),
254                        origin: None,
255                    }),
256                    base_hash: "abcdef".to_string(),
257                    latest_hash: "abcdef".to_string(),
258                    paths: std::collections::BTreeMap::new(),
259                },
260            );
261        }
262
263        local_domain
264            .lineage
265            .write(&local_domain.storage, lineage)
266            .await?;
267
268        // Now list_installed_packages should return packages in sorted order
269        let packages = local_domain.list_installed_packages().await?;
270        assert_eq!(packages.len(), 3);
271
272        // Check that packages are returned in sorted order by namespace
273        assert_eq!(packages[0].namespace, Namespace::from(("abc", "xyz")));
274        assert_eq!(packages[1].namespace, Namespace::from(("foo", "bar")));
275        assert_eq!(packages[2].namespace, Namespace::from(("test", "package")));
276
277        Ok(())
278    }
279}