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 if let Some(stdinfo) = att.as_standard_info() {
95 accessed = Some(ntfs_to_unix_time(stdinfo.access_time));
96 created = Some(ntfs_to_unix_time(stdinfo.creation_time));
97 modified = Some(ntfs_to_unix_time(stdinfo.modification_time));
98 }
99 }
100
101 if att.header.type_id == NtfsAttributeType::Data as u32 {
102 if att.header.is_non_resident == 0 {
103 if let Some(header) = att.resident_header() {
104 size = header.value_length as u64;
105 }
106 } else if let Some(header) = att.nonresident_header() {
107 size = header.data_size;
108 }
109 }
110 });
111
112 FileInfo {
113 name: String::new(),
114 path: PathBuf::new(),
115 is_directory: file.is_directory(),
116 size,
117 created,
118 accessed,
119 modified,
120 }
121 }
122
123 fn _compute_path(&mut self, mft: &Mft, file: &NtfsFile) {
124 let mut next_parent;
125
126 if let Some(name) = file.get_best_file_name(mft) {
127 self.name = name.to_string();
128 next_parent = name.parent();
129 } else {
130 return;
132 }
133
134 let mut components = Vec::new();
135 loop {
136 if next_parent == ROOT_RECORD {
137 break;
138 }
139
140 let cur_file = mft.get_record(next_parent);
141 if cur_file.is_none() {
142 return;
143 }
144 let cur_file = cur_file.unwrap();
145
146 if let Some(cur_name_att) = cur_file.get_best_file_name(mft) {
147 let cur_name = cur_name_att.to_string();
148 components.push((cur_file.number(), PathBuf::from(cur_name)));
149 next_parent = cur_name_att.parent();
150 } else {
151 return;
152 }
153 }
154
155 let mut path = mft.volume.path.clone();
156 for (_, comp) in components.iter().rev() {
157 path.push(comp);
158 }
159 path.push(&self.name);
160
161 self.path = path;
162 }
163
164 fn _compute_path_with_cache<C: for<'a> FileInfoCache<'a>>(
165 &mut self,
166 mft: &Mft,
167 file: &NtfsFile,
168 cache: &mut C,
169 ) {
170 let mut next_parent;
171
172 if let Some(name) = file.get_best_file_name(mft) {
173 self.name = name.to_string();
174 next_parent = name.parent();
175 } else {
176 return;
177 }
178
179 let mut components = Vec::new();
180 let mut cached_path = None;
181 loop {
182 if next_parent == ROOT_RECORD {
183 break;
184 }
185
186 if let Some(cur_path) = cache.get(next_parent) {
188 cached_path = Some(cur_path);
189 break;
190 }
191
192 let cur_file = mft.get_record(next_parent);
193 if cur_file.is_none() {
194 return;
195 }
196 let cur_file = cur_file.unwrap();
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}