1use std::{
2 fs::{self, File},
3 io,
4 path::Path,
5 sync::Arc,
6 time::SystemTime,
7};
8
9use nwnrs_checksums::prelude::*;
10use nwnrs_exo::prelude::*;
11use nwnrs_resman::prelude::*;
12use nwnrs_resref::prelude::*;
13use tracing::{debug, instrument};
14
15use crate::{ResFile, ResFileError, ResFileResult};
16
17#[allow(clippy :: redundant_closure_call)]
match (move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: ResFileResult<ResFile> =
loop {};
return __tracing_attr_fake_return;
}
{
let path = path.as_ref();
let file_name =
path.file_name().and_then(|value|
value.to_str()).ok_or_else(||
ResFileError::msg(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0} has no valid filename",
path.display()))
})))?;
let resolved = ResolvedResRef::from_filename(file_name)?;
read_resfile_as(path, resolved.base().clone())
}
})()
{
#[allow(clippy :: unit_arg)]
Ok(x) => Ok(x),
Err(e) => {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/read.rs:23",
"nwnrs_resfile::read", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/read.rs"),
::tracing_core::__macro_support::Option::Some(23u32),
::tracing_core::__macro_support::Option::Some("nwnrs_resfile::read"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("error")
}> =
::tracing::__macro_support::FieldName::new("error");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Err(e)
}
}#[instrument(level = "debug", skip_all, err, fields(path = %path.as_ref().display()))]
24pub fn read_resfile(path: impl AsRef<Path>) -> ResFileResult<ResFile> {
25 let path = path.as_ref();
26 let file_name = path
27 .file_name()
28 .and_then(|value| value.to_str())
29 .ok_or_else(|| ResFileError::msg(format!("{} has no valid filename", path.display())))?;
30 let resolved = ResolvedResRef::from_filename(file_name)?;
31 read_resfile_as(path, resolved.base().clone())
32}
33
34#[allow(clippy :: redundant_closure_call)]
match (move ||
{
#[allow(unknown_lints, unreachable_code, clippy ::
diverging_sub_expression, clippy :: empty_loop, clippy ::
let_unit_value, clippy :: let_with_type_underscore, clippy
:: needless_return, clippy :: unreachable)]
if false {
let __tracing_attr_fake_return: ResFileResult<ResFile> =
loop {};
return __tracing_attr_fake_return;
}
{
let path = path.as_ref();
let metadata = fs::metadata(path)?;
if !metadata.is_file() {
return Err(ResFileError::msg(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("{0} is not a regular file",
path.display()))
})));
}
let label = path.display().to_string();
let mtime =
metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
let io_size = metadata.len().cast_signed();
let path_for_io = path.to_path_buf();
let origin_label = label.clone();
let spawner =
Arc::new(move ||
-> io::Result<Box<dyn nwnrs_resman::ReadSeek + Send>>
{ Ok(Box::new(File::open(&path_for_io)?)) });
let result =
ResFile {
path: path.to_path_buf(),
label: label.clone(),
entry: Res::new_with_spawner(new_res_origin(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("ResFile:{0}", label))
}), origin_label), resref, mtime, spawner, io_size, 0,
ExoResFileCompressionType::None, None,
usize::try_from(io_size.max(0)).unwrap_or(usize::MAX),
EMPTY_SECURE_HASH),
};
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/read.rs:78",
"nwnrs_resfile::read", ::tracing::Level::DEBUG,
::tracing_core::__macro_support::Option::Some("src/read.rs"),
::tracing_core::__macro_support::Option::Some(78u32),
::tracing_core::__macro_support::Option::Some("nwnrs_resfile::read"),
::tracing_core::field::FieldSet::new(&["message",
{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("io_size")
}> =
::tracing::__macro_support::FieldName::new("io_size");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::DEBUG <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::DEBUG <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("read resource file")
as &dyn ::tracing::field::Value)),
(::tracing::__macro_support::Option::Some(&io_size as
&dyn ::tracing::field::Value))])
});
} else { ; }
};
Ok(result)
}
})()
{
#[allow(clippy :: unit_arg)]
Ok(x) => Ok(x),
Err(e) => {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/read.rs:40",
"nwnrs_resfile::read", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/read.rs"),
::tracing_core::__macro_support::Option::Some(40u32),
::tracing_core::__macro_support::Option::Some("nwnrs_resfile::read"),
::tracing_core::field::FieldSet::new(&[{
const NAME:
::tracing::__macro_support::FieldName<{
::tracing::__macro_support::FieldName::len("error")
}> =
::tracing::__macro_support::FieldName::new("error");
NAME.as_str()
}], ::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&::tracing::field::display(&e)
as &dyn ::tracing::field::Value))])
});
} else { ; }
};
Err(e)
}
}#[instrument(level = "debug", skip_all, err, fields(path = %path.as_ref().display(), resref = %resref))]
41pub fn read_resfile_as(path: impl AsRef<Path>, resref: ResRef) -> ResFileResult<ResFile> {
42 let path = path.as_ref();
43 let metadata = fs::metadata(path)?;
44 if !metadata.is_file() {
45 return Err(ResFileError::msg(format!(
46 "{} is not a regular file",
47 path.display()
48 )));
49 }
50
51 let label = path.display().to_string();
52 let mtime = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
53 let io_size = metadata.len().cast_signed();
54 let path_for_io = path.to_path_buf();
55 let origin_label = label.clone();
56 let spawner = Arc::new(
57 move || -> io::Result<Box<dyn nwnrs_resman::ReadSeek + Send>> {
58 Ok(Box::new(File::open(&path_for_io)?))
59 },
60 );
61
62 let result = ResFile {
63 path: path.to_path_buf(),
64 label: label.clone(),
65 entry: Res::new_with_spawner(
66 new_res_origin(format!("ResFile:{label}"), origin_label),
67 resref,
68 mtime,
69 spawner,
70 io_size,
71 0,
72 ExoResFileCompressionType::None,
73 None,
74 usize::try_from(io_size.max(0)).unwrap_or(usize::MAX),
75 EMPTY_SECURE_HASH,
76 ),
77 };
78 debug!(io_size, "read resource file");
79 Ok(result)
80}
81
82#[allow(clippy::panic)]
83#[cfg(test)]
84mod tests {
85 use std::{
86 fs,
87 path::PathBuf,
88 time::{SystemTime, UNIX_EPOCH},
89 };
90
91 use nwnrs_resman::{CachePolicy, ResContainer};
92 use nwnrs_resref::{ResRef, ResolvedResRef};
93 use nwnrs_restype::ResType;
94
95 use crate::{read_resfile, read_resfile_as};
96
97 fn unique_test_dir(prefix: &str) -> PathBuf {
98 let nanos = SystemTime::now()
99 .duration_since(UNIX_EPOCH)
100 .unwrap_or_default()
101 .as_nanos();
102 std::env::temp_dir().join(format!("nwnrs-rf-{prefix}-{nanos}"))
103 }
104
105 #[test]
106 fn reads_resref_from_filename() {
107 let root = unique_test_dir("auto");
108 if let Err(error) = fs::create_dir_all(&root) {
109 panic!("create root: {error}");
110 }
111 let path = root.join("alpha.utc");
112 if let Err(error) = fs::write(&path, b"payload") {
113 panic!("write file: {error}");
114 }
115
116 let resfile = match read_resfile(&path) {
117 Ok(value) => value,
118 Err(error) => panic!("read resfile: {error}"),
119 };
120 let filename = match path.file_name().and_then(|value| value.to_str()) {
121 Some(value) => value,
122 None => panic!("filename should be valid utf-8"),
123 };
124 let rr = match ResolvedResRef::from_filename(filename) {
125 Ok(value) => value,
126 Err(error) => panic!("resolve rr: {error}"),
127 };
128 assert!(resfile.contains(rr.base()));
129 let bytes = match resfile.res().read_all(CachePolicy::Bypass) {
130 Ok(value) => value,
131 Err(error) => panic!("read payload: {error}"),
132 };
133 assert_eq!(bytes, b"payload".to_vec());
134 }
135
136 #[test]
137 fn supports_explicit_resref_override() {
138 let root = unique_test_dir("override");
139 if let Err(error) = fs::create_dir_all(&root) {
140 panic!("create root: {error}");
141 }
142 let path = root.join("payload.bin");
143 if let Err(error) = fs::write(&path, b"payload") {
144 panic!("write file: {error}");
145 }
146 let rr = match ResRef::new("custom", ResType(2027)) {
147 Ok(value) => value,
148 Err(error) => panic!("custom rr: {error}"),
149 };
150
151 let resfile = match read_resfile_as(&path, rr.clone()) {
152 Ok(value) => value,
153 Err(error) => panic!("read resfile as: {error}"),
154 };
155 assert!(resfile.contains(&rr));
156 }
157}