lotus_lib/toc/
toc.rs

1use std::fs::File;
2use std::io::{Read, Seek, SeekFrom};
3use std::path::{Component, PathBuf};
4
5use anyhow::Result;
6use zerocopy::FromBytes;
7
8use crate::toc::node::{DirectoryNode, Node, NodeKind};
9use crate::toc::toc_entry::{TocEntry, TOC_ENTRY_SIZE};
10
11pub(crate) struct Toc {
12    toc_path: PathBuf,
13    directories: Vec<Node>,
14    files: Vec<Node>,
15}
16
17impl Toc {
18    pub fn new(toc_path: PathBuf) -> Self {
19        Self {
20            toc_path,
21            directories: Vec::new(),
22            files: Vec::new(),
23        }
24    }
25
26    pub fn directories(&self) -> &Vec<Node> {
27        &self.directories
28    }
29
30    pub fn files(&self) -> &Vec<Node> {
31        &self.files
32    }
33
34    pub fn root(&self) -> Option<Node> {
35        self.directories.get(0).cloned()
36    }
37
38    pub fn is_loaded(&self) -> bool {
39        !self.directories.is_empty()
40    }
41
42    pub fn read_toc(&mut self) -> Result<()> {
43        if self.is_loaded() {
44            return Ok(()); // TOC already loaded
45        }
46
47        // Clear the directory and file vectors in case they were populated
48        // from a previous read
49        self.unread_toc();
50
51        let mut toc_reader = File::open(&self.toc_path).unwrap();
52        let entry_count = (toc_reader.metadata().unwrap().len() as usize - 8) / TOC_ENTRY_SIZE;
53        toc_reader.seek(SeekFrom::Start(8)).unwrap();
54
55        // Reserve space for the entries in the vectors to avoid unnecessary
56        // reallocations
57        self.files.reserve(entry_count);
58        self.directories.reserve(entry_count);
59
60        let mut file_count = 0;
61        let mut dir_count = 1; // Hardcoded root directory
62
63        self.directories.insert(0, Node::root());
64
65        let mut buffer = vec![0u8; TOC_ENTRY_SIZE * entry_count];
66        toc_reader.read_exact(&mut buffer).unwrap();
67
68        let entries = TocEntry::slice_from(&buffer).unwrap();
69        for entry in entries {
70            // Entry timestamp of 0 means the entry has been replaced with a
71            // newer version with the same name and path with a valid timestamp
72            if entry.timestamp == 0 {
73                continue;
74            }
75
76            // Entry name is a null-terminated string, so we need to find the
77            // index of the null byte and truncate the string there
78            let entry_name = {
79                let null_index = entry.name.iter().position(|&x| x == 0).unwrap_or(64);
80                let entry_name = std::str::from_utf8(&entry.name[..null_index])?;
81                entry_name
82            };
83
84            let parent_node = self
85                .directories
86                .get_mut(entry.parent_dir_index as usize)
87                .unwrap();
88
89            // If the cache offset is -1, then the entry is a directory
90            if entry.cache_offset == -1 {
91                let dir_node = Node::directory(entry_name);
92
93                parent_node.append(dir_node.clone());
94                self.directories.insert(dir_count, dir_node);
95
96                dir_count += 1;
97            } else {
98                let file_node = Node::file(
99                    entry_name,
100                    entry.cache_offset,
101                    entry.timestamp,
102                    entry.comp_len,
103                    entry.len,
104                );
105
106                parent_node.append(file_node.clone());
107                self.files.insert(file_count, file_node);
108
109                file_count += 1;
110            }
111        }
112
113        // Shrink the vectors to the actual size of the vectors to save memory
114        self.directories.shrink_to_fit();
115        self.files.shrink_to_fit();
116
117        Ok(()) // TOC read successfully
118    }
119
120    pub fn unread_toc(&mut self) {
121        self.directories.clear();
122        self.files.clear();
123    }
124
125    fn get_node(&self, path: PathBuf) -> Option<Node> {
126        if !self.is_loaded() {
127            return None;
128        }
129
130        if !path.has_root() {
131            panic!("Path must be absolute");
132        }
133
134        let mut components = path.components();
135        let mut current_node = self.root().unwrap().clone();
136
137        // Skip root
138        components.next();
139
140        for component in components {
141            match component {
142                Component::Normal(name) => {
143                    let name = name.to_str().unwrap();
144                    current_node = match current_node.get_child(name) {
145                        Some(child) => child,
146                        _ => return None,
147                    };
148                }
149                Component::ParentDir => {
150                    current_node = match current_node.parent() {
151                        Some(parent) => parent,
152                        _ => return None,
153                    };
154                }
155                Component::CurDir => continue,
156                _ => return None,
157            }
158        }
159
160        Some(current_node)
161    }
162
163    pub fn get_directory_node(&self, path: PathBuf) -> Option<Node> {
164        match self.get_node(path) {
165            Some(node) => match node.kind() {
166                NodeKind::Directory => Some(node),
167                _ => None,
168            },
169            _ => None,
170        }
171    }
172
173    pub fn get_file_node(&self, path: PathBuf) -> Option<Node> {
174        match self.get_node(path) {
175            Some(node) => match node.kind() {
176                NodeKind::File => Some(node),
177                _ => None,
178            },
179            _ => None,
180        }
181    }
182}