Show debug console only for "pre-release" builds.
[simple-x264-launcher.git] / src / model_jobList.cpp
blob87367a1e5c1d3a66ead648452036bdefaf4529da
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2015 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
22 //Internal
23 #include "global.h"
24 #include "model_jobList.h"
25 #include "thread_encode.h"
26 #include "model_options.h"
27 #include "model_preferences.h"
28 #include "resource.h"
30 //MUtils
31 #include <MUtils/Sound.h>
33 //Qt
34 #include <QIcon>
35 #include <QFileInfo>
36 #include <QSettings>
38 static const char *KEY_ENTRY_COUNT = "entry_count";
39 static const char *KEY_SOURCE_FILE = "source_file";
40 static const char *KEY_OUTPUT_FILE = "output_file";
41 static const char *KEY_ENC_OPTIONS = "enc_options";
43 static const char *JOB_TEMPLATE = "job_%08x";
45 #define VALID_INDEX(INDEX) ((INDEX).isValid() && ((INDEX).row() >= 0) && ((INDEX).row() < m_jobs.count()))
47 JobListModel::JobListModel(PreferencesModel *preferences)
49 m_preferences = preferences;
52 JobListModel::~JobListModel(void)
54 while(!m_jobs.isEmpty())
56 QUuid id = m_jobs.takeFirst();
57 EncodeThread *thread = m_threads.value(id, NULL);
58 LogFileModel *logFile = m_logFile.value(id, NULL);
59 MUTILS_DELETE(thread);
60 MUTILS_DELETE(logFile);
64 ///////////////////////////////////////////////////////////////////////////////
65 // Model interface
66 ///////////////////////////////////////////////////////////////////////////////
68 int JobListModel::columnCount(const QModelIndex &parent) const
70 return 4;
73 int JobListModel::rowCount(const QModelIndex &parent) const
75 return m_jobs.count();
78 QVariant JobListModel::headerData(int section, Qt::Orientation orientation, int role) const
80 if((orientation == Qt::Horizontal) && (role == Qt::DisplayRole))
82 switch(section)
84 case 0:
85 return QVariant::fromValue<QString>(tr("Job"));
86 break;
87 case 1:
88 return QVariant::fromValue<QString>(tr("Status"));
89 break;
90 case 2:
91 return QVariant::fromValue<QString>(tr("Progress"));
92 break;
93 case 3:
94 return QVariant::fromValue<QString>(tr("Details"));
95 break;
96 default:
97 return QVariant();
98 break;
102 return QVariant();
105 QModelIndex JobListModel::index(int row, int column, const QModelIndex &parent) const
107 return createIndex(row, column, NULL);
110 QModelIndex JobListModel::parent(const QModelIndex &index) const
112 return QModelIndex();
115 QVariant JobListModel::data(const QModelIndex &index, int role) const
117 if(role == Qt::DisplayRole)
119 if(index.row() >= 0 && index.row() < m_jobs.count())
121 switch(index.column())
123 case 0:
124 return m_name.value(m_jobs.at(index.row()));
125 break;
126 case 1:
127 switch(m_status.value(m_jobs.at(index.row())))
129 case JobStatus_Enqueued:
130 return QVariant::fromValue<QString>(tr("Enqueued."));
131 break;
132 case JobStatus_Starting:
133 return QVariant::fromValue<QString>(tr("Starting..."));
134 break;
135 case JobStatus_Indexing:
136 return QVariant::fromValue<QString>(tr("Indexing..."));
137 break;
138 case JobStatus_Running:
139 return QVariant::fromValue<QString>(tr("Running..."));
140 break;
141 case JobStatus_Running_Pass1:
142 return QVariant::fromValue<QString>(tr("Running... (Pass 1)"));
143 break;
144 case JobStatus_Running_Pass2:
145 return QVariant::fromValue<QString>(tr("Running... (Pass 2)"));
146 break;
147 case JobStatus_Completed:
148 return QVariant::fromValue<QString>(tr("Completed."));
149 break;
150 case JobStatus_Failed:
151 return QVariant::fromValue<QString>(tr("Failed!"));
152 break;
153 case JobStatus_Pausing:
154 return QVariant::fromValue<QString>(tr("Pausing..."));
155 break;
156 case JobStatus_Paused:
157 return QVariant::fromValue<QString>(tr("Paused."));
158 break;
159 case JobStatus_Resuming:
160 return QVariant::fromValue<QString>(tr("Resuming..."));
161 break;
162 case JobStatus_Aborting:
163 return QVariant::fromValue<QString>(tr("Aborting..."));
164 break;
165 case JobStatus_Aborted:
166 return QVariant::fromValue<QString>(tr("Aborted!"));
167 break;
168 default:
169 return QVariant::fromValue<QString>(tr("(Unknown)"));
170 break;
172 break;
173 case 2:
174 return QString().sprintf("%d%%", m_progress.value(m_jobs.at(index.row())));
175 break;
176 case 3:
177 return m_details.value(m_jobs.at(index.row()));
178 break;
179 default:
180 return QVariant();
181 break;
185 else if(role == Qt::DecorationRole)
187 if(index.row() >= 0 && index.row() < m_jobs.count() && index.column() == 0)
189 switch(m_status.value(m_jobs.at(index.row())))
191 case JobStatus_Enqueued:
192 return QIcon(":/buttons/hourglass.png");
193 break;
194 case JobStatus_Starting:
195 return QIcon(":/buttons/lightning.png");
196 break;
197 case JobStatus_Indexing:
198 return QIcon(":/buttons/find.png");
199 break;
200 case JobStatus_Running:
201 case JobStatus_Running_Pass1:
202 case JobStatus_Running_Pass2:
203 return QIcon(":/buttons/play.png");
204 break;
205 case JobStatus_Completed:
206 return QIcon(":/buttons/accept.png");
207 break;
208 case JobStatus_Failed:
209 return QIcon(":/buttons/exclamation.png");
210 break;
211 case JobStatus_Pausing:
212 return QIcon(":/buttons/clock_pause.png");
213 break;
214 case JobStatus_Paused:
215 return QIcon(":/buttons/suspended.png");
216 break;
217 case JobStatus_Resuming:
218 return QIcon(":/buttons/clock_play.png");
219 break;
220 case JobStatus_Aborting:
221 return QIcon(":/buttons/clock_stop.png");
222 break;
223 case JobStatus_Aborted:
224 return QIcon(":/buttons/error.png");
225 break;
226 default:
227 return QVariant();
228 break;
233 return QVariant();
236 ///////////////////////////////////////////////////////////////////////////////
237 // Public interface
238 ///////////////////////////////////////////////////////////////////////////////
240 QModelIndex JobListModel::insertJob(EncodeThread *thread)
242 QUuid id = thread->getId();
244 if(m_jobs.contains(id))
246 return QModelIndex();
249 QString config = "N/A";
251 switch(thread->options()->rcMode())
253 case OptionsModel::EncType_X264:
254 config = QString("x264");
255 break;
256 case OptionsModel::EncType_X265:
257 config = QString("x265");
258 break;
261 switch(thread->options()->rcMode())
263 case OptionsModel::RCMode_CRF:
264 config = QString("%1 CRF@%2") .arg(config, QString::number(thread->options()->quantizer()));
265 break;
266 case OptionsModel::RCMode_CQ:
267 config = QString("%1 CQ@%2") .arg(config, QString::number(qRound(thread->options()->quantizer())));
268 break;
269 case OptionsModel::RCMode_2Pass:
270 config = QString("%1 2Pass@%2").arg(config, QString::number(thread->options()->bitrate()));
271 break;
272 case OptionsModel::RCMode_ABR:
273 config = QString("%1 ABR@%2") .arg(config, QString::number(thread->options()->bitrate()));
274 break;
277 int n = 2;
278 QString jobName = QString("%1 (%2)").arg(QFileInfo(thread->sourceFileName()).completeBaseName().simplified(), config);
280 forever
282 bool unique = true;
283 for(int i = 0; i < m_jobs.count(); i++)
285 if(m_name.value(m_jobs.at(i)).compare(jobName, Qt::CaseInsensitive) == 0)
287 unique = false;
288 break;
291 if(!unique)
293 jobName = QString("%1 %2 (%3)").arg(QFileInfo(thread->sourceFileName()).completeBaseName().simplified(), QString::number(n++), config);
294 continue;
296 break;
299 LogFileModel *logFile = new LogFileModel(thread->sourceFileName(), thread->outputFileName(), config);
301 beginInsertRows(QModelIndex(), m_jobs.count(), m_jobs.count());
302 m_jobs.append(id);
303 m_name.insert(id, jobName);
304 m_status.insert(id, JobStatus_Enqueued);
305 m_progress.insert(id, 0);
306 m_threads.insert(id, thread);
307 m_logFile.insert(id, logFile);
308 m_details.insert(id, tr("Not started yet."));
309 endInsertRows();
311 connect(thread, SIGNAL(statusChanged(QUuid, JobStatus)), this, SLOT(updateStatus(QUuid, JobStatus)), Qt::QueuedConnection);
312 connect(thread, SIGNAL(progressChanged(QUuid, unsigned int)), this, SLOT(updateProgress(QUuid, unsigned int)), Qt::QueuedConnection);
313 connect(thread, SIGNAL(messageLogged(QUuid, QString)), logFile, SLOT(addLogMessage(QUuid, QString)), Qt::QueuedConnection);
314 connect(thread, SIGNAL(detailsChanged(QUuid, QString)), this, SLOT(updateDetails(QUuid, QString)), Qt::QueuedConnection);
316 return createIndex(m_jobs.count() - 1, 0, NULL);
319 bool JobListModel::startJob(const QModelIndex &index)
321 if(VALID_INDEX(index))
323 QUuid id = m_jobs.at(index.row());
324 if(m_status.value(id) == JobStatus_Enqueued)
326 updateStatus(id, JobStatus_Starting);
327 updateDetails(id, tr("Starting up, please wait..."));
328 m_threads.value(id)->start();
329 return true;
333 return false;
336 bool JobListModel::pauseJob(const QModelIndex &index)
338 if(VALID_INDEX(index))
340 QUuid id = m_jobs.at(index.row());
341 JobStatus status = m_status.value(id);
342 if((status == JobStatus_Indexing) || (status == JobStatus_Running) ||
343 (status == JobStatus_Running_Pass1) || (status == JobStatus_Running_Pass2))
345 updateStatus(id, JobStatus_Pausing);
346 m_threads.value(id)->pauseJob();
347 return true;
351 return false;
354 bool JobListModel::resumeJob(const QModelIndex &index)
356 if(VALID_INDEX(index))
358 QUuid id = m_jobs.at(index.row());
359 JobStatus status = m_status.value(id);
360 if(status == JobStatus_Paused)
362 updateStatus(id, JobStatus_Resuming);
363 m_threads.value(id)->resumeJob();
364 return true;
368 return false;
371 bool JobListModel::abortJob(const QModelIndex &index)
373 if(VALID_INDEX(index))
375 QUuid id = m_jobs.at(index.row());
376 if(m_status.value(id) == JobStatus_Indexing || m_status.value(id) == JobStatus_Running ||
377 m_status.value(id) == JobStatus_Running_Pass1 || JobStatus_Running_Pass2)
379 updateStatus(id, JobStatus_Aborting);
380 m_threads.value(id)->abortJob();
381 return true;
385 return false;
388 bool JobListModel::deleteJob(const QModelIndex &index)
390 if(VALID_INDEX(index))
392 QUuid id = m_jobs.at(index.row());
393 if(m_status.value(id) == JobStatus_Completed || m_status.value(id) == JobStatus_Failed ||
394 m_status.value(id) == JobStatus_Aborted || m_status.value(id) == JobStatus_Enqueued)
396 int idx = index.row();
397 QUuid id = m_jobs.at(idx);
398 EncodeThread *thread = m_threads.value(id, NULL);
399 LogFileModel *logFile = m_logFile.value(id, NULL);
400 if((thread == NULL) || (!thread->isRunning()))
403 beginRemoveRows(QModelIndex(), idx, idx);
404 m_jobs.removeAt(index.row());
405 m_name.remove(id);
406 m_threads.remove(id);
407 m_status.remove(id);
408 m_progress.remove(id);
409 m_logFile.remove(id);
410 m_details.remove(id);
411 endRemoveRows();
412 MUTILS_DELETE(thread);
413 MUTILS_DELETE(logFile);
414 return true;
419 return false;
422 bool JobListModel::moveJob(const QModelIndex &index, const int &direction)
424 if(VALID_INDEX(index))
426 if((direction == MOVE_UP) && (index.row() > 0))
428 beginMoveRows(QModelIndex(), index.row(), index.row(), QModelIndex(), index.row() - 1);
429 m_jobs.swap(index.row(), index.row() - 1);
430 endMoveRows();
431 return true;
433 if((direction == MOVE_DOWN) && (index.row() < m_jobs.size() - 1))
435 beginMoveRows(QModelIndex(), index.row(), index.row(), QModelIndex(), index.row() + 2);
436 m_jobs.swap(index.row(), index.row() + 1);
437 endMoveRows();
438 return true;
442 return false;
445 LogFileModel *JobListModel::getLogFile(const QModelIndex &index)
447 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
449 return m_logFile.value(m_jobs.at(index.row()));
452 return NULL;
455 const QString &JobListModel::getJobSourceFile(const QModelIndex &index)
457 static QString nullStr;
459 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
461 EncodeThread *thread = m_threads.value(m_jobs.at(index.row()));
462 return (thread != NULL) ? thread->sourceFileName() : nullStr;
465 return nullStr;
468 const QString &JobListModel::getJobOutputFile(const QModelIndex &index)
470 static QString nullStr;
472 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
474 EncodeThread *thread = m_threads.value(m_jobs.at(index.row()));
475 return (thread != NULL) ? thread->outputFileName() : nullStr;
478 return nullStr;
481 JobStatus JobListModel::getJobStatus(const QModelIndex &index)
483 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
485 return m_status.value(m_jobs.at(index.row()));
488 return static_cast<JobStatus>(-1);
491 unsigned int JobListModel::getJobProgress(const QModelIndex &index)
493 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
495 return m_progress.value(m_jobs.at(index.row()));
498 return 0;
501 const OptionsModel *JobListModel::getJobOptions(const QModelIndex &index)
503 static QString nullStr;
505 if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
507 EncodeThread *thread = m_threads.value(m_jobs.at(index.row()));
508 return (thread != NULL) ? thread->options() : NULL;
511 return NULL;
514 QModelIndex JobListModel::getJobIndexById(const QUuid &id)
516 if(m_jobs.contains(id))
518 return createIndex(m_jobs.indexOf(id), 0);
521 return QModelIndex();
524 ///////////////////////////////////////////////////////////////////////////////
525 // Slots
526 ///////////////////////////////////////////////////////////////////////////////
528 void JobListModel::updateStatus(const QUuid &jobId, JobStatus newStatus)
530 int index = -1;
532 if((index = m_jobs.indexOf(jobId)) >= 0)
534 m_status.insert(jobId, newStatus);
535 emit dataChanged(createIndex(index, 0), createIndex(index, 1));
537 if(m_preferences->getEnableSounds())
539 switch(newStatus)
541 case JobStatus_Completed:
542 MUtils::Sound::play_sound("tada", true);
543 break;
544 case JobStatus_Aborted:
545 MUtils::Sound::play_sound("shattering", true);
546 break;
547 case JobStatus_Failed:
548 MUtils::Sound::play_sound("failure", true);
549 break;
555 void JobListModel::updateProgress(const QUuid &jobId, unsigned int newProgress)
557 int index = -1;
559 if((index = m_jobs.indexOf(jobId)) >= 0)
561 m_progress.insert(jobId, qBound(0U, newProgress, 100U));
562 emit dataChanged(createIndex(index, 2), createIndex(index, 2));
566 void JobListModel::updateDetails(const QUuid &jobId, const QString &details)
568 int index = -1;
570 if((index = m_jobs.indexOf(jobId)) >= 0)
572 m_details.insert(jobId, details);
573 emit dataChanged(createIndex(index, 3), createIndex(index, 3));
577 size_t JobListModel::saveQueuedJobs(void)
579 const QString appDir = x264_data_path();
580 QSettings settings(QString("%1/queue.ini").arg(appDir), QSettings::IniFormat);
582 settings.clear();
583 settings.setValue(KEY_ENTRY_COUNT, 0);
584 size_t jobCounter = 0;
586 for(QList<QUuid>::ConstIterator iter = m_jobs.constBegin(); iter != m_jobs.constEnd(); iter++)
588 if(m_status.value(*iter) == JobStatus_Enqueued)
590 if(const EncodeThread *thread = m_threads.value(*iter))
592 settings.beginGroup(QString().sprintf(JOB_TEMPLATE, jobCounter++));
593 settings.setValue(KEY_SOURCE_FILE, thread->sourceFileName());
594 settings.setValue(KEY_OUTPUT_FILE, thread->outputFileName());
596 settings.beginGroup(KEY_ENC_OPTIONS);
597 OptionsModel::saveOptions(thread->options(), settings);
599 settings.endGroup();
600 settings.endGroup();
602 settings.setValue(KEY_ENTRY_COUNT, jobCounter);
607 settings.sync();
608 return jobCounter;
611 size_t JobListModel::loadQueuedJobs(const SysinfoModel *sysinfo)
613 const QString appDir = x264_data_path();
614 QSettings settings(QString("%1/queue.ini").arg(appDir), QSettings::IniFormat);
616 bool ok = false;
617 const size_t jobCounter = settings.value(KEY_ENTRY_COUNT, 0).toUInt(&ok);
619 if((!ok) || (jobCounter < 1))
621 return 0;
624 const QStringList groups = settings.childGroups();
625 for(size_t i = 0; i < jobCounter; i++)
627 if(!groups.contains(QString().sprintf(JOB_TEMPLATE, i)))
629 return 0;
633 size_t jobsCreated = 0;
634 for(size_t i = 0; i < jobCounter; i++)
636 settings.beginGroup(QString().sprintf(JOB_TEMPLATE, i));
637 const QString sourceFileName = settings.value(KEY_SOURCE_FILE, QString()).toString().trimmed();
638 const QString outputFileName = settings.value(KEY_OUTPUT_FILE, QString()).toString().trimmed();
640 if(sourceFileName.isEmpty() || outputFileName.isEmpty())
642 settings.endGroup();
643 continue;
646 settings.beginGroup(KEY_ENC_OPTIONS);
647 OptionsModel options(sysinfo);
648 const bool okay = OptionsModel::loadOptions(&options, settings);
650 settings.endGroup();
651 settings.endGroup();
653 if(okay)
655 EncodeThread *thread = new EncodeThread(sourceFileName, outputFileName, &options, sysinfo, m_preferences);
656 insertJob(thread);
657 jobsCreated++;
661 return jobsCreated;
664 void JobListModel::clearQueuedJobs(void)
666 const QString appDir = x264_data_path();
667 QSettings settings(QString("%1/queue.ini").arg(appDir), QSettings::IniFormat);
668 settings.clear();
669 settings.setValue(KEY_ENTRY_COUNT, 0);
670 settings.sync();