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
This commit is contained in:
x3
2026-09-07 17:45:05 +00:00
parent 5422ce17ba
commit 7b50af6bbf
2 changed files with 53 additions and 14 deletions
+10 -2
View File
@@ -142,8 +142,6 @@ void MainWindow::build_ui() {
gtk_container_add(GTK_CONTAINER(window_), vbox); gtk_container_add(GTK_CONTAINER(window_), vbox);
build_menu(); build_menu();
gtk_box_pack_start(GTK_BOX(vbox), toolbar_, FALSE, FALSE, 0);
build_toolbar(); build_toolbar();
gtk_box_pack_start(GTK_BOX(vbox), toolbar_, FALSE, FALSE, 0); 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); GtkWidget* left_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
gtk_widget_set_margin_start(left_header, 8); 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_top(left_header, 6);
gtk_widget_set_margin_bottom(left_header, 6);
GtkWidget* left_title = gtk_label_new(NULL); GtkWidget* left_title = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(left_title), "<b>Snapshots</b>"); gtk_label_set_markup(GTK_LABEL(left_title), "<b>Snapshots</b>");
gtk_widget_set_halign(left_title, GTK_ALIGN_START); gtk_widget_set_halign(left_title, GTK_ALIGN_START);
gtk_box_pack_start(GTK_BOX(left_header), left_title, TRUE, TRUE, 0); 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<MainWindow*>(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), left_header, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(left_box), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(left_box), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), FALSE, FALSE, 0);
+43 -12
View File
@@ -5,6 +5,7 @@
#include <regex> #include <regex>
#include <unistd.h> #include <unistd.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <poll.h>
RsyncManager::RsyncManager(Config& config) : config_(config) {} RsyncManager::RsyncManager(Config& config) : config_(config) {}
@@ -62,22 +63,52 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name,
} }
RsyncProgress prog; RsyncProgress prog;
char buf[4096]; int fd = fileno(pipe);
while (fgets(buf, sizeof(buf), 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; if (cancel_requested_) break;
std::string line(buf);
/* Remove trailing newline */ struct pollfd pfd = {fd, POLLIN, 0};
if (!line.empty() && line.back() == '\n') line.pop_back(); int pret = poll(&pfd, 1, 200);
if (!line.empty() && line.back() == '\r') line.pop_back(); if (pret < 0) break;
if (pret == 0) continue;
{ if (!(pfd.revents & POLLIN)) {
std::lock_guard<std::mutex> lock(log_mutex_); if (pfd.revents & (POLLHUP | POLLERR)) break;
log_buffer_.push_back(line); continue;
} }
parse_progress_line(line, prog); ssize_t n = read(fd, raw, sizeof(raw) - 1);
prog.log_line = line; 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<std::mutex> 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); if (on_progress) on_progress(prog);
} }