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

KDE's Doxygen guidelines are available online.