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