From 46928a3347303d23ac08956cf6049025ab56bdab Mon Sep 17 00:00:00 2001 From: Timothy Hutchins Date: Sun, 21 May 2023 18:58:33 -0500 Subject: [PATCH] Implemented Linux equivalent to Windows file_path* This adds a Linux equivalent to file path operations done in the Windows headers and source files. Some differences that will occur are the invalid path names and contents, as there are no invalid path names, and only one invalid path content (excluding non-printable characters) on Linux. --- .../implementation/linux/file_path.cc | 209 +++++ .../platform/implementation/linux/file_path.h | 49 ++ .../implementation/linux/file_path_test.cc | 770 ++++++++++++++++++ 3 files changed, 1028 insertions(+) create mode 100644 internal/platform/implementation/linux/file_path.cc create mode 100644 internal/platform/implementation/linux/file_path.h create mode 100644 internal/platform/implementation/linux/file_path_test.cc diff --git a/internal/platform/implementation/linux/file_path.cc b/internal/platform/implementation/linux/file_path.cc new file mode 100644 index 00000000..f66fe6a4 --- /dev/null +++ b/internal/platform/implementation/linux/file_path.cc @@ -0,0 +1,209 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/file_path.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/strings/str_cat.h" +#include "internal/platform/implementation/linux/utils.h" +#include "internal/platform/implementation/linux/device_info.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +const wchar_t* kUpOneLevel = L"/.."; +constexpr wchar_t kPathDelimiter = L'/'; +constexpr wchar_t kReplacementChar = L'_'; +constexpr wchar_t kForwardSlash = L'/'; +constexpr wchar_t kBackSlash = L'\\'; + +std::wstring FilePath::GetCustomSavePath(std::wstring parent_folder, + std::wstring file_name) { + std::wstring path; + path += parent_folder + kPathDelimiter + file_name; + return CreateOutputFileWithRename(path); +} + +std::wstring FilePath::GetDownloadPath(std::wstring parent_folder, + std::wstring file_name) { + return CreateOutputFileWithRename( + GetDownloadPathInternal(parent_folder, file_name)); +} + +std::wstring FilePath::GetDownloadPathInternal(std::wstring parent_folder, + std::wstring file_name) { + DeviceInfo info = DeviceInfo(); + + std::optional download_path = info.GetDownloadPath(); + + std::string base_path; + + std::wstring wide_path(string_to_wstring(base_path)); + + if (!download_path) { + // If grabbing the download path fails then we make a custom one + base_path = getenv("HOME"); + base_path.append("/Downloads"); + } + else { + base_path = download_path.value(); + } + + // If parent_folder starts with a \\ or /, then strip it + while (!parent_folder.empty() && (*parent_folder.begin() == kBackSlash || + *parent_folder.begin() == kForwardSlash)) { + parent_folder.erase(0, 1); + } + + // If parent_folder ends with a \\ or /, then strip it + while (!parent_folder.empty() && (*parent_folder.rbegin() == kBackSlash || + *parent_folder.rbegin() == kForwardSlash)) { + parent_folder.erase(parent_folder.size() - 1, 1); + } + + // If file_name starts with a \\, then strip it + while (!file_name.empty() && (*file_name.begin() == kBackSlash || + *file_name.begin() == kForwardSlash)) { + file_name.erase(0, 1); + } + + // If file_name ends with a \\, then strip it + while (!file_name.empty() && (*file_name.rbegin() == kBackSlash || + *file_name.rbegin() == kForwardSlash)) { + file_name.erase(file_name.size() - 1, 1); + } + + std::wstring path; + + if (parent_folder.empty()) { + path = + file_name.empty() ? wide_path : wide_path + kForwardSlash + file_name; + } else { + path = file_name.empty() ? wide_path + kForwardSlash + parent_folder + : wide_path + kForwardSlash + parent_folder + + kForwardSlash + file_name; + } + + // Convert to UTF8 format. + return path; +} + +// If the file already exists we add " (x)", where x is an incrementing number, +// starting at 1, using the next non-existing number, to the file name, just +// before the first dot, or at the end if no dot. The absolute path is returned. +std::wstring FilePath::CreateOutputFileWithRename(std::wstring path) { + std::wstring sanitized_path(path); + + // Replace any \\ with / + std::replace(sanitized_path.begin(), sanitized_path.end(), kBackSlash, + kForwardSlash); + + // Remove any /..'s + SanitizePath(sanitized_path); + + auto last_delimiter = sanitized_path.find_last_of(kPathDelimiter); + std::wstring folder(sanitized_path.substr(0, last_delimiter)); + std::wstring file_name(sanitized_path.substr(last_delimiter)); + + // Locate the last dot + auto first = file_name.find_last_of('.'); + + if (first == std::string::npos) { + first = file_name.size(); + } + + // Break the string at the dot. + auto file_name1 = file_name.substr(0, first); + auto file_name2 = file_name.substr(first); + + // Construct the target file name + std::wstring target(sanitized_path); + + std::fstream file; + + // Open file as std::wstring + file.open(wstring_to_string(target), std::fstream::binary | std::fstream::in); + + // While we successfully open the file, keep incrementing the count. + int count = 0; + while (!(file.rdstate() & std::ifstream::failbit)) { + file.close(); + + target = (folder + file_name1 + L" (" + std::to_wstring(++count) + L")" + + file_name2); + + file.clear(); + file.open(wstring_to_string(target), std::fstream::binary | std::fstream::in); + } + + if (count > 0) { + NEARBY_LOGS(INFO) << "Renamed " << wstring_to_string(path) << " to " + << wstring_to_string(target); + } + + // The above leaves the file open, so close it. + file.close(); + + return target; +} + +std::wstring FilePath::MutateForbiddenPathElements(std::wstring& str) { + // There are no forbidden paths in Linux + return str; +} + +void FilePath::SanitizePath(std::wstring& path) { + size_t pos = std::wstring::npos; + // Search for the substring in string in a loop until nothing is found + while ((pos = path.find(kUpOneLevel)) != std::string::npos) { + // If found then erase it from string + path.erase(pos, wcslen(kUpOneLevel)); + } + + ReplaceInvalidCharacters(path); +} + +// Legit the only illegal character in Linux +char kIllegalFileCharacters[] = {'/'}; + +void FilePath::ReplaceInvalidCharacters(std::wstring& path) { + + for (auto &character : path) { + // If 0 < character < 32, it's illegal, replace it + if (character > 0 && character < 32) { + NEARBY_LOGS(INFO) << "In path " << wstring_to_string(path) + << " replaced \'" << std::string(1, character) << "\' with \'" + << std::string(1, kReplacementChar); + character = kReplacementChar; + } + for (auto illegal_character : kIllegalFileCharacters) { + if (character == illegal_character) { + NEARBY_LOGS(INFO) << "In path " << wstring_to_string(path) + << " replaced \'" << std::string(1, character) + << "\' with \'" << std::string(1, kReplacementChar); + character = kReplacementChar; + } + } + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/file_path.h b/internal/platform/implementation/linux/file_path.h new file mode 100644 index 00000000..6053b2f4 --- /dev/null +++ b/internal/platform/implementation/linux/file_path.h @@ -0,0 +1,49 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_FILE_PATH_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_FILE_PATH_H_ + +#include + +#include "absl/strings/string_view.h" + +namespace nearby { +namespace linux { + +class FilePath { + public: + static std::wstring GetCustomSavePath(std::wstring parent_folder, + std::wstring file_name); + static std::wstring GetDownloadPath(std::wstring parent_folder, + std::wstring file_name); + + private: + // If the file already exists we add " (x)", where x is an incrementing + // number, starting at 1, using the next non-existing number, to the + // file name, just before the first dot, or at the end if no dot. The + // absolute path is returned. + static std::wstring CreateOutputFileWithRename(std::wstring path); + + static void ReplaceInvalidCharacters(std::wstring& path); + static void SanitizePath(std::wstring& path); + static std::wstring MutateForbiddenPathElements(std::wstring& str); + static std::wstring GetDownloadPathInternal(std::wstring parent_folder, + std::wstring file_name); +}; + +} // namespace linux +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_LINUX_FILE_PATH_H_ diff --git a/internal/platform/implementation/linux/file_path_test.cc b/internal/platform/implementation/linux/file_path_test.cc new file mode 100644 index 00000000..a9c7e878 --- /dev/null +++ b/internal/platform/implementation/linux/file_path_test.cc @@ -0,0 +1,770 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "internal/platform/implementation/linux/file_path.h" + +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include +#include + +namespace nearby { +namespace linux { + +namespace { + +const wchar_t* kFileName(L"increment_file_test.txt"); +const wchar_t* kFirstIterationFileName(L"/increment_file_test (1).txt"); +const wchar_t* kSecondIterationFileName(L"/increment_file_test (2).txt"); +const wchar_t* kThirdIterationFileName(L"/increment_file_test (3).txt"); +const wchar_t* kNoDotsFileName(L"incrementfiletesttxt"); +const wchar_t* kOneIterationNoDotsFileName(L"/incrementfiletesttxt (1)"); +const wchar_t* kMultipleDotsFileName(L"increment.file.test.txt"); +const wchar_t* kOneIterationMultipleDotsFileName( + L"/increment.file.test (1).txt"); +const wchar_t* kImmediateEscape(L"../"); +const wchar_t* kLongEscapeBackSlash(L"..\\test\\..\\..\\test"); +const wchar_t* kTwoLevelFolder(L"/test/test"); +const wchar_t* kLongEscapeSlash(L"../test/../../test"); +const wchar_t* kLongEscapeMixedSlash(L"../test\\..\\../test"); +const wchar_t* kLongEscapeEndingEscape(L"../test/../../test/.."); +const wchar_t* kLongEscapeEndingEscapeWithSlash( + L"../test/../../test/../../../"); +} // namespace + +// Can't run on google 3, I presume the SHGetKnownFolderPath +// fails. +class FilePathTests : public testing::Test { + protected: + // You can define per-test set-up logic as usual. + FilePathTests() { + default_download_path_ = string_to_wstring(DeviceInfo().GetDownloadPath().value_or(std::string(getenv("HOME")).append("/Downloads"))); + } + std::wstring default_download_path_; +}; + +TEST_F(FilePathTests, GetDownloadPathWithEmptyStringArguments\ +ShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} // NOLINT false lint error here + +TEST_F(FilePathTests, GetDownloadPathWithSlashParent\ +FolderArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L"/"); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} // NOLINT false lint error here + +TEST_F(FilePathTests, GetDownloadPathWithBackslashParent\ +FolderArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L"\\"); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} // NOLINT false lint error here + +TEST_F(FilePathTests, GetDownloadPathWithAttemptToEscape\ +UsersDownloadFolderShouldReturnDownloadPathNotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kImmediateEscape); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithBackslashShouldReturnDownloadPath\ +NotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeBackSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderShouldReturnDownloadPathNotEscapingUsers\ +DownloadFolder) { + std::wstring parent_folder(kLongEscapeSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithMixedSlashShouldReturnDownloadPath\ +NotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeMixedSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithEndingEscapeShouldReturnDownload\ +PathNotEscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeEndingEscape); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithMultiple\ +AttemptsToEscapeUsersDownloadFolderWithEndingSlashShouldReturnDownloadPathNot\ +EscapingUsersDownloadFolder) { + std::wstring parent_folder(kLongEscapeEndingEscapeWithSlash); + std::wstring file_name(L""); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_ + kTwoLevelFolder); +} + +TEST_F(FilePathTests, GetDownloadPathWithSlashFileName\ +ArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"/"); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, default_download_path_); +} + +TEST_F(FilePathTests, GetDownloadPathWithBackslashFile\ +NameArgumentsShouldReturnBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"\\"); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + auto result_size = actual.size(); + auto default_size = default_download_path_.size(); + + EXPECT_EQ(actual, default_download_path_); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +ShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +StartingWithSlashArgumentsShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"/test_parent_folder"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +StartingWithBackslashArgumentsShouldReturnParentFolderAppendedToBase\ +DownloadPath) { + std::wstring parent_folder(L"\\test_parent_folder"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +EndingWithSlashArgumentsShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder/"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +EndingWithBackslashArguments\ +ShouldReturnParentFolderAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder\\"); + std::wstring file_name(L""); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileName\ +BeginningWithSlashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"/test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileName\ +BeginningWithBackslashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"\\test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, path.str().c_str()); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileNameEnding\ +WithSlashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"test_file_name.name/"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileNameEnding\ +WithBackslashArgumentsShouldReturnFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L""); + std::wstring file_name(L"test_file_name.name\\"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolderAnd\ +FileNameArgumentsShould\ +ReturnParentFolderAndFileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder"); + std::wstring file_name(L"test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder" + << "/" + << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithParentFolder\ +EndingWithBackslashAndFileNameArgumentsShouldReturnParentFolderAndFileName\ +AppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder\\"); + std::wstring file_name(L"test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder" + << "/" + << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPathWithFileName\ +StartingWithBackslashAndParentFolderArgumentsShouldReturnParentFolderAnd\ +FileNameAppendedToBaseDownloadPath) { + std::wstring parent_folder(L"test_parent_folder"); + std::wstring file_name(L"\\test_file_name.name"); + + std::wstringstream path(L""); + path << default_download_path_ << L"/" << "test_parent_folder" + << "/" + << "test_file_name.name"; + + std::wstring expected = path.str(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacters\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x05, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test\x5Test"); + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_LowestIllegalFileNameCharacter\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x01, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test\x1Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_HighestIllegalFileNameCharacter\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x1f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test\x1fTest"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterQuestionMark\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test?Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterAsterisk\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test*Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterLessThan\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"TestTest"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterVerticalBar\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test|Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_IllegalFileNameCharacterColon\ +ReturnsFileNameWithUnderbarSubstituted) { + // char illegal_character_sequence[]{ 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2f, + // 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0 }; + auto illegal_character_sequence(L"Test:Test"); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/Test_Test"); + + auto actual(FilePath::GetDownloadPath( + parent_folder, std::wstring(illegal_character_sequence))); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_FileDoesntExist\ +ReturnsFileWithPassedName) { + std::wstring file_name(kFileName); + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(L"/"); + expected.append(file_name); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); +} + +TEST_F(FilePathTests, GetDownloadPath_FileExistsReturns\ +FileWithIncrementedName) { + std::wstring file_name(kFileName); + std::wstring renamed_file_name(kFirstIterationFileName); + std::wstring parent_folder(L""); + + std::wstring output_file_path(default_download_path_); + output_file_path.append(L"/"); + output_file_path.append(file_name); + + std::wstring expected(default_download_path_); + expected += renamed_file_name; + + std::wifstream input_file; + std::wofstream output_file; + + output_file.open(wstring_to_string(output_file_path), + std::ofstream::binary | std::ofstream::out); + + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(actual, expected); + + // Remove the file and check that it is removed + // File 1 + std::filesystem::remove(output_file_path.c_str()); + + input_file.open(wstring_to_string(output_file_path), std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_MultipleFilesExist\ +ReturnsNextIncrementedFileName) { + std::ofstream output_file; + std::ifstream input_file; + + std::wstring file_name(kFileName); + std::wstring first_renamed_file_name(kFirstIterationFileName); + std::wstring second_renamed_file_name(kSecondIterationFileName); + + std::wstring parent_folder(L""); + + std::wstring expected(default_download_path_); + expected.append(second_renamed_file_name.c_str()); + + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(L"/" + file_name); + + std::wstring output_file2_path(default_download_path_); + output_file2_path.append(first_renamed_file_name); + + // Create the test files + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + output_file.clear(); + + output_file.open(wstring_to_string(output_file2_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + // Remove the test files and check that it is removed + // File 1 + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(output_file1_path, std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); + + // File 2 + std::filesystem::remove(wstring_to_string(output_file2_path).c_str()); + + input_file.clear(); + input_file.open(wstring_to_string(output_file2_path), std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_FileNameContains\ +MultipleDotsReturnsIncrementBeforeFirstDot) { + std::ifstream input_file; + std::ofstream output_file; + + std::wstring file_name(kMultipleDotsFileName); + std::wstring renamed_file_name(kOneIterationMultipleDotsFileName); + + std::wstring parent_folder(L""); + + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(L"/" + file_name); + + std::wstring output_file2_path(default_download_path_); + output_file2_path.append(renamed_file_name); + + std::wstring expected(default_download_path_); + expected.append(renamed_file_name); + + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(wstring_to_string(output_file1_path), std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_FileNameContainsNo\ +DotsReturnsWithIncrementAtEnd) { + std::ifstream input_file; + std::ofstream output_file; + + std::wstring file_name(kNoDotsFileName); + std::wstring renamed_file_name(kOneIterationNoDotsFileName); + + std::wstring parent_folder(L""); + + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(L"/" + file_name); + + std::wstring output_file2_path(default_download_path_); + output_file2_path.append(L"/" + renamed_file_name); + + std::wstring expected(default_download_path_); + expected.append(renamed_file_name); + + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(wstring_to_string(output_file1_path), std::ifstream::binary | std::ifstream::in); + + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} + +TEST_F(FilePathTests, GetDownloadPath_FileNameExistsWith\ +AHoleBetweenRenamedFiles) { + std::ifstream input_file; + std::ofstream output_file; + + std::wstring file_name(kFileName); + std::wstring file_name1(kFirstIterationFileName); + std::wstring file_name2(kSecondIterationFileName); + std::wstring file_name3(kThirdIterationFileName); + + std::wstring parent_folder(L""); + + // Create the path for the original file name + std::wstring output_file_path(default_download_path_); + output_file_path.append( + L"/" + + file_name); // Original file name example: "increment_file_test.txt" + + // Create the path for the first iteration of the original file name + std::wstring output_file1_path(default_download_path_); + output_file1_path.append(file_name1); // First iteration on original file + // name example: + // "increment_file_test (1).txt" + + // Create the path for the third iteration of the original file name + std::wstring output_file3_path(default_download_path_); + output_file3_path.append( + file_name3); // Third iteration on original file + // name example: "increment_file_test (3).txt" + + // Create the expected result which is the second iteration of the original + // file name + std::wstring expected(default_download_path_); + expected.append(file_name2); // Second iteration on original file name + // example: "increment_file_test (2).txt" + + // Create the original file + output_file.open(wstring_to_string(output_file_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + // Create the first iteration of the original file + output_file.clear(); + output_file.open(wstring_to_string(output_file1_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + // Create the third iteration of the original file + output_file.clear(); + output_file.open(wstring_to_string(output_file3_path), + std::ofstream::binary | std::ofstream::out); + ASSERT_TRUE(output_file.rdstate() == std::ofstream::goodbit); + output_file.close(); + + // This should return the second iteration of the original file + auto actual(FilePath::GetDownloadPath(parent_folder, file_name)); + + EXPECT_EQ(expected, actual); + + // Delete the original file + std::filesystem::remove(wstring_to_string(output_file_path).c_str()); + input_file.open(wstring_to_string(output_file_path), std::ifstream::binary | std::ifstream::in); + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); + + // Delete the first iteration of the original file + input_file.clear(); // Reset the input_file state + std::filesystem::remove(wstring_to_string(output_file1_path).c_str()); + input_file.open(wstring_to_string(output_file1_path), std::ifstream::binary | std::ifstream::in); + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); + + // Delete the third iteration of the original file + input_file.clear(); // Reset the input_file state + std::filesystem::remove(wstring_to_string(output_file3_path).c_str()); + input_file.open(wstring_to_string(output_file3_path), std::ifstream::binary | std::ifstream::in); + ASSERT_FALSE(input_file.rdstate() == std::ifstream::goodbit); +} +} // namespace linux +} // namespace nearby