KIO

fileundomanager.cpp
1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2000 Simon Hausmann <hausmann@kde.org>
4 SPDX-FileCopyrightText: 2006, 2008 David Faure <faure@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "fileundomanager.h"
10#include "askuseractioninterface.h"
11#include "clipboardupdater_p.h"
12#include "fileundomanager_adaptor.h"
13#include "fileundomanager_p.h"
14#include "kio_widgets_debug.h"
15#include <job_p.h>
16#include <kdirnotify.h>
17#include <kio/batchrenamejob.h>
18#include <kio/copyjob.h>
19#include <kio/filecopyjob.h>
20#include <kio/jobuidelegate.h>
21#include <kio/mkdirjob.h>
22#include <kio/mkpathjob.h>
23#include <kio/statjob.h>
24
25#include <KJobTrackerInterface>
26#include <KJobWidgets>
27#include <KLocalizedString>
28#include <KMessageBox>
29
30#include <QDBusConnection>
31#include <QDateTime>
32#include <QFileInfo>
33#include <QLocale>
34
35using namespace KIO;
36
37static const char *undoStateToString(UndoState state)
38{
39 static const char *const s_undoStateToString[] = {"MAKINGDIRS", "MOVINGFILES", "STATINGFILE", "REMOVINGDIRS", "REMOVINGLINKS"};
40 return s_undoStateToString[state];
41}
42
43static QDataStream &operator<<(QDataStream &stream, const KIO::BasicOperation &op)
44{
45 stream << op.m_valid << (qint8)op.m_type << op.m_renamed << op.m_src << op.m_dst << op.m_target << qint64(op.m_mtime.toMSecsSinceEpoch() / 1000);
46 return stream;
47}
48static QDataStream &operator>>(QDataStream &stream, BasicOperation &op)
49{
50 qint8 type;
51 qint64 mtime;
52 stream >> op.m_valid >> type >> op.m_renamed >> op.m_src >> op.m_dst >> op.m_target >> mtime;
53 op.m_type = static_cast<BasicOperation::Type>(type);
55 return stream;
56}
57
58static QDataStream &operator<<(QDataStream &stream, const UndoCommand &cmd)
59{
60 stream << cmd.m_valid << (qint8)cmd.m_type << cmd.m_opQueue << cmd.m_src << cmd.m_dst;
61 return stream;
62}
63
64static QDataStream &operator>>(QDataStream &stream, UndoCommand &cmd)
65{
66 qint8 type;
67 stream >> cmd.m_valid >> type >> cmd.m_opQueue >> cmd.m_src >> cmd.m_dst;
68 cmd.m_type = static_cast<FileUndoManager::CommandType>(type);
69 return stream;
70}
71
72QDebug operator<<(QDebug dbg, const BasicOperation &op)
73{
74 if (op.m_valid) {
75 static const char *s_types[] = {"File", "Link", "Directory"};
76 dbg << "BasicOperation: type" << s_types[op.m_type] << "src" << op.m_src << "dest" << op.m_dst << "target" << op.m_target << "renamed" << op.m_renamed;
77 } else {
78 dbg << "Invalid BasicOperation";
79 }
80 return dbg;
81}
82/**
83 * checklist:
84 * copy dir -> overwrite -> works
85 * move dir -> overwrite -> works
86 * copy dir -> rename -> works
87 * move dir -> rename -> works
88 *
89 * copy dir -> works
90 * move dir -> works
91 *
92 * copy files -> works
93 * move files -> works (TODO: optimize (change FileCopyJob to use the renamed arg for copyingDone)
94 *
95 * copy files -> overwrite -> works (sorry for your overwritten file...)
96 * move files -> overwrite -> works (sorry for your overwritten file...)
97 *
98 * copy files -> rename -> works
99 * move files -> rename -> works
100 *
101 * -> see also fileundomanagertest, which tests some of the above (but not renaming).
102 *
103 */
104
105class KIO::UndoJob : public KIO::Job
106{
108public:
109 UndoJob(bool showProgressInfo)
110 : KIO::Job()
111 {
112 if (showProgressInfo) {
114 }
115
116 d_ptr->m_privilegeExecutionEnabled = true;
117 d_ptr->m_operationType = d_ptr->Other;
118 d_ptr->m_title = i18n("Undo Changes");
119 d_ptr->m_message = i18n("Undoing this operation requires root privileges. Do you want to continue?");
120 }
121
122 ~UndoJob() override = default;
123
124 virtual void kill(bool) // TODO should be doKill
125 {
126 FileUndoManager::self()->d->stopUndo(true);
128 }
129
130 void emitCreatingDir(const QUrl &dir)
131 {
132 Q_EMIT description(this, i18n("Creating directory"), qMakePair(i18n("Directory"), dir.toDisplayString()));
133 }
134
135 void emitMovingOrRenaming(const QUrl &src, const QUrl &dest, FileUndoManager::CommandType cmdType)
136 {
137 static const QString srcMsg(i18nc("The source of a file operation", "Source"));
138 static const QString destMsg(i18nc("The destination of a file operation", "Destination"));
139
140 Q_EMIT description(this, //
141 cmdType == FileUndoManager::Move ? i18n("Moving") : i18n("Renaming"),
142 {srcMsg, src.toDisplayString()},
143 {destMsg, dest.toDisplayString()});
144 }
145
146 void emitDeleting(const QUrl &url)
147 {
148 Q_EMIT description(this, i18n("Deleting"), qMakePair(i18n("File"), url.toDisplayString()));
149 }
150 void emitResult()
151 {
153 }
154};
155
156CommandRecorder::CommandRecorder(FileUndoManager::CommandType op, const QList<QUrl> &src, const QUrl &dst, KIO::Job *job)
157 : QObject(job)
158 , m_cmd(op, src, dst, FileUndoManager::self()->newCommandSerialNumber())
159{
160 connect(job, &KJob::result, this, &CommandRecorder::slotResult);
161 if (auto *copyJob = qobject_cast<KIO::CopyJob *>(job)) {
162 connect(copyJob, &KIO::CopyJob::copyingDone, this, &CommandRecorder::slotCopyingDone);
163 connect(copyJob, &KIO::CopyJob::copyingLinkDone, this, &CommandRecorder::slotCopyingLinkDone);
164 } else if (auto *mkpathJob = qobject_cast<KIO::MkpathJob *>(job)) {
165 connect(mkpathJob, &KIO::MkpathJob::directoryCreated, this, &CommandRecorder::slotDirectoryCreated);
166 } else if (auto *batchRenameJob = qobject_cast<KIO::BatchRenameJob *>(job)) {
167 connect(batchRenameJob, &KIO::BatchRenameJob::fileRenamed, this, &CommandRecorder::slotBatchRenamingDone);
168 }
169}
170
171void CommandRecorder::slotResult(KJob *job)
172{
173 const int err = job->error();
174 if (err) {
175 if (err != KIO::ERR_USER_CANCELED) {
176 qCDebug(KIO_WIDGETS) << "CommandRecorder::slotResult:" << job->errorString() << " - no undo command will be added";
177 }
178 return;
179 }
180
181 // For CopyJob, don't add an undo command unless the job actually did something,
182 // e.g. if user selected to skip all, there is nothing to undo.
183 // Note: this doesn't apply to other job types, e.g. for Mkdir m_opQueue is
184 // expected to be empty
185 if (qobject_cast<KIO::CopyJob *>(job)) {
186 if (!m_cmd.m_opQueue.isEmpty()) {
187 FileUndoManager::self()->d->addCommand(m_cmd);
188 }
189 return;
190 }
191
192 FileUndoManager::self()->d->addCommand(m_cmd);
193}
194
195void CommandRecorder::slotCopyingDone(KIO::Job *, const QUrl &from, const QUrl &to, const QDateTime &mtime, bool directory, bool renamed)
196{
197 const BasicOperation::Type type = directory ? BasicOperation::Directory : BasicOperation::File;
198 m_cmd.m_opQueue.enqueue(BasicOperation(type, renamed, from, to, mtime));
199}
200
201void CommandRecorder::slotCopyingLinkDone(KIO::Job *, const QUrl &from, const QString &target, const QUrl &to)
202{
203 m_cmd.m_opQueue.enqueue(BasicOperation(BasicOperation::Link, false, from, to, {}, target));
204}
205
206void CommandRecorder::slotDirectoryCreated(const QUrl &dir)
207{
208 m_cmd.m_opQueue.enqueue(BasicOperation(BasicOperation::Directory, false, QUrl{}, dir, {}));
209}
210
211void CommandRecorder::slotBatchRenamingDone(const QUrl &from, const QUrl &to)
212{
213 m_cmd.m_opQueue.enqueue(BasicOperation(BasicOperation::Item, true, from, to, {}));
214}
215
216////
217
218class KIO::FileUndoManagerSingleton
219{
220public:
221 FileUndoManager self;
222};
223Q_GLOBAL_STATIC(KIO::FileUndoManagerSingleton, globalFileUndoManager)
224
226{
227 return &globalFileUndoManager()->self;
228}
229
230// m_nextCommandIndex is initialized to a high number so that konqueror can
231// assign low numbers to closed items loaded "on-demand" from a config file
232// in KonqClosedWindowsManager::readConfig and thus maintaining the real
233// order of the undo items.
234FileUndoManagerPrivate::FileUndoManagerPrivate(FileUndoManager *qq)
235 : m_uiInterface(new FileUndoManager::UiInterface())
236 , m_nextCommandIndex(1000)
237 , q(qq)
238{
239#if !defined(Q_OS_WIN) && !defined(Q_OS_MAC)
240 (void)new KIOFileUndoManagerAdaptor(this);
241 const QString dbusPath = QStringLiteral("/FileUndoManager");
242 const QString dbusInterface = QStringLiteral("org.kde.kio.FileUndoManager");
243
245 dbus.registerObject(dbusPath, this);
246 dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("lock"), this, SLOT(slotLock()));
247 dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("pop"), this, SLOT(slotPop()));
248 dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("push"), this, SLOT(slotPush(QByteArray)));
249 dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("unlock"), this, SLOT(slotUnlock()));
250#endif
251}
252
253FileUndoManager::FileUndoManager()
254 : d(new FileUndoManagerPrivate(this))
255{
256}
257
258FileUndoManager::~FileUndoManager() = default;
259
260void FileUndoManager::recordJob(CommandType op, const QList<QUrl> &src, const QUrl &dst, KIO::Job *job)
261{
262 // This records what the job does and calls addCommand when done
263 (void)new CommandRecorder(op, src, dst, job);
265}
266
268{
269 CommandType commandType;
270 switch (copyJob->operationMode()) {
271 case CopyJob::Copy:
272 commandType = Copy;
273 break;
274 case CopyJob::Move:
275 commandType = Move;
276 break;
277 case CopyJob::Link:
278 commandType = Link;
279 break;
280 default:
281 Q_UNREACHABLE();
282 }
283 recordJob(commandType, copyJob->srcUrls(), copyJob->destUrl(), copyJob);
284}
285
286void FileUndoManagerPrivate::addCommand(const UndoCommand &cmd)
287{
288 pushCommand(cmd);
289 Q_EMIT q->jobRecordingFinished(cmd.m_type);
290}
291
293{
294 return !d->m_commands.isEmpty() && !d->m_lock;
295}
296
298{
299 if (d->m_commands.isEmpty()) {
300 return i18n("Und&o");
301 }
302
303 FileUndoManager::CommandType t = d->m_commands.top().m_type;
304 switch (t) {
305 case FileUndoManager::Copy:
306 return i18n("Und&o: Copy");
307 case FileUndoManager::Link:
308 return i18n("Und&o: Link");
309 case FileUndoManager::Move:
310 return i18n("Und&o: Move");
311 case FileUndoManager::Rename:
312 return i18n("Und&o: Rename");
313 case FileUndoManager::Trash:
314 return i18n("Und&o: Trash");
315 case FileUndoManager::Mkdir:
316 return i18n("Und&o: Create Folder");
318 return i18n("Und&o: Create Folder(s)");
320 return i18n("Und&o: Create File");
322 return i18n("Und&o: Batch Rename");
323 }
324 /* NOTREACHED */
325 return QString();
326}
327
329{
330 return ++(d->m_nextCommandIndex);
331}
332
333quint64 FileUndoManager::currentCommandSerialNumber() const
334{
335 if (!d->m_commands.isEmpty()) {
336 const UndoCommand &cmd = d->m_commands.top();
337 Q_ASSERT(cmd.m_valid);
338 return cmd.m_serialNumber;
339 }
340
341 return 0;
342}
343
345{
346 Q_ASSERT(!d->m_commands.isEmpty()); // forgot to record before calling undo?
347
348 // Make a copy of the command to undo before slotPop() pops it.
349 UndoCommand cmd = d->m_commands.last();
350 Q_ASSERT(cmd.m_valid);
351 d->m_currentCmd = cmd;
352 const CommandType commandType = cmd.m_type;
353
354 // Note that m_opQueue is empty for simple operations like Mkdir.
355 const auto &opQueue = d->m_currentCmd.m_opQueue;
356
357 // Let's first ask for confirmation if we need to delete any file (#99898)
358 QList<QUrl> itemsToDelete;
359 for (auto it = opQueue.crbegin(); it != opQueue.crend(); ++it) {
360 const BasicOperation &op = *it;
361 const auto destination = op.m_dst;
362 if (op.m_type == BasicOperation::File && commandType == FileUndoManager::Copy) {
363 if (destination.isLocalFile() && !QFileInfo::exists(destination.toLocalFile())) {
364 continue;
365 }
366 itemsToDelete.append(destination);
367 } else if (commandType == FileUndoManager::Mkpath) {
368 itemsToDelete.append(destination);
369 }
370 }
371 if (commandType == FileUndoManager::Mkdir || commandType == FileUndoManager::Put) {
372 itemsToDelete.append(d->m_currentCmd.m_dst);
373 }
374 if (!itemsToDelete.isEmpty()) {
375 AskUserActionInterface *askUserInterface = nullptr;
376 d->m_uiInterface->virtual_hook(UiInterface::HookGetAskUserActionInterface, &askUserInterface);
377 if (askUserInterface) {
378 if (!d->m_connectedToAskUserInterface) {
379 d->m_connectedToAskUserInterface = true;
380 QObject::connect(askUserInterface, &KIO::AskUserActionInterface::askUserDeleteResult, this, [this](bool allowDelete) {
381 if (allowDelete) {
382 d->startUndo();
383 }
384 });
385 }
386
387 // Because undo can happen with an accidental Ctrl-Z, we want to always confirm.
388 askUserInterface->askUserDelete(itemsToDelete,
389 KIO::AskUserActionInterface::Delete,
391 d->m_uiInterface->parentWidget());
392 return;
393 }
394 }
395
396 d->startUndo();
397}
398
399void FileUndoManagerPrivate::startUndo()
400{
401 slotPop();
402 slotLock();
403
404 m_dirCleanupStack.clear();
405 m_dirStack.clear();
406 m_dirsToUpdate.clear();
407
408 m_undoState = MOVINGFILES;
409
410 // Let's have a look at the basic operations we need to undo.
411 auto &opQueue = m_currentCmd.m_opQueue;
412 for (auto it = opQueue.rbegin(); it != opQueue.rend(); ++it) {
413 const BasicOperation::Type type = (*it).m_type;
414 if (type == BasicOperation::Directory && !(*it).m_renamed) {
415 // If any directory has to be created/deleted, we'll start with that
416 m_undoState = MAKINGDIRS;
417 // Collect all the dirs that have to be created in case of a move undo.
418 if (m_currentCmd.isMoveOrRename()) {
419 m_dirStack.push((*it).m_src);
420 }
421 // Collect all dirs that have to be deleted
422 // from the destination in both cases (copy and move).
423 m_dirCleanupStack.prepend((*it).m_dst);
424 } else if (type == BasicOperation::Link) {
425 m_fileCleanupStack.prepend((*it).m_dst);
426 }
427 }
428 auto isBasicOperation = [this](const BasicOperation &op) {
429 return (op.m_type == BasicOperation::Directory && !op.m_renamed) //
430 || (op.m_type == BasicOperation::Link && !m_currentCmd.isMoveOrRename());
431 };
432 opQueue.erase(std::remove_if(opQueue.begin(), opQueue.end(), isBasicOperation), opQueue.end());
433
434 const FileUndoManager::CommandType commandType = m_currentCmd.m_type;
435 if (commandType == FileUndoManager::Put) {
436 m_fileCleanupStack.append(m_currentCmd.m_dst);
437 }
438
439 qCDebug(KIO_WIDGETS) << "starting with" << undoStateToString(m_undoState);
440 m_undoJob = new UndoJob(m_uiInterface->showProgressInfo());
441 auto undoFunc = [this]() {
442 undoStep();
443 };
445}
446
447void FileUndoManagerPrivate::stopUndo(bool step)
448{
449 m_currentCmd.m_opQueue.clear();
450 m_dirCleanupStack.clear();
451 m_fileCleanupStack.clear();
452 m_undoState = REMOVINGDIRS;
453 m_undoJob = nullptr;
454
455 if (m_currentJob) {
456 m_currentJob->kill();
457 }
458
459 m_currentJob = nullptr;
460
461 if (step) {
462 undoStep();
463 }
464}
465
466void FileUndoManagerPrivate::slotResult(KJob *job)
467{
468 m_currentJob = nullptr;
469 if (job->error()) {
470 qWarning() << job->errorString();
471 m_uiInterface->jobError(static_cast<KIO::Job *>(job));
472 delete m_undoJob;
473 stopUndo(false);
474 } else if (m_undoState == STATINGFILE) {
475 const BasicOperation op = m_currentCmd.m_opQueue.head();
476 // qDebug() << "stat result for " << op.m_dst;
477 KIO::StatJob *statJob = static_cast<KIO::StatJob *>(job);
479 if (mtime != op.m_mtime) {
480 qCDebug(KIO_WIDGETS) << op.m_dst << "was modified after being copied. Initial timestamp" << mtime << "now" << op.m_mtime;
481 QDateTime srcTime = op.m_mtime.toLocalTime();
482 QDateTime destTime = mtime.toLocalTime();
483 if (!m_uiInterface->copiedFileWasModified(op.m_src, op.m_dst, srcTime, destTime)) {
484 stopUndo(false);
485 }
486 }
487 }
488
489 undoStep();
490}
491
492void FileUndoManagerPrivate::addDirToUpdate(const QUrl &url)
493{
494 if (!m_dirsToUpdate.contains(url)) {
495 m_dirsToUpdate.prepend(url);
496 }
497}
498
499void FileUndoManagerPrivate::undoStep()
500{
501 m_currentJob = nullptr;
502
503 if (m_undoState == MAKINGDIRS) {
504 stepMakingDirectories();
505 }
506
507 if (m_undoState == MOVINGFILES || m_undoState == STATINGFILE) {
508 stepMovingFiles();
509 }
510
511 if (m_undoState == REMOVINGLINKS) {
512 stepRemovingLinks();
513 }
514
515 if (m_undoState == REMOVINGDIRS) {
516 stepRemovingDirectories();
517 }
518
519 if (m_currentJob) {
520 if (m_uiInterface) {
521 KJobWidgets::setWindow(m_currentJob, m_uiInterface->parentWidget());
522 }
523 QObject::connect(m_currentJob, &KJob::result, this, &FileUndoManagerPrivate::slotResult);
524 }
525}
526
527void FileUndoManagerPrivate::stepMakingDirectories()
528{
529 if (!m_dirStack.isEmpty()) {
530 QUrl dir = m_dirStack.pop();
531 // qDebug() << "creatingDir" << dir;
532 m_currentJob = KIO::mkdir(dir);
533 m_currentJob->setParentJob(m_undoJob);
534 m_undoJob->emitCreatingDir(dir);
535 } else {
536 m_undoState = MOVINGFILES;
537 }
538}
539
540// Misnamed method: It moves files back, but it also
541// renames directories back, recreates symlinks,
542// deletes copied files, and restores trashed files.
543void FileUndoManagerPrivate::stepMovingFiles()
544{
545 if (m_currentCmd.m_opQueue.isEmpty()) {
546 m_undoState = REMOVINGLINKS;
547 return;
548 }
549
550 const BasicOperation op = m_currentCmd.m_opQueue.head();
551 Q_ASSERT(op.m_valid);
552 if (op.m_type == BasicOperation::Directory || op.m_type == BasicOperation::Item) {
553 Q_ASSERT(op.m_renamed);
554 // qDebug() << "rename" << op.m_dst << op.m_src;
555 m_currentJob = KIO::rename(op.m_dst, op.m_src, KIO::HideProgressInfo);
556 m_undoJob->emitMovingOrRenaming(op.m_dst, op.m_src, m_currentCmd.m_type);
557 } else if (op.m_type == BasicOperation::Link) {
558 // qDebug() << "symlink" << op.m_target << op.m_src;
559 m_currentJob = KIO::symlink(op.m_target, op.m_src, KIO::Overwrite | KIO::HideProgressInfo);
560 } else if (m_currentCmd.m_type == FileUndoManager::Copy) {
561 if (m_undoState == MOVINGFILES) { // dest not stat'ed yet
562 // Before we delete op.m_dst, let's check if it was modified (#20532)
563 // qDebug() << "stat" << op.m_dst;
564 m_currentJob = KIO::stat(op.m_dst, KIO::HideProgressInfo);
565 m_undoState = STATINGFILE; // temporarily
566 return; // no pop() yet, we'll finish the work in slotResult
567 } else { // dest was stat'ed, and the deletion was approved in slotResult
568 m_currentJob = KIO::file_delete(op.m_dst, KIO::HideProgressInfo);
569 m_undoJob->emitDeleting(op.m_dst);
570 m_undoState = MOVINGFILES;
571 }
572 } else if (m_currentCmd.isMoveOrRename() || m_currentCmd.m_type == FileUndoManager::Trash) {
573 m_currentJob = KIO::file_move(op.m_dst, op.m_src, -1, KIO::HideProgressInfo);
574 m_currentJob->uiDelegateExtension()->createClipboardUpdater(m_currentJob, JobUiDelegateExtension::UpdateContent);
575 m_undoJob->emitMovingOrRenaming(op.m_dst, op.m_src, m_currentCmd.m_type);
576 }
577
578 if (m_currentJob) {
579 m_currentJob->setParentJob(m_undoJob);
580 }
581
582 m_currentCmd.m_opQueue.dequeue();
583 // The above KIO jobs are lowlevel, they don't trigger KDirNotify notification
584 // So we need to do it ourselves (but schedule it to the end of the undo, to compress them)
586 addDirToUpdate(url);
587
589 addDirToUpdate(url);
590}
591
592void FileUndoManagerPrivate::stepRemovingLinks()
593{
594 // qDebug() << "REMOVINGLINKS";
595 if (!m_fileCleanupStack.isEmpty()) {
596 const QUrl file = m_fileCleanupStack.pop();
597 // qDebug() << "file_delete" << file;
598 m_currentJob = KIO::file_delete(file, KIO::HideProgressInfo);
599 m_currentJob->setParentJob(m_undoJob);
600 m_undoJob->emitDeleting(file);
601
603 addDirToUpdate(url);
604 } else {
605 m_undoState = REMOVINGDIRS;
606
607 if (m_dirCleanupStack.isEmpty() && m_currentCmd.m_type == FileUndoManager::Mkdir) {
608 m_dirCleanupStack << m_currentCmd.m_dst;
609 }
610 }
611}
612
613void FileUndoManagerPrivate::stepRemovingDirectories()
614{
615 if (!m_dirCleanupStack.isEmpty()) {
616 QUrl dir = m_dirCleanupStack.pop();
617 // qDebug() << "rmdir" << dir;
618 m_currentJob = KIO::rmdir(dir);
619 m_currentJob->setParentJob(m_undoJob);
620 m_undoJob->emitDeleting(dir);
621 addDirToUpdate(dir);
622 } else {
623 m_currentCmd.m_valid = false;
624 m_currentJob = nullptr;
625 if (m_undoJob) {
626 // qDebug() << "deleting undojob";
627 m_undoJob->emitResult();
628 m_undoJob = nullptr;
629 }
630 for (const QUrl &url : std::as_const(m_dirsToUpdate)) {
631 // qDebug() << "Notifying FilesAdded for " << url;
632 org::kde::KDirNotify::emitFilesAdded(url);
633 }
634 Q_EMIT q->undoJobFinished();
635 slotUnlock();
636 }
637}
638
639// const ref doesn't work due to QDataStream
640void FileUndoManagerPrivate::slotPush(QByteArray data)
641{
642 QDataStream strm(&data, QIODevice::ReadOnly);
643 UndoCommand cmd;
644 strm >> cmd;
645 pushCommand(cmd);
646}
647
648void FileUndoManagerPrivate::pushCommand(const UndoCommand &cmd)
649{
650 m_commands.push(cmd);
651 Q_EMIT q->undoAvailable(true);
652 Q_EMIT q->undoTextChanged(q->undoText());
653}
654
655void FileUndoManagerPrivate::slotPop()
656{
657 m_commands.pop();
658 Q_EMIT q->undoAvailable(q->isUndoAvailable());
659 Q_EMIT q->undoTextChanged(q->undoText());
660}
661
662void FileUndoManagerPrivate::slotLock()
663{
664 // Q_ASSERT(!m_lock);
665 m_lock = true;
666 Q_EMIT q->undoAvailable(q->isUndoAvailable());
667}
668
669void FileUndoManagerPrivate::slotUnlock()
670{
671 // Q_ASSERT(m_lock);
672 m_lock = false;
673 Q_EMIT q->undoAvailable(q->isUndoAvailable());
674}
675
676QByteArray FileUndoManagerPrivate::get() const
677{
678 QByteArray data;
679 QDataStream stream(&data, QIODevice::WriteOnly);
680 stream << m_commands;
681 return data;
682}
683
685{
686 d->m_uiInterface.reset(ui);
687}
688
690{
691 return d->m_uiInterface.get();
692}
693
694////
695
696class Q_DECL_HIDDEN FileUndoManager::UiInterface::UiInterfacePrivate
697{
698public:
699 QPointer<QWidget> m_parentWidget;
700 bool m_showProgressInfo = true;
701};
702
703FileUndoManager::UiInterface::UiInterface()
704 : d(new UiInterfacePrivate)
705{
706}
707
708FileUndoManager::UiInterface::~UiInterface() = default;
709
714
715bool FileUndoManager::UiInterface::copiedFileWasModified(const QUrl &src, const QUrl &dest, const QDateTime &srcTime, const QDateTime &destTime)
716{
717 Q_UNUSED(srcTime); // not sure it should appear in the msgbox
718 // Possible improvement: only show the time if date is today
719 const QString timeStr = QLocale().toString(destTime, QLocale::ShortFormat);
720 const QString msg = i18n(
721 "The file %1 was copied from %2, but since then it has apparently been modified at %3.\n"
722 "Undoing the copy will delete the file, and all modifications will be lost.\n"
723 "Are you sure you want to delete %4?",
726 timeStr,
728
729 const auto result = KMessageBox::warningContinueCancel(d->m_parentWidget,
730 msg,
731 i18n("Undo File Copy Confirmation"),
734 QString(),
736 return result == KMessageBox::Continue;
737}
738
740{
741 return d->m_parentWidget;
742}
743
745{
746 d->m_parentWidget = parentWidget;
747}
748
750{
751 d->m_showProgressInfo = b;
752}
753
755{
756 return d->m_showProgressInfo;
757}
758
760{
761 if (id == HookGetAskUserActionInterface) {
762 auto *p = static_cast<AskUserActionInterface **>(data);
764 static auto *askUserInterface = delegate ? delegate->findChild<AskUserActionInterface *>(QString(), Qt::FindDirectChildrenOnly) : nullptr;
765 *p = askUserInterface;
766 }
767}
768
769#include "fileundomanager.moc"
770#include "moc_fileundomanager.cpp"
771#include "moc_fileundomanager_p.cpp"
The AskUserActionInterface class allows a KIO::Job to prompt the user for a decision when e....
void askUserDeleteResult(bool allowDelete, const QList< QUrl > &urls, KIO::AskUserActionInterface::DeletionType deletionType, QWidget *parent)
Implementations of this interface must emit this signal when the dialog invoked by askUserDelete() fi...
virtual void askUserDelete(const QList< QUrl > &urls, DeletionType deletionType, ConfirmationType confirmationType, QWidget *parent=nullptr)=0
Ask for confirmation before moving urls (files/directories) to the Trash, emptying the Trash,...
@ ForceConfirmation
Always ask the user for confirmation.
void fileRenamed(const QUrl &oldUrl, const QUrl &newUrl)
Signals that a file was renamed.
CopyJob is used to move, copy or symlink files and directories.
Definition copyjob.h:41
QList< QUrl > srcUrls() const
Returns the list of source URLs.
Definition copyjob.cpp:444
void copyingLinkDone(KIO::Job *job, const QUrl &from, const QString &target, const QUrl &to)
The job is copying or moving a symbolic link, that points to target.
CopyMode operationMode() const
Returns the mode of the operation (copy, move, or link), depending on whether KIO::copy(),...
Definition copyjob.cpp:2582
QUrl destUrl() const
Returns the destination URL.
Definition copyjob.cpp:449
void copyingDone(KIO::Job *job, const QUrl &from, const QUrl &to, const QDateTime &mtime, bool directory, bool renamed)
The job emits this signal when copying or moving a file or directory successfully finished.
Interface for the gui handling of FileUndoManager.
void setShowProgressInfo(bool b)
Sets whether to show progress info when running the KIO jobs for undoing.
virtual bool copiedFileWasModified(const QUrl &src, const QUrl &dest, const QDateTime &srcTime, const QDateTime &destTime)
Called when dest was modified since it was copied from src.
virtual void jobError(KIO::Job *job)
Called when an undo job errors; default implementation displays a message box.
virtual void virtual_hook(int id, void *data)
void setParentWidget(QWidget *parentWidget)
Sets the parent widget to use for message boxes.
FileUndoManager: makes it possible to undo kio jobs.
void recordCopyJob(KIO::CopyJob *copyJob)
Record this CopyJob while it's happening and add a command for it so that the user can undo it.
static FileUndoManager * self()
quint64 newCommandSerialNumber()
These two functions are useful when wrapping FileUndoManager and adding custom commands.
void setUiInterface(UiInterface *ui)
Set a new UiInterface implementation.
void jobRecordingStarted(CommandType op)
Emitted when a job recording has been started by FileUndoManager::recordJob() or FileUndoManager::rec...
UiInterface * uiInterface() const
void recordJob(CommandType op, const QList< QUrl > &src, const QUrl &dst, KIO::Job *job)
Record this job while it's happening and add a command for it so that the user can undo it.
void undo()
Undoes the last command Remember to call uiInterface()->setParentWidget(parentWidget) first,...
CommandType
The type of job.
@ Put
Represents the creation of a file from data in memory. Used when pasting data from clipboard or drag-...
@ BatchRename
Represents a KIO::batchRename() job. Used when renaming multiple files.
@ Mkpath
Represents a KIO::mkpath() job.
The base class for all jobs.
Definition job_base.h:45
bool doKill() override
Abort this job.
Definition job.cpp:157
void directoryCreated(const QUrl &url)
Signals that a directory was created.
A KIO job that retrieves information about a file or directory.
Definition statjob.h:26
const UDSEntry & statResult() const
Result of the stat operation.
Definition statjob.cpp:80
long long numberValue(uint field, long long defaultValue=0) const
Definition udsentry.cpp:370
@ UDS_MODIFICATION_TIME
The last time the file was modified. Required time format: seconds since UNIX epoch.
Definition udsentry.h:234
virtual void registerJob(KJob *job)
virtual void showErrorMessage()
void description(KJob *job, const QString &title, const QPair< QString, QString > &field1=QPair< QString, QString >(), const QPair< QString, QString > &field2=QPair< QString, QString >())
virtual QString errorString() const
void emitResult()
int error() const
void result(KJob *job)
KJobUiDelegate * uiDelegate() const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
A namespace for KIO globals.
KIOCORE_EXPORT SimpleJob * rmdir(const QUrl &url)
Removes a single directory.
KIOCORE_EXPORT MkdirJob * mkdir(const QUrl &url, int permissions=-1)
Creates a single directory.
Definition mkdirjob.cpp:110
KIOCORE_EXPORT SimpleJob * rename(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Rename a file or directory.
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
Definition statjob.cpp:203
KIOCORE_EXPORT SimpleJob * file_delete(const QUrl &src, JobFlags flags=DefaultFlags)
Delete a single file.
Definition job.cpp:373
KIOCORE_EXPORT KJobUiDelegate * createDefaultJobUiDelegate()
Convenience method: use default factory, if there's one, to create a delegate and return it.
KIOCORE_EXPORT SimpleJob * symlink(const QString &target, const QUrl &dest, JobFlags flags=DefaultFlags)
Create or move a symlink.
KIOCORE_EXPORT FileCopyJob * file_move(const QUrl &src, const QUrl &dest, int permissions=-1, JobFlags flags=DefaultFlags)
Move a single file.
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition job_base.h:251
@ Overwrite
When set, automatically overwrite the destination if it exists already.
Definition job_base.h:267
KIOCORE_EXPORT KJobTrackerInterface * getJobTracker()
Returns the job tracker to be used by all KIO jobs (in which HideProgressInfo is not set)
void setWindow(QObject *job, QWidget *widget)
ButtonCode warningContinueCancel(QWidget *parent, const QString &text, const QString &title=QString(), const KGuiItem &buttonContinue=KStandardGuiItem::cont(), const KGuiItem &buttonCancel=KStandardGuiItem::cancel(), const QString &dontAskAgainName=QString(), Options options=Notify)
KIOCORE_EXPORT QString dir(const QString &fileClass)
Returns the most recently used directory associated with this file-class.
KGuiItem cont()
KGuiItem cancel()
QDebug operator<<(QDebug dbg, const PerceptualColor::LchaDouble &value)
QDateTime fromSecsSinceEpoch(qint64 secs)
QDateTime toLocalTime() const const
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
bool exists() const const
void append(QList< T > &&value)
bool isEmpty() const const
QString toString(QDate date, FormatType format) const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
Q_OBJECTQ_OBJECT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
T findChild(const QString &name, Qt::FindChildOptions options) const const
QueuedConnection
FindDirectChildrenOnly
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
RemoveFilename
QUrl adjusted(FormattingOptions options) const const
QString toDisplayString(FormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.