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