Working with files and directories in Qt5

In this part of the Qt5 C++ programming tutorial, we work with files and directories.

QFile, QDir, and QFileInfo are fundamental classes for working with files in Qt5. QFile provides an interface for reading from and writing to files. QDir provides access to directory structures and their contents. QFileInfo provides system-independent file information, including file's name and position in the file system, access time and modification time, permissions, or file ownership.

File size

In the next example, we determine the size of a file.

file_size.cpp
#include <QTextStream>
#include <QFileInfo>

int main(int argc, char *argv[]) {
    
  QTextStream out(stdout);

  if (argc != 2) {
      
    qWarning("Usage: file_size file");
    return 1;
  }
  
  QString filename = argv[1];
  
  if (!QFile(filename).exists()) {
  
    qWarning("The file does not exist");
    return 1;
  }  
  
  QFileInfo fileinfo(filename);
  
  qint64 size = fileinfo.size();
  
  QString str = "The size is: %1 bytes";
  
  out << str.arg(size) << endl;
}

The size of the file is determined with the QFileInfo's size() method.

QString filename = argv[1];

The name of the file is passed as an argument to the program.

if (!QFile(filename).exists()) {
  qWarning("The file does not exist");
  return 1;
}

The existence of the file is checked with the exists() method of the QFile class. If it does not exist, we issue a warning and terminate the program.

QFileInfo fileinfo(filename);

An instance of the QFileInfo is created.

qint64 size = fileinfo.size();

The file size is determined with the size() method. The qint64 is a type guaranteed to be 64-bit on all platforms supported by Qt.

QString str = "The size is: %1 bytes";

out << str.arg(size) << endl;

The outcome is printed to the console.

Output
$ ./file_size Makefile 
The size is: 28029 bytes

Reading file contents

In order to read the contents of a file, we must first open the file for reading. Then an input file stream is created; from this stream, the data is read.

read_file.cpp
#include <QTextStream>
#include <QFile>

int main(void) {
    
  QTextStream out(stdout);

  QFile file("colours");
  
  if (!file.open(QIODevice::ReadOnly)) {
      
    qWarning("Cannot open file for reading");
    return 1;
  }

  QTextStream in(&file);

  while (!in.atEnd()) {
      
    QString line = in.readLine();    
    out << line << endl;
  }

  file.close();
}

The example reads data from the colours file. The file contains the names of eight colours.

QFile file("colours");

An instance of the QFile object is created.

if (!file.open(QIODevice::ReadOnly)) {
    
  qWarning("Cannot open file for reading");
  return 1;
}

The QFile's open() method opens the file in the read-only mode. If the method fails, we issue a warning and terminate the program.

QTextStream in(&file);

An input stream is created. The QTextStream receives the file handle. The data will be read from this stream.

while (!in.atEnd()) {
    
  QString line = in.readLine();    
  out << line << endl;
}

In the while loop we read the file line by line until the end of the file. The atEnd() method returns true if there is no more data to be read from the stream. The readLine() method reads one line from the stream.

file.close();

The close() method flushes the data and closes the file handle.

Output
$ ./read_file colours 
red
green
blue
yellow
brown
white
black
violet

Writing to a file

In order to write to a file, we open the file in the write mode, create an output stream directed to the file, and use a write operator to write to that stream.

write2file.cpp
#include <QTextStream>
#include <QFile>

int main(void) {
    
  QTextStream out(stdout);
    
  QString filename = "distros";
  QFile file(filename);
  
  if (file.open(QIODevice::WriteOnly)) {
      
    QTextStream out(&file);
    out << "Xubuntu" << endl;
    out << "Arch" << endl;
    out << "Debian" << endl;
    out << "Redhat" << endl;
    out << "Slackware" << endl;
    
  } else {
      
    qWarning("Could not open file");
  }  
  
  file.close(); 
}

The example writes the names of five Linux distributions to the file name called distros.

QString filename = "distros";
QFile file(filename);

The QFile object is created with the provided file name.

