2025-09-02 16:44:28 -07:00
import logging
import os
import pathlib
import shutil
import typing
import tempfile
import fs
import fs . base
import fs . copy
import fs . osfs
from . import core
2025-09-29 20:31:42 -07:00
## \brief Handler for virtual filesystem.
# \internal
2025-09-02 16:44:28 -07:00
class GameVFSHandler ( object ) :
vfs = None
log = logging . getLogger ( ) . getChild ( " vfs " )
proto_handlers = { }
def _osfs_handle ( self , path ) :
if self . osfs_cwd . exists ( path ) :
return self . osfs_cwd . opendir ( path )
elif self . osfs_appdir . exists ( path ) :
return self . osfs_appdir . opendir ( path )
else :
raise FileNotFoundError
def __init__ ( self ) - > None :
self . vfs = fs . open_fs ( " mem:// " )
self . proto_handlers [ " osfs " ] = self . _osfs_handle
self . osfs_cwd = fs . osfs . OSFS ( os . getcwd ( ) )
self . osfs_appdir = fs . osfs . OSFS ( core . path_appdir . as_posix ( ) )
self . pth_temp = pathlib . Path ( tempfile . mkdtemp ( ) )
self . osfs_temp = fs . osfs . OSFS ( self . pth_temp . as_posix ( ) )
def __del__ ( self ) :
if self . pth_temp and self . pth_temp . exists ( ) :
shutil . rmtree ( self . pth_temp )
def __getattr__ ( self , name : str ) - > typing . Any :
try :
return getattr ( self . vfs , name )
except :
raise
2025-09-29 20:31:42 -07:00
## \brief Load files from a given data source.
# \param source The data source. This can be a pyfilesystem2 object, or any string or path-like
# object that can reasonably be interpreted as a directory or zip file.
# \param proto The <a href="https://pyfilesystem2.readthedocs.io/en/latest/builtin.html">PyFilesystem2 filesystem protocol</a> to use. Defaults to `"osfs"`, which loads directories.
2025-09-02 16:44:28 -07:00
def load_data_source ( self , source : pathlib . Path | fs . base . FS | typing . Text , proto = " osfs " ) :
print ( f " data source: { source } ( { proto } ) " )
assert self . vfs is not None
if isinstance ( source , pathlib . Path ) :
assert isinstance ( source , pathlib . Path ) # for linter
self . log . info ( f " loading vfs source: { source . as_posix ( ) } " )
if proto in self . proto_handlers :
fs_source = self . proto_handlers [ proto ] ( source . as_posix ( ) )
fs . copy . copy_fs ( fs_source , self . vfs )
else :
fs_source = fs . open_fs ( f " { proto } :/ { source . as_posix ( ) } " )
fs . copy . copy_fs ( fs_source , self . vfs )
else :
if proto in self . proto_handlers :
fs_source = self . proto_handlers [ proto ] ( source )
else :
fs_source = fs . open_fs ( f " { proto } :// { source } " )
self . log . info ( f " loading vfs source: { fs_source } (pyfilesystem2 handler) " )
fs . copy . copy_fs ( fs_source , self . vfs )
2025-09-29 20:31:42 -07:00
## \brief Copies a file out of VFS into the real filesystem.
# \param filepath The source file path to copy out.
# \param dest The path to write the file to. Defaults to temporary directory.
# \return The full destination file path.
2025-09-02 16:44:28 -07:00
def copy_out ( self , filepath , dest = None ) :
if not dest :
self . osfs_temp . makedirs ( pathlib . Path ( filepath ) . parent . as_posix ( ) , recreate = True )
fs . copy . copy_file ( self . vfs , filepath , self . osfs_temp , filepath )
return self . pth_temp / filepath
else :
pth_dest = pathlib . Path ( dest )
pth_file = pathlib . Path ( filepath )
osfs_dest = fs . osfs . OSFS ( dest )
osfs_dest . makedirs ( pth_file . parent . as_posix ( ) , recreate = True )
fs . copy . copy_file ( self . vfs , filepath , dest , filepath )
return ( pth_dest / filepath ) . as_posix ( )
2025-10-05 11:11:54 -07:00
def copy_dir ( self , dirpath , dest = None ) :
if not dest :
self . osfs_temp . makedirs ( pathlib . Path ( dirpath ) . parent . as_posix ( ) , recreate = True )
fs . copy . copy_dir ( self . vfs , dirpath , self . osfs_temp , dirpath )
return self . pth_temp / dirpath
else :
pth_dest = pathlib . Path ( dest )
pth_file = pathlib . Path ( dirpath )
osfs_dest = fs . osfs . OSFS ( dest )
osfs_dest . makedirs ( pth_file . parent . as_posix ( ) , recreate = True )
fs . copy . copy_dir ( self . vfs , dirpath , dest , dirpath )
return ( pth_dest / dirpath ) . as_posix ( )
2025-09-02 16:44:28 -07:00
2025-09-29 20:31:42 -07:00
## \brief Primary VFS handler.
# \internal
2025-09-02 16:44:28 -07:00
vfs = GameVFSHandler ( )