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 | |
19 | namespace KIO |
20 | { |
21 | class DirectorySizeJobPrivate : public KIO::JobPrivate |
22 | { |
23 | public: |
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 | |
75 | using namespace KIO; |
76 | |
77 | DirectorySizeJob::DirectorySizeJob(DirectorySizeJobPrivate &dd) |
78 | : KIO::Job(dd) |
79 | { |
80 | } |
81 | |
82 | DirectorySizeJob::~DirectorySizeJob() |
83 | { |
84 | } |
85 | |
86 | KIO::filesize_t DirectorySizeJob::totalSize() const |
87 | { |
88 | return d_func()->m_totalSize; |
89 | } |
90 | |
91 | KIO::filesize_t DirectorySizeJob::totalFiles() const |
92 | { |
93 | return d_func()->m_totalFiles; |
94 | } |
95 | |
96 | KIO::filesize_t DirectorySizeJob::totalSubdirs() const |
97 | { |
98 | return d_func()->m_totalSubdirs; |
99 | } |
100 | |
101 | void 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 | |
130 | void 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 | |
142 | void 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 | |
178 | void 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 |
195 | DirectorySizeJob *KIO::directorySize(const QUrl &directory) |
196 | { |
197 | return DirectorySizeJobPrivate::newJob(directory); // useless - but consistent with other jobs |
198 | } |
199 | |
200 | // static |
201 | DirectorySizeJob *KIO::directorySize(const KFileItemList &lstItems) |
202 | { |
203 | return DirectorySizeJobPrivate::newJob(lstItems); |
204 | } |
205 | |
206 | #include "moc_directorysizejob.cpp" |
207 | |