1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2004 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kio_trash.h"
9#include "../../utils_p.h"
10#include "kiotrashdebug.h"
11#include "transferjob.h"
12
13#include <KDirNotify>
14#include <kio/jobuidelegateextension.h>
15
16#include <KLocalizedString>
17
18#include <QCoreApplication>
19#include <QDataStream>
20#include <QEventLoop>
21#include <QFile>
22#include <QJsonDocument>
23#include <QJsonObject>
24#include <QMimeDatabase>
25#include <QMimeType>
26
27#include <grp.h>
28#include <pwd.h>
29#include <sys/stat.h>
30#include <sys/types.h>
31
32// Pseudo plugin class to embed meta data
33class KIOPluginForMetaData : public QObject
34{
35 Q_OBJECT
36 Q_PLUGIN_METADATA(IID "org.kde.kio.worker.trash" FILE "trash.json")
37};
38
39extern "C" {
40int Q_DECL_EXPORT kdemain(int argc, char **argv)
41{
42 // necessary to use other KIO workers
43 QCoreApplication app(argc, argv);
44
45 KIO::setDefaultJobUiDelegateExtension(nullptr);
46 // start the worker
47 TrashProtocol worker(argv[1], argv[2], argv[3]);
48 worker.dispatchLoop();
49 return 0;
50}
51}
52
53static bool isTopLevelEntry(const QUrl &url)
54{
55 const QString dir = url.adjusted(options: QUrl::RemoveFilename).path();
56 return dir.length() <= 1;
57}
58
59TrashProtocol::TrashProtocol(const QByteArray &protocol, const QByteArray &pool, const QByteArray &app)
60 : WorkerBase(protocol, pool, app)
61{
62 m_userId = getuid();
63 struct passwd *user = getpwuid(uid: m_userId);
64 if (user) {
65 m_userName = QString::fromLatin1(ba: user->pw_name);
66 }
67 m_groupId = getgid();
68 struct group *grp = getgrgid(gid: m_groupId);
69 if (grp) {
70 m_groupName = QString::fromLatin1(ba: grp->gr_name);
71 }
72}
73
74TrashProtocol::~TrashProtocol()
75{
76}
77
78KIO::WorkerResult TrashProtocol::initImpl()
79{
80 if (!impl.init()) {
81 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
82 }
83
84 return KIO::WorkerResult::pass();
85}
86
87KIO::WorkerResult TrashProtocol::enterLoop()
88{
89 int errorId = 0;
90 QString errorText;
91
92 QEventLoop eventLoop;
93 connect(sender: this, signal: &TrashProtocol::leaveModality, context: &eventLoop, slot: [&](int _errorId, const QString &_errorText) {
94 errorId = _errorId;
95 errorText = _errorText;
96 eventLoop.quit();
97 });
98 eventLoop.exec(flags: QEventLoop::ExcludeUserInputEvents);
99
100 if (errorId != 0) {
101 return KIO::WorkerResult::fail(error: errorId, errorString: errorText);
102 }
103 return KIO::WorkerResult::pass();
104}
105
106KIO::WorkerResult TrashProtocol::restore(const QUrl &trashURL)
107{
108 int trashId;
109 QString fileId;
110 QString relativePath;
111 bool ok = TrashImpl::parseURL(url: trashURL, trashId, fileId, relativePath);
112 if (!ok) {
113 return KIO::WorkerResult::fail(error: KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", trashURL.toString()));
114 }
115 TrashedFileInfo info;
116 ok = impl.infoForFile(trashId, fileId, info);
117 if (!ok) {
118 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
119 }
120 QUrl dest = QUrl::fromLocalFile(localfile: info.origPath);
121 if (!relativePath.isEmpty()) {
122 dest.setPath(path: Utils::concatPaths(path1: dest.path(), path2: relativePath));
123 }
124
125 // Check that the destination directory exists, to improve the error code in case it doesn't.
126 const QString destDir = dest.adjusted(options: QUrl::RemoveFilename).path();
127 QT_STATBUF buff;
128
129 if (QT_LSTAT(file: QFile::encodeName(fileName: destDir).constData(), buf: &buff) == -1) {
130 return KIO::WorkerResult::fail(
131 error: KIO::ERR_WORKER_DEFINED,
132 i18n("The directory %1 does not exist anymore, so it is not possible to restore this item to its original location. "
133 "You can either recreate that directory and use the restore operation again, or drag the item anywhere else to restore it.",
134 destDir));
135 }
136
137 return copyOrMoveFromTrash(src: trashURL, dest, overwrite: false /*overwrite*/, action: Move);
138}
139
140KIO::WorkerResult TrashProtocol::rename(const QUrl &oldURL, const QUrl &newURL, KIO::JobFlags flags)
141{
142 if (const auto initResult = initImpl(); !initResult.success()) {
143 return initResult;
144 }
145
146 qCDebug(KIO_TRASH) << "TrashProtocol::rename(): old=" << oldURL << " new=" << newURL << " overwrite=" << (flags & KIO::Overwrite);
147
148 if (oldURL.scheme() == QLatin1String("trash") && newURL.scheme() == QLatin1String("trash")) {
149 if (!isTopLevelEntry(url: oldURL) || !isTopLevelEntry(url: newURL)) {
150 return KIO::WorkerResult::fail(error: KIO::ERR_CANNOT_RENAME, errorString: oldURL.toString());
151 }
152 int oldTrashId;
153 QString oldFileId;
154 QString oldRelativePath;
155 bool oldOk = TrashImpl::parseURL(url: oldURL, trashId&: oldTrashId, fileId&: oldFileId, relativePath&: oldRelativePath);
156 if (!oldOk) {
157 return KIO::WorkerResult::fail(error: KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", oldURL.toString()));
158 }
159 if (!oldRelativePath.isEmpty()) {
160 return KIO::WorkerResult::fail(error: KIO::ERR_CANNOT_RENAME, errorString: oldURL.toString());
161 }
162 // Dolphin/KIO can't specify a trashid in the new URL so here path == filename
163 // bool newOk = TrashImpl::parseURL(newURL, newTrashId, newFileId, newRelativePath);
164 const QString newFileId = newURL.path().mid(position: 1);
165 if (newFileId.contains(c: QLatin1Char('/'))) {
166 return KIO::WorkerResult::fail(error: KIO::ERR_CANNOT_RENAME, errorString: oldURL.toString());
167 }
168 bool ok = impl.moveInTrash(trashId: oldTrashId, oldFileId, newFileId);
169 if (!ok) {
170 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
171 }
172 const QUrl finalUrl = TrashImpl::makeURL(trashId: oldTrashId, fileId: newFileId, relativePath: QString());
173 org::kde::KDirNotify::emitFileRenamed(src: oldURL, dst: finalUrl);
174 return KIO::WorkerResult::pass();
175 }
176
177 if (oldURL.scheme() == QLatin1String("trash") && newURL.isLocalFile()) {
178 return copyOrMoveFromTrash(src: oldURL, dest: newURL, overwrite: (flags & KIO::Overwrite), action: Move);
179 }
180 if (oldURL.isLocalFile() && newURL.scheme() == QLatin1String("trash")) {
181 return copyOrMoveToTrash(src: oldURL, dest: newURL, action: Move);
182 }
183 return KIO::WorkerResult::fail(error: KIO::ERR_UNSUPPORTED_ACTION, i18n("Invalid combination of protocols."));
184}
185
186KIO::WorkerResult TrashProtocol::copy(const QUrl &src, const QUrl &dest, int /*permissions*/, KIO::JobFlags flags)
187{
188 if (const auto initResult = initImpl(); !initResult.success()) {
189 return initResult;
190 }
191
192 qCDebug(KIO_TRASH) << "TrashProtocol::copy(): " << src << " " << dest;
193
194 if (src.scheme() == QLatin1String("trash") && dest.scheme() == QLatin1String("trash")) {
195 return KIO::WorkerResult::fail(error: KIO::ERR_UNSUPPORTED_ACTION, i18n("This file is already in the trash bin."));
196 }
197
198 if (src.scheme() == QLatin1String("trash") && dest.isLocalFile()) {
199 return copyOrMoveFromTrash(src, dest, overwrite: (flags & KIO::Overwrite), action: Copy);
200 }
201 if (src.isLocalFile() && dest.scheme() == QLatin1String("trash")) {
202 return copyOrMoveToTrash(src, dest, action: Copy);
203 }
204 return KIO::WorkerResult::fail(error: KIO::ERR_UNSUPPORTED_ACTION, i18n("Invalid combination of protocols."));
205}
206
207KIO::WorkerResult TrashProtocol::copyOrMoveFromTrash(const QUrl &src, const QUrl &dest, bool overwrite, CopyOrMove action)
208{
209 // Extracting (e.g. via dnd). Ignore original location stored in info file.
210 int trashId;
211 QString fileId;
212 QString relativePath;
213 bool ok = TrashImpl::parseURL(url: src, trashId, fileId, relativePath);
214 if (!ok) {
215 return KIO::WorkerResult::fail(error: KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", src.toString()));
216 }
217 const QString destPath = dest.path();
218 if (QFile::exists(fileName: destPath)) {
219 if (overwrite) {
220 ok = QFile::remove(fileName: destPath);
221 Q_ASSERT(ok); // ### TODO
222 } else {
223 return KIO::WorkerResult::fail(error: KIO::ERR_FILE_ALREADY_EXIST, errorString: destPath);
224 }
225 }
226
227 if (action == Move) {
228 qCDebug(KIO_TRASH) << "calling moveFromTrash(" << destPath << " " << trashId << " " << fileId << ")";
229 ok = impl.moveFromTrash(origPath: destPath, trashId, fileId, relativePath);
230 } else { // Copy
231 qCDebug(KIO_TRASH) << "calling copyFromTrash(" << destPath << " " << trashId << " " << fileId << ")";
232 ok = impl.copyFromTrash(origPath: destPath, trashId, fileId, relativePath);
233 }
234 if (!ok) {
235 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
236 }
237
238 if (action == Move && relativePath.isEmpty()) {
239 (void)impl.deleteInfo(trashId, fileId);
240 }
241 return KIO::WorkerResult::pass();
242}
243
244KIO::WorkerResult TrashProtocol::copyOrMoveToTrash(const QUrl &src, const QUrl &dest, CopyOrMove action)
245{
246 qCDebug(KIO_TRASH) << "trashing a file" << src << dest;
247
248 // Trashing a file
249 // We detect the case where this isn't normal trashing, but
250 // e.g. if kwrite tries to save (moving tempfile over destination)
251 if (isTopLevelEntry(url: dest) && src.fileName() == dest.fileName()) { // new toplevel entry
252 const QString srcPath = src.path();
253 // In theory we should use TrashImpl::parseURL to give the right filename to createInfo,
254 // in case the trash URL didn't contain the same filename as srcPath.
255 // But this can only happen with copyAs/moveAs, not available in the GUI
256 // for the trash (New/... or Rename from iconview/listview).
257 int trashId;
258 QString fileId;
259 if (!impl.createInfo(origPath: srcPath, trashId, fileId)) {
260 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
261 }
262 bool ok;
263 if (action == Move) {
264 qCDebug(KIO_TRASH) << "calling moveToTrash(" << srcPath << " " << trashId << " " << fileId << ")";
265 ok = impl.moveToTrash(origPath: srcPath, trashId, fileId);
266 } else { // Copy
267 qCDebug(KIO_TRASH) << "calling copyToTrash(" << srcPath << " " << trashId << " " << fileId << ")";
268 ok = impl.copyToTrash(origPath: srcPath, trashId, fileId);
269 }
270 if (!ok) {
271 (void)impl.deleteInfo(trashId, fileId);
272 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
273 }
274 // Inform caller of the final URL. Used by konq_undo.
275 const QUrl url = impl.makeURL(trashId, fileId, relativePath: QString());
276 setMetaData(key: QLatin1String("trashURL-") + srcPath, value: url.url());
277 return KIO::WorkerResult::pass();
278 }
279
280 qCDebug(KIO_TRASH) << "returning KIO::ERR_ACCESS_DENIED, it's not allowed to add a file to an existing trash directory";
281 // It's not allowed to add a file to an existing trash directory.
282 return KIO::WorkerResult::fail(error: KIO::ERR_ACCESS_DENIED, errorString: dest.toString());
283}
284
285void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry &entry)
286{
287 entry.reserve(size: entry.count() + 8);
288 entry.fastInsert(field: KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
289 entry.fastInsert(field: KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Trash"));
290 entry.fastInsert(field: KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
291 entry.fastInsert(field: KIO::UDSEntry::UDS_ACCESS, l: 0700);
292 entry.fastInsert(field: KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
293 entry.fastInsert(field: KIO::UDSEntry::UDS_ICON_NAME, value: impl.isEmpty() ? QStringLiteral("user-trash") : QStringLiteral("user-trash-full"));
294 entry.fastInsert(field: KIO::UDSEntry::UDS_USER, value: m_userName);
295 entry.fastInsert(field: KIO::UDSEntry::UDS_GROUP, value: m_groupName);
296 entry.fastInsert(field: KIO::UDSEntry::UDS_LOCAL_USER_ID, l: m_userId);
297 entry.fastInsert(field: KIO::UDSEntry::UDS_LOCAL_GROUP_ID, l: m_groupId);
298}
299
300KIO::StatDetails TrashProtocol::getStatDetails()
301{
302 const QString statDetails = metaData(QStringLiteral("details"));
303 return statDetails.isEmpty() ? KIO::StatDefaultDetails : static_cast<KIO::StatDetails>(statDetails.toInt());
304}
305
306KIO::WorkerResult TrashProtocol::stat(const QUrl &url)
307{
308 if (const auto initResult = initImpl(); !initResult.success()) {
309 return initResult;
310 }
311
312 const QString path = url.path();
313 if (path.isEmpty() || path == QLatin1String("/")) {
314 // The root is "virtual" - it's not a single physical directory
315 KIO::UDSEntry entry = impl.trashUDSEntry(details: getStatDetails());
316 createTopLevelDirEntry(entry);
317 statEntry(entry: entry);
318 } else {
319 int trashId;
320 QString fileId;
321 QString relativePath;
322
323 bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
324
325 if (!ok) {
326 // ######## do we still need this?
327 qCDebug(KIO_TRASH) << url << " looks fishy, returning does-not-exist";
328 // A URL like trash:/file simply means that CopyJob is trying to see if
329 // the destination exists already (it made up the URL by itself).
330 // error( KIO::ERR_WORKER_DEFINED, i18n( "Malformed URL %1" ).arg( url.toString() ) );
331 return KIO::WorkerResult::fail(error: KIO::ERR_DOES_NOT_EXIST, errorString: url.toString());
332 }
333
334 qCDebug(KIO_TRASH) << "parsed" << url << "got" << trashId << fileId << relativePath;
335
336 const QString filePath = impl.physicalPath(trashId, fileId, relativePath);
337 if (filePath.isEmpty()) {
338 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
339 }
340
341 // For a toplevel file, use the fileId as display name (to hide the trashId)
342 // For a file in a subdir, use the fileName as is.
343 QString fileDisplayName = relativePath.isEmpty() ? fileId : url.fileName();
344
345 QUrl fileURL;
346 if (url.path().length() > 1) {
347 fileURL = url;
348 }
349
350 KIO::UDSEntry entry;
351 TrashedFileInfo info;
352 ok = impl.infoForFile(trashId, fileId, info);
353 if (ok) {
354 ok = createUDSEntry(physicalPath: filePath, displayFileName: fileDisplayName, internalFileName: fileURL.fileName(), entry, info);
355 }
356
357 if (!ok) {
358 return KIO::WorkerResult::fail(error: KIO::ERR_CANNOT_STAT, errorString: url.toString());
359 }
360
361 statEntry(entry: entry);
362 }
363 return KIO::WorkerResult::pass();
364}
365
366KIO::WorkerResult TrashProtocol::del(const QUrl &url, bool /*isfile*/)
367{
368 if (const auto initResult = initImpl(); !initResult.success()) {
369 return initResult;
370 }
371
372 int trashId;
373 QString fileId;
374 QString relativePath;
375
376 bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
377 if (!ok) {
378 return KIO::WorkerResult::fail(error: KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", url.toString()));
379 }
380
381 ok = relativePath.isEmpty();
382 if (!ok) {
383 return KIO::WorkerResult::fail(error: KIO::ERR_ACCESS_DENIED, errorString: url.toString());
384 }
385
386 ok = impl.del(trashId, fileId);
387 if (!ok) {
388 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
389 }
390
391 return KIO::WorkerResult::pass();
392}
393
394KIO::WorkerResult TrashProtocol::listDir(const QUrl &url)
395{
396 if (const auto initResult = initImpl(); !initResult.success()) {
397 return initResult;
398 }
399
400 qCDebug(KIO_TRASH) << "listdir: " << url;
401 const QString path = url.path();
402 if (path.isEmpty() || path == QLatin1String("/")) {
403 return listRoot();
404 }
405 int trashId;
406 QString fileId;
407 QString relativePath;
408 bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
409 if (!ok) {
410 return KIO::WorkerResult::fail(error: KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", url.toString()));
411 }
412 // was: const QString physicalPath = impl.physicalPath( trashId, fileId, relativePath );
413
414 // Get info for deleted directory - the date of deletion and orig path will be used
415 // for all the items in it, and we need the physicalPath.
416 TrashedFileInfo info;
417 ok = impl.infoForFile(trashId, fileId, info);
418 if (!ok || info.physicalPath.isEmpty()) {
419 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
420 }
421 if (!relativePath.isEmpty()) {
422 info.physicalPath += QLatin1Char('/') + relativePath;
423 }
424
425 // List subdir. Can't use kio_file here since we provide our own info...
426 qCDebug(KIO_TRASH) << "listing " << info.physicalPath;
427 const QStringList entryNames = impl.listDir(physicalPath: info.physicalPath);
428 totalSize(bytes: entryNames.count());
429 KIO::UDSEntry entry;
430 for (const QString &fileName : entryNames) {
431 if (fileName == QLatin1String("..")) {
432 continue;
433 }
434 const QString filePath = info.physicalPath + QLatin1Char('/') + fileName;
435 // shouldn't be necessary
436 // const QString url = TrashImpl::makeURL( trashId, fileId, relativePath + '/' + fileName );
437 entry.clear();
438 TrashedFileInfo infoForItem(info);
439 infoForItem.origPath += QLatin1Char('/') + fileName;
440 if (createUDSEntry(physicalPath: filePath, displayFileName: fileName, internalFileName: fileName, entry, info: infoForItem)) {
441 listEntry(entry);
442 }
443 }
444 entry.clear();
445 return KIO::WorkerResult::pass();
446}
447
448bool TrashProtocol::createUDSEntry(const QString &physicalPath,
449 const QString &displayFileName,
450 const QString &internalFileName,
451 KIO::UDSEntry &entry,
452 const TrashedFileInfo &info)
453{
454 entry.reserve(size: 14);
455 QByteArray physicalPath_c = QFile::encodeName(fileName: physicalPath);
456 QT_STATBUF buff;
457 if (QT_LSTAT(file: physicalPath_c.constData(), buf: &buff) == -1) {
458 qCWarning(KIO_TRASH) << "couldn't stat " << physicalPath << ", relevant trashinfo file will be removed";
459 impl.deleteInfo(trashId: info.trashId, fileId: info.fileId);
460 return false;
461 }
462 if (S_ISLNK(buff.st_mode)) {
463 char buffer2[1000];
464 int n = ::readlink(path: physicalPath_c.constData(), buf: buffer2, len: 999);
465 if (n != -1) {
466 buffer2[n] = 0;
467 }
468
469 // this does not follow symlink on purpose
470 entry.fastInsert(field: KIO::UDSEntry::UDS_LINK_DEST, value: QFile::decodeName(localFileName: buffer2));
471 }
472
473 mode_t type = buff.st_mode & S_IFMT; // extract file type
474 mode_t access = buff.st_mode & 07777; // extract permissions
475 access &= 07555; // make it readonly, since it's in the trashcan
476 Q_ASSERT(!internalFileName.isEmpty());
477 entry.fastInsert(field: KIO::UDSEntry::UDS_NAME, value: internalFileName); // internal filename, like "0-foo"
478 entry.fastInsert(field: KIO::UDSEntry::UDS_DISPLAY_NAME, value: displayFileName); // user-visible filename, like "foo"
479 entry.fastInsert(field: KIO::UDSEntry::UDS_FILE_TYPE, l: type);
480 entry.fastInsert(field: KIO::UDSEntry::UDS_LOCAL_PATH, value: physicalPath);
481 // if ( !url.isEmpty() )
482 // entry.insert( KIO::UDSEntry::UDS_URL, url );
483
484 QMimeDatabase db;
485 QMimeType mt = db.mimeTypeForFile(fileName: physicalPath);
486 if (mt.isValid()) {
487 entry.fastInsert(field: KIO::UDSEntry::UDS_MIME_TYPE, value: mt.name());
488 }
489 entry.fastInsert(field: KIO::UDSEntry::UDS_ACCESS, l: access);
490 entry.fastInsert(field: KIO::UDSEntry::UDS_SIZE, l: buff.st_size);
491 entry.fastInsert(field: KIO::UDSEntry::UDS_USER, value: m_userName); // assumption
492 entry.fastInsert(field: KIO::UDSEntry::UDS_GROUP, value: m_groupName); // assumption
493 entry.fastInsert(field: KIO::UDSEntry::UDS_MODIFICATION_TIME, l: buff.st_mtime);
494 entry.fastInsert(field: KIO::UDSEntry::UDS_ACCESS_TIME, l: buff.st_atime); // ## or use it for deletion time?
495 entry.fastInsert(field: KIO::UDSEntry::UDS_EXTRA, value: info.origPath);
496 entry.fastInsert(field: KIO::UDSEntry::UDS_EXTRA + 1, value: info.deletionDate.toString(format: Qt::ISODate));
497 return true;
498}
499
500KIO::WorkerResult TrashProtocol::listRoot()
501{
502 if (const auto initResult = initImpl(); !initResult.success()) {
503 return initResult;
504 }
505
506 const TrashedFileInfoList lst = impl.list();
507 totalSize(bytes: lst.count());
508 KIO::UDSEntry entry;
509 createTopLevelDirEntry(entry);
510 listEntry(entry);
511 for (const TrashedFileInfo &fileInfo : lst) {
512 const QUrl url = TrashImpl::makeURL(trashId: fileInfo.trashId, fileId: fileInfo.fileId, relativePath: QString());
513 entry.clear();
514 const QString fileDisplayName = fileInfo.fileId;
515
516 if (createUDSEntry(physicalPath: fileInfo.physicalPath, displayFileName: fileDisplayName, internalFileName: url.fileName(), entry, info: fileInfo)) {
517 listEntry(entry);
518 }
519 }
520 entry.clear();
521 return KIO::WorkerResult::pass();
522}
523
524KIO::WorkerResult TrashProtocol::special(const QByteArray &data)
525{
526 if (const auto initResult = initImpl(); !initResult.success()) {
527 return initResult;
528 }
529
530 QDataStream stream(data);
531 int cmd;
532 stream >> cmd;
533
534 switch (cmd) {
535 case 1:
536 if (!impl.emptyTrash()) {
537 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
538 }
539 break;
540 case 2:
541 impl.migrateOldTrash();
542 break;
543 case 3: {
544 QUrl url;
545 stream >> url;
546 return restore(trashURL: url);
547 }
548 case 4: {
549 QJsonObject json;
550 const auto map = impl.trashDirectories();
551 for (auto it = map.begin(); it != map.end(); ++it) {
552 json[QString::number(it.key())] = it.value();
553 }
554 setMetaData(QStringLiteral("TRASH_DIRECTORIES"), value: QString::fromLocal8Bit(ba: QJsonDocument(json).toJson()));
555 sendMetaData();
556 break;
557 }
558 default:
559 qCWarning(KIO_TRASH) << "Unknown command in special(): " << cmd;
560 return KIO::WorkerResult::fail(error: KIO::ERR_UNSUPPORTED_ACTION, errorString: QString::number(cmd));
561 }
562 return KIO::WorkerResult::pass();
563}
564
565KIO::WorkerResult TrashProtocol::put(const QUrl &url, int /*permissions*/, KIO::JobFlags)
566{
567 if (const auto initResult = initImpl(); !initResult.success()) {
568 return initResult;
569 }
570
571 qCDebug(KIO_TRASH) << "put: " << url;
572 // create deleted file. We need to get the mtime and original location from metadata...
573 // Maybe we can find the info file for url.fileName(), in case ::rename() was called first, and failed...
574 return KIO::WorkerResult::fail(error: KIO::ERR_ACCESS_DENIED, errorString: url.toString());
575}
576
577KIO::WorkerResult TrashProtocol::get(const QUrl &url)
578{
579 if (const auto initResult = initImpl(); !initResult.success()) {
580 return initResult;
581 }
582
583 qCDebug(KIO_TRASH) << "get() : " << url;
584 if (!url.isValid()) {
585 // qCDebug(KIO_TRASH) << kBacktrace();
586 return KIO::WorkerResult::fail(error: KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", url.url()));
587 }
588 if (url.path().length() <= 1) {
589 return KIO::WorkerResult::fail(error: KIO::ERR_IS_DIRECTORY, errorString: url.toString());
590 }
591 int trashId;
592 QString fileId;
593 QString relativePath;
594 bool ok = TrashImpl::parseURL(url, trashId, fileId, relativePath);
595 if (!ok) {
596 return KIO::WorkerResult::fail(error: KIO::ERR_WORKER_DEFINED, i18n("Malformed URL %1", url.toString()));
597 }
598 const QString physicalPath = impl.physicalPath(trashId, fileId, relativePath);
599 if (physicalPath.isEmpty()) {
600 return KIO::WorkerResult::fail(error: impl.lastErrorCode(), errorString: impl.lastErrorMessage());
601 }
602
603 // Usually we run jobs in TrashImpl (for e.g. future kdedmodule)
604 // But for this one we wouldn't use DCOP for every bit of data...
605 QUrl fileURL = QUrl::fromLocalFile(localfile: physicalPath);
606 KIO::TransferJob *job = KIO::get(url: fileURL, reload: KIO::NoReload, flags: KIO::HideProgressInfo);
607 connect(sender: job, signal: &KIO::TransferJob::data, context: this, slot: &TrashProtocol::slotData);
608 connect(sender: job, signal: &KIO::TransferJob::mimeTypeFound, context: this, slot: &TrashProtocol::slotMimetype);
609 connect(sender: job, signal: &KJob::result, context: this, slot: &TrashProtocol::jobFinished);
610 return enterLoop();
611}
612
613void TrashProtocol::slotData(KIO::Job *, const QByteArray &arr)
614{
615 data(data: arr);
616}
617
618void TrashProtocol::slotMimetype(KIO::Job *, const QString &mt)
619{
620 mimeType(type: mt);
621}
622
623void TrashProtocol::jobFinished(KJob *job)
624{
625 Q_EMIT leaveModality(errid: job->error(), text: job->errorText());
626}
627
628KIO::WorkerResult TrashProtocol::fileSystemFreeSpace(const QUrl &url)
629{
630 qCDebug(KIO_TRASH) << "fileSystemFreeSpace:" << url;
631
632 if (const auto initResult = initImpl(); !initResult.success()) {
633 return initResult;
634 }
635
636 TrashImpl::TrashSpaceInfo spaceInfo;
637 if (!impl.trashSpaceInfo(path: url.path(), info&: spaceInfo)) {
638 return KIO::WorkerResult::fail(error: KIO::ERR_CANNOT_STAT, errorString: url.toDisplayString());
639 }
640
641 setMetaData(QStringLiteral("total"), value: QString::number(spaceInfo.totalSize));
642 setMetaData(QStringLiteral("available"), value: QString::number(spaceInfo.availableSize));
643
644 return KIO::WorkerResult::pass();
645}
646
647#include "kio_trash.moc"
648
649#include "moc_kio_trash.cpp"
650

source code of kio/src/kioworkers/trash/kio_trash.cpp