From e4ddfb975a2be2b059e0ab641126c43b39109173 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Mon, 30 Mar 2020 01:35:11 -0700 Subject: [PATCH] Further work on wrapping NSUserDefaults --- examples/defaults.rs | 30 +++++++++++ src/defaults.rs | 123 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- src/macos/app/mod.rs | 11 +++- src/user_defaults.rs | 52 ------------------ 5 files changed, 164 insertions(+), 54 deletions(-) create mode 100644 examples/defaults.rs create mode 100644 src/defaults.rs delete mode 100644 src/user_defaults.rs diff --git a/examples/defaults.rs b/examples/defaults.rs new file mode 100644 index 0000000..2a49cf7 --- /dev/null +++ b/examples/defaults.rs @@ -0,0 +1,30 @@ +//! This tests the `defaults` module to ensure things behave as they should. + +use cacao::macos::app::{App, AppDelegate}; +use cacao::defaults::UserDefaults; + +#[derive(Default)] +struct DefaultsTest; + +impl AppDelegate for DefaultsTest { + fn did_finish_launching(&self) { + let mut defaults = UserDefaults::standard(); + + match defaults.get_string("LOL") { + Some(s) => { + println!("Retrieved {}", s); + }, + + None => { + defaults.set_string("LOL", "laugh"); + println!("Run this again to get a laugh"); + } + } + + App::terminate(); + } +} + +fn main() { + App::new("com.cacao.defaults-test", DefaultsTest::default()).run(); +} diff --git a/src/defaults.rs b/src/defaults.rs new file mode 100644 index 0000000..f529c4e --- /dev/null +++ b/src/defaults.rs @@ -0,0 +1,123 @@ +//! Wraps `NSUserDefaults`, providing an interface to store and query small amounts of data. +//! +//! It may seem a bit verbose at points, but it aims to implement everything on the Objective-C +//! side as closely as possible. + +use std::unreachable; + +use objc::{class, msg_send, sel, sel_impl}; +use objc::runtime::Object; +use objc_id::Id; + +use crate::foundation::{id, nil, YES, NO, BOOL, NSString}; + +/// Wraps and provides methods for interacting with `NSUserDefaults`, which can be used for storing +/// pieces of information (preferences, or _defaults_) to persist across application restores. +/// +/// This should not be used for sensitive data - use the Keychain for that. +#[derive(Debug)] +pub struct UserDefaults(pub Id); + +impl Default for UserDefaults { + /// Equivalent to calling `UserDefaults::standard()`. + fn default() -> Self { + UserDefaults::standard() + } +} + +impl UserDefaults { + /// Returns the `standardUserDefaults`, which is... exactly what it sounds like. + pub fn standard() -> Self { + UserDefaults(unsafe { + Id::from_ptr(msg_send![class!(NSUserDefaults), standardUserDefaults]) + }) + } + + /// Returns a new user defaults to work with. You probably don't want this, and either want + /// `suite()` or `standard()`. + pub fn new() -> Self { + UserDefaults(unsafe { + let alloc: id = msg_send![class!(NSUserDefaults), alloc]; + Id::from_ptr(msg_send![alloc, init]) + }) + } + + /// Returns a user defaults instance for the given suite name. You typically use this to share + /// preferences across apps and extensions. + pub fn suite(named: &str) -> Self { + let name = NSString::new(named); + + UserDefaults(unsafe { + let alloc: id = msg_send![class!(NSUserDefaults), alloc]; + Id::from_ptr(msg_send![alloc, initWithSuiteName:name.into_inner()]) + }) + } + + /// Remove the default associated with the key. If the key doesn't exist, this is a noop. + pub fn remove(&mut self, key: &str) { + let key = NSString::new(key); + + unsafe { + let _: () = msg_send![&*self.0, removeObjectForKey:key.into_inner()]; + } + } + + /// Returns a bool for the given key. If the key doesn't exist, it returns `false`. + /// + /// Note that behind the scenes, this will coerce certain "truthy" and "falsy" values - this is + /// done on the system side, and is not something that can be changed. + /// + /// e.g: + /// `"true"`, `"YES"`, `"1"`, `1`, `1.0` will become `true` + /// `"false"`, `"NO"`, `"0"`, `0`, `0.0` will become `false` + pub fn get_bool(&self, key: &str) -> bool { + let key = NSString::new(key); + + let result: BOOL = unsafe { + msg_send![&*self.0, boolForKey:key.into_inner()] + }; + + match result { + YES => true, + NO => false, + _ => unreachable!() + } + } + + /// Sets the bool for the given key to the specified value. + pub fn set_bool(&mut self, key: &str, value: bool) { + let key = NSString::new(key); + + unsafe { + let _: () = msg_send![&*self.0, setBool:match value { + true => YES, + false => NO + } forKey:key]; + } + } + + /// Returns the given String if it exists, mapping Objective-C's `nil` to `None`. + pub fn get_string(&self, key: &str) -> Option { + let key = NSString::new(key); + + let result: id = unsafe { + msg_send![&*self.0, stringForKey:key.into_inner()] + }; + + if result == nil { + None + } else { + Some(NSString::wrap(result).to_str().to_string()) + } + } + + /// Sets the string for the given key to the specified value. + pub fn set_string(&mut self, key: &str, value: &str) { + let key = NSString::new(key); + let value = NSString::new(value); + + unsafe { + let _: () = msg_send![&*self.0, setObject:value.into_inner() forKey:key.into_inner()]; + } + } +} diff --git a/src/lib.rs b/src/lib.rs index f797200..3bc93c8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,6 +85,7 @@ pub mod color; pub mod dragdrop; pub mod error; pub mod events; +pub mod defaults; pub mod filesystem; pub mod foundation; pub mod geometry; @@ -98,7 +99,6 @@ pub mod user_notifications; pub mod user_activity; pub(crate) mod utils; -pub mod user_defaults; pub mod view; diff --git a/src/macos/app/mod.rs b/src/macos/app/mod.rs index 3c53d8a..f48a636 100644 --- a/src/macos/app/mod.rs +++ b/src/macos/app/mod.rs @@ -38,7 +38,7 @@ use objc_id::Id; use objc::runtime::Object; use objc::{class, msg_send, sel, sel_impl}; -use crate::foundation::{id, YES, NO, NSUInteger, AutoReleasePool}; +use crate::foundation::{id, nil, YES, NO, NSUInteger, AutoReleasePool}; use crate::macos::menu::Menu; use crate::notification_center::Dispatcher; @@ -236,4 +236,13 @@ impl App { let _: () = msg_send![app, setMainMenu:main_menu]; }); } + + /// Terminates the application, firing the requisite cleanup delegate methods in the process. + /// + /// This is typically called when the user chooses to quit via the App menu. + pub fn terminate() { + shared_application(|app| unsafe { + let _: () = msg_send![app, terminate:nil]; + }); + } } diff --git a/src/user_defaults.rs b/src/user_defaults.rs deleted file mode 100644 index 730710d..0000000 --- a/src/user_defaults.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Wraps `NSUserDefaults`, providing an interface to store and query small amounts of data. -//! -//! It mirrors much of the API of the standard Rust `HashMap`, but uses `NSUserDefaults` as a -//! backing store. - -use objc::{class, msg_send, sel, sel_impl}; -use objc::runtime::Object; -use objc_id::Id; - -use crate::foundation::{id, NSString}; - -#[derive(Debug)] -pub struct UserDefaults(pub Id); - -impl Default for UserDefaults { - fn default() -> Self { - UserDefaults::standard() - } -} - -impl UserDefaults { - pub fn standard() -> Self { - UserDefaults(unsafe { - Id::from_ptr(msg_send![class!(NSUserDefaults), standardUserDefaults]) - }) - } - - pub fn new() -> Self { - UserDefaults(unsafe { - let alloc: id = msg_send![class!(NSUserDefaults), alloc]; - Id::from_ptr(msg_send![alloc, init]) - }) - } - - pub fn suite(named: &str) -> Self { - let name = NSString::new(named); - - UserDefaults(unsafe { - let alloc: id = msg_send![class!(NSUserDefaults), alloc]; - Id::from_ptr(msg_send![alloc, initWithSuiteName:name.into_inner()]) - }) - } - - /// Remove the default associated with the key. If the key doesn't exist, this is a noop. - pub fn remove(&self, key: &str) { - let key = NSString::new(key); - - unsafe { - let _: () = msg_send![&*self.0, removeObjectForKey:key.into_inner()]; - } - } -}