File indexing completed on 2024-04-28 03:43:17
0001 /* 0002 SPDX-FileCopyrightText: 2021 Kwon-Young Choi <kwon-young.choi@hotmail.fr> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "placeholderpath.h" 0008 0009 #include "sequencejob.h" 0010 #include "kspaths.h" 0011 0012 #include <QString> 0013 #include <QStringList> 0014 0015 #include <cmath> 0016 #include <algorithm> 0017 #include <ekos_capture_debug.h> 0018 0019 namespace Ekos 0020 { 0021 0022 PlaceholderPath::PlaceholderPath(const QString &seqFilename): 0023 m_frameTypes( 0024 { 0025 {FRAME_LIGHT, "Light"}, 0026 {FRAME_DARK, "Dark"}, 0027 {FRAME_BIAS, "Bias"}, 0028 {FRAME_FLAT, "Flat"}, 0029 {FRAME_NONE, ""}, 0030 }), 0031 m_seqFilename(seqFilename) 0032 { 0033 } 0034 0035 PlaceholderPath::PlaceholderPath(): 0036 PlaceholderPath(QString()) 0037 { 0038 } 0039 0040 PlaceholderPath::~PlaceholderPath() 0041 { 0042 } 0043 0044 QString PlaceholderPath::defaultFormat(bool useFilter, bool useExposure, bool useTimestamp) 0045 { 0046 QString tempFormat = QDir::separator() + "%t" + QDir::separator() + "%T" + QDir::separator(); 0047 if (useFilter) 0048 tempFormat.append("%F" + QDir::separator()); 0049 tempFormat.append("%t_%T_"); 0050 if (useFilter) 0051 tempFormat.append("%F_"); 0052 if (useExposure) 0053 tempFormat.append("%e_"); 0054 if (useTimestamp) 0055 tempFormat.append("%D"); 0056 return tempFormat; 0057 } 0058 0059 void PlaceholderPath::processJobInfo(SequenceJob *job) 0060 { 0061 QString jobTargetName = job->getCoreProperty(SequenceJob::SJ_TargetName).toString(); 0062 auto frameType = getFrameType(job->getFrameType()); 0063 auto filterType = job->getCoreProperty(SequenceJob::SJ_Filter).toString(); 0064 auto exposure = job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble(); 0065 const auto isDarkFlat = job->jobType() == SequenceJob::JOBTYPE_DARKFLAT; 0066 0067 if (isDarkFlat) 0068 frameType = "DarkFlat"; 0069 0070 // Sanitize name 0071 QString tempTargetName = KSUtils::sanitize(jobTargetName); 0072 0073 // Because scheduler sets the target name in capture module 0074 // it would be the same as the raw prefix 0075 if (tempTargetName.isEmpty() == false && jobTargetName.isEmpty()) 0076 jobTargetName = tempTargetName; 0077 0078 // Make full prefix 0079 QString imagePrefix = jobTargetName; 0080 0081 if (imagePrefix.isEmpty() == false) 0082 imagePrefix += '_'; 0083 0084 imagePrefix += frameType; 0085 0086 if (isFilterEnabled(job->getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString()) && filterType.isEmpty() == false && 0087 (job->getFrameType() == FRAME_LIGHT || job->getFrameType() == FRAME_FLAT || job->getFrameType() == FRAME_NONE 0088 || isDarkFlat)) 0089 { 0090 imagePrefix += '_'; 0091 0092 imagePrefix += filterType; 0093 } 0094 0095 // JM 2021.08.21 For flat frames with specific ADU, the exposure duration is only advisory 0096 // and the final exposure time would depend on how many seconds are needed to arrive at the 0097 // target ADU. Therefore we should add duration to the signature. 0098 //if (expEnabled && !(job->getFrameType() == FRAME_FLAT && job->getFlatFieldDuration() == DURATION_ADU)) 0099 if (isExpEnabled(job->getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString())) 0100 { 0101 imagePrefix += '_'; 0102 0103 double fractpart, intpart; 0104 fractpart = std::modf(exposure, &intpart); 0105 if (fractpart == 0) 0106 { 0107 imagePrefix += QString::number(exposure, 'd', 0) + QString("_secs"); 0108 } 0109 else if (exposure >= 1e-3) 0110 { 0111 imagePrefix += QString::number(exposure, 'f', 3) + QString("_secs"); 0112 } 0113 else 0114 { 0115 imagePrefix += QString::number(exposure, 'f', 6) + QString("_secs"); 0116 } 0117 } 0118 0119 job->setCoreProperty(SequenceJob::SJ_FullPrefix, imagePrefix); 0120 0121 QString signature = generateSequenceFilename(*job, true, true, 1, ".fits", "", false, true); 0122 job->setCoreProperty(SequenceJob::SJ_Signature, signature); 0123 } 0124 0125 void PlaceholderPath::addJob(SequenceJob *job, const QString &targetName) 0126 { 0127 auto frameType = job->getFrameType(); 0128 auto frameTypeString = getFrameType(job->getFrameType()); 0129 QString imagePrefix = KSUtils::sanitize(targetName); 0130 0131 const auto isDarkFlat = job->jobType() == SequenceJob::JOBTYPE_DARKFLAT; 0132 if (isDarkFlat) 0133 frameTypeString = "DarkFlat"; 0134 0135 QString tempPrefix = constructPrefix(job, imagePrefix); 0136 0137 job->setCoreProperty(SequenceJob::SJ_FullPrefix, tempPrefix); 0138 0139 QString directoryPostfix; 0140 0141 const auto filterName = job->getCoreProperty(SequenceJob::SJ_Filter).toString(); 0142 0143 /* FIXME: Refactor directoryPostfix assignment, whose code is duplicated in scheduler.cpp */ 0144 if (targetName.isEmpty()) 0145 directoryPostfix = QDir::separator() + frameTypeString; 0146 else 0147 directoryPostfix = QDir::separator() + imagePrefix + QDir::separator() + frameTypeString; 0148 0149 0150 if ((frameType == FRAME_LIGHT || frameType == FRAME_FLAT || frameType == FRAME_NONE || isDarkFlat) 0151 && filterName.isEmpty() == false) 0152 directoryPostfix += QDir::separator() + filterName; 0153 0154 job->setCoreProperty(SequenceJob::SJ_RemoteFormatDirectory, directoryPostfix); 0155 job->setCoreProperty(SequenceJob::SJ_RemoteFormatFilename, directoryPostfix); 0156 } 0157 0158 QString PlaceholderPath::constructPrefix(const SequenceJob *job, const QString &imagePrefix) 0159 { 0160 CCDFrameType frameType = job->getFrameType(); 0161 auto placeholderFormat = job->getCoreProperty(SequenceJob::SJ_PlaceholderFormat).toString(); 0162 auto filter = job->getCoreProperty(SequenceJob::SJ_Filter).toString(); 0163 0164 double exposure = job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble(); 0165 0166 QString tempImagePrefix = imagePrefix; 0167 if (tempImagePrefix.isEmpty() == false) 0168 tempImagePrefix += '_'; 0169 0170 const auto isDarkFlat = job->jobType() == SequenceJob::JOBTYPE_DARKFLAT; 0171 0172 tempImagePrefix += isDarkFlat ? "DarkFlat" : CCDFrameTypeNames[frameType]; 0173 0174 if (isFilterEnabled(placeholderFormat) && filter.isEmpty() == false && 0175 (frameType == FRAME_LIGHT || 0176 frameType == FRAME_FLAT || 0177 frameType == FRAME_NONE || 0178 isDarkFlat)) 0179 { 0180 tempImagePrefix += '_'; 0181 tempImagePrefix += filter; 0182 } 0183 if (isExpEnabled(placeholderFormat)) 0184 { 0185 tempImagePrefix += '_'; 0186 0187 double exposureValue = job->getCoreProperty(SequenceJob::SJ_Exposure).toDouble(); 0188 0189 // Don't use the locale for exposure value in the capture file name, so that we get a "." as decimal separator 0190 if (exposureValue == static_cast<int>(exposureValue)) 0191 // Whole number 0192 tempImagePrefix += QString::number(exposure, 'd', 0) + QString("_secs"); 0193 else 0194 { 0195 // Decimal 0196 if (exposure >= 0.001) 0197 tempImagePrefix += QString::number(exposure, 'f', 3) + QString("_secs"); 0198 else 0199 tempImagePrefix += QString::number(exposure, 'f', 6) + QString("_secs"); 0200 } 0201 } 0202 if (isTsEnabled(placeholderFormat)) 0203 { 0204 tempImagePrefix += SequenceJob::ISOMarker; 0205 } 0206 0207 return tempImagePrefix; 0208 } 0209 0210 QString PlaceholderPath::generateSequenceFilename(const SequenceJob &job, 0211 bool local, 0212 const bool batch_mode, 0213 const int nextSequenceID, 0214 const QString &extension, 0215 const QString &filename, 0216 const bool glob, 0217 const bool gettingSignature) 0218 { 0219 QMap<PathProperty, QVariant> pathPropertyMap; 0220 setGenerateFilenameSettings(job, pathPropertyMap, local, gettingSignature); 0221 0222 return generateFilenameInternal(pathPropertyMap, local, batch_mode, nextSequenceID, extension, filename, glob, 0223 gettingSignature); 0224 } 0225 0226 QString PlaceholderPath::generateOutputFilename(const bool local, const bool batch_mode, const int nextSequenceID, 0227 const QString &extension, 0228 const QString &filename, const bool glob, const bool gettingSignature) const 0229 { 0230 return generateFilenameInternal(m_PathPropertyMap, local, batch_mode, nextSequenceID, extension, filename, glob, 0231 gettingSignature); 0232 } 0233 0234 QString PlaceholderPath::generateReplacement(const QMap<PathProperty, QVariant> &pathPropertyMap, PathProperty property, 0235 bool usePattern) const 0236 { 0237 if (usePattern) 0238 { 0239 switch (propertyType(property)) 0240 { 0241 case PP_TYPE_UINT: 0242 case PP_TYPE_DOUBLE: 0243 return "-?\\d+"; 0244 case PP_TYPE_BOOL: 0245 return "(true|false)"; 0246 case PP_TYPE_POINT: 0247 return "\\d+x\\d+"; 0248 default: 0249 if (property == PP_PIERSIDE) 0250 return "(East|West|Unknown)"; 0251 else 0252 return "\\w+"; 0253 } 0254 } 0255 else if (pathPropertyMap[property].isValid()) 0256 { 0257 switch (propertyType(property)) 0258 { 0259 case PP_TYPE_DOUBLE: 0260 return QString::number(pathPropertyMap[property].toDouble(), 'd', 0); 0261 case PP_TYPE_UINT: 0262 return QString::number(pathPropertyMap[property].toUInt()); 0263 case PP_TYPE_POINT: 0264 return QString("%1x%2").arg(pathPropertyMap[PP_BIN].toPoint().x()).arg(pathPropertyMap[PP_BIN].toPoint().y()); 0265 case PP_TYPE_STRING: 0266 if (property == PP_PIERSIDE) 0267 { 0268 switch (static_cast<ISD::Mount::PierSide>(pathPropertyMap[property].toInt())) 0269 { 0270 case ISD::Mount::PIER_EAST: 0271 return "East"; 0272 case ISD::Mount::PIER_WEST: 0273 return "West"; 0274 default: 0275 return "Unknown"; 0276 } 0277 } 0278 else 0279 return pathPropertyMap[property].toString(); 0280 default: 0281 return pathPropertyMap[property].toString(); 0282 } 0283 } 0284 else 0285 { 0286 switch (propertyType(property)) 0287 { 0288 case PP_TYPE_DOUBLE: 0289 case PP_TYPE_UINT: 0290 return "-1"; 0291 case PP_TYPE_POINT: 0292 return "0x0"; 0293 case PP_TYPE_BOOL: 0294 return "false"; 0295 default: 0296 return "Unknown"; 0297 } 0298 } 0299 } 0300 0301 QString PlaceholderPath::generateFilenameInternal(const QMap<PathProperty, QVariant> &pathPropertyMap, 0302 const bool local, 0303 const bool batch_mode, 0304 const int nextSequenceID, 0305 const QString &extension, 0306 const QString &filename, 0307 const bool glob, 0308 const bool gettingSignature) const 0309 { 0310 QString targetNameSanitized = KSUtils::sanitize(pathPropertyMap[PP_TARGETNAME].toString()); 0311 int i = 0; 0312 0313 const QString format = pathPropertyMap[PP_FORMAT].toString(); 0314 const bool isDarkFlat = pathPropertyMap[PP_DARKFLAT].isValid() && pathPropertyMap[PP_DARKFLAT].toBool(); 0315 const CCDFrameType frameType = static_cast<CCDFrameType>(pathPropertyMap[PP_FRAMETYPE].toUInt()); 0316 QString tempFilename = filename; 0317 QString currentDir; 0318 if (batch_mode) 0319 currentDir = pathPropertyMap[PP_DIRECTORY].toString(); 0320 else 0321 currentDir = QDir::toNativeSeparators(KSPaths::writableLocation(QStandardPaths::TempLocation) + "/kstars/"); 0322 0323 // ensure, that there is exactly one separator is between non empty directory and format 0324 if(!currentDir.isEmpty() && !format.isEmpty()) 0325 { 0326 if(!currentDir.endsWith(QDir::separator()) && !format.startsWith(QDir::separator())) 0327 currentDir.append(QDir::separator()); 0328 if(currentDir.endsWith(QDir::separator()) && format.startsWith(QDir::separator())) 0329 currentDir = currentDir.left(currentDir.length() - 1); 0330 } 0331 0332 QString tempFormat = currentDir + format + "_%s" + QString::number(pathPropertyMap[PP_SUFFIX].toUInt()); 0333 0334 #if defined(Q_OS_WIN) 0335 tempFormat.replace("\\", "/"); 0336 #endif 0337 QRegularExpressionMatch match; 0338 QRegularExpression 0339 #if defined(Q_OS_WIN) 0340 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>[_\\\\])?"); 0341 #else 0342 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>[_/])?"); 0343 #endif 0344 0345 while ((i = tempFormat.indexOf(re, i, &match)) != -1) 0346 { 0347 QString replacement = ""; 0348 if ((match.captured("name") == "filename") || (match.captured("name") == "f")) 0349 replacement = m_seqFilename.baseName(); 0350 else if ((match.captured("name") == "Datetime") || (match.captured("name") == "D")) 0351 { 0352 if (glob || gettingSignature) 0353 { 0354 if (local) 0355 replacement = "\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d-\\d\\d-\\d\\d"; 0356 else 0357 replacement = "ISO8601"; 0358 0359 } 0360 else 0361 replacement = QDateTime::currentDateTime().toString("yyyy-MM-ddThh-mm-ss"); 0362 } 0363 else if ((match.captured("name") == "Type") || (match.captured("name") == "T")) 0364 { 0365 if (isDarkFlat) 0366 replacement = "DarkFlat"; 0367 else 0368 replacement = getFrameType(frameType); 0369 } 0370 else if ((match.captured("name") == "exposure") || (match.captured("name") == "e") || 0371 (match.captured("name") == "exp") || (match.captured("name") == "E")) 0372 { 0373 double fractpart, intpart; 0374 double exposure = pathPropertyMap[PP_EXPOSURE].toDouble(); 0375 fractpart = std::modf(exposure, &intpart); 0376 if (fractpart == 0) 0377 replacement = QString::number(exposure, 'd', 0); 0378 else if (exposure >= 1e-3) 0379 replacement = QString::number(exposure, 'f', 3); 0380 else 0381 replacement = QString::number(exposure, 'f', 6); 0382 // append _secs for placeholders "exposure" and "e" 0383 if ((match.captured("name") == "exposure") || (match.captured("name") == "e")) 0384 replacement += QString("_secs"); 0385 } 0386 else if ((match.captured("name") == "Filter") || (match.captured("name") == "F")) 0387 { 0388 QString filter = pathPropertyMap[PP_FILTER].toString(); 0389 if (filter.isEmpty() == false 0390 && (frameType == FRAME_LIGHT 0391 || frameType == FRAME_FLAT 0392 || frameType == FRAME_NONE 0393 || isDarkFlat)) 0394 { 0395 replacement = filter; 0396 } 0397 } 0398 else if ((match.captured("name") == "target") || (match.captured("name") == "t")) 0399 { 0400 replacement = targetNameSanitized; 0401 } 0402 else if (((match.captured("name") == "temperature") || (match.captured("name") == "C"))) 0403 { 0404 replacement = generateReplacement(pathPropertyMap, PP_TEMPERATURE, 0405 (glob || gettingSignature) && pathPropertyMap[PP_TEMPERATURE].isValid() == false); 0406 } 0407 else if (((match.captured("name") == "bin") || (match.captured("name") == "B"))) 0408 { 0409 replacement = generateReplacement(pathPropertyMap, PP_BIN, 0410 (glob || gettingSignature) && pathPropertyMap[PP_BIN].isValid() == false); 0411 } 0412 else if (((match.captured("name") == "gain") || (match.captured("name") == "G"))) 0413 { 0414 replacement = generateReplacement(pathPropertyMap, PP_GAIN, 0415 (glob || gettingSignature) && pathPropertyMap[PP_GAIN].isValid() == false); 0416 } 0417 else if (((match.captured("name") == "offset") || (match.captured("name") == "O"))) 0418 { 0419 replacement = generateReplacement(pathPropertyMap, PP_OFFSET, 0420 (glob || gettingSignature) && pathPropertyMap[PP_OFFSET].isValid() == false); 0421 } 0422 else if (((match.captured("name") == "iso") || (match.captured("name") == "I")) 0423 && pathPropertyMap[PP_ISO].isValid()) 0424 { 0425 replacement = generateReplacement(pathPropertyMap, PP_ISO, 0426 (glob || gettingSignature) && pathPropertyMap[PP_ISO].isValid() == false); 0427 } 0428 else if (((match.captured("name") == "pierside") || (match.captured("name") == "P"))) 0429 { 0430 replacement = generateReplacement(pathPropertyMap, PP_PIERSIDE, glob || gettingSignature); 0431 } 0432 // Disable for now %d & %p tags to simplfy 0433 // else if ((match.captured("name") == "directory") || (match.captured("name") == "d") || 0434 // (match.captured("name") == "path") || (match.captured("name") == "p")) 0435 // { 0436 // int level = 0; 0437 // if (!match.captured("level").isEmpty()) 0438 // level = match.captured("level").toInt() - 1; 0439 // QFileInfo dir = m_seqFilename; 0440 // for (int j = 0; j < level; ++j) 0441 // dir = QFileInfo(dir.dir().path()); 0442 // if (match.captured("name") == "directory" || match.captured("name") == "d") 0443 // replacement = dir.dir().dirName(); 0444 // else if (match.captured("name") == "path" || match.captured("name") == "p") 0445 // replacement = dir.path(); 0446 // } 0447 else if ((match.captured("name") == "sequence") || (match.captured("name") == "s")) 0448 { 0449 if (glob) 0450 replacement = "(?<id>\\d+)"; 0451 else 0452 { 0453 int level = 0; 0454 if (!match.captured("level").isEmpty()) 0455 level = match.captured("level").toInt(); 0456 replacement = QString("%1").arg(nextSequenceID, level, 10, QChar('0')); 0457 } 0458 } 0459 else 0460 qWarning() << "Unknown replacement string: " << match.captured("replace"); 0461 0462 if (replacement.isEmpty()) 0463 tempFormat = tempFormat.replace(match.capturedStart(), match.capturedLength(), replacement); 0464 else 0465 tempFormat = tempFormat.replace(match.capturedStart("replace"), match.capturedLength("replace"), replacement); 0466 i += replacement.length(); 0467 } 0468 0469 if (!gettingSignature) 0470 tempFilename = tempFormat + extension; 0471 else 0472 tempFilename = tempFormat.left(tempFormat.lastIndexOf("_")); 0473 0474 return tempFilename; 0475 } 0476 0477 void PlaceholderPath::setGenerateFilenameSettings(const SequenceJob &job, QMap<PathProperty, QVariant> &pathPropertyMap, 0478 const bool local, const bool gettingSignature) 0479 { 0480 setPathProperty(pathPropertyMap, PP_TARGETNAME, job.getCoreProperty(SequenceJob::SJ_TargetName)); 0481 setPathProperty(pathPropertyMap, PP_FRAMETYPE, QVariant(job.getFrameType())); 0482 setPathProperty(pathPropertyMap, PP_FILTER, job.getCoreProperty(SequenceJob::SJ_Filter)); 0483 setPathProperty(pathPropertyMap, PP_EXPOSURE, job.getCoreProperty(SequenceJob::SJ_Exposure)); 0484 setPathProperty(pathPropertyMap, PP_DIRECTORY, 0485 job.getCoreProperty(local ? SequenceJob::SJ_LocalDirectory : SequenceJob::SJ_RemoteDirectory)); 0486 setPathProperty(pathPropertyMap, PP_FORMAT, job.getCoreProperty(SequenceJob::SJ_PlaceholderFormat)); 0487 setPathProperty(pathPropertyMap, PP_SUFFIX, job.getCoreProperty(SequenceJob::SJ_PlaceholderSuffix)); 0488 setPathProperty(pathPropertyMap, PP_DARKFLAT, job.jobType() == SequenceJob::JOBTYPE_DARKFLAT); 0489 setPathProperty(pathPropertyMap, PP_BIN, job.getCoreProperty(SequenceJob::SJ_Binning)); 0490 setPathProperty(pathPropertyMap, PP_PIERSIDE, QVariant(job.getPierSide())); 0491 setPathProperty(pathPropertyMap, PP_ISO, job.getCoreProperty(SequenceJob::SJ_ISO)); 0492 0493 // handle optional parameters 0494 if (job.getCoreProperty(SequenceJob::SJ_EnforceTemperature).toBool()) 0495 setPathProperty(pathPropertyMap, PP_TEMPERATURE, QVariant(job.getTargetTemperature())); 0496 else if (job.currentTemperature() != Ekos::INVALID_VALUE && !gettingSignature) 0497 setPathProperty(pathPropertyMap, PP_TEMPERATURE, QVariant(job.currentTemperature())); 0498 else 0499 pathPropertyMap.remove(PP_TEMPERATURE); 0500 0501 if (job.getCoreProperty(SequenceJob::SequenceJob::SJ_Gain).toInt() >= 0) 0502 setPathProperty(pathPropertyMap, PP_GAIN, job.getCoreProperty(SequenceJob::SJ_Gain)); 0503 else if (job.currentGain() >= 0 && !gettingSignature) 0504 setPathProperty(pathPropertyMap, PP_GAIN, job.currentGain()); 0505 else 0506 pathPropertyMap.remove(PP_GAIN); 0507 0508 if (job.getCoreProperty(SequenceJob::SequenceJob::SJ_Offset).toInt() >= 0) 0509 setPathProperty(pathPropertyMap, PP_OFFSET, job.getCoreProperty(SequenceJob::SJ_Offset)); 0510 else if (job.currentOffset() >= 0 && !gettingSignature) 0511 setPathProperty(pathPropertyMap, PP_OFFSET, job.currentOffset()); 0512 else 0513 pathPropertyMap.remove(PP_OFFSET); 0514 } 0515 0516 QStringList PlaceholderPath::remainingPlaceholders(const QString &filename) 0517 { 0518 QList<QString> placeholders = {}; 0519 QRegularExpressionMatch match; 0520 #if defined(Q_OS_WIN) 0521 QRegularExpression re("(?<replace>\\%(?<name>[a-zA-Z])(?<level>\\d+)?)(?<sep>[_\\\\])+"); 0522 #else 0523 QRegularExpression re("(?<replace>%(?<name>[a-zA-Z])(?<level>\\d+)?)(?<sep>[_/])+"); 0524 #endif 0525 int i = 0; 0526 while ((i = filename.indexOf(re, i, &match)) != -1) 0527 { 0528 if (match.hasMatch()) 0529 placeholders.push_back(match.captured("replace")); 0530 i += match.capturedLength("replace"); 0531 } 0532 return placeholders; 0533 } 0534 0535 QList<int> PlaceholderPath::getCompletedFileIds(const SequenceJob &job) 0536 { 0537 QString path = generateSequenceFilename(job, true, true, 0, ".*", "", true); 0538 auto sanitizedPath = path; 0539 0540 // This is needed for Windows as the regular expression confuses path search 0541 QString idRE = "(?<id>\\d+).*"; 0542 QString datetimeRE = "\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d-\\d\\d-\\d\\d"; 0543 sanitizedPath.replace(idRE, "{IDRE}"); 0544 sanitizedPath.replace(datetimeRE, "{DATETIMERE}"); 0545 0546 // Now we can get a proper directory 0547 QFileInfo path_info(sanitizedPath); 0548 QDir dir(path_info.dir()); 0549 0550 // e.g. Light_R_(?<id>\\d+).* 0551 auto filename = path_info.fileName(); 0552 0553 // Next replace back the problematic regular expressions 0554 filename.replace("{IDRE}", idRE); 0555 filename.replace("{DATETIMERE}", datetimeRE); 0556 0557 QStringList matchingFiles = dir.entryList(QDir::Files); 0558 QRegularExpressionMatch match; 0559 QRegularExpression re("^" + filename + "$"); 0560 QList<int> ids = {}; 0561 for (auto &name : matchingFiles) 0562 { 0563 match = re.match(name); 0564 if (match.hasMatch()) 0565 ids << match.captured("id").toInt(); 0566 } 0567 0568 return ids; 0569 } 0570 0571 int PlaceholderPath::getCompletedFiles(const SequenceJob &job) 0572 { 0573 return getCompletedFileIds(job).length(); 0574 } 0575 0576 int PlaceholderPath::getCompletedFiles(const QString &path) 0577 { 0578 int seqFileCount = 0; 0579 #ifdef Q_OS_WIN 0580 // Splitting directory and baseName in QFileInfo does not distinguish regular expression backslash from directory separator on Windows. 0581 // So do not use QFileInfo for the code that separates directory and basename for Windows. 0582 // Conditions for calling this function: 0583 // - Directory separators must always be "/". 0584 // - Directory separators must not contain backslash. 0585 QString sig_dir; 0586 QString sig_file; 0587 int index = path.lastIndexOf('/'); 0588 if (0 <= index) 0589 { 0590 // found '/'. path has both dir and filename 0591 sig_dir = path.left(index); 0592 sig_file = path.mid(index + 1); 0593 } // not found '/'. path has only filename 0594 else 0595 { 0596 sig_file = path; 0597 } 0598 // remove extension 0599 index = sig_file.lastIndexOf('.'); 0600 if (0 <= index) 0601 { 0602 // found '.', then remove extension 0603 sig_file = sig_file.left(index); 0604 } 0605 qCDebug(KSTARS_EKOS_CAPTURE) << "Scheduler::PlaceholderPath path:" << path << " sig_dir:" << sig_dir << " sig_file:" << 0606 sig_file; 0607 #else 0608 QFileInfo const path_info(path); 0609 QString const sig_dir(path_info.dir().path()); 0610 QString const sig_file(path_info.completeBaseName()); 0611 #endif 0612 QRegularExpression re(sig_file); 0613 0614 QDirIterator it(sig_dir, QDir::Files); 0615 0616 /* FIXME: this counts all files with prefix in the storage location, not just captures. DSS analysis files are counted in, for instance. */ 0617 while (it.hasNext()) 0618 { 0619 QString const fileName = QFileInfo(it.next()).completeBaseName(); 0620 0621 QRegularExpressionMatch match = re.match(fileName); 0622 if (match.hasMatch()) 0623 seqFileCount++; 0624 } 0625 0626 return seqFileCount; 0627 } 0628 0629 int PlaceholderPath::checkSeqBoundary(const SequenceJob &job) 0630 { 0631 auto ids = getCompletedFileIds(job); 0632 if (ids.length() > 0) 0633 return *std::max_element(ids.begin(), ids.end()) + 1; 0634 else 0635 return 1; 0636 } 0637 0638 PlaceholderPath::PathPropertyType PlaceholderPath::propertyType(PathProperty property) 0639 { 0640 switch (property) 0641 { 0642 case PP_FORMAT: 0643 case PP_DIRECTORY: 0644 case PP_TARGETNAME: 0645 case PP_FILTER: 0646 case PP_PIERSIDE: 0647 return PP_TYPE_STRING; 0648 0649 case PP_DARKFLAT: 0650 return PP_TYPE_BOOL; 0651 0652 case PP_SUFFIX: 0653 case PP_FRAMETYPE: 0654 case PP_ISO: 0655 return PP_TYPE_UINT; 0656 0657 case PP_EXPOSURE: 0658 case PP_GAIN: 0659 case PP_OFFSET: 0660 case PP_TEMPERATURE: 0661 return PP_TYPE_DOUBLE; 0662 0663 case PP_BIN: 0664 return PP_TYPE_POINT; 0665 0666 default: 0667 return PP_TYPE_NONE; 0668 } 0669 } 0670 0671 // An "emergency" method--the code should not be overwriting files, 0672 // however, if we've detected an overwrite, we generate a new filename 0673 // by looking for numbers at its end (before its extension) and incrementing 0674 // that number, checking to make sure the new filename with the incremented number doesn't exist. 0675 QString PlaceholderPath::repairFilename(const QString &filename) 0676 { 0677 QRegularExpression re("^(.*[^\\d])(\\d+)\\.(\\w+)$"); 0678 0679 auto match = re.match(filename); 0680 if (match.hasMatch()) 0681 { 0682 QString prefix = match.captured(1); 0683 int number = match.captured(2).toInt(); 0684 int numberLength = match.captured(2).size(); 0685 QString extension = match.captured(3); 0686 QString candidate = QString("%1%2.%3").arg(prefix).arg(number + 1, numberLength, 10, QLatin1Char('0')).arg(extension); 0687 int maxIterations = 2000; 0688 while (QFile::exists(candidate)) 0689 { 0690 number = number + 1; 0691 candidate = QString("%1%2.%3").arg(prefix).arg(number, numberLength, 10, QLatin1Char('0')).arg(extension); 0692 if (--maxIterations <= 0) 0693 return filename; 0694 } 0695 return candidate; 0696 } 0697 return filename;; 0698 } 0699 0700 } 0701