ragit/index/commands/
remove.rs

1use super::Index;
2use crate::error::Error;
3use crate::index::{CHUNK_DIR_NAME, IIStatus};
4use ragit_fs::{exists, get_relative_path, remove_file, set_extension};
5use std::collections::HashSet;
6
7pub type Path = String;
8
9#[derive(Clone, Copy, Default)]
10pub struct RemoveResult {
11    pub staged: usize,
12    pub processed: usize,
13}
14
15impl Index {
16    pub fn remove_file(
17        &mut self,
18        path: Path,
19        dry_run: bool,
20        recursive: bool,
21        auto: bool,
22        staged: bool,
23        processed: bool,
24    ) -> Result<RemoveResult, Error> {
25        let mut rel_path = get_relative_path(&self.root_dir, &path)?;
26        let (mut staged_candidates, mut processed_candidates) = if recursive {
27            if !rel_path.ends_with("/") {
28                rel_path = format!("{rel_path}/");
29            }
30
31            let mut staged_candidates = vec![];
32            let mut processed_candidates = vec![];
33
34            // `--all`
35            // TODO: sometimes it's `"/"` and sometimes it's `""`. Why?
36            if rel_path == "/" || rel_path == "" {
37                staged_candidates = self.staged_files.iter().map(|f| f.to_string()).collect();
38                processed_candidates = self.processed_files.keys().map(|f| f.to_string()).collect();
39            }
40
41            else {
42                for file in self.staged_files.iter() {
43                    if file.starts_with(&rel_path) {
44                        staged_candidates.push(file.to_string());
45                    }
46                }
47
48                for file in self.processed_files.keys() {
49                    if file.starts_with(&rel_path) {
50                        processed_candidates.push(file.to_string());
51                    }
52                }
53            }
54
55            (staged_candidates, processed_candidates)
56        } else {
57            let staged_candidates = if self.staged_files.contains(&rel_path) {
58                vec![rel_path.clone()]
59            } else {
60                vec![]
61            };
62            let processed_candidates = if self.processed_files.contains_key(&rel_path) {
63                vec![rel_path.clone()]
64            } else {
65                vec![]
66            };
67
68            (staged_candidates, processed_candidates)
69        };
70
71        if staged_candidates.is_empty() && processed_candidates.is_empty() {
72            return Err(Error::NoSuchFile { path: Some(path), uid: None });
73        }
74
75        if !staged {
76            staged_candidates = vec![];
77        }
78
79        if !processed {
80            processed_candidates = vec![];
81        }
82
83        if auto {
84            let mut staged_candidates_new = vec![];
85            let mut processed_candidates_new = vec![];
86
87            for file in staged_candidates.into_iter() {
88                if !exists(&Index::get_data_path(&self.root_dir, &file)?) {
89                    staged_candidates_new.push(file)
90                }
91            }
92
93            for file in processed_candidates.into_iter() {
94                if !exists(&Index::get_data_path(&self.root_dir, &file)?) {
95                    processed_candidates_new.push(file)
96                }
97            }
98
99            staged_candidates = staged_candidates_new;
100            processed_candidates = processed_candidates_new;
101        }
102
103        if !dry_run {
104            let staged_candidates: HashSet<_> = staged_candidates.iter().collect();
105            self.staged_files = self.staged_files.iter().filter(
106                |file| !staged_candidates.contains(file)
107            ).map(
108                |file| file.to_string()
109            ).collect();
110
111            self.ii_status = IIStatus::Outdated;
112
113            for file in processed_candidates.iter() {
114                match self.processed_files.get(file).map(|uid| *uid) {
115                    Some(file_uid) => {
116                        for uid in self.get_chunks_of_file(file_uid)? {
117                            self.chunk_count -= 1;
118                            let chunk_path = Index::get_uid_path(
119                                &self.root_dir,
120                                CHUNK_DIR_NAME,
121                                uid,
122                                Some("chunk"),
123                            )?;
124                            remove_file(&chunk_path)?;
125                            let tfidf_path = set_extension(&chunk_path, "tfidf")?;
126
127                            if exists(&tfidf_path) {
128                                remove_file(&tfidf_path)?;
129                            }
130                        }
131
132                        self.processed_files.remove(file).unwrap();
133                        self.remove_file_index(file_uid)?;
134                    },
135                    _ => {},
136                }
137            }
138
139            self.reset_uid(false /* save_to_file */)?;
140            self.save_to_file()?;
141
142            // If there's no chunk, an empty ii is a complete ii!
143            if self.chunk_count == 0 {
144                self.ii_status = IIStatus::Complete;
145            }
146        }
147
148        Ok(RemoveResult {
149            staged: staged_candidates.len(),
150            processed: processed_candidates.len(),
151        })
152    }
153}
154
155impl RemoveResult {
156    pub fn is_empty(&self) -> bool {
157        self.staged == 0 && self.processed == 0
158    }
159}
160
161impl std::ops::AddAssign<Self> for RemoveResult {
162    fn add_assign(&mut self, rhs: Self) {
163        self.staged += rhs.staged;
164        self.processed += rhs.processed;
165    }
166}