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

KDE's Doxygen guidelines are available online.