1/* -*- C++ -*-
2 This file is part of ThreadWeaver.
3
4 SPDX-FileCopyrightText: 2005-2014 Mirko Boehm <mirko@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include <algorithm> //for transform
10#include <numeric> //for accumulate
11
12#include <QDir>
13#include <QFileInfo>
14#include <QMutexLocker>
15#include <QStringList>
16#include <QtDebug>
17
18#include <ThreadWeaver/DebuggingAids>
19#include <ThreadWeaver/Exception>
20#include <ThreadWeaver/ThreadWeaver>
21
22#include "ComputeThumbNailJob.h"
23#include "FileLoaderJob.h"
24#include "ImageLoaderJob.h"
25#include "Model.h"
26#include "PriorityDecorator.h"
27
28using namespace std;
29using namespace ThreadWeaver;
30
31Model::Model(QObject *parent)
32 : QAbstractListModel(parent)
33 , m_fileLoaderRestriction(4)
34 , m_imageLoaderRestriction(4)
35 , m_imageScalerRestriction(4)
36 , m_fileWriterRestriction(4)
37{
38 ThreadWeaver::setDebugLevel(debug: true, level: 0);
39 connect(sender: this, SIGNAL(signalElementChanged(int)), receiver: this, SLOT(slotElementChanged(int)));
40}
41
42int Model::fileLoaderCap() const
43{
44 return m_fileLoaderRestriction.cap();
45}
46
47void Model::setFileLoaderCap(int cap)
48{
49 m_fileLoaderRestriction.setCap(cap);
50 Queue::instance()->reschedule();
51}
52
53int Model::imageLoaderCap() const
54{
55 return m_imageLoaderRestriction.cap();
56}
57
58void Model::setImageLoaderCap(int cap)
59{
60 m_imageLoaderRestriction.setCap(cap);
61 Queue::instance()->reschedule();
62}
63
64int Model::computeThumbNailCap() const
65{
66 return m_imageScalerRestriction.cap();
67}
68
69void Model::setComputeThumbNailCap(int cap)
70{
71 m_imageScalerRestriction.setCap(cap);
72}
73
74int Model::saveThumbNailCap() const
75{
76 return m_fileWriterRestriction.cap();
77}
78
79void Model::setSaveThumbNailCap(int cap)
80{
81 m_imageScalerRestriction.setCap(cap);
82}
83
84void Model::clear()
85{
86 beginResetModel();
87 m_images.clear();
88 endResetModel();
89}
90
91void Model::prepareConversions(const QFileInfoList &filenames, const QString &outputDirectory)
92{
93 beginResetModel();
94 Q_ASSERT(m_images.isEmpty());
95 m_images.reserve(asize: filenames.size());
96 int counter = 0;
97 auto initializeImage = [this, outputDirectory, &counter](const QFileInfo &file) {
98 auto const out = QFileInfo(outputDirectory, file.fileName()).absoluteFilePath();
99 return Image(file.absoluteFilePath(), out, this, counter++);
100 };
101 for (const auto &filename : filenames) {
102 m_images << initializeImage(filename);
103 }
104 endResetModel();
105}
106
107bool Model::computeThumbNailsBlockingInLoop()
108{
109 for (auto it = m_images.begin(); it != m_images.end(); ++it) {
110 Image &image = *it;
111 try {
112 image.loadFile();
113 image.loadImage();
114 image.computeThumbNail();
115 image.saveThumbNail();
116
117 } catch (const ThreadWeaver::Exception &ex) {
118 qDebug() << ex.message();
119 return false;
120 }
121 }
122 return true;
123}
124
125bool Model::computeThumbNailsBlockingConcurrent()
126{
127 auto queue = stream();
128 for (auto it = m_images.begin(); it != m_images.end(); ++it) {
129 Image &image = *it;
130 auto sequence = new Sequence();
131 *sequence << make_job(t: [&image]() {
132 image.loadFile();
133 });
134 *sequence << make_job(t: [&image]() {
135 image.loadImage();
136 });
137 *sequence << make_job(t: [&image]() {
138 image.computeThumbNail();
139 });
140 *sequence << make_job(t: [&image]() {
141 image.saveThumbNail();
142 });
143 queue << sequence;
144 }
145 queue.flush();
146 Queue::instance()->finish();
147 // figure out result:
148 for (const Image &image : std::as_const(t&: m_images)) {
149 if (image.progress().first != Image::Step_NumberOfSteps) {
150 return false;
151 }
152 }
153 return true;
154}
155
156void Model::queueUpConversion(const QStringList &files, const QString &outputDirectory)
157{
158 QFileInfoList fileInfos;
159 transform(first: files.begin(), last: files.end(), result: back_inserter(x&: fileInfos), unary_op: [](const QString &file) {
160 return QFileInfo(file);
161 });
162 prepareConversions(filenames: fileInfos, outputDirectory);
163 // FIXME duplicated code
164 auto queue = stream();
165 for (auto it = m_images.begin(); it != m_images.end(); ++it) {
166 Image &image = *it;
167 auto saveThumbNail = [&image]() {
168 image.saveThumbNail();
169 };
170 auto saveThumbNailJob = new Lambda<decltype(saveThumbNail)>(saveThumbNail);
171 {
172 QMutexLocker l(saveThumbNailJob->mutex());
173 saveThumbNailJob->assignQueuePolicy(&m_fileWriterRestriction);
174 }
175
176 auto sequence = new Sequence();
177 /* clang-format off */
178 *sequence << new FileLoaderJob(&image, &m_fileLoaderRestriction)
179 << new ImageLoaderJob(&image, &m_imageLoaderRestriction)
180 << new ComputeThumbNailJob(&image, &m_imageScalerRestriction)
181 << new PriorityDecorator(Image::Step_SaveThumbNail, saveThumbNailJob);
182 /* clang-format on */
183 queue << sequence;
184 }
185}
186
187Progress Model::progress() const
188{
189 auto sumItUp = [](const Progress &sum, const Image &image) {
190 auto const values = image.progress();
191 return qMakePair(value1: sum.first + values.first, value2: sum.second + values.second);
192 };
193 auto const soFar = accumulate(first: m_images.begin(), last: m_images.end(), init: Progress(), binary_op: sumItUp);
194 return soFar;
195}
196
197void Model::progressChanged()
198{
199 auto const p = progress();
200 Q_EMIT progressStepChanged(p.first, p.second);
201}
202
203void Model::elementChanged(int id)
204{
205 signalElementChanged(id);
206}
207
208int Model::rowCount(const QModelIndex &parent) const
209{
210 Q_UNUSED(parent);
211 return m_images.size();
212}
213
214QVariant Model::data(const QModelIndex &index, int role) const
215{
216 if (!index.isValid()) {
217 return QVariant();
218 }
219 if (index.row() < 0 || index.row() >= rowCount()) {
220 return QVariant();
221 }
222 const Image &image = m_images.at(i: index.row());
223 if (role == Qt::DisplayRole) {
224 return image.description();
225 } else if (role == Role_SortRole) {
226 return -image.processingOrder();
227 } else if (role == Role_ImageRole) {
228 return QVariant::fromValue(value: &image);
229 } else if (role == Role_StepRole) {
230 return QVariant::fromValue(value: image.progress().first);
231 }
232 return QVariant();
233}
234
235QVariant Model::headerData(int section, Qt::Orientation orientation, int role) const
236{
237 Q_UNUSED(section);
238 Q_UNUSED(orientation);
239 Q_UNUSED(role);
240 return QVariant();
241}
242
243void Model::slotElementChanged(int id)
244{
245 if (id >= 0 && id < m_images.count()) {
246 auto const i = index(row: id, column: 0);
247 Q_EMIT dataChanged(topLeft: i, bottomRight: i);
248 }
249}
250
251#include "moc_Model.cpp"
252

source code of threadweaver/examples/ThumbNailer/Model.cpp