Buttplug sex toy control library
at master 5.0 kB view raw
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 super::{ 9 ButtplugServerError, 10 device::{ServerDeviceManager, ServerDeviceManagerBuilder}, 11 ping_timer::PingTimer, 12 server::ButtplugServer, 13}; 14use buttplug_core::{ 15 errors::*, 16 message::{self, ButtplugServerMessageV4}, 17 util::async_manager, 18}; 19use buttplug_server_device_config::DeviceConfigurationManagerBuilder; 20use std::sync::{ 21 Arc, 22 atomic::{AtomicBool, Ordering}, 23}; 24use tokio::sync::broadcast; 25use tracing_futures::Instrument; 26 27/// Configures and creates [ButtplugServer] instances. 28pub struct ButtplugServerBuilder { 29 /// Name of the server, will be sent to the client as part of the [initial connection 30 /// handshake](https://buttplug-spec.docs.buttplug.io/architecture.html#stages). 31 name: String, 32 /// Maximum time system will live without receiving a Ping message before disconnecting. If None, 33 /// ping timer does not run. 34 max_ping_time: Option<u32>, 35 /// Device manager builder for the server 36 device_manager: Arc<ServerDeviceManager>, 37} 38 39impl Default for ButtplugServerBuilder { 40 fn default() -> Self { 41 Self { 42 name: "Buttplug Server".to_owned(), 43 max_ping_time: None, 44 device_manager: Arc::new( 45 ServerDeviceManagerBuilder::new( 46 DeviceConfigurationManagerBuilder::default() 47 .finish() 48 .unwrap(), 49 ) 50 .finish() 51 .unwrap(), 52 ), 53 } 54 } 55} 56 57impl ButtplugServerBuilder { 58 pub fn new(device_manager: ServerDeviceManager) -> Self { 59 Self { 60 name: "Buttplug Server".to_owned(), 61 max_ping_time: None, 62 device_manager: Arc::new(device_manager), 63 } 64 } 65 66 pub fn with_shared_device_manager(device_manager: Arc<ServerDeviceManager>) -> Self { 67 Self { 68 name: "Buttplug Server".to_owned(), 69 max_ping_time: None, 70 device_manager, 71 } 72 } 73 74 /// Set the name of the server, which is relayed to the client on connection (mostly for 75 /// confirmation in UI dialogs) 76 pub fn name(&mut self, name: &str) -> &mut Self { 77 self.name = name.to_owned(); 78 self 79 } 80 81 /// Set the maximum ping time, in milliseconds, for the server. If the server does not receive a 82 /// [Ping](buttplug_core::messages::Ping) message in this amount of time after the handshake has 83 /// succeeded, the server will automatically disconnect. If this is not called, the ping timer 84 /// will not be activated. 85 /// 86 /// Note that this has nothing to do with communication medium specific pings, like those built 87 /// into the Websocket protocol. This ping is specific to the Buttplug protocol. 88 pub fn max_ping_time(&mut self, ping_time: u32) -> &mut Self { 89 self.max_ping_time = Some(ping_time); 90 self 91 } 92 93 /// Try to build a [ButtplugServer] using the parameters given. 94 pub fn finish(&self) -> Result<ButtplugServer, ButtplugServerError> { 95 // Create the server 96 debug!("Creating server '{}'", self.name); 97 98 // Set up our channels to different parts of the system. 99 let (output_sender, _) = broadcast::channel(256); 100 let output_sender_clone = output_sender.clone(); 101 102 let connected = Arc::new(AtomicBool::new(false)); 103 let connected_clone = connected.clone(); 104 105 // TODO this should use a cancellation token instead of passing around the timer itself. 106 let ping_time = self.max_ping_time.unwrap_or(0); 107 let ping_timer = Arc::new(PingTimer::new(ping_time)); 108 let ping_timeout_notifier = ping_timer.ping_timeout_waiter(); 109 110 // Spawn the ping timer task, assuming the ping time is > 0. 111 if ping_time > 0 { 112 let device_manager_clone = self.device_manager.clone(); 113 async_manager::spawn( 114 async move { 115 // This will only exit if we've pinged out. 116 ping_timeout_notifier.await; 117 error!("Ping out signal received, stopping server"); 118 connected_clone.store(false, Ordering::Relaxed); 119 async_manager::spawn(async move { 120 if let Err(e) = device_manager_clone.stop_all_devices().await { 121 error!("Could not stop devices on ping timeout: {:?}", e); 122 } 123 }); 124 // TODO Should the event sender return a result instead of an error message? 125 if output_sender_clone 126 .send(ButtplugServerMessageV4::Error(message::ErrorV0::from( 127 ButtplugError::from(ButtplugPingError::PingedOut), 128 ))) 129 .is_err() 130 { 131 error!("Server disappeared, cannot update about ping out."); 132 }; 133 } 134 .instrument(tracing::info_span!("Buttplug Server Ping Timeout Task")), 135 ); 136 } 137 138 // Assuming everything passed, return the server. 139 Ok(ButtplugServer::new( 140 &self.name, 141 ping_time, 142 ping_timer, 143 self.device_manager.clone(), 144 connected, 145 output_sender, 146 )) 147 } 148}