KIO

fileundomanager.cpp
1 /*
2  This file is part of the KDE project
3  SPDX-FileCopyrightText: 2000 Simon Hausmann <[email protected]>
4  SPDX-FileCopyrightText: 2006, 2008 David Faure <[email protected]>
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 
35 using namespace KIO;
36 
37 static 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 
43 static 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 }
48 static 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);
54  op.m_mtime = QDateTime::fromSecsSinceEpoch(mtime, Qt::UTC);
55  return stream;
56 }
57 
58 static 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 
64 static 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 
72 QDebug 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 
105 class KIO::UndoJob : public KIO::Job
106 {
107  Q_OBJECT
108 public:
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 
156 CommandRecorder::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 
171 void 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 
195 void 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 
201 void 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 
206 void CommandRecorder::slotDirectoryCreated(const QUrl &dir)
207 {
208  m_cmd.m_opQueue.enqueue(BasicOperation(BasicOperation::Directory, false, QUrl{}, dir, {}));
209 }
210 
211 void 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 
218 class KIO::FileUndoManagerSingleton
219 {
220 public:
221  FileUndoManager self;
222 };
223 Q_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.
234 FileUndoManagerPrivate::FileUndoManagerPrivate(FileUndoManager *qq)
235  : m_uiInterface(new FileUndoManager::UiInterface())
236  , m_nextCommandIndex(1000)
237  , q(qq)
238 {
239  (void)new KIOFileUndoManagerAdaptor(this);
240  const QString dbusPath = QStringLiteral("/FileUndoManager");
241  const QString dbusInterface = QStringLiteral("org.kde.kio.FileUndoManager");
242 
244  dbus.registerObject(dbusPath, this);
245  dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("lock"), this, SLOT(slotLock()));
246  dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("pop"), this, SLOT(slotPop()));
247  dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("push"), this, SLOT(slotPush(QByteArray)));
248  dbus.connect(QString(), dbusPath, dbusInterface, QStringLiteral("unlock"), this, SLOT(slotUnlock()));
249 }
250 
251 FileUndoManager::FileUndoManager()
252  : d(new FileUndoManagerPrivate(this))
253 {
254 }
255 
256 FileUndoManager::~FileUndoManager() = default;
257 
258 void FileUndoManager::recordJob(CommandType op, const QList<QUrl> &src, const QUrl &dst, KIO::Job *job)
259 {
260  // This records what the job does and calls addCommand when done
261  (void)new CommandRecorder(op, src, dst, job);
263 }
264 
266 {
267  CommandType commandType;
268  switch (copyJob->operationMode()) {
269  case CopyJob::Copy:
270  commandType = Copy;
271  break;
272  case CopyJob::Move:
273  commandType = Move;
274  break;
275  case CopyJob::Link:
276  commandType = Link;
277  break;
278  default:
279  Q_UNREACHABLE();
280  }
281  recordJob(commandType, copyJob->srcUrls(), copyJob->destUrl(), copyJob);
282 }
283 
284 void FileUndoManagerPrivate::addCommand(const UndoCommand &cmd)
285 {
286  pushCommand(cmd);
287  Q_EMIT q->jobRecordingFinished(cmd.m_type);
288 }
289 
290 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 79)
292 {
293  return isUndoAvailable();
294 }
295 #endif
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 
338 quint64 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, [=](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 
404 void 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 
452 void 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 
471 void 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 
497 void FileUndoManagerPrivate::addDirToUpdate(const QUrl &url)
498 {
499  if (!m_dirsToUpdate.contains(url)) {
500  m_dirsToUpdate.prepend(url);
501  }
502 }
503 
504 void 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 
532 void 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.
548 void 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 
597 void 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 
618 void 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  for (const QUrl &url : std::as_const(m_dirsToUpdate)) {
636  // qDebug() << "Notifying FilesAdded for " << url;
637  org::kde::KDirNotify::emitFilesAdded(url);
638  }
639  Q_EMIT q->undoJobFinished();
640  slotUnlock();
641  }
642 }
643 
644 // const ref doesn't work due to QDataStream
645 void FileUndoManagerPrivate::slotPush(QByteArray data)
646 {
647  QDataStream strm(&data, QIODevice::ReadOnly);
648  UndoCommand cmd;
649  strm >> cmd;
650  pushCommand(cmd);
651 }
652 
653 void FileUndoManagerPrivate::pushCommand(const UndoCommand &cmd)
654 {
655  m_commands.push(cmd);
656  Q_EMIT q->undoAvailable(true);
657  Q_EMIT q->undoTextChanged(q->undoText());
658 }
659 
660 void FileUndoManagerPrivate::slotPop()
661 {
662  m_commands.pop();
663  Q_EMIT q->undoAvailable(q->isUndoAvailable());
664  Q_EMIT q->undoTextChanged(q->undoText());
665 }
666 
667 void FileUndoManagerPrivate::slotLock()
668 {
669  // Q_ASSERT(!m_lock);
670  m_lock = true;
671  Q_EMIT q->undoAvailable(q->isUndoAvailable());
672 }
673 
674 void FileUndoManagerPrivate::slotUnlock()
675 {
676  // Q_ASSERT(m_lock);
677  m_lock = false;
678  Q_EMIT q->undoAvailable(q->isUndoAvailable());
679 }
680 
682 {
683  QByteArray data;
684  QDataStream stream(&data, QIODevice::WriteOnly);
685  stream << m_commands;
686  return data;
687 }
688 
690 {
691  d->m_uiInterface.reset(ui);
692 }
693 
695 {
696  return d->m_uiInterface.get();
697 }
698 
699 ////
700 
701 class Q_DECL_HIDDEN FileUndoManager::UiInterface::UiInterfacePrivate
702 {
703 public:
704  QWidget *m_parentWidget = nullptr;
705  bool m_showProgressInfo = true;
706 };
707 
708 FileUndoManager::UiInterface::UiInterface()
709  : d(new UiInterfacePrivate)
710 {
711 }
712 
713 FileUndoManager::UiInterface::~UiInterface() = default;
714 
716 {
717  job->uiDelegate()->showErrorMessage();
718 }
719 
720 bool FileUndoManager::UiInterface::copiedFileWasModified(const QUrl &src, const QUrl &dest, const QDateTime &srcTime, const QDateTime &destTime)
721 {
722  Q_UNUSED(srcTime); // not sure it should appear in the msgbox
723  // Possible improvement: only show the time if date is today
724  const QString timeStr = QLocale().toString(destTime, QLocale::ShortFormat);
725  const QString msg = i18n(
726  "The file %1 was copied from %2, but since then it has apparently been modified at %3.\n"
727  "Undoing the copy will delete the file, and all modifications will be lost.\n"
728  "Are you sure you want to delete %4?",
731  timeStr,
733 
734  const auto result = KMessageBox::warningContinueCancel(d->m_parentWidget,
735  msg,
736  i18n("Undo File Copy Confirmation"),
739  QString(),
741  return result == KMessageBox::Continue;
742 }
743 
745 {
746  KIO::JobUiDelegate uiDelegate(JobUiDelegate::Version::V2);
747  uiDelegate.setWindow(d->m_parentWidget);
748  // Because undo can happen with an accidental Ctrl-Z, we want to always confirm.
749  return uiDelegate.askDeleteConfirmation(files, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::ForceConfirmation);
750 }
751 
753 {
754  return d->m_parentWidget;
755 }
756 
758 {
759  d->m_parentWidget = parentWidget;
760 }
761 
763 {
764  d->m_showProgressInfo = b;
765 }
766 
768 {
769  return d->m_showProgressInfo;
770 }
771 
773 {
774  if (id == HookGetAskUserActionInterface) {
775  auto *p = static_cast<AskUserActionInterface **>(data);
776  static KJobUiDelegate *delegate = KIO::createDefaultJobUiDelegate();
777  static auto *askUserInterface = delegate ? delegate->findChild<AskUserActionInterface *>(QString(), Qt::FindDirectChildrenOnly) : nullptr;
778  *p = askUserInterface;
779  }
780 }
781 
782 #include "fileundomanager.moc"
783 #include "moc_fileundomanager.cpp"
784 #include "moc_fileundomanager_p.cpp"
@ Overwrite
When set, automatically overwrite the destination if it exists already.
Definition: job_base.h:290
Q_OBJECTQ_OBJECT
void append(const T &value)
void setParentWidget(QWidget *parentWidget)
Sets the parent widget to use for message boxes.
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
Interface for the gui handling of FileUndoManager.
CopyMode operationMode() const
Returns the mode of the operation (copy, move, or link), depending on whether KIO::copy(),...
Definition: copyjob.cpp:2605
virtual void registerJob(KJob *job)
KIOCORE_EXPORT SimpleJob * rename(const QUrl &src, const QUrl &dest, JobFlags flags=DefaultFlags)
Rename a file or directory.
Definition: simplejob.cpp:363
KIOCORE_EXPORT SimpleJob * file_delete(const QUrl &src, JobFlags flags=DefaultFlags)
Delete a single file.
Definition: job.cpp:380
virtual bool confirmDeletion(const QList< QUrl > &files)
Called when we are about to remove those files.
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)
Q_EMITQ_EMIT
void result(KJob *job)
quint64 newCommandSerialNumber()
These two functions are useful when wrapping FileUndoManager and adding custom commands.
Type type(const QSqlDatabase &db)
The AskUserActionInterface class allows a KIO::Job to prompt the user for a decision when e....
QUrl destUrl() const
Returns the destination URL.
Definition: copyjob.cpp:436
KJOBWIDGETS_EXPORT void setWindow(KJob *job, QWidget *widget)
QDataStream & operator<<(QDataStream &out, const KDateTime &dateTime)
QString undoText() const
bool registerObject(const QString &path, QObject *object, QDBusConnection::RegisterOptions options)
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.
virtual void showErrorMessage()
bool kill(KillVerbosity verbosity=Quietly)
bool exists() const const
void setShowProgressInfo(bool b)
Sets whether to show progress info when running the KIO jobs for undoing.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
KJobUiDelegate * uiDelegate() const
void setWindow(QWidget *window) override
Associate this job with a window given by window.
Q_GLOBAL_STATIC(Internal::StaticControl, s_instance) class ControlPrivate
KIOCORE_EXPORT KJobTrackerInterface * getJobTracker()
Returns the job tracker to be used by all KIO jobs (in which HideProgressInfo is not set)
Definition: jobtracker.cpp:14
static FileUndoManager * self()
@ Put
Represents the creation of a file from data in memory. Used when pasting data from clipboard or drag-...
QList< QUrl > srcUrls() const
Returns the list of source URLs.
Definition: copyjob.cpp:431
KGuiItem cancel()
@ ForceConfirmation
Always ask the user for confirmation.
QString i18n(const char *text, const TYPE &arg...)
QDBusConnection sessionBus()
RemoveFilename
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.
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 recordCopyJob(KIO::CopyJob *copyJob)
Record this CopyJob while it's happening and add a command for it so that the user can undo it.
FindDirectChildrenOnly
QString toDisplayString(QUrl::FormattingOptions options) const const
void description(KJob *job, const QString &title, const QPair< QString, QString > &field1=QPair< QString, QString >(), const QPair< QString, QString > &field2=QPair< QString, QString >())
QString toString(qlonglong i) const const
virtual void jobError(KIO::Job *job)
Called when an undo job errors; default implementation displays a message box.
KIOCORE_EXPORT KJobUiDelegate * createDefaultJobUiDelegate()
Convenience method: use default factory, if there's one, to create a delegate and return it.
QueuedConnection
bool isEmpty() const const
bool isUndoAvailable() const
bool doKill() override
Abort this job.
Definition: job.cpp:164
KIOCORE_EXPORT FileCopyJob * file_move(const QUrl &src, const QUrl &dest, int permissions=-1, JobFlags flags=DefaultFlags)
Move a single file.
UiInterface * uiInterface() const
const UDSEntry & statResult() const
Result of the stat operation.
Definition: statjob.cpp:115
QDateTime toLocalTime() const const
T findChild(const QString &name, Qt::FindChildOptions options) const const
void setUiInterface(UiInterface *ui)
Set a new UiInterface implementation.
CommandType
The type of job.
long long numberValue(uint field, long long defaultValue=0) const
Definition: udsentry.cpp:381
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
Returns the most recently used directory associated with this file-class.
Definition: krecentdirs.cpp:47
@ UDS_MODIFICATION_TIME
The last time the file was modified. Required time format: seconds since UNIX epoch.
Definition: udsentry.h:259
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
QUrl adjusted(QUrl::FormattingOptions options) const const
QDateTime fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offsetSeconds)
A namespace for KIO globals.
@ Mkpath
Represents a KIO::mkpath() job.
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.
KGuiItem cont()
KIOCORE_EXPORT SimpleJob * rmdir(const QUrl &url)
Removes a single directory.
Definition: simplejob.cpp:336
QString i18nc(const char *context, const char *text, const TYPE &arg...)
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,...
@ HideProgressInfo
Hide progress information dialog, i.e. don't show a GUI.
Definition: job_base.h:274
void jobRecordingStarted(CommandType op)
Emitted when a job recording has been started by FileUndoManager::recordJob() or FileUndoManager::rec...
@ BatchRename
Represents a KIO::batchRename() job. Used when renaming multiple files.
void fileRenamed(const QUrl &oldUrl, const QUrl &newUrl)
Signals that a file was renamed.
void emitResult()
void directoryCreated(const QUrl &url)
Signals that a directory was created.
void undo()
Undoes the last command Remember to call uiInterface()->setParentWidget(parentWidget) first,...
virtual QString errorString() const
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
Find all details for one file or directory.
Definition: statjob.cpp:215
int error() const
KIOCORE_EXPORT SimpleJob * symlink(const QString &target, const QUrl &dest, JobFlags flags=DefaultFlags)
Create or move a symlink.
Definition: simplejob.cpp:370
QDataStream & operator>>(QDataStream &in, KDateTime &dateTime)
virtual void virtual_hook(int id, void *data)
bool askDeleteConfirmation(const QList< QUrl > &urls, DeletionType deletionType, ConfirmationType confirmationType) override
Ask for confirmation before deleting/trashing urls.
virtual QVariant get(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName)
KIOCORE_EXPORT MkdirJob * mkdir(const QUrl &url, int permissions=-1)
Creates a single directory.
Definition: mkdirjob.cpp:110
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Tue Feb 7 2023 04:00:35 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.