fix: backup progress window + real-time rsync output
- Replace small popup with big backup progress window (750x500) - Shows rsync command in header, real-time output in monospace textview - Progress bar with percentage, Cancel/Close button - Output with color-coded tags (error=red, success=green, cmd=gray italic) - Auto-scrolls to bottom during backup - Fixed live log not updating (stdbuf -oL for unbuffered rsync output) - Fixed double closing brace compilation error - Downloaded proper backup icon
This commit is contained in:
+194
-43
@@ -43,42 +43,65 @@ MainWindow::MainWindow(Config& config)
|
||||
auto* ctx = static_cast<Ctx*>(data);
|
||||
auto* self = ctx->self;
|
||||
const RsyncProgress& p = ctx->prog;
|
||||
if (self->progress_bar_) {
|
||||
|
||||
/* Update backup progress window */
|
||||
if (self->backup_win_) {
|
||||
if (p.bytes_total > 0) {
|
||||
double frac = (double)p.bytes_transferred / p.bytes_total;
|
||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(self->progress_bar_), frac);
|
||||
}
|
||||
if (!p.current_file.empty()) {
|
||||
std::string label = "Syncing: " + p.current_file;
|
||||
gtk_label_set_text(GTK_LABEL(self->progress_label_), label.c_str());
|
||||
gtk_label_set_text(GTK_LABEL(self->live_log_label_), label.c_str());
|
||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(self->backup_progress_), frac);
|
||||
char pct[32];
|
||||
snprintf(pct, sizeof(pct), "%.0f%%", frac * 100);
|
||||
gtk_label_set_text(GTK_LABEL(self->backup_status_label_), pct);
|
||||
}
|
||||
if (!p.log_line.empty()) {
|
||||
const char* tag = nullptr;
|
||||
if (p.log_line.find("[error]") != std::string::npos) tag = "error";
|
||||
else if (p.log_line.find("[done]") != std::string::npos) tag = "success";
|
||||
else if (p.log_line.find("[cmd]") != std::string::npos) tag = "info";
|
||||
else if (p.log_line.find("[done]") != std::string::npos || p.log_line.find("completed") != std::string::npos) tag = "success";
|
||||
else if (p.log_line.find("[cmd]") != std::string::npos) {
|
||||
tag = "cmd";
|
||||
/* Show the rsync command in the header */
|
||||
std::string cmd_display = p.log_line;
|
||||
if (cmd_display.find("[cmd] ") == 0) cmd_display = cmd_display.substr(6);
|
||||
gtk_label_set_text(GTK_LABEL(self->backup_cmd_label_), cmd_display.c_str());
|
||||
}
|
||||
else if (p.log_line.find("[restore") != std::string::npos) tag = "info";
|
||||
self->append_live_log(p.log_line, tag);
|
||||
self->append_backup_line(p.log_line, tag);
|
||||
}
|
||||
if (p.finished) {
|
||||
if (self->progress_window_) {
|
||||
gtk_widget_destroy(self->progress_window_);
|
||||
self->progress_window_ = nullptr;
|
||||
}
|
||||
self->refresh_snapshot_list();
|
||||
if (p.error) {
|
||||
self->append_live_log("[ERROR] " + p.error_message, "error");
|
||||
self->append_backup_line("[ERROR] " + p.error_message, "error");
|
||||
self->tray_.show_notification("x3-Backup", p.error_message, true);
|
||||
gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup failed");
|
||||
gtk_label_set_text(GTK_LABEL(self->backup_status_label_), "Failed");
|
||||
gtk_button_set_label(GTK_BUTTON(self->backup_cancel_btn_), "Close");
|
||||
} else {
|
||||
self->append_live_log("[SUCCESS] Backup completed", "success");
|
||||
self->append_backup_line("[SUCCESS] Backup completed!", "success");
|
||||
self->tray_.show_notification("x3-Backup", "Backup completed successfully");
|
||||
gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup completed");
|
||||
gtk_label_set_text(GTK_LABEL(self->backup_status_label_), "Done");
|
||||
gtk_button_set_label(GTK_BUTTON(self->backup_cancel_btn_), "Close");
|
||||
}
|
||||
self->tray_.set_status("x3-Backup - Idle");
|
||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(self->backup_progress_), 1.0);
|
||||
self->refresh_snapshot_list();
|
||||
}
|
||||
}
|
||||
|
||||
/* Also update live log tab */
|
||||
if (!p.log_line.empty()) {
|
||||
const char* tag = nullptr;
|
||||
if (p.log_line.find("[error]") != std::string::npos) tag = "error";
|
||||
else if (p.log_line.find("[done]") != std::string::npos) tag = "success";
|
||||
else if (p.log_line.find("[cmd]") != std::string::npos) tag = "info";
|
||||
else if (p.log_line.find("[restore") != std::string::npos) tag = "info";
|
||||
self->append_live_log(p.log_line, tag);
|
||||
}
|
||||
if (p.finished) {
|
||||
if (p.error) {
|
||||
gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup failed");
|
||||
} else {
|
||||
gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup completed");
|
||||
}
|
||||
}
|
||||
|
||||
delete ctx;
|
||||
return G_SOURCE_REMOVE;
|
||||
}, ctx);
|
||||
@@ -406,12 +429,6 @@ void MainWindow::build_toolbar() {
|
||||
gtk_box_pack_start(GTK_BOX(tbar), gtk_separator_new(GTK_ORIENTATION_VERTICAL), FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(tbar), mkbtn("Settings", G_CALLBACK(on_settings), this), FALSE, FALSE, 0);
|
||||
|
||||
progress_bar_ = gtk_progress_bar_new();
|
||||
gtk_widget_set_size_request(progress_bar_, 200, -1);
|
||||
gtk_box_pack_end(GTK_BOX(tbar), progress_bar_, FALSE, FALSE, 0);
|
||||
progress_label_ = gtk_label_new("");
|
||||
gtk_box_pack_end(GTK_BOX(tbar), progress_label_, FALSE, FALSE, 4);
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(toolbar_), tbar, FALSE, FALSE, 0);
|
||||
}
|
||||
|
||||
@@ -650,34 +667,168 @@ void MainWindow::show_sudo_dialog(std::function<void(const std::string&)> callba
|
||||
gtk_widget_destroy(dialog);
|
||||
}
|
||||
|
||||
void MainWindow::show_progress_dialog(const std::string& title) {
|
||||
progress_window_ = gtk_window_new(GTK_WINDOW_POPUP);
|
||||
gtk_window_set_title(GTK_WINDOW(progress_window_), title.c_str());
|
||||
gtk_window_set_default_size(GTK_WINDOW(progress_window_), 400, 120);
|
||||
gtk_window_set_position(GTK_WINDOW(progress_window_), GTK_WIN_POS_CENTER);
|
||||
gtk_window_set_keep_above(GTK_WINDOW(progress_window_), TRUE);
|
||||
void MainWindow::open_backup_window(const std::string& description) {
|
||||
if (backup_win_) {
|
||||
/* Clear previous output */
|
||||
GtkTextBuffer* buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(backup_output_view_));
|
||||
gtk_text_buffer_set_text(buf, "", -1);
|
||||
gtk_label_set_text(GTK_LABEL(backup_cmd_label_), "");
|
||||
gtk_label_set_text(GTK_LABEL(backup_status_label_), "Running...");
|
||||
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(backup_progress_), 0.0);
|
||||
gtk_button_set_label(GTK_BUTTON(backup_cancel_btn_), "Cancel");
|
||||
gtk_widget_show_all(backup_win_);
|
||||
return;
|
||||
}
|
||||
|
||||
GtkWidget* vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_widget_set_margin_start(vbox, 16);
|
||||
gtk_widget_set_margin_top(vbox, 16);
|
||||
gtk_container_add(GTK_CONTAINER(progress_window_), vbox);
|
||||
backup_win_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_title(GTK_WINDOW(backup_win_), "x3-Backup - Backup Progress");
|
||||
gtk_window_set_default_size(GTK_WINDOW(backup_win_), 750, 500);
|
||||
gtk_window_set_position(GTK_WINDOW(backup_win_), GTK_WIN_POS_CENTER);
|
||||
gtk_window_set_transient_for(GTK_WINDOW(backup_win_), GTK_WINDOW(window_));
|
||||
gtk_window_set_modal(GTK_WINDOW(backup_win_), TRUE);
|
||||
|
||||
GtkWidget* label = gtk_label_new(title.c_str());
|
||||
gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
|
||||
const char* icon_paths[] = {
|
||||
"icons/x3-backup.png",
|
||||
"/usr/share/icons/hicolor/256x256/apps/x3-backup.png",
|
||||
nullptr
|
||||
};
|
||||
for (int i = 0; icon_paths[i]; ++i) {
|
||||
if (g_file_test(icon_paths[i], G_FILE_TEST_EXISTS)) {
|
||||
gtk_window_set_icon_from_file(GTK_WINDOW(backup_win_), icon_paths[i], nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
progress_bar_ = gtk_progress_bar_new();
|
||||
gtk_box_pack_start(GTK_BOX(vbox), progress_bar_, FALSE, FALSE, 0);
|
||||
GtkWidget* vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
|
||||
gtk_widget_set_margin_start(vbox, 8);
|
||||
gtk_widget_set_margin_end(vbox, 8);
|
||||
gtk_widget_set_margin_top(vbox, 8);
|
||||
gtk_widget_set_margin_bottom(vbox, 8);
|
||||
gtk_container_add(GTK_CONTAINER(backup_win_), vbox);
|
||||
|
||||
progress_label_ = gtk_label_new("Starting...");
|
||||
gtk_box_pack_start(GTK_BOX(vbox), progress_label_, FALSE, FALSE, 0);
|
||||
/* Header */
|
||||
GtkWidget* hdr_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
GtkWidget* hdr_icon = gtk_image_new_from_icon_name("drive-harddisk", GTK_ICON_SIZE_DIALOG);
|
||||
gtk_box_pack_start(GTK_BOX(hdr_box), hdr_icon, FALSE, FALSE, 0);
|
||||
|
||||
gtk_widget_show_all(progress_window_);
|
||||
GtkWidget* hdr_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
|
||||
std::string title_text = description.empty() ? "Creating Backup..." : description;
|
||||
GtkWidget* hdr_title = gtk_label_new(nullptr);
|
||||
char title_markup[512];
|
||||
snprintf(title_markup, sizeof(title_markup), "<b><big>%s</big></big>", title_text.c_str());
|
||||
gtk_label_set_markup(GTK_LABEL(hdr_title), title_markup);
|
||||
gtk_widget_set_halign(hdr_title, GTK_ALIGN_START);
|
||||
gtk_box_pack_start(GTK_BOX(hdr_vbox), hdr_title, FALSE, FALSE, 0);
|
||||
|
||||
backup_cmd_label_ = gtk_label_new("Waiting for rsync...");
|
||||
gtk_widget_set_halign(backup_cmd_label_, GTK_ALIGN_START);
|
||||
PangoAttrList* mono = pango_attr_list_new();
|
||||
pango_attr_list_insert(mono, pango_attr_family_new("monospace"));
|
||||
pango_attr_list_insert(mono, pango_attr_size_new(8000));
|
||||
gtk_label_set_attributes(GTK_LABEL(backup_cmd_label_), mono);
|
||||
pango_attr_list_unref(mono);
|
||||
gtk_box_pack_start(GTK_BOX(hdr_vbox), backup_cmd_label_, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(hdr_box), hdr_vbox, TRUE, TRUE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), hdr_box, FALSE, FALSE, 0);
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(vbox), gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), FALSE, FALSE, 0);
|
||||
|
||||
/* rsync output */
|
||||
GtkWidget* output_scroll = gtk_scrolled_window_new(nullptr, nullptr);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(output_scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
backup_output_view_ = gtk_text_view_new();
|
||||
gtk_text_view_set_editable(GTK_TEXT_VIEW(backup_output_view_), FALSE);
|
||||
gtk_text_view_set_monospace(GTK_TEXT_VIEW(backup_output_view_), TRUE);
|
||||
gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(backup_output_view_), GTK_WRAP_WORD_CHAR);
|
||||
|
||||
/* Color tags */
|
||||
GtkTextBuffer* buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(backup_output_view_));
|
||||
gtk_text_buffer_create_tag(buf, "error", "foreground", "#e74c3c", "weight", PANGO_WEIGHT_BOLD, nullptr);
|
||||
gtk_text_buffer_create_tag(buf, "success", "foreground", "#2ecc71", "weight", PANGO_WEIGHT_BOLD, nullptr);
|
||||
gtk_text_buffer_create_tag(buf, "info", "foreground", "#3498db", nullptr);
|
||||
gtk_text_buffer_create_tag(buf, "cmd", "foreground", "#95a5a6", "style", PANGO_STYLE_ITALIC, nullptr);
|
||||
|
||||
gtk_container_add(GTK_CONTAINER(output_scroll), backup_output_view_);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), output_scroll, TRUE, TRUE, 0);
|
||||
|
||||
/* Progress bar + status */
|
||||
GtkWidget* prog_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
backup_progress_ = gtk_progress_bar_new();
|
||||
gtk_widget_set_hexpand(backup_progress_, TRUE);
|
||||
gtk_box_pack_start(GTK_BOX(prog_box), backup_progress_, TRUE, TRUE, 0);
|
||||
backup_status_label_ = gtk_label_new("Running...");
|
||||
gtk_box_pack_start(GTK_BOX(prog_box), backup_status_label_, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), prog_box, FALSE, FALSE, 0);
|
||||
|
||||
/* Buttons */
|
||||
GtkWidget* btn_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_widget_set_halign(btn_box, GTK_ALIGN_END);
|
||||
|
||||
backup_cancel_btn_ = gtk_button_new_with_label("Cancel");
|
||||
g_signal_connect(backup_cancel_btn_, "clicked", G_CALLBACK(+[](GtkWidget*, gpointer d) {
|
||||
auto* self = static_cast<MainWindow*>(d);
|
||||
const char* label = gtk_button_get_label(GTK_BUTTON(self->backup_cancel_btn_));
|
||||
if (strcmp(label, "Close") == 0) {
|
||||
self->close_backup_window();
|
||||
} else {
|
||||
self->rsync_.cancel();
|
||||
gtk_label_set_text(GTK_LABEL(self->backup_status_label_), "Cancelling...");
|
||||
gtk_button_set_label(GTK_BUTTON(self->backup_cancel_btn_), "Close");
|
||||
}
|
||||
}), this);
|
||||
gtk_box_pack_start(GTK_BOX(btn_box), backup_cancel_btn_, FALSE, FALSE, 0);
|
||||
|
||||
gtk_box_pack_start(GTK_BOX(vbox), btn_box, FALSE, FALSE, 0);
|
||||
|
||||
g_signal_connect(backup_win_, "delete-event", G_CALLBACK(+[](GtkWidget*, GdkEvent*, gpointer d) -> gboolean {
|
||||
auto* self = static_cast<MainWindow*>(d);
|
||||
if (self->rsync_.is_running()) {
|
||||
/* Don't close while running, just hide */
|
||||
return TRUE;
|
||||
}
|
||||
self->close_backup_window();
|
||||
return TRUE;
|
||||
}), this);
|
||||
|
||||
gtk_widget_show_all(backup_win_);
|
||||
}
|
||||
|
||||
void MainWindow::close_backup_window() {
|
||||
if (backup_win_) {
|
||||
gtk_widget_destroy(backup_win_);
|
||||
backup_win_ = nullptr;
|
||||
backup_output_view_ = nullptr;
|
||||
backup_cmd_label_ = nullptr;
|
||||
backup_progress_ = nullptr;
|
||||
backup_status_label_ = nullptr;
|
||||
backup_cancel_btn_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::append_backup_line(const std::string& line, const char* tag) {
|
||||
if (!backup_output_view_) return;
|
||||
GtkTextBuffer* buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(backup_output_view_));
|
||||
GtkTextIter end;
|
||||
gtk_text_buffer_get_end_iter(buf, &end);
|
||||
if (tag) {
|
||||
gtk_text_buffer_insert_with_tags_by_name(buf, &end, line.c_str(), -1, tag);
|
||||
} else {
|
||||
gtk_text_buffer_insert(buf, &end, line.c_str(), -1);
|
||||
}
|
||||
gtk_text_buffer_insert(buf, &end, "\n", -1);
|
||||
|
||||
/* Auto-scroll */
|
||||
GtkTextView* tv = GTK_TEXT_VIEW(backup_output_view_);
|
||||
GtkTextMark* mark = gtk_text_buffer_create_mark(buf, nullptr, &end, FALSE);
|
||||
gtk_text_view_scroll_mark_onscreen(tv, mark);
|
||||
gtk_text_buffer_delete_mark(buf, mark);
|
||||
}
|
||||
|
||||
void MainWindow::on_backup(GtkWidget*, gpointer d) {
|
||||
auto* self = static_cast<MainWindow*>(d);
|
||||
self->tray_.set_status("x3-Backup - Creating backup...", true);
|
||||
self->show_progress_dialog("Creating Snapshot...");
|
||||
self->open_backup_window("Creating Snapshot...");
|
||||
gtk_label_set_text(GTK_LABEL(self->live_log_label_), "Backup running...");
|
||||
|
||||
g_thread_new("backup", [](gpointer data) -> gpointer {
|
||||
auto* s = static_cast<MainWindow*>(data);
|
||||
|
||||
Reference in New Issue
Block a user