Skip to content

Commit 78a50ee

Browse files
authored
Merge pull request #313 from crymp-net/use-temp-files-for-download
Use temporary unique filenames during download
2 parents 5bebdba + 064fd2b commit 78a50ee

File tree

12 files changed

+184
-433
lines changed

12 files changed

+184
-433
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,8 @@ add_library(Base OBJECT
626626
Code/Library/PathTools.h
627627
Code/Library/PathTree.h
628628
Code/Library/SlotVector.h
629+
Code/Library/StdFile.cpp
630+
Code/Library/StdFile.h
629631
Code/Library/StlportMap.h
630632
Code/Library/StlportSet.h
631633
Code/Library/StlportVector.h

Code/CryMP/Client/Client.cpp

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "CrySystem/Logger.h"
1616
#include "CrySystem/RandomGenerator.h"
1717
#include "Launcher/Resources.h"
18+
#include "Library/StdFile.h"
1819
#include "Library/StringTools.h"
1920
#include "Library/Util.h"
2021
#include "Library/WinAPI.h"
@@ -40,19 +41,11 @@ void Client::InitMasters()
4041
{
4142
std::string content;
4243

43-
WinAPI::File file("masters.txt", WinAPI::FileAccess::READ_ONLY); // Crysis main directory
44-
if (file)
44+
if (StdFile file("masters.txt", "r"); file.IsOpen()) // Crysis main directory
4545
{
4646
CryLogAlways("$6[CryMP] Using local masters.txt as the master server list provider");
4747

48-
try
49-
{
50-
content = file.Read();
51-
}
52-
catch (const std::exception& ex)
53-
{
54-
CryLogAlways("$4[CryMP] Failed to read the masters.txt file: %s", ex.what());
55-
}
48+
content = file.ReadAll();
5649
}
5750
else
5851
{

Code/CryMP/Client/FileCache.cpp

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
#include "CryCommon/CrySystem/ISystem.h"
44
#include "CryCommon/CrySystem/ICryPak.h"
5-
#include "Library/StringTools.h"
5+
#include "Library/StdFile.h"
66
#include "Library/Util.h"
7-
#include "Library/WinAPI.h"
87

98
#include "FileCache.h"
109
#include "Client.h"
@@ -14,58 +13,43 @@ json FileCache::LoadIndex()
1413
{
1514
json index;
1615

17-
try
16+
if (StdFile file((m_cacheDir / "index").string().c_str(), "r"); file.IsOpen())
1817
{
19-
WinAPI::File indexFile(m_cacheDir / "index", WinAPI::FileAccess::READ_ONLY);
20-
if (!indexFile)
21-
{
22-
throw StringTools::SysErrorFormat("Failed to open the index file for reading");
23-
}
24-
25-
const std::string content = indexFile.Read();
26-
27-
if (!content.empty())
28-
{
29-
index = json::parse(content);
30-
}
31-
}
32-
catch (const std::exception & ex)
33-
{
34-
CryLogAlways("$4[CryMP] [FileCache] Index load error: %s", ex.what());
18+
index = json::parse(file.GetHandle(), nullptr, false); // no exceptions
3519
}
3620

3721
if (!index.contains("files") || !index["files"].is_object())
22+
{
3823
index["files"] = json::object();
24+
}
3925

4026
if (!index.contains("lru") || !index["lru"].is_array())
27+
{
4128
index["lru"] = json::array();
29+
}
4230

4331
return index;
4432
}
4533

4634
void FileCache::SaveIndex(const json & index)
4735
{
48-
try
49-
{
50-
WinAPI::File indexFile(m_cacheDir / "index", WinAPI::FileAccess::WRITE_ONLY);
51-
if (!indexFile)
52-
{
53-
throw StringTools::SysErrorFormat("Failed to open the index file for writing");
54-
}
36+
std::string content = index.dump(-1, ' ', false, json::error_handler_t::replace); // no exceptions
5537

56-
indexFile.Write(index.dump());
57-
}
58-
catch (const std::exception & ex)
38+
StdFile file((m_cacheDir / "index").string().c_str(), "w");
39+
if (!file.IsOpen())
5940
{
60-
CryLogAlways("$4[CryMP] [FileCache] Index save error: %s", ex.what());
41+
CryLogAlways("$4[CryMP] [FileCache] Failed to open index file for writing");
42+
return;
6143
}
44+
45+
file.Write(content.c_str(), content.length());
6246
}
6347

6448
void FileCache::DownloadFile(FileCacheRequest && request, const std::filesystem::path & filePath)
6549
{
6650
FileDownloaderRequest download;
6751
download.url = request.fileURL;
68-
download.filePath = filePath;
52+
download.filePath = FileDownloaderRequest::MakeFileNameUnique(filePath); // temporary file during download
6953

7054
CryLogAlways("$3[CryMP] [FileCache] Downloading from $6%s$3", download.url.c_str());
7155

@@ -77,7 +61,7 @@ void FileCache::DownloadFile(FileCacheRequest && request, const std::filesystem:
7761
return true;
7862
};
7963

80-
download.onComplete = [request = std::move(request), this](FileDownloaderResult & result)
64+
download.onComplete = [request = std::move(request), finalPath = filePath, this](FileDownloaderResult & result)
8165
{
8266
bool success = false;
8367

@@ -111,12 +95,27 @@ void FileCache::DownloadFile(FileCacheRequest && request, const std::filesystem:
11195
success = true;
11296
}
11397

98+
if (success)
99+
{
100+
// rename the temporary file
101+
// no exceptions
102+
std::error_code error;
103+
std::filesystem::rename(result.filePath, finalPath, error);
104+
if (error)
105+
{
106+
CryLogAlways("$4[CryMP] [FileCache] Renaming temporary file failed: Error %d (%s)",
107+
error.value(), error.message().c_str());
108+
109+
success = false;
110+
}
111+
}
112+
114113
if (!success)
115114
{
116115
RemoveFile(result.filePath);
117116
}
118117

119-
CompleteRequest(request, success, result.filePath);
118+
CompleteRequest(request, success, finalPath);
120119
};
121120

122121
gClient->GetFileDownloader()->Request(std::move(download));
@@ -149,9 +148,6 @@ FileCache::FileCache()
149148

150149
// make sure the cache directory exists
151150
std::filesystem::create_directories(m_cacheDir);
152-
153-
// make sure the index file exists
154-
WinAPI::File(m_cacheDir / "index", WinAPI::FileAccess::READ_WRITE_CREATE);
155151
}
156152

157153
FileCache::~FileCache()

Code/CryMP/Client/FileDownloader.cpp

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
#include <chrono>
33

44
#include "CryMP/Common/Executor.h"
5+
#include "CrySystem/RandomGenerator.h"
6+
#include "Library/StdFile.h"
57
#include "Library/StringTools.h"
68
#include "Library/Util.h"
79
#include "Library/WinAPI.h"
@@ -10,33 +12,6 @@
1012
#include "SpeedAggregator.h"
1113
#include "Client.h"
1214

13-
class OutputFile
14-
{
15-
WinAPI::File m_file;
16-
17-
public:
18-
OutputFile(const std::filesystem::path & path)
19-
{
20-
bool created = false;
21-
22-
if (!m_file.Open(path, WinAPI::FileAccess::WRITE_ONLY_CREATE, &created))
23-
{
24-
throw StringTools::SysErrorFormat("Failed to open the output file");
25-
}
26-
27-
if (!created)
28-
{
29-
// clear the existing file
30-
m_file.Resize(0);
31-
}
32-
}
33-
34-
void Write(const char *chunk, size_t chunkLength)
35-
{
36-
m_file.Write(std::string_view(chunk, chunkLength));
37-
}
38-
};
39-
4015
struct FileDownloaderTask : public IExecutorTask
4116
{
4217
FileDownloaderRequest request;
@@ -77,25 +52,30 @@ struct FileDownloaderTask : public IExecutorTask
7752

7853
try
7954
{
80-
OutputFile file(request.filePath);
81-
8255
result.statusCode = WinAPI::HTTPRequest(
8356
"GET",
8457
request.url,
8558
{}, // data
8659
{}, // headers
87-
[this, &file](uint64_t contentLength, const WinAPI::HTTPRequestReader & reader)
60+
[this](uint64_t contentLength, const WinAPI::HTTPRequestReader& reader)
8861
{
62+
StdFile file(request.filePath.string().c_str(), "wb");
63+
if (!file.IsOpen())
64+
{
65+
throw StringTools::SysErrorErrnoFormat("Output file open failed");
66+
}
67+
8968
// content length is zero if not provided by the server
9069
result.contentLength = contentLength;
9170

9271
while (isActive)
9372
{
9473
char chunk[8192];
9574
const size_t chunkLength = reader(chunk, sizeof chunk);
96-
9775
if (chunkLength == 0)
76+
{
9877
break;
78+
}
9979

10080
file.Write(chunk, chunkLength);
10181
result.downloadedBytes += chunkLength;
@@ -142,6 +122,38 @@ std::string FileDownloaderProgress::ToString() const
142122
}
143123
}
144124

125+
std::string FileDownloaderRequest::CreateUniqueFileName(std::string_view baseName, std::string_view extension)
126+
{
127+
std::string name(baseName);
128+
129+
// lowercase
130+
StringTools::ToLowerInPlace(name);
131+
132+
// sanitize
133+
for (char& ch : name)
134+
{
135+
if (!(ch >= '0' && ch <= '9') && !(ch >= 'a' && ch <= 'z'))
136+
{
137+
ch = '_';
138+
}
139+
}
140+
141+
// add a random suffix for uniqueness
142+
StringTools::FormatTo(name, "_%09u", RandomGenerator::GenerateUInt32(0, 999'999'999));
143+
144+
// extension
145+
name += extension;
146+
147+
return name;
148+
}
149+
150+
std::filesystem::path FileDownloaderRequest::MakeFileNameUnique(const std::filesystem::path& path)
151+
{
152+
std::filesystem::path newPath(path);
153+
newPath.replace_filename(CreateUniqueFileName(path.filename().string(), path.extension().string()));
154+
return newPath;
155+
}
156+
145157
FileDownloader::FileDownloader()
146158
{
147159
}

Code/CryMP/Client/FileDownloader.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <stdint.h>
44
#include <string>
5+
#include <string_view>
56
#include <filesystem>
67
#include <functional>
78

@@ -32,6 +33,9 @@ struct FileDownloaderRequest
3233
std::filesystem::path filePath;
3334
std::function<bool(FileDownloaderProgress&)> onProgress; // return false to cancel download
3435
std::function<void(FileDownloaderResult&)> onComplete;
36+
37+
static std::string CreateUniqueFileName(std::string_view baseName, std::string_view extension);
38+
static std::filesystem::path MakeFileNameUnique(const std::filesystem::path& path);
3539
};
3640

3741
class FileDownloader

Code/CryMP/Client/MapDownloader.cpp

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
#include "CryCommon/CryAction/IGameFramework.h"
44
#include "CryCommon/CryAction/ILevelSystem.h"
55
#include "CrySystem/CryPak.h"
6+
#include "Library/StdFile.h"
67
#include "Library/Util.h"
7-
#include "Library/WinAPI.h"
88

99
#include "MapDownloader.h"
1010
#include "MapExtractor.h"
@@ -13,12 +13,9 @@
1313

1414
void MapDownloader::DownloadMap(MapDownloaderRequest && request)
1515
{
16-
// convert "multiplayer/ps/mymap" to "mymap.zip"
17-
const std::string zipFileName = std::filesystem::path(request.map).filename().string() + ".zip";
18-
1916
FileDownloaderRequest download;
2017
download.url = request.mapURL;
21-
download.filePath = m_downloadDir / zipFileName;
18+
download.filePath = m_downloadDir / FileDownloaderRequest::CreateUniqueFileName(request.map, ".zip");
2219

2320
CryLogAlways("$3[CryMP] [MapDownloader] Downloading map $6%s$3", request.map.c_str());
2421

@@ -152,41 +149,25 @@ bool MapDownloader::CheckMapExists(const std::string_view & mapName)
152149

153150
bool MapDownloader::CheckMapVersion(const std::string_view & mapName, const std::string_view & mapVersion)
154151
{
155-
if (mapVersion.empty())
152+
StdFile file(GetVersionFilePath(mapName).string().c_str(), "rb");
153+
if (!file.IsOpen())
156154
{
157-
return true;
155+
return mapVersion.empty();
158156
}
159157

160-
const std::filesystem::path versionFilePath = GetVersionFilePath(mapName);
161-
162-
try
163-
{
164-
WinAPI::File file(versionFilePath, WinAPI::FileAccess::READ_ONLY);
165-
166-
return file.IsOpen() && file.Read() == mapVersion;
167-
}
168-
catch (const CryMP_Error& error)
169-
{
170-
CryLogAlways("$4[CryMP] [MapDownloader] Failed to read map version: %s", error.what());
171-
return false;
172-
}
158+
return file.ReadAll() == mapVersion;
173159
}
174160

175161
void MapDownloader::StoreMapVersion(const std::string_view & mapName, const std::string_view & mapVersion)
176162
{
177-
const std::filesystem::path versionFilePath = GetVersionFilePath(mapName);
178-
179-
try
180-
{
181-
WinAPI::File file(versionFilePath, WinAPI::FileAccess::WRITE_ONLY_CREATE);
182-
183-
file.Resize(0);
184-
file.Write(mapVersion);
185-
}
186-
catch (const CryMP_Error& error)
163+
StdFile file(GetVersionFilePath(mapName).string().c_str(), "wb");
164+
if (!file.IsOpen())
187165
{
188-
CryLogAlways("$4[CryMP] [MapDownloader] Failed to store map version: %s", error.what());
166+
CryLogAlways("$4[CryMP] [MapDownloader] Failed to open map version file for writing");
167+
return;
189168
}
169+
170+
file.Write(mapVersion.data(), mapVersion.length());
190171
}
191172

192173
std::filesystem::path MapDownloader::GetVersionFilePath(const std::string_view & mapName)

0 commit comments

Comments
 (0)