File indexing completed on 2024-05-05 17:56:57

0001 /*
0002     SPDX-FileCopyrightText: 2003 Shie Erlich <erlich@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2003 Rafi Yanai <yanai@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "virtualfilesystem.h"
0010 
0011 // QtCore
0012 #include <QDir>
0013 #include <QEventLoop>
0014 #include <QUrl>
0015 // QtWidgets
0016 #include <QApplication>
0017 
0018 #include <KCoreAddons/KUrlMimeData>
0019 #include <KI18n/KLocalizedString>
0020 #include <KIO/CopyJob>
0021 #include <KIO/DeleteJob>
0022 #include <KIO/DirectorySizeJob>
0023 #include <KIO/StatJob>
0024 #include <KIOCore/KFileItem>
0025 #include <KWidgetsAddons/KMessageBox>
0026 
0027 #include "../defaults.h"
0028 #include "../krglobal.h"
0029 #include "../krservices.h"
0030 #include "fileitem.h"
0031 
0032 #define VIRTUALFILESYSTEM_DB "virtualfilesystem.db"
0033 
0034 QHash<QString, QList<QUrl> *> VirtualFileSystem::_virtFilesystemDict;
0035 QHash<QString, QString> VirtualFileSystem::_metaInfoDict;
0036 
0037 VirtualFileSystem::VirtualFileSystem()
0038 {
0039     if (_virtFilesystemDict.isEmpty()) {
0040         restore();
0041     }
0042 
0043     _type = FS_VIRTUAL;
0044 }
0045 
0046 void VirtualFileSystem::copyFiles(const QList<QUrl> &urls,
0047                                   const QUrl &destination,
0048                                   KIO::CopyJob::CopyMode /*mode*/,
0049                                   bool /*showProgressInfo*/,
0050                                   JobMan::StartMode /*startMode*/)
0051 {
0052     const QString dir = QDir(destination.path()).absolutePath().remove('/');
0053 
0054     if (dir.isEmpty()) {
0055         showError(
0056             i18n("You cannot copy files directly to the 'virt:/' folder.\n"
0057                  "You can create a sub folder and copy your files into it."));
0058         return;
0059     }
0060 
0061     if (!_virtFilesystemDict.contains(dir)) {
0062         mkDirInternal(dir);
0063     }
0064 
0065     QList<QUrl> *urlList = _virtFilesystemDict[dir];
0066     for (const QUrl &fileUrl : urls) {
0067         if (!urlList->contains(fileUrl)) {
0068             urlList->push_back(fileUrl);
0069         }
0070     }
0071 
0072     emit fileSystemChanged(QUrl("virt:///" + dir), false); // may call refresh()
0073 }
0074 
0075 void VirtualFileSystem::dropFiles(const QUrl &destination, QDropEvent *event)
0076 {
0077     const QList<QUrl> &urls = KUrlMimeData::urlsFromMimeData(event->mimeData());
0078     // dropping on virtual filesystem is always copy operation
0079     copyFiles(urls, destination);
0080 }
0081 
0082 void VirtualFileSystem::addFiles(const QList<QUrl> &fileUrls, KIO::CopyJob::CopyMode /*mode*/, const QString &dir)
0083 {
0084     QUrl destination(_currentDirectory);
0085     if (!dir.isEmpty()) {
0086         destination.setPath(QDir::cleanPath(destination.path() + '/' + dir));
0087     }
0088     copyFiles(fileUrls, destination);
0089 }
0090 
0091 void VirtualFileSystem::remove(const QStringList &fileNames)
0092 {
0093     const QString parentDir = currentDir();
0094     if (parentDir == "/") { // remove virtual directory
0095         for (const QString &filename : fileNames) {
0096             _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + filename));
0097             delete _virtFilesystemDict[filename];
0098             _virtFilesystemDict.remove(filename);
0099             _metaInfoDict.remove(filename);
0100         }
0101     } else {
0102         // remove the URLs from the collection
0103         for (const QString &name : fileNames) {
0104             if (_virtFilesystemDict.find(parentDir) != _virtFilesystemDict.end()) {
0105                 QList<QUrl> *urlList = _virtFilesystemDict[parentDir];
0106                 urlList->removeAll(getUrl(name));
0107             }
0108         }
0109     }
0110 
0111     emit fileSystemChanged(currentDirectory(), true); // will call refresh()
0112 }
0113 
0114 QUrl VirtualFileSystem::getUrl(const QString &name) const
0115 {
0116     FileItem *item = getFileItem(name);
0117     if (!item) {
0118         return QUrl(); // not found
0119     }
0120 
0121     return item->getUrl();
0122 }
0123 
0124 void VirtualFileSystem::mkDir(const QString &name)
0125 {
0126     if (currentDir() != "/") {
0127         showError(i18n("Creating new folders is allowed only in the 'virt:/' folder."));
0128         return;
0129     }
0130 
0131     mkDirInternal(name);
0132 
0133     emit fileSystemChanged(currentDirectory(), false); // will call refresh()
0134 }
0135 
0136 void VirtualFileSystem::rename(const QString &fileName, const QString &newName)
0137 {
0138     FileItem *item = getFileItem(fileName);
0139     if (!item)
0140         return; // not found
0141 
0142     if (currentDir() == "/") { // rename virtual directory
0143         _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + newName));
0144         _virtFilesystemDict["/"]->removeAll(QUrl(QStringLiteral("virt:/") + fileName));
0145         _virtFilesystemDict.insert(newName, _virtFilesystemDict.take(fileName));
0146         refresh();
0147         return;
0148     }
0149 
0150     // newName can be a (local) path or a full url
0151     QUrl dest(newName);
0152     if (dest.scheme().isEmpty())
0153         dest.setScheme("file");
0154 
0155     // add the new url to the list
0156     // the list is refreshed, only existing files remain -
0157     // so we don't have to worry if the job was successful
0158     _virtFilesystemDict[currentDir()]->append(dest);
0159 
0160     KIO::Job *job = KIO::moveAs(item->getUrl(), dest, KIO::HideProgressInfo);
0161     connect(job, &KIO::Job::result, this, [=](KJob *job) {
0162         slotJobResult(job, false);
0163     });
0164     connect(job, &KIO::Job::result, this, [=]() {
0165         emit fileSystemChanged(currentDirectory(), false);
0166     });
0167 }
0168 
0169 bool VirtualFileSystem::canMoveToTrash(const QStringList &fileNames) const
0170 {
0171     if (isRoot())
0172         return false;
0173 
0174     for (const QString &fileName : fileNames) {
0175         if (!getUrl(fileName).isLocalFile()) {
0176             return false;
0177         }
0178     }
0179     return true;
0180 }
0181 
0182 void VirtualFileSystem::setMetaInformation(const QString &info)
0183 {
0184     _metaInfoDict[currentDir()] = info;
0185 }
0186 
0187 // ==== protected ====
0188 
0189 bool VirtualFileSystem::refreshInternal(const QUrl &directory, bool onlyScan)
0190 {
0191     _currentDirectory = cleanUrl(directory);
0192     _currentDirectory.setHost("");
0193     // remove invalid subdirectories
0194     _currentDirectory.setPath('/' + _currentDirectory.path().remove('/'));
0195 
0196     if (!_virtFilesystemDict.contains(currentDir())) {
0197         if (onlyScan) {
0198             return false; // virtual dir does not exist
0199         } else {
0200             // Silently creating non-existing directories here. The search and locate tools
0201             // expect this. And the user can enter some directory and it will be created.
0202             mkDirInternal(currentDir());
0203             save();
0204             // infinite loop possible
0205             // emit fileSystemChanged(currentDirectory());
0206             return true;
0207         }
0208     }
0209 
0210     QList<QUrl> *urlList = _virtFilesystemDict[currentDir()];
0211 
0212     if (!onlyScan) {
0213         const QString metaInfo = _metaInfoDict[currentDir()];
0214         emit fileSystemInfoChanged(metaInfo.isEmpty() ? i18n("Virtual filesystem") : metaInfo, "", 0, 0);
0215     }
0216 
0217     QMutableListIterator<QUrl> it(*urlList);
0218     while (it.hasNext()) {
0219         const QUrl url = it.next();
0220         FileItem *item = createFileItem(url);
0221         if (!item) { // remove URL from the list for a file that no longer exists
0222             it.remove();
0223         } else {
0224             addFileItem(item);
0225         }
0226     }
0227 
0228     save();
0229     return true;
0230 }
0231 
0232 // ==== private ====
0233 
0234 void VirtualFileSystem::mkDirInternal(const QString &name)
0235 {
0236     // clean path, consistent with currentDir()
0237     QString dirName = name;
0238     dirName = dirName.remove('/');
0239     if (dirName.isEmpty())
0240         dirName = '/';
0241 
0242     _virtFilesystemDict.insert(dirName, new QList<QUrl>());
0243     _virtFilesystemDict["/"]->append(QUrl(QStringLiteral("virt:/") + dirName));
0244 }
0245 
0246 void VirtualFileSystem::save()
0247 {
0248     KConfig *db = &VirtualFileSystem::getVirtDB();
0249     db->deleteGroup("virt_db");
0250     KConfigGroup group(db, "virt_db");
0251 
0252     QHashIterator<QString, QList<QUrl> *> it(_virtFilesystemDict);
0253     while (it.hasNext()) {
0254         it.next();
0255         QList<QUrl> *urlList = it.value();
0256 
0257         QList<QUrl>::iterator url;
0258         QStringList entry;
0259         for (url = urlList->begin(); url != urlList->end(); ++url) {
0260             entry.append((*url).toDisplayString());
0261         }
0262         // KDE 4.0 workaround: 'Item_' prefix is added as KConfig fails on 1 char names (such as /)
0263         group.writeEntry("Item_" + it.key(), entry);
0264         group.writeEntry("MetaInfo_" + it.key(), _metaInfoDict[it.key()]);
0265     }
0266 
0267     db->sync();
0268 }
0269 
0270 void VirtualFileSystem::restore()
0271 {
0272     KConfig *db = &VirtualFileSystem::getVirtDB();
0273     const KConfigGroup dbGrp(db, "virt_db");
0274 
0275     const QMap<QString, QString> map = db->entryMap("virt_db");
0276     QMapIterator<QString, QString> it(map);
0277     while (it.hasNext()) {
0278         it.next();
0279 
0280         // KDE 4.0 workaround: check and remove 'Item_' prefix
0281         if (!it.key().startsWith(QLatin1String("Item_")))
0282             continue;
0283         const QString key = it.key().mid(5);
0284 
0285         const QList<QUrl> urlList = KrServices::toUrlList(dbGrp.readEntry(it.key(), QStringList()));
0286         _virtFilesystemDict.insert(key, new QList<QUrl>(urlList));
0287         _metaInfoDict.insert(key, dbGrp.readEntry("MetaInfo_" + key, QString()));
0288     }
0289 
0290     if (!_virtFilesystemDict["/"]) { // insert root element if missing for some reason
0291         _virtFilesystemDict.insert("/", new QList<QUrl>());
0292     }
0293 }
0294 
0295 FileItem *VirtualFileSystem::createFileItem(const QUrl &url)
0296 {
0297     if (url.scheme() == "virt") { // return a virtual directory in root
0298         QString path = url.path().mid(1);
0299         if (path.isEmpty())
0300             path = '/';
0301         return FileItem::createVirtualDir(path, url);
0302     }
0303 
0304     const QUrl directory = url.adjusted(QUrl::RemoveFilename);
0305 
0306     if (url.isLocalFile()) {
0307         QFileInfo file(url.path());
0308         return file.exists() ? FileSystem::createLocalFileItem(url.fileName(), directory.path(), true) : nullptr;
0309     }
0310 
0311     KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
0312     connect(statJob, &KIO::Job::result, this, &VirtualFileSystem::slotStatResult);
0313 
0314     // ugly: we have to wait here until the stat job is finished
0315     QEventLoop eventLoop;
0316     connect(statJob, &KJob::finished, &eventLoop, &QEventLoop::quit);
0317     eventLoop.exec(); // blocking until quit()
0318 
0319     if (_fileEntry.count() == 0) {
0320         return nullptr; // stat job failed
0321     }
0322 
0323     if (!_fileEntry.contains(KIO::UDSEntry::UDS_MODIFICATION_TIME)) {
0324         // TODO this also happens for FTP directories
0325         return nullptr; // file not found
0326     }
0327 
0328     return FileSystem::createFileItemFromKIO(_fileEntry, directory, true);
0329 }
0330 
0331 KConfig &VirtualFileSystem::getVirtDB()
0332 {
0333     // virtualfilesystem_db = new KConfig("data",VIRTUALFILESYSTEM_DB,KConfig::NoGlobals);
0334     static KConfig db(VIRTUALFILESYSTEM_DB, KConfig::CascadeConfig, QStandardPaths::AppDataLocation);
0335     return db;
0336 }
0337 
0338 void VirtualFileSystem::slotStatResult(KJob *job)
0339 {
0340     _fileEntry = job->error() ? KIO::UDSEntry() : dynamic_cast<KIO::StatJob *>(job)->statResult();
0341 }
0342 
0343 void VirtualFileSystem::showError(const QString &error)
0344 {
0345     QWidget *window = QApplication::activeWindow();
0346     KMessageBox::error(window, error); // window can be null, is allowed
0347 }