use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use regex::Regex;
use serde::{Deserialize, Serialize};
use walkdir::WalkDir;
use crate::constant;
use crate::squire::authenticator;
use crate::squire::settings;
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ContentPayload {
#[serde(default = "default_structure")]
pub files: Vec<HashMap<String, String>>,
#[serde(default = "default_structure")]
pub directories: Vec<HashMap<String, String>>,
#[serde(default = "default_structure")]
pub secured_directories: Vec<HashMap<String, String>>,
}
pub fn default_structure() -> Vec<HashMap<String, String>> {
Vec::new()
}
fn natural_sort_key(regex: &Regex, filename: &str) -> Vec<Result<i32, String>> {
regex.find_iter(filename)
.map(|part| {
part.as_str().parse::<i32>().map_err(|e| e.to_string())
})
.collect()
}
pub fn get_file_font(extn: &str) -> String {
let font = if constant::IMAGE_FORMATS.contains(&extn) {
"fa-regular fa-file-image"
} else {
"fa-regular fa-file-video"
};
font.to_string()
}
fn get_folder_font(structure: &Path,
auth_response: &authenticator::AuthToken) -> HashMap<String, String> {
let directory = structure.to_string_lossy().to_string();
let mut entry_map = HashMap::new();
entry_map.insert("path".to_string(), format!("stream/{}", &directory));
let depth = &structure.iter().count();
for component in structure.iter() {
let secured = format!("{}_{}", &auth_response.username, constant::SECURE_INDEX);
if secured == component.to_string_lossy() {
entry_map.insert("name".to_string(), directory);
entry_map.insert("font".to_string(), "fa-solid fa-lock".to_string());
entry_map.insert("secured".to_string(), "true".to_string());
return entry_map;
} else if component.to_string_lossy().ends_with(constant::SECURE_INDEX) {
return HashMap::new();
}
}
entry_map.insert("name".to_string(), directory);
if *depth > 1 {
entry_map.insert("font".to_string(), "fa-solid fa-folder-tree".to_string());
} else {
entry_map.insert("font".to_string(), "fa fa-folder".to_string());
}
entry_map
}
pub fn get_all_stream_content(config: &settings::Config, auth_response: &authenticator::AuthToken) -> ContentPayload {
let mut payload = ContentPayload::default();
for entry in WalkDir::new(&config.media_source).into_iter().filter_map(|e| e.ok()) {
if entry.path().ends_with("__") {
continue;
}
if let Some(file_name) = entry.file_name().to_str() {
if file_name.starts_with('_') || file_name.starts_with('.') {
continue;
}
if let Some(extension) = PathBuf::from(file_name).extension().and_then(|ext| ext.to_str()) {
if config.file_formats.iter().any(|format| extension == format) {
let path = entry.path().strip_prefix(&config.media_source)
.unwrap_or_else(|_| Path::new(""));
let components: &Vec<_> = &path.components().collect();
if components.len() == 1 {
let mut entry_map = HashMap::new();
entry_map.insert("path".to_string(), format!("stream/{}", &file_name));
entry_map.insert("name".to_string(), file_name.to_string());
entry_map.insert("font".to_string(), get_file_font(extension));
payload.files.push(entry_map);
} else {
let skimmed = path.components().rev().skip(1)
.collect::<Vec<_>>().iter().rev()
.collect::<PathBuf>();
let entry_map = get_folder_font(&skimmed, auth_response);
if entry_map.get("secured").unwrap_or(&"".to_string()) == "true" {
if payload.secured_directories.contains(&entry_map) || entry_map.is_empty() { continue; }
payload.secured_directories.push(entry_map);
} else {
if payload.directories.contains(&entry_map) || entry_map.is_empty() { continue; }
payload.directories.push(entry_map);
}
}
}
}
}
}
let re = Regex::new(r"(\D+|\d+)").unwrap();
payload.files.sort_by(|a, b| natural_sort_key(&re, &a["name"]).cmp(&natural_sort_key(&re, &b["name"])));
payload.directories.sort_by(|a, b| natural_sort_key(&re, &a["name"]).cmp(&natural_sort_key(&re, &b["name"])));
payload
}
pub fn get_dir_stream_content(parent: &str,
child: &str,
file_formats: &[String]) -> ContentPayload {
let mut files = Vec::new();
for entry in fs::read_dir(parent).unwrap().flatten() {
let file_name = entry.file_name().into_string().unwrap();
if file_name.starts_with('_') || file_name.starts_with('.') {
continue;
}
let file_path = Path::new(child).join(&file_name);
let file_extn = &file_path.extension().unwrap_or_default().to_string_lossy().to_string();
if file_formats.contains(file_extn) {
let map = HashMap::from([
("name".to_string(), file_name),
("path".to_string(), file_path.to_string_lossy().to_string()),
("font".to_string(), get_file_font(file_extn))
]);
files.push(map);
}
}
let re = Regex::new(r"(\D+|\d+)").unwrap();
files.sort_by_key(|a| natural_sort_key(&re, a.get("name").unwrap()));
ContentPayload { files, ..Default::default() }
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct Iter {
pub previous: Option<String>,
pub next: Option<String>,
}
pub fn get_iter(filepath: &Path, file_formats: &[String]) -> Iter {
let parent = filepath.parent().unwrap();
let mut dir_content: Vec<String> = fs::read_dir(parent)
.ok().unwrap()
.flatten()
.filter_map(|entry| {
let file_name = entry.file_name().to_string_lossy().to_string();
let file_extn = Path::new(&file_name).extension().unwrap_or_default().to_string_lossy().to_string();
if file_formats.contains(&file_extn) {
Some(file_name)
} else {
None
}
})
.collect();
let re = Regex::new(r"(\D+|\d+)").unwrap();
dir_content.sort_by_key(|a| natural_sort_key(&re, a));
let idx = dir_content.iter().position(|file| file == filepath.file_name().unwrap().to_str().unwrap()).unwrap();
let previous_ = if idx > 0 {
let previous_ = &dir_content[idx - 1];
if previous_ == filepath.file_name().unwrap().to_str().unwrap() {
None
} else {
Some(previous_.clone())
}
} else {
None
};
let next_ = dir_content.get(idx + 1).cloned();
Iter { previous: previous_, next: next_ }
}