Skip to content

Commit 923859f

Browse files
authored
Merge pull request ddnet#9574 from heinrich5991/pr_ddnet_http_easier_file_download
Make it easier to download a file and have `If-Modified-Since` support
2 parents 9f0f75e + 79206b0 commit 923859f

File tree

5 files changed

+219
-112
lines changed

5 files changed

+219
-112
lines changed

src/engine/client/client.cpp

Lines changed: 13 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2393,51 +2393,6 @@ void CClient::ResetDDNetInfoTask()
23932393
}
23942394
}
23952395

2396-
void CClient::FinishDDNetInfo()
2397-
{
2398-
if(m_ServerBrowser.DDNetInfoSha256() == m_pDDNetInfoTask->ResultSha256())
2399-
{
2400-
log_debug("client/info", "DDNet info already up-to-date");
2401-
return;
2402-
}
2403-
2404-
char aTempFilename[IO_MAX_PATH_LENGTH];
2405-
IStorage::FormatTmpPath(aTempFilename, sizeof(aTempFilename), DDNET_INFO_FILE);
2406-
IOHANDLE File = Storage()->OpenFile(aTempFilename, IOFLAG_WRITE, IStorage::TYPE_SAVE);
2407-
if(!File)
2408-
{
2409-
log_error("client/info", "Failed to open temporary DDNet info '%s' for writing", aTempFilename);
2410-
return;
2411-
}
2412-
2413-
unsigned char *pResult;
2414-
size_t ResultLength;
2415-
m_pDDNetInfoTask->Result(&pResult, &ResultLength);
2416-
bool Error = io_write(File, pResult, ResultLength) != ResultLength;
2417-
Error |= io_close(File) != 0;
2418-
if(Error)
2419-
{
2420-
log_error("client/info", "Error writing temporary DDNet info to file '%s'", aTempFilename);
2421-
return;
2422-
}
2423-
2424-
if(Storage()->FileExists(DDNET_INFO_FILE, IStorage::TYPE_SAVE) && !Storage()->RemoveFile(DDNET_INFO_FILE, IStorage::TYPE_SAVE))
2425-
{
2426-
log_error("client/info", "Failed to remove old DDNet info '%s'", DDNET_INFO_FILE);
2427-
Storage()->RemoveFile(aTempFilename, IStorage::TYPE_SAVE);
2428-
return;
2429-
}
2430-
if(!Storage()->RenameFile(aTempFilename, DDNET_INFO_FILE, IStorage::TYPE_SAVE))
2431-
{
2432-
log_error("client/info", "Failed to rename temporary DDNet info '%s' to '%s'", aTempFilename, DDNET_INFO_FILE);
2433-
Storage()->RemoveFile(aTempFilename, IStorage::TYPE_SAVE);
2434-
return;
2435-
}
2436-
2437-
log_debug("client/info", "Loading new DDNet info");
2438-
LoadDDNetInfo();
2439-
}
2440-
24412396
typedef std::tuple<int, int, int> TVersion;
24422397
static const TVersion gs_InvalidVersion = std::make_tuple(-1, -1, -1);
24432398

@@ -2927,7 +2882,16 @@ void CClient::Update()
29272882
{
29282883
if(m_pDDNetInfoTask->State() == EHttpState::DONE)
29292884
{
2930-
FinishDDNetInfo();
2885+
if(m_ServerBrowser.DDNetInfoSha256() == m_pDDNetInfoTask->ResultSha256())
2886+
{
2887+
log_debug("client/info", "DDNet info already up-to-date");
2888+
}
2889+
else
2890+
{
2891+
log_debug("client/info", "Loading new DDNet info");
2892+
LoadDDNetInfo();
2893+
}
2894+
29312895
ResetDDNetInfoTask();
29322896
}
29332897
else if(m_pDDNetInfoTask->State() == EHttpState::ERROR || m_pDDNetInfoTask->State() == EHttpState::ABORTED)
@@ -5084,9 +5048,10 @@ void CClient::RequestDDNetInfo()
50845048
str_append(aUrl, aEscaped);
50855049
}
50865050

5087-
// Use ipv4 so we can know the ingame ip addresses of players before they join game servers
5088-
m_pDDNetInfoTask = HttpGet(aUrl);
5051+
m_pDDNetInfoTask = HttpGetFile(aUrl, Storage(), DDNET_INFO_FILE, IStorage::TYPE_SAVE);
50895052
m_pDDNetInfoTask->Timeout(CTimeout{10000, 0, 500, 10});
5053+
m_pDDNetInfoTask->SkipByFileTime(false); // Always re-download.
5054+
// Use ipv4 so we can know the ingame ip addresses of players before they join game servers
50905055
m_pDDNetInfoTask->IpResolve(IPRESOLVE::V4);
50915056
Http()->Run(m_pDDNetInfoTask);
50925057
}

src/engine/client/client.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,6 @@ class CClient : public IClient, public CDemoPlayer::IListener
377377

378378
void RequestDDNetInfo() override;
379379
void ResetDDNetInfoTask();
380-
void FinishDDNetInfo();
381380
void LoadDDNetInfo();
382381

