libpijul_compat/
file_operations.rs

1//! Manipulating the internal representation of files and directories
2//! tracked by Pijul (i.e. adding files, removing files, getting file
3//! names…).
4
5use backend;
6use backend::*;
7use {ErrorKind, Result};
8
9use rand;
10use std;
11use std::collections::BTreeMap;
12use std::iter::Iterator;
13use std::path::{Path, PathBuf};
14
15impl<'env, R: rand::Rng> MutTxn<'env, R> {
16    pub fn mark_inode_moved(&mut self, inode: Inode) {
17        let mut header = None;
18        if let Some(h) = self.get_inodes(inode) {
19            header = Some(h.clone())
20        }
21        if let Some(mut h) = header {
22            h.status = FileStatus::Moved;
23            self.replace_inodes(inode, h).unwrap();
24        }
25    }
26
27    /// Create an inode that doesn't exist in the repository, but
28    /// doesn't put it into the repository.
29    pub fn create_new_inode(&self) -> Inode {
30        let mut already_taken = true;
31        let mut inode: Inode = ROOT_INODE.clone();
32        while already_taken {
33            for i in inode.iter_mut() {
34                *i = rand::random()
35            }
36            already_taken = self.get_revtree(inode).is_some();
37        }
38        inode
39    }
40
41    /// Record the information that `parent_inode` is now a parent of
42    /// file `filename`, and `filename` has inode `child_inode`.
43    fn make_new_child(
44        &mut self,
45        parent_inode: Inode,
46        filename: &str,
47        is_dir: bool,
48        child_inode: Option<Inode>,
49    ) -> Result<Inode> {
50        let parent_id = OwnedFileId {
51            parent_inode: parent_inode.clone(),
52            basename: SmallString::from_str(filename),
53        };
54        if let Some(inode) = self.get_tree(&parent_id.as_file_id()) {
55            // If we already have the file, make sure the file status
56            // is Ok (i.e. not zombie, not deleted).
57            let mut header = if let Some(header) = self.get_inodes(inode) {
58                header.to_owned()
59            } else {
60                return Err(ErrorKind::AlreadyAdded.into());
61            };
62            if let FileStatus::Ok = header.status {
63            } else {
64                header.status = FileStatus::Ok;
65                self.replace_inodes(inode, header)?;
66            }
67            Ok(inode)
68        } else {
69            // Else, add a new file.
70
71            let child_inode = match child_inode {
72                None => self.create_new_inode(),
73                Some(i) => i.clone(),
74            };
75            self.put_tree(&parent_id.as_file_id(), child_inode)?;
76            self.put_revtree(child_inode, &parent_id.as_file_id())?;
77
78            if is_dir {
79                // If this new file is a directory, add a name-less
80                // file id without a reverse in revtree.
81                let dir_id = OwnedFileId {
82                    parent_inode: child_inode.clone(),
83                    basename: SmallString::from_str(""),
84                };
85                self.put_tree(&dir_id.as_file_id(), child_inode)?;
86            };
87            Ok(child_inode)
88        }
89    }
90
91    pub fn add_inode(
92        &mut self,
93        inode: Option<Inode>,
94        path: &std::path::Path,
95        is_dir: bool,
96    ) -> Result<()> {
97        if let Some(parent) = path.parent() {
98            let (mut current_inode, unrecorded_path) =
99                self.closest_in_repo_ancestor(&parent).unwrap();
100
101            for c in unrecorded_path {
102                current_inode =
103                    self.make_new_child(current_inode, c.as_os_str().to_str().unwrap(), true, None)?
104            }
105
106            self.make_new_child(
107                current_inode,
108                path.file_name().unwrap().to_str().unwrap(),
109                is_dir,
110                inode,
111            )?;
112        }
113        Ok(())
114    }
115
116    pub fn inode_is_ancestor_of(&self, a: Inode, mut b: Inode) -> bool {
117        loop {
118            if a == b {
119                return true;
120            }
121            if let Some(b_parent) = self.get_revtree(b) {
122                b = b_parent.parent_inode
123            } else {
124                return false;
125            }
126        }
127    }
128
129    pub fn move_file(
130        &mut self,
131        path: &std::path::Path,
132        path_: &std::path::Path,
133        is_dir: bool,
134    ) -> Result<()> {
135        debug!("move_file: {:?},{:?}", path, path_);
136        if let Some(parent) = path.parent() {
137            let fileref = OwnedFileId {
138                parent_inode: self.find_inode(parent)?,
139                basename: SmallString::from_str(path.file_name().unwrap().to_str().unwrap()),
140            };
141
142            if let Some(inode) = self.get_tree(&fileref.as_file_id()).map(|i| i.clone()) {
143                // Now the last inode is in "*inode"
144                debug!("txn.del fileref={:?}", fileref);
145                self.del_tree(&fileref.as_file_id(), None)?;
146                self.del_revtree(inode, None)?;
147
148                debug!("inode={} path_={:?}", inode.to_hex(), path_);
149                self.add_inode(Some(inode), path_, is_dir)?;
150                self.mark_inode_moved(inode);
151
152                return Ok(());
153            }
154        }
155        Err(ErrorKind::FileNotInRepo(path.to_path_buf()).into())
156    }
157
158    // Deletes a directory, given by its inode, recursively.
159    pub fn rec_delete(&mut self, key: Inode) -> Result<bool> {
160        debug!("rec_delete, key={:?}", key.to_hex());
161        let file_id = OwnedFileId {
162            parent_inode: key.clone(),
163            basename: SmallString::from_str(""),
164        };
165
166        let children: Vec<(_, Inode)> = self.iter_tree(Some((&file_id.as_file_id(), None)))
167            .take_while(|&(ref k, _)| key == k.parent_inode)
168            .filter(|&(ref k, _)| !k.basename.is_empty())
169            .map(|(k, v)| (k.to_owned(), v.to_owned()))
170            .collect();
171
172        let mut has_recorded_descendants = false;
173        for (_, b) in children {
174            debug!("deleting from tree {:?}", b);
175            has_recorded_descendants |= self.rec_delete(b)?;
176        }
177
178        // Now that the directory is empty, mark the corresponding node as deleted (flag '2').
179        if let Some(mut header) = self.get_inodes(key).map(|h| h.clone()) {
180            // If this is was recorded, mark deleted.
181            debug!("key {:?}, header = {:?}", key, header);
182            header.status = FileStatus::Deleted;
183            self.replace_inodes(key, header)?;
184            debug!("after = {:?}", self.get_inodes(key).map(|h| h.clone()));
185        } else if !has_recorded_descendants {
186            // Else, simply delete from the tree.
187            let parent = self.get_revtree(key).unwrap().to_owned();
188            debug!("key = {:?}, parent = {:?}", key, parent);
189            self.del_tree(&parent.as_file_id(), None)?;
190            self.del_revtree(key, None)?;
191        }
192        Ok(has_recorded_descendants)
193    }
194
195    /// Removes a file from the repository.
196    pub fn remove_file(&mut self, path: &std::path::Path) -> Result<()> {
197        debug!("remove_file");
198        let inode = self.find_inode(path)?;
199        debug!("rec_delete");
200        self.rec_delete(inode)?;
201        debug!("/rec_delete");
202        Ok(())
203    }
204}
205
206impl<A: Transaction, R> backend::GenericTxn<A, R> {
207    /// Traverses the `tree` base recursively, collecting all descendants of `key`.
208    fn collect(
209        &self,
210        key: Inode,
211        pb: &Path,
212        basename: &str,
213        files: &mut Vec<PathBuf>,
214    ) -> Result<()> {
215        debug!("collecting {:?},{:?}", key, basename);
216        let add = match self.get_inodes(key) {
217            Some(inode) => {
218                debug!("node = {:?}", inode);
219                inode.status != FileStatus::Deleted
220            }
221            None => true,
222        };
223        if add {
224            debug!("basename = {:?}", basename);
225            let next_pb = pb.join(basename);
226            let next_pb_ = next_pb.clone();
227            if basename.len() > 0 {
228                files.push(next_pb)
229            }
230
231            debug!("starting iterator, key={:?}", key);
232            let fileid = OwnedFileId {
233                parent_inode: key.clone(),
234                basename: SmallString::from_str(""),
235            };
236            for (k, v) in self.iter_tree(Some((&fileid.as_file_id(), None)))
237                .take_while(|&(ref k, _)| k.parent_inode == key)
238            {
239                debug!("iter: {:?} {:?}", k, v);
240                if k.basename.len() > 0 {
241                    self.collect(v.to_owned(), next_pb_.as_path(), k.basename.as_str(), files)?;
242                }
243            }
244            debug!("ending iterator {:?}", {
245                let v: Vec<_> = self.iter_tree(Some((&fileid.as_file_id(), None))).collect();
246                v
247            });
248        }
249        Ok(())
250    }
251
252    /// Returns a vector containing all files in the repository.
253    pub fn list_files(&self, inode: Inode) -> Result<Vec<PathBuf>> {
254        debug!("list_files {:?}", inode);
255        let mut files = Vec::new();
256        let mut pathbuf = PathBuf::new();
257        self.collect(inode, &mut pathbuf, "", &mut files)?;
258        Ok(files)
259    }
260
261    /// Returns a list of files under the given inode.
262    pub fn list_files_under_inode(
263        &self,
264        inode: Inode,
265    ) -> Vec<(SmallString, Option<Key<PatchId>>, Inode)> {
266        let mut result = Vec::new();
267
268        let file_id = OwnedFileId {
269            parent_inode: inode,
270            basename: SmallString::from_str(""),
271        };
272        for (k, v) in self.iter_tree(Some((&file_id.as_file_id(), None)))
273            .take_while(|&(ref k, _)| k.parent_inode == inode)
274        {
275            let header = self.get_inodes(k.parent_inode).map(|x| x.clone());
276            // add: checking that this file has neither been moved nor deleted.
277            println!("============= {:?} {:?}", k, v);
278            let add = match header {
279                Some(ref h) => h.status == FileStatus::Ok,
280                None => true,
281            };
282            if add && k.basename.len() > 0 {
283                result.push((
284                    k.basename.to_owned(),
285                    header.map(|h| h.key.clone()),
286                    v.clone(),
287                ))
288            }
289        }
290
291        result
292    }
293
294    /// Returns a list of files under the given inode.
295    pub fn list_files_under_node(
296        &self,
297        branch: &Branch,
298        key: Key<PatchId>,
299    ) -> BTreeMap<Key<PatchId>, Vec<(FileMetadata, &str)>> {
300        let mut result = BTreeMap::new();
301
302        let e = Edge::zero(EdgeFlags::FOLDER_EDGE);
303        for (_, child) in self.iter_nodes(branch, Some((key, Some(&e)))).take_while(
304            |&(k, ref v)| k == key && v.flag <= EdgeFlags::FOLDER_EDGE | EdgeFlags::PSEUDO_EDGE,
305        ) {
306            let name = self.get_contents(child.dest).unwrap();
307            // This is supposed to be a small string anyway.
308            let (perms, basename) = name.as_slice().split_at(2);
309            let perms = FileMetadata::from_contents(perms);
310            let basename = std::str::from_utf8(basename).unwrap();
311
312            for (_, grandchild) in self.iter_nodes(branch, Some((child.dest, Some(&e))))
313                .take_while(|&(k, ref v)| {
314                    k == child.dest && v.flag <= EdgeFlags::FOLDER_EDGE | EdgeFlags::PSEUDO_EDGE
315                }) {
316                let names = result.entry(grandchild.dest.to_owned()).or_insert(vec![]);
317                names.push((perms, basename))
318            }
319        }
320        result
321    }
322
323    pub fn is_directory(&self, inode: &Inode) -> bool {
324        let file_id = OwnedFileId {
325            parent_inode: inode.clone(),
326            basename: SmallString::from_str(""),
327        };
328        inode == &ROOT_INODE || self.get_tree(&file_id.as_file_id()).is_some()
329    }
330
331    /// Splits a path into (1) the deepest inode from the root that is
332    /// an ancestor of the path or the path itself and (2) the
333    /// remainder of this path
334    fn closest_in_repo_ancestor<'a>(
335        &self,
336        path: &'a std::path::Path,
337    ) -> Result<(Inode, std::iter::Peekable<std::path::Components<'a>>)> {
338        let mut components = path.components().peekable();
339        let mut fileid = OwnedFileId {
340            parent_inode: ROOT_INODE,
341            basename: SmallString::from_str(""),
342        };
343
344        loop {
345            if let Some(c) = components.peek() {
346                fileid.basename = SmallString::from_str(c.as_os_str().to_str().unwrap());
347                if let Some(v) = self.get_tree(&fileid.as_file_id()) {
348                    fileid.parent_inode = v.clone()
349                } else {
350                    break;
351                }
352            } else {
353                break;
354            }
355            components.next();
356        }
357        Ok((fileid.parent_inode.clone(), components))
358    }
359
360    /// Find the inode corresponding to that path, or return an error if there's no such inode.
361    pub fn find_inode(&self, path: &std::path::Path) -> Result<Inode> {
362        let (inode, mut remaining_path_components) = self.closest_in_repo_ancestor(path)?;
363        if remaining_path_components.next().is_none() {
364            Ok(inode)
365        } else {
366            Err(ErrorKind::FileNotInRepo(path.to_path_buf()).into())
367        }
368    }
369
370    pub fn file_names(
371        &self,
372        branch: &Branch,
373        key: Key<PatchId>,
374    ) -> Vec<(Key<PatchId>, FileMetadata, &str)> {
375        let mut result = Vec::new();
376        let e = Edge::zero(EdgeFlags::FOLDER_EDGE | EdgeFlags::PARENT_EDGE);
377
378        debug!("file_names, key {:?}", key);
379        for (_, parent) in self.iter_nodes(branch, Some((key, Some(&e))))
380            .take_while(|&(k, _)| k == key)
381            .filter(|&(_, ref v)| {
382                v.flag
383                    .contains(EdgeFlags::FOLDER_EDGE | EdgeFlags::PARENT_EDGE)
384            }) {
385            debug!("file_names, parent {:?}", parent);
386            match self.get_contents(parent.dest) {
387                Some(ref name) if name.len() >= 2 => {
388                    // This is supposed to be a small string anyway.
389                    let (perms, basename) = name.as_slice().split_at(2);
390                    let perms = FileMetadata::from_contents(perms);
391                    let basename = std::str::from_utf8(basename).unwrap();
392
393                    for (_, grandparent) in self.iter_nodes(branch, Some((parent.dest, Some(&e))))
394                        .take_while(|&(k, _)| k == parent.dest)
395                        .filter(|&(_, ref v)| {
396                            v.flag
397                                .contains(EdgeFlags::FOLDER_EDGE | EdgeFlags::PARENT_EDGE)
398                        }) {
399                        result.push((grandparent.dest.to_owned(), perms, basename));
400                        break;
401                    }
402                }
403                _ => error!("Key: {:?}, file {}, line {}", key, file!(), line!()),
404            }
405        }
406        result
407    }
408
409    pub fn prefix_keys(&self, branch: &Branch, path: &str) -> Result<Vec<Key<PatchId>>> {
410        let mut result = Vec::new();
411        let e = Edge::zero(EdgeFlags::FOLDER_EDGE);
412
413        let mut current_key = ROOT_KEY;
414
415        for comp in path.split(std::path::MAIN_SEPARATOR) {
416            let mut is_first = true;
417            let cur = current_key;
418            for (_, child) in self.iter_nodes(branch, Some((current_key, Some(&e))))
419                .take_while(|&(k, _)| k == cur)
420                .filter(|&(_, ref v)| v.flag.contains(EdgeFlags::FOLDER_EDGE))
421            {
422                let contents = self.get_contents(child.dest).unwrap();
423                if contents.into_cow().split_at(2).1 == comp.as_bytes() {
424                    if !is_first {
425                        return Err(ErrorKind::FileNameCount(current_key).into());
426                    }
427
428                    for (_, grandchild) in self.iter_nodes(branch, Some((child.dest, Some(&e))))
429                        .take_while(|&(k, _)| k == child.dest)
430                        .filter(|&(_, ref v)| v.flag.contains(EdgeFlags::FOLDER_EDGE))
431                    {
432                        result.push(grandchild.dest);
433                        current_key = grandchild.dest;
434                    }
435                }
436            }
437        }
438        Ok(result)
439    }
440}