-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3a1e77e
commit 6bec176
Showing
4 changed files
with
624 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.