From 7b50af6bbfd0ce62de14f2536a5057003912f4bf Mon Sep 17 00:00:00 2001 From: x3 Date: Mon, 7 Sep 2026 17:45:05 +0000 Subject: [PATCH] fix: rsync real-time output + refresh button - Use read() + poll() instead of fgets() to handle rsync \r progress - rsync uses \r for in-place progress updates, fgets() blocked on \n - Added poll() with 200ms timeout for non-blocking read - Added refresh button (view-refresh icon) in snapshot sidebar header - Fixed duplicate build_menu/build_toolbar calls --- src/main_window.cpp | 12 ++++++++-- src/rsync_manager.cpp | 55 +++++++++++++++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/main_window.cpp b/src/main_window.cpp index a84f5f0..a0c62bf 100644 --- a/src/main_window.cpp +++ b/src/main_window.cpp @@ -142,8 +142,6 @@ void MainWindow::build_ui() { gtk_container_add(GTK_CONTAINER(window_), vbox); build_menu(); - gtk_box_pack_start(GTK_BOX(vbox), toolbar_, FALSE, FALSE, 0); - build_toolbar(); gtk_box_pack_start(GTK_BOX(vbox), toolbar_, FALSE, FALSE, 0); @@ -157,11 +155,21 @@ void MainWindow::build_ui() { GtkWidget* left_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); gtk_widget_set_margin_start(left_header, 8); + gtk_widget_set_margin_end(left_header, 4); gtk_widget_set_margin_top(left_header, 6); + gtk_widget_set_margin_bottom(left_header, 6); GtkWidget* left_title = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(left_title), "Snapshots"); gtk_widget_set_halign(left_title, GTK_ALIGN_START); gtk_box_pack_start(GTK_BOX(left_header), left_title, TRUE, TRUE, 0); + + GtkWidget* refresh_btn = gtk_button_new_from_icon_name("view-refresh", GTK_ICON_SIZE_MENU); + gtk_widget_set_tooltip_text(refresh_btn, "Refresh snapshot list"); + g_signal_connect(refresh_btn, "clicked", G_CALLBACK(+[](GtkWidget*, gpointer d) { + static_cast(d)->refresh_snapshot_list(); + }), this); + gtk_box_pack_end(GTK_BOX(left_header), refresh_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(left_box), left_header, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(left_box), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), FALSE, FALSE, 0); diff --git a/src/rsync_manager.cpp b/src/rsync_manager.cpp index 7b6c53a..03e6956 100644 --- a/src/rsync_manager.cpp +++ b/src/rsync_manager.cpp @@ -5,6 +5,7 @@ #include #include #include +#include RsyncManager::RsyncManager(Config& config) : config_(config) {} @@ -62,22 +63,52 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name, } RsyncProgress prog; - char buf[4096]; - while (fgets(buf, sizeof(buf), pipe)) { + int fd = fileno(pipe); + char raw[4096]; + std::string accumulator; + + /* Read raw bytes, split on \r and \n for real-time rsync progress */ + while (true) { if (cancel_requested_) break; - std::string line(buf); - /* Remove trailing newline */ - if (!line.empty() && line.back() == '\n') line.pop_back(); - if (!line.empty() && line.back() == '\r') line.pop_back(); - - { - std::lock_guard lock(log_mutex_); - log_buffer_.push_back(line); + struct pollfd pfd = {fd, POLLIN, 0}; + int pret = poll(&pfd, 1, 200); + if (pret < 0) break; + if (pret == 0) continue; + if (!(pfd.revents & POLLIN)) { + if (pfd.revents & (POLLHUP | POLLERR)) break; + continue; } - parse_progress_line(line, prog); - prog.log_line = line; + ssize_t n = read(fd, raw, sizeof(raw) - 1); + if (n <= 0) break; + raw[n] = '\0'; + + accumulator += raw; + + /* Split on \r and \n */ + size_t pos; + while ((pos = accumulator.find_first_of("\r\n")) != std::string::npos) { + std::string line = accumulator.substr(0, pos); + accumulator.erase(0, pos + 1); + + if (line.empty()) continue; + + { + std::lock_guard lock(log_mutex_); + log_buffer_.push_back(line); + } + + parse_progress_line(line, prog); + prog.log_line = line; + if (on_progress) on_progress(prog); + } + } + + /* Flush remaining */ + if (!accumulator.empty()) { + parse_progress_line(accumulator, prog); + prog.log_line = accumulator; if (on_progress) on_progress(prog); }