File indexing completed on 2024-05-05 17:56:54
0001 /* 0002 SPDX-FileCopyrightText: 2000 Rafi Yanai <krusader@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "defaultfilesystem.h" 0009 0010 // QtCore 0011 #include <QDebug> 0012 #include <QDir> 0013 #include <QEventLoop> 0014 0015 #include <KConfigCore/KSharedConfig> 0016 #include <KCoreAddons/KUrlMimeData> 0017 #include <KI18n/KLocalizedString> 0018 #include <KIO/DropJob> 0019 #include <KIO/FileUndoManager> 0020 #include <KIO/JobUiDelegate> 0021 #include <KIO/ListJob> 0022 #include <KIO/MkpathJob> 0023 #include <KIOCore/KDiskFreeSpaceInfo> 0024 #include <KIOCore/KFileItem> 0025 #include <KIOCore/KMountPoint> 0026 #include <KIOCore/KProtocolManager> 0027 #include <kio_version.h> 0028 0029 #include "../JobMan/krjob.h" 0030 #include "../defaults.h" 0031 #include "../krglobal.h" 0032 #include "../krservices.h" 0033 #include "fileitem.h" 0034 0035 DefaultFileSystem::DefaultFileSystem() 0036 { 0037 _type = FS_DEFAULT; 0038 } 0039 0040 void DefaultFileSystem::copyFiles(const QList<QUrl> &urls, 0041 const QUrl &destination, 0042 KIO::CopyJob::CopyMode mode, 0043 bool showProgressInfo, 0044 JobMan::StartMode startMode) 0045 { 0046 // resolve relative path before resolving symlinks 0047 const QUrl dest = resolveRelativePath(destination); 0048 0049 KIO::JobFlags flags = showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; 0050 0051 KrJob *krJob = KrJob::createCopyJob(mode, urls, dest, flags); 0052 // destination can be a full path with filename when copying/moving a single file 0053 const QUrl destDir = dest.adjusted(QUrl::RemoveFilename); 0054 connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { 0055 connectJobToDestination(job, destDir); 0056 }); 0057 if (mode == KIO::CopyJob::Move) { // notify source about removed files 0058 connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { 0059 connectJobToSources(job, urls); 0060 }); 0061 } 0062 0063 krJobMan->manageJob(krJob, startMode); 0064 } 0065 0066 void DefaultFileSystem::dropFiles(const QUrl &destination, QDropEvent *event) 0067 { 0068 qDebug() << "destination=" << destination; 0069 0070 // resolve relative path before resolving symlinks 0071 const QUrl dest = resolveRelativePath(destination); 0072 0073 KIO::DropJob *job = KIO::drop(event, dest); 0074 0075 // NOTE: a DropJob "starts" with showing a menu. If the operation is chosen (copy/move/link) 0076 // the actual CopyJob starts automatically - we cannot manage the start of the CopyJob (see 0077 // documentation for KrJob) 0078 connect(job, &KIO::DropJob::copyJobStarted, this, [=](KIO::CopyJob *kJob) { 0079 connectJobToDestination(job, dest); // now we have to refresh the destination 0080 0081 KrJob *krJob = KrJob::createDropJob(job, kJob); 0082 krJobMan->manageStartedJob(krJob, kJob); 0083 if (kJob->operationMode() == KIO::CopyJob::Move) { // notify source about removed files 0084 connectJobToSources(kJob, kJob->srcUrls()); 0085 } 0086 }); 0087 } 0088 0089 void DefaultFileSystem::addFiles(const QList<QUrl> &fileUrls, KIO::CopyJob::CopyMode mode, const QString &dir) 0090 { 0091 QUrl destination(_currentDirectory); 0092 if (!dir.isEmpty()) { 0093 destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); 0094 const QString scheme = destination.scheme(); 0095 if (scheme == "tar" || scheme == "zip" || scheme == "krarc") { 0096 if (QDir(destination.path()).exists()) 0097 // if we get out from the archive change the protocol 0098 destination.setScheme("file"); 0099 } 0100 } 0101 0102 destination = ensureTrailingSlash(destination); // destination is always a directory 0103 copyFiles(fileUrls, destination, mode); 0104 } 0105 0106 void DefaultFileSystem::mkDir(const QString &name) 0107 { 0108 KJob *job; 0109 if (name.contains('/')) { 0110 job = KIO::mkpath(getUrl(name)); 0111 } else { 0112 job = KIO::mkdir(getUrl(name)); 0113 } 0114 connectJobToDestination(job, currentDirectory()); 0115 } 0116 0117 void DefaultFileSystem::rename(const QString &oldName, const QString &newName) 0118 { 0119 const QUrl oldUrl = getUrl(oldName); 0120 const QUrl newUrl = getUrl(newName); 0121 KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); 0122 connectJobToDestination(job, currentDirectory()); 0123 0124 KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); 0125 } 0126 0127 QUrl DefaultFileSystem::getUrl(const QString &name) const 0128 { 0129 // NOTE: on non-local fs file URL does not have to be path + name! 0130 FileItem *fileItem = getFileItem(name); 0131 if (fileItem) 0132 return fileItem->getUrl(); 0133 0134 QUrl absoluteUrl(_currentDirectory); 0135 if (name.startsWith('/')) { 0136 absoluteUrl.setPath(name); 0137 } else { 0138 absoluteUrl.setPath(absoluteUrl.path() + '/' + name); 0139 } 0140 return absoluteUrl; 0141 } 0142 0143 void DefaultFileSystem::updateFilesystemInfo() 0144 { 0145 if (!KConfigGroup(krConfig, "Look&Feel").readEntry("ShowSpaceInformation", true)) { 0146 _mountPoint = ""; 0147 emit fileSystemInfoChanged(i18n("Space information disabled"), "", 0, 0); 0148 return; 0149 } 0150 0151 // TODO get space info for trash:/ with KIO spaceInfo job 0152 if (!_currentDirectory.isLocalFile()) { 0153 _mountPoint = ""; 0154 emit fileSystemInfoChanged(i18n("No space information on non-local filesystems"), "", 0, 0); 0155 return; 0156 } 0157 0158 const QString path = _currentDirectory.path(); 0159 const KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(path); 0160 if (!info.isValid()) { 0161 _mountPoint = ""; 0162 emit fileSystemInfoChanged(i18n("Space information unavailable"), "", 0, 0); 0163 return; 0164 } 0165 _mountPoint = info.mountPoint(); 0166 0167 const KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(path); 0168 const QString fsType = mountPoint ? mountPoint->mountType() : ""; 0169 0170 emit fileSystemInfoChanged("", fsType, info.size(), info.available()); 0171 } 0172 0173 // ==== protected ==== 0174 0175 bool DefaultFileSystem::refreshInternal(const QUrl &directory, bool onlyScan) 0176 { 0177 qDebug() << "refresh internal to URL=" << directory.toDisplayString(); 0178 if (!KProtocolManager::supportsListing(directory)) { 0179 emit error(i18n("Protocol not supported by Krusader:\n%1", directory.url())); 0180 return false; 0181 } 0182 0183 delete _watcher; // stop watching the old dir 0184 0185 if (directory.isLocalFile()) { 0186 qDebug() << "start local refresh to URL=" << directory.toDisplayString(); 0187 // we could read local directories with KIO but using Qt is a lot faster! 0188 return refreshLocal(directory, onlyScan); 0189 } 0190 0191 _currentDirectory = cleanUrl(directory); 0192 0193 // start the listing job 0194 KIO::ListJob *job = KIO::listDir(_currentDirectory, KIO::HideProgressInfo, showHiddenFiles()); 0195 connect(job, &KIO::ListJob::entries, this, &DefaultFileSystem::slotAddFiles); 0196 connect(job, &KIO::ListJob::redirection, this, &DefaultFileSystem::slotRedirection); 0197 connect(job, &KIO::ListJob::permanentRedirection, this, &DefaultFileSystem::slotRedirection); 0198 connect(job, &KIO::Job::result, this, &DefaultFileSystem::slotListResult); 0199 0200 // ensure connection credentials are asked only once 0201 if (!parentWindow.isNull()) { 0202 auto *ui = dynamic_cast<KIO::JobUiDelegate *>(job->uiDelegate()); 0203 ui->setWindow(parentWindow); 0204 } 0205 0206 emit refreshJobStarted(job); 0207 0208 _listError = false; 0209 // ugly: we have to wait here until the list job is finished 0210 QEventLoop eventLoop; 0211 connect(job, &KJob::finished, &eventLoop, &QEventLoop::quit); 0212 eventLoop.exec(); // blocking until quit() 0213 0214 return !_listError; 0215 } 0216 0217 // ==== protected slots ==== 0218 0219 void DefaultFileSystem::slotListResult(KJob *job) 0220 { 0221 qDebug() << "got list result"; 0222 if (job && job->error()) { 0223 // we failed to refresh 0224 _listError = true; 0225 qDebug() << "error=" << job->errorString() << "; text=" << job->errorText(); 0226 emit error(job->errorString()); // display error message (in panel) 0227 } 0228 } 0229 0230 void DefaultFileSystem::slotAddFiles(KIO::Job *, const KIO::UDSEntryList &entries) 0231 { 0232 for (const KIO::UDSEntry &entry : entries) { 0233 FileItem *fileItem = FileSystem::createFileItemFromKIO(entry, _currentDirectory); 0234 if (fileItem) { 0235 addFileItem(fileItem); 0236 } 0237 } 0238 } 0239 0240 void DefaultFileSystem::slotRedirection(KIO::Job *job, const QUrl &url) 0241 { 0242 qDebug() << "redirection to URL=" << url.toDisplayString(); 0243 0244 // some protocols (zip, tar) send redirect to local URL without scheme 0245 const QUrl newUrl = preferLocalUrl(url); 0246 0247 if (newUrl.scheme() != _currentDirectory.scheme()) { 0248 // abort and start over again, 0249 // some protocols (iso, zip, tar) do this on transition to local fs 0250 job->kill(); 0251 _isRefreshing = false; 0252 refresh(newUrl); 0253 return; 0254 } 0255 0256 _currentDirectory = cleanUrl(newUrl); 0257 } 0258 0259 void DefaultFileSystem::slotWatcherCreated(const QString &path) 0260 { 0261 qDebug() << "path created (doing nothing): " << path; 0262 } 0263 0264 void DefaultFileSystem::slotWatcherDirty(const QString &path) 0265 { 0266 qDebug() << "path dirty: " << path; 0267 if (path == realPath()) { 0268 // this happens 0269 // 1. if a directory was created/deleted/renamed inside this directory. 0270 // 2. during and after a file operation (create/delete/rename/touch) inside this directory 0271 // KDirWatcher doesn't reveal the name of changed directories and we have to refresh. 0272 // (QFileSystemWatcher in Qt5.7 can't help here either) 0273 refresh(); 0274 return; 0275 } 0276 0277 const QString name = QUrl::fromLocalFile(path).fileName(); 0278 0279 FileItem *fileItem = getFileItem(name); 0280 if (!fileItem) { 0281 qWarning() << "file not found (unexpected), path=" << path; 0282 // this happens at least for cifs mounted filesystems: when a new file is created, a dirty 0283 // signal with its file path but no other signals are sent (buggy behaviour of KDirWatch) 0284 refresh(); 0285 return; 0286 } 0287 0288 // we have an updated file.. 0289 FileItem *newFileItem = createLocalFileItem(name); 0290 addFileItem(newFileItem); 0291 emit updatedFileItem(newFileItem); 0292 0293 delete fileItem; 0294 } 0295 0296 void DefaultFileSystem::slotWatcherDeleted(const QString &path) 0297 { 0298 qDebug() << "path deleted: " << path; 0299 if (path != _currentDirectory.toLocalFile()) { 0300 // ignore deletion of files here, a 'dirty' signal will be send anyway 0301 return; 0302 } 0303 0304 // the current directory was deleted. Try a refresh, which will fail. An error message will 0305 // be emitted and the empty (non-existing) directory remains. 0306 refresh(); 0307 } 0308 0309 bool DefaultFileSystem::refreshLocal(const QUrl &directory, bool onlyScan) 0310 { 0311 const QString path = KrServices::urlToLocalPath(directory); 0312 0313 #ifdef Q_OS_WIN 0314 if (!path.contains("/")) { // change C: to C:/ 0315 path = path + QString("/"); 0316 } 0317 #endif 0318 0319 // check if the new directory exists 0320 if (!QDir(path).exists()) { 0321 emit error(i18n("The folder %1 does not exist.", path)); 0322 return false; 0323 } 0324 0325 // mount if needed 0326 emit aboutToOpenDir(path); 0327 0328 // set the current directory... 0329 _currentDirectory = directory; 0330 _currentDirectory.setPath(QDir::cleanPath(_currentDirectory.path())); 0331 0332 // Note: we are using low-level Qt functions here. 0333 // It's around twice as fast as using the QDir class. 0334 0335 QT_DIR *dir = QT_OPENDIR(path.toLocal8Bit()); 0336 if (!dir) { 0337 emit error(i18n("Cannot open the folder %1.", path)); 0338 return false; 0339 } 0340 0341 // change directory to the new directory 0342 const QString savedDir = QDir::currentPath(); 0343 if (!QDir::setCurrent(path)) { 0344 emit error(i18nc("%1=folder path", "Access to %1 denied", path)); 0345 QT_CLOSEDIR(dir); 0346 return false; 0347 } 0348 0349 QT_DIRENT *dirEnt; 0350 QString name; 0351 const bool showHidden = showHiddenFiles(); 0352 QSet<QString> hiddenFiles = filesInDotHidden(path); 0353 while ((dirEnt = QT_READDIR(dir)) != nullptr) { 0354 name = QString::fromLocal8Bit(dirEnt->d_name); 0355 0356 // show hidden files? 0357 if (!showHidden && name.left(1) == ".") 0358 continue; 0359 // show file in .hidden file? 0360 if (!showHidden && hiddenFiles.contains(name)) 0361 continue; 0362 // we don't need the "." and ".." entries 0363 if (name == "." || name == "..") 0364 continue; 0365 0366 FileItem *temp = createLocalFileItem(name); 0367 addFileItem(temp); 0368 } 0369 // clean up 0370 QT_CLOSEDIR(dir); 0371 QDir::setCurrent(savedDir); 0372 0373 if (!onlyScan) { 0374 // start watching the new dir for file changes 0375 _watcher = new KDirWatch(this); 0376 // if the current dir is a link path the watcher needs to watch the real path - and signal 0377 // parameters will be the real path 0378 _watcher->addDir(realPath(), KDirWatch::WatchFiles); 0379 connect(_watcher.data(), &KDirWatch::dirty, this, &DefaultFileSystem::slotWatcherDirty); 0380 // NOTE: not connecting 'created' signal. A 'dirty' is send after that anyway 0381 // connect(_watcher.data(), &KDirWatch::created, this, &DefaultFileSystem::slotWatcherCreated); 0382 connect(_watcher.data(), &KDirWatch::deleted, this, &DefaultFileSystem::slotWatcherDeleted); 0383 _watcher->startScan(false); 0384 } 0385 0386 return true; 0387 } 0388 0389 QSet<QString> DefaultFileSystem::filesInDotHidden(const QString &dir) 0390 { 0391 // code "borrowed" from KIO, Copyright (C) by Bruno Nova <brunomb.nova@gmail.com> 0392 const QString path = dir + QLatin1String("/.hidden"); 0393 QFile dotHiddenFile(path); 0394 0395 if (dotHiddenFile.exists()) { 0396 if (dotHiddenFile.open(QIODevice::ReadOnly | QIODevice::Text)) { 0397 QSet<QString> filesToHide; 0398 QTextStream stream(&dotHiddenFile); 0399 while (!stream.atEnd()) { 0400 QString name = stream.readLine(); 0401 if (!name.isEmpty()) { 0402 filesToHide.insert(name); 0403 } 0404 } 0405 return filesToHide; 0406 } 0407 } 0408 0409 return QSet<QString>(); 0410 } 0411 0412 FileItem *DefaultFileSystem::createLocalFileItem(const QString &name) 0413 { 0414 return FileSystem::createLocalFileItem(name, _currentDirectory.path()); 0415 } 0416 0417 QString DefaultFileSystem::realPath() 0418 { 0419 // NOTE: current dir must exist 0420 return QDir(_currentDirectory.toLocalFile()).canonicalPath(); 0421 } 0422 0423 QUrl DefaultFileSystem::resolveRelativePath(const QUrl &url) 0424 { 0425 // if e.g. "/tmp/bin" is a link to "/bin", 0426 // resolve "/tmp/bin/.." to "/tmp" and not "/" 0427 return url.adjusted(QUrl::NormalizePathSegments); 0428 }