Buttplug sex toy control library

feat: Add events via sse

+66 -21
+66 -21
crates/intiface_engine/src/rest_server.rs
··· 1 - use std::{collections::BTreeMap, io, net::SocketAddr, str::FromStr, sync::Arc}; 1 + use std::{collections::BTreeMap, convert::Infallible, io, net::SocketAddr, str::FromStr, sync::Arc}; 2 2 3 3 use axum::{ 4 - Json, Router, 5 - extract::{Path, State, rejection::JsonRejection}, 6 - http::StatusCode, 7 - response::{IntoResponse, Response}, 8 - routing::{get, put}, 4 + extract::{rejection::JsonRejection, Path, State}, http::StatusCode, response::{sse::{Event, KeepAlive}, IntoResponse, Response, Sse}, routing::{get, put}, Json, Router 9 5 }; 10 6 use buttplug_client::{ 11 - ButtplugClient, ButtplugClientDevice, ButtplugClientError, device::ClientDeviceOutputCommand, 7 + device::{ClientDeviceFeature, ClientDeviceOutputCommand}, ButtplugClient, ButtplugClientDevice, ButtplugClientError, ButtplugClientEvent 12 8 }; 13 9 use buttplug_client_in_process::ButtplugInProcessClientConnectorBuilder; 14 10 use buttplug_core::message::{DeviceFeature, OutputType}; 15 11 use buttplug_server::ButtplugServer; 12 + use futures::{Stream, StreamExt}; 16 13 use serde::Serialize; 17 14 use strum::IntoEnumIterator; 18 15 use thiserror::Error; ··· 102 99 .cloned() 103 100 } 104 101 102 + fn get_feature( 103 + client: &ButtplugClient, 104 + index: u32, 105 + feature_index: u32 106 + ) -> Result<ClientDeviceFeature, IntifaceRestError> { 107 + get_device(client, index)? 108 + .device_features() 109 + .get(&feature_index) 110 + .ok_or(IntifaceRestError::InvalidFeature(index, feature_index)) 111 + .cloned() 112 + } 113 + 105 114 async fn start_scanning( 106 115 State(client): State<Arc<ButtplugClient>>, 107 116 ) -> Result<(), IntifaceRestError> { ··· 141 150 142 151 async fn set_device_output( 143 152 State(client): State<Arc<ButtplugClient>>, 144 - Path((index, command, level)): Path<(u32, String, f64)>, 153 + Path((index, command, level)): Path<(u32, OutputType, f64)>, 145 154 ) -> Result<(), IntifaceRestError> { 146 - let command_type = OutputType::from_str(&command).map_err(|_| { 147 - IntifaceRestError::InvalidOutputType(command, OutputType::iter().collect::<Vec<OutputType>>()) 148 - })?; 155 + let cmd = ClientDeviceOutputCommand::from_command_value_float(command, level) 156 + .map_err(|e| IntifaceRestError::ButtplugClientError(e))?; 157 + 158 + Ok( 159 + get_device(&client, index)? 160 + .send_command(&cmd) 161 + .await 162 + .map_err(|e| IntifaceRestError::ButtplugClientError(e))?, 163 + ) 164 + } 149 165 150 - let cmd = ClientDeviceOutputCommand::from_command_value_float(command_type, level) 166 + async fn set_feature_output( 167 + State(client): State<Arc<ButtplugClient>>, 168 + Path((index, feature_index, command, level)): Path<(u32, u32, OutputType, f64)>, 169 + ) -> Result<(), IntifaceRestError> { 170 + let cmd = ClientDeviceOutputCommand::from_command_value_float(command, level) 151 171 .map_err(|e| IntifaceRestError::ButtplugClientError(e))?; 152 172 153 173 Ok( 154 - get_device(&client, index)? 174 + get_feature(&client, index, feature_index)? 155 175 .send_command(&cmd) 156 176 .await 157 177 .map_err(|e| IntifaceRestError::ButtplugClientError(e))?, ··· 198 218 ) 199 219 } 200 220 201 - async fn get_feature( 221 + async fn get_feature_info( 202 222 State(client): State<Arc<ButtplugClient>>, 203 223 Path((index, feature_index)): Path<(u32, u32)>, 204 224 ) -> Result<Json<DeviceFeature>, IntifaceRestError> { ··· 213 233 ) 214 234 } 215 235 236 + async fn feature_input_command( 237 + State(client): State<Arc<ButtplugClient>>, 238 + Path((index, feature_index, input_type, command)): Path<(u32, u32, String, String)>, 239 + ) -> Result<(), IntifaceRestError> { 240 + /* 241 + let cmd = convert_output_command(&command, level)?; 242 + 243 + Ok( 244 + get_feature(&client, index, feature_index)? 245 + .send_command(&cmd) 246 + .await 247 + .map_err(|e| IntifaceRestError::ButtplugClientError(e))?, 248 + ) 249 + */ 250 + Ok(()) 251 + } 252 + 253 + async fn server_sse(State(client): State<Arc<ButtplugClient>>,) -> Sse<impl Stream<Item = Result<Event, Infallible>>> { 254 + let stream = client.event_stream().map(|e| Ok(Event::default().data(format!("{:?}", e)))); 255 + 256 + Sse::new(stream).keep_alive(KeepAlive::default()) 257 + } 258 + 216 259 impl IntifaceRestServer { 217 260 pub async fn run(server: ButtplugServer) -> Result<(), io::Error> { 218 261 let connector = ButtplugInProcessClientConnectorBuilder::default() ··· 232 275 .route("/devices/{index}", get(get_device_info)) 233 276 .route("/devices/{index}/stop", put(stop_device)) 234 277 .route("/devices/{index}/features", get(get_features)) 235 - .route("/devices/{index}/features/{index}/", put(get_feature)) 278 + .route("/devices/{index}/features/{index}/", put(get_feature_info)) 236 279 .route( 237 - "/devices/{index}/outputs/{output_type}/", 280 + "/devices/{index}/outputs/{command}/{level}", 238 281 put(set_device_output), 239 282 ) 240 - /* 241 283 .route( 242 - "/devices/{index}/features/{index}/outputs/{output_type}/", 284 + "/devices/{index}/features/{index}/outputs/{output_type}/{level}", 243 285 put(set_feature_output), 244 286 ) 287 + /* 245 288 .route( 246 289 "/devices/{index}/inputs/{input_type}/{input_command}", 247 - put(set_device_input), 290 + put(device_input_command), 248 291 ) 292 + */ 249 293 .route( 250 294 "/devices/{index}/features/{index}/inputs/{input_type}/{input_command}", 251 - put(set_feature_input), 295 + put(feature_input_command), 252 296 ) 297 + /* 253 298 .route("/devices/{index}/events", get(device_sse)) 254 - .route("/events", get(server_sse)) 255 299 */ 300 + .route("/events", get(server_sse)) 256 301 //.route("/devices/{*index}/vibrate", post(set_feature_vibrate_speed)) 257 302 .with_state(Arc::new(client)), 258 303 );