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