1use std::collections::HashMap;
6use std::io::{self, Write};
7use std::str::{self, FromStr};
8
9use clap::builder::PossibleValue;
10use clap::ValueEnum;
11use memchr::memchr;
12use serde::{Deserialize, Serialize};
13use serde_bytes::ByteBuf;
14
15use crate::frcode;
16
17#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
29pub enum FileNode<T> {
30 Regular {
33 size: u64,
35 executable: bool,
37 },
38 Symlink {
40 target: ByteBuf,
42 },
43 Directory {
47 size: u64,
49
50 contents: T,
53 },
54}
55
56#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
65pub enum FileType {
66 Regular { executable: bool },
67 Directory,
68 Symlink,
69}
70
71impl ValueEnum for FileType {
72 fn value_variants<'a>() -> &'a [Self] {
73 &[
74 FileType::Regular { executable: false },
75 FileType::Regular { executable: true },
76 FileType::Directory,
77 FileType::Symlink,
78 ]
79 }
80
81 fn to_possible_value(&self) -> Option<PossibleValue> {
82 match self {
83 FileType::Regular { executable: false } => Some(PossibleValue::new("r")),
84 FileType::Regular { executable: true } => Some(PossibleValue::new("x")),
85 FileType::Directory => Some(PossibleValue::new("d")),
86 FileType::Symlink => Some(PossibleValue::new("s")),
87 }
88 }
89}
90
91impl FromStr for FileType {
92 type Err = &'static str;
93
94 fn from_str(s: &str) -> Result<Self, Self::Err> {
95 match s {
96 "r" => Ok(FileType::Regular { executable: false }),
97 "x" => Ok(FileType::Regular { executable: true }),
98 "d" => Ok(FileType::Directory),
99 "s" => Ok(FileType::Symlink),
100 _ => Err("invalid file type"),
101 }
102 }
103}
104
105pub const ALL_FILE_TYPES: &[FileType] = &[
107 FileType::Regular { executable: true },
108 FileType::Regular { executable: false },
109 FileType::Directory,
110 FileType::Symlink,
111];
112
113impl<T> FileNode<T> {
114 pub fn split_contents(&self) -> (FileNode<()>, Option<&T>) {
117 use self::FileNode::*;
118 match *self {
119 Regular { size, executable } => (Regular { size, executable }, None),
120 Symlink { ref target } => (
121 Symlink {
122 target: target.clone(),
123 },
124 None,
125 ),
126 Directory { size, ref contents } => (Directory { size, contents: () }, Some(contents)),
127 }
128 }
129
130 pub fn get_type(&self) -> FileType {
132 match *self {
133 FileNode::Regular { executable, .. } => FileType::Regular { executable },
134 FileNode::Directory { .. } => FileType::Directory,
135 FileNode::Symlink { .. } => FileType::Symlink,
136 }
137 }
138}
139
140impl FileNode<()> {
141 fn encode<W: Write>(&self, encoder: &mut frcode::Encoder<W>) -> io::Result<()> {
142 use self::FileNode::*;
143 match *self {
144 Regular { executable, size } => {
145 let e = if executable { "x" } else { "r" };
146 encoder.write_meta(format!("{}{}", size, e).as_bytes())?;
147 }
148 Symlink { ref target } => {
149 encoder.write_meta(target)?;
150 encoder.write_meta(b"s")?;
151 }
152 Directory { size, contents: () } => {
153 encoder.write_meta(format!("{}d", size).as_bytes())?;
154 }
155 }
156 Ok(())
157 }
158
159 pub fn decode(buf: &[u8]) -> Option<Self> {
160 use self::FileNode::*;
161 buf.split_last().and_then(|(kind, buf)| match *kind {
162 b'x' | b'r' => {
163 let executable = *kind == b'x';
164 str::from_utf8(buf)
165 .ok()
166 .and_then(|s| s.parse().ok())
167 .map(|size| Regular { executable, size })
168 }
169 b's' => Some(Symlink {
170 target: ByteBuf::from(buf),
171 }),
172 b'd' => str::from_utf8(buf)
173 .ok()
174 .and_then(|s| s.parse().ok())
175 .map(|size| Directory { size, contents: () }),
176 _ => None,
177 })
178 }
179}
180
181#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
186pub struct FileTree(FileNode<HashMap<ByteBuf, FileTree>>);
187
188pub struct FileTreeEntry {
193 pub path: Vec<u8>,
194 pub node: FileNode<()>,
195}
196
197impl FileTreeEntry {
198 pub fn encode<W: Write>(self, encoder: &mut frcode::Encoder<W>) -> io::Result<()> {
199 self.node.encode(encoder)?;
200 encoder.write_path(self.path)?;
201 Ok(())
202 }
203
204 pub fn decode(buf: &[u8]) -> Option<FileTreeEntry> {
205 memchr(b'\0', buf).and_then(|sep| {
206 let path = &buf[(sep + 1)..];
207 let node = &buf[0..sep];
208 FileNode::decode(node).map(|node| FileTreeEntry {
209 path: path.to_vec(),
210 node,
211 })
212 })
213 }
214}
215
216impl FileTree {
217 pub fn regular(size: u64, executable: bool) -> Self {
218 FileTree(FileNode::Regular { size, executable })
219 }
220
221 pub fn symlink(target: ByteBuf) -> Self {
222 FileTree(FileNode::Symlink { target })
223 }
224
225 pub fn directory(entries: HashMap<ByteBuf, FileTree>) -> Self {
226 FileTree(FileNode::Directory {
227 size: entries.len() as u64,
228 contents: entries,
229 })
230 }
231
232 pub fn to_list(&self, filter_prefix: &[u8]) -> Vec<FileTreeEntry> {
233 let mut result = Vec::new();
234
235 let mut stack = Vec::with_capacity(16);
236 stack.push((Vec::new(), self));
237
238 while let Some(entry) = stack.pop() {
239 let path = entry.0;
240 let FileTree(current) = entry.1;
241 let (node, contents) = current.split_contents();
242 if let Some(entries) = contents {
243 let mut entries = entries.iter().collect::<Vec<_>>();
244 entries.sort_by(|a, b| Ord::cmp(a.0, b.0));
245 for (name, entry) in entries {
246 let mut path = path.clone();
247 path.push(b'/');
248 path.extend_from_slice(name);
249 stack.push((path, entry));
250 }
251 }
252 if path.starts_with(filter_prefix) {
253 result.push(FileTreeEntry { path, node });
254 }
255 }
256 result
257 }
258}