diff --git a/CMakeLists.txt b/CMakeLists.txt index ed53fa771..3a67618be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2784,6 +2784,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST) test.cpp test.h thread.cpp + timestamp.cpp unix.cpp uuid.cpp ) diff --git a/src/base/system.cpp b/src/base/system.cpp index 16082cca1..6f7dc717a 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -9,7 +9,9 @@ #include #include #include +#include // std::get_time #include // std::size +#include // std::istringstream #include #include "lock.h" @@ -3446,6 +3448,22 @@ void str_timestamp(char *buffer, int buffer_size) { str_timestamp_format(buffer, buffer_size, FORMAT_NOSPACE); } + +bool timestamp_from_str(const char *string, const char *format, time_t *timestamp) +{ + std::tm tm{}; + std::istringstream ss(string); + ss >> std::get_time(&tm, format); + if(ss.fail() || !ss.eof()) + return false; + + time_t result = mktime(&tm); + if(result < 0) + return false; + + *timestamp = result; + return true; +} #ifdef __GNUC__ #pragma GCC diagnostic pop #endif diff --git a/src/base/system.h b/src/base/system.h index c575949e6..50f08e5de 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -1711,6 +1711,21 @@ void str_timestamp_format(char *buffer, int buffer_size, const char *format) void str_timestamp_ex(time_t time, char *buffer, int buffer_size, const char *format) GNUC_ATTRIBUTE((format(strftime, 4, 0))); +/** + * Parses a string into a timestamp following a specified format. + * + * @ingroup Timestamp + * + * @param string Pointer to the string to parse + * @param format The time format to use (for example FORMAT_NOSPACE below) + * @param timestamp Pointer to the timestamp result + * + * @return true on success, false if the string could not be parsed with the specified format + * + */ +bool timestamp_from_str(const char *string, const char *format, time_t *timestamp) + GNUC_ATTRIBUTE((format(strftime, 2, 0))); + #define FORMAT_TIME "%H:%M:%S" #define FORMAT_SPACE "%Y-%m-%d %H:%M:%S" #define FORMAT_NOSPACE "%Y-%m-%d_%H-%M-%S" diff --git a/src/engine/shared/filecollection.cpp b/src/engine/shared/filecollection.cpp index 6a31c41a5..835d7bddc 100644 --- a/src/engine/shared/filecollection.cpp +++ b/src/engine/shared/filecollection.cpp @@ -8,88 +8,9 @@ #include "filecollection.h" -bool CFileCollection::IsFilenameValid(const char *pFilename) -{ - if(!str_endswith(pFilename, m_aFileExt)) - return false; - - if(m_aFileDesc[0] == '\0') - { - int FilenameLength = str_length(pFilename); - if(m_FileExtLength + TIMESTAMP_LENGTH > FilenameLength) - { - return false; - } - - pFilename += FilenameLength - m_FileExtLength - TIMESTAMP_LENGTH; - } - else - { - if(str_length(pFilename) != m_FileDescLength + TIMESTAMP_LENGTH + m_FileExtLength || - !str_startswith(pFilename, m_aFileDesc)) - return false; - - pFilename += m_FileDescLength; - } - - return pFilename[0] == '_' && - pFilename[1] >= '0' && pFilename[1] <= '9' && - pFilename[2] >= '0' && pFilename[2] <= '9' && - pFilename[3] >= '0' && pFilename[3] <= '9' && - pFilename[4] >= '0' && pFilename[4] <= '9' && - pFilename[5] == '-' && - pFilename[6] >= '0' && pFilename[6] <= '9' && - pFilename[7] >= '0' && pFilename[7] <= '9' && - pFilename[8] == '-' && - pFilename[9] >= '0' && pFilename[9] <= '9' && - pFilename[10] >= '0' && pFilename[10] <= '9' && - pFilename[11] == '_' && - pFilename[12] >= '0' && pFilename[12] <= '9' && - pFilename[13] >= '0' && pFilename[13] <= '9' && - pFilename[14] == '-' && - pFilename[15] >= '0' && pFilename[15] <= '9' && - pFilename[16] >= '0' && pFilename[16] <= '9' && - pFilename[17] == '-' && - pFilename[18] >= '0' && pFilename[18] <= '9' && - pFilename[19] >= '0' && pFilename[19] <= '9'; -} - -int64_t CFileCollection::ExtractTimestamp(const char *pTimestring) -{ - int64_t Timestamp = pTimestring[0] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[1] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[2] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[3] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[5] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[6] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[8] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[9] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[11] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[12] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[14] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[15] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[17] - '0'; - Timestamp <<= 4; - Timestamp += pTimestring[18] - '0'; - - return Timestamp; -} - void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries) { - m_vTimestamps.clear(); + m_vFileEntries.clear(); str_copy(m_aFileDesc, pFileDesc); m_FileDescLength = str_length(m_aFileDesc); str_copy(m_aFileExt, pFileExt); @@ -98,12 +19,12 @@ void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pF m_pStorage = pStorage; m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, FilelistCallback, this); - std::sort(m_vTimestamps.begin(), m_vTimestamps.end(), [](const CFileEntry &lhs, const CFileEntry &rhs) { return lhs.m_Timestamp < rhs.m_Timestamp; }); + std::sort(m_vFileEntries.begin(), m_vFileEntries.end(), [](const CFileEntry &lhs, const CFileEntry &rhs) { return lhs.m_Timestamp < rhs.m_Timestamp; }); int FilesDeleted = 0; - for(auto FileEntry : m_vTimestamps) + for(auto FileEntry : m_vFileEntries) { - if((int)m_vTimestamps.size() - FilesDeleted <= MaxEntries) + if((int)m_vFileEntries.size() - FilesDeleted <= MaxEntries) break; char aBuf[IO_MAX_PATH_LENGTH]; @@ -123,32 +44,59 @@ void CFileCollection::Init(IStorage *pStorage, const char *pPath, const char *pF } } -int64_t CFileCollection::GetTimestamp(const char *pFilename) +bool CFileCollection::ExtractTimestamp(const char *pTimestring, time_t *pTimestamp) { + // Discard anything after timestamp length from pTimestring (most likely the extension) + char aStrippedTimestring[TIMESTAMP_LENGTH]; + str_copy(aStrippedTimestring, pTimestring); + return timestamp_from_str(aStrippedTimestring, FORMAT_NOSPACE, pTimestamp); +} + +bool CFileCollection::ParseFilename(const char *pFilename, time_t *pTimestamp) +{ + // Check if filename is valid + if(!str_endswith(pFilename, m_aFileExt)) + return false; + + const char *pTimestring = pFilename; + if(m_aFileDesc[0] == '\0') { int FilenameLength = str_length(pFilename); - return ExtractTimestamp(pFilename + FilenameLength - m_FileExtLength - TIMESTAMP_LENGTH + 1); + if(m_FileExtLength + TIMESTAMP_LENGTH > FilenameLength) + { + return false; + } + + pTimestring += FilenameLength - m_FileExtLength - TIMESTAMP_LENGTH + 1; } else { - return ExtractTimestamp(pFilename + m_FileDescLength + 1); + if(str_length(pFilename) != m_FileDescLength + TIMESTAMP_LENGTH + m_FileExtLength || + !str_startswith(pFilename, m_aFileDesc)) + return false; + + pTimestring += m_FileDescLength + 1; } + + // Extract timestamp + if(!ExtractTimestamp(pTimestring, pTimestamp)) + return false; + + return true; } int CFileCollection::FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser) { CFileCollection *pThis = static_cast(pUser); - // check for valid file name format - if(IsDir || !pThis->IsFilenameValid(pFilename)) + // Try to parse filename and extract timestamp + time_t Timestamp; + if(IsDir || !pThis->ParseFilename(pFilename, &Timestamp)) return 0; - // extract the timestamp - int64_t Timestamp = pThis->GetTimestamp(pFilename); - - // add the entry - pThis->m_vTimestamps.emplace_back(Timestamp, pFilename); + // Add the entry + pThis->m_vFileEntries.emplace_back(Timestamp, pFilename); return 0; } diff --git a/src/engine/shared/filecollection.h b/src/engine/shared/filecollection.h index cbc54cd2f..6fb066a55 100644 --- a/src/engine/shared/filecollection.h +++ b/src/engine/shared/filecollection.h @@ -20,7 +20,7 @@ class CFileCollection struct CFileEntry { - int64_t m_Timestamp; + time_t m_Timestamp; char m_aFilename[IO_MAX_PATH_LENGTH]; CFileEntry(int64_t Timestamp, const char *pFilename) { @@ -29,7 +29,7 @@ class CFileCollection } }; - std::vector m_vTimestamps; + std::vector m_vFileEntries; char m_aFileDesc[128]; int m_FileDescLength; char m_aFileExt[32]; @@ -37,9 +37,8 @@ class CFileCollection char m_aPath[IO_MAX_PATH_LENGTH]; IStorage *m_pStorage; - bool IsFilenameValid(const char *pFilename); - int64_t ExtractTimestamp(const char *pTimestring); - int64_t GetTimestamp(const char *pFilename); + bool ExtractTimestamp(const char *pTimestring, time_t *pTimestamp); + bool ParseFilename(const char *pFilename, time_t *pTimestamp); public: void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries); diff --git a/src/test/timestamp.cpp b/src/test/timestamp.cpp new file mode 100644 index 000000000..0b69348b8 --- /dev/null +++ b/src/test/timestamp.cpp @@ -0,0 +1,61 @@ +#include + +#include +#include + +class TimestampTest : public testing::Test +{ +protected: + void SetUp() override + { +#if defined(CONF_FAMILY_WINDOWS) + _putenv_s("TZ", "UTC"); + _tzset(); +#else + setenv("TZ", "UTC", 1); + tzset(); +#endif + } +}; + +TEST_F(TimestampTest, FromStr) +{ + time_t Timestamp; + EXPECT_TRUE(timestamp_from_str("2023-12-31_12-58-55", FORMAT_NOSPACE, &Timestamp)); + EXPECT_EQ(Timestamp, 1704027535); + + EXPECT_TRUE(timestamp_from_str("2012-02-29_13-00-00", FORMAT_NOSPACE, &Timestamp)); + EXPECT_EQ(Timestamp, 1330520400); + + EXPECT_TRUE(timestamp_from_str("2004-05-15 18:13:53", FORMAT_SPACE, &Timestamp)); + EXPECT_EQ(Timestamp, 1084644833); +} + +TEST_F(TimestampTest, FromStrFailing) +{ + time_t Timestamp; + // Invalid time string + EXPECT_FALSE(timestamp_from_str("123 2023-12-31_12-58-55", FORMAT_NOSPACE, &Timestamp)); + + // Invalid time string + EXPECT_FALSE(timestamp_from_str("555-02-29_13-12-7", FORMAT_NOSPACE, &Timestamp)); + + // Time string does not fit the format + EXPECT_FALSE(timestamp_from_str("2004-05-15 18-13-53", FORMAT_SPACE, &Timestamp)); + + // Invalid time string + EXPECT_FALSE(timestamp_from_str("2000-01-01 00:00:00:00", FORMAT_SPACE, &Timestamp)); +} + +TEST_F(TimestampTest, WithSpecifiedFormatAndTimestamp) +{ + char aTimestamp[20]; + str_timestamp_ex(1704027535, aTimestamp, sizeof(aTimestamp), FORMAT_NOSPACE); + EXPECT_STREQ(aTimestamp, "2023-12-31_12-58-55"); + + str_timestamp_ex(1330520400, aTimestamp, sizeof(aTimestamp), FORMAT_NOSPACE); + EXPECT_STREQ(aTimestamp, "2012-02-29_13-00-00"); + + str_timestamp_ex(1084644833, aTimestamp, sizeof(aTimestamp), FORMAT_SPACE); + EXPECT_STREQ(aTimestamp, "2004-05-15 18:13:53"); +}