1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2022 Ahmad Samir <a.samirh78@gmail.com>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "deleteortrashjob.h"
9
10#include "fileundomanager.h"
11#include "widgetsaskuseractionhandler.h"
12#include <kio/copyjob.h>
13#include <kio/deletejob.h>
14#include <kio/emptytrashjob.h>
15#include <kio/job.h>
16#include <kio/jobuidelegatefactory.h>
17#include <kio_widgets_debug.h>
18
19#include <KJobWidgets>
20
21namespace KIO
22{
23
24using AskIface = AskUserActionInterface;
25
26class DeleteOrTrashJobPrivate
27{
28public:
29 DeleteOrTrashJobPrivate(const QList<QUrl> &urls, //
30 AskIface::DeletionType deletionType,
31 AskIface::ConfirmationType confirm,
32 QObject *parent,
33 DeleteOrTrashJob *qq)
34 : q(qq)
35 , m_urls(urls)
36 , m_delType(deletionType)
37 , m_confirm(confirm)
38 , m_parentWindow(qobject_cast<QWidget *>(o: parent))
39 {
40 // trashing an already trashed file is deleting it, BUG 459545
41 if (m_delType == AskIface::Trash && m_urls.first().scheme() == QStringLiteral("trash")) {
42 m_delType = AskIface::Delete;
43 }
44 }
45
46 void slotAskUser(bool allowDelete, const QList<QUrl> &urls, AskIface::DeletionType delType, QWidget *parentWindow);
47
48 DeleteOrTrashJob *q = nullptr;
49 QList<QUrl> m_urls;
50 AskIface::DeletionType m_delType;
51 AskIface::ConfirmationType m_confirm;
52 QWidget *m_parentWindow = nullptr;
53 QMetaObject::Connection m_handlerConnection;
54};
55
56void DeleteOrTrashJobPrivate::slotAskUser(bool allowDelete, const QList<QUrl> &urls, AskIface::DeletionType delType, QWidget *parentWindow)
57{
58 if (!allowDelete) {
59 q->setError(KIO::ERR_USER_CANCELED);
60 q->emitResult();
61 return;
62 }
63
64 KIO::Job *job = nullptr;
65 switch (delType) {
66 case AskIface::Trash:
67 Q_ASSERT(!urls.isEmpty());
68 job = KIO::trash(src: urls);
69 using UndoMananger = KIO::FileUndoManager;
70 UndoMananger::self()->recordJob(op: UndoMananger::Trash, src: urls, dst: QUrl(QStringLiteral("trash:/")), job);
71 break;
72 case AskIface::DeleteInsteadOfTrash:
73 case AskIface::Delete:
74 Q_ASSERT(!urls.isEmpty());
75 job = KIO::del(src: urls);
76 break;
77 case AskIface::EmptyTrash:
78 job = KIO::emptyTrash();
79 break;
80 }
81
82 if (job) {
83 KJobWidgets::setWindow(job, widget: parentWindow);
84 // showErrorMessage() is used in slotResult() instead of AutoErrorHandling,
85 // because if Trashing fails (e.g. due to size constraints), we'll re-ask the
86 // user about deleting instead of Trashing, in which case we don't want to
87 // show the "File is too large to Trash" error message
88 job->uiDelegate()->setAutoErrorHandlingEnabled(false);
89 q->addSubjob(job);
90
91 Q_EMIT q->started();
92 }
93}
94
95DeleteOrTrashJob::DeleteOrTrashJob(const QList<QUrl> &urls, //
96 AskIface::DeletionType deletionType,
97 AskIface::ConfirmationType confirm,
98 QObject *parent)
99 : KCompositeJob(parent)
100 , d(new DeleteOrTrashJobPrivate{urls, deletionType, confirm, parent, this})
101{
102}
103
104DeleteOrTrashJob::~DeleteOrTrashJob() = default;
105
106void DeleteOrTrashJob::start()
107{
108 auto *askHandler = KIO::delegateExtension<AskIface *>(job: this);
109 if (!askHandler) {
110 auto *uiDelegate = new KJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled);
111 auto *widgetAskHandler = new WidgetsAskUserActionHandler(uiDelegate);
112 widgetAskHandler->setWindow(d->m_parentWindow);
113 setUiDelegate(uiDelegate);
114 askHandler = widgetAskHandler;
115 }
116
117 Q_ASSERT(askHandler);
118
119 auto askFunc = [this](bool allowDelete, //
120 const QList<QUrl> &urls,
121 AskIface::DeletionType deletionType,
122 QWidget *window) {
123 d->slotAskUser(allowDelete, urls, delType: deletionType, parentWindow: window);
124 };
125
126 // Make it a unique connection, as the same UI delegate could get re-used
127 // if e.g. Trashing failed and we're re-asking the user about deleting instead
128 // of Trashing
129 disconnect(d->m_handlerConnection);
130 d->m_handlerConnection = connect(sender: askHandler, signal: &AskIface::askUserDeleteResult, context: this, slot&: askFunc);
131 askHandler->askUserDelete(urls: d->m_urls, deletionType: d->m_delType, confirmationType: d->m_confirm, parent: d->m_parentWindow);
132}
133
134void DeleteOrTrashJob::slotResult(KJob *job)
135{
136 const int errCode = job->error();
137
138 if (errCode == KIO::ERR_TRASH_FILE_TOO_LARGE) {
139 removeSubjob(job);
140 d->m_delType = AskIface::DeleteInsteadOfTrash;
141 start();
142 return;
143 }
144
145 if (errCode) {
146 setError(errCode);
147 // We're a KJob, not a KIO::Job, so build the error string here
148 setErrorText(KIO::buildErrorString(errorCode: errCode, errorText: job->errorText()));
149 job->uiDelegate()->showErrorMessage();
150 }
151 emitResult();
152}
153
154} // namespace KIO
155
156#include "moc_deleteortrashjob.cpp"
157

source code of kio/src/widgets/deleteortrashjob.cpp