1
#![allow(unused_variables)]
2
use super::{BackupObject, BackupTarget, Listing, Node, NodeType, RestoreObject, RestoreTarget};
3
use crate::manifest::archive::Extent;
4
use crate::manifest::driver::{BackupDriver, RestoreDriver};
5

6
use async_trait::async_trait;
7
use blocking::unblock;
8
use smol::lock::RwLock;
9
use walkdir::WalkDir;
10

11
use std::collections::HashMap;
12
use std::fs::{create_dir_all, File};
13
use std::path::Path;
14
use std::sync::Arc;
15

16
#[derive(Clone)]
17
/// A type that handles the complexities of dealing with a file system for you.
18
pub struct FileSystemTarget {
19
    root_directory: String,
20
    listing: Arc<RwLock<Listing>>,
21
}
22

23
impl FileSystemTarget {
24
    /// Creates a new `FileSystemTarget` with the given path as its top level directory.
25
    ///
26
    /// The `FileSystemTarget` will consider all paths below this directory for backup.
27 1
    pub fn new(root_directory: &str) -> FileSystemTarget {
28
        FileSystemTarget {
29 1
            root_directory: root_directory.to_string(),
30 1
            listing: Arc::new(RwLock::new(Listing::default())),
31
        }
32
    }
33

34 0
    pub fn set_root_directory(&mut self, new_root: &str) {
35 0
        self.root_directory = new_root.to_string();
36
    }
37
}
38

39
#[async_trait]
40
impl BackupTarget<File> for FileSystemTarget {
41 1
    async fn backup_paths(&self) -> Listing {
42 1
        let mut listing = Listing::default();
43 1
        for entry in WalkDir::new(&self.root_directory)
44 0
            .into_iter()
45 0
            .filter_map(Result::ok)
46 0
            .skip(1)
47
        {
48 1
            let rel_path = entry
49
                .path()
50 1
                .strip_prefix(&self.root_directory)
51
                .expect("Failed getting realtive path in file system target")
52
                .to_owned();
53 1
            let parent_path = rel_path
54
                .parent()
55
                .expect("Failed getting parent path in filesystem target");
56 0
            let metadata = {
57 1
                let path = entry.path().to_owned();
58

59 1
                unblock(move || path.metadata().expect("Failed getting file metatdata")).await
60
            };
61
            // FIXME: Making an assuming that the object is either a file or a directory
62 1
            let node_type = if metadata.is_file() {
63 1
                NodeType::File
64
            } else {
65
                NodeType::Directory {
66 1
                    children: Vec::new(),
67
                }
68
            };
69

70 1
            let path = rel_path
71
                .to_str()
72
                .expect("Path contained non-utf8")
73
                .to_string();
74

75 1
            let extents = if metadata.is_file() && metadata.len() > 0 {
76 1
                Some(vec![Extent {
77 0
                    start: 0,
78 1
                    end: metadata.len() - 1,
79
                }])
80
            } else {
81 1
                None
82
            };
83

84
            let node = Node {
85
                path,
86 1
                total_length: metadata.len(),
87 1
                total_size: metadata.len(),
88
                extents,
89
                node_type,
90
            };
91

92 1
            listing.add_child(parent_path.to_str().expect("Path contained non-utf8"), node);
93
        }
94 1
        listing
95
    }
96 1
    async fn backup_object(&self, node: Node) -> HashMap<String, BackupObject<File>> {
97 1
        let mut output = HashMap::new();
98
        // FIXME: Store directory metatdata
99 1
        if node.is_file() {
100
            // Get the actual path on the filesystem this referes to
101 1
            let root_path = Path::new(&self.root_directory);
102 1
            let path = root_path.join(&node.path);
103
            // Construct the file_object based on the information in the node
104 1
            let mut file_object = BackupObject::new(node.total_length);
105
            // add each extent from the node to the object
106 1
            if let Some(extents) = node.extents.as_ref() {
107 1
                for extent in extents {
108 0
                    let file = {
109 1
                        let path = path.clone();
110

111 1
                        unblock(move || File::open(&path).expect("Unable to open file")).await
112
                    };
113 1
                    file_object.direct_add_range(extent.start, extent.end, file);
114
                }
115
            }
116 1
            output.insert(String::new(), file_object);
117
        }
118 1
        let path = node.path.clone();
119 1
        let parent_path = Path::new(&path)
120
            .parent()
121
            .expect("Unable to get parent path")
122
            .to_str()
123
            .expect("Invalid utf-8 in path");
124 1
        self.listing.write().await.add_child(parent_path, node);
125 1
        output
126
    }
127 1
    async fn backup_listing(&self) -> Listing {
128 1
        self.listing.read().await.clone()
129
    }
130
}
131

