use core::ffi::{c_char, c_void};
use core::ptr;
use crate::database::{CKDatabase, CKDatabaseScope};
use crate::error::CloudKitError;
use crate::ffi;
use crate::private::{
box_closure, error_from_status, json_cstring, opt_cstring_ptr, optional_cstring_from_str,
parse_borrowed_error_ptr, parse_json_ptr, parse_json_str,
};
use crate::record::CKRecordID;
use crate::share::CKShareParticipant;
use crate::user_identity::{CKUserIdentity, CKUserIdentityLookupInfo};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum AccountStatus {
CouldNotDetermine,
Available,
Restricted,
NoAccount,
TemporarilyUnavailable,
Unknown(i32),
}
impl AccountStatus {
pub(crate) const fn from_raw(raw: i32) -> Self {
match raw {
0 => Self::CouldNotDetermine,
1 => Self::Available,
2 => Self::Restricted,
3 => Self::NoAccount,
4 => Self::TemporarilyUnavailable,
other => Self::Unknown(other),
}
}
}
impl core::fmt::Display for AccountStatus {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let label = match self {
Self::CouldNotDetermine => "couldNotDetermine",
Self::Available => "available",
Self::Restricted => "restricted",
Self::NoAccount => "noAccount",
Self::TemporarilyUnavailable => "temporarilyUnavailable",
Self::Unknown(raw) => return write!(f, "unknown({raw})"),
};
f.write_str(label)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CKContainer {
identifier: Option<String>,
}
impl Default for CKContainer {
fn default() -> Self {
Self::new()
}
}
impl CKContainer {
pub fn new() -> Self {
Self { identifier: None }
}
pub fn default() -> Self {
Self::new()
}
pub fn container(identifier: impl Into<String>) -> Self {
Self {
identifier: Some(identifier.into()),
}
}
pub fn container_identifier(&self) -> Option<&str> {
self.identifier.as_deref()
}
pub fn private_cloud_database(&self) -> CKDatabase {
CKDatabase::new(self.clone(), CKDatabaseScope::Private)
}
pub fn public_cloud_database(&self) -> CKDatabase {
CKDatabase::new(self.clone(), CKDatabaseScope::Public)
}
pub fn shared_cloud_database(&self) -> CKDatabase {
CKDatabase::new(self.clone(), CKDatabaseScope::Shared)
}
pub fn database_with_scope(&self, scope: CKDatabaseScope) -> CKDatabase {
CKDatabase::new(self.clone(), scope)
}
pub fn account_status(&self) -> Result<AccountStatus, CloudKitError> {
let identifier =
optional_cstring_from_str(self.identifier.as_deref(), "container identifier")?;
let mut out_status = 0_i32;
let mut out_error: *mut c_char = ptr::null_mut();
let status = unsafe {
ffi::ck_container_account_status_sync(
opt_cstring_ptr(&identifier),
&mut out_status,
&mut out_error,
)
};
if status != ffi::status::OK {
return Err(unsafe { error_from_status(status, out_error) });
}
Ok(AccountStatus::from_raw(out_status))
}
pub fn account_status_with_completion_handler<F>(
&self,
callback: F,
) -> Result<(), CloudKitError>
where
F: FnOnce(Result<AccountStatus, CloudKitError>) + Send + 'static,
{
let identifier =
optional_cstring_from_str(self.identifier.as_deref(), "container identifier")?;
let callback_ptr = box_closure(Box::new(callback) as AccountStatusCallback);
unsafe {
ffi::ck_container_account_status_async(
opt_cstring_ptr(&identifier),
account_status_trampoline,
callback_ptr,
);
}
Ok(())
}
pub fn fetch_user_record_id(&self) -> Result<CKRecordID, CloudKitError> {
let identifier =
optional_cstring_from_str(self.identifier.as_deref(), "container identifier")?;
let mut out_json: *mut c_char = ptr::null_mut();
let mut out_error: *mut c_char = ptr::null_mut();
let status = unsafe {
ffi::ck_container_fetch_user_record_id_sync(
opt_cstring_ptr(&identifier),
&mut out_json,
&mut out_error,
)
};
if status != ffi::status::OK {
return Err(unsafe { error_from_status(status, out_error) });
}
let record_id = unsafe {
parse_json_ptr::<crate::private::CKRecordIDPayload>(out_json, "user record ID")?
};
Ok(CKRecordID::from_payload(record_id))
}
pub fn fetch_user_record_id_with_completion_handler<F>(
&self,
callback: F,
) -> Result<(), CloudKitError>
where
F: FnOnce(Result<CKRecordID, CloudKitError>) + Send + 'static,
{
let identifier =
optional_cstring_from_str(self.identifier.as_deref(), "container identifier")?;
let callback_ptr = box_closure(Box::new(callback) as RecordIdCallback);
unsafe {
ffi::ck_container_fetch_user_record_id_async(
opt_cstring_ptr(&identifier),
record_id_trampoline,
callback_ptr,
);
}
Ok(())
}
pub fn discover_user_identity(
&self,
lookup_info: &CKUserIdentityLookupInfo,
) -> Result<CKUserIdentity, CloudKitError> {
let identifier =
optional_cstring_from_str(self.identifier.as_deref(), "container identifier")?;
let lookup_json = json_cstring(&lookup_info.to_payload(), "user identity lookup info")?;
let mut out_json: *mut c_char = ptr::null_mut();
let mut out_error: *mut c_char = ptr::null_mut();
let status = unsafe {
ffi::ck_container_discover_user_identity_sync(
opt_cstring_ptr(&identifier),
lookup_json.as_ptr(),
&mut out_json,
&mut out_error,
)
};
if status != ffi::status::OK {
return Err(unsafe { error_from_status(status, out_error) });
}
let payload = unsafe {
parse_json_ptr::<crate::private::CKUserIdentityPayload>(out_json, "user identity")?
};
Ok(CKUserIdentity::from_payload(payload))
}
pub fn discover_user_identity_with_email_address(
&self,
email_address: impl Into<String>,
) -> Result<CKUserIdentity, CloudKitError> {
self.discover_user_identity(&CKUserIdentityLookupInfo::with_email_address(email_address))
}
pub fn discover_user_identity_with_phone_number(
&self,
phone_number: impl Into<String>,
) -> Result<CKUserIdentity, CloudKitError> {
self.discover_user_identity(&CKUserIdentityLookupInfo::with_phone_number(phone_number))
}
pub fn discover_user_identity_with_user_record_id(
&self,
user_record_id: CKRecordID,
) -> Result<CKUserIdentity, CloudKitError> {
self.discover_user_identity(&CKUserIdentityLookupInfo::with_user_record_id(user_record_id))
}
pub fn fetch_share_participant(
&self,
lookup_info: &CKUserIdentityLookupInfo,
) -> Result<CKShareParticipant, CloudKitError> {
let identifier =
optional_cstring_from_str(self.identifier.as_deref(), "container identifier")?;
let lookup_json = json_cstring(&lookup_info.to_payload(), "share participant lookup info")?;
let mut out_json: *mut c_char = ptr::null_mut();
let mut out_error: *mut c_char = ptr::null_mut();
let status = unsafe {
ffi::ck_container_fetch_share_participant_sync(
opt_cstring_ptr(&identifier),
lookup_json.as_ptr(),
&mut out_json,
&mut out_error,
)
};
if status != ffi::status::OK {
return Err(unsafe { error_from_status(status, out_error) });
}
let payload = unsafe {
parse_json_ptr::<crate::private::CKShareParticipantPayload>(
out_json,
"share participant",
)?
};
Ok(CKShareParticipant::from_payload(payload))
}
pub fn fetch_share_participant_with_email_address(
&self,
email_address: impl Into<String>,
) -> Result<CKShareParticipant, CloudKitError> {
self.fetch_share_participant(&CKUserIdentityLookupInfo::with_email_address(email_address))
}
pub fn fetch_share_participant_with_phone_number(
&self,
phone_number: impl Into<String>,
) -> Result<CKShareParticipant, CloudKitError> {
self.fetch_share_participant(&CKUserIdentityLookupInfo::with_phone_number(phone_number))
}
pub fn fetch_share_participant_with_user_record_id(
&self,
user_record_id: CKRecordID,
) -> Result<CKShareParticipant, CloudKitError> {
self.fetch_share_participant(&CKUserIdentityLookupInfo::with_user_record_id(user_record_id))
}
}
type AccountStatusCallback = Box<dyn FnOnce(Result<AccountStatus, CloudKitError>) + Send + 'static>;
type RecordIdCallback = Box<dyn FnOnce(Result<CKRecordID, CloudKitError>) + Send + 'static>;
unsafe extern "C" fn account_status_trampoline(
refcon: *mut c_void,
status_raw: i32,
error_json: *const c_char,
) {
let callback: Box<AccountStatusCallback> = Box::from_raw(refcon.cast());
let result = if error_json.is_null() {
Ok(AccountStatus::from_raw(status_raw))
} else {
Err(parse_borrowed_error_ptr(error_json))
};
callback(result);
}
unsafe extern "C" fn record_id_trampoline(
refcon: *mut c_void,
json: *const c_char,
error_json: *const c_char,
) {
let callback: Box<RecordIdCallback> = Box::from_raw(refcon.cast());
let result = if error_json.is_null() {
let payload = parse_json_str::<crate::private::CKRecordIDPayload>(
&std::ffi::CStr::from_ptr(json).to_string_lossy(),
"user record ID",
);
payload.map(CKRecordID::from_payload)
} else {
Err(parse_borrowed_error_ptr(error_json))
};
callback(result);
}