8. Files, Directories and Streams

This chapter is about files and actions associated with files. It will cover the important parts of Qt's platform independent file and file system classes. After each introduction of the classes some actual code is used to further demonstrate the use of the classes.

The QFile

Qt handles files as objects. For example, a file is represented by an instance of the QFile class. The file does not have to exist, not be opened. The method exists() is used to determine if the file exists. If the file exists, it can be removed using the remove() method. For reading and writing (or both) the file must be opened. This is done using the open() method. A file can be opened in several modes, these are the most common:

When working with log files, i.e. appending information to a file, it is required to open the file as IO_WriteOnly | IO_Append in order to make the file writeable. When calling the open() method it will return TRUE if successful or FALSE if the file failed to open. If any of the writeable modes (IO_WriteOnly or IO_ReadWrite) is used a file is created if it does not exist.

When dealing with file objects it is sometimes desireable to see if a file is open or not. This is easily done with the isOpen() method. When done with a file, call the close() method to close it. This flushes any buffers not written back to disk. If it is required to flush a buffer without closing the file, use the flush() method.

Example 8-1 shows how the QFile class can be used. Example 8-2 shows a Bash session using the application from example 8-1. The touch command creates the file f.txt while the chmod -r removes the read access to the file.

#include <qapplication.h>
#include <qfile.h>

#include <iostream>

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  QFile f( "f.txt" );

  if( !f.exists() )
  {
    // It does not exist
    std::cout << "The file does not exist." << std::endl;

    return 0;
  }

  // It exists, open it
  if( !f.open( IO_ReadOnly ) )
  {
    // It could not open
    std::cout << "Failed to open." << std::endl;

    return 0;
  }

  // It opened, now we need to close it
  std::cout << "It worked." << std::endl;

  f.close();

  return 0;
}

Example 8-1

$ ./qfile
The file does not exist.
$ touch f.txt
$ ./qfile
It worked.
$ chmod -r f.txt
$ ./qfile
Failed to open.

Example 8-2

Streams

A file in it self is not much use. It has some primitive members for reading and writing but by using a stream class to do the actual reading and/or writing the system becomes easier to use and more flexible.

For handling text information the QTextStream. For binary data there is the QDataStream, but this chapter will focus on the text half.

The curious reader might allready have discovered that the stream classes' relation to the QFile class the the IODevice class. This means that any code that uses a stream to read from or write to, for example, a file can also read from sockets, databases, memory, etc. This makes it possible to create very flexible, re-useable code.

The QTextStream supplies the << and >> operators for the common types. In Trolltech's second tutorial, in the Data Elements chapter in the section titled "Reading and Writing Data Elements" it is demonstrated how to implement these operators for any class or structure used.

Using the flags property of the text stream the formatting of the output can be controlled. For example, the base (binary, octal, decimal, hexadecimal) of the outputted numbers can be controlled.

Finally, the atEnd() method returns a boolean value indicating if there is more data in the current data source.

Example 8-3 shows how to use a text stream in combination with a file. Example 8-4 shows a Bash session using the resulting application. The command wc -l counts the number of lines in the given file.

#include <qapplication.h>
#include <qfile.h>
#include <qtextstream.h>

#include <iostream>

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  QFile f( "f.txt" );

  if( !f.open( IO_WriteOnly | IO_Append ) )
  {
    std::cout << "Failed to open file." << std::endl;
    return 0;
  }

  QTextStream ts( &f );

  ts << "This is a test-run." << endl;

  f.close();

  return 0;
}

Example 8-3

$ wc -l f.txt
wc: f.txt: No such file or directory
$ ./stream
$ wc -l f.txt
      1 f.txt
$ ./stream
$ wc -l f.txt
      2 f.txt

Example 8-4

Directory Paths

Different platforms have different ways of specifying paths. Windows has drives and folders, like c:\foo\bar, while UNIX, and MacOS X which is a UNIX derivate, uses a single root with folders, like /foo/bar. They also differ in the characted used to separate folders from each other. In order to represent paths in a platform independent way, Qt provides the QDir class.

When exploring a file system a place to start from is nice to have. For that purpose QDir has a number of static members. First is the current() method that simply returns a QDir representing the applications current directory. Then, there is the root() method that returns the root of the current drive. For systems with several drives there is the drives() method that returns a set of QFileInfo instances (do not try to delete the return pointer, it belongs to Qt). Finally, the home() method points to the current user's home directory.

