diff --git a/ddio/CMakeLists.txt b/ddio/CMakeLists.txt index bb79eee2..bb7acd15 100644 --- a/ddio/CMakeLists.txt +++ b/ddio/CMakeLists.txt @@ -35,3 +35,7 @@ target_include_directories(ddio PUBLIC ${PROJECT_SOURCE_DIR}/ddio > ) + +if(BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/ddio/ddio.h b/ddio/ddio.h index e590d886..4ac18a09 100644 --- a/ddio/ddio.h +++ b/ddio/ddio.h @@ -383,6 +383,15 @@ void ddio_DoForeachFile(const std::filesystem::path &search_path, const std::reg // Returns TRUE if successful, FALSE if an error bool ddio_GetTempFileName(const char *basedir, const char *prefix, char *filename); +/** + * Generates a temporary filename based on the prefix in basedir. Function ensures that generated + * filename does not exists in basedir directory. + * @param basedir directory to put the files + * @param prefix prefix for the temp filename + * @return generated filename with ".tmp" extension in basedir directory or empty path on failure + */ +std::filesystem::path ddio_GetTmpFileName(const std::filesystem::path &basedir, const char *prefix); + /** * Check process existence by PID * @param pid PID of requested process @@ -412,4 +421,4 @@ bool ddio_CreateLockFile(const std::filesystem::path &dir); */ bool ddio_DeleteLockFile(const std::filesystem::path &dir); -#endif \ No newline at end of file +#endif diff --git a/ddio/file.cpp b/ddio/file.cpp index 45bba732..36773688 100644 --- a/ddio/file.cpp +++ b/ddio/file.cpp @@ -17,13 +17,16 @@ */ #include +#include #include #include #include #include #include "IOOps.h" +#include "chrono_timer.h" #include "ddio.h" +#include "mem.h" #include "pserror.h" const std::array LOCK_TAG = {'L', 'O', 'C', 'K'}; @@ -156,3 +159,36 @@ void ddio_DoForeachFile(const std::filesystem::path &search_path, const std::reg } } } + +std::filesystem::path ddio_GetTmpFileName(const std::filesystem::path &basedir, const char *prefix) { + static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + // size of random part + const int len = 10; + const char *ext = ".tmp"; + std::filesystem::path result; + size_t len_result = strlen((basedir / prefix).u8string().c_str()); + char *random_name = (char *)mem_malloc(len_result + len + strlen(ext) + 1); + strncpy(random_name, (basedir / prefix).u8string().c_str(), len_result); + + srand(D3::ChronoTimer::GetTimeMS()); + + int tries = 20; + while (tries > 0) { + for (size_t i = len_result; i < len_result + len; i++) { + random_name[i] = alphanum[rand() % (sizeof(alphanum) - 1)]; + } + random_name[len_result + len] = '\0'; + strcat(random_name, ext); + if (!std::filesystem::exists(random_name)) { + // Found unique name, break the loop + result = random_name; + break; + } + tries--; + } + mem_free(random_name); + return result; +} diff --git a/ddio/tests/CMakeLists.txt b/ddio/tests/CMakeLists.txt new file mode 100644 index 00000000..44368d4e --- /dev/null +++ b/ddio/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +set(CMAKE_FOLDER "tests") + +add_executable(ddio_tests + ddio_tests.cpp +) +target_link_libraries(ddio_tests PRIVATE + GTest::gtest_main + ddio +) +gtest_discover_tests(ddio_tests) diff --git a/ddio/tests/ddio_tests.cpp b/ddio/tests/ddio_tests.cpp new file mode 100644 index 00000000..38300f14 --- /dev/null +++ b/ddio/tests/ddio_tests.cpp @@ -0,0 +1,30 @@ +/* + * Descent 3 + * Copyright (C) 2024 Descent Developers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "ddio.h" + +TEST(D3, DDIO_GetTmpFileName) { + std::filesystem::path temp_dir = std::filesystem::temp_directory_path(); + std::filesystem::path result = ddio_GetTmpFileName(temp_dir, "prefix_"); + EXPECT_FALSE(result.empty()); + EXPECT_EQ(result.extension(), ".tmp"); + EXPECT_TRUE(canonical(result.parent_path()) == canonical(temp_dir)); +}