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);
}