Fix crash on editor autosave

Add system method `timestamp_from_str` and add tests.
Replace `int64_t` with `time_t` to be more consistent.
This commit is contained in:
Corantin H 2023-12-31 14:32:33 +01:00
parent caeebaf4a0
commit 3c9294321e
6 changed files with 140 additions and 98 deletions

View file

@ -2784,6 +2784,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
test.cpp test.cpp
test.h test.h
thread.cpp thread.cpp
timestamp.cpp
unix.cpp unix.cpp
uuid.cpp uuid.cpp
) )

View file

@ -9,7 +9,9 @@
#include <cstdarg> #include <cstdarg>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <iomanip> // std::get_time
#include <iterator> // std::size #include <iterator> // std::size
#include <sstream> // std::istringstream
#include <string_view> #include <string_view>
#include "lock.h" #include "lock.h"
@ -3446,6 +3448,22 @@ void str_timestamp(char *buffer, int buffer_size)
{ {
str_timestamp_format(buffer, buffer_size, FORMAT_NOSPACE); 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__ #ifdef __GNUC__
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#endif #endif

View file

@ -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) void str_timestamp_ex(time_t time, char *buffer, int buffer_size, const char *format)
GNUC_ATTRIBUTE((format(strftime, 4, 0))); 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_TIME "%H:%M:%S"
#define FORMAT_SPACE "%Y-%m-%d %H:%M:%S" #define FORMAT_SPACE "%Y-%m-%d %H:%M:%S"
#define FORMAT_NOSPACE "%Y-%m-%d_%H-%M-%S" #define FORMAT_NOSPACE "%Y-%m-%d_%H-%M-%S"

View file

@ -8,88 +8,9 @@
#include "filecollection.h" #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) 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); str_copy(m_aFileDesc, pFileDesc);
m_FileDescLength = str_length(m_aFileDesc); m_FileDescLength = str_length(m_aFileDesc);
str_copy(m_aFileExt, pFileExt); 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 = pStorage;
m_pStorage->ListDirectory(IStorage::TYPE_SAVE, m_aPath, FilelistCallback, this); 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; 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; break;
char aBuf[IO_MAX_PATH_LENGTH]; 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') if(m_aFileDesc[0] == '\0')
{ {
int FilenameLength = str_length(pFilename); 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 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) int CFileCollection::FilelistCallback(const char *pFilename, int IsDir, int StorageType, void *pUser)
{ {
CFileCollection *pThis = static_cast<CFileCollection *>(pUser); CFileCollection *pThis = static_cast<CFileCollection *>(pUser);
// check for valid file name format // Try to parse filename and extract timestamp
if(IsDir || !pThis->IsFilenameValid(pFilename)) time_t Timestamp;
if(IsDir || !pThis->ParseFilename(pFilename, &Timestamp))
return 0; return 0;
// extract the timestamp // Add the entry
int64_t Timestamp = pThis->GetTimestamp(pFilename); pThis->m_vFileEntries.emplace_back(Timestamp, pFilename);
// add the entry
pThis->m_vTimestamps.emplace_back(Timestamp, pFilename);
return 0; return 0;
} }

View file

@ -20,7 +20,7 @@ class CFileCollection
struct CFileEntry struct CFileEntry
{ {
int64_t m_Timestamp; time_t m_Timestamp;
char m_aFilename[IO_MAX_PATH_LENGTH]; char m_aFilename[IO_MAX_PATH_LENGTH];
CFileEntry(int64_t Timestamp, const char *pFilename) CFileEntry(int64_t Timestamp, const char *pFilename)
{ {
@ -29,7 +29,7 @@ class CFileCollection
} }
}; };
std::vector<CFileEntry> m_vTimestamps; std::vector<CFileEntry> m_vFileEntries;
char m_aFileDesc[128]; char m_aFileDesc[128];
int m_FileDescLength; int m_FileDescLength;
char m_aFileExt[32]; char m_aFileExt[32];
@ -37,9 +37,8 @@ class CFileCollection
char m_aPath[IO_MAX_PATH_LENGTH]; char m_aPath[IO_MAX_PATH_LENGTH];
IStorage *m_pStorage; IStorage *m_pStorage;
bool IsFilenameValid(const char *pFilename); bool ExtractTimestamp(const char *pTimestring, time_t *pTimestamp);
int64_t ExtractTimestamp(const char *pTimestring); bool ParseFilename(const char *pFilename, time_t *pTimestamp);
int64_t GetTimestamp(const char *pFilename);
public: public:
void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries); void Init(IStorage *pStorage, const char *pPath, const char *pFileDesc, const char *pFileExt, int MaxEntries);

61
src/test/timestamp.cpp Normal file
View file

@ -0,0 +1,61 @@
#include <gtest/gtest.h>
#include <base/system.h>
#include <cstdlib>
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");
}