use crate::{errors::FSError, fsemul::filesystem::ItemInFolder};
use std::path::{Path, PathBuf};
use tokio::fs::read_dir;
use valuable::Valuable;
#[cfg(feature = "nus")]
use crate::fsemul::filesystem::nus_fuse::NUSFuse;
#[cfg(feature = "nus")]
use sachet::{common::CafeContentFileInformation, title::TitleID};
#[derive(Clone, Debug, PartialEq, Eq, Valuable)]
pub struct DirectoryListing {
local_files: Vec<PathBuf>,
#[cfg(feature = "nus")]
nus_files: Vec<(
Option<TitleID>,
PathBuf,
PathBuf,
Option<CafeContentFileInformation>,
)>,
current_idx: usize,
is_at_end: bool,
root_path: PathBuf,
stream_owner: Option<u64>,
}
impl DirectoryListing {
pub async fn new(
path: &Path,
mlc_dir: PathBuf,
for_stream: Option<u64>,
#[cfg(feature = "nus")] nus_client: Option<&NUSFuse>,
) -> Result<Self, FSError> {
let mut local_files = Vec::new();
let mut prev_err: Option<FSError> = None;
match read_dir(path).await {
Ok(mut stream) => {
while let Ok(Some(item)) = stream.next_entry().await {
local_files.push(item.path());
}
}
Err(cause) => {
prev_err = Some(cause.into());
}
}
local_files.sort();
#[cfg(feature = "nus")]
let nus_files = Self::get_items_from_nus(&local_files, path, mlc_dir, nus_client).await?;
#[cfg(feature = "nus")]
if let Some(c) = prev_err
&& nus_files.is_empty()
{
return Err(c);
}
Ok(Self {
local_files,
#[cfg(feature = "nus")]
nus_files,
current_idx: 0,
is_at_end: false,
root_path: path.to_path_buf(),
stream_owner: for_stream,
})
}
#[allow(
// dependent on features.
unreachable_code,
)]
pub fn next_item(&mut self) -> Option<ItemInFolder> {
if self.is_at_end {
return None;
}
let max_files: usize;
#[cfg(feature = "nus")]
{
max_files = self.local_files.len() + self.nus_files.len();
}
#[cfg(not(feature = "nus"))]
{
max_files = self.local_files.len();
}
if self.current_idx >= max_files {
self.is_at_end = true;
return None;
}
while self.current_idx < self.local_files.len() {
let next_item = &self.local_files[self.current_idx];
self.current_idx += 1;
if (!next_item.is_file() && !next_item.is_dir()) || next_item.is_symlink() {
continue;
}
return Some(ItemInFolder::Local(
next_item.clone(),
self.root_path.components().count(),
));
}
#[cfg(feature = "nus")]
{
let actual_idx = self.current_idx - self.local_files.len();
self.current_idx += 1;
let nus_item = &self.nus_files[actual_idx];
return Some(ItemInFolder::Nus(
nus_item.0,
nus_item.1.clone(),
nus_item.2.clone(),
nus_item.3,
));
}
unreachable!("Not reachable path, will either return from NUS.")
}
pub const fn reverse_folder(&mut self) {
if self.current_idx == 0 {
return;
}
self.current_idx -= 1;
self.is_at_end = false;
}
#[must_use]
pub const fn stream_owner(&self) -> Option<u64> {
self.stream_owner
}
#[cfg(feature = "nus")]
async fn get_items_from_nus(
local_files: &[PathBuf],
path: &Path,
mut mlc_dir: PathBuf,
nus_client: Option<&NUSFuse>,
) -> Result<
Vec<(
Option<TitleID>,
PathBuf,
PathBuf,
Option<CafeContentFileInformation>,
)>,
FSError,
> {
let Some(nc) = nus_client else {
return Ok(Vec::with_capacity(0));
};
mlc_dir.push("usr");
mlc_dir.push("title");
let Ok(leftover) = path.strip_prefix(&mlc_dir) else {
return Ok(Vec::with_capacity(0));
};
let mut nus_files = Vec::new();
if leftover.components().count() == 0 {
let gids = nc.get_sorted_group_ids();
for group in gids {
let mut final_path = mlc_dir.clone();
final_path.push("{group:08x}");
if !local_files.contains(&final_path) {
nus_files.push((
None,
PathBuf::from(format!("{group:08x}")),
final_path,
None,
));
}
}
} else if leftover.components().count() == 1 {
let Some(gid_component) = leftover.components().next() else {
return Ok(Vec::with_capacity(0));
};
let Ok(gid) =
u32::from_str_radix(gid_component.as_os_str().to_string_lossy().as_ref(), 16)
else {
return Ok(Vec::with_capacity(0));
};
let tids = nc.get_sorted_tids_in_group(gid);
let mut base_path = mlc_dir;
base_path.push(format!("{gid:08x}"));
for title in tids {
let mut final_path = base_path.clone();
final_path.push("{title:08x}");
if !local_files.contains(&final_path) {
nus_files.push((
None,
PathBuf::from(format!("{title:08x}")),
final_path,
None,
));
}
}
} else {
let mut components = leftover.components();
let Some(gid_component) = components.next() else {
return Ok(Vec::with_capacity(0));
};
let Ok(group) =
u32::from_str_radix(gid_component.as_os_str().to_string_lossy().as_ref(), 16)
else {
return Ok(Vec::with_capacity(0));
};
let Some(tid_component) = components.next() else {
return Ok(Vec::with_capacity(0));
};
let Ok(title) =
u32::from_str_radix(tid_component.as_os_str().to_string_lossy().as_ref(), 16)
else {
return Ok(Vec::with_capacity(0));
};
let title_id = TitleID::new_with_ids(group, title);
let mut final_path = mlc_dir;
final_path.push(format!("{group:08x}"));
final_path.push(format!("{title:08x}"));
let mut seen_one = false;
let mut is_code = false;
for comp in leftover.components().skip(2) {
is_code = !seen_one && comp.as_os_str().to_string_lossy().as_ref() == "code";
final_path.push(comp);
seen_one = true;
}
for item in nc
.get_files_in_folder(title_id, components.as_path(), is_code)
.await
{
let mut final_path_frd = final_path.clone();
for component_part in item.0.components() {
final_path_frd.push(component_part);
}
nus_files.push((Some(title_id), item.0, final_path_frd, item.1));
}
}
Ok(nus_files)
}
}