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