mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-17 21:48:19 +00:00
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:
parent
caeebaf4a0
commit
3c9294321e
|
@ -2784,6 +2784,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
|||
test.cpp
|
||||
test.h
|
||||
thread.cpp
|
||||
timestamp.cpp
|
||||
unix.cpp
|
||||
uuid.cpp
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
61
src/test/timestamp.cpp
Normal 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");
|
||||
}
|
Loading…
Reference in a new issue