mirror of
https://github.com/mackron/miniaudio.git
synced 2026-04-22 00:06:59 +02:00
Update fs and fix some build errors with -std=c89.
This commit is contained in:
Vendored
+1429
-613
File diff suppressed because it is too large
Load Diff
Vendored
+457
-148
@@ -39,49 +39,154 @@ if (result != FS_SUCCESS) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you don't need any of the advanced features of the library, you can just pass in NULL for the
|
||||||
|
`fs` object which will just use the native file system like normal:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_file_open(NULL, "file.txt", FS_READ, &pFile);
|
||||||
|
```
|
||||||
|
|
||||||
|
From here on out, examples will use an `fs` object for the sake of consistency, but all basic IO
|
||||||
|
APIs that do not use things like mounting and archive registration will work with NULL.
|
||||||
|
|
||||||
|
To close a file, use `fs_file_close()`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_file_close(pFile);
|
||||||
|
```
|
||||||
|
|
||||||
Reading content from the file is very standard:
|
Reading content from the file is very standard:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
size_t bytesRead;
|
size_t bytesRead;
|
||||||
|
|
||||||
result = fs_file_read(pFS, pBuffer, bytesToRead, &bytesRead);
|
result = fs_file_read(pFile, pBuffer, bytesToRead, &bytesRead);
|
||||||
if (result != FS_SUCCESS) {
|
if (result != FS_SUCCESS) {
|
||||||
// Failed to read file. You can use FS_AT_END to check if reading failed due to being at EOF.
|
// Failed to read file. You can use FS_AT_END to check if reading failed due to being at EOF.
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In the code above, the number of bytes actually read is output to a variable. You can use this to
|
In the code above, the number of bytes actually read is output to a variable. You can use this to
|
||||||
determine if you've reached the end of the file. You can also check if the result is FS_AT_END.
|
determine if you've reached the end of the file. You can also check if the result is FS_AT_END. You
|
||||||
|
can pass in null for the last parameter of `fs_file_read()` in which an error will be returned if
|
||||||
|
the exact number of bytes requested could not be read.
|
||||||
|
|
||||||
To do more advanced stuff, such as opening from archives, you'll need to configure the `fs` object
|
Writing works the same way as reading:
|
||||||
with a config, which you pass into `fs_init()`:
|
|
||||||
|
```c
|
||||||
|
fs_file* pFile;
|
||||||
|
|
||||||
|
result = fs_file_open(pFS, "file.txt", FS_WRITE, &pFile);
|
||||||
|
if (result != FS_SUCCESS) {
|
||||||
|
// Failed to open file.
|
||||||
|
}
|
||||||
|
|
||||||
|
result = fs_file_write(pFile, pBuffer, bytesToWrite, &bytesWritten);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `FS_WRITE` option will default to `FS_TRUNCATE`. You can also use `FS_APPEND` and
|
||||||
|
`FS_OVERWRITE`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_file_open(pFS, "file.txt", FS_APPEND, &pFile); // You need not specify FS_WRITE when using FS_TRUNCATE, FS_APPEND or FS_OVERWRITE as it is implied.
|
||||||
|
```
|
||||||
|
|
||||||
|
Files can be opened for both reading and writing by simply combining the two:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_file_open(pFS, "file.txt", FS_READ | FS_WRITE, &pFile);
|
||||||
|
```
|
||||||
|
|
||||||
|
Seeking and telling is very standard as well:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_file_seek(pFile, 0, FS_SEEK_END);
|
||||||
|
|
||||||
|
fs_int64 cursorPos;
|
||||||
|
fs_file_tell(pFile, &cursorPos);
|
||||||
|
```
|
||||||
|
|
||||||
|
Retrieving information about a file is done with `fs_file_info()`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_file_info info;
|
||||||
|
fs_file_info(pFile, &info);
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to get information about a file without opening it, you can use `fs_info()`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_file_info info;
|
||||||
|
fs_info(pFS, "file.txt", &info);
|
||||||
|
```
|
||||||
|
|
||||||
|
A file handle can be duplicated with `fs_file_duplicate()`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_file* pFileDup;
|
||||||
|
fs_file_duplicate(pFile, &pFileDup);
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this will only duplicate the file handle. It does not make a copy of the file on the file
|
||||||
|
system itself. The duplicated file handle will be entirely independent of the original handle.
|
||||||
|
|
||||||
|
To delete a file, use `fs_remove()`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_remove(pFS, "file.txt");
|
||||||
|
```
|
||||||
|
|
||||||
|
Files can be renamed and moved with `fs_rename()`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_rename(pFS, "file.txt", "new-file.txt");
|
||||||
|
```
|
||||||
|
|
||||||
|
To create a directory, use `fs_mkdir()`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_mkdir(pFS, "new-directory", 0);
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, `fs_mkdir()` will create the directory hierarchy for you. If you want to disable this
|
||||||
|
so it fails if the directory hierarchy doesn't exist, you can use `FS_NO_CREATE_DIRS`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_mkdir(pFS, "new-directory", FS_NO_CREATE_DIRS);
|
||||||
|
```
|
||||||
|
|
||||||
|
1.2. Archives
|
||||||
|
-------------
|
||||||
|
To enable support for archives, you need an `fs` object, and it must be initialized with a config:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
#include "extras/backends/zip/fs_zip.h" // <-- This is where FS_ZIP is declared.
|
#include "extras/backends/zip/fs_zip.h" // <-- This is where FS_ZIP is declared.
|
||||||
|
#include "extras/backends/pak/fs_pak.h" // <-- This is where FS_PAK is declared.
|
||||||
|
|
||||||
...
|
...
|
||||||
|
|
||||||
fs_archive_type pArchiveTypes[] =
|
fs_archive_type pArchiveTypes[] =
|
||||||
{
|
{
|
||||||
{FS_ZIP, "zip"},
|
{FS_ZIP, "zip"},
|
||||||
{FS_ZIP, "pac"}
|
{FS_PAK, "pak"}
|
||||||
};
|
};
|
||||||
|
|
||||||
fs_config fsConfig = fs_config_init(FS_STDIO, NULL, NULL);
|
fs_config fsConfig = fs_config_init(FS_STDIO, NULL, NULL);
|
||||||
fsConfig.pArchiveTypes = pArchiveTypes;
|
fsConfig.pArchiveTypes = pArchiveTypes;
|
||||||
fsConfig.archiveTypeCount = sizeof(pArchiveTypes) / sizeof(pArchiveTypes[0]);
|
fsConfig.archiveTypeCount = sizeof(pArchiveTypes) / sizeof(pArchiveTypes[0]);
|
||||||
|
|
||||||
|
fs* pFS;
|
||||||
fs_init(&fsConfig, &pFS);
|
fs_init(&fsConfig, &pFS);
|
||||||
```
|
```
|
||||||
|
|
||||||
In the code above we are registering support for ZIP archives (`FS_ZIP`). Whenever a file with a
|
In the code above we are registering support for ZIP archives (`FS_ZIP`) and Quake PAK archives
|
||||||
"zip" or "pac" extension is found, the library will be able to access the archive. The library will
|
(`FS_PAK`). Whenever a file with a "zip" or "pak" extension is found, the library will be able to
|
||||||
determine whether or not a file is an archive based on it's extension. You can use whatever
|
access the archive. The library will determine whether or not a file is an archive based on it's
|
||||||
extension you would like for a backend, and you can associated multiple extensions to the same
|
extension. You can use whatever extension you would like for a backend, and you can associated
|
||||||
backend. You can also associated different backends to the same extension, in which case the
|
multiple extensions to the same backend. You can also associate different backends to the same
|
||||||
library will use the first one that works. If the extension of a file does not match with one of
|
extension, in which case the library will use the first one that works. If the extension of a file
|
||||||
the registered archive types it'll assume it's not an archive and will skip it. Below is an example
|
does not match with one of the registered archive types it'll assume it's not an archive and will
|
||||||
of one way you can read from an archive:
|
skip it. Below is an example of one way you can read from an archive:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
result = fs_file_open(pFS, "archive.zip/file-inside-archive.txt", FS_READ, &pFile);
|
result = fs_file_open(pFS, "archive.zip/file-inside-archive.txt", FS_READ, &pFile);
|
||||||
@@ -116,8 +221,8 @@ if (result != FS_SUCCESS) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In the example above, `FS_OPAQUE` is telling the library to treat archives as if they're totally
|
In the example above, opening the file will fail because `FS_OPAQUE` is telling the library to
|
||||||
opaque and that the files within cannot be accessed.
|
treat archives as if they're totally opaque which means the files within cannot be accessed.
|
||||||
|
|
||||||
Up to this point the handling of archives has been done automatically via `fs_file_open()`, however
|
Up to this point the handling of archives has been done automatically via `fs_file_open()`, however
|
||||||
the library allows you to manage archives manually. To do this you just initialize a `fs` object to
|
the library allows you to manage archives manually. To do this you just initialize a `fs` object to
|
||||||
@@ -170,34 +275,45 @@ In addition to the above, you can use `fs_open_archive()` to open an archive fro
|
|||||||
|
|
||||||
```c
|
```c
|
||||||
fs* pArchive;
|
fs* pArchive;
|
||||||
|
fs_open_archive(pFS, "archive.zip", FS_READ, &pArchive);
|
||||||
|
|
||||||
result = fs_open_archive(pFS, "archive.zip", FS_READ, &pArchive);
|
...
|
||||||
|
|
||||||
|
// When tearing down, do *not* use `fs_uninit()`. Use `fs_close_archive()` instead.
|
||||||
|
fs_close_archive(pArchive);
|
||||||
```
|
```
|
||||||
|
|
||||||
When opening an archive like this, it will inherit the archive types from the parent `fs` object
|
Note that you need to use `fs_close_archive()` when opening an archive like this. The reason for
|
||||||
and will therefore support archives within archives. Use caution when doing this because if both
|
this is that there's some internal reference counting and memory management happening under the
|
||||||
archives are compressed you will get a big performance hit. Only the inner-most archive should be
|
hood. You should only call `fs_close_archive()` if `fs_open_archive()` succeeds.
|
||||||
compressed.
|
|
||||||
|
When opening an archive with `fs_open_archive()`, it will inherit the archive types from the parent
|
||||||
|
`fs` object and will therefore support archives within archives. Use caution when doing this
|
||||||
|
because if both archives are compressed you will get a big performance hit. Only the inner-most
|
||||||
|
archive should be compressed.
|
||||||
|
|
||||||
|
|
||||||
1.2. Mounting
|
1.3. Mounting
|
||||||
-------------
|
-------------
|
||||||
There is no notion of a "current directory" in this library. By default, relative paths will be
|
There is no ability to change the working directory in this library. Instead you can mount a
|
||||||
relative to whatever the backend deems appropriate. In practice, this means the "current" directory
|
physical directory to a virtual path, similar in concept to Unix operating systems. The difference,
|
||||||
for the default system backend, and the root directory for archives. There is still control over
|
however, is that you can mount multiple directories to the same mount point in which case a
|
||||||
how to load files from a relative path, however: mounting.
|
prioritization system will be used. There are separate mount points for reading and writing. Below
|
||||||
|
is an example of mounting for reading:
|
||||||
You can mount a physical directory to virtual path, similar in concept to Unix operating systems.
|
|
||||||
The difference, however, is that you can mount multiple directories to the same mount point in
|
|
||||||
which case a prioritization system will be used. There are separate mount points for reading and
|
|
||||||
writing. Below is an example of mounting for reading:
|
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fs_mount(pFS, "/some/actual/path", NULL, FS_MOUNT_PRIORITY_HIGHEST);
|
fs_mount(pFS, "/some/actual/path", NULL, FS_READ);
|
||||||
```
|
```
|
||||||
|
|
||||||
In the example above, `NULL` is equivalent to an empty path. If, for example, you have a file with
|
To unmount, you need to specify the actual path, not the virtual path:
|
||||||
the path "/some/actual/path/file.txt", you can open it like the following:
|
|
||||||
|
```c
|
||||||
|
fs_unmount(pFS, "/some/actual/path", FS_READ);
|
||||||
|
```
|
||||||
|
|
||||||
|
In the example above, using `NULL` for the virtual path is equivalent to an empty path. If, for
|
||||||
|
example, you have a file with the path "/some/actual/path/file.txt", you can open it like the
|
||||||
|
following:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fs_file_open(pFS, "file.txt", FS_READ, &pFile);
|
fs_file_open(pFS, "file.txt", FS_READ, &pFile);
|
||||||
@@ -207,76 +323,75 @@ You don't need to specify the "/some/actual/path" part because it's handled by t
|
|||||||
specify a virtual path, you can do something like the following:
|
specify a virtual path, you can do something like the following:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fs_mount(pFS, "/some/actual/path", "assets", FS_MOUNT_PRIORITY_HIGHEST);
|
fs_mount(pFS, "/some/actual/path", "assets", FS_READ);
|
||||||
```
|
```
|
||||||
|
|
||||||
In this case, loading files that are physically located in "/some/actual/path" would need to be
|
In this case, loading files that are physically located in "/some/actual/path" would need to be
|
||||||
prexied with "assets":
|
prefixed with "assets":
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fs_file_open(pFS, "assets/file.txt", FS_READ, &pFile);
|
fs_file_open(pFS, "assets/file.txt", FS_READ, &pFile);
|
||||||
```
|
```
|
||||||
|
|
||||||
Archives can also be mounted:
|
You can mount multiple paths to the same virtual path in which case a prioritization system will be
|
||||||
|
used:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fs_mount(pFS, "/game/data/base/assets.zip", "assets", FS_MOUNT_PRIORITY_HIGHEST);
|
fs_mount(pFS, "/usr/share/mygame/gamedata/base", "gamedata", FS_READ); // Base game. Lowest priority.
|
||||||
|
fs_mount(pFS, "/home/user/.local/share/mygame/gamedata/mod1", "gamedata", FS_READ); // Mod #1. Middle priority.
|
||||||
|
fs_mount(pFS, "/home/user/.local/share/mygame/gamedata/mod2", "gamedata", FS_READ); // Mod #2. Highest priority.
|
||||||
```
|
```
|
||||||
|
|
||||||
You can mount multiple paths to the same mount point:
|
The example above shows a basic system for setting up some kind of modding support in a game. In
|
||||||
|
this case, attempting to load a file from the "gamedata" mount point will first check the "mod2"
|
||||||
|
directory, and if it cannot be opened from there, it will check "mod1", and finally it'll fall back
|
||||||
|
to the base game data.
|
||||||
|
|
||||||
|
Internally there are a separate set of mounts for reading and writing. To set up a mount point for
|
||||||
|
opening files in write mode, you need to specify the `FS_WRITE` option:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fs_mount(pFS, "/game/data/base.zip", "assets", FS_MOUNT_PRIORITY_HIGHEST);
|
fs_mount(pFS, "/home/user/.config/mygame", "config", FS_WRITE);
|
||||||
fs_mount(pFS, "/game/data/mod1.zip", "assets", FS_MOUNT_PRIORITY_HIGHEST);
|
fs_mount(pFS, "/home/user/.local/share/mygame/saves", "saves", FS_WRITE);
|
||||||
fs_mount(pFS, "/game/data/mod2.zip", "assets", FS_MOUNT_PRIORITY_HIGHEST);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
In the example above, the "base.zip" archive is mounted first. Then "mod1.zip" is mounted, which
|
To open a file for writing, you need only prefix the path with the mount's virtual path, exactly
|
||||||
takes higher priority over "base.zip". Then "mod2.zip" is mounted which takes higher priority
|
like read mode:
|
||||||
again. With this set up, any file that is loaded from the "assets" mount point will first be loaded
|
|
||||||
from "mod2.zip", and if it doesn't exist there, "mod1.zip", and if not there, finally "base.zip".
|
|
||||||
You could use this set up to support simple modding prioritization in a game, for example.
|
|
||||||
|
|
||||||
If the file cannot be opened from any mounts it will attempt to open the file from the backend's
|
|
||||||
default search path. Mounts always take priority. When opening in transparent mode with
|
|
||||||
`FS_TRANSPARENT` (default), it will first try opening the file as if it were not in an archive. If
|
|
||||||
that fails, it will look inside archives.
|
|
||||||
|
|
||||||
You can also mount directories for writing:
|
|
||||||
|
|
||||||
```c
|
|
||||||
fs_mount_write(pFS, "/home/user/.config/mygame", "config", FS_MOUNT_PRIORITY_HIGHEST);
|
|
||||||
```
|
|
||||||
|
|
||||||
You can then open a file for writing like so:
|
|
||||||
|
|
||||||
```c
|
|
||||||
fs_file_open(pFS, "config/game.cfg", FS_WRITE, &pFile);
|
|
||||||
```
|
|
||||||
|
|
||||||
When opening a file in write mode, the prefix is what determines which write mount point to use.
|
|
||||||
You can therefore have multiple write mounts:
|
|
||||||
|
|
||||||
```c
|
|
||||||
fs_mount_write(pFS, "/home/user/.config/mygame", "config", FS_MOUNT_PRIORITY_HIGHEST);
|
|
||||||
fs_mount_write(pFS, "/home/user/.local/share/mygame/saves", "saves", FS_MOUNT_PRIORITY_HIGHEST);
|
|
||||||
```
|
|
||||||
|
|
||||||
Now you can write out different types of files, with the prefix being used to determine where it'll
|
|
||||||
be saved:
|
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fs_file_open(pFS, "config/game.cfg", FS_WRITE, &pFile); // Prefixed with "config", so will use the "config" mount point.
|
fs_file_open(pFS, "config/game.cfg", FS_WRITE, &pFile); // Prefixed with "config", so will use the "config" mount point.
|
||||||
fs_file_open(pFS, "saves/save0.sav", FS_WRITE, &pFile); // Prefixed with "saves", so will use the "saves" mount point.
|
fs_file_open(pFS, "saves/save0.sav", FS_WRITE, &pFile); // Prefixed with "saves", so will use the "saves" mount point.
|
||||||
```
|
```
|
||||||
|
|
||||||
When opening a file for writing, if you pass in NULL for the `pFS` parameter it will open the file
|
If you want to mount a directory for reading and writing, you can use both `FS_READ` and
|
||||||
like normal using the standard file system. That is it'll work exactly as if you were using stdio
|
`FS_WRITE` together:
|
||||||
`fopen()` and you will not be able use mount points. Keep in mind that there is no notion of a
|
|
||||||
"current directory" in this library so you'll be stuck with the initial working directory.
|
```c
|
||||||
|
fs_mount(pFS, "/home/user/.config/mygame", "config", FS_READ | FS_WRITE);
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set up read and write mount points to the same virtual path:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_mount(pFS, "/usr/share/mygame/config", "config", FS_READ);
|
||||||
|
fs_mount(pFS, "/home/user/.local/share/mygame/config", "config", FS_READ | FS_WRITE);
|
||||||
|
```
|
||||||
|
|
||||||
|
When opening a file for reading, it'll first try searching the second mount point, and if it's not
|
||||||
|
found will fall back to the first. When opening in write mode, it will only ever use the second
|
||||||
|
mount point as the output directory because that's the only one set up with `FS_WRITE`. With this
|
||||||
|
setup, the first mount point is essentially protected from modification.
|
||||||
|
|
||||||
|
When mounting a directory for writing, the library will create the directory hierarchy for you. If
|
||||||
|
you want to disable this functionality, you can use the `FS_NO_CREATE_DIRS` flag:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_mount(pFS, "/home/user/.config/mygame", "config", FS_WRITE | FS_NO_CREATE_DIRS);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
By default, you can move outside the mount point with ".." segments. If you want to disable this
|
By default, you can move outside the mount point with ".." segments. If you want to disable this
|
||||||
functionality, you can use the `FS_NO_ABOVE_ROOT_NAVIGATION` flag:
|
functionality, you can use the `FS_NO_ABOVE_ROOT_NAVIGATION` flag when opening the file:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fs_file_open(pFS, "../file.txt", FS_READ | FS_NO_ABOVE_ROOT_NAVIGATION, &pFile);
|
fs_file_open(pFS, "../file.txt", FS_READ | FS_NO_ABOVE_ROOT_NAVIGATION, &pFile);
|
||||||
@@ -286,19 +401,75 @@ In addition, any mount points that start with a "/" will be considered absolute
|
|||||||
any above-root navigation:
|
any above-root navigation:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
fs_mount(pFS, "/game/data/base", "/gamedata", FS_MOUNT_PRIORITY_HIGHEST);
|
fs_mount(pFS, "/usr/share/mygame/gamedata/base", "/gamedata", FS_READ);
|
||||||
```
|
```
|
||||||
|
|
||||||
In the example above, the "/gamedata" mount point starts with a "/", so it will not allow any
|
In the example above, the "/gamedata" mount point starts with a "/", so it will not allow any
|
||||||
above-root navigation which means you cannot navigate above "/game/data/base" when using this mount
|
above-root navigation which means you cannot navigate above "/usr/share/mygame/gamedata/base". When
|
||||||
point.
|
opening a file with this kind of mount point, you would need to specify the leading slash:
|
||||||
|
|
||||||
Note that writing directly into an archive is not supported by this API. To write into an archive,
|
```c
|
||||||
the backend itself must support writing, and you will need to manually initialize a `fs` object for
|
fs_file_open(pFS, "/gamedata/file.txt", FS_READ, &pFile); // Note how the path starts with "/".
|
||||||
the archive an write into it directly.
|
```
|
||||||
|
|
||||||
|
|
||||||
1.3. Enumeration
|
You can also mount a archives to a virtual path:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_mount(pFS, "/usr/share/mygame/gamedata.zip", "gamedata", FS_READ);
|
||||||
|
```
|
||||||
|
|
||||||
|
In order to do this, the `fs` object must have been configured with support for the given archive
|
||||||
|
type. Note that writing directly into an archive is not supported by this API. To write into an
|
||||||
|
archive, the backend itself must support writing, and you will need to manually initialize a `fs`
|
||||||
|
object for the archive an write into it directly.
|
||||||
|
|
||||||
|
|
||||||
|
The examples above have been hard coding paths, but you can use `fs_mount_sysdir()` to mount a
|
||||||
|
system directory to a virtual path. This is just a convenience helper function, and you need not
|
||||||
|
use it if you'd rather deal with system directories yourself:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_mount_sysdir(pFS, FS_SYSDIR_CONFIG, "myapp", "/config", FS_READ | FS_WRITE);
|
||||||
|
```
|
||||||
|
|
||||||
|
This function requires that you specify a sub-directory of the system directory to mount. The reason
|
||||||
|
for this is to encourage the application to use good practice to avoid cluttering the file system.
|
||||||
|
|
||||||
|
Use `fs_unmount_sysdir()` to unmount a system directory. When using this you must specify the
|
||||||
|
sub-directory you used when mounting it:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_unmount_sysdir(pFS, FS_SYSDIR_CONFIG, "myapp", FS_READ | FS_WRITE);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Mounting a `fs` object to a virtual path is also supported.
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs* pSomeOtherFS; // <-- This would have been initialized earlier.
|
||||||
|
|
||||||
|
fs_mount_fs(pFS, pSomeOtherFS, "assets.zip", FS_READ);
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
fs_unmount_fs(pFS, pSomeOtherFS, FS_READ);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
If the file cannot be opened from any mounts it will attempt to open the file from the backend's
|
||||||
|
default search path. Mounts always take priority. When opening in transparent mode with
|
||||||
|
`FS_TRANSPARENT` (default), it will first try opening the file as if it were not in an archive. If
|
||||||
|
that fails, it will look inside archives.
|
||||||
|
|
||||||
|
When opening a file, if you pass in NULL for the `pFS` parameter it will open the file like normal
|
||||||
|
using the standard file system. That is, it'll work exactly as if you were using stdio `fopen()`,
|
||||||
|
and you will not have access to mount points. Keep in mind that there is no notion of a "current
|
||||||
|
directory" in this library so you'll be stuck with the initial working directory.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1.4. Enumeration
|
||||||
----------------
|
----------------
|
||||||
You can enumerate over the contents of a directory like the following:
|
You can enumerate over the contents of a directory like the following:
|
||||||
|
|
||||||
@@ -321,8 +492,101 @@ Internally, `fs_first()` will gather all of the enumerated files. This means you
|
|||||||
|
|
||||||
Enumerated entries will be sorted by name in terms of `strcmp()`.
|
Enumerated entries will be sorted by name in terms of `strcmp()`.
|
||||||
|
|
||||||
Enumeration is not recursive. If you want to enumerate recursively you can inspect the `directory`
|
Enumeration is not recursive. If you want to enumerate recursively you will need to do it manually.
|
||||||
member of the `info` member in `fs_iterator`.
|
You can inspect the `directory` member of the `info` member in `fs_iterator` to determine if the
|
||||||
|
entry is a directory.
|
||||||
|
|
||||||
|
|
||||||
|
1.5. System Directories
|
||||||
|
-----------------------
|
||||||
|
It can often be useful to know the exact paths of known standard system directories, such as the
|
||||||
|
home directory. You can use the `fs_sysdir()` function for this:
|
||||||
|
|
||||||
|
```c
|
||||||
|
char pPath[256];
|
||||||
|
size_t pathLen = fs_sysdir(FS_SYSDIR_HOME, pPath, sizeof(pPath));
|
||||||
|
if (pathLen > 0) {
|
||||||
|
if (pathLen < sizeof(pPath)) {
|
||||||
|
// Success!
|
||||||
|
} else {
|
||||||
|
// The buffer was too small. Expand the buffer to at least `pathLen + 1` and try again.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// An error occurred.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`fs_sysdir()` will return the length of the path written to `pPath`, or 0 if an error occurred. If
|
||||||
|
the buffer is too small, it will return the required size, not including the null terminator.
|
||||||
|
|
||||||
|
Recognized system directories include the following:
|
||||||
|
|
||||||
|
- FS_SYSDIR_HOME
|
||||||
|
- FS_SYSDIR_TEMP
|
||||||
|
- FS_SYSDIR_CONFIG
|
||||||
|
- FS_SYSDIR_DATA
|
||||||
|
- FS_SYSDIR_CACHE
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
1.6. Temporary Files
|
||||||
|
--------------------
|
||||||
|
You can create a temporary file or folder with `fs_mktmp()`. To create a temporary folder, use the
|
||||||
|
`FS_MKTMP_DIR` option:
|
||||||
|
|
||||||
|
```c
|
||||||
|
char pTmpPath[256];
|
||||||
|
fs_result result = fs_mktmp("prefix", pTmpPath, sizeof(pTmpPath), FS_MKTMP_DIR);
|
||||||
|
if (result != FS_SUCCESS) {
|
||||||
|
// Failed to create temporary file.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Similarly, to create a temporary file, use the `FS_MKTMP_FILE` option:
|
||||||
|
|
||||||
|
```c
|
||||||
|
char pTmpPath[256];
|
||||||
|
fs_result result = fs_mktmp("prefix", pTmpPath, sizeof(pTmpPath), FS_MKTMP_FILE);
|
||||||
|
if (result != FS_SUCCESS) {
|
||||||
|
// Failed to create temporary file.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`fs_mktmp()` will create a temporary file or folder with a unique name based on the provided
|
||||||
|
prefix and will return the full path to the created file or folder in `pTmpPath`. To open the
|
||||||
|
temporary file, you can pass in the path to `fs_file_open()`, making sure to ignore mount points
|
||||||
|
with `FS_IGNORE_MOUNTS`:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_file* pFile;
|
||||||
|
result = fs_file_open(pFS, pTmpPath, FS_WRITE | FS_IGNORE_MOUNTS, &pFile);
|
||||||
|
if (result != FS_SUCCESS) {
|
||||||
|
// Failed to open temporary file.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you just want to create a temporary file and don't care about the name, you can use
|
||||||
|
`fs_file_open()` with the `FS_TEMP` flag. In this case, the library will treat the file path
|
||||||
|
as the prefix:
|
||||||
|
|
||||||
|
```c
|
||||||
|
fs_file* pFile;
|
||||||
|
result = fs_file_open(pFS, "prefix", FS_TEMP, &pFile);
|
||||||
|
if (result != FS_SUCCESS) {
|
||||||
|
// Failed to open temporary file.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The use of temporary files is only valid with `fs` objects that make use of the standard file
|
||||||
|
system, such as the stdio backend.
|
||||||
|
|
||||||
|
The prefix can include subdirectories, such as "myapp/subdir". In this case the library will create
|
||||||
|
the directory hierarchy for you, unless you pass in `FS_NO_CREATE_DIRS`. Note that not all
|
||||||
|
platforms treat the name portion of the prefix the same. In particular, Windows will only use up to
|
||||||
|
the first 3 characters of the name portion of the prefix.
|
||||||
|
|
||||||
|
If you don't like the behavior of `fs_mktmp()`, you can consider using `fs_sysdir()` with
|
||||||
|
`FS_SYSDIR_TEMP` and create the temporary file yourself.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -354,7 +618,8 @@ in the `fs_backend` structure.
|
|||||||
|
|
||||||
A ZIP backend is included in the "extras" folder of this library's repository. Refer to this for
|
A ZIP backend is included in the "extras" folder of this library's repository. Refer to this for
|
||||||
a complete example for how to implement a backend (not including write support, but I'm sure
|
a complete example for how to implement a backend (not including write support, but I'm sure
|
||||||
you'll figure it out!).
|
you'll figure it out!). A PAK backend is also included in the "extras" folder, and is simpler than
|
||||||
|
the ZIP backend which might also be a good place to start.
|
||||||
|
|
||||||
The backend abstraction is designed to relieve backends from having to worry about the
|
The backend abstraction is designed to relieve backends from having to worry about the
|
||||||
implementation details of the main library. Backends should only concern themselves with their
|
implementation details of the main library. Backends should only concern themselves with their
|
||||||
@@ -400,10 +665,15 @@ not ever close or otherwise take ownership of the stream - that will be handled
|
|||||||
|
|
||||||
The `uninit` function is where you should do any cleanup. Do not close the stream here.
|
The `uninit` function is where you should do any cleanup. Do not close the stream here.
|
||||||
|
|
||||||
The `remove` function is used to remove a file. This is not recursive. If the path is a directory,
|
The `ioctl` function is optional. You can use this to implement custom IO control commands. Return
|
||||||
the backend should return an error if it is not empty. Backends do not need to implement this
|
`FS_INVALID_COMMAND` if the command is not recognized. The format of the `pArg` parameter is
|
||||||
function in which case they can leave the callback pointer as `NULL`, or have it return
|
command specific. If the backend does not need to implement this function, it can be left as `NULL`
|
||||||
`FS_NOT_IMPLEMENTED`.
|
or return `FS_NOT_IMPLEMENTED`.
|
||||||
|
|
||||||
|
The `remove` function is used to delete a file or directory. This is not recursive. If the path is
|
||||||
|
a directory, the backend should return an error if it is not empty. Backends do not need to
|
||||||
|
implement this function in which case they can leave the callback pointer as `NULL`, or have it
|
||||||
|
return `FS_NOT_IMPLEMENTED`.
|
||||||
|
|
||||||
The `rename` function is used to rename a file. This will act as a move if the source and
|
The `rename` function is used to rename a file. This will act as a move if the source and
|
||||||
destination are in different directories. If the destination already exists, it should be
|
destination are in different directories. If the destination already exists, it should be
|
||||||
@@ -416,7 +686,8 @@ or return `FS_NOT_IMPLEMENTED`.
|
|||||||
The `info` function is used to get information about a file. If the backend does not have the
|
The `info` function is used to get information about a file. If the backend does not have the
|
||||||
notion of the last modified or access time, it can set those values to 0. Set `directory` to 1 (or
|
notion of the last modified or access time, it can set those values to 0. Set `directory` to 1 (or
|
||||||
FS_TRUE) if it's a directory. Likewise, set `symlink` to 1 if it's a symbolic link. It is important
|
FS_TRUE) if it's a directory. Likewise, set `symlink` to 1 if it's a symbolic link. It is important
|
||||||
that this function return the info of the exact file that would be opened with `file_open()`.
|
that this function return the info of the exact file that would be opened with `file_open()`. This
|
||||||
|
function is mandatory.
|
||||||
|
|
||||||
Like when initializing a `fs` object, the library needs to know how much backend-specific data to
|
Like when initializing a `fs` object, the library needs to know how much backend-specific data to
|
||||||
allocate for the `fs_file` object. This is done with the `file_alloc_size` function. This function
|
allocate for the `fs_file` object. This is done with the `file_alloc_size` function. This function
|
||||||
@@ -426,14 +697,16 @@ not need any additional data, it can return 0. The backend can access this data
|
|||||||
|
|
||||||
The `file_open` function is where the backend should open the file. If the `fs` object that owns
|
The `file_open` function is where the backend should open the file. If the `fs` object that owns
|
||||||
the file was initialized with a stream, i.e. it's an archive, the stream will be non-null. You
|
the file was initialized with a stream, i.e. it's an archive, the stream will be non-null. You
|
||||||
should store this pointer for later use in `file_read`, etc. The `openMode` parameter will be a
|
should store this pointer for later use in `file_read`, etc. Do *not* make a duplicate of the
|
||||||
combination of `FS_READ`, `FS_WRITE`, `FS_TRUNCATE`, `FS_APPEND` and `FS_OVERWRITE`. When opening
|
stream with `fs_stream_duplicate()`. Instead just take a copy of the pointer. The `openMode`
|
||||||
in write mode (`FS_WRITE`), it will default to truncate mode. You should ignore the `FS_OPAQUE`,
|
parameter will be a combination of `FS_READ`, `FS_WRITE`, `FS_TRUNCATE`, `FS_APPEND` and
|
||||||
`FS_VERBOSE` and `FS_TRANSPARENT` flags. If the file does not exist, the backend should return
|
`FS_OVERWRITE`. When opening in write mode (`FS_WRITE`), it will default to truncate mode. You
|
||||||
`FS_DOES_NOT_EXIST`. If the file is a directory, it should return `FS_IS_DIRECTORY`.
|
should ignore the `FS_OPAQUE`, `FS_VERBOSE` and `FS_TRANSPARENT` flags. If the file does not exist,
|
||||||
|
the backend should return `FS_DOES_NOT_EXIST`. If the file is a directory, it should return
|
||||||
|
`FS_IS_DIRECTORY`.
|
||||||
|
|
||||||
The file should be closed with `file_close`. This is where the backend should release any resources
|
The file should be closed with `file_close`. This is where the backend should release any resources
|
||||||
associated with the file. The stream should not be closed here - it'll be cleaned up at a higher
|
associated with the file. Do not uninitialize the stream here - it'll be cleaned up at a higher
|
||||||
level.
|
level.
|
||||||
|
|
||||||
The `file_read` function is used to read data from the file. The backend should return `FS_AT_END`
|
The `file_read` function is used to read data from the file. The backend should return `FS_AT_END`
|
||||||
@@ -446,13 +719,14 @@ specified if the backend supports writing.
|
|||||||
The `file_seek` function is used to seek the cursor. The backend should return `FS_BAD_SEEK` if the
|
The `file_seek` function is used to seek the cursor. The backend should return `FS_BAD_SEEK` if the
|
||||||
seek is out of bounds.
|
seek is out of bounds.
|
||||||
|
|
||||||
The `file_tell` function is used to get the cursor position.
|
The `file_tell` function is used to get the current cursor position. There is only one cursor, even
|
||||||
|
when the file is opened in read and write mode.
|
||||||
|
|
||||||
The `file_flush` function is used to flush any buffered data to the file. This is optional and can
|
The `file_flush` function is used to flush any buffered data to the file. This is optional and can
|
||||||
be left as `NULL` or return `FS_NOT_IMPLEMENTED`.
|
be left as `NULL` or return `FS_NOT_IMPLEMENTED`.
|
||||||
|
|
||||||
The `file_info` function is used to get information about an opened file. It returns the same
|
The `file_info` function is used to get information about an opened file. It returns the same
|
||||||
information as `info` but for an opened file.
|
information as `info` but for an opened file. This is mandatory.
|
||||||
|
|
||||||
The `file_duplicate` function is used to duplicate a file. The destination file will be a new file
|
The `file_duplicate` function is used to duplicate a file. The destination file will be a new file
|
||||||
and already allocated. The backend need only copy the necessary backend-specific data to the new
|
and already allocated. The backend need only copy the necessary backend-specific data to the new
|
||||||
@@ -469,7 +743,9 @@ to deal with this is to allocate the allocate additional space for the name imme
|
|||||||
Backends are responsible for guaranteeing thread-safety of different files across different
|
Backends are responsible for guaranteeing thread-safety of different files across different
|
||||||
threads. This should typically be quite easy since most system backends, such as stdio, are already
|
threads. This should typically be quite easy since most system backends, such as stdio, are already
|
||||||
thread-safe, and archive backends are typically read-only which should make thread-safety trivial
|
thread-safe, and archive backends are typically read-only which should make thread-safety trivial
|
||||||
on that front as well.
|
on that front as well. You need not worry about thread-safety of a single individual file handle.
|
||||||
|
But when you have two different file handles, they must be able to be used on two different threads
|
||||||
|
at the same time.
|
||||||
|
|
||||||
|
|
||||||
4. Streams
|
4. Streams
|
||||||
@@ -486,7 +762,7 @@ For `fs_file`, simply opening the file is enough. For `fs_memory_stream`, you ne
|
|||||||
implement your own stream type you would need to implement a similar initialization function.
|
implement your own stream type you would need to implement a similar initialization function.
|
||||||
|
|
||||||
Use `fs_stream_read()` and `fs_stream_write()` to read and write data from a stream. If the stream
|
Use `fs_stream_read()` and `fs_stream_write()` to read and write data from a stream. If the stream
|
||||||
does not support reading or writing, the respective function should return `FS_NOT_IMPLEMENTED`.
|
does not support reading or writing, the respective function will return `FS_NOT_IMPLEMENTED`.
|
||||||
|
|
||||||
The cursor can be set and retrieved with `fs_stream_seek()` and `fs_stream_tell()`. There is only
|
The cursor can be set and retrieved with `fs_stream_seek()` and `fs_stream_tell()`. There is only
|
||||||
a single cursor which is shared between reading and writing.
|
a single cursor which is shared between reading and writing.
|
||||||
@@ -500,8 +776,8 @@ uninitialize a duplicated stream - `fs_stream_delete_duplicate()` will deal with
|
|||||||
|
|
||||||
Streams are not thread safe. If you want to use a stream across multiple threads, you will need to
|
Streams are not thread safe. If you want to use a stream across multiple threads, you will need to
|
||||||
synchronize access to it yourself. Using different stream objects across multiple threads is safe.
|
synchronize access to it yourself. Using different stream objects across multiple threads is safe.
|
||||||
A duplicated stream is entirely independent of the original stream and can be used across on a
|
A duplicated stream is entirely independent of the original stream and can be used on a different
|
||||||
different thread to the original stream.
|
thread to the original stream.
|
||||||
|
|
||||||
The `fs_stream` object is a base class. If you want to implement your own stream, you should make
|
The `fs_stream` object is a base class. If you want to implement your own stream, you should make
|
||||||
the first member of your stream object a `fs_stream` object. This will allow you to cast between
|
the first member of your stream object a `fs_stream` object. This will allow you to cast between
|
||||||
@@ -521,14 +797,14 @@ see some random tags and stuff in this file. These are just used for doing a dum
|
|||||||
#ifndef fs_h
|
#ifndef fs_h
|
||||||
#define fs_h
|
#define fs_h
|
||||||
|
|
||||||
#include <stddef.h> /* For size_t. */
|
|
||||||
#include <stdarg.h> /* For va_list. */
|
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
#if defined(__cplusplus)
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* BEG fs_compiler_compat.h */
|
/* BEG fs_compiler_compat.h */
|
||||||
|
#include <stddef.h> /* For size_t. */
|
||||||
|
#include <stdarg.h> /* For va_list. */
|
||||||
|
|
||||||
#if defined(SIZE_MAX)
|
#if defined(SIZE_MAX)
|
||||||
#define FS_SIZE_MAX SIZE_MAX
|
#define FS_SIZE_MAX SIZE_MAX
|
||||||
#else
|
#else
|
||||||
@@ -665,11 +941,13 @@ typedef enum fs_result
|
|||||||
FS_ALREADY_EXISTS = -8,
|
FS_ALREADY_EXISTS = -8,
|
||||||
FS_INVALID_FILE = -10,
|
FS_INVALID_FILE = -10,
|
||||||
FS_TOO_BIG = -11,
|
FS_TOO_BIG = -11,
|
||||||
|
FS_PATH_TOO_LONG = -12,
|
||||||
FS_NOT_DIRECTORY = -14,
|
FS_NOT_DIRECTORY = -14,
|
||||||
FS_IS_DIRECTORY = -15,
|
FS_IS_DIRECTORY = -15,
|
||||||
FS_DIRECTORY_NOT_EMPTY = -16,
|
FS_DIRECTORY_NOT_EMPTY = -16,
|
||||||
FS_AT_END = -17,
|
FS_AT_END = -17,
|
||||||
FS_BUSY = -19,
|
FS_BUSY = -19,
|
||||||
|
FS_INTERRUPT = -21,
|
||||||
FS_BAD_SEEK = -25,
|
FS_BAD_SEEK = -25,
|
||||||
FS_NOT_IMPLEMENTED = -29,
|
FS_NOT_IMPLEMENTED = -29,
|
||||||
FS_TIMEOUT = -34,
|
FS_TIMEOUT = -34,
|
||||||
@@ -710,8 +988,6 @@ The stream vtable can support both reading and writing, but it doesn't need to s
|
|||||||
the same time. If one is not supported, simply leave the relevant `read` or `write` callback as
|
the same time. If one is not supported, simply leave the relevant `read` or `write` callback as
|
||||||
`NULL`, or have them return FS_NOT_IMPLEMENTED.
|
`NULL`, or have them return FS_NOT_IMPLEMENTED.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Seek Origins. */
|
|
||||||
typedef enum fs_seek_origin
|
typedef enum fs_seek_origin
|
||||||
{
|
{
|
||||||
FS_SEEK_SET = 0,
|
FS_SEEK_SET = 0,
|
||||||
@@ -728,9 +1004,11 @@ struct fs_stream_vtable
|
|||||||
fs_result (* write )(fs_stream* pStream, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten);
|
fs_result (* write )(fs_stream* pStream, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten);
|
||||||
fs_result (* seek )(fs_stream* pStream, fs_int64 offset, fs_seek_origin origin);
|
fs_result (* seek )(fs_stream* pStream, fs_int64 offset, fs_seek_origin origin);
|
||||||
fs_result (* tell )(fs_stream* pStream, fs_int64* pCursor);
|
fs_result (* tell )(fs_stream* pStream, fs_int64* pCursor);
|
||||||
|
/* BEG fs_stream_vtable_duplicate */
|
||||||
size_t (* duplicate_alloc_size)(fs_stream* pStream); /* Optional. Returns the allocation size of the stream. When not defined, duplicating is disabled. */
|
size_t (* duplicate_alloc_size)(fs_stream* pStream); /* Optional. Returns the allocation size of the stream. When not defined, duplicating is disabled. */
|
||||||
fs_result (* duplicate )(fs_stream* pStream, fs_stream* pDuplicatedStream); /* Optional. Duplicate the stream. */
|
fs_result (* duplicate )(fs_stream* pStream, fs_stream* pDuplicatedStream); /* Optional. Duplicate the stream. */
|
||||||
void (* uninit )(fs_stream* pStream); /* Optional. Uninitialize the stream. */
|
void (* uninit )(fs_stream* pStream); /* Optional. Uninitialize the stream. */
|
||||||
|
/* END fs_stream_vtable_duplicate */
|
||||||
};
|
};
|
||||||
|
|
||||||
struct fs_stream
|
struct fs_stream
|
||||||
@@ -741,13 +1019,17 @@ struct fs_stream
|
|||||||
FS_API fs_result fs_stream_init(const fs_stream_vtable* pVTable, fs_stream* pStream);
|
FS_API fs_result fs_stream_init(const fs_stream_vtable* pVTable, fs_stream* pStream);
|
||||||
FS_API fs_result fs_stream_read(fs_stream* pStream, void* pDst, size_t bytesToRead, size_t* pBytesRead);
|
FS_API fs_result fs_stream_read(fs_stream* pStream, void* pDst, size_t bytesToRead, size_t* pBytesRead);
|
||||||
FS_API fs_result fs_stream_write(fs_stream* pStream, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten);
|
FS_API fs_result fs_stream_write(fs_stream* pStream, const void* pSrc, size_t bytesToWrite, size_t* pBytesWritten);
|
||||||
|
FS_API fs_result fs_stream_seek(fs_stream* pStream, fs_int64 offset, fs_seek_origin origin);
|
||||||
|
FS_API fs_result fs_stream_tell(fs_stream* pStream, fs_int64* pCursor);
|
||||||
|
|
||||||
|
/* BEG fs_stream_writef.h */
|
||||||
FS_API fs_result fs_stream_writef(fs_stream* pStream, const char* fmt, ...) FS_ATTRIBUTE_FORMAT(2, 3);
|
FS_API fs_result fs_stream_writef(fs_stream* pStream, const char* fmt, ...) FS_ATTRIBUTE_FORMAT(2, 3);
|
||||||
FS_API fs_result fs_stream_writef_ex(fs_stream* pStream, const fs_allocation_callbacks* pAllocationCallbacks, const char* fmt, ...) FS_ATTRIBUTE_FORMAT(3, 4);
|
FS_API fs_result fs_stream_writef_ex(fs_stream* pStream, const fs_allocation_callbacks* pAllocationCallbacks, const char* fmt, ...) FS_ATTRIBUTE_FORMAT(3, 4);
|
||||||
FS_API fs_result fs_stream_writefv(fs_stream* pStream, const char* fmt, va_list args);
|
FS_API fs_result fs_stream_writefv(fs_stream* pStream, const char* fmt, va_list args);
|
||||||
FS_API fs_result fs_stream_writefv_ex(fs_stream* pStream, const fs_allocation_callbacks* pAllocationCallbacks, const char* fmt, va_list args);
|
FS_API fs_result fs_stream_writefv_ex(fs_stream* pStream, const fs_allocation_callbacks* pAllocationCallbacks, const char* fmt, va_list args);
|
||||||
FS_API fs_result fs_stream_seek(fs_stream* pStream, fs_int64 offset, fs_seek_origin origin);
|
/* END fs_stream_writef.h */
|
||||||
FS_API fs_result fs_stream_tell(fs_stream* pStream, fs_int64* pCursor);
|
|
||||||
|
|
||||||
|
/* BEG fs_stream_duplicate.h */
|
||||||
/*
|
/*
|
||||||
Duplicates a stream.
|
Duplicates a stream.
|
||||||
|
|
||||||
@@ -762,8 +1044,9 @@ Deletes a duplicated stream.
|
|||||||
Do not use this for a stream that was not duplicated with `fs_stream_duplicate()`.
|
Do not use this for a stream that was not duplicated with `fs_stream_duplicate()`.
|
||||||
*/
|
*/
|
||||||
FS_API void fs_stream_delete_duplicate(fs_stream* pDuplicatedStream, const fs_allocation_callbacks* pAllocationCallbacks);
|
FS_API void fs_stream_delete_duplicate(fs_stream* pDuplicatedStream, const fs_allocation_callbacks* pAllocationCallbacks);
|
||||||
|
/* END fs_stream_duplicate.h */
|
||||||
|
|
||||||
|
/* BEG fs_stream_helpers.h */
|
||||||
/*
|
/*
|
||||||
Helper functions for reading the entire contents of a stream, starting from the current cursor position. Free
|
Helper functions for reading the entire contents of a stream, starting from the current cursor position. Free
|
||||||
the returned pointer with fs_free().
|
the returned pointer with fs_free().
|
||||||
@@ -781,9 +1064,32 @@ typedef enum fs_format
|
|||||||
} fs_format;
|
} fs_format;
|
||||||
|
|
||||||
FS_API fs_result fs_stream_read_to_end(fs_stream* pStream, fs_format format, const fs_allocation_callbacks* pAllocationCallbacks, void** ppData, size_t* pDataSize);
|
FS_API fs_result fs_stream_read_to_end(fs_stream* pStream, fs_format format, const fs_allocation_callbacks* pAllocationCallbacks, void** ppData, size_t* pDataSize);
|
||||||
|
/* END fs_stream_helpers.h */
|
||||||
/* END fs_stream.h */
|
/* END fs_stream.h */
|
||||||
|
|
||||||
|
|
||||||
|
/* BEG fs_sysdir.h */
|
||||||
|
typedef enum fs_sysdir_type
|
||||||
|
{
|
||||||
|
FS_SYSDIR_HOME,
|
||||||
|
FS_SYSDIR_TEMP,
|
||||||
|
FS_SYSDIR_CONFIG,
|
||||||
|
FS_SYSDIR_DATA,
|
||||||
|
FS_SYSDIR_CACHE
|
||||||
|
} fs_sysdir_type;
|
||||||
|
|
||||||
|
FS_API size_t fs_sysdir(fs_sysdir_type type, char* pDst, size_t dstCap); /* Returns the length of the string, or 0 on failure. If the return value is >= to dstCap it means the output buffer was too small. Use the returned value to know how big to make the buffer. Set pDst to NULL to calculate the required length. */
|
||||||
|
/* END fs_sysdir.h */
|
||||||
|
|
||||||
|
|
||||||
|
/* BEG fs_mktmp.h */
|
||||||
|
/* Make sure these options do not conflict with FS_NO_CREATE_DIRS. */
|
||||||
|
#define FS_MKTMP_DIR 0x0800 /* Create a temporary directory. */
|
||||||
|
#define FS_MKTMP_FILE 0x1000 /* Create a temporary file. */
|
||||||
|
|
||||||
|
FS_API fs_result fs_mktmp(const char* pPrefix, char* pTmpPath, size_t tmpPathCap, int options); /* Returns FS_PATH_TOO_LONG if the output buffer is too small. Use FS_MKTMP_FILE to create a file and FS_MKTMP_DIR to create a directory. Use FS_MKTMP_BASE_DIR to query the system base temp folder. pPrefix should not include the name of the system's base temp directory. Do not include paths like "/tmp" in the prefix. The output path will include the system's base temp directory and the prefix. */
|
||||||
|
/* END fs_mktmp.h */
|
||||||
|
|
||||||
|
|
||||||
/* BEG fs.h */
|
/* BEG fs.h */
|
||||||
/* Open mode flags. */
|
/* Open mode flags. */
|
||||||
@@ -792,6 +1098,7 @@ FS_API fs_result fs_stream_read_to_end(fs_stream* pStream, fs_format format, con
|
|||||||
#define FS_APPEND (FS_WRITE | 0x0004)
|
#define FS_APPEND (FS_WRITE | 0x0004)
|
||||||
#define FS_OVERWRITE (FS_WRITE | 0x0008)
|
#define FS_OVERWRITE (FS_WRITE | 0x0008)
|
||||||
#define FS_TRUNCATE (FS_WRITE)
|
#define FS_TRUNCATE (FS_WRITE)
|
||||||
|
#define FS_TEMP (FS_TRUNCATE | 0x0010)
|
||||||
|
|
||||||
#define FS_TRANSPARENT 0x0000 /* Default. Opens a file such that archives of a known type are handled transparently. For example, "somefolder/archive.zip/file.txt" can be opened with "somefolder/file.txt" (the "archive.zip" part need not be specified). This assumes the `fs` object has been initialized with support for the relevant archive types. */
|
#define FS_TRANSPARENT 0x0000 /* Default. Opens a file such that archives of a known type are handled transparently. For example, "somefolder/archive.zip/file.txt" can be opened with "somefolder/file.txt" (the "archive.zip" part need not be specified). This assumes the `fs` object has been initialized with support for the relevant archive types. */
|
||||||
#define FS_OPAQUE 0x0010 /* When used, files inside archives cannot be opened automatically. For example, "somefolder/archive.zip/file.txt" will fail. Mounted archives work fine. */
|
#define FS_OPAQUE 0x0010 /* When used, files inside archives cannot be opened automatically. For example, "somefolder/archive.zip/file.txt" will fail. Mounted archives work fine. */
|
||||||
@@ -803,6 +1110,9 @@ FS_API fs_result fs_stream_read_to_end(fs_stream* pStream, fs_format format, con
|
|||||||
#define FS_NO_SPECIAL_DIRS 0x0200 /* When used, the presence of special directories like "." and ".." will be result in an error when opening files. */
|
#define FS_NO_SPECIAL_DIRS 0x0200 /* When used, the presence of special directories like "." and ".." will be result in an error when opening files. */
|
||||||
#define FS_NO_ABOVE_ROOT_NAVIGATION 0x0400 /* When used, navigating above the mount point with leading ".." segments will result in an error. Can be also be used with fs_path_normalize(). */
|
#define FS_NO_ABOVE_ROOT_NAVIGATION 0x0400 /* When used, navigating above the mount point with leading ".." segments will result in an error. Can be also be used with fs_path_normalize(). */
|
||||||
|
|
||||||
|
#define FS_LOWEST_PRIORITY 0x2000 /* Only used with mounting. When set will create the mount with a lower priority to existing mounts. */
|
||||||
|
|
||||||
|
#define FS_NO_INCREMENT_REFCOUNT 0x4000 /* Internal use only. Used with fs_open_archive_ex() internally. */
|
||||||
|
|
||||||
/* Garbage collection policies.*/
|
/* Garbage collection policies.*/
|
||||||
#define FS_GC_POLICY_THRESHOLD 0x0001 /* Only garbage collect unreferenced opened archives until the count is below the configured threshold. */
|
#define FS_GC_POLICY_THRESHOLD 0x0001 /* Only garbage collect unreferenced opened archives until the count is below the configured threshold. */
|
||||||
@@ -816,11 +1126,12 @@ typedef struct fs_file_info fs_file_info;
|
|||||||
typedef struct fs_iterator fs_iterator;
|
typedef struct fs_iterator fs_iterator;
|
||||||
typedef struct fs_backend fs_backend;
|
typedef struct fs_backend fs_backend;
|
||||||
|
|
||||||
typedef enum fs_mount_priority
|
/*
|
||||||
{
|
This callback is fired when the reference count of a fs object changes. This is useful if you want
|
||||||
FS_MOUNT_PRIORITY_HIGHEST = 0,
|
to do some kind of advanced memory management, such as garbage collection. If the new reference count
|
||||||
FS_MOUNT_PRIORITY_LOWEST = 1
|
is 1, it means no other objects are referencing the fs object.
|
||||||
} fs_mount_priority;
|
*/
|
||||||
|
typedef void (* fs_on_refcount_changed_proc)(void* pUserData, fs* pFS, fs_uint32 newRefCount, fs_uint32 oldRefCount);
|
||||||
|
|
||||||
typedef struct fs_archive_type
|
typedef struct fs_archive_type
|
||||||
{
|
{
|
||||||
@@ -852,6 +1163,8 @@ struct fs_config
|
|||||||
fs_stream* pStream;
|
fs_stream* pStream;
|
||||||
const fs_archive_type* pArchiveTypes;
|
const fs_archive_type* pArchiveTypes;
|
||||||
size_t archiveTypeCount;
|
size_t archiveTypeCount;
|
||||||
|
fs_on_refcount_changed_proc onRefCountChanged;
|
||||||
|
void* pRefCountChangedUserData;
|
||||||
const fs_allocation_callbacks* pAllocationCallbacks;
|
const fs_allocation_callbacks* pAllocationCallbacks;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -859,7 +1172,7 @@ FS_API fs_config fs_config_init_default(void);
|
|||||||
FS_API fs_config fs_config_init(const fs_backend* pBackend, void* pBackendConfig, fs_stream* pStream);
|
FS_API fs_config fs_config_init(const fs_backend* pBackend, void* pBackendConfig, fs_stream* pStream);
|
||||||
|
|
||||||
|
|
||||||
typedef struct fs_backend
|
struct fs_backend
|
||||||
{
|
{
|
||||||
size_t (* alloc_size )(const void* pBackendConfig);
|
size_t (* alloc_size )(const void* pBackendConfig);
|
||||||
fs_result (* init )(fs* pFS, const void* pBackendConfig, fs_stream* pStream); /* Return 0 on success or an errno result code on error. pBackendConfig is a pointer to a backend-specific struct. The documentation for your backend will tell you how to use this. You can usually pass in NULL for this. */
|
fs_result (* init )(fs* pFS, const void* pBackendConfig, fs_stream* pStream); /* Return 0 on success or an errno result code on error. pBackendConfig is a pointer to a backend-specific struct. The documentation for your backend will tell you how to use this. You can usually pass in NULL for this. */
|
||||||
@@ -868,9 +1181,9 @@ typedef struct fs_backend
|
|||||||
fs_result (* remove )(fs* pFS, const char* pFilePath);
|
fs_result (* remove )(fs* pFS, const char* pFilePath);
|
||||||
fs_result (* rename )(fs* pFS, const char* pOldName, const char* pNewName);
|
fs_result (* rename )(fs* pFS, const char* pOldName, const char* pNewName);
|
||||||
fs_result (* mkdir )(fs* pFS, const char* pPath); /* This is not recursive. Return FS_SUCCESS if directory already exists. */
|
fs_result (* mkdir )(fs* pFS, const char* pPath); /* This is not recursive. Return FS_SUCCESS if directory already exists. */
|
||||||
fs_result (* info )(fs* pFS, const char* pPath, int openMode, fs_file_info* pInfo); /* openMode flags can be ignored by most backends. It's primarily used by proxy of passthrough style backends. */
|
fs_result (* info )(fs* pFS, const char* pPath, int openMode, fs_file_info* pInfo); /* openMode flags can be ignored by most backends. It's primarily used by passthrough style backends. */
|
||||||
size_t (* file_alloc_size )(fs* pFS);
|
size_t (* file_alloc_size )(fs* pFS);
|
||||||
fs_result (* file_open )(fs* pFS, fs_stream* pStream, const char* pFilePath, int openMode, fs_file* pFile); /* Return 0 on success or an errno result code on error. Return ENOENT if the file does not exist. pStream will be null if the backend does not need a stream (the `pFS` object was not initialized with one). */
|
fs_result (* file_open )(fs* pFS, fs_stream* pStream, const char* pFilePath, int openMode, fs_file* pFile); /* Return 0 on success or an errno result code on error. Return FS_DOES_NOT_EXIST if the file does not exist. pStream will be null if the backend does not need a stream (the `pFS` object was not initialized with one). */
|
||||||
fs_result (* file_open_handle)(fs* pFS, void* hBackendFile, fs_file* pFile); /* Optional. Open a file from a file handle. Backend-specific. The format of hBackendFile will be specified by the backend. */
|
fs_result (* file_open_handle)(fs* pFS, void* hBackendFile, fs_file* pFile); /* Optional. Open a file from a file handle. Backend-specific. The format of hBackendFile will be specified by the backend. */
|
||||||
void (* file_close )(fs_file* pFile);
|
void (* file_close )(fs_file* pFile);
|
||||||
fs_result (* file_read )(fs_file* pFile, void* pDst, size_t bytesToRead, size_t* pBytesRead); /* Return 0 on success, or FS_AT_END on end of file. Only return FS_AT_END if *pBytesRead is 0. Return an errno code on error. Implementations must support reading when already at EOF, in which case FS_AT_END should be returned and *pBytesRead should be 0. */
|
fs_result (* file_read )(fs_file* pFile, void* pDst, size_t bytesToRead, size_t* pBytesRead); /* Return 0 on success, or FS_AT_END on end of file. Only return FS_AT_END if *pBytesRead is 0. Return an errno code on error. Implementations must support reading when already at EOF, in which case FS_AT_END should be returned and *pBytesRead should be 0. */
|
||||||
@@ -883,19 +1196,22 @@ typedef struct fs_backend
|
|||||||
fs_iterator* (* first )(fs* pFS, const char* pDirectoryPath, size_t directoryPathLen);
|
fs_iterator* (* first )(fs* pFS, const char* pDirectoryPath, size_t directoryPathLen);
|
||||||
fs_iterator* (* next )(fs_iterator* pIterator); /* <-- Must return null when there are no more files. In this case, free_iterator must be called internally. */
|
fs_iterator* (* next )(fs_iterator* pIterator); /* <-- Must return null when there are no more files. In this case, free_iterator must be called internally. */
|
||||||
void (* free_iterator )(fs_iterator* pIterator); /* <-- Free the `fs_iterator` object here since `first` and `next` were the ones who allocated it. Also do any uninitialization routines. */
|
void (* free_iterator )(fs_iterator* pIterator); /* <-- Free the `fs_iterator` object here since `first` and `next` were the ones who allocated it. Also do any uninitialization routines. */
|
||||||
} fs_backend;
|
};
|
||||||
|
|
||||||
FS_API fs_result fs_init(const fs_config* pConfig, fs** ppFS);
|
FS_API fs_result fs_init(const fs_config* pConfig, fs** ppFS);
|
||||||
FS_API void fs_uninit(fs* pFS);
|
FS_API void fs_uninit(fs* pFS);
|
||||||
FS_API fs_result fs_ioctl(fs* pFS, int op, void* pArg);
|
FS_API fs_result fs_ioctl(fs* pFS, int op, void* pArg);
|
||||||
FS_API fs_result fs_remove(fs* pFS, const char* pFilePath);
|
FS_API fs_result fs_remove(fs* pFS, const char* pFilePath); /* Does not consider mounts. */
|
||||||
FS_API fs_result fs_rename(fs* pFS, const char* pOldName, const char* pNewName);
|
FS_API fs_result fs_rename(fs* pFS, const char* pOldName, const char* pNewName); /* Does not consider mounts. */
|
||||||
FS_API fs_result fs_mkdir(fs* pFS, const char* pPath); /* Does not consider mounts. Returns FS_SUCCESS if directory already exists. */
|
FS_API fs_result fs_mkdir(fs* pFS, const char* pPath, int options); /* Recursive. Will consider mounts unless FS_IGNORE_MOUNTS is specified. Returns FS_SUCCESS if directory already exists. */
|
||||||
FS_API fs_result fs_info(fs* pFS, const char* pPath, int openMode, fs_file_info* pInfo); /* openMode flags specify same options as openMode in file_open(), but FS_READ, FS_WRITE, FS_TRUNCATE, FS_APPEND, and FS_OVERWRITE are ignored. */
|
FS_API fs_result fs_info(fs* pFS, const char* pPath, int openMode, fs_file_info* pInfo); /* openMode flags specify same options as openMode in file_open(), but FS_READ, FS_WRITE, FS_TRUNCATE, FS_APPEND, and FS_OVERWRITE are ignored. */
|
||||||
FS_API fs_stream* fs_get_stream(fs* pFS);
|
FS_API fs_stream* fs_get_stream(fs* pFS);
|
||||||
FS_API const fs_allocation_callbacks* fs_get_allocation_callbacks(fs* pFS);
|
FS_API const fs_allocation_callbacks* fs_get_allocation_callbacks(fs* pFS);
|
||||||
FS_API void* fs_get_backend_data(fs* pFS); /* For use by the backend. Will be the size returned by the alloc_size() function in the vtable. */
|
FS_API void* fs_get_backend_data(fs* pFS); /* For use by the backend. Will be the size returned by the alloc_size() function in the vtable. */
|
||||||
FS_API size_t fs_get_backend_data_size(fs* pFS);
|
FS_API size_t fs_get_backend_data_size(fs* pFS);
|
||||||
|
FS_API fs* fs_ref(fs* pFS); /* Increments the reference count. Returns pFS. */
|
||||||
|
FS_API fs_uint32 fs_unref(fs* pFS); /* Decrements the reference count. Does not uninitialize. */
|
||||||
|
FS_API fs_uint32 fs_refcount(fs* pFS);
|
||||||
|
|
||||||
FS_API fs_result fs_open_archive_ex(fs* pFS, const fs_backend* pBackend, void* pBackendConfig, const char* pArchivePath, size_t archivePathLen, int openMode, fs** ppArchive);
|
FS_API fs_result fs_open_archive_ex(fs* pFS, const fs_backend* pBackend, void* pBackendConfig, const char* pArchivePath, size_t archivePathLen, int openMode, fs** ppArchive);
|
||||||
FS_API fs_result fs_open_archive(fs* pFS, const char* pArchivePath, int openMode, fs** ppArchive);
|
FS_API fs_result fs_open_archive(fs* pFS, const char* pArchivePath, int openMode, fs** ppArchive);
|
||||||
@@ -926,13 +1242,12 @@ FS_API fs_iterator* fs_first(fs* pFS, const char* pDirectoryPath, int mode);
|
|||||||
FS_API fs_iterator* fs_next(fs_iterator* pIterator);
|
FS_API fs_iterator* fs_next(fs_iterator* pIterator);
|
||||||
FS_API void fs_free_iterator(fs_iterator* pIterator);
|
FS_API void fs_free_iterator(fs_iterator* pIterator);
|
||||||
|
|
||||||
FS_API fs_result fs_mount(fs* pFS, const char* pPathToMount, const char* pMountPoint, fs_mount_priority priority);
|
FS_API fs_result fs_mount(fs* pFS, const char* pActualPath, const char* pVirtualPath, int options);
|
||||||
FS_API fs_result fs_unmount(fs* pFS, const char* pPathToMount_NotMountPoint);
|
FS_API fs_result fs_unmount(fs* pFS, const char* pActualPath, int options);
|
||||||
FS_API fs_result fs_mount_fs(fs* pFS, fs* pOtherFS, const char* pMountPoint, fs_mount_priority priority);
|
FS_API fs_result fs_mount_sysdir(fs* pFS, fs_sysdir_type type, const char* pSubDir, const char* pVirtualPath, int options);
|
||||||
FS_API fs_result fs_unmount_fs(fs* pFS, fs* pOtherFS); /* Must be matched up with fs_mount_fs(). */
|
FS_API fs_result fs_unmount_sysdir(fs* pFS, fs_sysdir_type type, const char* pSubDir, int options);
|
||||||
|
FS_API fs_result fs_mount_fs(fs* pFS, fs* pOtherFS, const char* pVirtualPath, int options);
|
||||||
FS_API fs_result fs_mount_write(fs* pFS, const char* pPathToMount, const char* pMountPoint, fs_mount_priority priority);
|
FS_API fs_result fs_unmount_fs(fs* pFS, fs* pOtherFS, int options); /* Must be matched up with fs_mount_fs(). */
|
||||||
FS_API fs_result fs_unmount_write(fs* pFS, const char* pPathToMount_NotMountPoint);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Helper functions for reading the entire contents of a file, starting from the current cursor position. Free
|
Helper functions for reading the entire contents of a file, starting from the current cursor position. Free
|
||||||
@@ -957,27 +1272,18 @@ extern const fs_backend* FS_STDIO; /* The default stdio backend. The handle for
|
|||||||
/* END fs.h */
|
/* END fs.h */
|
||||||
|
|
||||||
|
|
||||||
|
/* BEG fs_errno.h */
|
||||||
/* BEG fs_helpers.h */
|
|
||||||
/*
|
|
||||||
This section just contains various helper functions, mainly for custom backends.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Converts an errno code to our own error code. */
|
|
||||||
FS_API fs_result fs_result_from_errno(int error);
|
FS_API fs_result fs_result_from_errno(int error);
|
||||||
|
/* END fs_errno.h */
|
||||||
|
|
||||||
/* END fs_helpers.h */
|
|
||||||
|
|
||||||
|
|
||||||
/* BEG fs_path.h */
|
/* BEG fs_path.h */
|
||||||
/*
|
/*
|
||||||
These functions are low-level functions for working with paths. The most important part of this API
|
These functions are low-level functions for working with paths. The most important part of this API
|
||||||
is probably the iteration functions. These functions are used for iterating over each of the
|
is probably the iteration functions. These functions are used for iterating over each of the
|
||||||
segments of a path. This library will recognize both '\' and '/'. If you want to use just one or
|
segments of a path. This library will recognize both '\' and '/'. If you want to use a different
|
||||||
the other, or a different separator, you'll need to use a different library. Likewise, this library
|
separator, you'll need to use a different library. Likewise, this library will treat paths as case
|
||||||
will treat paths as case sensitive. Again, you'll need to use a different library if this is not
|
sensitive. Again, you'll need to use a different library if this is not suitable for you.
|
||||||
suitable for you.
|
|
||||||
|
|
||||||
Iteration will always return both sides of a separator. For example, if you iterate "abc/def",
|
Iteration will always return both sides of a separator. For example, if you iterate "abc/def",
|
||||||
you will get two items: "abc" and "def". Where this is of particular importance and where you must
|
you will get two items: "abc" and "def". Where this is of particular importance and where you must
|
||||||
@@ -1005,11 +1311,13 @@ FS_API fs_result fs_path_prev(fs_path_iterator* pIterator);
|
|||||||
FS_API fs_bool32 fs_path_is_first(const fs_path_iterator* pIterator);
|
FS_API fs_bool32 fs_path_is_first(const fs_path_iterator* pIterator);
|
||||||
FS_API fs_bool32 fs_path_is_last(const fs_path_iterator* pIterator);
|
FS_API fs_bool32 fs_path_is_last(const fs_path_iterator* pIterator);
|
||||||
FS_API int fs_path_iterators_compare(const fs_path_iterator* pIteratorA, const fs_path_iterator* pIteratorB);
|
FS_API int fs_path_iterators_compare(const fs_path_iterator* pIteratorA, const fs_path_iterator* pIteratorB);
|
||||||
|
FS_API int fs_path_compare(const char* pPathA, size_t pathALen, const char* pPathB, size_t pathBLen);
|
||||||
FS_API const char* fs_path_file_name(const char* pPath, size_t pathLen); /* Does *not* include the null terminator. Returns an offset of pPath. Will only be null terminated if pPath is. Returns null if the path ends with a slash. */
|
FS_API const char* fs_path_file_name(const char* pPath, size_t pathLen); /* Does *not* include the null terminator. Returns an offset of pPath. Will only be null terminated if pPath is. Returns null if the path ends with a slash. */
|
||||||
FS_API int fs_path_directory(char* pDst, size_t dstCap, const char* pPath, size_t pathLen); /* Returns the length, or < 0 on error. pDst can be null in which case the required length will be returned. Will not include a trailing slash. */
|
FS_API int fs_path_directory(char* pDst, size_t dstCap, const char* pPath, size_t pathLen); /* Returns the length, or < 0 on error. pDst can be null in which case the required length will be returned. Will not include a trailing slash. */
|
||||||
FS_API const char* fs_path_extension(const char* pPath, size_t pathLen); /* Does *not* include the null terminator. Returns an offset of pPath. Will only be null terminated if pPath is. Returns null if the extension cannot be found. */
|
FS_API const char* fs_path_extension(const char* pPath, size_t pathLen); /* Does *not* include the null terminator. Returns an offset of pPath. Will only be null terminated if pPath is. Returns null if the extension cannot be found. */
|
||||||
FS_API fs_bool32 fs_path_extension_equal(const char* pPath, size_t pathLen, const char* pExtension, size_t extensionLen); /* Returns true if the extension is equal to the given extension. Case insensitive. */
|
FS_API fs_bool32 fs_path_extension_equal(const char* pPath, size_t pathLen, const char* pExtension, size_t extensionLen); /* Returns true if the extension is equal to the given extension. Case insensitive. */
|
||||||
FS_API const char* fs_path_trim_base(const char* pPath, size_t pathLen, const char* pBasePath, size_t basePathLen);
|
FS_API const char* fs_path_trim_base(const char* pPath, size_t pathLen, const char* pBasePath, size_t basePathLen);
|
||||||
|
FS_API fs_bool32 fs_path_begins_with(const char* pPath, size_t pathLen, const char* pBasePath, size_t basePathLen);
|
||||||
FS_API int fs_path_append(char* pDst, size_t dstCap, const char* pBasePath, size_t basePathLen, const char* pPathToAppend, size_t pathToAppendLen); /* pDst can be equal to pBasePath in which case it will be appended in-place. pDst can be null in which case the function will return the required length. */
|
FS_API int fs_path_append(char* pDst, size_t dstCap, const char* pBasePath, size_t basePathLen, const char* pPathToAppend, size_t pathToAppendLen); /* pDst can be equal to pBasePath in which case it will be appended in-place. pDst can be null in which case the function will return the required length. */
|
||||||
FS_API int fs_path_normalize(char* pDst, size_t dstCap, const char* pPath, size_t pathLen, unsigned int options); /* The only root component recognized is "/". The path cannot start with "C:", "//<address>", etc. This is not intended to be a general cross-platform path normalization routine. If the path starts with "/", this will fail with a negative result code if normalization would result in the path going above the root directory. Will convert all separators to "/". Will remove trailing slash. pDst can be null in which case the required length will be returned. */
|
FS_API int fs_path_normalize(char* pDst, size_t dstCap, const char* pPath, size_t pathLen, unsigned int options); /* The only root component recognized is "/". The path cannot start with "C:", "//<address>", etc. This is not intended to be a general cross-platform path normalization routine. If the path starts with "/", this will fail with a negative result code if normalization would result in the path going above the root directory. Will convert all separators to "/". Will remove trailing slash. pDst can be null in which case the required length will be returned. */
|
||||||
/* END fs_path.h */
|
/* END fs_path.h */
|
||||||
@@ -1028,7 +1336,7 @@ also supports reading.
|
|||||||
You can overwrite data by seeking to the required location and then just writing like normal. To
|
You can overwrite data by seeking to the required location and then just writing like normal. To
|
||||||
append data, just seek to the end:
|
append data, just seek to the end:
|
||||||
|
|
||||||
fs_memory_stream_seek(pStream, 0, fs_SEEK_ORIGIN_END);
|
fs_memory_stream_seek(pStream, 0, FS_SEEK_END);
|
||||||
|
|
||||||
The memory stream need not be uninitialized in read-only mode. In write mode you can use
|
The memory stream need not be uninitialized in read-only mode. In write mode you can use
|
||||||
`fs_memory_stream_uninit()` to free the data. Alternatively you can just take ownership of the
|
`fs_memory_stream_uninit()` to free the data. Alternatively you can just take ownership of the
|
||||||
@@ -1121,6 +1429,7 @@ FS_API void* fs_binary_search(const void* pKey, const void* pList, size_t count,
|
|||||||
FS_API void* fs_linear_search(const void* pKey, const void* pList, size_t count, size_t stride, int (*compareProc)(void*, const void*, const void*), void* pUserData);
|
FS_API void* fs_linear_search(const void* pKey, const void* pList, size_t count, size_t stride, int (*compareProc)(void*, const void*, const void*), void* pUserData);
|
||||||
FS_API void* fs_sorted_search(const void* pKey, const void* pList, size_t count, size_t stride, int (*compareProc)(void*, const void*, const void*), void* pUserData);
|
FS_API void* fs_sorted_search(const void* pKey, const void* pList, size_t count, size_t stride, int (*compareProc)(void*, const void*, const void*), void* pUserData);
|
||||||
|
|
||||||
|
FS_API int fs_strncmp(const char* str1, const char* str2, size_t maxLen);
|
||||||
FS_API int fs_strnicmp(const char* str1, const char* str2, size_t count);
|
FS_API int fs_strnicmp(const char* str1, const char* str2, size_t count);
|
||||||
/* END fs_utils.h */
|
/* END fs_utils.h */
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
#include "../../external/fs/fs.c" /* <-- Must come first due to some (hopefully temporary) hacks to work around some `-std=c89` errors. */
|
||||||
#include "../../miniaudio.c"
|
#include "../../miniaudio.c"
|
||||||
#include "../../external/fs/fs.c"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ int ma_run_tests(int argc, char** argv)
|
|||||||
ma_bool32 hasError = MA_FALSE;
|
ma_bool32 hasError = MA_FALSE;
|
||||||
size_t iTest;
|
size_t iTest;
|
||||||
|
|
||||||
fs_mkdir(NULL, TEST_OUTPUT_DIR);
|
fs_mkdir(NULL, TEST_OUTPUT_DIR, FS_IGNORE_MOUNTS);
|
||||||
|
|
||||||
for (iTest = 0; iTest < g_Tests.count; iTest += 1) {
|
for (iTest = 0; iTest < g_Tests.count; iTest += 1) {
|
||||||
printf("=== BEGIN %s ===\n", g_Tests.pTests[iTest].pName);
|
printf("=== BEGIN %s ===\n", g_Tests.pTests[iTest].pName);
|
||||||
|
|||||||
Reference in New Issue
Block a user