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 | |
28 | using namespace std; |
29 | using namespace ThreadWeaver; |
30 | |
31 | Model::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 | |
42 | int Model::fileLoaderCap() const |
43 | { |
44 | return m_fileLoaderRestriction.cap(); |
45 | } |
46 | |
47 | void Model::setFileLoaderCap(int cap) |
48 | { |
49 | m_fileLoaderRestriction.setCap(cap); |
50 | Queue::instance()->reschedule(); |
51 | } |
52 | |
53 | int Model::imageLoaderCap() const |
54 | { |
55 | return m_imageLoaderRestriction.cap(); |
56 | } |
57 | |
58 | void Model::setImageLoaderCap(int cap) |
59 | { |
60 | m_imageLoaderRestriction.setCap(cap); |
61 | Queue::instance()->reschedule(); |
62 | } |
63 | |
64 | int Model::computeThumbNailCap() const |
65 | { |
66 | return m_imageScalerRestriction.cap(); |
67 | } |
68 | |
69 | void Model::setComputeThumbNailCap(int cap) |
70 | { |
71 | m_imageScalerRestriction.setCap(cap); |
72 | } |
73 | |
74 | int Model::saveThumbNailCap() const |
75 | { |
76 | return m_fileWriterRestriction.cap(); |
77 | } |
78 | |
79 | void Model::setSaveThumbNailCap(int cap) |
80 | { |
81 | m_imageScalerRestriction.setCap(cap); |
82 | } |
83 | |
84 | void Model::clear() |
85 | { |
86 | beginResetModel(); |
87 | m_images.clear(); |
88 | endResetModel(); |
89 | } |
90 | |
91 | void 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 | |
107 | bool 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 | |
125 | bool 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 | |
156 | void 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 | |
187 | Progress 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 | |
197 | void Model::progressChanged() |
198 | { |
199 | auto const p = progress(); |
200 | Q_EMIT progressStepChanged(p.first, p.second); |
201 | } |
202 | |
203 | void Model::elementChanged(int id) |
204 | { |
205 | signalElementChanged(id); |
206 | } |
207 | |
208 | int Model::rowCount(const QModelIndex &parent) const |
209 | { |
210 | Q_UNUSED(parent); |
211 | return m_images.size(); |
212 | } |
213 | |
214 | QVariant 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 | |
235 | QVariant Model::(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 | |
243 | void 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 | |