1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000, 2006 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "directorysizejob.h"
9#include "global.h"
10#include "listjob.h"
11#include <QDebug>
12#include <QTimer>
13#include <kio/jobuidelegatefactory.h>
14
15#include "job_p.h"
16
17#include <set>
18
19namespace KIO
20{
21class DirectorySizeJobPrivate : public KIO::JobPrivate
22{
23public:
24 DirectorySizeJobPrivate()
25 : m_totalSize(0L)
26 , m_totalFiles(0L)
27 , m_totalSubdirs(0L)
28 , m_currentItem(0)
29 {
30 }
31 explicit DirectorySizeJobPrivate(const KFileItemList &lstItems)
32 : m_totalSize(0L)
33 , m_totalFiles(0L)
34 , m_totalSubdirs(0L)
35 , m_lstItems(lstItems)
36 , m_currentItem(0)
37 {
38 }
39 KIO::filesize_t m_totalSize;
40 KIO::filesize_t m_totalFiles;
41 KIO::filesize_t m_totalSubdirs;
42 KFileItemList m_lstItems;
43 int m_currentItem;
44 QHash<long, std::set<long>> m_visitedInodes; // device -> set of inodes
45
46 void startNextJob(const QUrl &url);
47 void slotEntries(KIO::Job *, const KIO::UDSEntryList &);
48 void processNextItem();
49
50 Q_DECLARE_PUBLIC(DirectorySizeJob)
51
52 static inline DirectorySizeJob *newJob(const QUrl &directory)
53 {
54 DirectorySizeJobPrivate *d = new DirectorySizeJobPrivate;
55 DirectorySizeJob *job = new DirectorySizeJob(*d);
56 job->setUiDelegate(KIO::createDefaultJobUiDelegate());
57 d->startNextJob(url: directory);
58 return job;
59 }
60
61 static inline DirectorySizeJob *newJob(const KFileItemList &lstItems)
62 {
63 DirectorySizeJobPrivate *d = new DirectorySizeJobPrivate(lstItems);
64 DirectorySizeJob *job = new DirectorySizeJob(*d);
65 job->setUiDelegate(KIO::createDefaultJobUiDelegate());
66 QTimer::singleShot(interval: 0, receiver: job, slot: [d]() {
67 d->processNextItem();
68 });
69 return job;
70 }
71};
72
73} // namespace KIO
74
75using namespace KIO;
76
77DirectorySizeJob::DirectorySizeJob(DirectorySizeJobPrivate &dd)
78 : KIO::Job(dd)
79{
80}
81
82DirectorySizeJob::~DirectorySizeJob()
83{
84}
85
86KIO::filesize_t DirectorySizeJob::totalSize() const
87{
88 return d_func()->m_totalSize;
89}
90
91KIO::filesize_t DirectorySizeJob::totalFiles() const
92{
93 return d_func()->m_totalFiles;
94}
95
96KIO::filesize_t DirectorySizeJob::totalSubdirs() const
97{
98 return d_func()->m_totalSubdirs;
99}
100
101void DirectorySizeJobPrivate::processNextItem()
102{
103 Q_Q(DirectorySizeJob);
104 while (m_currentItem < m_lstItems.count()) {
105 const KFileItem item = m_lstItems[m_currentItem++];
106 // qDebug() << item;
107 if (!item.isLink()) {
108 if (item.isDir()) {
109 // qDebug() << "dir -> listing";
110 const auto localPath = item.localPath();
111 if (!localPath.isNull()) {
112 startNextJob(url: QUrl::fromLocalFile(localfile: localPath));
113 } else {
114 startNextJob(url: item.targetUrl());
115 }
116 return; // we'll come back later, when this one's finished
117 } else {
118 m_totalSize += item.size();
119 m_totalFiles++;
120 // qDebug() << "file -> " << m_totalSize;
121 }
122 } else {
123 m_totalFiles++;
124 }
125 }
126 // qDebug() << "finished";
127 q->emitResult();
128}
129
130void DirectorySizeJobPrivate::startNextJob(const QUrl &url)
131{
132 Q_Q(DirectorySizeJob);
133 // qDebug() << url;
134 KIO::ListJob *listJob = KIO::listRecursive(url, flags: KIO::HideProgressInfo);
135 listJob->addMetaData(QStringLiteral("details"), value: QString::number(KIO::StatBasic | KIO::StatResolveSymlink | KIO::StatInode));
136 q->connect(sender: listJob, signal: &KIO::ListJob::entries, context: q, slot: [this](KIO::Job *job, const KIO::UDSEntryList &list) {
137 slotEntries(job, list);
138 });
139 q->addSubjob(job: listJob);
140}
141
142void DirectorySizeJobPrivate::slotEntries(KIO::Job *, const KIO::UDSEntryList &list)
143{
144 KIO::UDSEntryList::ConstIterator it = list.begin();
145 const KIO::UDSEntryList::ConstIterator end = list.end();
146 for (; it != end; ++it) {
147 const KIO::UDSEntry &entry = *it;
148
149 const long device = entry.numberValue(field: KIO::UDSEntry::UDS_DEVICE_ID, defaultValue: 0);
150 if (device && !entry.isLink()) {
151 // Hard-link detection (#67939)
152 const long inode = entry.numberValue(field: KIO::UDSEntry::UDS_INODE, defaultValue: 0);
153 std::set<long> &visitedInodes = m_visitedInodes[device]; // find or insert
154 const auto [it, isNewInode] = visitedInodes.insert(x: inode);
155 if (!isNewInode) {
156 continue;
157 }
158 }
159 const KIO::filesize_t size = entry.numberValue(field: KIO::UDSEntry::UDS_SIZE, defaultValue: 0);
160 const QString name = entry.stringValue(field: KIO::UDSEntry::UDS_NAME);
161 if (name == QLatin1Char('.')) {
162 m_totalSize += size;
163 // qDebug() << "'.': added" << size << "->" << m_totalSize;
164 } else if (name != QLatin1String("..")) {
165 if (!entry.isLink()) {
166 m_totalSize += size;
167 }
168 if (!entry.isDir()) {
169 m_totalFiles++;
170 } else {
171 m_totalSubdirs++;
172 }
173 // qDebug() << name << ":" << size << "->" << m_totalSize;
174 }
175 }
176}
177
178void DirectorySizeJob::slotResult(KJob *job)
179{
180 Q_D(DirectorySizeJob);
181 // qDebug() << d->m_totalSize;
182 removeSubjob(job);
183 if (d->m_currentItem < d->m_lstItems.count()) {
184 d->processNextItem();
185 } else {
186 if (job->error()) {
187 setError(job->error());
188 setErrorText(job->errorText());
189 }
190 emitResult();
191 }
192}
193
194// static
195DirectorySizeJob *KIO::directorySize(const QUrl &directory)
196{
197 return DirectorySizeJobPrivate::newJob(directory); // useless - but consistent with other jobs
198}
199
200// static
201DirectorySizeJob *KIO::directorySize(const KFileItemList &lstItems)
202{
203 return DirectorySizeJobPrivate::newJob(lstItems);
204}
205
206#include "moc_directorysizejob.cpp"
207

source code of kio/src/core/directorysizejob.cpp