rustream/squire/
content.rs1use std::collections::HashMap;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5use regex::Regex;
6use serde::{Deserialize, Serialize};
7use walkdir::WalkDir;
8
9use crate::constant;
10use crate::squire::authenticator;
11use crate::squire::settings;
12
13#[derive(Debug, Serialize, Deserialize, Default)]
18pub struct ContentPayload {
19 #[serde(default = "default_structure")]
21 pub files: Vec<HashMap<String, String>>,
22 #[serde(default = "default_structure")]
24 pub directories: Vec<HashMap<String, String>>,
25 #[serde(default = "default_structure")]
27 pub secured_directories: Vec<HashMap<String, String>>,
28}
29
30pub fn default_structure() -> Vec<HashMap<String, String>> {
32 Vec::new()
33}
34
35fn natural_sort_key(regex: &Regex, filename: &str) -> Vec<Result<i32, String>> {
52 regex.find_iter(filename)
54 .map(|part| {
55 part.as_str().parse::<i32>().map_err(|e| e.to_string())
57 })
63 .collect()
64}
65
66
67pub fn get_file_font(extn: &str) -> String {
79 let font = if constant::IMAGE_FORMATS.contains(&extn) {
80 "fa-regular fa-file-image"
81 } else {
82 "fa-regular fa-file-video"
83 };
84 font.to_string()
85}
86
87fn get_folder_font(structure: &Path,
99 auth_response: &authenticator::AuthToken) -> HashMap<String, String> {
100 let directory = structure.to_string_lossy().to_string();
101 let mut entry_map = HashMap::new();
102 entry_map.insert("path".to_string(), format!("stream/{}", &directory));
103 let depth = &structure.iter().count();
104 for component in structure.iter() {
105 let secured = format!("{}_{}", &auth_response.username, constant::SECURE_INDEX);
106 if secured == component.to_string_lossy() {
107 entry_map.insert("name".to_string(), directory);
108 entry_map.insert("font".to_string(), "fa-solid fa-lock".to_string());
109 entry_map.insert("secured".to_string(), "true".to_string());
110 return entry_map;
111 } else if component.to_string_lossy().ends_with(constant::SECURE_INDEX) {
112 return HashMap::new();
114 }
115 }
116 entry_map.insert("name".to_string(), directory);
117 if *depth > 1 {
118 entry_map.insert("font".to_string(), "fa-solid fa-folder-tree".to_string());
119 } else {
120 entry_map.insert("font".to_string(), "fa fa-folder".to_string());
121 }
122 entry_map
123}
124
125pub fn get_all_stream_content(config: &settings::Config, auth_response: &authenticator::AuthToken) -> ContentPayload {
135 let mut payload = ContentPayload::default();
136
137 for entry in WalkDir::new(&config.media_source).into_iter().filter_map(|e| e.ok()) {
138 if entry.path().ends_with("__") {
139 continue;
140 }
141
142 if let Some(file_name) = entry.file_name().to_str() {
143 if file_name.starts_with('_') || file_name.starts_with('.') {
144 continue;
145 }
146
147 if let Some(extension) = PathBuf::from(file_name).extension().and_then(|ext| ext.to_str()) {
148 if config.file_formats.iter().any(|format| extension == format) {
149 let path = entry.path().strip_prefix(&config.media_source)
150 .unwrap_or_else(|_| Path::new(""));
151 let components: &Vec<_> = &path.components().collect();
152 if components.len() == 1 {
153 let mut entry_map = HashMap::new();
154 entry_map.insert("path".to_string(), format!("stream/{}", &file_name));
155 entry_map.insert("name".to_string(), file_name.to_string());
156 entry_map.insert("font".to_string(), get_file_font(extension));
157 payload.files.push(entry_map);
158 } else {
159 let skimmed = path.components().rev().skip(1)
165 .collect::<Vec<_>>().iter().rev()
166 .collect::<PathBuf>();
167 let entry_map = get_folder_font(&skimmed, auth_response);
168 if entry_map.get("secured").unwrap_or(&"".to_string()) == "true" {
169 if payload.secured_directories.contains(&entry_map) || entry_map.is_empty() { continue; }
170 payload.secured_directories.push(entry_map);
171 } else {
172 if payload.directories.contains(&entry_map) || entry_map.is_empty() { continue; }
173 payload.directories.push(entry_map);
174 }
175 }
176 }
177 }
178 }
179 }
180
181 let re = Regex::new(r"(\D+|\d+)").unwrap();
182 payload.files.sort_by(|a, b| natural_sort_key(&re, &a["name"]).cmp(&natural_sort_key(&re, &b["name"])));
183 payload.directories.sort_by(|a, b| natural_sort_key(&re, &a["name"]).cmp(&natural_sort_key(&re, &b["name"])));
184
185 payload
186}
187
188pub fn get_dir_stream_content(parent: &str,
200 child: &str,
201 file_formats: &[String]) -> ContentPayload {
202 let mut files = Vec::new();
203 for entry in fs::read_dir(parent).unwrap().flatten() {
204 let file_name = entry.file_name().into_string().unwrap();
205 if file_name.starts_with('_') || file_name.starts_with('.') {
206 continue;
207 }
208 let file_path = Path::new(child).join(&file_name);
209 let file_extn = &file_path.extension().unwrap_or_default().to_string_lossy().to_string();
210 if file_formats.contains(file_extn) {
211 let map = HashMap::from([
212 ("name".to_string(), file_name),
213 ("path".to_string(), file_path.to_string_lossy().to_string()),
214 ("font".to_string(), get_file_font(file_extn))
215 ]);
216 files.push(map);
217 }
218 }
219 let re = Regex::new(r"(\D+|\d+)").unwrap();
220 files.sort_by_key(|a| natural_sort_key(&re, a.get("name").unwrap()));
221 ContentPayload { files, ..Default::default() }
222}
223
224#[derive(Debug, Serialize, Deserialize, Default)]
226pub struct Iter {
227 pub previous: Option<String>,
229 pub next: Option<String>,
231}
232
233pub fn get_iter(filepath: &Path, file_formats: &[String]) -> Iter {
244 let parent = filepath.parent().unwrap();
245 let mut dir_content: Vec<String> = fs::read_dir(parent)
246 .ok().unwrap()
247 .flatten()
248 .filter_map(|entry| {
249 let file_name = entry.file_name().to_string_lossy().to_string();
250 let file_extn = Path::new(&file_name).extension().unwrap_or_default().to_string_lossy().to_string();
251 if file_formats.contains(&file_extn) {
252 Some(file_name)
253 } else {
254 None
255 }
256 })
257 .collect();
258 let re = Regex::new(r"(\D+|\d+)").unwrap();
259 dir_content.sort_by_key(|a| natural_sort_key(&re, a));
260
261 let idx = dir_content.iter().position(|file| file == filepath.file_name().unwrap().to_str().unwrap()).unwrap();
262
263 let previous_ = if idx > 0 {
264 let previous_ = &dir_content[idx - 1];
265 if previous_ == filepath.file_name().unwrap().to_str().unwrap() {
266 None
267 } else {
268 Some(previous_.clone())
269 }
270 } else {
271 None
272 };
273
274 let next_ = dir_content.get(idx + 1).cloned();
275
276 Iter { previous: previous_, next: next_ }
277}