From 0371b608dd7a59664e7c8e1494335709ad21943c Mon Sep 17 00:00:00 2001 From: SW van Heerden Date: Mon, 6 Mar 2023 17:58:29 +0200 Subject: [PATCH] feat: add favourite flag to contact (#5217) Description --- Adds a favourite tag to contacts Motivation and Context --- Mobile applications have favorite tags on contacts. For data integrity, we link the two. How Has This Been Tested? --- Unit tests and manual --- .../src/ui/state/app_state.rs | 2 +- .../down.sql | 1 + .../2023-02-28-100000_add_fav_contact/up.sql | 1 + .../src/contacts_service/storage/database.rs | 10 ++++- .../src/contacts_service/storage/sqlite_db.rs | 14 +++++- base_layer/wallet/src/schema.rs | 1 + base_layer/wallet/tests/contacts_service.rs | 3 +- base_layer/wallet/tests/wallet.rs | 6 +-- .../wallet_ffi/src/callback_handler_tests.rs | 1 + base_layer/wallet_ffi/src/lib.rs | 44 +++++++++++++++++-- base_layer/wallet_ffi/wallet.h | 19 ++++++++ integration_tests/tests/utils/ffi/contact.rs | 1 + .../tests/utils/ffi/ffi_import.rs | 1 + 13 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 base_layer/wallet/migrations/2023-02-28-100000_add_fav_contact/down.sql create mode 100644 base_layer/wallet/migrations/2023-02-28-100000_add_fav_contact/up.sql diff --git a/applications/tari_console_wallet/src/ui/state/app_state.rs b/applications/tari_console_wallet/src/ui/state/app_state.rs index 05ecec2faf..dd1eb6c9fc 100644 --- a/applications/tari_console_wallet/src/ui/state/app_state.rs +++ b/applications/tari_console_wallet/src/ui/state/app_state.rs @@ -219,7 +219,7 @@ impl AppState { .map_err(|_| UiError::PublicKeyParseError)?, }; - let contact = Contact::new(alias, address, None, None); + let contact = Contact::new(alias, address, None, None, false); inner.wallet.contacts_service.upsert_contact(contact).await?; inner.refresh_contacts_state().await?; diff --git a/base_layer/wallet/migrations/2023-02-28-100000_add_fav_contact/down.sql b/base_layer/wallet/migrations/2023-02-28-100000_add_fav_contact/down.sql new file mode 100644 index 0000000000..8d587d72aa --- /dev/null +++ b/base_layer/wallet/migrations/2023-02-28-100000_add_fav_contact/down.sql @@ -0,0 +1 @@ +ALTER TABLE contacts drop favourite; \ No newline at end of file diff --git a/base_layer/wallet/migrations/2023-02-28-100000_add_fav_contact/up.sql b/base_layer/wallet/migrations/2023-02-28-100000_add_fav_contact/up.sql new file mode 100644 index 0000000000..3fb7d720f0 --- /dev/null +++ b/base_layer/wallet/migrations/2023-02-28-100000_add_fav_contact/up.sql @@ -0,0 +1 @@ +ALTER TABLE contacts ADD favourite INTEGER DEFAULT 0 NOT NULL; diff --git a/base_layer/wallet/src/contacts_service/storage/database.rs b/base_layer/wallet/src/contacts_service/storage/database.rs index 3811e7e08f..bf52045637 100644 --- a/base_layer/wallet/src/contacts_service/storage/database.rs +++ b/base_layer/wallet/src/contacts_service/storage/database.rs @@ -41,16 +41,24 @@ pub struct Contact { pub node_id: NodeId, pub last_seen: Option, pub latency: Option, + pub favourite: bool, } impl Contact { - pub fn new(alias: String, address: TariAddress, last_seen: Option, latency: Option) -> Self { + pub fn new( + alias: String, + address: TariAddress, + last_seen: Option, + latency: Option, + favourite: bool, + ) -> Self { Self { alias, node_id: NodeId::from_key(address.public_key()), address, last_seen, latency, + favourite, } } } diff --git a/base_layer/wallet/src/contacts_service/storage/sqlite_db.rs b/base_layer/wallet/src/contacts_service/storage/sqlite_db.rs index 186252a93a..f09edaaaf0 100644 --- a/base_layer/wallet/src/contacts_service/storage/sqlite_db.rs +++ b/base_layer/wallet/src/contacts_service/storage/sqlite_db.rs @@ -85,6 +85,7 @@ impl ContactsBackend for ContactsServiceSqliteDatabase { alias: Some(c.clone().alias), last_seen: None, latency: None, + favourite: Some(i32::from(c.favourite)), }) .is_err() { @@ -100,6 +101,7 @@ impl ContactsBackend for ContactsServiceSqliteDatabase { alias: None, last_seen: Some(Some(date_time)), latency: Some(latency), + favourite: None, })?; return Ok(Some(DbValue::TariAddress(Box::new( TariAddress::from_bytes(&contact.address) @@ -140,6 +142,7 @@ struct ContactSql { alias: String, last_seen: Option, latency: Option, + favourite: i32, } impl ContactSql { @@ -245,6 +248,11 @@ impl TryFrom for Contact { alias: o.alias, last_seen: o.last_seen, latency: o.latency.map(|val| val as u32), + favourite: match o.favourite { + 0 => false, + 1 => true, + _ => return Err(ContactsServiceStorageError::ConversionError), + }, }) } } @@ -260,6 +268,7 @@ impl From for ContactSql { alias: o.alias, last_seen: o.last_seen, latency: o.latency.map(|val| val as i32), + favourite: i32::from(o.favourite), } } } @@ -270,6 +279,7 @@ pub struct UpdateContact { alias: Option, last_seen: Option>, latency: Option>, + favourite: Option, } #[cfg(test)] @@ -324,7 +334,7 @@ mod test { for i in 0..names.len() { let pub_key = PublicKey::from_secret_key(&PrivateKey::random(&mut OsRng)); let address = TariAddress::new(pub_key, Network::default()); - contacts.push(Contact::new(names[i].clone(), address, None, None)); + contacts.push(Contact::new(names[i].clone(), address, None, None, false)); ContactSql::from(contacts[i].clone()).commit(&mut conn).unwrap(); } @@ -354,11 +364,13 @@ mod test { alias: Some("Fred".to_string()), last_seen: None, latency: None, + favourite: Some(i32::from(true)), }) .unwrap(); let c_updated = ContactSql::find_by_address(&contacts[1].address.to_bytes(), &mut conn).unwrap(); assert_eq!(c_updated.alias, "Fred".to_string()); + assert_eq!(c_updated.favourite, i32::from(true)); }); } } diff --git a/base_layer/wallet/src/schema.rs b/base_layer/wallet/src/schema.rs index 47f603f781..fdd854ea8b 100644 --- a/base_layer/wallet/src/schema.rs +++ b/base_layer/wallet/src/schema.rs @@ -39,6 +39,7 @@ diesel::table! { alias -> Text, last_seen -> Nullable, latency -> Nullable, + favourite -> Integer, } } diff --git a/base_layer/wallet/tests/contacts_service.rs b/base_layer/wallet/tests/contacts_service.rs index 62a734fe8f..c18ad51a18 100644 --- a/base_layer/wallet/tests/contacts_service.rs +++ b/base_layer/wallet/tests/contacts_service.rs @@ -144,7 +144,7 @@ pub fn test_contacts_service() { let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); let address = TariAddress::new(public_key, Network::default()); - contacts.push(Contact::new(random::string(8), address, None, None)); + contacts.push(Contact::new(random::string(8), address, None, None, false)); runtime .block_on(contacts_service.upsert_contact(contacts[i].clone())) @@ -189,6 +189,7 @@ pub fn test_contacts_service() { let mut updated_contact = contacts[1].clone(); updated_contact.alias = "Fred".to_string(); + updated_contact.favourite = true; runtime .block_on(contacts_service.upsert_contact(updated_contact.clone())) diff --git a/base_layer/wallet/tests/wallet.rs b/base_layer/wallet/tests/wallet.rs index 131d7e81fd..435ab3110b 100644 --- a/base_layer/wallet/tests/wallet.rs +++ b/base_layer/wallet/tests/wallet.rs @@ -314,7 +314,7 @@ async fn test_wallet() { let (_secret_key, public_key) = PublicKey::random_keypair(&mut OsRng); let address = TariAddress::new(public_key, Network::LocalNet); - contacts.push(Contact::new(random::string(8), address, None, None)); + contacts.push(Contact::new(random::string(8), address, None, None, false)); alice_wallet .contacts_service @@ -869,7 +869,7 @@ async fn test_contacts_service_liveness() { .add_peer(bob_identity.to_peer()) .await .unwrap(); - let contact_bob = Contact::new(random::string(8), bob_address.clone(), None, None); + let contact_bob = Contact::new(random::string(8), bob_address.clone(), None, None, false); alice_wallet.contacts_service.upsert_contact(contact_bob).await.unwrap(); bob_wallet @@ -878,7 +878,7 @@ async fn test_contacts_service_liveness() { .add_peer(alice_identity.to_peer()) .await .unwrap(); - let contact_alice = Contact::new(random::string(8), alice_address.clone(), None, None); + let contact_alice = Contact::new(random::string(8), alice_address.clone(), None, None, false); bob_wallet.contacts_service.upsert_contact(contact_alice).await.unwrap(); alice_wallet diff --git a/base_layer/wallet_ffi/src/callback_handler_tests.rs b/base_layer/wallet_ffi/src/callback_handler_tests.rs index e990387372..7a398c5930 100644 --- a/base_layer/wallet_ffi/src/callback_handler_tests.rs +++ b/base_layer/wallet_ffi/src/callback_handler_tests.rs @@ -731,6 +731,7 @@ mod test { faux_unconfirmed_tx.destination_address, None, None, + false, ); let data = ContactsLivenessData::new( contact.address.clone(), diff --git a/base_layer/wallet_ffi/src/lib.rs b/base_layer/wallet_ffi/src/lib.rs index 42405ce8d2..a2c2ee460d 100644 --- a/base_layer/wallet_ffi/src/lib.rs +++ b/base_layer/wallet_ffi/src/lib.rs @@ -2651,6 +2651,7 @@ pub unsafe extern "C" fn seed_words_destroy(seed_words: *mut TariSeedWords) { pub unsafe extern "C" fn contact_create( alias: *const c_char, address: *mut TariWalletAddress, + favourite: bool, error_out: *mut c_int, ) -> *mut TariContact { let mut error = 0; @@ -2679,7 +2680,7 @@ pub unsafe extern "C" fn contact_create( return ptr::null_mut(); } - let contact = Contact::new(alias_string, (*address).clone(), None, None); + let contact = Contact::new(alias_string, (*address).clone(), None, None, favourite); Box::into_raw(Box::new(contact)) } @@ -2716,6 +2717,34 @@ pub unsafe extern "C" fn contact_get_alias(contact: *mut TariContact, error_out: CString::into_raw(a) } +/// Gets the favourite status of the TariContact +/// +/// ## Arguments +/// `contact` - The pointer to a TariContact +/// `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions +/// as an out parameter. +/// +/// ## Returns +/// `bool` - Returns a bool indicating the favourite status of a contact. NOTE this will return false if the pointer is +/// null as well. +/// +/// # Safety +/// The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak +#[no_mangle] +pub unsafe extern "C" fn contact_get_favourite(contact: *mut TariContact, error_out: *mut c_int) -> bool { + let mut error = 0; + let mut favourite = false; + ptr::swap(error_out, &mut error as *mut c_int); + if contact.is_null() { + error = LibWalletError::from(InterfaceError::NullError("contact".to_string())).code; + ptr::swap(error_out, &mut error as *mut c_int); + } else { + favourite = (*contact).favourite; + } + + favourite +} + /// Gets the TariWalletAddress of the TariContact /// /// ## Arguments @@ -9078,7 +9107,9 @@ mod test { let test_str = "Test Contact"; let test_contact_str = CString::new(test_str).unwrap(); let test_contact_alias: *const c_char = CString::into_raw(test_contact_str) as *const c_char; - let test_contact = contact_create(test_contact_alias, test_address, error_ptr); + let test_contact = contact_create(test_contact_alias, test_address, true, error_ptr); + let favourite = contact_get_favourite(test_contact, error_ptr); + assert!(favourite); let alias = contact_get_alias(test_contact, error_ptr); let alias_string = CString::from_raw(alias).to_str().unwrap().to_owned(); assert_eq!(alias_string, test_str); @@ -9104,12 +9135,12 @@ mod test { let test_str = "Test Contact"; let test_contact_str = CString::new(test_str).unwrap(); let test_contact_alias: *const c_char = CString::into_raw(test_contact_str) as *const c_char; - let mut _test_contact = contact_create(ptr::null_mut(), test_contact_address, error_ptr); + let mut _test_contact = contact_create(ptr::null_mut(), test_contact_address, false, error_ptr); assert_eq!( error, LibWalletError::from(InterfaceError::NullError("alias_ptr".to_string())).code ); - _test_contact = contact_create(test_contact_alias, ptr::null_mut(), error_ptr); + _test_contact = contact_create(test_contact_alias, ptr::null_mut(), false, error_ptr); assert_eq!( error, LibWalletError::from(InterfaceError::NullError("public_key_ptr".to_string())).code @@ -9124,6 +9155,11 @@ mod test { error, LibWalletError::from(InterfaceError::NullError("contact_ptr".to_string())).code ); + let _contact_address = contact_get_favourite(ptr::null_mut(), error_ptr); + assert_eq!( + error, + LibWalletError::from(InterfaceError::NullError("contact_ptr".to_string())).code + ); let contact_key_bytes = public_key_get_bytes(ptr::null_mut(), error_ptr); assert_eq!( error, diff --git a/base_layer/wallet_ffi/wallet.h b/base_layer/wallet_ffi/wallet.h index 3a0cf244b6..e72975c37f 100644 --- a/base_layer/wallet_ffi/wallet.h +++ b/base_layer/wallet_ffi/wallet.h @@ -1368,6 +1368,7 @@ void seed_words_destroy(struct TariSeedWords *seed_words); */ TariContact *contact_create(const char *alias, TariWalletAddress *address, + bool favourite, int *error_out); /** @@ -1388,6 +1389,24 @@ TariContact *contact_create(const char *alias, char *contact_get_alias(TariContact *contact, int *error_out); +/** + * Gets the favourite status of the TariContact + * + * ## Arguments + * `contact` - The pointer to a TariContact + * `error_out` - Pointer to an int which will be modified to an error code should one occur, may not be null. Functions + * as an out parameter. + * + * ## Returns + * `bool` - Returns a bool indicating the favourite status of a contact. NOTE this will return false if the pointer is + * null as well. + * + * # Safety + * The ```string_destroy``` method must be called when finished with a string from rust to prevent a memory leak + */ +bool contact_get_favourite(TariContact *contact, + int *error_out); + /** * Gets the TariWalletAddress of the TariContact * diff --git a/integration_tests/tests/utils/ffi/contact.rs b/integration_tests/tests/utils/ffi/contact.rs index 1bad8d5658..2485219b51 100644 --- a/integration_tests/tests/utils/ffi/contact.rs +++ b/integration_tests/tests/utils/ffi/contact.rs @@ -53,6 +53,7 @@ impl Contact { ptr = ffi_import::contact_create( CString::new(alias).unwrap().into_raw(), WalletAddress::from_hex(address).get_ptr(), + false, &mut error, ); if error > 0 { diff --git a/integration_tests/tests/utils/ffi/ffi_import.rs b/integration_tests/tests/utils/ffi/ffi_import.rs index 144069025d..7ab9639fa5 100644 --- a/integration_tests/tests/utils/ffi/ffi_import.rs +++ b/integration_tests/tests/utils/ffi/ffi_import.rs @@ -190,6 +190,7 @@ extern "C" { pub fn contact_create( alias: *const c_char, address: *mut TariWalletAddress, + favourite: bool, error_out: *mut c_int, ) -> *mut TariContact; pub fn contact_get_alias(contact: *mut TariContact, error_out: *mut c_int) -> *mut c_char;