1use std::{
6 collections::HashMap,
7 path::{Path, PathBuf},
8};
9
10use time::OffsetDateTime;
11
12use crate::{
13 api::{ntfs_to_unix_time, NtfsAttributeType, ROOT_RECORD},
14 file::NtfsFile,
15 mft::Mft,
16};
17
18pub trait FileInfoCache<'a> {
19 fn get(&self, number: u64) -> Option<&Path>;
20 fn insert(&mut self, number: u64, path: PathBuf);
21}
22
23#[derive(Default)]
24pub struct HashMapCache(pub HashMap<u64, PathBuf>);
25impl FileInfoCache<'_> for HashMapCache {
26 fn get(&self, number: u64) -> Option<&Path> {
27 if let Some(p) = self.0.get(&number) {
28 Some(p)
29 } else {
30 None
31 }
32 }
33
34 fn insert(&mut self, number: u64, path: PathBuf) {
35 self.0.insert(number, path);
36 }
37}
38
39#[derive(Default)]
40pub struct VecCache(pub Vec<Option<PathBuf>>);
41impl FileInfoCache<'_> for VecCache {
42 fn get(&self, number: u64) -> Option<&Path> {
43 if self.0.len() > number as usize {
44 if let Some(p) = &self.0[number as usize] {
45 return Some(p);
46 }
47 }
48 None
49 }
50
51 fn insert(&mut self, number: u64, path: PathBuf) {
52 if self.0.len() <= number as usize {
53 self.0.resize(number as usize + 1, None);
54 }
55 self.0[number as usize] = Some(path);
56 }
57}
58
59pub struct FileInfo {
60 pub name: String,
61 pub path: PathBuf,
62 pub is_directory: bool,
63 pub size: u64,
64 pub created: Option<OffsetDateTime>,
65 pub accessed: Option<OffsetDateTime>,
66 pub modified: Option<OffsetDateTime>,
67}
68
69impl FileInfo {
70 pub fn new(mft: &Mft, file: &NtfsFile) -> Self {
71 let mut info = Self::_new(file);
72 info._compute_path(mft, file);
73 info
74 }
75
76 pub fn with_cache<C: for<'a> FileInfoCache<'a>>(
77 mft: &Mft,
78 file: &NtfsFile,
79 cache: &mut C,
80 ) -> Self {
81 let mut info = Self::_new(file);
82 info._compute_path_with_cache(mft, file, cache);
83 info
84 }
85
86 fn _new(file: &NtfsFile) -> Self {
87 let mut accessed = None;
88 let mut created = None;
89 let mut modified = None;
90 let mut size = 0u64;
91
92 file.attributes(|att| {
93 if att.header.type_id == NtfsAttributeType::StandardInformation as u32 {
94 let stdinfo = att.as_standard_info();
95
96 accessed = Some(ntfs_to_unix_time(stdinfo.access_time));
97 created = Some(ntfs_to_unix_time(stdinfo.creation_time));
98 modified = Some(ntfs_to_unix_time(stdinfo.modification_time));
99 }
100
101 if att.header.type_id == NtfsAttributeType::Data as u32 {
102 if att.header.is_non_resident == 0 {
103 size = att.header_res.value_length as u64;
104 } else {
105 size = att.header_nonres.data_size;
106 }
107 }
108 });
109
110 FileInfo {
111 name: String::new(),
112 path: PathBuf::new(),
113 is_directory: file.is_directory(),
114 size,
115 created,
116 accessed,
117 modified,
118 }
119 }
120
121 fn _compute_path(&mut self, mft: &Mft, file: &NtfsFile) {
122 let mut next_parent;
123
124 if let Some(name) = file.get_best_file_name(mft) {
125 self.name = name.to_string();
126 next_parent = name.parent();
127 } else {
128 return;
130 }
131
132 let mut components = Vec::new();
133 loop {
134 if next_parent == ROOT_RECORD {
135 break;
136 }
137
138 let cur_file = mft.get_record(next_parent);
139 if cur_file.is_none() {
140 return;
141 }
142 let cur_file = cur_file.unwrap();
143
144 if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
145 let cur_name = cur_name_att.to_string();
146 components.push((cur_file.number(), PathBuf::from(cur_name)));
147 next_parent = cur_name_att.parent();
148 } else {
149 return;
150 }
151 }
152
153 let mut path = mft.volume.path.clone();
154 for (_, comp) in components.iter().rev() {
155 path.push(comp);
156 }
157 path.push(&self.name);
158
159 self.path = path;
160 }
161
162 fn _compute_path_with_cache<C: for<'a> FileInfoCache<'a>>(
163 &mut self,
164 mft: &Mft,
165 file: &NtfsFile,
166 cache: &mut C,
167 ) {
168 let mut next_parent;
169
170 if let Some(name) = file.get_best_file_name(mft) {
171 self.name = name.to_string();
172 next_parent = name.parent();
173 } else {
174 return;
175 }
176
177 let mut components = Vec::new();
178 let mut cached_path = None;
179 loop {
180 if next_parent == ROOT_RECORD {
181 break;
182 }
183
184 if let Some(cur_path) = cache.get(next_parent) {
186 cached_path = Some(cur_path);
187 break;
188 }
189
190 let cur_file = mft.get_record(next_parent);
191 if cur_file.is_none() {
192 return;
193 }
194 let cur_file = cur_file.unwrap();
195
196 if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
197 let cur_name = cur_name_att.to_string();
198 components.push((cur_file.number(), PathBuf::from(cur_name)));
199 next_parent = cur_name_att.parent();
200 } else {
201 return;
202 }
203 }
204
205 let mut path = PathBuf::from(cached_path.unwrap_or(&mft.volume.path));
206
207 for (number, comp) in components.iter().rev() {
208 path.push(comp);
209 cache.insert(*number, path.clone());
210 }
211
212 path.push(&self.name);
213 cache.insert(file.number, path.clone());
214
215 self.path = path;
216 }
217}