Line data Source code
1 : // TempDir 2 : // SPDX-FileCopyrightText: 2024-present Benno Waldhauer 3 : // SPDX-License-Identifier: MIT 4 : 5 : #pragma once 6 : 7 : #include <chrono> 8 : #include <filesystem> 9 : #include <functional> 10 : #include <iostream> 11 : #include <random> 12 : #include <string> 13 : 14 : namespace bw::tempdir 15 : { 16 : namespace fs = std::filesystem; 17 : 18 2 : void log(const std::string& message) { std::cout << message << std::endl; } 19 : 20 : // Exception class for errors related to TempDir operations. 21 : // E.g. for issues encountered during the creation, usage, or deletion of temporary directories. 22 : // It wraps another exception to retain the original error message. 23 : class TempDirException : public std::runtime_error 24 : { 25 : public: 26 4 : TempDirException(const std::exception& ex) 27 4 : : std::runtime_error(std::string("TempDirExcepton: ") + ex.what()) 28 : { 29 4 : } 30 : }; 31 : 32 : // enum of cleanup modes for TempDir 33 : // When a scope with a temporary directory completes, it might be 34 : // useful in some cases to be able to view the contents of the temporary directory used by the 35 : // scope. Cleanup allows you to control how a TempDir is cleaned up. 36 : enum class Cleanup 37 : { 38 : always, // Always clean up a temporary directory after TempDir goes out of scope. 39 : on_success, // Clean up a temporary directory after TempDir goes out of scope 40 : // without uncaught exceptions. 41 : never // Never clean up a temporary directory after TempDir goes out of scope. 42 : }; 43 : 44 : // struct holding configuration options for TempDir 45 : // It allows to specify the root path of temporary directory, the cleanup and logging behavior 46 : // as well as the temporary directory prefix. 47 : struct Config 48 : { 49 : fs::path root_path = fs::temp_directory_path(); 50 : Cleanup cleanup = Cleanup::always; 51 : std::string temp_dir_prefix = "temp_dir"; 52 : std::function<void(const std::string&)> log_impl; 53 : 54 6 : Config& set_root_path(const fs::path& root_path) 55 : { 56 6 : this->root_path = root_path; 57 6 : return *this; 58 : } 59 : 60 7 : Config& set_cleanup(Cleanup cleanup) 61 : { 62 7 : this->cleanup = cleanup; 63 7 : return *this; 64 : } 65 : 66 1 : Config& set_temp_dir_prefix(const std::string& temp_dir_prefix) 67 : { 68 1 : this->temp_dir_prefix = temp_dir_prefix; 69 1 : return *this; 70 : } 71 : 72 1 : Config& enable_logging() 73 : { 74 1 : this->log_impl = bw::tempdir::log; 75 1 : return *this; 76 : } 77 : 78 1 : Config& enable_logging(std::function<void(const std::string&)> log_impl) 79 : { 80 1 : this->log_impl = log_impl; 81 1 : return *this; 82 : } 83 : }; 84 : 85 : // TempDir manages temporary directories with automatic cleanup based on user-defined policies. 86 : // 87 : // The TempDir class is designed to simplify the creation and management of temporary directories. 88 : // It supports automatic cleanup based on configurable policies (e.g., always cleanup, cleanup only 89 : // on successful execution, or never cleanup). The class ensures proper handling of errors during 90 : // directory creation and cleanup, and on demand logs relevant messages for each operation. 91 : // 92 : // The directory is created in a user-specified or default root path and given a unique name 93 : // generated based on a timestamp and a random number. Errors related to directory operations 94 : // are wrapped in TempDirException. 95 : // 96 : // Cleanup will be automatically handled based on the configured policy when the TempDir object goes 97 : // out of scope or when cleanup method is called explicitly 98 : class TempDir 99 : { 100 : public: 101 : // Constructs a TempDir with a specified root path 102 : // where the temporary directory will be created. 103 5 : explicit TempDir(fs::path root_path) : TempDir(Config().set_root_path(root_path)) {} 104 : 105 : // Constructs a TempDir with a specified root path and cleanup policy. 106 : // root_path: The root path where the temporary directory will be created. 107 : // cleanup: The cleanup policy to apply when the object is destroyed. 108 1 : explicit TempDir(fs::path root_path, Cleanup cleanup) 109 1 : : TempDir(Config().set_root_path(root_path).set_cleanup(cleanup)) 110 : { 111 1 : } 112 : 113 : // Constructs a TempDir with a specified cleanup policy 114 : // which will be applied when the object is destroyed. 115 6 : explicit TempDir(Cleanup cleanup) : TempDir(Config().set_cleanup(cleanup)) {} 116 : 117 : // Constructs a TempDir with a fully specified configuration for the TempDir, 118 : // including path, prefix, cleanup policy, and logging. 119 : // If an error occurs during construction, a TempDirException is thrown. 120 17 : explicit TempDir(Config config = {}) : _config(config) 121 : { 122 : try 123 : { 124 17 : _temp_dir = config.root_path / generate_dir_name(); 125 17 : fs::create_directories(_temp_dir); 126 16 : log("TempDir create '" + _temp_dir.string() + "'"); 127 : } 128 1 : catch (const std::exception& ex) 129 : { 130 1 : log("TempDir creation of '" + _temp_dir.string() + "' failed. Error: " + ex.what()); 131 1 : throw TempDirException(ex); 132 1 : } 133 18 : } 134 : 135 : // Destructor that handles automatic cleanup based on the configured policy. 136 : // 137 : // Attempts to clean up the temporary directory if the cleanup policy allows it. 138 : // Errors during cleanup are logged but not rethrown 139 17 : ~TempDir() 140 : { 141 : try 142 : { 143 17 : cleanup(); 144 : } 145 2 : catch (const std::exception& e) 146 : { 147 : // do nothing as rethrowing is not allowed in destructor 148 : // error was already logged in cleanup method 149 2 : } 150 17 : } 151 : 152 : // Copying TempDir is disabled 153 : TempDir(const TempDir&) = delete; 154 : TempDir& operator=(const TempDir&) = delete; 155 : 156 : // Moving TempDir is enabeld 157 1 : TempDir(TempDir&&) = default; 158 : TempDir& operator=(TempDir&&) = default; 159 : 160 : // Returns the path of the managed temporary directory. 161 22 : const std::filesystem::path& path() const { return _temp_dir; } 162 : 163 : // Manually triggers cleanup of the temporary directory. 164 : // Attempts to delete the directory and its contents based on the configured 165 : // cleanup policy. If an error occurs during cleanup, a TempDirException is thrown. 166 18 : void cleanup() 167 : { 168 18 : if (!fs::exists(_temp_dir)) 169 4 : return; 170 : 171 14 : bool always_cleanup = _config.cleanup == Cleanup::always; 172 14 : bool on_sucess_cleanup_no_exception = 173 14 : _config.cleanup == Cleanup::on_success && std::uncaught_exceptions() <= 0; 174 : 175 14 : if (!always_cleanup && !on_sucess_cleanup_no_exception) 176 : { 177 3 : log("TempDir keep '" + _temp_dir.string() + "'"); 178 3 : return; 179 : } 180 : 181 : try 182 : { 183 11 : fs::remove_all(_temp_dir); 184 8 : log("TempDir remove '" + _temp_dir.string() + "'"); 185 : } 186 3 : catch (const std::exception& ex) 187 : { 188 3 : log("TempDir removal of '" + _temp_dir.string() + "' failed. Error: " + ex.what()); 189 3 : throw TempDirException(ex); 190 3 : } 191 : } 192 : 193 : private: 194 : // Generates a unique name for the temporary directory. 195 17 : std::string generate_dir_name() 196 : { 197 : using namespace std::chrono; 198 : 199 17 : std::random_device rd; 200 17 : std::mt19937 gen(rd()); 201 17 : std::uniform_int_distribution<> dist(10000, 99999); 202 17 : int random_number = dist(gen); 203 : 204 17 : auto now = system_clock::now(); 205 17 : auto timestamp = duration_cast<milliseconds>(now.time_since_epoch()).count(); 206 : 207 17 : std::string ts = std::to_string(timestamp); 208 17 : std::string rn = std::to_string(random_number); 209 51 : return _config.temp_dir_prefix + "_" + ts + "_" + rn; 210 17 : } 211 : 212 : // Logs a message using the configured logging implementation. 213 31 : void log(const std::string& message) 214 : { 215 31 : if (_config.log_impl) 216 4 : _config.log_impl(message); 217 31 : } 218 : 219 : std::filesystem::path _temp_dir; 220 : Config _config; 221 : }; 222 : 223 : } // namespace bw::tempdir