Bug 99898

Summary: Pressing Control+z may lead to data loss
Product: [Unmaintained] kdesktop Reporter: Jordi <jordiyeh>
Component: generalAssignee: David Faure <faure>
Status: CLOSED FIXED    
Severity: grave CC: finex
Priority: NOR    
Version: unspecified   
Target Milestone: ---   
Platform: Gentoo Packages   
OS: Linux   
Latest Commit: Version Fixed In:
Sentry Crash Report:

Description Jordi 2005-02-21 01:44:49 UTC
Version:            (using KDE KDE 3.3.2)
Installed from:    Gentoo Packages
Compiler:          3.3.4 
OS:                Linux

This is the sequence to reproduce the bug.

1- Mount USB drive in /mnt/usbdrive (user has write permission)
2- Copy a file using konqueror to the Desktop (using drag & drop)
3- Umount the /mnt/usbdrive (and remove the usb drive)
4- Close konqueror and click "Show Desktop"
5- Press Control + Z (Ctrl+z / Undo)
6- The file is deleted without notifying the user.

As a consequence, the file was lost since I no longer have the usb drive.

>>>> Edited transcript from the IRC channel
(...)
 <thiago>no file should be erased without confirmation
 [06:17:1718pm] <richmoore>that's kind of what i was trying to say (obviously badly!)
 [06:17:4518pm] <thiago>I thought you both wanted it to display a warning if and only if the original file wasn't there
 [06:18:0118pm] <thiago>in other words: that it shouldn't display the warning if the original was there
 [06:18:1618pm] <richmoore>i said that because i thought that was the only case that it could happen until you pointed out another one
 [06:19:0718pm] <richmoore>i don't care if the file just moves, and it didn't occur to me that the undo could delete things permantently for any other reason
 [06:19:5018pm] <thiago>the only case of undoing that could erase a file and not ask for confirmation would be a move
 [06:20:2818pm] <thinkb5>How should this be reported?
 [06:20:3418pm] <richmoore>thiago: the move back can still fail
 [06:23:1318pm] <richmoore>thinkb5: it is probably a bug in libkonq
 [06:25:5118pm] <thiago>richmoore: true, but then no files will be erased
 [06:26:2418pm] <thiago>thinkb5: file a bug report against kdesktop, on bugs.kde.org

>>>> End
Comment 1 Stefan Monov 2007-02-06 00:39:56 UTC
Elevating to grave.
Comment 2 David Faure 2007-02-11 11:59:50 UTC
SVN commit 632461 by dfaure:

It's just too easy to lose files when pressing Ctrl+z by mistake in konqueror or kdesktop, after copying a file.
E.g. when the file comes from a removable device, or has been removed meanwhile...
Let's ask for confirmation before Undo deletes any file.
BUG: 99898


 M  +13 -11    konq_operations.cc  
 M  +12 -3     konq_operations.h  
 M  +39 -12    konq_undo.cc  
 M  +9 -0      konq_undo.h  
 M  +26 -1     tests/konqundomanagertest.cpp  


--- trunk/KDE/kdebase/libkonq/konq_operations.cc #632460:632461
@@ -93,7 +93,7 @@
     }
 
     KonqOperations * op = new KonqOperations( parent );
-    int confirmation = DEFAULT_CONFIRMATION;
+    ConfirmationType confirmation = DEFAULT_CONFIRMATION;
     op->_del( method, selectedUrls, confirmation );
 }
 
@@ -171,7 +171,7 @@
         KonqUndoManager::self()->recordJob( method==MOVE?KonqUndoManager::MOVE:KonqUndoManager::LINK, selectedUrls, destUrl, job );
 }
 
-void KonqOperations::_del( Operation method, const KUrl::List & _selectedUrls, int confirmation )
+void KonqOperations::_del( Operation method, const KUrl::List & _selectedUrls, ConfirmationType confirmation )
 {
     KUrl::List selectedUrls;
     for (KUrl::List::ConstIterator it = _selectedUrls.begin(); it != _selectedUrls.end(); ++it)
@@ -182,11 +182,11 @@
         return;
     }
 
