1use crate::edit::ExactScrobbleEdit;
8use serde::{Deserialize, Serialize};
9use tokio::sync::{broadcast, watch};
10
11#[derive(Clone, Debug, Serialize, Deserialize)]
13pub struct RequestInfo {
14 pub method: String,
16 pub uri: String,
18 pub query_params: Vec<(String, String)>,
20 pub path: String,
22}
23
24impl RequestInfo {
25 pub fn from_url_and_method(url: &str, method: &str) -> Self {
27 let (path, query_params) = if let Some(query_start) = url.find('?') {
29 let path = url[..query_start].to_string();
30 let query_string = &url[query_start + 1..];
31
32 let query_params: Vec<(String, String)> = query_string
33 .split('&')
34 .filter_map(|pair| {
35 if let Some(eq_pos) = pair.find('=') {
36 let key = &pair[..eq_pos];
37 let value = &pair[eq_pos + 1..];
38 Some((key.to_string(), value.to_string()))
39 } else if !pair.is_empty() {
40 Some((pair.to_string(), String::new()))
41 } else {
42 None
43 }
44 })
45 .collect();
46
47 (path, query_params)
48 } else {
49 (url.to_string(), Vec::new())
50 };
51
52 let path = if path.starts_with("http://") || path.starts_with("https://") {
54 if let Some(third_slash) = path[8..].find('/') {
55 path[8 + third_slash..].to_string()
56 } else {
57 "/".to_string()
58 }
59 } else {
60 path
61 };
62
63 Self {
64 method: method.to_string(),
65 uri: url.to_string(),
66 query_params,
67 path,
68 }
69 }
70
71 pub fn short_description(&self) -> String {
73 let mut desc = format!("{} {}", self.method, self.path);
74 if !self.query_params.is_empty() {
75 let params: Vec<String> = self
76 .query_params
77 .iter()
78 .map(|(k, v)| format!("{k}={v}"))
79 .collect();
80 if params.len() <= 2 {
81 desc.push_str(&format!("?{}", params.join("&")));
82 } else {
83 desc.push_str(&format!("?{}...", params[0]));
84 }
85 }
86 desc
87 }
88}
89
90#[derive(Clone, Debug, Serialize, Deserialize)]
92pub enum RateLimitType {
93 Http429,
95 Http403,
97 ResponsePattern,
99}
100
101#[derive(Clone, Debug, Serialize, Deserialize)]
103pub enum ClientEvent {
104 RequestStarted {
106 request: RequestInfo,
108 },
109 RequestCompleted {
111 request: RequestInfo,
113 status_code: u16,
115 duration_ms: u64,
117 },
118 RateLimited {
120 delay_seconds: u64,
122 request: Option<RequestInfo>,
124 rate_limit_type: RateLimitType,
126 },
127 EditAttempted {
129 edit: ExactScrobbleEdit,
131 success: bool,
133 error_message: Option<String>,
135 duration_ms: u64,
137 },
138}
139
140pub type ClientEventReceiver = broadcast::Receiver<ClientEvent>;
142
143pub type ClientEventWatcher = watch::Receiver<Option<ClientEvent>>;
145
146#[derive(Clone)]
148pub struct SharedEventBroadcaster {
149 event_tx: broadcast::Sender<ClientEvent>,
150 last_event_tx: watch::Sender<Option<ClientEvent>>,
151}
152
153impl SharedEventBroadcaster {
154 pub fn new() -> Self {
156 let (event_tx, _) = broadcast::channel(100);
157 let (last_event_tx, _) = watch::channel(None);
158
159 Self {
160 event_tx,
161 last_event_tx,
162 }
163 }
164
165 pub fn broadcast_event(&self, event: ClientEvent) {
167 let _ = self.event_tx.send(event.clone());
168 let _ = self.last_event_tx.send(Some(event));
169 }
170
171 pub fn subscribe(&self) -> ClientEventReceiver {
173 self.event_tx.subscribe()
174 }
175
176 pub fn latest_event(&self) -> Option<ClientEvent> {
178 self.last_event_tx.borrow().clone()
179 }
180}
181
182impl Default for SharedEventBroadcaster {
183 fn default() -> Self {
184 Self::new()
185 }
186}
187
188impl std::fmt::Debug for SharedEventBroadcaster {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 f.debug_struct("SharedEventBroadcaster")
191 .field("subscribers", &self.event_tx.receiver_count())
192 .finish()
193 }
194}