URI: 
       Implemented async ssh + modem. - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
  HTML git clone https://git.drkhsh.at/icy_draw.git
   DIR Log
   DIR Files
   DIR Refs
   DIR README
       ---
   DIR commit cb69b11a334def21561eb8755933fb7584b02870
   DIR parent d647d762e83feead015ca89f41b5ae263bf1ae8d
  HTML Author: Mike Krüger <mkrueger@posteo.de>
       Date:   Wed,  1 May 2024 09:40:58 +0200
       
       Implemented async ssh + modem.
       
       Diffstat:
         M crates/icy_term/Cargo.toml          |       1 -
         M crates/icy_term/src/data/modem.rs   |      50 ++++++++++++++-----------------
         M crates/icy_term/src/ui/app.rs       |       3 ++-
         M crates/icy_term/src/ui/dialogs/dia… |       6 +++---
         M crates/icy_term/src/ui/dialogs/set… |      37 ++++++++++++++++---------------
         M crates/icy_term/src/ui/mod.rs       |       4 ++--
         M crates/icy_term/src/ui/terminal_th… |     143 ++++++++++++++++---------------
       
       7 files changed, 122 insertions(+), 122 deletions(-)
       ---
   DIR diff --git a/crates/icy_term/Cargo.toml b/crates/icy_term/Cargo.toml
       @@ -27,7 +27,6 @@ versions = "6.1.0"
        regex = { workspace = true }
        github_release_check = "0.2.1"
        semver = { workspace = true }
       -serial = "0.4.0"
        
        #sound
        rodio = { version = "0.17.1" , default-features = false, features = [] }
   DIR diff --git a/crates/icy_term/src/data/modem.rs b/crates/icy_term/src/data/modem.rs
       @@ -1,17 +1,17 @@
        use std::io::Write;
        
       -use serial::{CharSize, FlowControl, StopBits};
       +use icy_net::serial::{CharSize, FlowControl, Parity, StopBits};
        
        use crate::TerminalResult;
        
        #[derive(Clone, Debug, PartialEq)]
        pub struct Modem {
            pub device: String,
       -    pub baud_rate: usize,
       +    pub baud_rate: u32,
        
            pub char_size: CharSize,
            pub stop_bits: StopBits,
       -    pub parity: serial::Parity,
       +    pub parity: Parity,
        
            pub flow_control: FlowControl,
        
       @@ -35,22 +35,22 @@ impl Modem {
                file.write_all(format!("char_size = {cs}\n").as_bytes())?;
        
                let cs = match self.stop_bits {
       -            StopBits::Stop1 => 1,
       -            StopBits::Stop2 => 2,
       +            StopBits::One => 1,
       +            StopBits::Two => 2,
                };
                file.write_all(format!("stop_bits = {cs}\n").as_bytes())?;
        
                let cs = match self.parity {
       -            serial::Parity::ParityNone => "None",
       -            serial::Parity::ParityOdd => "Odd",
       -            serial::Parity::ParityEven => "Even",
       +            Parity::None => "None",
       +            Parity::Odd => "Odd",
       +            Parity::Even => "Even",
                };
                file.write_all(format!("parity = \"{cs}\"\n").as_bytes())?;
        
                let cs = match self.flow_control {
       -            FlowControl::FlowNone => "None",
       -            FlowControl::FlowSoftware => "Software",
       -            FlowControl::FlowHardware => "Hardware",
       +            FlowControl::None => "None",
       +            FlowControl::XonXoff => "Software",
       +            FlowControl::RtsCts => "Hardware",
                };
                file.write_all(format!("flow_control = \"{cs}\"\n").as_bytes())?;
                file.write_all(format!("init_string = \"{}\"\n", self.init_string).as_bytes())?;
       @@ -70,7 +70,7 @@ impl Modem {
                        }
                        "baud_rate" => {
                            if let toml::Value::Integer(i) = v {
       -                        result.baud_rate = *i as usize;
       +                        result.baud_rate = *i as u32;
                            }
                        }
                        "char_size" => {
       @@ -79,7 +79,6 @@ impl Modem {
                                    5 => CharSize::Bits5,
                                    6 => CharSize::Bits6,
                                    7 => CharSize::Bits7,
       -                            //8 => CharSize::Bits8,
                                    _ => CharSize::Bits8,
                                };
                            }
       @@ -87,29 +86,26 @@ impl Modem {
                        "stop_bits" => {
                            if let toml::Value::Integer(i) = v {
                                result.stop_bits = match i {
       -                            //1 => StopBits::Stop1,
       -                            2 => StopBits::Stop2,
       -                            _ => StopBits::Stop1,
       +                            2 => StopBits::Two,
       +                            _ => StopBits::One,
                                };
                            }
                        }
                        "parity" => {
                            if let toml::Value::String(s) = v {
                                result.parity = match s.as_str() {
       -                            //"None" => serial::Parity::ParityNone,
       -                            "Odd" => serial::Parity::ParityOdd,
       -                            "Even" => serial::Parity::ParityEven,
       -                            _ => serial::Parity::ParityNone,
       +                            "Odd" => Parity::Odd,
       +                            "Even" => Parity::Even,
       +                            _ => Parity::None,
                                };
                            }
                        }
                        "flow_control" => {
                            if let toml::Value::String(s) = v {
                                result.flow_control = match s.as_str() {
       -                            // "None" => FlowControl::FlowNone,
       -                            "Software" => FlowControl::FlowSoftware,
       -                            "Hardware" => FlowControl::FlowHardware,
       -                            _ => FlowControl::FlowNone,
       +                            "Software" => FlowControl::XonXoff,
       +                            "Hardware" => FlowControl::RtsCts,
       +                            _ => FlowControl::None,
                                };
                            }
                        }
       @@ -139,9 +135,9 @@ impl Default for Modem {
                    device: "/dev/ttyS0".to_string(),
                    baud_rate: 9600,
                    char_size: CharSize::Bits8,
       -            stop_bits: StopBits::Stop1,
       -            parity: serial::Parity::ParityNone,
       -            flow_control: FlowControl::FlowNone,
       +            stop_bits: StopBits::One,
       +            parity: Parity::None,
       +            flow_control: FlowControl::None,
                    init_string: "ATZ".to_string(),
                    dial_string: "ATDT".to_string(),
                }
   DIR diff --git a/crates/icy_term/src/ui/app.rs b/crates/icy_term/src/ui/app.rs
       @@ -204,7 +204,8 @@ impl eframe::App for MainWindow {
                                if state.is_finished {
                                    self.set_mode(MainWindowMode::ShowTerminal);
                                } else {
       -                            let _ = self.tx.send(super::connect::SendData::CancelTransfer);
       +                            println!("send cancel transfer") ;
       +                            self.send_data(super::connect::SendData::CancelTransfer);
                                }
                            }
                            dialogs::up_download_dialog::FileTransferDialogAction::Close => {
   DIR diff --git a/crates/icy_term/src/ui/dialogs/dialing_directory_dialog.rs b/crates/icy_term/src/ui/dialogs/dialing_directory_dialog.rs
       @@ -7,7 +7,7 @@ use eframe::{
        use egui::{Align, Id, ImageButton, Key, Rect};
        use i18n_embed_fl::fl;
        use icy_engine::ansi::{BaudEmulation, MusicOption};
       -use icy_net::{telnet::TerminalEmulation, ConnectionType};
       +use icy_net::telnet::TerminalEmulation;
        
        use crate::{
            addresses::{self, Address},
       @@ -633,7 +633,7 @@ impl DialogState {
                                }
                            });
                        ui.end_row();
       -
       +/* Currenlty unsupported by the ssh backend :(
                        if adr.protocol == ConnectionType::SSH {
                            // Port row
                            ui.with_layout(Layout::right_to_left(egui::Align::Center), |ui| {
       @@ -641,7 +641,7 @@ impl DialogState {
                            });
                            ui.add(TextEdit::singleline(&mut adr.proxy_command.to_string()));
                            ui.end_row();
       -                }
       +                }*/
                    });
            }
        
   DIR diff --git a/crates/icy_term/src/ui/dialogs/settings_dialog.rs b/crates/icy_term/src/ui/dialogs/settings_dialog.rs
       @@ -2,6 +2,7 @@ use eframe::egui::{self, RichText};
        use egui::{Layout, TextEdit, Vec2};
        use i18n_embed_fl::fl;
        use icy_engine_gui::show_monitor_settings;
       +use icy_net::serial::{CharSize, Parity, StopBits};
        
        use crate::{
            ui::{MainWindowMode, MainWindowState},
       @@ -285,35 +286,35 @@ fn show_modem_settings(state: &MainWindowState, ui: &mut egui::Ui) -> Option<Mes
        
                    ui.horizontal(|ui| {
                        let txt = match modem.char_size {
       -                    serial::CharSize::Bits5 => "5",
       -                    serial::CharSize::Bits6 => "6",
       -                    serial::CharSize::Bits7 => "7",
       -                    serial::CharSize::Bits8 => "8",
       +                    CharSize::Bits5 => "5",
       +                    CharSize::Bits6 => "6",
       +                    CharSize::Bits7 => "7",
       +                    CharSize::Bits8 => "8",
                        };
                        egui::ComboBox::from_id_source("combobox1").selected_text(RichText::new(txt)).show_ui(ui, |ui| {
       -                    ui.selectable_value(&mut modem.char_size, serial::CharSize::Bits5, "5");
       -                    ui.selectable_value(&mut modem.char_size, serial::CharSize::Bits6, "6");
       -                    ui.selectable_value(&mut modem.char_size, serial::CharSize::Bits7, "7");
       -                    ui.selectable_value(&mut modem.char_size, serial::CharSize::Bits8, "8");
       +                    ui.selectable_value(&mut modem.char_size, CharSize::Bits5, "5");
       +                    ui.selectable_value(&mut modem.char_size, CharSize::Bits6, "6");
       +                    ui.selectable_value(&mut modem.char_size, CharSize::Bits7, "7");
       +                    ui.selectable_value(&mut modem.char_size, CharSize::Bits8, "8");
                        });
        
                        let txt = match modem.stop_bits {
       -                    serial::StopBits::Stop1 => "1",
       -                    serial::StopBits::Stop2 => "2",
       +                    StopBits::One => "1",
       +                    StopBits::Two => "2",
                        };
                        egui::ComboBox::from_id_source("combobox2").selected_text(RichText::new(txt)).show_ui(ui, |ui| {
       -                    ui.selectable_value(&mut modem.stop_bits, serial::StopBits::Stop1, "1");
       -                    ui.selectable_value(&mut modem.stop_bits, serial::StopBits::Stop2, "2");
       +                    ui.selectable_value(&mut modem.stop_bits, StopBits::One, "1");
       +                    ui.selectable_value(&mut modem.stop_bits, StopBits::Two, "2");
                        });
                        let txt = match modem.parity {
       -                    serial::Parity::ParityNone => "None",
       -                    serial::Parity::ParityOdd => "Odd",
       -                    serial::Parity::ParityEven => "Even",
       +                    Parity::None => "None",
       +                    Parity::Odd => "Odd",
       +                    Parity::Even => "Even",
                        };
                        egui::ComboBox::from_id_source("combobox3").selected_text(RichText::new(txt)).show_ui(ui, |ui| {
       -                    ui.selectable_value(&mut modem.parity, serial::Parity::ParityNone, "None");
       -                    ui.selectable_value(&mut modem.parity, serial::Parity::ParityOdd, "Odd");
       -                    ui.selectable_value(&mut modem.parity, serial::Parity::ParityEven, "Even");
       +                    ui.selectable_value(&mut modem.parity, Parity::None, "None");
       +                    ui.selectable_value(&mut modem.parity, Parity::Odd, "Odd");
       +                    ui.selectable_value(&mut modem.parity, Parity::Even, "Even");
                        });
                    });
                    ui.end_row();
   DIR diff --git a/crates/icy_term/src/ui/mod.rs b/crates/icy_term/src/ui/mod.rs
       @@ -54,7 +54,7 @@ macro_rules! check_error {
            }};
        }
        
       -#[derive(Clone, PartialEq, Eq, Default)]
       +#[derive(Clone, PartialEq, Eq, Default, Debug)]
        pub enum MainWindowMode {
            ShowTerminal,
            #[default]
       @@ -139,6 +139,7 @@ impl MainWindow {
            }
        
            pub fn set_mode(&mut self, mode: MainWindowMode) {
       +        println!("Setting mode: {:?}", mode);
                self.state.mode = mode;
            }
        
       @@ -304,7 +305,6 @@ impl MainWindow {
                }
                self.buffer_parser = get_parser(&data.term_caps.terminal, data.use_ansi_music, PathBuf::new());
                let (update_thread_handle, tx, rx) = crate::ui::terminal_thread::start_update_thread(ctx, data, self.buffer_update_thread.clone());
       -
                self.update_thread_handle = Some(update_thread_handle);
                self.tx = tx;
                self.rx = rx;
   DIR diff --git a/crates/icy_term/src/ui/terminal_thread.rs b/crates/icy_term/src/ui/terminal_thread.rs
       @@ -10,12 +10,7 @@ use icy_engine::{ansi::MusicOption, rip::bgi::MouseField, BufferParser, Caret};
        use icy_engine_gui::BufferView;
        use icy_net::{
            // modem::{ModemConfiguration, ModemConnection, Serial},
       -    protocol::{TransferProtocolType, TransferState},
       -    raw::RawConnection,
       -    // ssh::{Credentials, SSHConnection},
       -    telnet::{TelnetConnection, TerminalEmulation},
       -    Connection,
       -    NullConnection,
       +    modem::{ModemConfiguration, ModemConnection}, protocol::{Protocol, TransferProtocolType, TransferState}, raw::RawConnection, serial::Serial, ssh::{Credentials, SSHConnection}, telnet::{TelnetConnection, TerminalEmulation}, Connection, NullConnection
        };
        use std::{collections::VecDeque, mem, path::PathBuf, sync::Arc, thread};
        use tokio::sync::mpsc;
       @@ -53,19 +48,17 @@ pub struct TerminalThread {
        impl TerminalThread {
            pub async fn update_state(
                &mut self,
       -        ctx: &egui::Context,
                connection: &mut ConnectionThreadData,
                buffer_parser: &mut dyn BufferParser,
                data: &[u8],
            ) -> TerminalResult<(u64, usize)> {
                self.sound_thread.lock().update_state()?;
       -        let res = self.update_buffer(ctx, connection, buffer_parser, data).await;
       +        let res = self.update_buffer( connection, buffer_parser, data).await;
                Ok(res)
            }
        
            async fn update_buffer(
                &mut self,
       -        ctx: &egui::Context,
                connection: &mut ConnectionThreadData,
                buffer_parser: &mut dyn BufferParser,
                data: &[u8],
       @@ -91,7 +84,6 @@ impl TerminalThread {
        
                    if update {
                        self.buffer_view.lock().get_edit_state_mut().set_is_buffer_dirty();
       -                ctx.request_repaint();
                        mem::swap(&mut caret, self.buffer_view.lock().get_caret_mut());
                        return (ms as u64, 0);
                    }
       @@ -117,7 +109,6 @@ impl TerminalThread {
        
                    if p {
                        self.buffer_view.lock().get_edit_state_mut().set_is_buffer_dirty();
       -                ctx.request_repaint();
                        return (ms as u64, idx);
                    }
                }
       @@ -215,6 +206,7 @@ pub fn start_update_thread(
                                Ok(com) => com,
                                Err(err) => {
                                    let _ = tx.send(SendData::Disconnect);
       +                            log::error!("run_update_thread::open_connection: {err}");
                                    println(&update_thread, &mut buffer_parser, &format!("\n{err}\n"));
                                    update_thread.lock().is_connected = false;
                                    return;
       @@ -234,36 +226,48 @@ pub fn start_update_thread(
                                rx: rx2,
                            };
                            let mut data = [0; 1024 * 64];
       -
                            loop {
                                tokio::select! {
       -                            Ok(size) = connection.com.read(&mut data) => {
       -                                let mut idx = 0;
       -                                while idx < size {
       -                                    let update_state = update_thread.lock().update_state(&ctx, &mut connection, &mut *buffer_parser, &data[idx..size]).await;
       -                                    match &update_state {
       -                                        Err(err) => {
       -                                            println(&update_thread, &mut buffer_parser, &format!("\n{err}\n"));
       -                                            log::error!("run_update_thread::update_state: {err}");
       -                                            idx = size;
       -                                        }
       -                                        Ok((sleep_ms, parsed_data)) => {
       -                                            let data = buffer_parser.get_picture_data();
       -                                            if data.is_some() {
       -                                                update_thread.lock().mouse_field = buffer_parser.get_mouse_fields();
       -                                                update_thread.lock().buffer_view.lock().set_reference_image(data);
       -                                            }
       -                                            if *sleep_ms > 0 {
       -                                                thread::sleep(Duration::from_millis(*sleep_ms));
       +                            read_data = connection.com.read(&mut data) => {
       +                                match read_data {
       +                                    Err(err) => {
       +                                        println(&update_thread, &mut buffer_parser, &format!("\n{err}\n"));
       +                                        log::error!("run_update_thread::read_data: {err}");
       +                                        update_thread.lock().is_connected = false;
       +                                        break;
       +                                    }
       +                                    Ok(size) => {
       +                                        if size > 0 {
       +                                            let mut idx = 0;
       +                                            while idx < size {
       +                                                let update_state = update_thread.lock().update_state(&mut connection, &mut *buffer_parser, &data[idx..size]).await;
       +                                                match &update_state {
       +                                                    Err(err) => {
       +                                                        println(&update_thread, &mut buffer_parser, &format!("\n{err}\n"));
       +                                                        log::error!("run_update_thread::update_state: {err}");
       +                                                        idx = size;
       +                                                    }
       +                                                    Ok((sleep_ms, parsed_data)) => {
       +                                                        let data = buffer_parser.get_picture_data();
       +                                                        if data.is_some() {
       +                                                            update_thread.lock().mouse_field = buffer_parser.get_mouse_fields();
       +                                                            update_thread.lock().buffer_view.lock().set_reference_image(data);
       +                                                        }
       +                                                        if *sleep_ms > 0 {
       +                                                            thread::sleep(Duration::from_millis(*sleep_ms));
       +                                                        }
       +                                                        idx += *parsed_data;
       +                                                    }
       +                                                }
                                                    }
       -                                            idx += *parsed_data;
       +                                            ctx.request_repaint();
       +                                            update_thread.lock().buffer_view.lock().get_buffer_mut().update_hyperlinks();
                                                }
                                            }
                                        }
       -                                update_thread.lock().buffer_view.lock().get_buffer_mut().update_hyperlinks();
                                    }
                                    Some(data) = connection.rx.recv() => {
       -                                let _ = handle_receive(&mut connection, data, &update_thread).await;
       +                                let _ = handle_receive(&ctx, &mut connection, data, &update_thread).await;
                                    }
                                };
                            }
       @@ -291,22 +295,22 @@ async fn open_connection(connection_data: &OpenConnectionData) -> Res<Box<dyn Co
            if connection_data.term_caps.window_size.0 == 0 {
                return Ok(Box::new(NullConnection {}));
            }
       +    println!("Opening connection: {:?}", connection_data.connection_type);
        
            match connection_data.connection_type {
                icy_net::ConnectionType::Raw => Ok(Box::new(RawConnection::open(&connection_data.address, connection_data.timeout.clone()).await?)),
                icy_net::ConnectionType::Telnet => Ok(Box::new(
                    TelnetConnection::open(&connection_data.address, connection_data.term_caps.clone(), connection_data.timeout.clone()).await?,
                )),
       -        /*
       -                icy_net::ConnectionType::SSH => Ok(Box::new(SSHConnection::open(
       -                    &connection_data.address,
       -                    connection_data.term_caps.clone(),
       -                    Credentials {
       -                        user_name: connection_data.user_name.clone(),
       -                        password: connection_data.password.clone(),
       -                        proxy_command: connection_data.proxy_command.clone(),
       -                    },
       -                ).await?)),
       +        icy_net::ConnectionType::SSH => Ok(Box::new(SSHConnection::open(
       +            &connection_data.address,
       +            connection_data.term_caps.clone(),
       +            Credentials {
       +                user_name: connection_data.user_name.clone(),
       +                password: connection_data.password.clone(),
       +                proxy_command: connection_data.proxy_command.clone(),
       +            },
       +        ).await?)), 
                        icy_net::ConnectionType::Modem => {
                            let Some(m) = &connection_data.modem else {
                                return Err("Modem configuration is required for modem connections".into());
       @@ -323,16 +327,16 @@ async fn open_connection(connection_data: &OpenConnectionData) -> Res<Box<dyn Co
                                init_string: m.init_string.clone(),
                                dial_string: m.dial_string.clone(),
                            };
       -                    Ok(Box::new(ModemConnection::open(serial, modem, connection_data.address.clone())?))
       -                }
       -                icy_net::ConnectionType::Websocket => Ok(Box::new(icy_net::websocket::connect(&connection_data.address, false).await?)),
       -                icy_net::ConnectionType::SecureWebsocket => Ok(Box::new(icy_net::websocket::connect(&connection_data.address, true).await?)),
       -        */
       -        _ => Ok(Box::new(NullConnection {})),
       +                    Ok(Box::new(ModemConnection::open(serial, modem, connection_data.address.clone()).await?))
       +                } 
       +        icy_net::ConnectionType::Websocket => Ok(Box::new(icy_net::websocket::connect(&connection_data.address, false).await?)),
       +        icy_net::ConnectionType::SecureWebsocket => Ok(Box::new(icy_net::websocket::connect(&connection_data.address, true).await?)),
       +      
       +        _ => panic!("Unsupported connection type"),
            }
        }
        
       -async fn handle_receive(c: &mut ConnectionThreadData, data: SendData, update_thread: &Arc<Mutex<TerminalThread>>) -> Res<()> {
       +async fn handle_receive(ctx: &egui::Context, c: &mut ConnectionThreadData, data: SendData, update_thread: &Arc<Mutex<TerminalThread>>) -> Res<()> {
            match data {
                SendData::Data(buf) => {
                    c.com.send(&buf).await?;
       @@ -347,15 +351,19 @@ async fn handle_receive(c: &mut ConnectionThreadData, data: SendData, update_thr
                }
        
                SendData::Upload(protocol, files) => {
       -            if let Err(err) = upload(c, protocol, files, update_thread).await {
       +            println!("upload !");
       +            if let Err(err) = upload(ctx, c, protocol, files, update_thread).await {
                        log::error!("Failed to upload files: {err}");
                    }
       +            println!("upload done !");
                }
        
                SendData::Download(protocol) => {
       -            if let Err(err) = download(c, protocol, update_thread).await {
       +            println!("download !");
       +            if let Err(err) = download(ctx, c, protocol, update_thread).await {
                        log::error!("Failed to download files: {err}");
                    }
       +            println!("download done !");
                }
        
                _ => {}
       @@ -363,35 +371,31 @@ async fn handle_receive(c: &mut ConnectionThreadData, data: SendData, update_thr
            Ok(())
        }
        
       -async fn download(c: &mut ConnectionThreadData, protocol: TransferProtocolType, update_thread: &Arc<Mutex<TerminalThread>>) -> Res<()> {
       +async fn download(ctx: &egui::Context, c: &mut ConnectionThreadData, protocol: TransferProtocolType, update_thread: &Arc<Mutex<TerminalThread>>) -> Res<()> {
            let mut prot = protocol.create();
       -    let mut transfer_state = prot.initiate_recv(&mut *c.com).await?;
       -    while !transfer_state.is_finished {
       -        prot.update_transfer(&mut *c.com, &mut transfer_state).await?;
       -        update_thread.lock().current_transfer = transfer_state.clone();
       -        match c.rx.try_recv() {
       -            Ok(SendData::CancelTransfer) => {
       -                prot.cancel_transfer(&mut *c.com).await?;
       -                break;
       -            }
       -            _ => {}
       -        }
       -    }
       -    copy_downloaded_files(&mut transfer_state)?;
       -    update_thread.lock().current_transfer = transfer_state.clone();
       +    let transfer_state = prot.initiate_recv(&mut *c.com).await?;
       +    file_transfer(ctx, transfer_state, &mut *prot, c, update_thread).await?;
            Ok(())
        }
        
       -async fn upload(c: &mut ConnectionThreadData, protocol: TransferProtocolType, files: Vec<PathBuf>, update_thread: &Arc<Mutex<TerminalThread>>) -> Res<()> {
       +async fn upload(ctx: &egui::Context, c: &mut ConnectionThreadData, protocol: TransferProtocolType, files: Vec<PathBuf>, update_thread: &Arc<Mutex<TerminalThread>>) -> Res<()> {
            let mut prot = protocol.create();
       -    let mut transfer_state = prot.initiate_send(&mut *c.com, &files).await?;
       +    let transfer_state = prot.initiate_send(&mut *c.com, &files).await?;
       +    file_transfer(ctx, transfer_state, &mut *prot, c, update_thread).await?;
       +    Ok(())
       +}
       +
       +async fn file_transfer(ctx: &egui::Context, mut transfer_state: TransferState, prot: &mut dyn Protocol, c: &mut ConnectionThreadData, update_thread: &Arc<Mutex<TerminalThread>>) -> Res<()> {
       +    ctx.request_repaint();
            while !transfer_state.is_finished {
                tokio::select! {
                    Ok(()) = prot.update_transfer(&mut *c.com, &mut transfer_state) => {
       +                update_thread.lock().current_transfer = transfer_state.clone();
                    }
                    data = c.rx.recv() => {
                        match data {
                            Some(SendData::CancelTransfer) => {
       +                        ctx.request_repaint();
                                prot.cancel_transfer(&mut *c.com).await?;
                                break;
                            }
       @@ -400,7 +404,6 @@ async fn upload(c: &mut ConnectionThreadData, protocol: TransferProtocolType, fi
                    }
                };
            }
       -    // needed for potential bi-directional protocols
            copy_downloaded_files(&mut transfer_state)?;
            update_thread.lock().current_transfer = transfer_state.clone();
            Ok(())