1 #include "common/common_pch.h"
2
3 #include <QDebug>
4 #include <QDesktopServices>
5 #include <QFile>
6 #include <QFileInfo>
7 #include <QSettings>
8 #include <QUrl>
9
10 #include "common/list_utils.h"
11 #include "common/logger.h"
12 #include "common/qt.h"
13 #include "mkvtoolnix-gui/app.h"
14 #include "mkvtoolnix-gui/info/job_settings.h"
15 #include "mkvtoolnix-gui/jobs/info_job.h"
16 #include "mkvtoolnix-gui/jobs/job.h"
17 #include "mkvtoolnix-gui/jobs/job_p.h"
18 #include "mkvtoolnix-gui/jobs/mux_job.h"
19 #include "mkvtoolnix-gui/merge/mux_config.h"
20 #include "mkvtoolnix-gui/util/config_file.h"
21 #include "mkvtoolnix-gui/util/file.h"
22 #include "mkvtoolnix-gui/util/settings.h"
23
24 namespace mtx::gui::Jobs {
25
26 static uint64_t s_next_id = 0;
27
JobPrivate(Job::Status pStatus)28 JobPrivate::JobPrivate(Job::Status pStatus)
29 : id{s_next_id++}
30 , status{pStatus}
31 {
32 }
33
Job(Status status)34 Job::Job(Status status)
35 : p_ptr{new JobPrivate{status}}
36 {
37 setupJobConnections();
38 }
39
Job(JobPrivate & p)40 Job::Job(JobPrivate &p)
41 : p_ptr{&p}
42 {
43 setupJobConnections();
44 }
45
~Job()46 Job::~Job() {
47 }
48
49 void
setupJobConnections()50 Job::setupJobConnections() {
51 connect(this, &Job::lineRead, this, &Job::addLineToInternalLogs);
52 }
53
54 uint64_t
id() const55 Job::id()
56 const {
57 return p_func()->id;
58 }
59
60 QUuid
uuid() const61 Job::uuid()
62 const {
63 return p_func()->uuid;
64 }
65
66 Job::Status
status() const67 Job::status()
68 const {
69 return p_func()->status;
70 }
71
72 QString
description() const73 Job::description()
74 const {
75 return p_func()->description;
76 }
77
78 unsigned int
progress() const79 Job::progress()
80 const {
81 return p_func()->progress;
82 }
83
84 QStringList const &
output() const85 Job::output()
86 const {
87 return p_func()->output;
88 }
89
90 QStringList const &
warnings() const91 Job::warnings()
92 const {
93 return p_func()->warnings;
94 }
95
96 QStringList const &
errors() const97 Job::errors()
98 const {
99 return p_func()->errors;
100 }
101
102 QStringList const &
fullOutput() const103 Job::fullOutput()
104 const {
105 return p_func()->fullOutput;
106 }
107
108 QDateTime
dateAdded() const109 Job::dateAdded()
110 const {
111 return p_func()->dateAdded;
112 }
113
114 QDateTime
dateStarted() const115 Job::dateStarted()
116 const {
117 return p_func()->dateStarted;
118 }
119
120 QDateTime
dateFinished() const121 Job::dateFinished()
122 const {
123 return p_func()->dateFinished;
124 }
125
126 bool
isModified() const127 Job::isModified()
128 const {
129 return p_func()->modified;
130 }
131
132 void
setDescription(QString const & pDescription)133 Job::setDescription(QString const &pDescription) {
134 auto p = p_func();
135 p->description = pDescription;
136 p->modified = true;
137 }
138
139 void
setDateAdded(QDateTime const & pDateAdded)140 Job::setDateAdded(QDateTime const &pDateAdded) {
141 auto p = p_func();
142 p->dateAdded = pDateAdded;
143 p->modified = true;
144 }
145
146 void
setDateFinished(QDateTime const & pDateFinished)147 Job::setDateFinished(QDateTime const &pDateFinished) {
148 auto p = p_func();
149 p->dateFinished = pDateFinished;
150 p->modified = true;
151 }
152
153 void
setQuitAfterFinished(bool pQuitAfterFinished)154 Job::setQuitAfterFinished(bool pQuitAfterFinished) {
155 auto p = p_func();
156 p->quitAfterFinished = pQuitAfterFinished;
157 p->modified = true;
158 }
159
160 void
action(std::function<void ()> code)161 Job::action(std::function<void()> code) {
162 auto p = p_func();
163
164 QMutexLocker locked{&p->mutex};
165
166 code();
167 }
168
169 void
setStatus(Status status)170 Job::setStatus(Status status) {
171 auto p = p_func();
172
173 QMutexLocker locked{&p->mutex};
174
175 if (status == p->status)
176 return;
177
178 auto oldStatus = p->status;
179 p->status = status;
180 p->modified = true;
181
182 if (Running == status) {
183 p->dateStarted = QDateTime::currentDateTime();
184 p->fullOutput.clear();
185 p->output.clear();
186 p->warnings.clear();
187 p->errors.clear();
188 p->warningsAcknowledged = 0;
189 p->errorsAcknowledged = 0;
190
191 } else if ((DoneOk == status) || (DoneWarnings == status) || (Failed == status) || (Aborted == status))
192 p->dateFinished = QDateTime::currentDateTime();
193
194 if (oldStatus == Running)
195 runProgramsAfterCompletion();
196
197 qDebug() << "setStatus emitting statusChanged(" << oldStatus << status << ")";
198 Q_EMIT statusChanged(p->id, oldStatus, status);
199 }
200
201 bool
isToBeProcessed() const202 Job::isToBeProcessed()
203 const {
204 return mtx::included_in(p_func()->status, Running, PendingAuto);
205 }
206
207 void
setProgress(unsigned int progress)208 Job::setProgress(unsigned int progress) {
209 auto p = p_func();
210
211 QMutexLocker locked{&p->mutex};
212
213 if (progress == p->progress)
214 return;
215
216 p->progress = progress;
217 p->modified = true;
218 Q_EMIT progressChanged(p->id, p->progress);
219 }
220
221 void
setPendingAuto()222 Job::setPendingAuto() {
223 auto p = p_func();
224
225 QMutexLocker locked{&p->mutex};
226
227 if (!mtx::included_in(p->status, PendingAuto, Running)) {
228 setProgress(0);
229 setStatus(PendingAuto);
230 }
231 }
232
233 void
setPendingManual()234 Job::setPendingManual() {
235 auto p = p_func();
236
237 QMutexLocker locked{&p->mutex};
238
239 if ((PendingManual != p->status) && (Running != p->status)) {
240 setProgress(0);
241 setStatus(PendingManual);
242 }
243 }
244
245 void
addLineToInternalLogs(QString const & line,LineType type)246 Job::addLineToInternalLogs(QString const &line,
247 LineType type) {
248 auto p = p_func();
249 auto &storage = InfoLine == type ? p->output
250 : WarningLine == type ? p->warnings
251 : p->errors;
252
253 auto prefix = InfoLine == type ? Q("")
254 : WarningLine == type ? Q("%1 ").arg(QY("Warning:"))
255 : Q("%1 ").arg(QY("Error:"));
256
257 p->fullOutput << Q("%1%2").arg(prefix).arg(line);
258 storage << line;
259
260 p->modified = true;
261
262 if ((WarningLine == type) || (ErrorLine == type))
263 updateUnacknowledgedWarningsAndErrors();
264 }
265
266 QString
displayableStatus(Status status)267 Job::displayableStatus(Status status) {
268 return PendingManual == status ? QY("Pending manual start")
269 : PendingAuto == status ? QY("Pending automatic start")
270 : Running == status ? QY("Running")
271 : DoneOk == status ? QY("Completed OK")
272 : DoneWarnings == status ? QY("Completed with warnings")
273 : Failed == status ? QY("Failed")
274 : Aborted == status ? QY("Aborted by user")
275 : Disabled == status ? QY("Disabled")
276 : QY("Unknown");
277 }
278
279 QString
outputFolder() const280 Job::outputFolder()
281 const {
282 auto destination = destinationFileName();
283 return destination.isEmpty() ? QString{} : QFileInfo{destination}.dir().path();
284 }
285
286 void
openOutputFolder() const287 Job::openOutputFolder()
288 const {
289 auto folder = outputFolder();
290
291 if (!folder.isEmpty())
292 QDesktopServices::openUrl(Util::pathToFileUrl(folder));
293 }
294
295 QString
queueLocation()296 Job::queueLocation() {
297 return Q("%1/%2").arg(Util::Settings::iniFileLocation()).arg("jobQueue");
298 }
299
300 QString
queueFileName() const301 Job::queueFileName()
302 const {
303 return Q("%1/%2.mtxcfg").arg(queueLocation()).arg(p_func()->uuid.toString());
304 }
305
306 void
removeQueueFile() const307 Job::removeQueueFile()
308 const {
309 QFile{queueFileName()}.remove();
310 }
311
312 void
saveQueueFile()313 Job::saveQueueFile() {
314 auto p = p_func();
315
316 if (p->uuid.isNull())
317 p->uuid = QUuid::createUuid();
318
319 auto fileName = queueFileName();
320 if (!isModified() && QFileInfo{ fileName }.exists())
321 return;
322
323 auto settings = Util::ConfigFile::create(fileName);
324 saveJob(*settings);
325 settings->save();
326 }
327
328 void
saveJob(Util::ConfigFile & settings)329 Job::saveJob(Util::ConfigFile &settings) {
330 auto p = p_func();
331 auto resetCounters = Util::Settings::get().m_resetJobWarningErrorCountersOnExit;
332
333 settings.setValue("uuid", p->uuid);
334 settings.setValue("status", static_cast<unsigned int>(p->status));
335 settings.setValue("description", p->description);
336 settings.setValue("output", p->output);
337 settings.setValue("warnings", p->warnings);
338 settings.setValue("errors", p->errors);
339 settings.setValue("fullOutput", p->fullOutput);
340 settings.setValue("progress", p->progress);
341 settings.setValue("exitCode", p->exitCode);
342 settings.setValue("warningsAcknowledged", resetCounters ? p->warnings.count() : p->warningsAcknowledged);
343 settings.setValue("errorsAcknowledged", resetCounters ? p->errors.count() : p->errorsAcknowledged);
344 settings.setValue("dateAdded", p->dateAdded);
345 settings.setValue("dateStarted", p->dateStarted);
346 settings.setValue("dateFinished", p->dateFinished);
347
348 saveJobInternal(settings);
349
350 p->modified = false;
351 }
352
353 void
loadJobBasis(Util::ConfigFile & settings)354 Job::loadJobBasis(Util::ConfigFile &settings) {
355 auto p = p_func();
356 p->modified = false;
357 p->uuid = settings.value("uuid").toUuid();
358 p->status = static_cast<Status>(settings.value("status", static_cast<unsigned int>(PendingManual)).toUInt());
359 p->description = settings.value("description").toString();
360 p->output = settings.value("output").toStringList();
361 p->warnings = settings.value("warnings").toStringList();
362 p->errors = settings.value("errors").toStringList();
363 p->fullOutput = settings.value("fullOutput").toStringList();
364 p->progress = settings.value("progress").toUInt();
365 p->exitCode = settings.value("exitCode").toUInt();
366 p->warningsAcknowledged = settings.value("warningsAcknowledged", 0).toUInt();
367 p->errorsAcknowledged = settings.value("errorsAcknowledged", 0).toUInt();
368 p->dateAdded = settings.value("dateAdded").toDateTime();
369 p->dateStarted = settings.value("dateStarted").toDateTime();
370 p->dateFinished = settings.value("dateFinished").toDateTime();
371
372 if (p->uuid.isNull())
373 p->uuid = QUuid::createUuid();
374
375 if (Running == p->status)
376 p->status = Aborted;
377
378 if (Util::Settings::get().m_resetJobWarningErrorCountersOnExit) {
379 p->warningsAcknowledged = p->warnings.count();
380 p->errorsAcknowledged = p->errors.count();
381 }
382 }
383
384 JobPtr
loadJob(QString const & fileName)385 Job::loadJob(QString const &fileName) {
386 if (!QFileInfo{fileName}.exists())
387 return {};
388
389 auto settings = Util::ConfigFile::open(fileName);
390 if (!settings)
391 throw Merge::InvalidSettingsX{};
392
393 return loadJob(*settings);
394 }
395
396 JobPtr
loadJob(Util::ConfigFile & settings)397 Job::loadJob(Util::ConfigFile &settings) {
398 auto jobType = settings.value("jobType").toString();
399
400 if (jobType.isEmpty() && settings.childKeys().contains("muxConfig"))
401 // Older versions didn't write a jobType attribute and only
402 // supported MuxJobs.
403 jobType = "MuxJob";
404
405 if (jobType == "MuxJob")
406 return MuxJob::loadMuxJob(settings);
407
408 if (jobType == "InfoJob")
409 return InfoJob::loadInfoJob(settings);
410
411 log_it(fmt::format("MTX Job::loadJob: Unknown job type encountered ({0}) in {1}", jobType, settings.fileName()));
412 throw Merge::InvalidSettingsX{};
413 }
414
415 void
acknowledgeWarnings()416 Job::acknowledgeWarnings() {
417 auto p = p_func();
418
419 if (p->warnings.count() == p->warningsAcknowledged)
420 return;
421
422 p->warningsAcknowledged = p->warnings.count();
423 p->modified = true;
424 updateUnacknowledgedWarningsAndErrors();
425 }
426
427 void
acknowledgeErrors()428 Job::acknowledgeErrors() {
429 auto p = p_func();
430
431 if (p->errors.count() == p->errorsAcknowledged)
432 return;
433
434 p->errorsAcknowledged = p->errors.count();
435 p->modified = true;
436 updateUnacknowledgedWarningsAndErrors();
437 }
438
439 void
updateUnacknowledgedWarningsAndErrors()440 Job::updateUnacknowledgedWarningsAndErrors() {
441 Q_EMIT numUnacknowledgedWarningsOrErrorsChanged(p_func()->id, numUnacknowledgedWarnings(), numUnacknowledgedErrors());
442 }
443
444 int
numUnacknowledgedWarnings() const445 Job::numUnacknowledgedWarnings()
446 const {
447 auto p = p_func();
448
449 return p->warnings.count() - p->warningsAcknowledged;
450 }
451
452 int
numUnacknowledgedErrors() const453 Job::numUnacknowledgedErrors()
454 const {
455 auto p = p_func();
456
457 return p->errors.count() - p->errorsAcknowledged;
458 }
459
460 void
runProgramsAfterCompletion()461 Job::runProgramsAfterCompletion() {
462 auto p = p_func();
463
464 if (p->status == Aborted) {
465 maybeRemoveOutputFile();
466 return;
467 }
468
469 if (!mtx::included_in(p->status, DoneOk, DoneWarnings, Failed))
470 return;
471
472 auto event = p->status == Failed ? Util::Settings::RunAfterJobCompletesWithErrors
473 : p->status == DoneWarnings ? Util::Settings::RunAfterJobCompletesWithWarnings
474 : Util::Settings::RunAfterJobCompletesSuccessfully;
475
476 App::programRunner().run(event, [this](ProgramRunner::VariableMap &variables) {
477 runProgramSetupVariables(variables);
478 });
479
480 App::programRunner().executeActionsAfterJobFinishes(*this);
481
482 maybeRemoveOutputFile();
483 }
484
485 void
runProgramSetupVariables(ProgramRunner::VariableMap & variables) const486 Job::runProgramSetupVariables(ProgramRunner::VariableMap &variables)
487 const{
488 auto p = p_func();
489
490 variables[Q("JOB_START_TIME")] << p->dateStarted.toString(Qt::ISODate);
491 variables[Q("JOB_END_TIME")] << p->dateFinished.toString(Qt::ISODate);
492 variables[Q("JOB_DESCRIPTION")] << p->description;
493 variables[Q("JOB_EXIT_CODE")] << QString::number(p->exitCode);
494 }
495
496 void
maybeRemoveOutputFile()497 Job::maybeRemoveOutputFile() {
498 auto p = p_func();
499
500 if ( !Util::Settings::get().m_removeOutputFileOnJobFailure
501 || !mtx::included_in(p->status, Aborted, Failed))
502 return;
503
504 auto fileName = destinationFileName();
505
506 qDebug() << "maybeRemoveOutputFile:" << fileName;
507
508 if (!fileName.isEmpty())
509 QFile::remove(fileName);
510 }
511
512 }
513