1use std::path::PathBuf;
2
3use super::{LoadError, Runner, RunnerError};
4use crate::config::NamedPackage;
5use crate::logger::LoggerOutput;
6use crate::trace::PkgTrace;
7
8impl<O: LoggerOutput> Runner<O> {
9 pub fn load_module(
10 &mut self,
11 package: &NamedPackage,
12 trace: Option<&PkgTrace>,
13 ) -> Result<PkgTrace, RunnerError> {
14 self.logger.load_module(package.name());
15
16 let result = if let Some(trace) = trace {
17 self.load_with_trace(package, trace)
18 } else {
19 self.load_directly(package)
20 };
21
22 result.map_err(|e| RunnerError::LoadModuleError {
23 source: e,
24 module: package.name().to_string(),
25 })
26 }
27
28 fn load_directly(&mut self, package: &NamedPackage) -> Result<PkgTrace, LoadError> {
29 let mut trace = PkgTrace::new(package.get_directory());
30
31 let pkg_dir = self.absolute_path_from(&trace.directory);
32 if !pkg_dir.exists() {
33 return Err(LoadError::PkgDirNotFound(package.name().to_string()));
34 }
35
36 for (src, dst) in package.maps() {
37 let src_path = pkg_dir.join(src);
38 if !src_path.exists() {
39 return Err(LoadError::SrcNotExists(src.to_string()));
40 }
41
42 let dst_path = PathBuf::from(&dst);
43 if dst_path.exists() {
44 return Err(LoadError::DstAlreadyExists {
45 src: src.clone(),
46 dst: dst_path,
47 });
48 }
49
50 if let Some(parent) = dst_path.parent()
51 && !parent.exists()
52 {
53 self.create_dir(parent)?;
54 }
55
56 self.create_symlink(&src_path, &dst_path)?;
57
58 trace.maps.insert(src.into(), dst.into());
59 }
60
61 Ok(trace)
62 }
63
64 fn load_with_trace(
65 &mut self,
66 package: &NamedPackage,
67 old_trace: &PkgTrace,
68 ) -> Result<PkgTrace, LoadError> {
69 let directory = package.get_directory();
70 if directory != old_trace.directory {
71 return self.load_with_pkg_dir_changed(package, old_trace);
72 }
73
74 let mut trace = PkgTrace::new(directory);
75
76 let pkg_dir = self.absolute_path_from(&trace.directory);
77 if !pkg_dir.exists() {
78 return Err(LoadError::PkgDirNotFound(package.name().to_string()));
79 }
80
81 for (src, dst) in package.maps() {
82 let src_path = pkg_dir.join(src);
83 if !src_path.exists() {
84 return Err(LoadError::SrcNotExists(src.to_string()));
85 }
86
87 let dst_path = PathBuf::from(&dst);
88
89 if let Some(dst_in_trace) = old_trace.maps.get(src) {
90 let dst_in_trace = PathBuf::from(dst_in_trace);
91 if dst_in_trace.exists() {
92 if !dst_in_trace.is_symlink() {
93 return Err(LoadError::DstNotSymlink {
94 src: src.clone(),
95 dst: dst_in_trace,
96 });
97 }
98
99 if dst_path == dst_in_trace {
100 trace.maps.insert(src.into(), dst.into());
101 continue;
102 }
103
104 self.remove_symlink(&src_path, dst_in_trace)?;
105 }
106 }
107
108 if dst_path.exists() {
109 return Err(LoadError::DstAlreadyExists {
110 src: src.clone(),
111 dst: dst_path,
112 });
113 }
114
115 if let Some(parent) = dst_path.parent()
116 && !parent.exists()
117 {
118 self.create_dir(parent)?;
119 }
120
121 self.create_symlink(&src_path, dst)?;
122
123 trace.maps.insert(src.into(), dst.into());
124 }
125
126 for (src, dst) in &old_trace.maps {
127 let dst_path = PathBuf::from(&dst);
128
129 if dst_path.exists() && !trace.maps.contains_key(src) {
130 if !dst_path.is_symlink() {
131 return Err(LoadError::DstNotSymlink {
132 src: src.clone(),
133 dst: dst_path,
134 });
135 }
136 self.remove_symlink(pkg_dir.join(src), dst)?;
137 }
138 }
139
140 Ok(trace)
141 }
142
143 fn load_with_pkg_dir_changed(
144 &mut self,
145 _package: &NamedPackage,
146 _old_trace: &PkgTrace,
147 ) -> Result<PkgTrace, LoadError> {
148 todo!()
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use std::fs;
155
156 use super::*;
157 use crate::test_utils::prelude::*;
158
159 mod load_without_trace {
160 use super::*;
161
162 #[gtest]
163 fn it_works() -> Result<()> {
164 let (td, pkg, mut runner) = common_local_pkg()?;
165
166 let trace = runner.load_module(&pkg, None)?;
167
168 let dst_file = td.join(DST_FILE_PATH);
169 let dst_dir = td.join(DST_DIR_PATH);
170
171 expect_that!(
172 dst_file,
173 is_symlink_for(td.join(SRC_FILE_PATH).canonicalize()?),
174 "dst_file should point to the absolute path of src_file"
175 );
176
177 expect_that!(
178 dst_dir,
179 is_symlink_for(td.join(SRC_DIR_PATH).canonicalize()?),
180 "dst_dir should point to the absolute path of src_dir"
181 );
182
183 expect_eq!(trace.directory, "test_package");
184 expect_eq!(trace.maps.len(), 2);
185 expect_eq!(
186 trace.maps["src_file"],
187 td.join(DST_FILE_PATH).to_str().unwrap()
188 );
189 expect_eq!(
190 trace.maps["src_dir"],
191 td.join(DST_DIR_PATH).to_str().unwrap()
192 );
193
194 Ok(())
195 }
196
197 #[gtest]
198 fn runner_output() -> Result<()> {
199 let (td, pkg, mut runner) = common_local_pkg()?;
200 runner.load_module(&pkg, None)?;
201
202 let messages = runner.messages();
203 expect_eq!(
204 *messages,
205 [
206 LogMessage::LoadModule("test_package".into()),
207 LogMessage::CreateDir(td.join("./test_pkg")),
208 LogMessage::CreateSymlink {
209 src: td.join(SRC_FILE_PATH).canonicalize()?,
210 dst: td.join(DST_FILE_PATH)
211 },
212 LogMessage::CreateDir(td.join("./test_a/test_b")),
213 LogMessage::CreateSymlink {
214 src: td.join(SRC_DIR_PATH).canonicalize()?,
215 dst: td.join(DST_DIR_PATH)
216 }
217 ]
218 );
219
220 Ok(())
221 }
222
223 #[gtest]
224 fn no_pkg_dir() -> Result<()> {
225 let (td, pkg, mut runner) = common_local_pkg()?;
226 fs::remove_dir_all(td.join("test_package"))?;
227
228 let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
229 expect_that!(result, pat!(LoadError::PkgDirNotFound("test_package")));
230
231 Ok(())
232 }
233
234 #[gtest]
235 fn src_not_exists() -> Result<()> {
236 let (td, pkg, mut runner) = common_local_pkg()?;
237 fs::remove_file(td.join(SRC_FILE_PATH))?;
238
239 let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
240 expect_that!(result, pat!(LoadError::SrcNotExists("src_file")));
241
242 Ok(())
243 }
244
245 #[gtest]
246 fn dst_already_exists() -> Result<()> {
247 let (td, pkg, mut runner) = common_local_pkg()?;
248 fs::create_dir_all(td.join(DST_FILE_PATH))?;
249
250 let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
251 expect_that!(
252 result,
253 pat!(LoadError::DstAlreadyExists {
254 src: "src_file",
255 dst: &td.join(DST_FILE_PATH)
256 })
257 );
258
259 Ok(())
260 }
261 }
262
263 mod load_with_trace_without_dir_changed {
264 use super::*;
265
266 fn setup() -> Result<(TempDir, NamedPackage, PkgTrace)> {
267 let (td, pkg, mut runner) = common_local_pkg()?;
268 let trace = runner.load_module(&pkg, None)?;
269 Ok((td, pkg, trace))
270 }
271
272 #[gtest]
273 fn no_pkg_dir() -> Result<()> {
274 let (td, pkg, trace) = setup()?;
275 let mut runner = common_runner(td.path());
276 fs::remove_dir_all(td.join("test_package"))?;
277
278 let result = runner
279 .load_module(&pkg, Some(&trace))
280 .unwrap_err()
281 .unwrap_load();
282 expect_that!(result, pat!(LoadError::PkgDirNotFound("test_package")));
283
284 Ok(())
285 }
286
287 #[gtest]
288 fn no_changed() -> Result<()> {
289 let (td, pkg, trace) = setup()?;
290 let mut runner = common_runner(td.path());
291 let new_trace = runner.load_module(&pkg, Some(&trace))?;
292
293 expect_eq!(new_trace, trace);
294 expect_eq!(runner.messages().len(), 1);
295 expect_that!(
296 runner.messages()[0],
297 pat!(LogMessage::LoadModule("test_package"))
298 );
299
300 Ok(())
301 }
302
303 #[gtest]
304 fn just_update() -> Result<()> {
305 let (td, mut pkg, trace) = setup()?;
306 pkg.insert_map("src_file", td.join("new_dest_file").to_string_lossy());
307
308 let mut runner = common_runner(td.path());
309 let new_trace = runner.load_module(&pkg, Some(&trace))?;
310
311 expect_eq!(new_trace.directory, trace.directory);
312 expect_eq!(new_trace.maps.len(), trace.maps.len());
313 expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
314 expect_eq!(
315 new_trace.maps["src_file"],
316 td.join("new_dest_file").to_str().unwrap()
317 );
318
319 expect_that!(
320 runner.messages(),
321 superset_of([
322 &LogMessage::RemoveSymlink {
323 src: td.join(SRC_FILE_PATH).canonicalize()?,
324 dst: td.join(DST_FILE_PATH)
325 },
326 &LogMessage::CreateSymlink {
327 src: td.join(SRC_FILE_PATH).canonicalize()?,
328 dst: td.join("new_dest_file")
329 },
330 ])
331 );
332
333 Ok(())
334 }
335
336 #[gtest]
337 fn add_new() -> Result<()> {
338 let (td, mut pkg, trace) = setup()?;
339 let td = td.file("test_package/new_src_file", "")?;
340 let new_dst_path = td.join("nonexistent_parent/new_dest_file");
341 pkg.insert_map("new_src_file", new_dst_path.to_string_lossy());
342
343 let mut runner = common_runner(td.path());
344 let new_trace = runner.load_module(&pkg, Some(&trace))?;
345
346 expect_eq!(new_trace.directory, trace.directory);
347 expect_eq!(new_trace.maps.len(), trace.maps.len() + 1);
348 expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
349 expect_eq!(new_trace.maps["src_file"], trace.maps["src_file"]);
350 expect_eq!(
351 new_trace.maps["new_src_file"],
352 new_dst_path.to_str().unwrap(),
353 );
354
355 expect_that!(
356 runner.messages(),
357 superset_of([&LogMessage::CreateSymlink {
358 src: td.join("test_package/new_src_file"),
359 dst: new_dst_path
360 },])
361 );
362
363 Ok(())
364 }
365
366 #[gtest]
367 fn remove_old() -> Result<()> {
368 let (td, mut pkg, trace) = setup()?;
369 pkg.remove_map("src_file");
370
371 let mut runner = common_runner(td.path());
372 let new_trace = runner.load_module(&pkg, Some(&trace))?;
373
374 expect_eq!(new_trace.directory, trace.directory);
375 expect_eq!(new_trace.maps.len(), trace.maps.len() - 1);
376 expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
377 expect_eq!(new_trace.maps.get("src_file"), None);
378
379 expect_that!(
380 runner.messages(),
381 superset_of([&LogMessage::RemoveSymlink {
382 src: td.join(SRC_FILE_PATH),
383 dst: td.join(DST_FILE_PATH)
384 },])
385 );
386
387 Ok(())
388 }
389
390 #[gtest]
391 fn remove_old_but_dst_not_a_symlink() -> Result<()> {
392 let (td, mut pkg, trace) = setup()?;
393 pkg.remove_map("src_file");
394
395 fs::remove_file(td.join(DST_FILE_PATH))?;
396 fs::write(td.join(DST_FILE_PATH), "")?;
397
398 let mut runner = common_runner(td.path());
399 let err = runner
400 .load_module(&pkg, Some(&trace))
401 .unwrap_err()
402 .unwrap_load();
403 expect_that!(
404 err,
405 pat!(LoadError::DstNotSymlink {
406 src: "src_file",
407 dst: &td.join(DST_FILE_PATH)
408 })
409 );
410
411 Ok(())
412 }
413
414 #[gtest]
415 fn src_not_exists() -> Result<()> {
416 let (td, pkg, trace) = setup()?;
417 fs::remove_file(td.join(SRC_FILE_PATH))?;
418
419 let mut runner = common_runner(td.path());
420 let err = runner
421 .load_module(&pkg, Some(&trace))
422 .unwrap_err()
423 .unwrap_load();
424 expect_that!(err, pat!(LoadError::SrcNotExists("src_file")));
425
426 Ok(())
427 }
428
429 #[gtest]
430 fn dst_exists_but_not_in_trace() -> Result<()> {
431 let (td, mut pkg, trace) = setup()?;
432 let td = td
433 .file("test_package/new_src_file", "")?
434 .file("new_dest_file", "")?;
435 pkg.insert_map("new_src_file", td.join("new_dest_file").to_string_lossy());
436
437 let mut runner = common_runner(td.path());
438 let err = runner
439 .load_module(&pkg, Some(&trace))
440 .unwrap_err()
441 .unwrap_load();
442 expect_that!(
443 err,
444 pat!(LoadError::DstAlreadyExists {
445 src: "new_src_file",
446 dst: &td.join("new_dest_file")
447 })
448 );
449
450 Ok(())
451 }
452
453 #[gtest]
454 fn dst_in_trace_but_not_a_symlink() -> Result<()> {
455 let (td, pkg, trace) = setup()?;
456 fs::remove_file(td.join(DST_FILE_PATH))?;
457 fs::write(td.join(DST_FILE_PATH), "")?;
458
459 let mut runner = common_runner(td.path());
460 let err = runner
461 .load_module(&pkg, Some(&trace))
462 .unwrap_err()
463 .unwrap_load();
464 expect_that!(
465 err,
466 pat!(LoadError::DstNotSymlink {
467 src: "src_file",
468 dst: &td.join(DST_FILE_PATH)
469 })
470 );
471
472 Ok(())
473 }
474
475 #[gtest]
476 fn dst_in_trace_but_not_exists() -> Result<()> {
477 let (td, pkg, trace) = setup()?;
478 fs::remove_file(td.join(DST_FILE_PATH))?;
479
480 let mut runner = common_runner(td.path());
481 let new_trace = runner.load_module(&pkg, Some(&trace))?;
482
483 expect_eq!(new_trace, trace);
484 expect_that!(
485 runner.messages(),
486 superset_of([&LogMessage::CreateSymlink {
487 src: td.join(SRC_FILE_PATH),
488 dst: td.join(DST_FILE_PATH)
489 }])
490 );
491
492 Ok(())
493 }
494 }
495}