Cogs.Core
IO.Windows.cpp
1#include "IO.h"
2
3#include "MemoryBufferBackedFileContents.h"
4#include "Unicode.h"
5
6#include "Unicode.h"
7#include "../Logging/Logger.h"
8#include "../StringUtilities.h"
9#include "../StringViewFormat.h"
10
11#include "Unicode.h"
12
13#include <ShlObj.h>
14#include <Shlwapi.h>
15#include <cinttypes>
16#include <fstream>
17#include <regex>
18namespace
19{
20 Cogs::Logging::Log envLogger = Cogs::Logging::getLogger("PlatformIO");
21
22 void logError(HRESULT hr, const char* prefix = "") {
23 thread_local static char err[2000];
24
25 err[0] = 0;
26 if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
27 nullptr,
28 hr,
29 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
30 err,
31 sizeof(err),
32 nullptr) != 0) {
33 // Kill newlines.
34 for(size_t i = 0; i < std::size(err); i++) {
35 if(err[i] == '\r' || err[i] == '\n') err[i] = ' ';
36 }
37 LOG_ERROR(envLogger, "%s: %s", prefix, err);
38 }
39 else {
40 LOG_ERROR(envLogger, "%s: Failed to retrieve error message for error code %08x.", prefix, hr);
41 }
42 }
43}
44
45void Cogs::IO::readFileAsync(const std::string & fileName, const FileContents::Callback& callback, uint64_t offset, uint64_t size, FileContentsHints hints)
46{
47 if (size == 0 && offset != 0) {
48 LOG_ERROR(envLogger, "Cogs::IO::readFileAsync: Illegal range, trying to read zero bytes from offset (%" PRIu64 ")", offset);
49 callback(std::unique_ptr<FileContents>());
50 return;
51 }
52
53 std::ifstream file(fileName, std::ios::binary | std::ios::in | std::ios::ate);
54 if (!file.is_open()) {
55 LOG_ERROR(envLogger, "Cogs::IO::readFileAsync: Failed to open file %s", fileName.c_str());
56 callback(std::unique_ptr<FileContents>());
57 return;
58 }
59
60 file.seekg(0, std::ios::end);
61 {
62 std::streampos fileSize = file.tellg();
63 if (fileSize < 0) {
64 LOG_ERROR(envLogger, "Cogs::IO::readFileAsync: tellg returned an error.");
65 goto error;
66 }
67 uint64_t readOffset = 0;
68 uint64_t readSize = static_cast<uint64_t>(fileSize);
69 if (size != 0) {
70 if (readSize < offset) {
71 LOG_ERROR(envLogger, "Cogs::IO::readFileAsync: offset (%" PRIu64 ") is greater than filesize (%" PRIu64 ")", offset, static_cast<uint64_t>(fileSize));
72 goto error;
73 }
74 readOffset = offset;
75 readSize -= readOffset;
76 if (readSize < size) {
77 LOG_ERROR(envLogger, "Cogs::IO::readFileAsync: offset (%" PRIu64 ") + size(%" PRIu64 ") is greater than filesize (%" PRIu64 ")", offset, size, static_cast<uint64_t>(fileSize));
78 goto error;
79 }
80 readSize = size;
81 }
82
83 if (static_cast<uint64_t>(std::numeric_limits<size_t>::max()) < static_cast<uint64_t>(readSize)) {
84 LOG_ERROR(envLogger, "Cogs::IO::readFileAsync: offset (%" PRIu64 ") + size(%" PRIu64 ") is greater than max size_t (>4GB read on 32bit?)", offset, size);
85 goto error;
86 }
87
88 auto contents = std::make_unique<MemoryBufferBackedFileContents>(readSize, fileName, hints);
89 file.seekg(static_cast<std::streamoff>(readOffset), std::ios::beg);
90 file.read((char*)contents->bytes.data(), contents->bytes.size());
91 file.close();
92 callback(std::move(contents));
93 }
94 return;
95
96error:
97 file.close();
98 callback(std::unique_ptr<FileContents>());
99 return;
100}
101
102Cogs::FileContents::Ptr Cogs::IO::readFileSync(const std::string & fileName, uint64_t offset, uint64_t size, FileContentsHints hints)
103{
104 if (size == 0 && offset != 0) {
105 LOG_ERROR(envLogger, "Cogs::IO::readFileSync: Illegal range, trying to read zero bytes from offset (%" PRIu64 ")", offset);
106 return std::unique_ptr<FileContents>();
107 }
108
109 std::ifstream file(fileName, std::ios::binary | std::ios::in | std::ios::ate);
110 if (!file.is_open()) {
111 LOG_ERROR(envLogger, "Cogs::IO::readFileSync: Failed to open file %s", fileName.c_str());
112 return std::unique_ptr<FileContents>();
113 }
114
115 file.seekg(0, std::ios::end);
116 std::streampos fileSize = file.tellg();
117 if (fileSize < 0) {
118 LOG_ERROR(envLogger, "Cogs::IO::readFileSync: tellg returned an error.");
119 return std::unique_ptr<FileContents>();
120 }
121 uint64_t readOffset = 0;
122 uint64_t readSize = static_cast<uint64_t>(fileSize);
123 if (size != 0) {
124 if (readSize < offset) {
125 LOG_ERROR(envLogger, "Cogs::IO::readFileSync: offset (%" PRIu64 ") is greater than filesize (%" PRIu64 ")", offset, static_cast<uint64_t>(fileSize));
126 return std::unique_ptr<FileContents>();
127 }
128 readOffset = offset;
129 readSize -= readOffset;
130 if (readSize < size) {
131 LOG_ERROR(envLogger, "Cogs::IO::readFileSync: offset (%" PRIu64 ") + size(%" PRIu64 ") is greater than filesize (%" PRIu64 ")", offset, size, static_cast<uint64_t>(fileSize));
132 return std::unique_ptr<FileContents>();
133 }
134 readSize = size;
135 }
136
137 if (static_cast<uint64_t>(std::numeric_limits<size_t>::max()) < readSize) {
138 LOG_ERROR(envLogger, "Cogs::IO::readFileSync: offset (%" PRIu64 ") + size(%" PRIu64 ") is greater than max size_t (>4GB read on 32bit?)", offset, size);
139 return std::unique_ptr<FileContents>();
140 }
141
142 auto contents = std::make_unique<MemoryBufferBackedFileContents>(readSize, fileName, hints);
143 file.seekg(static_cast<std::streamoff>(readOffset), std::ios::beg);
144 file.read((char*)contents->bytes.data(), contents->bytes.size());
145 file.close();
146 return contents;
147}
148
149bool Cogs::IO::readTextFile(std::string& content, const std::string & fileName) {
150 std::ifstream in(fileName);
151
152 if (in.good()) {
153 content = std::string((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());
154 return true;
155 }
156 LOG_ERROR(envLogger, "Cogs::IO::readTextFile(%s): Failed to open file.", fileName.c_str());
157 return false;
158}
159
160void Cogs::IO::autoExpandEnvironmentVariables(std::string & text) {
161 static std::regex env("%([0-9A-Za-z\\/]*)%");
162
163 std::smatch match;
164 while (std::regex_search(text, match, env)) {
165 const char * s = std::getenv(match[1].str().c_str());
166 const std::string environmentVariable(s == nullptr ? "" : s);
167
168 text.replace(match[0].first, match[0].second, environmentVariable);
169 }
170}
171
172std::string Cogs::IO::expandEnvironmentVariables(const std::string & input) {
173 std::string text = input;
174 autoExpandEnvironmentVariables(text);
175 return text;
176}
177
178std::string Cogs::IO::expandEnvironmentVariable(const std::string & name) {
179 const std::string decoratedName = "%" + name + "%";
180 char buffer[2048];
181
182 ExpandEnvironmentStringsA(decoratedName.c_str(), buffer, 2048);
183 return buffer;
184}
185
186char Cogs::IO::pathSeparator() {
187 return '\\';
188}
189
190bool Cogs::IO::exists(std::string_view path) {
191 return fs::exists(path);
192}
193
194bool Cogs::IO::isFile(std::string_view path) {
195 return fs::is_regular_file(path);
196}
197
198bool Cogs::IO::isDirectory(std::string_view path) {
199 return fs::is_directory(path);
200}
201
202std::string Cogs::IO::absolute(std::string_view path) {
203 return fs::absolute(fs::path(path)).string();
204}
205
206std::string Cogs::IO::canonical(std::string_view path) {
207 std::error_code ec;
208 return fs::canonical(fs::path(path), ec).string();
209}
210
211bool Cogs::IO::remove(std::string_view path) {
212 return fs::remove(path);
213}
214
215std::string Cogs::IO::combine(std::string_view path, std::string_view extensionPath) {
216 return (fs::path(path).append(extensionPath)).string();
217}
218
219std::string Cogs::IO::expandPath(std::string_view fileName) {
220 return expandEnvironmentVariables(fs::path(fileName).string());
221}
222
223std::string Cogs::IO::parentPath(std::string_view str) {
224 return fs::path(str.begin(), str.end()).parent_path().string();
225}
226
227std::string Cogs::IO::relativePath(std::string_view str) {
228 return fs::path(str).relative_path().string();
229}
230
231std::string Cogs::IO::relativePath(std::string_view str, std::string_view base) {
232 auto dir = fs::path(base);
233 auto p = fs::path(str);
234
235 dir = fs::absolute(dir);
236 p = fs::absolute(p);
237
238 auto beginA = p.begin();
239 auto beginB = dir.begin();
240
241 while (beginA != p.end() && beginB != dir.end()) {
242 if (*beginA != *beginB) break;
243
244 ++beginA;
245 ++beginB;
246 }
247
248 fs::path relative;
249 for (; beginB != dir.end(); ++beginB) {
250 relative /= "..";
251 }
252
253 for (; beginA != p.end(); ++beginA) {
254 relative /= *beginA;
255 }
256
257 return relative.string();
258}
259
260std::string Cogs::IO::getPathToExe() {
261 char path[_MAX_PATH];
262
263 if (GetModuleFileNameA(nullptr, path, sizeof(path))) {
264 return path;
265 }
266 logError(GetLastError(), "Cogs::UI::getPathToExe()");
267 return "";
268}
269
270std::string Cogs::IO::getPathRelativeToDll(const std::string& fileName, void* handle) {
271 wchar_t dllPath[MAX_PATH];
272 wchar_t fullPath[MAX_PATH];
273 HMODULE hm = nullptr;
274
275 if (!GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
276 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
277 static_cast<LPCSTR>(handle),
278 &hm)) {
279 std::string prefix = "Cogs::UI::getPathRelativeToDll(" + fileName + ")";
280 logError(GetLastError(), prefix.c_str());
281 return fileName;
282 }
283
284 GetModuleFileNameW(hm, dllPath, MAX_PATH);
285 PathRemoveFileSpecW(dllPath);
286 PathCombineW(fullPath, dllPath, Cogs::widen(fileName).c_str());
287
288 return narrow(fullPath);
289}
290
291std::string Cogs::IO::getTempPath() {
292 PWSTR str;
293 std::string result;
294 HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &str);
295
296 if (SUCCEEDED(hr)) {
297 char converted[_MAX_PATH];
298
299 if (!WideCharToMultiByte(CP_UTF8, 0, str, -1, converted, sizeof(converted), nullptr, nullptr)) {
300 logError(GetLastError(), "Cogs::IO::getTempPath()");
301 return "";
302 }
303 result = converted;
304 result += "\\Temp\\";
305 CoTaskMemFree(str);
306 }
307 else {
308 logError(hr, "Cogs::IO::getTempPath()");
309 }
310 return result;
311}
312
313std::string Cogs::IO::getAppDataPath() {
314 return getOrgLocalDataPath() + "\\Cogs\\";
315}
316
317std::string Cogs::IO::getOrgLocalDataPath() {
318 PWSTR ptr;
319 std::string path;
320
321 if (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &ptr) == S_OK) {
322 char converted[_MAX_PATH];
323
324 if (!WideCharToMultiByte(CP_UTF8, 0, ptr, -1, converted, sizeof(converted), nullptr, nullptr)) {
325 logError(GetLastError(), "Cogs::IO::getAppDataPath()");
326 return "";
327 }
328 path = converted;
329 path += "\\Kongsberg Digital\\";
330 CoTaskMemFree(ptr);
331 }
332 return path;
333}
334
335bool Cogs::IO::isAbsolute(std::string_view str) {
336 return !str.empty() && ((str.size() >= 1 && (str[0] == '/' || str[0] == '\\')) || (str.size() >= 2 && str[1] == ':'));
337}
338
339bool Cogs::IO::isRelative(std::string_view str) {
340 return !isAbsolute(str);
341}
342
343std::string Cogs::IO::fileName(std::string_view str) {
344 return fs::path(str).filename().string();
345}
346
347std::string Cogs::IO::extension(std::string_view str) {
348 return fs::path(str).extension().string();
349}
350
351std::string Cogs::IO::stem(std::string_view str) {
352 return fs::path(str.begin(), str.end()).stem().string();
353}
354
355void Cogs::IO::replaceAll(std::string& str, std::string_view from, std::string_view to) {
356 if (from.empty()) return;
357
358 size_t startPos = 0;
359 while ((startPos = str.find(from, startPos)) != std::string::npos) {
360 str.replace(startPos, from.length(), to);
361 startPos += to.length();
362 }
363}
364
365Cogs::IO::FileTimeType Cogs::IO::getFileTime(std::string_view path) {
366 std::error_code ec;
367 return fs::last_write_time(fs::path(path.begin(), path.end()), ec);
368}
369
370uint64_t Cogs::IO::getFileSize(std::string_view path) {
371 WIN32_FILE_ATTRIBUTE_DATA fad;
372 if (!GetFileAttributesExW(Cogs::widen(path).c_str(), GetFileExInfoStandard, &fad)) {
373 std::string prefix = Cogs::stringConcatenate({ "Cogs::IO::getFileSize(", path, ")" });
374 logError(GetLastError(), prefix.c_str());
375 return 0;
376 }
377 LARGE_INTEGER size;
378 size.HighPart = fad.nFileSizeHigh;
379 size.LowPart = fad.nFileSizeLow;
380 return static_cast<uint64_t>(size.QuadPart);
381}
382
383bool Cogs::IO::writeBinaryFile(const std::string& fileName, const void* content, size_t contentSize) {
384 if (!createDirectories(fileName)) {
385 return false;
386 }
387
388 HANDLE hFile = CreateFileW(Cogs::widen(fs::path(fileName).string()).c_str(),
389 GENERIC_WRITE,
390 0,
391 nullptr,
392 CREATE_ALWAYS,
393 FILE_ATTRIBUTE_NORMAL,
394 nullptr);
395
396 if (hFile == INVALID_HANDLE_VALUE) {
397 std::string prefix = "Cogs::IO::writeBinaryFile(" + fileName + ")";
398 logError(GetLastError(), prefix.c_str());
399 return false;
400 }
401
402 const uint8_t *src = static_cast<const uint8_t*>(content);
403 size_t remaining = contentSize;
404 while(remaining){
405 DWORD write_size = static_cast<DWORD>(std::min(remaining, static_cast<size_t>(std::numeric_limits<DWORD>::max())));
406 DWORD written = 0;
407
408 if (!WriteFile(hFile, src, write_size, &written, nullptr)) {
409 std::string prefix = "Cogs::IO::writeBinaryFile(" + fileName + ")";
410 logError(GetLastError(), prefix.c_str());
411 CloseHandle(hFile);
412 return false;
413 }
414 src += written;
415 remaining -= written;
416 }
417 CloseHandle(hFile);
418 return true;
419}
420
421bool Cogs::IO::createDirectories(std::string_view fileName) {
422 fs::path path(fileName);
423
424 if (path.has_parent_path() && !fs::exists(path.parent_path()) && !fs::create_directories(path.parent_path())) {
425 LOG_ERROR(envLogger, "Cogs::IO::createDirectories(%.*s) failed.", StringViewFormat(fileName));
426 return false;
427 }
428 return true;
429}
430
431Cogs::IO::RecursiveDirIterator Cogs::IO::directoryIterator(std::string_view directory) {
432 return RecursiveDirIterator(directory);
433}
434
435std::string Cogs::IO::getPath(const DirectoryEntry& entry) {
436 return entry.path().string();
437}
438
439Cogs::FileHandle::Ptr Cogs::IO::openFile(std::string_view path, FileHandle::OpenMode openMode, FileHandle::AccessMode accessMode) {
440 return FileHandle::open(path, openMode, accessMode);
441}
442
443void Cogs::IO::closeFile(const FileHandle::Ptr& handle) {
444 if (handle) {
445 handle->close();
446 }
447}
448
449size_t Cogs::IO::fileSize(const FileHandle::Ptr& handle) {
450 return handle ? handle->getSize() : 0;
451}
452
453void* Cogs::IO::mapFile(const FileHandle::Ptr& handle, FileHandle::ProtectionMode protectionMode, size_t offset, size_t size) {
454 return handle ? handle->map(protectionMode, offset, size) : nullptr;
455}
456
457void Cogs::IO::unmapFile(const FileHandle::Ptr& handle) {
458 if (handle) {
459 handle->unmap();
460 }
461}
462
463bool Cogs::IO::copyFile(std::string_view src, std::string_view dest) {
464 BOOL bFailIfExists = FALSE;
465
466 if (!CopyFileW(Cogs::widen(src).c_str(), Cogs::widen(dest).c_str(), bFailIfExists)) {
467 std::string prefix = Cogs::stringConcatenate({ "Cogs::IO::copyFile(", src, ", ", dest, ")" });
468 logError(GetLastError(), prefix.c_str());
469 return false;
470 }
471 return true;
472}
Log implementation class.
Definition: LogManager.h:139
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180