File System Access

The systems programming library (Pt::System) provides functionality to identify, create, rename, move or remove files and directories. An iterator based API can be used to traverse through the entries in a directory. It can be used with the iterator based algorithms in the C++ standard library. The FileDevice is an implementation of the IODevice to read and write files.

File Information

The Pt::System::FileInfo class provides operations to query information about files and directories in the file system and to add, remove and modify them. FileInfo objects can be created with a path, are assignable, comparable and can be used as keys for e.g. std::map. The path needs not to refer to existing items in the file system, when a FileInfo object is constructed. It can be checked whether a file exists and what type of file it is, as shown in the following example:

Pt::System::Path path("/tmp/logout.txt");
if( fi.type() == Pt::System::FileInfo::File )
{
std::cout << fi.path().toLocal() << ": " << fi.size() << " bytes.\n";
}
else
{
std::cout << fi.path().toLocal() << " is invalid.\n";
}

Most operations are available as non-member functions, so it is not neccessary to create temporary FileInfo objects. Only the paths to files or directories are required to perform file system operations. The next example illustrates some of the non-member functions for file operations:

try
{
Pt::System::Path tmp1("/tmp/tmpfile1");
Pt::System::Path tmp2("/tmp/tmpfile2");
// create a temporary file
// move it to a new location
// remove the file
}
catch(const Pt::System::AccessFailed& e)
{
std::cerr << "file error: " << e.resource() << std::endl;
}

The code shown above creates a file, moves it to a new location and finally deletes it. If an operation fails, for example because the file could not be created, an exception of type AccessFailed is thrown. This is also the case for all other operations such as size(), createFile(), createDirectory(), resize() and remove(). The exception reports the name of the resource that could not be accessed.

Directory Contents

The Pt::System::DirectoryIterator can be used to iterate over the contents of directories. It is created with a path to a directory and satisfies the requirements for a forward iterator. The iterator successivly reads the contents of the assoziated directory and returns a FileInfo when dereferenced. Like the stream iterators of the C++ standard library, it changes to a special state when the end of the directory is reached. This state is identical to a default constructed iterator, so instances thereof can serve as the iterator to the end of the directory.

try
{
Pt::System::Path path = "/tmp";
for( ; it != end; ++it)
{
std::cout << "name : " << it->path().toLocal() << std::endl;
}
}
catch(const Pt::System::AccessFailed& e)
{
std::cerr << "failed to access directory" << std::endl;
}

The constructor throws an AccessFailed exception if the path is not a valid directory. The exception can be avoided by checking the path with FileInfo::type() first, to make sure it is valid. The DirectoryIterator can then be advanced to get the FileInfo for the next file in the directory, until the end of the directory contents is reached.

File I/O

A Pt::System::FileDevice reads and writes files in the filesystem either synchronously or asynchronously. If used synchronously, it offers similar functionality like a std::fstream or the file I/O functions from the C library (fopen...). However, most applications need to perform input and output asynchronously, which makes using a FileDevice attractive. Since it inherits Pt::System::IODevice, it can be used as the endpoint of an Pt::System::IOStream or Pt::System::IOBuffer, respectively. If no buffering is required, a FileDevice can be used on its own, as shown in the following example:

void onOpen(Pt::System::FileDevice& file);
void onOutput(Pt::System::IODevice& device);
int main(int argc, char** argv)
{
try
{
file.setActive(loop);
file.opened() += Pt::slot( &onOpen );
file.outputReady() += Pt::slot( &onOutput );
Pt::System::Path path = "tmpfile.txt";
file.beginOpen(path, std::ios::out);
loop.run();
return 0;
}
{
std::cerr << "I/O error: " << e.what() << std::endl;
}
return -1;
}

An EventLoop is required for all asynchronous operations. This includes not only reading and writing, but also opening the file. The function beginOpen() begins to open a file and the signal returned by opened() is sent when the file was opened. It is connected to the slot shown in the next example:

void onOpen(Pt::System::FileDevice& file)
{
file.endOpen();
file.beginWrite("Hello world!", 12);
}

The asynchronous open operation is ended by calling endOpen(), and a write operation is started with beginWrite() to write bytes from a buffer to the file. The signal outputReady() is sent, when data was written to the file. A slot is connected to that signal to handle output:

void onOutput(Pt::System::IODevice& device)
{
std::size_t n = device.endWrite();
std::cout << "wrote: " << n << "bytes." << std::endl;
}

The signal is inherited from IODevice, so the signature of the slot needs a reference to a IODevice as parameter. The write operation is ended by endWrite(), which returns the number of bytes written to the file. This might be less than what was requested by beginWrite(), in which case another write operation has to be started.

All functions to begin or end asynchronous operations throw an exception of type Pt::System::IOError on failure. If IOErrors are not catched and handled in the slots, they will propagate through the EventLoop into the main() function and end the program. Normally, larger applications will need to process errors in the slots, so the EventLoop is not stopped.