在Qt里面,有一种Model/View框架,Model负责收集信息,View负责显示信息。QFileSystemModel可以读取文件大小,但是默认情况下不能读取文件夹大小。

  QFileSystemModel里面有一个size()函数,获取一个index对应的文件的大小。

1 qint64 QFileSystemModel::size(const QModelIndex &index) const
2 {
3     Q_D(const QFileSystemModel);
4     if (!index.isValid())
5         return 0;
6     return d->node(index)->size();
7 }

  这里的Q_D指针通过宏定义指向QFileSystemModel的私有类:QFileSystemPrivate。

1     #define Q_D(Class) Class##Private * const d = d_func()

     写一个类MyQFileSystemInfo继承QFileSystemInfo,重写它的size()函数,对具体文件直接返回文件大小,而对文件夹则递归加和计算每个子文件夹的大小以得到它的大小。

  可惜,仍然还是不能在view中实时显示文件夹的大小,看来view中使用的size并不是model的size()函数。github的代码链接:https://github.com/1171597779/big_file_inspector

  在QFileSystemModelPrivate里面也有一个size()函数。

 1 QString QFileSystemModelPrivate::size(const QModelIndex &index) const
 2 {
 3     if (!index.isValid())
 4         return QString();
 5     const QFileSystemNode *n = node(index);
 6     if (n->isDir()) {
 7 #ifdef Q_OS_MAC
 8         return QLatin1String("--");
 9 #else
10         return QLatin1String("");
11 #endif
12     // Windows   - ""
13     // OS X      - "--"
14     // Konqueror - "4 KB"
15     // Nautilus  - "9 items" (the number of children)
16     }
17     return size(n->size());
18 }

  上面函数的17行调用了重载size()函数:

 1 QString QFileSystemModelPrivate::size(qint64 bytes)
 2 {
 3     // According to the Si standard KB is 1000 bytes, KiB is 1024
 4     // but on windows sizes are calculated by dividing by 1024 so we do what they do.
 5     const qint64 kb = 1024;
 6     const qint64 mb = 1024 * kb;
 7     const qint64 gb = 1024 * mb;
 8     const qint64 tb = 1024 * gb;
 9     if (bytes >= tb)
10         return QFileSystemModel::tr("%1 TB").arg(QLocale().toString(qreal(bytes) / tb, 'f', 3));
11     if (bytes >= gb)
12         return QFileSystemModel::tr("%1 GB").arg(QLocale().toString(qreal(bytes) / gb, 'f', 2));
13     if (bytes >= mb)
14         return QFileSystemModel::tr("%1 MB").arg(QLocale().toString(qreal(bytes) / mb, 'f', 1));
15     if (bytes >= kb)
16         return QFileSystemModel::tr("%1 KB").arg(QLocale().toString(bytes / kb));
17     return QFileSystemModel::tr("%1 bytes").arg(QLocale().toString(bytes));
18 }

  QFileSystemModel的头文件里声明了私有类QFileSystemPrivate,转到这个私有类的头文件里面去,在这个类里定义了另外一个类QFileSystemNode。在这个类的构造函数中,出现一个QExtendedInformation*指针类型的info实参。这个实参在QFileSystemPrivate类里面是一个公有成员,文件或者文件夹的大小就是通过下面的QFileSystemNode的内联函数实现的。

