1use std::convert::TryFrom;
2use std::sync::Arc;
3
4use thiserror::Error;
5use tokio::sync::{oneshot, Mutex};
6use validator::Validate;
7
8use crate::{
9 component::ComponentName,
10 global::{Global, InputMessage, InputMessageData, InputSourceHandle, Message},
11 image::{RawImage, RawImageError},
12 instance::{InstanceHandle, InstanceHandleError, StartEffectError},
13};
14
15pub mod message;
17use message::{HyperionCommand, HyperionMessage, HyperionResponse};
18
19#[derive(Debug, Error)]
20pub enum JsonApiError {
21 #[error("error broadcasting update: {0}")]
22 Broadcast(#[from] tokio::sync::broadcast::error::SendError<InputMessage>),
23 #[error("request not implemented")]
24 NotImplemented,
25 #[error("error decoding image")]
26 Image(#[from] RawImageError),
27 #[error("error validating request: {0}")]
28 Validation(#[from] validator::ValidationErrors),
29 #[error("error receiving system response: {0}")]
30 Recv(#[from] oneshot::error::RecvError),
31 #[error("error accessing the current instance: {0}")]
32 Instance(#[from] InstanceHandleError),
33 #[error("no current instance found")]
34 InstanceNotFound,
35 #[error(transparent)]
36 StartEffect(#[from] StartEffectError),
37}
38
39pub struct ClientConnection {
41 source: InputSourceHandle<InputMessage>,
42 current_instance: Option<i32>,
43}
44
45impl ClientConnection {
46 pub fn new(source: InputSourceHandle<InputMessage>) -> Self {
47 Self {
48 source,
49 current_instance: None,
50 }
51 }
52
53 async fn current_instance(&mut self, global: &Global) -> Result<InstanceHandle, JsonApiError> {
54 if let Some(current_instance) = self.current_instance {
55 if let Some(instance) = global.get_instance(current_instance).await {
56 return Ok(instance);
57 } else {
58 self.current_instance = None;
60 }
61 }
62
63 if let Some((id, inst)) = global.default_instance().await {
64 self.set_current_instance(id);
65 return Ok(inst);
66 }
67
68 Err(JsonApiError::InstanceNotFound)
69 }
70
71 fn set_current_instance(&mut self, id: i32) {
72 debug!("{}: switch to instance {}", &self.source.name(), id);
73 self.current_instance = Some(id);
74 }
75
76 #[instrument(skip(request, global))]
77 pub async fn handle_request(
78 &mut self,
79 request: HyperionMessage,
80 global: &Global,
81 ) -> Result<HyperionResponse, JsonApiError> {
82 request.validate()?;
83
84 match request.command {
85 HyperionCommand::ClearAll => {
86 self.source
88 .send(ComponentName::All, InputMessageData::ClearAll)?;
89 }
90
91 HyperionCommand::Clear(message::Clear { priority }) => {
92 self.source
94 .send(ComponentName::All, InputMessageData::Clear { priority })?;
95 }
96
97 HyperionCommand::Color(message::Color {
98 priority,
99 duration,
100 color,
101 origin: _,
102 }) => {
103 self.source.send(
107 ComponentName::Color,
108 InputMessageData::SolidColor {
109 priority,
110 duration: duration.map(|ms| chrono::Duration::milliseconds(ms as _)),
111 color,
112 },
113 )?;
114 }
115
116 HyperionCommand::Image(message::Image {
117 priority,
118 duration,
119 imagewidth,
120 imageheight,
121 imagedata,
122 origin: _,
123 format: _,
124 scale: _,
125 name: _,
126 }) => {
127 let raw_image = RawImage::try_from((imagedata, imagewidth, imageheight))?;
130
131 self.source.send(
132 ComponentName::Image,
133 InputMessageData::Image {
134 priority,
135 duration: duration.map(|ms| chrono::Duration::milliseconds(ms as _)),
136 image: Arc::new(raw_image),
137 },
138 )?;
139 }
140
141 HyperionCommand::Effect(message::Effect {
142 priority,
143 duration,
144 origin: _,
145 effect,
146 python_script: _,
147 image_data: _,
148 }) => {
149 let instance = self.current_instance(global).await?;
152 let (tx, rx) = oneshot::channel();
153
154 instance
155 .send(InputMessage::new(
156 self.source.id(),
157 ComponentName::All,
158 InputMessageData::Effect {
159 priority,
160 duration: duration.map(|ms| chrono::Duration::milliseconds(ms as _)),
161 effect: effect.into(),
162 response: Arc::new(Mutex::new(Some(tx))),
163 },
164 ))
165 .await?;
166
167 return Ok(rx.await?.map(|_| HyperionResponse::success())?);
168 }
169
170 HyperionCommand::ServerInfo(message::ServerInfoRequest { subscribe: _ }) => {
171 let (adjustments, priorities) =
174 if let Ok(handle) = self.current_instance(global).await {
175 (
176 handle
177 .config()
178 .await?
179 .color
180 .channel_adjustment
181 .iter()
182 .map(|adj| message::ChannelAdjustment::from(adj.clone()))
183 .collect(),
184 handle.current_priorities().await?,
185 )
186 } else {
187 Default::default()
188 };
189
190 let effects: Vec<message::EffectDefinition> = global
193 .read_effects(|effects| effects.iter().map(Into::into).collect())
194 .await;
195
196 return Ok(global
198 .read_config(|config| {
199 let instances = config
200 .instances
201 .iter()
202 .map(|instance_config| (&instance_config.1.instance).into())
203 .collect();
204
205 HyperionResponse::server_info(priorities, adjustments, effects, instances)
206 })
207 .await);
208 }
209
210 HyperionCommand::Authorize(message::Authorize { subcommand, .. }) => match subcommand {
211 message::AuthorizeCommand::AdminRequired => {
212 return Ok(HyperionResponse::admin_required(false));
214 }
215 message::AuthorizeCommand::TokenRequired => {
216 return Ok(HyperionResponse::token_required(false));
218 }
219 _ => {
220 return Err(JsonApiError::NotImplemented);
221 }
222 },
223
224 HyperionCommand::SysInfo => {
225 return Ok(HyperionResponse::sys_info(
226 global.read_config(|config| config.uuid()).await,
227 ));
228 }
229
230 HyperionCommand::Instance(message::Instance {
231 subcommand: message::InstanceCommand::SwitchTo,
232 instance: Some(id),
233 ..
234 }) => {
235 if global.get_instance(id).await.is_some() {
236 self.set_current_instance(id);
237 return Ok(HyperionResponse::switch_to(Some(id)));
238 } else {
239 return Ok(HyperionResponse::switch_to(None));
242 }
243 }
244
245 _ => return Err(JsonApiError::NotImplemented),
246 };
247
248 Ok(HyperionResponse::success())
249 }
250}
251
252impl std::fmt::Debug for ClientConnection {
253 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
254 f.debug_struct("ClientConnection")
255 .field("source", &format!("{}", &*self.source))
256 .finish()
257 }
258}