Move things round a bit.
[dcpomatic.git] / src / lib / job.cc
1 /*
2     Copyright (C) 2012 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 /** @file src/job.cc
21  *  @brief A parent class to represent long-running tasks which are run in their own thread.
22  */
23
24 #include <boost/thread.hpp>
25 #include "job.h"
26 #include "util.h"
27
28 using namespace std;
29 using namespace boost;
30
31 /** @param s FilmState for the film that we are operating on.
32  *  @param o Options.
33  *  @param l A log that we can write to.
34  */
35 Job::Job (shared_ptr<const FilmState> s, shared_ptr<const Options> o, Log* l)
36         : _fs (s)
37         , _opt (o)
38         , _log (l)
39         , _state (NEW)
40         , _start_time (0)
41         , _progress_unknown (false)
42 {
43         assert (_log);
44         
45         descend (1);
46 }
47
48 /** Start the job in a separate thread, returning immediately */
49 void
50 Job::start ()
51 {
52         set_state (RUNNING);
53         _start_time = time (0);
54         boost::thread (boost::bind (&Job::run_wrapper, this));
55 }
56
57 /** A wrapper for the ::run() method to catch exceptions */
58 void
59 Job::run_wrapper ()
60 {
61         try {
62
63                 run ();
64
65         } catch (std::exception& e) {
66
67                 set_progress (1);
68                 set_state (FINISHED_ERROR);
69                 set_error (e.what ());
70
71         }
72 }
73
74 /** @return true if the job is running */
75 bool
76 Job::running () const
77 {
78         boost::mutex::scoped_lock lm (_state_mutex);
79         return _state == RUNNING;
80 }
81
82 /** @return true if the job has finished (either successfully or unsuccessfully) */
83 bool
84 Job::finished () const
85 {
86         boost::mutex::scoped_lock lm (_state_mutex);
87         return _state == FINISHED_OK || _state == FINISHED_ERROR;
88 }
89
90 /** @return true if the job has finished successfully */
91 bool
92 Job::finished_ok () const
93 {
94         boost::mutex::scoped_lock lm (_state_mutex);
95         return _state == FINISHED_OK;
96 }
97
98 /** @return true if the job has finished unsuccessfully */
99 bool
100 Job::finished_in_error () const
101 {
102         boost::mutex::scoped_lock lm (_state_mutex);
103         return _state == FINISHED_ERROR;
104 }
105
106 /** Set the state of this job.
107  *  @param s New state.
108  */
109 void
110 Job::set_state (State s)
111 {
112         boost::mutex::scoped_lock lm (_state_mutex);
113         _state = s;
114 }
115
116 /** A hack to work around our lack of cross-thread
117  *  signalling; this emits Finished, and listeners
118  *  assume that it will be emitted in the GUI thread,
119  *  so this method must be called from the GUI thread.
120  */
121 void
122 Job::emit_finished ()
123 {
124         Finished ();
125 }
126
127 /** @return Time (in seconds) that this job has been running */
128 int
129 Job::elapsed_time () const
130 {
131         if (_start_time == 0) {
132                 return 0;
133         }
134         
135         return time (0) - _start_time;
136 }
137
138 /** Set the progress of the current part of the job.
139  *  @param p Progress (from 0 to 1)
140  */
141 void
142 Job::set_progress (float p)
143 {
144         boost::mutex::scoped_lock lm (_progress_mutex);
145         _stack.back().normalised = p;
146 }
147
148 /** @return fractional overall progress, or -1 if not known */
149 float
150 Job::overall_progress () const
151 {
152         boost::mutex::scoped_lock lm (_progress_mutex);
153         if (_progress_unknown) {
154                 return -1;
155         }
156
157         float overall = 0;
158         float factor = 1;
159         for (list<Level>::const_iterator i = _stack.begin(); i != _stack.end(); ++i) {
160                 factor *= i->allocation;
161                 overall += i->normalised * factor;
162         }
163
164         if (overall > 1) {
165                 overall = 1;
166         }
167         
168         return overall;
169 }
170
171 /** Ascend up one level in terms of progress reporting; see descend() */
172 void
173 Job::ascend ()
174 {
175         boost::mutex::scoped_lock lm (_progress_mutex);
176         
177         assert (!_stack.empty ());
178         float const a = _stack.back().allocation;
179         _stack.pop_back ();
180         _stack.back().normalised += a;
181 }
182
183 /** Descend down one level in terms of progress reporting; e.g. if
184  *  there is a task which is split up into N subtasks, each of which
185  *  report their progress from 0 to 100%, call descend() before executing
186  *  each subtask, and ascend() afterwards to ensure that overall progress
187  *  is reported correctly.
188  *
189  *  @param a Fraction (from 0 to 1) of the current task to allocate to the subtask.
190  */
191 void
192 Job::descend (float a)
193 {
194         boost::mutex::scoped_lock lm (_progress_mutex);
195         _stack.push_back (Level (a));
196 }
197
198 /** @return Any error string that the job has generated */
199 string
200 Job::error () const
201 {
202         boost::mutex::scoped_lock lm (_state_mutex);
203         return _error;
204 }
205
206 /** Set the current error string.
207  *  @param e New error string.
208  */
209 void
210 Job::set_error (string e)
211 {
212         boost::mutex::scoped_lock lm (_state_mutex);
213         _error = e;
214 }
215
216 /** Set that this job's progress will always be unknown */
217 void
218 Job::set_progress_unknown ()
219 {
220         boost::mutex::scoped_lock lm (_progress_mutex);
221         _progress_unknown = true;
222 }
223
224 string
225 Job::status () const
226 {
227         float const p = overall_progress ();
228         int const t = elapsed_time ();
229         
230         stringstream s;
231         if (!finished () && p >= 0 && t > 10) {
232                 s << rint (p * 100) << "%; about " << seconds_to_approximate_hms (t / p - t) << " remaining";
233         } else if (!finished () && t <= 10) {
234                 s << rint (p * 100) << "%";
235         } else if (finished_ok ()) {
236                 s << "OK (ran for " << seconds_to_hms (t) << ")";
237         } else if (finished_in_error ()) {
238                 s << "Error (" << error() << ")";
239         }
240
241         return s.str ();
242 }