ezEngine  Milestone 9
ezFileSystem Class Reference

The ezFileSystem provides high-level functionality to manage files in a virtual file system. More...

#include <FileSystem.h>

Classes

struct  FileEvent
 The event data that is broadcast by the ezFileSystem upon certain file operations. More...
 
struct  FileEventType
 Describes the type of events that are broadcast by the ezFileSystem. More...
 

Public Types

enum  DataDirUsage { ReadOnly, AllowWrites }
 Describes in which mode a data directory is mounted.
 

Static Public Member Functions

static void RegisterEventHandler (ezEvent< const FileEvent &>::Handler handler)
 Registers an Event Handler that will be informed about all the events that the file system broadcasts.
 
static void UnregisterEventHandler (ezEvent< const FileEvent &>::Handler handler)
 Unregisters a previously registered Event Handler.
 
static ezResult CreateDirectoryStructure (const char *szPath)
 
static void DeleteFile (const char *szFile)
 Deletes the given file from all data directories, if possible. More...
 
static bool ExistsFile (const char *szFile)
 Checks whether the given file exists in any data directory. More...
 
static ezResult GetFileStats (const char *szFileOrFolder, ezFileStats &out_Stats)
 Tries to get the ezFileStats for the given file. Typically should give the same results as ezOSFile::GetFileStats, but some data dir implementations may not support retrieving all data (e.g. GetFileStats on folders might not always work).
 
static ezResult ResolvePath (const char *szPath, ezStringBuilder *out_sAbsolutePath, ezStringBuilder *out_sDataDirRelativePath)
 Tries to resolve the given path and returns the absolute and relative path to the final file. More...
 
static ezResult FindFolderWithSubPath (const char *szStartDirectory, const char *szSubPath, ezStringBuilder &result)
 Starts at szStartDirectory and goes up until it finds a folder that contains the given sub folder structure. Returns EZ_FAILURE if nothing is found. Otherwise result is the absolute path to the existing folder that has a given sub-folder.
 
static bool ResolveAssetRedirection (const char *szPathOrAssetGuid, ezStringBuilder &out_sRedirection)
 Returns true, if any data directory knows how to redirect the given path. Otherwise the original string is returned in out_sRedirection.
 
Special Directories
static ezResult DetectSdkRootDirectory (const char *szExpectedSubFolder="Data/Base")
 Searches for a directory to use as the "Sdk" special directory. More...
 
static void SetSdkRootDirectory (const char *szSdkDir)
 the special directory ">Sdk" is the root folder of the SDK data, it is often used as the main reference from where other data directories are found. For higher level code (e.g. ezApplication) it is often vital that this is set early at startup. More...
 
static const char * GetSdkRootDirectory ()
 Returns the previously set Sdk root directory. More...
 
static void SetSpecialDirectory (const char *szName, const char *szReplacement)
 Special directories are used when mounting data directories as basic references. More...
 
static ezResult ResolveSpecialDirectory (const char *szDirectory, ezStringBuilder &out_Path)
 Returns the absolute path to szDirectory. More...
 

Friends

class ezDataDirectoryReaderWriterBase
 
class ezFileReaderBase
 
class ezFileWriterBase
 

Data Directory Modifications

All functions that add / remove data directories are not thread safe and require that this is done on a single thread with no other thread accessing anything in ezFileSystem simultaneously.

typedef ezDataDirectoryType *(* ezDataDirFactory) (const char *szDataDirectory, const char *szGroup, const char *szRootName, ezFileSystem::DataDirUsage Usage)
 This factory creates a data directory type, if it can handle the given data directory. Otherwise it returns nullptr. More...
 
static void RegisterDataDirectoryFactory (ezDataDirFactory Factory, float fPriority=0)
 This function allows to register another data directory factory, which might be invoked when a new data directory is to be added.
 
static void ClearAllDataDirectoryFactories ()
 Will remove all known data directory factories.
 
static ezResult AddDataDirectory (const char *szDataDirectory, const char *szGroup="", const char *szRootName="", ezFileSystem::DataDirUsage Usage=ReadOnly)
 Adds a data directory. It will try all the registered factories to find a data directory type that can handle the given path. More...
 
static ezUInt32 RemoveDataDirectoryGroup (const char *szGroup)
 Removes all data directories that belong to the given group. Returns the number of data directories that were removed.
 
