Skip to content

Commit

Permalink
initial post rename commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nyoungbq authored and imikejackson committed Feb 7, 2024
1 parent 3a1e77e commit 6bec176
Show file tree
Hide file tree
Showing 4 changed files with 624 additions and 0 deletions.
146 changes: 146 additions & 0 deletions src/complex/Common/AtomicFile.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
#include "AtomicFile.hpp"

#include "complex/Utilities/FilterUtilities.hpp"
#include "complex/Utilities/FileUtilities.hpp"

#include <fmt/format.h>

#include <random>

using namespace complex;

namespace
{
constexpr std::array<char, 62> chars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
std::string randomDirName()
{
std::mt19937_64 gen(static_cast<std::mt19937_64::result_type>(std::chrono::steady_clock::now().time_since_epoch().count()));
std::uniform_int_distribution<uint32> dist(0, chars.size() - 1);

std::string randomDir = "";
for(uint32 i = 0; i < 24; i++)
{
randomDir += chars[dist(gen)];
}
return randomDir;
}
} // namespace

AtomicFile::AtomicFile(const std::string& filename, bool autoCommit)
: m_FilePath(fs::path(filename))
, m_AutoCommit(autoCommit)
, m_Result({})
{
// If the path is relative, then make it absolute
if(!m_FilePath.is_absolute())
{
try
{
m_FilePath = fs::absolute(m_FilePath);
} catch(const std::filesystem::filesystem_error& error)
{
m_Result = MergeResults(m_Result, MakeErrorResult(-15780, fmt::format("When attempting to create an absolute path, AtomicFile encountered the following error: '{}'", error.what())));
}
}

// Validate write permissions
auto result = ValidateDirectoryWritePermission(m_FilePath, true);
if(result.invalid())
{
m_Result = MergeResults(m_Result, result);
}

m_TempFilePath = fs::path(fmt::format("{}/{}/{}", m_FilePath.parent_path().string(), ::randomDirName(), m_FilePath.filename().string()));
result = createOutputDirectories();
if(result.invalid())
{
m_Result = MergeResults(m_Result, result);
}
}

AtomicFile::AtomicFile(fs::path&& filepath, bool autoCommit)
: m_FilePath(std::move(filepath))
, m_AutoCommit(autoCommit)
, m_Result({})
{
// If the path is relative, then make it absolute
if(!m_FilePath.is_absolute())
{
try
{
m_FilePath = fs::absolute(m_FilePath);
} catch(const std::filesystem::filesystem_error& error)
{
m_Result = MergeResults(m_Result, MakeErrorResult(-15780, fmt::format("When attempting to create an absolute path, AtomicFile encountered the following error: '{}'", error.what())));
}
}

// Validate write permissions
auto result = ValidateDirectoryWritePermission(m_FilePath, true);
if(result.invalid())
{
m_Result = MergeResults(m_Result, result);
}

m_TempFilePath = fs::path(fmt::format("{}/{}/{}", m_FilePath.parent_path().string(), ::randomDirName(), m_FilePath.filename().string()));
result = createOutputDirectories();
if(result.invalid())
{
m_Result = MergeResults(m_Result, result);
}
}

AtomicFile::~AtomicFile()
{
if(m_AutoCommit)
{
commit();
}
if(fs::exists(m_TempFilePath) || fs::exists(m_TempFilePath.parent_path()))
{
removeTempFile();
}
}

fs::path AtomicFile::tempFilePath() const
{
return m_TempFilePath;
}

void AtomicFile::commit() const
{
if(!fs::exists(m_TempFilePath))
{
throw std::runtime_error(m_TempFilePath.string() + " does not exist");
}

fs::rename(m_TempFilePath, m_FilePath);
}

void AtomicFile::setAutoCommit(bool value)
{
m_AutoCommit = value;
}

bool AtomicFile::getAutoCommit() const
{
return m_AutoCommit;
}

void AtomicFile::removeTempFile() const
{
fs::remove_all(m_TempFilePath.parent_path());
}

Result<> AtomicFile::getResult() const
{
return m_Result;
}

Result<> AtomicFile::createOutputDirectories()
{
// Make sure any directory path is also available as the user may have just typed
// in a path without actually creating the full path
return CreateOutputDirectories(m_TempFilePath.parent_path());
}
46 changes: 46 additions & 0 deletions src/complex/Common/AtomicFile.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#include "complex/complex_export.hpp"

#include "complex/Common/Result.hpp"

#include <filesystem>
#include <string>

namespace complex
{
namespace fs = std::filesystem;

/**
* @class AtomicFile
* @brief The AtomicFile class accepts a filepath which it stores and
* used to create a temporary file. This temporary can be written to in place
* of the original file path. Upon commit() the temporary file will be moved to
* the end location passed in originally. By enabling autoCommit the temporary file
* will be swapped upon object destruction. The temporary file will always be deleted
* upon object destruction.
*/
class COMPLEX_EXPORT AtomicFile
{
public:
explicit AtomicFile(const std::string& filename, bool autoCommit = false);
explicit AtomicFile(fs::path&& filename, bool autoCommit = false);

~AtomicFile();

fs::path tempFilePath() const;
void commit() const;
void setAutoCommit(bool value);
bool getAutoCommit() const;
void removeTempFile() const;
Result<> getResult() const;

private:
fs::path m_FilePath;
fs::path m_TempFilePath;
Result<> m_Result;
bool m_AutoCommit = false;

Result<> createOutputDirectories();
};
} // namespace complex
173 changes: 173 additions & 0 deletions src/complex/Parameters/FileSystemPathParameter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#include "FileSystemPathParameter.hpp"