-    m_method = method;
-    if ( confirmation == SKIP_CONFIRMATION || askDeleteConfirmation( selectedUrls, confirmation ) )
+    if ( askDeleteConfirmation( selectedUrls, method, confirmation, parentWidget() ) )
     {
         //m_srcUrls = selectedUrls;
         KIO::Job *job;
+        m_method = method;
         switch( method )
         {
         case TRASH:
@@ -229,16 +229,18 @@
              SLOT( slotResult( KJob * ) ) );
 }
 
-bool KonqOperations::askDeleteConfirmation( const KUrl::List & selectedUrls, int confirmation )
+bool KonqOperations::askDeleteConfirmation( const KUrl::List & selectedUrls, int method, ConfirmationType confirmation, QWidget* widget )
 {
+    if ( confirmation == SKIP_CONFIRMATION )
+        return true;
     QString keyName;
     bool ask = ( confirmation == FORCE_CONFIRMATION );
     if ( !ask )
     {
         KConfig config("konquerorrc", true, false);
         config.setGroup( "Trash" );
-        keyName = ( m_method == DEL ? "ConfirmDelete" : "ConfirmTrash" );
-        bool defaultValue = ( m_method == DEL ? DEFAULT_CONFIRMDELETE : DEFAULT_CONFIRMTRASH );
+        keyName = ( method == DEL ? "ConfirmDelete" : "ConfirmTrash" );
+        bool defaultValue = ( method == DEL ? DEFAULT_CONFIRMDELETE : DEFAULT_CONFIRMTRASH );
         ask = config.readEntry( keyName, QVariant(defaultValue )).toBool();
     }
     if ( ask )
@@ -256,11 +258,11 @@
         }
 
         int result;
