Buttplug sex toy control library
1// Buttplug Rust Source Code File - See https://buttplug.io for more info.
2//
3// Copyright 2016-2024 Nonpolynomial Labs LLC. All rights reserved.
4//
5// Licensed under the BSD 3-Clause license. See LICENSE file in the project root
6// for full license information.
7
8use crate::device::hardware::HardwareConnector;
9use async_trait::async_trait;
10use buttplug_core::{
11 util::{async_manager, sleep},
12 {ButtplugResultFuture, errors::ButtplugDeviceError},
13};
14use futures::future::{self, FutureExt};
15use serde::{Deserialize, Serialize};
16use std::{sync::Arc, time::Duration};
17use thiserror::Error;
18use tokio::sync::mpsc::Sender;
19use tokio_util::sync::CancellationToken;
20
21#[derive(Debug)]
22pub enum HardwareCommunicationManagerEvent {
23 // This event only means that a device has been found. The work still needs
24 // to be done to make sure we can use it.
25 DeviceFound {
26 name: String,
27 address: String,
28 creator: Box<dyn HardwareConnector>,
29 },
30 ScanningFinished,
31}
32
33pub trait HardwareCommunicationManagerBuilder: Send {
34 fn finish(
35 &mut self,
36 sender: Sender<HardwareCommunicationManagerEvent>,
37 ) -> Box<dyn HardwareCommunicationManager>;
38}
39
40pub trait HardwareCommunicationManager: Send + Sync {
41 fn name(&self) -> &'static str;
42 fn start_scanning(&mut self) -> ButtplugResultFuture;
43 fn stop_scanning(&mut self) -> ButtplugResultFuture;
44 fn scanning_status(&self) -> bool {
45 false
46 }
47 fn can_scan(&self) -> bool;
48 // Events happen via channel senders passed to the comm manager.
49}
50
51#[derive(Error, Debug, Clone, Display, Serialize, Deserialize, PartialEq, Eq)]
52pub enum HardwareSpecificError {
53 // HardwareSpecificError: {} Error: {}
54 HardwareSpecificError(String, String),
55}
56
57#[async_trait]
58pub trait TimedRetryCommunicationManagerImpl: Sync + Send {
59 fn name(&self) -> &'static str;
60 fn can_scan(&self) -> bool;
61 fn rescan_wait_duration(&self) -> Duration {
62 Duration::from_secs(1)
63 }
64 async fn scan(&self) -> Result<(), ButtplugDeviceError>;
65}
66
67pub struct TimedRetryCommunicationManager<T: TimedRetryCommunicationManagerImpl + 'static> {
68 comm_manager: Arc<T>,
69 cancellation_token: Option<CancellationToken>,
70}
71
72impl<T: TimedRetryCommunicationManagerImpl> TimedRetryCommunicationManager<T> {
73 pub fn new(comm_manager: T) -> Self {
74 Self {
75 comm_manager: Arc::new(comm_manager),
76 cancellation_token: None,
77 }
78 }
79}
80
81impl<T: TimedRetryCommunicationManagerImpl> HardwareCommunicationManager
82 for TimedRetryCommunicationManager<T>
83{
84 fn name(&self) -> &'static str {
85 self.comm_manager.name()
86 }
87
88 fn start_scanning(&mut self) -> ButtplugResultFuture {
89 if self.cancellation_token.is_some() {
90 return future::ready(Ok(())).boxed();
91 }
92 let comm_manager = self.comm_manager.clone();
93 let token = CancellationToken::new();
94 let child_token = token.child_token();
95 self.cancellation_token = Some(token);
96 let duration = self.comm_manager.rescan_wait_duration();
97 async move {
98 async_manager::spawn(async move {
99 loop {
100 if let Err(err) = comm_manager.scan().await {
101 error!("Timed Device Communication Manager Failure: {}", err);
102 break;
103 }
104 tokio::select! {
105 _ = sleep(duration) => continue,
106 _ = child_token.cancelled() => break,
107 }
108 }
109 });
110 Ok(())
111 }
112 .boxed()
113 }
114
115 fn stop_scanning(&mut self) -> ButtplugResultFuture {
116 if self.cancellation_token.is_none() {
117 return future::ready(Ok(())).boxed();
118 }
119 self.cancellation_token.take().unwrap().cancel();
120 future::ready(Ok(())).boxed()
121 }
122
123 fn scanning_status(&self) -> bool {
124 self.cancellation_token.is_some()
125 }
126 fn can_scan(&self) -> bool {
127 self.comm_manager.can_scan()
128 }
129}
130
131impl<T: TimedRetryCommunicationManagerImpl> Drop for TimedRetryCommunicationManager<T> {
132 fn drop(&mut self) {
133 // We set the cancellation token without doing anything with the future, so we're fine to ignore
134 // the return.
135 let _ = self.stop_scanning();
136 }
137}