option to enter new name in overwrite dialog
[case] / src / fileoperator.cpp
index 8d422e6..a779a87 100644 (file)
@@ -23,6 +23,8 @@
 #include <QHBoxLayout>
 #include <QChar>
 
+#include "dialog.h"
+
 #include <math.h>
 #include <errno.h>
 #include <iostream>
 }
 
 
-#define ERROR_PROMPT_XP(operation, promptString, fileName, onIgnore, quitCmd)               \
+#define SPECIAL_COPY_ERROR_PROMPT(operation, promptString, fileName)                        \
 {                                                                                           \
     ERROR_PROMPT(operation, promptString, fileName)                                         \
     if (abort || response == FileOperator::IGNORE) {                                        \
-        if (!abort) onIgnore;                                                               \
-        quitCmd;                                                                            \
+        if (!abort) {                                                                       \
+            updateProgress(fileSizeMap[path]);                                              \
+            removeExcludeFiles.insert(path);                                                \
+        }                                                                                   \
+        return;                                                                             \
     }                                                                                       \
 }
 
 {                                                                                           \
     response = FileOperator::NONE;                                                          \
                                                                                             \
-    if (newFile.exists()) {                                                                 \
+    while (response == FileOperator::NONE && newFile.exists()) {                            \
         if (overwriteAll != FileOperator::NONE) {                                           \
             response = overwriteAll;                                                        \
         } else {                                                                            \
-            bool dirOverDir = false;                                                        \
-            if (newFile.isDir() && file.isDir()) dirOverDir = true;                         \
-            emit showOverwritePrompt(this, newFile.absoluteFilePath(), dirOverDir);         \
+            emit showOverwritePrompt(this, newFile.absoluteFilePath(),                      \
+                newFile.isDir() && file.isDir());                                           \
             waitCond.wait(&mutex);                                                          \
+                                                                                            \
+            if (response == FileOperator::NONE) {                                           \
+                emit showInputFilenamePrompt(this, newFile, file.isDir());                  \
+                waitCond.wait(&mutex);                                                      \
+                if (newNameFromDialog.size()) {                                             \
+                    newFile.setFile(newNameFromDialog);                                     \
+                }                                                                           \
+            }                                                                               \
         }                                                                                   \
     }                                                                                       \
+    if (response == FileOperator::ASK) response = FileOperator::NONE;                       \
 }
 
 
@@ -87,6 +100,7 @@ FileOperator::FileOperator(QWidget *parent) : QWidget(parent) {
     layout->setContentsMargins(0, 0, 0, 0);
     layout->setSpacing(0);
     setLayout(layout);
+    qRegisterMetaType<QFileInfo>("QFileInfo");
 }
 
 
@@ -215,41 +229,88 @@ void FileOperator::showOverwritePrompt(
     const QString &fileName,
     const bool dirOverDir)
 {
-    QMessageBox msgBox;
-    msgBox.addButton(QMessageBox::Cancel);
-    QAbstractButton *yesButton = msgBox.addButton(QMessageBox::Yes);
-    QAbstractButton *yesToAllButton = msgBox.addButton(QMessageBox::YesToAll);
-    QAbstractButton *noButton = msgBox.addButton(QMessageBox::No);
-    QAbstractButton *noToAllButton = msgBox.addButton(QMessageBox::NoToAll);
-    QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
+    Dialog msgBox;
+    msgBox.addButtonFirst(QDialogButtonBox::Cancel);
+    QAbstractButton *yesButton = msgBox.addButtonFirst(QDialogButtonBox::Yes);
+    QAbstractButton *yesToAllButton = msgBox.addButtonFirst(QDialogButtonBox::YesToAll);
+    QAbstractButton *noButton = msgBox.addButtonSecond(QDialogButtonBox::No);
+    QAbstractButton *noToAllButton = msgBox.addButtonSecond(QDialogButtonBox::NoToAll);
+    QAbstractButton *abortButton = msgBox.addButtonSecond(tr("Abort"), QDialogButtonBox::DestructiveRole);
+    QAbstractButton *newNameButton = msgBox.addButtonFirst(tr("New Name"), QDialogButtonBox::AcceptRole);
     QAbstractButton *askButton = 0;
+    QAbstractButton *skipDirButton = 0;
 
     if (dirOverDir) {
         msgBox.setText(tr("Directory %1 already exists. Overwrite the files inside?")
             .arg(FileOperator::shortenPath(fileName)));
-        askButton = msgBox.addButton(tr("Ask"), QMessageBox::AcceptRole);
+        askButton = msgBox.addButtonFirst(tr("Ask"), QDialogButtonBox::AcceptRole);
+        skipDirButton = msgBox.addButtonSecond(tr("Skip"), QDialogButtonBox::NoRole);
     } else {
         msgBox.setText(tr("File %1 already exists. Overwrite?").arg(FileOperator::shortenPath(fileName)));
     }
 
     msgBox.exec();
 
-    if (msgBox.clickedButton() == abortButton) {
+    if (msgBox.clickedButton == abortButton) {
         manipulator->setResponse(ABORT);
-    } else if (msgBox.clickedButton() == yesButton) {
+    } else if (msgBox.clickedButton == yesButton) {
         manipulator->setResponse(OVERWRITE);
-    } else if (msgBox.clickedButton() == yesToAllButton) {
+    } else if (msgBox.clickedButton == yesToAllButton) {
         manipulator->setResponse(OVERWRITE, true);
-    } else if (msgBox.clickedButton() == noButton) {
+    } else if (msgBox.clickedButton == noButton) {
         manipulator->setResponse(KEEP);
-    } else if (msgBox.clickedButton() == noToAllButton) {
+    } else if (msgBox.clickedButton == noToAllButton) {
         manipulator->setResponse(KEEP, true);
-    } else if (msgBox.clickedButton() == askButton) {
-        manipulator->setResponse(NONE, true);
+    } else if (msgBox.clickedButton == askButton) {
+        manipulator->setResponse(ASK);
+    } else if (msgBox.clickedButton == newNameButton) {
+        manipulator->setResponse(NONE);
+    } else if (msgBox.clickedButton == skipDirButton) {
+        manipulator->setResponse(SKIP_DIR);
     }
 }
 
 
+void FileOperator::showInputFilenamePrompt(FileManipulatorThread* manipulator,
+    const QFileInfo &file,
+    const bool dir)
+{
+    bool ok;
+    QString prompt, error;
+
+    if (dir) {
+        prompt = tr("Enter the new directory name.");
+    } else {
+        prompt = tr("Enter the new file name.");
+    }
+
+    manipulator->mutex.lock();
+
+    manipulator->newNameFromDialog = "";
+    QString text = file.fileName();
+
+    
+    while (true) {
+        text = QInputDialog::getText(this, QString(), prompt + error, QLineEdit::Normal, text, &ok);
+
+        if (!ok) break;
+
+        error = "";
+        if (text.contains(QRegExp("[\"*/:<>?\\\\|]"))) {
+            error = "<small><br/><font color = 'red'>" + tr("The name cannot contain any of the following characters: ") +
+                "\"*/:&lt;&gt;?\\|</font></small>";
+        } else if (ok && !text.isEmpty()) {
+            QFileInfo info(file.path() + "/" + text);
+            manipulator->newNameFromDialog = info.absoluteFilePath();
+            break;
+        }
+    }
+
+    manipulator->mutex.unlock();
+    manipulator->waitCond.wakeAll();
+}
+
+
 void FileOperator::remove(FileManipulatorThread* manipulator) {
     manipulator->wait();
     layout()->removeWidget(manipulator->progressBar);
@@ -279,6 +340,8 @@ void FileOperator::caterNewThread(FileManipulatorThread *thread) {
         this, SLOT(showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int)));
     connect(thread, SIGNAL(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)),
         this, SLOT(showOverwritePrompt(FileManipulatorThread*, const QString&, bool)));
+    connect(thread, SIGNAL(showInputFilenamePrompt(FileManipulatorThread*, const QFileInfo&, bool)),
+        this, SLOT(showInputFilenamePrompt(FileManipulatorThread*, const QFileInfo&, bool)));
     connect(thread, SIGNAL(finished(FileManipulatorThread*)),
         this, SLOT(remove(FileManipulatorThread*)));
     connect(thread, SIGNAL(setBarSize(FileManipulatorThread*, unsigned int)),
@@ -286,8 +349,6 @@ void FileOperator::caterNewThread(FileManipulatorThread *thread) {
     connect(thread, SIGNAL(updateProgress(FileManipulatorThread*, int)),
         this, SLOT(updateProgress(FileManipulatorThread*, int)));
 
-    thread->progressBar->setValue(0);
-
     layout()->addWidget(thread->progressBar);
     thread->start(QThread::LowestPriority);
 }
@@ -308,7 +369,9 @@ FileManipulatorThread::FileManipulatorThread(const QFileInfoList files, QDir des
     fileValue(0)
 {
     memset(ignoreAll, false, sizeof(ignoreAll));
-    progressBar->setMaximum(0);
+    progressBar->setMaximum(1);
+    progressBar->setValue(0);
+    progressBar->setMinimum(0);
     QFont barFont = progressBar->font();
     barFont.setPointSize(12);
     progressBar->setFont(barFont);
@@ -320,7 +383,7 @@ FileManipulatorThread::FileManipulatorThread(const QFileInfoList files, QDir des
 
 
 FileManipulatorThread::~FileManipulatorThread() {
-    if (progressBar->value() < progressBar->maximum()) {
+    if (!abort && progressBar->value() < progressBar->maximum()) {
         std::cout << "WARNING: deleting a progressbar which's value " << progressBar->value() <<
             " has not reached maximum of " << progressBar->maximum() << std::endl;
     }
@@ -384,12 +447,21 @@ bool FileManipulatorThread::remove(const QFileInfo &file, const bool doUpdates)
     std::cout << "DELETING " << file.absoluteFilePath().toStdString() << std::endl;
 
     QString path = file.absoluteFilePath();
+
+    if (removeExcludeFiles.contains(path)) {
+        if (doUpdates) updateProgress(1);
+        return false;
+    }
+
     QFSFileEngine engine(path);
 
     if (doUpdates) updateFile(path);
 
     if (file.isDir()) {
-        if (!remove(listDirFiles(path), doUpdates)) return false;
+        if (!remove(listDirFiles(path), doUpdates)) {
+            if (doUpdates) updateProgress(1);
+            return false;
+        }
 
         if (!listDirFiles(path).size()) {
             ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1."), path)
@@ -410,10 +482,8 @@ void FileManipulatorThread::copy(const QFileInfo &file) {
         << " to " << dest.absolutePath().toStdString() << std::endl;
 
     QString path(file.absoluteFilePath());
-    QString newPath(dest.absolutePath() + "/" + file.fileName());
     QFSFileEngine engine(path);
-    QFSFileEngine newEngine(newPath);
-    QFileInfo newFile(newPath);
+    QFileInfo newFile(dest.absolutePath() + "/" + file.fileName());
 
     updateFile(path);
 
@@ -424,36 +494,54 @@ void FileManipulatorThread::copy(const QFileInfo &file) {
         OVERWRITE_PROMPT(file, newFile)
     }
 
-    if (abort) return;
+    QString newPath(newFile.absoluteFilePath());
+    QFSFileEngine newEngine(newPath);
 
-    if (response == FileOperator::KEEP) {
-        updateProgress(fileSizeMap[path]);
-        return;
-    }
+    if (abort) return;
 
     if (file.isDir()) {
+        // save the overwrite response, because the response variable will get ovewritten in remove(...)
         FileOperator::Response overwriteResponse = response;
 
         if (newFile.exists() && !newFile.isDir()) {
+            // overwriting a file, so check for KEEP and handle it
+            if (response == FileOperator::KEEP) {
+                updateProgress(fileSizeMap[path]);
+                removeExcludeFiles.insert(path);
+                return;
+            }
+
+            // if it should not be kept, remove it and return on failure
             if(!remove(newPath)) {
                 updateProgress(fileSizeMap[path]);
                 return;
             }
+            // create new info since we deleted the file - is it needed?
             newFile = QFileInfo(newPath);
+        } else {
+            // overwriting a directory - response KEEP means to keep the files inside,
+            // SKIP_DIR means to skip the dir completely
+            if (response == FileOperator::SKIP_DIR) {
+                updateProgress(fileSizeMap[path]);
+                removeExcludeFiles.insert(path);
+                return;
+            }
         }
 
         if (!newFile.exists()) {
-            ERROR_PROMPT_XP(!engine.mkdir(newPath, false),
-                tr("Error creating directory %1."), newPath,
-                updateProgress(fileSizeMap[path]),
-                return)
+            SPECIAL_COPY_ERROR_PROMPT(!engine.mkdir(newPath, false),
+                tr("Error creating directory %1."), newPath)
         }
 
+        // we've done the job with the dir, so update progress and recurse into the dir
         updateProgress(1);
         
+        // change the dest for the recursion
         QDir destBackup = dest;
         dest = newPath;
 
+        // and set overwriteAll to the response we got a while ago
+        // because it applies to the files inside the dir
         FileOperator::Response tmpResp = overwriteAll;
         overwriteAll = overwriteResponse;
 
@@ -468,38 +556,40 @@ void FileManipulatorThread::copy(const QFileInfo &file) {
 
         dest = destBackup;
     } else {
-        ERROR_PROMPT_XP(engine.isSequential(),
-            tr("Cannot copy sequential file %1."), path,
-            updateProgress(fileSizeMap[path]),
-            return)
+        if (response == FileOperator::KEEP) {
+            updateProgress(fileSizeMap[path]);
+            removeExcludeFiles.insert(path);
+            return;
+        }
+
+        SPECIAL_COPY_ERROR_PROMPT(engine.isSequential(), tr("Cannot copy sequential file %1."), path)
 
         if (newFile.exists() && newFile.isDir()) {
-            ERROR_PROMPT_XP(!remove(newPath),
-                tr("Cannot replace directory %1 due to previous errors."), newPath,
-                updateProgress(fileSizeMap[path]),
-                return)
+            SPECIAL_COPY_ERROR_PROMPT(!remove(newPath),
+                tr("Cannot replace directory %1 due to previous errors."), newPath)
         }
 
-        ERROR_PROMPT_XP(!engine.open(QIODevice::ReadOnly),
-            tr("Error reading file %1."), path,
-            updateProgress(fileSizeMap[path]),
-            return)
+        SPECIAL_COPY_ERROR_PROMPT(!engine.open(QIODevice::ReadOnly), tr("Error reading file %1."), path)
 
-        bool ignore = false;
+        bool ignore = false, newFileWritten = false;
         while (!abort && !ignore) {
             engine.seek(0);
+            fileValue = 0;
 
             ERROR_PROMPT(!newEngine.open(QIODevice::WriteOnly | QIODevice::Truncate),
                 tr("Error writing file %1."), newPath)
 
             if (abort || response == FileOperator::IGNORE) {
                 if (response == FileOperator::IGNORE) {
-                    updateProgress(fileSizeMap[path] - fileValue);
+                    updateProgress(fileSizeMap[path]);
+                    removeExcludeFiles.insert(path);
                     ignore = true;
                 }
                 break;
             }
 
+            newFileWritten = true;
+
             bool error = false;
             char block[BLOCK_SIZE];
             qint64 bytes;
@@ -514,6 +604,7 @@ void FileManipulatorThread::copy(const QFileInfo &file) {
                     if (!abort) {
                         if (response == FileOperator::IGNORE) {
                             updateProgress(fileSizeMap[path] - fileValue);
+                            removeExcludeFiles.insert(path);
                             ignore = true;
                         } else {
                             updateProgress(-fileValue);
@@ -533,7 +624,9 @@ void FileManipulatorThread::copy(const QFileInfo &file) {
         newEngine.close();
 
         if (abort || ignore) {
-            newEngine.remove();
+            if (newFileWritten) {
+                newEngine.remove();
+            }
         } else {
             ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
                 tr("Error setting permissions for file %1."), newPath)
@@ -694,20 +787,34 @@ void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
     for (int i = 0; i < files.size(); ++i) {
         QString path = files[i].absoluteFilePath();
         QFSFileEngine engine(path);
-        QString newPath = dest.absolutePath() + "/" + files[i].fileName();
+        QFileInfo newFile(dest.absolutePath() + "/" + files[i].fileName());
 
         updateFile(path);
 
-        OVERWRITE_PROMPT(files[i], QFileInfo(newPath))
+        OVERWRITE_PROMPT(files[i], newFile)
 
-        if (response == FileOperator::KEEP) {
-            // TODO lets not remove the source for now, I'm not sure what is correct behavior
-            // remove(path);
-            if (abort) break;
-            updateProgress(1);
-            continue;
+        // if we are owerwriting dir over a dir, we will get SKIP_DIR
+        // as a response from OVERWRITE_PROMT meaning we should skip it
+        // (KEEP would mean to keep the files inside)
+        if (files[i].isDir() && newFile.exists() && newFile.isDir()) {
+            if (response == FileOperator::SKIP_DIR) {
+                if (abort) break;
+                updateProgress(1);
+                removeExcludeFiles.insert(path);
+                continue;
+            }
+        } else {
+            if (response == FileOperator::KEEP) {
+                if (abort) break;
+                updateProgress(1);
+                removeExcludeFiles.insert(path);
+                continue;
+            }
         }
 
+        QString newPath(newFile.absoluteFilePath());
+        QFSFileEngine newEngine(newPath);
+
         while (!abort && !engine.rename(newPath)) {
             // source and target are on different partitions
             // this should happen on the first file, unless some are skipped by overwrite prompt
@@ -740,7 +847,7 @@ void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
 
                 overwriteAll = tmpResp;
 
-                ERROR_PROMPT(!engine.rmdir(path, false), tr("Error deleting directory %1."), path)
+                remove(files[i]);
 
                 break;
             // source and target are nonmatching types(file and dir)