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

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