static void ClearAllDataDirectories ()
 Removes all data directories.
 
static ezUInt32 GetNumDataDirectories ()
 Returns the number of currently active data directories.
 
static ezDataDirectoryTypeGetDataDirectory (ezUInt32 uiDataDirIndex)
 Returns the n-th currently active data directory.
 
static void ReloadAllExternalDataDirectoryConfigs ()
 Calls ezDataDirectoryType::ReloadExternalConfigs() on all active data directories.
 

Detailed Description

The ezFileSystem provides high-level functionality to manage files in a virtual file system.

There are two sides at which the file system can be extended: Data directories are the 'sources' of data. These can be simple folders, zip files, data-bases, HTTP servers, etc. Different ezDataDirectoryType's can implement these different 'decoding methods', i.e. they handle how to actually access the data and they use their own readers/writers to implement a common interface for passing data streams to and from the data directory. On the other end there are the actual file readers/writers, which implement policies how to optimize these reads/writes. The default ezFileReader and ezFileWriter implement a buffering policy, i.e. they use an internal cache to only sporadically read or write to the actual data stream. A 'threaded' or 'parallel' file reader/writer could implement a different policy, where a file is read/written in a thread and thus allows to have non-blocking file accesses.

Which policy to use is defined by the user every time he needs to access a file, by simply using the desired reader/writer class. How to mount data directories (i.e. with which ezDataDirectoryType) is defined by the 'DataDirFactories', which are functions that create ezDataDirectoryType's. This way one can mount the same data directory (e.g. "MyTestDir") differently, depending on which Factories have been registered previously. This allows to easily configure how to set up data directories. E.g. by default ordinary folders will be mounted to be read from the local file system. However, by registering a different Factory, the same directory could also be mounted over a network on a remote file serving machine.

Additionally ezFileSystem also broadcasts events about which files are (about to be) accessed. This allows to hook into the system and implement stuff like automatic asset transformations before/after certain file accesses, checking out files from revision control systems, or simply logging all file activity.

All operations that go through the ezFileSystem are protected by a mutex, which means that opening, closing, deleting files, as well as adding or removing data directories etc. will be synchronized and cannot happen in parallel. Reading/writing file streams can happen in parallel, only the administrative tasks need to be protected. File events are broadcast as they occur, that means they will be executed on whichever thread triggered them. Since they are executed from within the filesystem mutex, they cannot occur in parallel.

Member Typedef Documentation

◆ ezDataDirFactory

typedef ezDataDirectoryType*(* ezFileSystem::ezDataDirFactory) (const char *szDataDirectory, const char *szGroup, const char *szRootName, ezFileSystem::DataDirUsage Usage)

This factory creates a data directory type, if it can handle the given data directory. Otherwise it returns nullptr.

Every time a data directory is supposed to be added, the file system will query its data dir factories, which one can successfully create an ezDataDirectoryType. In this process the last factory added has the highest priority. Once a factory is found that was able to create a ezDataDirectoryType, that one is used. Different factories can be used to mount different types of data directories. But the same directory can also be mounted in different ways. For example a simple folder could be mounted on the local system, or via a HTTP server over a network (lets call it a 'FileServer'). Thus depending on which type of factories are registered, the file system can provide data from very different sources.

Member Function Documentation

◆ AddDataDirectory()

ezResult ezFileSystem::AddDataDirectory ( const char *  szDataDirectory,
const char *  szGroup = "",
const char *  szRootName = "",
ezFileSystem::DataDirUsage  Usage = ReadOnly 
)
static

Adds a data directory. It will try all the registered factories to find a data directory type that can handle the given path.

If Usage is ReadOnly, writing to the data directory is not allowed. This is independent of whether the data directory type COULD write anything. szGroup defines to what 'group' of data directories this data dir belongs. This is only used in calls to RemoveDataDirectoryGroup, to remove all data directories of the same group. You could use groups such as 'Base', 'Project', 'Settings', 'Level', 'Temp' to distinguish between different sets of data directories. You can also specify the exact same string as szDataDirectory for szGroup, and thus uniquely identify the data dir, to be able to remove just that one. szRootName is optional for read-only data dirs, but mandatory for writable ones. It has to be unique to clearly identify a file within that data directory. It must be used when writing to a file in this directory. For instance, if a data dir root name is "mydata", then the path ":mydata/SomeFile.txt" can be used to write to the top level folder of this data directory. The same can be used for reading exactly that file and ignoring the other data dirs.

