try to index media immediately on import
Since indexing is supplemental, it can be made a background task that can occur at more or less any time. So we run it as soon as possible.
This commit is contained in:
@@ -26,6 +26,8 @@
|
||||
|
||||
#include "codec/ffmpeg/ffmpegdecoder.h"
|
||||
#include "codec/oiio/oiiodecoder.h"
|
||||
#include "task/index/index.h"
|
||||
#include "task/taskmanager.h"
|
||||
|
||||
Decoder::Decoder() :
|
||||
open_(false),
|
||||
@@ -126,6 +128,14 @@ bool Decoder::ProbeMedia(Footage *f)
|
||||
|
||||
// FIXME: Cache the results so we don't have to probe if this media is added a second time
|
||||
|
||||
// Start an index task
|
||||
foreach (StreamPtr stream, f->streams()) {
|
||||
qDebug() << "Starting index task on" << stream->footage()->filename() << stream->index();
|
||||
|
||||
IndexTask* index_task = new IndexTask(stream);
|
||||
TaskManager::instance()->AddTask(index_task);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -161,3 +171,7 @@ void Decoder::Conform(const AudioRenderingParams ¶ms)
|
||||
qCritical() << "Conform called on an audio decoder that does not have a handler for it:" << id();
|
||||
abort();
|
||||
}
|
||||
|
||||
void Decoder::Index()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -219,6 +219,17 @@ public:
|
||||
*/
|
||||
virtual void Conform(const AudioRenderingParams& params);
|
||||
|
||||
/**
|
||||
* @brief Create an index for this media
|
||||
*
|
||||
* Indexes are used to improve speed and reliability of imported media. Calling Retrieve() will automatically check
|
||||
* for an index and create one if it doesn't exist.
|
||||
*
|
||||
* Indexing is slow so it's recommended to do it in a background thread. Index() must be called while the Decoder is
|
||||
* open, and does not automatically call Open() and Close() the Decoder. The caller must call thse manually.
|
||||
*/
|
||||
virtual void Index();
|
||||
|
||||
protected:
|
||||
bool open_;
|
||||
|
||||
|
||||
@@ -62,6 +62,11 @@ bool FFmpegDecoder::Open()
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!stream()) {
|
||||
Error(QStringLiteral("Tried to open a decoder with no footage stream set"));
|
||||
return false;
|
||||
}
|
||||
|
||||
int error_code;
|
||||
|
||||
// Convert QString to a C string
|
||||
@@ -344,7 +349,7 @@ FramePtr FFmpegDecoder::RetrieveAudio(const rational &timecode, const rational &
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ValidateIndex();
|
||||
Index();
|
||||
|
||||
Conform(params);
|
||||
|
||||
@@ -437,7 +442,7 @@ void FFmpegDecoder::Conform(const AudioRenderingParams ¶ms)
|
||||
return;
|
||||
}
|
||||
|
||||
ValidateIndex();
|
||||
Index();
|
||||
|
||||
// Get indexed WAV file
|
||||
WaveInput input(GetIndexFilename());
|
||||
@@ -659,7 +664,7 @@ bool FFmpegDecoder::Probe(Footage *f)
|
||||
Open();
|
||||
|
||||
// Use index to find duration
|
||||
ValidateIndex();
|
||||
Index();
|
||||
|
||||
// Use last frame index as the duration
|
||||
// FIXME: Does this skip the last frame?
|
||||
@@ -706,17 +711,25 @@ void FFmpegDecoder::Index()
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset state
|
||||
Seek(0);
|
||||
stream()->index_lock_.lock();
|
||||
|
||||
if (!LoadIndex()) {
|
||||
|
||||
// Reset state
|
||||
Seek(0);
|
||||
|
||||
if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
IndexVideo(pkt_, frame_);
|
||||
} else if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
IndexAudio(pkt_, frame_);
|
||||
}
|
||||
|
||||
// Reset state
|
||||
Seek(0);
|
||||
|
||||
if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
IndexVideo(pkt_, frame_);
|
||||
} else if (avstream_->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
IndexAudio(pkt_, frame_);
|
||||
}
|
||||
|
||||
// Reset state
|
||||
Seek(0);
|
||||
stream()->index_lock_.unlock();
|
||||
}
|
||||
|
||||
QString FFmpegDecoder::GetIndexFilename()
|
||||
@@ -756,17 +769,6 @@ QString FFmpegDecoder::GetConformedFilename(const AudioRenderingParams ¶ms)
|
||||
return index_fn;
|
||||
}
|
||||
|
||||
void FFmpegDecoder::ValidateIndex()
|
||||
{
|
||||
stream()->index_lock_.lock();
|
||||
|
||||
if (!LoadIndex()) {
|
||||
Index();
|
||||
}
|
||||
|
||||
stream()->index_lock_.unlock();
|
||||
}
|
||||
|
||||
bool FFmpegDecoder::LoadIndex()
|
||||
{
|
||||
switch (avstream_->codecpar->codec_type) {
|
||||
@@ -991,7 +993,7 @@ int64_t FFmpegDecoder::GetClosestTimestampInIndex(const int64_t &ts)
|
||||
{
|
||||
// Index now if we haven't already
|
||||
if (frame_index_.isEmpty()) {
|
||||
ValidateIndex();
|
||||
Index();
|
||||
}
|
||||
|
||||
if (frame_index_.isEmpty()) {
|
||||
|
||||
@@ -92,18 +92,7 @@ private:
|
||||
*/
|
||||
int GetFrame(AVPacket* pkt, AVFrame* frame);
|
||||
|
||||
/**
|
||||
* @brief Create an index for this media
|
||||
*
|
||||
* Indexes are used to improve speed and reliability of imported media. Calling Retrieve() will automatically check
|
||||
* for an index and create one if it doesn't exist.
|
||||
*
|
||||
* Indexing is slow so it's recommended to do it in a background thread. Index() must be called while the Decoder is
|
||||
* open, and does not automatically call Open() and Close() the Decoder. The caller must call thse manually.
|
||||
*
|
||||
* FIXME: This should perhaps become a common function for the base Decoder class
|
||||
*/
|
||||
void Index();
|
||||
virtual void Index() override;
|
||||
|
||||
/**
|
||||
* @brief Returns the filename for the index
|
||||
@@ -117,8 +106,6 @@ private:
|
||||
*/
|
||||
QString GetConformedFilename(const AudioRenderingParams ¶ms);
|
||||
|
||||
void ValidateIndex();
|
||||
|
||||
/**
|
||||
* @brief Used internally to load a frame index into frame_index_
|
||||
*
|
||||
|
||||
+16
-32
@@ -133,6 +133,8 @@ void Core::Stop()
|
||||
|
||||
MenuShared::DestroyInstance();
|
||||
|
||||
TaskManager::DestroyInstance();
|
||||
|
||||
PanelManager::DestroyInstance();
|
||||
|
||||
AudioManager::DestroyInstance();
|
||||
@@ -184,26 +186,6 @@ const bool &Core::snapping()
|
||||
return snapping_;
|
||||
}
|
||||
|
||||
void Core::StartModalTask(Task *t)
|
||||
{
|
||||
QDialog dialog(main_window_);
|
||||
|
||||
QHBoxLayout* layout = new QHBoxLayout(&dialog);
|
||||
layout->setMargin(0);
|
||||
|
||||
TaskViewItem* task_view = new TaskViewItem(&dialog);
|
||||
task_view->SetTask(t);
|
||||
layout->addWidget(task_view);
|
||||
|
||||
connect(t, SIGNAL(Finished()), &dialog, SLOT(accept()));
|
||||
|
||||
// FIXME: Risk of task finishing before dialog execs?
|
||||
|
||||
if (t->Start()) {
|
||||
dialog.exec();
|
||||
}
|
||||
}
|
||||
|
||||
void Core::SetTool(const Tool::Item &tool)
|
||||
{
|
||||
tool_ = tool;
|
||||
@@ -375,7 +357,6 @@ void Core::ImportTaskComplete(QUndoCommand *command)
|
||||
|
||||
void Core::DeclareTypesForQt()
|
||||
{
|
||||
qRegisterMetaType<Task::Status>("Task::Status");
|
||||
qRegisterMetaType<NodeDependency>();
|
||||
qRegisterMetaType<rational>();
|
||||
qRegisterMetaType<OpenGLTexturePtr>();
|
||||
@@ -404,11 +385,14 @@ void Core::StartGUI(bool full_screen)
|
||||
// Initialize disk service
|
||||
DiskManager::CreateInstance();
|
||||
|
||||
// Initialize task manager
|
||||
TaskManager::CreateInstance();
|
||||
|
||||
// Connect the PanelFocusManager to the application's focus change signal
|
||||
connect(qApp,
|
||||
SIGNAL(focusChanged(QWidget*, QWidget*)),
|
||||
&QApplication::focusChanged,
|
||||
PanelManager::instance(),
|
||||
SLOT(FocusChanged(QWidget*, QWidget*)));
|
||||
&PanelManager::FocusChanged);
|
||||
|
||||
// Create main window and open it
|
||||
main_window_ = new MainWindow();
|
||||
@@ -419,7 +403,7 @@ void Core::StartGUI(bool full_screen)
|
||||
}
|
||||
|
||||
// When a new project is opened, update the mainwindow
|
||||
connect(this, SIGNAL(ProjectOpened(Project*)), main_window_, SLOT(ProjectOpen(Project*)));
|
||||
connect(this, &Core::ProjectOpened, main_window_, &MainWindow::ProjectOpen);
|
||||
|
||||
// Start autorecovery timer using the config value as its interval
|
||||
SetAutorecoveryInterval(Config::Current()["AutorecoveryInterval"].toInt());
|
||||
@@ -616,7 +600,7 @@ int Core::CountFilesInFileList(const QFileInfoList &filenames)
|
||||
return file_count;
|
||||
}
|
||||
|
||||
void Core::InitiateOpenSaveProcess(ProjectFileManagerBase *manager, const QString& dialog_text, const QString& dialog_title)
|
||||
void Core::InitiateOpenSaveProcess(Task *manager, const QString& dialog_text, const QString& dialog_title)
|
||||
{
|
||||
// Create save dialog
|
||||
LoadSaveDialog* lsd = new LoadSaveDialog(dialog_text, dialog_title, main_window_);
|
||||
@@ -630,17 +614,17 @@ void Core::InitiateOpenSaveProcess(ProjectFileManagerBase *manager, const QStrin
|
||||
manager->moveToThread(save_thread);
|
||||
|
||||
// Connect the save manager progress signal to the progress bar update on the dialog
|
||||
connect(manager, &ProjectFileManagerBase::ProgressChanged, lsd, &LoadSaveDialog::SetProgress, Qt::QueuedConnection);
|
||||
connect(manager, &Task::ProgressChanged, lsd, &LoadSaveDialog::SetProgress, Qt::QueuedConnection);
|
||||
|
||||
// Connect cancel signal (must be a direct connection or it'll be queued after the save is already finished)
|
||||
connect(lsd, &LoadSaveDialog::Cancelled, manager, &ProjectFileManagerBase::Cancel, Qt::DirectConnection);
|
||||
connect(lsd, &LoadSaveDialog::Cancelled, manager, &Task::Cancel, Qt::DirectConnection);
|
||||
|
||||
// Connect cleanup functions (ensure everything new'd in this function is deleteLater'd)
|
||||
connect(manager, &ProjectFileManagerBase::Finished, lsd, &LoadSaveDialog::accept, Qt::QueuedConnection);
|
||||
connect(manager, &ProjectFileManagerBase::Finished, lsd, &LoadSaveDialog::deleteLater, Qt::QueuedConnection);
|
||||
connect(manager, &ProjectFileManagerBase::Finished, manager, &ProjectFileManagerBase::deleteLater, Qt::QueuedConnection);
|
||||
connect(manager, &ProjectFileManagerBase::Finished, save_thread, &QThread::quit, Qt::QueuedConnection);
|
||||
connect(manager, &ProjectFileManagerBase::Finished, save_thread, &QThread::deleteLater, Qt::QueuedConnection);
|
||||
connect(manager, &Task::Finished, lsd, &LoadSaveDialog::accept, Qt::QueuedConnection);
|
||||
connect(manager, &Task::Finished, lsd, &LoadSaveDialog::deleteLater, Qt::QueuedConnection);
|
||||
connect(manager, &Task::Finished, manager, &Task::deleteLater, Qt::QueuedConnection);
|
||||
connect(manager, &Task::Finished, save_thread, &QThread::quit, Qt::QueuedConnection);
|
||||
connect(manager, &Task::Finished, save_thread, &QThread::deleteLater, Qt::QueuedConnection);
|
||||
|
||||
// Start the save process
|
||||
QMetaObject::invokeMethod(manager, "Start", Qt::QueuedConnection);
|
||||
|
||||
+1
-11
@@ -28,7 +28,6 @@
|
||||
#include "common/rational.h"
|
||||
#include "project/item/footage/footage.h"
|
||||
#include "project/project.h"
|
||||
#include "project/projectfilemanagerbase.h"
|
||||
#include "project/projectviewmodel.h"
|
||||
#include "task/task.h"
|
||||
#include "tool/tool.h"
|
||||
@@ -111,15 +110,6 @@ public:
|
||||
*/
|
||||
const bool& snapping();
|
||||
|
||||
/**
|
||||
* @brief Starts a modal task
|
||||
*
|
||||
* This function does NOT take ownership of the Task.
|
||||
*
|
||||
* @param t
|
||||
*/
|
||||
void StartModalTask(Task* t);
|
||||
|
||||
/**
|
||||
* @brief Get the currently active project
|
||||
*
|
||||
@@ -283,7 +273,7 @@ private:
|
||||
* The load and save process are largely similar, both OpenProjectInternal() and SaveProjectInternal() can run
|
||||
* this function with some minor setup differences.
|
||||
*/
|
||||
void InitiateOpenSaveProcess(ProjectFileManagerBase* manager, const QString &dialog_text, const QString &dialog_title);
|
||||
void InitiateOpenSaveProcess(Task* manager, const QString &dialog_text, const QString &dialog_title);
|
||||
|
||||
/**
|
||||
* @brief Declare custom types/classes for Qt's signal/slot system
|
||||
|
||||
@@ -20,8 +20,6 @@ set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
project/project.h
|
||||
project/project.cpp
|
||||
project/projectfilemanagerbase.h
|
||||
project/projectfilemanagerbase.cpp
|
||||
project/projectimportmanager.h
|
||||
project/projectimportmanager.cpp
|
||||
project/projectloadmanager.h
|
||||
|
||||
@@ -134,11 +134,11 @@ void Footage::set_timestamp(const QDateTime &t)
|
||||
|
||||
void Footage::add_stream(StreamPtr s)
|
||||
{
|
||||
// Set its footage parent to this
|
||||
s->set_footage(this);
|
||||
|
||||
// Add a copy of this stream to the list
|
||||
streams_.append(s);
|
||||
|
||||
// Set its footage parent to this
|
||||
streams_.last()->set_footage(this);
|
||||
}
|
||||
|
||||
StreamPtr Footage::stream(int index) const
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
#include "projectfilemanagerbase.h"
|
||||
|
||||
ProjectFileManagerBase::ProjectFileManagerBase() :
|
||||
cancelled_(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ProjectFileManagerBase::Start()
|
||||
{
|
||||
Action();
|
||||
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
void ProjectFileManagerBase::Cancel()
|
||||
{
|
||||
cancelled_ = true;
|
||||
}
|
||||
|
||||
const QAtomicInt &ProjectFileManagerBase::IsCancelled() const
|
||||
{
|
||||
return cancelled_;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
#ifndef PROJECTFILEMANAGERBASE_H
|
||||
#define PROJECTFILEMANAGERBASE_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "project.h"
|
||||
|
||||
class ProjectFileManagerBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ProjectFileManagerBase();
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Start the save process
|
||||
*
|
||||
* It's recommended to invoke this through Qt signals/slots/QueuedConnection after moving this object to a separate
|
||||
* thread.
|
||||
*/
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* @brief Cancel the current save
|
||||
*
|
||||
* Always connect to this with a DirectConnection. Otherwise, it'll be queued AFTER the save function is already
|
||||
* complete.
|
||||
*/
|
||||
void Cancel();
|
||||
|
||||
protected:
|
||||
virtual void Action() = 0;
|
||||
|
||||
const QAtomicInt& IsCancelled() const;
|
||||
|
||||
signals:
|
||||
void ProgressChanged(int);
|
||||
|
||||
void Finished();
|
||||
|
||||
private:
|
||||
QAtomicInt cancelled_;
|
||||
|
||||
};
|
||||
|
||||
#endif // PROJECTFILEMANAGERBASE_H
|
||||
@@ -4,10 +4,10 @@
|
||||
#include <QFileInfoList>
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "projectfilemanagerbase.h"
|
||||
#include "projectviewmodel.h"
|
||||
#include "task/task.h"
|
||||
|
||||
class ProjectImportManager : public ProjectFileManagerBase
|
||||
class ProjectImportManager : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
@@ -46,5 +46,5 @@ void ProjectLoadManager::Action()
|
||||
project_file.close();
|
||||
}
|
||||
|
||||
emit Finished();
|
||||
emit Succeeeded();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifndef PROJECTLOADMANAGER_H
|
||||
#define PROJECTLOADMANAGER_H
|
||||
|
||||
#include "projectfilemanagerbase.h"
|
||||
#include "project/project.h"
|
||||
#include "task/task.h"
|
||||
|
||||
class ProjectLoadManager : public ProjectFileManagerBase
|
||||
class ProjectLoadManager : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
@@ -32,5 +32,5 @@ void ProjectSaveManager::Action()
|
||||
project_file.close();
|
||||
}
|
||||
|
||||
emit Finished();
|
||||
emit Succeeeded();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifndef PROJECTSAVEMANAGER_H
|
||||
#define PROJECTSAVEMANAGER_H
|
||||
|
||||
#include "projectfilemanagerbase.h"
|
||||
#include "project/project.h"
|
||||
#include "task/task.h"
|
||||
|
||||
class ProjectSaveManager : public ProjectFileManagerBase
|
||||
class ProjectSaveManager : public Task
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(index)
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/task.h
|
||||
task/task.cpp
|
||||
task/taskmanager.h
|
||||
task/taskmanager.cpp
|
||||
task/taskthread.h
|
||||
task/taskthread.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Olive - Non-Linear Video Editor
|
||||
# Copyright (C) 2019 Olive Team
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
set(OLIVE_SOURCES
|
||||
${OLIVE_SOURCES}
|
||||
task/index/index.h
|
||||
task/index/index.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "index.h"
|
||||
|
||||
#include "codec/decoder.h"
|
||||
|
||||
IndexTask::IndexTask(StreamPtr stream) :
|
||||
stream_(stream)
|
||||
{
|
||||
SetTitle(tr("Indexing %1:%2").arg(stream_->footage()->filename(), QString::number(stream_->index())));
|
||||
}
|
||||
|
||||
void IndexTask::Action()
|
||||
{
|
||||
if (stream_->footage()->decoder().isEmpty()) {
|
||||
emit Failed(QStringLiteral("Stream has no decoder"));
|
||||
} else {
|
||||
DecoderPtr decoder = Decoder::CreateFromID(stream_->footage()->decoder());
|
||||
|
||||
decoder->set_stream(stream_);
|
||||
|
||||
decoder->Open();
|
||||
decoder->Index();
|
||||
decoder->Close();
|
||||
|
||||
emit Succeeeded();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef INDEXTASK_H
|
||||
#define INDEXTASK_H
|
||||
|
||||
#include "project/item/footage/footage.h"
|
||||
#include "task/task.h"
|
||||
|
||||
class IndexTask : public Task
|
||||
{
|
||||
public:
|
||||
IndexTask(StreamPtr stream);
|
||||
|
||||
protected:
|
||||
virtual void Action() override;
|
||||
|
||||
private:
|
||||
StreamPtr stream_;
|
||||
|
||||
};
|
||||
|
||||
#endif // INDEXTASK_H
|
||||
+10
-139
@@ -21,168 +21,39 @@
|
||||
#include "task.h"
|
||||
|
||||
Task::Task() :
|
||||
status_(kWaiting),
|
||||
thread_(this),
|
||||
text_(tr("Task")),
|
||||
title_(tr("Task")),
|
||||
cancelled_(false)
|
||||
{
|
||||
connect(&thread_, SIGNAL(finished()), this, SLOT(ThreadComplete()));
|
||||
}
|
||||
|
||||
bool Task::Start()
|
||||
void Task::Start()
|
||||
{
|
||||
// Tasks can only start if they're waiting
|
||||
if (status_ != kWaiting) {
|
||||
return false;
|
||||
}
|
||||
Action();
|
||||
|
||||
// Check if this task has any dependencies (tasks that should complete before this one starts)
|
||||
|
||||
for (int i=0;i<dependencies_.size();i++) {
|
||||
Task* dependency = dependencies_.at(i);
|
||||
|
||||
if (dependency->status() == kWaiting || dependency->status() == kWorking) {
|
||||
|
||||
// We need this task to finish before this task can start, so keep waiting
|
||||
return false;
|
||||
|
||||
} else if (dependency->status() == kError) {
|
||||
|
||||
// A dependency errored, so this task is likely invalid too
|
||||
set_error(tr("A dependency task failed"));
|
||||
set_status(kError);
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
cancelled_ = false;
|
||||
|
||||
// Run Prologue() function
|
||||
if (!Prologue()) {
|
||||
set_status(kError);
|
||||
return false;
|
||||
}
|
||||
|
||||
set_status(kWorking);
|
||||
|
||||
thread_.start();
|
||||
|
||||
return true;
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
bool Task::Prologue()
|
||||
const QString &Task::GetTitle()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Task::Action()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Task::Epilogue()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const Task::Status &Task::status()
|
||||
{
|
||||
return status_;
|
||||
}
|
||||
|
||||
const QString &Task::text()
|
||||
{
|
||||
return text_;
|
||||
}
|
||||
|
||||
const QString &Task::error()
|
||||
{
|
||||
return error_;
|
||||
}
|
||||
|
||||
void Task::AddDependency(Task *dependency)
|
||||
{
|
||||
// Dependencies cannot be added if the Task is working or complete
|
||||
Q_ASSERT(status_ == kWaiting);
|
||||
|
||||
dependencies_.append(dependency);
|
||||
}
|
||||
|
||||
void Task::ResetState()
|
||||
{
|
||||
if (status_ == kWaiting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (status_ == kWorking) {
|
||||
Cancel();
|
||||
}
|
||||
|
||||
cancelled_ = false;
|
||||
|
||||
set_error(QString());
|
||||
|
||||
set_status(kWaiting);
|
||||
return title_;
|
||||
}
|
||||
|
||||
void Task::Cancel()
|
||||
{
|
||||
if (status_ == kWaiting) {
|
||||
set_status(kFinished);
|
||||
}
|
||||
|
||||
if (status_ != kWorking) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelled_ = true;
|
||||
|
||||
// FIXME: Should we limit the wait time?
|
||||
thread_.wait();
|
||||
}
|
||||
|
||||
void Task::set_error(const QString &s)
|
||||
void Task::SetErrorText(const QString &s)
|
||||
{
|
||||
error_ = s;
|
||||
}
|
||||
|
||||
void Task::set_text(const QString &s)
|
||||
void Task::SetTitle(const QString &s)
|
||||
{
|
||||
text_ = s;
|
||||
title_ = s;
|
||||
}
|
||||
|
||||
bool Task::cancelled()
|
||||
bool Task::IsCancelled()
|
||||
{
|
||||
return cancelled_;
|
||||
}
|
||||
|
||||
void Task::set_status(const Task::Status &status)
|
||||
{
|
||||
status_ = status;
|
||||
|
||||
if (status_ == kFinished || status_ == kError) {
|
||||
emit Finished();
|
||||
}
|
||||
|
||||
emit StatusChanged(status_);
|
||||
}
|
||||
|
||||
void Task::ThreadComplete()
|
||||
{
|
||||
// thread_.result() will be set to the return value of Action()
|
||||
bool succeeded = thread_.result();
|
||||
|
||||
// Run the Prologue() function for any final tasks
|
||||
// User cancelling is not considered an error, so we need to check it too
|
||||
if (succeeded && !cancelled()) {
|
||||
succeeded = Epilogue();
|
||||
}
|
||||
|
||||
// If everything succeeded, we set the status accordingly
|
||||
if (succeeded) {
|
||||
set_status(kFinished);
|
||||
} else {
|
||||
set_status(kError);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-142
@@ -24,8 +24,6 @@
|
||||
#include <memory>
|
||||
#include <QObject>
|
||||
|
||||
#include "task/taskthread.h"
|
||||
|
||||
/**
|
||||
* @brief A base class for background tasks running in Olive.
|
||||
*
|
||||
@@ -47,31 +45,17 @@ class Task : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* @brief The Status enum
|
||||
*
|
||||
* All states that a Task can be in. When subclassing, you don't need to set the Task's status as the base class
|
||||
* does that automatically.
|
||||
*/
|
||||
enum Status {
|
||||
/// This Task is yet to start
|
||||
kWaiting,
|
||||
|
||||
/// This Task is currently running (see Action())
|
||||
kWorking,
|
||||
|
||||
/// This Task has completed successfully
|
||||
kFinished,
|
||||
|
||||
/// This Task failed and could not complete
|
||||
kError
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Task Constructor
|
||||
*/
|
||||
Task();
|
||||
|
||||
/**
|
||||
* @brief Retrieve the current title of this Task
|
||||
*/
|
||||
const QString& GetTitle();
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Try to start this Task
|
||||
*
|
||||
@@ -81,29 +65,18 @@ public:
|
||||
* This function also checks its dependency Tasks and will only start if all of them are complete. If they are still
|
||||
* working, this function will return FALSE and the status will continue to be kWaiting. If any of them failed, this
|
||||
* Task will also fail - this function will return FALSE and the status will be set to kError.
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* TRUE if the Task started, FALSE if not.
|
||||
*/
|
||||
bool Start();
|
||||
void Start();
|
||||
|
||||
/**
|
||||
* @brief Perform opening tasks before main Task thread begins
|
||||
* @brief Cancel the Task
|
||||
*
|
||||
* If a Task needs to perform any actions in the main thread before starting the Task's thread, (e.g. copying or
|
||||
* altering information) this function should be overridden and those actions should be performed here. It's
|
||||
* guaranteed that Prologue() will run in the main thread, and as such, functions here should remain as minimal as
|
||||
* possible as to not block the main thread for a noticeable amount of time. If your Task does not need any such
|
||||
* actions, you don't need to override this.
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* TRUE if the prologue was successful and we can start the Task now. If Prologue returns FALSE, the thread is never
|
||||
* created and Action()/Epilogue() are never run.
|
||||
* Sends a signal to the Task to stop as soon as possible. Always call this directly or connect with
|
||||
* Qt::DirectConnection, or else it'll be queued *after* the task has already finished.
|
||||
*/
|
||||
virtual bool Prologue();
|
||||
void Cancel();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief The main Task function which is run in a separate thread
|
||||
*
|
||||
@@ -119,86 +92,15 @@ public:
|
||||
* could not finish, not if the Task found a negative result (see Task documentation for details). Before returning
|
||||
* FALSE, it's recommended to use set_error() to signal to the user what caused the failure.
|
||||
*/
|
||||
virtual bool Action();
|
||||
virtual void Action() = 0;
|
||||
|
||||
/**
|
||||
* @brief Perform any closing Tasks in the main thread after the Task thread finishes
|
||||
*
|
||||
* It's likely your Task modifies data used throughout the program in some way, and to prevent race conditions, it's
|
||||
* recommended to work with "copies" of that data in Action() (which is run in separate thread) and never
|
||||
* access/modify any data used in other threads. Then, after the Action() thread is complete, that data can be used to
|
||||
* "apply" that data in the main thread here.
|
||||
*
|
||||
* As this runs in the main thread, these functions shouldn't be kept fairly minimal to prevent blocking the main
|
||||
* thread.
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* TRUE if the Epilogue completed successfully. FALSE if not. A FALSE result here is considered a complete failure
|
||||
* of the Task, even though the bulk of the processing has been performed in Action().
|
||||
*/
|
||||
virtual bool Epilogue();
|
||||
|
||||
/**
|
||||
* @brief Current status of the Task
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* A member of the Task::Status enum.
|
||||
*/
|
||||
const Status& status();
|
||||
|
||||
/**
|
||||
* @brief Retrieve the current title of this Task
|
||||
*/
|
||||
const QString& text();
|
||||
|
||||
/**
|
||||
* @brief Retrieve the current error message (empty if no error)
|
||||
*/
|
||||
const QString& error();
|
||||
|
||||
/**
|
||||
* @brief Add a dependency Task
|
||||
*
|
||||
* If another Task needs to complete before this one can begin, it can be added as a "dependency task". If a task
|
||||
* has dependencies, Start() will not start the task until the dependency tasks have all completed. If any of the
|
||||
* dependency tasks fail, this Task will also fail before starting.
|
||||
*
|
||||
* Dependencies can only be added if the Task is kWaiting.
|
||||
*
|
||||
* Naturally Tasks should never be dependent on each other. Circular dependencies will result in Tasks that never
|
||||
* begin.
|
||||
*
|
||||
* @param dependency
|
||||
*/
|
||||
void AddDependency(Task* dependency);
|
||||
|
||||
/**
|
||||
* @brief Reset this Task back to the waiting state
|
||||
*/
|
||||
void ResetState();
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* @brief Cancel the Task
|
||||
*
|
||||
* Sends a signal to the Task to stop and waits for the Task to finish before returning. Tasks must be responsive to
|
||||
* cancelling so that the main thread doesn't halt for too long.
|
||||
*
|
||||
* Cancel()'s function is fairly simple, it sets cancelled_ to TRUE and waits for the thread to return. It's the
|
||||
* responsibility of the code in Action() to be able to respond quickly to cancelled_ changing.
|
||||
*/
|
||||
void Cancel();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Set the error message
|
||||
*
|
||||
* It is recommended to use this if your Action() function ever returns FALSE to tell the user why the failure
|
||||
* occurred.
|
||||
*/
|
||||
void set_error(const QString& s);
|
||||
void SetErrorText(const QString& s);
|
||||
|
||||
/**
|
||||
* @brief Set the Task title
|
||||
@@ -207,19 +109,14 @@ protected:
|
||||
* and shouldn't need to change during the life of the Task. To show an error message, it's recommended to use
|
||||
* set_error() instead.
|
||||
*/
|
||||
void set_text(const QString& s);
|
||||
void SetTitle(const QString& s);
|
||||
|
||||
/**
|
||||
* @brief Returns whether the thread has been explicitly cancelled or not
|
||||
*/
|
||||
bool cancelled();
|
||||
bool IsCancelled();
|
||||
|
||||
signals:
|
||||
/**
|
||||
* @brief Signal emitted whenever the Task status changes
|
||||
*/
|
||||
void StatusChanged(Task::Status s);
|
||||
|
||||
/**
|
||||
* @brief Signal emitted whenever progress is made
|
||||
*
|
||||
@@ -231,9 +128,10 @@ signals:
|
||||
*/
|
||||
void ProgressChanged(int p);
|
||||
|
||||
/**
|
||||
* @brief Signal emitted when the Task finishes whether it succeeded or failed
|
||||
*/
|
||||
void Succeeeded();
|
||||
|
||||
void Failed(const QString& error);
|
||||
|
||||
void Finished();
|
||||
|
||||
/**
|
||||
@@ -242,30 +140,12 @@ signals:
|
||||
void Removed();
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Set the status of this Task (also emits StatusChanged())
|
||||
*/
|
||||
void set_status(const Task::Status& status);
|
||||
|
||||
Status status_;
|
||||
|
||||
TaskThread thread_;
|
||||
|
||||
QString text_;
|
||||
QString title_;
|
||||
|
||||
QString error_;
|
||||
|
||||
QList<Task*> dependencies_;
|
||||
QAtomicInt cancelled_;
|
||||
|
||||
bool cancelled_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief A slot when the inner thread completes either successfully or unsuccessfully
|
||||
*/
|
||||
void ThreadComplete();
|
||||
};
|
||||
|
||||
using TaskPtr = std::shared_ptr<Task>;
|
||||
|
||||
#endif // TASK_H
|
||||
|
||||
+166
-66
@@ -23,119 +23,219 @@
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
TaskManager TaskManager::instance_;
|
||||
TaskManager* TaskManager::instance_ = nullptr;
|
||||
|
||||
TaskManager::TaskManager()
|
||||
TaskManager::TaskManager() :
|
||||
active_thread_count_(0)
|
||||
{
|
||||
maximum_task_count_ = QThread::idealThreadCount();
|
||||
// Initialize threads to run tasks on
|
||||
threads_.resize(QThread::idealThreadCount());
|
||||
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
QThread* t = new QThread(this);
|
||||
t->start(QThread::LowPriority);
|
||||
threads_.replace(i, {t, false});
|
||||
}
|
||||
}
|
||||
|
||||
TaskManager::~TaskManager()
|
||||
{
|
||||
Clear();
|
||||
// First send the signal to all tasks to start cancelling
|
||||
foreach (const TaskContainer& task_info, tasks_) {
|
||||
if (task_info.status == kWorking) {
|
||||
task_info.task->Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
// Next, signal each thread to quit as its next event in the queue
|
||||
foreach (const ThreadContainer& tc, threads_) {
|
||||
tc.thread->quit();
|
||||
}
|
||||
|
||||
// Wait for each thread's event queue to finish
|
||||
foreach (const ThreadContainer& tc, threads_) {
|
||||
tc.thread->wait();
|
||||
|
||||
// This is technically unnecessary since each QThread is a child of this object, but we may as well
|
||||
delete tc.thread;
|
||||
}
|
||||
|
||||
// Finally delete all task objects (they shouldn't have been deleted by TaskSucceeded() or TaskFailed() because our
|
||||
// event queue shouldn't be active by this point
|
||||
foreach (const TaskContainer& task_info, tasks_) {
|
||||
delete task_info.task;
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::CreateInstance()
|
||||
{
|
||||
instance_ = new TaskManager();
|
||||
}
|
||||
|
||||
void TaskManager::DestroyInstance()
|
||||
{
|
||||
delete instance_;
|
||||
}
|
||||
|
||||
TaskManager *TaskManager::instance()
|
||||
{
|
||||
return &instance_;
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void TaskManager::AddTask(TaskPtr t)
|
||||
void TaskManager::AddTask(Task* t)
|
||||
{
|
||||
// Connect Task's status signal to the Callback
|
||||
connect(t.get(), SIGNAL(StatusChanged(Task::Status)), this, SLOT(TaskCallback(Task::Status)));
|
||||
connect(t, &Task::Succeeeded, this, &TaskManager::TaskSucceeded, Qt::QueuedConnection);
|
||||
connect(t, &Task::Failed, this, &TaskManager::TaskFailed, Qt::QueuedConnection);
|
||||
|
||||
// Add the Task to the queue
|
||||
tasks_.append(t);
|
||||
tasks_.append({t, kWaiting});
|
||||
|
||||
// Emit signal that a Task was added
|
||||
emit TaskAdded(t.get());
|
||||
emit TaskAdded(t);
|
||||
|
||||
// Scan through queue and start any Tasks that can (including this one)
|
||||
StartNextWaiting();
|
||||
}
|
||||
|
||||
void TaskManager::Clear()
|
||||
{
|
||||
// Delete Tasks from memory
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
tasks_.at(i)->Cancel();
|
||||
}
|
||||
tasks_.clear();
|
||||
}
|
||||
|
||||
void TaskManager::StartNextWaiting()
|
||||
{
|
||||
// Count the tasks that are currently active
|
||||
int working_count = 0;
|
||||
// If there are no tasks in the queue, there is nothing to be done
|
||||
if (tasks_.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
TaskPtr t = tasks_.at(i);
|
||||
|
||||
if (t->status() == Task::kWorking) {
|
||||
|
||||
// Task is active, add it to the count
|
||||
working_count++;
|
||||
|
||||
} else if (t->status() == Task::kWaiting) {
|
||||
|
||||
// Task is waiting and we have available threads, try to start it
|
||||
if (t->Start()) {
|
||||
// If it started, add it to the working count
|
||||
working_count++;
|
||||
}
|
||||
// If all threads are occupied, nothing to be done
|
||||
if (active_thread_count_ == threads_.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a list of tasks that are waiting
|
||||
QList<Task*> waiting_tasks;
|
||||
foreach (const TaskContainer& task_info, tasks_) {
|
||||
if (task_info.status == kWaiting) {
|
||||
waiting_tasks.append(task_info.task);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the count exceeds our maximum threads, if so stop here
|
||||
if (working_count == maximum_task_count_) {
|
||||
break;
|
||||
// No tasks waiting to start
|
||||
if (waiting_tasks.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For any inactive threads,
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
if (!threads_.at(i).active) {
|
||||
// This thread is inactive and needs a new Task
|
||||
Task* task = waiting_tasks.takeFirst();
|
||||
|
||||
task->moveToThread(threads_.at(i).thread);
|
||||
|
||||
threads_[i].active = true;
|
||||
active_thread_count_++;
|
||||
|
||||
SetTaskStatus(task, kWorking);
|
||||
|
||||
QMetaObject::invokeMethod(task,
|
||||
"Start",
|
||||
Qt::QueuedConnection);
|
||||
|
||||
if (active_thread_count_ == threads_.size() || waiting_tasks.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::DeleteTask(Task *t)
|
||||
{
|
||||
// Cancel the task
|
||||
t->Cancel();
|
||||
if (GetTaskStatus(t) == kWorking) {
|
||||
// Send a signal to the task to cancel, it will likely continue to cancel in the background after it's removed
|
||||
t->Cancel();
|
||||
}
|
||||
|
||||
// Remove instances of Task from queue
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
if (tasks_.at(i).get() == t) {
|
||||
emit t->Removed();
|
||||
if (tasks_.at(i).task == t) {
|
||||
tasks_.removeAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
emit t->Removed();
|
||||
|
||||
if (GetTaskStatus(t) != kWorking) {
|
||||
// If the task isn't doing anything, we can simply delete it
|
||||
delete t;
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::TaskFinished(Task* task)
|
||||
{
|
||||
// Set this thread's active value to false
|
||||
for (int i=0;i<threads_.size();i++) {
|
||||
if (threads_.at(i).thread == task->thread()) {
|
||||
threads_[i].active = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement the active thread count
|
||||
active_thread_count_--;
|
||||
}
|
||||
|
||||
TaskManager::TaskStatus TaskManager::GetTaskStatus(Task *t)
|
||||
{
|
||||
foreach (const TaskContainer& container, tasks_) {
|
||||
if (container.task == t) {
|
||||
return container.status;
|
||||
}
|
||||
}
|
||||
|
||||
return kError;
|
||||
}
|
||||
|
||||
void TaskManager::SetTaskStatus(Task *t, TaskStatus status)
|
||||
{
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
TaskContainer& cont = tasks_[i];
|
||||
|
||||
if (cont.task == t) {
|
||||
cont.status = status;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TaskManager::TaskCallback(Task::Status status)
|
||||
void TaskManager::TaskSucceeded()
|
||||
{
|
||||
if (status == Task::kFinished || status == Task::kError) {
|
||||
// The Task has finished, we can start a new one
|
||||
StartNextWaiting();
|
||||
Task* task_sender = static_cast<Task*>(sender());
|
||||
|
||||
if (status == Task::kFinished) {
|
||||
// The Task was successful, remove this Task from the queue
|
||||
DeleteTask(static_cast<Task*>(sender()));
|
||||
SetTaskStatus(task_sender, kFinished);
|
||||
|
||||
TaskFinished(task_sender);
|
||||
|
||||
// Delete this task
|
||||
DeleteTask(task_sender);
|
||||
}
|
||||
|
||||
void TaskManager::TaskFailed()
|
||||
{
|
||||
Task* task_sender = static_cast<Task*>(sender());
|
||||
|
||||
SetTaskStatus(task_sender, kError);
|
||||
|
||||
TaskFinished(task_sender);
|
||||
|
||||
// If this task has already been deleted, we'll free its memory now
|
||||
bool was_deleted = true;
|
||||
|
||||
for (int i=0;i<tasks_.size();i++) {
|
||||
if (tasks_.at(i).task == task_sender) {
|
||||
was_deleted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TaskManager::AddTaskCommand::AddTaskCommand(TaskPtr t, QUndoCommand *parent) :
|
||||
QUndoCommand(parent),
|
||||
task_(t)
|
||||
{
|
||||
}
|
||||
|
||||
void TaskManager::AddTaskCommand::redo()
|
||||
{
|
||||
TaskManager::instance()->AddTask(task_);
|
||||
}
|
||||
|
||||
void TaskManager::AddTaskCommand::undo()
|
||||
{
|
||||
TaskManager::instance()->DeleteTask(task_.get());
|
||||
|
||||
task_->ResetState();
|
||||
if (was_deleted) {
|
||||
delete task_sender;
|
||||
}
|
||||
}
|
||||
|
||||
+53
-66
@@ -24,6 +24,7 @@
|
||||
#include <QVector>
|
||||
#include <QUndoCommand>
|
||||
|
||||
#include "common/constructors.h"
|
||||
#include "task/task.h"
|
||||
|
||||
/**
|
||||
@@ -50,25 +51,11 @@ public:
|
||||
*/
|
||||
virtual ~TaskManager();
|
||||
|
||||
/**
|
||||
* @brief Deleted copy constructor
|
||||
*/
|
||||
TaskManager(const TaskManager& other) = delete;
|
||||
DISABLE_COPY_MOVE(TaskManager)
|
||||
|
||||
/**
|
||||
* @brief Deleted move constructor
|
||||
*/
|
||||
TaskManager(TaskManager&& other) = delete;
|
||||
static void CreateInstance();
|
||||
|
||||
/**
|
||||
* @brief Deleted copy assignment
|
||||
*/
|
||||
TaskManager& operator=(const TaskManager& other) = delete;
|
||||
|
||||
/**
|
||||
* @brief Deleted move assignment
|
||||
*/
|
||||
TaskManager& operator=(TaskManager&& other) = delete;
|
||||
static void DestroyInstance();
|
||||
|
||||
static TaskManager* instance();
|
||||
|
||||
@@ -87,38 +74,7 @@ public:
|
||||
*
|
||||
* The task to add and run. TaskManager takes ownership of this Task and will be responsible for freeing it.
|
||||
*/
|
||||
void AddTask(TaskPtr t);
|
||||
|
||||
/**
|
||||
* @brief Forcibly cancel all commands and clear them
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* @brief Undoable command for adding a Task to the TaskManager
|
||||
*/
|
||||
class AddTaskCommand : public QUndoCommand {
|
||||
public:
|
||||
AddTaskCommand(TaskPtr t, QUndoCommand* parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Adds the Task to the TaskManager
|
||||
*
|
||||
* If there are available threads, TaskManager will start running it.
|
||||
*/
|
||||
virtual void redo() override;
|
||||
|
||||
/**
|
||||
* @brief Undoes adding the Task
|
||||
*
|
||||
* If the Task is running, it is cancelled. Then the Task is removed from the TaskManager and the Task's state is
|
||||
* reset.
|
||||
*/
|
||||
virtual void undo() override;
|
||||
|
||||
private:
|
||||
TaskPtr task_;
|
||||
};
|
||||
void AddTask(Task *t);
|
||||
|
||||
signals:
|
||||
/**
|
||||
@@ -131,6 +87,36 @@ signals:
|
||||
void TaskAdded(Task* t);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief The Status enum
|
||||
*
|
||||
* All states that a Task can be in. When subclassing, you don't need to set the Task's status as the base class
|
||||
* does that automatically.
|
||||
*/
|
||||
enum TaskStatus {
|
||||
/// This Task is yet to start
|
||||
kWaiting,
|
||||
|
||||
/// This Task is currently running (see Action())
|
||||
kWorking,
|
||||
|
||||
/// This Task has completed successfully
|
||||
kFinished,
|
||||
|
||||
/// This Task failed and could not complete
|
||||
kError
|
||||
};
|
||||
|
||||
struct TaskContainer {
|
||||
Task* task;
|
||||
TaskStatus status;
|
||||
};
|
||||
|
||||
struct ThreadContainer {
|
||||
QThread* thread;
|
||||
bool active;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Scan through the task queue and start any Tasks that are able to start
|
||||
*
|
||||
@@ -155,35 +141,36 @@ private:
|
||||
*/
|
||||
void DeleteTask(Task* t);
|
||||
|
||||
void TaskFinished(Task *task);
|
||||
|
||||
TaskStatus GetTaskStatus(Task* t);
|
||||
|
||||
void SetTaskStatus(Task* t, TaskStatus status);
|
||||
|
||||
/**
|
||||
* @brief Internal task array
|
||||
*/
|
||||
QVector<TaskPtr> tasks_;
|
||||
QVector<TaskContainer> tasks_;
|
||||
|
||||
/**
|
||||
* @brief Constant set at run-time of how many Tasks can run concurrently
|
||||
*
|
||||
* Currently set in the Constructor to QThread::idealThreadCount()
|
||||
* @brief Background threads to run tasks on
|
||||
*/
|
||||
int maximum_task_count_;
|
||||
QVector<ThreadContainer> threads_;
|
||||
|
||||
/**
|
||||
* @brief Value for how many threads are currently active
|
||||
*/
|
||||
int active_thread_count_;
|
||||
|
||||
/**
|
||||
* @brief TaskManager singleton instance
|
||||
*/
|
||||
static TaskManager instance_;
|
||||
static TaskManager* instance_;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* @brief Callback when a Task's status changes
|
||||
*
|
||||
* When a Task is added, it's connected to this so TaskManager is alerted whenever a Task's status changes. It can
|
||||
* therefore keep track of Tasks starting, completing, or failing.
|
||||
*
|
||||
* @param status
|
||||
*
|
||||
* The new status of the Task
|
||||
*/
|
||||
void TaskCallback(Task::Status status);
|
||||
void TaskSucceeded();
|
||||
|
||||
void TaskFailed();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "taskthread.h"
|
||||
|
||||
#include "task/task.h"
|
||||
|
||||
TaskThread::TaskThread(Task *parent) :
|
||||
parent_(parent),
|
||||
result_(false)
|
||||
{
|
||||
}
|
||||
|
||||
void TaskThread::run()
|
||||
{
|
||||
result_ = parent_->Action();
|
||||
}
|
||||
|
||||
bool TaskThread::result()
|
||||
{
|
||||
return result_;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 Olive Team
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef TASKTHREAD_H
|
||||
#define TASKTHREAD_H
|
||||
|
||||
#include <QThread>
|
||||
|
||||
class Task;
|
||||
|
||||
/**
|
||||
* @brief An internal class only used by Task.
|
||||
*
|
||||
* TaskThread is a simple QThread subclass designed to create a thread and run the
|
||||
* Task's Action() function. It also stores the result of Action() which can be read using result() when the thread
|
||||
* signals that it has finished().
|
||||
*/
|
||||
class TaskThread : public QThread
|
||||
{
|
||||
public:
|
||||
TaskThread(Task* parent);
|
||||
|
||||
virtual void run() override;
|
||||
|
||||
bool result();
|
||||
|
||||
private:
|
||||
Task* parent_;
|
||||
|
||||
bool result_;
|
||||
};
|
||||
|
||||
#endif // TASKTHREAD_H
|
||||
@@ -44,9 +44,5 @@ TaskView::TaskView(QWidget* parent) :
|
||||
void TaskView::AddTask(Task *t)
|
||||
{
|
||||
// Create TaskViewItem (UI representation of a Task) and connect it
|
||||
TaskViewItem* item = new TaskViewItem(central_widget_);
|
||||
|
||||
item->SetTask(t);
|
||||
|
||||
layout_->insertWidget(0, item);
|
||||
layout_->insertWidget(0, new TaskViewItem(t));
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
|
||||
#include "ui/icons/icons.h"
|
||||
|
||||
TaskViewItem::TaskViewItem(QWidget *parent) :
|
||||
TaskViewItem::TaskViewItem(Task* task, QWidget *parent) :
|
||||
QFrame(parent),
|
||||
task_(nullptr)
|
||||
task_(task)
|
||||
{
|
||||
// Draw border around this item
|
||||
setFrameShape(QFrame::StyledPanel);
|
||||
@@ -36,6 +36,7 @@ TaskViewItem::TaskViewItem(QWidget *parent) :
|
||||
|
||||
// Create header label
|
||||
task_name_lbl_ = new QLabel(this);
|
||||
task_name_lbl_->setText(task_->GetTitle());
|
||||
layout->addWidget(task_name_lbl_);
|
||||
|
||||
// Create center layout (combines progress bar and a cancel button)
|
||||
@@ -55,54 +56,9 @@ TaskViewItem::TaskViewItem(QWidget *parent) :
|
||||
// Create status label
|
||||
task_status_lbl_ = new QLabel(this);
|
||||
layout->addWidget(task_status_lbl_);
|
||||
}
|
||||
|
||||
void TaskViewItem::SetTask(Task *t)
|
||||
{
|
||||
// Check if we already have a task and disconnect from it if so
|
||||
if (task_ != nullptr) {
|
||||
disconnect(task_, SIGNAL(StatusChanged(Task::Status)), this, SLOT(TaskStatusChange(Task::Status)));
|
||||
disconnect(task_, SIGNAL(ProgressChanged(int)), progress_bar_, SLOT(setValue(int)));
|
||||
disconnect(task_, SIGNAL(destroyed()), this, SLOT(deleteLater()));
|
||||
}
|
||||
|
||||
// Set task
|
||||
task_ = t;
|
||||
|
||||
// Set name label to the name (bolded)
|
||||
task_name_lbl_->setText(QStringLiteral("<b>%1</b>").arg(task_->text()));
|
||||
|
||||
// Connect to the task
|
||||
connect(task_, SIGNAL(StatusChanged(Task::Status)), this, SLOT(TaskStatusChange(Task::Status)));
|
||||
connect(task_, SIGNAL(ProgressChanged(int)), progress_bar_, SLOT(setValue(int)));
|
||||
connect(task_, SIGNAL(Removed()), this, SLOT(deleteLater()));
|
||||
connect(cancel_btn_, SIGNAL(clicked(bool)), task_, SLOT(Cancel()));
|
||||
}
|
||||
|
||||
void TaskViewItem::TaskStatusChange(Task::Status status)
|
||||
{
|
||||
switch (status) {
|
||||
case Task::kWaiting:
|
||||
task_status_lbl_->setText(tr("Waiting..."));
|
||||
progress_bar_->setValue(0);
|
||||
cancel_btn_->setEnabled(true);
|
||||
break;
|
||||
case Task::kWorking:
|
||||
task_status_lbl_->setText(tr("Working..."));
|
||||
progress_bar_->setValue(0);
|
||||
cancel_btn_->setEnabled(true);
|
||||
break;
|
||||
case Task::kFinished:
|
||||
task_status_lbl_->setText(tr("Done"));
|
||||
progress_bar_->setValue(100);
|
||||
cancel_btn_->setEnabled(false);
|
||||
break;
|
||||
case Task::kError:
|
||||
task_status_lbl_->setText(
|
||||
tr("Error: %1").arg(static_cast<Task*>(sender())->error())
|
||||
);
|
||||
progress_bar_->setValue(0);
|
||||
cancel_btn_->setEnabled(false);
|
||||
break;
|
||||
}
|
||||
connect(task_, &Task::ProgressChanged, progress_bar_, &QProgressBar::setValue);
|
||||
connect(task_, &Task::Removed, this, &TaskViewItem::deleteLater);
|
||||
connect(cancel_btn_, &QPushButton::clicked, task_, &Task::Cancel, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
@@ -41,16 +41,7 @@ class TaskViewItem : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TaskViewItem(QWidget* parent);
|
||||
|
||||
/**
|
||||
* @brief Connects a Task to this object
|
||||
*
|
||||
* If a Task has already been connected, this will disconnect this TaskViewItem from the previously connected
|
||||
* Task before connecting to the next one - however there are very few circumstances where this would be necessary
|
||||
* since TaskViewItem is designed to delete itself when a Task is complete.
|
||||
*/
|
||||
void SetTask(Task* t);
|
||||
TaskViewItem(Task *task, QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
QLabel* task_name_lbl_;
|
||||
@@ -60,9 +51,6 @@ private:
|
||||
|
||||
Task* task_;
|
||||
|
||||
private slots:
|
||||
void TaskStatusChange(Task::Status status);
|
||||
|
||||
};
|
||||
|
||||
#endif // TASKVIEWITEM_H
|
||||
|
||||
Reference in New Issue
Block a user