1use std::sync::Arc;
4
5pub use crossbeam::channel::{RecvError, RecvTimeoutError, SendError, TryRecvError};
6use parking_lot::RwLock;
7pub use re_quota_channel::sync::TrySendError;
8use re_uri::RedapUri;
9
10mod data_source_message;
11mod receiver;
12mod receiver_set;
13mod sender;
14
15pub use self::data_source_message::{
16 BlueprintTarget, DataSourceMessage, DataSourceUiCommand, DefaultBlueprintRegistration,
17 InspectError, SaveScreenshotError,
18};
19pub use self::receiver::LogReceiver;
20pub use self::receiver_set::LogReceiverSet;
21pub use self::sender::LogSender;
22
23#[derive(
27 Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
28)]
29pub enum RecordingOpenBehavior {
30 Background,
34
35 Open,
37
38 OpenAndSelect,
40}
41
42#[derive(Debug, thiserror::Error)]
44pub enum FlushError {
45 #[error("Received closed before flushing completed")]
46 Closed,
47
48 #[error("Flush timed out - not all messages were sent.")]
49 Timeout,
50}
51
52#[derive(
55 Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
56)]
57pub enum LogSource {
58 File { path: std::path::PathBuf },
61
62 #[serde(alias = "RrdHttpStream")]
64 HttpStream {
65 url: String,
67 },
68
69 RrdWebEvent,
75
76 JsChannel {
78 channel_name: String,
80 },
81
82 Sdk,
84
85 Stdin,
87
88 RedapGrpcStream {
91 uri: re_uri::DatasetUri,
92
93 open_behavior: RecordingOpenBehavior,
94 },
95
96 MessageProxy(re_uri::ProxyUri),
98}
99
100impl std::fmt::Display for LogSource {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 match self {
103 Self::File { path } => write!(f, "file://{}", path.to_string_lossy()),
104 Self::HttpStream { url } => url_display_name(url).fmt(f),
105 Self::MessageProxy(uri) => uri.fmt(f),
106 Self::RedapGrpcStream { uri, .. } => uri.fmt(f),
107 Self::RrdWebEvent => "Web event listener".fmt(f),
108 Self::JsChannel { channel_name } => write!(f, "Javascript channel: {channel_name}"),
109 Self::Sdk => "SDK".fmt(f),
110 Self::Stdin => "stdin".fmt(f),
111 }
112 }
113}
114
115impl LogSource {
116 pub fn is_redap(&self) -> bool {
117 matches!(self, Self::RedapGrpcStream { .. })
118 }
119
120 pub fn is_network(&self) -> bool {
121 match self {
122 Self::File { .. } | Self::Sdk | Self::RrdWebEvent | Self::Stdin => false,
123 Self::HttpStream { .. }
124 | Self::JsChannel { .. }
125 | Self::RedapGrpcStream { .. }
126 | Self::MessageProxy { .. } => true,
127 }
128 }
129
130 pub fn open_behavior(&self) -> RecordingOpenBehavior {
131 match self {
132 Self::File { .. }
133 | Self::Sdk
134 | Self::RrdWebEvent
135 | Self::Stdin
136 | Self::HttpStream { .. }
137 | Self::JsChannel { .. }
138 | Self::MessageProxy { .. } => RecordingOpenBehavior::OpenAndSelect,
139
140 Self::RedapGrpcStream { open_behavior, .. } => *open_behavior,
141 }
142 }
143
144 pub fn redap_uri(&self) -> Option<RedapUri> {
145 match self {
146 Self::RedapGrpcStream { uri, .. } => Some(RedapUri::Dataset(uri.clone())),
147 Self::MessageProxy(uri) => Some(RedapUri::Proxy(uri.clone())),
148
149 Self::File { .. }
150 | Self::Sdk
151 | Self::RrdWebEvent
152 | Self::Stdin
153 | Self::HttpStream { .. }
154 | Self::JsChannel { .. } => None,
155 }
156 }
157
158 pub fn stripped_redap_uri(&self) -> Option<RedapUri> {
160 self.redap_uri().map(|uri| match uri {
161 RedapUri::Catalog(_)
162 | RedapUri::Entry(_)
163 | RedapUri::Folder(_)
164 | RedapUri::Proxy(_) => uri,
165 RedapUri::Dataset(uri) => RedapUri::Dataset(uri.without_fragment()),
166 })
167 }
168
169 pub fn loading_name(&self) -> Option<String> {
174 match self {
175 Self::File { path } => Some(path.to_string_lossy().into_owned()),
177 Self::HttpStream { url } => Some(url_display_name(url)),
178 Self::RedapGrpcStream { uri, .. } => uri
179 .segment_id
180 .as_ref()
181 .map(|segment_id| segment_id.as_str().to_owned()),
182
183 Self::RrdWebEvent
184 | Self::JsChannel { .. }
185 | Self::MessageProxy { .. }
186 | Self::Sdk
187 | Self::Stdin => {
188 None
191 }
192 }
193 }
194
195 pub fn status_string(&self) -> String {
197 match self {
198 Self::File { path } => {
199 format!("Loading {}…", path.display())
200 }
201 Self::Stdin => "Loading stdin…".to_owned(),
202 Self::HttpStream { url } => {
203 format!("Waiting for data on {}…", url_display_name(url))
204 }
205 Self::MessageProxy(uri) => {
206 format!("Waiting for data on {uri}…")
207 }
208 Self::RedapGrpcStream { uri, .. } => {
209 format!("Waiting for data on {}…", uri.clone().without_fragment())
210 }
211 Self::RrdWebEvent | Self::JsChannel { .. } => "Waiting for logging data…".to_owned(),
212 Self::Sdk => "Waiting for logging data from SDK".to_owned(),
213 }
214 }
215
216 pub fn is_same_ignoring_uri_fragments(&self, other: &Self) -> bool {
219 match (self, other) {
220 (Self::RedapGrpcStream { uri: uri1, .. }, Self::RedapGrpcStream { uri: uri2, .. }) => {
221 uri1.clone().without_fragment() == uri2.clone().without_fragment()
222 }
223 (Self::HttpStream { url: url1 }, Self::HttpStream { url: url2 }) => url1 == url2,
224 _ => self == other,
225 }
226 }
227}
228
229pub fn url_display_name(url: &str) -> String {
233 if url.starts_with("data:")
236 && let Some(comma) = url.find(',')
237 {
238 return format!("{}…", &url[..=comma]);
239 }
240
241 url.to_owned()
242}
243
244#[derive(Default)]
248pub(crate) struct Channel {
249 waker: RwLock<Option<Box<dyn Fn() + Send + Sync + 'static>>>,
253}
254
255pub fn log_channel(source: LogSource) -> (LogSender, LogReceiver) {
257 let max_bytes_on_wire = 128 * 1024 * 1024; let source = Arc::new(source);
260 let channel = Arc::new(Channel::default());
261 let (tx, rx) = re_quota_channel::channel(format!("log_channel({source})"), max_bytes_on_wire);
262 let sender = LogSender::new(tx, source.clone(), channel.clone());
263 let receiver = LogReceiver::new(rx, channel, source);
264 (sender, receiver)
265}
266
267#[derive(re_byte_size::SizeBytes)]
273pub enum SmartMessagePayload {
274 Msg(DataSourceMessage),
276
277 Flush {
279 #[size_bytes(ignore)]
280 on_flush_done: Box<dyn FnOnce() + Send>,
281 },
282
283 Quit(#[size_bytes(ignore)] Option<Box<dyn std::error::Error + Send>>),
287}
288
289impl std::fmt::Debug for SmartMessagePayload {
290 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
291 match self {
292 Self::Msg(_) => f.write_str("Msg(_)"),
293 Self::Flush { .. } => f.write_str("Flush"),
294 Self::Quit(_) => f.write_str("Quit"),
295 }
296 }
297}
298
299#[derive(Debug, re_byte_size::SizeBytes)]
300pub struct SmartMessage {
301 #[size_bytes(ignore)]
302 pub source: Arc<LogSource>,
303 pub payload: SmartMessagePayload,
304}
305
306impl SmartMessage {
307 pub fn data(&self) -> Option<&DataSourceMessage> {
308 match &self.payload {
309 SmartMessagePayload::Msg(msg) => Some(msg),
310 SmartMessagePayload::Flush { .. } | SmartMessagePayload::Quit(_) => None,
311 }
312 }
313
314 pub fn into_data(self) -> Option<DataSourceMessage> {
315 match self.payload {
316 SmartMessagePayload::Msg(msg) => Some(msg),
317 SmartMessagePayload::Flush { .. } | SmartMessagePayload::Quit(_) => None,
318 }
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::url_display_name;
325
326 #[test]
327 fn url_display_name_keeps_short_urls() {
328 let url = "https://example.com/data.rrd";
329 assert_eq!(url_display_name(url), url);
330 }
331
332 #[test]
333 fn url_display_name_keeps_long_real_urls() {
334 let url = format!("https://example.com/data.rrd?token={}", "x".repeat(1000));
336 assert_eq!(url_display_name(&url), url);
337 }
338
339 #[test]
340 fn url_display_name_truncates_long_data_url() {
341 let payload = "A".repeat(5_000_000);
343 let url = format!("data:application/octet-stream;base64,{payload}");
344
345 let name = url_display_name(&url);
346
347 assert_eq!(name, "data:application/octet-stream;base64,…");
348 assert!(name.len() < 100);
349 }
350}