// Buttplug Rust Source Code File - See https://buttplug.io for more info. // // Copyright 2016-2024 Nonpolynomial Labs LLC. All rights reserved. // // Licensed under the BSD 3-Clause license. See LICENSE file in the project root // for full license information. use crate::device::hardware::HardwareConnector; use async_trait::async_trait; use buttplug_core::{ util::{async_manager, sleep}, {ButtplugResultFuture, errors::ButtplugDeviceError}, }; use futures::future::{self, FutureExt}; use serde::{Deserialize, Serialize}; use std::{sync::Arc, time::Duration}; use thiserror::Error; use tokio::sync::mpsc::Sender; use tokio_util::sync::CancellationToken; #[derive(Debug)] pub enum HardwareCommunicationManagerEvent { // This event only means that a device has been found. The work still needs // to be done to make sure we can use it. DeviceFound { name: String, address: String, creator: Box, }, ScanningFinished, } pub trait HardwareCommunicationManagerBuilder: Send { fn finish( &mut self, sender: Sender, ) -> Box; } pub trait HardwareCommunicationManager: Send + Sync { fn name(&self) -> &'static str; fn start_scanning(&mut self) -> ButtplugResultFuture; fn stop_scanning(&mut self) -> ButtplugResultFuture; fn scanning_status(&self) -> bool { false } fn can_scan(&self) -> bool; // Events happen via channel senders passed to the comm manager. } #[derive(Error, Debug, Clone, Display, Serialize, Deserialize, PartialEq, Eq)] pub enum HardwareSpecificError { // HardwareSpecificError: {} Error: {} HardwareSpecificError(String, String), } #[async_trait] pub trait TimedRetryCommunicationManagerImpl: Sync + Send { fn name(&self) -> &'static str; fn can_scan(&self) -> bool; fn rescan_wait_duration(&self) -> Duration { Duration::from_secs(1) } async fn scan(&self) -> Result<(), ButtplugDeviceError>; } pub struct TimedRetryCommunicationManager { comm_manager: Arc, cancellation_token: Option, } impl TimedRetryCommunicationManager { pub fn new(comm_manager: T) -> Self { Self { comm_manager: Arc::new(comm_manager), cancellation_token: None, } } } impl HardwareCommunicationManager for TimedRetryCommunicationManager { fn name(&self) -> &'static str { self.comm_manager.name() } fn start_scanning(&mut self) -> ButtplugResultFuture { if self.cancellation_token.is_some() { return future::ready(Ok(())).boxed(); } let comm_manager = self.comm_manager.clone(); let token = CancellationToken::new(); let child_token = token.child_token(); self.cancellation_token = Some(token); let duration = self.comm_manager.rescan_wait_duration(); async move { async_manager::spawn(async move { loop { if let Err(err) = comm_manager.scan().await { error!("Timed Device Communication Manager Failure: {}", err); break; } tokio::select! { _ = sleep(duration) => continue, _ = child_token.cancelled() => break, } } }); Ok(()) } .boxed() } fn stop_scanning(&mut self) -> ButtplugResultFuture { if self.cancellation_token.is_none() { return future::ready(Ok(())).boxed(); } self.cancellation_token.take().unwrap().cancel(); future::ready(Ok(())).boxed() } fn scanning_status(&self) -> bool { self.cancellation_token.is_some() } fn can_scan(&self) -> bool { self.comm_manager.can_scan() } } impl Drop for TimedRetryCommunicationManager { fn drop(&mut self) { // We set the cancellation token without doing anything with the future, so we're fine to ignore // the return. let _ = self.stop_scanning(); } }