Isis 3.0 Programmer Reference
Back | Home
FileName.cpp
Go to the documentation of this file.
1 
24 #include "FileName.h"
25 
26 #include <cmath>
27 
28 #include <QDate>
29 #include <QDebug>
30 #include <QDir>
31 #include <QPair>
32 #include <QString>
33 #include <QTemporaryFile>
34 
35 #include "Preference.h"
36 #include "IException.h"
37 #include "IString.h"
38 
39 using namespace std;
40 
41 namespace Isis {
42 
44  FileName::FileName() {
45  m_d = new Data;
46  }
47 
48 
49  FileName::FileName(const char *file) {
50  m_d = new Data;
51  m_d->setOriginal(file);
52  }
53 
54 
55  FileName::FileName(const QString &file) {
56  m_d = new Data;
57  m_d->setOriginal(file);
58  }
59 
60 
61  FileName::FileName(const FileName &other) : m_d(other.m_d) {
62  }
63 
64 
67  }
68 
69 
70  QString FileName::originalPath() const {
71  return QFileInfo(m_d->original(false)).path();
72  }
73 
74 
88  QString FileName::path() const {
89  return QFileInfo(expanded()).path();
90  }
91 
92 
93  QString FileName::attributes() const {
94  QString result;
95  QString fileNameWithAttribs = QFileInfo(m_d->original(true)).fileName();
96 
97  int attribStartPos = fileNameWithAttribs.indexOf("+");
98 
99  if (attribStartPos != -1)
100  result = fileNameWithAttribs.mid(attribStartPos + 1);
101 
102  return result;
103  }
104 
105 
106  QString FileName::baseName() const {
107  return QFileInfo(m_d->original(false)).completeBaseName();
108  }
109 
110 
111  QString FileName::name() const {
112  return QFileInfo(m_d->original(false)).fileName();
113  }
114 
115 
116  QString FileName::extension() const {
117  return QFileInfo(m_d->original(false)).suffix();
118  }
119 
120 
121  QString FileName::expanded() const {
122  return m_d->expanded(false);
123  }
124 
125 
126  QString FileName::original() const {
127  return m_d->original(true);
128  }
129 
130 
131  FileName FileName::addExtension(const QString &newExtension) const {
132  FileName result = *this;
133 
134  if (result.extension() != newExtension) {
135  QString attributesStr = result.attributes();
136 
137  if (attributesStr == "")
138  result = FileName(result.originalPath() + "/" + result.name() + "." + newExtension);
139  else
140  result = FileName(result.originalPath() + "/" + result.name() + "." + newExtension
141  + "+" + attributesStr);
142  }
143 
144  return result;
145  }
146 
147 
148  FileName FileName::removeExtension() const {
149  QString attributesStr = attributes();
150 
151  FileName result;
152  if (attributesStr == "")
153  result = FileName(originalPath() + "/" + baseName());
154  else
155  result = FileName(originalPath() + "/" + baseName() + "+" + attributesStr);
156 
157  return result;
158  }
159 
160 
161  FileName FileName::setExtension(const QString &newExtension) const {
162  FileName result = *this;
163 
164  if (extension() != newExtension) {
165  result = result.removeExtension().addExtension(newExtension);
166  }
167 
168  return result;
169  }
170 
171 
172  bool FileName::isVersioned() const {
174 
175  return isNumericallyVersioned() || isDateVersioned();
176  }
177 
178 
179  bool FileName::isNumericallyVersioned() const {
180  return FileName(expanded()).name().contains("?");
181  }
182 
183 
184  bool FileName::isDateVersioned() const {
185  return FileName(expanded()).name().contains(QRegExp("\\{.*\\}"));
186  }
187 
188 
189  FileName FileName::highestVersion() const {
191 
192  FileName result = *this;
193 
194  if (!isVersioned()) {
195  throw IException(IException::Unknown,
196  QObject::tr("Asked for highest version of file named [%1] in [%2] but there "
197  "are no version sequences in the name")
198  .arg(name()).arg(originalPath()),
199  _FILEINFO_);
200  }
201 
202  // Look for dates
203  if (isDateVersioned()) {
204  result = result.version(result.highestVersionDate());
205  }
206 
207  // Look for number's
208  if (isNumericallyVersioned()) {
209  result = result.version(result.highestVersionNum());
210  }
211 
212  return result;
213  }
214 
215 
216  FileName FileName::newVersion() const {
218 
219  FileName result = *this;
220 
221  if (!isVersioned()) {
222  throw IException(IException::Unknown,
223  QObject::tr("Asked for new version of file named [%1] in [%2] but there "
224  "are no version sequences in the name")
225  .arg(name()).arg(originalPath()),
226  _FILEINFO_);
227  }
228 
229  // Look for date
230  if (isDateVersioned()) {
231  result = result.version(QDate::currentDate());
232  }
233 
234  // Look for #'s
235  if (isNumericallyVersioned()) {
236  try {
237  result = result.version(result.highestVersionNum() + 1);
238  }
239  catch (IException &) {
240  result = result.version(1);
241  }
242  }
243 
244  if (result.fileExists()) {
245  throw IException(IException::Unknown,
246  QObject::tr("Could not generate unique new version of file named [%1] in "
247  "[%2] because the file [%3] exists")
248  .arg(name()).arg(originalPath()).arg(result.name()),
249  _FILEINFO_);
250 
251  }
252 
253  return result;
254  }
255 
256 
257  FileName FileName::version(long versionNumber) const {
258  QString file = FileName(expanded()).name();
259 
260  int width = file.count("?");
261 
262  if (versionNumber < 0) {
263  throw IException(IException::Unknown,
264  QObject::tr("FileName does not support negative version numbers in the file name, "
265  "tried to get version [%1] in file named [%2]")
266  .arg(versionNumber).arg(originalPath() + "/" + file),
267  _FILEINFO_);
268  }
269 
270  if (versionNumber >= pow(10.0, width)) {
271  throw IException(IException::Unknown,
272  QObject::tr("FileName does not support version numbers greater than what would fit in "
273  "the file name, tried to get version [%1] in file named [%2]")
274  .arg(versionNumber).arg(originalPath() + "/" + file),
275  _FILEINFO_);
276  }
277 
278  QPair<QString, QString> splitName = splitNameAroundVersionNum();
279  QString &before = splitName.first;
280  QString &after = splitName.second;
281 
282  file = before + QString("%1").arg(QString::number(versionNumber), width, '0') + after;
283 
284  return FileName(originalPath() + "/" + file);
285  }
286 
287 
288  FileName FileName::version(QDate versionDate) const {
289  QString newName = versionDate.toString(fileNameQDatePattern());
290 
291  return FileName(originalPath() + "/" + newName);
292  }
293 
294 
295  bool FileName::fileExists() const {
296  return QFileInfo(expanded()).exists();
297  }
298 
299 
300  QDir FileName::dir() const {
301  return QFileInfo(expanded()).dir();
302  }
303 
304 
305  FileName FileName::createTempFile(FileName templateFileName) {
306  QString preppedFileName = QString("%1/%2XXXXXX.%3").arg(templateFileName.path())
307  .arg(templateFileName.baseName()).arg(templateFileName.extension());
308  QTemporaryFile tempFile(preppedFileName);
309  tempFile.setAutoRemove(false);
310 
311  if (!tempFile.open()) {
312  throw IException(IException::Io,
313  QObject::tr("Could not create a unique temporary file name based on [%1]")
314  .arg(templateFileName.original()),
315  _FILEINFO_);
316  }
317 
318  // We want to set the 'original' path as correctly as possible. So let's use the input original
319  // path with the output temp file's file name in our result.
320  FileName result;
321  QString newTempFileNameStr = templateFileName.originalPath() + "/" +
322  QFileInfo(tempFile.fileName()).fileName();
323  result = FileName(newTempFileNameStr);
324 
325  return result;
326  }
327 
328 
329  QString FileName::toString() const {
330  return expanded();
331  }
332 
333 
341  m_d = rhs.m_d;
342  return *this;
343  }
344 
345 
346  bool FileName::operator==(const FileName &rhs) {
347  QString expandedOfThis = expanded();
348  QString canonicalOfThis = QFileInfo(expandedOfThis).canonicalFilePath();
349 
350  QString expandedOfRhs = rhs.expanded();
351  QString canonicalOfRhs = QFileInfo(expandedOfRhs).canonicalFilePath();
352 
353  // Cononical file paths return empty strings if the file does not exist. Either both canonicals
354  // are valid and the same (equal is initialized to true), or neither canonical is valid but
355  // the expandeds are the same (equal is set to true when it isn't initialized to true).
356  bool equal = (!canonicalOfThis.isEmpty() && canonicalOfThis == canonicalOfRhs);
357 
358  if (!equal) {
359  equal = (canonicalOfThis.isEmpty() && canonicalOfRhs.isEmpty() &&
360  expandedOfThis == expandedOfRhs);
361  }
362 
363  return equal;
364  }
365 
366 
367 
368  bool FileName::operator!=(const FileName &rhs) {
369  return !(*this == rhs);
370  }
371 
372 
373  QDate FileName::highestVersionDate() const {
374  QString fileQDatePattern = fileNameQDatePattern();
375 
376  QPair<int, int> truncateRange(-1, -1);
377  if (fileQDatePattern.contains("?")) {
378  QString trueLengthName = name().replace(QRegExp("[{}]"), "");
379  truncateRange.first = trueLengthName.indexOf("?");
380  truncateRange.second = trueLengthName.lastIndexOf("?");
381  fileQDatePattern = fileQDatePattern.replace("?", "");
382  }
383 
384  QString file = name();
385 
386  QDate result;
387  QDate sputnikLaunch(1957, 10, 4);
388 
389  QString before = file.mid(0, file.indexOf("{"));
390  QString after = file.mid(file.lastIndexOf("}") + 1);
391 
392  QStringList nameFilters;
393 
394  nameFilters.append(before + "*" + after);
395  QStringList files = dir().entryList(nameFilters);
396 
397  // We can't sort the files to get our answer, so we need to go through every possible file.
398  foreach (QString foundFile, files) {
399  // Toss any numerical versioning sequence
400  if (truncateRange.first >= 0 && truncateRange.second > truncateRange.first) {
401  foundFile = foundFile.mid(0, truncateRange.first) +
402  foundFile.mid(truncateRange.second + 1);
403 
404  }
405 
406  QDate fileDate = QDate::fromString(foundFile, fileQDatePattern);
407 
408  if (fileDate.isValid()) {
409  // No missions before Sputnik 1, so we must be in the new millenium
410  if (fileDate < sputnikLaunch)
411  fileDate = fileDate.addYears(100);
412 
413  if (!result.isValid() || fileDate > result) {
414  result = fileDate;
415  }
416  }
417  }
418 
419  if (!result.isValid()) {
420  throw IException(IException::Unknown,
421  QObject::tr("No existing files found with a date version matching [%1] in "
422  "[%2]")
423  .arg(FileName(expanded()).name()).arg(path()),
424  _FILEINFO_);
425  }
426 
427  return result;
428  }
429 
430 
431  long FileName::highestVersionNum() const {
432  QString file = FileName(expanded()).name();
433  int result = 0;
434 
435  int width = file.count("?");
436 
437  QPair<QString, QString> splitName = splitNameAroundVersionNum();
438  QString &before = splitName.first;
439  QString &after = splitName.second;
440 
441  QStringList nameFilters;
442  nameFilters.append(before + QString("%1").arg("", width, '?') + after);
443  QStringList files = dir().entryList(nameFilters, QDir::NoFilter, QDir::Name);
444 
445  long foundValue = -1;
446  bool success = false;
447 
448  for (int i = files.count() - 1; !success && i >= 0; i--) {
449  foundValue = files[i].mid(before.count(), width).toLong(&success);
450  }
451 
452  if (success) {
453  result = foundValue;
454  }
455  else {
456  throw IException(IException::Unknown,
457  QObject::tr("No existing files found with a numerial version matching [%1] "
458  "in [%2]")
459  .arg(FileName(expanded()).name()).arg(path()),
460  _FILEINFO_);
461  }
462 
463  return result;
464  }
465 
466 
472  QString file = QFileInfo(expanded()).fileName();
473 
474  if (file.contains(QRegExp("\\?\\?*[^?][^?]*\\?"))) {
476  QObject::tr("Only one numerical version sequence is allowed in a filename; "
477  "there are multiple in [%1]").arg(file),
478  _FILEINFO_);
479  }
480 
481  if (isDateVersioned()) {
482  QString fileDatePattern = FileName(expanded()).name();
483 
484  // {} needs to be removed from the fileDatePattern (i.e. an empty date version sequence).
485  // This prevents the replacement of {} with '' in the pattern, since
486  // Qt5's QDate.toString(pattern) handles these two adjacent single quotes in the pattern
487  // differently than Qt4 did.
488  fileDatePattern.replace(QRegExp("\\{\\}"), "");
489 
490  fileDatePattern = "'" + fileDatePattern.replace(QRegExp("[{}]"), "'") + "'";
491 
492  QString dated = QDate::currentDate().toString(fileDatePattern);
493  if (file.contains("'")) {
495  QObject::tr("Date version sequenced file names cannot have single quotes in them; "
496  "the file named [%1] is not usable").arg(file),
497  _FILEINFO_);
498  }
499  else if (dated == "") {
501  QObject::tr("The date version sequence is not usable in the file named [%1]").arg(file),
502  _FILEINFO_);
503  }
504  else if (dated == fileDatePattern.replace(QRegExp("'"), "")) {
506  QObject::tr("The date version sequences are not recognized in the file named [%1]")
507  .arg(file),
508  _FILEINFO_);
509  }
510  }
511  }
512 
513  QString FileName::fileNameQDatePattern() const {
514  // We need to quote everything not in {} with single quotes.
515  QString file = FileName(expanded()).name();
516 
517  // Current Text: {VAR}XXX{VAR}XXX{VAR} or XXX{VAR}XXX{VAR} or XXX{VAR}XXX or {VAR}XXX
518  file = file.replace(QRegExp("[{}]"), "'");
519 
520  // Current Text: 'VAR'XXX'VAR'XXX'VAR' or XXX'VAR'XXX'VAR' or XXX'VAR'XXX or 'VAR'XXX
521  if (file.startsWith("'"))
522  file = file.mid(1);
523  else
524  file = "'" + file;
525 
526  // Current Text: VAR'XXX'VAR'XXX'VAR' or 'XXX'VAR'XXX'VAR' or 'XXX'VAR'XXX or VAR'XXX
527  if (file.endsWith("'"))
528  file = file.mid(0, file.length() - 1);
529  else
530  file = file + "'";
531 
532  // Current Text: VAR'XXX'VAR'XXX'VAR or 'XXX'VAR'XXX'VAR or 'XXX'VAR'XXX' or VAR'XXX' -- VAR's
533  // are the only text not quoted; this is success.
534  return file;
535  }
536 
537 
538  QPair<QString, QString> FileName::splitNameAroundVersionNum() const {
539  QString file = FileName(expanded()).name();
540  QString before;
541  QString after;
542 
543  if (!isNumericallyVersioned()) {
544  before = file;
545  }
546  else {
547  before = file.mid(0, file.indexOf("?"));
548  after = file.mid(file.lastIndexOf("?") + 1);
549  }
550 
551  return QPair<QString, QString>(before, after);
552  }
553 
554 
555  FileName::Data::Data() {
556  m_originalFileNameString = NULL;
557  m_expandedFileNameString = NULL;
558 
559  m_originalFileNameString = new QString;
560  m_expandedFileNameString = new QString;
561  }
562 
563 
564  FileName::Data::Data(const Data &other) : QSharedData(other) {
565  m_originalFileNameString = NULL;
566  m_expandedFileNameString = NULL;
567 
568  m_originalFileNameString = new QString(*other.m_originalFileNameString);
569  m_expandedFileNameString = new QString(*other.m_expandedFileNameString);
570  }
571 
572 
573  FileName::Data::~Data() {
574  delete m_originalFileNameString;
575  m_originalFileNameString = NULL;
576 
577  delete m_expandedFileNameString;
578  m_expandedFileNameString = NULL;
579  }
580 
581 
582  QString FileName::Data::original(bool includeAttributes) const {
583  QString result = *m_originalFileNameString;
584 
585 
586  if (!includeAttributes) {
587  int attributesPos = result.indexOf("+");
588 
589  if (attributesPos != -1)
590  result = result.left(attributesPos);
591  }
592 
593  return result;
594  }
595 
596 
597  void FileName::Data::setOriginal(const QString &originalStr) {
598  *m_originalFileNameString = originalStr;
599 
600  // Expand the file name and store that too.
601  QString expandedStr = original(true);
602 
603  int varSearchStartPos = 0;
604  int varStartPos = -1;
605  // Loop while there are any "$" at the current position or after
606  // Some "$" might be skipped if no translation can be found
607  while((varStartPos = expandedStr.indexOf("$", varSearchStartPos)) != -1) {
608  int varEndPos = expandedStr.indexOf(QRegExp("[^a-zA-Z{}0-9]"), varStartPos + 1);
609  if (varEndPos == -1)
610  varEndPos = expandedStr.length();
611 
612  bool variableValid = false;
613  int varNameLength = varEndPos - varStartPos;
614 
615  if (varNameLength > 0) {
616  QString varName = expandedStr.mid(varStartPos + 1, varEndPos - varStartPos - 1);
617 
618  if (varName.length()) {
619  if (varName[0] =='{' && varName[varName.length() - 1] == '}')
620  varName = varName.mid(1, varName.length() - 2);
621 
622  QString varValue;
623 
624  // Find the corresponding Isis Preference if one exists
625  if(Preference::Preferences().hasGroup("DataDirectory")) {
626  PvlGroup &dataDir = Preference::Preferences().findGroup("DataDirectory");
627  if(dataDir.hasKeyword(varName)) {
628  varValue = ((QString)dataDir[varName.toStdString().c_str()][0]);
629  }
630  }
631 
632  // Find the corresponding environment variable if one exists
633  if (varValue.isEmpty()) {
634  char *val;
635  val = getenv(varName.toStdString().c_str());
636  if(val != NULL) varValue = val;
637  }
638 
639  // Replace the $xxxx with the pref/env, but don't move
640  // the pointer. We may have replaced one $ for another.
641  // Note: May need to put a test for circular replaces in here
642  if (!varValue.isEmpty()) {
643  expandedStr = expandedStr.replace(varStartPos, varNameLength, varValue);
644  variableValid = true;
645  }
646  }
647  }
648 
649  if (variableValid) {
650  // We could expand multiple times...
651  varSearchStartPos = varStartPos;
652  }
653  else {
654  // We failed to understand this variable, move along
655  varSearchStartPos = varStartPos + 1;
656  }
657  }
658 
659  *m_expandedFileNameString = expandedStr;
660  }
661 
662 
663  QString FileName::Data::expanded(bool includeAttributes) const {
664  QString result = *m_expandedFileNameString;
665 
666 
667  if (!includeAttributes) {
668  int attributesPos = result.indexOf("+");
669 
670  if (attributesPos != -1)
671  result = result.left(attributesPos);
672  }
673 
674  return result;
675  }
676 }
677 
File name manipulation and expansion.
Definition: FileName.h:111
A type of error that occurred when performing an actual I/O operation.
Definition: IException.h:163
void validateVersioningState() const
This verifies the class invariant when using versioning - that the FileName is in an acceptable state...
Definition: FileName.cpp:471
#define _FILEINFO_
Macro for the filename and line number.
Definition: IException.h:38
A type of error that cannot be classified as any of the other error types.
Definition: IException.h:126
This is the reference-counted data for FileName.
Definition: FileName.h:173
FileName & operator=(const FileName &rhs)
Clears the current contents of the FileName object and reinitializes it with the argument.
Definition: FileName.cpp:340
Isis exception class.
Definition: IException.h:99
~FileName()
Destroys the FileName object.
Definition: FileName.cpp:66
FileName()
Constructs an empty FileName object.
Definition: FileName.cpp:44
QString path() const
Returns the path.
Definition: FileName.cpp:88
QSharedDataPointer< Data > m_d
Definition: FileName.h:193

U.S. Department of the Interior | U.S. Geological Survey
ISIS | Privacy & Disclaimers | Astrogeology Research Program
To contact us, please post comments and questions on the ISIS Support Center
File Modified: 07/12/2023 23:18:08