1 /*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2009 Tobias Koenig <tokoe@kde.org>
4 SPDX-FileCopyrightText: 2014 David Faure <faure@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7 */
8
9 #include "trashsizecache.h"
10
11 #include "discspaceutil.h"
12 #include "kiotrashdebug.h"
13
14 #include <QDateTime>
15 #include <QDir>
16 #include <QDirIterator>
17 #include <QFile>
18 #include <QSaveFile>
19 #include <qplatformdefs.h> // QT_LSTAT, QT_STAT, QT_STATBUF
20
TrashSizeCache(const QString & path)21 TrashSizeCache::TrashSizeCache(const QString &path)
22 : mTrashSizeCachePath(path + QLatin1String("/directorysizes"))
23 , mTrashPath(path)
24 {
25 // qCDebug(KIO_TRASH) << "CACHE:" << mTrashSizeCachePath;
26 }
27
28 // Only the last part of the line: space, directory name, '\n'
spaceAndDirectoryAndNewline(const QString & directoryName)29 static QByteArray spaceAndDirectoryAndNewline(const QString &directoryName)
30 {
31 const QByteArray encodedDir = QFile::encodeName(directoryName).toPercentEncoding();
32 return ' ' + encodedDir + '\n';
33 }
34
add(const QString & directoryName,qint64 directorySize)35 void TrashSizeCache::add(const QString &directoryName, qint64 directorySize)
36 {
37 // qCDebug(KIO_TRASH) << directoryName << directorySize;
38 const QByteArray spaceAndDirAndNewline = spaceAndDirectoryAndNewline(directoryName);
39 QFile file(mTrashSizeCachePath);
40 QSaveFile out(mTrashSizeCachePath);
41 if (out.open(QIODevice::WriteOnly)) {
42 if (file.open(QIODevice::ReadOnly)) {
43 while (!file.atEnd()) {
44 const QByteArray line = file.readLine();
45 if (line.endsWith(spaceAndDirAndNewline)) {
46 // Already there!
47 out.cancelWriting();
48 // qCDebug(KIO_TRASH) << "already there!";
49 return;
50 }
51 out.write(line);
52 }
53 }
54
55 QDateTime mtime = getTrashFileInfo(directoryName).lastModified();
56 QByteArray newLine = QByteArray::number(directorySize) + ' ' + QByteArray::number(mtime.toMSecsSinceEpoch()) + spaceAndDirAndNewline;
57 out.write(newLine);
58 out.commit();
59 }
60 // qCDebug(KIO_TRASH) << mTrashSizeCachePath << "exists:" << QFile::exists(mTrashSizeCachePath);
61 }
62
remove(const QString & directoryName)63 void TrashSizeCache::remove(const QString &directoryName)
64 {
65 // qCDebug(KIO_TRASH) << directoryName;
66 const QByteArray spaceAndDirAndNewline = spaceAndDirectoryAndNewline(directoryName);
67 QFile file(mTrashSizeCachePath);
68 QSaveFile out(mTrashSizeCachePath);
69 if (file.open(QIODevice::ReadOnly) && out.open(QIODevice::WriteOnly)) {
70 while (!file.atEnd()) {
71 const QByteArray line = file.readLine();
72 if (line.endsWith(spaceAndDirAndNewline)) {
73 // Found it -> skip it
74 continue;
75 }
76 out.write(line);
77 }
78 }
79 out.commit();
80 }
81
rename(const QString & oldDirectoryName,const QString & newDirectoryName)82 void TrashSizeCache::rename(const QString &oldDirectoryName, const QString &newDirectoryName)
83 {
84 const QByteArray spaceAndDirAndNewline = spaceAndDirectoryAndNewline(oldDirectoryName);
85 QFile file(mTrashSizeCachePath);
86 QSaveFile out(mTrashSizeCachePath);
87 if (file.open(QIODevice::ReadOnly) && out.open(QIODevice::WriteOnly)) {
88 while (!file.atEnd()) {
89 QByteArray line = file.readLine();
90 if (line.endsWith(spaceAndDirAndNewline)) {
91 // Found it -> rename it, keeping the size
92 line = line.left(line.length() - spaceAndDirAndNewline.length()) + spaceAndDirectoryAndNewline(newDirectoryName);
93 }
94 out.write(line);
95 }
96 }
97 out.commit();
98 }
99
clear()100 void TrashSizeCache::clear()
101 {
102 QFile::remove(mTrashSizeCachePath);
103 }
104
calculateSize()105 qint64 TrashSizeCache::calculateSize()
106 {
107 return this->calculateSizeAndLatestModDate().size;
108 }
109
getTrashFileInfo(const QString & fileName)110 QFileInfo TrashSizeCache::getTrashFileInfo(const QString &fileName)
111 {
112 const QString fileInfoPath = mTrashPath + QLatin1String("/info/") + fileName + QLatin1String(".trashinfo");
113 return QFileInfo(fileInfoPath);
114 }
115
calculateSizeAndLatestModDate()116 TrashSizeCache::SizeAndModTime TrashSizeCache::calculateSizeAndLatestModDate()
117 {
118 // First read the directorysizes cache into memory
119 QFile file(mTrashSizeCachePath);
120 typedef QHash<QByteArray, SizeAndModTime> DirCacheHash;
121 DirCacheHash dirCache;
122 if (file.open(QIODevice::ReadOnly)) {
123 while (!file.atEnd()) {
124 const QByteArray line = file.readLine();
125 const int firstSpace = line.indexOf(' ');
126 const int secondSpace = line.indexOf(' ', firstSpace + 1);
127 SizeAndModTime data;
128 data.size = line.left(firstSpace).toLongLong();
129 // "012 4567 name" -> firstSpace=3, secondSpace=8, we want mid(4,4)
130 data.mtime = line.mid(firstSpace + 1, secondSpace - firstSpace - 1).toLongLong();
131 dirCache.insert(line.mid(secondSpace + 1), data);
132 }
133 }
134 // Iterate over the actual trashed files.
135 // Orphan items (no .fileinfo) still take space.
136 QDirIterator it(mTrashPath + QLatin1String("/files/"), QDirIterator::NoIteratorFlags);
137
138 qint64 sum = 0;
139 qint64 max_mtime = 0;
140 const auto checkMaxTime = [&max_mtime](const qint64 lastModTime) {
141 if (lastModTime > max_mtime) {
142 max_mtime = lastModTime;
143 }
144 };
145 const auto checkLastModTime = [this, checkMaxTime](const QString &fileName) {
146 const auto trashFileInfo = getTrashFileInfo(fileName);
147 if (!trashFileInfo.exists()) {
148 return;
149 }
150 checkMaxTime(trashFileInfo.lastModified().toMSecsSinceEpoch());
151 };
152 while (it.hasNext()) {
153 const QFileInfo fileInfo = it.next();
154 const QString fileName = fileInfo.fileName();
155 if (fileName == QLatin1Char('.') || fileName == QLatin1String("..")) {
156 continue;
157 }
158 if (fileInfo.isSymLink()) {
159 // QFileInfo::size does not return the actual size of a symlink. #253776
160 QT_STATBUF buff;
161 if (QT_LSTAT(QFile::encodeName(fileInfo.absoluteFilePath()).constData(), &buff) == 0) {
162 sum += static_cast<unsigned long long>(buff.st_size);
163 checkLastModTime(fileName);
164 }
165 } else if (fileInfo.isFile()) {
166 sum += static_cast<unsigned long long>(fileInfo.size());
167 checkLastModTime(fileName);
168 } else {
169 // directories
170 bool usableCache = false;
171 auto it = dirCache.constFind(QFile::encodeName(fileName));
172 if (it != dirCache.constEnd()) {
173 const SizeAndModTime &data = *it;
174 const auto trashFileInfo = getTrashFileInfo(fileName);
175 if (trashFileInfo.exists() && trashFileInfo.lastModified().toMSecsSinceEpoch() == data.mtime) {
176 sum += data.size;
177 usableCache = true;
178 checkMaxTime(data.mtime);
179 }
180 }
181 if (!usableCache) {
182 // directories with no cache data (or outdated)
183 const qint64 size = DiscSpaceUtil::sizeOfPath(fileInfo.absoluteFilePath());
184 sum += size;
185 // NOTE: this does not take into account the directory content modification date
186 checkMaxTime(QFileInfo(fileInfo.absolutePath()).lastModified().toMSecsSinceEpoch());
187 add(fileName, size);
188 }
189 }
190 }
191 return {sum, max_mtime};
192 }
193