use serde::Serialize;
use tauri::{
plugin::{PluginApi, PluginHandle},
AppHandle, Runtime,
};
use crate::error::ServiceError;
use crate::manager::MobileKeepalive;
use crate::models::{
AutoStartConfig, IOSSchedulingStatus, PendingTaskInfo, StartConfig, StartKeepaliveArgs,
};
pub struct MobileLifecycle<R: Runtime> {
pub handle: PluginHandle<R>,
}
impl<R: Runtime> MobileLifecycle<R> {
#[allow(clippy::too_many_arguments)]
pub fn start_keepalive(
&self,
label: &str,
foreground_service_type: &str,
ios_safety_timeout_secs: Option<f64>,
ios_processing_safety_timeout_secs: Option<f64>,
ios_earliest_refresh_begin_minutes: Option<f64>,
ios_earliest_processing_begin_minutes: Option<f64>,
ios_requires_external_power: Option<bool>,
ios_requires_network_connectivity: Option<bool>,
) -> Result<Option<IOSSchedulingStatus>, ServiceError> {
let result: serde_json::Value = self
.handle
.run_mobile_plugin(
"startKeepalive",
StartKeepaliveArgs {
label,
foreground_service_type,
ios_safety_timeout_secs,
ios_processing_safety_timeout_secs,
ios_earliest_refresh_begin_minutes,
ios_earliest_processing_begin_minutes,
ios_requires_external_power,
ios_requires_network_connectivity,
},
)
.map_err(|e| ServiceError::Platform(e.to_string()))?;
if let Ok(status) = serde_json::from_value::<IOSSchedulingStatus>(result) {
if status.refresh_error.is_some() {
log::warn!(
"iOS BGAppRefreshTask scheduling error: {:?}",
status.refresh_error
);
}
if status.processing_error.is_some() {
log::warn!(
"iOS BGProcessingTask scheduling error: {:?}",
status.processing_error
);
}
Ok(Some(status))
} else {
Ok(None)
}
}
pub fn stop_keepalive(&self) -> Result<(), ServiceError> {
self.handle
.run_mobile_plugin::<()>("stopKeepalive", ())
.map_err(|e| ServiceError::Platform(e.to_string()))?;
Ok(())
}
pub fn complete_bg_task(&self, success: bool) -> Result<(), ServiceError> {
self.handle
.run_mobile_plugin::<()>("completeBgTask", CompleteBgTaskArgs { success })
.map_err(|e| ServiceError::Platform(e.to_string()))?;
Ok(())
}
pub fn wait_for_cancel(&self) -> Result<(), ServiceError> {
self.handle
.run_mobile_plugin::<()>("waitForCancel", ())
.map_err(|e| ServiceError::Platform(e.to_string()))?;
Ok(())
}
pub fn cancel_cancel_listener(&self) -> Result<(), ServiceError> {
self.handle
.run_mobile_plugin::<()>("cancelCancelListener", ())
.map_err(|e| ServiceError::Platform(e.to_string()))?;
Ok(())
}
pub fn get_auto_start_config(&self) -> Result<Option<StartConfig>, ServiceError> {
let config: AutoStartConfig = self
.handle
.run_mobile_plugin("getAutoStartConfig", ())
.map_err(|e| ServiceError::Platform(e.to_string()))?;
Ok(config.into_start_config())
}
pub fn clear_auto_start_config(&self) -> Result<(), ServiceError> {
self.handle
.run_mobile_plugin::<()>("clearAutoStartConfig", ())
.map_err(|e| ServiceError::Platform(e.to_string()))?;
Ok(())
}
pub fn move_task_to_background(&self) -> Result<(), ServiceError> {
self.handle
.run_mobile_plugin::<()>("moveTaskToBackground", ())
.map_err(|e| ServiceError::Platform(e.to_string()))?;
Ok(())
}
pub fn get_scheduling_status(&self) -> Result<Option<IOSSchedulingStatus>, ServiceError> {
let result: serde_json::Value = self
.handle
.run_mobile_plugin("getSchedulingStatus", ())
.map_err(|e| ServiceError::Platform(e.to_string()))?;
serde_json::from_value::<IOSSchedulingStatus>(result)
.map(Some)
.map_err(|e| ServiceError::Platform(e.to_string()))
}
pub fn get_scheduling_status_raw(&self) -> Result<serde_json::Value, ServiceError> {
self.handle
.run_mobile_plugin("getSchedulingStatus", ())
.map_err(|e| ServiceError::Platform(e.to_string()))
}
pub fn get_pending_bg_task(&self) -> Result<Option<PendingTaskInfo>, ServiceError> {
let result: serde_json::Value = self
.handle
.run_mobile_plugin("getPendingBgTask", ())
.map_err(|e| ServiceError::Platform(e.to_string()))?;
if result["taskKind"].is_null() {
Ok(None)
} else {
serde_json::from_value::<PendingTaskInfo>(result)
.map(Some)
.map_err(|e| ServiceError::Platform(e.to_string()))
}
}
pub fn clear_pending_bg_task(&self) -> Result<(), ServiceError> {
self.handle
.run_mobile_plugin::<()>("clearPendingBgTask", ())
.map_err(|e| ServiceError::Platform(e.to_string()))?;
Ok(())
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct CompleteBgTaskArgs {
success: bool,
}
impl<R: Runtime> MobileKeepalive for MobileLifecycle<R> {
#[allow(clippy::too_many_arguments)]
fn start_keepalive(
&self,
label: &str,
foreground_service_type: &str,
ios_safety_timeout_secs: Option<f64>,
ios_processing_safety_timeout_secs: Option<f64>,
ios_earliest_refresh_begin_minutes: Option<f64>,
ios_earliest_processing_begin_minutes: Option<f64>,
ios_requires_external_power: Option<bool>,
ios_requires_network_connectivity: Option<bool>,
) -> Result<(), ServiceError> {
self.start_keepalive(
label,
foreground_service_type,
ios_safety_timeout_secs,
ios_processing_safety_timeout_secs,
ios_earliest_refresh_begin_minutes,
ios_earliest_processing_begin_minutes,
ios_requires_external_power,
ios_requires_network_connectivity,
)
.map(|_| ())
}
fn stop_keepalive(&self) -> Result<(), ServiceError> {
self.stop_keepalive()
}
}
pub fn init<R: Runtime, C: serde::de::DeserializeOwned>(
_app: &AppHandle<R>,
api: PluginApi<R, C>,
) -> Result<MobileLifecycle<R>, ServiceError> {
#[cfg(target_os = "android")]
let handle = api
.register_android_plugin("app.tauri.backgroundservice", "BackgroundServicePlugin")
.map_err(|e| ServiceError::Platform(e.to_string()))?;
#[cfg(target_os = "ios")]
let handle = api
.register_ios_plugin(crate::init_plugin_background_service)
.map_err(|e| ServiceError::Platform(e.to_string()))?;
Ok(MobileLifecycle { handle })
}