mirror of
https://github.com/ddnet/ddnet.git
synced 2024-09-20 09:34:19 +00:00
Merge #2322
2322: Use proper CSV writing for save codes r=def- a=heinrich5991 Supersedes #2296. Co-authored-by: def <dennis@felsin9.de> Co-authored-by: heinrich5991 <heinrich5991@gmail.com>
This commit is contained in:
commit
1652d73a5e
|
@ -731,6 +731,8 @@ set_src(ENGINE_SHARED GLOB src/engine/shared
|
||||||
config_variables.h
|
config_variables.h
|
||||||
console.cpp
|
console.cpp
|
||||||
console.h
|
console.h
|
||||||
|
csv.cpp
|
||||||
|
csv.h
|
||||||
datafile.cpp
|
datafile.cpp
|
||||||
datafile.h
|
datafile.h
|
||||||
demo.cpp
|
demo.cpp
|
||||||
|
@ -1336,6 +1338,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST)
|
||||||
set_src(TESTS GLOB src/test
|
set_src(TESTS GLOB src/test
|
||||||
aio.cpp
|
aio.cpp
|
||||||
color.cpp
|
color.cpp
|
||||||
|
csv.cpp
|
||||||
datafile.cpp
|
datafile.cpp
|
||||||
fs.cpp
|
fs.cpp
|
||||||
git_revision.cpp
|
git_revision.cpp
|
||||||
|
|
40
src/engine/shared/csv.cpp
Normal file
40
src/engine/shared/csv.cpp
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#include "csv.h"
|
||||||
|
|
||||||
|
void CsvWrite(IOHANDLE File, int NumColumns, const char *const *ppColumns)
|
||||||
|
{
|
||||||
|
for(int i = 0; i < NumColumns; i++)
|
||||||
|
{
|
||||||
|
if(i != 0)
|
||||||
|
{
|
||||||
|
io_write(File, ",", 1);
|
||||||
|
}
|
||||||
|
const char *pColumn = ppColumns[i];
|
||||||
|
int ColumnLength = str_length(pColumn);
|
||||||
|
if(!str_find(pColumn, "\"") && !str_find(pColumn, ","))
|
||||||
|
{
|
||||||
|
io_write(File, pColumn, ColumnLength);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Start = 0;
|
||||||
|
io_write(File, "\"", 1);
|
||||||
|
for(int j = 0; j < ColumnLength; j++)
|
||||||
|
{
|
||||||
|
if(pColumn[j] == '"')
|
||||||
|
{
|
||||||
|
if(Start != j)
|
||||||
|
{
|
||||||
|
io_write(File, pColumn + Start, j - Start);
|
||||||
|
}
|
||||||
|
Start = j + 1;
|
||||||
|
io_write(File, "\"\"", 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(Start != ColumnLength)
|
||||||
|
{
|
||||||
|
io_write(File, pColumn + Start, ColumnLength - Start);
|
||||||
|
}
|
||||||
|
io_write(File, "\"", 1);
|
||||||
|
}
|
||||||
|
io_write_newline(File);
|
||||||
|
}
|
8
src/engine/shared/csv.h
Normal file
8
src/engine/shared/csv.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#ifndef ENGINE_SHARED_CSV_H
|
||||||
|
#define ENGINE_SHARED_CSV_H
|
||||||
|
|
||||||
|
#include <base/system.h>
|
||||||
|
|
||||||
|
void CsvWrite(IOHANDLE File, int NumColumns, const char *const *pColumns);
|
||||||
|
|
||||||
|
#endif // ENGINE_SHARED_CSV_H
|
|
@ -9,6 +9,7 @@
|
||||||
#include <engine/textrender.h>
|
#include <engine/textrender.h>
|
||||||
#include <engine/keys.h>
|
#include <engine/keys.h>
|
||||||
#include <engine/shared/config.h>
|
#include <engine/shared/config.h>
|
||||||
|
#include <engine/shared/csv.h>
|
||||||
|
|
||||||
#include <game/generated/protocol.h>
|
#include <game/generated/protocol.h>
|
||||||
#include <game/generated/client_data.h>
|
#include <game/generated/client_data.h>
|
||||||
|
@ -543,6 +544,63 @@ bool CChat::LineShouldHighlight(const char *pLine, const char *pName)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define SAVES_FILE "ddnet-saves.txt"
|
||||||
|
const char *SAVES_HEADER[] = {
|
||||||
|
"Time",
|
||||||
|
"Player",
|
||||||
|
"Map",
|
||||||
|
"Code",
|
||||||
|
};
|
||||||
|
|
||||||
|
void CChat::StoreSave(const char *pText)
|
||||||
|
{
|
||||||
|
const char *pStart = str_find(pText, "Team successfully saved by ");
|
||||||
|
const char *pMid = str_find(pText, ". Use '/load ");
|
||||||
|
const char *pEnd = str_find(pText, "' to continue");
|
||||||
|
|
||||||
|
if(!pStart || !pMid || !pEnd || pMid < pStart || pEnd < pMid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
char aName[16];
|
||||||
|
str_copy(aName, pStart + 27, minimum(static_cast<size_t>(pMid - pStart - 26), sizeof(aName)));
|
||||||
|
|
||||||
|
char aSaveCode[64];
|
||||||
|
str_copy(aSaveCode, pMid + 13, minimum(static_cast<size_t>(pEnd - pMid - 12), sizeof(aSaveCode)));
|
||||||
|
|
||||||
|
char aTimestamp[20];
|
||||||
|
str_timestamp_format(aTimestamp, sizeof(aTimestamp), FORMAT_SPACE);
|
||||||
|
|
||||||
|
// TODO: Find a simple way to get the names of team members. This doesn't
|
||||||
|
// work since team is killed first, then save message gets sent:
|
||||||
|
/*
|
||||||
|
for(int i = 0; i < MAX_CLIENTS; i++)
|
||||||
|
{
|
||||||
|
const CNetObj_PlayerInfo *pInfo = GameClient()->m_Snap.m_paInfoByDDTeam[i];
|
||||||
|
if(!pInfo)
|
||||||
|
continue;
|
||||||
|
pInfo->m_Team // All 0
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
IOHANDLE File = Storage()->OpenFile(SAVES_FILE, IOFLAG_APPEND, IStorage::TYPE_SAVE);
|
||||||
|
if(!File)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const char *apColumns[4] = {
|
||||||
|
aTimestamp,
|
||||||
|
aName,
|
||||||
|
Client()->GetCurrentMap(),
|
||||||
|
aSaveCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(io_tell(File) == 0)
|
||||||
|
{
|
||||||
|
CsvWrite(File, 4, SAVES_HEADER);
|
||||||
|
}
|
||||||
|
CsvWrite(File, 4, apColumns);
|
||||||
|
io_close(File);
|
||||||
|
}
|
||||||
|
|
||||||
void CChat::AddLine(int ClientID, int Team, const char *pLine)
|
void CChat::AddLine(int ClientID, int Team, const char *pLine)
|
||||||
{
|
{
|
||||||
if(*pLine == 0 ||
|
if(*pLine == 0 ||
|
||||||
|
@ -640,6 +698,9 @@ void CChat::AddLine(int ClientID, int Team, const char *pLine)
|
||||||
{
|
{
|
||||||
str_copy(m_aLines[m_CurrentLine].m_aName, "*** ", sizeof(m_aLines[m_CurrentLine].m_aName));
|
str_copy(m_aLines[m_CurrentLine].m_aName, "*** ", sizeof(m_aLines[m_CurrentLine].m_aName));
|
||||||
str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), "%s", pLine);
|
str_format(m_aLines[m_CurrentLine].m_aText, sizeof(m_aLines[m_CurrentLine].m_aText), "%s", pLine);
|
||||||
|
|
||||||
|
if(Client()->State() != IClient::STATE_DEMOPLAYBACK)
|
||||||
|
StoreSave(m_aLines[m_CurrentLine].m_aText);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -91,6 +91,7 @@ class CChat : public CComponent
|
||||||
static void ConEcho(IConsole::IResult *pResult, void *pUserData);
|
static void ConEcho(IConsole::IResult *pResult, void *pUserData);
|
||||||
|
|
||||||
bool LineShouldHighlight(const char *pLine, const char *pName);
|
bool LineShouldHighlight(const char *pLine, const char *pName);
|
||||||
|
void StoreSave(const char *pText);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CChat();
|
CChat();
|
||||||
|
|
56
src/test/csv.cpp
Normal file
56
src/test/csv.cpp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#include "test.h"
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <engine/shared/csv.h>
|
||||||
|
|
||||||
|
static void Expect(int NumColumns, const char *const *ppColumns, const char *pExpected)
|
||||||
|
{
|
||||||
|
CTestInfo Info;
|
||||||
|
|
||||||
|
IOHANDLE File = io_open(Info.m_aFilename, IOFLAG_WRITE);
|
||||||
|
ASSERT_TRUE(File);
|
||||||
|
CsvWrite(File, NumColumns, ppColumns);
|
||||||
|
io_close(File);
|
||||||
|
|
||||||
|
char aBuf[1024];
|
||||||
|
File = io_open(Info.m_aFilename, IOFLAG_READ);
|
||||||
|
ASSERT_TRUE(File);
|
||||||
|
int Read = io_read(File, aBuf, sizeof(aBuf));
|
||||||
|
io_close(File);
|
||||||
|
fs_remove(Info.m_aFilename);
|
||||||
|
|
||||||
|
ASSERT_TRUE(Read >= 1);
|
||||||
|
Read -= 1;
|
||||||
|
ASSERT_EQ(aBuf[Read], '\n');
|
||||||
|
aBuf[Read] = 0;
|
||||||
|
|
||||||
|
#if defined(CONF_FAMILY_WINDOWS)
|
||||||
|
ASSERT_TRUE(Read >= 1);
|
||||||
|
Read -= 1;
|
||||||
|
ASSERT_EQ(aBuf[Read], '\r');
|
||||||
|
aBuf[Read] = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for(int i = 0; i < Read; i++)
|
||||||
|
{
|
||||||
|
EXPECT_NE(aBuf[i], 0);
|
||||||
|
}
|
||||||
|
EXPECT_STREQ(aBuf, pExpected);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Csv, Simple)
|
||||||
|
{
|
||||||
|
const char *apCols1[] = {"a", "b"}; Expect(2, apCols1, "a,b");
|
||||||
|
const char *apCols2[] = {"こんにちは"}; Expect(1, apCols2, "こんにちは");
|
||||||
|
const char *apCols3[] = {"я", "", "й"}; Expect(3, apCols3, "я,,й");
|
||||||
|
const char *apCols4[] = {""}; Expect(1, apCols4, "");
|
||||||
|
const char *apCols5[] = {0}; Expect(0, apCols5, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Csv, LetTheQuotingBegin)
|
||||||
|
{
|
||||||
|
const char *apCols1[] = {"\""}; Expect(1, apCols1, "\"\"\"\"");
|
||||||
|
const char *apCols2[] = {","}; Expect(1, apCols2, "\",\"");
|
||||||
|
const char *apCols3[] = {",,", ",\"\"\""}; Expect(2, apCols3, "\",,\",\",\"\"\"\"\"\"\"");
|
||||||
|
const char *apCols4[] = {"\",", " "}; Expect(2, apCols4, "\"\"\",\", ");
|
||||||
|
}
|
Loading…
Reference in a new issue