132
#[async_trait]
133
impl RestoreTarget<File> for FileSystemTarget {
134 1
    async fn load_listing(root_path: &str, listing: Listing) -> Self {
135
        FileSystemTarget {
136 1
            root_directory: root_path.to_string(),
137 1
            listing: Arc::new(RwLock::new(listing)),
138
        }
139
    }
140 1
    async fn restore_object(&self, node: Node) -> HashMap<String, RestoreObject<File>> {
141 1
        let mut output = HashMap::new();
142
        // Get the actual path on the filesystem this refers to
143 1
        let root_path = Path::new(&self.root_directory);
144 1
        let rel_path = Path::new(&node.path);
145 1
        let path = root_path.join(rel_path);
146
        // FIXME: currently assumes that nodes are only files or direcotires
147 1
        if node.is_directory() {
148
            // If the node is a directory, just create it
149 1
            let path = path.to_owned();
150 1
            unblock(move || {
151 1
                create_dir_all(path).expect("Unable to create directory (restore_object)")
152
            })
153 0
            .await;
154 1
            output
155
        } else {
156
            // Get the parent directory, and create it if it does not exist
157 1
            let parent_path = path
158
                .parent()
159
                .expect("Unable to get parent(restore_object)")
160
                .to_owned();
161 1
            unblock(move || {
162 1
                create_dir_all(parent_path).expect("Unable to create parent (restore_object)")
163
            })
164 0
            .await;
165
            // Check to see if we have any extents
166 1
            if let Some(extents) = node.extents.as_ref() {
167
                // if the extents are empty, just touch the file and leave it
168 1
                if extents.is_empty() {
169 0
                    let path = path.to_owned();
170 0
                    unblock(|| File::create(path).expect("Unable to open file")).await;
171 0
                    output
172
                } else {
173 1
                    let mut file_object = RestoreObject::new(node.total_length);
174 1
                    for extent in extents {
175 1
                        file_object.direct_add_range(
176 1
                            extent.start,
177 1
                            extent.end,
178 1
                            File::create(path.clone()).expect("Unable to open file"),
179
                        );
180
                    }
181 1
                    output.insert(String::new(), file_object);
182 1
                    output
183
                }
184
            } else {
185 1
                let path = path.to_owned();
186 1
                unblock(|| File::create(path).expect("Unable to open file")).await;
187

188 1
                output
189
            }
190
        }
191
    }
192 1
    async fn restore_listing(&self) -> Listing {
193 1
        self.listing.read().await.clone()
194
    }
195
}
196

197
impl BackupDriver<File> for FileSystemTarget {}
198
impl RestoreDriver<File> for FileSystemTarget {}
199

200
#[cfg(test)]
201
mod tests {
202
    use super::*;
203
    use dir_diff;
204
    use std::fs::{create_dir, File};
205
    use tempfile::{tempdir, TempDir};
206

207
    fn make_test_directory() -> TempDir {
208
        let root = tempdir().unwrap();
209
        let root_path = root.path();
210

211
        create_dir(root_path.join("A")).unwrap();
212
        create_dir(root_path.join("B")).unwrap();
213
        create_dir(root_path.join("B").join("C")).unwrap();
214

215
        File::create(root_path.join("1")).unwrap();
216
        File::create(root_path.join("2")).unwrap();
217
        File::create(root_path.join("3")).unwrap();
218
        File::create(root_path.join("A").join("4")).unwrap();
219
        File::create(root_path.join("B").join("5")).unwrap();
220
        File::create(root_path.join("B").join("C").join("6")).unwrap();
221

222
        root
223
    }
224

225
    #[test]
226
    fn backup_restore_structure() {
227
        smol::block_on(async {
228
            let input_dir = make_test_directory();
229
            let root_path = input_dir.path().to_owned();
230

231
            let input_target = FileSystemTarget::new(&root_path.display().to_string());
232

233
            let listing = input_target.backup_paths().await;
234
            for node in listing {
235
                println!("Backing up: {}", node.path);
236
                input_target.backup_object(node).await;
237
            }
238

239
            let listing = input_target.backup_listing().await;
240
            println!("{:?}", listing);
241

242
            let output_dir = tempdir().unwrap();
243

244
            let output_target =
245
                FileSystemTarget::load_listing(&output_dir.path().display().to_string(), listing)
246
                    .await;
247

248
            let output_listing = output_target.restore_listing().await;
249
            for entry in output_listing {
250
                println!("Restore listing:");
251
                println!(" - {}", entry.path);
252
                output_target.restore_object(entry).await;
253
            }
254

255
            let _input_path = input_dir.path().display().to_string();
256
            let _output_path = output_dir.path().display().to_string();
257

258
            assert!(!dir_diff::is_different(&input_dir.path(), &output_dir.path()).unwrap());
259
        });
260
    }
261
}

Read our documentation on viewing source code .

Loading