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