use crate::{
errors::{CatBridgeError, FSError},
fsemul::filesystem::host::utilities::get_new_unique_file_fd,
};
use std::path::{Path, PathBuf};
use tokio::fs::{File, OpenOptions};
use valuable::{Fields, NamedField, NamedValues, StructDef, Structable, Valuable, Value, Visit};
#[cfg(feature = "nus")]
use crate::fsemul::filesystem::nus_fuse::NUSFuse;
#[cfg(feature = "nus")]
use sachet::{common::CafeContentFileInformation, title::TitleID};
#[cfg(any(feature = "clients", feature = "servers"))]
use crate::fsemul::{filesystem::host::HostFilesystem, pcfs::sata::proto::SataFDInfo};
#[derive(Debug)]
pub struct OpenFileHandle {
disk_path: PathBuf,
file_size: u64,
local_file_handle: Option<(File, i32)>,
#[cfg(feature = "nus")]
nus_file_info: Option<(TitleID, CafeContentFileInformation, i32)>,
open_options: OpenOptions,
owner: Option<u64>,
}
impl OpenFileHandle {
pub(crate) async fn open_file(
force_unique_fds: bool,
open_options: OpenOptions,
full_disk_path: &Path,
stream_owner: Option<u64>,
#[cfg(feature = "nus")] mlc_usr_title_path: &Path,
#[cfg(feature = "nus")] nus: Option<&NUSFuse>,
) -> Result<Self, FSError> {
#[cfg(feature = "nus")]
if let Some(result) = Self::check_and_potentially_open_from_nus(
nus,
mlc_usr_title_path,
full_disk_path,
open_options.clone(),
stream_owner,
)
.await
{
return Ok(result);
}
let local_file = open_options.clone().open(full_disk_path).await?;
let raw_fd;
#[cfg(unix)]
{
use std::os::fd::AsRawFd;
raw_fd = local_file.as_raw_fd();
}
#[cfg(target_os = "windows")]
{
use std::os::windows::io::AsRawHandle;
raw_fd = local_file.as_raw_handle() as i32;
}
#[cfg(all(not(unix), not(target_os = "windows")))]
{
raw_fd = get_new_unique_file_fd();
}
let md = local_file.metadata().await?;
let final_fd = if force_unique_fds {
get_new_unique_file_fd()
} else {
raw_fd
};
Ok(Self {
disk_path: full_disk_path.to_path_buf(),
file_size: md.len(),
local_file_handle: Some((local_file, final_fd)),
#[cfg(feature = "nus")]
nus_file_info: None,
open_options,
owner: stream_owner,
})
}
#[must_use]
pub fn disk_path(&self) -> &Path {
&self.disk_path
}
#[must_use]
pub const fn fd(&self) -> i32 {
if let Some((_, fd)) = self.local_file_handle.as_ref() {
return *fd;
}
#[cfg(feature = "nus")]
{
if let Some((_, _, fd)) = self.nus_file_info.as_ref() {
return *fd;
}
}
unreachable!();
}
#[must_use]
pub const fn file_size(&self) -> u64 {
self.file_size
}
pub async fn force_file_handle(
&mut self,
#[cfg(feature = "nus")] nus: Option<&NUSFuse>,
) -> Result<&mut File, CatBridgeError> {
#[cfg(feature = "nus")]
if let Some((title_id, file_info, fd)) = self.nus_file_info {
let nc = nus.ok_or_else(|| {
FSError::IO(std::io::Error::other(
"NUS file pointer without NUS client?",
))
})?;
nc.download_to(&self.disk_path, title_id, file_info).await?;
self.nus_file_info = None;
_ = self.local_file_handle.insert((
self.open_options
.open(&self.disk_path)
.await
.map_err(FSError::IO)?,
fd,
));
}
if let Some((handle, _)) = self.local_file_handle.as_mut() {
return Ok(handle);
}
unreachable!();
}
#[cfg_attr(docsrs, doc(cfg(any(feature = "clients", feature = "servers"))))]
#[cfg(any(feature = "clients", feature = "servers"))]
pub async fn sata_fd_info(
&self,
host_filesystem: &HostFilesystem,
) -> Result<SataFDInfo, FSError> {
#[cfg(feature = "nus")]
{
if let Some((_title_id, file_info, _fd)) = self.nus_file_info.as_ref() {
return Ok(SataFDInfo::new_from_nus(
host_filesystem,
&self.disk_path,
Some(*file_info),
)
.await);
}
}
if let Some((handle, _)) = self.local_file_handle.as_ref() {
return Ok(SataFDInfo::get_info(
host_filesystem,
&handle.metadata().await?,
&self.disk_path,
)
.await);
}
unreachable!()
}
#[must_use]
pub const fn stream_owner(&self) -> Option<u64> {
self.owner
}
#[cfg(feature = "nus")]
#[must_use]
async fn check_and_potentially_open_from_nus(
opt_nus: Option<&NUSFuse>,
mlc_user_title_path: &Path,
full_disk_path: &Path,
open_options: OpenOptions,
stream_owner: Option<u64>,
) -> Option<Self> {
let nus = opt_nus?;
if full_disk_path.exists() {
return None;
}
let relative_path = full_disk_path.strip_prefix(mlc_user_title_path).ok()?;
if relative_path.components().count() < 3 {
return None;
}
let mut comp = relative_path.components();
let group =
u32::from_str_radix(comp.next()?.as_os_str().to_string_lossy().as_ref(), 16).ok()?;
let title =
u32::from_str_radix(comp.next()?.as_os_str().to_string_lossy().as_ref(), 16).ok()?;
let tid = TitleID::new_with_ids(group, title);
let relative_path = comp.as_path();
let nus_info = nus.exists(tid, relative_path).await??;
let unique_fd = get_new_unique_file_fd();
Some(Self {
disk_path: full_disk_path.to_path_buf(),
file_size: u64::from(nus_info.file_size()),
local_file_handle: None,
nus_file_info: Some((tid, nus_info, unique_fd)),
open_options,
owner: stream_owner,
})
}
}
const OPEN_FILE_HANDLE_FIELDS: &[NamedField<'static>] = &[
NamedField::new("disk_path"),
NamedField::new("file_size"),
NamedField::new("local_file_handle"),
#[cfg(feature = "nus")]
NamedField::new("nus_file_info"),
NamedField::new("open_options"),
];
impl Structable for OpenFileHandle {
fn definition(&self) -> StructDef<'_> {
StructDef::new_static("OpenFileHandle", Fields::Named(OPEN_FILE_HANDLE_FIELDS))
}
}
impl Valuable for OpenFileHandle {
fn as_value(&self) -> Value<'_> {
Value::Structable(self)
}
fn visit(&self, visitor: &mut dyn Visit) {
visitor.visit_named_fields(&NamedValues::new(
OPEN_FILE_HANDLE_FIELDS,
&[
Valuable::as_value(&self.disk_path),
Valuable::as_value(&self.file_size),
Valuable::as_value(&format!("{:?}", self.local_file_handle)),
#[cfg(feature = "nus")]
Valuable::as_value(&self.nus_file_info),
Valuable::as_value(&format!("{:?}", self.open_options)),
],
));
}
}