use std::ffi::c_void;
use std::path::Path;
use std::time::Duration;
use objc2::runtime::{AnyClass, AnyObject};
use tokio::sync::oneshot;
use tokio::time::sleep;
use crate::configuration::MacHardwareModel;
use crate::error::{VZError, VZResult};
use crate::ffi::{
_Block_release, ObjectResult, StateResult, create_object_completion_block,
create_state_completion_block, get_class, nsstring_to_string, nsurl_file_path, release,
};
use crate::vm::VirtualMachine;
use crate::{msg_send, msg_send_u64, msg_send_void};
pub struct MacOSConfigurationRequirements {
pub hardware_model: MacHardwareModel,
pub minimum_cpu_count: u64,
pub minimum_memory_size: u64,
}
pub struct MacOSRestoreImage {
inner: *mut AnyObject,
}
unsafe impl Send for MacOSRestoreImage {}
impl MacOSRestoreImage {
pub async fn latest_supported() -> VZResult<Self> {
let cls = get_class("VZMacOSRestoreImage").ok_or_else(|| VZError::Internal {
code: -1,
message: "VZMacOSRestoreImage class not found".into(),
})?;
let (tx, rx) = oneshot::channel::<ObjectResult>();
let block = create_object_completion_block(tx);
unsafe {
let sel = objc2::sel!(fetchLatestSupportedWithCompletionHandler:);
let func: unsafe extern "C" fn(*const AnyClass, objc2::runtime::Sel, *const c_void) =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
func(cls, sel, block);
}
let received = rx.await;
unsafe { _Block_release(block) };
Self::from_received(received)
}
pub async fn load_from_url(path: impl AsRef<Path>) -> VZResult<Self> {
let path_str = path.as_ref().to_string_lossy();
let cls = get_class("VZMacOSRestoreImage").ok_or_else(|| VZError::Internal {
code: -1,
message: "VZMacOSRestoreImage class not found".into(),
})?;
let (tx, rx) = oneshot::channel::<ObjectResult>();
let block = create_object_completion_block(tx);
unsafe {
let url = nsurl_file_path(&path_str);
let sel = objc2::sel!(loadFileURL:completionHandler:);
let func: unsafe extern "C" fn(
*const AnyClass,
objc2::runtime::Sel,
*const AnyObject,
*const c_void,
) = std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
func(cls, sel, url as *const AnyObject, block);
release(url);
}
let received = rx.await;
unsafe { _Block_release(block) };
Self::from_received(received)
}
fn from_received(received: Result<ObjectResult, oneshot::error::RecvError>) -> VZResult<Self> {
let bits = received
.map_err(|_| VZError::Internal {
code: -1,
message: "restore image completion handler dropped".into(),
})?
.map_err(|message| VZError::Internal { code: -1, message })?;
Ok(Self {
inner: bits as *mut AnyObject,
})
}
pub fn requirements(&self) -> VZResult<MacOSConfigurationRequirements> {
unsafe {
let reqs = msg_send!(self.inner, mostFeaturefulSupportedConfiguration);
if reqs.is_null() {
return Err(VZError::InvalidConfiguration(
"restore image has no supported configuration".into(),
));
}
let hw_ptr = msg_send!(reqs, hardwareModel);
let hardware_model = MacHardwareModel::from_retained_ptr(hw_ptr).ok_or_else(|| {
VZError::InvalidConfiguration("restore image has no hardware model".into())
})?;
let minimum_cpu_count = msg_send_u64!(reqs, minimumSupportedCPUCount);
let minimum_memory_size = msg_send_u64!(reqs, minimumSupportedMemorySize);
Ok(MacOSConfigurationRequirements {
hardware_model,
minimum_cpu_count,
minimum_memory_size,
})
}
}
#[must_use]
pub fn url(&self) -> Option<String> {
unsafe {
let url = msg_send!(self.inner, URL);
if url.is_null() {
return None;
}
let s = msg_send!(url, absoluteString);
let out = nsstring_to_string(s);
if out.is_empty() { None } else { Some(out) }
}
}
}
impl Drop for MacOSRestoreImage {
fn drop(&mut self) {
if !self.inner.is_null() {
release(self.inner);
}
}
}
pub struct MacOSInstaller {
inner: *mut AnyObject,
progress: *mut AnyObject,
}
unsafe impl Send for MacOSInstaller {}
impl MacOSInstaller {
pub fn new(
virtual_machine: &VirtualMachine,
restore_image_path: impl AsRef<Path>,
) -> VZResult<Self> {
let path_str = restore_image_path.as_ref().to_string_lossy().into_owned();
let vm_ptr = virtual_machine.as_ptr();
let (inner, progress) = virtual_machine.dispatch_sync(|| {
unsafe {
let cls = get_class("VZMacOSInstaller").ok_or_else(|| VZError::Internal {
code: -1,
message: "VZMacOSInstaller class not found".into(),
})?;
let url = nsurl_file_path(&path_str);
let alloc = msg_send!(cls, alloc);
let obj = msg_send!(
alloc,
initWithVirtualMachine: vm_ptr,
restoreImageURL: url
);
release(url);
if obj.is_null() {
return Err(VZError::InvalidConfiguration(
"failed to create VZMacOSInstaller".into(),
));
}
let progress = msg_send!(obj, progress);
Ok((obj, progress))
}
})?;
Ok(Self { inner, progress })
}
#[must_use]
pub fn fraction_completed(&self) -> f64 {
if self.progress.is_null() {
return 0.0;
}
unsafe {
let sel = objc2::sel!(fractionCompleted);
let func: unsafe extern "C" fn(*const AnyObject, objc2::runtime::Sel) -> f64 =
std::mem::transmute(crate::ffi::runtime::objc_msgSend as *const c_void);
func(self.progress as *const AnyObject, sel)
}
}
pub async fn install(
&self,
virtual_machine: &VirtualMachine,
mut on_progress: impl FnMut(f64),
) -> VZResult<()> {
let (tx, mut rx) = oneshot::channel::<StateResult>();
let block = create_state_completion_block(tx);
let installer = self.inner;
virtual_machine.dispatch_sync(|| unsafe {
msg_send_void!(installer, installWithCompletionHandler: block);
});
let result = loop {
tokio::select! {
biased;
received = &mut rx => {
break received
.unwrap_or_else(|_| Err("install completion handler dropped".to_string()));
}
() = sleep(Duration::from_secs(2)) => {
on_progress(self.fraction_completed());
}
}
};
unsafe { _Block_release(block) };
result.map_err(|message| VZError::Internal { code: -1, message })
}
}
impl Drop for MacOSInstaller {
fn drop(&mut self) {
if !self.inner.is_null() {
release(self.inner);
}
}
}