383382
bool IsSixup() const override { return m_Sixup; }

src/engine/shared/http.cpp

Lines changed: 148 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,68 @@ CHttpRequest::~CHttpRequest()
7575
free(m_pBuffer);
7676
curl_slist_free_all((curl_slist *)m_pHeaders);
7777
free(m_pBody);
78+
if(m_State == EHttpState::DONE && m_ValidateBeforeOverwrite)
79+
{
80+
OnValidation(false);
81+
}
82+
}
83+
84+
static bool CalculateSha256(const char *pAbsoluteFilename, SHA256_DIGEST *pSha256)
85+
{
86+
IOHANDLE File = io_open(pAbsoluteFilename, IOFLAG_READ);
87+
if(!File)
88+
{
89+
return false;
90+
}
91+
SHA256_CTX Sha256Ctxt;
92+
sha256_init(&Sha256Ctxt);
93+
unsigned char aBuffer[64 * 1024];
94+
while(true)
95+
{
96+
unsigned Bytes = io_read(File, aBuffer, sizeof(aBuffer));
97+
if(Bytes == 0)
98+
break;
99+
sha256_update(&Sha256Ctxt, aBuffer, Bytes);
100+
}
101+
io_close(File);
102+
*pSha256 = sha256_finish(&Sha256Ctxt);
103+
return true;
104+
}
105+
106+
bool CHttpRequest::ShouldSkipRequest()
107+
{
108+
if(m_WriteToFile && m_ExpectedSha256 != SHA256_ZEROED)
109+
{
110+
SHA256_DIGEST Sha256;
111+
if(CalculateSha256(m_aDestAbsolute, &Sha256) && Sha256 == m_ExpectedSha256)
112+
{
113+
log_debug("http", "skipping download because expected file already exists: %s", m_aDest);
114+
return true;
115+
}
116+
}
117+
return false;
78118
}
79119

80120
bool CHttpRequest::BeforeInit()
81121
{
82122
if(m_WriteToFile)
83123
{
84-
if(fs_makedir_rec_for(m_aDestAbsolute) < 0)
124+
if(m_SkipByFileTime)
125+
{
126+
time_t FileCreatedTime, FileModifiedTime;
127+
if(fs_file_time(m_aDestAbsolute, &FileCreatedTime, &FileModifiedTime) == 0)
128+
{
129+
m_IfModifiedSince = FileModifiedTime;
130+
}
131+
}
132+
133+
if(fs_makedir_rec_for(m_aDestAbsoluteTmp) < 0)
85134
{
86135
log_error("http", "i/o error, cannot create folder for: %s", m_aDest);
87136
return false;
88137
}
89138

90-
m_File = io_open(m_aDestAbsolute, IOFLAG_WRITE);
139+
m_File = io_open(m_aDestAbsoluteTmp, IOFLAG_WRITE);
91140
if(!m_File)
92141
{
93142
log_error("http", "i/o error, cannot open file: %s", m_aDest);
@@ -268,14 +317,17 @@ size_t CHttpRequest::OnData(char *pData, size_t DataSize)
268317
return 0;
269318
}
270319

320+
if(DataSize == 0)
321+
{
322+
return DataSize;
323+
}
324+
271325
sha256_update(&m_ActualSha256Ctx, pData, DataSize);
272326

273-
if(!m_WriteToFile)
327+
size_t Result = DataSize;
328+
329+
if(m_WriteToMemory)
274330
{
275-
if(DataSize == 0)
276-
{
277-
return DataSize;
278-
}
279331
size_t NewBufferSize = maximum((size_t)1024, m_BufferSize);
280332
while(m_ResponseLength + DataSize > NewBufferSize)
281333
{
@@ -287,14 +339,13 @@ size_t CHttpRequest::OnData(char *pData, size_t DataSize)
287339
m_BufferSize = NewBufferSize;
288340
}
289341
mem_copy(m_pBuffer + m_ResponseLength, pData, DataSize);
290-
m_ResponseLength += DataSize;
291-
return DataSize;
292342
}
293-
else
343+
if(m_WriteToFile)
294344
{
295-
m_ResponseLength += DataSize;
296-
return io_write(m_File, pData, DataSize);
345+
Result = io_write(m_File, pData, DataSize);
297346
}
347+
m_ResponseLength += DataSize;
348+
return Result;
298349
}
299350

300351
size_t CHttpRequest::HeaderCallback(char *pData, size_t Size, size_t Number, void *pUser)
@@ -375,7 +426,44 @@ void CHttpRequest::OnCompletionInternal(void *pHandle, unsigned int Result)
375426

376427
if(State == EHttpState::ERROR || State == EHttpState::ABORTED)
377428
{
378-
fs_remove(m_aDestAbsolute);
429+
fs_remove(m_aDestAbsoluteTmp);
430+
}
431+
else if(m_IfModifiedSince >= 0 && m_StatusCode == 304) // 304 Not Modified
432+
{
433+
fs_remove(m_aDestAbsoluteTmp);
434+
if(m_WriteToMemory)
435+
{
436+
free(m_pBuffer);
437+
m_pBuffer = nullptr;
438+
m_ResponseLength = 0;
439+
void *pBuffer;
440+
unsigned Length;
441+
IOHANDLE File = io_open(m_aDestAbsolute, IOFLAG_READ);
442+
bool Success = File && io_read_all(File, &pBuffer, &Length);
443+
if(File)
444+
{
445+
io_close(File);
446+
}
447+
if(Success)
448+
{
449+
m_pBuffer = (unsigned char *)pBuffer;
450+
m_ResponseLength = Length;
451+
}
452+
else
453+
{
454+
log_error("http", "i/o error, cannot read existing file: %s", m_aDest);
455+
State = EHttpState::ERROR;
456+
}
457+
}
458+
}
459+
else if(!m_ValidateBeforeOverwrite)
460+
{
461+
if(fs_rename(m_aDestAbsoluteTmp, m_aDestAbsolute))
462+
{
463+
log_error("http", "i/o error, cannot move file: %s", m_aDest);
464+
State = EHttpState::ERROR;
465+
fs_remove(m_aDestAbsoluteTmp);
466+
}
379467
}
380468
}
381469

