1 #include "pool_status_provider.hpp"
2 #include "pool/pool.hpp"
3 #include "util/util.hpp"
4 #include "pool-update/pool-update.hpp"
5 #include <git2.h>
6 #include "util/autofree_ptr.hpp"
7 #include "logger/logger.hpp"
8
9 namespace horizon {
10 namespace fs = std::filesystem;
11
PoolStatusProviderBase(const std::string & bp)12 PoolStatusProviderBase::PoolStatusProviderBase(const std::string &bp) : base_path(fs::u8path(bp))
13 {
14 dispatcher.connect([this] {
15 {
16 std::lock_guard<std::mutex> lck(mutex);
17 if (pool_status_thread)
18 pool_status = std::move(pool_status_thread);
19 }
20 if (pool_status->status == PoolStatusBase::Status::DONE && thread.joinable())
21 thread.join();
22 s_signal_changed.emit();
23 });
24 }
25
refresh()26 void PoolStatusProviderBase::refresh()
27 {
28 reset();
29 start_worker();
30 }
31
start_worker()32 void PoolStatusProviderBase::start_worker()
33 {
34 if (thread.joinable())
35 return;
36 pool_status = std::make_unique<PoolStatusBase>();
37 pool_status->status = PoolStatusBase::Status::BUSY;
38 s_signal_changed.emit();
39 thread = std::thread(&PoolStatusProviderBase::worker_wrapper, this);
40 }
41
worker_wrapper()42 void PoolStatusProviderBase::worker_wrapper()
43 {
44 std::string err;
45 try {
46 worker();
47 }
48 catch (const std::exception &e) {
49 err = e.what();
50 }
51 catch (...) {
52 err = "unknown exception";
53 }
54 if (err.size()) {
55 {
56 std::lock_guard<std::mutex> lck(mutex);
57 pool_status_thread = std::make_unique<PoolStatusBase>();
58 pool_status_thread->msg = "Error";
59 pool_status_thread->status = PoolStatusBase::Status::DONE;
60 }
61 dispatcher.emit();
62 Logger::log_warning(err);
63 }
64 }
65
~PoolStatusProviderBase()66 PoolStatusProviderBase::~PoolStatusProviderBase()
67 {
68 }
69
join_thread()70 void PoolStatusProviderBase::join_thread()
71 {
72 if (thread.joinable())
73 thread.join();
74 }
75
PoolStatusProviderPoolManager(const std::string & bp)76 PoolStatusProviderPoolManager::PoolStatusProviderPoolManager(const std::string &bp)
77 : PoolStatusProviderBase(bp), remote_path(base_path / ".remote")
78 {
79 }
80
81
apply_update(const PoolStatusPoolManager & st)82 void PoolStatusProviderPoolManager::apply_update(const PoolStatusPoolManager &st)
83 {
84 st_update.emplace(st);
85 start_worker();
86 }
87
check_for_updates()88 void PoolStatusProviderPoolManager::check_for_updates()
89 {
90 do_check_updates = true;
91 start_worker();
92 }
93
reset()94 void PoolStatusProviderPoolManager::reset()
95 {
96 st_update.reset();
97 do_check_updates = false;
98 }
99
worker()100 void PoolStatusProviderPoolManager::worker()
101 {
102 if (!fs::is_directory(remote_path)) {
103 std::lock_guard<std::mutex> lck(mutex);
104 pool_status_thread = std::make_unique<PoolStatusBase>();
105 pool_status_thread->status = PoolStatusBase::Status::DONE;
106 dispatcher.emit();
107 return;
108 }
109
110 if (st_update) {
111 apply_update();
112 }
113 else if (do_check_updates) {
114 check_update();
115 }
116
117 {
118 std::lock_guard<std::mutex> lck(mutex);
119 pool_status_thread = std::make_unique<PoolStatusBase>();
120 }
121 dispatcher.emit();
122
123 auto x = std::make_unique<PoolStatusPoolManager>();
124 x->status = PoolStatusBase::Status::DONE;
125 using ItemState = PoolStatusPoolManager::ItemInfo::ItemState;
126
127 Pool pool_local(base_path.u8string());
128 {
129 SQLite::Query q(pool_local.db, "ATTACH ? AS remote");
130 q.bind(1, (remote_path / "pool.db").u8string());
131 q.step();
132 }
133 {
134 SQLite::Query q(pool_local.db,
135 // items present in local+remote
136 "SELECT main.all_items_view.type, main.all_items_view.uuid, "
137 "main.all_items_view.name, main.all_items_view.filename, "
138 "remote.all_items_view.filename FROM main.all_items_view "
139 "INNER JOIN remote.all_items_view "
140 "ON (main.all_items_view.uuid = remote.all_items_view.uuid AND "
141 "main.all_items_view.type = remote.all_items_view.type) "
142 "WHERE remote.all_items_view.pool_uuid = $pool_uuid "
143 "UNION "
144
145 // items present remote only
146 "SELECT remote.all_items_view.type, "
147 "remote.all_items_view.uuid, remote.all_items_view.name, '', "
148 "remote.all_items_view.filename FROM remote.all_items_view "
149 "LEFT JOIN main.all_items_view "
150 "ON (main.all_items_view.uuid = remote.all_items_view.uuid AND "
151 "main.all_items_view.type = remote.all_items_view.type) "
152 "WHERE main.all_items_view.uuid IS NULL "
153 "AND remote.all_items_view.pool_uuid = $pool_uuid "
154 "UNION "
155
156 // items present local only
157 "SELECT main.all_items_view.type, main.all_items_view.uuid, "
158 "main.all_items_view.name, main.all_items_view.filename, '' "
159 "FROM main.all_items_view "
160 "LEFT JOIN remote.all_items_view "
161 "ON (main.all_items_view.uuid = remote.all_items_view.uuid AND "
162 "main.all_items_view.type = remote.all_items_view.type) "
163 "WHERE remote.all_items_view.uuid IS NULL "
164 "AND main.all_items_view.pool_uuid = $pool_uuid");
165 q.bind("$pool_uuid", pool_local.get_pool_info().uuid);
166 while (q.step()) {
167 x->items.emplace_back();
168 auto &it = x->items.back();
169
170 it.type = q.get<ObjectType>(0);
171 it.uuid = q.get<std::string>(1);
172 it.name = q.get<std::string>(2);
173 it.filename_local = q.get<std::string>(3);
174 it.filename_remote = q.get<std::string>(4);
175 it.merge = true;
176
177
178 if (it.filename_remote.size() == 0) {
179 it.state = ItemState::LOCAL_ONLY;
180 }
181 else if (it.filename_local.size() == 0) {
182 it.state = ItemState::REMOTE_ONLY;
183 }
184 else if (it.filename_local.size() && it.filename_remote.size()) {
185 json j_local;
186 {
187 j_local = load_json_from_file((base_path / fs::u8path(it.filename_local)).u8string());
188 if (j_local.count("_imp"))
189 j_local.erase("_imp");
190 }
191 json j_remote;
192 {
193 j_remote = load_json_from_file((remote_path / fs::u8path(it.filename_remote)).u8string());
194 if (j_remote.count("_imp"))
195 j_remote.erase("_imp");
196 }
197 it.delta = json::diff(j_local, j_remote);
198
199
200 const bool moved = it.filename_local != it.filename_remote;
201 const bool changed = it.delta.size();
202 if (moved && changed) {
203 it.state = ItemState::MOVED_CHANGED;
204 }
205 else if (moved && !changed) {
206 it.state = ItemState::MOVED;
207 }
208 else if (!moved && changed) {
209 it.state = ItemState::CHANGED;
210 }
211 else if (!moved && !changed) {
212 it.state = ItemState::CURRENT;
213 }
214 }
215 }
216 }
217 {
218 SQLite::Query q(pool_local.db,
219 "SELECT DISTINCT model_filename FROM remote.models "
220 "INNER JOIN remote.packages ON remote.models.package_uuid = remote.packages.uuid "
221 "WHERE packages.pool_uuid = $pool_uuid");
222 q.bind("$pool_uuid", pool_local.get_pool_info().uuid);
223 while (q.step()) {
224 const auto filename = fs::u8path(q.get<std::string>(0));
225 const auto remote_filename = remote_path / filename;
226 const auto local_filename = base_path / filename;
227
228 if (fs::is_regular_file(remote_filename)) { // remote is there
229 x->items.emplace_back();
230 auto &it = x->items.back();
231 it.type = ObjectType::MODEL_3D;
232 it.name = filename.filename().u8string();
233 it.filename_local = filename.u8string();
234 it.filename_remote = filename.u8string();
235 it.merge = true;
236 if (fs::is_regular_file(local_filename)) {
237 if (compare_files(remote_filename.u8string(), local_filename.u8string())) {
238 it.state = ItemState::CURRENT;
239 }
240 else {
241 it.state = ItemState::CHANGED;
242 }
243 }
244 else {
245 it.state = ItemState::REMOTE_ONLY;
246 }
247 }
248 }
249 }
250
251 {
252 const auto tables_remote = remote_path / "tables.json";
253 const auto tables_local = base_path / "tables.json";
254 const auto tables_remote_exist = fs::is_regular_file(tables_remote);
255 auto tables_local_exist = fs::is_regular_file(tables_local);
256
257 if (tables_remote_exist && !tables_local_exist) {
258 x->tables_needs_update = PoolStatusPoolManager::UpdateState::REQUIRED;
259 }
260 else if (!tables_remote_exist) {
261 x->tables_needs_update = PoolStatusPoolManager::UpdateState::NO;
262 }
263 else if (tables_remote_exist && tables_local_exist) {
264 auto j_tables_remote = load_json_from_file(tables_remote.u8string());
265 auto j_tables_local = load_json_from_file(tables_local.u8string());
266 auto diff = json::diff(j_tables_local, j_tables_remote);
267 if (diff.size() > 0) { // different
268 x->tables_needs_update = PoolStatusPoolManager::UpdateState::OPTIONAL;
269 }
270 else {
271 x->tables_needs_update = PoolStatusPoolManager::UpdateState::NO;
272 }
273 }
274 }
275
276 {
277 const auto layer_help_remote = remote_path / "layer_help";
278 const auto layer_help_local = base_path / "layer_help";
279 const bool layer_help_remote_exist = fs::is_directory(layer_help_remote);
280 const bool layer_help_local_exist = fs::is_directory(layer_help_local);
281 if (layer_help_remote_exist && !layer_help_local_exist) {
282 x->layer_help_needs_update = PoolStatusPoolManager::UpdateState::REQUIRED;
283 }
284 else if (!layer_help_remote_exist) {
285 x->layer_help_needs_update = PoolStatusPoolManager::UpdateState::NO;
286 }
287 else if (layer_help_remote_exist && layer_help_local_exist) {
288 for (const auto &it_remote : fs::directory_iterator(layer_help_remote)) {
289 const auto it_local = layer_help_local / fs::relative(it_remote.path(), layer_help_remote);
290 if (fs::is_regular_file(it_local)) {
291 if (!compare_files(it_remote.path().u8string(), it_local.u8string())) {
292 x->layer_help_needs_update = PoolStatusPoolManager::UpdateState::OPTIONAL;
293 break;
294 }
295 }
296 else {
297 x->layer_help_needs_update = PoolStatusPoolManager::UpdateState::OPTIONAL;
298 break;
299 }
300 }
301 }
302 }
303
304
305 {
306 std::lock_guard<std::mutex> lck(mutex);
307 pool_status_thread = std::move(x);
308 }
309
310
311 dispatcher.emit();
312 }
313
~PoolStatusProviderPoolManager()314 PoolStatusProviderPoolManager::~PoolStatusProviderPoolManager()
315 {
316 join_thread();
317 }
318
apply_update()319 void PoolStatusProviderPoolManager::apply_update()
320 {
321 const auto &st = *st_update;
322 {
323 std::lock_guard<std::mutex> lck(mutex);
324 pool_status_thread = std::make_unique<PoolStatusBase>();
325 pool_status_thread->msg = "Updating items";
326 }
327 dispatcher.emit();
328
329 using ItemState = PoolStatusPoolManager::ItemInfo::ItemState;
330
331
332 for (const auto &it : st.items) {
333 if (it.state == ItemState::REMOTE_ONLY) { // remote only, copy to local
334 const auto filename = base_path / fs::u8path(it.filename_remote);
335 const auto dirname = filename.parent_path(); // Glib::path_get_dirname(filename);
336 fs::create_directories(dirname);
337 fs::copy(remote_path / fs::u8path(it.filename_remote), filename);
338 }
339 else if (it.state == ItemState::MOVED) { // moved, move local item to new
340 // filename
341 const auto filename_src = base_path / fs::u8path(it.filename_local);
342 const auto filename_dest = base_path / fs::u8path(it.filename_remote);
343 const auto dirname_dest = filename_dest.parent_path();
344 fs::create_directories(dirname_dest);
345 fs::rename(filename_src, filename_dest);
346 }
347 else if (it.state == ItemState::CHANGED || it.state == ItemState::MOVED_CHANGED) {
348 const auto filename_local = base_path / fs::u8path(it.filename_local);
349 const auto filename_local_new = base_path / fs::u8path(it.filename_remote);
350 const auto dirname_local_new = filename_local_new.parent_path();
351 const auto filename_remote = remote_path / fs::u8path(it.filename_remote);
352 fs::remove(filename_local);
353 fs::create_directories(dirname_local_new);
354 fs::copy(filename_remote, filename_local_new);
355 }
356 }
357
358 if (st_update->tables_needs_update == PoolStatusPoolManager::UpdateState::REQUIRED) {
359 const auto tables_remote = remote_path / "tables.json";
360 const auto tables_local = base_path / "tables.json";
361 fs::copy(tables_remote, tables_local, fs::copy_options::overwrite_existing);
362 }
363
364 if (st_update->layer_help_needs_update == PoolStatusPoolManager::UpdateState::REQUIRED) {
365 const auto layer_help_remote = remote_path / "layer_help";
366 const auto layer_help_local = base_path / "layer_help";
367 fs::copy(layer_help_remote, layer_help_local,
368 fs::copy_options::recursive | fs::copy_options::overwrite_existing);
369 }
370
371 {
372 std::lock_guard<std::mutex> lck(mutex);
373 pool_status_thread = std::make_unique<PoolStatusBase>();
374 pool_status_thread->msg = "Updating pool…";
375 }
376 dispatcher.emit();
377
378 pool_update(base_path.u8string(), nullptr, true);
379 }
380
check_update()381 void PoolStatusProviderPoolManager::check_update()
382 {
383 {
384 std::lock_guard<std::mutex> lck(mutex);
385 pool_status_thread = std::make_unique<PoolStatusBase>();
386 pool_status_thread->msg = "Checking for updates…";
387 }
388 dispatcher.emit();
389
390 autofree_ptr<git_repository> repo(git_repository_free);
391 if (git_repository_open(&repo.ptr, remote_path.u8string().c_str()) != 0) {
392 throw std::runtime_error("error opening repo");
393 }
394
395 autofree_ptr<git_remote> remote(git_remote_free);
396 if (git_remote_lookup(&remote.ptr, repo, "origin") != 0) {
397 throw std::runtime_error("error looking up remote");
398 }
399
400 {
401 std::lock_guard<std::mutex> lck(mutex);
402 pool_status_thread = std::make_unique<PoolStatusBase>();
403 pool_status_thread->msg = "Fetching…";
404 }
405 dispatcher.emit();
406
407 git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
408 if (git_remote_fetch(remote, NULL, &fetch_opts, NULL) != 0) {
409 throw std::runtime_error("error fetching");
410 }
411
412 autofree_ptr<git_annotated_commit> latest_commit(git_annotated_commit_free);
413 if (git_annotated_commit_from_revspec(&latest_commit.ptr, repo, "origin/master") != 0) {
414 throw std::runtime_error("error getting latest commit ");
415 }
416
417 auto oid = git_annotated_commit_id(latest_commit);
418
419 git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
420 checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
421
422 const git_annotated_commit *com = latest_commit.ptr;
423 git_merge_analysis_t merge_analysis;
424 git_merge_preference_t merge_prefs = GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY;
425 if (git_merge_analysis(&merge_analysis, &merge_prefs, repo, &com, 1) != 0) {
426 throw std::runtime_error("error getting merge analysis");
427 }
428
429 if (!(merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)) {
430 if (!(merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)) {
431 throw std::runtime_error("can't fast-forward");
432 }
433
434 autofree_ptr<git_object> obj(git_object_free);
435 if (git_object_lookup(&obj.ptr, repo, oid, GIT_OBJ_COMMIT) != 0) {
436 throw std::runtime_error("error lookup");
437 }
438
439 if (git_checkout_tree(repo, obj, &checkout_opts) != 0) {
440 throw std::runtime_error("error checkout");
441 }
442
443 autofree_ptr<git_reference> ref_head(git_reference_free);
444 if (git_repository_head(&ref_head.ptr, repo) != 0) {
445 throw std::runtime_error("error head");
446 }
447
448 autofree_ptr<git_reference> ref_new(git_reference_free);
449 if (git_reference_set_target(&ref_new.ptr, ref_head, oid, "test") != 0) {
450 throw std::runtime_error("error target");
451 }
452
453 {
454 std::lock_guard<std::mutex> lck(mutex);
455 pool_status_thread = std::make_unique<PoolStatusBase>();
456 pool_status_thread->msg = "Updating remote pool…";
457 }
458 dispatcher.emit();
459 pool_update(remote_path.u8string());
460 }
461 else {
462 std::cout << "up to date" << std::endl;
463 }
464 }
465
get()466 const PoolStatusBase &PoolStatusProviderBase::get()
467 {
468 return *pool_status;
469 }
470
get_brief() const471 std::string PoolStatusPoolManager::get_brief() const
472 {
473 if (status == Status::BUSY) {
474 return "Loading…";
475 }
476 else {
477 std::string s;
478 const auto n_new = std::count_if(items.begin(), items.end(),
479 [](const auto &x) { return x.state == ItemInfo::ItemState::REMOTE_ONLY; });
480 const auto n_changed = std::count_if(items.begin(), items.end(), [](const auto &x) {
481 return any_of(x.state, {ItemInfo::ItemState::CHANGED, ItemInfo::ItemState::MOVED_CHANGED,
482 ItemInfo::ItemState::MOVED});
483 });
484 if (n_new)
485 s = std::to_string(n_new) + " new";
486 if (n_changed) {
487 if (s.size())
488 s += ", ";
489 s += std::to_string(n_changed) + " changed";
490 }
491 if (layer_help_needs_update == UpdateState::REQUIRED || layer_help_needs_update == UpdateState::OPTIONAL) {
492 if (s.size())
493 s += ", ";
494 s += "layer help changed";
495 }
496 if (tables_needs_update == UpdateState::REQUIRED || tables_needs_update == UpdateState::OPTIONAL) {
497 if (s.size())
498 s += ", ";
499 s += " tables changed";
500 }
501 if (s.size() == 0)
502 s = "Up to date";
503 return s;
504 }
505 }
506
can_update() const507 bool PoolStatusPoolManager::can_update() const
508 {
509 const auto n_changed = std::count_if(items.begin(), items.end(), [](const auto &x) {
510 return any_of(x.state, {ItemInfo::ItemState::CHANGED, ItemInfo::ItemState::MOVED_CHANGED,
511 ItemInfo::ItemState::MOVED, ItemInfo::ItemState::REMOTE_ONLY});
512 });
513 return n_changed || layer_help_needs_update == UpdateState::REQUIRED
514 || layer_help_needs_update == UpdateState::OPTIONAL || tables_needs_update == UpdateState::REQUIRED
515 || tables_needs_update == UpdateState::OPTIONAL;
516 }
517
518
519 } // namespace horizon
520