2025-03-06 22:24:39 +00:00
use std ::{
path ::Path ,
sync ::{ Arc , RwLock },
};
use delegate ::delegate ;
2025-12-29 17:40:08 +00:00
use libloot ::{ EvalMode , MergeMode , error ::DatabaseLockPoisonError };
2025-04-22 18:39:13 +01:00
use libloot_ffi_errors ::UnsupportedEnumValueError ;
2025-03-06 22:24:39 +00:00
use crate ::{
2025-04-25 18:27:35 +01:00
OptionalPluginMetadata , VerboseError ,
2025-12-29 17:40:08 +00:00
ffi ::{ EdgeType , MetadataWriteOptionsImpl },
2025-04-25 18:27:35 +01:00
metadata ::{ Group , Message , PluginMetadata , to_vec_of_unwrapped },
2025-03-06 22:24:39 +00:00
};
#[derive(Debug)]
#[repr(transparent)]
pub struct Database ( Arc < RwLock < libloot ::Database >> );
impl Database {
pub fn new ( db : Arc < RwLock < libloot ::Database >> ) -> Self {
Self ( db )
}
pub fn load_masterlist ( & self , path : & str ) -> Result < (), VerboseError > {
self . 0
. write ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-03-06 22:24:39 +00:00
. load_masterlist ( Path ::new ( path ))
. map_err ( Into ::into )
}
pub fn load_masterlist_with_prelude (
& self ,
masterlist_path : & str ,
prelude_path : & str ,
) -> Result < (), VerboseError > {
self . 0
. write ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-03-06 22:24:39 +00:00
. load_masterlist_with_prelude ( Path ::new ( masterlist_path ), Path ::new ( prelude_path ))
. map_err ( Into ::into )
}
pub fn load_userlist ( & self , path : & str ) -> Result < (), VerboseError > {
self . 0
. write ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-03-06 22:24:39 +00:00
. load_userlist ( Path ::new ( path ))
. map_err ( Into ::into )
}
pub fn write_user_metadata (
& self ,
output_path : & str ,
2025-12-29 17:40:08 +00:00
options : MetadataWriteOptionsImpl ,
2025-03-06 22:24:39 +00:00
) -> Result < (), VerboseError > {
self . 0
. read ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-12-29 17:40:08 +00:00
. write_user_metadata ( Path ::new ( output_path ), & options . into ())
2025-03-06 22:24:39 +00:00
. map_err ( Into ::into )
}
pub fn write_minimal_list (
& self ,
output_path : & str ,
2025-12-29 17:40:08 +00:00
options : MetadataWriteOptionsImpl ,
2025-03-06 22:24:39 +00:00
) -> Result < (), VerboseError > {
self . 0
. read ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-12-29 17:40:08 +00:00
. write_minimal_list ( Path ::new ( output_path ), & options . into ())
2025-03-06 22:24:39 +00:00
. map_err ( Into ::into )
}
2025-04-20 12:59:19 +01:00
pub fn evaluate ( & self , condition : & str ) -> Result < bool , VerboseError > {
self . 0
. read ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-04-20 12:59:19 +01:00
. evaluate ( condition )
. map_err ( Into ::into )
}
2025-09-07 10:58:50 +01:00
pub fn clear_condition_cache ( & self ) -> Result < (), VerboseError > {
self . 0
. write ()
. map_err ( DatabaseLockPoisonError ::from ) ?
. clear_condition_cache ();
Ok (())
}
2025-12-30 11:06:01 +00:00
pub fn known_bash_tags (
& self ,
include_user_metadata : bool ,
) -> Result < Vec < String > , VerboseError > {
2025-03-06 22:24:39 +00:00
Ok ( self
. 0
. read ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-12-30 11:06:01 +00:00
. known_bash_tags ( to_merge_mode ( include_user_metadata )))
2025-03-06 22:24:39 +00:00
}
2025-12-30 10:57:58 +00:00
pub fn user_known_bash_tags ( & self ) -> Result < Vec < String > , VerboseError > {
Ok ( self
. 0
. read ()
. map_err ( DatabaseLockPoisonError ::from ) ?
. user_known_bash_tags ()
. to_vec ())
}
2025-12-30 10:59:24 +00:00
pub fn set_user_known_bash_tags ( & self , bash_tags : Vec < String > ) -> Result < (), VerboseError > {
self . 0
. write ()
. map_err ( DatabaseLockPoisonError ::from ) ?
. set_user_known_bash_tags ( bash_tags );
Ok (())
}
2025-03-06 22:24:39 +00:00
pub fn general_messages (
& self ,
2025-12-30 11:12:10 +00:00
include_user_metadata : bool ,
2025-03-06 22:24:39 +00:00
evaluate_conditions : bool ,
) -> Result < Vec < Message > , VerboseError > {
self . 0
2026-01-02 13:07:58 +00:00
. read ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-12-30 11:12:10 +00:00
. general_messages (
to_merge_mode ( include_user_metadata ),
to_eval_mode ( evaluate_conditions ),
)
2025-03-06 22:24:39 +00:00
. map ( | v | v . into_iter (). map ( Into ::into ). collect ())
. map_err ( Into ::into )
}
2025-12-30 11:08:57 +00:00
pub fn user_general_messages (
& self ,
evaluate_conditions : bool ,
) -> Result < Vec < Message > , VerboseError > {
Ok ( self
. 0
. read ()
. map_err ( DatabaseLockPoisonError ::from ) ?
. user_general_messages ( to_eval_mode ( evaluate_conditions )) ?
. into_iter ()
. map ( Into ::into )
. collect ())
}
2025-12-30 11:11:58 +00:00
#[expect(
clippy::vec_box,
reason = "Message is an opaque type to C++ so needs to be held in a Box."
)]
pub fn set_user_general_messages (
& self ,
messages : Vec < Box < Message >> ,
) -> Result < (), VerboseError > {
self . 0
. write ()
. map_err ( DatabaseLockPoisonError ::from ) ?
. set_user_general_messages ( to_vec_of_unwrapped ( messages ));
Ok (())
}
2025-03-06 22:24:39 +00:00
pub fn groups ( & self , include_user_metadata : bool ) -> Result < Vec < Group > , VerboseError > {
Ok ( self
. 0
. read ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-08-02 16:04:51 +01:00
. groups ( to_merge_mode ( include_user_metadata ))
2025-03-06 22:24:39 +00:00
. into_iter ()
. map ( Into ::into )
. collect ())
}
// I tried returning a GroupRef<'_> here, but it borrows from the DB read guard, which the borrow checker sees as an owned value within this scope, so won't allow me to return a reference to it. This is the only place that uses a group reference, so it's probably not worth figuring out a workaround, and cloning the groups is fine.
pub fn user_groups ( & self ) -> Result < Vec < Group > , VerboseError > {
Ok ( self
. 0
. read ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-03-06 22:24:39 +00:00
. user_groups ()
. iter ()
. cloned ()
. map ( Into ::into )
. collect ())
}
2025-12-30 17:19:35 +00:00
#[expect(
clippy::vec_box,
reason = "Group is an opaque type to C++ so needs to be held in a Box."
)]
pub fn set_user_groups ( & self , groups : Vec < Box < Group >> ) -> Result < (), VerboseError > {
2025-03-06 22:24:39 +00:00
let groups = to_vec_of_unwrapped ( groups );
self . 0
. write ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-03-06 22:24:39 +00:00
. set_user_groups ( groups );
Ok (())
}
pub fn groups_path (
& self ,
from_group_name : & str ,
to_group_name : & str ,
) -> Result < Vec < Vertex > , VerboseError > {
self . 0
. read ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-03-06 22:24:39 +00:00
. groups_path ( from_group_name , to_group_name )
. map ( | v | v . into_iter (). map ( Into ::into ). collect ())
. map_err ( Into ::into )
}
pub fn plugin_metadata (
& self ,
plugin_name : & str ,
include_user_metadata : bool ,
evaluate_conditions : bool ,
) -> Result < Box < OptionalPluginMetadata > , VerboseError > {
self . 0
. read ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-08-02 16:04:51 +01:00
. plugin_metadata (
plugin_name ,
to_merge_mode ( include_user_metadata ),
to_eval_mode ( evaluate_conditions ),
)
2025-03-06 22:24:39 +00:00
. map ( | p | Box ::new ( p . map ( Into ::into ). into ()))
. map_err ( Into ::into )
}
pub fn plugin_user_metadata (
& self ,
plugin_name : & str ,
evaluate_conditions : bool ,
) -> Result < Box < OptionalPluginMetadata > , VerboseError > {
self . 0
. read ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-08-02 16:04:51 +01:00
. plugin_user_metadata ( plugin_name , to_eval_mode ( evaluate_conditions ))
2025-03-06 22:24:39 +00:00
. map ( | p | Box ::new ( p . map ( Into ::into ). into ()))
. map_err ( Into ::into )
}
pub fn set_plugin_user_metadata (
& mut self ,
plugin_metadata : Box < PluginMetadata > ,
) -> Result < (), VerboseError > {
self . 0
. write ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-03-06 22:24:39 +00:00
. set_plugin_user_metadata ( plugin_metadata . into ());
Ok (())
}
pub fn discard_plugin_user_metadata ( & self , plugin : & str ) -> Result < (), VerboseError > {
self . 0
. write ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-03-06 22:24:39 +00:00
. discard_plugin_user_metadata ( plugin );
Ok (())
}
pub fn discard_all_user_metadata ( & self ) -> Result < (), VerboseError > {
self . 0
. write ()
2025-04-22 23:46:47 +01:00
. map_err ( DatabaseLockPoisonError ::from ) ?
2025-03-06 22:24:39 +00:00
. discard_all_user_metadata ();
Ok (())
}
}
2025-08-02 16:04:51 +01:00
fn to_eval_mode ( value : bool ) -> EvalMode {
if value {
EvalMode ::Evaluate
} else {
EvalMode ::DoNotEvaluate
}
}
fn to_merge_mode ( value : bool ) -> MergeMode {
if value {
MergeMode ::WithUserMetadata
} else {
MergeMode ::WithoutUserMetadata
}
}
2025-03-06 22:24:39 +00:00
#[derive(Clone, Debug)]
#[repr(transparent)]
pub struct Vertex ( libloot ::Vertex );
2025-05-14 18:23:27 +01:00
pub fn new_vertex ( name : String , out_edge_type : EdgeType ) -> Result < Box < Vertex > , VerboseError > {
let mut vertex = libloot ::Vertex ::new ( name );
if out_edge_type != EdgeType ::None {
vertex = vertex . with_out_edge_type ( out_edge_type . try_into () ? );
}
Ok ( Box ::new ( Vertex ( vertex )))
2025-03-06 22:24:39 +00:00
}
impl Vertex {
// A value of 255 is used to indicate that there is no out edge.
2025-05-14 18:23:27 +01:00
pub fn out_edge_type ( & self ) -> Result < EdgeType , VerboseError > {
2025-03-06 22:24:39 +00:00
match self . 0. out_edge_type () {
2025-05-14 18:23:27 +01:00
Some ( e ) => EdgeType ::try_from ( e ). map_err ( Into ::into ),
None => Ok ( EdgeType ::None ),
2025-03-06 22:24:39 +00:00
}
}
pub fn boxed_clone ( & self ) -> Box < Self > {
Box ::new ( Self ( self . 0. clone ()))
}
delegate! {
to self . 0 {
pub fn name ( & self ) -> & str ;
}
}
}
impl From < libloot ::Vertex > for Vertex {
fn from ( value : libloot ::Vertex ) -> Self {
Self ( value )
}
}
impl TryFrom < libloot ::EdgeType > for EdgeType {
type Error = UnsupportedEnumValueError ;
fn try_from ( value : libloot ::EdgeType ) -> Result < Self , Self ::Error > {
match value {
2025-04-23 18:02:29 +01:00
libloot ::EdgeType ::Hardcoded => Ok ( EdgeType ::Hardcoded ),
libloot ::EdgeType ::MasterFlag => Ok ( EdgeType ::MasterFlag ),
libloot ::EdgeType ::Master => Ok ( EdgeType ::Master ),
libloot ::EdgeType ::MasterlistRequirement => Ok ( EdgeType ::MasterlistRequirement ),
libloot ::EdgeType ::UserRequirement => Ok ( EdgeType ::UserRequirement ),
libloot ::EdgeType ::MasterlistLoadAfter => Ok ( EdgeType ::MasterlistLoadAfter ),
libloot ::EdgeType ::UserLoadAfter => Ok ( EdgeType ::UserLoadAfter ),
libloot ::EdgeType ::MasterlistGroup => Ok ( EdgeType ::MasterlistGroup ),
libloot ::EdgeType ::UserGroup => Ok ( EdgeType ::UserGroup ),
libloot ::EdgeType ::RecordOverlap => Ok ( EdgeType ::RecordOverlap ),
libloot ::EdgeType ::AssetOverlap => Ok ( EdgeType ::AssetOverlap ),
libloot ::EdgeType ::TieBreak => Ok ( EdgeType ::TieBreak ),
libloot ::EdgeType ::BlueprintMaster => Ok ( EdgeType ::BlueprintMaster ),
2025-03-06 22:24:39 +00:00
_ => Err ( UnsupportedEnumValueError ),
}
}
}
impl TryFrom < EdgeType > for libloot ::EdgeType {
type Error = UnsupportedEnumValueError ;
fn try_from ( value : EdgeType ) -> Result < Self , Self ::Error > {
match value {
2025-04-23 18:02:29 +01:00
EdgeType ::Hardcoded => Ok ( libloot ::EdgeType ::Hardcoded ),
EdgeType ::MasterFlag => Ok ( libloot ::EdgeType ::MasterFlag ),
EdgeType ::Master => Ok ( libloot ::EdgeType ::Master ),
EdgeType ::MasterlistRequirement => Ok ( libloot ::EdgeType ::MasterlistRequirement ),
EdgeType ::UserRequirement => Ok ( libloot ::EdgeType ::UserRequirement ),
EdgeType ::MasterlistLoadAfter => Ok ( libloot ::EdgeType ::MasterlistLoadAfter ),
EdgeType ::UserLoadAfter => Ok ( libloot ::EdgeType ::UserLoadAfter ),
EdgeType ::MasterlistGroup => Ok ( libloot ::EdgeType ::MasterlistGroup ),
EdgeType ::UserGroup => Ok ( libloot ::EdgeType ::UserGroup ),
EdgeType ::RecordOverlap => Ok ( libloot ::EdgeType ::RecordOverlap ),
EdgeType ::AssetOverlap => Ok ( libloot ::EdgeType ::AssetOverlap ),
EdgeType ::TieBreak => Ok ( libloot ::EdgeType ::TieBreak ),
EdgeType ::BlueprintMaster => Ok ( libloot ::EdgeType ::BlueprintMaster ),
2025-03-06 22:24:39 +00:00
_ => Err ( UnsupportedEnumValueError ),
}
}
}