When moving about it is possible to start from one of the static methods metioned in the previous paragraph or from a string. Such a string can be cleaned using the cleanDirPath() method. The existence of the path can be verified using the exists() method. If a relative path is given, the convertToAbs() comes in handy. To start moving around the two methods cd() and cdUp() are provided. The work just like the Bash/Cmd/DOS equivalent cd and cd ... When the method isRoot() returns TRUE there is no use to call cdUp().

It may be useful to move around, but even more useful to actually do something. The method mkdir() tries to create a new directory. It returns TRUE if successful, otherwise FALSE. The methods rmdir() and rename() also return TRUE if successful but the remove and renames directories respectively. rename() can actually rename files too, but to remove files the method remove() is used.

When moving around in the file system and acting on parts of it it is important to know what options there are. Before going in on how to iterate over the contents of the current directory there are two methods to discuss.

First there is setSorting. This method makes it possible to control in what order the entries will appear. The method is controlled by OR-ing together a set of flags. There are four exclusive modes that cannot be combined: QDir::Name, QDir::Time, QDir::Size and Unsorted. In addition to this there are some modifiers for ignoring case, put directories first etc. They are detailed here.

Then there is the setFilter method. This methods controls the items that will be seen when iterating over the contents of a directory. The options are OR-ed together to form a filter. Since there are so many of them I would recommend a look at the official documentation.

Example 8-5 shows an example of using QDir. It simply iterates over all sub directories of the current directory and prints them to the console.

#include <qapplication.h>
#include <qdir.h>

#include <iostream>

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  QDir currentDir = QDir::current();

  currentDir.setFilter( QDir::Dirs );
  QStringList entries = currentDir.entryList();
  for( QStringList::ConstIterator entry=entries.begin(); entry!=entries.end(); ++entry )
    std::cout << *entry << std::endl;

  return 0;
}

Example 8-5

Notice that a QStringList is returned. To access its members an ConstIterator is used. These work pretty much as their STL equivalents list<string> and list<string>::const_iterator and will not be discussed further here.

More Information About Files and Directories

setFilter may be useful to find the directory entries that are interesting but there is alot more information associated with each entry. Instead of using entryList() one can use entryInfoList. This returns a list of QFileInfo instances holding all the information associated with each entry. One thing worth noting is that the list returned from the call to entryInfoList belongs to the QDir object and must thus not be deleted. The returned list is reused in the next call to the same methods, thus, if the list is to be used later it must be copied.

What information does each QFileInfo contain then? First, we can get to know what type of entry we have run into using isDir, isFile and isSymLink. These methods returns a boolean value that is TRUE if the referred entry is what the method name claims.

It is also interesting to know what the entry is called. For this purpose there is a plethora of methods. First, the path can be found using filePath(). The absolute path (i.e. not relative) can be gotten from absFilePath(). The name of the file can be found using fileName() while the base name (without extension) and extension can be found using baseName() and extension() respectively. These two take an optional boolean that if TRUE returns the complete extension, e.g. for the file test.tar.gz the complete extension is tar.gz while the default is gz. Using the dir() method a QDir instance representing the path of the directory entry is given.

It is also interesting to know what attributes an entry has. For that purpose the boolean returning methods isReadable(), isWriteable, isExecutable and isHidden exist.

Then there is the time information regarding the entry. It is accessible through the methods created(), lastModified() and lastRead(). These methods all return QDateTime instances. This class can be converted to QString objects using the toString() method.

Example 8-6 demonstrates how to get a set of QFileInfo objects from a QDir object and how to iterate over the entries.

#include <qapplication.h>
#include <qdir.h>

#include <iostream>

int main( int argc, char **argv )
{
  QApplication a( argc, argv );

  QDir currentDir = QDir::current();
  currentDir.setFilter( QDir::Files );

  const QFileInfoList *infos = currentDir.entryInfoList();
  const QFileInfo *info;

  QFileInfoListIterator infoIt( *infos );
  while( (info=infoIt.current()) != 0 )
  {
    std::cout << "Base name: " << info->baseName( TRUE ) << std::endl;
    std::cout << "Extension: " << info->extension( TRUE ) << std::endl;
    std::cout << "Created: " << info->created().toString() << std::endl;

    ++infoIt;
  } }

Example 8-6

Summary

There is no downloadable example code for this chapter.

This is a part of digitalfanatics.org and is valid XHTML.