Mantid
Loading...
Searching...
No Matches
InstrumentFileFinder.cpp
Go to the documentation of this file.
2
8
9#include <Poco/SAX/Attributes.h>
10#include <Poco/SAX/ContentHandler.h>
11#include <Poco/SAX/SAXParser.h>
12#include <boost/algorithm/string/find.hpp>
13#include <boost/regex.hpp>
14
15#include <algorithm>
16#include <filesystem>
17
18#include <string>
19#include <utility>
20#include <vector>
21
22using namespace Mantid::Kernel;
23using namespace Mantid::Types::Core;
24using namespace Poco::XML;
25
26namespace {
28Mantid::Kernel::Logger g_log("InstrumentFileFinder");
29
30// used to terminate SAX process
31class DummyException {
32public:
33 std::string m_validFrom;
34 std::string m_validTo;
35 DummyException(std::string validFrom, std::string validTo)
36 : m_validFrom(std::move(validFrom)), m_validTo(std::move(validTo)) {}
37};
38// SAX content handler for grapping stuff quickly from IDF
39class myContentHandler : public Poco::XML::ContentHandler {
40 void startElement(const XMLString & /*uri*/, const XMLString &localName, const XMLString & /*qname*/,
41 const Attributes &attrList) override {
42 if (localName == "instrument" || localName == "parameter-file") {
43 throw DummyException(static_cast<std::string>(attrList.getValue("", "valid-from")),
44 static_cast<std::string>(attrList.getValue("", "valid-to")));
45 }
46 }
47 void endElement(const XMLString & /*uri*/, const XMLString & /*localName*/, const XMLString & /*qname*/) override {}
48 void startDocument() override {}
49 void endDocument() override {}
50 void characters(const XMLChar /*ch*/[], int /*start*/, int /*length*/) override {}
51 void endPrefixMapping(const XMLString & /*prefix*/) override {}
52 void ignorableWhitespace(const XMLChar /*ch*/[], int /*start*/, int /*length*/) override {}
53 void processingInstruction(const XMLString & /*target*/, const XMLString & /*data*/) override {}
54 void setDocumentLocator(const Locator * /*loc*/) override {}
55 void skippedEntity(const XMLString & /*name*/) override {}
56 void startPrefixMapping(const XMLString & /*prefix*/, const XMLString & /*uri*/) override {}
57};
58} // namespace
59
60namespace Mantid::API {
61
67const std::string InstrumentFileFinder::getNormalisedDate(const std::string &date) {
68 static const boost::regex dateOnlyRegex("\\d{4}-\\d{2}-\\d{2}");
69 return boost::regex_match(date, dateOnlyRegex) ? date + "T00:00:00" : date;
70};
71
90std::string InstrumentFileFinder::getFilenameByInstrumentDateAndSearchTerm(const std::string &instrumentName,
91 const std::string &date,
92 const std::string &searchTerm,
93 const std::vector<std::string> &fileFormats,
94 const std::string &dirHint) {
95 std::string fileType;
96 if (searchTerm == "_Definition")
97 fileType = "instrument file";
98 else if (searchTerm == "_Parameters")
99 fileType = "parameter file";
100 else
101 fileType = searchTerm + " file";
102
103 g_log.debug() << "Looking for " << fileType << " for " << instrumentName << " that is valid on '" << date << "'\n";
104 // Lookup the instrument (long) name, falling back to the provided name if not found in any facility
105 std::string instrument;
106 try {
107 instrument = Kernel::ConfigService::Instance().getInstrument(instrumentName).name();
108 } catch (const Kernel::Exception::NotFoundError &) {
109 instrument = instrumentName;
110 }
111
112 // Build the directory search list: dirHint (if any) is checked first so that
113 // parameter files co-located with the IDF in a non-standard directory (e.g.
114 // unit_testing/) are preferred over files in the standard instrument dirs.
115 const std::vector<std::string> &configDirs = Kernel::ConfigService::Instance().getInstrumentDirectories();
116 std::vector<std::string> directoryNames;
117 if (!dirHint.empty()) {
118 directoryNames.push_back(dirHint);
119 directoryNames.insert(directoryNames.end(), configDirs.begin(), configDirs.end());
120 } else {
121 directoryNames = configDirs;
122 }
123
124 // matching files sorted with newest files coming first
125 const std::vector<std::string> matchingFiles =
126 getResourceFilenames(instrument + searchTerm, fileFormats, directoryNames, date);
127 std::string foundFile;
128 if (!matchingFiles.empty()) {
129 foundFile = matchingFiles[0];
130 g_log.debug() << "The " << fileType << " selected is " << foundFile << '\n';
131 } else {
132 g_log.debug() << "No " << fileType << " found\n";
133 }
134 return foundFile;
135}
136
155std::string InstrumentFileFinder::getInstrumentFilename(const std::string &instrumentName, const std::string &date) {
156 return getFilenameByInstrumentDateAndSearchTerm(instrumentName, date, "_Definition", {"xml", "nxs", "hdf5"});
157}
158
178std::string InstrumentFileFinder::getParameterFilename(const std::string &instrumentName, const std::string &date,
179 const std::string &dirHint) {
180 return getFilenameByInstrumentDateAndSearchTerm(instrumentName, date, "_Parameters", {"xml"}, dirHint);
181}
182
185// directoryName must include a final '/'.
186std::string InstrumentFileFinder::getParameterPath(const std::string &instName, const std::string &dirHint) {
187 // Remove the path from the filename, some legacy callers will pass in
188 // a full path rather than a filename
189 std::filesystem::path filePath(instName);
190 const std::string filename = filePath.filename().string();
191
192 // Try the hinted dir first
193 if (!dirHint.empty()) {
194 const std::string result = lookupIPF(dirHint, filename);
195 if (!result.empty()) {
196 return result;
197 }
198 }
199
200 const Kernel::ConfigServiceImpl &configService = Kernel::ConfigService::Instance();
201 const std::vector<std::string> directoryNames = configService.getInstrumentDirectories();
202
203 for (const auto &dirName : directoryNames) {
204 // This will iterate around the directories from user ->etc ->install, and
205 // find the first beat file
206 const std::string result = lookupIPF(dirName, filename);
207 if (!result.empty()) {
208 g_log.debug() << "Found: " << result << '\n';
209 return result;
210 }
211 }
212
213 g_log.debug() << "Found Nothing \n";
214 return "";
215}
216
217std::string InstrumentFileFinder::lookupIPF(const std::string &dir, std::string filename) {
218 const std::string ext = ".xml";
219 // Remove .xml for example if abc.xml was passed
220 boost::algorithm::ierase_all(filename, ext);
221
222 const std::string suffixSeperator("_Definition");
223
224 std::string prefix;
225 std::string suffix;
226
227 if (auto sepPos = boost::algorithm::ifind_first(filename, suffixSeperator)) {
228 prefix = std::string(filename.begin(), sepPos.begin());
229 suffix = std::string(sepPos.end(), filename.end());
230 } else {
231 prefix = filename;
232 }
233
234 std::filesystem::path directoryPath(dir);
235
236 // Assemble parameter file name
237 std::string fullPathParamIDF = (directoryPath / (prefix + "_Parameters" + suffix + ext)).string();
238
239 if (std::filesystem::exists(fullPathParamIDF)) {
240 return fullPathParamIDF;
241 }
242
243 fullPathParamIDF = (directoryPath / (prefix + "_Parameters" + ext)).string();
244 if (std::filesystem::exists(fullPathParamIDF)) {
245 return fullPathParamIDF;
246 }
247
248 return "";
249}
250
266std::vector<std::string> InstrumentFileFinder::getResourceFilenames(const std::string &prefix,
267 const std::vector<std::string> &fileFormats,
268 const std::vector<std::string> &directoryNames,
269 const std::string &date) {
270
271 if (date.empty()) {
272 // Just use the current date
273 g_log.debug() << "No date specified, using current date and time.\n";
274 const std::string now = Types::Core::DateAndTime::getCurrentTime().toISO8601String();
275 // Recursively call this method, but with all parameters.
276 return InstrumentFileFinder::getResourceFilenames(prefix, fileFormats, directoryNames, now);
277 }
278
279 // Join all the file formats into a single string
280 std::stringstream ss;
281 ss << "(";
282 for (size_t i = 0; i < fileFormats.size(); ++i) {
283 if (i != 0)
284 ss << "|";
285 ss << fileFormats[i];
286 }
287 ss << ")";
288 const std::string allFileFormats = ss.str();
289
290 const boost::regex regex(prefix + ".*\\." + allFileFormats, boost::regex_constants::icase);
291
292 // Normalise date: if only YYYY-MM-DD was provided (no time component), append midnight so
293 // DateAndTime can parse it. Parameter files commonly store date-only valid-from attributes.
294 const std::string normalisedDate = getNormalisedDate(date);
295
296 DateAndTime d;
297 try {
298 d = DateAndTime(normalisedDate);
299 } catch (const std::invalid_argument &) {
300 // Some legacy data files store dates in non-ISO8601 formats.
301 // In this case fall back to the current time so we select the most recent matching file.
302 g_log.warning() << "Could not parse date '" << date
303 << "' as ISO8601; using current time for instrument file lookup.\n";
304 d = DateAndTime::getCurrentTime();
305 }
306
307 DateAndTime refDate("1899-01-01 23:59:00"); // used to help determine the most
308 // recently starting file, if none match
309 DateAndTime refDateGoodFile("1899-01-01 23:59:00"); // used to help determine the most recently
310
311 struct MatchingFile {
312 DateAndTime validFrom;
313 size_t directoryIndex;
314 std::string path;
315 };
316 std::vector<MatchingFile> matchingFiles;
317 bool foundFile = false;
318 std::string mostRecentFile; // path to the file with most recent "valid-from"
319 for (size_t directoryIndex = 0; directoryIndex < directoryNames.size(); ++directoryIndex) {
320 const auto &directoryName = directoryNames[directoryIndex];
321 // Iterate over the directories from user ->etc ->install, and find the
322 // first beat file
323 for (const auto &dir_entry : std::filesystem::directory_iterator(directoryName)) {
324
325 const auto &filePath = dir_entry.path();
326 if (!std::filesystem::is_regular_file(filePath))
327 continue;
328
329 const std::string l_filenamePart = filePath.filename().string();
330 if (regex_match(l_filenamePart, regex)) {
331 const std::string pathName = filePath.string();
332 g_log.debug() << "Found file: '" << pathName << "'\n";
333
334 std::string validFrom, validTo;
335 getValidFromTo(pathName, validFrom, validTo);
336 g_log.debug() << "File '" << pathName << " valid dates: from '" << validFrom << "' to '" << validTo << "'\n";
337 // Use default valid "from" and "to" dates if none were found.
338 // Normalise date-only strings (YYYY-MM-DD) to full datetimes before parsing.
339 // Some legacy instrument files store dates in non-ISO8601 formats; treat them as lowest-priority
340 // catch-alls (valid for all time) so they are still considered but ranked last.
341 DateAndTime to, from;
342 try {
343 if (validFrom.length() > 0) {
344 const std::string normFrom = getNormalisedDate(validFrom);
345 from.setFromISO8601(normFrom);
346 } else {
347 from = refDate;
348 }
349 } catch (const std::invalid_argument &) {
350 g_log.debug() << "Could not parse valid-from='" << validFrom << "' in '" << pathName
351 << "'; treating as lowest priority.\n";
352 from = refDate;
353 }
354 try {
355 if (validTo.length() > 0) {
356 const std::string normTo = getNormalisedDate(validTo);
357 to.setFromISO8601(normTo);
358 } else {
359 to.setFromISO8601("2100-01-01T00:00:00");
360 }
361 } catch (const std::invalid_argument &) {
362 g_log.debug() << "Could not parse valid-to='" << validTo << "' in '" << pathName
363 << "'; treating as lowest priority.\n";
364 to.setFromISO8601("2100-01-01T00:00:00");
365 }
366
367 if (from <= d && d <= to) {
368 foundFile = true;
369 matchingFiles.emplace_back(from, directoryIndex, pathName);
370 }
371 // Consider the most recent file in the absence of matching files
372 if (!foundFile && (from >= refDate)) {
373 refDate = from;
374 mostRecentFile = pathName;
375 }
376 }
377 }
378 }
379
380 // Retrieve the file names only
381 std::vector<std::string> pathNames;
382 if (!matchingFiles.empty()) {
383 std::sort(matchingFiles.begin(), matchingFiles.end(), [](const auto &lhs, const auto &rhs) {
384 if (lhs.validFrom != rhs.validFrom)
385 return lhs.validFrom > rhs.validFrom;
386 if (lhs.directoryIndex != rhs.directoryIndex)
387 return lhs.directoryIndex < rhs.directoryIndex;
388 return lhs.path < rhs.path;
389 });
390 pathNames.reserve(matchingFiles.size());
391
392 std::transform(matchingFiles.begin(), matchingFiles.end(), std::back_inserter(pathNames),
393 [](const auto &elem) { return elem.path; });
394 } else {
395 pathNames.emplace_back(std::move(mostRecentFile));
396 }
397
398 return pathNames;
399}
400
407void InstrumentFileFinder::getValidFromTo(const std::string &IDFfilename, std::string &outValidFrom,
408 std::string &outValidTo) {
409 SAXParser pParser;
410 // Create on stack to ensure deletion. Relies on pParser also being local
411 // variable.
412 myContentHandler conHand;
413 pParser.setContentHandler(&conHand);
414
415 try {
416 pParser.parse(IDFfilename);
417 } catch (const DummyException &e) {
418 outValidFrom = e.m_validFrom;
419 outValidTo = e.m_validTo;
420 } catch (...) {
421 // should throw some sensible here
422 }
423}
424
425} // Namespace Mantid::API
const std::vector< double > & rhs
static void getValidFromTo(const std::string &IDFfilename, std::string &outValidFrom, std::string &outValidTo)
Utility to retrieve the validity dates for the given IDF.
static std::string getFilenameByInstrumentDateAndSearchTerm(const std::string &instrumentName, const std::string &date, const std::string &searchTerm, const std::vector< std::string > &fileFormats, const std::string &dirHint="")
This method returns a file name which finds a file which contains the given instrument name + search ...
static std::vector< std::string > getResourceFilenames(const std::string &prefix, const std::vector< std::string > &fileFormats, const std::vector< std::string > &directoryNames, const std::string &date)
Utility to retrieve a resource file (IDF, Parameters, ..)
static std::string getParameterPath(const std::string &instName, const std::string &dirHint="")
Search instrument directories for Parameter file, return full path name if found, else "".
static std::string getParameterFilename(const std::string &instrumentName, const std::string &date="", const std::string &dirHint="")
Get the Parameter file using the instrument name and date, optionally searching dirHint first before ...
static std::string lookupIPF(const std::string &dir, std::string filename)
static const std::string getNormalisedDate(const std::string &date)
If date with only YYYY - MM - DD was provided (no time component), append midnight so DateAndTime can...
static std::string getInstrumentFilename(const std::string &instrumentName, const std::string &date="")
Get the IDF using the instrument name and date.
The ConfigService class provides a simple facade to access the Configuration functionality of the Man...
const std::vector< std::string > & getInstrumentDirectories() const
Get instrument search directories.
Exception for when an item is not found in a collection.
Definition Exception.h:145
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition Logger.h:51
void debug(const std::string &msg)
Logs at debug level.
Definition Logger.cpp:145
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
Kernel::Logger g_log("ExperimentInfo")
static logger object
STL namespace.