File indexing completed on 2024-05-05 16:13:53

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