inline qint64 size() const { if (info && !info->isDir()) return info->size(); return 0; }

  QExtendedinformation类中存在一个QFileInfo类的成员mFileInfo,并通过size()方法来根据具体情况获取文件大小:

 1     qint64 size() const {
 2         qint64 size = -1;
 3         if (type() == QExtendedInformation::Dir)
 4             size = 0;
 5         if (type() == QExtendedInformation::File)
 6             size = mFileInfo.size();
 7         if (!mFileInfo.exists() && !mFileInfo.isSymLink())
 8             size = -1;
 9         return size;
10     }

  而QFileInfo的size()方法如下:

 1 qint64 QFileInfo::size() const
 2 {
 3     Q_D(const QFileInfo);
 4     if (d->isDefaultConstructed)
 5         return 0;
 6     if (d->fileEngine == 0) {
 7         if (!d->cache_enabled || !d->metaData.hasFlags(QFileSystemMetaData::SizeAttribute))
 8             QFileSystemEngine::fillMetaData(d->fileEntry, d->metaData, QFileSystemMetaData::SizeAttribute);
 9         return d->metaData.size();
10     }
11     if (!d->getCachedFlag(QFileInfoPrivate::CachedSize)) {
12         d->setCachedFlag(QFileInfoPrivate::CachedSize);
13         d->fileSize = d->fileEngine->size();
14     }
15     return d->fileSize;
16 }

  问题来了,要计算一个文件夹的大小,需要很长的时间,不能用内联函数,因此需要重载一下,放弃内联函数的写法。

  总结一下,QFileSystemModel里面的size()调用QFileSystemModelPrivate的size(),而这个函数里会使用的QFileSystemNode的内联函数size(),这里面又转到QExtendedinformation里面去,最后是QFileInfo的size()方法。通过重写QFileSystemModel的size()函数并不能实现treeview中文件夹大小的实时显示,需要找出treeview中什么时候调用size()函数。

  从QFileSystemModelPrivate类的init()函数可以看到一些端倪:

 1 void QFileSystemModelPrivate::init()
 2 {
 3     Q_Q(QFileSystemModel);
 4     qRegisterMetaType<QVector<QPair<QString,QFileInfo> > >();
 5 #ifndef QT_NO_FILESYSTEMWATCHER
 6     q->connect(&fileInfoGatherer, SIGNAL(newListOfFiles(QString,QStringList)),
 7                q, SLOT(_q_directoryChanged(QString,QStringList)));
 8     q->connect(&fileInfoGatherer, SIGNAL(updates(QString,QVector<QPair<QString,QFileInfo> >)),
 9             q, SLOT(_q_fileSystemChanged(QString,QVector<QPair<QString,QFileInfo> >)));
10     q->connect(&fileInfoGatherer, SIGNAL(nameResolved(QString,QString)),
11             q, SLOT(_q_resolvedName(QString,QString)));
12     q->connect(&fileInfoGatherer, SIGNAL(directoryLoaded(QString)),
13                q, SIGNAL(directoryLoaded(QString)));
14 #endif // !QT_NO_FILESYSTEMWATCHER
15     q->connect(&delayedSortTimer, SIGNAL(timeout()), q, SLOT(_q_performDelayedSort()), Qt::QueuedConnection);
16 
17     roleNames.insertMulti(QFileSystemModel::FileIconRole, QByteArrayLiteral("fileIcon")); // == Qt::decoration
18     roleNames.insert(QFileSystemModel::FilePathRole, QByteArrayLiteral("filePath"));
19     roleNames.insert(QFileSystemModel::FileNameRole, QByteArrayLiteral("fileName"));
20     roleNames.insert(QFileSystemModel::FilePermissions, QByteArrayLiteral("filePermissions"));
21 }

  通过看到QFileSystemModelPrivate里面的方法addNote()才看到一些希望:

 1 QFileSystemModelPrivate::QFileSystemNode* QFileSystemModelPrivate::addNode(QFileSystemNode *parentNode, const QString &fileName, const QFileInfo& info)
 2 {
 3     // In the common case, itemLocation == count() so check there first
 4     QFileSystemModelPrivate::QFileSystemNode *node = new QFileSystemModelPrivate::QFileSystemNode(fileName, parentNode);
 5 #ifndef QT_NO_FILESYSTEMWATCHER
 6     node->populate(info);
 7 #else
 8     Q_UNUSED(info)
 9 #endif
10 #if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
11     //The parentNode is "" so we are listing the drives
12     if (parentNode->fileName.isEmpty()) {
13         wchar_t name[MAX_PATH + 1];
14         //GetVolumeInformation requires to add trailing backslash
15         const QString nodeName = fileName + QLatin1String("\\");
16         BOOL success = ::GetVolumeInformation((wchar_t *)(nodeName.utf16()),
17                 name, MAX_PATH + 1, NULL, 0, NULL, NULL, 0);
18         if (success && name[0])
19             node->volumeName = QString::fromWCharArray(name);
20     }
21 #endif
22     Q_ASSERT(!parentNode->children.contains(fileName));
23     parentNode->children.insert(fileName, node);
24     return node;
25 }

   上面的node->populate(),这里面就提取了文件信息:

1         void populate(const QExtendedInformation &fileInfo) {
2             if (!info)
3                 info = new QExtendedInformation(fileInfo.fileInfo());
4             (*info) = fileInfo;
5         }