summaryrefslogtreecommitdiffstats
path: root/src/common/fs/file.h
blob: 2e2396075d758070b7ef78ea14c9385517cff14e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <cstdio>
#include <filesystem>
#include <span>
#include <type_traits>

#include "common/concepts.h"
#include "common/fs/fs_types.h"
#include "common/fs/fs_util.h"

namespace Common::FS {

enum class SeekOrigin {
    SetOrigin,       // Seeks from the start of the file.
    CurrentPosition, // Seeks from the current file pointer position.
    End,             // Seeks from the end of the file.
};

/**
 * Opens a file stream at path with the specified open mode.
 *
 * @param file_stream Reference to file stream
 * @param path Filesystem path
 * @param open_mode File stream open mode
 */
template <typename FileStream>
void OpenFileStream(FileStream& file_stream, const std::filesystem::path& path,
                    std::ios_base::openmode open_mode) {
    file_stream.open(path, open_mode);
}

#ifdef _WIN32
template <typename FileStream, typename Path>
void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::openmode open_mode) {
    if constexpr (IsChar<typename Path::value_type>) {
        file_stream.open(std::filesystem::path{ToU8String(path)}, open_mode);
    } else {
        file_stream.open(std::filesystem::path{path}, open_mode);
    }
}
#endif

/**
 * Reads an entire file at path and returns a string of the contents read from the file.
 * If the filesystem object at path is not a regular file, this function returns an empty string.
 *
 * @param path Filesystem path
 * @param type File type
 *
 * @returns A string of the contents read from the file.
 */
[[nodiscard]] std::string ReadStringFromFile(const std::filesystem::path& path, FileType type);

#ifdef _WIN32
template <typename Path>
[[nodiscard]] std::string ReadStringFromFile(const Path& path, FileType type) {
    if constexpr (IsChar<typename Path::value_type>) {
        return ReadStringFromFile(ToU8String(path), type);
    } else {
        return ReadStringFromFile(std::filesystem::path{path}, type);
    }
}
#endif

/**
 * Writes a string to a file at path and returns the number of characters successfully written.
 * If a file already exists at path, its contents will be erased.
 * If a file does not exist at path, it creates and opens a new empty file for writing.
 * If the filesystem object at path exists and is not a regular file, this function returns 0.
 *
 * @param path Filesystem path
 * @param type File type
 *
 * @returns Number of characters successfully written.
 */
[[nodiscard]] size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
                                       std::string_view string);

#ifdef _WIN32
template <typename Path>
[[nodiscard]] size_t WriteStringToFile(const Path& path, FileType type, std::string_view string) {
    if constexpr (IsChar<typename Path::value_type>) {
        return WriteStringToFile(ToU8String(path), type, string);
    } else {
        return WriteStringToFile(std::filesystem::path{path}, type, string);
    }
}
#endif

/**
 * Appends a string to a file at path and returns the number of characters successfully written.
 * If a file does not exist at path, it creates and opens a new empty file for appending.
 * If the filesystem object at path exists and is not a regular file, this function returns 0.
 *
 * @param path Filesystem path
 * @param type File type
 *
 * @returns Number of characters successfully written.
 */
[[nodiscard]] size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
                                        std::string_view string);

#ifdef _WIN32
template <typename Path>
[[nodiscard]] size_t AppendStringToFile(const Path& path, FileType type, std::string_view string) {
    if constexpr (IsChar<typename Path::value_type>) {
        return AppendStringToFile(ToU8String(path), type, string);
    } else {
        return AppendStringToFile(std::filesystem::path{path}, type, string);
    }
}
#endif

class IOFile final {
public:
    IOFile();

    explicit IOFile(const std::string& path, FileAccessMode mode,
                    FileType type = FileType::BinaryFile,
                    FileShareFlag flag = FileShareFlag::ShareReadOnly);

    explicit IOFile(std::string_view path, FileAccessMode mode,
                    FileType type = FileType::BinaryFile,
                    FileShareFlag flag = FileShareFlag::ShareReadOnly);

    /**
     * An IOFile is a lightweight wrapper on C Library file operations.
     * Automatically closes an open file on the destruction of an IOFile object.
     *
     * @param path Filesystem path
     * @param mode File access mode
     * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
     * @param flag (Windows only) File-share access flag, default is ShareReadOnly
     */
    explicit IOFile(const std::filesystem::path& path, FileAccessMode mode,
                    FileType type = FileType::BinaryFile,
                    FileShareFlag flag = FileShareFlag::ShareReadOnly);

