Open main menu

Google Summer of Code: Filesystem API

This page holds additional information concerning my proposal for working to redesign the current Filesystem API.

It should be noted that the diagrams don't list all methods (for example, when a class inherits them) for brevity and clarity. During the project development, the diagrams will become accurate and exact.

Common::File redesign discussion

While analizing the current state of the Common::File class, I've been noticing several important things that I feel, should be discussed. First of all, is the relevance of breaking up this class (like the FilesystemNode) one into a different implementation for each architecture. This is the primary motivation for this section.

External references

The following table presents the files importing a given header throughout all of ScummVM:

Header File Imports
file.h 105
fs.h 32
abstract-fs. 11

From this table it's easy to infer that the abstract-fs.h header has no issues. It's only called by the 10 backends plus its cpp file.

The file.h header though, it's called from a -lot- of other files. This means that breaking the current Common::File implementation is like opening a pandora box.

The fs.h doesn't seem as bad, although I haven't had the time to research it's use in each of the modules. Being a wrapper around the AbstractFilesystemNode, it's a lot easier to change the wrapped class, because the classes are designed to treat FilesystemNode as a wrapper.

Inheritance

Another issue with breaking up Common::File is the Scumm::BaseScummFile class. If the Common::File gets broken in architecture specific implementations, creating an structure like this:

Level 0 Level 1 Level 2
AbstractFile
BaseScummFile
ScummFile
ScummNESFile
POSIXFile
WindowsFile
...File

Carefully looking, you'll see that BaseScummFile and all the architecture dependent implementations are on the same inheritance level! Of course this is not the intended behavior, since you'd want BaseScummFile to work with a backend like POSIXFile. In my second diagram I propose a solution to this issue.

This solution requires the BaseScummFile to use a factory, in order to obtain the correct architecture dependent File implementation. The BaseScummFile would then defer invocations to said backend.

Yet another, less than ideal, solution would be to use #ifdef to inherit the BaseScummFile from the correct backend.

Finally, lets not forget that BaseScummFile uses other tricks to handle the inherited methods from Common::File. Since they are not virtual, the subclasses can create a method with the same signature and still call the base class independently (as done in ScummFile for example). By using the deferred method, this technique could pose problems.

Update 28/03: BaseScummFile could be implemented as a wrapper around Common::File instead of inheriting from it.

Methods affected by different architectures

The methods whose algorithm suffers changes depending on the current backend are only two: fopenNoCase and open. The fopenNoCase changes if the amigaos4, GP32 or palmos are defined. The open one changes only if MACOSX is defined.

There's also methods whose signature gets redefined via the preprocessor using #define's. The architectures using this are three out of ten: Playstation 2, Dreamcast and Symbian32. This raises yet another problem. Since the algorithm doesn't change, to properly do this in subclasses, one would have to retype the whole algorithm -line by line- and only change fopen for say, ps2_fopen. Needless to say, this is not a good solution.

Another option would be to use a pattern like, template for example. And define the algorithm in parts, where each subclass redefines parts as needed. This seems like overkill to me, since most of the backends would use the default implementation. As in a comment inside file.cpp, "such a high price for a simple beautification".

Update 28/03: functions like ps2_open are actually implemented by ScummVM. If Common::File gets redefined, said implementation will be moved to the appropiate subclass of the new File class. Update 29/03: the ps2sdk does provide fopen, fread, etc... but they get redefined because ScummVM uses inneficient I/O. The redefinitions are then used to -avoid- calling the SDK functions, since the custom ones provide caching.

A possible wrapper

Given that the Common::File class inherits itself from two classes: Common::SeekableReadStream and Common::WriteStream, the wrapper itself would have to handle things like "data >> wrapper", since streams usually overload the >> and << operators. Again, this would add a lot of complexity.

Update 28/03: The operators << and >> and not being currently overloaded by the classes Common::File inherits from.

New methods

Adding new methods to the existing Common::File class wouldn't have an impact, unless it redefines or changes the signature of the old ones. Instead of breaking up said class, it could be cleaned up and extended with new functionality.

Current Filesystem API implementation diagrams

 

Filesystem Node Redesign proposal

 

Common::File Redesign proposal

 

Possible new operations

This section describes possible new file/path operations to be added to ScummVM. If the method will be similar to an existing one, it'll be noted in parentheses after the description.

The format used is methodName(param1, ..., paramN) -> ret1, ..., retN: description.


split(path) -> head, tail: split the pathname path into a pair, (head, tail) where tail is the last pathname component and head is everything leading up to that. (python os.path)

splitDrive(path) -> drive, tail: split the pathname path into a pair (drive, tail) where drive is either a drive specification or the empty string. (python os.path)

getSeparator() -> string: returns the separator used in the current backend. For example, "/" for Unix or "\" for Windows.

isAbsolute(path) -> bool: return True if path is an absolute pathname (begins with a slash). (python os.path)

isReadable(path) -> bool: true if the file can be read.

isWriteable(path) -> bool: true if the file can be written.

count(dir) -> int: returns the total number of directories and files in the directory. (Qt)

listDir(list, mode, filter) -> list: lists the dir, with the given mode and filter. This function is already present. The idea is to augment it with filters

isHidden(path) -> bool: tests whether the file pathname is a hidden file. (Java)

getCanonicalPath(path) -> path: returns the canonical pathname string of the abstract pathname. (Java)

Misc

You might want to keep in minde that a "path" can refer to:

  • an existing file (which hence might be writable or readable or both)
  • an existing directory (which can be writeable, too, with different meaning)
  • a not yet existing but valid path to a file/dir (which is neither readable nor writable, I guess).

Well, that's not really a brilliant observation ; I just wanted to make sure this will be considered in the design process Fingolfin 00:22, 24 March 2007 (CET)

Return to the user page of "David corrales".