@@ -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
80120bool 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
300351size_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+
393506void 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
407529void CHttpRequest::Header (const char *pNameColonValue)
@@ -421,7 +543,7 @@ void CHttpRequest::Wait()
421543void 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