@@ -390,10 +478,37 @@ void CHttpRequest::OnCompletionInternal(void *pHandle, unsigned int Result)
390478
m_WaitCondition.notify_all();
391479
}
392480

481+
void CHttpRequest::OnValidation(bool Success)
482+
{
483+
dbg_assert(m_ValidateBeforeOverwrite, "this function is illegal to call without having set ValidateBeforeOverwrite");
484+
m_ValidateBeforeOverwrite = false;
485+
if(Success)
486+
{
487+
if(m_IfModifiedSince >= 0 && m_StatusCode == 304) // 304 Not Modified
488+
{
489+
fs_remove(m_aDestAbsoluteTmp);
490+
return;
491+
}
492+
if(fs_rename(m_aDestAbsoluteTmp, m_aDestAbsolute))
493+
{
494+
log_error("http", "i/o error, cannot move file: %s", m_aDest);
495+
m_State = EHttpState::ERROR;
496+
fs_remove(m_aDestAbsoluteTmp);
497+
}
498+
}
499+
else
500+
{
501+
m_State = EHttpState::ERROR;
502+
fs_remove(m_aDestAbsoluteTmp);
503+
}
504+
}
505+
393506
void CHttpRequest::WriteToFile(IStorage *pStorage, const char *pDest, int StorageType)
394507
{
508+
m_WriteToMemory = false;
395509
m_WriteToFile = true;
396510
str_copy(m_aDest, pDest);
511+
m_StorageType = StorageType;
397512
if(StorageType == -2)
398513
{
399514
pStorage->GetBinaryPath(m_aDest, m_aDestAbsolute, sizeof(m_aDestAbsolute));
@@ -402,6 +517,13 @@ void CHttpRequest::WriteToFile(IStorage *pStorage, const char *pDest, int Storag
402517
{
403518
pStorage->GetCompletePath(StorageType, m_aDest, m_aDestAbsolute, sizeof(m_aDestAbsolute));
404519
}
520+
IStorage::FormatTmpPath(m_aDestAbsoluteTmp, sizeof(m_aDestAbsoluteTmp), m_aDestAbsolute);
521+
}
522+
523+
void CHttpRequest::WriteToFileAndMemory(IStorage *pStorage, const char *pDest, int StorageType)
524+
{
525+
WriteToFile(pStorage, pDest, StorageType);
526+
m_WriteToMemory = true;
405527
}
406528

407529
void CHttpRequest::Header(const char *pNameColonValue)
@@ -421,7 +543,7 @@ void CHttpRequest::Wait()
421543
void CHttpRequest::Result(unsigned char **ppResult, size_t *pResultLength) const
422544
{
423545
dbg_assert(State() == EHttpState::DONE, "Request not done");
424-
dbg_assert(!m_WriteToFile, "Result not usable together with WriteToFile");
546+
dbg_assert(m_WriteToMemory, "Result only usable when written to memory");
425547
*ppResult = m_pBuffer;
426548
*pResultLength = m_ResponseLength;
427549
}
@@ -584,6 +706,18 @@ void CHttp::RunLoop()
584706
if(g_Config.m_DbgCurl)
585707
log_debug("http", "task: %s %s", CHttpRequest::GetRequestType(pRequest->m_Type), pRequest->m_aUrl);
586708

709+
if(pRequest->ShouldSkipRequest())
710+
{
711+
pRequest->OnCompletion(EHttpState::DONE);
712+
{
713+
std::unique_lock WaitLock(pRequest->m_WaitMutex);
714+
pRequest->m_State = EHttpState::DONE;
715+
}
716+
pRequest->m_WaitCondition.notify_all();
717+
NewRequests.pop_front();
718+
continue;
719+
}
720+
587721
CURL *pEH = curl_easy_init();
588722
if(!pEH)
589723
{

0 commit comments

Comments
 (0)