File indexing completed on 2024-04-21 05:48:32
0001 /*********************************************************************** 0002 * SPDX-FileCopyrightText: 2003-2004 Max Howell <max.howell@methylblue.com> 0003 * SPDX-FileCopyrightText: 2008-2009 Martin Sandsmark <martin.sandsmark@kde.org> 0004 * SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0007 ***********************************************************************/ 0008 0009 #include "scan.h" 0010 0011 #include "fileTree.h" 0012 #include "filelight_debug.h" 0013 #include "remoteLister.h" 0014 0015 #include <QCursor> 0016 #include <QDir> 0017 #include <QGuiApplication> 0018 #include <QStringBuilder> 0019 0020 namespace Filelight 0021 { 0022 0023 ScanManager::ScanManager(QObject *parent) 0024 : QObject(parent) 0025 , m_abort(false) 0026 , m_files(0) 0027 , m_thread(nullptr) 0028 { 0029 connect(this, &ScanManager::branchCacheHit, this, &ScanManager::foundCached, Qt::QueuedConnection); 0030 // Bit aggressive this. Completed is emitted per folder I think. 0031 connect(this, &ScanManager::completed, this, &ScanManager::runningChanged, Qt::QueuedConnection); 0032 } 0033 0034 ScanManager::~ScanManager() 0035 { 0036 if (m_thread) { 0037 qCDebug(FILELIGHT_LOG) << "Attempting to abort scan operation..."; 0038 m_abort = true; 0039 m_thread->wait(); 0040 } 0041 0042 // RemoteListers are QObjects and get automatically deleted 0043 } 0044 0045 bool ScanManager::running() const 0046 { 0047 return (m_thread && m_thread->isRunning()) || (m_remoteLister && !m_remoteLister->isFinished()); 0048 } 0049 0050 bool ScanManager::start(const QUrl &url) 0051 { 0052 QMutexLocker locker(&m_mutex); // The m_mutex gets released once locker is destroyed (goes out of scope). 0053 0054 // Refresh mounts on each scan in case some have been mounted or unmounted, etc. 0055 Filelight::LocalLister::readMounts(); 0056 0057 // url is guaranteed clean and safe 0058 0059 qCDebug(FILELIGHT_LOG) << "Scan requested for: " << url; 0060 0061 if (running()) { 0062 qCWarning(FILELIGHT_LOG) << "Tried to launch two concurrent scans, aborting old one..."; 0063 abort(); 0064 } 0065 0066 m_files = 0; 0067 m_totalSize = 0; 0068 m_abort = false; 0069 0070 if (!url.isLocalFile()) { 0071 QGuiApplication::changeOverrideCursor(Qt::BusyCursor); 0072 // will start listing straight away 0073 m_remoteLister = std::make_unique<Filelight::RemoteLister>(url, this); 0074 connect(m_remoteLister.get(), &Filelight::RemoteLister::branchCompleted, this, &ScanManager::cacheTree, Qt::QueuedConnection); 0075 auto updateRunning = [this] { 0076 if (m_remoteLister && m_remoteLister->isFinished()) { 0077 m_remoteLister = nullptr; 0078 Q_EMIT runningChanged(); 0079 } 0080 }; 0081 connect(m_remoteLister.get(), &Filelight::RemoteLister::completed, this, updateRunning); 0082 connect(m_remoteLister.get(), &Filelight::RemoteLister::canceled, this, updateRunning); 0083 m_remoteLister->openUrl(url); 0084 Q_EMIT runningChanged(); 0085 return true; 0086 } 0087 0088 QString path = url.toLocalFile(); 0089 0090 // Cross-platform consideration: we get the path from a URL and in there the 0091 // separator is always the portable slash, as such toLocalFile will also get us a slash 0092 // separator, not the native one. i.e. on windows this is C:forwardslash not C:backslash 0093 // do not use QDir::separator! 0094 // https://bugs.kde.org/show_bug.cgi?id=450863 0095 if (!path.endsWith(QLatin1Char('/'))) { 0096 path += QLatin1Char('/'); 0097 } 0098 0099 auto *trees = new QList<std::shared_ptr<Folder>>; 0100 0101 /* CHECK CACHE 0102 * user wants: /usr/local/ 0103 * cached: /usr/ 0104 * 0105 * user wants: /usr/ 0106 * cached: /usr/local/, /usr/include/ 0107 */ 0108 0109 QMutableListIterator<std::shared_ptr<Folder>> it(m_cache); 0110 while (it.hasNext()) { 0111 auto folder = it.next(); 0112 const QString cachePath = folder->decodedName(); 0113 0114 if (path.startsWith(cachePath)) { // then whole tree already scanned 0115 // find a pointer to the requested branch 0116 0117 qCDebug(FILELIGHT_LOG) << "Cache-(a)hit: " << cachePath; 0118 QList<QStringView> split = QStringView(path).mid(cachePath.length()).split(QLatin1Char('/')); 0119 std::shared_ptr<Folder> d = folder; 0120 0121 while (!split.isEmpty() && d != nullptr) { // if NULL we have got lost so abort!! 0122 if (split.first().isEmpty()) { // found the dir 0123 break; 0124 } 0125 QString s = split.first() % QLatin1Char('/'); // % is the string concatenation operator for QStringBuilder 0126 0127 QListIterator<std::shared_ptr<File>> it(d->files); 0128 d = nullptr; 0129 while (it.hasNext()) { 0130 auto subfolder = it.next(); 0131 if (s == subfolder->decodedName()) { 0132 d = std::dynamic_pointer_cast<Folder>(subfolder); 0133 break; 0134 } 0135 } 0136 0137 split.pop_front(); 0138 } 0139 0140 if (d) { 0141 delete trees; 0142 0143 // we found a completed tree, thus no need to scan 0144 qCDebug(FILELIGHT_LOG) << "Found cache-handle, generating map.."; 0145 0146 Q_EMIT branchCacheHit(d); 0147 0148 return true; 0149 } // something went wrong, we couldn't find the folder we were expecting 0150 qCWarning(FILELIGHT_LOG) << "Didn't find " << path << " in the cache!\n"; 0151 it.remove(); 0152 Q_EMIT aboutToEmptyCache(); 0153 break; // do a full scan 0154 } 0155 if (cachePath.startsWith(path)) { // then part of the requested tree is already scanned 0156 qCDebug(FILELIGHT_LOG) << "Cache-(b)hit: " << cachePath; 0157 it.remove(); 0158 trees->append(folder); 0159 } 0160 } 0161 0162 QGuiApplication::changeOverrideCursor(QCursor(Qt::BusyCursor)); 0163 // starts listing by itself 0164 m_thread = std::make_unique<Filelight::LocalLister>(path, trees, this); 0165 connect(m_thread.get(), &LocalLister::branchCompleted, this, &ScanManager::cacheTree, Qt::QueuedConnection); 0166 m_thread->start(); 0167 0168 Q_EMIT runningChanged(); 0169 0170 return true; 0171 } 0172 0173 bool ScanManager::abort() 0174 { 0175 m_abort = true; 0176 0177 if (m_remoteLister) { 0178 m_remoteLister->stop(); 0179 } 0180 m_remoteLister = nullptr; 0181 const bool ret = m_thread && m_thread->wait(); 0182 Q_EMIT runningChanged(); 0183 0184 Q_EMIT aborted(); 0185 return ret; 0186 } 0187 0188 void ScanManager::invalidateCacheFor(const QUrl &url) 0189 { 0190 m_abort = true; 0191 0192 if (m_thread && m_thread->isRunning()) { 0193 m_thread->wait(); 0194 } 0195 0196 if (!url.isLocalFile()) { 0197 qWarning() << "Remote cache clearing not implemented"; 0198 return; 0199 } 0200 0201 QString path = url.toLocalFile(); 0202 // Do not use QDir::separator! The path is using / even on windows. 0203 if (!path.endsWith(QLatin1Char('/'))) { 0204 path += QLatin1Char('/'); 0205 } 0206 0207 Q_EMIT aboutToEmptyCache(); 0208 0209 QMutableListIterator<std::shared_ptr<Folder>> cacheIterator(m_cache); 0210 QList<std::shared_ptr<Folder>> subCaches; 0211 QList<std::shared_ptr<Folder>> oldCache = m_cache; 0212 while (cacheIterator.hasNext()) { 0213 auto folder = cacheIterator.next(); 0214 cacheIterator.remove(); 0215 0216 QString cachePath = folder->decodedName(); 0217 0218 if (!path.startsWith(cachePath)) { 0219 continue; 0220 } 0221 QList<QStringView> splitPath = QStringView(path).mid(cachePath.length()).split(QLatin1Char('/')); 0222 auto d = folder; 0223 0224 while (!splitPath.isEmpty() && d != nullptr) { // if NULL we have got lost so abort!! 0225 if (splitPath.first().isEmpty()) { // found the dir 0226 break; 0227 } 0228 QString wantedName = splitPath.takeFirst() % QLatin1Char('/'); // % is the string concatenation operator for QStringBuilder 0229 0230 QListIterator<std::shared_ptr<File>> it(d->files); 0231 d = nullptr; 0232 while (it.hasNext()) { 0233 auto subfolder = it.next(); 0234 if (subfolder->decodedName() == wantedName) { 0235 // This is the one we want to remove 0236 continue; 0237 } 0238 if (!subfolder->isFolder()) { 0239 continue; 0240 } 0241 0242 auto newFolder = std::dynamic_pointer_cast<Folder>(subfolder)->duplicate(); 0243 newFolder->setName((cachePath.toLocal8Bit() + subfolder->name8Bit())); 0244 subCaches.append(newFolder); 0245 d = nullptr; 0246 } 0247 } 0248 0249 if (!d || !d->parent()) { 0250 continue; 0251 } 0252 d->parent()->remove(d); 0253 } 0254 0255 for (const auto &folder : subCaches) { 0256 m_cache.append(folder); 0257 } 0258 } 0259 0260 void ScanManager::emptyCache() 0261 { 0262 m_abort = true; 0263 0264 if (m_thread && m_thread->isRunning()) { 0265 m_thread->wait(); 0266 } 0267 0268 Q_EMIT aboutToEmptyCache(); 0269 0270 m_cache.clear(); 0271 } 0272 0273 void ScanManager::cacheTree(std::shared_ptr<Folder> tree) 0274 { 0275 QMutexLocker locker(&m_mutex); // This gets released once it is destroyed. 0276 0277 if (m_thread) { 0278 qCDebug(FILELIGHT_LOG) << "Waiting for thread to terminate ..."; 0279 m_thread->wait(); 0280 qCDebug(FILELIGHT_LOG) << "Thread terminated!"; 0281 m_thread = nullptr; 0282 } 0283 0284 Q_EMIT completed(tree); 0285 0286 if (tree) { 0287 // we don't cache foreign stuff 0288 // we don't recache stuff (thus only type 1000 events) 0289 // we always just have one tree cached, so we really don't need a list.. 0290 m_cache.append(tree); 0291 } else { // scan failed 0292 m_cache.clear(); 0293 } 0294 0295 QGuiApplication::restoreOverrideCursor(); 0296 } 0297 0298 void ScanManager::foundCached(std::shared_ptr<Folder> tree) 0299 { 0300 Q_EMIT completed(tree); 0301 QGuiApplication::restoreOverrideCursor(); 0302 } 0303 0304 }