skootrs_statestore/
lib.rs

1//
2// Copyright 2024 The Skootrs Authors.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! This is the crate where the statestore where the management of `Skootrs` project state is defined.
17//! The statestore currently supports an in memory `SurrealDB` instance that writes to a file.
18
19use std::collections::HashSet;
20
21use skootrs_lib::service::{
22    repo::{LocalRepoService, RepoService},
23    source::{LocalSourceService, SourceService},
24};
25
26use skootrs_model::skootrs::{InitializedProject, InitializedRepo, InitializedSource, SkootError};
27
28pub trait ProjectStateStore {
29    fn create(
30        &self,
31        project: InitializedProject,
32    ) -> impl std::future::Future<Output = Result<(), SkootError>> + Send;
33    fn read(
34        &self,
35    ) -> impl std::future::Future<Output = Result<Option<InitializedProject>, SkootError>> + Send;
36    fn update(
37        &self,
38        project: InitializedProject,
39    ) -> impl std::future::Future<Output = Result<(), SkootError>> + Send;
40}
41
42pub struct GitProjectStateStore<S: SourceService> {
43    // TODO: This should be a git repo type of some sort
44    pub source: InitializedSource,
45    pub source_service: S,
46}
47
48impl ProjectStateStore for GitProjectStateStore<LocalSourceService> {
49    async fn create(&self, project: InitializedProject) -> Result<(), SkootError> {
50        self.source_service.write_file(
51            self.source.clone(),
52            "./",
53            ".skootrs".to_string(),
54            serde_json::to_string(&project)?,
55        )?;
56        self.source_service.commit_and_push_changes(
57            self.source.clone(),
58            "Updated skootrs project state".to_string(),
59        )?;
60        Ok(())
61    }
62
63    async fn read(&self) -> Result<Option<InitializedProject>, SkootError> {
64        let project = self
65            .source_service
66            .read_file(&self.source, "./", ".skootrs".to_string())?;
67        Ok(Some(serde_json::from_str(&project).unwrap()))
68    }
69
70    async fn update(&self, project: InitializedProject) -> Result<(), SkootError> {
71        self.create(project).await
72    }
73}
74
75pub trait ProjectReferenceCache {
76    fn list(&self)
77        -> impl std::future::Future<Output = Result<HashSet<String>, SkootError>> + Send;
78    fn get(
79        &mut self,
80        repo_url: String,
81    ) -> impl std::future::Future<Output = Result<InitializedProject, SkootError>> + Send;
82    fn set(
83        &mut self,
84        repo_url: String,
85    ) -> impl std::future::Future<Output = Result<(), SkootError>> + Send;
86    fn delete(
87        &mut self,
88        repo_url: String,
89    ) -> impl std::future::Future<Output = Result<(), SkootError>> + Send;
90}
91
92pub struct InMemoryProjectReferenceCache {
93    pub save_path: String,
94    pub cache: HashSet<String>,
95    pub local_source_service: LocalSourceService,
96    pub local_repo_service: LocalRepoService,
97    pub clone_path: String,
98}
99
100impl ProjectReferenceCache for InMemoryProjectReferenceCache {
101    async fn list(&self) -> Result<HashSet<String>, SkootError> {
102        Ok(self.cache.clone())
103    }
104
105    async fn get(&mut self, repo_url: String) -> Result<InitializedProject, SkootError> {
106        let repo = InitializedRepo::try_from(repo_url)?;
107        let project = self
108            .local_repo_service
109            .fetch_file_content(&repo, ".skootrs")
110            .await?;
111        let initialized_project: InitializedProject = serde_json::from_str(&project)?;
112        Ok(initialized_project)
113    }
114
115    async fn set(&mut self, repo_url: String) -> Result<(), SkootError> {
116        self.cache.insert(repo_url);
117        self.save()?;
118        Ok(())
119    }
120
121    async fn delete(&mut self, repo_url: String) -> Result<(), SkootError> {
122        self.cache.remove(&repo_url);
123        self.save()?;
124        Ok(())
125    }
126}
127
128impl InMemoryProjectReferenceCache {
129    /// Create a new `InMemoryProjectReferenceCache` instance. The `save_path` is the path to the file where the cache will be saved.
130    #[must_use]
131    pub fn new(save_path: String) -> Self {
132        Self {
133            save_path,
134            cache: HashSet::new(),
135            local_source_service: LocalSourceService {},
136            local_repo_service: LocalRepoService {},
137            clone_path: "/tmp".to_string(),
138        }
139    }
140
141    /// Load the cache from the file at `save_path` or create a new cache if the file does not exist.
142    ///
143    /// # Errors
144    ///
145    /// Returns an error if the cache can't be loaded or created.
146    pub fn load_or_create(path: &str) -> Result<Self, SkootError> {
147        let mut cache = Self::new(path.to_string());
148        Ok(if cache.load().is_ok() {
149            cache
150        } else {
151            cache.save()?;
152            cache
153        })
154    }
155
156    /// Load the cache from the file at `save_path`.
157    ///
158    /// # Errors
159    ///
160    /// Returns an error if the cache can't be loaded.
161    pub fn load(&mut self) -> Result<(), SkootError> {
162        let deserialized_cache: HashSet<String> =
163            serde_json::from_str(&std::fs::read_to_string(&self.save_path)?)?;
164        self.cache = deserialized_cache;
165        Ok(())
166    }
167
168    /// Save the cache to the file at `save_path` in the struct.
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if the cache can't be saved.
173    pub fn save(&self) -> Result<(), SkootError> {
174        let serialized_cache = serde_json::to_string(&self.cache)?;
175        std::fs::write(&self.save_path, serialized_cache)?;
176        Ok(())
177    }
178}