if (file.open(QIODevice::WriteOnly)) {

With the open() method, we open the file in the write-only method.

QTextStream out(&file);

This line creates a QTextStream that operates on a file handle. In other words, the stream of data to be written is directed to the file.

out << "Xubuntu" << endl;
out << "Arch" << endl;
out << "Debian" << endl;
out << "Redhat" << endl;
out << "Slackware" << endl;

The data is written with the << operator.

file.close(); 

In the end, the file handle is closed.

Output
$ ./write2file 
$ cat distros 
Xubuntu
Arch
Debian
Redhat
Slackware

Copying a file

When we copy a file, we create an exact reproduction of the file with a different name or in a different place of the filesystem.

copy_file.cpp
#include <QTextStream>
#include <QFile>

int main(int argc, char *argv[]) {
    
  QTextStream out(stdout);
  
  if (argc != 3) {
      
      qWarning("Usage: copyfile source destination");
      return 1;
  }
  
  QString source = argv[1];
  
  if (!QFile(source).exists()) {
      qWarning("The source file does not exist");
      return 1;
  }
  
  QString destin(argv[2]);

  QFile::copy(source, destin);
}

The example creates a copy of the provided file with the QFile::copy() method.

if (argc != 3) {
    
    qWarning("Usage: copyfile source destination");
    return 1;
}

The program takes two parameters; if they are not given, it ends with a warning message.

QString source = argv[1];

From the command line arguments of the program, we get the name of the source file.

if (!QFile(source).exists()) {
    qWarning("The source file does not exist");
    return 1;
}

We check for the existence of the source file with the QFile's exists() method. If it does not exist, we terminate the program with a warning message.

QString destin(argv[2]);

We get the destination file name.

QFile::copy(source, destin);

The source file is copied with the QFile::copy() method. The first parameter is the source file name, the second parameter is the destination file name.

File owner and group

Each file has a user who is its owner. A file also belongs to a group of users for better management and protection of files.

owner.cpp
#include <QTextStream>
#include <QFileInfo>

int main(int argc, char *argv[]) {
    
  QTextStream out(stdout);

  if (argc != 2) {
      
      qWarning("Usage: owner file");
      return 1;
  }
  
  QString filename = argv[1];
  
  QFileInfo fileinfo(filename);
  
  QString group = fileinfo.group();
  QString owner = fileinfo.owner();
  
  out << "Group: " << group << endl;
  out << "Owner: " << owner << endl;
}

The example prints the owner and the primary group of the given file.

QFileInfo fileinfo(filename);

An instance of the QFileInfo class is created. Its parameter is the file name given as a command line argument.

QString group = fileinfo.group();

The primary group of the file is determined with the QFileInfo's group() method.

QString owner = fileinfo.owner();

The owner of the file is determined with the QFileInfo's owner() method.

Output
$ touch myfile
$ ./owner myfile 
Group: janbodnar
Owner: janbodnar

Last read, last modified

Files store information about the last time they were read or modified. To get this information, we use the QFileInfo class.

file_times.cpp
#include <QTextStream>
#include <QFileInfo>
#include <QDateTime>

int main(int argc, char *argv[]) {
    
  QTextStream out(stdout);

  if (argc != 2) {
      
      qWarning("Usage: file_times file");
      return 1;
  }
  
  QString filename = argv[1];
  
  QFileInfo fileinfo(filename);
  
  QDateTime last_rea = fileinfo.lastRead();
  QDateTime last_mod = fileinfo.lastModified();
  
  out << "Last read: " << last_rea.toString() << endl;
  out << "Last modified: " << last_mod.toString() << endl;
}

The example prints the last read and last modified times of the given file.

QFileInfo fileinfo(filename);

The QFileInfo object is created.

QDateTime last_rea = fileinfo.lastRead();

The lastRead() method returns the date and time when the file was last read (accessed).

QDateTime last_mod = fileinfo.lastModified();

The lastModified() method returns the date and time when the file was last modified.

Output
$ ./file_times Makefile 
Last read: Sun Nov 1 17:54:31 2015
Last modified: Sun Nov 1 17:54:30 2015

Working with directories

The QDir class has methods for working with directories.

dirs.cpp
#include <QTextStream>
#include <QDir>

int main(void) {
    
  QTextStream out(stdout);
  QDir dir;
  
  if (dir.mkdir("mydir")) {
    out << "mydir successfully created" << endl;
  }
  
  dir.mkdir("mydir2");
  
  if (dir.exists("mydir2")) {
    dir.rename("mydir2", "newdir");    
  }
  
  dir.mkpath("temp/newdir");
}

In the example, we present four methods for working with directories.

if (dir.mkdir("mydir")) {
  out << "mydir successfully created" << endl;
}

The mkdir() method creates a directory. It returns true if the directory was successfully created.

if (dir.exists("mydir2")) {
  dir.rename("mydir2", "newdir");    
}

The exists() checks for the existence of a directory. The rename() method renames the directory.

dir.mkpath("temp/newdir");

The mkpath() creates a new directory and all necessary parent directories in one shot.

Special paths

There are some special paths in the filesystem; for instance a home diretory or a root directory. The QDir class is used to get the special paths in the system.

special_paths.cpp
#include <QTextStream>
#include <QDir>

int main(void) {
    
  QTextStream out(stdout);
   
  out << "Current path:" << QDir::currentPath() << endl;
  out << "Home path:" << QDir::homePath() << endl;
  out << "Temporary path:" << QDir::tempPath() << endl;
  out << "Rooth path:" << QDir::rootPath() << endl;
}

The example prints four special paths.

out << "Current path:" << QDir::currentPath() << endl;

The current working directory is retrieved with the QDir::currentPath() method.

out << "Home path:" << QDir::homePath() << endl;

The home directory is returned with the QDir::homePath() method.

out << "Temporary path:" << QDir::tempPath() << endl;

The temporary directory is retrieved with the QDir::tempPath() method.

out << "Rooth path:" << QDir::rootPath() << endl;

The root directory is returned by the QDir::rootPath() method.

Output
$ ./special_paths 
Current path:/home/janbodnar/prog/qt4/files/special_paths
Home path:/home/janbodnar
Temporary path:/tmp
Rooth path:/

File path

A file is identified by its name and path; a path consists of a file name, a base name, and a suffix.

file_path.cpp
#include <QTextStream>
#include <QFileInfo>

int main(int argc, char *argv[]) {
    
  QTextStream out(stdout);

  if (argc != 2) {
      
      out << "Usage: file_times file" << endl;
      return 1;
  }
  
  QString filename = argv[1];
  
  QFileInfo fileinfo(filename);
  
  QString absPath = fileinfo.absoluteFilePath();
  QString baseName = fileinfo.baseName();
  QString compBaseName = fileinfo.completeBaseName();
  QString fileName = fileinfo.fileName();
  QString suffix = fileinfo.suffix();
  QString compSuffix = fileinfo.completeSuffix();
  
  out << "Absolute file path: " << absPath << endl;
  out << "Base name: " << baseName << endl;
  out << "Complete base name: " << compBaseName << endl;
  out << "File name: " << fileName << endl;
  out << "Suffix: " << suffix << endl;
  out << "Whole suffix: " << compSuffix << endl;
}

In the example, we use several methods to print the file path and its parts of the given file name.

QFileInfo fileinfo(filename);

The file path is identified using the QFileInfo class.

QString absPath = fileinfo.absoluteFilePath();

The absoluteFilePath() method returns an absolute path including the file name.

QString baseName = fileinfo.baseName();

The baseName() method returns the base name—the name of the file without the path.

QString compBaseName = fileinfo.completeBaseName();

The completeBaseName() method returns the complete base name—all characters in the file up to (but not including) the last dot character.

QString fileName = fileinfo.fileName();

The fileName() method returns the file name, which is the base name and the extension.

QString suffix = fileinfo.suffix();

The suffix() method returns the file ending, which consists of all characters in the file after (but not including) the last dot character.

QString compSuffix = fileinfo.completeSuffix();

A file ending may consist of several parts; the completeSuffix() method returns all characters in the file after (but not including) the first dot character.

Output
$ ./file_path ~/Downloads/qt-everywhere-opensource-src-5.5.1.tar.gz 
Absolute file path: /home/janbodnar/Downloads/qt-everywhere-opensource-src-5.5.1.tar.gz
Base name: qt-everywhere-opensource-src-5
Complete base name: qt-everywhere-opensource-src-5.5.1.tar
File name: qt-everywhere-opensource-src-5.5.1.tar.gz
Suffix: gz
Whole suffix: 5.1.tar.gz

Permissions

Files in the filesystem have a system of protection. Files are given flags which determine who can access and modify them. The QFile::permissions() method returns an enumeration of OR-ed flags for the file in question.

permissions.cpp
#include <QTextStream>
#include <QFile>

int main(int argc, char *argv[]) {
    
  QTextStream out(stdout);

  if (argc != 2) {
      
      out << "Usage: permissions file" << endl;
      return 1;
  }
  
  QString filename = argv[1];
  
  QFile::Permissions ps = QFile::permissions(filename);
  
  QString fper;
  
  if (ps & QFile::ReadOwner) {
      fper.append('r');
  } else {
      fper.append('-');
  }
  
  if (ps & QFile::WriteOwner) {
      fper.append('w');
  } else {
      fper.append('-');
  }  
  
  if (ps & QFile::ExeOwner) {
      fper.append('x');
  } else {
      fper.append('-');
  }    
  
  if (ps & QFile::ReadGroup) {
      fper.append('r');
  } else {
      fper.append('-');
  }
  
  if (ps & QFile::WriteGroup) {
      fper.append('w');
  } else {
      fper.append('-');
  }  
  
  if (ps & QFile::ExeGroup) {
      fper.append('x');
  } else {
      fper.append('-');
  }    
  
  if (ps & QFile::ReadOther) {
      fper.append('r');
  } else {
      fper.append('-');
  }
  
  if (ps & QFile::WriteOther) {
      fper.append('w');
  } else {
      fper.append('-');
  }  
  
  if (ps & QFile::ExeOther) {
      fper.append('x');
  } else {
      fper.append('-');
  }      
  
  out << fper << endl;
}

The example produces a Unix-like list of permissions for the given file. There are tree kinds of possible users: owner, the group where the file belongs, and the rest of the users referred as others. The first three positions belong to the owner of the file, the next three positions to the file's group, and the last three characters belong to the others. There are four kinds of rights: reading (r), writing or modifying (w), executing (x), and no rights (-).

QFile::Permissions ps = QFile::permissions(filename);

With the QFile::permissions() method, we get the enumeration of permission flags.

QString fper;

This string is dynamically built based on the given permissions.

if (ps & QFile::ReadOwner) {
    fper.append('r');
} else {
    fper.append('-');
}

We use the & operator to determine whether the returned enumeration consists of the QFile::ReadOwner flag.

Output
$ ./permissions Makefile 
rw-rw-r--

The owner and the group of users where the file belongs have the right to read the file and modify it. Other users have the right to read the file. Since the file is not an executable, there are no rights to execute the file.

Listing directory contents

In the following example, we display the contents of the given directory.

list_dir.cpp
#include <QTextStream>
#include <QFileInfo>
#include <QDir>

int main(int argc, char *argv[]) {
    
  QTextStream out(stdout);

  if (argc != 2) {
      
      qWarning("Usage: list_dir directory");
      return 1;
  }
  
  QString directory = argv[1];
  
  QDir dir(directory);
  
  if (!dir.exists()) {
      qWarning("The directory does not exist");
      return 1;
  }  
  
  dir.setFilter(QDir::Files | QDir::AllDirs);
  dir.setSorting(QDir::Size | QDir::Reversed);

  QFileInfoList list = dir.entryInfoList();
  
  int max_size = 0;
  
  foreach (QFileInfo finfo, list) {
      
      QString name = finfo.fileName();
      int size = name.size();
      
      if (size > max_size) {
          
          max_size = size;
      }
  }
  
  int len = max_size + 2;
  
  out << QString("Filename").leftJustified(len).append("Bytes") << endl;
  
  for (int i = 0; i < list.size(); ++i) {
      
    QFileInfo fileInfo = list.at(i);
    QString str = fileInfo.fileName().leftJustified(len);
    str.append(QString("%1").arg(fileInfo.size()));
    out << str << endl;
  }
    
  return 0;
}

To list the contents of a directory, we use the QDir class and its entryInfoList() method. The list of the files is reversely sorted by its size and neatly lined up. There are two columns; the first column contains file names and the second column file sizes.

QDir dir(directory);

A QDir object with the given directory name is created.

dir.setFilter(QDir::Files | QDir::AllDirs);

The setFilter() method specifies the kind of files that should be returned by the entryInfoList() method.

dir.setSorting(QDir::Size | QDir::Reversed);

The setSorting() method specifies the sort order used by the entryInfoList() method.

QFileInfoList list = dir.entryInfoList();

The entryInfoList() method returns a list of QFileInfo objects for all the files and directories in the directory, filtered and ordered by the filtering and ordering methods. QFileInfoList is a synonym for QList<QFileInfo>.

foreach (QFileInfo finfo, list) {
    
    QString name = finfo.fileName();
    int size = name.size();
    
    if (size > max_size) {
        
        max_size = size;
    }
}

We go through the list and determine the maximum file name size. This information is needed to organize the output neatly.

int len = max_size + 2;

We give additional two spaces to the length of a column.

out << QString("Filename").leftJustified(len).append("Bytes") << endl;

Here we print the column names. The leftJustified() method returns a string of the given size, whose string is left justified and padded by the fill character (defaults to space) to its right.

for (int i = 0; i < list.size(); ++i) {
      
  QFileInfo fileInfo = list.at(i);
  QString str = fileInfo.fileName().leftJustified(len);
  str.append(QString("%1").arg(fileInfo.size()));
  out << str << endl;
}

We go through the list of files and print their names and sizes. The first column is left justified and padded with spaces as necessary; the second column is simply appended and the end of the line.

Output
$ ./list_dir .
Filename      Bytes
list_dir.pro  291
list_dir.cpp  1092
..            4096
.             4096
list_dir.o    10440
list_dir      19075
Makefile      28369

This is a sample output of the example.

In this chapter, we worked with files and directories.