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 |
36 | class KIOPluginForMetaData : public QObject |
37 | { |
38 | Q_OBJECT |
39 | Q_PLUGIN_METADATA(IID "org.kde.kio.worker.trash" FILE "trash.json" ) |
40 | }; |
41 | |
42 | extern "C" { |
43 | int 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 | |
56 | static bool isTopLevelEntry(const QUrl &url) |
57 | { |
58 | const QString dir = url.adjusted(options: QUrl::RemoveFilename).path(); |
59 | return dir.length() <= 1; |
60 | } |
61 | |
62 | TrashProtocol::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 | |
77 | TrashProtocol::~TrashProtocol() |
78 | { |
79 | } |
80 | |
81 | KIO::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 | |
90 | KIO::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 | |
109 | KIO::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 | |
143 | KIO::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 | |
191 | KIO::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 | |
212 | KIO::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 | |
249 | KIO::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 | |
290 | void 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 | |
305 | KIO::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 | |
311 | KIO::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 | |
371 | KIO::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 | |
399 | KIO::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 | |
453 | bool 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 | |
505 | KIO::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 | |
529 | KIO::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 | |
570 | KIO::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 | |
582 | KIO::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 | |
618 | void TrashProtocol::slotData(KIO::Job *, const QByteArray &arr) |
619 | { |
620 | data(data: arr); |
621 | } |
622 | |
623 | void TrashProtocol::slotMimetype(KIO::Job *, const QString &mt) |
624 | { |
625 | mimeType(type: mt); |
626 | } |
627 | |
628 | void TrashProtocol::jobFinished(KJob *job) |
629 | { |
630 | Q_EMIT leaveModality(errid: job->error(), text: job->errorText()); |
631 | } |
632 | |
633 | KIO::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 | |