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.cpp
|
||||||
test.h
|
test.h
|
||||||
thread.cpp
|
thread.cpp
|
||||||
|
timestamp.cpp
|
||||||
unix.cpp
|
unix.cpp
|
||||||
uuid.cpp
|
uuid.cpp
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
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