Chapter 13. File Management

Table of Contents

1. Pathname Conventions
2. Creating and Opening Files
3. Input/Output Operations on Text Files
3.1. Writing Data in a File
3.2. Reading Data from a File
4. Renaming, Moving, Copying, and Deleting A File
4.1. Renaming a File
4.2. Moving a File
4.3. Copying a File
4.4. Deleting a File
5. Directory Operations
5.1. Creating a New Directory
5.2. Deleting a Directory
5.3. Renaming a Directory
5.4. Operations on the Content of a Directory
6. Alias Management
7. Modifying File and Directory Attributes
8. Other Properties of the File and Folder Classes

Biferno provides the file and folder classes for server-side file management operations. In this chapter we will discuss Biferno pathname conventions and then describe the main methods and properties of these classes.

1. Pathname Conventions

Biferno pathname conventions are similar to those used by the HTTP protocol with a few differences.

We can define a pathname in two ways:

  • Using a relative path, i.e. starting from the current Web directory or from the web server root directory.

  • Using an absolute path, i.e. tracing the full file pathname starting from the root directory of the file system.

Relative pathnames follow the HTTP protocol and HTML language rules. The "mypage.bfr" path indicates that the file is in the current Web directory, which is the directory of the .bfr file that is executing. If the path starts with a slash (/), e.g. "/mypage.bfr", the file is in the server or site root directory.

To clarify the difference, assume we have implemented on our web server a virtual host called mysite.com that points to the "mysite" directory, which is a subdirectory of the server root directory. According to the HTTP protocol a file in the mysite.com domain with path "/mypage.bfr" is located in the "mysite" directory. If from a script contained in the "mysite" folder we want to refer to a file called "myfile.txt" in the same "mysite" directory, we can use either the relative path ("myfile.txt") or the absolute path from the site root ("/myfile.txt").

