File indexing completed on 2024-05-05 03:56:08

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kio_trash.h"
0009 #include "../../utils_p.h"
0010 #include "kiotrashdebug.h"
0011 #include "transferjob.h"
0012 
0013 #include <KDirNotify>
0014 #include <kio/jobuidelegateextension.h>
0015 
0016 #include <KLocalizedString>
0017 
0018 #include <QCoreApplication>
0019 #include <QDataStream>
0020 #include <QEventLoop>
0021 #include <QFile>
0022 #include <QJsonDocument>
0023 #include <QJsonObject>
0024 #include <QMimeDatabase>
0025 #include <QMimeType>
0026 
0027 #include <grp.h>
0028 #include <pwd.h>
0029 #include <sys/stat.h>
0030 #include <sys/types.h>
0031 
0032 // Pseudo plugin class to embed meta data
0033 class KIOPluginForMetaData : public QObject
0034 {
0035     Q_OBJECT
0036     Q_PLUGIN_METADATA(IID "org.kde.kio.worker.trash" FILE "trash.json")
0037 };
0038 
0039 extern "C" {
0040 int Q_DECL_EXPORT kdemain(int argc, char **argv)
0041 {
0042     // necessary to use other KIO workers
0043     QCoreApplication app(argc, argv);
0044 
0045     KIO::setDefaultJobUiDelegateExtension(nullptr);
0046     // start the worker
0047     TrashProtocol worker(argv[1], argv[2], argv[3]);
0048     worker.dispatchLoop();
0049     return 0;
0050 }
0051 }
0052 
0053 static bool isTopLevelEntry(const QUrl &url)
0054 {
0055     const QString dir = url.adjusted(QUrl::RemoveFilename).path();
0056     return dir.length() <= 1;
0057 }
0058 
0059 TrashProtocol::TrashProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app)
0060     : WorkerBase(protocol, pool, app)
0061 {
0062     m_userId = getuid();
0063     struct passwd *user = getpwuid(m_userId);
0064     if (user) {
0065         m_userName = QString::fromLatin1(user->pw_name);
0066     }
0067     m_groupId = getgid();
0068     struct group *grp = getgrgid(m_groupId);
0069     if (grp) {
0070         m_groupName = QString::fromLatin1(grp->gr_name);
0071     }
0072 }
0073 
0074 TrashProtocol::~TrashProtocol()
0075 {
0076 }
0077 
0078 KIO::WorkerResult TrashProtocol::initImpl()
0079 {
0080     if (!impl.init()) {
0081         return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0082     }
0083 
0084     return KIO::WorkerResult::pass();
0085 }
0086 
0087 KIO::WorkerResult TrashProtocol::enterLoop()
0088 {
0089     int errorId = 0;
0090     QString errorText;
0091 
0092     QEventLoop eventLoop;
0093     connect(this, &TrashProtocol::leaveModality, &eventLoop, [&](int _errorId, const QString &_errorText) {
0094         errorId = _errorId;
0095         errorText = _errorText;
0096         eventLoop.quit();
0097     });
0098     eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
0099 
0100     if (errorId != 0) {
0101         return KIO::WorkerResult::fail(errorId, errorText);
0102     }
0103     return KIO::WorkerResult::pass();
0104 }
0105 
0106 KIO::WorkerResult TrashProtocol::restore(const QUrl &trashURL)
0107 {
0108     int trashId;
0109     QString fileId;
0110     QString relativePath;
0111     bool ok = TrashImpl::parseURL(trashURL, trashId, fileId, relativePath);
0112     if (!ok) {
0113         return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", trashURL.toString()));
0114     }
0115     TrashedFileInfo info;
0116     ok = impl.infoForFile(trashId, fileId, info);
0117     if (!ok) {
0118         return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0119     }
0120     QUrl dest = QUrl::fromLocalFile(info.origPath);
0121     if (!relativePath.isEmpty()) {
0122         dest.setPath(Utils::concatPaths(dest.path(), relativePath));
0123     }
0124 
0125     // Check that the destination directory exists, to improve the error code in case it doesn't.
0126     const QString destDir = dest.adjusted(QUrl::RemoveFilename).path();
0127     QT_STATBUF buff;
0128 
0129     if (QT_LSTAT(QFile::encodeName(destDir).constData(), &buff) == -1) {
0130         return KIO::WorkerResult::fail(
0131             KIO::ERR_WORKER_DEFINED,
0132             i18n("The directory %1 does not exist anymore, so it is not possible to restore this item to its original location. "
0133                  "You can either recreate that directory and use the restore operation again, or drag the item anywhere else to restore it.",
0134                  destDir));
0135     }
0136 
0137     return copyOrMoveFromTrash(trashURL, dest, false /*overwrite*/, Move);
0138 }
0139 
0140 KIO::WorkerResult TrashProtocol::rename(const QUrl &oldURL, const QUrl &newURL, KIO::JobFlags flags)
0141 {
0142     if (const auto initResult = initImpl(); !initResult.success()) {
0143         return initResult;
0144     }
0145 
0146     qCDebug(KIO_TRASH) << "TrashProtocol::rename(): old=" << oldURL << " new=" << newURL << " overwrite=" << (flags & KIO::Overwrite);
0147 
0148     if (oldURL.scheme() == QLatin1String("trash") && newURL.scheme() == QLatin1String("trash")) {
0149         if (!isTopLevelEntry(oldURL) || !isTopLevelEntry(newURL)) {
0150             return KIO::WorkerResult::fail(KIO::ERR_CANNOT_RENAME, oldURL.toString());
0151         }
0152         int oldTrashId;
0153         QString oldFileId;
0154         QString oldRelativePath;
0155         bool oldOk = TrashImpl::parseURL(oldURL, oldTrashId, oldFileId, oldRelativePath);
0156         if (!oldOk) {
0157             return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", oldURL.toString()));
0158         }
0159         if (!oldRelativePath.isEmpty()) {
0160             return KIO::WorkerResult::fail(KIO::ERR_CANNOT_RENAME, oldURL.toString());
0161         }
0162         // Dolphin/KIO can't specify a trashid in the new URL so here path == filename
0163         // bool newOk = TrashImpl::parseURL(newURL, newTrashId, newFileId, newRelativePath);
0164         const QString newFileId = newURL.path().mid(1);
0165         if (newFileId.contains(QLatin1Char('/'))) {
0166             return KIO::WorkerResult::fail(KIO::ERR_CANNOT_RENAME, oldURL.toString());
0167         }
0168         bool ok = impl.moveInTrash(oldTrashId, oldFileId, newFileId);
0169         if (!ok) {
0170             return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0171         }
0172         const QUrl finalUrl = TrashImpl::makeURL(oldTrashId, newFileId, QString());
0173         org::kde::KDirNotify::emitFileRenamed(oldURL, finalUrl);
0174         return KIO::WorkerResult::pass();
0175     }
0176 
0177     if (oldURL.scheme() == QLatin1String("trash") && newURL.isLocalFile()) {
0178         return copyOrMoveFromTrash(oldURL, newURL, (flags & KIO::Overwrite), Move);
0179     }
0180     if (oldURL.isLocalFile() && newURL.scheme() == QLatin1String("trash")) {
0181         return copyOrMoveToTrash(oldURL, newURL, Move);
0182     }
0183     return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, i18n("Invalid combination of protocols."));
0184 }
0185 
0186 KIO::WorkerResult TrashProtocol::copy(const QUrl &src, const QUrl &dest, int /*permissions*/, KIO::JobFlags flags)
0187 {
0188     if (const auto initResult = initImpl(); !initResult.success()) {
0189         return initResult;
0190     }
0191 
0192     qCDebug(KIO_TRASH) << "TrashProtocol::copy(): " << src << " " << dest;
0193 
0194     if (src.scheme() == QLatin1String("trash") && dest.scheme() == QLatin1String("trash")) {
0195         return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, i18n("This file is already in the trash bin."));
0196     }
0197 
0198     if (src.scheme() == QLatin1String("trash") && dest.isLocalFile()) {
0199         return copyOrMoveFromTrash(src, dest, (flags & KIO::Overwrite), Copy);
0200     }
0201     if (src.isLocalFile() && dest.scheme() == QLatin1String("trash")) {
0202         return copyOrMoveToTrash(src, dest, Copy);
0203     }
0204     return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, i18n("Invalid combination of protocols."));
0205 }
0206 
0207 KIO::WorkerResult TrashProtocol::copyOrMoveFromTrash(const QUrl &src, const QUrl &dest, bool overwrite, CopyOrMove action)
0208 {
0209     // Extracting (e.g. via dnd). Ignore original location stored in info file.
0210     int trashId;
0211     QString fileId;
0212     QString relativePath;
0213     bool ok = TrashImpl::parseURL(src, trashId, fileId, relativePath);
0214     if (!ok) {
0215         return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", src.toString()));
0216     }
0217     const QString destPath = dest.path();
0218     if (QFile::exists(destPath)) {
0219         if (overwrite) {
0220             ok = QFile::remove(destPath);
0221             Q_ASSERT(ok); // ### TODO
0222         } else {
0223             return KIO::WorkerResult::fail(KIO::ERR_FILE_ALREADY_EXIST, destPath);
0224         }
0225     }
0226 
0227     if (action == Move) {
0228         qCDebug(KIO_TRASH) << "calling moveFromTrash(" << destPath << " " << trashId << " " << fileId << ")";
0229         ok = impl.moveFromTrash(destPath, trashId, fileId, relativePath);
0230     } else { // Copy
0231         qCDebug(KIO_TRASH) << "calling copyFromTrash(" << destPath << " " << trashId << " " << fileId << ")";
0232         ok = impl.copyFromTrash(destPath, trashId, fileId, relativePath);
0233     }
0234     if (!ok) {
0235         return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0236     }
0237 
0238     if (action == Move && relativePath.isEmpty()) {
0239         (void)impl.deleteInfo(trashId, fileId);
0240     }
0241     return KIO::WorkerResult::pass();
0242 }
0243 
0244 KIO::WorkerResult TrashProtocol::copyOrMoveToTrash(const QUrl &src, const QUrl &dest, CopyOrMove action)
0245 {
0246     qCDebug(KIO_TRASH) << "trashing a file" << src << dest;
0247 
0248     // Trashing a file
0249     // We detect the case where this isn't normal trashing, but
0250     // e.g. if kwrite tries to save (moving tempfile over destination)
0251     if (isTopLevelEntry(dest) && src.fileName() == dest.fileName()) { // new toplevel entry
0252         const QString srcPath = src.path();
0253         // In theory we should use TrashImpl::parseURL to give the right filename to createInfo,
0254         // in case the trash URL didn't contain the same filename as srcPath.
0255         // But this can only happen with copyAs/moveAs, not available in the GUI
0256         // for the trash (New/... or Rename from iconview/listview).
0257         int trashId;
0258         QString fileId;
0259         if (!impl.createInfo(srcPath, trashId, fileId)) {
0260             return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0261         }
0262         bool ok;
0263         if (action == Move) {
0264             qCDebug(KIO_TRASH) << "calling moveToTrash(" << srcPath << " " << trashId << " " << fileId << ")";
0265             ok = impl.moveToTrash(srcPath, trashId, fileId);
0266         } else { // Copy
0267             qCDebug(KIO_TRASH) << "calling copyToTrash(" << srcPath << " " << trashId << " " << fileId << ")";
0268             ok = impl.copyToTrash(srcPath, trashId, fileId);
0269         }
0270         if (!ok) {
0271             (void)impl.deleteInfo(trashId, fileId);
0272             return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0273         }
0274         // Inform caller of the final URL. Used by konq_undo.
0275         const QUrl url = impl.makeURL(trashId, fileId, QString());
0276         setMetaData(QLatin1String("trashURL-") + srcPath, url.url());
0277         return KIO::WorkerResult::pass();
0278     }
0279 
0280     qCDebug(KIO_TRASH) << "returning KIO::ERR_ACCESS_DENIED, it's not allowed to add a file to an existing trash directory";
0281     // It's not allowed to add a file to an existing trash directory.
0282     return KIO::WorkerResult::fail(KIO::ERR_ACCESS_DENIED, dest.toString());
0283 }
0284 
0285 void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry &entry)
0286 {
0287     entry.reserve(entry.count() + 8);
0288     entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
0289     entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Trash"));
0290     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
0291     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700);
0292     entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
0293     entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, impl.isEmpty() ? QStringLiteral("user-trash") : QStringLiteral("user-trash-full"));
0294     entry.fastInsert(KIO::UDSEntry::UDS_USER, m_userName);
0295     entry.fastInsert(KIO::UDSEntry::UDS_GROUP, m_groupName);
0296     entry.fastInsert(KIO::UDSEntry::UDS_LOCAL_USER_ID, m_userId);
0297     entry.fastInsert(KIO::UDSEntry::UDS_LOCAL_GROUP_ID, m_groupId);
0298 }
0299 
0300 KIO::StatDetails TrashProtocol::getStatDetails()
0301 {
0302     const QString statDetails = metaData(QStringLiteral("details"));
0303     return statDetails.isEmpty() ? KIO::StatDefaultDetails : static_cast<KIO::StatDetails>(statDetails.toInt());
0304 }
0305 
0306 KIO::WorkerResult TrashProtocol::stat(const QUrl &url)
0307 {
0308     if (const auto initResult = initImpl(); !initResult.success()) {
0309         return initResult;
0310     }
0311 
0312     const QString path = url.path();
0313     if (path.isEmpty() || path == QLatin1String("/")) {
0314         // The root is "virtual" - it's not a single physical directory
0315         KIO::UDSEntry entry = impl.trashUDSEntry(getStatDetails());
0316         createTopLevelDirEntry(entry);
0317         statEntry(entry);
0318     } else {
0319         int trashId;
0320         QString fileId;
0321         QString relativePath;
0322 
0323         bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
0324 
0325         if (!ok) {
0326             // ######## do we still need this?
0327             qCDebug(KIO_TRASH) << url << " looks fishy, returning does-not-exist";
0328             // A URL like trash:/file simply means that CopyJob is trying to see if
0329             // the destination exists already (it made up the URL by itself).
0330             // error( KIO::ERR_WORKER_DEFINED, i18n( "Malformed URL %1" ).arg( url.toString() ) );
0331             return KIO::WorkerResult::fail(KIO::ERR_DOES_NOT_EXIST, url.toString());
0332         }
0333 
0334         qCDebug(KIO_TRASH) << "parsed" << url << "got" << trashId << fileId << relativePath;
0335 
0336         const QString filePath = impl.physicalPath(trashId, fileId, relativePath);
0337         if (filePath.isEmpty()) {
0338             return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0339         }
0340 
0341         // For a toplevel file, use the fileId as display name (to hide the trashId)
0342         // For a file in a subdir, use the fileName as is.
0343         QString fileDisplayName = relativePath.isEmpty() ? fileId : url.fileName();
0344 
0345         QUrl fileURL;
0346         if (url.path().length() > 1) {
0347             fileURL = url;
0348         }
0349 
0350         KIO::UDSEntry entry;
0351         TrashedFileInfo info;
0352         ok = impl.infoForFile(trashId, fileId, info);
0353         if (ok) {
0354             ok = createUDSEntry(filePath, fileDisplayName, fileURL.fileName(), entry, info);
0355         }
0356 
0357         if (!ok) {
0358             return KIO::WorkerResult::fail(KIO::ERR_CANNOT_STAT, url.toString());
0359         }
0360 
0361         statEntry(entry);
0362     }
0363     return KIO::WorkerResult::pass();
0364 }
0365 
0366 KIO::WorkerResult TrashProtocol::del(const QUrl &url, bool /*isfile*/)
0367 {
0368     if (const auto initResult = initImpl(); !initResult.success()) {
0369         return initResult;
0370     }
0371 
0372     int trashId;
0373     QString fileId;
0374     QString relativePath;
0375 
0376     bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
0377     if (!ok) {
0378         return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", url.toString()));
0379     }
0380 
0381     ok = relativePath.isEmpty();
0382     if (!ok) {
0383         return KIO::WorkerResult::fail(KIO::ERR_ACCESS_DENIED, url.toString());
0384     }
0385 
0386     ok = impl.del(trashId, fileId);
0387     if (!ok) {
0388         return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0389     }
0390 
0391     return KIO::WorkerResult::pass();
0392 }
0393 
0394 KIO::WorkerResult TrashProtocol::listDir(const QUrl &url)
0395 {
0396     if (const auto initResult = initImpl(); !initResult.success()) {
0397         return initResult;
0398     }
0399 
0400     qCDebug(KIO_TRASH) << "listdir: " << url;
0401     const QString path = url.path();
0402     if (path.isEmpty() || path == QLatin1String("/")) {
0403         return listRoot();
0404     }
0405     int trashId;
0406     QString fileId;
0407     QString relativePath;
0408     bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
0409     if (!ok) {
0410         return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", url.toString()));
0411     }
0412     // was: const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath );
0413 
0414     // Get info for deleted directory - the date of deletion and orig path will be used
0415     // for all the items in it, and we need the physicalPath.
0416     TrashedFileInfo info;
0417     ok = impl.infoForFile(trashId, fileId, info);
0418     if (!ok || info.physicalPath.isEmpty()) {
0419         return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0420     }
0421     if (!relativePath.isEmpty()) {
0422         info.physicalPath += QLatin1Char('/') + relativePath;
0423     }
0424 
0425     // List subdir. Can't use kio_file here since we provide our own info...
0426     qCDebug(KIO_TRASH) << "listing " << info.physicalPath;
0427     const QStringList entryNames = impl.listDir(info.physicalPath);
0428     totalSize(entryNames.count());
0429     KIO::UDSEntry entry;
0430     for (const QString &fileName : entryNames) {
0431         if (fileName == QLatin1String("..")) {
0432             continue;
0433         }
0434         const QString filePath = info.physicalPath + QLatin1Char('/') + fileName;
0435         // shouldn't be necessary
0436         // const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + '/' + fileName );
0437         entry.clear();
0438         TrashedFileInfo infoForItem(info);
0439         infoForItem.origPath += QLatin1Char('/') + fileName;
0440         if (createUDSEntry(filePath, fileName, fileName, entry, infoForItem)) {
0441             listEntry(entry);
0442         }
0443     }
0444     entry.clear();
0445     return KIO::WorkerResult::pass();
0446 }
0447 
0448 bool TrashProtocol::createUDSEntry(const QString &physicalPath,
0449                                    const QString &displayFileName,
0450                                    const QString &internalFileName,
0451                                    KIO::UDSEntry &entry,
0452                                    const TrashedFileInfo &info)
0453 {
0454     entry.reserve(14);
0455     QByteArray physicalPath_c = QFile::encodeName(physicalPath);
0456     QT_STATBUF buff;
0457     if (QT_LSTAT(physicalPath_c.constData(), &buff) == -1) {
0458         qCWarning(KIO_TRASH) << "couldn't stat " << physicalPath << ", relevant trashinfo file will be removed";
0459         impl.deleteInfo(info.trashId, info.fileId);
0460         return false;
0461     }
0462     if (S_ISLNK(buff.st_mode)) {
0463         char buffer2[1000];
0464         int n = ::readlink(physicalPath_c.constData(), buffer2, 999);
0465         if (n != -1) {
0466             buffer2[n] = 0;
0467         }
0468 
0469         // this does not follow symlink on purpose
0470         entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, QFile::decodeName(buffer2));
0471     }
0472 
0473     mode_t type = buff.st_mode & S_IFMT; // extract file type
0474     mode_t access = buff.st_mode & 07777; // extract permissions
0475     access &= 07555; // make it readonly, since it's in the trashcan
0476     Q_ASSERT(!internalFileName.isEmpty());
0477     entry.fastInsert(KIO::UDSEntry::UDS_NAME, internalFileName); // internal filename, like "0-foo"
0478     entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, displayFileName); // user-visible filename, like "foo"
0479     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, type);
0480     entry.fastInsert(KIO::UDSEntry::UDS_LOCAL_PATH, physicalPath);
0481     // if ( !url.isEmpty() )
0482     //    entry.insert( KIO::UDSEntry::UDS_URL, url );
0483 
0484     QMimeDatabase db;
0485     QMimeType mt = db.mimeTypeForFile(physicalPath);
0486     if (mt.isValid()) {
0487         entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, mt.name());
0488     }
0489     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, access);
0490     entry.fastInsert(KIO::UDSEntry::UDS_SIZE, buff.st_size);
0491     entry.fastInsert(KIO::UDSEntry::UDS_USER, m_userName); // assumption
0492     entry.fastInsert(KIO::UDSEntry::UDS_GROUP, m_groupName); // assumption
0493     entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, buff.st_mtime);
0494     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, buff.st_atime); // ## or use it for deletion time?
0495     entry.fastInsert(KIO::UDSEntry::UDS_EXTRA, info.origPath);
0496     entry.fastInsert(KIO::UDSEntry::UDS_EXTRA + 1, info.deletionDate.toString(Qt::ISODate));
0497     return true;
0498 }
0499 
0500 KIO::WorkerResult TrashProtocol::listRoot()
0501 {
0502     if (const auto initResult = initImpl(); !initResult.success()) {
0503         return initResult;
0504     }
0505 
0506     const TrashedFileInfoList lst = impl.list();
0507     totalSize(lst.count());
0508     KIO::UDSEntry entry;
0509     createTopLevelDirEntry(entry);
0510     listEntry(entry);
0511     for (const TrashedFileInfo &fileInfo : lst) {
0512         const QUrl url = TrashImpl::makeURL(fileInfo.trashId, fileInfo.fileId, QString());
0513         entry.clear();
0514         const QString fileDisplayName = fileInfo.fileId;
0515 
0516         if (createUDSEntry(fileInfo.physicalPath, fileDisplayName, url.fileName(), entry, fileInfo)) {
0517             listEntry(entry);
0518         }
0519     }
0520     entry.clear();
0521     return KIO::WorkerResult::pass();
0522 }
0523 
0524 KIO::WorkerResult TrashProtocol::special(const QByteArray &data)
0525 {
0526     if (const auto initResult = initImpl(); !initResult.success()) {
0527         return initResult;
0528     }
0529 
0530     QDataStream stream(data);
0531     int cmd;
0532     stream >> cmd;
0533 
0534     switch (cmd) {
0535     case 1:
0536         if (!impl.emptyTrash()) {
0537             return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0538         }
0539         break;
0540     case 2:
0541         impl.migrateOldTrash();
0542         break;
0543     case 3: {
0544         QUrl url;
0545         stream >> url;
0546         return restore(url);
0547     }
0548     case 4: {
0549         QJsonObject json;
0550         const auto map = impl.trashDirectories();
0551         for (auto it = map.begin(); it != map.end(); ++it) {
0552             json[QString::number(it.key())] = it.value();
0553         }
0554         setMetaData(QStringLiteral("TRASH_DIRECTORIES"), QString::fromLocal8Bit(QJsonDocument(json).toJson()));
0555         sendMetaData();
0556         break;
0557     }
0558     default:
0559         qCWarning(KIO_TRASH) << "Unknown command in special(): " << cmd;
0560         return KIO::WorkerResult::fail(KIO::ERR_UNSUPPORTED_ACTION, QString::number(cmd));
0561     }
0562     return KIO::WorkerResult::pass();
0563 }
0564 
0565 KIO::WorkerResult TrashProtocol::put(const QUrl &url, int /*permissions*/, KIO::JobFlags)
0566 {
0567     if (const auto initResult = initImpl(); !initResult.success()) {
0568         return initResult;
0569     }
0570 
0571     qCDebug(KIO_TRASH) << "put: " << url;
0572     // create deleted file. We need to get the mtime and original location from metadata...
0573     // Maybe we can find the info file for url.fileName(), in case ::rename() was called first, and failed...
0574     return KIO::WorkerResult::fail(KIO::ERR_ACCESS_DENIED, url.toString());
0575 }
0576 
0577 KIO::WorkerResult TrashProtocol::get(const QUrl &url)
0578 {
0579     if (const auto initResult = initImpl(); !initResult.success()) {
0580         return initResult;
0581     }
0582 
0583     qCDebug(KIO_TRASH) << "get() : " << url;
0584     if (!url.isValid()) {
0585         // qCDebug(KIO_TRASH) << kBacktrace();
0586         return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", url.url()));
0587     }
0588     if (url.path().length() <= 1) {
0589         return KIO::WorkerResult::fail(KIO::ERR_IS_DIRECTORY, url.toString());
0590     }
0591     int trashId;
0592     QString fileId;
0593     QString relativePath;
0594     bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
0595     if (!ok) {
0596         return KIO::WorkerResult::fail(KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", url.toString()));
0597     }
0598     const QString physicalPath = impl.physicalPath(trashId, fileId, relativePath);
0599     if (physicalPath.isEmpty()) {
0600         return KIO::WorkerResult::fail(impl.lastErrorCode(), impl.lastErrorMessage());
0601     }
0602 
0603     // Usually we run jobs in TrashImpl (for e.g. future kdedmodule)
0604     // But for this one we wouldn't use DCOP for every bit of data...
0605     QUrl fileURL = QUrl::fromLocalFile(physicalPath);
0606     KIO::TransferJob *job = KIO::get(fileURL, KIO::NoReload, KIO::HideProgressInfo);
0607     connect(job, &KIO::TransferJob::data, this, &TrashProtocol::slotData);
0608     connect(job, &KIO::TransferJob::mimeTypeFound, this, &TrashProtocol::slotMimetype);
0609     connect(job, &KJob::result, this, &TrashProtocol::jobFinished);
0610     return enterLoop();
0611 }
0612 
0613 void TrashProtocol::slotData(KIO::Job *, const QByteArray &arr)
0614 {
0615     data(arr);
0616 }
0617 
0618 void TrashProtocol::slotMimetype(KIO::Job *, const QString &mt)
0619 {
0620     mimeType(mt);
0621 }
0622 
0623 void TrashProtocol::jobFinished(KJob *job)
0624 {
0625     Q_EMIT leaveModality(job->error(), job->errorText());
0626 }
0627 
0628 KIO::WorkerResult TrashProtocol::fileSystemFreeSpace(const QUrl &url)
0629 {
0630     qCDebug(KIO_TRASH) << "fileSystemFreeSpace:" << url;
0631 
0632     if (const auto initResult = initImpl(); !initResult.success()) {
0633         return initResult;
0634     }
0635 
0636     TrashImpl::TrashSpaceInfo spaceInfo;
0637     if (!impl.trashSpaceInfo(url.path(), spaceInfo)) {
0638         return KIO::WorkerResult::fail(KIO::ERR_CANNOT_STAT, url.toDisplayString());
0639     }
0640 
0641     setMetaData(QStringLiteral("total"), QString::number(spaceInfo.totalSize));
0642     setMetaData(QStringLiteral("available"), QString::number(spaceInfo.availableSize));
0643 
0644     return KIO::WorkerResult::pass();
0645 }
0646 
0647 #include "kio_trash.moc"
0648 
0649 #include "moc_kio_trash.cpp"