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 |
33 | class KIOPluginForMetaData : public QObject |
34 | { |
35 | Q_OBJECT |
36 | Q_PLUGIN_METADATA(IID "org.kde.kio.worker.trash" FILE "trash.json" ) |
37 | }; |
38 | |
39 | extern "C" { |
40 | int 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 | |
53 | static bool isTopLevelEntry(const QUrl &url) |
54 | { |
55 | const QString dir = url.adjusted(options: QUrl::RemoveFilename).path(); |
56 | return dir.length() <= 1; |
57 | } |
58 | |
59 | TrashProtocol::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 | |
74 | TrashProtocol::~TrashProtocol() |
75 | { |
76 | } |
77 | |
78 | KIO::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 | |
87 | KIO::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 | |
106 | KIO::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 | |
140 | KIO::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 | |
186 | KIO::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 | |
207 | KIO::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 | |
244 | KIO::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 | |
285 | void 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 | |
300 | KIO::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 | |
306 | KIO::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 | |
366 | KIO::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 | |
394 | KIO::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 | |
448 | bool 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 | |
500 | KIO::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 | |
524 | KIO::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 | |
565 | KIO::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 | |
577 | KIO::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 | |
613 | void TrashProtocol::slotData(KIO::Job *, const QByteArray &arr) |
614 | { |
615 | data(data: arr); |
616 | } |
617 | |
618 | void TrashProtocol::slotMimetype(KIO::Job *, const QString &mt) |
619 | { |
620 | mimeType(type: mt); |
621 | } |
622 | |
623 | void TrashProtocol::jobFinished(KJob *job) |
624 | { |
625 | Q_EMIT leaveModality(errid: job->error(), text: job->errorText()); |
626 | } |
627 | |
628 | KIO::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 | |