#include "MantidAPI/FileProperty.h" #include "MantidAPI/FileFinder.h" #include "MantidKernel/ConfigService.h" #include "MantidKernel/DirectoryValidator.h" #include "MantidKernel/FacilityInfo.h" #include "MantidKernel/FileValidator.h" #include "MantidKernel/Strings.h" #include <Poco/File.h> #include <Poco/Path.h> #include <boost/make_shared.hpp> #include <algorithm> #include <cctype> #include <cstdlib> #include <iostream> //REMEMBER TO REMOVE THIS namespace Mantid { namespace API { using Mantid::Kernel::ConfigService; using Mantid::Kernel::DirectoryValidator; using Mantid::Kernel::FileValidator; using Mantid::Kernel::IValidator_sptr; namespace { /** * Create the appropriate validator based on the parameters * @param action The type of property that is being defined, @see FileAction * @param exts A list of extensions, only use for File-type actions and are * passed to the validator */ IValidator_sptr createValidator(unsigned int action, const std::vector<std::string> &exts) { if (action == FileProperty::Directory || action == FileProperty::OptionalDirectory) { return boost::make_shared<DirectoryValidator>(action == FileProperty::Directory); } else { return boost::make_shared<FileValidator>( exts, (action == FileProperty::Load), (action == FileProperty::Save)); } } /** * If the given extension doesn't exist in the list then add it * @param extension A string listing the extension * @param extensions The existing collection */ void addExtension(const std::string &extension, std::vector<std::string> &extensions) { if (std::find(extensions.begin(), extensions.end(), extension) != extensions.end()) return; else extensions.push_back(extension); } } //----------------------------------------------------------------- // Public member functions //----------------------------------------------------------------- /** * Constructor * @param name The name of the property * @param defaultValue A default value for the property * @param action Inndicate whether this should be a load/save * property * @param exts The allowed extensions. The front entry in the vector * will be the default extension * @param direction An optional direction (default=Input) */ FileProperty::FileProperty(const std::string &name, const std::string &defaultValue, unsigned int action, const std::vector<std::string> &exts, unsigned int direction) : PropertyWithValue<std::string>(name, defaultValue, createValidator(action, exts), direction), m_action(action), m_defaultExt((!exts.empty()) ? exts.front() : ""), m_runFileProp(isLoadProperty() && extsMatchRunFiles()), m_oldLoadPropValue(""), m_oldLoadFoundFile("") {} /** * Constructor * @param name :: The name of the property * @param default_value :: A default value for the property * @param ext :: The allowed extension * @param action :: An enum indicating whether this should be a load/save * property * @param direction :: An optional direction (default=Input) */ FileProperty::FileProperty(const std::string &name, const std::string &default_value, unsigned int action, const std::string &ext, unsigned int direction) : FileProperty(name, default_value, action, std::vector<std::string>(1, ext), direction) {} /** * Constructor * @param name :: The name of the property * @param default_value :: A default value for the property * @param exts :: The braced-list of allowed extensions * @param action :: An enum indicating whether this should be a load/save * property * @param direction :: An optional direction (default=Input) */ FileProperty::FileProperty(const std::string &name, const std::string &default_value, unsigned int action, std::initializer_list<std::string> exts, unsigned int direction) : FileProperty(name, default_value, action, std::vector<std::string>(exts), direction) {} /** * Check if this is a load property * @returns True if the property is a Load property and false otherwise */ bool FileProperty::isLoadProperty() const { return m_action == Load || m_action == OptionalLoad; } /** * Check if this is a Save property * @returns True if the property is a Save property and false otherwise */ bool FileProperty::isSaveProperty() const { return m_action == Save || m_action == OptionalSave; } /** * Check if this is a directory selection property * @returns True if the property is a Directory property */ bool FileProperty::isDirectoryProperty() const { return m_action == Directory || m_action == OptionalDirectory; } /** * Check if this property is optional * @returns True if the property is optinal, false otherwise */ bool FileProperty::isOptional() const { return (m_action == OptionalLoad || m_action == OptionalSave || m_action == OptionalDirectory); } /** * Set the value of the property * @param propValue :: The value here is treated as relating to a filename * @return A string indicating the outcome of the attempt to set the property. * An empty string indicates success. */ std::string FileProperty::setValue(const std::string &propValue) { std::string strippedValue = Kernel::Strings::strip(propValue); // Empty value is allowed if optional if (strippedValue.empty()) { PropertyWithValue<std::string>::setValue(""); return isEmptyValueValid(); } // Expand user variables, if there are any strippedValue = expandUser(strippedValue); // If this looks like an absolute path then don't do any searching but make // sure the // directory exists for a Save property if (Poco::Path(strippedValue).isAbsolute()) { std::string error; if (isSaveProperty()) { error = createDirectory(strippedValue); if (!error.empty()) return error; } return PropertyWithValue<std::string>::setValue(strippedValue); } std::string errorMsg; // For relative paths, differentiate between load and save types if (isLoadProperty()) { errorMsg = setLoadProperty(strippedValue); } else { errorMsg = setSaveProperty(strippedValue); } return errorMsg; } /** * Checks whether the current value is considered valid. Use the validator * unless the * value is an empty string. In this case it is only valid if the property is * not optional * @returns an empty string if the property is valid, otherwise contains an * error message */ std::string FileProperty::isValid() const { const std::string &value = (*this)(); if (value.empty()) { return isEmptyValueValid(); } else { return PropertyWithValue<std::string>::isValid(); } } /** * @returns a string depending on whether an empty value is valid */ std::string FileProperty::isEmptyValueValid() const { if (isOptional()) { return ""; } else { return "No file specified."; } } /** * Do the allowed values match the facility preference extensions for run files * @returns True if the extensions match those in the facility's preference list * for * run file extensions, false otherwise */ bool FileProperty::extsMatchRunFiles() { bool match(false); try { Kernel::FacilityInfo facilityInfo = Kernel::ConfigService::Instance().getFacility(); const std::vector<std::string> facilityExts = facilityInfo.extensions(); auto facilityExtsBegin = facilityExts.cbegin(); auto facilityExtsEnd = facilityExts.cend(); const std::vector<std::string> allowedExts = this->allowedValues(); for (const auto &ext : allowedExts) { if (std::find(facilityExtsBegin, facilityExtsEnd, ext) != facilityExtsEnd) { match = true; break; } } } catch (Mantid::Kernel::Exception::NotFoundError &) { } // facility could not be found, do nothing this will return the default // match of false return match; } /** * Handles the filename if this is a load property * @param propValue :: The filename to treat as a filepath to be loaded * @returns A string contain the result of the operation, empty if successful. */ std::string FileProperty::setLoadProperty(const std::string &propValue) { // determine the initial version of foundFile std::string foundFile; if ((propValue == m_oldLoadPropValue) && (!m_oldLoadFoundFile.empty())) { foundFile = m_oldLoadFoundFile; } // cache the new version of propValue m_oldLoadPropValue = propValue; // if foundFile is not empty then it is the cached file if (foundFile.empty()) { if (m_runFileProp) // runfiles go through FileFinder::findRun { std::vector<std::string> allowedExts(allowedValues()); std::vector<std::string> exts; if (!m_defaultExt.empty()) { addExtension(m_defaultExt, exts); std::string lower(m_defaultExt); std::transform(m_defaultExt.begin(), m_defaultExt.end(), lower.begin(), tolower); addExtension(lower, exts); std::string upper(m_defaultExt); std::transform(m_defaultExt.begin(), m_defaultExt.end(), upper.begin(), toupper); addExtension(upper, exts); } for (auto &ext : allowedExts) { std::string lower(ext); std::string upper(ext); std::transform(ext.begin(), ext.end(), lower.begin(), tolower); std::transform(ext.begin(), ext.end(), upper.begin(), toupper); addExtension(ext, exts); addExtension(lower, exts); addExtension(upper, exts); } foundFile = FileFinder::Instance().findRun(propValue, exts); } else // non-runfiles go through FileFinder::getFullPath { foundFile = FileFinder::Instance().getFullPath(propValue); } } // cache the new version of foundFile m_oldLoadFoundFile = foundFile; if (foundFile.empty()) { return PropertyWithValue<std::string>::setValue(propValue); } else { return PropertyWithValue<std::string>::setValue(foundFile); } } /** * Handles the filename if this is a save property * @param propValue :: The filename to treat as a filepath to be saved * @returns A string contain the result of the operation, empty if successful. */ std::string FileProperty::setSaveProperty(const std::string &propValue) { if (propValue.empty()) { if (m_action == OptionalSave) { return PropertyWithValue<std::string>::setValue(""); } else return "Empty filename not allowed."; } std::string errorMsg; // We have a relative save path so just prepend the path that is in the // 'defaultsave.directory' // Note that this catches the Poco::NotFoundException and returns an empty // string in that case std::string save_path = ConfigService::Instance().getString("defaultsave.directory"); Poco::Path save_dir; if (save_path.empty()) { save_dir = Poco::Path(propValue).parent(); // If we only have a stem filename, parent() will make save_dir empty and // then Poco::File throws if (save_dir.toString().empty()) { save_dir = Poco::Path::current(); } } else { save_dir = Poco::Path(save_path).makeDirectory(); } errorMsg = createDirectory(save_dir.toString()); if (errorMsg.empty()) { std::string fullpath = save_dir.resolve(propValue).toString(); errorMsg = PropertyWithValue<std::string>::setValue(fullpath); } return errorMsg; } /** * Create a given directory if it does not already exist. * @param path :: The path to the directory, which can include file stem * @returns A string indicating a problem if one occurred */ std::string FileProperty::createDirectory(const std::string &path) const { Poco::Path stempath(path); if (stempath.isFile()) { stempath.makeParent(); } if (!stempath.toString().empty()) { Poco::File stem(stempath); if (!stem.exists()) { try { stem.createDirectories(); } catch (Poco::Exception &e) { std::stringstream msg; msg << "Failed to create directory \"" << stempath.toString() << "\": " << e.what(); return msg.str(); } } } else { return "Invalid directory."; } return ""; // everything went fine } /** * Check file extension to see if a lower- or upper-cased version will also * match if the given one does not exist * @param filepath :: A filename whose extension is checked and converted to * lower/upper case if necessary. * @returns The new filename */ std::string FileProperty::convertExtension(const std::string &filepath) const { Poco::Path fullpath(filepath); std::string ext = fullpath.getExtension(); if (ext.empty()) return filepath; const size_t nchars = ext.size(); for (size_t i = 0; i < nchars; ++i) { int c = static_cast<int>(ext[i]); if (std::islower(c)) { ext[i] = static_cast<char>(std::toupper(c)); } else if (std::isupper(c)) { ext[i] = static_cast<char>(std::tolower(c)); } else { } } fullpath.setExtension(ext); return fullpath.toString(); } /** Expand user variables in file path. * On UNIX, ~ is replaced by the user's home directory, if found. * On Windows, a combination of HOMEPATH and HOMEDRIVE will be used. * If the path contains no user variables, or expansion fails, the path is * returned unchanged, for errors to be dealt with by the calling function * @param filepath The path to expand * @return The expanded path */ std::string FileProperty::expandUser(const std::string &filepath) const { auto start = filepath.begin(); if (*start != '~') // No user variables in filepath return filepath; // Position of the first slash after the variable auto nextSlash = find_if(start, filepath.end(), &isSlash); // UNIX-style home variable (ie ~/blah) if (start + 1 == nextSlash){ char *home; if (!(home = std::getenv("HOME"))) if (!(home = std::getenv("USERPROFILE"))) if (!(home = std::getenv("HOMEPATH"))) // Couldn't find any relevant environment variables return filepath; std::string homeStr = std::string(home); return homeStr + std::string(nextSlash, filepath.end()); } // Windows-style home variables (ie ~user/blah) char *drive = std::getenv("HomeDrive"); char *home = std::getenv("HomePath"); if (drive && home){ std::string driveStr = std::string(drive); std::string homeStr = std::string(home); return driveStr + homeStr + std::string(nextSlash, filepath.end()); } // Couldn't find enough relevant environment variables return filepath; } bool FileProperty::isSlash(const char &c){ return c == '/' || c == '\\'; } } }