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#ifdef WITH_QTDBUS
13#include "fileundomanager_adaptor.h"
14#endif
15#include "fileundomanager_p.h"
16#include "kio_widgets_debug.h"
17#include <job_p.h>
18#include <kdirnotify.h>
19#include <kio/batchrenamejob.h>
20#include <kio/copyjob.h>
21#include <kio/filecopyjob.h>
22#include <kio/jobuidelegate.h>
23#include <kio/mkdirjob.h>
24#include <kio/mkpathjob.h>
25#include <kio/statjob.h>
26
27#include <KJobTrackerInterface>
28#include <KJobWidgets>
29#include <KLocalizedString>
30#include <KMessageBox>
31
32#ifdef WITH_QTDBUS
33#include <QDBusConnection>
34#endif
35
36#include <QDateTime>
37#include <QFileInfo>
38#include <QLocale>
39
40using namespace KIO;
41
42static const char *undoStateToString(UndoState state)
43{
44 static const char *const s_undoStateToString[] = {"MAKINGDIRS", "MOVINGFILES", "STATINGFILE", "REMOVINGDIRS", "REMOVINGLINKS"};
45 return s_undoStateToString[state];
46}
47
48static QDataStream &operator<<(QDataStream &stream, const KIO::BasicOperation &op)
49{
50 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);
51 return stream;
52}
53static QDataStream &operator>>(QDataStream &stream, BasicOperation &op)
54{
55 qint8 type;
56 qint64 mtime;
57 stream >> op.m_valid >> type >> op.m_renamed >> op.m_src >> op.m_dst >> op.m_target >> mtime;
58 op.m_type = static_cast<BasicOperation::Type>(type);
60 return stream;
61}
62
63static QDataStream &operator<<(QDataStream &stream, const UndoCommand &cmd)
64{
65 stream << cmd.m_valid << (qint8)cmd.m_type << cmd.m_opQueue << cmd.m_src << cmd.m_dst;
66 return stream;
67}
68
69static QDataStream &operator>>(QDataStream &stream, UndoCommand &cmd)
70{
71 qint8 type;
72 stream >> cmd.m_valid >> type >> cmd.m_opQueue >> cmd.m_src >> cmd.m_dst;
73 cmd.m_type = static_cast<FileUndoManager::CommandType>(type);
74 return stream;
75}
76
77QDebug operator<<(QDebug dbg, const BasicOperation &op)
78{
79 if (op.m_valid) {
80 static const char *s_types[] = {"File", "Link", "Directory"};
81 dbg << "BasicOperation: type" << s_types[op.m_type] << "src" << op.m_src << "dest" << op.m_dst << "target" << op.m_target << "renamed" << op.m_renamed;
82 } else {
83 dbg << "Invalid BasicOperation";
84 }
85 return dbg;
86}
87/**
88 * checklist:
89 * copy dir -> overwrite -> works
90 * move dir -> overwrite -> works
91 * copy dir -> rename -> works
92 * move dir -> rename -> works
93 *
94 * copy dir -> works
95 * move dir -> works
96 *
97 * copy files -> works
98 * move files -> works (TODO: optimize (change FileCopyJob to use the renamed arg for copyingDone)
99 *
100 * copy files -> overwrite -> works (sorry for your overwritten file...)
101 * move files -> overwrite -> works (sorry for your overwritten file...)
102 *
103 * copy files -> rename -> works
104 * move files -> rename -> works
105 *
106 * -> see also fileundomanagertest, which tests some of the above (but not renaming).
107 *
108 */
109
110class KIO::UndoJob : public KIO::Job
111{
113public:
114 UndoJob(bool showProgressInfo)
115 : KIO::Job()
116 {
117 if (showProgressInfo) {
119 }
120
121 d_ptr->m_privilegeExecutionEnabled = true;
122 d_ptr->m_operationType = d_ptr->Other;
123 d_ptr->m_title = i18n("Undo Changes");
124 d_ptr->m_message = i18n("Undoing this operation requires root privileges. Do you want to continue?");
125 }
126
127 ~UndoJob() override = default;
128
129 virtual void kill(bool) // TODO should be doKill
130 {
131 FileUndoManager::self()->d->stopUndo(true);
133 }
134
135 void emitCreatingDir(const QUrl &dir)
136 {
137 Q_EMIT description(this, i18n("Creating directory"), qMakePair(i18n("Directory"), dir.toDisplayString()));
138 }
139
140 void emitMovingOrRenaming(const QUrl &src, const QUrl &dest, FileUndoManager::CommandType cmdType)
141 {
142 static const QString srcMsg(i18nc("The source of a file operation", "Source"));
143 static const QString destMsg(i18nc("The destination of a file operation", "Destination"));
144
145 Q_EMIT description(this, //
146 cmdType == FileUndoManager::Move ? i18n("Moving") : i18n("Renaming"),
147 {srcMsg, src.toDisplayString()},
148 {destMsg, dest.toDisplayString()});
149 }
150
151 void emitDeleting(const QUrl &url)
152 {
153 Q_EMIT description(this, i18n("Deleting"), qMakePair(i18n("File"), url.toDisplayString()));
154 }
155 void emitResult()
156 {
158 }
159};
160
161CommandRecorder::CommandRecorder(FileUndoManager::CommandType op, const QList<QUrl> &src, const QUrl &dst, KIO::Job *job)
162 : QObject(job)
163 , m_cmd(op, src, dst, FileUndoManager::self()->newCommandSerialNumber())
164{
165 connect(job, &KJob::result, this, &CommandRecorder::slotResult);
166 if (auto *copyJob = qobject_cast<KIO::CopyJob *>(job)) {
167 connect(copyJob, &KIO::CopyJob::copyingDone, this, &CommandRecorder::slotCopyingDone);
168 connect(copyJob, &KIO::CopyJob::copyingLinkDone, this, &CommandRecorder::slotCopyingLinkDone);
169 } else if (auto *mkpathJob = qobject_cast<KIO::MkpathJob *>(job)) {
170 connect(mkpathJob, &KIO::MkpathJob::directoryCreated, this, &CommandRecorder::slotDirectoryCreated);
171 } else if (auto *batchRenameJob = qobject_cast<KIO::BatchRenameJob *>(job)) {
172 connect(batchRenameJob, &KIO::BatchRenameJob::fileRenamed, this, &CommandRecorder::slotBatchRenamingDone);
173 }
174}
175
176void CommandRecorder::slotResult(KJob *job)
177{
178 const int err = job->error();
179 if (err) {
180 if (err != KIO::ERR_USER_CANCELED) {
181 qCDebug(KIO_WIDGETS) << "CommandRecorder::slotResult:" << job->errorString() << " - no undo command will be added";
182 }
183 return;
184 }
185
186 // For CopyJob, don't add an undo command unless the job actually did something,
187 // e.g. if user selected to skip all, there is nothing to undo.
188 // Note: this doesn't apply to other job types, e.g. for Mkdir m_opQueue is
189 // expected to be empty
190 if (qobject_cast<KIO::CopyJob *>(job)) {
191 if (!m_cmd.m_opQueue.isEmpty()) {
192 FileUndoManager::self()->d->addCommand(m_cmd);
193 }
194 return;
195 }
196
197 FileUndoManager::self()->d->addCommand(m_cmd);
198}
199
200void CommandRecorder::slotCopyingDone(KIO::Job *, const QUrl &from, const QUrl &to, const QDateTime &mtime, bool directory, bool renamed)
201{
202 const BasicOperation::Type type = directory ? BasicOperation::Directory : BasicOperation::File;
203 m_cmd.m_opQueue.enqueue(BasicOperation(type, renamed, from, to, mtime));
204}
205
206void CommandRecorder::slotCopyingLinkDone(KIO::Job *, const QUrl &from, const QString &target, const QUrl &to)
207{
208 m_cmd.m_opQueue.enqueue(BasicOperation(BasicOperation::Link, false, from, to, {}, target));
209}
210
211void CommandRecorder::slotDirectoryCreated(const QUrl &dir)
212{
213 m_cmd.m_opQueue.enqueue(BasicOperation(BasicOperation::Directory, false, QUrl{}, dir, {}));
214}
215
216void CommandRecorder::slotBatchRenamingDone(const QUrl &from, const QUrl &to)
217{
218 m_cmd.m_opQueue.enqueue(BasicOperation(BasicOperation::Item, true, from, to, {}));
219}
220
221////
222
223class KIO::FileUndoManagerSingleton
224{
225public:
226 FileUndoManager self;
227};
228Q_GLOBAL_STATIC(KIO::FileUndoManagerSingleton, globalFileUndoManager)
229
231{
232 return &globalFileUndoManager()->self;
233}
234
235// m_nextCommandIndex is initialized to a high number so that konqueror can
236// assign low numbers to closed items loaded "on-demand" from a config file
237// in KonqClosedWindowsManager::readConfig and thus maintaining the real
238// order of the undo items.
239FileUndoManagerPrivate::FileUndoManagerPrivate(FileUndoManager *qq)
240 : m_uiInterface(new FileUndoManager::UiInterface())
241 , m_nextCommandIndex(1000)
242 , q(qq)
243{
244#ifdef WITH_QTDBUS
245 (void)new KIOFileUndoManagerAdaptor(this);
246 const QString dbusPath = QStringLiteral("/FileUndoManager");
247 const QString dbusInterface = QStringLiteral("org.kde.kio.FileUndoManager");
248
250 dbus.registerObject(dbusPath, this);
251 dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("lock"), this, SLOT(slotLock()));
252 dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("pop"), this, SLOT(slotPop()));
253 dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("push"), this, SLOT(slotPush(QByteArray)));
254 dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("unlock"), this, SLOT(slotUnlock()));
255#endif
256}
257
258FileUndoManager::FileUndoManager()
259 : d(new FileUndoManagerPrivate(this))
260{
261}
262
263FileUndoManager::~FileUndoManager() = default;
264
265void FileUndoManager::recordJob(CommandType op, const QList<QUrl> &src, const QUrl &dst, KIO::Job *job)
266{
267 // This records what the job does and calls addCommand when done
268 (void)new CommandRecorder(op, src, dst, job);
270}
271
273{
274 CommandType commandType;
275 switch (copyJob->operationMode()) {
276 case CopyJob::Copy:
277 commandType = Copy;
278 break;
279 case CopyJob::Move:
280 commandType = Move;
281 break;
282 case CopyJob::Link:
283 commandType = Link;
284 break;
285 default:
286 Q_UNREACHABLE();
287 }
288 recordJob(commandType, copyJob->srcUrls(), copyJob->destUrl(), copyJob);
289}
290
291void FileUndoManagerPrivate::addCommand(const UndoCommand &cmd)
292{
293 pushCommand(cmd);
294 Q_EMIT q->jobRecordingFinished(cmd.m_type);
295}
296
298{
299 return !d->m_commands.isEmpty() && !d->m_lock;
300}
301
303{
304 if (d->m_commands.isEmpty()) {
305 return i18n("Und&o");
306 }
307
308 FileUndoManager::CommandType t = d->m_commands.top().m_type;
309 switch (t) {
310 case FileUndoManager::Copy:
311 return i18n("Und&o: Copy");
312 case FileUndoManager::Link:
313 return i18n("Und&o: Link");
314 case FileUndoManager::Move:
315 return i18n("Und&o: Move");
316 case FileUndoManager::Rename:
317 return i18n("Und&o: Rename");
318 case FileUndoManager::Trash:
319 return i18n("Und&o: Trash");
320 case FileUndoManager::Mkdir:
321 return i18n("Und&o: Create Folder");
323 return i18n("Und&o: Create Folder(s)");
325 return i18n("Und&o: Create File");
327 return i18n("Und&o: Batch Rename");
328 }
329 /* NOTREACHED */
330 return QString();
331}
332
334{
335 return ++(d->m_nextCommandIndex);
336}
337
338quint64 FileUndoManager::currentCommandSerialNumber() const
339{
340 if (!d->m_commands.isEmpty()) {
341 const UndoCommand &cmd = d->m_commands.top();
342 Q_ASSERT(cmd.m_valid);
343 return cmd.m_serialNumber;
344 }
345
346 return 0;
347}
348
350{
351 Q_ASSERT(!d->m_commands.isEmpty()); // forgot to record before calling undo?
352
353 // Make a copy of the command to undo before slotPop() pops it.
354 UndoCommand cmd = d->m_commands.last();
355 Q_ASSERT(cmd.m_valid);
356 d->m_currentCmd = cmd;
357 const CommandType commandType = cmd.m_type;
358
359 // Note that m_opQueue is empty for simple operations like Mkdir.
360 const auto &opQueue = d->m_currentCmd.m_opQueue;
361
362 // Let's first ask for confirmation if we need to delete any file (#99898)
363 QList<QUrl> itemsToDelete;
364 for (auto it = opQueue.crbegin(); it != opQueue.crend(); ++it) {
365 const BasicOperation &op = *it;
366 const auto destination = op.m_dst;
367 if (op.m_type == BasicOperation::File && commandType == FileUndoManager::Copy) {
368 if (destination.isLocalFile() && !QFileInfo::exists(destination.toLocalFile())) {
369 continue;
370 }
371 itemsToDelete.append(destination);
372 } else if (commandType == FileUndoManager::Mkpath) {
373 itemsToDelete.append(destination);
374 }
375 }
376 if (commandType == FileUndoManager::Mkdir || commandType == FileUndoManager::Put) {
377 itemsToDelete.append(d->m_currentCmd.m_dst);
378 }
379 if (!itemsToDelete.isEmpty()) {
380 AskUserActionInterface *askUserInterface = nullptr;
381 d->m_uiInterface->virtual_hook(UiInterface::HookGetAskUserActionInterface, &askUserInterface);
382 if (askUserInterface) {
383 if (!d->m_connectedToAskUserInterface) {
384 d->m_connectedToAskUserInterface = true;
385 QObject::connect(askUserInterface, &KIO::AskUserActionInterface::askUserDeleteResult, this, [this](bool allowDelete) {
386 if (allowDelete) {
387 d->startUndo();
388 }
389 });
390 }
391
392 // Because undo can happen with an accidental Ctrl-Z, we want to always confirm.
393 askUserInterface->askUserDelete(itemsToDelete,
394 KIO::AskUserActionInterface::Delete,
396 d->m_uiInterface->parentWidget());
397 return;
398 }
399 }
400
401 d->startUndo();
402}
403
404void FileUndoManagerPrivate::startUndo()
405{
406 slotPop();
407 slotLock();
408
409 m_dirCleanupStack.clear();
410 m_dirStack.clear();
411 m_dirsToUpdate.clear();
412
413 m_undoState = MOVINGFILES;
414
415 // Let's have a look at the basic operations we need to undo.
416 auto &opQueue = m_currentCmd.m_opQueue;
417 for (auto it = opQueue.rbegin(); it != opQueue.rend(); ++it) {
418 const BasicOperation::Type type = (*it).m_type;
419 if (type == BasicOperation::Directory && !(*it).m_renamed) {
420 // If any directory has to be created/deleted, we'll start with that
421 m_undoState = MAKINGDIRS;
422 // Collect all the dirs that have to be created in case of a move undo.
423 if (m_currentCmd.isMoveOrRename()) {
424 m_dirStack.push((*it).m_src);
425 }
426 // Collect all dirs that have to be deleted
427 // from the destination in both cases (copy and move).
428 m_dirCleanupStack.prepend((*it).m_dst);
429 } else if (type == BasicOperation::Link) {
430 m_fileCleanupStack.prepend((*it).m_dst);
431 }
432 }
433 auto isBasicOperation = [this](const BasicOperation &op) {
434 return (op.m_type == BasicOperation::Directory && !op.m_renamed) //
435 || (op.m_type == BasicOperation::Link && !m_currentCmd.isMoveOrRename());
436 };
437 opQueue.erase(std::remove_if(opQueue.begin(), opQueue.end(), isBasicOperation), opQueue.end());
438
439 const FileUndoManager::CommandType commandType = m_currentCmd.m_type;
440 if (commandType == FileUndoManager::Put) {
441 m_fileCleanupStack.append(m_currentCmd.m_dst);
442 }
443
444 qCDebug(KIO_WIDGETS) << "starting with" << undoStateToString(m_undoState);
445 m_undoJob = new UndoJob(m_uiInterface->showProgressInfo());
446 auto undoFunc = [this]() {
447 undoStep();
448 };
450}
451
452void FileUndoManagerPrivate::stopUndo(bool step)
453{
454 m_currentCmd.m_opQueue.clear();
455 m_dirCleanupStack.clear();
456 m_fileCleanupStack.clear();
457 m_undoState = REMOVINGDIRS;
458 m_undoJob = nullptr;
459
460 if (m_currentJob) {
461 m_currentJob->kill();
462 }
463
464 m_currentJob = nullptr;
465
466 if (step) {
467 undoStep();
468 }
469}
470
471void FileUndoManagerPrivate::slotResult(KJob *job)
472{
473 m_currentJob = nullptr;
474 if (job->error()) {
475 qWarning() << job->errorString();
476 m_uiInterface->jobError(static_cast<KIO::Job *>(job));
477 delete m_undoJob;
478 stopUndo(false);
479 } else if (m_undoState == STATINGFILE) {
480 const BasicOperation op = m_currentCmd.m_opQueue.head();
481 // qDebug() << "stat result for " << op.m_dst;
482 KIO::StatJob *statJob = static_cast<KIO::StatJob *>(job);
484 if (mtime != op.m_mtime) {
485 qCDebug(KIO_WIDGETS) << op.m_dst << "was modified after being copied. Initial timestamp" << mtime << "now" << op.m_mtime;
486 QDateTime srcTime = op.m_mtime.toLocalTime();
487 QDateTime destTime = mtime.toLocalTime();
488 if (!m_uiInterface->copiedFileWasModified(op.m_src, op.m_dst, srcTime, destTime)) {
489 stopUndo(false);
490 }
491 }
492 }
493
494 undoStep();
495}
496
497void FileUndoManagerPrivate::addDirToUpdate(const QUrl &url)
498{
499 if (!m_dirsToUpdate.contains(url)) {
500 m_dirsToUpdate.prepend(url);
501 }
502}
503
504void FileUndoManagerPrivate::undoStep()
505{
506 m_currentJob = nullptr;
507
508 if (m_undoState == MAKINGDIRS) {
509 stepMakingDirectories();
510 }
511
512 if (m_undoState == MOVINGFILES || m_undoState == STATINGFILE) {
513 stepMovingFiles();
514 }
515
516 if (m_undoState == REMOVINGLINKS) {
517 stepRemovingLinks();
518 }
519
520 if (m_undoState == REMOVINGDIRS) {
521 stepRemovingDirectories();
522 }
523
524 if (m_currentJob) {
525 if (m_uiInterface) {
526 KJobWidgets::setWindow(m_currentJob, m_uiInterface->parentWidget());
527 }
528 QObject::connect(m_currentJob, &KJob::result, this, &FileUndoManagerPrivate::slotResult);
529 }
530}
531
532void FileUndoManagerPrivate::stepMakingDirectories()
533{
534 if (!m_dirStack.isEmpty()) {
535 QUrl dir = m_dirStack.pop();
536 // qDebug() << "creatingDir" << dir;
537 m_currentJob = KIO::mkdir(dir);
538 m_currentJob->setParentJob(m_undoJob);
539 m_undoJob->emitCreatingDir(dir);
540 } else {
541 m_undoState = MOVINGFILES;
542 }
543}
544
545// Misnamed method: It moves files back, but it also
546// renames directories back, recreates symlinks,
547// deletes copied files, and restores trashed files.
548void FileUndoManagerPrivate::stepMovingFiles()
549{
550 if (m_currentCmd.m_opQueue.isEmpty()) {
551 m_undoState = REMOVINGLINKS;
552 return;
553 }
554
555 const BasicOperation op = m_currentCmd.m_opQueue.head();
556 Q_ASSERT(op.m_valid);
557 if (op.m_type == BasicOperation::Directory || op.m_type == BasicOperation::Item) {
558 Q_ASSERT(op.m_renamed);
559 // qDebug() << "rename" << op.m_dst << op.m_src;
560 m_currentJob = KIO::rename(op.m_dst, op.m_src, KIO::HideProgressInfo);
561 m_undoJob->emitMovingOrRenaming(op.m_dst, op.m_src, m_currentCmd.m_type);
562 } else if (op.m_type == BasicOperation::Link) {
563 // qDebug() << "symlink" << op.m_target << op.m_src;
564 m_currentJob = KIO::symlink(op.m_target, op.m_src, KIO::Overwrite | KIO::HideProgressInfo);
565 } else if (m_currentCmd.m_type == FileUndoManager::Copy) {
566 if (m_undoState == MOVINGFILES) { // dest not stat'ed yet
567 // Before we delete op.m_dst, let's check if it was modified (#20532)
568 // qDebug() << "stat" << op.m_dst;
569 m_currentJob = KIO::stat(op.m_dst, KIO::HideProgressInfo);
570 m_undoState = STATINGFILE; // temporarily
571 return; // no pop() yet, we'll finish the work in slotResult
572 } else { // dest was stat'ed, and the deletion was approved in slotResult
573 m_currentJob = KIO::file_delete(op.m_dst, KIO::HideProgressInfo);
574 m_undoJob->emitDeleting(op.m_dst);
575 m_undoState = MOVINGFILES;
576 }
577 } else if (m_currentCmd.isMoveOrRename() || m_currentCmd.m_type == FileUndoManager::Trash) {
578 m_currentJob = KIO::file_move(op.m_dst, op.m_src, -1, KIO::HideProgressInfo);
579 m_currentJob->uiDelegateExtension()->createClipboardUpdater(m_currentJob, JobUiDelegateExtension::UpdateContent);
580 m_undoJob->emitMovingOrRenaming(op.m_dst, op.m_src, m_currentCmd.m_type);
581 }
582
583 if (m_currentJob) {
584 m_currentJob->setParentJob(m_undoJob);
585 }
586
587 m_currentCmd.m_opQueue.dequeue();
588 // The above KIO jobs are lowlevel, they don't trigger KDirNotify notification
589 // So we need to do it ourselves (but schedule it to the end of the undo, to compress them)
591 addDirToUpdate(url);
592
594 addDirToUpdate(url);
595}
596
597void FileUndoManagerPrivate::stepRemovingLinks()
598{
599 // qDebug() << "REMOVINGLINKS";
600 if (!m_fileCleanupStack.isEmpty()) {
601 const QUrl file = m_fileCleanupStack.pop();
602 // qDebug() << "file_delete" << file;
603 m_currentJob = KIO::file_delete(file, KIO::HideProgressInfo);
604 m_currentJob->setParentJob(m_undoJob);
605 m_undoJob->emitDeleting(file);
606
608 addDirToUpdate(url);
609 } else {
610 m_undoState = REMOVINGDIRS;
611
612 if (m_dirCleanupStack.isEmpty() && m_currentCmd.m_type == FileUndoManager::Mkdir) {
613 m_dirCleanupStack << m_currentCmd.m_dst;
614 }
615 }
616}
617
618void FileUndoManagerPrivate::stepRemovingDirectories()
619{
620 if (!m_dirCleanupStack.isEmpty()) {
621 QUrl dir = m_dirCleanupStack.pop();
622 // qDebug() << "rmdir" << dir;
623 m_currentJob = KIO::rmdir(dir);
624 m_currentJob->setParentJob(m_undoJob);
625 m_undoJob->emitDeleting(dir);
626 addDirToUpdate(dir);
627 } else {
628 m_currentCmd.m_valid = false;
629 m_currentJob = nullptr;
630 if (m_undoJob) {
631 // qDebug() << "deleting undojob";
632 m_undoJob->emitResult();
633 m_undoJob = nullptr;
634 }
635#ifdef WITH_QTDBUS
636 for (const QUrl &url : std::as_const(m_dirsToUpdate)) {
637 // qDebug() << "Notifying FilesAdded for " << url;
638 org::kde::KDirNotify::emitFilesAdded(url);
639 }
640#endif
641 Q_EMIT q->undoJobFinished();
642 slotUnlock();
643 }
644}
645
646// const ref doesn't work due to QDataStream
647void FileUndoManagerPrivate::slotPush(QByteArray data)
648{
649 QDataStream strm(&data, QIODevice::ReadOnly);
650 UndoCommand cmd;
651 strm >> cmd;
652 pushCommand(cmd);
653}
654
655void FileUndoManagerPrivate::pushCommand(const UndoCommand &cmd)
656{
657 m_commands.push(cmd);
658 Q_EMIT q->undoAvailable(true);
659 Q_EMIT q->undoTextChanged(q->undoText());
660}
661
662void FileUndoManagerPrivate::slotPop()
663{
664 m_commands.pop();
665 Q_EMIT q->undoAvailable(q->isUndoAvailable());
666 Q_EMIT q->undoTextChanged(q->undoText());
667}
668
669void FileUndoManagerPrivate::slotLock()
670{
671 // Q_ASSERT(!m_lock);
672 m_lock = true;
673 Q_EMIT q->undoAvailable(q->isUndoAvailable());
674}
675
676void FileUndoManagerPrivate::slotUnlock()
677{
678 // Q_ASSERT(m_lock);
679 m_lock = false;
680 Q_EMIT q->undoAvailable(q->isUndoAvailable());
681}
682
683QByteArray FileUndoManagerPrivate::get() const
684{
685 QByteArray data;
686 QDataStream stream(&data, QIODevice::WriteOnly);
687 stream << m_commands;
688 return data;
689}
690
692{
693 d->m_uiInterface.reset(ui);
694}
695
697{
698 return d->m_uiInterface.get();
699}
700
701////
702
703class Q_DECL_HIDDEN FileUndoManager::UiInterface::UiInterfacePrivate
704{
705public:
706 QPointer<QWidget> m_parentWidget;
707 bool m_showProgressInfo = true;
708};
709
710FileUndoManager::UiInterface::UiInterface()
711 : d(new UiInterfacePrivate)
712{
713}
714
715FileUndoManager::UiInterface::~UiInterface() = default;
716
721
722bool FileUndoManager::UiInterface::copiedFileWasModified(const QUrl &src, const QUrl &dest, const QDateTime &srcTime, const QDateTime &destTime)
723{
724 Q_UNUSED(srcTime); // not sure it should appear in the msgbox
725 // Possible improvement: only show the time if date is today
726 const QString timeStr = QLocale().toString(destTime, QLocale::ShortFormat);
727 const QString msg = i18n(
728 "The file %1 was copied from %2, but since then it has apparently been modified at %3.\n"
729 "Undoing the copy will delete the file, and all modifications will be lost.\n"
730 "Are you sure you want to delete %4?",
733 timeStr,
735
736 const auto result = KMessageBox::warningContinueCancel(d->m_parentWidget,
737 msg,
738 i18n("Undo File Copy Confirmation"),
741 QString(),
742 KMessageBox::Options(KMessageBox::Notify) | KMessageBox::Dangerous);
743 return result == KMessageBox::Continue;
744}
745
747{
748 return d->m_parentWidget;
749}
750
752{
753 d->m_parentWidget = parentWidget;
754}
755
757{
758 d->m_showProgressInfo = b;
759}
760
762{
763 return d->m_showProgressInfo;
764}
765
767{
768 if (id == HookGetAskUserActionInterface) {
769 auto *p = static_cast<AskUserActionInterface **>(data);
771 static auto *askUserInterface = delegate ? delegate->findChild<AskUserActionInterface *>(QString(), Qt::FindDirectChildrenOnly) : nullptr;
772 *p = askUserInterface;
773 }
774}
775
776#include "fileundomanager.moc"
777#include "moc_fileundomanager.cpp"
778#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.
QList< QUrl > srcUrls() const
Returns the list of source URLs.
Definition copyjob.cpp:452
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:2613
QUrl destUrl() const
Returns the destination URL.
Definition copyjob.cpp:457
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.
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.
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::MultiSpinBoxSection &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 Fri Dec 6 2024 12:09:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.