-        switch(m_method)
+        switch(method)
         {
         case DEL:
             result = KMessageBox::warningContinueCancelList(
-                parentWidget(),
+                widget,
              	i18np( "Do you really want to delete this item?", "Do you really want to delete these %n items?", prettyList.count()),
              	prettyList,
 		i18n( "Delete Files" ),
@@ -271,7 +273,7 @@
         case MOVE:
         default:
             result = KMessageBox::warningContinueCancelList(
-                parentWidget(),
+                widget,
                 i18np( "Do you really want to move this item to the trash?", "Do you really want to move these %n items to the trash?", prettyList.count()),
                 prettyList,
 		i18n( "Move to Trash" ),
@@ -492,7 +494,7 @@
         }
 
         m_method = TRASH;
-        if ( askDeleteConfirmation( mlst, DEFAULT_CONFIRMATION ) )
+        if ( askDeleteConfirmation( mlst, TRASH, DEFAULT_CONFIRMATION, parentWidget() ) )
             action = Qt::MoveAction;
         else
         {
--- trunk/KDE/kdebase/libkonq/konq_operations.h #632460:632461
@@ -132,15 +132,24 @@
      */
     static void newDir( QWidget * parent, const KUrl & baseUrl );
 
+    enum ConfirmationType { DEFAULT_CONFIRMATION, SKIP_CONFIRMATION, FORCE_CONFIRMATION };
+    /**
+     * Ask for confirmation before deleting/trashing @p selectedUrls.
+     * @param selectedUrls the urls about to be deleted
+     * @param method the type of deletion (DEL for real deletion, anything else for trash)
+     * @param confirmation default (based on config file), skip (no confirmation) or force (always confirm)
+     * @param widget parent widget for message boxes
+     * @return true if confirmed
+     */
+    static bool askDeleteConfirmation( const KUrl::List & selectedUrls, int method, ConfirmationType confirmation, QWidget* widget );
+
 Q_SIGNALS:
     void statFinished( const KFileItem * item );
     void aboutToCreate(const QPoint &pos, const QList<KIO::CopyInfo> &files);
 
 private:
     QWidget* parentWidget() const;
-    enum { DEFAULT_CONFIRMATION, SKIP_CONFIRMATION, FORCE_CONFIRMATION };
-    bool askDeleteConfirmation( const KUrl::List & selectedUrls, int confirmation );
-    void _del( Operation method, const KUrl::List & selectedUrls, int confirmation );
+    void _del( Operation method, const KUrl::List & selectedUrls, ConfirmationType confirmation );
     void _restoreTrashedItems( const KUrl::List& urls );
     void _statUrl( const KUrl & url, const QObject *receiver, const char *member );
 
--- trunk/KDE/kdebase/libkonq/konq_undo.cc #632460:632461
@@ -21,8 +21,9 @@
 #include "konq_undo.h"
 #include "konq_undo_p.h"
 #include "undomanageradaptor.h"
+#include "konq_operations.h"
 
-#include "kio/observer.h"
+#include <kio/observer.h>
 #include <kio/job.h>
 #include <kio/jobuidelegate.h>
 #include <QtDBus/QtDBus>
@@ -277,24 +278,40 @@
 {
     // Make a copy of the command to undo before broadcastPop() pops it.
     KonqCommand cmd = d->m_commands.top();
+    assert( cmd.m_valid );
+    d->m_current = cmd;
+
+    KonqBasicOperation::Stack& opStack = d->m_current.m_opStack;
+    assert( !opStack.isEmpty() );
+
+    // Let's first ask for confirmation if we need to delete any file (#99898)
+    KUrl::List fileCleanupStack;
+    QStack<KonqBasicOperation>::Iterator it = opStack.begin();
+    for ( ; it != opStack.end() ; ++it ) {
+        KonqBasicOperation::Type type = (*it).m_type;
+        if ( type == KonqBasicOperation::File && d->m_current.m_type == KonqUndoManager::COPY ) {
+            fileCleanupStack.append( (*it).m_dst );
+        }
+    }
+    if ( !fileCleanupStack.isEmpty() ) {
+        if ( !d->m_uiInterface->confirmDeletion( fileCleanupStack ) ) {
+            return;
+        }
+    }
+
     broadcastPop();
     broadcastLock();
 
-    assert( cmd.m_valid );
-
-    d->m_current = cmd;
     d->m_dirCleanupStack.clear();
     d->m_dirStack.clear();
     d->m_dirsToUpdate.clear();
 
     d->m_undoState = MOVINGFILES;
+
     // Let's have a look at the basic operations we need to undo.
     // While we're at it, collect all links that should be deleted.
 
-    KonqBasicOperation::Stack& opStack = d->m_current.m_opStack;
-    assert( !opStack.isEmpty() );
-
-    QStack<KonqBasicOperation>::Iterator it = opStack.begin();
+    it = opStack.begin();
     while ( it != opStack.end() ) // don't cache end() here, erase modifies it
     {
         bool removeBasicOperation = false;
@@ -396,9 +413,11 @@
     if ( d->m_undoState == REMOVINGDIRS )
         stepRemovingDirectories();
 
-    if ( d->m_currentJob )
+    if ( d->m_currentJob ) {
+        //TODO d->m_currentJob->setWindow(...);
         connect( d->m_currentJob, SIGNAL( result( KJob * ) ),
                  this, SLOT( slotResult( KJob * ) ) );
+    }
 }
 
 void KonqUndoManager::stepMakingDirectories()
@@ -658,9 +677,9 @@
     d->m_uiInterface = ui;
 }
 
-KonqUndoManager::UiInterface::UiInterface( QWidget* )
+KonqUndoManager::UiInterface::UiInterface( QWidget* w )
+    : m_parentWidget( w )
 {
-    // TODO store widget
 }
 
 void KonqUndoManager::UiInterface::jobError( KIO::Job* job )
@@ -675,7 +694,7 @@
     // Possible improvement: only show the time if date is today
     const QString timeStr = KGlobal::locale()->formatDateTime( destDt, true /*short*/ );
     return KMessageBox::warningContinueCancel(
-        0 /*TODO parent*/,
+        m_parentWidget,
         i18n( "The file %1 was copied from %2, but since then it has apparently been modified at %3.\n"
               "Undoing the copy will delete the file, and all modifications will be lost.\n"
               "Are you sure you want to delete %4?", dest.pathOrUrl(), src.pathOrUrl(), timeStr, dest.pathOrUrl() ),
@@ -685,5 +704,13 @@
         KMessageBox::Notify | KMessageBox::Dangerous ) == KMessageBox::Continue;
 }
 
+bool KonqUndoManager::UiInterface::confirmDeletion( const KUrl::List& files )
+{
+    // Because undo can happen with an accidental Ctrl-Z, we want to always confirm.
+    return KonqOperations::askDeleteConfirmation( files, KonqOperations::DEL,
+                                                  KonqOperations::FORCE_CONFIRMATION,
+                                                  m_parentWidget );
+}
+
 #include "konq_undo.moc"
 #include "konq_undo_p.moc"
--- trunk/KDE/kdebase/libkonq/konq_undo.h #632460:632461
@@ -63,10 +63,19 @@
     virtual void jobError( KIO::Job* job );
 
     /**
+     * Called when we are about to remove those files.
+     * Return true if we should proceed with deleting them.
+     */
+    virtual bool confirmDeletion( const KUrl::List& files );
+
+    /**
      * Called when dest was modified since it was copied from src.
+     * Note that this is called after confirmDeletion.
      * Return true if we should proceed with deleting dest.
      */
     virtual bool copiedFileWasModified( const KUrl& src, const KUrl& dest, time_t srcTime, time_t destTime );
+  private:
+    QWidget* m_parentWidget;
   };
 
   /**
--- trunk/KDE/kdebase/libkonq/tests/konqundomanagertest.cpp #632460:632461
@@ -118,7 +118,7 @@
 class TestUiInterface : public KonqUndoManager::UiInterface
 {
 public:
-    TestUiInterface() : KonqUndoManager::UiInterface(0) {}
+    TestUiInterface() : KonqUndoManager::UiInterface(0), m_nextReplyToConfirmDeletion(true) {}
     virtual void jobError( KIO::Job* job ) {
         kFatal() << job->errorString() << endl;
     }
@@ -129,9 +129,23 @@
         Q_UNUSED( destTime );
         return true;
     }
+    virtual bool confirmDeletion( const KUrl::List& files ) {
+        m_files = files;
+        return m_nextReplyToConfirmDeletion;
+    }
+    void setNextReplyToConfirmDeletion( bool b ) {
+        m_nextReplyToConfirmDeletion = b;
+    }
+    KUrl::List files() const { return m_files; }
     KUrl dest() const { return m_dest; }
+    void clear() {
+        m_dest = KUrl();
+        m_files.clear();
+    }
 private:
+    bool m_nextReplyToConfirmDeletion;
     KUrl m_dest;
+    KUrl::List m_files;
 };
 
 void KonqUndoManagerTest::initTestCase()
@@ -211,12 +225,23 @@
     QVERIFY( KonqUndoManager::self()->undoAvailable() );
     QCOMPARE( spyUndoAvailable.count(), 1 );
     QCOMPARE( spyTextChanged.count(), 1 );
+    m_uiInterface->clear();
 
+    m_uiInterface->setNextReplyToConfirmDeletion( false ); // act like the user didn't confirm
+    KonqUndoManager::self()->undo();
+    QCOMPARE( m_uiInterface->files().count(), 1 ); // confirmDeletion was called
+    QCOMPARE( m_uiInterface->files()[0].url(), KUrl(destFile()).url() );
+    QVERIFY( QFile::exists( destFile() ) ); // nothing happened yet
+
+    // OK, now do it
+    m_uiInterface->setNextReplyToConfirmDeletion( true );
     doUndo();
 
     QVERIFY( !KonqUndoManager::self()->undoAvailable() );
     QVERIFY( spyUndoAvailable.count() >= 2 ); // it's in fact 3, due to lock/unlock emitting it as well
     QCOMPARE( spyTextChanged.count(), 2 );
+    QCOMPARE( m_uiInterface->files().count(), 1 ); // confirmDeletion was called
+    QCOMPARE( m_uiInterface->files()[0].url(), KUrl(destFile()).url() );
 
     // Check that undo worked
     QVERIFY( !QFile::exists( destFile() ) );
Comment 3 David Faure 2007-02-12 11:51:11 UTC
Backport to 3.x branch now (commit 632792)
Will be in kde-3.5.7.
Comment 4 FiNeX 2009-01-02 20:30:21 UTC
Bug closed. Kdesktop is no more mantained.