    ~IOFile();

    IOFile(const IOFile&) = delete;
    IOFile& operator=(const IOFile&) = delete;

    IOFile(IOFile&& other) noexcept;
    IOFile& operator=(IOFile&& other) noexcept;

    /**
     * Gets the path of the file.
     *
     * @returns The path of the file.
     */
    [[nodiscard]] std::filesystem::path GetPath() const;

    /**
     * Gets the access mode of the file.
     *
     * @returns The access mode of the file.
     */
    [[nodiscard]] FileAccessMode GetAccessMode() const;

    /**
     * Gets the type of the file.
     *
     * @returns The type of the file.
     */
    [[nodiscard]] FileType GetType() const;

    /**
     * Opens a file at path with the specified file access mode.
     * This function behaves differently depending on the FileAccessMode.
     * These behaviors are documented in each enum value of FileAccessMode.
     *
     * @param path Filesystem path
     * @param mode File access mode
     * @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
     * @param flag (Windows only) File-share access flag, default is ShareReadOnly
     */
    void Open(const std::filesystem::path& path, FileAccessMode mode,
              FileType type = FileType::BinaryFile,
              FileShareFlag flag = FileShareFlag::ShareReadOnly);

#ifdef _WIN32
    template <typename Path>
    void Open(const Path& path, FileAccessMode mode, FileType type = FileType::BinaryFile,
              FileShareFlag flag = FileShareFlag::ShareReadOnly) {
        using ValueType = typename Path::value_type;
        if constexpr (IsChar<ValueType>) {
            Open(ToU8String(path), mode, type, flag);
        } else {
            Open(std::filesystem::path{path}, mode, type, flag);
        }
    }
#endif

    /// Closes the file if it is opened.
    void Close();

    /**
     * Checks whether the file is open.
     * Use this to check whether the calls to Open() or Close() succeeded.
     *
     * @returns True if the file is open, false otherwise.
     */
    [[nodiscard]] bool IsOpen() const;

    /**
     * Helper function which deduces the value type of a contiguous STL container used in ReadSpan.
     * If T is not a contiguous container as defined by the concept IsContiguousContainer, this
     * calls ReadObject and T must be a trivially copyable object.
     *
     * See ReadSpan for more details if T is a contiguous container.
     * See ReadObject for more details if T is a trivially copyable object.
     *
     * @tparam T Contiguous container or trivially copyable object
     *
     * @param data Container of T::value_type data or reference to object
     *
     * @returns Count of T::value_type data or objects successfully read.
     */
    template <typename T>
    [[nodiscard]] size_t Read(T& data) const {
        if constexpr (IsContiguousContainer<T>) {
            using ContiguousType = typename T::value_type;
            static_assert(std::is_trivially_copyable_v<ContiguousType>,
                          "Data type must be trivially copyable.");
            return ReadSpan<ContiguousType>(data);
        } else {
            return ReadObject(data) ? 1 : 0;
        }
    }

    /**
     * Helper function which deduces the value type of a contiguous STL container used in WriteSpan.
     * If T is not a contiguous STL container as defined by the concept IsContiguousContainer, this
     * calls WriteObject and T must be a trivially copyable object.
     *
     * See WriteSpan for more details if T is a contiguous container.
     * See WriteObject for more details if T is a trivially copyable object.
     *
     * @tparam T Contiguous container or trivially copyable object
     *
     * @param data Container of T::value_type data or const reference to object
     *
     * @returns Count of T::value_type data or objects successfully written.
     */
    template <typename T>
    [[nodiscard]] size_t Write(const T& data) const {
        if constexpr (IsContiguousContainer<T>) {
            using ContiguousType = typename T::value_type;
            static_assert(std::is_trivially_copyable_v<ContiguousType>,
                          "Data type must be trivially copyable.");
            return WriteSpan<ContiguousType>(data);
        } else {
            static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
            return WriteObject(data) ? 1 : 0;
        }
    }

    /**
     * Reads a span of T data from a file sequentially.
     * This function reads from the current position of the file pointer and
     * advances it by the (count of T * sizeof(T)) bytes successfully read.
     *
     * Failures occur when:
     * - The file is not open
     * - The opened file lacks read permissions
     * - Attempting to read beyond the end-of-file
     *
     * @tparam T Data type
     *
     * @param data Span of T data
     *
     * @returns Count of T data successfully read.
     */
    template <typename T>
    [[nodiscard]] size_t ReadSpan(std::span<T> data) const {
        static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");

        if (!IsOpen()) {
            return 0;
        }

        return std::fread(data.data(), sizeof(T), data.size(), file);
    }

