Cbeam
Loading...
Searching...
No Matches
path.hpp
Go to the documentation of this file.
1/*
2Copyright (c) 2025 acrion innovations GmbH
3Authors: Stefan Zipproth, s.zipproth@acrion.ch
4
5This file is part of Cbeam, see https://github.com/acrion/cbeam and https://cbeam.org
6
7Cbeam is offered under a commercial and under the AGPL license.
8For commercial licensing, contact us at https://acrion.ch/sales. For AGPL licensing, see below.
9
10AGPL licensing:
11
12Cbeam is free software: you can redistribute it and/or modify
13it under the terms of the GNU Affero General Public License as published by
14the Free Software Foundation, either version 3 of the License, or
15(at your option) any later version.
16
17Cbeam is distributed in the hope that it will be useful,
18but WITHOUT ANY WARRANTY; without even the implied warranty of
19MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20GNU Affero General Public License for more details.
21
22You should have received a copy of the GNU Affero General Public License
23along with Cbeam. If not, see <https://www.gnu.org/licenses/>.
24*/
25
26#pragma once
27
28#include <cbeam/error/runtime_error.hpp> // for cbeam::error:runtime_error
29#include <cbeam/random/generators.hpp> // for cbeam::random::random_string
30
31#include <cstddef> // for std::size_t
32
33#include <algorithm> // for std::equal
34#include <exception> // for std::exception
35#include <filesystem> // for std::filesystem::path, std::filesystem::exists, std::filesystem::operator!=, std::filesystem::is_directory, std::filesystem::operator/, std::filesystem::operator==, std::filesystem::directory_iterator
36#include <list> // for std::list
37#include <regex> // for std::regex_search, std::regex
38#include <string> // for std::allocator, std::operator+, std::operator!=, std::string_literals::operator""s, std::char_traits, std::string, std::operator==, std::basic_string, std::string_literals
39#include <system_error> // for std::error_code
40
41namespace cbeam::filesystem
42{
43 class path
44 {
45 public:
55 explicit path(const std::filesystem::path& path)
56 : _base_path(normalize(path)) {}
57
67 operator std::filesystem::path() // NOLINT(google-explicit-constructor)
68 {
69 return _base_path;
70 }
71
86 std::list<std::filesystem::path> get_subdirs(const std::regex& filter = std::regex(".*"))
87 {
88 std::list<std::filesystem::path> directories_list;
89
90 if (std::filesystem::exists(_base_path) && std::filesystem::is_directory(_base_path))
91 {
92 for (auto it = std::filesystem::directory_iterator(_base_path); it != std::filesystem::directory_iterator(); ++it)
93 {
94 bool is_directory = std::filesystem::is_directory(*it);
95 std::string path_str = it->path().string();
96
97 if (is_directory && std::regex_search(path_str, filter))
98 {
99 directories_list.emplace_back(path_str);
100 }
101 }
102 }
103
104 return directories_list;
105 }
106
122 void create_directory(bool delete_prior_creating = false) const
123 {
124 using namespace std::string_literals;
125
126 bool success = false;
127 std::string msg = "unknown error"s;
128 try
129 {
130 if (delete_prior_creating)
131 {
132 std::filesystem::remove_all(_base_path);
133 }
134
135 if (!std::filesystem::exists(_base_path))
136 {
137 // According to https://en.cppreference.com/w/cpp/filesystem/create_directory this should return true
138 // if the directory was created. But in Windows SDK 10.0.19041.0, it returns false if the path ends with
139 // a '\' and its last component has been created successfully.
140 std::filesystem::create_directories(_base_path);
141 }
142 success = std::filesystem::exists(_base_path);
143 }
144 catch (std::exception& ex)
145 {
146 msg = ex.what();
147 }
148
149 if (!success)
150 {
151 std::string err_msg("cbeam::filesystem::path::create(\"" + _base_path.string() + "\"): " + msg);
152 throw cbeam::error::runtime_error(err_msg);
153 }
154 }
155
166 void copy_to(const std::filesystem::path& target) const
167 {
168 if (!std::filesystem::exists(_base_path) || !std::filesystem::is_directory(_base_path))
169 {
170 throw cbeam::error::runtime_error("cbeam::filesystem::path::copy_to: source directory " + _base_path.string() + " does not exist or is not a directory");
171 }
172 if (std::filesystem::exists(target))
173 {
174 path(target).remove();
175 }
176
177 std::filesystem::copy(_base_path, target, std::filesystem::copy_options::recursive);
178 }
179
193 void remove() const
194 {
195 std::error_code error;
196 const std::filesystem::path temp_path = std::filesystem::path(remove_trailing_directory_separators(_base_path).string() + cbeam::random::random_string(16));
197
198 // We first try to rename, because under Windows, remove_all can result in a modified directory with partly deleted files in case an error occurs.
199 // We deliberately do not move to a unique subdir of std::filesystem::temp_directory_path(), because this might require to actually copy the complete
200 // directory tree. In contrast, we rename only the last path component, which is an atomic operation. The usage of this method implies that we
201 // should have write access to rename the directory to a path next to it (if not, removing the directory would also fail).
202 // Note that under Windows, a DLL inside the directory that is in use does not prevent the directory from being renamed by this method.
203 std::filesystem::rename(_base_path, temp_path, error);
204 if (error)
205 {
206 throw cbeam::error::runtime_error(error.message());
207 }
208 std::filesystem::remove_all(temp_path, error);
209 if (error)
210 {
211 std::filesystem::rename(temp_path, _base_path, error); // rename back to original
212 throw cbeam::error::runtime_error(error.message());
213 }
214 }
215
227 bool operator==(const path& other) const
228 {
229 return _base_path == other._base_path;
230 }
231
243 bool operator!=(const path& other) const
244 {
245 return _base_path != other._base_path;
246 }
247
259 bool operator==(const std::filesystem::path& other) const
260 {
261 return _base_path == other;
262 }
263
275 bool operator!=(const std::filesystem::path& other) const
276 {
277 return _base_path != other;
278 }
279
291 static std::filesystem::path remove_trailing_directory_separators(const std::filesystem::path& p)
292 {
293 std::string s = p.string();
294
295 // although '/' and '\' should be the only path separators, this is not defined by the C++ standard, so we check the preferred one in addition to care about potential exotic platforms
296 while (!s.empty() && (s.back() == std::filesystem::path::preferred_separator || s.back() == '/' || s.back() == '\\'))
297 {
298 s.pop_back();
299 }
300 return std::filesystem::path(s);
301 }
302
303 private:
319 static std::filesystem::path normalize(const std::filesystem::path& p)
320 {
321 using namespace std::string_literals;
322 std::filesystem::path result;
323
324 std::size_t numParent = 0;
325 auto it = p.end();
326
327 while (it != p.begin())
328 {
329 --it;
330 if (it->string() != "/" && it->string() != "\\" && !it->empty())
331 {
332 if (it->string() == "..")
333 {
334 ++numParent;
335 }
336 else if (numParent > 0)
337 {
338 --numParent;
339 }
340 else
341 {
342 std::string colon = ":"s;
343 if (std::equal(colon.rbegin(), colon.rend(), it->string().rbegin()))
344 {
345#ifdef _WIN32
346 result = (it->wstring() + std::filesystem::path::preferred_separator) / result;
347#else
348 result = (it->string() + std::filesystem::path::preferred_separator) / result;
349#endif
350 }
351 else
352 {
353 result = *it / result;
354 }
355 }
356 }
357 }
358
359 if (*p.begin() == "/"s)
360 {
361 result = *p.begin() / result;
362 }
363
364 return result;
365 }
366
367 std::filesystem::path _base_path;
368 };
369}
A Cbeam-specific runtime error that also acts like std::runtime_error.
Definition runtime_error.hpp:46
void create_directory(bool delete_prior_creating=false) const
Creates the directory specified by _base_path.
Definition path.hpp:122
void copy_to(const std::filesystem::path &target) const
Copies the directory specified by _base_path to a target location.
Definition path.hpp:166
bool operator!=(const path &other) const
Inequality comparison operator for cbeam::filesystem::path.
Definition path.hpp:243
std::list< std::filesystem::path > get_subdirs(const std::regex &filter=std::regex(".*"))
Retrieves a list of subdirectories matching a given regular expression filter.
Definition path.hpp:86
operator std::filesystem::path()
Cast operator to std::filesystem::path.
Definition path.hpp:67
bool operator==(const path &other) const
Equality comparison operator for cbeam::filesystem::path.
Definition path.hpp:227
bool operator==(const std::filesystem::path &other) const
Equality comparison operator for cbeam::filesystem::path.
Definition path.hpp:259
path(const std::filesystem::path &path)
Explicit constructor for cbeam::filesystem::path.
Definition path.hpp:55
bool operator!=(const std::filesystem::path &other) const
Inequality comparison operator for cbeam::filesystem::path.
Definition path.hpp:275
void remove() const
Removes the directory specified by _base_path.
Definition path.hpp:193
static std::filesystem::path remove_trailing_directory_separators(const std::filesystem::path &p)
Removes trailing directory separators from a given path.
Definition path.hpp:291
Defines Cbeam-specific exception types that behave like their standard counterparts....
Definition base_error.hpp:31
Facilitates file I/O, path normalization, and directory operations in a cross-platform manner....
Definition io.hpp:36
std::string random_string(std::string::size_type length, std::mt19937 &gen=default_generator())
Generates a random string of specified length.
Definition generators.hpp:67