Requirements
What do we need in a file system? We need support for opening, closing, reading and writing to files in both binary and text mode, we also need to be able to navigate a tree of directories and want the user to be able to add support for additional "file systems" like tar.gz files if (s)he want to. We also want as much as possible from the user.
Implementation
In the requirements we said we needed to let the user add functionality, we will accomplish this with interfaces. For example we have a binary input file, its interface is:
namespace FileSystem{ class iBinaryIFile { public: typedef std::streamsize StreamSize; iBinaryIFile(){} virtual ~iBinaryIFile(){} virtual ErrorCode Open(const String& pFilename) = 0; virtual void Close() = 0; virtual void Read(Byte* pData,StreamSize pSize) = 0; template<typename T> void Read(T& pData) { assert( IsOpen() ); assert( sizeof(T) < sizeof(StreamSize) ); Read(&pData,static_cast(sizeof(T)) ); } virtual Boolean IsOpen()const = 0; virtual String Filename() const = 0; };}
Then if the user want to add functionality for some file system or even their custom ones they can just derive a new class from this interface. We still need a way to navigate the file system. This file system "library" will assume that the file system are navigated through a tree of directories, with n files and m directories in each directory. Just like most file systems today. If a file system doesn't support directories it can just emulate directories, for example if it is instructed to create a file in directory(/ seperates directories) "Test1/Test2/Test3/file.dat" it can just save the file with a name like this:
"Test1_Test2_Test3_file.dat"
One problem could be that this file:
"Test1/Test2/Test3_file.dat"
Would have the same name, so if we want to be sure not to encounter name collisions we could just replace _ with __. So a name will be transformed like this:
"Test1_Test2/Test3/Test4__Test5/Test6.da_t" // Original"Test1__Test2/Test3/Test4____Test5/Test6.da__t" // Replace('_',"__")"Test1__Test2_Test3_Test4____Test5_Test6.da__t" // Replace('/','_')
Then name collisions are impossible.
Of course we also need an interface for directories, so we add an iDirectory like this:
namespace FileSystem{ class iDirectory { public: iDirectory(){} virtual ~iDirectory(){} // Note: ".." to go one back virtual bool OpenDirectory(const String& pName) = 0; virtual std::string ToStr() = 0; };}
This interface needs lots of extra functionality, but let's discuss this first. We have OpenDirectory which open a sub-directory, just like the cd(change directory) command. The ToStr returns the current directory as a string.
We now have a very big problem which is that we can't get any info about what is in the directory. Here I have chosen to go with an iterator approach, so we have an iterator which traverses through all elements (both files and directories) in the directory.
One problem though is that we have four file types:
iBinaryIFileiTextIFileiBinaryOFileiTextOFile
And all of them require us to actually open the file which would be way too slow; also they wouldn't work for directories. I guess I could do something like this:
// I don't remember if that is the correct syntax for unions, but you get the ideaunion{ iBinaryIFile BIFile; iTextIFile TIFile; iBinaryOFile BOFile; iTextOFile TOFile; std::string DirectoryName;};
Instead I have chosen to create a new type:
iFile // can also represent directories
All it have is a filename (string) and a boolean variable telling whether it is a file or a directory. Then it contains the following pure-virtual functions:
virtual boost::shared_ptr OpenBinaryOutputFile( Boolean pAppend = false)const = 0;virtual boost::shared_ptr OpenTextOutputFile( Boolean pAppend = false)const = 0;virtual boost::shared_ptr OpenBinaryInputFile()const = 0;virtual boost::shared_ptr OpenTextInputFile()const = 0;
When we do it this way we can also hide the details of the classes derived from i[Binary|Text][O|I]File since only the definitions of Open[Binary|Text][Output|Input]File will create the file and the user's code will just use pointers to the interfaces.
We will of course not use iFile as the iterator, since it isn't an iterator. Instead we will have an iterator and let the dereference operator(*) return an iFile object.
One important part of the iDirectory class is also that it can open files like iFile, it just have to supply the filename. This is for performance reasons, imagine we have all our resources in a single file Res.cfs (cfs = custom file system) This file uses our custom file system and might contain 2500 different files so we would have to iterate through, on average, 1250 files before we could open the resource we were looking for.
Conclusion
I hope this gave you a good idea of how I have designed my file system, if you have any questions or suggestions just post a comment here or send me a PM.