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:
+10
-2
@@ -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);
|
||||||
|
|
||||||
|
|||||||
+38
-7
@@ -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,14 +63,36 @@ 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];
|
||||||
if (cancel_requested_) break;
|
std::string accumulator;
|
||||||
std::string line(buf);
|
|
||||||
|
|
||||||
/* Remove trailing newline */
|
/* Read raw bytes, split on \r and \n for real-time rsync progress */
|
||||||
if (!line.empty() && line.back() == '\n') line.pop_back();
|
while (true) {
|
||||||
if (!line.empty() && line.back() == '\r') line.pop_back();
|
if (cancel_requested_) break;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<std::mutex> lock(log_mutex_);
|
std::lock_guard<std::mutex> lock(log_mutex_);
|
||||||
@@ -80,6 +103,14 @@ bool RsyncManager::create_snapshot(const std::string& snapshot_name,
|
|||||||
prog.log_line = line;
|
prog.log_line = line;
|
||||||
if (on_progress) on_progress(prog);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
int status = pclose(pipe);
|
int status = pclose(pipe);
|
||||||
running_ = false;
|
running_ = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user