1#![allow(clippy::module_name_repetitions)]
17
18use std::{fs, path::Path, process::Command};
19
20use sha2::Digest;
21use tracing::{debug, info};
22
23use skootrs_model::skootrs::{
24 InitializedRepo, InitializedSource, SkootError, SourceInitializeParams,
25};
26
27use super::repo::{LocalRepoService, RepoService};
28pub trait SourceService {
32 fn initialize(
39 &self,
40 params: SourceInitializeParams,
41 initialized_repo: InitializedRepo,
42 ) -> Result<InitializedSource, SkootError>;
43
44 fn commit_and_push_changes(
50 &self,
51 source: InitializedSource,
52 message: String,
53 ) -> Result<(), SkootError>;
54
55 fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(
61 &self,
62 source: InitializedSource,
63 path: P,
64 name: String,
65 contents: C,
66 ) -> Result<(), SkootError>;
67
68 fn read_file<P: AsRef<Path>>(
74 &self,
75 source: &InitializedSource,
76 path: P,
77 name: String,
78 ) -> Result<String, SkootError>;
79
80 fn hash_file<P: AsRef<Path>>(
86 &self,
87 source: &InitializedSource,
88 path: P,
89 name: String,
90 ) -> Result<String, SkootError>;
91
92 fn pull_updates(&self, source: InitializedSource) -> Result<(), SkootError>;
98}
99
100#[derive(Debug)]
103pub struct LocalSourceService {}
104
105impl SourceService for LocalSourceService {
106 fn initialize(
109 &self,
110 params: SourceInitializeParams,
111 initialized_repo: InitializedRepo,
112 ) -> Result<InitializedSource, SkootError> {
113 let repo_service = LocalRepoService {};
114 repo_service.clone_local(initialized_repo, params.parent_path)
115 }
116
117 fn commit_and_push_changes(
118 &self,
119 source: InitializedSource,
120 message: String,
121 ) -> Result<(), SkootError> {
122 let _output = Command::new("git")
123 .arg("add")
124 .arg(".")
125 .current_dir(&source.path)
126 .output()?;
127
128 let _output = Command::new("git")
129 .arg("commit")
130 .arg("-m")
131 .arg(message)
132 .current_dir(&source.path)
133 .output()?;
134 info!("Committed changes for {}", source.path);
135
136 let _output = Command::new("git")
137 .arg("push")
138 .current_dir(&source.path)
139 .output()?;
140 info!("Pushed changes for {}", source.path);
141 Ok(())
142 }
143
144 fn write_file<P: AsRef<Path>, C: AsRef<[u8]>>(
147 &self,
148 source: InitializedSource,
149 path: P,
150 name: String,
151 contents: C,
152 ) -> Result<(), SkootError> {
153 let full_path = Path::new(&source.path).join(&path);
154 info!("Creating path {:?}", &full_path);
156 fs::create_dir_all(&full_path)?;
157 let complete_path = full_path.join(name);
158 fs::write(complete_path, contents)?;
159 debug!("{:?} file written", &full_path);
160 Ok(())
161 }
162
163 fn read_file<P: AsRef<Path>>(
164 &self,
165 source: &InitializedSource,
166 path: P,
167 name: String,
168 ) -> Result<String, SkootError> {
169 let full_path = Path::new(&source.path).join(&path).join(name);
170 let contents = fs::read_to_string(full_path)?;
171 Ok(contents)
172 }
173
174 fn hash_file<P: AsRef<Path>>(
175 &self,
176 source: &InitializedSource,
177 path: P,
178 name: String,
179 ) -> Result<String, SkootError> {
180 let full_path: std::path::PathBuf = Path::new(&source.path).join(&path).join(name);
181 debug!("Hashing file {:?}", &full_path);
182 let mut contents = fs::File::open(&full_path)?;
183 let mut hasher = sha2::Sha256::new();
184 std::io::copy(&mut contents, &mut hasher)?;
185 let hash = hasher.finalize();
186 debug!("Hashed file {:?} with hash: {:x}", &full_path, &hash);
189 Ok(format!("{hash:x}"))
190 }
191
192 fn pull_updates(&self, source: InitializedSource) -> Result<(), SkootError> {
193 let _output = Command::new("git")
194 .arg("pull")
195 .current_dir(&source.path)
196 .output()?;
197 info!("Pulled updates for {}", source.path);
198 Ok(())
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use skootrs_model::skootrs::{GithubUser, InitializedGithubRepo};
206 use std::path::PathBuf;
207 use tempdir::TempDir;
208
209 #[test]
210 fn test_initialize() {
211 let source_service = LocalSourceService {};
212 let temp_dir = TempDir::new("test").unwrap();
213 let parent_path = temp_dir.path().to_str().unwrap();
214 let params = SourceInitializeParams {
215 parent_path: parent_path.to_string(),
216 };
217 let initialized_repo = InitializedRepo::Github(InitializedGithubRepo {
218 name: "skootrs".to_string(),
219 organization: GithubUser::Organization("kusaridev".to_string()),
220 });
221 let result = source_service.initialize(params, initialized_repo);
222 assert!(result.is_ok());
223 let initialized_source = result.unwrap();
224 assert_eq!(
225 initialized_source.path,
226 format!("{}/{}", parent_path, "skootrs")
227 );
228 }
229
230 #[test]
231 fn test_write_file() {
232 let source_service = LocalSourceService {};
233 let temp_dir = TempDir::new("test").unwrap();
234 let initialized_source = InitializedSource {
235 path: temp_dir.path().to_str().unwrap().to_string(),
236 };
237 let path = "subdirectory";
238 let name = "file.txt".to_string();
239 let contents = "File contents".as_bytes();
240 let result = source_service.write_file(initialized_source, path, name.clone(), contents);
241 assert!(result.is_ok());
242 let file_path =
243 PathBuf::from(format!("{}/{}", temp_dir.path().to_str().unwrap(), path)).join(name);
244 assert!(file_path.exists());
245 let file_contents = fs::read_to_string(file_path).unwrap();
246 assert_eq!(file_contents, "File contents");
247 }
248
249 #[test]
250 fn test_read_file() {
251 let source_service = LocalSourceService {};
252 let temp_dir = TempDir::new("test").unwrap();
253 let initialized_source = InitializedSource {
254 path: temp_dir.path().to_str().unwrap().to_string(),
255 };
256 let path = "subdirectory";
257 let name = "file.txt".to_string();
258 let contents = "File contents".as_bytes();
259 let result =
260 source_service.write_file(initialized_source.clone(), path, name.clone(), contents);
261 assert!(result.is_ok());
262 let file_path = PathBuf::from(format!("{}/{}", temp_dir.path().to_str().unwrap(), path))
263 .join(name.clone());
264 assert!(file_path.exists());
265 let file_contents = source_service
266 .read_file(&initialized_source, path, name)
267 .unwrap();
268 assert_eq!(file_contents, "File contents");
269 }
270}