◆ DeleteFile()

void ezFileSystem::DeleteFile ( const char *  szFile)
static

Deletes the given file from all data directories, if possible.

The path must be absolute or rooted, to uniquely identify which file to delete. For example ":appdata/SomeData.txt", assuming a writable data directory has been mounted with the "appdata" root name.

◆ DetectSdkRootDirectory()

ezResult ezFileSystem::DetectSdkRootDirectory ( const char *  szExpectedSubFolder = "Data/Base")
static

Searches for a directory to use as the "Sdk" special directory.

It does so by starting at the directory where the application binary is located and then going up until it finds a folder that contains the given sub-folder. The sub-folder is usually where the engine loads the most basic data from, so it should exist.

Upon success SetSdkRootDirectory() is called with the resulting path.

Note
If the Sdk root directory has been set before, this function does nothing! It will not override a previously set value. If that is desired, call SetSdkRootDirectory("") first.

◆ ExistsFile()

bool ezFileSystem::ExistsFile ( const char *  szFile)
static

Checks whether the given file exists in any data directory.

The search can be restricted to directories of certain categories (see AddDataDirectory).

◆ GetSdkRootDirectory()

const char * ezFileSystem::GetSdkRootDirectory ( )
static

Returns the previously set Sdk root directory.

Note
Asserts that the path is not empty!
See also
SetSdkRootDirectory
DetectSdkRootDirectory

◆ ResolvePath()

ezResult ezFileSystem::ResolvePath ( const char *  szPath,
ezStringBuilder out_sAbsolutePath,
ezStringBuilder out_sDataDirRelativePath 
)
static

Tries to resolve the given path and returns the absolute and relative path to the final file.

If the given path is a rooted path, for instance something like ":appdata/UserData.txt", (which is necessary for writing to files), the path can be converted easily and the file does not need to exist. Only the data directory with the given root name must be mounted.

If the path is relative, it is attempted to open the specified file, which means it is searched in all available data directories. The path to the file that is found will be returned.

Parameters
out_sAbsolutePathwill contain the absolute path to the file. Might be nullptr.
out_sDataDirRelativePathwill contain the relative path to the file (from the data directory in which it might end up in). Might be nullptr.
szPathcan be a relative, an absolute or a rooted path. This can also be used to find the relative location to the data directory that would handle it. The function will return EZ_FAILURE if it was not able to determine any location where the file could be read from or written to.
Todo:
We might also need the none-redirected path as an output
Todo:
We might also need the none-redirected path as an output

◆ ResolveSpecialDirectory()

ezResult ezFileSystem::ResolveSpecialDirectory ( const char *  szDirectory,
ezStringBuilder out_Path 
)
static

Returns the absolute path to szDirectory.

If the path starts with a known special directory marker (">marker/") it is replaced accordingly. See SetSpecialDirectory() for setting custom special directories.

Built-in special directories (always available) are:

">sdk/" - Resolves to what GetSdkRootDirectory() returns. ">user/" - Resolves to what ezOSFile::GetUserDataFolder() returns. ">appdir/" - Resolves to what ezOSFile::GetApplicationDirectory() returns.

Returns EZ_FAILURE if szDirectory starts with an unknown special directory.

◆ SetSdkRootDirectory()

void ezFileSystem::SetSdkRootDirectory ( const char *  szSdkDir)
static

the special directory ">Sdk" is the root folder of the SDK data, it is often used as the main reference from where other data directories are found. For higher level code (e.g. ezApplication) it is often vital that this is set early at startup.

See also
DetectSdkRootDirectory()

◆ SetSpecialDirectory()

void ezFileSystem::SetSpecialDirectory ( const char *  szName,
const char *  szReplacement 
)
static

Special directories are used when mounting data directories as basic references.

They are indicated with a ">", ie. ">sdk/Test", but using them is only allowed in few places, e.g. in AddDataDirectory(). Special directories are needed to be able to set up other paths relative to them and to be able to use different ones on different PCs. For instance when using file-serve functionality, the special directories may be different on the host and client machines, but the paths used to mount data directories can stay the same because of this.


The documentation for this class was generated from the following files: