"""
Virtual filesystem (VFS) interface types and callbacks.
.. seealso::
:class:`.FileSystemDriver`
The :class:`~typing.Protocol` that uses these types to implement VFS support in libretro.py.
:mod:`libretro.drivers.vfs`
libretro.py's included :class:`.FileSystemDriver` implementations.
"""
from copy import deepcopy
from ctypes import (
POINTER,
Structure,
c_bool,
c_char_p,
c_int,
c_int32,
c_int64,
c_uint,
c_uint32,
c_uint64,
pointer,
)
from dataclasses import dataclass
from enum import IntEnum, IntFlag
from os import PathLike
from typing import Literal
from libretro.ctypes import (
CBoolArg,
CIntArg,
CStringArg,
Pointer,
TypedFunctionPointer,
TypedPointer,
c_void_ptr,
)
from ._utils import MemoDict
RETRO_VFS_FILE_ACCESS_READ = 1 << 0
RETRO_VFS_FILE_ACCESS_WRITE = 1 << 1
RETRO_VFS_FILE_ACCESS_READ_WRITE = RETRO_VFS_FILE_ACCESS_READ | RETRO_VFS_FILE_ACCESS_WRITE
RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING = 1 << 2
RETRO_VFS_FILE_ACCESS_HINT_NONE = 0
RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS = 1 << 0
RETRO_VFS_SEEK_POSITION_START = 0
RETRO_VFS_SEEK_POSITION_CURRENT = 1
RETRO_VFS_SEEK_POSITION_END = 2
RETRO_VFS_STAT_IS_VALID = 1 << 0
RETRO_VFS_STAT_IS_DIRECTORY = 1 << 1
RETRO_VFS_STAT_IS_CHARACTER_SPECIAL = 1 << 2
[docs]
@dataclass(slots=True)
class retro_vfs_file_handle(Structure):
r"""
Opaque handle for an open VFS file.
Corresponds to :c:type:`retro_vfs_file_handle` in ``libretro.h``.
.. note::
Unlike most other :mod:`ctypes`-wrapped ``struct``\s in libretro.py,
the fields in this class are not part of libretro.h.
They are provided as a convenience for :class:`.FileSystemDriver` implementations.
:class:`.Core`\s should treat instances of this class as opaque handles
and _not_ access or modify its fields directly.
.. seealso::
:meth:`.FileSystemDriver.open`
The suggested method for creating instances of this class.
"""
id: int
"""
Opaque identifier for this file handle.
The :class:`.FileSystemDriver` that creates this handle can assign any value,
but it should be unique among opened files.
"""
path: bytes | None
"""Path that was used to open this file."""
mode: int
"""
File access mode flags.
.. seealso::
:class:`.VfsFileAccess`
The flags that can be set in this field.
"""
hints: int
"""
File access hint flags.
.. seealso::
:class:`.VfsFileAccessHint`
The flags that can be set in this field.
"""
_fields_ = (
("id", c_uint64),
("path", c_char_p),
("mode", c_uint),
("hints", c_uint),
)
[docs]
def __init__(self, id: int, path: bytes | None, mode: int, hints: int):
"""
Initialize a new file handle with the given values.
..seealso::
:class:`retro_vfs_open_t`
"""
self.id = id
self.path = path
self.mode = mode
self.hints = hints
[docs]
@dataclass(init=False, slots=True)
class retro_vfs_dir_handle(Structure):
r"""
Opaque handle for an open VFS directory.
Corresponds to :c:type:`retro_vfs_dir_handle` in ``libretro.h``.
.. note::
Unlike most other :mod:`ctypes`-wrapped ``struct``\s in libretro.py,
the fields in this class are not part of libretro.h.
They are provided as a convenience for :class:`.FileSystemDriver` implementations.
:class:`.Core`\s should treat instances of this class as opaque handles
and _not_ access or modify its fields directly.
.. seealso::
:meth:`.FileSystemDriver.opendir`
The method that creates instances of this class.
"""
id: int
"""
Opaque identifier for this directory handle.
The :class:`.FileSystemDriver` that creates this handle can assign any value,
but it should be unique among opened directories.
"""
dir: bytes | None
"""Path to the open directory."""
include_hidden: bool
"""
Whether hidden entries are included when enumerating the directory's contents.
"""
_fields_ = (
("id", c_uint64),
("dir", c_char_p),
("include_hidden", c_bool),
)
[docs]
class VfsFileAccess(IntFlag):
"""
File access mode flags for VFS operations.
Corresponds to the ``RETRO_VFS_FILE_ACCESS_*`` constants in ``libretro.h``.
>>> from libretro.api import VfsFileAccess
>>> VfsFileAccess.READ
<VfsFileAccess.READ: 1>
"""
READ = RETRO_VFS_FILE_ACCESS_READ
WRITE = RETRO_VFS_FILE_ACCESS_WRITE
READ_WRITE = RETRO_VFS_FILE_ACCESS_READ_WRITE
UPDATE_EXISTING = RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING
READ_WRITE_EXISTING = READ_WRITE | UPDATE_EXISTING
@property
def open_flag(self) -> Literal["rb", "wb", "w+b", "r+b"]:
"""
Returns the Python :func:`open` mode string for this access mode.
>>> from libretro.api import VfsFileAccess
>>> VfsFileAccess.READ.open_flag
'rb'
"""
match self:
case VfsFileAccess.READ:
return "rb"
case VfsFileAccess.WRITE:
return "wb"
case VfsFileAccess.READ_WRITE:
return "w+b"
case VfsFileAccess.READ_WRITE_EXISTING:
return "r+b"
case _:
raise ValueError(f"Invalid VfsFileAccess: {self}")
retro_vfs_get_path_t = TypedFunctionPointer[c_char_p, [TypedPointer[retro_vfs_file_handle]]]
"""
Return the path that was used to open a VFS file.
Registered by the :term:`frontend` and called by the :term:`core`.
:param stream: Pointer to an open :class:`retro_vfs_file_handle`.
:return: The path that was used to open ``stream``, as a :obj:`bytes` string.
The string is owned by the frontend and must not be modified.
Corresponds to :c:type:`retro_vfs_get_path_t` in ``libretro.h``.
"""
retro_vfs_open_t = TypedFunctionPointer[
TypedPointer[retro_vfs_file_handle], [CStringArg, CIntArg[c_uint], CIntArg[c_uint]]
]
"""
Open a file for reading, writing, or both.
Registered by the :term:`frontend` and called by the :term:`core`.
:param path: The path of the file to open.
:param mode: A bitmask of :class:`VfsFileAccess` flags.
:param hints: A bitmask of :class:`VfsFileAccessHint` flags.
:return: A :class:`~libretro.ctypes.c_void_ptr` to a new :class:`retro_vfs_file_handle` on success,
or :obj:`None` on failure (including when ``path`` names a directory).
Corresponds to :c:type:`retro_vfs_open_t` in ``libretro.h``.
"""
retro_vfs_close_t = TypedFunctionPointer[c_int, [TypedPointer[retro_vfs_file_handle]]]
"""
Close an open VFS file and release its resources.
Registered by the :term:`frontend` and called by the :term:`core`.
After this returns the handle is no longer valid.
:param stream: Pointer to the :class:`retro_vfs_file_handle` to close.
:return: ``0`` on success, ``-1`` on failure or if ``stream`` is :obj:`None`.
Corresponds to :c:type:`retro_vfs_close_t` in ``libretro.h``.
"""
retro_vfs_size_t = TypedFunctionPointer[c_int64, [TypedPointer[retro_vfs_file_handle]]]
"""
Return the size of an open VFS file.
Registered by the :term:`frontend` and called by the :term:`core`.
:param stream: Pointer to the :class:`retro_vfs_file_handle` to query.
:return: The size of the file in bytes, or ``-1`` on error.
Corresponds to :c:type:`retro_vfs_size_t` in ``libretro.h``.
"""
retro_vfs_truncate_t = TypedFunctionPointer[
c_int64, [TypedPointer[retro_vfs_file_handle], CIntArg[c_int64]]
]
"""
Set the length of an open VFS file.
Registered by the :term:`frontend` and called by the :term:`core`.
Shorter ``length`` values discard the trailing bytes;
longer values pad with platform-defined contents.
:param stream: Pointer to the :class:`retro_vfs_file_handle` to truncate.
:param length: New length of the file, in bytes.
:return: ``0`` on success, ``-1`` on failure.
Corresponds to :c:type:`retro_vfs_truncate_t` in ``libretro.h``.
"""
retro_vfs_tell_t = TypedFunctionPointer[c_int64, [TypedPointer[retro_vfs_file_handle]]]
"""
Return the current read/write position of an open VFS file.
Registered by the :term:`frontend` and called by the :term:`core`.
:param stream: Pointer to the :class:`retro_vfs_file_handle` to query.
:return: The current stream position in bytes, or ``-1`` on error.
Corresponds to :c:type:`retro_vfs_tell_t` in ``libretro.h``.
"""
retro_vfs_seek_t = TypedFunctionPointer[
c_int64, [TypedPointer[retro_vfs_file_handle], CIntArg[c_int64], CIntArg[c_int]]
]
"""
Set the read/write position of an open VFS file.
Registered by the :term:`frontend` and called by the :term:`core`.
:param stream: Pointer to the :class:`retro_vfs_file_handle` to seek.
:param offset: New offset in bytes, relative to ``seek_position``.
:param seek_position: A :class:`VfsSeekPosition` indicating the seek origin.
:return: The resulting stream position, or ``-1`` on error.
Corresponds to :c:type:`retro_vfs_seek_t` in ``libretro.h``.
"""
retro_vfs_read_t = TypedFunctionPointer[
c_int64, [TypedPointer[retro_vfs_file_handle], c_void_ptr, CIntArg[c_uint64]]
]
"""
Read data from an open VFS file.
Registered by the :term:`frontend` and called by the :term:`core`.
:param stream: Pointer to the :class:`retro_vfs_file_handle` to read from.
:param s: A :class:`~libretro.ctypes.c_void_ptr` to the buffer that will receive the data.
:param len: Maximum number of bytes to read.
:return: The number of bytes actually read, or ``-1`` on error.
Corresponds to :c:type:`retro_vfs_read_t` in ``libretro.h``.
"""
retro_vfs_write_t = TypedFunctionPointer[
c_int64, [TypedPointer[retro_vfs_file_handle], c_void_ptr, CIntArg[c_uint64]]
]
"""
Write data to an open VFS file.
Registered by the :term:`frontend` and called by the :term:`core`.
:param stream: Pointer to the :class:`retro_vfs_file_handle` to write to.
:param s: A :class:`~libretro.ctypes.c_void_ptr` to the buffer of bytes to write.
:param len: Number of bytes to write from ``s``.
:return: The number of bytes actually written, or ``-1`` on error.
Corresponds to :c:type:`retro_vfs_write_t` in ``libretro.h``.
"""
retro_vfs_flush_t = TypedFunctionPointer[c_int, [TypedPointer[retro_vfs_file_handle]]]
"""
Flush pending writes for an open VFS file.
Registered by the :term:`frontend` and called by the :term:`core`.
:param stream: Pointer to the :class:`retro_vfs_file_handle` to flush.
:return: ``0`` on success, ``-1`` on failure.
Corresponds to :c:type:`retro_vfs_flush_t` in ``libretro.h``.
"""
retro_vfs_remove_t = TypedFunctionPointer[c_int, [CStringArg]]
"""
Delete the file at the given path.
Registered by the :term:`frontend` and called by the :term:`core`.
:param path: Path of the file to delete.
:return: ``0`` on success, ``-1`` on failure.
Corresponds to :c:type:`retro_vfs_remove_t` in ``libretro.h``.
"""
retro_vfs_rename_t = TypedFunctionPointer[c_int, [CStringArg, CStringArg]]
"""
Rename a file from one path to another.
Registered by the :term:`frontend` and called by the :term:`core`.
:param old_path: Path to an existing file.
:param new_path: Destination path; must not refer to an existing file.
:return: ``0`` on success, ``-1`` on failure.
Corresponds to :c:type:`retro_vfs_rename_t` in ``libretro.h``.
"""
retro_vfs_stat_t = TypedFunctionPointer[c_int, [CStringArg, TypedPointer[c_int32]]]
"""
Get information about a file at the given path.
Registered by the :term:`frontend` and called by the :term:`core`.
:param path: Path of the file to query.
:param size: Pointer to an :class:`~ctypes.c_int32` that receives the file's size in bytes,
or :obj:`None` to ignore the size.
:return: A bitmask of :class:`VfsStat` flags,
or ``0`` if ``path`` does not refer to a valid file.
Corresponds to :c:type:`retro_vfs_stat_t` in ``libretro.h``.
"""
retro_vfs_mkdir_t = TypedFunctionPointer[c_int, [CStringArg]]
"""
Create a directory at the given path.
Registered by the :term:`frontend` and called by the :term:`core`.
:param dir: Path of the directory to create.
:return: A :class:`VfsMkdirResult` value;
``0`` if the directory was created,
``-2`` if it already exists,
or ``-1`` for any other error.
Corresponds to :c:type:`retro_vfs_mkdir_t` in ``libretro.h``.
"""
retro_vfs_opendir_t = TypedFunctionPointer[
TypedPointer[retro_vfs_dir_handle], [CStringArg, CBoolArg]
]
"""
Open a directory so its contents can be enumerated.
Registered by the :term:`frontend` and called by the :term:`core`.
:param dir: Path to an existing directory.
:param include_hidden: :obj:`True` to include hidden files in the listing.
The exact semantics depend on the platform.
:return: A :class:`~libretro.ctypes.c_void_ptr` to a new :class:`retro_vfs_dir_handle` on success,
or :obj:`None` on failure.
Corresponds to :c:type:`retro_vfs_opendir_t` in ``libretro.h``.
"""
retro_vfs_readdir_t = TypedFunctionPointer[c_bool, [TypedPointer[retro_vfs_dir_handle]]]
"""
Advance to the next directory entry.
Registered by the :term:`frontend` and called by the :term:`core`.
:param dirstream: Pointer to the :class:`retro_vfs_dir_handle` to advance.
:return: :obj:`True` if a new entry is available,
:obj:`False` if no more entries remain.
Corresponds to :c:type:`retro_vfs_readdir_t` in ``libretro.h``.
"""
retro_vfs_dirent_get_name_t = TypedFunctionPointer[c_char_p, [TypedPointer[retro_vfs_dir_handle]]]
"""
Return the filename of the current directory entry.
Registered by the :term:`frontend` and called by the :term:`core`.
The returned pointer is valid until the next call to :c:type:`retro_vfs_readdir_t`
or :c:type:`retro_vfs_closedir_t` on this handle.
:param dirstream: Pointer to the :class:`retro_vfs_dir_handle` to query.
:return: The current entry's filename as a :obj:`bytes` string,
or :obj:`None` on error.
Corresponds to :c:type:`retro_vfs_dirent_get_name_t` in ``libretro.h``.
"""
retro_vfs_dirent_is_dir_t = TypedFunctionPointer[c_bool, [TypedPointer[retro_vfs_dir_handle]]]
"""
Return whether the current directory entry is itself a directory.
Registered by the :term:`frontend` and called by the :term:`core`.
:param dirstream: Pointer to the :class:`retro_vfs_dir_handle` to query.
:return: :obj:`True` if the current entry names a subdirectory,
:obj:`False` otherwise or on error.
Corresponds to :c:type:`retro_vfs_dirent_is_dir_t` in ``libretro.h``.
"""
retro_vfs_closedir_t = TypedFunctionPointer[c_int, [TypedPointer[retro_vfs_dir_handle]]]
"""
Close an open VFS directory handle.
Registered by the :term:`frontend` and called by the :term:`core`.
After this returns the handle is no longer valid.
:param dirstream: Pointer to the :class:`retro_vfs_dir_handle` to close.
:return: ``0`` on success, ``-1`` on failure.
Corresponds to :c:type:`retro_vfs_closedir_t` in ``libretro.h``.
"""
[docs]
@dataclass(init=False, slots=True)
class retro_vfs_interface(Structure):
"""
Corresponds to :c:type:`retro_vfs_interface` in ``libretro.h``.
A complete set of callbacks for virtual filesystem operations.
>>> from libretro.api import retro_vfs_interface
>>> vfs = retro_vfs_interface()
>>> vfs.open is None
True
"""
get_path: retro_vfs_get_path_t | None
"""Returns the path of an open file handle."""
open: retro_vfs_open_t | None
"""Opens a file with the given path, mode, and hints."""
close: retro_vfs_close_t | None
"""Closes an open file handle."""
size: retro_vfs_size_t | None
"""Returns the size of an open file in bytes."""
tell: retro_vfs_tell_t | None
"""Returns the current read/write position."""
seek: retro_vfs_seek_t | None
"""Sets the current read/write position."""
read: retro_vfs_read_t | None
"""Reads data from an open file."""
write: retro_vfs_write_t | None
"""Writes data to an open file."""
flush: retro_vfs_flush_t | None
"""Flushes pending writes to an open file."""
remove: retro_vfs_remove_t | None
"""Deletes a file at the given path."""
rename: retro_vfs_rename_t | None
"""Renames a file from one path to another."""
truncate: retro_vfs_truncate_t | None
"""Sets an open file's length."""
stat: retro_vfs_stat_t | None
"""Gets status flags and size of a file."""
mkdir: retro_vfs_mkdir_t | None
"""Creates a directory at the given path."""
opendir: retro_vfs_opendir_t | None
"""Opens a directory for iteration."""
readdir: retro_vfs_readdir_t | None
"""Advances to the next directory entry."""
dirent_get_name: retro_vfs_dirent_get_name_t | None
"""Returns the name of the current directory entry."""
dirent_is_dir: retro_vfs_dirent_is_dir_t | None
"""Returns whether the current directory entry is a subdirectory."""
closedir: retro_vfs_closedir_t | None
"""Closes an open directory handle."""
_fields_ = (
("get_path", retro_vfs_get_path_t),
("open", retro_vfs_open_t),
("close", retro_vfs_close_t),
("size", retro_vfs_size_t),
("tell", retro_vfs_tell_t),
("seek", retro_vfs_seek_t),
("read", retro_vfs_read_t),
("write", retro_vfs_write_t),
("flush", retro_vfs_flush_t),
("remove", retro_vfs_remove_t),
("rename", retro_vfs_rename_t),
("truncate", retro_vfs_truncate_t),
("stat", retro_vfs_stat_t),
("mkdir", retro_vfs_mkdir_t),
("opendir", retro_vfs_opendir_t),
("readdir", retro_vfs_readdir_t),
("dirent_get_name", retro_vfs_dirent_get_name_t),
("dirent_is_dir", retro_vfs_dirent_is_dir_t),
("closedir", retro_vfs_closedir_t),
)
[docs]
def __deepcopy__(self, _):
"""Return a shallow copy."""
return retro_vfs_interface(
self.get_path,
self.open,
self.close,
self.size,
self.tell,
self.seek,
self.read,
self.write,
self.flush,
self.remove,
self.rename,
self.truncate,
self.stat,
self.mkdir,
self.opendir,
self.readdir,
self.dirent_get_name,
self.dirent_is_dir,
self.closedir,
)
[docs]
@dataclass(init=False, slots=True)
class retro_vfs_interface_info(Structure):
"""
Corresponds to :c:type:`retro_vfs_interface_info` in ``libretro.h``.
Wraps a :class:`retro_vfs_interface` pointer with a version number.
>>> from libretro.api import retro_vfs_interface_info
>>> info = retro_vfs_interface_info()
>>> info.required_interface_version
0
"""
required_interface_version: int
"""Minimum VFS API version required by the core."""
iface: TypedPointer[retro_vfs_interface] | Pointer[retro_vfs_interface] | None
"""VFS interface provided by the frontend."""
_fields_ = (
("required_interface_version", c_uint32),
("iface", POINTER(retro_vfs_interface)),
)
[docs]
def __deepcopy__(self, memo: MemoDict = None):
"""
Return a deep copy of this object, including all subobjects.
Intended for use with :func:`copy.deepcopy`.
"""
return retro_vfs_interface_info(
self.required_interface_version,
pointer(deepcopy(self.iface[0], memo)) if self.iface else None,
)
[docs]
class VfsFileAccessHint(IntFlag):
"""
Hints for file access patterns.
>>> from libretro.api import VfsFileAccessHint
>>> VfsFileAccessHint.FREQUENT_ACCESS
<VfsFileAccessHint.FREQUENT_ACCESS: 1>
"""
NONE = RETRO_VFS_FILE_ACCESS_HINT_NONE
FREQUENT_ACCESS = RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS
[docs]
class VfsSeekPosition(IntEnum):
"""
Seek origin for VFS seek operations.
>>> from libretro.api import VfsSeekPosition
>>> VfsSeekPosition.START
<VfsSeekPosition.START: 0>
"""
START = RETRO_VFS_SEEK_POSITION_START
CURRENT = RETRO_VFS_SEEK_POSITION_CURRENT
END = RETRO_VFS_SEEK_POSITION_END
[docs]
class VfsStat(IntFlag):
"""
Flags returned by VFS stat operations.
>>> from libretro.api import VfsStat
>>> VfsStat.IS_DIRECTORY
<VfsStat.IS_DIRECTORY: 2>
"""
IS_VALID = RETRO_VFS_STAT_IS_VALID
IS_DIRECTORY = RETRO_VFS_STAT_IS_DIRECTORY
IS_CHARACTER_SPECIAL = RETRO_VFS_STAT_IS_CHARACTER_SPECIAL
[docs]
class VfsMkdirResult(IntEnum):
"""
Return codes for VFS mkdir operations.
>>> from libretro.api import VfsMkdirResult
>>> VfsMkdirResult.SUCCESS
<VfsMkdirResult.SUCCESS: 0>
"""
SUCCESS = 0
ERROR = -1
ALREADY_EXISTS = -2
VfsPath = bytes | str | PathLike[bytes] | PathLike[str]
__all__ = [
"retro_vfs_file_handle",
"retro_vfs_dir_handle",
"VfsFileAccess",
"retro_vfs_get_path_t",
"retro_vfs_open_t",
"retro_vfs_close_t",
"retro_vfs_size_t",
"retro_vfs_truncate_t",
"retro_vfs_tell_t",
"retro_vfs_seek_t",
"retro_vfs_read_t",
"retro_vfs_write_t",
"retro_vfs_flush_t",
"retro_vfs_remove_t",
"retro_vfs_rename_t",
"retro_vfs_stat_t",
"retro_vfs_mkdir_t",
"retro_vfs_opendir_t",
"retro_vfs_readdir_t",
"retro_vfs_dirent_get_name_t",
"retro_vfs_dirent_is_dir_t",
"retro_vfs_closedir_t",
"retro_vfs_interface",
"retro_vfs_interface_info",
"VfsFileAccessHint",
"VfsSeekPosition",
"VfsStat",
"VfsMkdirResult",
"VfsPath",
]