File indexing completed on 2024-04-14 03:52:51

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                 const auto localPath = item.localPath();
0111                 if (!localPath.isNull()) {
0112                     startNextJob(QUrl::fromLocalFile(localPath));
0113                 } else {
0114                     startNextJob(item.targetUrl());
0115                 }
0116                 return; // we'll come back later, when this one's finished
0117             } else {
0118                 m_totalSize += item.size();
0119                 m_totalFiles++;
0120                 // qDebug() << "file -> " << m_totalSize;
0121             }
0122         } else {
0123             m_totalFiles++;
0124         }
0125     }
0126     // qDebug() << "finished";
0127     q->emitResult();
0128 }
0129 
0130 void DirectorySizeJobPrivate::startNextJob(const QUrl &url)
0131 {
0132     Q_Q(DirectorySizeJob);
0133     // qDebug() << url;
0134     KIO::ListJob *listJob = KIO::listRecursive(url, KIO::HideProgressInfo);
0135     listJob->addMetaData(QStringLiteral("details"), QString::number(KIO::StatBasic | KIO::StatResolveSymlink | KIO::StatInode));
0136     q->connect(listJob, &KIO::ListJob::entries, q, [this](KIO::Job *job, const KIO::UDSEntryList &list) {
0137         slotEntries(job, list);
0138     });
0139     q->addSubjob(listJob);
0140 }
0141 
0142 void DirectorySizeJobPrivate::slotEntries(KIO::Job *, const KIO::UDSEntryList &list)
0143 {
0144     KIO::UDSEntryList::ConstIterator it = list.begin();
0145     const KIO::UDSEntryList::ConstIterator end = list.end();
0146     for (; it != end; ++it) {
0147         const KIO::UDSEntry &entry = *it;
0148 
0149         const long device = entry.numberValue(KIO::UDSEntry::UDS_DEVICE_ID, 0);
0150         if (device && !entry.isLink()) {
0151             // Hard-link detection (#67939)
0152             const long inode = entry.numberValue(KIO::UDSEntry::UDS_INODE, 0);
0153             std::set<long> &visitedInodes = m_visitedInodes[device]; // find or insert
0154             const auto [it, isNewInode] = visitedInodes.insert(inode);
0155             if (!isNewInode) {
0156                 continue;
0157             }
0158         }
0159         const KIO::filesize_t size = entry.numberValue(KIO::UDSEntry::UDS_SIZE, 0);
0160         const QString name = entry.stringValue(KIO::UDSEntry::UDS_NAME);
0161         if (name == QLatin1Char('.')) {
0162             m_totalSize += size;
0163             // qDebug() << "'.': added" << size << "->" << m_totalSize;
0164         } else if (name != QLatin1String("..")) {
0165             if (!entry.isLink()) {
0166                 m_totalSize += size;
0167             }
0168             if (!entry.isDir()) {
0169                 m_totalFiles++;
0170             } else {
0171                 m_totalSubdirs++;
0172             }
0173             // qDebug() << name << ":" << size << "->" << m_totalSize;
0174         }
0175     }
0176 }
0177 
0178 void DirectorySizeJob::slotResult(KJob *job)
0179 {
0180     Q_D(DirectorySizeJob);
0181     // qDebug() << d->m_totalSize;
0182     removeSubjob(job);
0183     if (d->m_currentItem < d->m_lstItems.count()) {
0184         d->processNextItem();
0185     } else {
0186         if (job->error()) {
0187             setError(job->error());
0188             setErrorText(job->errorText());
0189         }
0190         emitResult();
0191     }
0192 }
0193 
0194 // static
0195 DirectorySizeJob *KIO::directorySize(const QUrl &directory)
0196 {
0197     return DirectorySizeJobPrivate::newJob(directory); // useless - but consistent with other jobs
0198 }
0199 
0200 // static
0201 DirectorySizeJob *KIO::directorySize(const KFileItemList &lstItems)
0202 {
0203     return DirectorySizeJobPrivate::newJob(lstItems);
0204 }
0205 
0206 #include "moc_directorysizejob.cpp"