File indexing completed on 2025-02-02 03:49:27
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org> 0004 SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-or-later 0007 */ 0008 0009 #include "trashsizecache.h" 0010 0011 #include "discspaceutil.h" 0012 #include "kiotrashdebug.h" 0013 0014 #include <QDateTime> 0015 #include <QDir> 0016 #include <QDirIterator> 0017 #include <QFile> 0018 #include <QSaveFile> 0019 #include <qplatformdefs.h> // QT_LSTAT, QT_STAT, QT_STATBUF 0020 0021 TrashSizeCache::TrashSizeCache(const QString &path) 0022 : mTrashSizeCachePath(path + QLatin1String("/directorysizes")) 0023 , mTrashPath(path) 0024 { 0025 // qCDebug(KIO_TRASH) << "CACHE:" << mTrashSizeCachePath; 0026 } 0027 0028 // Only the last part of the line: space, directory name, '\n' 0029 static QByteArray spaceAndDirectoryAndNewline(const QString &directoryName) 0030 { 0031 const QByteArray encodedDir = QFile::encodeName(directoryName).toPercentEncoding(); 0032 return ' ' + encodedDir + '\n'; 0033 } 0034 0035 void TrashSizeCache::add(const QString &directoryName, qint64 directorySize) 0036 { 0037 // qCDebug(KIO_TRASH) << directoryName << directorySize; 0038 const QByteArray spaceAndDirAndNewline = spaceAndDirectoryAndNewline(directoryName); 0039 QFile file(mTrashSizeCachePath); 0040 QSaveFile out(mTrashSizeCachePath); 0041 if (out.open(QIODevice::WriteOnly)) { 0042 if (file.open(QIODevice::ReadOnly)) { 0043 while (!file.atEnd()) { 0044 const QByteArray line = file.readLine(); 0045 if (line.endsWith(spaceAndDirAndNewline)) { 0046 // Already there! 0047 out.cancelWriting(); 0048 // qCDebug(KIO_TRASH) << "already there!"; 0049 return; 0050 } 0051 out.write(line); 0052 } 0053 } 0054 0055 const qint64 mtime = getTrashFileInfo(directoryName).lastModified().toMSecsSinceEpoch(); 0056 QByteArray newLine = QByteArray::number(directorySize) + ' ' + QByteArray::number(mtime) + spaceAndDirAndNewline; 0057 out.write(newLine); 0058 out.commit(); 0059 } 0060 // qCDebug(KIO_TRASH) << mTrashSizeCachePath << "exists:" << QFile::exists(mTrashSizeCachePath); 0061 } 0062 0063 void TrashSizeCache::remove(const QString &directoryName) 0064 { 0065 // qCDebug(KIO_TRASH) << directoryName; 0066 const QByteArray spaceAndDirAndNewline = spaceAndDirectoryAndNewline(directoryName); 0067 QFile file(mTrashSizeCachePath); 0068 QSaveFile out(mTrashSizeCachePath); 0069 if (file.open(QIODevice::ReadOnly) && out.open(QIODevice::WriteOnly)) { 0070 while (!file.atEnd()) { 0071 const QByteArray line = file.readLine(); 0072 if (line.endsWith(spaceAndDirAndNewline)) { 0073 // Found it -> skip it 0074 continue; 0075 } 0076 out.write(line); 0077 } 0078 } 0079 out.commit(); 0080 } 0081 0082 void TrashSizeCache::rename(const QString &oldDirectoryName, const QString &newDirectoryName) 0083 { 0084 const QByteArray spaceAndDirAndNewline = spaceAndDirectoryAndNewline(oldDirectoryName); 0085 QFile file(mTrashSizeCachePath); 0086 QSaveFile out(mTrashSizeCachePath); 0087 if (file.open(QIODevice::ReadOnly) && out.open(QIODevice::WriteOnly)) { 0088 while (!file.atEnd()) { 0089 QByteArray line = file.readLine(); 0090 if (line.endsWith(spaceAndDirAndNewline)) { 0091 // Found it -> rename it, keeping the size 0092 line = line.left(line.length() - spaceAndDirAndNewline.length()) + spaceAndDirectoryAndNewline(newDirectoryName); 0093 } 0094 out.write(line); 0095 } 0096 } 0097 out.commit(); 0098 } 0099 0100 void TrashSizeCache::clear() 0101 { 0102 QFile::remove(mTrashSizeCachePath); 0103 } 0104 0105 QFileInfo TrashSizeCache::getTrashFileInfo(const QString &fileName) 0106 { 0107 const QString fileInfoPath = mTrashPath + QLatin1String("/info/") + fileName + QLatin1String(".trashinfo"); 0108 Q_ASSERT(QFile::exists(fileInfoPath)); 0109 return QFileInfo(fileInfoPath); 0110 } 0111 0112 QHash<QByteArray, TrashSizeCache::SizeAndModTime> TrashSizeCache::readDirCache() 0113 { 0114 // First read the directorysizes cache into memory 0115 QFile file(mTrashSizeCachePath); 0116 QHash<QByteArray, SizeAndModTime> dirCache; 0117 if (file.open(QIODevice::ReadOnly)) { 0118 while (!file.atEnd()) { 0119 const QByteArray line = file.readLine(); 0120 const int firstSpace = line.indexOf(' '); 0121 const int secondSpace = line.indexOf(' ', firstSpace + 1); 0122 SizeAndModTime data; 0123 data.size = line.left(firstSpace).toLongLong(); 0124 // "012 4567 name\n" -> firstSpace=3, secondSpace=8, we want mid(4,4) 0125 data.mtime = line.mid(firstSpace + 1, secondSpace - firstSpace - 1).toLongLong(); 0126 const auto name = line.mid(secondSpace + 1, line.length() - secondSpace - 2); 0127 dirCache.insert(name, data); 0128 } 0129 } 0130 return dirCache; 0131 } 0132 0133 qint64 TrashSizeCache::calculateSize() 0134 { 0135 return scanFilesInTrash(ScanFilesInTrashOption::DonTcheckModificationTime).size; 0136 } 0137 0138 TrashSizeCache::SizeAndModTime TrashSizeCache::calculateSizeAndLatestModDate() 0139 { 0140 return scanFilesInTrash(ScanFilesInTrashOption::CheckModificationTime); 0141 } 0142 0143 TrashSizeCache::SizeAndModTime TrashSizeCache::scanFilesInTrash(ScanFilesInTrashOption checkDateTime) 0144 { 0145 const QHash<QByteArray, SizeAndModTime> dirCache = readDirCache(); 0146 0147 // Iterate over the actual trashed files. 0148 // Orphan items (no .fileinfo) still take space. 0149 QDirIterator it(mTrashPath + QLatin1String("/files/"), QDir::NoDotAndDotDot); 0150 qint64 sum = 0; 0151 qint64 max_mtime = 0; 0152 const auto checkMaxTime = [&max_mtime](const qint64 lastModTime) { 0153 if (lastModTime > max_mtime) { 0154 max_mtime = lastModTime; 0155 } 0156 }; 0157 const auto checkLastModTime = [this, checkMaxTime](const QString &fileName) { 0158 const auto trashFileInfo = getTrashFileInfo(fileName); 0159 if (!trashFileInfo.exists()) { 0160 return; 0161 } 0162 checkMaxTime(trashFileInfo.lastModified().toMSecsSinceEpoch()); 0163 }; 0164 while (it.hasNext()) { 0165 it.next(); 0166 const QString fileName = it.fileName(); 0167 const QFileInfo fileInfo = it.fileInfo(); 0168 if (fileInfo.isSymLink()) { 0169 // QFileInfo::size does not return the actual size of a symlink. #253776 0170 QT_STATBUF buff; 0171 if (QT_LSTAT(QFile::encodeName(fileInfo.absoluteFilePath()).constData(), &buff) == 0) { 0172 sum += static_cast<unsigned long long>(buff.st_size); 0173 if (checkDateTime == ScanFilesInTrashOption::CheckModificationTime) { 0174 checkLastModTime(fileName); 0175 } 0176 } 0177 } else if (fileInfo.isFile()) { 0178 sum += static_cast<unsigned long long>(fileInfo.size()); 0179 if (checkDateTime == ScanFilesInTrashOption::CheckModificationTime) { 0180 checkLastModTime(fileName); 0181 } 0182 } else { 0183 // directories 0184 bool usableCache = false; 0185 auto dirIt = dirCache.constFind(QFile::encodeName(fileName)); 0186 if (dirIt != dirCache.constEnd()) { 0187 const SizeAndModTime &data = *dirIt; 0188 const auto trashFileInfo = getTrashFileInfo(fileName); 0189 if (trashFileInfo.exists() && trashFileInfo.lastModified().toMSecsSinceEpoch() == data.mtime) { 0190 sum += data.size; 0191 usableCache = true; 0192 if (checkDateTime == ScanFilesInTrashOption::CheckModificationTime) { 0193 checkMaxTime(data.mtime); 0194 } 0195 } 0196 } 0197 if (!usableCache) { 0198 // directories with no cache data (or outdated) 0199 const qint64 size = DiscSpaceUtil::sizeOfPath(fileInfo.absoluteFilePath()); 0200 sum += size; 0201 if (checkDateTime == ScanFilesInTrashOption::CheckModificationTime) { 0202 // NOTE: this does not take into account the directory content modification date 0203 checkMaxTime(QFileInfo(fileInfo.absolutePath()).lastModified().toMSecsSinceEpoch()); 0204 } 0205 add(fileName, size); 0206 } 0207 } 0208 } 0209 return {sum, max_mtime}; 0210 }