From 38827f2b4b2d16407a0e70de3b3cb9c81685cad5 Mon Sep 17 00:00:00 2001 From: Francis Tsui Date: Tue, 5 Aug 2025 16:06:31 -0700 Subject: [PATCH] Allow set and get last modified times on files. PiperOrigin-RevId: 791407269 --- internal/platform/BUILD | 1 + internal/platform/file.cc | 12 +++ internal/platform/file.h | 5 ++ internal/platform/implementation/input_file.h | 5 +- .../platform/implementation/output_file.h | 2 + internal/platform/implementation/shared/BUILD | 1 + .../platform/implementation/shared/file.cc | 12 ++- .../platform/implementation/shared/file.h | 7 +- .../platform/implementation/windows/file.cc | 85 +++++++++++++++++++ .../platform/implementation/windows/file.h | 3 + .../implementation/windows/file_test.cc | 17 ++++ 11 files changed, 144 insertions(+), 6 deletions(-) diff --git a/internal/platform/BUILD b/internal/platform/BUILD index 52c221d5..724f01fc 100644 --- a/internal/platform/BUILD +++ b/internal/platform/BUILD @@ -352,6 +352,7 @@ cc_library( "@com_google_absl//absl/status", "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/time", "@com_google_absl//absl/types:optional", ], ) diff --git a/internal/platform/file.cc b/internal/platform/file.cc index 14f1b1a4..590afa17 100644 --- a/internal/platform/file.cc +++ b/internal/platform/file.cc @@ -13,6 +13,10 @@ // limitations under the License. #include "internal/platform/file.h" +#include +#include +#include +#include "absl/time/time.h" namespace nearby { @@ -37,6 +41,10 @@ std::string InputFile::GetFilePath() const { return impl_->GetFilePath(); } // Returns total size of this file in bytes. std::int64_t InputFile::GetTotalSize() const { return impl_->GetTotalSize(); } +absl::Time InputFile::GetLastModifiedTime() const { + return impl_->GetLastModifiedTime(); +} + ExceptionOr InputFile::Skip(size_t offset) { return impl_->Skip(offset); } @@ -81,4 +89,8 @@ Exception OutputFile::Close() { return impl_->Close(); } // versa. OutputStream& OutputFile::GetOutputStream() { return *impl_; } +void OutputFile::SetLastModifiedTime(absl::Time last_modified_time) { + impl_->SetLastModifiedTime(last_modified_time); +} + } // namespace nearby diff --git a/internal/platform/file.h b/internal/platform/file.h index dd3c8e17..992f3b72 100644 --- a/internal/platform/file.h +++ b/internal/platform/file.h @@ -20,6 +20,7 @@ #include #include +#include "absl/time/time.h" #include "internal/platform/byte_array.h" #include "internal/platform/exception.h" #include "internal/platform/implementation/input_file.h" @@ -66,6 +67,8 @@ class InputFile final { // versa. InputStream& GetInputStream(); + absl::Time GetLastModifiedTime() const; + private: std::unique_ptr impl_; }; @@ -98,6 +101,8 @@ class OutputFile final { // versa. OutputStream& GetOutputStream(); + void SetLastModifiedTime(absl::Time last_modified_time); + private: std::unique_ptr impl_; }; diff --git a/internal/platform/implementation/input_file.h b/internal/platform/implementation/input_file.h index 45d2d74a..3f0e4751 100644 --- a/internal/platform/implementation/input_file.h +++ b/internal/platform/implementation/input_file.h @@ -16,9 +16,9 @@ #define PLATFORM_API_INPUT_FILE_H_ #include +#include -#include "internal/platform/byte_array.h" -#include "internal/platform/exception.h" +#include "absl/time/time.h" #include "internal/platform/input_stream.h" namespace nearby { @@ -30,6 +30,7 @@ class InputFile : public InputStream { ~InputFile() override = default; virtual std::string GetFilePath() const = 0; virtual std::int64_t GetTotalSize() const = 0; + virtual absl::Time GetLastModifiedTime() const = 0; }; } // namespace api diff --git a/internal/platform/implementation/output_file.h b/internal/platform/implementation/output_file.h index 434a57bc..79ff25e5 100644 --- a/internal/platform/implementation/output_file.h +++ b/internal/platform/implementation/output_file.h @@ -15,6 +15,7 @@ #ifndef PLATFORM_API_OUTPUT_FILE_H_ #define PLATFORM_API_OUTPUT_FILE_H_ +#include "absl/time/time.h" #include "internal/platform/exception.h" #include "internal/platform/output_stream.h" @@ -25,6 +26,7 @@ namespace api { class OutputFile : public OutputStream { public: ~OutputFile() override = default; + virtual void SetLastModifiedTime(absl::Time last_modified_time) = 0; // File flush is a no-op. Exception Flush() override { return {Exception::kSuccess}; } }; diff --git a/internal/platform/implementation/shared/BUILD b/internal/platform/implementation/shared/BUILD index d76759a6..ce871842 100644 --- a/internal/platform/implementation/shared/BUILD +++ b/internal/platform/implementation/shared/BUILD @@ -66,6 +66,7 @@ cc_library( "//internal/platform/implementation:types", "@com_google_absl//absl/memory", "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", ], ) diff --git a/internal/platform/implementation/shared/file.cc b/internal/platform/implementation/shared/file.cc index a78ca9f2..40ec0294 100644 --- a/internal/platform/implementation/shared/file.cc +++ b/internal/platform/implementation/shared/file.cc @@ -21,6 +21,8 @@ #include "absl/memory/memory.h" #include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" #include "internal/platform/exception.h" namespace nearby { @@ -94,9 +96,13 @@ Exception IOFile::Write(const ByteArray& data) { return {file_.good() ? Exception::kSuccess : Exception::kIo}; } -Exception IOFile::Flush() { - file_.flush(); - return {file_.good() ? Exception::kSuccess : Exception::kIo}; +absl::Time IOFile::GetLastModifiedTime() const { + // TODO(ftsui): Implement this method. + return absl::Now(); +} + +void IOFile::SetLastModifiedTime(absl::Time last_modified_time) { + // TODO(ftsui): Implement this method. } } // namespace shared diff --git a/internal/platform/implementation/shared/file.h b/internal/platform/implementation/shared/file.h index bf4b77a0..0923b462 100644 --- a/internal/platform/implementation/shared/file.h +++ b/internal/platform/implementation/shared/file.h @@ -15,11 +15,14 @@ #ifndef PLATFORM_IMPL_SHARED_FILE_H_ #define PLATFORM_IMPL_SHARED_FILE_H_ +#include #include #include +#include #include #include "absl/strings/string_view.h" +#include "absl/time/time.h" #include "internal/platform/exception.h" #include "internal/platform/implementation/input_file.h" #include "internal/platform/implementation/output_file.h" @@ -42,7 +45,9 @@ class IOFile final : public api::InputFile, public api::OutputFile { Exception Close() override; Exception Write(const ByteArray& data) override; - Exception Flush() override; + + absl::Time GetLastModifiedTime() const override; + void SetLastModifiedTime(absl::Time last_modified_time) override; private: explicit IOFile(const absl::string_view file_path, size_t size); diff --git a/internal/platform/implementation/windows/file.cc b/internal/platform/implementation/windows/file.cc index 489875c5..4cca12de 100644 --- a/internal/platform/implementation/windows/file.cc +++ b/internal/platform/implementation/windows/file.cc @@ -16,6 +16,7 @@ #include +#include #include #include #include @@ -24,6 +25,9 @@ #include "absl/memory/memory.h" #include "absl/strings/string_view.h" +#include "absl/time/civil_time.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" #include "internal/platform/byte_array.h" #include "internal/platform/exception.h" #include "internal/platform/implementation/windows/string_utils.h" @@ -31,6 +35,57 @@ namespace nearby::windows { +namespace { +static const absl::Time kFiletimeEpoch = + absl::FromCivil(absl::CivilYear(1601), absl::UTCTimeZone()); + +constexpr absl::Duration kFiletimeUnit = absl::Nanoseconds(100); +const int64_t kFiletimeUnitsPerSecond = absl::Seconds(1) / kFiletimeUnit; + +static const absl::Time kMinDosFileTime = + absl::FromCivil(absl::CivilYear(1980), absl::UTCTimeZone()); +static const absl::Time kMaxDosFileTime = + absl::FromCivil(absl::CivilYear(2108), absl::UTCTimeZone()) - kFiletimeUnit; + +absl::Duration DurationFromFiletime(const FILETIME& file_time) { + LONGLONG quadDateTime = + (static_cast(file_time.dwHighDateTime) << 32) | + static_cast(file_time.dwLowDateTime); + + const absl::Duration subsecond = + kFiletimeUnit * (quadDateTime % kFiletimeUnitsPerSecond); + const absl::Duration seconds = + absl::Seconds(quadDateTime / kFiletimeUnitsPerSecond); + return seconds + subsecond; +} + +absl::Time TimeFromFiletime(const FILETIME& file_time) { + return kFiletimeEpoch + DurationFromFiletime(file_time); +} + +bool DurationToFiletime(const absl::Duration duration, FILETIME* file_time) { + absl::Duration remainder; + const int64_t filetime_value = + absl::IDivDuration(duration, kFiletimeUnit, &remainder); + if (remainder <= -kFiletimeUnit || remainder >= kFiletimeUnit) { + return false; + } + file_time->dwLowDateTime = static_cast(filetime_value); + file_time->dwHighDateTime = static_cast(filetime_value >> 32); + return true; +} + +void TimeToFiletime(absl::Time time, FILETIME* file_time) { + // Clamp to the range that FileTimeToDosDateTime supports. Explorer's date + // display and built-in zip feature can only handle this range. + time = std::clamp(time, kMinDosFileTime, kMaxDosFileTime); + + if (!DurationToFiletime(time - kFiletimeEpoch, file_time)) { + DurationToFiletime(kMaxDosFileTime - kFiletimeEpoch, file_time); + } +} +} // namespace + // InputFile std::unique_ptr IOFile::CreateInputFile(absl::string_view file_path, size_t size) { @@ -128,4 +183,34 @@ Exception IOFile::Write(const ByteArray& data) { return {Exception::kSuccess}; } +absl::Time IOFile::GetLastModifiedTime() const { + if (file_ == INVALID_HANDLE_VALUE) { + LOG(ERROR) << "Failed to get file modified time for: " << path_; + return absl::Now(); + } + FILETIME last_write_time; + if (::GetFileTime(file_, /*lpCreationTime=*/nullptr, + /*lpLastAccessTime=*/nullptr, &last_write_time) == 0) { + LOG(ERROR) << "Failed to get file last write time: " << path_ + << " with error: " << ::GetLastError(); + return absl::Now(); + } + return TimeFromFiletime(last_write_time); +} + +void IOFile::SetLastModifiedTime(absl::Time last_modified_time) { + if (file_ == INVALID_HANDLE_VALUE) { + LOG(ERROR) << "Failed to set file modified time for: " << path_; + return; + } + FILETIME last_write_time; + TimeToFiletime(last_modified_time, &last_write_time); + // Set both creation time and last write time. + if (::SetFileTime(file_, /*lpCreationTime=*/&last_write_time, + /*lpLastAccessTime=*/nullptr, &last_write_time) == 0) { + LOG(ERROR) << "Failed to set file last write time: " << path_ + << " with error: " << ::GetLastError(); + } +} + } // namespace nearby::windows diff --git a/internal/platform/implementation/windows/file.h b/internal/platform/implementation/windows/file.h index 1afa649e..7ee1280e 100644 --- a/internal/platform/implementation/windows/file.h +++ b/internal/platform/implementation/windows/file.h @@ -23,6 +23,7 @@ #include #include "absl/strings/string_view.h" +#include "absl/time/time.h" #include "internal/platform/byte_array.h" #include "internal/platform/exception.h" #include "internal/platform/implementation/input_file.h" @@ -49,6 +50,8 @@ class IOFile final : public api::InputFile, public api::OutputFile { Exception Close() override; Exception Write(const ByteArray& data) override; + absl::Time GetLastModifiedTime() const override; + void SetLastModifiedTime(absl::Time last_modified_time) override; private: explicit IOFile(absl::string_view file_path, size_t size); diff --git a/internal/platform/implementation/windows/file_test.cc b/internal/platform/implementation/windows/file_test.cc index 767de341..72826a88 100644 --- a/internal/platform/implementation/windows/file_test.cc +++ b/internal/platform/implementation/windows/file_test.cc @@ -23,6 +23,7 @@ #include "gtest/gtest.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "absl/time/time.h" #include "internal/platform/byte_array.h" #include "internal/platform/exception.h" #include "internal/platform/logging.h" @@ -187,5 +188,21 @@ TEST(IOFileTest, OutputFileWrite) { ::DeleteFileA(temp_file.data()); } +TEST(IOFileTest, GetSetModifiedTime) { + std::string temp_file = GetTempFileName("GetSetModifiedTime"); + std::unique_ptr output_file = IOFile::CreateOutputFile(temp_file); + ASSERT_NE(output_file, nullptr); + + output_file->SetLastModifiedTime(absl::FromUnixSeconds(1234567890)); + output_file->Close(); + + std::unique_ptr input_file = + IOFile::CreateInputFile(temp_file, /*size=*/0); + EXPECT_EQ(input_file->GetLastModifiedTime(), + absl::FromUnixSeconds(1234567890)); + + ::DeleteFileA(temp_file.data()); +} + } // namespace } // namespace nearby::windows