FileName
A FileName object represents the name of a file on the host operating system. This class provides methods to build and parse path names using the local operating system's syntax rules. It also offers methods to manipulate the file system object corresponding to a given filename, such as deleting or renaming a file, creating or deleting a directory, listing the contents of a directory, and retrieving a file's metadata (such as its creation date and size).
It might seem strange to use a special class to represent filenames, since most people - and most programming languages, for that matter - think of filenames as ordinary character strings. Even TADS itself traditionally did just this. The problem with treating filenames as plain text strings is that filenames have an internal structure to them, and that structure varies by operating system. This makes it hard to write portable code that builds and parses filenames. For example, if you want to write the name of a file in a subfolder, how do you do this with character strings? If you're a Windows user, the simplest approach is to write something like 'images\\pic.jpg'. But if you're a Unix user, you'd instead write 'images/pic.jpg'. Okay, you say, we can solve this little snag with the old Windows hack where we take advantage of the bug/feature where Windows happens to accept "/" slashes in place of "\" slashes - so we just write 'images/pic.jpg' and everyone's happy, right? Well, not really. For one thing, that really is a hack, not an officially supported feature, and it's not clear that Microsoft is committed to supporting it forever. (Changing it would break a lot of existing programs, but that hasn't always stopped Microsoft in the past.) More importantly, this hack doesn't help at all on other operating systems that use yet other path separators and different path syntax. VMS paths are utterly different, for instance - the VMS equivalent of our example here would be [.IMAGES]PIC.JPG. When you take into account all of the OSes in use, there's simply no way to write a native filename path as a string that will work everywhere.
This is why we need a class like FileName. This class takes care of the file syntax variations among operating systems, making it much easier to write portable code that operates on filenames. FileName knows that the string it contains is a file name, not just an ordinary text string, and it knows the local rules for constructing and parsing those names on each operating system where TADS runs.
You can use a FileName object as the file name argument in any of the "open" routines in the File class. You can also use a FileName in most other system functions and methods where file names are required, such as saveGame()
FileName objects are like ordinary strings in one important respect: they merely represent names, not actual objects in the host file system. This means that it's perfectly fine to create FileName objects with non-existent file names or non-existent directory names. Merely creating a FileName object makes no attempt to verify that the named object actually exists anywhere, or that any of its path components are valid. When you use a FileName to open a file, create a directory, etc., though, the name will obviously be validated at that point.
When using the FileName class, you should #include either file.h or filename.h in your source files. You should also add file.t (from the system library) to your build.
Construction
new FileName()
new FileName(str)
new FileName(path, name)
You can also use the fromUniversal() method to create a FileName from a hard-coded string in universal syntax.
Operators
FileName + string yields a new FileName object combining the directory path given by the FileName object with the file name given by the string. This uses the correct local syntax to combine the path elements, so this provides an easy way to build path names from subdirectory components.
For example, new FileName('images') + 'pic.jpg' yields a FileName object representing images/pic.jpg when running on Unix, images\\pic.jpg on Windows, etc.
FileName + FileName yields a combined directory path for the two elements, just like adding a string to a FileName object.
Comparing two FileName objects with == or !=, or comparing a FileName to a string, compares the names using the local file system naming rules. This is almost the same as comparing the name strings directly, but it ignores meaningless syntax differences between the two names (for example, it treats "a/b" as equal to "a//b" on Unix, since Unix treats multiple slashes in a row the same as a single slash), and it takes into account whether or not the local file system is sensitive to case (so "a" == "A" on Windows, but not on Unix).
Save and restore
When a FileName object is saved to a file via saveGame(), it's converted to the "universal" syntax (see fromUniversal()) for storage. When that file is later restored, the universal syntax is automatically converted back to the correct local syntax. This means that if a player saves a game on one machine and restores it on another, the FileName objects restored will automatically adapt to the local syntax on the new machine.
Network storage server
When a game runs in Web UI mode, with files stored on the network storage server, the FileName class uses storage server rules for building and parsing filenames. Storage server filenames are syntactically similar to Unix filenames, but there's no concept of a root directory; all files are within the user's folder for the current game, and nothing outside of this folder is visible or accessible.
The FileName methods for accessing the local file system, such as createDirectory() and getFileInfo(), are generally not supported on the storage server. The storage server is designed only for deploying traditional text games, so it provides only the core functions needed for storing and retrieving saved games, log files, and any other files the game creates for its own use.
Methods
addToPath(element)
createDirectory(createParents?)
If createParents is specified, it must be true or nil; this indicates whether or not the method should create any missing intermediate directories in the path, if the path has multiple elements. For example, suppose we're running on Unix, and the FileName object represents path '/a/b/c', and the directory '/a' currently exists but doesn't have a subdirectory 'b'; in this case, the function creates '/a/b' first if createParents is true. If createParents is omitted or nil in this case, the function will typically fail, since most systems don't allow creating '/a/b/c' if '/a/b' doesn't exist. (This rule is enforced by the operating system, though, not by the createDirectory() method. If the underlying OS creates the intermediate directories automatically, then this method will also do so even when createParents is nil.)
This function throws an error if the directory creation fails. The file safety settings must allow write access to the directory containing the new subdirectory.
deleteFile()
The file safety settings must allow write access to the file; if not, a file safety exception is thrown.
forEachFile(func, recursive?)
If recursive is true, the function recursively scans the contents of each subdirectory of the original directory, along with any subdirectories of the subdirectories, and so on. If recursive is nil or is omitted, only the direct contents of the named directory are scanned.
This method is similar to lisDir(), but is more convenient for recursive directory tree scans. In addition, since it doesn't build a list of results, it uses less memory when you only need to perform an operation per file rather than compiling a list of files.
fromUniversal(path)
The universal syntax is similar to Unix-style path notation, with forward slashes ("/") separating path elements. For example, 'files/data/stats.txt' refers to a file named stats.txt within a folder data within a parent folder files within the working directory.
This is a static method that you call on the FileName object itself:
local name = FileName.fromUniversal('files/data/stat.txt');
This method doesn't open the file or check its validity, so you can use it with files and paths that don't exist on the local machine.
The purpose of this method is to make it convenient to write hard-coded path names in a program without tying the program to a particular operating system. If you wrote hard-coded paths using your own OS's syntax, your program wouldn't work properly if someone ran it on a different OS. This method solves the problem by letting you write the path in a universal format, and then translating it at run-time to the local OS syntax.
getAbsolutePath()
On some systems, it might not be possible to convert every path to absolute format. If the path can't be converted, this returns nil.
getFileInfo(asLink?)
The optional asLink flag specifies the behavior if the named file is a symbolic link, which is a special type of file supported on some operating systems that functions as a pointer or proxy for another file. For most file operations, such as opening, reading, or writing the file, the operating system automatically follows the link and carries out the operation on the target file. However, the link file also has its own separate identity as a link and its own separate creation time and so forth, so in some cases it's useful to be able to retrieve information on the link itself instead of the target file. For example, when listing a directory containing links, the unresolved links are included in the listing, not the target files. That's where asLink comes in. If it's true, the return value from getFileInfo() describes the link file itself; if asLink is omitted or nil, the return value describes the target of the link. Any additional links are resolved as well in this case: if this file is a link that points to another link which points to third link, etc., the function keeps following those links until it reaches a regular file, and returns the information on that file. asLink has no effect for files that aren't symbolic links. Some operating systems also support "hard" links, which allow multiple file names to point to the same underlying data; hard links are by design indistinguishable from ordinary files on most systems that support them, so asLink usually has no effect if the named file is a hard link.
The FileInfo object is defined in the system library file file.t, which you should include in your build if you use this function. FileInfo has the following properties:
- fileType - an integer giving the type of the file, as a combination of FileTypeXxx bit flags. This is the same value returned by getFileType().
- fileAttrs - an integer giving the attributes of the file,
as a combination of FileAttrXxx bit flags. Test for the presence
of an attribute using the & operator (e.g.,
if ((info.fileAttrs & FileAttrHidden) != 0)). The attribute
flags are:
- FileAttrHidden - the file is marked as hidden.
Some systems use a naming convention to indicate that a file
is hidden (e.g., the "." prefix on Unix); others (such as
Windows) use special attribute flags in the file system. Not
all systems have a concept of hidden files; this flag will never
appear in a file's attributes on systems that don't have an
equivalent.
The exact behavior of hidden files varies by system, but on most systems, hidden files are excluded from the default view of a directory listing presented to the user, and from command wildcards (e.g., "rm *" on Unix doesn't delete any files whose names start with "."). However, the "hidden" attribute isn't a security or permissions mechanism, and hidden files aren't truly invisible. Users can still usually view these files in directory listings by specifying special overrides (e.g., "ls -a" on Unix) and can usually manipulate them by naming them explicitly in commands. The purpose of the "hidden" flag is to reduce clutter in the user interface, by hiding internal bookkeeping files that are maintained by the operating system or applications (such as preference files, caches, and indices). Users don't generally need to manage these sorts of files manually, so it's easier for the user to find her own files if the system omits the non-user files from most directory views.
Hidden files are not filtered out of the directory listings returned by listDir(), since that method is for programmatic access and thus returns all files, whether hidden or not. It's up to you to filter out hidden files, if appropriate, when displaying the results to the user.
- FileAttrSystem - the file is marked as a "system" file. This is primarily a DOS/Windows concept; this flag will never appear in a file's attributes on most other systems. The purpose of the "system" flag is to mark a file as belonging to the operating system; Windows uses this for its bootstrap loader files, swap file, and similar core OS files. For practical purposes, "system" files should be treated exactly like "hidden" files: they should be omitted from default directory views, and excluded from wildcard ("*.*") matches. The only reason that we distinguish "system" as a separate flag from "hidden" is to allow programs to display the two attributes separately on systems that use both of them, since users might be accustomed to seeing the two distinct attributes.
- FileAttrRead - the program has read access to the file, as determined by operating system permissions. This attribute only reflects the permission settings for the file; it doesn't guarantee that a given attempt to read the file will succeed, since conditions at the time of the read attempt could interfere, such as physical media errors or locking by another process.
- FileAttrWrite - the program has write access to the file, as determined by operating system permissions. On systems where files can be marked as read-only (e.g., DOS or Windows) separately from permissions settings, this attribute also indicates that the read-only flag is not set. Note that this attribute doesn't guarantee that a given attempt to write the file will succeed, since conditions at the time of the write attempt could interfere, such as insufficient disk space, physical media errors, or locking by another process.
- FileAttrHidden - the file is marked as hidden.
Some systems use a naming convention to indicate that a file
is hidden (e.g., the "." prefix on Unix); others (such as
Windows) use special attribute flags in the file system. Not
all systems have a concept of hidden files; this flag will never
appear in a file's attributes on systems that don't have an
equivalent.
- isDir - true if this is a directory/folder, nil if not. This is the same information as the FileTypeDir bit in the fileType property, but FileInfo breaks it out as a separate property for convenience, since this is an especially common attribute to test for.
- specialLink - this duplicates the FileTypeSelfLink and FileTypeParentLink flags from the fileType property. This is for convenience: you can test if a file represents "." or ".." (or the local system's equivalent) simply by comparing specialLink to zero. If specialLink is non-zero, the file is one of these special links.
- fileSize - an integer value giving the size in bytes of the file. If the value is too big for the 32-bit integer type, fileSize will be a BigNumber value. The size is usually meaningful only for ordinary files (those with type FileTypeFile).
- fileCreateTime - the file's creation time, as a Date object. If the operating system doesn't record a file's creation time, this is nil.
- fileModifyTime - the time of the file's last modification, as a Date object. If the OS doesn't record the modification time, this is nil.
- fileAccessTime - the time of the last access to the file, as a Date object. (The access time records when the file was last accessed, whether or not it was modified at that time, whereas the modification time is only updated when the contents of the file are changed.) If the OS doesn't record the access time, this is nil.
Some operating systems don't record all three timestamps. If a given timestamp isn't available on the local system, it'll be set to nil. Nearly all systems minimally keep track of the modification time. Unix-like and Windows systems keep all three, when the standard file systems are used (although the FAT32 file system on windows only records the access time to the nearest day, so files on FAT32 disks will always show midnight as the time of day for the access time).
The file safety level must allow ordinary read access to the file, otherwise a file safety exception is thrown. There's one special case: if the file safety settings allow read access to the working directory, and the file in question is a parent directory of the working directory, getFileInfo() access is allowed. The parent folder of the working directory, and its parent, and so on, are all part of the path to the working directory, so their metadata are considered part of the working folder's metadata. The parent folders therefore get the same file safety treatment as the working folder for the purposes of getFileInfo(). For other operations, the parent folders are considered outside the sandbox.
getFileType(asLink?)
- FileTypeFile - an ordinary file (such as a disk file)
- FileTypeDir - a directory (folder)
- FileTypeChar - a character-mode device, known as a character special file on Unix-like systms. This represents an input/output device that works in terms of a character stream, such as a console or printer.
- FileTypeBlock - a block-mode device, known as a block special file on Unix-like systems. This provides low-level access to a hard disk, CD-ROM, etc. at the sector level, bypassing any file system structure on the device. (Disk devices that also have file systems aren't normally accessible in this mode except under special conditions or with special privileges, since bypassing the file system can corrupt the file layout.)
- FileTypePipe - a pipe or similar interprocess communications channel (e.g., a Unix FIFO)
- FileTypeSocket - a network socket
- FileTypeLink - a symbolic link (a file that acts as a proxy or pointer to another file or directory; when the link file is opened, read, written, etc., the file system normally accesses the target of the link, transparently to the caller)
- FileTypeSelfLink - a special system-defined directory link to self (e.g., "." on Unix or Windows)
- FileTypeParentLink - a special system-defined link to the parent directory (e.g., ".." on Unix or Windows)
The type codes are bit flags, so more than one can apply to a given file; use the & operator to check if a particular flag is set. For example, to check if a given filename refers to a directory, use (f.getFileType() & FileTypeDir) != 0.
asLink has the same meaning as in getFileInfo.
Note that a return value of zero has a different meaning than nil. nil means that the file doesn't exist; 0 means that the file exists, but that it's a type of object that doesn't fit any of the FileTypeXxx flags. (This shouldn't happen on current versions of Windows, Mac OS, or Unix-like systems, since these flags cover all of the file types on those systems. Future versions of those OSes might add new file types outside of our categories, though, and more exotic platforms might already have some.)
getName()
The result is the same string returned from toString(self), and is the same same text used if the FileName is implicitly converted to a string, such as if you display the FileName via "<< >>" string embedding.
getPath()
This method works purely in terms of the path string stored in the FileName object. It doesn't look anything up in the host file system. For example, if the FileName represents 'pic.jpg', this method simply returns an empty string, since the stored name doesn't have a directory path portion.
getRootDirs()
This is a static method, so you call it on the FileName class itself:
local roots = FileName.getRootDirs();
Most Unix-like systems only have one root directory, usually called "/". Many other systems have a separate root directory for each volume or device; for example, Windows has a root folder for each drive letter, so the root list might contain paths like C:\, D:\, etc. Some systems have no concept of a root directory at all, in which case the result will be an empty list; this is the case for the network storage server.
getBaseName()
isAbsolute()
listDir()
If the file named by this object isn't a directory, or if the directory can't be accessed, a FileOpenException is thrown. The file safety settings must allow read access to the directory; if not, a file safety exception is thrown. For the purposes of directory listings, the sandbox folder itself is considered within the sandbox.
The list of files is returned in an order that's determined by the operating system. You can use the sort() method on the returned list if you want to sort the entries in a particular order.
Some operating systems, including Windows and Unix-like systems, include special files in the results that represent relative links to the listed directory and its parent. On Windows and Unix, these are called "." and "..", respectively - but other systems use different names, so don't just compare the name. Instead, use getFileType(), and check the FileTypeSelfLink and FileTypeParentLink flags (or use getFileInfo(), and check the specialLink property of the returned FileInfo object).
Here's an example that lists the contents of a directory, recursively listing the contents of any subdirectories. This illustrates how to test a result to see if it's a regular file or a directory, and how to filter out the special "." and ".." links to avoid infinite recursion. This function expects dir to be a FileName object naming the directory to list.
listDir(dir, level = 0) { for (local file in dir.listDir()) { local info = file.getFileInfo(); "<<makeString('\t', level)>><<file.getBaseName().htmlify()>>\n"; if (info.isDir && !info.specialLink) listDir(file, level + 1); } }
This function is similar to forEachFile(), but is more convenient if you need a list of results, such as for sorting. forEachFile() is better when you only need to perform an operation per file rather than compiling a list, and it's also more convenient for recursive scans since it can do those automatically.
Note that listDir() does not filter out files marked with the "hidden" or "system" attributes (see the getFileInfo() method and the fileAttrs property of the FileInfo object). Those attributes are only meant to affect the way a directory listing is presented to a user, whereas listDir() is designed to be used for more general file management purposes where you might need the full file list including hidden and system files. When you use listDir() to get a list of files to display to the user, you should filter the list yourself to remove hidden and system files, if desired, by checking each file's attributes via getFileInfo().
removeDirectory(deleteContents?)
If removeContents is specified, it must be true or nil. This indicates whether or not the routine should delete the contents of the directory before removing the directory itself. If this is true, the function attempts to delete any files and subdirectories in the directory (recursively deleting subdirectory contents) before deleting the directory itself. If any file within the directory can't be deleted, the method throws an error; if this happens, the directory's contents might be partially deleted, since some files might already have been successfully deleted before the error was encountered.
If removeContents is omitted or is nil, and the directory isn't already empty, the routine returns nil (indicating failure) without deleting anything. (Special system files that are always present in a directory, such as "." and ".." on Unix, don't count when determining if the directory is empty.) This is the default, since it helps avoid accidentally deleting files that the application didn't explicitly choose to remove.
For obvious reasons, use caution when using the removeContents flag.
This method requires that the file safety settings allow write access to the directory to be deleted.
renameFile(newname)
There's no return value. If the operation fails, the method throws an error.
newname must not refer to an existing file; if it does, the operation isn't allowed. The file safety settings must allow write access to both the old and new files (so you can't, for example, move a file to a directory where you wouldn't have access to the relocated file).
toUniversal()