OSDN Git Service

Some re-design of AbstractEncoderInfo to allow more flexible encoder variants and...
[x264-launcher/x264-launcher.git] / src / model_jobList.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2016 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.
14 //
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.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 //Internal
23 #include "global.h"
24 #include "model_jobList.h"
25 #include "thread_encode.h"
26 #include "encoder_factory.h"
27 #include "model_options.h"
28 #include "model_preferences.h"
29 #include "resource.h"
30
31 //MUtils
32 #include <MUtils/Sound.h>
33
34 //Qt
35 #include <QIcon>
36 #include <QFileInfo>
37 #include <QSettings>
38
39 static const char *KEY_ENTRY_COUNT = "entry_count";
40 static const char *KEY_SOURCE_FILE = "source_file";
41 static const char *KEY_OUTPUT_FILE = "output_file";
42 static const char *KEY_ENC_OPTIONS = "enc_options";
43
44 static const char *JOB_TEMPLATE = "job_%08x";
45
46 #define VALID_INDEX(INDEX) ((INDEX).isValid() && ((INDEX).row() >= 0) && ((INDEX).row() < m_jobs.count()))
47
48 JobListModel::JobListModel(PreferencesModel *preferences)
49 {
50         m_preferences = preferences;
51 }
52
53 JobListModel::~JobListModel(void)
54 {
55         while(!m_jobs.isEmpty())
56         {
57                 QUuid id = m_jobs.takeFirst();
58                 EncodeThread *thread = m_threads.value(id, NULL);
59                 LogFileModel *logFile = m_logFile.value(id, NULL);
60                 MUTILS_DELETE(thread);
61                 MUTILS_DELETE(logFile);
62         }
63 }
64
65 ///////////////////////////////////////////////////////////////////////////////
66 // Model interface
67 ///////////////////////////////////////////////////////////////////////////////
68
69 int JobListModel::columnCount(const QModelIndex &parent) const
70 {
71         return 4;
72 }
73
74 int JobListModel::rowCount(const QModelIndex &parent) const
75 {
76         return m_jobs.count();
77 }
78
79 QVariant JobListModel::headerData(int section, Qt::Orientation orientation, int role) const 
80 {
81         if((orientation == Qt::Horizontal) && (role == Qt::DisplayRole))
82         {
83                 switch(section)
84                 {
85                 case 0:
86                         return QVariant::fromValue<QString>(tr("Job"));
87                         break;
88                 case 1:
89                         return QVariant::fromValue<QString>(tr("Status"));
90                         break;
91                 case 2:
92                         return QVariant::fromValue<QString>(tr("Progress"));
93                         break;
94                 case 3:
95                         return QVariant::fromValue<QString>(tr("Details"));
96                         break;
97                 default:
98                         return QVariant();
99                         break;
100                 }
101         }
102
103         return QVariant();
104 }
105
106 QModelIndex JobListModel::index(int row, int column, const QModelIndex &parent) const
107 {
108         return createIndex(row, column, NULL);
109 }
110
111 QModelIndex JobListModel::parent(const QModelIndex &index) const
112 {
113         return QModelIndex();
114 }
115
116 QVariant JobListModel::data(const QModelIndex &index, int role) const
117 {
118         if(role == Qt::DisplayRole)
119         {
120                 if(index.row() >= 0 && index.row() < m_jobs.count())
121                 {
122                         switch(index.column())
123                         {
124                         case 0:
125                                 return m_name.value(m_jobs.at(index.row()));
126                                 break;
127                         case 1:
128                                 switch(m_status.value(m_jobs.at(index.row())))
129                                 {
130                                 case JobStatus_Enqueued:
131                                         return QVariant::fromValue<QString>(tr("Enqueued."));
132                                         break;
133                                 case JobStatus_Starting:
134                                         return QVariant::fromValue<QString>(tr("Starting..."));
135                                         break;
136                                 case JobStatus_Indexing:
137                                         return QVariant::fromValue<QString>(tr("Indexing..."));
138                                         break;
139                                 case JobStatus_Running:
140                                         return QVariant::fromValue<QString>(tr("Running..."));
141                                         break;
142                                 case JobStatus_Running_Pass1:
143                                         return QVariant::fromValue<QString>(tr("Running... (Pass 1)"));
144                                         break;
145                                 case JobStatus_Running_Pass2:
146                                         return QVariant::fromValue<QString>(tr("Running... (Pass 2)"));
147                                         break;
148                                 case JobStatus_Completed:
149                                         return QVariant::fromValue<QString>(tr("Completed."));
150                                         break;
151                                 case JobStatus_Failed:
152                                         return QVariant::fromValue<QString>(tr("Failed!"));
153                                         break;
154                                 case JobStatus_Pausing:
155                                         return QVariant::fromValue<QString>(tr("Pausing..."));
156                                         break;
157                                 case JobStatus_Paused:
158                                         return QVariant::fromValue<QString>(tr("Paused."));
159                                         break;
160                                 case JobStatus_Resuming:
161                                         return QVariant::fromValue<QString>(tr("Resuming..."));
162                                         break;
163                                 case JobStatus_Aborting:
164                                         return QVariant::fromValue<QString>(tr("Aborting..."));
165                                         break;
166                                 case JobStatus_Aborted:
167                                         return QVariant::fromValue<QString>(tr("Aborted!"));
168                                         break;
169                                 default:
170                                         return QVariant::fromValue<QString>(tr("(Unknown)"));
171                                         break;
172                                 }
173                                 break;
174                         case 2:
175                                 return QString().sprintf("%d%%", m_progress.value(m_jobs.at(index.row())));
176                                 break;
177                         case 3:
178                                 return m_details.value(m_jobs.at(index.row()));
179                                 break;
180                         default:
181                                 return QVariant();
182                                 break;
183                         }
184                 }
185         }
186         else if(role == Qt::DecorationRole)
187         {
188                 if(index.row() >= 0 && index.row() < m_jobs.count() && index.column() == 0)
189                 {
190                         switch(m_status.value(m_jobs.at(index.row())))
191                         {
192                         case JobStatus_Enqueued:
193                                 return QIcon(":/buttons/hourglass.png");
194                                 break;
195                         case JobStatus_Starting:
196                                 return QIcon(":/buttons/lightning.png");
197                                 break;
198                         case JobStatus_Indexing:
199                                 return QIcon(":/buttons/find.png");
200                                 break;
201                         case JobStatus_Running:
202                         case JobStatus_Running_Pass1:
203                         case JobStatus_Running_Pass2:
204                                 return QIcon(":/buttons/play.png");
205                                 break;
206                         case JobStatus_Completed:
207                                 return QIcon(":/buttons/accept.png");
208                                 break;
209                         case JobStatus_Failed:
210                                 return QIcon(":/buttons/exclamation.png");
211                                 break;
212                         case JobStatus_Pausing:
213                                 return QIcon(":/buttons/clock_pause.png");
214                                 break;
215                         case JobStatus_Paused:
216                                 return QIcon(":/buttons/suspended.png");
217                                 break;
218                         case JobStatus_Resuming:
219                                 return QIcon(":/buttons/clock_play.png");
220                                 break;
221                         case JobStatus_Aborting:
222                                 return QIcon(":/buttons/clock_stop.png");
223                                 break;
224                         case JobStatus_Aborted:
225                                 return QIcon(":/buttons/error.png");
226                                 break;
227                         default:
228                                 return QVariant();
229                                 break;
230                         }
231                 }
232         }
233
234         return QVariant();
235 }
236
237 ///////////////////////////////////////////////////////////////////////////////
238 // Public interface
239 ///////////////////////////////////////////////////////////////////////////////
240
241 QModelIndex JobListModel::insertJob(EncodeThread *thread)
242 {
243         const QUuid id = thread->getId();
244         if(m_jobs.contains(id))
245         {
246                 return QModelIndex();
247         }
248         
249         const AbstractEncoderInfo &encoderInfo = EncoderFactory::getEncoderInfo(thread->options()->encType());
250         QString config = encoderInfo.getName();
251         switch(encoderInfo.rcModeToType(thread->options()->rcMode()))
252         {
253         case AbstractEncoderInfo::RC_TYPE_QUANTIZER:
254                 config.append(QString(", %1@%2").arg(encoderInfo.rcModeToString(thread->options()->rcMode()), QString::number(qRound(thread->options()->quantizer()))));
255                 break;
256         case AbstractEncoderInfo::RC_TYPE_RATE_KBPS:
257         case AbstractEncoderInfo::RC_TYPE_MULTIPASS:
258                 config.append(QString(", %1@%2").arg(encoderInfo.rcModeToString(thread->options()->rcMode()), QString::number(thread->options()->bitrate())));
259                 break;
260         }
261
262         int n = 2;
263         QString jobName = QString("%1 (%2)").arg(QFileInfo(thread->sourceFileName()).completeBaseName().simplified(), config);
264         forever
265         {
266                 bool unique = true;
267                 for(int i = 0; i < m_jobs.count(); i++)
268                 {
269                         if(m_name.value(m_jobs.at(i)).compare(jobName, Qt::CaseInsensitive) == 0)
270                         {
271                                 unique = false;
272                                 break;
273                         }
274                 }
275                 if(!unique)
276                 {
277                         jobName = QString("%1 %2 (%3)").arg(QFileInfo(thread->sourceFileName()).completeBaseName().simplified(), QString::number(n++), config);
278                         continue;
279                 }
280                 break;
281         }
282         
283         LogFileModel *logFile = new LogFileModel(thread->sourceFileName(), thread->outputFileName(), config);
284         
285         beginInsertRows(QModelIndex(), m_jobs.count(), m_jobs.count());
286         m_jobs.append(id);
287         m_name.insert(id, jobName);
288         m_status.insert(id, JobStatus_Enqueued);
289         m_progress.insert(id, 0);
290         m_threads.insert(id, thread);
291         m_logFile.insert(id, logFile);
292         m_details.insert(id, tr("Not started yet."));
293         endInsertRows();
294
295         connect(thread, SIGNAL(statusChanged(QUuid, JobStatus)), this, SLOT(updateStatus(QUuid, JobStatus)), Qt::QueuedConnection);
296         connect(thread, SIGNAL(progressChanged(QUuid, unsigned int)), this, SLOT(updateProgress(QUuid, unsigned int)), Qt::QueuedConnection);
297         connect(thread, SIGNAL(messageLogged(QUuid, QString)), logFile, SLOT(addLogMessage(QUuid, QString)), Qt::QueuedConnection);
298         connect(thread, SIGNAL(detailsChanged(QUuid, QString)), this, SLOT(updateDetails(QUuid, QString)), Qt::QueuedConnection);
299         
300         return createIndex(m_jobs.count() - 1, 0, NULL);
301 }
302
303 bool JobListModel::startJob(const QModelIndex &index)
304 {
305         if(VALID_INDEX(index))
306         {
307                 QUuid id = m_jobs.at(index.row());
308                 if(m_status.value(id) == JobStatus_Enqueued)
309                 {
310                         updateStatus(id, JobStatus_Starting);
311                         updateDetails(id, tr("Starting up, please wait..."));
312                         m_threads.value(id)->start();
313                         return true;
314                 }
315         }
316
317         return false;
318 }
319
320 bool JobListModel::pauseJob(const QModelIndex &index)
321 {
322         if(VALID_INDEX(index))
323         {
324                 QUuid id = m_jobs.at(index.row());
325                 JobStatus status = m_status.value(id);
326                 if((status == JobStatus_Indexing) || (status == JobStatus_Running) ||
327                         (status == JobStatus_Running_Pass1) || (status == JobStatus_Running_Pass2))
328                 {
329                         updateStatus(id, JobStatus_Pausing);
330                         m_threads.value(id)->pauseJob();
331                         return true;
332                 }
333         }
334
335         return false;
336 }
337
338 bool JobListModel::resumeJob(const QModelIndex &index)
339 {
340         if(VALID_INDEX(index))
341         {
342                 QUuid id = m_jobs.at(index.row());
343                 JobStatus status = m_status.value(id);
344                 if(status == JobStatus_Paused)
345                 {
346                         updateStatus(id, JobStatus_Resuming);
347                         m_threads.value(id)->resumeJob();
348                         return true;
349                 }
350         }
351
352         return false;
353 }
354
355 bool JobListModel::abortJob(const QModelIndex &index)
356 {
357         if(VALID_INDEX(index))
358         {
359                 QUuid id = m_jobs.at(index.row());
360                 if(m_status.value(id) == JobStatus_Indexing || m_status.value(id) == JobStatus_Running ||
361                         m_status.value(id) == JobStatus_Running_Pass1 || JobStatus_Running_Pass2)
362                 {
363                         updateStatus(id, JobStatus_Aborting);
364                         m_threads.value(id)->abortJob();
365                         return true;
366                 }
367         }
368
369         return false;
370 }
371
372 bool JobListModel::deleteJob(const QModelIndex &index)
373 {
374         if(VALID_INDEX(index))
375         {
376                 QUuid id = m_jobs.at(index.row());
377                 if(m_status.value(id) == JobStatus_Completed || m_status.value(id) == JobStatus_Failed ||
378                         m_status.value(id) == JobStatus_Aborted || m_status.value(id) == JobStatus_Enqueued)
379                 {
380                         int idx = index.row();
381                         QUuid id = m_jobs.at(idx);
382                         EncodeThread *thread = m_threads.value(id, NULL);
383                         LogFileModel *logFile = m_logFile.value(id, NULL);
384                         if((thread == NULL) || (!thread->isRunning()))
385                         {
386                                 
387                                 beginRemoveRows(QModelIndex(), idx, idx);
388                                 m_jobs.removeAt(index.row());
389                                 m_name.remove(id);
390                                 m_threads.remove(id);
391                                 m_status.remove(id);
392                                 m_progress.remove(id);
393                                 m_logFile.remove(id);
394                                 m_details.remove(id);
395                                 endRemoveRows();
396                                 MUTILS_DELETE(thread);
397                                 MUTILS_DELETE(logFile);
398                                 return true;
399                         }
400                 }
401         }
402
403         return false;
404 }
405
406 bool JobListModel::moveJob(const QModelIndex &index, const int &direction)
407 {
408         if(VALID_INDEX(index))
409         {
410                 if((direction == MOVE_UP) && (index.row() > 0))
411                 {
412                         beginMoveRows(QModelIndex(), index.row(), index.row(), QModelIndex(), index.row() - 1);
413                         m_jobs.swap(index.row(), index.row() - 1);
414                         endMoveRows();
415                         return true;
416                 }
417                 if((direction == MOVE_DOWN) && (index.row() < m_jobs.size() - 1))
418                 {
419                         beginMoveRows(QModelIndex(), index.row(), index.row(), QModelIndex(), index.row() + 2);
420                         m_jobs.swap(index.row(), index.row() + 1);
421                         endMoveRows();
422                         return true;
423                 }
424         }
425
426         return false;
427 }
428
429 LogFileModel *JobListModel::getLogFile(const QModelIndex &index)
430 {
431         if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
432         {
433                 return m_logFile.value(m_jobs.at(index.row()));
434         }
435
436         return NULL;
437 }
438
439 const QString &JobListModel::getJobSourceFile(const QModelIndex &index)
440 {
441         static QString nullStr;
442         
443         if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
444         {
445                 EncodeThread *thread = m_threads.value(m_jobs.at(index.row()));
446                 return (thread != NULL) ? thread->sourceFileName() : nullStr;
447         }
448
449         return nullStr;
450 }
451
452 const QString &JobListModel::getJobOutputFile(const QModelIndex &index)
453 {
454         static QString nullStr;
455         
456         if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
457         {
458                 EncodeThread *thread = m_threads.value(m_jobs.at(index.row()));
459                 return (thread != NULL) ? thread->outputFileName() : nullStr;
460         }
461
462         return nullStr;
463 }
464
465 JobStatus JobListModel::getJobStatus(const QModelIndex &index)
466 {
467         if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
468         {
469                 return m_status.value(m_jobs.at(index.row()));
470         }
471
472         return static_cast<JobStatus>(-1);
473 }
474
475 unsigned int JobListModel::getJobProgress(const QModelIndex &index)
476 {
477         if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
478         {
479                 return m_progress.value(m_jobs.at(index.row()));
480         }
481
482         return 0;
483 }
484
485 const OptionsModel *JobListModel::getJobOptions(const QModelIndex &index)
486 {
487         static QString nullStr;
488         
489         if(index.isValid() && index.row() >= 0 && index.row() < m_jobs.count())
490         {
491                 EncodeThread *thread = m_threads.value(m_jobs.at(index.row()));
492                 return (thread != NULL) ? thread->options() : NULL;
493         }
494
495         return NULL;
496 }
497
498 QModelIndex JobListModel::getJobIndexById(const QUuid &id)
499 {
500         if(m_jobs.contains(id))
501         {
502                 return createIndex(m_jobs.indexOf(id), 0);
503         }
504
505         return QModelIndex();
506 }
507
508 ///////////////////////////////////////////////////////////////////////////////
509 // Slots
510 ///////////////////////////////////////////////////////////////////////////////
511
512 void JobListModel::updateStatus(const QUuid &jobId, JobStatus newStatus)
513 {
514         int index = -1;
515         
516         if((index = m_jobs.indexOf(jobId)) >= 0)
517         {
518                 m_status.insert(jobId, newStatus);
519                 emit dataChanged(createIndex(index, 0), createIndex(index, 1));
520
521                 if(m_preferences->getEnableSounds())
522                 {
523                         switch(newStatus)
524                         {
525                         case JobStatus_Completed:
526                                 MUtils::Sound::play_sound("tada", true);
527                                 break;
528                         case JobStatus_Aborted:
529                                 MUtils::Sound::play_sound("shattering", true);
530                                 break;
531                         case JobStatus_Failed:
532                                 MUtils::Sound::play_sound("failure", true);
533                                 break;
534                         }
535                 }
536         }
537 }
538
539 void JobListModel::updateProgress(const QUuid &jobId, unsigned int newProgress)
540 {
541         int index = -1;
542
543         if((index = m_jobs.indexOf(jobId)) >= 0)
544         {
545                 m_progress.insert(jobId, qBound(0U, newProgress, 100U));
546                 emit dataChanged(createIndex(index, 2), createIndex(index, 2));
547         }
548 }
549
550 void JobListModel::updateDetails(const QUuid &jobId, const QString &details)
551 {
552         int index = -1;
553
554         if((index = m_jobs.indexOf(jobId)) >= 0)
555         {
556                 m_details.insert(jobId, details);
557                 emit dataChanged(createIndex(index, 3), createIndex(index, 3));
558         }
559 }
560
561 size_t JobListModel::saveQueuedJobs(void)
562 {
563         const QString appDir = x264_data_path();
564         QSettings settings(QString("%1/queue.ini").arg(appDir), QSettings::IniFormat);
565         
566         settings.clear();
567         settings.setValue(KEY_ENTRY_COUNT, 0);
568         size_t jobCounter = 0;
569
570         for(QList<QUuid>::ConstIterator iter = m_jobs.constBegin(); iter != m_jobs.constEnd(); iter++)
571         {
572                 if(m_status.value(*iter) == JobStatus_Enqueued)
573                 {
574                         if(const EncodeThread *thread = m_threads.value(*iter))
575                         {
576                                 settings.beginGroup(QString().sprintf(JOB_TEMPLATE, jobCounter++));
577                                 settings.setValue(KEY_SOURCE_FILE, thread->sourceFileName());
578                                 settings.setValue(KEY_OUTPUT_FILE, thread->outputFileName());
579
580                                 settings.beginGroup(KEY_ENC_OPTIONS);
581                                 OptionsModel::saveOptions(thread->options(), settings);
582
583                                 settings.endGroup();
584                                 settings.endGroup();
585
586                                 settings.setValue(KEY_ENTRY_COUNT, jobCounter);
587                         }
588                 }
589         }
590
591         settings.sync();
592         return jobCounter;
593 }
594
595 size_t JobListModel::loadQueuedJobs(const SysinfoModel *sysinfo)
596 {
597         const QString appDir = x264_data_path();
598         QSettings settings(QString("%1/queue.ini").arg(appDir), QSettings::IniFormat);
599
600         bool ok = false;
601         const size_t jobCounter = settings.value(KEY_ENTRY_COUNT, 0).toUInt(&ok);
602
603         if((!ok) || (jobCounter < 1))
604         {
605                 return 0;
606         }
607
608         const QStringList groups = settings.childGroups();
609         for(size_t i = 0; i < jobCounter; i++)
610         {
611                 if(!groups.contains(QString().sprintf(JOB_TEMPLATE, i)))
612                 {
613                         return 0;
614                 }
615         }
616
617         size_t jobsCreated = 0;
618         for(size_t i = 0; i < jobCounter; i++)
619         {
620                 settings.beginGroup(QString().sprintf(JOB_TEMPLATE, i));
621                 const QString sourceFileName = settings.value(KEY_SOURCE_FILE, QString()).toString().trimmed();
622                 const QString outputFileName = settings.value(KEY_OUTPUT_FILE, QString()).toString().trimmed();
623
624                 if(sourceFileName.isEmpty() || outputFileName.isEmpty())
625                 {
626                         settings.endGroup();
627                         continue;
628                 }
629
630                 settings.beginGroup(KEY_ENC_OPTIONS);
631                 OptionsModel options(sysinfo);
632                 const bool okay = OptionsModel::loadOptions(&options, settings);
633
634                 settings.endGroup();
635                 settings.endGroup();
636
637                 if(okay)
638                 {
639                         EncodeThread *thread = new EncodeThread(sourceFileName, outputFileName, &options, sysinfo, m_preferences);
640                         insertJob(thread);
641                         jobsCreated++;
642                 }
643         }
644
645         return jobsCreated;
646 }
647
648 void JobListModel::clearQueuedJobs(void)
649 {
650         const QString appDir = x264_data_path();
651         QSettings settings(QString("%1/queue.ini").arg(appDir), QSettings::IniFormat);
652         settings.clear();
653         settings.setValue(KEY_ENTRY_COUNT, 0);
654         settings.sync();
655 }