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.h
thread.cpp
timestamp.cpp
unix.cpp
uuid.cpp
)

View file

@ -9,7 +9,9 @@
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <iomanip> // std::get_time
#include <iterator> // std::size
#include <sstream> // std::istringstream
#include <string_view>
#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

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)
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"

View file

@ -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<CFileCollection *>(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;
}

View file

@ -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<CFileEntry> m_vTimestamps;
std::vector<CFileEntry> 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);

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");
}