Skip to content

Commit acd78ce

Browse files
authored
Replace create_temp_directory with the new create_temporary_directory (#198)
* Replace create_temp_directory with the new create_temporary_directory - The newly added `create_temporary_directory(..)` uses std::filesystem::path and doesn't have platform-specific code. - Also deprecated `create_temp_directory(..)` and `temp_directory_path` Signed-off-by: Michael Orlov <[email protected]>
1 parent bdb90b4 commit acd78ce

File tree

3 files changed

+74
-13
lines changed

3 files changed

+74
-13
lines changed

include/rcpputils/filesystem_helper.hpp

+23-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#define RCPPUTILS__FILESYSTEM_HELPER_HPP_
4141

4242
#include <cstdint>
43+
#include <filesystem>
4344
#include <string>
4445
#include <vector>
4546

@@ -269,6 +270,7 @@ RCPPUTILS_PUBLIC bool exists(const path & path_to_check);
269270
*
270271
* \return A path to a directory for storing temporary files and directories.
271272
*/
273+
[[deprecated("Please use std::filesystem::temp_directory_path() instead")]]
272274
RCPPUTILS_PUBLIC path temp_directory_path();
273275

274276
/**
@@ -284,9 +286,29 @@ RCPPUTILS_PUBLIC path temp_directory_path();
284286
*
285287
* \throws std::system_error If any OS APIs do not succeed.
286288
*/
289+
[[deprecated("Please use rcpputils::fs::create_temporary_directory(..) instead")]]
287290
RCPPUTILS_PUBLIC path create_temp_directory(
288291
const std::string & base_name,
289-
const path & parent_path = temp_directory_path());
292+
const path & parent_path = path(std::filesystem::temp_directory_path().generic_string()));
293+
294+
/// \brief Construct a uniquely named temporary directory, in "parent", with format base_nameXXXXXX
295+
/// The output, if successful, is guaranteed to be a newly-created directory.
296+
/// The underlying implementation keeps generating paths until one that does not exist is found or
297+
/// until the number of iterations exceeded the maximum tries.
298+
/// This guarantees that there will be no existing files in the returned directory.
299+
/// \param[in] base_name User-specified portion of the created directory.
300+
/// \param[in] parent_path The parent path of the directory that will be created.
301+
/// \param[in] max_tries The maximum number of tries to find a unique directory (default 1000)
302+
/// \return A path to a newly created directory with base_name and a 6-character unique suffix.
303+
/// \throws std::invalid_argument If base_name contain directory-separator defined as
304+
/// std::filesystem::path::preferred_separator.
305+
/// \throws std::system_error If any OS APIs do not succeed.
306+
/// \throws std::runtime_error If the number of the iterations exceeds the maximum tries and
307+
/// a unique directory is not found.
308+
RCPPUTILS_PUBLIC std::filesystem::path create_temporary_directory(
309+
const std::string & base_name,
310+
const std::filesystem::path & parent_path = std::filesystem::temp_directory_path(),
311+
size_t max_tries = 1000);
290312

