HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
file_win32_impl.hpp
1// Copyright Take Vos 2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
8
9#include "access_mode.hpp"
10#include "seek_whence.hpp"
11#include "../utility/utility.hpp"
12#include "../macros.hpp"
13#include <filesystem>
14
15hi_export_module(hikogui.file.file_impl);
16
17hi_export namespace hi { inline namespace v1 { namespace detail {
18
19hi_export class file_impl {
20public:
21 file_impl(file_impl const&) = delete;
22 file_impl(file_impl&&) = delete;
23 file_impl& operator=(file_impl const&) = delete;
24 file_impl& operator=(file_impl&&) = delete;
25
27 {
28 close();
29 }
30
31 file_impl(std::filesystem::path const& path, hi::access_mode access_mode) : _access_mode(access_mode)
32 {
33 DWORD desired_access = 0;
34 if (to_bool(access_mode & access_mode::read) and to_bool(access_mode & access_mode::write)) {
35 desired_access = GENERIC_READ | GENERIC_WRITE;
36 } else if (to_bool(access_mode & access_mode::read)) {
37 desired_access = GENERIC_READ;
38 } else if (to_bool(access_mode & access_mode::write)) {
39 desired_access = GENERIC_WRITE;
40 } else {
41 throw io_error(std::format("{}: Invalid AccessMode; expecting Readable and/or Writeable.", path.string()));
42 }
43
44 DWORD share_mode;
45 if (to_bool(access_mode & access_mode::write_lock)) {
46 share_mode = 0;
47 } else if (to_bool(access_mode & access_mode::read_lock)) {
48 share_mode = FILE_SHARE_READ;
49 } else {
50 // Allow files to be renamed and deleted.
51 share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
52 }
53
54 DWORD creation_disposition;
55 if (to_bool(access_mode & access_mode::create) and to_bool(access_mode & access_mode::open)) {
56 if (to_bool(access_mode & access_mode::truncate)) {
57 creation_disposition = CREATE_ALWAYS;
58 } else {
59 creation_disposition = OPEN_ALWAYS;
60 }
61
62 } else if (to_bool(access_mode & access_mode::create)) {
63 creation_disposition = CREATE_NEW;
64
65 } else if (to_bool(access_mode & access_mode::open)) {
66 if (to_bool(access_mode & access_mode::truncate)) {
67 creation_disposition = TRUNCATE_EXISTING;
68 } else {
69 creation_disposition = OPEN_EXISTING;
70 }
71
72 } else {
73 throw io_error(std::format("{}: Invalid AccessMode; expecting CreateFile and/or OpenFile.", path.string()));
74 }
75
76 DWORD flags_and_attributes = 0;
77 if (to_bool(access_mode & access_mode::random)) {
78 flags_and_attributes |= FILE_FLAG_RANDOM_ACCESS;
79 }
80 if (to_bool(access_mode & access_mode::sequential)) {
81 flags_and_attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
82 }
84 flags_and_attributes |= FILE_FLAG_WRITE_THROUGH;
85 }
86
87 if (to_bool(access_mode & access_mode::rename)) {
88 desired_access |= DELETE;
89 }
90
91 auto const file_name = path.native();
92 if ((_file_handle = CreateFileW(
93 file_name.data(), desired_access, share_mode, NULL, creation_disposition, flags_and_attributes, NULL)) !=
94 INVALID_HANDLE_VALUE) {
95 return;
96 }
97
98 auto const error = GetLastError();
99 if (to_bool(access_mode & access_mode::create_directories) and error == ERROR_PATH_NOT_FOUND and
100 (creation_disposition == CREATE_ALWAYS or creation_disposition == OPEN_ALWAYS or
101 creation_disposition == CREATE_NEW)) {
102 // Retry opening the file, by first creating the directory hierarchy.
103 auto directory = path;
104 directory.remove_filename();
105 std::filesystem::create_directories(directory);
106
107 if ((_file_handle = CreateFileW(
108 file_name.data(), desired_access, share_mode, NULL, creation_disposition, flags_and_attributes, NULL)) !=
109 INVALID_HANDLE_VALUE) {
110 return;
111 }
112 }
113 throw io_error(std::format("{}: Could not open file, '{}'", path.string(), get_last_error_message()));
114 }
115
116 [[nodiscard]] bool closed() noexcept
117 {
118 return _file_handle == nullptr;
119 }
120
121 [[nodiscard]] hi::access_mode access_mode() const
122 {
123 return _access_mode;
124 }
125
126 [[nodiscard]] HANDLE file_handle() const noexcept
127 {
128 return _file_handle;
129 }
130
131 void flush()
132 {
133 hi_assert_not_null(_file_handle);
134
135 if (not FlushFileBuffers(_file_handle)) {
136 throw io_error(std::format("{}: Could not flush file.", get_last_error_message()));
137 }
138 }
139
140 void close()
141 {
142 if (_file_handle != INVALID_HANDLE_VALUE) {
143 if (not CloseHandle(_file_handle)) {
144 throw io_error(std::format("{}: Could not close file.", get_last_error_message()));
145 }
146 _file_handle = INVALID_HANDLE_VALUE;
147 }
148 }
149
150 [[nodiscard]] std::size_t size() const
151 {
152 BY_HANDLE_FILE_INFORMATION file_information;
153
154 if (not GetFileInformationByHandle(_file_handle, &file_information)) {
155 throw io_error(std::format("{}: Could not get file information.", get_last_error_message()));
156 }
157
158 return merge_bit_cast<std::size_t>(file_information.nFileSizeHigh, file_information.nFileSizeLow);
159 }
160
161 std::size_t seek(ssize_t offset, seek_whence whence)
162 {
163 hi_assert_not_null(_file_handle);
164
165 DWORD whence_;
166 switch (whence) {
167 using enum seek_whence;
168 case begin:
169 whence_ = FILE_BEGIN;
170 break;
171 case current:
172 whence_ = FILE_CURRENT;
173 break;
174 case end:
175 whence_ = FILE_END;
176 break;
177 default:
178 hi_no_default();
179 }
180
181 LARGE_INTEGER offset_;
182 LARGE_INTEGER new_offset;
183 offset_.QuadPart = narrow_cast<LONGLONG>(offset);
184 if (not SetFilePointerEx(_file_handle, offset_, &new_offset, whence_)) {
185 throw io_error(std::format("{}: Could not seek in file.", get_last_error_message()));
186 }
187
188 return narrow_cast<std::size_t>(new_offset.QuadPart);
189 }
190
191 void rename(std::filesystem::path const& destination, bool overwrite_existing)
192 {
193 auto dst_filename = destination.native();
194 auto dst_filename_wsize = (dst_filename.size() + 1) * sizeof(WCHAR);
195
196 auto const rename_info_size = narrow_cast<DWORD>(sizeof(_FILE_RENAME_INFO) + dst_filename_wsize);
197
198 auto rename_info_alloc = std::string{};
199 rename_info_alloc.resize(rename_info_size);
200
201 auto rename_info = reinterpret_cast<PFILE_RENAME_INFO>(rename_info_alloc.data());
202
203 rename_info->ReplaceIfExists = overwrite_existing;
204 rename_info->RootDirectory = nullptr;
205 rename_info->FileNameLength = narrow_cast<DWORD>(dst_filename_wsize);
206 std::memcpy(rename_info->FileName, dst_filename.c_str(), dst_filename_wsize);
207
208 if (not SetFileInformationByHandle(_file_handle, FileRenameInfo, rename_info, rename_info_size)) {
209 throw io_error(std::format("Could not rename file to '{}': {}", destination.string(), get_last_error_message()));
210 }
211 }
212
213 void write(void const *data, std::size_t size)
214 {
215 hi_assert(_file_handle != INVALID_HANDLE_VALUE);
216
217 while (size != 0) {
218 // Copy in blocks of 32 kByte
219 auto to_write = size < 0x8000 ? narrow_cast<DWORD>(size) : DWORD{0x8000};
220 auto written = DWORD{};
221 if (not WriteFile(_file_handle, data, to_write, &written, nullptr)) {
222 throw io_error(std::format("{}: Could not write to file.", get_last_error_message()));
223
224 } else if (written == 0) {
225 throw io_error("Could not write to file. Reached end-of-file.");
226 }
227
228 advance_bytes(data, written);
229 size -= written;
230 }
231 }
232
233 [[nodiscard]] std::size_t read(void *data, std::size_t size)
234 {
235 hi_assert(_file_handle != INVALID_HANDLE_VALUE);
236
237 ssize_t total_read = 0;
238 while (size) {
239 auto to_read = size < 0x8000 ? narrow_cast<DWORD>(size) : DWORD{0x8000};
240 auto has_read = DWORD{};
241
242 if (!ReadFile(_file_handle, data, to_read, &has_read, nullptr)) {
243 throw io_error(std::format("{}: Could not read from file.", get_last_error_message()));
244
245 } else if (has_read == 0) {
246 // Read to end-of-file.
247 break;
248 }
249
250 advance_bytes(data, has_read);
251 size -= has_read;
252 total_read += has_read;
253 }
254
255 return total_read;
256 }
257
258private:
259 hi::access_mode _access_mode;
260 HANDLE _file_handle = nullptr;
261};
262
263}}} // namespace hi::v1::detail
Rules for working with win32 headers.
seek_whence
The position in the file to seek from.
Definition seek_whence.hpp:14
access_mode
The mode in which way to open a file.
Definition access_mode.hpp:17
@ current
Continue from the current position.
@ end
Start from the end of the file.
@ begin
Start from the beginning of the file.
@ truncate
After the file has been opened, truncate it.
@ read_lock
Lock the file for reading, i.e. shared-lock.
@ sequential
Hint that the data should be prefetched.
@ create
Create file if it does not exist, or fail.
@ open
Open file if it exist, or fail.
@ random
Hint the data should not be prefetched.
@ write_through
Hint that writes should be send directly to disk.
@ write_lock
Lock the file for writing, i.e. exclusive-lock.
@ rename
Allow renaming an open file.
@ read
Allow read access to a file.
@ write
Allow write access to a file.
@ create_directories
Create directory hierarchy, if the file could not be created.
The HikoGUI namespace.
Definition array_generic.hpp:20
hi_export std::string get_last_error_message()
Get the OS error message from the last error received on this thread.
Definition exception_win32_impl.hpp:30
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
Definition file_win32_impl.hpp:19
Exception thrown during I/O on an error.
Definition exception_intf.hpp:173
T memcpy(T... args)
T resize(T... args)