1use fs_extra::error::{Error, ErrorKind, Result};
2use fs_extra::{dir, file};
3use lazy_static::lazy_static;
4use same_file::is_same_file;
5use std::fs;
6use std::path::Path;
7
8#[derive(PartialEq, Debug)]
9pub enum FileType {
10 File,
11 Dir,
12 Unknown,
13}
14
15impl From<&Path> for FileType {
16 fn from(path: &Path) -> Self {
17 match path.metadata() {
18 Ok(metadata) => {
19 if metadata.is_dir() {
20 FileType::Dir
21 } else {
22 FileType::File
23 }
24 }
25 Err(_) => FileType::Unknown,
26 }
27 }
28}
29
30#[derive(Clone, Copy)]
31pub enum TransferMode {
32 Move,
33 Copy,
34}
35
36pub fn transfer_path(src_path: &Path, dst_path: &Path, mode: TransferMode) -> Result<()> {
37 match (FileType::from(src_path), FileType::from(dst_path)) {
38 (FileType::Unknown, _) => Err(Error::new(
39 ErrorKind::NotFound,
40 &format!(
41 "Path '{}' not found or user lacks permission",
42 src_path.to_string_lossy()
43 ),
44 )),
45
46 (FileType::File, FileType::Dir) => Err(Error::new(
47 ErrorKind::Other,
48 &format!(
49 "Cannot to overwrite directory '{}' with file '{}'",
50 dst_path.to_string_lossy(),
51 src_path.to_string_lossy()
52 ),
53 )),
54
55 (FileType::Dir, FileType::File) => Err(Error::new(
56 ErrorKind::Other,
57 &format!(
58 "Cannot to overwrite file '{}' with directory '{}'",
59 dst_path.to_string_lossy(),
60 src_path.to_string_lossy()
61 ),
62 )),
63
64 (FileType::File, dst_type) => {
65 if let Some(dst_parent) = dst_path.parent() {
66 dir::create_all(dst_parent, false)?;
67 }
68 match mode {
69 TransferMode::Move => {
70 if fs::rename(src_path, dst_path).is_err() {
71 file::move_file(src_path, dst_path, &FILE_COPY_OPTIONS)?;
72 }
73 }
74 TransferMode::Copy => {
75 if dst_type == FileType::Unknown || !is_same_file(src_path, dst_path)? {
76 file::copy(src_path, dst_path, &FILE_COPY_OPTIONS)?;
77 }
78 }
79 }
80 Ok(())
81 }
82
83 (FileType::Dir, dst_type) => {
84 dir::create_all(dst_path, false)?;
85 match mode {
86 TransferMode::Move => {
87 if fs::rename(src_path, dst_path).is_err() {
88 dir::move_dir(src_path, dst_path, &DIR_COPY_OPTIONS)?;
89 }
90 }
91 TransferMode::Copy => {
92 if dst_type == FileType::Unknown || !is_same_file(src_path, dst_path)? {
93 dir::copy(src_path, dst_path, &DIR_COPY_OPTIONS)?;
94 }
95 }
96 }
97 Ok(())
98 }
99 }
100}
101
102lazy_static! {
103 pub static ref FILE_COPY_OPTIONS: file::CopyOptions = get_file_copy_options();
104 pub static ref DIR_COPY_OPTIONS: dir::CopyOptions = get_dir_copy_options();
105}
106
107fn get_file_copy_options() -> file::CopyOptions {
108 let mut options = file::CopyOptions::new();
109 options.overwrite = true;
110 options.skip_exist = false;
111 options
112}
113
114fn get_dir_copy_options() -> dir::CopyOptions {
115 let mut options = dir::CopyOptions::new();
116 options.overwrite = true;
117 options.skip_exist = false;
118 options.copy_inside = true;
119 options.content_only = true;
120 options
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::transfer::testing::{debug_fse_error_kind, unpack_fse_error};
127 use assert_fs::prelude::*;
128 use assert_fs::{NamedTempFile, TempDir};
129 use fs_extra::error::ErrorKind;
130 use ntest::*;
131 use test_case::test_case;
132
133 #[test_case(temp_dir().path(), FileType::Dir ; "dir")]
134 #[test_case(touch(temp_file("a")).path(), FileType::File ; "file")]
135 #[test_case(temp_file("b").path(), FileType::Unknown ; "unknown")]
136 fn file_type(path: &Path, file_type: FileType) {
137 assert_eq!(FileType::from(path), file_type);
138 }
139
140 mod transfer_path {
141 use super::*;
142
143 #[test]
144 fn path_not_found() {
145 let src_file = temp_file("a");
146
147 assert_eq!(
148 transfer_path(src_file.path(), &Path::new("b"), TransferMode::Move) .map_err(unpack_fse_error),
150 Err((
151 debug_fse_error_kind(ErrorKind::NotFound),
152 format!(
153 "Path '{}' not found or user lacks permission",
154 src_file.path().to_string_lossy()
155 )
156 ))
157 );
158
159 src_file.assert(predicates::path::missing());
160 }
161
162 #[test]
163 fn overwrite_dir_with_file() {
164 let src_file = touch(temp_file("a"));
165 let dst_dir = temp_dir();
166
167 assert_eq!(
168 transfer_path(src_file.path(), dst_dir.path(), TransferMode::Move) .map_err(unpack_fse_error),
170 Err((
171 debug_fse_error_kind(ErrorKind::Other),
172 format!(
173 "Cannot to overwrite directory '{}' with file '{}'",
174 dst_dir.path().to_string_lossy(),
175 src_file.path().to_string_lossy()
176 ),
177 ))
178 );
179
180 src_file.assert(predicates::path::is_file());
181 dst_dir.assert(predicates::path::is_dir());
182 }
183
184 #[test]
185 fn overwrite_file_with_dir() {
186 let src_dir = temp_dir();
187 let dst_file = touch(temp_file("a"));
188
189 assert_eq!(
190 transfer_path(src_dir.path(), dst_file.path(), TransferMode::Move) .map_err(unpack_fse_error),
192 Err((
193 debug_fse_error_kind(ErrorKind::Other),
194 format!(
195 "Cannot to overwrite file '{}' with directory '{}'",
196 dst_file.path().to_string_lossy(),
197 src_dir.path().to_string_lossy()
198 ),
199 ))
200 );
201
202 src_dir.assert(predicates::path::is_dir());
203 dst_file.assert(predicates::path::is_file());
204 }
205
206 #[test]
207 fn rename_file() {
208 let src_file = write(temp_file("a"), "1");
209 let dst_file = temp_file("b");
210
211 assert_eq!(
212 transfer_path(src_file.path(), dst_file.path(), TransferMode::Move)
213 .map_err(unpack_fse_error),
214 Ok(())
215 );
216
217 src_file.assert(predicates::path::missing());
218 dst_file.assert("1");
219 }
220
221 #[test]
222 fn rename_file_to_itself() {
223 let src_file = write(temp_file("a"), "1");
224
225 assert_eq!(
226 transfer_path(src_file.path(), src_file.path(), TransferMode::Move)
227 .map_err(unpack_fse_error),
228 Ok(())
229 );
230
231 src_file.assert("1");
232 }
233
234 #[test]
235 fn move_file_to_other() {
236 let src_file = write(temp_file("a"), "1");
237 let dst_file = write(temp_file("b"), "2");
238
239 assert_eq!(
240 transfer_path(src_file.path(), dst_file.path(), TransferMode::Move)
241 .map_err(unpack_fse_error),
242 Ok(())
243 );
244
245 src_file.assert(predicates::path::missing());
246 dst_file.assert("1");
247 }
248
249 #[test]
250 fn copy_file() {
251 let src_file = write(temp_file("a"), "1");
252 let dst_file = temp_file("b");
253
254 assert_eq!(
255 transfer_path(src_file.path(), dst_file.path(), TransferMode::Copy)
256 .map_err(unpack_fse_error),
257 Ok(())
258 );
259
260 src_file.assert("1");
261 dst_file.assert("1");
262 }
263
264 #[test]
265 #[timeout(5000)] fn copy_file_to_itself() {
267 let src_file = write(temp_file("a"), "1");
268
269 assert_eq!(
270 transfer_path(src_file.path(), src_file.path(), TransferMode::Copy)
271 .map_err(unpack_fse_error),
272 Ok(())
273 );
274
275 src_file.assert("1");
276 }
277
278 #[test]
279 fn copy_file_to_other() {
280 let src_file = write(temp_file("a"), "1");
281 let dst_file = write(temp_file("b"), "2");
282
283 assert_eq!(
284 transfer_path(src_file.path(), dst_file.path(), TransferMode::Copy)
285 .map_err(unpack_fse_error),
286 Ok(())
287 );
288
289 src_file.assert("1");
290 dst_file.assert("1");
291 }
292
293 #[test]
294 fn rename_dir() {
295 let root_dir = temp_dir();
296
297 let src_dir = mkdir(root_dir.child("a"));
298 let src_file = write(src_dir.child("c"), "1");
299
300 let dst_dir = root_dir.child("b");
301 let dst_file = dst_dir.child("c");
302
303 assert_eq!(
304 transfer_path(src_dir.path(), dst_dir.path(), TransferMode::Move)
305 .map_err(unpack_fse_error),
306 Ok(())
307 );
308
309 src_dir.assert(predicates::path::missing());
310 src_file.assert(predicates::path::missing());
311
312 dst_dir.assert(predicates::path::is_dir());
313 dst_file.assert("1");
314 }
315
316 #[test]
317 fn rename_dir_to_itself() {
318 let src_dir = temp_dir();
319 let src_file = write(src_dir.child("a"), "1");
320
321 assert_eq!(
322 transfer_path(src_dir.path(), src_dir.path(), TransferMode::Move)
323 .map_err(unpack_fse_error),
324 Ok(())
325 );
326
327 src_dir.assert(predicates::path::is_dir());
328 src_file.assert("1");
329 }
330
331 #[test]
332 fn move_dir_to_other() {
333 let root_dir = temp_dir();
334
335 let src_dir = mkdir(root_dir.child("a"));
336 let src_file = write(src_dir.child("c"), "1");
337
338 let dst_dir = mkdir(root_dir.child("b"));
339 let dst_file = write(dst_dir.child("c"), "2");
340
341 assert_eq!(
342 transfer_path(src_dir.path(), dst_dir.path(), TransferMode::Move)
343 .map_err(unpack_fse_error),
344 Ok(())
345 );
346
347 src_dir.assert(predicates::path::missing());
348 src_file.assert(predicates::path::missing());
349
350 dst_dir.assert(predicates::path::is_dir());
351 dst_file.assert("1");
352 }
353
354 #[test]
355 fn copy_dir() {
356 let root_dir = temp_dir();
357
358 let src_dir = mkdir(root_dir.child("a"));
359 let src_file = write(src_dir.child("c"), "1");
360
361 let dst_dir = root_dir.child("b");
362 let dst_file = dst_dir.child("c");
363
364 assert_eq!(
365 transfer_path(src_dir.path(), dst_dir.path(), TransferMode::Copy)
366 .map_err(unpack_fse_error),
367 Ok(())
368 );
369
370 src_dir.assert(predicates::path::is_dir());
371 src_file.assert("1");
372
373 dst_dir.assert(predicates::path::is_dir());
374 dst_file.assert("1");
375 }
376
377 #[test]
378 #[timeout(5000)] fn copy_dir_to_itself() {
380 let src_dir = temp_dir();
381 let src_file = write(src_dir.child("a"), "1");
382
383 assert_eq!(
384 transfer_path(src_dir.path(), src_dir.path(), TransferMode::Copy)
385 .map_err(unpack_fse_error),
386 Ok(())
387 );
388
389 src_dir.assert(predicates::path::is_dir());
390 src_file.assert("1");
391 }
392
393 #[test]
394 fn copy_dir_to_other() {
395 let root_dir = temp_dir();
396
397 let src_dir = mkdir(root_dir.child("a"));
398 let src_file = write(src_dir.child("c"), "1");
399
400 let dst_dir = mkdir(root_dir.child("b"));
401 let dst_file = write(dst_dir.child("c"), "2");
402
403 assert_eq!(
404 transfer_path(src_dir.path(), dst_dir.path(), TransferMode::Copy)
405 .map_err(unpack_fse_error),
406 Ok(())
407 );
408
409 src_dir.assert(predicates::path::is_dir());
410 src_file.assert("1");
411
412 dst_dir.assert(predicates::path::is_dir());
413 dst_file.assert("1");
414 }
415 }
416
417 #[test]
418 fn same_dir_and_file_copy_options() {
419 assert_eq!(DIR_COPY_OPTIONS.overwrite, FILE_COPY_OPTIONS.overwrite);
420 assert_eq!(DIR_COPY_OPTIONS.skip_exist, FILE_COPY_OPTIONS.skip_exist);
421 assert_eq!(DIR_COPY_OPTIONS.buffer_size, FILE_COPY_OPTIONS.buffer_size);
422 }
423
424 fn temp_dir() -> TempDir {
425 TempDir::new().unwrap()
426 }
427
428 fn temp_file(name: &str) -> NamedTempFile {
429 NamedTempFile::new(name).unwrap()
430 }
431
432 fn mkdir<P: PathCreateDir>(path: P) -> P {
433 path.create_dir_all().unwrap();
434 path
435 }
436
437 fn touch<F: FileTouch>(file: F) -> F {
438 file.touch().unwrap();
439 file
440 }
441
442 fn write<F: FileWriteStr>(file: F, data: &str) -> F {
443 file.write_str(data).unwrap();
444 file
445 }
446}