Skip to content
API Documentation
BastionGuard™
app_window.cpp
Go to the documentation of this file.
1#include "app_window.hpp"
2#include "i18n.hpp"
3#include "report_exporter.hpp"
4
5#include <iomanip>
6#include <sstream>
7#include <cstring>
8#include <memory>
9
10Glib::RefPtr<ResultRow> ResultRow::create(const Glib::ustring& v_offset,
11 const Glib::ustring& v_size,
12 const Glib::ustring& v_type,
13 const Glib::ustring& v_source,
14 const Glib::ustring& v_detail,
15 const Glib::ustring& v_confidence) {
16 auto row = Glib::make_refptr_for_instance<ResultRow>(new ResultRow());
17 row->offset = v_offset;
18 row->size = v_size;
19 row->type = v_type;
20 row->source = v_source;
21 row->detail = v_detail;
22 row->confidence = v_confidence;
23 return row;
24}
25
27 : Glib::ObjectBase("ResultRow"),
28 offset(*this, "offset", ""),
29 size(*this, "size", ""),
30 type(*this, "type", ""),
31 source(*this, "source", ""),
32 detail(*this, "detail", ""),
33 confidence(*this, "confidence", "") {}
34
36 : btn_open_(tr("Open image")),
37 btn_scan_(tr("Scan")),
38 btn_save_report_(tr("Save report")),
39 btn_recover_(tr("Recover data")),
40 status_(tr("Ready")) {
41 set_title(tr("BastionGuard Recovery Data"));
42 set_default_size(1280, 760);
43
44 set_child(root_);
45 root_.set_margin_top(12);
46 root_.set_margin_bottom(12);
47 root_.set_margin_start(12);
48 root_.set_margin_end(12);
49
50 entry_path_.set_hexpand(true);
51 entry_path_.set_editable(false);
52 btn_save_report_.set_sensitive(false);
53 btn_recover_.set_sensitive(false);
54 btn_recover_.set_sensitive(false);
55
56 toolbar_.append(btn_open_);
57 toolbar_.append(entry_path_);
58 toolbar_.append(btn_scan_);
60 toolbar_.append(btn_recover_);
61
62 root_.append(toolbar_);
63 root_.append(status_);
64
65 store_ = Gio::ListStore<ResultRow>::create();
66 selection_model_ = Gtk::SingleSelection::create(store_);
69
71 results_scroller_.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
72
73 log_buffer_ = Gtk::TextBuffer::create();
74 log_view_.set_buffer(log_buffer_);
75 log_view_.set_editable(false);
76 log_view_.set_monospace(true);
77 logs_scroller_.set_child(log_view_);
78
79 paned_.set_start_child(results_scroller_);
80 paned_.set_end_child(logs_scroller_);
81 paned_.set_position(460);
82
83 root_.append(paned_);
84
85 btn_open_.signal_clicked().connect(sigc::mem_fun(*this, &AppWindow::on_choose_file));
86 btn_scan_.signal_clicked().connect(sigc::mem_fun(*this, &AppWindow::on_scan_clicked));
87 btn_save_report_.signal_clicked().connect(sigc::mem_fun(*this, &AppWindow::on_save_report_clicked));
88 btn_recover_.signal_clicked().connect(sigc::mem_fun(*this, &AppWindow::on_choose_output_folder));
89}
90
91AppWindow::~AppWindow() = default;
92
94 add_text_column(tr("Offset"), "offset");
95 add_text_column(tr("Size"), "size");
96 add_text_column(tr("Type"), "type");
97 add_text_column(tr("Source"), "source");
98 add_text_column(tr("Detail"), "detail");
99 add_text_column(tr("Confidence"), "confidence");
100}
101
102void AppWindow::add_text_column(const Glib::ustring& title, const char* property_name) {
103 auto factory = Gtk::SignalListItemFactory::create();
104
105 factory->signal_setup().connect([](const Glib::RefPtr<Gtk::ListItem>& item) {
106 auto* label = Gtk::make_managed<Gtk::Label>();
107 label->set_xalign(0.0f);
108 label->set_selectable(true);
109 label->set_wrap(true);
110 item->set_child(*label);
111 });
112
113 factory->signal_bind().connect([property_name](const Glib::RefPtr<Gtk::ListItem>& item) {
114 auto base = item->get_item();
115 auto row = std::dynamic_pointer_cast<ResultRow>(base);
116 auto* label = dynamic_cast<Gtk::Label*>(item->get_child());
117
118 if (!row || !label) {
119 return;
120 }
121
122 Glib::ustring text;
123
124 if (std::strcmp(property_name, "offset") == 0) {
125 text = row->offset.get_value();
126 } else if (std::strcmp(property_name, "size") == 0) {
127 text = row->size.get_value();
128 } else if (std::strcmp(property_name, "type") == 0) {
129 text = row->type.get_value();
130 } else if (std::strcmp(property_name, "source") == 0) {
131 text = row->source.get_value();
132 } else if (std::strcmp(property_name, "detail") == 0) {
133 text = row->detail.get_value();
134 } else if (std::strcmp(property_name, "confidence") == 0) {
135 text = row->confidence.get_value();
136 }
137
138 label->set_text(text);
139 });
140
141 auto column = Gtk::ColumnViewColumn::create(title, factory);
142 column->set_resizable(true);
143 column_view_.append_column(column);
144}
145
147 file_dialog_ = Gtk::FileDialog::create();
148 file_dialog_->set_title(tr("Select disk image"));
149 file_dialog_->open(*this, sigc::mem_fun(*this, &AppWindow::on_file_chosen));
150}
151
152void AppWindow::on_file_chosen(const Glib::RefPtr<Gio::AsyncResult>& result) {
153 try {
154 if (!file_dialog_) {
155 append_log(trs("Internal error: file dialog unavailable."));
156 return;
157 }
158 auto file = file_dialog_->open_finish(result);
159 if (file) {
160 selected_path_ = file->get_path();
161 entry_path_.set_text(selected_path_);
162 append_log(trs("Selected file:") + " " + selected_path_);
163 status_.set_text(tr("File ready for scan"));
164 }
165 } catch (const Glib::Error& ex) {
166 append_log(trs("File selection cancelled or failed:") + " " + ex.what());
167 }
168 file_dialog_.reset();
169}
170
172 if (selected_path_.empty()) {
173 append_log(trs("No file selected."));
174 status_.set_text(tr("Select a file first"));
175 return;
176 }
177 if (worker_.running()) {
178 append_log(trs("A scan is already running."));
179 return;
180 }
181
182 while (store_->get_n_items() > 0) {
183 store_->remove(store_->get_n_items() - 1);
184 }
185 last_report_ = {};
186 log_buffer_->set_text("");
187 append_log(trs("Starting read-only scan..."));
188 status_.set_text(tr("Scanning in progress..."));
189 btn_open_.set_sensitive(false);
190 btn_scan_.set_sensitive(false);
191 btn_save_report_.set_sensitive(false);
192 btn_recover_.set_sensitive(false);
193
194 worker_.start(selected_path_, [this](const ScanReport& report) {
195 Glib::signal_idle().connect_once([this, report]() {
196 on_scan_completed(report);
197 });
198 });
199}
200
202 last_report_ = report;
203
204 for (const auto& line : report.logs) {
205 append_log(line);
206 }
207
208 for (const auto& r : report.results) {
209 std::ostringstream conf;
210 conf << std::fixed << std::setprecision(2) << (r.confidence * 100.0) << "%";
211 store_->append(ResultRow::create(human_offset(r.offset_bytes),
212 human_size(r.size_bytes),
213 r.type,
214 r.source,
215 r.detail,
216 conf.str()));
217 }
218
219 status_.set_text(tr("Scan completed"));
220 btn_open_.set_sensitive(true);
221 btn_scan_.set_sensitive(true);
222 btn_save_report_.set_sensitive(true);
223 btn_recover_.set_sensitive(true);
224}
225
227 if (last_report_.input_path.empty()) {
228 append_log(trs("No scan report available."));
229 return;
230 }
231
232 save_dialog_ = Gtk::FileDialog::create();
233 save_dialog_->set_title(tr("Save report"));
234 save_dialog_->save(*this, sigc::mem_fun(*this, &AppWindow::on_report_file_chosen));
235}
236
237void AppWindow::on_report_file_chosen(const Glib::RefPtr<Gio::AsyncResult>& result) {
238 try {
239 if (!save_dialog_) {
240 append_log(trs("Internal error: save dialog unavailable."));
241 return;
242 }
243
244 auto file = save_dialog_->save_finish(result);
245 if (!file) {
246 save_dialog_.reset();
247 return;
248 }
249
250 const std::string path = file->get_path();
251 std::string err;
252 bool ok = false;
253 if (path.size() >= 5 && path.substr(path.size() - 5) == ".json") {
255 } else {
257 }
258
259 if (ok) {
260 append_log(trs("Report saved:") + " " + path);
261 status_.set_text(tr("Report saved"));
262 } else {
263 append_log(trs("Failed to save report:") + " " + err);
264 status_.set_text(tr("Report save failed"));
265 }
266 } catch (const Glib::Error& ex) {
267 append_log(trs("Report save cancelled or failed:") + " " + ex.what());
268 }
269 save_dialog_.reset();
270}
271
272void AppWindow::append_log(const std::string& line) {
273 auto iter = log_buffer_->end();
274 log_buffer_->insert(iter, line + "\n");
275}
276
277std::string AppWindow::human_size(uint64_t bytes) {
278 const char* units[] = {"B", "KiB", "MiB", "GiB", "TiB"};
279 double value = static_cast<double>(bytes);
280 int idx = 0;
281 while (value >= 1024.0 && idx < 4) {
282 value /= 1024.0;
283 ++idx;
284 }
285 std::ostringstream oss;
286 oss << std::fixed << std::setprecision(2) << value << ' ' << units[idx];
287 return oss.str();
288}
289
290std::string AppWindow::human_offset(uint64_t bytes) {
291 std::ostringstream oss;
292 oss << bytes << " (0x" << std::hex << std::uppercase << bytes << ')';
293 return oss.str();
294}
295
297 if (last_report_.input_path.empty()) {
298 append_log(trs("No scan report available."));
299 return;
300 }
302 append_log(trs("A recovery task is already running."));
303 return;
304 }
305
306 output_dialog_ = Gtk::FileDialog::create();
307 output_dialog_->set_title(tr("Select destination folder"));
308 output_dialog_->select_folder(*this, sigc::mem_fun(*this, &AppWindow::on_output_folder_chosen));
309}
310
311void AppWindow::on_output_folder_chosen(const Glib::RefPtr<Gio::AsyncResult>& result) {
312 try {
313 if (!output_dialog_) {
314 append_log(trs("Internal error: output dialog unavailable."));
315 return;
316 }
317 auto file = output_dialog_->select_folder_finish(result);
318 if (!file) {
319 output_dialog_.reset();
320 return;
321 }
322 output_dir_ = file->get_path();
323 append_log(trs("Recovery destination:") + std::string(" ") + output_dir_);
324 status_.set_text(tr("Recovery in progress..."));
325 btn_open_.set_sensitive(false);
326 btn_scan_.set_sensitive(false);
327 btn_save_report_.set_sensitive(false);
328 btn_recover_.set_sensitive(false);
329
331 Glib::signal_idle().connect_once([this, rr]() {
333 });
334 });
335 } catch (const Glib::Error& ex) {
336 append_log(trs("Destination selection cancelled or failed:") + std::string(" ") + ex.what());
337 }
338 output_dialog_.reset();
339}
340
342 for (const auto& line : result.logs) {
343 append_log(line);
344 }
345 if (result.ok) {
346 append_log(trs("Exported images:") + std::string(" ") + std::to_string(result.summary.exported_images.size()));
347 append_log(trs("Recovered files:") + std::string(" ") + std::to_string(result.summary.recovered_files.size()));
348 status_.set_text(tr("Recovery completed"));
349 } else {
350 append_log(trs("Recovery failed:") + std::string(" ") + result.error);
351 status_.set_text(tr("Recovery failed"));
352 }
353 btn_open_.set_sensitive(true);
354 btn_scan_.set_sensitive(true);
355 btn_save_report_.set_sensitive(true);
356 btn_recover_.set_sensitive(true);
357}
Gtk::ColumnView column_view_
void on_scan_clicked()
void on_recovery_completed(const RecoveryResult &result)
Glib::RefPtr< Gtk::FileDialog > file_dialog_
void on_output_folder_chosen(const Glib::RefPtr< Gio::AsyncResult > &result)
ScanWorker worker_
std::string output_dir_
RecoveryWorker recovery_worker_
static std::string human_offset(uint64_t bytes)
void on_report_file_chosen(const Glib::RefPtr< Gio::AsyncResult > &result)
void on_save_report_clicked()
void on_file_chosen(const Glib::RefPtr< Gio::AsyncResult > &result)
void on_choose_output_folder()
Gtk::Paned paned_
void append_log(const std::string &line)
Gtk::Button btn_save_report_
Glib::RefPtr< Gtk::TextBuffer > log_buffer_
std::string selected_path_
Gtk::Button btn_open_
~AppWindow() override
Gtk::ScrolledWindow results_scroller_
static std::string human_size(uint64_t bytes)
Glib::RefPtr< Gtk::FileDialog > save_dialog_
Gtk::Box toolbar_
Glib::RefPtr< Gtk::SingleSelection > selection_model_
void on_choose_file()
void on_scan_completed(const ScanReport &report)
Gtk::Entry entry_path_
void add_text_column(const Glib::ustring &title, const char *property_name)
Glib::RefPtr< Gtk::FileDialog > output_dialog_
Gtk::Label status_
Glib::RefPtr< Gio::ListStore< ResultRow > > store_
void setup_columns()
Gtk::TextView log_view_
Gtk::Button btn_scan_
Gtk::Box root_
Gtk::ScrolledWindow logs_scroller_
ScanReport last_report_
Gtk::Button btn_recover_
void start(const ScanReport &report, const std::string &output_dir, const std::vector< size_t > &selected_result_indices, CompletionCallback cb)
bool running() const
static bool save_json(const ScanReport &report, const std::string &path, std::string &error_out)
static bool save_text(const ScanReport &report, const std::string &path, std::string &error_out)
static Glib::RefPtr< ResultRow > create(const Glib::ustring &offset, const Glib::ustring &size, const Glib::ustring &type, const Glib::ustring &source, const Glib::ustring &detail, const Glib::ustring &confidence)
Glib::Property< Glib::ustring > offset
Glib::Property< Glib::ustring > confidence
Glib::Property< Glib::ustring > type
Glib::Property< Glib::ustring > source
Glib::Property< Glib::ustring > size
Glib::Property< Glib::ustring > detail
void start(const std::string &path, CompletionCallback cb, ProgressCallback progress_cb={})
bool running() const
const char * tr(const char *msgid)
Definition i18n.cpp:18
std::string trs(const char *msgid)
Definition i18n.cpp:22
RecoverySummary summary
std::vector< std::string > logs
std::vector< std::string > exported_images
std::vector< std::string > recovered_files
std::vector< ScanResult > results
Definition models.hpp:40
std::vector< std::string > logs
Definition models.hpp:41
std::string input_path
Definition models.hpp:38