1// Aseprite
2// Copyright (C) 2021 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/job.h"
13
14#include "app/app.h"
15#include "app/console.h"
16#include "app/context.h"
17#include "app/i18n/strings.h"
18#include "fmt/format.h"
19#include "ui/alert.h"
20#include "ui/widget.h"
21#include "ui/window.h"
22
23#include <atomic>
24
25static const int kMonitoringPeriod = 100;
26static std::atomic<int> g_runningJobs(0);
27
28namespace app {
29
30// static
31int Job::runningJobs()
32{
33 return g_runningJobs;
34}
35
36Job::Job(const char* jobName)
37{
38 m_last_progress = 0.0;
39 m_done_flag = false;
40 m_canceled_flag = false;
41
42 if (App::instance()->isGui()) {
43 m_alert_window = ui::Alert::create(
44 fmt::format(Strings::alerts_job_working(), jobName));
45 m_alert_window->addProgress();
46
47 m_timer.reset(new ui::Timer(kMonitoringPeriod, m_alert_window.get()));
48 m_timer->Tick.connect(&Job::onMonitoringTick, this);
49 m_timer->start();
50 }
51}
52
53Job::~Job()
54{
55 if (App::instance()->isGui()) {
56 ASSERT(!m_timer->isRunning());
57
58 if (m_alert_window)
59 m_alert_window->closeWindow(NULL);
60 }
61}
62
63void Job::startJob()
64{
65 m_thread = std::thread(&Job::thread_proc, this);
66 ++g_runningJobs;
67
68 if (m_alert_window) {
69 m_alert_window->openWindowInForeground();
70
71 // The job was canceled by the user?
72 {
73 std::unique_lock<std::mutex> hold(m_mutex);
74 if (!m_done_flag)
75 m_canceled_flag = true;
76 }
77
78 // In case of error, take the "cancel" path (i.e. it's like the
79 // user canceled the operation).
80 if (m_error) {
81 m_canceled_flag = true;
82 try {
83 std::rethrow_exception(m_error);
84 }
85 catch (const std::exception& ex) {
86 Console::showException(ex);
87 }
88 catch (...) {
89 Console console;
90 console.printf("Unknown error performing the task");
91 }
92 }
93 }
94}
95
96void Job::waitJob()
97{
98 if (m_timer && m_timer->isRunning())
99 m_timer->stop();
100
101 if (m_thread.joinable()) {
102 m_thread.join();
103
104 --g_runningJobs;
105 }
106}
107
108void Job::jobProgress(double f)
109{
110 m_last_progress = f;
111}
112
113bool Job::isCanceled()
114{
115 return m_canceled_flag;
116}
117
118void Job::onMonitoringTick()
119{
120 std::unique_lock<std::mutex> hold(m_mutex);
121
122 // update progress
123 m_alert_window->setProgress(m_last_progress);
124
125 // is job done? we can close the monitor
126 if (m_done_flag || m_canceled_flag) {
127 m_timer->stop();
128 m_alert_window->closeWindow(NULL);
129 }
130}
131
132void Job::done()
133{
134 std::unique_lock<std::mutex> hold(m_mutex);
135 m_done_flag = true;
136}
137
138// Called to start the worker thread.
139void Job::thread_proc(Job* self)
140{
141 try {
142 self->onJob();
143 }
144 catch (...) {
145 self->m_error = std::current_exception();
146 }
147 self->done();
148}
149
150} // namespace app
151