Kstars

placeholderpath.cpp
1/*
2 SPDX-FileCopyrightText: 2021 Kwon-Young Choi <kwon-young.choi@hotmail.fr>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "placeholderpath.h"
8
9#include "sequencejob.h"
10#include "kspaths.h"
11
12#include <QString>
13#include <QStringList>
14
15#include <cmath>
16#include <algorithm>
17#include <ekos_capture_debug.h>
18
19namespace Ekos
20{
21
22QMap<CCDFrameType, QString> PlaceholderPath::m_frameTypes =
23{
24 {FRAME_LIGHT, "Light"},
25 {FRAME_DARK, "Dark"},
26 {FRAME_BIAS, "Bias"},
27 {FRAME_FLAT, "Flat"},
28 {FRAME_NONE, ""},
29};
30
31PlaceholderPath::PlaceholderPath(const QString &seqFilename)
32 : m_seqFilename(seqFilename)
33{
34}
35
36PlaceholderPath::PlaceholderPath():
37 PlaceholderPath(QString())
38{
39}
40
41PlaceholderPath::~PlaceholderPath()
42{
43}
44
45QString PlaceholderPath::defaultFormat(bool useFilter, bool useExposure, bool useTimestamp)
46{
48 if (useFilter)
49 tempFormat.append("%F" + QDir::separator());
50 tempFormat.append("%t_%T_");
51 if (useFilter)
52 tempFormat.append("%F_");
53 if (useExposure)
54 tempFormat.append("%e_");
55 if (useTimestamp)
56 tempFormat.append("%D");
57 return tempFormat;
58}
59
60void PlaceholderPath::processJobInfo(SequenceJob *job)
61{
62 QString jobTargetName = job->getCoreProperty(SequenceJob::SJ_TargetName).toString();
63 auto frameType = getFrameType(job->getFrameType());
64 auto filterType = job->getCoreProperty(SequenceJob::SJ_Filter).toString();
65 auto exposure = job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble();
66 const auto isDarkFlat = job->jobType() == SequenceJob::JOBTYPE_DARKFLAT;
67
68 if (isDarkFlat)
69 frameType = "DarkFlat";
70
71 // Sanitize name
72 QString tempTargetName = KSUtils::sanitize(jobTargetName);
73
74 // Because scheduler sets the target name in capture module
75 // it would be the same as the raw prefix
76 if (tempTargetName.isEmpty() == false && jobTargetName.isEmpty())
78
79 // Make full prefix
81
82 if (imagePrefix.isEmpty() == false)
83 imagePrefix += '_';
84
85 imagePrefix += frameType;
86
87 if (isFilterEnabled(job->getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString()) && filterType.isEmpty() == false &&
88 (job->getFrameType() == FRAME_LIGHT || job->getFrameType() == FRAME_FLAT || job->getFrameType() == FRAME_NONE
89 || isDarkFlat))
90 {
91 imagePrefix += '_';
92
93 imagePrefix += filterType;
94 }
95
96 // JM 2021.08.21 For flat frames with specific ADU, the exposure duration is only advisory
97 // and the final exposure time would depend on how many seconds are needed to arrive at the
98 // target ADU. Therefore we should add duration to the signature.
99 //if (expEnabled && !(job->getFrameType() == FRAME_FLAT && job->getFlatFieldDuration() == DURATION_ADU))
100 if (isExpEnabled(job->getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString()))
101 {
102 imagePrefix += '_';
103
104 double fractpart, intpart;
105 fractpart = std::modf(exposure, &intpart);
106 if (fractpart == 0)
107 {
108 imagePrefix += QString::number(exposure, 'd', 0) + QString("_secs");
109 }
110 else if (exposure >= 1e-3)
111 {
112 imagePrefix += QString::number(exposure, 'f', 3) + QString("_secs");
113 }
114 else
115 {
116 imagePrefix += QString::number(exposure, 'f', 6) + QString("_secs");
117 }
118 }
119
120 job->setCoreProperty(SequenceJob::SJ_FullPrefix, imagePrefix);
121
122 QString signature = generateSequenceFilename(*job, true, true, 1, ".fits", "", false, true);
123 job->setCoreProperty(SequenceJob::SJ_Signature, signature);
124}
125
126void PlaceholderPath::updateFullPrefix(SequenceJob *job, const QString &targetName)
127{
128 QString imagePrefix = KSUtils::sanitize(targetName);
129 QString fullPrefix = constructPrefix(job, imagePrefix);
130
131 job->setCoreProperty(SequenceJob::SJ_FullPrefix, fullPrefix);
132}
133
134QString PlaceholderPath::constructPrefix(const SequenceJob *job, const QString &imagePrefix)
135{
136 CCDFrameType frameType = job->getFrameType();
137 auto placeholderFormat = job->getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString();
138 auto filter = job->getCoreProperty(SequenceJob::SJ_Filter).toString();
139
140 double exposure = job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble();
141
143 if (tempImagePrefix.isEmpty() == false)
144 tempImagePrefix += '_';
145
146 const auto isDarkFlat = job->jobType() == SequenceJob::JOBTYPE_DARKFLAT;
147
148 tempImagePrefix += isDarkFlat ? "DarkFlat" : CCDFrameTypeNames[frameType];
149
150 if (isFilterEnabled(placeholderFormat) && filter.isEmpty() == false &&
151 (frameType == FRAME_LIGHT ||
152 frameType == FRAME_FLAT ||
153 frameType == FRAME_NONE ||
154 isDarkFlat))
155 {
156 tempImagePrefix += '_';
158 }
159 if (isExpEnabled(placeholderFormat))
160 {
161 tempImagePrefix += '_';
162
163 double exposureValue = job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble();
164
165 // Don't use the locale for exposure value in the capture file name, so that we get a "." as decimal separator
166 if (exposureValue == static_cast<int>(exposureValue))
167 // Whole number
168 tempImagePrefix += QString::number(exposure, 'd', 0) + QString("_secs");
169 else
170 {
171 // Decimal
172 if (exposure >= 0.001)
173 tempImagePrefix += QString::number(exposure, 'f', 3) + QString("_secs");
174 else
175 tempImagePrefix += QString::number(exposure, 'f', 6) + QString("_secs");
176 }
177 }
178 if (isTsEnabled(placeholderFormat))
179 {
180 tempImagePrefix += SequenceJob::ISOMarker;
181 }
182
183 return tempImagePrefix;
184}
185
186QString PlaceholderPath::generateSequenceFilename(const SequenceJob &job,
187 bool local,
188 const bool batch_mode,
189 const int nextSequenceID,
190 const QString &extension,
191 const QString &filename,
192 const bool glob,
193 const bool gettingSignature)
194{
196 setGenerateFilenameSettings(job, pathPropertyMap, local, gettingSignature);
197
198 return generateFilenameInternal(pathPropertyMap, local, batch_mode, nextSequenceID, extension, filename, glob,
200}
201
202QString PlaceholderPath::generateOutputFilename(const bool local, const bool batch_mode, const int nextSequenceID,
203 const QString &extension,
204 const QString &filename, const bool glob, const bool gettingSignature) const
205{
206 return generateFilenameInternal(m_PathPropertyMap, local, batch_mode, nextSequenceID, extension, filename, glob,
208}
209
210QString PlaceholderPath::generateReplacement(const QMap<PathProperty, QVariant> &pathPropertyMap, PathProperty property,
211 bool usePattern) const
212{
213 if (usePattern)
214 {
215 switch (propertyType(property))
216 {
217 case PP_TYPE_UINT:
218 case PP_TYPE_DOUBLE:
219 return "-?\\d+";
220 case PP_TYPE_BOOL:
221 return "(true|false)";
222 case PP_TYPE_POINT:
223 return "\\d+x\\d+";
224 default:
225 if (property == PP_PIERSIDE)
226 return "(East|West|Unknown)";
227 else
228 return "\\w+";
229 }
230 }
231 else if (pathPropertyMap[property].isValid())
232 {
233 switch (propertyType(property))
234 {
235 case PP_TYPE_DOUBLE:
236 return QString::number(pathPropertyMap[property].toDouble(), 'd', 0);
237 case PP_TYPE_UINT:
238 return QString::number(pathPropertyMap[property].toUInt());
239 case PP_TYPE_POINT:
240 return QString("%1x%2").arg(pathPropertyMap[PP_BIN].toPoint().x()).arg(pathPropertyMap[PP_BIN].toPoint().y());
241 case PP_TYPE_STRING:
242 if (property == PP_PIERSIDE)
243 {
244 switch (static_cast<ISD::Mount::PierSide>(pathPropertyMap[property].toInt()))
245 {
246 case ISD::Mount::PIER_EAST:
247 return "East";
248 case ISD::Mount::PIER_WEST:
249 return "West";
250 default:
251 return "Unknown";
252 }
253 }
254 else
255 return pathPropertyMap[property].toString();
256 default:
257 return pathPropertyMap[property].toString();
258 }
259 }
260 else
261 {
262 switch (propertyType(property))
263 {
264 case PP_TYPE_DOUBLE:
265 case PP_TYPE_UINT:
266 return "-1";
267 case PP_TYPE_POINT:
268 return "0x0";
269 case PP_TYPE_BOOL:
270 return "false";
271 default:
272 return "Unknown";
273 }
274 }
275}
276
277QString PlaceholderPath::generateFilenameInternal(const QMap<PathProperty, QVariant> &pathPropertyMap,
278 const bool local,
279 const bool batch_mode,
280 const int nextSequenceID,
281 const QString &extension,
282 const QString &filename,
283 const bool glob,
284 const bool gettingSignature) const
285{
286 QString targetNameSanitized = KSUtils::sanitize(pathPropertyMap[PP_TARGETNAME].toString());
287 int i = 0;
288
289 const QString format = pathPropertyMap[PP_FORMAT].toString();
290 const bool isDarkFlat = pathPropertyMap[PP_DARKFLAT].isValid() && pathPropertyMap[PP_DARKFLAT].toBool();
291 const CCDFrameType frameType = static_cast<CCDFrameType>(pathPropertyMap[PP_FRAMETYPE].toUInt());
292 QString tempFilename = filename;
293 QString currentDir;
294 if (batch_mode)
295 currentDir = pathPropertyMap[PP_DIRECTORY].toString();
296 else
297 currentDir = QDir::toNativeSeparators(KSPaths::writableLocation(QStandardPaths::TempLocation) + "/kstars/");
298
299 // ensure, that there is exactly one separator is between non empty directory and format
300 if(!currentDir.isEmpty() && !format.isEmpty())
301 {
302 if(!currentDir.endsWith(QDir::separator()) && !format.startsWith(QDir::separator()))
303 currentDir.append(QDir::separator());
304 if(currentDir.endsWith(QDir::separator()) && format.startsWith(QDir::separator()))
305 currentDir = currentDir.left(currentDir.length() - 1);
306 }
307
308 QString tempFormat = currentDir + format + "_%s" + QString::number(pathPropertyMap[PP_SUFFIX].toUInt());
309
310#if defined(Q_OS_WIN)
311 tempFormat.replace("\\", "/");
312#endif
315#if defined(Q_OS_WIN)
316 re("(?<replace>\\%(?<name>(filename|f|Datetime|D|Type|T|exposure|e|exp|E|Filter|F|target|t|temperature|C|bin|B|gain|G|offset|O|iso|I|pierside|P|sequence|s))(?<level>\\d+)?)(?<sep>[_\\\\])?");
317#else
318 re("(?<replace>\\%(?<name>(filename|f|Datetime|D|Type|T|exposure|e|exp|E|Filter|F|target|t|temperature|C|bin|B|gain|G|offset|O|iso|I|pierside|P|sequence|s))(?<level>\\d+)?)(?<sep>[_/])?");
319#endif
320
321 while ((i = tempFormat.indexOf(re, i, &match)) != -1)
322 {
323 QString replacement = "";
324 if ((match.captured("name") == "filename") || (match.captured("name") == "f"))
325 replacement = m_seqFilename.baseName();
326 else if ((match.captured("name") == "Datetime") || (match.captured("name") == "D"))
327 {
328 if (glob || gettingSignature)
329 {
330 if (local)
331 replacement = "\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d-\\d\\d-\\d\\d";
332 else
333 replacement = "ISO8601";
334
335 }
336 else
337 replacement = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
338 }
339 else if ((match.captured("name") == "Type") || (match.captured("name") == "T"))
340 {
341 if (isDarkFlat)
342 replacement = "DarkFlat";
343 else
344 replacement = getFrameType(frameType);
345 }
346 else if ((match.captured("name") == "exposure") || (match.captured("name") == "e") ||
347 (match.captured("name") == "exp") || (match.captured("name") == "E"))
348 {
349 double fractpart, intpart;
350 double exposure = pathPropertyMap[PP_EXPOSURE].toDouble();
351 fractpart = std::modf(exposure, &intpart);
352 if (fractpart == 0)
353 replacement = QString::number(exposure, 'd', 0);
354 else if (exposure >= 1e-3)
355 replacement = QString::number(exposure, 'f', 3);
356 else
357 replacement = QString::number(exposure, 'f', 6);
358 // append _secs for placeholders "exposure" and "e"
359 if ((match.captured("name") == "exposure") || (match.captured("name") == "e"))
360 replacement += QString("_secs");
361 }
362 else if ((match.captured("name") == "Filter") || (match.captured("name") == "F"))
363 {
364 QString filter = pathPropertyMap[PP_FILTER].toString();
365 if (filter.isEmpty() == false
366 && (frameType == FRAME_LIGHT
367 || frameType == FRAME_FLAT
368 || frameType == FRAME_NONE
369 || isDarkFlat))
370 {
371 replacement = filter;
372 }
373 }
374 else if ((match.captured("name") == "target") || (match.captured("name") == "t"))
375 {
376 replacement = targetNameSanitized;
377 }
378 else if (((match.captured("name") == "temperature") || (match.captured("name") == "C")))
379 {
380 replacement = generateReplacement(pathPropertyMap, PP_TEMPERATURE,
381 (glob || gettingSignature) && pathPropertyMap[PP_TEMPERATURE].isValid() == false);
382 }
383 else if (((match.captured("name") == "bin") || (match.captured("name") == "B")))
384 {
385 replacement = generateReplacement(pathPropertyMap, PP_BIN,
386 (glob || gettingSignature) && pathPropertyMap[PP_BIN].isValid() == false);
387 }
388 else if (((match.captured("name") == "gain") || (match.captured("name") == "G")))
389 {
390 replacement = generateReplacement(pathPropertyMap, PP_GAIN,
391 (glob || gettingSignature) && pathPropertyMap[PP_GAIN].isValid() == false);
392 }
393 else if (((match.captured("name") == "offset") || (match.captured("name") == "O")))
394 {
395 replacement = generateReplacement(pathPropertyMap, PP_OFFSET,
396 (glob || gettingSignature) && pathPropertyMap[PP_OFFSET].isValid() == false);
397 }
398 else if (((match.captured("name") == "iso") || (match.captured("name") == "I"))
399 && pathPropertyMap[PP_ISO].isValid())
400 {
401 replacement = generateReplacement(pathPropertyMap, PP_ISO,
402 (glob || gettingSignature) && pathPropertyMap[PP_ISO].isValid() == false);
403 }
404 else if (((match.captured("name") == "pierside") || (match.captured("name") == "P")))
405 {
406 replacement = generateReplacement(pathPropertyMap, PP_PIERSIDE, glob || gettingSignature);
407 }
408 // Disable for now %d & %p tags to simplfy
409 // else if ((match.captured("name") == "directory") || (match.captured("name") == "d") ||
410 // (match.captured("name") == "path") || (match.captured("name") == "p"))
411 // {
412 // int level = 0;
413 // if (!match.captured("level").isEmpty())
414 // level = match.captured("level").toInt() - 1;
415 // QFileInfo dir = m_seqFilename;
416 // for (int j = 0; j < level; ++j)
417 // dir = QFileInfo(dir.dir().path());
418 // if (match.captured("name") == "directory" || match.captured("name") == "d")
419 // replacement = dir.dir().dirName();
420 // else if (match.captured("name") == "path" || match.captured("name") == "p")
421 // replacement = dir.path();
422 // }
423 else if ((match.captured("name") == "sequence") || (match.captured("name") == "s"))
424 {
425 if (glob)
426 replacement = "(?<id>\\d+)";
427 else if (local)
428 {
429 int level = 0;
430 if (!match.captured("level").isEmpty())
431 level = match.captured("level").toInt();
432 replacement = QString("%1").arg(nextSequenceID, level, 10, QChar('0'));
433 }
434 else
435 {
436 // fix string for remote, ID is set remotely
437 replacement = "XXX";
438 }
439 }
440 else
441 qWarning() << "Unknown replacement string: " << match.captured("replace");
442
443 if (replacement.isEmpty())
444 tempFormat = tempFormat.replace(match.capturedStart(), match.capturedLength(), replacement);
445 else
446 tempFormat = tempFormat.replace(match.capturedStart("replace"), match.capturedLength("replace"), replacement);
447 i += replacement.length();
448 }
449
450 if (!gettingSignature)
451 tempFilename = tempFormat + extension;
452 else
453 tempFilename = tempFormat.left(tempFormat.lastIndexOf("_"));
454
455 return tempFilename;
456}
457
458void PlaceholderPath::setGenerateFilenameSettings(const SequenceJob &job, QMap<PathProperty, QVariant> &pathPropertyMap,
459 const bool local, const bool gettingSignature)
460{
461 setPathProperty(pathPropertyMap, PP_TARGETNAME, job.getCoreProperty(SequenceJob::SJ_TargetName));
462 setPathProperty(pathPropertyMap, PP_FRAMETYPE, QVariant(job.getFrameType()));
463 setPathProperty(pathPropertyMap, PP_FILTER, job.getCoreProperty(SequenceJob::SJ_Filter));
464 setPathProperty(pathPropertyMap, PP_EXPOSURE, job.getCoreProperty(SequenceJob::SJ_Exposure));
465 setPathProperty(pathPropertyMap, PP_DIRECTORY,
466 job.getCoreProperty(local ? SequenceJob::SJ_LocalDirectory : SequenceJob::SJ_RemoteDirectory));
467 setPathProperty(pathPropertyMap, PP_FORMAT, job.getCoreProperty(SequenceJob::SJ_PlaceholderFormat));
468 setPathProperty(pathPropertyMap, PP_SUFFIX, job.getCoreProperty(SequenceJob::SJ_PlaceholderSuffix));
469 setPathProperty(pathPropertyMap, PP_DARKFLAT, job.jobType() == SequenceJob::JOBTYPE_DARKFLAT);
470 setPathProperty(pathPropertyMap, PP_BIN, job.getCoreProperty(SequenceJob::SJ_Binning));
471 setPathProperty(pathPropertyMap, PP_PIERSIDE, QVariant(job.getPierSide()));
472 setPathProperty(pathPropertyMap, PP_ISO, job.getCoreProperty(SequenceJob::SJ_ISO));
473
474 // handle optional parameters
475 if (job.getCoreProperty(SequenceJob::SJ_EnforceTemperature).toBool())
476 setPathProperty(pathPropertyMap, PP_TEMPERATURE, QVariant(job.getTargetTemperature()));
477 else if (job.currentTemperature() != Ekos::INVALID_VALUE && !gettingSignature)
478 setPathProperty(pathPropertyMap, PP_TEMPERATURE, QVariant(job.currentTemperature()));
479 else
480 pathPropertyMap.remove(PP_TEMPERATURE);
481
482 if (job.getCoreProperty(SequenceJob::SequenceJob::SJ_Gain).toInt() >= 0)
483 setPathProperty(pathPropertyMap, PP_GAIN, job.getCoreProperty(SequenceJob::SJ_Gain));
484 else if (job.currentGain() >= 0 && !gettingSignature)
485 setPathProperty(pathPropertyMap, PP_GAIN, job.currentGain());
486 else
487 pathPropertyMap.remove(PP_GAIN);
488
489 if (job.getCoreProperty(SequenceJob::SequenceJob::SJ_Offset).toInt() >= 0)
490 setPathProperty(pathPropertyMap, PP_OFFSET, job.getCoreProperty(SequenceJob::SJ_Offset));
491 else if (job.currentOffset() >= 0 && !gettingSignature)
492 setPathProperty(pathPropertyMap, PP_OFFSET, job.currentOffset());
493 else
494 pathPropertyMap.remove(PP_OFFSET);
495}
496
497QStringList PlaceholderPath::remainingPlaceholders(const QString &filename)
498{
501#if defined(Q_OS_WIN)
502 QRegularExpression re("(?<replace>\\%(?<name>[a-zA-Z])(?<level>\\d+)?)(?<sep>[_\\\\])+");
503#else
504 QRegularExpression re("(?<replace>%(?<name>[a-zA-Z])(?<level>\\d+)?)(?<sep>[_/])+");
505#endif
506 int i = 0;
507 while ((i = filename.indexOf(re, i, &match)) != -1)
508 {
509 if (match.hasMatch())
510 placeholders.push_back(match.captured("replace"));
511 i += match.capturedLength("replace");
512 }
513 return placeholders;
514}
515
516QList<int> PlaceholderPath::getCompletedFileIds(const SequenceJob &job)
517{
518 QString path = generateSequenceFilename(job, true, true, 0, ".*", "", true);
519 auto sanitizedPath = path;
520
521 // This is needed for Windows as the regular expression confuses path search
522 QString idRE = "(?<id>\\d+).*";
523 QString datetimeRE = "\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d-\\d\\d-\\d\\d";
524 sanitizedPath.replace(idRE, "{IDRE}");
525 sanitizedPath.replace(datetimeRE, "{DATETIMERE}");
526
527 // Now we can get a proper directory
529 QDir dir(path_info.dir());
530
531 // e.g. Light_R_(?<id>\\d+).*
532 auto filename = path_info.fileName();
533
534 // Next replace back the problematic regular expressions
535 filename.replace("{IDRE}", idRE);
536 filename.replace("{DATETIMERE}", datetimeRE);
537
540 QRegularExpression re("^" + filename + "$");
541 QList<int> ids = {};
542 for (auto &name : matchingFiles)
543 {
544 match = re.match(name);
545 if (match.hasMatch())
546 ids << match.captured("id").toInt();
547 }
548
549 return ids;
550}
551
552int PlaceholderPath::getCompletedFiles(const SequenceJob &job)
553{
554 return getCompletedFileIds(job).length();
555}
556
557int PlaceholderPath::getCompletedFiles(const QString &path)
558{
559 int seqFileCount = 0;
560#ifdef Q_OS_WIN
561 // Splitting directory and baseName in QFileInfo does not distinguish regular expression backslash from directory separator on Windows.
562 // So do not use QFileInfo for the code that separates directory and basename for Windows.
563 // Conditions for calling this function:
564 // - Directory separators must always be "/".
565 // - Directory separators must not contain backslash.
568 int index = path.lastIndexOf('/');
569 if (0 <= index)
570 {
571 // found '/'. path has both dir and filename
572 sig_dir = path.left(index);
573 sig_file = path.mid(index + 1);
574 } // not found '/'. path has only filename
575 else
576 {
577 sig_file = path;
578 }
579 // remove extension
580 index = sig_file.lastIndexOf('.');
581 if (0 <= index)
582 {
583 // found '.', then remove extension
584 sig_file = sig_file.left(index);
585 }
586 qCDebug(KSTARS_EKOS_CAPTURE) << "Scheduler::PlaceholderPath path:" << path << " sig_dir:" << sig_dir << " sig_file:" <<
587 sig_file;
588#else
589 QFileInfo const path_info(path);
590 QString const sig_dir(path_info.dir().path());
591 QString const sig_file(path_info.completeBaseName());
592#endif
594
596
597 /* FIXME: this counts all files with prefix in the storage location, not just captures. DSS analysis files are counted in, for instance. */
598 while (it.hasNext())
599 {
600 QString const fileName = QFileInfo(it.next()).completeBaseName();
601
602 QRegularExpressionMatch match = re.match(fileName);
603 if (match.hasMatch())
604 seqFileCount++;
605 }
606
607 return seqFileCount;
608}
609
610int PlaceholderPath::checkSeqBoundary(const SequenceJob &job)
611{
612 auto ids = getCompletedFileIds(job);
613 if (ids.length() > 0)
614 return *std::max_element(ids.begin(), ids.end()) + 1;
615 else
616 return 1;
617}
618
619PlaceholderPath::PathPropertyType PlaceholderPath::propertyType(PathProperty property)
620{
621 switch (property)
622 {
623 case PP_FORMAT:
624 case PP_DIRECTORY:
625 case PP_TARGETNAME:
626 case PP_FILTER:
627 case PP_PIERSIDE:
628 return PP_TYPE_STRING;
629
630 case PP_DARKFLAT:
631 return PP_TYPE_BOOL;
632
633 case PP_SUFFIX:
634 case PP_FRAMETYPE:
635 case PP_ISO:
636 return PP_TYPE_UINT;
637
638 case PP_EXPOSURE:
639 case PP_GAIN:
640 case PP_OFFSET:
641 case PP_TEMPERATURE:
642 return PP_TYPE_DOUBLE;
643
644 case PP_BIN:
645 return PP_TYPE_POINT;
646
647 default:
648 return PP_TYPE_NONE;
649 }
650}
651
652// An "emergency" method--the code should not be overwriting files,
653// however, if we've detected an overwrite, we generate a new filename
654// by looking for numbers at its end (before its extension) and incrementing
655// that number, checking to make sure the new filename with the incremented number doesn't exist.
656QString PlaceholderPath::repairFilename(const QString &filename)
657{
658 QRegularExpression re("^(.*[^\\d])(\\d+)\\.(\\w+)$");
659
660 auto match = re.match(filename);
661 if (match.hasMatch())
662 {
663 QString prefix = match.captured(1);
664 int number = match.captured(2).toInt();
665 int numberLength = match.captured(2).size();
666 QString extension = match.captured(3);
667 QString candidate = QString("%1%2.%3").arg(prefix).arg(number + 1, numberLength, 10, QLatin1Char('0')).arg(extension);
668 int maxIterations = 2000;
669 while (QFile::exists(candidate))
670 {
671 number = number + 1;
672 candidate = QString("%1%2.%3").arg(prefix).arg(number, numberLength, 10, QLatin1Char('0')).arg(extension);
673 if (--maxIterations <= 0)
674 return filename;
675 }
676 return candidate;
677 }
678 return filename;;
679}
680
681}
682
Sequence Job is a container for the details required to capture a series of images.
char * toString(const EngineQuery &query)
Ekos is an advanced Astrophotography tool for Linux.
Definition align.cpp:78
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
KIOCORE_EXPORT QString number(KIO::filesize_t size)
QString path(const QString &relativePath)
bool isValid(QStringView ifopt)
QStringView level(QStringView ifopt)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QDateTime currentDateTime()
QString toString(QStringView format, QCalendar cal) const const
QChar separator()
QString toNativeSeparators(const QString &pathName)
bool exists() const const
QString completeBaseName() const const
Int toInt() const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:19:02 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.