To identify an absolute path in Biferno we use the "file://" string in front of the pathname, which starts with the disk name (unit or partition) on Mac OS and Windows (e.g. file://Mac OS HD/Internet/Web pages/mysite/myfile.txt or file://C/Inetpub/myFile.txt), or with the name of the first directory on the path starting form the file system root on UNIX (e.g. file://export/home/usr/mysite/myfile.txt).

The syntax of Biferno paths allows the use of the ".." characters to denote the parent directory.

A path can identify a file or a directory. A path to a directory always ends with the slash character.

To discover the absolute path of the root directory of the Web server (or the current application, if associated to a virtual host) we can use the predefined property serverInfo.root. The corresponding absolute path can be derived from a relative path using the static method ResolvePath of the file class. We can write:

<?
	realPath = file.ResolvePath("documents/doc00001.txt")
?>
    

As we will see in the following when discussing alias handling, this method resolves also any aliases that may be contained in the path.

A file path can also be obtained in its native format, which depends on the platform (operating system and file system) we are working on, using the static method NativePath of the file class. We can write:

<?
	filePath = "file://C/Internet/Web pages/mysite/myfile.txt"
	nativePath = file.NativePath(filePath)
?>
    

On Windows the result is "C:\Internet\Web pages\mysite\myfile.txt".

2. Creating and Opening Files

In Biferno a file class variable can be initialized by simply passing to the class constructor a string containing the path (relative or absolute) of the file to associate to the variable. In the following example we instantiate three variables of the file class using the three kinds of path previously mentioned:

<?
	file1 = file("documents/doc00001.txt")
	file2 = file("/mysite/documents/doc00002.txt")
	file3 = file("file://home/usr/user1/mysite/documents/doc00003.txt")
?>
    

Normally, if the path passed to the constructor actually corresponds to an existing file on disk, the file is associated to the variable that is created and an attempt is made to open the file in read/write mode. If permissions do not allow to open the file in read/write mode, an error is generated.

This standard behavior can be modified by a number of optional parameters of the file class constructor, with prototype:

file(string path, int openMode, int permission)
    

Table 13.1. File class: Possible values of the openMode parameter

openMode ValueFile Opening Mode
openFileAlwaysIf the file does not exist, is created and opened.
openFileExistingIf the file does not exist, an error is generated.
createFileNewCreate a new file. If the file does already exist, an error is generated.
createFileAlwaysCreate a new file. If the file does already exist, is deleted and replaced by the newly created file.
dontOpenDo not open the file, simply check path validity.

The openMode parameter can assume one of the constant values listed in Table 13.1, “File class: Possible values of the openMode parameter”. The default value for this parameter is normally openFileExisting, except if the file is an alias. In this case the default is dontOpen.

The permission parameter can assume the constant values rw (read/write) or r (read only). The default value is rw.

The following example shows how to use the parameters:

<?
	// If the file exists, is opened read-only
	file1 = file("doc00001.txt", openFileExisting, r)
	// If the file does not exist, is created and opened
	file2 = file("doc00002.txt", openFileAlways)
	// A new file is created and opened
	file3 = file("doc00003.txt", createFileAlways)
?>
    

All constant values we have described so far are actually static public constants of the file class. In the previous example we have seen how in Biferno the class name can be omitted in front of static constants when calling a method of the same class (including the constructor). In the general case we would have to write something like

file.openFileExisting
    

After creating a file class object, the complete file path on disk is stored in the read-only property path, while the read-only property name contains only the filename. We have mentioned that a file class variable can be created without opening the file on disk. A file can be opened and closed using the Open and Close methods (without parameters). All files initialized and opened by a script are automatically closed when the script is terminated (or, more precisely, on the exit of the corresponding file variable scope).

To check if a file is open on disk we can use the static method IsOpen of the file class, with prototype:

static boolean IsOpen(string filePath)
    

This method returns true if the file that filePath points to is currently open (by some process active on the system), false if it is not open. To check if a file that an object of the file class points to is open, we use the read-only property isOpen.

<?
	if (file1.isOpen)
		file1.Close()
?>
    

To know in advance if a file or directory exists we can use the static method Exists of the file class, with prototype:

static boolean Exists(string path)
    

This method returns true if the path parameter is a valid path that points to an existing file or directory, false otherwise.

A similar method is the CheckPath method, which returns the integer value 0 (zero) if the file exists, or otherwise returns the error code generated by the file system.

We can check in a similar fashion if a path points to an alias (i.e., as we will see in the following, to a link to a file) or to a directory, using the static methods IsAlias and IsFolder of the file class. These methods return a Boolean value and take as parameter a string containing a file path. As for the IsOpen method, the read-only property isAlias corresponds to the IsAlias static method.

3. Input/Output Operations on Text Files

3.1. Writing Data in a File

Data can be written in a file using specific methods of the file class.

The Put method allows to write a string into a file with a given offset (distance, in characters, from the beginning of the file) and has prototype:

void Put(int offset, string text)
     

The inserted text overwrites the current content of the file starting from the given offset and, if necessary, increases the length of the file.

In the following example we create a new file called "test.txt" in the current directory and insert the test "This is the first line of the file" followed by a line feed.

<?
	myFile = file("test.txt", openFileAlways)
	myFile.Put(0, "This is the first line of the file\n")
?>
     

Since the file has just been created and is empty, we specify a zero offset. In general, the offset parameter has always to be less than the logical file size (total number of bytes, or characters).

To insert a new line of text in the file it is convenient to use the Append method, which always adds the text at the end of the file. In the case of the previous example, we can write the following code line:

myFile.Append("This is the second line of the file \n")
     

After the execution of this instruction the "test.txt" file contains the following two lines:

This is the first line of the file
This is the second line of the file
     

The Append method can also be used if the file is empty. In practice we will use the Put method only when we want to replace the content of an existing file with new data. Let's take the "test.txt" file we created and replace the second line with another string.

<?
	myFile = file("test.txt")
	offset = 36
	str = "New text"
	myFile.Put(offset, str)
	myFile.length = offset + str.length
?>
     

The offset value was computed by manually counting the characters of the first row, including the line feed. Notice that, since the str string is shorter than the second line in the file, it is necessary to adjust the file length to eliminate the remaining characters. A file size can be decreased or increased at will by directly operating on the length property (with the limitations imposed by the physically available space on disk).

3.2. Reading Data from a File

To read data from a file we use the Get method, with prototype:

string Get(int offset, int len = all)
     

In this case the offset represents the number of characters to skip from the beginning of the file before reading data. The len parameter represents the total number of character that we want to extract. The default value for len is all (predefined constant of the file class), which extracts all characters until the end of the file is reached. The Get method returns the characters extracted from a file in a string. In the following example we assign the entire content of the "test.txt" file to the text variable.

<?
	myFile = file("test.txt")
	text = myFile.Get(0, all)
	text = myFile.Get()	// same as the above
?>
     

A file can also be read one line at a time using the GetNextLine method with prototype:

string GetNextLine(void)
     

A line of a file is defined as a sequence of characters followed by a new line (CR, LF or CR+LF). In the following example we extract and print one at a time all lines of the "test.txt" file.

<?
	myFile = file("test.txt")
	do {
		$myFile.GetNextLine()
		} while (myFile.curLine != file.EOF)
?>
     

To check if the loop reached the end of the file, i.e. when the last line of text has been extracted, we check the value of the curLine property, which contains the index of the current line (the line after the last line extracted calling the GetNextLine method). After the last line has been extracted (read), the curLine property contains the value corresponding to the EOF (End Of File) constant of the file class.

The Get method ignores the value of curLine and in general the position reached by GetNextLine on the current file. It is advisable to avoid mixing calls to GetNextLine and calls to Get on the same file instance.

4. Renaming, Moving, Copying, and Deleting A File

4.1. Renaming a File

To rename a file we can use the Rename method of the file class and pass it a string that contains the new name to be assigned to the file (just the name, not the full path).

myFile.Rename("doc001.txt")
     

It is necessary to verify in advance if a file with the same name already exists in the same directory, otherwise the operation will fail.

4.2. Moving a File

To move a file from a directory into another directory, the file class provides the Move method, with prototype:

void Move(string filePath, boolean replace = false)
     

The filePath parameter specifies the new path to assign to the file, or, alternatively, the path to the directory that the file should be moved to. The replace parameter specifies if the file that is moved should replace a file with the same name in the target directory, if one exists. To move a file into a subdirectory we can write:

<?
	myFile.Move("docs/")
?>
     

In the following example we move a file into a subdirectory and rename an existing file with the same name, if present:

<?
	fileName = "doc001.txt"
	newPath = "docs/" + fileName
	if (file.Exists(newPath))
		{
			oldFile = file(newPath)
			oldFile.Rename(fileName + ".old")
		}
	myFile = file(fileName)
	myFile.Move(newPath)
?>
     

Notice that renaming the old file may fail if the name to be assigned is already used by another file in the same directory. In real life every time the name or location of a file is changed, the possible presence of duplicates should be checked.

4.3. Copying a File

The Copy method of the file class allows to create a copy of a file in a different location. This method has the same parameters as the Move method and the same considerations hold. The new file created by the Copy method is not automatically opened (if opening it is necessary, use the Open method).

In practice one will often want to move (or copy) a file and rename it at the same time. This can be done by specifying not only a new directory, but also a new file name in the filePath parameter. An example of renaming by using the Move method is:

<?
	myFile = file("texts/text1.txt")
	myFile.Move("archived/doc001.txt")
?>
     

The same can be done for the Copy method, a file can be duplicated and a new name can be assigned to the copy at the same time.

4.4. Deleting a File

To delete a file we use the Delete method of the file class, as in:

<?
	myFile.Delete()
?>
     

The variable is no longer valid after cancellation and should be reinitialized, even if the path property still contains the path to the deleted file.

5. Directory Operations

To create and manipulate a directory (also called a folder) Biferno provides the folder class, which is in many respects similar to the file class. This section describes the fundamental operations that can be executed on a directory using methods of the folder class.

5.1. Creating a New Directory

When a folder class variable is instantiated by passing to the class constructor a string containing the path to a directory, we can decide if we wish to create a new directory or to have the object point to an existing directory.

The folder class constructor has the same parameters of the file class constructor, except the permission parameter. The prototype is:

folder(string path, int openMode)
     

The openMode parameter for the folder class can assume the values listed in Table 13.2, “Folder class: Possible values of the openMode parameter”. The default value is folderExisting.

To create the "docs" directory in our site root we will write:

<?
	docsFolder = folder("/mysite/docs/", createFolderIfNeeded)
?>
     

Table 13.2. Folder class: Possible values of the openMode parameter

openMode ValueDirectory Creation Mode
createFolderIfNeededCreates a new directory if the directory does not exist. Otherwise the object points to the existing directory.
createFolderNewCreates a new directory if the directory does not exist. Otherwise an error is generated.
folderExistingDoes not create a new directory. If the directory does not exist an error is generated.

If the directory specified by the path parameter exists, the docsFolder variable points to the existing directory.

The static method:

static void Create(string path)
     

can be used to create a directory without instantiating a variable. This method has no effect if the directory exists.

5.2. Deleting a Directory

To delete a directory we use the Delete method of the folder class, as in:

<?
	myFolder = folder("garbage/")
	myFolder.Delete()
?>
     

A directory can only be deleted if it is empty. As in the case of files, the corresponding variable is no longer valid after deletion, even if the path property still contains the path to the deleted directory.

5.3. Renaming a Directory

To rename a directory we can use the Rename method of the folder class passing a string that contains the new name to be assigned to the directory. An example is:

<?
	myFolder = folder("/mysite/docs/")
	myFolder.Rename("documents")
?>
     

5.4. Operations on the Content of a Directory

Using the Walk method of the folder class a directory can be examined and operations can be executed automatically on every file contained in the directory, without previous knowledge of the number, nature and names of the files.

The Walk method has the following prototype:

int Walk(string suffix, boolean recursive, string callBack, obj *userData)
     

The suffix parameters denotes the type, i.e. the extension of the files we want to examine. Passing the ".txt" string will filter only text files, while the ".*" string will result in no filtering, i.e. all file types will be examined. Notice that passing an empty string in the suffix parameter will result in filtering only files with no extension. The Boolean recursive parameter specifies if we want to explore all subdirectories of the target directory. The callBack parameter is the name of a function we want to execute on each file found in the target directory. The userData optional parameter can be an object of any class and can be only passed by reference.

The method returns an integer value that contains an error code generated by the callback function. If there have been no errors during execution, the function must return the value 0 (zero).

The callBack function must have the following prototype:

function int function_name (string filePath, int variant, obj *userData)
     

When the Walk method is invoked, it examines one by one all files present in the directory, possibly discarding files that should be excluded on the base of the value of the suffix parameter (hidden files are always excluded). For each file it calls the callback function passing as parameters the file path, a value (variant), which tells to the function if the path points to an alias or directory, and, if present, the userData object.

In the following example the Walk method is used to collect general information on a directory (number of elements and their byte size). The GetItemInfo callback function modifies the properties of an object of the folderInfo class (defined at the beginning of our script) which, when the Walk method terminates, contains all desired information on the target directory. Notice the use of the variant parameter and of the isFolderBit and isAliasBit constants of the file class. The size (number of characters or bytes) is contained in the length property of the file class. The resForkLength property of the file class is meaningful only on the Mac OS platform and contains the size of the resource fork of Macintosh file (this property has always value zero on all other platforms).

<?
	class folderInfo
	{
		unsigned		totItems = 0
		unsigned		subFolders = 0
		unsigned		aliases = 0
		unsigned		totSize = 0
	}

	function int GetItemInfo(string filePath, int variant, folderInfo 
*fInfo)
	{
		fInfo.totItems++
		if (variant & file.isFolderBit)	// is a directory
			fInfo.subFolders++
		else
		{
			if (variant & file.isAliasBit)	// is an alias
				fInfo.aliases++
			theFile = file(filePath)
			fInfo.totSize += theFile.length + theFile.resForkLength
		}

		return 0
	}
	
	fInfo = folderInfo()
	myFolder = folder("file://HD/Projects/mysite/")
	theErr = myFolder.Walk(".*", true, "GetItemInfo", &fInfo)
?>

<html>
	<body>
		Folder: $myFolder.path$<br>
		Items: $fInfo.totItems$<br>
		Subfolders: $fInfo.subFolders$<br>
		Aliases: $fInfo.aliases$<br>
		Total Size: $fInfo.totSize$ bytes<br>
	</body>
</html>
     

This script will produce an output similar to the following:

Folder:  file://HD/Projects/mysite/
Items: 24
Subfolders: 2
Aliases: 2
Size: 89835 bytes
     

The behavior of the Walk method is slightly dependent on the operating system if the content of the folder is modified while executing the method itself. In particular, on MacOS Classic the identification of a file is implemented via a sequence number (position) within the folder and such numbering might become inconsistent if files are removed from the folder using the Walk method. For this reason it is not trivial to use the Walk method to delete the content of a directory on the MacOS Classic platform.

6. Alias Management

As mentioned previously an alias is a pointer, or symbolic link, to a physical file or directory. In general an alias can reside in a different directory than the original. In computer jargon, resolve an alias means to obtain the path to the original file or directory that the alias points to.

Alias Support

Beware that no operations on aliases are supported on the Windows platform.

When instantiating a variable of the file class passing the path to an alias, the class constructor does not automatically resolve the alias, but rather returns an object that points directly to the alias. Conversely, if a file path contains directory aliases, they are all automatically resolved by the constructor, as well as by other methods of the file class that accept a path as a parameter (Rename, Copy, Move, etc.).

An error message is returned if we specify in the constructor an opening mode other than dontOpen (which is the default for aliases), because an alias can not be opened. We have mentioned the IsAlias method, which determines if a path points to a physical file or to an alias. Once established that a path points to an alias, the ResolvePath static method of the file class can be used to obtain the path to the original file. This method has prototype:

static string ResolvePath(string aliasPath)
    

The following example shows the use of this method in combination with the IsAlias method.

<?
	myPath = "file://HD/WebSTAR Server Suite 4.1/mysite/test.txt"
	if (file.IsAlias(myPath))
		{
			originalPath = file.ResolvePath(myPath)
			myFile = file(originalPath)
		}
?>
    

Alternatively, we can use the ResolveAlias method that, invoked on a file class object pointing to an alias, returns a string containing the path to the original file. The previous example can be rewritten as follows:

<?
	myFile = file("file://HD/WebSTAR Server Suite 4.1/mysite/test.txt")
	if (myFile.isAlias)
		{
			myFile = file(myFile.ResolveAlias())
		}
?>
    

If, after the first initialization, the myFile variable points to an alias, the alias is resolved and the variable is reinitialized to make it point to the original file.

The Move, Rename and Copy methods of the file class applied to an alias act on the alias and not on the original file.

We can create an alias to a file using the MakeAlias method of the file class, with prototype:

void MakeAlias(string aliasPath)
    

An example is:

<?
	myFile = file("test.txt")
	// Create an alias in the same directory
	myFile.MakeAlias("test_alias.txt")
	// Create an alias in another directory
	myFile.MakeAlias("file://HD/Aliases/test_alias.txt")
?>

An alias to a directory can be created in a similar way using the MakeAlias method of the folder class.

7. Modifying File and Directory Attributes

On Linux file and directory access permissions can be modified at the owner, group and other granularity. The possible permission values are:

  • Read only

  • Read and write

  • Read, write and execute.

To modify permissions on a file or directory, we use the static method fchmod (common to the file and folder classes). This method takes two parameters, the file or directory path, and an integer resulting from the sum of predefined constants for the file and folder classes. The constants are listed in Table 13.3, “File class: Predefined constants for file and directory access permissions on Linux”.

Table 13.3. File class: Predefined constants for file and directory access permissions on Linux

Predefined CostantCorresponding Access Permission
S_IRGRPRead only for the group.
S_IROTHRead only for other users.
S_IRUSRRead only for the owner.
S_IWGRPRead and write only for the group.
S_IWOTHRead and write for other users.
S_IWUSRRead and write for the owner.
S_IXGRPRead, write and execution for the group.
S_IXOTHRead, write and execution for other users.
S_IXUSRRead, write and execution for the owner.

To assign read/write permission to the owner and read-only permission to everybody else on the file corresponding to the myFile variable we write:

file.fchmod(myFile.path, S_IWUSR + S_IRGRP + S_IROTH)
    

Information on the permissions assigned to a file or directory can be obtained using the fgetmod static method (common to the file and folder classes). An example is:

permission = file.fgetmod(myFile.path)
    

On the Mac OS platform we can obtain and, if desired, modify the type and creator of a file using the corresponding properties osType and osCreator of the file class. In the following example, after verification that the platform is "MacOS", we check that a file is a text file and assign it as creator the signature of the “SimpleText” application:

<?
	if (biferno.os.Begins("MacOS"))
		{
			if (myFile.osType == "TEXT")
				myFile.osCreator = "ttxt"
		}
?>
    

The two properties osType and osCreator are meaningful only on the Mac OS platform and contain the empty string on all other platforms.

The biferno.os predefined property returns a string that identified the operating system of the server running Biferno. This string always starts with the generic system name followed by a : character (colon), e.g. with "MacOS:" on the Macintosh Classic platform, with "Unix:" on Linux and MacOSX, and with "Windows:" on Windows. The rest of the string is the system version.

8. Other Properties of the File and Folder Classes

For all other properties of the file and folder classes (user, group, modifTime, creatTime, etc…) see the online Reference Guide as referenced in Chapter 2, Documentation.