option to enter new name in overwrite dialog
[case] / src / fileoperator.cpp
index 2aa8265..a779a87 100644 (file)
@@ -1,5 +1,5 @@
 // case - file manager for N900
-// Copyright (C) 2010 Lukas Hrazky
+// Copyright (C) 2010 Lukas Hrazky <lukkash@email.cz>
 // 
 // This program is free software: you can redistribute it and/or modify
 // it under the terms of the GNU General Public License as published by
 #include <QHBoxLayout>
 #include <QChar>
 
+#include "dialog.h"
+
 #include <math.h>
 #include <errno.h>
 #include <iostream>
 
 
-#define SHOW_ERROR_PROMPT(promptString)                                                 \
-    response = FileOperator::NONE;                                                      \
-    if (ignoreAll[errno]) {                                                             \
-        response = FileOperator::IGNORE;                                                \
-    } else {                                                                            \
-        char buf[255];                                                                  \
-        char *realBuf = strerror_r(errno, buf, 255);                                    \
-        emit showErrorPrompt(this, promptString + ". " + realBuf + ".", errno);         \
-        waitCond.wait(&mutex);                                                          \
-    }
-
-
-#define ERROR_PROMPT(operation, promptString)                                           \
-{                                                                                       \
-    response = FileOperator::NONE;                                                      \
-    while (!abort && operation) {                                                       \
-        SHOW_ERROR_PROMPT(promptString)                                                 \
-        if (response == FileOperator::IGNORE) {                                         \
-            break;                                                                      \
-        }                                                                               \
-    }                                                                                   \
-}
+#define BLOCK_SIZE 524288
 
 
-#define ERROR_PROMPT_XP(operation, promptString, onIgnore, quitCmd)                     \
-{                                                                                       \
-    ERROR_PROMPT(operation, promptString)                                               \
-    if (abort || response == FileOperator::IGNORE) {                                    \
-        if (!abort) onIgnore;                                                           \
-        quitCmd;                                                                        \
-    }                                                                                   \
-}
+#define SHOW_ERROR_PROMPT(promptString, fileName)                                           \
+    response = FileOperator::NONE;                                                          \
+    if (ignoreAll[errno]) {                                                                 \
+        response = FileOperator::IGNORE;                                                    \
+    } else {                                                                                \
+        char buf[255];                                                                      \
+        char *realBuf = strerror_r(errno, buf, 255);                                        \
+        emit showErrorPrompt(this, promptString + " " + realBuf + ".", fileName, errno);    \
+        waitCond.wait(&mutex);                                                              \
+    }
 
 
-#define OVERWRITE_PROMPT(file, newFile)                                                 \
-{                                                                                       \
-    response = FileOperator::NONE;                                                      \
-                                                                                        \
-    if (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);     \
-            waitCond.wait(&mutex);                                                      \
-        }                                                                               \
-    }                                                                                   \
+#define ERROR_PROMPT(operation, promptString, fileName)                                     \
+{                                                                                           \
+    response = FileOperator::NONE;                                                          \
+    while (!abort && operation) {                                                           \
+        SHOW_ERROR_PROMPT(promptString, fileName)                                           \
+        if (response == FileOperator::IGNORE) {                                             \
+            break;                                                                          \
+        }                                                                                   \
+    }                                                                                       \
+}
+
+
+#define SPECIAL_COPY_ERROR_PROMPT(operation, promptString, fileName)                        \
+{                                                                                           \
+    ERROR_PROMPT(operation, promptString, fileName)                                         \
+    if (abort || response == FileOperator::IGNORE) {                                        \
+        if (!abort) {                                                                       \
+            updateProgress(fileSizeMap[path]);                                              \
+            removeExcludeFiles.insert(path);                                                \
+        }                                                                                   \
+        return;                                                                             \
+    }                                                                                       \
+}
+
+
+#define OVERWRITE_PROMPT(file, newFile)                                                     \
+{                                                                                           \
+    response = FileOperator::NONE;                                                          \
+                                                                                            \
+    while (response == FileOperator::NONE && newFile.exists()) {                            \
+        if (overwriteAll != FileOperator::NONE) {                                           \
+            response = overwriteAll;                                                        \
+        } else {                                                                            \
+            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;                       \
 }
 
 
@@ -84,6 +100,18 @@ FileOperator::FileOperator(QWidget *parent) : QWidget(parent) {
     layout->setContentsMargins(0, 0, 0, 0);
     layout->setSpacing(0);
     setLayout(layout);
+    qRegisterMetaType<QFileInfo>("QFileInfo");
+}
+
+
+QString FileOperator::shortenPath(const QString &path) {
+    QString homePath = QFSFileEngine::homePath();
+    QString result = path;
+    if (path.indexOf(homePath, 0) == 0) {
+        result.replace(0, homePath.size(), "~");
+    }
+
+    return result;
 }
 
 
@@ -91,7 +119,8 @@ void FileOperator::deleteFiles(const QFileInfoList &files) {
     QString title, desc;
     if (files.size() == 1) {
         title = tr("Delete file");
-        desc = tr("Are you sure you want to delete %1?").arg(files[0].absoluteFilePath());
+        desc = tr("Are you sure you want to delete %1?")
+            .arg(FileOperator::shortenPath(files[0].absoluteFilePath()));
     } else {
         title = tr("Delete files");
         desc = tr("You are about to delete %1 files. Are you sure you want to continue?").arg(files.size());
@@ -115,12 +144,13 @@ void FileOperator::copyFiles(const QFileInfoList &files, QDir &destination) {
     QString title, desc;
     if (files.size() == 1) {
         title = tr("Copy file");
-        desc = tr("Are you sure you want to copy %1 to %2?").arg(files[0].absoluteFilePath())
-            .arg(destination.absolutePath());
+        desc = tr("Are you sure you want to copy %1 to %2?")
+            .arg(FileOperator::shortenPath(files[0].absoluteFilePath()))
+            .arg(FileOperator::shortenPath(destination.absolutePath()));
     } else {
         title = tr("Copy files");
         desc = tr("You are about to copy %1 files to %2. Are you sure you want to continue?")
-            .arg(files.size()).arg(destination.absolutePath());
+            .arg(files.size()).arg(FileOperator::shortenPath(destination.absolutePath()));
     }
 
     int confirm = QMessageBox::warning(
@@ -144,12 +174,13 @@ void FileOperator::moveFiles(const QFileInfoList &files, QDir &destination) {
     QString title, desc;
     if (files.size() == 1) {
         title = tr("Move file");
-        desc = tr("Are you sure you want to move %1 to %2?").arg(files[0].absoluteFilePath())
-            .arg(destination.absolutePath());
+        desc = tr("Are you sure you want to move %1 to %2?")
+            .arg(FileOperator::shortenPath(files[0].absoluteFilePath()))
+            .arg(FileOperator::shortenPath(destination.absolutePath()));
     } else {
         title = tr("Move files");
         desc = tr("You are about to move %1 files to %2. Are you sure you want to continue?")
-            .arg(files.size()).arg(destination.absolutePath());
+            .arg(files.size()).arg(FileOperator::shortenPath(destination.absolutePath()));
     }
 
     int confirm = QMessageBox::warning(
@@ -166,14 +197,18 @@ void FileOperator::moveFiles(const QFileInfoList &files, QDir &destination) {
 }
 
 
-void FileOperator::showErrorPrompt(FileManipulatorThread* manipulator, const QString &message, const int err) {
+void FileOperator::showErrorPrompt(FileManipulatorThread* manipulator,
+    const QString &message,
+    const QString &fileName,
+    const int err)
+{
     QMessageBox msgBox;
     msgBox.addButton(QMessageBox::Cancel);
     QAbstractButton *abortButton = msgBox.addButton(tr("Abort"), QMessageBox::DestructiveRole);
     QAbstractButton *retryButton = msgBox.addButton(QMessageBox::Retry);
     QAbstractButton *ignoreButton = msgBox.addButton(QMessageBox::Ignore);
     QAbstractButton *ignoreAllButton = msgBox.addButton(tr("Ignore All"), QMessageBox::AcceptRole);
-    msgBox.setText(message);
+    msgBox.setText(message.arg(FileOperator::shortenPath(fileName)));
 
     msgBox.exec();
 
@@ -194,68 +229,119 @@ 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(fileName));
-        askButton = msgBox.addButton(tr("Ask"), QMessageBox::AcceptRole);
+        msgBox.setText(tr("Directory %1 already exists. Overwrite the files inside?")
+            .arg(FileOperator::shortenPath(fileName)));
+        askButton = msgBox.addButtonFirst(tr("Ask"), QDialogButtonBox::AcceptRole);
+        skipDirButton = msgBox.addButtonSecond(tr("Skip"), QDialogButtonBox::NoRole);
     } else {
-        msgBox.setText(tr("File %1 already exists. Overwrite?").arg(fileName));
+        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) {
-    layout()->removeWidget(manipulator->widget);
+    manipulator->wait();
+    layout()->removeWidget(manipulator->progressBar);
     manipulatorList.removeAll(manipulator);
     delete manipulator;
 }
 
 
 void FileOperator::setBarSize(FileManipulatorThread* manipulator, unsigned int size) {
-    manipulator->widget->setMinimum(0);
-    manipulator->widget->setMaximum(size);
+    if (!manipulator->progressBar->maximum()) {
+        manipulator->startTime = time(0);
+    }
+    manipulator->progressBar->setMinimum(0);
+    manipulator->progressBar->setMaximum(size);
 }
 
 
 void FileOperator::updateProgress(FileManipulatorThread* manipulator, int value) {
-    if (manipulator->widget->value() + value > manipulator->widget->maximum()) {
-        std::cout << "WARNING, EXCEEDING MAXIMUM BY " << value << std::endl;
-    }
-    manipulator->widget->setValue(manipulator->widget->value() + value);
+    manipulator->setText(value);
 }
 
 
 void FileOperator::caterNewThread(FileManipulatorThread *thread) {
     manipulatorList.append(thread);
 
-    connect(thread, SIGNAL(showErrorPrompt(FileManipulatorThread*, const QString&, const int)),
-        this, SLOT(showErrorPrompt(FileManipulatorThread*, const QString&, const int)));
+    connect(thread, SIGNAL(showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int)),
+        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)),
@@ -263,27 +349,45 @@ void FileOperator::caterNewThread(FileManipulatorThread *thread) {
     connect(thread, SIGNAL(updateProgress(FileManipulatorThread*, int)),
         this, SLOT(updateProgress(FileManipulatorThread*, int)));
 
-    thread->widget->setValue(0);
-
-    layout()->addWidget(thread->widget);
-    thread->start();
+    layout()->addWidget(thread->progressBar);
+    thread->start(QThread::LowestPriority);
 }
 
 
 FileManipulatorThread::FileManipulatorThread(const QFileInfoList files, QDir dest) :
-    widget(new QProgressBar()),
+    progressBar(new QProgressBar()),
+    startTime(0),
     files(files),
     dest(dest),
     response(FileOperator::NONE),
     overwriteAll(FileOperator::NONE),
     abort(false),
+    lastTimeUpdate(0),
     barSize(0),
     barValue(0),
     fileSize(0),
     fileValue(0)
 {
     memset(ignoreAll, false, sizeof(ignoreAll));
-    //widget->setStyle(new QPlastiqueStyle);
+    progressBar->setMaximum(1);
+    progressBar->setValue(0);
+    progressBar->setMinimum(0);
+    QFont barFont = progressBar->font();
+    barFont.setPointSize(12);
+    progressBar->setFont(barFont);
+    progressBar->setFormat(tr("Gathering information..."));
+    progressBar->setMinimumHeight(44);
+    progressBar->setStyle(new QPlastiqueStyle);
+    //progressBar->setStyle(new QMotifStyle);
+}
+
+
+FileManipulatorThread::~FileManipulatorThread() {
+    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;
+    }
+    delete progressBar;
 }
 
 
@@ -324,53 +428,62 @@ void FileManipulatorThread::processFiles(const QFileInfoList &files) {
 }
 
 
-bool FileManipulatorThread::remove(QString &fileName, const bool ignoreDirNotEmpty) {
-    return remove(QFileInfo(fileName), ignoreDirNotEmpty);
+bool FileManipulatorThread::remove(QString &fileName, const bool doUpdates) {
+    return remove(QFileInfo(fileName), doUpdates);
 }
 
 
-bool FileManipulatorThread::remove(const QFileInfoList &files, const bool ignoreDirNotEmpty) {
+bool FileManipulatorThread::remove(const QFileInfoList &files, const bool doUpdates) {
     bool res = true;
     for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
-        if (!remove(*it, ignoreDirNotEmpty)) res = false;
+        if (!remove(*it, doUpdates)) res = false;
         if (abort) break;
     }
     return res;
 }
 
 
-bool FileManipulatorThread::remove(const QFileInfo &file, const bool ignoreDirNotEmpty) {
+bool FileManipulatorThread::remove(const QFileInfo &file, const bool doUpdates) {
+    std::cout << "DELETING " << file.absoluteFilePath().toStdString() << std::endl;
+
     QString path = file.absoluteFilePath();
-    QFSFileEngine engine(path);
 
-    if (file.isDir()) {
-        QFileInfoList list = listDirFiles(path);
+    if (removeExcludeFiles.contains(path)) {
+        if (doUpdates) updateProgress(1);
+        return false;
+    }
 
-        if (ignoreDirNotEmpty && list.size()) return true;
+    QFSFileEngine engine(path);
 
-        if (!remove(list, ignoreDirNotEmpty)) return false;
+    if (doUpdates) updateFile(path);
 
-        ERROR_PROMPT(!engine.rmdir(path, false),
-            tr("Error deleting directory %1.").arg(path))
+    if (file.isDir()) {
+        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)
+        }
     } else {
-        ERROR_PROMPT(!engine.remove(),
-            tr("Error deleting file %1.").arg(path))
+        ERROR_PROMPT(!engine.remove(), tr("Error deleting file %1."), path)
     }
 
+    if (!abort && doUpdates) updateProgress(1);
+
     if (abort || response == FileOperator::IGNORE) return false;
     return true;
 }
 
 
-void FileManipulatorThread::copy(const QFileInfo &file, const bool removeAfterCopy) {
-    std::cout << (removeAfterCopy ? "MOVING " : "COPYING ") << file.absoluteFilePath().toStdString()
+void FileManipulatorThread::copy(const QFileInfo &file) {
+    std::cout << "COPYING " << file.absoluteFilePath().toStdString()
         << " 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);
 
@@ -381,163 +494,174 @@ void FileManipulatorThread::copy(const QFileInfo &file, const bool removeAfterCo
         OVERWRITE_PROMPT(file, newFile)
     }
 
-    if (abort) return;
+    QString newPath(newFile.absoluteFilePath());
+    QFSFileEngine newEngine(newPath);
 
-    // this loop is here only to allow easily breaking out to the end (and remove the source file/dir)
-    while (1) {
-        if (response == FileOperator::KEEP) {
-            updateProgress(fileSizeMap[path]);
-            break;
-        }
+    if (abort) return;
 
+    if (file.isDir()) {
+        // save the overwrite response, because the response variable will get ovewritten in remove(...)
         FileOperator::Response overwriteResponse = response;
 
-        if (file.isDir()) {
-            if (newFile.exists() && !newFile.isDir()) {
-                if(!remove(newPath)) {
-                    updateProgress(fileSizeMap[path]);
-                    break;
-                }
-                newFile = QFileInfo(newPath);
+        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 (!newFile.exists()) {
-                ERROR_PROMPT_XP(!engine.mkdir(newPath, false),
-                    tr("Error creating directory %1.").arg(newPath),
-                    updateProgress(fileSizeMap[path]),
-                    break)
+            // 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;
             }
+        }
 
-            updateProgress(1);
-            
-            QDir destBackup = dest;
-            dest = newPath;
+        if (!newFile.exists()) {
+            SPECIAL_COPY_ERROR_PROMPT(!engine.mkdir(newPath, false),
+                tr("Error creating directory %1."), newPath)
+        }
 
-            FileOperator::Response tmpResp = overwriteAll;
-            overwriteAll = overwriteResponse;
+        // 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;
 
-            processFiles(listDirFiles(path));
+        // 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;
 
-            overwriteAll = tmpResp;
+        processFiles(listDirFiles(path));
 
-            ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
-                tr("Error setting permissions for directory %1.").arg(newPath))
+        overwriteAll = tmpResp;
 
-            if (abort) return;
+        ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
+            tr("Error setting permissions for directory %1."), newPath)
 
-            dest = destBackup;
-        } else {
-            ERROR_PROMPT_XP(engine.isSequential(),
-                tr("Cannot copy sequential file %1.").arg(path),
-                updateProgress(fileSizeMap[path]),
-                break)
-
-            if (newFile.exists() && newFile.isDir()) {
-                ERROR_PROMPT_XP(!remove(newPath),
-                    tr("Cannot replace directory %1 due to previous errors.").arg(newPath),
-                    updateProgress(fileSizeMap[path]),
-                    break)
-            }
+        if (abort) return;
 
-            ERROR_PROMPT_XP(!engine.open(QIODevice::ReadOnly),
-                tr("Error reading file %1.").arg(path),
-                updateProgress(fileSizeMap[path]),
-                break)
+        dest = destBackup;
+    } else {
+        if (response == FileOperator::KEEP) {
+            updateProgress(fileSizeMap[path]);
+            removeExcludeFiles.insert(path);
+            return;
+        }
 
-            bool ignore = false;
-            while (!abort && !ignore) {
-                engine.seek(0);
+        SPECIAL_COPY_ERROR_PROMPT(engine.isSequential(), tr("Cannot copy sequential file %1."), path)
 
-                ERROR_PROMPT(!newEngine.open(QIODevice::WriteOnly | QIODevice::Truncate),
-                    tr("Error writing file %1.").arg(newPath))
+        if (newFile.exists() && newFile.isDir()) {
+            SPECIAL_COPY_ERROR_PROMPT(!remove(newPath),
+                tr("Cannot replace directory %1 due to previous errors."), newPath)
+        }
 
-                if (abort || response == FileOperator::IGNORE) {
-                    if (response == FileOperator::IGNORE) {
-                        updateProgress(fileSizeMap[path] - fileValue);
-                        ignore = true;
-                    }
-                    break;
+        SPECIAL_COPY_ERROR_PROMPT(!engine.open(QIODevice::ReadOnly), tr("Error reading file %1."), path)
+
+        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]);
+                    removeExcludeFiles.insert(path);
+                    ignore = true;
                 }
+                break;
+            }
 
-                bool error = false;
-                char block[4096];
-                qint64 bytes;
-                while ((bytes = engine.read(block, sizeof(block))) > 0) {
-                    if (bytes == -1 || bytes != newEngine.write(block, bytes)) {
-                        if (bytes == -1) {
-                            SHOW_ERROR_PROMPT(tr("Error while reading from file %1.").arg(path));
-                        } else {
-                            SHOW_ERROR_PROMPT(tr("Error while writing to file %1.").arg(newPath));
-                        }
+            newFileWritten = true;
+
+            bool error = false;
+            char block[BLOCK_SIZE];
+            qint64 bytes;
+            while ((bytes = engine.read(block, sizeof(block))) > 0) {
+                if (bytes == -1 || bytes != newEngine.write(block, bytes)) {
+                    if (bytes == -1) {
+                        SHOW_ERROR_PROMPT(tr("Error while reading from file %1."), path);
+                    } else {
+                        SHOW_ERROR_PROMPT(tr("Error while writing to file %1."), newPath);
+                    }
 
-                        if (!abort) {
-                            if (response == FileOperator::IGNORE) {
-                                updateProgress(fileSizeMap[path] - fileValue);
-                                ignore = true;
-                            } else {
-                                updateProgress(-fileValue);
-                            }
+                    if (!abort) {
+                        if (response == FileOperator::IGNORE) {
+                            updateProgress(fileSizeMap[path] - fileValue);
+                            removeExcludeFiles.insert(path);
+                            ignore = true;
+                        } else {
+                            updateProgress(-fileValue);
                         }
-                        error = true;
-                        break;
                     }
-
-                    updateProgress(1);
+                    error = true;
+                    break;
                 }
 
-                if (!error) break;
+                updateProgress(1);
             }
 
-            engine.close();
-            newEngine.close();
+            if (!error) break;
+        }
+
+        engine.close();
+        newEngine.close();
 
-            if (abort || ignore) {
+        if (abort || ignore) {
+            if (newFileWritten) {
                 newEngine.remove();
-            } else {
-                ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
-                    tr("Error setting permissions for file %1.").arg(newPath))
             }
+        } else {
+            ERROR_PROMPT(!newEngine.setPermissions(file.permissions()),
+                tr("Error setting permissions for file %1."), newPath)
         }
-
-        break;
     }
-
-    if (removeAfterCopy && !abort) remove(path, true);
 }
 
 
-unsigned int FileManipulatorThread::countFiles(const QFileInfoList &files) {
+unsigned int FileManipulatorThread::calculateFileSize(const QFileInfoList &files,
+    const bool count,
+    const bool addSize)
+{
     unsigned int res = 0;
 
     for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
-        unsigned int size = 1;
+        unsigned int size = 0;
 
         if (it->isDir()) {
-            size += countFiles(listDirFiles(it->absoluteFilePath()));
+            size += calculateFileSize(listDirFiles(it->absoluteFilePath()), count, addSize);
         }
 
-        res += size;
-        fileSizeMap[it->absoluteFilePath()] = size;
-    }
-
-    return res;
-}
-
-
-unsigned int FileManipulatorThread::calculateFileSize(const QFileInfoList &files) {
-    unsigned int res = 0;
-
-    for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) {
-        unsigned int size = 1;
+        if (addSize) {
+            if (it->isDir()) {
+                ++size;
+            } else {
+                size += ceil(static_cast<float>(it->size()) / BLOCK_SIZE);
+            }
+            fileSizeMap[it->absoluteFilePath()] = size;
+        }
 
-        if (it->isDir()) {
-            size += calculateFileSize(listDirFiles(it->absoluteFilePath()));
-        } else {
-            size = ceil(static_cast<float>(it->size()) / 4096);
+        if (count) {
+            ++size;
         }
 
         res += size;
-        fileSizeMap[it->absoluteFilePath()] = size;
     }
 
     return res;
@@ -563,16 +687,50 @@ void FileManipulatorThread::updateProgress(int value) {
 }
 
 
-void FileManipulatorThread::updateFile(const QString &fileName) {
+void FileManipulatorThread::updateFile(const QString &name) {
     fileValue = 0;
-    emit updateFile(this, fileName);
+    fileName = FileOperator::shortenPath(name);
+    emit updateProgress(this, 0);
+}
+
+
+void FileManipulatorThread::setText(int value) {
+    if (progressBar->value() + value > progressBar->maximum()) {
+        std::cout << "WARNING: exceeding progressbar maximum (" << progressBar->maximum()
+            << ") by " << value << std::endl;
+    }
+    time_t now = time(0);
+    if (lastTimeUpdate < now) {
+        lastTimeUpdate = now;
+
+        time_t elapsed = now - startTime;
+        time_t remaining = (time_t) ((float) elapsed / barValue * (barSize - barValue));
+        struct tm *ts = gmtime(&remaining);
+        
+        if (remaining < 60) {
+            strftime(timeBuf, sizeof(timeBuf), "%Ss", ts);
+        } else if (remaining < 3600) {
+            strftime(timeBuf, sizeof(timeBuf), "%M:%S", ts);
+        } else {
+            strftime(timeBuf, sizeof(timeBuf), "%H:%M:%S", ts);
+        }
+    }
+
+    progressBar->setFormat(barText.arg(fileName) + "\n%p%   ETA " + timeBuf);
+    progressBar->setValue(progressBar->value() + value);
+}
+
+
+DeleteThread::DeleteThread(const QFileInfoList &files) : FileManipulatorThread(files) {
+    barText = tr("deleting %1");
 }
 
 
 void DeleteThread::run() {
     mutex.lock();
 
-    setBarSize(countFiles(files));
+    setBarSize(calculateFileSize(files, true));
 
     processFiles(files);
 
@@ -582,31 +740,19 @@ void DeleteThread::run() {
 
 
 void DeleteThread::perform(const QFileInfo &file) {
-    std::cout << "DELETING " << file.absoluteFilePath().toStdString() << std::endl;
-
-    QString path = file.absoluteFilePath();
-    QFSFileEngine engine(path);
-
-    if (file.isDir()) {
-        processFiles(listDirFiles(path));
+    remove(file, true);
+}
 
-        if (!listDirFiles(path).size()) {
-            ERROR_PROMPT(!engine.rmdir(path, false),
-                tr("Error deleting directory %1.").arg(path))
-        }
-    } else {
-        ERROR_PROMPT(!engine.remove(),
-            tr("Error deleting file %1.").arg(path))
-    }
 
-    if (!abort) updateProgress(1);
+CopyThread::CopyThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
+    barText = tr("copying %1");
 }
 
 
 void CopyThread::run() {
     mutex.lock();
 
-    setBarSize(calculateFileSize(files));
+    setBarSize(calculateFileSize(files, false, true));
 
     processFiles(files);
 
@@ -616,7 +762,12 @@ void CopyThread::run() {
 
 
 void CopyThread::perform(const QFileInfo &file) {
-    copy(file, false);
+    copy(file);
+}
+
+
+MoveThread::MoveThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) {
+    barText = tr("moving %1");
 }
 
 
@@ -636,35 +787,53 @@ 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) {
-            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
             // we calculate the actual file sizes, because from now on copy & remove takes over
             if (errno == EXDEV) {
-                setBarSize(barValue + calculateFileSize(files));
-
-                FileOperator::Response tmpResp = overwriteAll;
                 overwriteAll = response;
                 // hack: we already checked the first file we are sending to processFiles(...)
                 // so we don't want to ask about this one again
                 if (overwriteAll == FileOperator::NONE) overwriteAll = FileOperator::DONT_ASK_ONCE;
 
-                processFiles(files.mid(i));
+                QFileInfoList remainingFiles = files.mid(i);
 
-                overwriteAll = tmpResp;
+                setBarSize(barValue + calculateFileSize(remainingFiles, true, true));
+
+                processFiles(remainingFiles);
+
+                barText = tr("deleting %1");
+
+                remove(remainingFiles, true);
 
                 // just to quit the loops, we are done
                 abort = true;
@@ -678,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.").arg(path))
+                remove(files[i]);
 
                 break;
             // source and target are nonmatching types(file and dir)
@@ -686,7 +855,7 @@ void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
             } else if (errno == ENOTDIR || errno == EISDIR) {
                 if (!remove(newPath)) break;
             } else {
-                SHOW_ERROR_PROMPT(tr("Error moving %1.").arg(path))
+                SHOW_ERROR_PROMPT(tr("Error moving %1."), path)
 
                 if (response == FileOperator::IGNORE) {
                     break;
@@ -701,5 +870,5 @@ void MoveThread::rename(const QFileInfoList &files, const QDir &dest) {
 
 
 void MoveThread::perform(const QFileInfo &file) {
-    copy(file, true);
+    copy(file);
 }