1#[cfg(target_os = "macos")]
4#[macro_use]
5extern crate lazy_static;
6
7mod platform;
8
9use std::{fs, io};
10use std::path::Path;
11
12pub fn swap<A, B>(a: A, b: B) -> io::Result<()> where A: AsRef<Path>, B: AsRef<Path> {
14 platform::swap(a, b)
15}
16
17pub fn swap_nonatomic<A, B>(a: A, b: B) -> io::Result<()> where A: AsRef<Path>, B: AsRef<Path> {
19 const TMP_SWAP_FILE: &'static str = "tmp.fs_swap";
20
21 let a = a.as_ref();
22 let b = b.as_ref();
23
24 let tmp = a.parent()
25 .or_else(|| b.parent())
26 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Could not find a parent directory"))?
27 .join(TMP_SWAP_FILE);
28
29 match fs::metadata(&tmp) {
31 Ok(ref meta) if meta.is_dir() => fs::remove_dir_all(&tmp)?,
32 Ok(_) => fs::remove_file(&tmp)?,
33 Err(ref err) if err.kind() == io::ErrorKind::NotFound => (),
34 Err(err) => return Err(err),
35 }
36
37 fs::rename(a, &tmp)?;
40
41 match fs::rename(b, a) {
42 Ok(_) => (),
43 error => {
44 fs::rename(&tmp, a)?;
47 return error
48 },
49 }
50
51 match fs::rename(&tmp, b) {
53 Ok(_) => Ok(()),
54 error => {
55 fs::rename(a, b)?;
58 fs::rename(&tmp, a)?;
59 error
60 },
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 extern crate tempdir;
67 use std::fs;
68 use std::path::Path;
69 use std::io::{Write, Read};
70 use self::tempdir::TempDir;
71 use super::{swap, swap_nonatomic};
72
73 fn write_to_file<P: AsRef<Path>>(file: P, text: &str) {
74 let mut file = fs::OpenOptions::new()
75 .create(true)
76 .write(true)
77 .open(file)
78 .unwrap();
79 file.write_all(text.as_ref()).unwrap();
80 file.flush().unwrap();
81 }
82
83 fn read_from_file<P: AsRef<Path>>(file: P) -> String {
84 let mut buffer = String::new();
85 let mut file = fs::OpenOptions::new()
86 .read(true)
87 .open(file)
88 .unwrap();
89 file.read_to_string(&mut buffer).unwrap();
90 buffer
91 }
92
93 #[test]
94 fn test_swap_files() {
95 let dir = TempDir::new("").unwrap();
96 let path_a = dir.path().join("file_a");
97 let path_b = dir.path().join("file_b");
98 write_to_file(&path_a, "foo");
99 write_to_file(&path_b, "bar");
100 swap(&path_a, &path_b).unwrap();
101 let read_a = read_from_file(&path_a);
102 let read_b = read_from_file(&path_b);
103 assert_eq!("bar", read_a);
104 assert_eq!("foo", read_b);
105 }
106
107 #[cfg(not(target_os = "macos"))]
109 #[test]
110 fn test_swap_dirs() {
111 let dir_a = TempDir::new("a").unwrap();
112 let dir_b = TempDir::new("b").unwrap();
113 let path_a = dir_a.path().join("file");
114 let path_b = dir_b.path().join("file");
115 write_to_file(&path_a, "foo");
116 write_to_file(&path_b, "bar");
117 swap(&dir_a, &dir_b).unwrap();
118 let read_a = read_from_file(&path_a);
119 let read_b = read_from_file(&path_b);
120 assert_eq!("bar", read_a);
121 assert_eq!("foo", read_b);
122 }
123
124 #[test]
125 fn test_swap_nonatomic_files() {
126 let dir = TempDir::new("").unwrap();
127 let path_a = dir.path().join("file_a");
128 let path_b = dir.path().join("file_b");
129 write_to_file(&path_a, "foo");
130 write_to_file(&path_b, "bar");
131 swap_nonatomic(&path_a, &path_b).unwrap();
132 let read_a = read_from_file(&path_a);
133 let read_b = read_from_file(&path_b);
134 assert_eq!("bar", read_a);
135 assert_eq!("foo", read_b);
136 }
137
138 #[test]
140 fn test_swap_nonatomic_dirs() {
141 let dir_a = TempDir::new("a").unwrap();
142 let dir_b = TempDir::new("b").unwrap();
143 let path_a = dir_a.path().join("file");
144 let path_b = dir_b.path().join("file");
145 write_to_file(&path_a, "foo");
146 write_to_file(&path_b, "bar");
147 swap_nonatomic(&dir_a, &dir_b).unwrap();
148 let read_a = read_from_file(&path_a);
149 let read_b = read_from_file(&path_b);
150 assert_eq!("bar", read_a);
151 assert_eq!("foo", read_b);
152 }
153}