File indexing completed on 2024-04-28 15:26:16

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2000, 2006 David Faure <faure@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "directorysizejob.h"
0009 #include "global.h"
0010 #include "listjob.h"
0011 #include <QDebug>
0012 #include <QTimer>
0013 #include <kio/jobuidelegatefactory.h>
0014 
0015 #include "job_p.h"
0016 
0017 #include <set>
0018 
0019 namespace KIO
0020 {
0021 class DirectorySizeJobPrivate : public KIO::JobPrivate
0022 {
0023 public:
0024     DirectorySizeJobPrivate()
0025         : m_totalSize(0L)
0026         , m_totalFiles(0L)
0027         , m_totalSubdirs(0L)
0028         , m_currentItem(0)
0029     {
0030     }
0031     explicit DirectorySizeJobPrivate(const KFileItemList &lstItems)
0032         : m_totalSize(0L)
0033         , m_totalFiles(0L)
0034         , m_totalSubdirs(0L)
0035         , m_lstItems(lstItems)
0036         , m_currentItem(0)
0037     {
0038     }
0039     KIO::filesize_t m_totalSize;
0040     KIO::filesize_t m_totalFiles;
0041     KIO::filesize_t m_totalSubdirs;
0042     KFileItemList m_lstItems;
0043     int m_currentItem;
0044     QHash<long, std::set<long>> m_visitedInodes; // device -> set of inodes
0045 
0046     void startNextJob(const QUrl &url);
0047     void slotEntries(KIO::Job *, const KIO::UDSEntryList &);
0048     void processNextItem();
0049 
0050     Q_DECLARE_PUBLIC(DirectorySizeJob)
0051 
0052     static inline DirectorySizeJob *newJob(const QUrl &directory)
0053     {
0054         DirectorySizeJobPrivate *d = new DirectorySizeJobPrivate;
0055         DirectorySizeJob *job = new DirectorySizeJob(*d);
0056         job->setUiDelegate(KIO::createDefaultJobUiDelegate());
0057         d->startNextJob(directory);
0058         return job;
0059     }
0060 
0061     static inline DirectorySizeJob *newJob(const KFileItemList &lstItems)
0062     {
0063         DirectorySizeJobPrivate *d = new DirectorySizeJobPrivate(lstItems);
0064         DirectorySizeJob *job = new DirectorySizeJob(*d);
0065         job->setUiDelegate(KIO::createDefaultJobUiDelegate());
0066         QTimer::singleShot(0, job, [d]() {
0067             d->processNextItem();
0068         });
0069         return job;
0070     }
0071 };
0072 
0073 } // namespace KIO
0074 
0075 using namespace KIO;
0076 
0077 DirectorySizeJob::DirectorySizeJob(DirectorySizeJobPrivate &dd)
0078     : KIO::Job(dd)
0079 {
0080 }
0081 
0082 DirectorySizeJob::~DirectorySizeJob()
0083 {
0084 }
0085 
0086 KIO::filesize_t DirectorySizeJob::totalSize() const
0087 {
0088     return d_func()->m_totalSize;
0089 }
0090 
0091 KIO::filesize_t DirectorySizeJob::totalFiles() const
0092 {
0093     return d_func()->m_totalFiles;
0094 }
0095 
0096 KIO::filesize_t DirectorySizeJob::totalSubdirs() const
0097 {
0098     return d_func()->m_totalSubdirs;
0099 }
0100 
0101 void DirectorySizeJobPrivate::processNextItem()
0102 {
0103     Q_Q(DirectorySizeJob);
0104     while (m_currentItem < m_lstItems.count()) {
0105         const KFileItem item = m_lstItems[m_currentItem++];
0106         // qDebug() << item;
0107         if (!item.isLink()) {
0108             if (item.isDir()) {
0109                 // qDebug() << "dir -> listing";
0110                 startNextJob(item.targetUrl());
0111                 return; // we'll come back later, when this one's finished
0112             } else {
0113                 m_totalSize += item.size();
0114                 m_totalFiles++;
0115                 // qDebug() << "file -> " << m_totalSize;
0116             }
0117         } else {
0118             m_totalFiles++;
0119         }
0120     }
0121     // qDebug() << "finished";
0122     q->emitResult();
0123 }
0124 
0125 void DirectorySizeJobPrivate::startNextJob(const QUrl &url)
0126 {
0127     Q_Q(DirectorySizeJob);
0128     // qDebug() << url;
0129     KIO::ListJob *listJob = KIO::listRecursive(url, KIO::HideProgressInfo);
0130 #if KIOCORE_BUILD_DEPRECATED_SINCE(5, 69)
0131     // TODO KF6: remove legacy details code path
0132     listJob->addMetaData(QStringLiteral("details"), QStringLiteral("3"));
0133 #endif
0134     listJob->addMetaData(QStringLiteral("statDetails"), QString::number(KIO::StatBasic | KIO::StatResolveSymlink | KIO::StatInode));
0135     q->connect(listJob, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
0136         slotEntries(job, list);
0137     });
0138     q->addSubjob(listJob);
0139 }
0140 
0141 void DirectorySizeJobPrivate::slotEntries(KIO::Job *, const KIO::UDSEntryList &list)
0142 {
0143     KIO::UDSEntryList::ConstIterator it = list.begin();
0144     const KIO::UDSEntryList::ConstIterator end = list.end();
0145     for (; it != end; ++it) {
0146         const KIO::UDSEntry &entry = *it;
0147 
0148         const long device = entry.numberValue(KIO::UDSEntry::UDS_DEVICE_ID, 0);
0149         if (device && !entry.isLink()) {
0150             // Hard-link detection (#67939)
0151             const long inode = entry.numberValue(KIO::UDSEntry::UDS_INODE, 0);
0152             std::set<long> &visitedInodes = m_visitedInodes[device]; // find or insert
0153             const auto [it, isNewInode] = visitedInodes.insert(inode);
0154             if (!isNewInode) {
0155                 continue;
0156             }
0157         }
0158         const KIO::filesize_t size = entry.numberValue(KIO::UDSEntry::UDS_SIZE, 0);
0159         const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0160         if (name == QLatin1Char('.')) {
0161             m_totalSize += size;
0162             // qDebug() << "'.': added" << size << "->" << m_totalSize;
0163         } else if (name != QLatin1String("..")) {
0164             if (!entry.isLink()) {
0165                 m_totalSize += size;
0166             }
0167             if (!entry.isDir()) {
0168                 m_totalFiles++;
0169             } else {
0170                 m_totalSubdirs++;
0171             }
0172             // qDebug() << name << ":" << size << "->" << m_totalSize;
0173         }
0174     }
0175 }
0176 
0177 void DirectorySizeJob::slotResult(KJob *job)
0178 {
0179     Q_D(DirectorySizeJob);
0180     // qDebug() << d->m_totalSize;
0181     removeSubjob(job);
0182     if (d->m_currentItem < d->m_lstItems.count()) {
0183         d->processNextItem();
0184     } else {
0185         if (job->error()) {
0186             setError(job->error());
0187             setErrorText(job->errorText());
0188         }
0189         emitResult();
0190     }
0191 }
0192 
0193 // static
0194 DirectorySizeJob *KIO::directorySize(const QUrl &directory)
0195 {
0196     return DirectorySizeJobPrivate::newJob(directory); // useless - but consistent with other jobs
0197 }
0198 
0199 // static
0200 DirectorySizeJob *KIO::directorySize(const KFileItemList &lstItems)
0201 {
0202     return DirectorySizeJobPrivate::newJob(lstItems);
0203 }
0204 
0205 #include "moc_directorysizejob.cpp"