    /**
     * Writes a span of T data to a file sequentially.
     * This function writes from the current position of the file pointer and
     * advances it by the (count of T * sizeof(T)) bytes successfully written.
     *
     * Failures occur when:
     * - The file is not open
     * - The opened file lacks write permissions
     *
     * @tparam T Data type
     *
     * @param data Span of T data
     *
     * @returns Count of T data successfully written.
     */
    template <typename T>
    [[nodiscard]] size_t WriteSpan(std::span<const T> data) const {
        static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");

        if (!IsOpen()) {
            return 0;
        }

        return std::fwrite(data.data(), sizeof(T), data.size(), file);
    }

    /**
     * Reads a T object from a file sequentially.
     * This function reads from the current position of the file pointer and
     * advances it by the sizeof(T) bytes successfully read.
     *
     * Failures occur when:
     * - The file is not open
     * - The opened file lacks read permissions
     * - Attempting to read beyond the end-of-file
     *
     * @tparam T Data type
     *
     * @param object Reference to object
     *
     * @returns True if the object is successfully read from the file, false otherwise.
     */
    template <typename T>
    [[nodiscard]] bool ReadObject(T& object) const {
        static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
        static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");

        if (!IsOpen()) {
            return false;
        }

        return std::fread(&object, sizeof(T), 1, file) == 1;
    }

    /**
     * Writes a T object to a file sequentially.
     * This function writes from the current position of the file pointer and
     * advances it by the sizeof(T) bytes successfully written.
     *
     * Failures occur when:
     * - The file is not open
     * - The opened file lacks write permissions
     *
     * @tparam T Data type
     *
     * @param object Const reference to object
     *
     * @returns True if the object is successfully written to the file, false otherwise.
     */
    template <typename T>
    [[nodiscard]] bool WriteObject(const T& object) const {
        static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
        static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");

        if (!IsOpen()) {
            return false;
        }

        return std::fwrite(&object, sizeof(T), 1, file) == 1;
    }

    /**
     * Specialized function to read a string of a given length from a file sequentially.
     * This function writes from the current position of the file pointer and
     * advances it by the number of characters successfully read.
     * The size of the returned string may not match length if not all bytes are successfully read.
     *
     * @param length Length of the string
     *
     * @returns A string read from the file.
     */
    [[nodiscard]] std::string ReadString(size_t length) const;

    /**
     * Specialized function to write a string to a file sequentially.
     * This function writes from the current position of the file pointer and
     * advances it by the number of characters successfully written.
     *
     * @param string Span of const char backed std::string or std::string_view
     *
     * @returns Number of characters successfully written.
     */
    [[nodiscard]] size_t WriteString(std::span<const char> string) const;

    /**
     * Attempts to flush any unwritten buffered data into the file.
     *
     * @returns True if the flush was successful, false otherwise.
     */
    bool Flush() const;

    /**
     * Attempts to commit the file into the disk.
     * Note that this is an expensive operation as this forces the operating system to write
     * the contents of the file associated with the file descriptor into the disk.
     *
     * @returns True if the commit was successful, false otherwise.
     */
    bool Commit() const;

    /**
     * Resizes the file to a given size.
     * If the file is resized to a smaller size, the remainder of the file is discarded.
     * If the file is resized to a larger size, the new area appears as if zero-filled.
     *
     * Failures occur when:
     * - The file is not open
     *
     * @param size File size in bytes
     *
     * @returns True if the file resize succeeded, false otherwise.
     */
    [[nodiscard]] bool SetSize(u64 size) const;

    /**
     * Gets the size of the file.
     *
     * Failures occur when:
     * - The file is not open
     *
     * @returns The file size in bytes of the file. Returns 0 on failure.
     */
    [[nodiscard]] u64 GetSize() const;

    /**
     * Moves the current position of the file pointer with the specified offset and seek origin.
     *
     * @param offset Offset from seek origin
     * @param origin Seek origin
     *
     * @returns True if the file pointer has moved to the specified offset, false otherwise.
     */
    [[nodiscard]] bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const;

    /**
     * Gets the current position of the file pointer.
     *
     * @returns The current position of the file pointer.
     */
    [[nodiscard]] s64 Tell() const;

private:
    std::filesystem::path file_path;
    FileAccessMode file_access_mode{};
    FileType file_type{};

    std::FILE* file = nullptr;
};

} // namespace Common::FS