File indexing completed on 2024-05-12 15:50:11

0001 /* -*- C++ -*-
0002     This file is part of ThreadWeaver.
0003 
0004     SPDX-FileCopyrightText: 2005-2014 Mirko Boehm <mirko@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include <algorithm> //for transform
0010 #include <numeric> //for accumulate
0011 
0012 #include <QDir>
0013 #include <QFileInfo>
0014 #include <QMutexLocker>
0015 #include <QStringList>
0016 #include <QtDebug>
0017 
0018 #include <ThreadWeaver/DebuggingAids>
0019 #include <ThreadWeaver/Exception>
0020 #include <ThreadWeaver/ThreadWeaver>
0021 
0022 #include "ComputeThumbNailJob.h"
0023 #include "FileLoaderJob.h"
0024 #include "ImageLoaderJob.h"
0025 #include "Model.h"
0026 #include "PriorityDecorator.h"
0027 
0028 using namespace std;
0029 using namespace ThreadWeaver;
0030 
0031 Model::Model(QObject *parent)
0032     : QAbstractListModel(parent)
0033     , m_fileLoaderRestriction(4)
0034     , m_imageLoaderRestriction(4)
0035     , m_imageScalerRestriction(4)
0036     , m_fileWriterRestriction(4)
0037 {
0038     ThreadWeaver::setDebugLevel(true, 0);
0039     connect(this, SIGNAL(signalElementChanged(int)), this, SLOT(slotElementChanged(int)));
0040 }
0041 
0042 int Model::fileLoaderCap() const
0043 {
0044     return m_fileLoaderRestriction.cap();
0045 }
0046 
0047 void Model::setFileLoaderCap(int cap)
0048 {
0049     m_fileLoaderRestriction.setCap(cap);
0050     Queue::instance()->reschedule();
0051 }
0052 
0053 int Model::imageLoaderCap() const
0054 {
0055     return m_imageLoaderRestriction.cap();
0056 }
0057 
0058 void Model::setImageLoaderCap(int cap)
0059 {
0060     m_imageLoaderRestriction.setCap(cap);
0061     Queue::instance()->reschedule();
0062 }
0063 
0064 int Model::computeThumbNailCap() const
0065 {
0066     return m_imageScalerRestriction.cap();
0067 }
0068 
0069 void Model::setComputeThumbNailCap(int cap)
0070 {
0071     m_imageScalerRestriction.setCap(cap);
0072 }
0073 
0074 int Model::saveThumbNailCap() const
0075 {
0076     return m_fileWriterRestriction.cap();
0077 }
0078 
0079 void Model::setSaveThumbNailCap(int cap)
0080 {
0081     m_imageScalerRestriction.setCap(cap);
0082 }
0083 
0084 void Model::clear()
0085 {
0086     beginResetModel();
0087     m_images.clear();
0088     endResetModel();
0089 }
0090 
0091 void Model::prepareConversions(const QFileInfoList &filenames, const QString &outputDirectory)
0092 {
0093     beginResetModel();
0094     Q_ASSERT(m_images.isEmpty());
0095     m_images.reserve(filenames.size());
0096     int counter = 0;
0097     auto initializeImage = [=, &counter](const QFileInfo &file) {
0098         auto const out = QFileInfo(outputDirectory, file.fileName()).absoluteFilePath();
0099         return Image(file.absoluteFilePath(), out, this, counter++);
0100     };
0101     for (const auto &filename : filenames) {
0102         m_images << initializeImage(filename);
0103     }
0104     endResetModel();
0105 }
0106 
0107 bool Model::computeThumbNailsBlockingInLoop()
0108 {
0109     for (auto it = m_images.begin(); it != m_images.end(); ++it) {
0110         Image &image = *it;
0111         try {
0112             image.loadFile();
0113             image.loadImage();
0114             image.computeThumbNail();
0115             image.saveThumbNail();
0116 
0117         } catch (const ThreadWeaver::Exception &ex) {
0118             qDebug() << ex.message();
0119             return false;
0120         }
0121     }
0122     return true;
0123 }
0124 
0125 bool Model::computeThumbNailsBlockingConcurrent()
0126 {
0127     auto queue = stream();
0128     for (auto it = m_images.begin(); it != m_images.end(); ++it) {
0129         Image &image = *it;
0130         auto sequence = new Sequence();
0131         *sequence << make_job([&image]() {
0132             image.loadFile();
0133         });
0134         *sequence << make_job([&image]() {
0135             image.loadImage();
0136         });
0137         *sequence << make_job([&image]() {
0138             image.computeThumbNail();
0139         });
0140         *sequence << make_job([&image]() {
0141             image.saveThumbNail();
0142         });
0143         queue << sequence;
0144     }
0145     queue.flush();
0146     Queue::instance()->finish();
0147     // figure out result:
0148     for (const Image &image : std::as_const(m_images)) {
0149         if (image.progress().first != Image::Step_NumberOfSteps) {
0150             return false;
0151         }
0152     }
0153     return true;
0154 }
0155 
0156 void Model::queueUpConversion(const QStringList &files, const QString &outputDirectory)
0157 {
0158     QFileInfoList fileInfos;
0159     transform(files.begin(), files.end(), back_inserter(fileInfos), [](const QString &file) {
0160         return QFileInfo(file);
0161     });
0162     prepareConversions(fileInfos, outputDirectory);
0163     // FIXME duplicated code
0164     auto queue = stream();
0165     for (auto it = m_images.begin(); it != m_images.end(); ++it) {
0166         Image &image = *it;
0167         auto saveThumbNail = [&image]() {
0168             image.saveThumbNail();
0169         };
0170         auto saveThumbNailJob = new Lambda<decltype(saveThumbNail)>(saveThumbNail);
0171         {
0172             QMutexLocker l(saveThumbNailJob->mutex());
0173             saveThumbNailJob->assignQueuePolicy(&m_fileWriterRestriction);
0174         }
0175 
0176         auto sequence = new Sequence();
0177         /* clang-format off */
0178         *sequence << new FileLoaderJob(&image, &m_fileLoaderRestriction)
0179                   << new ImageLoaderJob(&image, &m_imageLoaderRestriction)
0180                   << new ComputeThumbNailJob(&image, &m_imageScalerRestriction)
0181                   << new PriorityDecorator(Image::Step_SaveThumbNail, saveThumbNailJob);
0182         /* clang-format on */
0183         queue << sequence;
0184     }
0185 }
0186 
0187 Progress Model::progress() const
0188 {
0189     auto sumItUp = [](const Progress &sum, const Image &image) {
0190         auto const values = image.progress();
0191         return qMakePair(sum.first + values.first, sum.second + values.second);
0192     };
0193     auto const soFar = accumulate(m_images.begin(), m_images.end(), Progress(), sumItUp);
0194     return soFar;
0195 }
0196 
0197 void Model::progressChanged()
0198 {
0199     auto const p = progress();
0200     Q_EMIT progressStepChanged(p.first, p.second);
0201 }
0202 
0203 void Model::elementChanged(int id)
0204 {
0205     signalElementChanged(id);
0206 }
0207 
0208 int Model::rowCount(const QModelIndex &parent) const
0209 {
0210     Q_UNUSED(parent);
0211     return m_images.size();
0212 }
0213 
0214 QVariant Model::data(const QModelIndex &index, int role) const
0215 {
0216     if (!index.isValid()) {
0217         return QVariant();
0218     }
0219     if (index.row() < 0 || index.row() >= rowCount()) {
0220         return QVariant();
0221     }
0222     const Image &image = m_images.at(index.row());
0223     if (role == Qt::DisplayRole) {
0224         return image.description();
0225     } else if (role == Role_SortRole) {
0226         return -image.processingOrder();
0227     } else if (role == Role_ImageRole) {
0228         return QVariant::fromValue(&image);
0229     } else if (role == Role_StepRole) {
0230         return QVariant::fromValue(image.progress().first);
0231     }
0232     return QVariant();
0233 }
0234 
0235 QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
0236 {
0237     Q_UNUSED(section);
0238     Q_UNUSED(orientation);
0239     Q_UNUSED(role);
0240     return QVariant();
0241 }
0242 
0243 void Model::slotElementChanged(int id)
0244 {
0245     if (id >= 0 && id < m_images.count()) {
0246         auto const i = index(id, 0);
0247         Q_EMIT dataChanged(i, i);
0248     }
0249 }
0250 
0251 #include "moc_Model.cpp"