#include "complex/Common/Any.hpp"
#include "complex/Common/StringLiteral.hpp"
#include "complex/Utilities/StringUtilities.hpp"
#include "complex/Utilities/FileUtilities.hpp"

#include <fmt/core.h>

#include <nlohmann/json.hpp>

#include <cctype>
#include <filesystem>
#include <iostream>
#include <stdexcept>

#ifdef _WIN32
#include <io.h>
#define FSPP_ACCESS_FUNC_NAME _access
#else
#include <unistd.h>
#define FSPP_ACCESS_FUNC_NAME access
#endif

namespace fs = std::filesystem;

using namespace complex;

namespace complex
{
//-----------------------------------------------------------------------------
FileSystemPathParameter::FileSystemPathParameter(const std::string& name, const std::string& humanName, const std::string& helpText, const ValueType& defaultValue,
const ExtensionsType& extensionsType, PathType pathType, bool acceptAllExtensions)
: ValueParameter(name, humanName, helpText)
, m_DefaultValue(defaultValue)
, m_PathType(pathType)
, m_AvailableExtensions(extensionsType)
, m_acceptAllExtensions(acceptAllExtensions)
{
ExtensionsType validatedExtensions;
for(const auto& ext : m_AvailableExtensions)
{
if(ext.empty())
{
throw std::runtime_error("FileSystemPathParameter: One of the given extensions was empty. The filter is required to use non-emtpy extensions");
}
if(ext.at(0) != '.')
{
validatedExtensions.insert('.' + complex::StringUtilities::toLower(ext));
}
else
{
validatedExtensions.insert(complex::StringUtilities::toLower(ext));
}
}
m_AvailableExtensions = validatedExtensions;
}

//-----------------------------------------------------------------------------
Uuid FileSystemPathParameter::uuid() const
{
return ParameterTraits<FileSystemPathParameter>::uuid;
}

//-----------------------------------------------------------------------------
IParameter::AcceptedTypes FileSystemPathParameter::acceptedTypes() const
{
return {typeid(ValueType)};
}

//-----------------------------------------------------------------------------
bool FileSystemPathParameter::acceptAllExtensions() const
{
return m_acceptAllExtensions;
}

//-----------------------------------------------------------------------------
nlohmann::json FileSystemPathParameter::toJson(const std::any& value) const
{
const auto& path = GetAnyRef<ValueType>(value);
nlohmann::json json = path.string();
return json;
}

//-----------------------------------------------------------------------------
Result<std::any> FileSystemPathParameter::fromJson(const nlohmann::json& json) const
{
static constexpr StringLiteral prefix = "FilterParameter 'FileSystemPathParameter' JSON Error: ";

if(!json.is_string())
{
return MakeErrorResult<std::any>(-2, fmt::format("{}JSON value for key '{}' is not a string", prefix, name()));
}
auto pathString = json.get<std::string>();
std::filesystem::path path = pathString;
return {{path}};
}

//-----------------------------------------------------------------------------
IParameter::UniquePointer FileSystemPathParameter::clone() const
{
return std::make_unique<FileSystemPathParameter>(name(), humanName(), helpText(), m_DefaultValue, m_AvailableExtensions, m_PathType, m_acceptAllExtensions);
}

//-----------------------------------------------------------------------------
std::any FileSystemPathParameter::defaultValue() const
{
return defaultPath();
}

//-----------------------------------------------------------------------------
typename FileSystemPathParameter::ValueType FileSystemPathParameter::defaultPath() const
{
return m_DefaultValue;
}

//-----------------------------------------------------------------------------
FileSystemPathParameter::PathType FileSystemPathParameter::getPathType() const
{
return m_PathType;
}

//-----------------------------------------------------------------------------
FileSystemPathParameter::ExtensionsType FileSystemPathParameter::getAvailableExtensions() const
{
return m_AvailableExtensions;
}

//-----------------------------------------------------------------------------
Result<> FileSystemPathParameter::validate(const std::any& value) const
{
const auto& path = GetAnyRef<ValueType>(value);
return validatePath(path);
}

//-----------------------------------------------------------------------------
Result<> FileSystemPathParameter::validatePath(const ValueType& path) const
{
const std::string prefix = fmt::format("\n Parameter Name: '{}'\n Parameter Key: '{}'\n Validation Error: ", humanName(), name());

if(path.empty())
{
return complex::MakeErrorResult(-3001, fmt::format("{} File System Path must not be empty", prefix));
}

if(!m_acceptAllExtensions && (m_PathType == complex::FileSystemPathParameter::PathType::InputFile || m_PathType == complex::FileSystemPathParameter::PathType::OutputFile))
{
if(!path.has_extension())
{
return {nonstd::make_unexpected(std::vector<Error>{{-3002, fmt::format("{} File System Path must include a file extension", prefix)}})};
}
std::string lowerExtension = complex::StringUtilities::toLower(path.extension().string());
if(path.has_extension() && !m_AvailableExtensions.empty() && m_AvailableExtensions.find(lowerExtension) == m_AvailableExtensions.end())
{
return {nonstd::make_unexpected(std::vector<Error>{{-3003, fmt::format("{} File extension '{}' is not a valid file extension", prefix, path.extension().string())}})};
}
}

switch(m_PathType)
{
case complex::FileSystemPathParameter::PathType::InputFile:
return ValidateInputFile(path);
case complex::FileSystemPathParameter::PathType::InputDir:
return ValidateInputDir(path);
case complex::FileSystemPathParameter::PathType::OutputFile:
return ValidateOutputFile(path);
case complex::FileSystemPathParameter::PathType::OutputDir:
return ValidateOutputDir(path);
}

return {};
}
} // namespace complex
Loading

0 comments on commit 6bec176

Please sign in to comment.