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 "ekos/scheduler/schedulerjob.h"
10 #include "sequencejob.h"
11 #include "Options.h"
12 #include "kspaths.h"
13
14 #include <QString>
15 #include <QStringList>
16
17 #include <cmath>
18
19 namespace Ekos
20 {
21
PlaceholderPath(QString seqFilename)22 PlaceholderPath::PlaceholderPath(QString seqFilename):
23 m_frameTypes(
24 {
25 {FRAME_LIGHT, "Light"},
26 {FRAME_DARK, "Dark"},
27 {FRAME_BIAS, "Bias"},
28 {FRAME_FLAT, "Flat"},
29 {FRAME_NONE, ""},
30 }),
31 m_seqFilename(seqFilename)
32 {
33 }
34
PlaceholderPath()35 PlaceholderPath::PlaceholderPath():
36 PlaceholderPath("")
37 {
38 }
39
~PlaceholderPath()40 PlaceholderPath::~PlaceholderPath()
41 {
42 }
43
processJobInfo(SequenceJob * job,QString targetName)44 void PlaceholderPath::processJobInfo(SequenceJob *job, QString targetName)
45 {
46 job->setTargetName(targetName);
47
48 QString frameType = getFrameType(job->getFrameType());
49 QString rawPrefix, filterType = job->getFilterName();
50 double exposure = job->getExposure();
51 bool filterEnabled = false, expEnabled = false, tsEnabled = false;
52 job->getPrefixSettings(rawPrefix, filterEnabled, expEnabled, tsEnabled);
53
54 // Sanitize name
55 //QString targetName = schedJob->getName();
56 targetName = targetName.replace( QRegularExpression("\\s|/|\\(|\\)|:|\\*|~|\"" ), "_" )
57 // Remove any two or more __
58 .replace( QRegularExpression("_{2,}"), "_")
59 // Remove any _ at the end
60 .replace( QRegularExpression("_$"), "");
61
62 // Because scheduler sets the target name in capture module
63 // it would be the same as the raw prefix
64 if (targetName.isEmpty() == false && rawPrefix.isEmpty())
65 rawPrefix = targetName;
66
67 // Make full prefix
68 QString imagePrefix = rawPrefix;
69
70 if (imagePrefix.isEmpty() == false)
71 imagePrefix += '_';
72
73 imagePrefix += frameType;
74
75 if (filterEnabled && filterType.isEmpty() == false &&
76 (job->getFrameType() == FRAME_LIGHT || job->getFrameType() == FRAME_FLAT || job->getFrameType() == FRAME_NONE))
77 {
78 imagePrefix += '_';
79
80 imagePrefix += filterType;
81 }
82
83 // JM 2021.08.21 For flat frames with specific ADU, the exposure duration is only advisory
84 // and the final exposure time would depend on how many seconds are needed to arrive at the
85 // target ADU. Therefore we should add duration to the signature.
86 if (expEnabled && !(job->getFrameType() == FRAME_FLAT && job->getFlatFieldDuration() == DURATION_ADU))
87 {
88 imagePrefix += '_';
89
90 double fractpart, intpart;
91 fractpart = std::modf(exposure, &intpart);
92 if (fractpart == 0)
93 {
94 imagePrefix += QString::number(exposure, 'd', 0) + QString("_secs");
95 }
96 else if (exposure >= 1e-3)
97 {
98 imagePrefix += QString::number(exposure, 'f', 3) + QString("_secs");
99 }
100 else
101 {
102 imagePrefix += QString::number(exposure, 'f', 6) + QString("_secs");
103 }
104 }
105
106 job->setFullPrefix(imagePrefix);
107
108 // Directory postfix
109 QString directoryPostfix;
110
111 /* FIXME: Refactor directoryPostfix assignment, whose code is duplicated in capture.cpp */
112 if (targetName.isEmpty())
113 directoryPostfix = QLatin1String("/") + frameType;
114 else
115 directoryPostfix = QLatin1String("/") + targetName + QLatin1String("/") + frameType;
116 if ((job->getFrameType() == FRAME_LIGHT || job->getFrameType() == FRAME_FLAT || job->getFrameType() == FRAME_NONE)
117 && filterType.isEmpty() == false)
118 directoryPostfix += QLatin1String("/") + filterType;
119
120 job->setDirectoryPostfix(directoryPostfix);
121 }
122
addJob(SequenceJob * job,QString targetName)123 void PlaceholderPath::addJob(SequenceJob *job, QString targetName)
124 {
125 job->setTargetName(targetName);
126
127 CCDFrameType frameType = job->getFrameType();
128 QString frameTypeStr = CCDFrameTypeNames[frameType];
129 QString imagePrefix;
130 QString rawFilePrefix;
131 bool filterEnabled, exposureEnabled, tsEnabled;
132 job->getPrefixSettings(rawFilePrefix, filterEnabled, exposureEnabled, tsEnabled);
133
134 imagePrefix = rawFilePrefix;
135
136 // JM 2019-11-26: In case there is no raw prefix set
137 // BUT target name is set, we update the prefix to include
138 // the target name, which is usually set by the scheduler.
139 if (imagePrefix.isEmpty() && !targetName.isEmpty())
140 {
141 imagePrefix = targetName;
142 }
143
144 constructPrefix(job, imagePrefix);
145
146 job->setFullPrefix(imagePrefix);
147
148 QString directoryPostfix;
149
150 /* FIXME: Refactor directoryPostfix assignment, whose code is duplicated in scheduler.cpp */
151 if (targetName.isEmpty())
152 directoryPostfix = QLatin1String("/") + frameTypeStr;
153 else
154 directoryPostfix = QLatin1String("/") + targetName + QLatin1String("/") + frameTypeStr;
155 if ((frameType == FRAME_LIGHT || frameType == FRAME_FLAT || frameType == FRAME_NONE)
156 && job->getFilterName().isEmpty() == false)
157 directoryPostfix += QLatin1String("/") + job->getFilterName();
158
159 job->setDirectoryPostfix(directoryPostfix);
160 }
161
constructPrefix(SequenceJob * job,QString & imagePrefix)162 void PlaceholderPath::constructPrefix(SequenceJob *job, QString &imagePrefix)
163 {
164 CCDFrameType frameType = job->getFrameType();
165 QString filter = job->getFilterName();
166 QString rawFilePrefix;
167 bool filterEnabled, exposureEnabled, tsEnabled;
168 job->getPrefixSettings(rawFilePrefix, filterEnabled, exposureEnabled, tsEnabled);
169 double exposure = job->getExposure();
170
171 if (imagePrefix.isEmpty() == false)
172 imagePrefix += '_';
173
174 imagePrefix += CCDFrameTypeNames[frameType];
175
176 /*if (fileFilterS->isChecked() && captureFilterS->currentText().isEmpty() == false &&
177 captureTypeS->currentText().compare("Bias", Qt::CaseInsensitive) &&
178 captureTypeS->currentText().compare("Dark", Qt::CaseInsensitive))*/
179 if (filterEnabled && filter.isEmpty() == false &&
180 (frameType == FRAME_LIGHT || frameType == FRAME_FLAT || frameType == FRAME_NONE))
181 {
182 imagePrefix += '_';
183 imagePrefix += filter;
184 }
185 if (exposureEnabled)
186 {
187 //if (imagePrefix.isEmpty() == false || frameTypeCheck->isChecked())
188 imagePrefix += '_';
189
190 double exposureValue = job->getExposure();
191
192 // Don't use the locale for exposure value in the capture file name, so that we get a "." as decimal separator
193 if (exposureValue == static_cast<int>(exposureValue))
194 // Whole number
195 imagePrefix += QString::number(exposure, 'd', 0) + QString("_secs");
196 else
197 {
198 // Decimal
199 if (exposure >= 0.001)
200 imagePrefix += QString::number(exposure, 'f', 3) + QString("_secs");
201 else
202 imagePrefix += QString::number(exposure, 'f', 6) + QString("_secs");
203 }
204 }
205 if (tsEnabled)
206 {
207 imagePrefix += SequenceJob::ISOMarker;
208 }
209 }
210
generateFilenameOld(const QString & format,bool batch_mode,QString * filename,QString fitsDir,QString seqPrefix,int nextSequenceID)211 void PlaceholderPath::generateFilenameOld(
212 const QString &format, bool batch_mode, QString *filename,
213 QString fitsDir, QString seqPrefix, int nextSequenceID
214 )
215 {
216 QString currentDir;
217 if (batch_mode)
218 currentDir = fitsDir.isEmpty() ? Options::fitsDir() : fitsDir;
219 else
220 currentDir = KSPaths::writableLocation(QStandardPaths::TempLocation) + "/kstars";
221
222 /*
223 if (QDir(currentDir).exists() == false)
224 QDir().mkpath(currentDir);
225 */
226
227 if (currentDir.endsWith('/') == false)
228 currentDir.append('/');
229
230 // IS8601 contains colons but they are illegal under Windows OS, so replacing them with '-'
231 // The timestamp is no longer ISO8601 but it should solve interoperality issues
232 // between different OS hosts
233 QString ts = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
234
235 if (seqPrefix.contains("_ISO8601"))
236 {
237 QString finalPrefix = seqPrefix;
238 finalPrefix.replace("ISO8601", ts);
239 *filename = currentDir + finalPrefix +
240 QString("_%1%2").arg(QString().asprintf("%03d", nextSequenceID), format);
241 }
242 else
243 *filename = currentDir + seqPrefix + (seqPrefix.isEmpty() ? "" : "_") +
244 QString("%1%2").arg(QString().asprintf("%03d", nextSequenceID), format);
245 }
246
generateFilename(QString format,SequenceJob & job,QString targetName,bool batch_mode,int nextSequenceID,const QString & extension,QString * filename) const247 void PlaceholderPath::generateFilename(
248 QString format, SequenceJob &job, QString targetName, bool batch_mode, int nextSequenceID, const QString &extension,
249 QString *filename) const
250 {
251 QString rawFilePrefix;
252 bool filterEnabled, exposureEnabled, tsEnabled;
253 job.getPrefixSettings(rawFilePrefix, filterEnabled, exposureEnabled, tsEnabled);
254
255 generateFilename(format, rawFilePrefix, filterEnabled, exposureEnabled,
256 tsEnabled, job.getFilterName(), job.getFrameType(), job.getExposure(),
257 targetName, batch_mode, nextSequenceID, extension, filename);
258 }
259
generateFilename(QString format,bool tsEnabled,bool batch_mode,int nextSequenceID,const QString & extension,QString * filename) const260 void PlaceholderPath::generateFilename(QString format, bool tsEnabled, bool batch_mode,
261 int nextSequenceID, const QString &extension, QString *filename) const
262 {
263 generateFilename(format, m_RawPrefix, m_filterPrefixEnabled, m_expPrefixEnabled,
264 tsEnabled, m_filter, m_frameType, m_exposure, m_targetName, batch_mode,
265 nextSequenceID, extension, filename);
266 }
267
generateFilename(QString format,QString rawFilePrefix,bool filterEnabled,bool exposureEnabled,bool tsEnabled,QString filter,CCDFrameType frameType,double exposure,QString targetName,bool batch_mode,int nextSequenceID,const QString & extension,QString * filename) const268 void PlaceholderPath::generateFilename(
269 QString format, QString rawFilePrefix, bool filterEnabled, bool exposureEnabled,
270 bool tsEnabled, QString filter, CCDFrameType frameType, double exposure, QString targetName,
271 bool batch_mode, int nextSequenceID, const QString &extension, QString *filename) const
272 {
273 targetName = targetName.replace( QRegularExpression("\\s|/|\\(|\\)|:|\\*|~|\"" ), "_" )
274 // Remove any two or more __
275 .replace( QRegularExpression("_{2,}"), "_")
276 // Remove any _ at the end
277 .replace( QRegularExpression("_$"), "");
278 int i = 0;
279
280 QString currentDir;
281 if (batch_mode)
282 {
283 currentDir = m_seqFilename.path().isEmpty() ? Options::fitsDir() : currentDir;
284 }
285 else
286 {
287 currentDir = KSPaths::writableLocation(QStandardPaths::TempLocation) + "/kstars";
288 }
289
290 if (currentDir.endsWith('/') == true)
291 currentDir.chop(1);
292
293 if (!currentDir.isEmpty())
294 format = currentDir + "/" + format.section("/", -1);
295
296 QRegularExpressionMatch match;
297 QRegularExpression re("(?<replace>\\%(?<name>[f,D,T,e,F,t,d,p,s])(?<level>\\d+)?)(?<sep>[_/])?");
298 while ((i = format.indexOf(re, i, &match)) != -1)
299 {
300 QString replacement = "";
301 if (match.captured("name") == "f")
302 {
303 replacement = m_seqFilename.baseName();
304 }
305 else if (match.captured("name") == "D")
306 {
307 if (tsEnabled)
308 {
309 replacement = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss");
310 }
311 }
312 else if (match.captured("name") == "T")
313 {
314 replacement = getFrameType(frameType);
315 }
316 else if (match.captured("name") == "e")
317 {
318 if (exposureEnabled)
319 {
320 double fractpart, intpart;
321 fractpart = std::modf(exposure, &intpart);
322 if (fractpart == 0)
323 {
324 replacement = QString::number(exposure, 'd', 0) + QString("_secs");
325 }
326 else if (exposure >= 1e-3)
327 {
328 replacement = QString::number(exposure, 'f', 3) + QString("_secs");
329 }
330 else
331 {
332 replacement = QString::number(exposure, 'f', 6) + QString("_secs");
333 }
334 }
335 }
336 else if (match.captured("name") == "F")
337 {
338 if (format.indexOf("/", match.capturedStart()) == -1)
339 {
340 // in the basename part of the path
341 if (filterEnabled && filter.isEmpty() == false
342 && (frameType == FRAME_LIGHT
343 || frameType == FRAME_FLAT
344 || frameType == FRAME_NONE))
345 {
346 replacement = filter;
347 }
348 }
349 else
350 {
351 // in the directory part of the path
352 if (filter.isEmpty() == false
353 && (frameType == FRAME_LIGHT
354 || frameType == FRAME_FLAT
355 || frameType == FRAME_NONE))
356 {
357 replacement = filter;
358 }
359 }
360 }
361 else if (match.captured("name") == "t")
362 {
363 if (format.indexOf("/", match.capturedStart()) != -1)
364 {
365 // in the directory part of the path
366 replacement = targetName;
367 }
368 else
369 {
370 // in the basename part of the path
371 replacement = rawFilePrefix;
372 if (replacement.isEmpty() && !targetName.isEmpty())
373 {
374 replacement = targetName;
375 }
376 }
377 }
378 else if (match.captured("name") == "d" || match.captured("name") == "p")
379 {
380 int level = 0;
381 if (!match.captured("level").isEmpty())
382 {
383 level = match.captured("level").toInt() - 1;
384 }
385 QFileInfo dir = m_seqFilename;
386 for (int j = 0; j < level; ++j)
387 {
388 dir = QFileInfo(dir.dir().path());
389 }
390 if (match.captured("name") == "d")
391 {
392 replacement = dir.dir().dirName();
393 }
394 else if (match.captured("name") == "p")
395 {
396 replacement = dir.path();
397 }
398 }
399 else if (match.captured("name") == "s")
400 {
401 int level = 1;
402 if (!match.captured("level").isEmpty())
403 {
404 level = match.captured("level").toInt();
405 }
406 replacement = QString("%1").arg(nextSequenceID, level, 10, QChar('0'));
407 }
408 else
409 {
410 qWarning() << "Unknown replacement string: " << match.captured("replace");
411 }
412 if (replacement.isEmpty())
413 {
414 format = format.replace(match.capturedStart(), match.capturedLength(), replacement);
415 }
416 else
417 {
418 format = format.replace(match.capturedStart("replace"), match.capturedLength("replace"), replacement);
419 }
420 i += replacement.length();
421 }
422 *filename = format + extension;
423 }
424
setGenerateFilenameSettings(const SequenceJob & job)425 void PlaceholderPath::setGenerateFilenameSettings(const SequenceJob &job)
426 {
427 m_RawPrefix = job.property("rawPrefix").toString();
428 m_filterPrefixEnabled = job.isFilterPrefixEnabled();
429 m_expPrefixEnabled = job.isExposurePrefixEnabled();
430 m_filter = job.getFilterName();
431 m_frameType = job.getFrameType();
432 m_exposure = job.getExposure();
433 m_targetName = job.getTargetName();
434 }
435
remainingPlaceholders(QString filename)436 QStringList PlaceholderPath::remainingPlaceholders(QString filename)
437 {
438 QList<QString> placeholders = {};
439 QRegularExpressionMatch match;
440 QRegularExpression re("(?<replace>\\%(?<name>[a-z])(?<level>\\d+)?)(?<sep>[_/])?");
441 int i = 0;
442 while ((i = filename.indexOf(re, i, &match)) != -1)
443 {
444 if (match.hasMatch())
445 {
446 placeholders.push_back(match.captured("replace"));
447 }
448 i += match.capturedLength("replace");
449 }
450 return placeholders;
451 }
452
453 }
454
455