@@ -81,6 +81,108 @@ static unzFile unzOpenUtf8(const char* path)
8181#endif
8282}
8383
84+ namespace
85+ {
86+ struct SHttpCacheCopyResult
87+ {
88+ CChecksum checksum;
89+ uint64 size = 0 ;
90+ SString tempPath;
91+ SString error;
92+ };
93+
94+ bool ReplaceHttpCacheFile (const SString& strTempPath, const SString& strCachedFilePath, int * pOutErrorCode)
95+ {
96+ #ifdef WIN32
97+ if (MoveFileExW (FromUTF8 (strTempPath), FromUTF8 (strCachedFilePath), MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH ) == 0 )
98+ {
99+ if (pOutErrorCode)
100+ *pOutErrorCode = GetLastError ();
101+ return false ;
102+ }
103+
104+ return true ;
105+ #else
106+ if (rename (strTempPath, strCachedFilePath) != 0 )
107+ {
108+ if (pOutErrorCode)
109+ *pOutErrorCode = errno;
110+ return false ;
111+ }
112+
113+ return true ;
114+ #endif
115+ }
116+
117+ SHttpCacheCopyResult CopyToHttpCacheAndChecksum (const SString& strPath, const SString& strCachedFilePath)
118+ {
119+ SHttpCacheCopyResult result;
120+ SString strTempPath = MakeUniquePath (SString (" %s.tmp" , *strCachedFilePath));
121+
122+ MakeSureDirExists (strTempPath);
123+
124+ FILE * pSource = File::FopenExclusive (strPath, " rb" );
125+ if (!pSource)
126+ {
127+ result.error = SString (" Could not read '%s' for HTTP cache\n " , *strPath);
128+ return result;
129+ }
130+
131+ FILE * pTemp = File::Fopen (strTempPath, " wb" );
132+ if (!pTemp)
133+ {
134+ fclose (pSource);
135+ result.error = SString (" Could not write temporary HTTP cache file '%s'\n " , *strTempPath);
136+ return result;
137+ }
138+
139+ CMD5Hasher md5;
140+ unsigned long crc = 0 ;
141+ char buffer[65536 ];
142+ bool ok = true ;
143+
144+ md5.Init ();
145+
146+ while (true )
147+ {
148+ size_t bytesRead = fread (buffer, 1 , sizeof (buffer), pSource);
149+ if (bytesRead == 0 )
150+ {
151+ if (ferror (pSource))
152+ ok = false ;
153+ break ;
154+ }
155+
156+ if (fwrite (buffer, 1 , bytesRead, pTemp) != bytesRead)
157+ {
158+ ok = false ;
159+ break ;
160+ }
161+
162+ crc = CRCGenerator::GetCRCFromBuffer (buffer, bytesRead, crc);
163+ md5.Update (reinterpret_cast <unsigned char *>(buffer), static_cast <unsigned int >(bytesRead));
164+ result.size += bytesRead;
165+ }
166+
167+ bool closeOk = fclose (pTemp) == 0 ;
168+ fclose (pSource);
169+
170+ if (!ok || !closeOk)
171+ {
172+ FileDelete (strTempPath);
173+ result.error = SString (" Could not copy '%s' to temporary HTTP cache file '%s'\n " , *strPath, *strTempPath);
174+ return result;
175+ }
176+
177+ md5.Finalize ();
178+ result.checksum .ulCRC = crc;
179+ memcpy (result.checksum .md5 .data , md5.GetResult (), sizeof (result.checksum .md5 .data ));
180+ result.tempPath = strTempPath;
181+
182+ return result;
183+ }
184+ } // namespace
185+
84186CResource::CResource (CResourceManager* pResourceManager, bool bIsZipped, const char * szAbsPath, const char * szResourceName)
85187 : m_pResourceManager(pResourceManager), m_bResourceIsZip(bIsZipped), m_strResourceName(SStringX(szResourceName)), m_strAbsPath(SStringX(szAbsPath))
86188{
@@ -535,12 +637,12 @@ std::future<SString> CResource::GenerateChecksumForFile(CResourceFile* pResource
535637 return SString (std::get<std::string>(checksumOrError));
536638 }
537639
538- pResourceFile-> SetLastChecksum ( std::get<CChecksum>(checksumOrError) );
539- pResourceFile-> SetLastFileSizeHint ( static_cast <uint>( FileSize (strPath)) );
640+ CChecksum checksum = std::get<CChecksum>(checksumOrError);
641+ uint64 fileSize = FileSize (strPath);
540642
541643 // Check if file is blocked
542644 char szHashResult[33 ];
543- CMD5Hasher::ConvertToHex (pResourceFile-> GetLastChecksum () .md5 , szHashResult);
645+ CMD5Hasher::ConvertToHex (checksum .md5 , szHashResult);
544646 SString strBlockReason = m_pResourceManager->GetBlockedFileReason (szHashResult);
545647
546648 if (!strBlockReason.empty ())
@@ -565,13 +667,32 @@ std::future<SString> CResource::GenerateChecksumForFile(CResourceFile* pResource
565667
566668 CChecksum cachedChecksum = CChecksum::GenerateChecksumFromFileUnsafe (strCachedFilePath);
567669
568- if (pResourceFile-> GetLastChecksum () != cachedChecksum)
670+ if (checksum != cachedChecksum)
569671 {
570- if (!FileCopy (strPath, strCachedFilePath))
672+ SHttpCacheCopyResult copyResult = CopyToHttpCacheAndChecksum (strPath, strCachedFilePath);
673+ if (!copyResult.error .empty ())
674+ return copyResult.error ;
675+
676+ CMD5Hasher::ConvertToHex (copyResult.checksum .md5 , szHashResult);
677+ strBlockReason = m_pResourceManager->GetBlockedFileReason (szHashResult);
678+
679+ if (!strBlockReason.empty ())
571680 {
572- return SString (" Could not copy '%s' to '%s'\n " , *strPath, *strCachedFilePath);
681+ FileDelete (copyResult.tempPath );
682+ return SString (" file '%s' is blocked (%s)" , pResourceFile->GetName (), *strBlockReason);
573683 }
574684
685+ // Publish only after the complete cached file matches the checksum we will advertise.
686+ int replaceError = 0 ;
687+ if (!ReplaceHttpCacheFile (copyResult.tempPath , strCachedFilePath, &replaceError))
688+ {
689+ FileDelete (copyResult.tempPath );
690+ return SString (" Could not replace HTTP cache file '%s' (error %d)\n " , *strCachedFilePath, replaceError);
691+ }
692+
693+ checksum = copyResult.checksum ;
694+ fileSize = copyResult.size ;
695+
575696 // If script is 'no client cache', make sure there is no trace of it in the output dir
576697 if (pResourceFile->IsNoClientCache ())
577698 FileDelete (pResourceFile->GetCachedPathFilename (true ));
@@ -583,6 +704,9 @@ std::future<SString> CResource::GenerateChecksumForFile(CResourceFile* pResource
583704 break ;
584705 }
585706
707+ pResourceFile->SetLastChecksum (checksum);
708+ pResourceFile->SetLastFileSizeHint (fileSize);
709+
586710 return SString ();
587711 });
588712}
0 commit comments