1 /*******************************************************************************************************
2  DkProcess.cpp
3  Created on:	27.12.2014
4 
5  nomacs is a fast and small image viewer with the capability of synchronizing multiple instances
6 
7  Copyright (C) 2011-2014 Markus Diem <markus@nomacs.org>
8  Copyright (C) 2011-2014 Stefan Fiel <stefan@nomacs.org>
9  Copyright (C) 2011-2014 Florian Kleber <florian@nomacs.org>
10 
11  This file is part of nomacs.
12 
13  nomacs is free software: you can redistribute it and/or modify
14  it under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  nomacs is distributed in the hope that it will be useful,
19  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  GNU General Public License for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 
26  *******************************************************************************************************/
27 
28 #include "DkProcess.h"
29 #include "DkUtils.h"
30 #include "DkImageContainer.h"
31 #include "DkImageStorage.h"
32 #include "DkPluginManager.h"
33 #include "DkSettings.h"
34 #include "DkMath.h"
35 #include "DkManipulators.h"
36 #include "DkTimer.h"
37 
38 #include "DkMetaData.h"
39 
40 #pragma warning(push, 0)	// no warnings from includes - begin
41 #include <QFuture>
42 #include <QFutureWatcher>
43 #include <QtConcurrentMap>
44 #include <QWidget>
45 #pragma warning(pop)		// no warnings from includes - end
46 
47 #include <cassert>
48 
49 namespace nmc {
50 
51 /// <summary>
52 /// Generic compute method for DkBatch Info <see cref="DkBatchInfo"/>.
53 /// This method allows for a simplified interface if a derived class
54 /// just needs to process the image itself (not meta data).
55 /// </summary>
56 /// <param name="container">Container the image container to be processed.</param>
57 /// <param name="logStrings">log strings.</param>
58 /// <param name="batchInfo">The batch information. You can save any information into this class.</param>
59 /// <returns>
60 /// true on success
61 /// </returns>
compute(QSharedPointer<DkImageContainer> container,const DkSaveInfo &,QStringList & logStrings,QVector<QSharedPointer<DkBatchInfo>> &) const62 bool DkAbstractBatch::compute(QSharedPointer<DkImageContainer> container, const DkSaveInfo& , QStringList& logStrings, QVector<QSharedPointer<DkBatchInfo> >&) const {
63 
64 	return compute(container, logStrings);
65 }
66 
67 /// <summary>
68 /// Generic compute method.
69 /// This method allows for a simplified interface if a derived class
70 /// just needs to process the image itself (not meta data).
71 /// </summary>
72 /// <param name="container">Container the image container to be processed.</param>
73 /// <param name="logStrings">log strings.</param>
74 /// <returns>true on success</returns>
compute(QSharedPointer<DkImageContainer> container,QStringList & logStrings) const75 bool DkAbstractBatch::compute(QSharedPointer<DkImageContainer> container, QStringList& logStrings) const {
76 
77 	QImage img = container->image();
78 
79 	bool isOk = compute(img, logStrings);
80 
81 	if (isOk)
82 		container->setImage(img, QObject::tr("Batch Action"));
83 
84 	return isOk;
85 }
86 
settingsName() const87 QString DkAbstractBatch::settingsName() const {
88 
89 	// make name() settings friendly
90 	QString sn = name();
91 	sn.replace("[", "");
92 	sn.replace("]", "");
93 	sn.replace(" ", "");
94 
95 	return sn;
96 }
97 
createFromName(const QString & settingsName)98 QSharedPointer<DkAbstractBatch> DkAbstractBatch::createFromName(const QString& settingsName) {
99 
100 	QSharedPointer<DkAbstractBatch> batch;
101 	batch = QSharedPointer<DkBatchTransform>::create();
102 
103 	if (batch->settingsName() == settingsName)
104 		return batch;
105 
106 	batch = QSharedPointer<DkManipulatorBatch>::create();
107 
108 	if (batch->settingsName() == settingsName)
109 		return batch;
110 
111 #ifdef WITH_PLUGINS
112 	batch = QSharedPointer<DkPluginBatch>::create();
113 
114 	if (batch->settingsName() == settingsName)
115 		return batch;
116 #endif
117 
118 	qCritical() << "cannot instantiate batch, illegal settings name: " << settingsName;
119 	return QSharedPointer<DkAbstractBatch>();
120 }
121 
122 // DkTransformBatch --------------------------------------------------------------------
DkBatchTransform()123 DkBatchTransform::DkBatchTransform() {
124 	mResizeIplMethod = DkImage::ipl_area;	// define here because of includes
125 }
126 
name() const127 QString DkBatchTransform::name() const {
128 	return QObject::tr("[Transform Batch]");
129 }
130 
setProperties(int angle,bool cropFromMetadata,QRect cropRect,float scaleFactor,const ResizeMode & mode,const ResizeProperty & prop,int iplMethod,bool correctGamma)131 void DkBatchTransform::setProperties(
132 	int angle,
133 	bool cropFromMetadata,
134 	QRect cropRect,
135 	float scaleFactor,
136 	const ResizeMode& mode /*= resize_mode_default*/,
137 	const ResizeProperty& prop /*= resize_prop_default*/,
138 	int iplMethod /*= DkImage::ipl_area*/,
139 	bool correctGamma /*= false*/) {
140 
141 	mAngle = angle;
142 	mCropFromMetadata = cropFromMetadata;
143 	mCropRect = cropRect;
144 
145 	mResizeScaleFactor = scaleFactor;
146 	mResizeMode = mode;
147 	mResizeProperty = prop;
148 	mResizeIplMethod = iplMethod;
149 	mResizeCorrectGamma = correctGamma;
150 }
151 
saveSettings(QSettings & settings) const152 void DkBatchTransform::saveSettings(QSettings & settings) const {
153 
154 	settings.beginGroup(settingsName());
155 	settings.setValue("Angle", mAngle);
156 	settings.setValue("CropFromMetadata", mCropFromMetadata);
157 	settings.setValue("CropRectangle", rectToString(mCropRect));
158 
159 	// resize
160 	settings.setValue("ScaleFactor", mResizeScaleFactor);
161 	settings.setValue("Mode", mResizeMode);
162 	settings.setValue("Property", mResizeProperty);
163 	settings.setValue("IplMethod", mResizeIplMethod);
164 	settings.setValue("CorrectGamma", mResizeCorrectGamma);
165 
166 	settings.endGroup();
167 }
168 
loadSettings(QSettings & settings)169 void DkBatchTransform::loadSettings(QSettings & settings) {
170 
171 	settings.beginGroup(settingsName());
172 	mAngle = settings.value("Angle", mAngle).toInt();
173 	mCropFromMetadata = settings.value("CropFromMetadata", mCropFromMetadata).toBool();
174 	mCropRect = stringToRect(settings.value("CropRectangle", mCropRect).toString());
175 
176 	mResizeScaleFactor	= settings.value("ScaleFactor", mResizeScaleFactor).toFloat();
177 	mResizeMode			= (ResizeMode)settings.value("Mode", mResizeMode).toInt();
178 	mResizeProperty		= (ResizeProperty)settings.value("Property", mResizeProperty).toInt();
179 	mResizeIplMethod	= settings.value("IplMethod", mResizeIplMethod).toInt();
180 	mResizeCorrectGamma	= settings.value("Correct Gamma", mResizeCorrectGamma).toBool();
181 	settings.endGroup();
182 }
183 
isResizeActive() const184 bool DkBatchTransform::isResizeActive() const {
185 
186 	if (mResizeMode != resize_mode_default)
187 		return true;
188 
189 	if (mResizeScaleFactor != 1.0f)
190 		return true;
191 
192 	return false;
193 }
194 
rectToString(const QRect & r) const195 QString DkBatchTransform::rectToString(const QRect & r) const {
196 
197 	QString str;
198 	str += QString::number(r.x()) + ",";
199 	str += QString::number(r.y()) + ",";
200 	str += QString::number(r.width()) + ",";
201 	str += QString::number(r.height());
202 
203 	return str;
204 }
205 
stringToRect(const QString & s) const206 QRect DkBatchTransform::stringToRect(const QString & s) const {
207 
208 	QStringList sl = s.split(",");
209 
210 	if (sl.size() != 4) {
211 		qWarning() << "could not parse rect from" << s;
212 		return QRect();
213 	}
214 
215 	QRect rect;
216 
217 	bool okX, okY, okW, okH = false;
218 	rect.setX(sl[0].toInt(&okX));
219 	rect.setY(sl[1].toInt(&okY));
220 	rect.setWidth(sl[2].toInt(&okW));
221 	rect.setHeight(sl[3].toInt(&okH));
222 
223 	if (!okX || !okY || !okW || !okH) {
224 		qWarning() << "could not parse rect from" << s;
225 		return QRect();
226 	}
227 
228 	return rect;
229 }
230 
isActive() const231 bool DkBatchTransform::isActive() const {
232 
233 	return mAngle != 0 || mCropFromMetadata || cropFromRectangle() || isResizeActive();
234 }
235 
angle() const236 int DkBatchTransform::angle() const {
237 	return mAngle;
238 }
239 
cropMetatdata() const240 bool DkBatchTransform::cropMetatdata() const {
241 	return mCropFromMetadata;
242 }
243 
cropFromRectangle() const244 bool DkBatchTransform::cropFromRectangle() const {
245 	return !mCropRect.isEmpty();
246 }
247 
cropRectangle() const248 QRect DkBatchTransform::cropRectangle() const {
249 	return mCropRect;
250 }
251 
mode() const252 DkBatchTransform::ResizeMode DkBatchTransform::mode() const {
253 	return mResizeMode;
254 }
255 
prop() const256 DkBatchTransform::ResizeProperty DkBatchTransform::prop() const {
257 	return mResizeProperty;
258 }
259 
iplMethod() const260 int DkBatchTransform::iplMethod() const {
261 	return mResizeIplMethod;
262 }
263 
scaleFactor() const264 float DkBatchTransform::scaleFactor() const {
265 	return mResizeScaleFactor;
266 }
267 
correctGamma() const268 bool DkBatchTransform::correctGamma() const {
269 	return mResizeCorrectGamma;
270 }
271 
compute(QSharedPointer<DkImageContainer> container,QStringList & logStrings) const272 bool DkBatchTransform::compute(QSharedPointer<DkImageContainer> container, QStringList& logStrings) const {
273 
274 
275 	if (!isActive()) {
276 		logStrings.append(QObject::tr("%1 inactive -> skipping").arg(name()));
277 		return true;
278 	}
279 
280 	DkRotatingRect rect = container->cropRect();
281 	if (mCropFromMetadata) {
282 		if (!rect.isEmpty())
283 			container->cropImage(rect, QColor(), false);
284 	}
285 
286 	QImage img = container->image();
287 	QImage tmpImg;
288 
289 	// resize
290 	if (isResizeActive()) {
291 		QSize size;
292 		float sf = 1.0f;
293 
294 		if (prepareProperties(img.size(), size, sf, logStrings))
295 			tmpImg = DkImage::resizeImage(img, size, sf, mResizeIplMethod, mResizeCorrectGamma);
296 		else
297 			tmpImg = img;
298 	}
299 	else
300 		tmpImg = img;
301 
302 	// rotate
303 	if (mAngle != 0) {
304 		QTransform rotationMatrix;
305 		rotationMatrix.rotate((double)mAngle);
306 		tmpImg = tmpImg.transformed(rotationMatrix);
307 	}
308 
309 	// crop from rectangle
310 	if (cropFromRectangle()) {
311 		QRect r = mCropRect.intersected(container->image().rect());
312 		tmpImg = tmpImg.copy(r);
313 	}
314 
315 	// logs
316 	if (!tmpImg.isNull()) {
317 
318 		container->setImage(tmpImg, QObject::tr("transformed"));
319 
320 		if (rect.isEmpty() && mCropFromMetadata)
321 			logStrings.append(QObject::tr("%1 image transformed.").arg(name()));
322 		else if (isResizeActive()) {
323 			if (mResizeMode == resize_mode_default)
324 				logStrings.append(QObject::tr("%1 image resized, scale factor: %2%").arg(name()).arg(mResizeScaleFactor*100.0f));
325 			else
326 				logStrings.append(QObject::tr("%1 image resized, new side: %2 px").arg(name()).arg(mResizeScaleFactor));
327 		}
328 		else
329 			logStrings.append(QObject::tr("%1 image transformed and cropped.").arg(name()));
330 
331 	}
332 	else {
333 		logStrings.append(QObject::tr("%1 error, could not transform image.").arg(name()));
334 		return false;
335 	}
336 
337 	return true;
338 }
339 
prepareProperties(const QSize & imgSize,QSize & size,float & scaleFactor,QStringList & logStrings) const340 bool DkBatchTransform::prepareProperties(const QSize& imgSize, QSize& size, float& scaleFactor, QStringList& logStrings) const {
341 
342 	float sf = 1.0f;
343 	QSize normalizedSize = imgSize;
344 
345 	if (mResizeMode == resize_mode_default) {
346 		scaleFactor = mResizeScaleFactor;
347 		return true;
348 	}
349 	else if (mResizeMode == resize_mode_long_side) {
350 
351 		if (imgSize.width() < imgSize.height())
352 			normalizedSize.transpose();
353 	}
354 	else if (mResizeMode == resize_mode_short_side) {
355 
356 		if (imgSize.width() > imgSize.height())
357 			normalizedSize.transpose();
358 	}
359 	else if (mResizeMode == resize_mode_height)
360 		normalizedSize.transpose();
361 
362 	sf = mResizeScaleFactor/normalizedSize.width();
363 
364 	if (sf > 1.0 && mResizeProperty == resize_prop_decrease_only) {
365 
366 		logStrings.append(QObject::tr("%1 I need to increase the image size, but the option is set to 'decrease only' -> skipping.").arg(name()));
367 		return false;
368 	}
369 	else if (sf < 1.0f && mResizeProperty == resize_prop_increase_only) {
370 		logStrings.append(QObject::tr("%1 I need to decrease the image size, but the option is set to 'increase only' -> skipping.").arg(name()));
371 		return false;
372 	}
373 	else if (sf == 1.0f) {
374 		logStrings.append(QObject::tr("%1 image size matches scale factor -> skipping.").arg(name()));
375 		return false;
376 	}
377 
378 	size.setWidth(qRound(mResizeScaleFactor));
379 	size.setHeight(qRound(sf*normalizedSize.height()));
380 
381 	if (normalizedSize != imgSize)
382 		size.transpose();
383 
384 	return true;
385 }
386 
387 
388 
389 // DkManipulatorBatch --------------------------------------------------------------------
DkManipulatorBatch()390 DkManipulatorBatch::DkManipulatorBatch() {
391 
392 }
393 
saveSettings(QSettings & settings) const394 void DkManipulatorBatch::saveSettings(QSettings & settings) const {
395 
396 	settings.beginGroup(settingsName());
397 	mManager.saveSettings(settings);
398 	settings.endGroup();
399 }
400 
loadSettings(QSettings & settings)401 void DkManipulatorBatch::loadSettings(QSettings & settings) {
402 
403 	settings.beginGroup(settingsName());
404 	mManager.loadSettings(settings);
405 	settings.endGroup();
406 }
407 
setProperties(const DkManipulatorManager & manager)408 void DkManipulatorBatch::setProperties(const DkManipulatorManager& manager) {
409 	mManager = manager;
410 }
411 
compute(QSharedPointer<DkImageContainer> container,QStringList & logStrings) const412 bool DkManipulatorBatch::compute(QSharedPointer<DkImageContainer> container, QStringList & logStrings) const {
413 
414 	if (!isActive()) {
415 		logStrings.append(QObject::tr("%1 inactive -> skipping").arg(name()));
416 		return true;
417 	}
418 
419 	if (container && container->hasImage()) {
420 		for (const QSharedPointer<DkBaseManipulator>& mpl : mManager.manipulators()) {
421 
422 			if (mpl->isSelected()) {
423 				QImage img = mpl->apply(container->image());
424 				if (!img.isNull()) {
425 					container->setImage(img, mpl->name());
426 					logStrings.append(QObject::tr("%1 %2 applied.").arg(name()).arg(mpl->name()));
427 				}
428 				else
429 					logStrings.append(QObject::tr("%1 Cannot apply %2.").arg(name()).arg(mpl->name()));
430 			}
431 		}
432 	}
433 
434 	if (!container || !container->hasImage()) {
435 		logStrings.append(QObject::tr("%1 error, could not apply image adjustments.").arg(name()));
436 		return false;
437 	}
438 
439 	return true;
440 }
441 
name() const442 QString DkManipulatorBatch::name() const {
443 	return QObject::tr("[Adjustment Batch]");
444 }
445 
isActive() const446 bool DkManipulatorBatch::isActive() const {
447 	return mManager.numSelected() > 0;
448 }
449 
manager() const450 DkManipulatorManager DkManipulatorBatch::manager() const {
451 	return mManager;
452 }
453 
454 #ifdef WITH_PLUGINS
455 // DkPluginBatch --------------------------------------------------------------------
DkPluginBatch()456 DkPluginBatch::DkPluginBatch() {
457 }
458 
setProperties(const QStringList & pluginList)459 void DkPluginBatch::setProperties(const QStringList & pluginList) {
460 	mPluginList = pluginList;
461 }
462 
saveSettings(QSettings & settings) const463 void DkPluginBatch::saveSettings(QSettings & settings) const {
464 
465 	settings.beginGroup(settingsName());
466 	settings.setValue("pluginList", mPluginList.join(";"));
467 
468 	for (const QSharedPointer<DkPluginContainer> plugin : mPlugins) {
469 
470 		if (!plugin)
471 			continue;
472 
473 		DkBatchPluginInterface* bPlugin = plugin->batchPlugin();
474 
475 		if (bPlugin) {
476 			bPlugin->saveSettings(settings);
477 		}
478 		else
479 			qWarning() << "Illegal plugin detected: " << plugin->pluginName();
480 	}
481 
482 	settings.endGroup();
483 }
484 
loadSettings(QSettings & settings)485 void DkPluginBatch::loadSettings(QSettings & settings) {
486 
487 	settings.beginGroup(settingsName());
488 	mPluginList = settings.value("pluginList", mPluginList).toString().split(";");
489 
490 	loadAllPlugins();
491 
492 	for (QSharedPointer<DkPluginContainer> plugin : mPlugins) {
493 
494 		if (!plugin)
495 			continue;
496 
497 		DkBatchPluginInterface* bPlugin = plugin->batchPlugin();
498 
499 		if (bPlugin) {
500 			assert(bPlugin);
501 			bPlugin->loadSettings(settings);
502 		}
503 	}
504 
505 	settings.endGroup();
506 }
507 
508 
preLoad()509 void DkPluginBatch::preLoad() {
510 
511 	loadAllPlugins();
512 }
513 
postLoad(const QVector<QSharedPointer<DkBatchInfo>> & batchInfo) const514 void DkPluginBatch::postLoad(const QVector<QSharedPointer<DkBatchInfo> >& batchInfo) const {
515 
516 	for (int idx = 0; idx < mPlugins.size(); idx++) {
517 
518 		QSharedPointer<DkPluginContainer> pluginContainer = mPlugins[idx];
519 		QString runID = mRunIDs[idx];
520 
521 		if (pluginContainer) {
522 
523 			// get plugin
524 			DkBatchPluginInterface* plugin = pluginContainer->batchPlugin();
525 
526 			qDebug() << "[POST LOAD]" << pluginContainer->pluginName() << "id:" << runID;
527 			QVector<QSharedPointer<DkBatchInfo> > fInfos = DkBatchInfo::filter(batchInfo, runID);
528 
529 			// check if it is ok
530 			if (plugin) {
531 				plugin->postLoadPlugin(fInfos);
532 			}
533 		}
534 	}
535 }
536 
compute(QSharedPointer<DkImageContainer> container,const DkSaveInfo & saveInfo,QStringList & logStrings,QVector<QSharedPointer<DkBatchInfo>> & batchInfos) const537 bool DkPluginBatch::compute(
538 	QSharedPointer<DkImageContainer> container,
539 	const DkSaveInfo& saveInfo,
540 	QStringList& logStrings,
541 	QVector<QSharedPointer<DkBatchInfo> >& batchInfos) const {
542 
543 	if (!isActive()) {
544 		logStrings.append(QObject::tr("%1 inactive -> skipping").arg(name()));
545 		return true;
546 	}
547 
548 	for (int idx = 0; idx < mPlugins.size(); idx++) {
549 
550 		QSharedPointer<DkPluginContainer> pluginContainer = mPlugins[idx];
551 		QString runID = mRunIDs[idx];
552 
553 		if (pluginContainer) {
554 			// get plugin
555 			DkPluginInterface* plugin = pluginContainer->plugin();
556 
557 			// check if it is ok
558 			if ( plugin &&
559 				(plugin->interfaceType() == DkPluginInterface::interface_basic ||
560 				 plugin->interfaceType() == DkPluginInterface::interface_batch)) {
561 
562 				// apply the plugin
563 				QSharedPointer<DkImageContainer> result;
564 
565 				if (plugin->interfaceType() == DkPluginInterface::interface_basic)
566 					result = plugin->runPlugin(runID, container);
567 				else if (plugin->interfaceType() == DkPluginInterface::interface_batch) {
568 
569 					DkBatchPluginInterface* bPlugin = pluginContainer->batchPlugin();
570 					QSharedPointer<DkBatchInfo> info;
571 
572 					if (bPlugin)
573 						result = bPlugin->runPlugin(runID, container, saveInfo, info);
574 					else
575 						logStrings.append(QObject::tr("%1 Cannot cast batch plugin %2.").arg(name()).arg(pluginContainer->pluginName()));
576 
577 					batchInfos << info;
578 				}
579 
580 				if (result && result->hasImage())
581 					container = result;
582 				else
583 					logStrings.append(QObject::tr("%1 Cannot apply %2.").arg(name()).arg(pluginContainer->pluginName()));
584 			}
585 			else
586 				logStrings.append(QObject::tr("%1 illegal plugin interface: %2").arg(name()).arg(pluginContainer->pluginName()));
587 		}
588 		else
589 			logStrings.append(QObject::tr("%1 Cannot apply plugin because it is NULL.").arg(name()));
590 	}
591 
592 	if (!container || !container->hasImage()) {
593 		logStrings.append(QObject::tr("%1 error, could not apply plugins.").arg(name()));
594 		return false;
595 	}
596 	else
597 		logStrings.append(QObject::tr("%1 plugins applied.").arg(name()));
598 
599 	return true;
600 }
601 
name() const602 QString DkPluginBatch::name() const {
603 	return QObject::tr("[Plugin Batch]");
604 }
605 
isActive() const606 bool DkPluginBatch::isActive() const {
607 
608 	return !mPluginList.empty();
609 }
610 
pluginList() const611 QStringList DkPluginBatch::pluginList() const {
612 	return mPluginList;
613 }
614 
loadAllPlugins()615 void DkPluginBatch::loadAllPlugins() {
616 
617 	// already loaded?
618 	if (mPlugins.size() == mPluginList.size())
619 		return;
620 
621 	// manager cares that they are not loaded twice
622 	// we need to load them here for CMD inputs
623 	DkPluginManager::instance().loadPlugins();
624 
625 	QString runId;
626 
627 	for (const QString& cPluginString : mPluginList) {
628 
629 		// load plugin
630 		QSharedPointer<DkPluginContainer> pluginContainer;
631 		QString runID;
632 		loadPlugin(cPluginString, pluginContainer, runID);
633 		mPlugins << pluginContainer;	// also add the empty ones...
634 		mRunIDs << runID;
635 
636 		if (pluginContainer) {
637 
638 			qDebug() << "loading" << pluginContainer->pluginName() << "id:" << runID;
639 
640 			// get plugin
641 			DkBatchPluginInterface* plugin = pluginContainer->batchPlugin();
642 
643 			// check if it is ok
644 			if (plugin) {
645 				plugin->preLoadPlugin();
646 			}
647 		}
648 		else
649 			qWarning() << "could not load: " << cPluginString;
650 	}
651 }
652 
loadPlugin(const QString & pluginString,QSharedPointer<DkPluginContainer> & plugin,QString & runID) const653 void DkPluginBatch::loadPlugin(const QString & pluginString, QSharedPointer<DkPluginContainer> & plugin, QString& runID) const {
654 
655 	QString uiSeparator = " | ";
656 
657 	QStringList ids = pluginString.split(uiSeparator);
658 
659 	if (ids.size() != 2) {
660 		qWarning() << "plugin string does not match:" << pluginString;
661 	}
662 	else {
663 		plugin = DkPluginManager::instance().getPluginByName(ids[0]);
664 
665 		if (plugin)
666 			runID = plugin->actionNameToRunId(ids[1]);
667 	}
668 }
669 #endif
670 
671 // DkBatchProcess --------------------------------------------------------------------
DkBatchProcess(const DkSaveInfo & saveInfo)672 DkBatchProcess::DkBatchProcess(const DkSaveInfo& saveInfo) {
673 	mSaveInfo = saveInfo;
674 }
675 
setProcessChain(const QVector<QSharedPointer<DkAbstractBatch>> processes)676 void DkBatchProcess::setProcessChain(const QVector<QSharedPointer<DkAbstractBatch> > processes) {
677 
678 	mProcessFunctions = processes;
679 }
680 
inputFile() const681 QString DkBatchProcess::inputFile() const {
682 
683 	return mSaveInfo.inputFilePath();
684 }
685 
outputFile() const686 QString DkBatchProcess::outputFile() const {
687 
688 	return mSaveInfo.outputFilePath();
689 }
690 
batchInfo() const691 QVector<QSharedPointer<DkBatchInfo> > DkBatchProcess::batchInfo() const {
692 
693 	return mInfos;
694 }
695 
hasFailed() const696 bool DkBatchProcess::hasFailed() const {
697 
698 	return mFailure != 0;
699 }
700 
wasProcessed() const701 bool DkBatchProcess::wasProcessed() const {
702 
703 	return mIsProcessed;
704 }
705 
compute()706 bool DkBatchProcess::compute() {
707 
708 	mIsProcessed = true;
709 
710 	QFileInfo fInfoIn(mSaveInfo.inputFilePath());
711 	QFileInfo fInfoOut(mSaveInfo.outputFilePath());
712 
713 	// check errors
714 	if ((mSaveInfo.mode() & DkSaveInfo::mode_do_not_save_output) == 0 && // do not save is not set
715 		(fInfoOut.exists() && mSaveInfo.mode() == DkSaveInfo::mode_skip_existing)) {
716 		mLogStrings.append(QObject::tr("%1 already exists -> skipping (check 'overwrite' if you want to overwrite the file)").arg(mSaveInfo.outputFilePath()));
717 		mFailure++;
718 		return mFailure == 0;
719 	}
720 	else if (!fInfoIn.exists()) {
721 		mLogStrings.append(QObject::tr("Error: input file does not exist"));
722 		mLogStrings.append(QObject::tr("Input: %1").arg(mSaveInfo.inputFilePath()));
723 		mFailure++;
724 		return mFailure == 0;
725 	}
726 	else if (mSaveInfo.inputFilePath() == mSaveInfo.outputFilePath() && mProcessFunctions.empty()) {
727 		mLogStrings.append(QObject::tr("Skipping: nothing to do here."));
728 		mFailure++;
729 		return mFailure == 0;
730 	}
731 
732 	// rename operation?
733 	if (mProcessFunctions.empty() &&
734 		mSaveInfo.inputFilePath() == mSaveInfo.outputFilePath() &&
735 		fInfoIn.suffix() == fInfoOut.suffix()) {
736 		if (!renameFile())
737 			mFailure++;
738 		return mFailure == 0;
739 	}
740 	// copy operation?
741 	else if (mProcessFunctions.empty() && fInfoIn.suffix() == fInfoOut.suffix()) {
742 		if (!copyFile())
743 			mFailure++;
744 		else
745 			deleteOriginalFile();
746 
747 		return mFailure == 0;
748 	}
749 
750 	// do the work
751 	process();
752 
753 	// delete the original file if the user requested it
754 	deleteOriginalFile();
755 
756 	return mFailure == 0;
757 }
758 
getLog() const759 QStringList DkBatchProcess::getLog() const {
760 
761 	return mLogStrings;
762 }
763 
process()764 bool DkBatchProcess::process() {
765 
766 	mLogStrings.append(QObject::tr("processing %1").arg(mSaveInfo.inputFilePath()));
767 
768 	QSharedPointer<DkImageContainer> imgC(new DkImageContainer(mSaveInfo.inputFilePath()));
769 
770 	if (!imgC->loadImage() || imgC->image().isNull()) {
771 		mLogStrings.append(QObject::tr("Error while loading..."));
772 		mFailure++;
773 		return false;
774 	}
775 
776 	for (QSharedPointer<DkAbstractBatch> batch : mProcessFunctions) {
777 
778 		if (!batch) {
779 			mLogStrings.append(QObject::tr("Error: cannot process a NULL function."));
780 			continue;
781 		}
782 
783 		QVector<QSharedPointer<DkBatchInfo> > cInfos;
784 		if (!batch->compute(imgC, mSaveInfo, mLogStrings, cInfos)) {
785 			mLogStrings.append(QObject::tr("%1 failed").arg(batch->name()));
786 			mFailure++;
787 		}
788 
789 		mInfos << cInfos;
790 	}
791 
792 	// report we could not back-up & break here
793 	if (!prepareDeleteExisting()) {
794 		mFailure++;
795 		return false;
796 	}
797 
798 	// early break
799 	if (mSaveInfo.mode() & DkSaveInfo::mode_do_not_save_output) {
800 		mLogStrings.append(QObject::tr("%1 not saved - option 'Do not Save' is checked...").arg(mSaveInfo.outputFilePath()));
801 		return true;
802 	}
803 
804 	// udpate metadata
805 	if (updateMetaData(imgC->getMetaData().data()))
806 		mLogStrings.append(QObject::tr("Original filename added to Exif"));
807 
808 	// save the image
809 	if (imgC->saveImage(mSaveInfo.outputFilePath(), mSaveInfo.compression())) {
810 		mLogStrings.append(QObject::tr("%1 saved...").arg(mSaveInfo.outputFilePath()));
811 	}
812 	else {
813 		mLogStrings.append(QObject::tr("Could not save: %1").arg(mSaveInfo.outputFilePath()));
814 		mFailure++;
815 	}
816 
817 	if (!deleteOrRestoreExisting()) {
818 		mFailure++;
819 		return false;
820 	}
821 
822 	return true;
823 }
824 
renameFile()825 bool DkBatchProcess::renameFile() {
826 
827 	if (QFileInfo(mSaveInfo.outputFilePath()).exists()) {
828 		mLogStrings.append(QObject::tr("Error: could not rename file, the target file exists already."));
829 		return false;
830 	}
831 
832 	QFile file(mSaveInfo.inputFilePath());
833 
834 	QSharedPointer<DkMetaDataT> md(new DkMetaDataT());
835 	md->readMetaData(mSaveInfo.inputFilePath());
836 
837 	if (updateMetaData(md.data())) {
838 		if (md->saveMetaData(mSaveInfo.inputFilePath()))
839 			mLogStrings.append(QObject::tr("Original filename added to Exif"));
840 	}
841 
842 	// Note: if two images are renamed at the same time to the same name, one image is lost -> see Qt comment Race Condition
843 	if (!file.rename(mSaveInfo.outputFilePath())) {
844 		mLogStrings.append(QObject::tr("Error: could not rename file"));
845 		mLogStrings.append(file.errorString());
846 		return false;
847 	}
848 	else
849 		mLogStrings.append(QObject::tr("Renaming: %1 -> %2").arg(mSaveInfo.inputFilePath()).arg(mSaveInfo.outputFilePath()));
850 
851 	return true;
852 }
853 
updateMetaData(DkMetaDataT * md)854 bool DkBatchProcess::updateMetaData(DkMetaDataT * md) {
855 
856 	if (!md)
857 		return false;
858 
859 	if (!md->isLoaded())
860 		return false;
861 
862 	QString ifn = mSaveInfo.inputFileInfo().fileName();
863 	if (ifn != mSaveInfo.outputFileInfo().fileName() &&
864 		md->getExifValue("ImageDescription").isEmpty()) {
865 
866 		if (md->setExifValue("Exif.Image.ImageDescription", ifn))
867 			return true;
868 	}
869 
870 	return false;
871 }
872 
copyFile()873 bool DkBatchProcess::copyFile() {
874 
875 	QFile file(mSaveInfo.inputFilePath());
876 
877 	if (mSaveInfo.mode() == DkSaveInfo::mode_do_not_save_output) {
878 		mLogStrings.append(QObject::tr("I should copy the file, but 'Do not Save' is checked - so I will do nothing..."));
879 		return false;
880 	}
881 
882 	// report we could not back-up & break here
883 	if (!prepareDeleteExisting()) {
884 		mFailure++;
885 		return false;
886 	}
887 
888 	QSharedPointer<DkMetaDataT> md(new DkMetaDataT());
889 	md->readMetaData(mSaveInfo.inputFilePath());
890 
891 	bool exifUpdated = updateMetaData(md.data());
892 
893 	if (!file.copy(mSaveInfo.outputFilePath())) {
894 		mLogStrings.append(QObject::tr("Error: could not copy file"));
895 		mLogStrings.append(QObject::tr("Input: %1").arg(mSaveInfo.inputFilePath()));
896 		mLogStrings.append(QObject::tr("Output: %1").arg(mSaveInfo.outputFilePath()));
897 		mLogStrings.append(file.errorString());
898 		return false;
899 	}
900 	else {
901 		if (exifUpdated && md->saveMetaData(mSaveInfo.outputFilePath()))
902 			mLogStrings.append(QObject::tr("Original filename added to Exif"));
903 
904 		mLogStrings.append(QObject::tr("Copying: %1 -> %2").arg(mSaveInfo.inputFilePath()).arg(mSaveInfo.outputFilePath()));
905 	}
906 
907 	if (!deleteOrRestoreExisting()) {
908 		mFailure++;
909 		return false;
910 	}
911 
912 	return true;
913 }
914 
prepareDeleteExisting()915 bool DkBatchProcess::prepareDeleteExisting() {
916 
917 	if (QFileInfo(mSaveInfo.outputFilePath()).exists() && mSaveInfo.mode() == DkSaveInfo::mode_overwrite) {
918 
919 		mSaveInfo.createBackupFilePath();
920 
921 		// check the uniqueness : )
922 		if (QFileInfo(mSaveInfo.backupFilePath()).exists()) {
923 			mLogStrings.append(QObject::tr("Error: back-up (%1) file already exists").arg(mSaveInfo.backupFilePath()));
924 			mSaveInfo.clearBackupFilePath();
925 			return false;
926 		}
927 
928 		QFile file(mSaveInfo.outputFilePath());
929 
930 		if (!file.rename(mSaveInfo.backupFilePath())) {
931 			mLogStrings.append(QObject::tr("Error: could not rename existing file to %1").arg(mSaveInfo.backupFilePath()));
932 			mLogStrings.append(file.errorString());
933 			mSaveInfo.clearBackupFilePath();
934 			return false;
935 		}
936 	}
937 
938 	return true;
939 }
940 
deleteOrRestoreExisting()941 bool DkBatchProcess::deleteOrRestoreExisting() {
942 
943 	QFileInfo outInfo(mSaveInfo.outputFilePath());
944 
945 	if (outInfo.exists() && !mSaveInfo.backupFilePath().isEmpty() && mSaveInfo.backupFileInfo().exists()) {
946 		QFile file(mSaveInfo.backupFilePath());
947 
948 		if (!file.remove()) {
949 			mLogStrings.append(QObject::tr("Error: could not delete existing file"));
950 			mLogStrings.append(file.errorString());
951 			return false;
952 		}
953 	}
954 	// fall-back
955 	else if (!outInfo.exists()) {
956 
957 		QFile file(mSaveInfo.backupFilePath());
958 
959 		if (!file.rename(mSaveInfo.outputFilePath())) {
960 			mLogStrings.append(QObject::tr("Ui - a lot of things went wrong. Your original file can be found here: %1").arg(mSaveInfo.backupFilePath()));
961 			mLogStrings.append(file.errorString());
962 			return false;
963 		}
964 		else {
965 			mLogStrings.append(QObject::tr("I could not save to %1 so I restored the original file.").arg(mSaveInfo.outputFilePath()));
966 		}
967 	}
968 
969 	return true;
970 }
971 
deleteOriginalFile()972 bool DkBatchProcess::deleteOriginalFile() {
973 
974 	if (mSaveInfo.inputFilePath() == mSaveInfo.outputFilePath())
975 		return true;
976 
977 	if (!mFailure && mSaveInfo.isDeleteOriginal()) {
978 		QFile oFile(mSaveInfo.inputFilePath());
979 
980 		if (oFile.remove())
981 			mLogStrings.append(QObject::tr("%1 deleted.").arg(mSaveInfo.inputFilePath()));
982 		else {
983 			mFailure++;
984 			mLogStrings.append(QObject::tr("I could not delete %1").arg(mSaveInfo.inputFilePath()));
985 			return false;
986 		}
987 	}
988 	else if (mFailure)
989 		mLogStrings.append(QObject::tr("I did not delete the original because I detected %1 failure(s).").arg(mFailure));
990 
991 	return true;
992 }
993 
994 // DkBatchConfig --------------------------------------------------------------------
DkBatchConfig(const QStringList & fileList,const QString & outputDir,const QString & fileNamePattern)995 DkBatchConfig::DkBatchConfig(const QStringList& fileList, const QString& outputDir, const QString& fileNamePattern) {
996 
997 	mFileList = fileList;
998 	mOutputDirPath = outputDir;
999 	mFileNamePattern = fileNamePattern;
1000 
1001 }
1002 
isOk() const1003 bool DkBatchConfig::isOk() const {
1004 
1005 	if (mOutputDirPath.isEmpty())
1006 		return false;
1007 
1008 	QDir oDir(mOutputDirPath);
1009 
1010 	if (!oDir.exists()) {
1011 		if (!oDir.mkpath("."))
1012 			return false;	// output dir does not exist & I cannot create it
1013 	}
1014 
1015 	if (mFileList.empty())
1016 		return false;
1017 
1018 	if (mFileNamePattern.isEmpty())
1019 		return false;
1020 
1021 	return true;
1022 }
1023 
1024 
1025 // DkBatchProcessing --------------------------------------------------------------------
DkBatchProcessing(const DkBatchConfig & config,QWidget * parent)1026 DkBatchProcessing::DkBatchProcessing(const DkBatchConfig& config, QWidget* parent /*= 0*/) : QObject(parent) {
1027 
1028 	mBatchConfig = config;
1029 
1030 	connect(&mBatchWatcher, SIGNAL(progressValueChanged(int)), this, SIGNAL(progressValueChanged(int)));
1031 	connect(&mBatchWatcher, SIGNAL(finished()), this, SIGNAL(finished()));
1032 }
1033 
init()1034 void DkBatchProcessing::init() {
1035 
1036 	mBatchItems.clear();
1037 
1038 	QStringList fileList = mBatchConfig.getFileList();
1039 
1040 	for (int idx = 0; idx < fileList.size(); idx++) {
1041 
1042 		DkSaveInfo si = mBatchConfig.saveInfo();
1043 
1044 		QFileInfo cFileInfo = QFileInfo(fileList.at(idx));
1045 		QString outDir = si.isInputDirOutputDir() ? cFileInfo.absolutePath() : mBatchConfig.getOutputDirPath();
1046 
1047 		DkFileNameConverter converter(cFileInfo.fileName(), mBatchConfig.getFileNamePattern(), idx);
1048 		QString outputFilePath = QFileInfo(outDir, converter.getConvertedFileName()).absoluteFilePath();
1049 
1050 		// set input/output file path
1051 		si.setInputFilePath(fileList.at(idx));
1052 		si.setOutputFilePath(outputFilePath);
1053 
1054 		DkBatchProcess cProcess(si);
1055 		cProcess.setProcessChain(mBatchConfig.getProcessFunctions());
1056 
1057 		mBatchItems.push_back(cProcess);
1058 	}
1059 }
1060 
saveSettings(QSettings & settings) const1061 void DkBatchConfig::saveSettings(QSettings & settings) const {
1062 
1063 	settings.beginGroup("General");		// this general group could be removed in future releases
1064 	settings.setValue("FileList", mFileList.join(";"));
1065 	settings.setValue("OutputDirPath", mOutputDirPath);
1066 	settings.setValue("FileNamePattern", mFileNamePattern);
1067 
1068 	mSaveInfo.saveSettings(settings);
1069 
1070 	for (auto pf : mProcessFunctions)
1071 		pf->saveSettings(settings);
1072 
1073 	settings.endGroup();
1074 }
1075 
loadSettings(QSettings & settings)1076 void DkBatchConfig::loadSettings(QSettings & settings) {
1077 
1078 	settings.beginGroup("General");
1079 	mFileList = settings.value("FileList", mFileList).toString().split(";");
1080 	mOutputDirPath = settings.value("OutputDirPath", mOutputDirPath).toString();
1081 	mFileNamePattern = settings.value("FileNamePattern", mFileNamePattern).toString();
1082 
1083 	mSaveInfo.loadSettings(settings);
1084 
1085 	QStringList groups = settings.childGroups();
1086 
1087 	for (const QString& name : groups) {
1088 
1089 		// known groups that are not batch processes
1090 		if (name == "SaveInfo")
1091 			continue;
1092 
1093 		QSharedPointer<DkAbstractBatch> batch = DkAbstractBatch::createFromName(name);
1094 
1095 		// if it is valid - append the process
1096 		if (batch) {
1097 			batch->loadSettings(settings);
1098 			mProcessFunctions << batch;
1099 		}
1100 	}
1101 
1102 	for (auto pf : mProcessFunctions)
1103 		pf->saveSettings(settings);
1104 
1105 	settings.endGroup();
1106 }
1107 
compute()1108 void DkBatchProcessing::compute() {
1109 
1110 	init();
1111 
1112 	qDebug() << "computing...";
1113 
1114 	if (mBatchWatcher.isRunning())
1115 		mBatchWatcher.waitForFinished();
1116 
1117 	QFuture<void> future = QtConcurrent::map(mBatchItems, &nmc::DkBatchProcessing::computeItem);
1118 	mBatchWatcher.setFuture(future);
1119 }
1120 
computeItem(DkBatchProcess & item)1121 bool DkBatchProcessing::computeItem(DkBatchProcess& item) {
1122 
1123 	return item.compute();
1124 }
1125 
postLoad()1126 void DkBatchProcessing::postLoad() {
1127 
1128 	// collect batch infos
1129 	QVector<QSharedPointer<DkBatchInfo> > batchInfo;
1130 
1131 	for (DkBatchProcess batch : mBatchItems) {
1132 		batchInfo << batch.batchInfo();
1133 	}
1134 
1135 	for (QSharedPointer<DkAbstractBatch> fun : mBatchConfig.getProcessFunctions()) {
1136 		fun->postLoad(batchInfo);
1137 	}
1138 }
1139 
computeBatch(const QString & settingsPath,const QString & logPath)1140 void DkBatchProcessing::computeBatch(const QString& settingsPath, const QString& logPath) {
1141 
1142 	DkTimer dt;
1143 	DkBatchConfig bc = DkBatchProfile::loadProfile(settingsPath);
1144 
1145 	// guarantee that the output path exists
1146 	if (!QDir().mkpath(bc.getOutputDirPath())) {
1147 		qCritical() << "Could not create:" << bc.getOutputDirPath();
1148 		return;
1149 	}
1150 
1151 	QSharedPointer<nmc::DkBatchProcessing> process(new nmc::DkBatchProcessing());
1152 	process->setBatchConfig(bc);
1153 	process->compute();
1154 
1155 	process->waitForFinished();	// block
1156 
1157 	qInfo() << "batch finished with" << process->getNumFailures() << "errors in" << dt;
1158 
1159 	if (!logPath.isEmpty()) {
1160 
1161 		QFileInfo fi(logPath);
1162 
1163 		QDir().mkpath(fi.absolutePath());
1164 
1165 		QFile file(logPath);
1166 		if (!file.open(QIODevice::WriteOnly))
1167 			qWarning() << "Sorry, I could not write to" << logPath;
1168 		else {
1169 			QStringList log = process->getLog();
1170 			QTextStream s(&file);
1171 			for (const QString& line : log)
1172 				s << line << '\n';
1173 			qInfo() << "log written to: " << logPath;
1174 		}
1175 	}
1176 }
1177 
1178 
getLog() const1179 QStringList DkBatchProcessing::getLog() const {
1180 
1181 	QStringList log;
1182 
1183 	for (DkBatchProcess batch : mBatchItems) {
1184 
1185 		log << batch.getLog();
1186 		log << "";	// add empty line between images
1187 	}
1188 
1189 	return log;
1190 }
1191 
getNumFailures() const1192 int DkBatchProcessing::getNumFailures() const {
1193 
1194 	int numFailures = 0;
1195 
1196 	for (DkBatchProcess batch : mBatchItems) {
1197 
1198 		if (batch.hasFailed())
1199 			numFailures++;
1200 	}
1201 
1202 	return numFailures;
1203 }
1204 
getNumProcessed() const1205 int DkBatchProcessing::getNumProcessed() const {
1206 
1207 	int numProcessed = 0;
1208 
1209 	for (DkBatchProcess batch : mBatchItems) {
1210 
1211 		if (batch.wasProcessed())
1212 			numProcessed++;
1213 	}
1214 
1215 	return numProcessed;
1216 }
1217 
getCurrentResults()1218 QList<int> DkBatchProcessing::getCurrentResults() {
1219 
1220 	if (mResList.empty()) {
1221 		for (int idx = 0; idx < mBatchItems.size(); idx++)
1222 			mResList.append(batch_item_not_computed);
1223 	}
1224 
1225 	for (int idx = 0; idx < mResList.size(); idx++) {
1226 
1227 		if (mResList.at(idx) != batch_item_not_computed)
1228 			continue;
1229 
1230 		if (mBatchItems.at(idx).wasProcessed())
1231 			mResList[idx] = mBatchItems.at(idx).hasFailed() ? batch_item_failed : batch_item_succeeded;
1232 	}
1233 
1234 	return mResList;
1235 }
1236 
getResultList() const1237 QStringList DkBatchProcessing::getResultList() const {
1238 
1239 	QStringList results;
1240 
1241 	for (DkBatchProcess batch : mBatchItems) {
1242 
1243 		if (batch.wasProcessed())
1244 			results.append(getBatchSummary(batch));
1245 	}
1246 
1247 	return results;
1248 }
1249 
getBatchSummary(const DkBatchProcess & batch) const1250 QString DkBatchProcessing::getBatchSummary(const DkBatchProcess& batch) const {
1251 
1252 	QString res = batch.inputFile() + "\t";
1253 
1254 	if (!batch.hasFailed())
1255 		res += " <span style=\" color:#00aa00;\">" + tr("[OK]") + "</span>";
1256 	else
1257 		res += " <span style=\" color:#aa0000;\">" + tr("[FAIL]") + "</span>";
1258 
1259 	return res;
1260 }
1261 
waitForFinished()1262 void DkBatchProcessing::waitForFinished() {
1263 	mBatchWatcher.waitForFinished();
1264 }
1265 
getNumItems() const1266 int DkBatchProcessing::getNumItems() const {
1267 
1268 	return mBatchItems.size();
1269 }
1270 
isComputing() const1271 bool DkBatchProcessing::isComputing() const {
1272 
1273 	return mBatchWatcher.isRunning();
1274 }
1275 
cancel()1276 void DkBatchProcessing::cancel() {
1277 
1278 	mBatchWatcher.cancel();
1279 }
1280 
1281 // DkBatchProfile --------------------------------------------------------------------
1282 QString DkBatchProfile::ext = "pnm";	// profile file extension
1283 
DkBatchProfile(const QString & profileDir)1284 DkBatchProfile::DkBatchProfile(const QString& profileDir) {
1285 
1286 	mProfileDir = (profileDir.isEmpty()) ? defaultProfilePath() : profileDir;
1287 }
1288 
loadProfile(const QString & profilePath)1289 DkBatchConfig DkBatchProfile::loadProfile(const QString & profilePath) {
1290 
1291 	QFileInfo fi(profilePath);
1292 	if (!fi.exists() || !fi.isFile()) {
1293 		qInfo() << "cannot read profile from:" << profilePath;
1294 		return DkBatchConfig();
1295 	}
1296 
1297 	QSettings s(profilePath, QSettings::IniFormat);
1298 	DkBatchConfig bc;
1299 	bc.loadSettings(s);
1300 
1301 	return bc;
1302 }
1303 
saveProfile(const QString & profilePath,const DkBatchConfig & batchConfig)1304 bool DkBatchProfile::saveProfile(const QString & profilePath, const DkBatchConfig & batchConfig) {
1305 
1306 	QSettings s(profilePath, QSettings::IniFormat);
1307 	s.clear();
1308 	batchConfig.saveSettings(s);
1309 
1310 	return true;
1311 }
1312 
defaultProfilePath()1313 QString DkBatchProfile::defaultProfilePath() {
1314 
1315 	return DkUtils::getAppDataPath() + QDir::separator() + "Profiles";
1316 }
1317 
profileNameToPath(const QString & profileName)1318 QString DkBatchProfile::profileNameToPath(const QString & profileName) {
1319 	return defaultProfilePath() + QDir::separator() + profileName + "." + ext;
1320 }
1321 
profileNames()1322 QStringList DkBatchProfile::profileNames() {
1323 
1324 	if (mProfilePaths.empty())
1325 		mProfilePaths = index(mProfileDir);
1326 
1327 	QStringList userNames;
1328 	for (const QString& p : mProfilePaths)
1329 		userNames << makeUserFriendly(p);
1330 
1331 	return userNames;
1332 }
1333 
index(const QString & profileDir) const1334 QStringList DkBatchProfile::index(const QString & profileDir) const {
1335 
1336 	QStringList exts;
1337 	exts << "*." + ext;
1338 
1339 	QDir pd(profileDir);
1340 	QStringList profiles = pd.entryList(exts, QDir::Files, QDir::Name);
1341 
1342 	qDebug() << "I have found these profiles: " << profiles;
1343 	if (profiles.empty())
1344 		qInfo() << "no profiles found in" << profileDir;
1345 
1346 	return profiles;
1347 }
1348 
makeUserFriendly(const QString & profilePath)1349 QString DkBatchProfile::makeUserFriendly(const QString & profilePath) {
1350 
1351 	QString pName = QFileInfo(profilePath).baseName();
1352 	return pName;
1353 }
1354 
extension()1355 QString DkBatchProfile::extension() {
1356 	return ext;
1357 }
1358 
1359 }