291313
/**
292314
* \brief Return current working directory.

src/filesystem_helper.cpp

+32
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@
4343
#include <algorithm>
4444
#include <climits>
4545
#include <cstring>
46+
#include <random>
4647
#include <string>
4748
#include <system_error>
49+
#include <stdexcept>
4850
#include <vector>
4951

5052
#ifdef _WIN32
@@ -341,6 +343,36 @@ path create_temp_directory(const std::string & base_name, const path & parent_pa
341343
return final_path;
342344
}
343345

346+
std::filesystem::path create_temporary_directory(
347+
const std::string & base_name, const std::filesystem::path & parent_path, size_t max_tries)
348+
{
349+
if (base_name.find(std::filesystem::path::preferred_separator) != std::string::npos) {
350+
throw std::invalid_argument("The base_name contain directory-separator");
351+
}
352+
// mersenne twister random generator engine seeded with the std::random_device
353+
std::mt19937 random_generator(std::random_device{}());
354+
std::uniform_int_distribution<> distribution(0, 0xFFFFFF);
355+
std::filesystem::path path_to_temp_dir;
356+
constexpr size_t kSuffixLength = 7; // 6 chars + 1 null terminator
357+
char random_suffix_str[kSuffixLength];
358+
size_t current_iteration = 0;
359+
while (true) {
360+
snprintf(random_suffix_str, kSuffixLength, "%06x", distribution(random_generator));
361+
const std::string random_dir_name = base_name + random_suffix_str;
362+
path_to_temp_dir = parent_path / random_dir_name;
363+
// true if the directory was newly created.
364+
if (std::filesystem::create_directories(path_to_temp_dir)) {
365+
break;
366+
}
367+
if (current_iteration == max_tries) {
368+
throw std::runtime_error(
369+
"Exceeded maximum allowed iterations to find non-existing directory");
370+
}
371+
current_iteration++;
372+
}
373+
return path_to_temp_dir;
374+
}
375+
344376
path current_path()
345377
{
346378
#ifdef _WIN32

test/test_filesystem_helper.cpp

+19-12
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ TEST(TestFilesystemHelper, filesystem_manipulation)
308308
EXPECT_TRUE(rcpputils::fs::remove(dir));
309309
EXPECT_FALSE(rcpputils::fs::exists(file));
310310
EXPECT_FALSE(rcpputils::fs::exists(dir));
311-
auto temp_dir = rcpputils::fs::temp_directory_path();
311+
auto temp_dir_std = std::filesystem::temp_directory_path();
312+
rcpputils::fs::path temp_dir = rcpputils::fs::path(temp_dir_std.generic_string());
312313
temp_dir = temp_dir / "rcpputils" / "test_folder";
313314
EXPECT_FALSE(rcpputils::fs::exists(temp_dir));
314315
EXPECT_TRUE(rcpputils::fs::create_directories(temp_dir));
@@ -446,13 +447,14 @@ TEST(TestFilesystemHelper, stream_operator)
446447
ASSERT_EQ(s.str(), "barfoo");
447448
}
448449

449-
TEST(TestFilesystemHelper, create_temp_directory)
450+
TEST(TestFilesystemHelper, create_temporary_directory)
450451
{
451452
// basic usage
452453
{
453454
const std::string basename = "test_base_name";
454455

455-
const auto tmpdir1 = rcpputils::fs::create_temp_directory(basename);
456+
const auto tmpdir1_std = rcpputils::fs::create_temporary_directory(basename);
457+
rcpputils::fs::path tmpdir1(tmpdir1_std.generic_string());
456458
EXPECT_TRUE(tmpdir1.exists());
457459
EXPECT_TRUE(tmpdir1.is_directory());
458460

@@ -464,7 +466,8 @@ TEST(TestFilesystemHelper, create_temp_directory)
464466
EXPECT_TRUE(rcpputils::fs::exists(tmp_file));
465467
EXPECT_TRUE(rcpputils::fs::is_regular_file(tmp_file));
466468

467-
const auto tmpdir2 = rcpputils::fs::create_temp_directory(basename);
469+
const auto tmpdir2_std = rcpputils::fs::create_temporary_directory(basename);
470+
rcpputils::fs::path tmpdir2(tmpdir2_std.generic_string());
468471
EXPECT_TRUE(tmpdir2.exists());
469472
EXPECT_TRUE(tmpdir2.is_directory());
470473

@@ -477,16 +480,21 @@ TEST(TestFilesystemHelper, create_temp_directory)
477480
// bad names
478481
{
479482
if (is_win32) {
480-
EXPECT_THROW(rcpputils::fs::create_temp_directory("illegalchar?"), std::system_error);
483+
EXPECT_THROW(rcpputils::fs::create_temporary_directory("illegalchar?"), std::system_error);
484+
EXPECT_THROW(rcpputils::fs::create_temporary_directory("base\\name"), std::invalid_argument);
481485
} else {
482-
EXPECT_THROW(rcpputils::fs::create_temp_directory("base/name"), std::system_error);
486+
EXPECT_THROW(rcpputils::fs::create_temporary_directory("base/name"), std::invalid_argument);
483487
}
484488
}
485489

486490
// newly created paths
487491
{
488492
const auto new_relative = rcpputils::fs::current_path() / "child1" / "child2";
489-
const auto tmpdir = rcpputils::fs::create_temp_directory("base_name", new_relative);
493+
const auto tmpdir_std = rcpputils::fs::create_temporary_directory(
494+
"base_name",
495+
std::filesystem::path(new_relative.string()));
496+
rcpputils::fs::path tmpdir(tmpdir_std.generic_string());
497+
490498
EXPECT_TRUE(tmpdir.exists());
491499
EXPECT_TRUE(tmpdir.is_directory());
492500
EXPECT_TRUE(rcpputils::fs::remove_all(tmpdir));
@@ -495,15 +503,14 @@ TEST(TestFilesystemHelper, create_temp_directory)
495503
// edge case inputs
496504
{
497505
// Provided no base name we should still get a path with the 6 unique template chars
498-
const auto tmpdir_emptybase = rcpputils::fs::create_temp_directory("");
506+
const auto tmpdir_emptybase = rcpputils::fs::create_temporary_directory("");
499507
EXPECT_EQ(tmpdir_emptybase.filename().string().size(), 6u);
500508

501-
// Empty path doesn't exist and cannot be created
502-
EXPECT_THROW(rcpputils::fs::create_temp_directory("basename", path()), std::system_error);
503-
504509
// With the template string XXXXXX already in the name, it will still be there, the unique
505510
// portion is appended to the end.
506-
const auto tmpdir_template_in_name = rcpputils::fs::create_temp_directory("base_XXXXXX");
511+
const auto tmpdir_template_in_name_std =
512+
rcpputils::fs::create_temporary_directory("base_XXXXXX");
513+
rcpputils::fs::path tmpdir_template_in_name(tmpdir_template_in_name_std.generic_string());
507514
EXPECT_TRUE(tmpdir_template_in_name.exists());
508515
EXPECT_TRUE(tmpdir_template_in_name.is_directory());
509516
// On Linux, it will not replace the base_name Xs, only the final 6 that the function appends.

0 commit comments

Comments
 (0)