diff --git a/SystemTools.cxx b/SystemTools.cxx index 595bec07b2..3a2dc5a980 100644 --- a/SystemTools.cxx +++ b/SystemTools.cxx @@ -2247,6 +2247,48 @@ SystemTools::CopyStatus SystemTools::CopyFileIfDifferent( return CopyStatus{ Status::Success(), CopyStatus::NoPath }; } +SystemTools::CopyStatus SystemTools::CopyFileIfNewer( + std::string const& source, std::string const& destination) +{ + // special check for a destination that is a directory + // FileTimeCompare does not handle file to directory compare + if (SystemTools::FileIsDirectory(destination)) { + std::string const new_destination = FileInDir(source, destination); + if (!SystemTools::ComparePath(new_destination, destination)) { + return SystemTools::CopyFileIfNewer(source, new_destination); + } + // If source and destination are the same path, don't copy + return CopyStatus{ Status::Success(), CopyStatus::NoPath }; + } + + // source and destination are files so do a copy if source is newer + // Check if source file exists first + if (!SystemTools::FileExists(source)) { + return CopyStatus{ Status::POSIX_errno(), CopyStatus::SourcePath }; + } + // If destination doesn't exist, always copy + if (!SystemTools::FileExists(destination)) { + return SystemTools::CopyFileAlways(source, destination); + } + // Check if source is newer than destination + int timeResult; + Status timeStatus = + SystemTools::FileTimeCompare(source, destination, &timeResult); + if (timeStatus.IsSuccess()) { + if (timeResult > 0) { + // Source is newer, copy it + return SystemTools::CopyFileAlways(source, destination); + } else { + // Source is not newer, no need to copy + return CopyStatus{ Status::Success(), CopyStatus::NoPath }; + } + } else { + // Time comparison failed, be conservative and copy to ensure updates are + // not missed + return SystemTools::CopyFileAlways(source, destination); + } +} + #define KWSYS_ST_BUFFER 4096 bool SystemTools::FilesDiffer(std::string const& source, @@ -2583,15 +2625,31 @@ SystemTools::CopyStatus SystemTools::CopyFileAlways( return status; } +SystemTools::CopyStatus SystemTools::CopyAFile(std::string const& source, + std::string const& destination, + SystemTools::CopyWhen when) +{ + switch (when) { + case SystemTools::CopyWhen::Always: + return SystemTools::CopyFileAlways(source, destination); + case SystemTools::CopyWhen::OnlyIfDifferent: + return SystemTools::CopyFileIfDifferent(source, destination); + case SystemTools::CopyWhen::OnlyIfNewer: + return SystemTools::CopyFileIfNewer(source, destination); + default: + break; + } + // Should not reach here + return CopyStatus{ Status::POSIX_errno(), CopyStatus::NoPath }; +} + SystemTools::CopyStatus SystemTools::CopyAFile(std::string const& source, std::string const& destination, bool always) { - if (always) { - return SystemTools::CopyFileAlways(source, destination); - } else { - return SystemTools::CopyFileIfDifferent(source, destination); - } + return SystemTools::CopyAFile(source, destination, + always ? CopyWhen::Always + : CopyWhen::OnlyIfDifferent); } /** @@ -2599,7 +2657,8 @@ SystemTools::CopyStatus SystemTools::CopyAFile(std::string const& source, * "destination". */ Status SystemTools::CopyADirectory(std::string const& source, - std::string const& destination, bool always) + std::string const& destination, + SystemTools::CopyWhen when) { Status status; Directory dir; @@ -2622,12 +2681,12 @@ Status SystemTools::CopyADirectory(std::string const& source, std::string fullDestPath = destination; fullDestPath += "/"; fullDestPath += dir.GetFile(static_cast(fileNum)); - status = SystemTools::CopyADirectory(fullPath, fullDestPath, always); + status = SystemTools::CopyADirectory(fullPath, fullDestPath, when); if (!status.IsSuccess()) { return status; } } else { - status = SystemTools::CopyAFile(fullPath, destination, always); + status = SystemTools::CopyAFile(fullPath, destination, when); if (!status.IsSuccess()) { return status; } @@ -2638,6 +2697,14 @@ Status SystemTools::CopyADirectory(std::string const& source, return status; } +Status SystemTools::CopyADirectory(std::string const& source, + std::string const& destination, bool always) +{ + return SystemTools::CopyADirectory(source, destination, + always ? CopyWhen::Always + : CopyWhen::OnlyIfDifferent); +} + // return size of file; also returns zero if no file exists unsigned long SystemTools::FileLength(std::string const& filename) { diff --git a/SystemTools.hxx.in b/SystemTools.hxx.in index daf6ba3c65..a902ab6e0f 100644 --- a/SystemTools.hxx.in +++ b/SystemTools.hxx.in @@ -577,6 +577,13 @@ public: static CopyStatus CopyFileIfDifferent(std::string const& source, std::string const& destination); + /** + * Copy the source file to the destination file only + * if the source file is newer than the destination file. + */ + static CopyStatus CopyFileIfNewer(std::string const& source, + std::string const& destination); + /** * Compare the contents of two files. Return true if different */ @@ -658,24 +665,34 @@ public: static CopyStatus CopyFileAlways(std::string const& source, std::string const& destination); + enum class CopyWhen + { + Always, + OnlyIfDifferent, + OnlyIfNewer, + }; + /** - * Copy a file. If the "always" argument is true the file is always - * copied. If it is false, the file is copied only if it is new or - * has changed. + * Copy a file with specified copy behavior. */ static CopyStatus CopyAFile(std::string const& source, std::string const& destination, - bool always = true); + CopyWhen when = CopyWhen::Always); + static CopyStatus CopyAFile(std::string const& source, + std::string const& destination, bool always); /** * Copy content directory to another directory with all files and - * subdirectories. If the "always" argument is true all files are - * always copied. If it is false, only files that have changed or - * are new are copied. + * subdirectories. The "when" argument controls when files are copied: + * Always: all files are always copied. + * OnlyIfDifferent: only files that have changed are copied. + * OnlyIfNewer: only files that are newer than the destination are copied. */ static Status CopyADirectory(std::string const& source, std::string const& destination, - bool always = true); + CopyWhen when = CopyWhen::Always); + static Status CopyADirectory(std::string const& source, + std::string const& destination, bool always); /** * Remove a file diff --git a/testSystemTools.cxx b/testSystemTools.cxx index a7002e10b4..08e51e1f7f 100644 --- a/testSystemTools.cxx +++ b/testSystemTools.cxx @@ -1160,6 +1160,94 @@ static bool CheckCopyFileIfDifferent() return ret; } +static bool CheckCopyFileIfNewer() +{ + bool ret = true; + + // Prepare "older" files. + if (!writeFile("older_source.txt", "old content")) { + return false; + } + if (!writeFile("older_dest.txt", "old content")) { + return false; + } + + // Small delay to ensure different timestamps +#if defined(_WIN32) + Sleep(1125); // Sleep for 1.125 seconds on Windows +#else + usleep(1125000); // Sleep for 1.125 seconds on Unix +#endif + + // Prepare "newer" files. + if (!writeFile("newer_source.txt", "test content")) { + return false; + } + if (!writeFile("newer_source2.txt", "newer content")) { + return false; + } + if (!writeFile("newer_dest2.txt", "new content")) { + return false; + } + if (!kwsys::SystemTools::MakeDirectory("newer_dir_a") || + !kwsys::SystemTools::MakeDirectory("newer_dir_b")) { + return false; + } + + // Test case 1: Copy when destination doesn't exist + if (!kwsys::SystemTools::CopyFileIfNewer("newer_source.txt", + "newer_dest1.txt")) { + std::cerr << "CopyFileIfNewer() failed when destination doesn't exist." + << std::endl; + ret = false; + } else { + std::string dest_content = readFile("newer_dest1.txt"); + if (dest_content != "test content") { + std::cerr << "CopyFileIfNewer() incorrect content when destination " + "doesn't exist." + << std::endl; + ret = false; + } + } + + // Test case 2: Don't copy when source is older + auto copy_result = + kwsys::SystemTools::CopyFileIfNewer("older_source.txt", "newer_dest2.txt"); + if (!copy_result) { + std::cerr << "CopyFileIfNewer() failed when source is older." << std::endl; + ret = false; + } else { + std::string dest_content = readFile("newer_dest2.txt"); + if (dest_content != "new content") { + std::cerr + << "CopyFileIfNewer() should not have copied when source is older." + << std::endl; + ret = false; + } + } + + // Test case 3: Copy when source is newer + if (!kwsys::SystemTools::CopyFileIfNewer("newer_source2.txt", + "older_dest.txt")) { + std::cerr << "CopyFileIfNewer() failed when source is newer." << std::endl; + ret = false; + } else { + std::string dest_content = readFile("older_dest.txt"); + if (dest_content != "newer content") { + std::cerr << "CopyFileIfNewer() incorrect content when source is newer." + << std::endl; + ret = false; + } + } + + // Test case 4: Directory to directory copy + if (!kwsys::SystemTools::CopyFileIfNewer("newer_dir_a/", "newer_dir_b")) { + ret = false; + } + + return ret; +} + static bool CheckURLParsing() { bool ret = true; @@ -1271,6 +1359,8 @@ int testSystemTools(int, char*[]) res &= CheckCopyFileIfDifferent(); + res &= CheckCopyFileIfNewer(); + res &= CheckURLParsing(); res &= CheckSplitString();