diff --git a/cpp/include/loot/database_interface.h b/cpp/include/loot/database_interface.h index df97ff58..05502b9f 100644 --- a/cpp/include/loot/database_interface.h +++ b/cpp/include/loot/database_interface.h @@ -191,6 +191,14 @@ public: virtual std::vector GetUserGeneralMessages( bool evaluateConditions = false) const = 0; + /** + * @brief Sets the general messages to store in the userlist, replacing any + * messages already stored there. + * @param messages + * The messages to set. + */ + virtual void SetUserGeneralMessages(const std::vector& messages) = 0; + /** * @brief Gets the groups that are defined in the loaded metadata lists. * @param includeUserMetadata diff --git a/cpp/src/api/database.cpp b/cpp/src/api/database.cpp index 1114165e..c930d2b7 100644 --- a/cpp/src/api/database.cpp +++ b/cpp/src/api/database.cpp @@ -105,6 +105,15 @@ std::vector Database::GetUserGeneralMessages( } } +void Database::SetUserGeneralMessages(const std::vector& messages) { + try { + database_->set_user_general_messages( + convert(messages)); + } catch (const ::rust::Error& e) { + std::rethrow_exception(mapError(e)); + } +} + std::vector Database::GetGroups(bool includeUserMetadata) const { try { return convert(database_->groups(includeUserMetadata)); diff --git a/cpp/src/api/database.h b/cpp/src/api/database.h index 2f06e7d3..37d3229c 100644 --- a/cpp/src/api/database.h +++ b/cpp/src/api/database.h @@ -41,6 +41,8 @@ public: std::vector GetUserGeneralMessages( bool evaluateConditions = false) const override; + void SetUserGeneralMessages(const std::vector& messages) override; + std::vector GetGroups(bool includeUserMetadata = true) const override; std::vector GetUserGroups() const override; void SetUserGroups(const std::vector& groups) override; diff --git a/cpp/src/database.rs b/cpp/src/database.rs index bf2183bc..3825048b 100644 --- a/cpp/src/database.rs +++ b/cpp/src/database.rs @@ -158,6 +158,22 @@ impl Database { .collect()) } + #[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>, + ) -> Result<(), VerboseError> { + self.0 + .write() + .map_err(DatabaseLockPoisonError::from)? + .set_user_general_messages(to_vec_of_unwrapped(messages)); + + Ok(()) + } + pub fn groups(&self, include_user_metadata: bool) -> Result, VerboseError> { Ok(self .0 diff --git a/cpp/src/lib.rs b/cpp/src/lib.rs index d26ef2e7..5baa8aa4 100644 --- a/cpp/src/lib.rs +++ b/cpp/src/lib.rs @@ -278,6 +278,8 @@ mod ffi { pub fn user_general_messages(&self, evaluate_conditions: bool) -> Result>; + pub fn set_user_general_messages(&self, messages: Vec>) -> Result<()>; + pub fn groups(&self, include_user_metadata: bool) -> Result>; pub fn user_groups(&self) -> Result>; diff --git a/cpp/src/tests/api/interface/database_interface_test.h b/cpp/src/tests/api/interface/database_interface_test.h index 286c2b3c..95a14863 100644 --- a/cpp/src/tests/api/interface/database_interface_test.h +++ b/cpp/src/tests/api/interface/database_interface_test.h @@ -581,6 +581,24 @@ TEST_P(DatabaseInterfaceTest, EXPECT_EQ(expectedMessages, messages); } +TEST_P(DatabaseInterfaceTest, + setUserGeneralMessagesShouldReplaceExistingUserGeneralMessages) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_NO_THROW(GenerateUserlist()); + ASSERT_NO_THROW(handle_->GetDatabase().LoadMasterlist(masterlistPath)); + ASSERT_NO_THROW(handle_->GetDatabase().LoadUserlist(userlistPath_)); + + std::vector replacementMessages({ + Message(MessageType::say, "A replacement message"), + }); + + handle_->GetDatabase().SetUserGeneralMessages(replacementMessages); + + auto messages = handle_->GetDatabase().GetUserGeneralMessages(); + + EXPECT_EQ(replacementMessages, messages); +} + TEST_P(DatabaseInterfaceTest, getPluginMetadataShouldReturnAnEmptyOptionalIfThePluginHasNoMetadata) { EXPECT_FALSE(handle_->GetDatabase().GetPluginMetadata(blankEsm)); diff --git a/nodejs/src/database.rs b/nodejs/src/database.rs index 8a71ae0a..2c43f7bc 100644 --- a/nodejs/src/database.rs +++ b/nodejs/src/database.rs @@ -196,6 +196,16 @@ impl Database { .map_err(Into::into) } + #[napi] + pub fn set_user_general_messages(&self, messages: Vec<&Message>) -> Result<(), VerboseError> { + self.0 + .write() + .map_err(DatabaseLockPoisonError::from)? + .set_user_general_messages(messages.into_iter().cloned().map(Into::into).collect()); + + Ok(()) + } + #[napi] pub fn groups(&self, include_user_metadata: MergeMode) -> Result, VerboseError> { Ok(self diff --git a/python/src/database.rs b/python/src/database.rs index db83e6d0..5e3ce5a3 100644 --- a/python/src/database.rs +++ b/python/src/database.rs @@ -160,6 +160,15 @@ impl Database { .map_err(Into::into) } + pub fn set_user_general_messages(&self, messages: Vec) -> Result<(), VerboseError> { + self.0 + .write() + .map_err(DatabaseLockPoisonError::from)? + .set_user_general_messages(messages.into_iter().map(Into::into).collect()); + + Ok(()) + } + pub fn groups(&self, include_user_metadata: bool) -> Result, VerboseError> { Ok(self .0 diff --git a/src/database/mod.rs b/src/database/mod.rs index b0769c3c..d7db1c1f 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -216,6 +216,12 @@ impl Database { ) } + /// Sets the general messages to store in the userlist, replacing any + /// existing values stored there. + pub fn set_user_general_messages(&mut self, general_messages: Vec) { + self.userlist.set_messages(general_messages); + } + /// Gets the groups that are defined in the loaded metadata lists. pub fn groups(&self, include_user_metadata: MergeMode) -> Vec { if include_user_metadata == MergeMode::WithUserMetadata { @@ -1110,6 +1116,32 @@ plugins: } } + #[test] + fn set_user_general_messages_should_replace_existing_user_general_messages() { + let fixture = Fixture::new(GameType::Oblivion); + let mut database = fixture.database(); + + let userlist_path = fixture.inner.local_path.join("userlist.yaml"); + std::fs::write( + &userlist_path, + "globals: [{type: say, content: 'A user message'}]", + ) + .unwrap(); + + database.load_userlist(&userlist_path).unwrap(); + + let message = Message::new(MessageType::Say, "replacement message".to_owned()); + database.set_user_general_messages(vec![message.clone()]); + + assert_eq!( + &[message], + database + .user_general_messages(EvalMode::DoNotEvaluate) + .unwrap() + .as_slice() + ); + } + mod groups { use super::*; diff --git a/src/metadata/metadata_document.rs b/src/metadata/metadata_document.rs index 0e2a8065..d5236439 100644 --- a/src/metadata/metadata_document.rs +++ b/src/metadata/metadata_document.rs @@ -327,6 +327,10 @@ impl MetadataDocument { } } + pub(crate) fn set_messages(&mut self, messages: Vec) { + self.messages = messages; + } + pub(crate) fn set_plugin_metadata(&mut self, plugin_metadata: PluginMetadata) { if plugin_metadata.is_regex_plugin() { self.regex_plugins.push(plugin_metadata);