1use std::fmt::{Display, Formatter};
2use std::fs;
3use std::io::Cursor;
4use std::sync::Arc;
5use std::sync::Mutex;
6use std::sync::atomic::AtomicU64;
7
8use crate::async_util::{perform_async_work, perform_work};
9use crate::channels::checked_send;
10use crate::cxxrtl_container::CxxrtlContainer;
11use crate::file_dialog::OpenMode;
12use crate::remote::{get_hierarchy_from_server, get_server_status, server_reload};
13use crate::transactions::TRANSACTIONS_FILE_EXTENSION;
14use crate::util::get_multi_extension;
15use camino::{Utf8Path, Utf8PathBuf};
16use eyre::Result;
17use eyre::{WrapErr as _, anyhow};
18use ftr_parser::parse;
19use futures_util::FutureExt;
20use serde::{Deserialize, Serialize};
21use tracing::{error, info, warn};
22use web_time::Instant;
23
24use crate::transaction_container::TransactionContainer;
25use crate::wave_container::WaveContainer;
26use crate::wellen::{
27 BodyResult, HeaderResult, LoadSignalPayload, LoadSignalsCmd, LoadSignalsResult,
28};
29use crate::{SystemState, message::Message};
30use surver::{
31 HTTP_SERVER_KEY, HTTP_SERVER_VALUE_SURFER, SurverFileInfo, WELLEN_SURFER_DEFAULT_OPTIONS,
32};
33
34#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
35pub enum CxxrtlKind {
36 Tcp { url: String },
37 Mailbox,
38}
39impl std::fmt::Display for CxxrtlKind {
40 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
41 match self {
42 CxxrtlKind::Tcp { url } => write!(f, "cxxrtl+tcp://{url}"),
43 CxxrtlKind::Mailbox => write!(f, "cxxrtl mailbox"),
44 }
45 }
46}
47
48#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
49pub enum WaveSource {
50 File(Utf8PathBuf),
51 Data,
52 DragAndDrop(Option<Utf8PathBuf>),
53 Url(String),
54 Cxxrtl(CxxrtlKind),
55}
56
57pub const STATE_FILE_EXTENSION: &str = "surf.ron";
58
59impl WaveSource {
60 #[must_use]
61 pub fn as_file(&self) -> Option<&Utf8Path> {
62 match self {
63 WaveSource::File(path) => Some(path.as_path()),
64 _ => None,
65 }
66 }
67
68 #[must_use]
69 pub fn path(&self) -> Option<&Utf8PathBuf> {
70 match self {
71 WaveSource::File(path) => Some(path),
72 WaveSource::DragAndDrop(Some(path)) => Some(path),
73 _ => None,
74 }
75 }
76
77 #[must_use]
78 pub fn sibling_state_file(&self) -> Option<Utf8PathBuf> {
79 let path = self.path()?;
80 let directory = path.parent()?;
81 let paths = fs::read_dir(directory).ok()?;
82
83 for entry in paths {
84 let Ok(entry) = entry else { continue };
85 if let Ok(path) = Utf8PathBuf::from_path_buf(entry.path()) {
86 let Some(ext) = get_multi_extension(&path) else {
87 continue;
88 };
89 if ext.as_str() == STATE_FILE_EXTENSION {
90 return Some(path);
91 }
92 }
93 }
94
95 None
96 }
97
98 #[must_use]
99 pub fn into_translation_type(&self) -> surfer_translation_types::WaveSource {
100 use surfer_translation_types::WaveSource as Ws;
101 match self {
102 WaveSource::File(file) => Ws::File(file.to_string()),
103 WaveSource::Data => Ws::Data,
104 WaveSource::DragAndDrop(file) => {
105 Ws::DragAndDrop(file.as_ref().map(ToString::to_string))
106 }
107 WaveSource::Url(u) => Ws::Url(u.clone()),
108 WaveSource::Cxxrtl(_) => Ws::Cxxrtl,
109 }
110 }
111
112 #[must_use]
116 pub(crate) fn title_name(&self) -> Option<String> {
117 match self {
118 WaveSource::File(_) | WaveSource::DragAndDrop(Some(_)) => self
119 .path()
120 .and_then(|p| p.file_name())
121 .map(ToString::to_string),
122 WaveSource::Url(url) => url
123 .split(['?', '#'])
124 .next()
125 .and_then(|u| u.rsplit('/').next())
126 .filter(|name| !name.is_empty())
127 .map(ToString::to_string),
128 WaveSource::Data | WaveSource::DragAndDrop(None) | WaveSource::Cxxrtl(_) => None,
129 }
130 }
131
132 #[must_use]
136 pub fn window_title(&self) -> String {
137 format_window_title(self.title_name().as_deref())
138 }
139}
140
141#[must_use]
144pub(crate) fn format_window_title(name: Option<&str>) -> String {
145 match name {
146 Some(name) => format!("{name} - Surfer"),
147 None => "Surfer".to_string(),
148 }
149}
150
151pub fn url_to_wavesource(url: &str) -> Option<WaveSource> {
152 if url.starts_with("https://") || url.starts_with("http://") {
153 info!("Wave source is url");
154 Some(WaveSource::Url(url.to_string()))
155 } else if url.starts_with("cxxrtl+tcp://") {
156 #[cfg(not(target_arch = "wasm32"))]
157 {
158 info!("Wave source is cxxrtl tcp");
159 Some(WaveSource::Cxxrtl(CxxrtlKind::Tcp {
160 url: url.replace("cxxrtl+tcp://", ""),
161 }))
162 }
163 #[cfg(target_arch = "wasm32")]
164 {
165 warn!("Loading waves from cxxrtl via tcp is unsupported in WASM builds.");
166 None
167 }
168 } else {
169 None
170 }
171}
172
173pub fn string_to_wavesource(path: &str) -> WaveSource {
174 if let Some(source) = url_to_wavesource(path) {
175 source
176 } else {
177 info!("Wave source is file");
178 WaveSource::File(path.into())
179 }
180}
181
182impl Display for WaveSource {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 match self {
185 WaveSource::File(file) => write!(f, "{file}"),
186 WaveSource::Data => write!(f, "File data"),
187 WaveSource::DragAndDrop(None) => write!(f, "Dropped file"),
188 WaveSource::DragAndDrop(Some(filename)) => write!(f, "Dropped file ({filename})"),
189 WaveSource::Url(url) => write!(f, "{url}"),
190 WaveSource::Cxxrtl(CxxrtlKind::Tcp { url }) => write!(f, "cxxrtl+tcp://{url}"),
191 WaveSource::Cxxrtl(CxxrtlKind::Mailbox) => write!(f, "cxxrtl mailbox"),
192 }
193 }
194}
195
196#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
197pub enum WaveFormat {
198 Vcd,
199 Fst,
200 Ghw,
201 CxxRtl,
202 Ftr,
203}
204
205impl Display for WaveFormat {
206 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
207 match self {
208 WaveFormat::Vcd => write!(f, "VCD"),
209 WaveFormat::Fst => write!(f, "FST"),
210 WaveFormat::Ghw => write!(f, "GHW"),
211 WaveFormat::CxxRtl => write!(f, "CXXRTL"),
212 WaveFormat::Ftr => write!(f, "FTR"),
213 }
214 }
215}
216
217#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy)]
218pub enum LoadOptions {
219 Clear,
220 KeepAvailable,
221 KeepAll,
222}
223
224impl From<(OpenMode, bool)> for LoadOptions {
225 fn from(val: (OpenMode, bool)) -> Self {
226 match val {
227 (OpenMode::Open, _) => LoadOptions::Clear,
228 (OpenMode::Switch, false) => LoadOptions::KeepAvailable,
229 (OpenMode::Switch, true) => LoadOptions::KeepAll,
230 }
231 }
232}
233
234pub struct LoadProgress {
235 pub started: Instant,
236 pub progress: LoadProgressStatus,
237}
238
239impl LoadProgress {
240 #[must_use]
241 pub fn new(progress: LoadProgressStatus) -> Self {
242 LoadProgress {
243 started: Instant::now(),
244 progress,
245 }
246 }
247}
248
249pub enum LoadProgressStatus {
250 Downloading(String),
251 Connecting(String),
252 ReadingHeader(WaveSource),
253 ReadingBody(WaveSource, u64, Arc<AtomicU64>),
254 LoadingVariables(u64),
255}
256
257impl SystemState {
258 pub fn load_from_file(
259 &mut self,
260 filename: Utf8PathBuf,
261 load_options: LoadOptions,
262 ) -> Result<()> {
263 match get_multi_extension(&filename) {
264 Some(ext) => match ext.as_str() {
265 STATE_FILE_EXTENSION => {
266 self.load_state_file(Some(filename));
267 Ok(())
268 }
269 TRANSACTIONS_FILE_EXTENSION => {
270 self.load_transactions_from_file(filename, load_options)
271 }
272 _ => self.load_wave_from_file(filename, load_options),
273 },
274 _ => self.load_wave_from_file(filename, load_options),
275 }
276 }
277
278 pub fn load_from_bytes(
279 &mut self,
280 source: WaveSource,
281 bytes: Vec<u8>,
282 load_options: LoadOptions,
283 ) {
284 if parse::is_ftr(&mut Cursor::new(&bytes)).is_ok_and(|is_ftr| is_ftr) {
285 self.load_transactions_from_bytes(source, bytes, load_options);
286 } else {
287 self.load_wave_from_bytes(source, bytes, load_options);
288 }
289 }
290
291 pub fn load_wave_from_file(
292 &mut self,
293 filename: Utf8PathBuf,
294 load_options: LoadOptions,
295 ) -> Result<()> {
296 info!("Loading a waveform file: {filename}");
297 let start = web_time::Instant::now();
298 let source = WaveSource::File(filename.clone());
299 let source_copy = source.clone();
300 let sender = self.channels.msg_sender.clone();
301 if !filename.exists() {
302 error!("Waveform file is missing: {filename}");
303 return Ok(());
304 }
305 perform_work(move || {
306 let header_result = wellen::viewers::read_header_from_file(
307 filename.as_str(),
308 &WELLEN_SURFER_DEFAULT_OPTIONS,
309 )
310 .map_err(|e| anyhow!("{e:?}"))
311 .with_context(|| format!("Failed to parse wave file: {source}"));
312
313 let header = match header_result {
314 Ok(header) => header,
315 Err(e) => {
316 error!("{e:?}");
317 return;
318 }
319 };
320 checked_send(
321 &sender,
322 Message::WaveHeaderLoaded(
323 start,
324 source,
325 load_options,
326 HeaderResult::LocalFile(Box::new(header)),
327 ),
328 );
329 });
330
331 self.progress_tracker = Some(LoadProgress::new(LoadProgressStatus::ReadingHeader(
332 source_copy,
333 )));
334 Ok(())
335 }
336
337 pub fn load_from_data(&mut self, data: Vec<u8>, load_options: LoadOptions) -> Result<()> {
338 self.load_from_bytes(WaveSource::Data, data, load_options);
339 Ok(())
340 }
341
342 #[cfg(not(target_arch = "wasm32"))]
343 pub fn load_from_dropped(&mut self, file: egui::DroppedFileHandle) -> Result<()> {
344 info!("Got a dropped file");
345
346 let path = file.path().to_str().map(Utf8PathBuf::from);
347
348 if let Some(path) = path {
349 return self.load_from_file(path, LoadOptions::Clear);
350 }
351
352 if let Ok(bytes) = file.bytes() {
353 return self.load_from_dropped_bytes(path, bytes.clone());
354 }
355
356 Err(anyhow!(
357 "Unknown how to load dropped file w/o path or bytes"
358 ))
359 }
360
361 #[cfg(target_arch = "wasm32")]
362 pub fn load_from_dropped(&mut self, file: egui::DroppedFileHandle) -> Result<()> {
363 info!("Got a dropped file");
364
365 let path = file
366 .path()
367 .to_str()
368 .and_then(|p| Some(Utf8PathBuf::from(p)));
369
370 let sender = self.channels.msg_sender.clone();
371 perform_async_work(async move {
372 match file.bytes_async().await {
373 Ok(bytes) => {
374 checked_send(
375 &sender,
376 Message::DroppedFileBytesLoaded(path, bytes.to_vec()),
377 );
378 }
379 Err(_) => {
380 if let Some(path) = path {
381 checked_send(&sender, Message::LoadFile(path, LoadOptions::Clear));
382 } else {
383 error!("Unknown how to load dropped file w/o path or bytes");
384 }
385 }
386 }
387 });
388
389 Ok(())
390 }
391
392 pub fn load_from_dropped_bytes(
393 &mut self,
394 path: Option<Utf8PathBuf>,
395 bytes: Vec<u8>,
396 ) -> Result<()> {
397 if bytes.is_empty() {
398 return Err(anyhow!("Dropped an empty file"));
399 }
400
401 if let Some(path) = path.clone() {
402 if get_multi_extension(&path) == Some(STATE_FILE_EXTENSION.to_string()) {
403 let sender = self.channels.msg_sender.clone();
404 perform_async_work(async move {
405 let new_state = match ron::de::from_bytes(&bytes)
406 .context(format!("Failed loading {path}"))
407 {
408 Ok(s) => s,
409 Err(e) => {
410 error!("Failed to load state: {e:#?}");
411 return;
412 }
413 };
414
415 checked_send(&sender, Message::LoadState(new_state, Some(path)));
416 });
417 } else {
418 self.load_from_bytes(
419 WaveSource::DragAndDrop(Some(path)),
420 bytes,
421 LoadOptions::Clear,
422 );
423 }
424 } else {
425 self.load_from_bytes(WaveSource::DragAndDrop(None), bytes, LoadOptions::Clear);
426 }
427
428 Ok(())
429 }
430
431 pub fn load_wave_from_url(
432 &mut self,
433 url: String,
434 load_options: LoadOptions,
435 force_switch: bool,
436 file_index: Option<usize>,
437 ) {
438 #[cfg(all(not(target_arch = "wasm32"), feature = "https"))]
439 crate::async_util::ensure_rustls_crypto_provider();
440 if file_index.is_some() {
441 self.user.selected_server_file_index = file_index;
442 *self.surver_selected_file.borrow_mut() = file_index;
443 }
444
445 match url_to_wavesource(&url) {
446 #[cfg(not(target_arch = "wasm32"))]
449 Some(WaveSource::Cxxrtl(kind)) => {
450 self.connect_to_cxxrtl(kind, load_options != LoadOptions::Clear);
451 }
452 _ => {
455 let sender = self.channels.msg_sender.clone();
456 let url_ = url.clone();
457 info!("Loading wave from url: {url}");
458 perform_async_work(async move {
459 let maybe_response = reqwest::get(&url)
460 .map(|e| e.with_context(|| format!("Failed fetch download {url}")))
461 .await;
462 let response: reqwest::Response = match maybe_response {
463 Ok(r) => r,
464 Err(e) => {
465 error!("{e:?}");
466 return;
467 }
468 };
469
470 if let Some(value) = response.headers().get(HTTP_SERVER_KEY)
472 && matches!(value.to_str(), Ok(HTTP_SERVER_VALUE_SURFER))
473 {
474 match load_options {
475 LoadOptions::Clear => {
476 info!("Connecting to a surfer server at: {url}");
477 get_server_status(sender.clone(), url.clone(), 0);
479 if let Some(file_index) = file_index {
481 get_hierarchy_from_server(
482 sender.clone(),
483 url,
484 load_options,
485 file_index,
486 );
487 }
488 }
489 LoadOptions::KeepAvailable | LoadOptions::KeepAll => {
490 if let Some(file_index) = file_index {
492 if force_switch {
493 get_hierarchy_from_server(
494 sender.clone(),
495 url,
496 load_options,
497 file_index,
498 );
499 } else {
500 info!("Reloading from surver instance at: {url}");
501 server_reload(
502 sender.clone(),
503 url,
504 load_options,
505 file_index,
506 );
507 }
508 } else if force_switch {
509 get_server_status(sender.clone(), url.clone(), 0);
511 } else {
512 warn!(
513 "Cannot reload from surver instance without a selected file index"
514 );
515 }
516 }
517 }
518 return;
519 }
520
521 let bytes = response
523 .bytes()
524 .map(|e| e.with_context(|| format!("Failed to download {url}")))
525 .await;
526
527 let bytes = match bytes {
528 Ok(bytes) => bytes,
529 Err(e) => {
530 error!("{e:?}");
531 return;
532 }
533 };
534 checked_send(&sender, Message::FileDownloaded(url, bytes, load_options));
535 });
536
537 self.progress_tracker =
538 Some(LoadProgress::new(LoadProgressStatus::Downloading(url_)));
539 }
540 }
541 }
542
543 pub fn load_transactions_from_file(
544 &mut self,
545 filename: camino::Utf8PathBuf,
546 load_options: LoadOptions,
547 ) -> Result<()> {
548 info!("Loading a transaction file: {filename}");
549 let sender = self.channels.msg_sender.clone();
550 let source = WaveSource::File(filename.clone());
551 let format = WaveFormat::Ftr;
552
553 let result = ftr_parser::parse::parse_ftr(filename.into_std_path_buf());
554
555 info!("Done with loading ftr file");
556
557 match result {
558 Ok(ftr) => checked_send(
559 &sender,
560 Message::TransactionStreamsLoaded(
561 source,
562 format,
563 TransactionContainer { inner: ftr },
564 load_options,
565 ),
566 ),
567 Err(e) => error!("{e:?}"),
568 };
569 Ok(())
570 }
571 pub fn load_transactions_from_bytes(
572 &mut self,
573 source: WaveSource,
574 bytes: Vec<u8>,
575 load_options: LoadOptions,
576 ) {
577 let sender = self.channels.msg_sender.clone();
578
579 let result = parse::parse_ftr_from_bytes(bytes);
580
581 info!("Done with loading ftr file");
582
583 match result {
584 Ok(ftr) => checked_send(
585 &sender,
586 Message::TransactionStreamsLoaded(
587 source,
588 WaveFormat::Ftr,
589 TransactionContainer { inner: ftr },
590 load_options,
591 ),
592 ),
593 Err(e) => error!("{e:?}"),
594 };
595 }
596
597 pub fn server_status_to_progress(&mut self, server: &str, file_info: &SurverFileInfo) {
599 let body_loaded = self
601 .user
602 .waves
603 .as_ref()
604 .is_some_and(|w| w.inner.body_loaded());
605 if !body_loaded {
606 let source = WaveSource::Url(server.to_string());
608 let sender = self.channels.msg_sender.clone();
609 self.progress_tracker = Some(LoadProgress::new(LoadProgressStatus::ReadingBody(
610 source,
611 file_info.bytes,
612 Arc::new(AtomicU64::new(file_info.bytes_loaded)),
613 )));
614 get_server_status(sender, server.to_string(), 250);
616 }
617 }
618
619 pub fn connect_to_cxxrtl(&mut self, kind: CxxrtlKind, keep_variables: bool) {
620 let sender = self.channels.msg_sender.clone();
621
622 self.progress_tracker = Some(LoadProgress::new(LoadProgressStatus::Connecting(format!(
623 "{kind}"
624 ))));
625
626 let task = async move {
627 let container = match &kind {
628 #[cfg(not(target_arch = "wasm32"))]
629 CxxrtlKind::Tcp { url } => {
630 CxxrtlContainer::new_tcp(url, self.channels.msg_sender.clone()).await
631 }
632 #[cfg(target_arch = "wasm32")]
633 CxxrtlKind::Tcp { .. } => {
634 error!("Cxxrtl tcp is not supported om wasm");
635 return;
636 }
637 #[cfg(not(target_arch = "wasm32"))]
638 CxxrtlKind::Mailbox => {
639 error!("CXXRTL mailboxes are only supported on wasm for now");
640 return;
641 }
642 #[cfg(target_arch = "wasm32")]
643 CxxrtlKind::Mailbox => CxxrtlContainer::new_wasm_mailbox(sender.clone()).await,
644 };
645
646 let load_options = if keep_variables {
647 LoadOptions::KeepAvailable
648 } else {
649 LoadOptions::Clear
650 };
651 match container {
652 Ok(c) => checked_send(
653 &sender,
654 Message::WavesLoaded(
655 WaveSource::Cxxrtl(kind),
656 WaveFormat::CxxRtl,
657 Box::new(WaveContainer::Cxxrtl(Box::new(Mutex::new(c)))),
658 load_options,
659 ),
660 ),
661 Err(e) => error!("{e:?}"),
662 };
663 };
664 #[cfg(not(target_arch = "wasm32"))]
665 futures::executor::block_on(task);
666 #[cfg(target_arch = "wasm32")]
667 wasm_bindgen_futures::spawn_local(task);
668 }
669
670 pub fn load_wave_from_bytes(
671 &mut self,
672 source: WaveSource,
673 bytes: Vec<u8>,
674 load_options: LoadOptions,
675 ) {
676 let start = web_time::Instant::now();
677 let sender = self.channels.msg_sender.clone();
678 let source_copy = source.clone();
679 perform_work(move || {
680 let header_result =
681 wellen::viewers::read_header(Cursor::new(bytes), &WELLEN_SURFER_DEFAULT_OPTIONS)
682 .map_err(|e| anyhow!("{e:?}"))
683 .with_context(|| format!("Failed to parse wave file: {source}"));
684
685 let header = match header_result {
686 Ok(header) => header,
687 Err(e) => {
688 error!("{e:?}");
689 return;
690 }
691 };
692 checked_send(
693 &sender,
694 Message::WaveHeaderLoaded(
695 start,
696 source,
697 load_options,
698 HeaderResult::LocalBytes(Box::new(header)),
699 ),
700 );
701 });
702
703 self.progress_tracker = Some(LoadProgress::new(LoadProgressStatus::ReadingHeader(
704 source_copy,
705 )));
706 }
707
708 fn get_thread_pool() -> Option<rayon::ThreadPool> {
709 match rayon::ThreadPoolBuilder::new().build() {
712 Ok(pool) => Some(pool),
713 Err(e) => {
714 warn!("failed to create thread pool: {e:?}");
716 None
717 }
718 }
719 }
720
721 pub fn load_wave_body<R: std::io::BufRead + std::io::Seek + Sync + Send + 'static>(
722 &mut self,
723 source: WaveSource,
724 cont: wellen::viewers::ReadBodyContinuation<R>,
725 body_len: u64,
726 hierarchy: Arc<wellen::Hierarchy>,
727 ) {
728 let start = web_time::Instant::now();
729 let sender = self.channels.msg_sender.clone();
730 let source_copy = source.clone();
731 let progress = Arc::new(AtomicU64::new(0));
732 let progress_copy = progress.clone();
733 let pool = Self::get_thread_pool();
734
735 perform_work(move || {
736 let action = || {
737 let p = Some(progress_copy);
738 let body_result = wellen::viewers::read_body(cont, &hierarchy, p)
739 .map_err(|e| anyhow!("{e:?}"))
740 .with_context(|| format!("Failed to parse body of wave file: {source}"));
741
742 let body = match body_result {
743 Ok(body) => body,
744 Err(e) => {
745 error!("{e:?}");
746 return;
747 }
748 };
749 checked_send(
750 &sender,
751 Message::WaveBodyLoaded(start, source, BodyResult::Local(body)),
752 );
753 };
754 if let Some(pool) = pool {
755 pool.install(action);
756 } else {
757 action();
758 }
759 });
760
761 self.progress_tracker = Some(LoadProgress::new(LoadProgressStatus::ReadingBody(
762 source_copy,
763 body_len,
764 progress,
765 )));
766 }
767
768 pub fn load_variables(&mut self, cmd: LoadSignalsCmd) {
769 let (signals, from_unique_id, payload) = cmd.destruct();
770 if signals.is_empty() {
771 return;
772 }
773 let num_signals = signals.len() as u64;
774 let start = web_time::Instant::now();
775 let sender = self.channels.msg_sender.clone();
776 let max_url_length = self.user.config.max_url_length;
777 match payload {
778 LoadSignalPayload::Local(mut source, hierarchy) => {
779 let pool = Self::get_thread_pool();
780
781 perform_work(move || {
782 let action = || {
783 let loaded = source.load_signals(&signals, &hierarchy, true);
784 let res = LoadSignalsResult::local(source, loaded, from_unique_id);
785 checked_send(&sender, Message::SignalsLoaded(start, res));
786 };
787 if let Some(pool) = pool {
788 pool.install(action);
789 } else {
790 action();
791 }
792 });
793 }
794 LoadSignalPayload::Remote(server, file_index) => {
795 perform_async_work(async move {
796 let res =
797 crate::remote::get_signals(
798 server.clone(),
799 &signals,
800 max_url_length,
801 file_index,
802 )
803 .await
804 .map_err(|e| anyhow!("{e:?}"))
805 .with_context(|| {
806 format!(
807 "Failed to retrieve signals from remote server {server} file index {file_index}"
808 )
809 });
810
811 let loaded = match res {
812 Ok(loaded) => loaded,
813 Err(e) => {
814 error!("{e:?}");
815 return;
816 }
817 };
818 let res = LoadSignalsResult::remote(server, loaded, from_unique_id);
819 checked_send(&sender, Message::SignalsLoaded(start, res));
820 });
821 }
822 }
823
824 self.progress_tracker = Some(LoadProgress::new(LoadProgressStatus::LoadingVariables(
825 num_signals,
826 )));
827 }
828}
829
830pub fn draw_progress_information(ui: &mut egui::Ui, progress_data: &LoadProgress) {
831 match &progress_data.progress {
832 LoadProgressStatus::Connecting(url) => {
833 ui.horizontal(|ui| {
834 ui.spinner();
835 ui.monospace(format!("Connecting {url}"));
836 });
837 }
838 LoadProgressStatus::Downloading(url) => {
839 ui.horizontal(|ui| {
840 ui.spinner();
841 ui.monospace(format!("Downloading {url}"));
842 });
843 }
844 LoadProgressStatus::ReadingHeader(source) => {
845 ui.spinner();
846 ui.monospace(format!("Loading variable names from {source}"));
847 }
848 LoadProgressStatus::ReadingBody(source, 0, _) => {
849 ui.spinner();
850 ui.monospace(format!("Loading variable change data from {source}"));
851 }
852 LoadProgressStatus::LoadingVariables(num) => {
853 ui.spinner();
854 ui.monospace(format!("Loading {num} variables"));
855 }
856 LoadProgressStatus::ReadingBody(source, total, bytes_done) => {
857 let num_bytes = bytes_done.load(std::sync::atomic::Ordering::SeqCst);
858 let progress = num_bytes as f32 / *total as f32;
859 ui.monospace(format!(
860 "Loading variable change data from {source}. {} / {}",
861 bytesize::ByteSize::b(num_bytes),
862 bytesize::ByteSize::b(*total),
863 ));
864 let progress_bar = egui::ProgressBar::new(progress)
865 .show_percentage()
866 .desired_width(300.);
867 ui.add(progress_bar);
868 }
869 }
870}