diff --git a/krusader/FileSystem/defaultfilesystem.cpp b/krusader/FileSystem/defaultfilesystem.cpp index ea0bf7a0..e19808e3 100644 --- a/krusader/FileSystem/defaultfilesystem.cpp +++ b/krusader/FileSystem/defaultfilesystem.cpp @@ -1,390 +1,394 @@ /*************************************************************************** defaultfilesystem.cpp ------------------- copyright : (C) 2000 by Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net --------------------------------------------------------------------------- *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD S o u r c e F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "defaultfilesystem.h" // QtCore #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fileitem.h" #include "../defaults.h" #include "../krglobal.h" #include "../krservices.h" #include "../JobMan/krjob.h" DefaultFileSystem::DefaultFileSystem(): FileSystem(), _watcher() { _type = FS_DEFAULT; } void DefaultFileSystem::copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode, bool showProgressInfo, JobMan::StartMode startMode) { // resolve relative path before resolving symlinks const QUrl dest = resolveRelativePath(destination); KIO::JobFlags flags = showProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo; - KrJob *krJob = KrJob::createCopyJob(mode, urls, destination, flags); - connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectJob(job, dest); }); + KrJob *krJob = KrJob::createCopyJob(mode, urls, dest, flags); + // destination can be a full path with filename when copying/moving a single file + const QUrl destDir = dest.adjusted(QUrl::RemoveFilename); + connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectJob(job, destDir); }); if (mode == KIO::CopyJob::Move) { // notify source about removed files connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectSourceFileSystem(job, urls); }); } krJobMan->manageJob(krJob, startMode); } void DefaultFileSystem::dropFiles(const QUrl &destination, QDropEvent *event) { // resolve relative path before resolving symlinks const QUrl dest = resolveRelativePath(destination); KIO::DropJob *job = KIO::drop(event, dest); // NOTE: DropJob does not provide information about the actual user choice // (move/copy/link/abort). We have to assume the worst (move) connectJob(job, dest); connectSourceFileSystem(job, KUrlMimeData::urlsFromMimeData(event->mimeData())); // NOTE: DrobJobs are internally recorded //recordJobUndo(job, type, dst, src); } void DefaultFileSystem::connectSourceFileSystem(KJob *job, const QList urls) { if (!urls.isEmpty()) { // NOTE: we assume that all files were in the same directory and only emit one signal for // the directory of the first file URL const QUrl url = urls.first().adjusted(QUrl::RemoveFilename); connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(url); }); } } -void DefaultFileSystem::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode, QString dir) +void DefaultFileSystem::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode, + const QString &dir) { QUrl destination(_currentDirectory); if (!dir.isEmpty()) { destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); const QString scheme = destination.scheme(); if (scheme == "tar" || scheme == "zip" || scheme == "krarc") { - if (QDir(cleanUrl(destination).path()).exists()) + if (QDir(destination.path()).exists()) // if we get out from the archive change the protocol destination.setScheme("file"); } } + destination = ensureTrailingSlash(destination); // destination is always a directory copyFiles(fileUrls, destination, mode); } void DefaultFileSystem::mkDir(const QString &name) { KIO::SimpleJob* job = KIO::mkdir(getUrl(name)); connectJob(job, currentDirectory()); } void DefaultFileSystem::rename(const QString &oldName, const QString &newName) { const QUrl oldUrl = getUrl(oldName); const QUrl newUrl = getUrl(newName); KIO::Job *job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo); connectJob(job, currentDirectory()); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {oldUrl}, newUrl, job); } QUrl DefaultFileSystem::getUrl(const QString& name) const { // NOTE: on non-local fs file URL does not have to be path + name! FileItem *fileItem = getFileItem(name); if (fileItem) return fileItem->getUrl(); QUrl absoluteUrl(_currentDirectory); absoluteUrl.setPath(absoluteUrl.path() + '/' + name); return absoluteUrl; } void DefaultFileSystem::updateFilesystemInfo() { if (!KConfigGroup(krConfig, "Look&Feel").readEntry("ShowSpaceInformation", true)) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("Space information disabled"), "", 0, 0); return; } // TODO get space info for trash:/ with KIO spaceInfo job if (!_currentDirectory.isLocalFile()) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("No space information on non-local filesystems"), "", 0, 0); return; } const QString path = _currentDirectory.path(); const KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(path); if (!info.isValid()) { _mountPoint = ""; emit fileSystemInfoChanged(i18n("Space information unavailable"), "", 0, 0); return; } _mountPoint = info.mountPoint(); const KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByPath(path); const QString fsType = mountPoint ? mountPoint->mountType() : ""; emit fileSystemInfoChanged("", fsType, info.size(), info.available()); } // ==== protected ==== bool DefaultFileSystem::refreshInternal(const QUrl &directory, bool onlyScan) { if (!KProtocolManager::supportsListing(directory)) { emit error(i18n("Protocol not supported by Krusader:\n%1", directory.url())); return false; } delete _watcher; // stop watching the old dir if (directory.isLocalFile()) { // we could read local directories with KIO but using Qt is a lot faster! return refreshLocal(directory, onlyScan); } _currentDirectory = cleanUrl(directory); // start the listing job KIO::ListJob *job = KIO::listDir(_currentDirectory, KIO::HideProgressInfo, showHiddenFiles()); connect(job, &KIO::ListJob::entries, this, &DefaultFileSystem::slotAddFiles); connect(job, &KIO::ListJob::redirection, this, &DefaultFileSystem::slotRedirection); connect(job, &KIO::ListJob::permanentRedirection, this, &DefaultFileSystem::slotRedirection); connect(job, &KIO::Job::result, this, &DefaultFileSystem::slotListResult); // ensure connection credentials are asked only once if(!parentWindow.isNull()) { KIO::JobUiDelegate *ui = static_cast(job->uiDelegate()); ui->setWindow(parentWindow); } emit refreshJobStarted(job); _listError = false; // ugly: we have to wait here until the list job is finished QEventLoop eventLoop; connect(job, &KJob::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // blocking until quit() return !_listError; } // ==== protected slots ==== void DefaultFileSystem::slotListResult(KJob *job) { if (job && job->error()) { // we failed to refresh _listError = true; emit error(job->errorString()); // display error message (in panel) } } void DefaultFileSystem::slotAddFiles(KIO::Job *, const KIO::UDSEntryList& entries) { for (const KIO::UDSEntry entry : entries) { FileItem *fileItem = FileSystem::createFileItemFromKIO(entry, _currentDirectory); if (fileItem) { addFileItem(fileItem); } } } void DefaultFileSystem::slotRedirection(KIO::Job *job, const QUrl &url) { krOut << "redirection to " << url; // some protocols (zip, tar) send redirect to local URL without scheme const QUrl newUrl = preferLocalUrl(url); if (newUrl.scheme() != _currentDirectory.scheme()) { // abort and start over again, // some protocols (iso, zip, tar) do this on transition to local fs job->kill(); _isRefreshing = false; refresh(newUrl); return; } _currentDirectory = cleanUrl(newUrl); } void DefaultFileSystem::slotWatcherDirty(const QString& path) { if (path == realPath()) { // this happens // 1. if a directory was created/deleted/renamed inside this directory. // 2. during and after a file operation (create/delete/rename/touch) inside this directory // KDirWatcher doesn't reveal the name of changed directories and we have to refresh. // (QFileSystemWatcher in Qt5.7 can't help here either) refresh(); return; } const QString name = QUrl::fromLocalFile(path).fileName(); FileItem *fileItem = getFileItem(name); if (!fileItem) { krOut << "dirty watcher file not found (unexpected): " << path; // this happens at least for cifs mounted filesystems: when a new file is created, a dirty // signal with its file path but no other signals are sent (buggy behaviour of KDirWatch) refresh(); return; } // we have an updated file.. FileItem *newFileItem = createLocalFileItem(name); addFileItem(newFileItem); emit updatedFileItem(newFileItem); delete fileItem; } void DefaultFileSystem::slotWatcherDeleted(const QString& path) { if (path != realPath()) { // ignore deletion of files here, a 'dirty' signal will be send anyway return; } // the current directory was deleted. Try a refresh, which will fail. An error message will // be emitted and the empty (non-existing) directory remains. refresh(); } bool DefaultFileSystem::refreshLocal(const QUrl &directory, bool onlyScan) { const QString path = KrServices::urlToLocalPath(directory); #ifdef Q_WS_WIN if (!path.contains("/")) { // change C: to C:/ path = path + QString("/"); } #endif // check if the new directory exists if (!QDir(path).exists()) { emit error(i18n("The folder %1 does not exist.", path)); return false; } // mount if needed emit aboutToOpenDir(path); // set the current directory... _currentDirectory = directory; _currentDirectory.setPath(QDir::cleanPath(_currentDirectory.path())); // Note: we are using low-level Qt functions here. // It's around twice as fast as using the QDir class. QT_DIR* dir = QT_OPENDIR(path.toLocal8Bit()); if (!dir) { emit error(i18n("Cannot open the folder %1.", path)); return false; } // change directory to the new directory const QString savedDir = QDir::currentPath(); if (!QDir::setCurrent(path)) { emit error(i18nc("%1=folder path", "Access to %1 denied", path)); QT_CLOSEDIR(dir); return false; } QT_DIRENT* dirEnt; QString name; const bool showHidden = showHiddenFiles(); while ((dirEnt = QT_READDIR(dir)) != NULL) { name = QString::fromLocal8Bit(dirEnt->d_name); // show hidden files? if (!showHidden && name.left(1) == ".") continue ; // we don't need the "." and ".." entries if (name == "." || name == "..") continue; FileItem* temp = createLocalFileItem(name); addFileItem(temp); } // clean up QT_CLOSEDIR(dir); QDir::setCurrent(savedDir); if (!onlyScan) { // start watching the new dir for file changes _watcher = new KDirWatch(this); // if the current dir is a link path the watcher needs to watch the real path - and signal // parameters will be the real path _watcher->addDir(realPath(), KDirWatch::WatchFiles); connect(_watcher.data(), &KDirWatch::dirty, this, &DefaultFileSystem::slotWatcherDirty); // NOTE: not connecting 'created' signal. A 'dirty' is send after that anyway //connect(_watcher, SIGNAL(created(QString)), this, SLOT(slotWatcherCreated(QString))); connect(_watcher.data(), &KDirWatch::deleted, this, &DefaultFileSystem::slotWatcherDeleted); _watcher->startScan(false); } return true; } FileItem *DefaultFileSystem::createLocalFileItem(const QString &name) { return FileSystem::createLocalFileItem(name, _currentDirectory.path()); } QString DefaultFileSystem::DefaultFileSystem::realPath() { return QDir(_currentDirectory.toLocalFile()).canonicalPath(); } QUrl DefaultFileSystem::resolveRelativePath(const QUrl &url) { // if e.g. "/tmp/bin" is a link to "/bin", // resolve "/tmp/bin/.." to "/tmp" and not "/" return url.adjusted(QUrl::NormalizePathSegments); } diff --git a/krusader/FileSystem/defaultfilesystem.h b/krusader/FileSystem/defaultfilesystem.h index fcbb59d6..b2d21a74 100644 --- a/krusader/FileSystem/defaultfilesystem.h +++ b/krusader/FileSystem/defaultfilesystem.h @@ -1,106 +1,106 @@ /*************************************************************************** defaultfilesystem.h ------------------- begin : Thu May 4 2000 copyright : (C) 2000 by Shie Erlich & Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD H e a d e r F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef DEFAULTFILESYSTEM_H #define DEFAULTFILESYSTEM_H #include "filesystem.h" #include #include /** * @brief Default filesystem implementation supporting all KIO protocols * * This filesystem implementation allows file operations and listing for all supported KIO protocols * (local and remote/network). * * Refreshing local directories is optimized for performance. * * NOTE: For detecting local file changes a filesystem watcher is used. It cannot be used for * refreshing the view after own file operations are performed because the detection is to slow * (~500ms delay between operation finished and watcher emits signals). * */ class DefaultFileSystem : public FileSystem { Q_OBJECT public: DefaultFileSystem(); void copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode = KIO::CopyJob::Copy, bool showProgressInfo = true, JobMan::StartMode startMode = JobMan::Default) Q_DECL_OVERRIDE; void dropFiles(const QUrl &destination, QDropEvent *event) Q_DECL_OVERRIDE; void addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode, - QString dir = "") Q_DECL_OVERRIDE; + const QString &dir = "") Q_DECL_OVERRIDE; void mkDir(const QString &name) Q_DECL_OVERRIDE; void rename(const QString &fileName, const QString &newName) Q_DECL_OVERRIDE; /// Return URL for file name - even if file does not exist. QUrl getUrl(const QString &name) const Q_DECL_OVERRIDE; bool canMoveToTrash(const QStringList &) const Q_DECL_OVERRIDE { return isLocal(); } QString mountPoint() const { return _mountPoint; } bool hasAutoUpdate() const Q_DECL_OVERRIDE { return !_watcher.isNull(); } void updateFilesystemInfo() Q_DECL_OVERRIDE; protected: bool refreshInternal(const QUrl &origin, bool onlyScan) Q_DECL_OVERRIDE; protected slots: /// Handle result after dir listing job is finished void slotListResult(KJob *job); /// Fill directory file list with new files from the dir lister void slotAddFiles(KIO::Job *job, const KIO::UDSEntryList &entries); /// URL redirection signal from dir lister void slotRedirection(KIO::Job *job, const QUrl &url); // React to filesystem changes nofified by watcher // NOTE: the path parameter can be the directory itself or files in this directory void slotWatcherDirty(const QString &path); void slotWatcherDeleted(const QString &path); private: void connectSourceFileSystem(KJob *job, const QList urls); bool refreshLocal(const QUrl &directory, bool onlyScan); // NOTE: this is very fast FileItem *createLocalFileItem(const QString &name); /// Returns the current path with symbolic links resolved QString realPath(); static QUrl resolveRelativePath(const QUrl &url); QPointer _watcher; // dir watcher used to detect changes in the current dir bool _listError; // for async operation, return list job result QString _mountPoint; // the mount point of the current dir }; #endif diff --git a/krusader/FileSystem/filesystem.cpp b/krusader/FileSystem/filesystem.cpp index 2e9cce1e..614792b7 100644 --- a/krusader/FileSystem/filesystem.cpp +++ b/krusader/FileSystem/filesystem.cpp @@ -1,295 +1,292 @@ /*************************************************************************** filesystem.cpp ------------------- copyright : (C) 2000 by Shie Erlich & Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net ------------------------------------------------------------------------ the filesystem class is an extendable class which by itself does (almost) nothing. other filesystems like the normal_filesystem inherits from this class and make it possible to use a consistent API for all types of filesystems. *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD S o u r c e F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "filesystem.h" // QtCore #include #include // QtWidgets #include #include #include #include #include "fileitem.h" #include "krpermhandler.h" #include "../defaults.h" #include "../krglobal.h" #include "../JobMan/jobman.h" #include "../JobMan/krjob.h" FileSystem::FileSystem() : DirListerInterface(0), _isRefreshing(false) {} FileSystem::~FileSystem() { clear(_fileItems); // please don't remove this line. This informs the view about deleting the file items. emit cleared(); } QList FileSystem::getUrls(const QStringList &names) const { QList urls; for (const QString name : names) { urls.append(getUrl(name)); } return urls; } FileItem *FileSystem::getFileItem(const QString &name) const { return _fileItems.contains(name) ? _fileItems.value(name) : 0; } KIO::filesize_t FileSystem::totalSize() const { KIO::filesize_t temp = 0; for (FileItem *item : _fileItems.values()) { if (!item->isDir() && item->getName() != "." && item->getName() != "..") { temp += item->getSize(); } } return temp; } QUrl FileSystem::ensureTrailingSlash(const QUrl &url) { if (url.path().endsWith('/')) { return url; } QUrl adjustedUrl(url); adjustedUrl.setPath(adjustedUrl.path() + '/'); return adjustedUrl; } QUrl FileSystem::preferLocalUrl(const QUrl &url){ if (url.isEmpty() || !url.scheme().isEmpty()) return url; QUrl adjustedUrl = url; adjustedUrl.setScheme("file"); return adjustedUrl; } bool FileSystem::scanOrRefresh(const QUrl &directory, bool onlyScan) { if (_isRefreshing) { // NOTE: this does not happen (unless async)"; return false; } // workaround for krarc: find out if transition to local fs is wanted and adjust URL manually QUrl url = directory; if (_currentDirectory.scheme() == "krarc" && url.scheme() == "krarc" && QDir(url.path()).exists()) { url.setScheme("file"); } const bool dirChange = !url.isEmpty() && cleanUrl(url) != _currentDirectory; const QUrl toRefresh = dirChange ? url.adjusted(QUrl::NormalizePathSegments) : _currentDirectory; if (!toRefresh.isValid()) { emit error(i18n("Malformed URL:\n%1", toRefresh.toDisplayString())); return false; } _isRefreshing = true; FileItemDict tempFileItems(_fileItems); // old file items are still used during refresh _fileItems.clear(); if (dirChange) // show an empty directory while loading the new one and clear selection emit cleared(); const bool refreshed = refreshInternal(toRefresh, onlyScan); _isRefreshing = false; if (!refreshed) { // cleanup and abort if (!dirChange) emit cleared(); clear(tempFileItems); return false; } emit scanDone(dirChange); clear(tempFileItems); updateFilesystemInfo(); return true; } void FileSystem::deleteFiles(const QStringList &fileNames, bool moveToTrash) { // get absolute URLs for file names const QList fileUrls = getUrls(fileNames); KrJob *krJob = KrJob::createDeleteJob(fileUrls, moveToTrash); connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { connectJob(job, currentDirectory()); }); if (moveToTrash) { // update destination: the trash bin (in case a panel/tab is showing it) connect(krJob, &KrJob::started, this, [=](KIO::Job *job) { // Note: the "trash" protocal should always have only one "/" after the "scheme:" part connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(QUrl("trash:/")); }); }); } krJobMan->manageJob(krJob); } void FileSystem::connectJob(KJob *job, const QUrl &destination) { - // destination can be a full path with filename when copying/moving a single file - const QUrl destDir = destination.adjusted(QUrl::RemoveFilename); - - connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(destDir); }); + connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(destination); }); // (additional) direct refresh if on local fs because watcher is too slow - const bool refresh = cleanUrl(destDir) == _currentDirectory && isLocal(); + const bool refresh = cleanUrl(destination) == _currentDirectory && isLocal(); connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, refresh); }); } bool FileSystem::showHiddenFiles() { const KConfigGroup gl(krConfig, "Look&Feel"); return gl.readEntry("Show Hidden", _ShowHidden); } void FileSystem::addFileItem(FileItem *item) { _fileItems.insert(item->getName(), item); } FileItem *FileSystem::createLocalFileItem(const QString &name, const QString &directory, bool virt) { const QDir dir = QDir(directory); const QString path = dir.filePath(name); const QByteArray filePath = path.toLocal8Bit(); QT_STATBUF stat_p; stat_p.st_size = 0; stat_p.st_mode = 0; stat_p.st_mtime = 0; stat_p.st_uid = 0; stat_p.st_gid = 0; QT_LSTAT(filePath.data(), &stat_p); const KIO::filesize_t size = stat_p.st_size; bool isDir = S_ISDIR(stat_p.st_mode); const bool isLink = S_ISLNK(stat_p.st_mode); QString linkDestination; bool brokenLink = false; if (isLink) { // find where the link is pointing to // the path of the symlink target cannot be longer than the file size of the symlink char buffer[stat_p.st_size]; memset(buffer, 0, sizeof(buffer)); int bytesRead = readlink(filePath.data(), buffer, sizeof(buffer)); if (bytesRead != -1) { linkDestination = QString::fromLocal8Bit(buffer, bytesRead); // absolute or relative const QFileInfo linkFile(dir, linkDestination); if (!linkFile.exists()) brokenLink = true; else if (linkFile.isDir()) isDir = true; } else { krOut << "Failed to read link: " << path; } } return new FileItem(virt ? path : name, QUrl::fromLocalFile(path), isDir, size, stat_p.st_mode, stat_p.st_mtime, stat_p.st_ctime, stat_p.st_atime, stat_p.st_uid, stat_p.st_gid, QString(), QString(), isLink, linkDestination, brokenLink); } FileItem *FileSystem::createFileItemFromKIO(const KIO::UDSEntry &entry, const QUrl &directory, bool virt) { const KFileItem kfi(entry, directory, true, true); const QString name = kfi.text(); // ignore un-needed entries if (name.isEmpty() || name == "." || name == "..") { return 0; } const QString localPath = kfi.localPath(); const QUrl url = !localPath.isEmpty() ? QUrl::fromLocalFile(localPath) : kfi.url(); const QString fname = virt ? url.toDisplayString() : name; // get file statistics... const time_t mtime = kfi.time(KFileItem::ModificationTime).toTime_t(); const time_t ctime = kfi.time(KFileItem::CreationTime).toTime_t(); // "Creation"? its "Changed" const time_t atime = kfi.time(KFileItem::AccessTime).toTime_t(); const mode_t mode = kfi.mode() | kfi.permissions(); // NOTE: we could get the mimetype (and file icon) from the kfileitem here but this is very // slow. Instead, the file item class has it's own (faster) way to determine the file type. // NOTE: "broken link" flag is always false, checking link destination existence is // considered to be too expensive return new FileItem(fname, url, kfi.isDir(), kfi.size(), mode, mtime, ctime, atime, (uid_t) -1, (gid_t) -1, kfi.user(), kfi.group(), kfi.isLink(), kfi.linkDest(), false, kfi.ACL().asString(), kfi.defaultACL().asString()); } void FileSystem::slotJobResult(KJob *job, bool refresh) { if (job->error() && job->uiDelegate()) { // show errors for modifying operations as popup (works always) job->uiDelegate()->showErrorMessage(); } if (refresh) { FileSystem::refresh(); } } void FileSystem::clear(FileItemDict &fileItems) { QHashIterator lit(fileItems); while (lit.hasNext()) { delete lit.next().value(); } fileItems.clear(); } diff --git a/krusader/FileSystem/filesystem.h b/krusader/FileSystem/filesystem.h index 3ecfff06..05265ae1 100644 --- a/krusader/FileSystem/filesystem.h +++ b/krusader/FileSystem/filesystem.h @@ -1,223 +1,223 @@ /*************************************************************************** filesystem.h ------------------- begin : Thu May 4 2000 copyright : (C) 2000 by Shie Erlich & Rafi Yanai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD H e a d e r F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef FILESYSTEM_H #define FILESYSTEM_H #include "dirlisterinterface.h" // QtCore #include #include #include #include // QtGui #include // QtWidgets #include #include #include #include "../JobMan/jobman.h" class FileItem; /** * An abstract filesystem. Use the implementations of this class for all file operations. * * It represents a directory and gives access to its files. All common file operations * are supported. Methods with absolute URL as argument can be used independently from the current * directory. Otherwise - if the methods argument is a file name - the operation is performed inside * the current directory. * * Notification signals are emitted if the directory content may have been changed. */ class FileSystem : public DirListerInterface { Q_OBJECT public: enum FS_TYPE { /// Virtual filesystem. Krusaders custom virt:/ protocol FS_VIRTUAL, /// Filesystem supporting all KIO protocols (file:/, ftp:/, smb:/, etc.) FS_DEFAULT }; FileSystem(); virtual ~FileSystem(); // DirListerInterface implementation inline QList fileItems() const { return _fileItems.values(); } inline unsigned long numFileItems() const { return _fileItems.count(); } inline bool isRoot() const { const QString path = _currentDirectory.path(); return path.isEmpty() || path == "/"; } /// Copy (copy, move or link) files in this filesystem. /// Destination is absolute URL. May implemented async. virtual void copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode = KIO::CopyJob::Copy, bool showProgressInfo = true, JobMan::StartMode startMode = JobMan::Default) = 0; /// Handle file dropping in this filesystem. Destination is absolute URL. May implemented async. virtual void dropFiles(const QUrl &destination, QDropEvent *event) = 0; /// Copy (copy, move or link) files to the current filesystem directory or to "dir", the /// directory name relative to the current dir. May implemented async. virtual void addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode, - QString dir = "") = 0; + const QString &dir = "") = 0; /// Create a new directory in the current directory. May implemented async. virtual void mkDir(const QString &name) = 0; /// Rename file/directory in the current directory. May implemented async. virtual void rename(const QString &fileName, const QString &newName) = 0; /// Return an absolute URL for a single file/directory name in the current directory - with no /// trailing slash. virtual QUrl getUrl(const QString &name) const = 0; /// Return a list of URLs for multiple files/directories in the current directory. QList getUrls(const QStringList &names) const; /// Return true if all files can be moved to trash, else false. virtual bool canMoveToTrash(const QStringList &fileNames) const = 0; /// Return the filesystem mount point of the current directory. Empty string by default. virtual QString mountPoint() const { return QString(); } /// Returns true if this filesystem implementation does not need to be notified about changes in the /// current directory. Else false. virtual bool hasAutoUpdate() const { return false; } /// Notify this filesystem that the filesystem info of the current directory may have changed. virtual void updateFilesystemInfo() {} /** * Scan all files and directories in a directory and create the file items for them. Blocking. * * @param directory if given, the lister tries to change to this directory, else the old * directory is refreshed * @return true if scan was successful, else (not implemented, scan failed or refresh job * was killed) false. */ bool scanDir(const QUrl &directory = QUrl()) { return scanOrRefresh(directory, false); } /// Change or refresh the current directory and scan it. Blocking. /// Returns true if directory was scanned. Returns false if failed or scan job was killed. bool refresh(const QUrl &directory = QUrl()) { return scanOrRefresh(directory, false); } /// Returns the current directory path of this filesystem. inline QUrl currentDirectory() const { return _currentDirectory; } /// Return the file item for a file name in the current directory. Or 0 if not found. FileItem *getFileItem(const QString &name) const; /// The total size of all files in the current directory (only valid after scan). // TODO unused KIO::filesize_t totalSize() const; /// Return the filesystem type. inline FS_TYPE type() const { return _type; } /// Return true if the current directory is local (without recognizing mount points). inline bool isLocal() const { return _currentDirectory.isLocalFile(); } /// Return true if the current directory is a remote (network) location. inline bool isRemote() const { const QString sc = _currentDirectory.scheme(); return (sc == "fish" || sc == "ftp" || sc == "sftp" || sc == "nfs" || sc == "smb" || sc == "webdav"); } /// Returns true if this filesystem is currently refreshing the current directory. inline bool isRefreshing() const { return _isRefreshing; } /// Delete or trash files in the current directory. Implemented async. void deleteFiles(const QStringList &fileNames, bool moveToTrash = true); /// Return the input URL with a trailing slash if absent. static QUrl ensureTrailingSlash(const QUrl &url); /// Return the input URL without trailing slash. static QUrl cleanUrl(const QUrl &url) { return url.adjusted(QUrl::StripTrailingSlash); } /// Add 'file' scheme to non-empty URL without scheme static QUrl preferLocalUrl(const QUrl &url); /// Return a file item for a local file inside a directory static FileItem *createLocalFileItem(const QString &name, const QString &directory, bool virt = false); /// Return a file item for a KIO result. Returns 0 if entry is not needed static FileItem *createFileItemFromKIO(const KIO::UDSEntry &entry, const QUrl &directory, bool virt = false); // set the parent window to be used for dialogs void setParentWindow(QWidget *widget) { parentWindow = widget; } signals: /// Emitted when this filesystem is currently refreshing the filesystem directory. void refreshJobStarted(KIO::Job *job); /// Emitted when an error occurred in this filesystem during refresh. void error(const QString &msg); /// Emitted when the content of a directory was changed by this filesystem. void fileSystemChanged(const QUrl &directory); /// Emitted when the information for the filesystem of the current directory changed. /// Information is either /// * 'metaInfo': a displayable string about the fs, empty by default, OR /// * 'fsType', 'total' and 'free': filesystem type, size and free space, /// empty string or 0 by default void fileSystemInfoChanged(const QString &metaInfo, const QString &fsType, KIO::filesize_t total, KIO::filesize_t free); /// Emitted before a directory path is opened for reading. Used for automounting. void aboutToOpenDir(const QString &path); protected: /// Fill the filesystem dictionary with file items, must be implemented for each filesystem. virtual bool refreshInternal(const QUrl &origin, bool stayInDir) = 0; /// Connect the result signal of a file operation job. void connectJob(KJob *job, const QUrl &destination); /// Returns true if showing hidden files is set in config. bool showHiddenFiles(); /// Add a new file item to the internal dictionary (while refreshing). void addFileItem(FileItem *item); FS_TYPE _type; // the filesystem type. QUrl _currentDirectory; // the path or file the filesystem originates from. bool _isRefreshing; // true if filesystem is busy with refreshing QPointer parentWindow; protected slots: /// Handle result after job (except when refreshing!) finished void slotJobResult(KJob *job, bool refresh); private: typedef QHash FileItemDict; // optional TODO: add an async version of this bool scanOrRefresh(const QUrl &directory, bool onlyScan); /// Delete and clear file items. void clear(FileItemDict &fileItems); FileItemDict _fileItems; // the list of files in the current dictionary }; #endif diff --git a/krusader/FileSystem/virtualfilesystem.cpp b/krusader/FileSystem/virtualfilesystem.cpp index e0e0eb5f..4b44cf00 100644 --- a/krusader/FileSystem/virtualfilesystem.cpp +++ b/krusader/FileSystem/virtualfilesystem.cpp @@ -1,352 +1,353 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This package is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this package; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #include "virtualfilesystem.h" // QtCore #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include "fileitem.h" #include "../defaults.h" #include "../krglobal.h" #include "../krservices.h" #define VIRTUALFILESYSTEM_DB "virtualfilesystem.db" QHash *> VirtualFileSystem::_virtFilesystemDict; QHash VirtualFileSystem::_metaInfoDict; VirtualFileSystem::VirtualFileSystem() : FileSystem() { if (_virtFilesystemDict.isEmpty()) { restore(); } _type = FS_VIRTUAL; } void VirtualFileSystem::copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode /*mode*/, bool /*showProgressInfo*/, JobMan::StartMode /*startMode*/) { const QString dir = QDir(destination.path()).absolutePath().remove('/'); if (dir.isEmpty()) { showError(i18n("You cannot copy files directly to the 'virt:/' folder.\n" "You can create a sub folder and copy your files into it.")); return; } if (!_virtFilesystemDict.contains(dir)) { mkDirInternal(dir); } QList *urlList = _virtFilesystemDict[dir]; for (const QUrl &fileUrl : urls) { if (!urlList->contains(fileUrl)) { urlList->push_back(fileUrl); } } emit fileSystemChanged(QUrl("virt:///" + dir)); // may call refresh() } void VirtualFileSystem::dropFiles(const QUrl &destination, QDropEvent *event) { const QList &urls = KUrlMimeData::urlsFromMimeData(event->mimeData()); // dropping on virtual filesystem is always copy operation copyFiles(urls, destination); } -void VirtualFileSystem::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode /*mode*/, QString dir) +void VirtualFileSystem::addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode /*mode*/, + const QString &dir) { QUrl destination(_currentDirectory); if (!dir.isEmpty()) { destination.setPath(QDir::cleanPath(destination.path() + '/' + dir)); } copyFiles(fileUrls, destination); } void VirtualFileSystem::remove(const QStringList &fileNames) { const QString parentDir = currentDir(); if (parentDir == "/") { // remove virtual directory for (const QString &filename : fileNames) { _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + filename)); delete _virtFilesystemDict[filename]; _virtFilesystemDict.remove(filename); _metaInfoDict.remove(filename); } } else { // remove the URLs from the collection for (const QString name : fileNames) { if (_virtFilesystemDict.find(parentDir) != _virtFilesystemDict.end()) { QList *urlList = _virtFilesystemDict[parentDir]; urlList->removeAll(getUrl(name)); } } } emit fileSystemChanged(currentDirectory()); // will call refresh() } QUrl VirtualFileSystem::getUrl(const QString &name) const { FileItem *item = getFileItem(name); if (!item) { return QUrl(); // not found } return item->getUrl(); } void VirtualFileSystem::mkDir(const QString &name) { if (currentDir() != "/") { showError(i18n("Creating new folders is allowed only in the 'virt:/' folder.")); return; } mkDirInternal(name); emit fileSystemChanged(currentDirectory()); // will call refresh() } void VirtualFileSystem::rename(const QString &fileName, const QString &newName) { FileItem *item = getFileItem(fileName); if (!item) return; // not found if (currentDir() == "/") { // rename virtual directory _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + newName)); _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + fileName)); _virtFilesystemDict.insert(newName, _virtFilesystemDict.take(fileName)); refresh(); return; } // newName can be a (local) path or a full url QUrl dest(newName); if (dest.scheme().isEmpty()) dest.setScheme("file"); // add the new url to the list // the list is refreshed, only existing files remain - // so we don't have to worry if the job was successful _virtFilesystemDict[currentDir()]->append(dest); KIO::Job *job = KIO::moveAs(item->getUrl(), dest, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, [=](KJob* job) { slotJobResult(job, false); }); connect(job, &KIO::Job::result, this, [=]() { emit fileSystemChanged(currentDirectory()); }); } bool VirtualFileSystem::canMoveToTrash(const QStringList &fileNames) const { if (isRoot()) return false; for (const QString fileName : fileNames) { if (!getUrl(fileName).isLocalFile()) { return false; } } return true; } void VirtualFileSystem::setMetaInformation(const QString &info) { _metaInfoDict[currentDir()] = info; } // ==== protected ==== bool VirtualFileSystem::refreshInternal(const QUrl &directory, bool onlyScan) { _currentDirectory = cleanUrl(directory); _currentDirectory.setHost(""); // remove invalid subdirectories _currentDirectory.setPath('/' + _currentDirectory.path().remove('/')); if (!_virtFilesystemDict.contains(currentDir())) { if (onlyScan) { return false; // virtual dir does not exist } else { // Silently creating non-existing directories here. The search and locate tools // expect this. And the user can enter some directory and it will be created. mkDirInternal(currentDir()); save(); // infinite loop possible // emit fileSystemChanged(currentDirectory()); return true; } } QList *urlList = _virtFilesystemDict[currentDir()]; if (!onlyScan) { const QString metaInfo = _metaInfoDict[currentDir()]; emit fileSystemInfoChanged(metaInfo.isEmpty() ? i18n("Virtual filesystem") : metaInfo, "", 0, 0); } QMutableListIterator it(*urlList); while (it.hasNext()) { const QUrl url = it.next(); FileItem *item = createFileItem(url); if (!item) { // remove URL from the list for a file that no longer exists it.remove(); } else { addFileItem(item); } } save(); return true; } // ==== private ==== void VirtualFileSystem::mkDirInternal(const QString &name) { // clean path, consistent with currentDir() QString dirName = name; dirName = dirName.remove('/'); if (dirName.isEmpty()) dirName = '/'; _virtFilesystemDict.insert(dirName, new QList()); _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + dirName)); } void VirtualFileSystem::save() { KConfig *db = &VirtualFileSystem::getVirtDB(); db->deleteGroup("virt_db"); KConfigGroup group(db, "virt_db"); QHashIterator *> it(_virtFilesystemDict); while (it.hasNext()) { it.next(); QList *urlList = it.value(); QList::iterator url; QStringList entry; for (url = urlList->begin(); url != urlList->end(); ++url) { entry.append((*url).toDisplayString()); } // KDE 4.0 workaround: 'Item_' prefix is added as KConfig fails on 1 char names (such as /) group.writeEntry("Item_" + it.key(), entry); group.writeEntry("MetaInfo_" + it.key(), _metaInfoDict[it.key()]); } db->sync(); } void VirtualFileSystem::restore() { KConfig *db = &VirtualFileSystem::getVirtDB(); const KConfigGroup dbGrp(db, "virt_db"); const QMap map = db->entryMap("virt_db"); QMapIterator it(map); while (it.hasNext()) { it.next(); // KDE 4.0 workaround: check and remove 'Item_' prefix if (!it.key().startsWith(QLatin1String("Item_"))) continue; const QString key = it.key().mid(5); const QList urlList = KrServices::toUrlList(dbGrp.readEntry(it.key(), QStringList())); _virtFilesystemDict.insert(key, new QList(urlList)); _metaInfoDict.insert(key, dbGrp.readEntry("MetaInfo_" + key, QString())); } if (!_virtFilesystemDict["/"]) { // insert root element if missing for some reason _virtFilesystemDict.insert("/", new QList()); } } FileItem *VirtualFileSystem::createFileItem(const QUrl &url) { if (url.scheme() == "virt") { // return a virtual directory in root QString path = url.path().mid(1); if (path.isEmpty()) path = '/'; return FileItem::createVirtualDir(path, url); } const QUrl directory = url.adjusted(QUrl::RemoveFilename); if (url.isLocalFile()) { QFileInfo file(url.path()); return file.exists() ? FileSystem::createLocalFileItem(url.fileName(), directory.path(), true) : 0; } KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo); connect(statJob, &KIO::Job::result, this, &VirtualFileSystem::slotStatResult); // ugly: we have to wait here until the stat job is finished QEventLoop eventLoop; connect(statJob, &KJob::finished, &eventLoop, &QEventLoop::quit); eventLoop.exec(); // blocking until quit() if (_fileEntry.count() == 0) { return 0; // stat job failed } if (!_fileEntry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)) { // TODO this also happens for FTP directories return 0; // file not found } return FileSystem::createFileItemFromKIO(_fileEntry, directory, true); } KConfig &VirtualFileSystem::getVirtDB() { //virtualfilesystem_db = new KConfig("data",VIRTUALFILESYSTEM_DB,KConfig::NoGlobals); static KConfig db(VIRTUALFILESYSTEM_DB, KConfig::CascadeConfig, QStandardPaths::AppDataLocation); return db; } void VirtualFileSystem::slotStatResult(KJob *job) { _fileEntry = job->error() ? KIO::UDSEntry() : static_cast(job)->statResult(); } void VirtualFileSystem::showError(const QString &error) { QWidget *window = QApplication::activeWindow(); KMessageBox::sorry(window, error); // window can be null, is allowed } diff --git a/krusader/FileSystem/virtualfilesystem.h b/krusader/FileSystem/virtualfilesystem.h index c6b1bc0e..1d43b273 100644 --- a/krusader/FileSystem/virtualfilesystem.h +++ b/krusader/FileSystem/virtualfilesystem.h @@ -1,105 +1,105 @@ /***************************************************************************** * Copyright (C) 2003 Shie Erlich * * Copyright (C) 2003 Rafi Yanai * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This package is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this package; if not, write to the Free Software * * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *****************************************************************************/ #ifndef VIRTUALFILESYSTEM_H #define VIRTUALFILESYSTEM_H // QtCore #include #include #include "filesystem.h" /** * Custom virtual filesystem implementation: It holds arbitrary lists of files which are only * virtual references to real files. The filename of a virtual file is the full path of the real * file. * * Only two filesystem levels are supported: On root level only directories can be created; these * virtual root directories can contain a set of virtual files and directories. Entering a directory * on the sublevel is out of scope and the real directory will be opened. * * The filesystem content is saved in a separate config file and preserved between application runs. * * Used at least by bookmarks, locate, search and synchronizer dialog. */ class VirtualFileSystem : public FileSystem { Q_OBJECT public: VirtualFileSystem(); /// Create virtual files in this filesystem. Copy mode and showProgressInfo are ignored. void copyFiles(const QList &urls, const QUrl &destination, KIO::CopyJob::CopyMode mode = KIO::CopyJob::Copy, bool showProgressInfo = false, JobMan::StartMode startMode = JobMan::Start) Q_DECL_OVERRIDE; /// Handle file dropping in this filesystem: Always creates virtual files. void dropFiles(const QUrl &destination, QDropEvent *event) Q_DECL_OVERRIDE; /// Add virtual files to the current directory. void addFiles(const QList &fileUrls, KIO::CopyJob::CopyMode mode = KIO::CopyJob::Copy, - QString dir = "") Q_DECL_OVERRIDE; + const QString &dir = "") Q_DECL_OVERRIDE; /// Create a virtual directory. Only possible in the root directory. void mkDir(const QString &name) Q_DECL_OVERRIDE; /// Rename a (real) file in the current directory. void rename(const QString &fileName, const QString &newName) Q_DECL_OVERRIDE; /// Returns the URL of the real file or an empty URL if file with name does not exist. QUrl getUrl(const QString& name) const Q_DECL_OVERRIDE; bool canMoveToTrash(const QStringList &fileNames) const Q_DECL_OVERRIDE; /// Remove virtual files or directories. Real files stay untouched. void remove(const QStringList &fileNames); /// Set meta information to be displayed in UI for the current directory void setMetaInformation(const QString &info); protected: bool refreshInternal(const QUrl &origin, bool onlyScan) Q_DECL_OVERRIDE; private: /// Return current dir: "/" or pure directory name inline QString currentDir() { const QString path = _currentDirectory.path().mid(1); // remove slash return path.isEmpty() ? "/" : path; } void mkDirInternal(const QString& name); /// Save the dictionary to file void save(); /// Restore the dictionary from file void restore(); /// Create local or KIO fileItem. Returns 0 if file does not exist FileItem *createFileItem(const QUrl &url); /// Return the configuration file storing the urls of virtual files KConfig &getVirtDB(); private slots: void slotStatResult(KJob *job); private: void showError(const QString &error); static QHash *> _virtFilesystemDict; // map virtual directories to containing files static QHash _metaInfoDict; // map virtual directories to meta info QString _metaInfo; // displayable string with information about the current virtual directory KIO::UDSEntry _fileEntry; // for async call, save stat job result here }; #endif