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!(messages.len(), 5);
204 expect_that!(messages[0], pat!(LogMessage::LoadModule("test_package")));
205 expect_that!(
206 messages,
207 superset_of([
208 &LogMessage::CreateDir(td.join("./test_pkg")),
209 &LogMessage::CreateSymlink {
210 src: td.join(SRC_FILE_PATH).canonicalize()?,
211 dst: td.join(DST_FILE_PATH)
212 }
213 ])
214 );
215 expect_that!(
216 messages,
217 superset_of([
218 &LogMessage::CreateDir(td.join("./test_a/test_b")),
219 &LogMessage::CreateSymlink {
220 src: td.join(SRC_DIR_PATH).canonicalize()?,
221 dst: td.join(DST_DIR_PATH)
222 }
223 ])
224 );
225
226 Ok(())
227 }
228
229 #[gtest]
230 fn no_pkg_dir() -> Result<()> {
231 let (td, pkg, mut runner) = common_local_pkg()?;
232 fs::remove_dir_all(td.join("test_package"))?;
233
234 let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
235 expect_that!(result, pat!(LoadError::PkgDirNotFound("test_package")));
236
237 Ok(())
238 }
239
240 #[gtest]
241 fn src_not_exists() -> Result<()> {
242 let (td, pkg, mut runner) = common_local_pkg()?;
243 fs::remove_file(td.join(SRC_FILE_PATH))?;
244
245 let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
246 expect_that!(result, pat!(LoadError::SrcNotExists("src_file")));
247
248 Ok(())
249 }
250
251 #[gtest]
252 fn dst_already_exists() -> Result<()> {
253 let (td, pkg, mut runner) = common_local_pkg()?;
254 fs::create_dir_all(td.join(DST_FILE_PATH))?;
255
256 let result = runner.load_module(&pkg, None).unwrap_err().unwrap_load();
257 expect_that!(
258 result,
259 pat!(LoadError::DstAlreadyExists {
260 src: "src_file",
261 dst: &td.join(DST_FILE_PATH)
262 })
263 );
264
265 Ok(())
266 }
267 }
268
269 mod load_with_trace_without_dir_changed {
270 use super::*;
271
272 fn setup() -> Result<(TempDir, NamedPackage, PkgTrace)> {
273 let (td, pkg, mut runner) = common_local_pkg()?;
274 let trace = runner.load_module(&pkg, None)?;
275 Ok((td, pkg, trace))
276 }
277
278 #[gtest]
279 fn no_pkg_dir() -> Result<()> {
280 let (td, pkg, trace) = setup()?;
281 let mut runner = common_runner(td.path());
282 fs::remove_dir_all(td.join("test_package"))?;
283
284 let result = runner
285 .load_module(&pkg, Some(&trace))
286 .unwrap_err()
287 .unwrap_load();
288 expect_that!(result, pat!(LoadError::PkgDirNotFound("test_package")));
289
290 Ok(())
291 }
292
293 #[gtest]
294 fn no_changed() -> Result<()> {
295 let (td, pkg, trace) = setup()?;
296 let mut runner = common_runner(td.path());
297 let new_trace = runner.load_module(&pkg, Some(&trace))?;
298
299 expect_eq!(new_trace, trace);
300 expect_eq!(runner.messages().len(), 1);
301 expect_that!(
302 runner.messages()[0],
303 pat!(LogMessage::LoadModule("test_package"))
304 );
305
306 Ok(())
307 }
308
309 #[gtest]
310 fn just_update() -> Result<()> {
311 let (td, mut pkg, trace) = setup()?;
312 pkg.insert_map("src_file", td.join("new_dest_file").to_string_lossy());
313
314 let mut runner = common_runner(td.path());
315 let new_trace = runner.load_module(&pkg, Some(&trace))?;
316
317 expect_eq!(new_trace.directory, trace.directory);
318 expect_eq!(new_trace.maps.len(), trace.maps.len());
319 expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
320 expect_eq!(
321 new_trace.maps["src_file"],
322 td.join("new_dest_file").to_str().unwrap()
323 );
324
325 expect_that!(
326 runner.messages(),
327 superset_of([
328 &LogMessage::RemoveSymlink {
329 src: td.join(SRC_FILE_PATH).canonicalize()?,
330 dst: td.join(DST_FILE_PATH)
331 },
332 &LogMessage::CreateSymlink {
333 src: td.join(SRC_FILE_PATH).canonicalize()?,
334 dst: td.join("new_dest_file")
335 },
336 ])
337 );
338
339 Ok(())
340 }
341
342 #[gtest]
343 fn add_new() -> Result<()> {
344 let (td, mut pkg, trace) = setup()?;
345 let td = td.file("test_package/new_src_file", "")?;
346 let new_dst_path = td.join("nonexistent_parent/new_dest_file");
347 pkg.insert_map("new_src_file", new_dst_path.to_string_lossy());
348
349 let mut runner = common_runner(td.path());
350 let new_trace = runner.load_module(&pkg, Some(&trace))?;
351
352 expect_eq!(new_trace.directory, trace.directory);
353 expect_eq!(new_trace.maps.len(), trace.maps.len() + 1);
354 expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
355 expect_eq!(new_trace.maps["src_file"], trace.maps["src_file"]);
356 expect_eq!(
357 new_trace.maps["new_src_file"],
358 new_dst_path.to_str().unwrap(),
359 );
360
361 expect_that!(
362 runner.messages(),
363 superset_of([&LogMessage::CreateSymlink {
364 src: td.join("test_package/new_src_file"),
365 dst: new_dst_path
366 },])
367 );
368
369 Ok(())
370 }
371
372 #[gtest]
373 fn remove_old() -> Result<()> {
374 let (td, mut pkg, trace) = setup()?;
375 pkg.remove_map("src_file");
376
377 let mut runner = common_runner(td.path());
378 let new_trace = runner.load_module(&pkg, Some(&trace))?;
379
380 expect_eq!(new_trace.directory, trace.directory);
381 expect_eq!(new_trace.maps.len(), trace.maps.len() - 1);
382 expect_eq!(new_trace.maps["src_dir"], trace.maps["src_dir"]);
383 expect_eq!(new_trace.maps.get("src_file"), None);
384
385 expect_that!(
386 runner.messages(),
387 superset_of([&LogMessage::RemoveSymlink {
388 src: td.join(SRC_FILE_PATH),
389 dst: td.join(DST_FILE_PATH)
390 },])
391 );
392
393 Ok(())
394 }
395
396 #[gtest]
397 fn remove_old_but_dst_not_a_symlink() -> Result<()> {
398 let (td, mut pkg, trace) = setup()?;
399 pkg.remove_map("src_file");
400
401 fs::remove_file(td.join(DST_FILE_PATH))?;
402 fs::write(td.join(DST_FILE_PATH), "")?;
403
404 let mut runner = common_runner(td.path());
405 let err = runner
406 .load_module(&pkg, Some(&trace))
407 .unwrap_err()
408 .unwrap_load();
409 expect_that!(
410 err,
411 pat!(LoadError::DstNotSymlink {
412 src: "src_file",
413 dst: &td.join(DST_FILE_PATH)
414 })
415 );
416
417 Ok(())
418 }
419
420 #[gtest]
421 fn src_not_exists() -> Result<()> {
422 let (td, pkg, trace) = setup()?;
423 fs::remove_file(td.join(SRC_FILE_PATH))?;
424
425 let mut runner = common_runner(td.path());
426 let err = runner
427 .load_module(&pkg, Some(&trace))
428 .unwrap_err()
429 .unwrap_load();
430 expect_that!(err, pat!(LoadError::SrcNotExists("src_file")));
431
432 Ok(())
433 }
434
435 #[gtest]
436 fn dst_exists_but_not_in_trace() -> Result<()> {
437 let (td, mut pkg, trace) = setup()?;
438 let td = td
439 .file("test_package/new_src_file", "")?
440 .file("new_dest_file", "")?;
441 pkg.insert_map("new_src_file", td.join("new_dest_file").to_string_lossy());
442
443 let mut runner = common_runner(td.path());
444 let err = runner
445 .load_module(&pkg, Some(&trace))
446 .unwrap_err()
447 .unwrap_load();
448 expect_that!(
449 err,
450 pat!(LoadError::DstAlreadyExists {
451 src: "new_src_file",
452 dst: &td.join("new_dest_file")
453 })
454 );
455
456 Ok(())
457 }
458
459 #[gtest]
460 fn dst_in_trace_but_not_a_symlink() -> Result<()> {
461 let (td, pkg, trace) = setup()?;
462 fs::remove_file(td.join(DST_FILE_PATH))?;
463 fs::write(td.join(DST_FILE_PATH), "")?;
464
465 let mut runner = common_runner(td.path());
466 let err = runner
467 .load_module(&pkg, Some(&trace))
468 .unwrap_err()
469 .unwrap_load();
470 expect_that!(
471 err,
472 pat!(LoadError::DstNotSymlink {
473 src: "src_file",
474 dst: &td.join(DST_FILE_PATH)
475 })
476 );
477
478 Ok(())
479 }
480
481 #[gtest]
482 fn dst_in_trace_but_not_exists() -> Result<()> {
483 let (td, pkg, trace) = setup()?;
484 fs::remove_file(td.join(DST_FILE_PATH))?;
485
486 let mut runner = common_runner(td.path());
487 let new_trace = runner.load_module(&pkg, Some(&trace))?;
488
489 expect_eq!(new_trace, trace);
490 expect_that!(
491 runner.messages(),
492 superset_of([&LogMessage::CreateSymlink {
493 src: td.join(SRC_FILE_PATH),
494 dst: td.join(DST_FILE_PATH)
495 }])
496 );
497
498 Ok(())
499 }
500 }
501}