From 2edb4f2f10329116492492b442c2957653381c28 Mon Sep 17 00:00:00 2001 From: Lukas Hrazky Date: Sat, 21 Aug 2010 22:54:08 +0200 Subject: [PATCH] code cleanup Signed-off-by: Lukas Hrazky --- case.pro | 2 + src/addressbar.cpp | 4 +- src/filelist.cpp | 4 +- src/fileoperator.cpp | 869 +++++------------------------------------------ src/fileoperator.h | 165 +-------- src/operationthread.cpp | 601 ++++++++++++++++++++++++++++++++ src/operationthread.h | 152 +++++++++ src/progressbar.cpp | 85 ++++- src/progressbar.h | 28 +- src/utils.cpp | 31 ++ src/utils.h | 3 + 11 files changed, 992 insertions(+), 952 deletions(-) create mode 100644 src/operationthread.cpp create mode 100644 src/operationthread.h diff --git a/case.pro b/case.pro index fe18116..5655ede 100644 --- a/case.pro +++ b/case.pro @@ -20,6 +20,7 @@ HEADERS += src/addressbar.h \ src/case.h \ src/filelist.h \ src/fileoperator.h \ + src/operationthread.h \ src/pane.h \ src/dialog.h \ src/progressbar.h \ @@ -30,6 +31,7 @@ SOURCES += src/addressbar.cpp \ src/case.cpp \ src/filelist.cpp \ src/fileoperator.cpp \ + src/operationthread.cpp \ src/main.cpp \ src/pane.cpp \ src/dialog.cpp \ diff --git a/src/addressbar.cpp b/src/addressbar.cpp index d189cf2..54db44b 100644 --- a/src/addressbar.cpp +++ b/src/addressbar.cpp @@ -17,7 +17,7 @@ #include "addressbar.h" -#include "fileoperator.h" +#include "utils.h" AddressBar::AddressBar(QWidget *parent) : QLineEdit(parent) { @@ -29,7 +29,7 @@ AddressBar::AddressBar(QWidget *parent) : QLineEdit(parent) { void AddressBar::setText(const QString &text) { - QLineEdit::setText(FileOperator::shortenPath(text)); + QLineEdit::setText(shortenPath(text)); } diff --git a/src/filelist.cpp b/src/filelist.cpp index a4927b8..fe281b8 100644 --- a/src/filelist.cpp +++ b/src/filelist.cpp @@ -24,7 +24,7 @@ #include #include -#include "fileoperator.h" +#include "utils.h" FileList::FileList(QWidget *parent) : @@ -62,7 +62,7 @@ const QString FileList::path() const { bool FileList::changePath(QString path) { - path = FileOperator::unwindPath(path); + path = unwindPath(path); QDir dir(fileSystemModel->rootPath()); if (dir.cd(path)) { setRootIndex(fileSystemModel->setRootPath(dir.absolutePath())); diff --git a/src/fileoperator.cpp b/src/fileoperator.cpp index bd4c667..b25ab3b 100644 --- a/src/fileoperator.cpp +++ b/src/fileoperator.cpp @@ -18,145 +18,34 @@ #include "fileoperator.h" #include -#include #include #include -#include #include "dialog.h" #include "utils.h" -#include -#include -#include - - -#define BLOCK_SIZE 524288 - - -#define PAUSE() \ - if (pause) { \ - emit operationPaused(this); \ - waitOnCond(); \ - } - - -#define SHOW_ERROR_PROMPT(promptString, fileName) \ - response = FileOperator::NONE; \ - if (ignoreAll[errno]) { \ - response = FileOperator::IGNORE; \ - } else { \ - char buf[255]; \ - char *realBuf = buf; \ - if (errno == 255) { \ - strcpy(buf, tr("File is sequential").toStdString().c_str()); \ - } else { \ - realBuf = strerror_r(errno, buf, 255); \ - } \ - emit showErrorPrompt(this, promptString + " " + realBuf + ".", fileName, errno); \ - waitOnCond(); \ - } - - -#define ERROR_PROMPT(operation, promptString, fileName) \ -{ \ - response = FileOperator::NONE; \ - while (!abort && operation) { \ - SHOW_ERROR_PROMPT(promptString, fileName) \ - if (response == FileOperator::IGNORE) { \ - break; \ - } \ - PAUSE() \ - } \ -} - - -#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 (!abort && response == FileOperator::NONE && newFile.exists()) { \ - if (overwriteAll != FileOperator::NONE) { \ - response = overwriteAll; \ - } else { \ - emit showOverwritePrompt(this, newFile.absoluteFilePath(), \ - newFile.isDir() && file.isDir()); \ - waitOnCond(); \ - \ - PAUSE() \ - else if (response == FileOperator::NONE) { \ - emit showInputFilenamePrompt(this, newFile, file.isDir()); \ - waitOnCond(); \ - if (newNameFromDialog.size()) { \ - newFile.setFile(newNameFromDialog); \ - } \ - } \ - } \ - } \ - if (response == FileOperator::ASK) response = FileOperator::NONE; \ -} - FileOperator::FileOperator(QWidget *parent) : QWidget(parent) { QHBoxLayout *layout = new QHBoxLayout; layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(1); setLayout(layout); + qRegisterMetaType("QFileInfo"); + qRegisterMetaType("time_t"); + loadOperationIcons(palette(), "delete_small", deleteIcon, inverseDeleteIcon); loadOperationIcons(palette(), "copy_small", copyIcon, inverseCopyIcon); loadOperationIcons(palette(), "move_small", moveIcon, inverseMoveIcon); } -QString FileOperator::shortenPath(const QString &path) { - QString homePath = QFSFileEngine::homePath(); - - if (path.indexOf(homePath, 0) == 0) { - QString result = path; - - result.replace(0, homePath.size(), "~"); - return result; - } - - return path; -} - - -QString FileOperator::unwindPath(const QString &path) { - QString result = path; - // if ~ is the first character and / or nothing follows it, replace with home dir - if (path == "~" || path.indexOf("~/", 0) == 0) { - QString homePath = QFSFileEngine::homePath(); - result.replace(0, 1, homePath); - // in case someone wants to enter a dir called ~ in the current dir, he can escape it with \~ - } else if (path == "\\~" || path.indexOf("\\~/", 0) == 0) { - result.replace(0, 2, "~"); - } - - return result; -} - - 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(FileOperator::shortenPath(files[0].absoluteFilePath())); + .arg(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()); @@ -171,9 +60,8 @@ void FileOperator::deleteFiles(const QFileInfoList &files) { ); if(confirm == QMessageBox::Yes) { - DeleteThread *t = new DeleteThread(files); - t->progressBar->setIcons(deleteIcon, inverseDeleteIcon); - caterNewThread(t); + ProgressBar *bar = new ProgressBar(deleteIcon, inverseDeleteIcon); + initOperation(new DeleteThread(files), bar); } } @@ -183,12 +71,12 @@ void FileOperator::copyFiles(const QFileInfoList &files, QDir &destination) { if (files.size() == 1) { title = tr("Copy file"); desc = tr("Are you sure you want to copy %1 to %2?") - .arg(FileOperator::shortenPath(files[0].absoluteFilePath())) - .arg(FileOperator::shortenPath(destination.absolutePath())); + .arg(shortenPath(files[0].absoluteFilePath())) + .arg(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(FileOperator::shortenPath(destination.absolutePath())); + .arg(files.size()).arg(shortenPath(destination.absolutePath())); } int confirm = QMessageBox::warning( @@ -200,11 +88,9 @@ void FileOperator::copyFiles(const QFileInfoList &files, QDir &destination) { ); if(confirm == QMessageBox::Yes) { - CopyThread *t = new CopyThread(files, destination); - t->progressBar->setIcons(copyIcon, inverseCopyIcon); - t->progressBar->fromText = shortenPath(files[0].absolutePath()); - t->progressBar->toText = FileOperator::shortenPath(destination.absolutePath()); - caterNewThread(t); + ProgressBar *bar = new ProgressBar(copyIcon, inverseCopyIcon); + bar->setBottomTexts(shortenPath(files[0].absolutePath()), shortenPath(destination.absolutePath())); + initOperation(new CopyThread(files, destination), bar); } } @@ -217,12 +103,12 @@ void FileOperator::moveFiles(const QFileInfoList &files, QDir &destination) { if (files.size() == 1) { title = tr("Move file"); desc = tr("Are you sure you want to move %1 to %2?") - .arg(FileOperator::shortenPath(files[0].absoluteFilePath())) - .arg(FileOperator::shortenPath(destination.absolutePath())); + .arg(shortenPath(files[0].absoluteFilePath())) + .arg(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(FileOperator::shortenPath(destination.absolutePath())); + .arg(files.size()).arg(shortenPath(destination.absolutePath())); } int confirm = QMessageBox::warning( @@ -234,16 +120,14 @@ void FileOperator::moveFiles(const QFileInfoList &files, QDir &destination) { ); if(confirm == QMessageBox::Yes) { - MoveThread *t = new MoveThread(files, destination); - t->progressBar->setIcons(moveIcon, inverseMoveIcon); - t->progressBar->fromText = shortenPath(files[0].absolutePath()); - t->progressBar->toText = shortenPath(destination.absolutePath()); - caterNewThread(t); + ProgressBar *bar = new ProgressBar(moveIcon, inverseMoveIcon); + bar->setBottomTexts(shortenPath(files[0].absolutePath()), shortenPath(destination.absolutePath())); + initOperation(new MoveThread(files, destination), bar); } } -void FileOperator::showErrorPrompt(FileManipulatorThread* manipulator, +void FileOperator::showErrorPrompt(OperationThread* op, const QString &message, const QString &fileName, const int err) @@ -254,27 +138,27 @@ void FileOperator::showErrorPrompt(FileManipulatorThread* manipulator, QAbstractButton *retryButton = msgBox.addButton(QMessageBox::Retry); QAbstractButton *ignoreButton = msgBox.addButton(QMessageBox::Ignore); QAbstractButton *ignoreAllButton = msgBox.addButton(tr("Ignore All"), QMessageBox::AcceptRole); - msgBox.setText(message.arg(FileOperator::shortenPath(fileName))); + msgBox.setText(message.arg(shortenPath(fileName))); msgBox.exec(); if (msgBox.clickedButton() == cancelButton) { - manipulator->pause = true; - manipulator->setResponse(RETRY); + op->pause = true; + op->setResponse(OperationThread::RETRY); } else if (msgBox.clickedButton() == abortButton) { - manipulator->setResponse(ABORT); + op->setResponse(OperationThread::ABORT); } else if (msgBox.clickedButton() == retryButton) { - manipulator->setResponse(RETRY); + op->setResponse(OperationThread::RETRY); } else if (msgBox.clickedButton() == ignoreButton) { - manipulator->setResponse(IGNORE); + op->setResponse(OperationThread::IGNORE); } else if (msgBox.clickedButton() == ignoreAllButton) { - manipulator->setResponse(IGNORE, true, err); + op->setResponse(OperationThread::IGNORE, true, err); } } void FileOperator::showOverwritePrompt( - FileManipulatorThread* manipulator, + OperationThread* op, const QString &fileName, const bool dirOverDir) { @@ -290,39 +174,39 @@ void FileOperator::showOverwritePrompt( if (dirOverDir) { msgBox.setText(tr("Directory %1 already exists. Overwrite the files inside?") - .arg(FileOperator::shortenPath(fileName))); + .arg(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(FileOperator::shortenPath(fileName))); + msgBox.setText(tr("File %1 already exists. Overwrite?").arg(shortenPath(fileName))); } msgBox.exec(); if (msgBox.clickedButton == 0) { - manipulator->pause = true; - manipulator->setResponse(NONE); + op->pause = true; + op->setResponse(OperationThread::NONE); } else if (msgBox.clickedButton == abortButton) { - manipulator->setResponse(ABORT); + op->setResponse(OperationThread::ABORT); } else if (msgBox.clickedButton == yesButton) { - manipulator->setResponse(OVERWRITE); + op->setResponse(OperationThread::OVERWRITE); } else if (msgBox.clickedButton == yesToAllButton) { - manipulator->setResponse(OVERWRITE, true); + op->setResponse(OperationThread::OVERWRITE, true); } else if (msgBox.clickedButton == noButton) { - manipulator->setResponse(KEEP); + op->setResponse(OperationThread::KEEP); } else if (msgBox.clickedButton == noToAllButton) { - manipulator->setResponse(KEEP, true); + op->setResponse(OperationThread::KEEP, true); } else if (msgBox.clickedButton == askButton) { - manipulator->setResponse(ASK); + op->setResponse(OperationThread::ASK); } else if (msgBox.clickedButton == newNameButton) { - manipulator->setResponse(NONE); + op->setResponse(OperationThread::NONE); } else if (msgBox.clickedButton == skipDirButton) { - manipulator->setResponse(SKIP_DIR); + op->setResponse(OperationThread::SKIP_DIR); } } -void FileOperator::showInputFilenamePrompt(FileManipulatorThread* manipulator, +void FileOperator::showInputFilenamePrompt(OperationThread* op, const QFileInfo &file, const bool dir) { @@ -335,9 +219,7 @@ void FileOperator::showInputFilenamePrompt(FileManipulatorThread* manipulator, prompt = tr("Enter the new file name."); } - manipulator->mutex.lock(); - - manipulator->newNameFromDialog = ""; + op->newNameFromDialog = ""; QString text = file.fileName(); while (true) { @@ -347,65 +229,44 @@ void FileOperator::showInputFilenamePrompt(FileManipulatorThread* manipulator, error = ""; if (text.contains(QRegExp("[\"*/:<>?\\\\|]"))) { - error = "
" + tr("The name cannot contain any of the following characters: ") + + error = "
" + + tr("The name cannot contain any of the following characters: ") + "\"*/:<>?\\|
"; } else if (ok && !text.isEmpty()) { QFileInfo info(file.path() + "/" + text); - manipulator->newNameFromDialog = info.absoluteFilePath(); + op->newNameFromDialog = info.absoluteFilePath(); break; } } - manipulator->mutex.unlock(); - manipulator->wake(); -} - - -void FileOperator::remove(FileManipulatorThread* manipulator) { - manipulator->wait(); - layout()->removeWidget(manipulator->progressBar); - manipulatorList.removeAll(manipulator); - delete manipulator; + op->wake(); } -void FileOperator::setBarSize(FileManipulatorThread* manipulator, unsigned int size) { - manipulator->progressBar->setMinimum(0); - manipulator->progressBar->setMaximum(size); +void FileOperator::remove(OperationThread* op) { + op->wait(); + ProgressBar *bar = get(op); + layout()->removeWidget(bar); + opList.removeAll(qMakePair(op, bar)); + delete op; + delete bar; } -void FileOperator::updateProgress(FileManipulatorThread* manipulator, int value) { - manipulator->setText(value); -} - - -void FileOperator::updateMainText(FileManipulatorThread* manipulator, const QString &text) { - manipulator->progressBar->mainText = text; - manipulator->progressBar->mainText.remove(0, manipulator->progressBar->fromText.size() + 1); - manipulator->progressBar->repaint(); -} - +void FileOperator::togglePauseOperation(ProgressBar* bar) { + OperationThread *op = get(bar); -void FileOperator::showPaused(FileManipulatorThread* manipulator) { - manipulator->progressBar->paused = true; - manipulator->progressBar->repaint(); -} - - -void FileOperator::togglePauseOperation(FileManipulatorThread* manipulator) { - if (manipulator->pause) { - manipulator->pause = false; - manipulator->progressBar->paused = false; - manipulator->progressBar->repaint(); - manipulator->wake(); + if (op->pause) { + op->wake(); } else { - manipulator->pause = true; + op->pause = true; } } -void FileOperator::abortOperation(FileManipulatorThread* manipulator) { +void FileOperator::abortOperation(ProgressBar* bar) { + OperationThread *op = get(bar); + int confirm = QMessageBox::warning( 0, tr("Abort operation"), @@ -415,598 +276,52 @@ void FileOperator::abortOperation(FileManipulatorThread* manipulator) { ); if(confirm == QMessageBox::Yes) { - manipulator->abort = true; - manipulator->pause = false; - manipulator->wake(); - } -} - - -void FileOperator::caterNewThread(FileManipulatorThread *thread) { - manipulatorList.append(thread); - - 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)), - this, SLOT(setBarSize(FileManipulatorThread*, unsigned int))); - connect(thread, SIGNAL(updateProgress(FileManipulatorThread*, int)), - this, SLOT(updateProgress(FileManipulatorThread*, int))); - connect(thread, SIGNAL(updateFileName(FileManipulatorThread*, QString)), - this, SLOT(updateMainText(FileManipulatorThread*, QString))); - connect(thread, SIGNAL(operationPaused(FileManipulatorThread*)), - this, SLOT(showPaused(FileManipulatorThread*))); - - connect(thread->progressBar, SIGNAL(togglePauseOperation(FileManipulatorThread*)), - this, SLOT(togglePauseOperation(FileManipulatorThread*))); - connect(thread->progressBar, SIGNAL(abortOperation(FileManipulatorThread*)), - this, SLOT(abortOperation(FileManipulatorThread*))); - - layout()->addWidget(thread->progressBar); - thread->start(QThread::LowestPriority); -} - - -FileManipulatorThread::FileManipulatorThread(const QFileInfoList files, QDir dest) : - progressBar(new ProgressBar(this)), - abort(false), - pause(false), - files(files), - dest(dest), - response(FileOperator::NONE), - overwriteAll(FileOperator::NONE), - lastTimeUpdate(0), - startTime(0), - waitTime(0), - barSize(0), - barValue(0), - fileSize(0), - fileValue(0) -{ - memset(ignoreAll, false, sizeof(ignoreAll)); -} - - -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; -} - - -void FileManipulatorThread::setResponse( - const FileOperator::Response response, - const bool applyToAll, - const int err) -{ - mutex.lock(); - - this->response = response; - - if (applyToAll) { - if (response == FileOperator::KEEP - || response == FileOperator::OVERWRITE - || response == FileOperator::NONE) - { - overwriteAll = response; - } - - if (response == FileOperator::IGNORE) { - ignoreAll[err] = true; - } - } - - if (response == FileOperator::ABORT) abort = true; - - mutex.unlock(); - wake(); -} - - -void FileManipulatorThread::processFiles(const QFileInfoList &files) { - for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) { - PAUSE(); - if (abort) break; - perform(*it); - } -} - - -bool FileManipulatorThread::remove(QString &fileName, const bool doUpdates) { - return remove(QFileInfo(fileName), doUpdates); -} - - -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, doUpdates)) res = false; - PAUSE(); - if (abort) break; - } - return res; -} - - -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)) { - 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."), path) - } - - if (!abort && doUpdates) updateProgress(1); - - PAUSE(); - if (abort || response == FileOperator::IGNORE) return false; - return true; -} - - -void FileManipulatorThread::copy(const QFileInfo &file) { - std::cout << "COPYING " << file.absoluteFilePath().toStdString() - << " to " << dest.absolutePath().toStdString() << std::endl; - - QString path(file.absoluteFilePath()); - QFSFileEngine engine(path); - QFileInfo newFile(dest.absolutePath() + "/" + file.fileName()); - - updateFile(path); - - // hack to prevent asking about the same file if we already asked in the rename(...) function - if (overwriteAll == FileOperator::DONT_ASK_ONCE) { - overwriteAll = FileOperator::NONE; - } else { - OVERWRITE_PROMPT(file, newFile) - } - - QString newPath(newFile.absoluteFilePath()); - QFSFileEngine newEngine(newPath); - - PAUSE(); - 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()) { - 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; - - processFiles(listDirFiles(path)); - - overwriteAll = tmpResp; - - ERROR_PROMPT(!newEngine.setPermissions(file.permissions()), - tr("Error setting permissions for directory %1."), newPath) - - PAUSE(); - if (abort) return; - - dest = destBackup; - } else { - if (response == FileOperator::KEEP) { - updateProgress(fileSizeMap[path]); - removeExcludeFiles.insert(path); - return; - } - - SPECIAL_COPY_ERROR_PROMPT(checkSequentialFile(engine), tr("Cannot copy file %1."), path) - - if (newFile.exists() && newFile.isDir()) { - SPECIAL_COPY_ERROR_PROMPT(!remove(newPath), - tr("Cannot replace directory %1 due to previous errors."), newPath) - } - - 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; - } - - 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); - removeExcludeFiles.insert(path); - ignore = true; - } else { - updateProgress(-fileValue); - } - } - error = true; - break; - } - - PAUSE(); - if (abort) break; - - updateProgress(1); - } - - if (!error) break; - PAUSE(); - } - - engine.close(); - newEngine.close(); - - PAUSE(); - if (abort || ignore) { - if (newFileWritten) { - newEngine.remove(); - } - } else { - ERROR_PROMPT(!newEngine.setPermissions(file.permissions()), - tr("Error setting permissions for file %1."), newPath) - } - } -} - - -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 = 0; - - PAUSE(); - if (abort) break; - - if (it->isDir()) { - size += calculateFileSize(listDirFiles(it->absoluteFilePath()), count, addSize); - } - - if (addSize) { - if (it->isDir()) { - ++size; - } else { - size += ceil(static_cast(it->size()) / BLOCK_SIZE); - } - fileSizeMap[it->absoluteFilePath()] = size; - } - - if (count) { - ++size; - } - - res += size; + op->abort = true; + op->pause = false; + op->wake(); } - - return res; -} - - -QFileInfoList FileManipulatorThread::listDirFiles(const QString &dirPath) { - QDir dir = dirPath; - return dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden); } -void FileManipulatorThread::setBarSize(unsigned int size) { - barSize = size; - emit setBarSize(this, size); -} +void FileOperator::initOperation(OperationThread *thread, ProgressBar *bar) { + opList.append(qMakePair(thread, bar)); + connect(thread, SIGNAL(showErrorPrompt(OperationThread*, const QString&, const QString&, const int)), + this, SLOT(showErrorPrompt(OperationThread*, const QString&, const QString&, const int))); + connect(thread, SIGNAL(showOverwritePrompt(OperationThread*, const QString&, bool)), + this, SLOT(showOverwritePrompt(OperationThread*, const QString&, bool))); + connect(thread, SIGNAL(showInputFilenamePrompt(OperationThread*, const QFileInfo&, bool)), + this, SLOT(showInputFilenamePrompt(OperationThread*, const QFileInfo&, bool))); + connect(thread, SIGNAL(finished(OperationThread*)), + this, SLOT(remove(OperationThread*))); -void FileManipulatorThread::updateProgress(int value) { - barValue += value; - fileValue += value; - emit updateProgress(this, value); -} - - -void FileManipulatorThread::updateFile(const QString &name) { - fileValue = 0; - emit updateFileName(this, FileOperator::shortenPath(name)); -} + connect(thread, SIGNAL(totalSizeChanged(int)), bar, SLOT(setMaximum(int))); + connect(thread, SIGNAL(progressUpdate(int)), bar, SLOT(updateProgress(int))); + connect(thread, SIGNAL(fileNameUpdated(QString)), bar, SLOT(updateMainText(QString))); + connect(thread, SIGNAL(operationStarted(time_t)), bar, SLOT(setStartTime(time_t))); + connect(thread, SIGNAL(operationPaused()), bar, SLOT(pause())); + connect(thread, SIGNAL(operationResumed(time_t)), bar, SLOT(resume(time_t))); + connect(thread, SIGNAL(removeAfterCopy()), bar, SLOT(showRemoveNotice())); + connect(bar, SIGNAL(togglePauseOperation(ProgressBar*)), this, SLOT(togglePauseOperation(ProgressBar*))); + connect(bar, SIGNAL(abortOperation(ProgressBar*)), this, SLOT(abortOperation(ProgressBar*))); -void FileManipulatorThread::waitOnCond() { - waitTime = time(0); - waitCond.wait(&mutex); -} - - -bool FileManipulatorThread::checkSequentialFile(const QFSFileEngine &engine) { - errno = 0; - if (engine.isSequential()) { - if (!errno) errno = 255; - return true; - } - - return false; -} - - -void FileManipulatorThread::wake() { - startTime += time(0) - waitTime; - waitCond.wakeAll(); + layout()->addWidget(bar); + thread->start(QThread::LowestPriority); } -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 *FileOperator::get(OperationThread *op) const { + for (OperationList::const_iterator it = opList.begin(); it != opList.end(); ++it) { + if (it->first == op) return it->second; } - - - progressBar->setFormat(QString("%p% ") + timeBuf); - progressBar->setValue(progressBar->value() + value); -} - - -DeleteThread::DeleteThread(const QFileInfoList &files) : FileManipulatorThread(files) { - barText = tr("deleting %1"); -} - - -void DeleteThread::run() { - mutex.lock(); - - setBarSize(calculateFileSize(files, true)); - startTime = time(0); - - processFiles(files); - - sleep(0.5); - emit finished(this); -} - - -void DeleteThread::perform(const QFileInfo &file) { - remove(file, true); -} - - -CopyThread::CopyThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) { - barText = tr("copying %1"); -} - - -void CopyThread::run() { - mutex.lock(); - - setBarSize(calculateFileSize(files, false, true)); - startTime = time(0); - - processFiles(files); - - sleep(0.5); - emit finished(this); -} - - -void CopyThread::perform(const QFileInfo &file) { - copy(file); -} - - -MoveThread::MoveThread(const QFileInfoList &files, QDir &dest) : FileManipulatorThread(files, dest) { - barText = tr("moving %1"); + return 0; } -void MoveThread::run() { - mutex.lock(); - - rename(files, dest); - - sleep(0.5); - emit finished(this); -} - - -void MoveThread::rename(const QFileInfoList &files, const QDir &dest) { - setBarSize(barSize + files.size()); - startTime = time(0); - - for (int i = 0; i < files.size(); ++i) { - QString path = files[i].absoluteFilePath(); - QFSFileEngine engine(path); - QFileInfo newFile(dest.absolutePath() + "/" + files[i].fileName()); - - updateFile(path); - - OVERWRITE_PROMPT(files[i], newFile) - - // 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) { - PAUSE(); - if (abort) break; - updateProgress(1); - removeExcludeFiles.insert(path); - continue; - } - } else { - if (response == FileOperator::KEEP) { - PAUSE(); - if (abort) break; - updateProgress(1); - removeExcludeFiles.insert(path); - continue; - } - } - - QString newPath(newFile.absoluteFilePath()); - QFSFileEngine newEngine(newPath); - - bool done = false; - - 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) { - 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; - - QFileInfoList remainingFiles = files.mid(i); - - setBarSize(barValue + calculateFileSize(remainingFiles, true, true)); - - processFiles(remainingFiles); - - barText = tr("deleting %1"); - - remove(remainingFiles, true); - - done = true; - break; - // the target is nonempty dir. lets call this recursively and rename the contents one by one - } else if (errno == ENOTEMPTY || errno == EEXIST) { - FileOperator::Response tmpResp = overwriteAll; - overwriteAll = response; - - rename(listDirFiles(path), QDir(newPath)); - PAUSE(); - if (abort) break; - - overwriteAll = tmpResp; - - remove(files[i]); - - break; - // source and target are nonmatching types(file and dir) - // remove the target and let it loop once again - } else if (errno == ENOTDIR || errno == EISDIR) { - if (!remove(newPath)) break; - } else { - SHOW_ERROR_PROMPT(tr("Error moving %1."), path) - - if (response == FileOperator::IGNORE) { - break; - } - } - PAUSE(); - } - - if (done) break; - - PAUSE(); - if (abort) break; - updateProgress(1); +OperationThread *FileOperator::get(ProgressBar *bar) const { + for (OperationList::const_iterator it = opList.begin(); it != opList.end(); ++it) { + if (it->second == bar) return it->first; } -} - - -void MoveThread::perform(const QFileInfo &file) { - copy(file); + return 0; } diff --git a/src/fileoperator.h b/src/fileoperator.h index 076250d..c0a47ee 100644 --- a/src/fileoperator.h +++ b/src/fileoperator.h @@ -19,188 +19,47 @@ #define FILEOPERATOR_H #include -#include -#include -#include -#include -#include -#include -#include -#include #include "progressbar.h" +#include "operationthread.h" -class FileManipulatorThread; - +typedef QList > OperationList; class FileOperator : public QWidget { Q_OBJECT public: - // DONT_ASK_ONCE is a hackish way to avoid asking twice to overwrite the same directory when moving - enum Response{NONE, ABORT, RETRY, IGNORE, KEEP, OVERWRITE, SKIP_DIR, ASK, DONT_ASK_ONCE}; - FileOperator(QWidget *parent = 0); - static QString shortenPath(const QString &path); - static QString unwindPath(const QString &path); - void deleteFiles(const QFileInfoList &files); void copyFiles(const QFileInfoList &files, QDir &destination); void moveFiles(const QFileInfoList &files, QDir &destination); public slots: - void showErrorPrompt(FileManipulatorThread* manipulator, + void showErrorPrompt(OperationThread* op, const QString &message, const QString &fileName, const int err); - void showOverwritePrompt(FileManipulatorThread* manipulator, + void showOverwritePrompt(OperationThread* op, const QString &fileName, const bool dirOverDir); - void showInputFilenamePrompt(FileManipulatorThread* manipulator, + void showInputFilenamePrompt(OperationThread* op, const QFileInfo &fileName, const bool dirOverDir); - void remove(FileManipulatorThread* manipulator); - void setBarSize(FileManipulatorThread* manipulator, unsigned int size); - void updateProgress(FileManipulatorThread* manipulator, int value); - void updateMainText(FileManipulatorThread* manipulator, const QString &text); - void showPaused(FileManipulatorThread* manipulator); + void remove(OperationThread* op); - void togglePauseOperation(FileManipulatorThread* manipulator); - void abortOperation(FileManipulatorThread* manipulator); + void togglePauseOperation(ProgressBar* bar); + void abortOperation(ProgressBar* bar); protected: - void caterNewThread(FileManipulatorThread *thread); + void initOperation(OperationThread *thread, ProgressBar *bar); + ProgressBar *get(OperationThread *op) const; + OperationThread *get(ProgressBar *bar) const; - QList manipulatorList; + OperationList opList; QPixmap deleteIcon, inverseDeleteIcon, copyIcon, inverseCopyIcon, moveIcon, inverseMoveIcon; }; - -class FileManipulatorThread : public QThread { - Q_OBJECT - -public: - explicit FileManipulatorThread(const QFileInfoList files, QDir dest = QDir()); - ~FileManipulatorThread(); - void setResponse(const FileOperator::Response response, const bool appyToAll = false, const int err = 0); - - void setText(int value); - - void wake(); - - ProgressBar *progressBar; - - QMutex mutex; - // the new name entered from the overwrite dialog - QString newNameFromDialog; - // flags to abort/pause the operation - bool abort, pause; - -protected: - void processFiles(const QFileInfoList &files); - virtual void perform(const QFileInfo &file) = 0; - - bool remove(QString &fileName, const bool doUpdates = false); - bool remove(const QFileInfoList &files, const bool doUpdates = false); - bool remove(const QFileInfo &file, const bool doUpdates = false); - - void copy(const QFileInfo &file); - - unsigned int calculateFileSize(const QFileInfoList &files, - const bool count = false, - const bool addSize = false); - - QFileInfoList listDirFiles(const QString &dirPath); - - void setBarSize(unsigned int size); - void updateProgress(int value); - void updateFile(const QString &name); - - void waitOnCond(); - - bool checkSequentialFile(const QFSFileEngine &engine); - - QWaitCondition waitCond; - - // files to process by the operation - const QFileInfoList files; - // destination for files - changes as the operation recurses into directories - QDir dest; - - // responses from the dialog prompts (error and overwrite) - FileOperator::Response response; - FileOperator::Response overwriteAll; - // an array indicating whether to always ignore the error of index errno - bool ignoreAll[256]; - - // set of files that won't be deleted by the remove(...) functions - // used when move(...) would not overwrite target file to ensure the source file doesn't get deleted - QSet removeExcludeFiles; - - // A map of file paths to their size. Not the actual size, but what is calculated for the - // purpose of the progressbar for the given operation. So either fileSize/BLOCK_SIZE or simply - // 1 for a file and file count for dirs (or both for copy&delete) - QMap fileSizeMap; - - // the text of the progressBar (the format) - QString barText; - // stamp of the last ETA recalculation - done every second - time_t lastTimeUpdate; - time_t startTime, waitTime; - char timeBuf[10]; - // progress information of the bar and for the current file - unsigned int barSize, barValue, fileSize, fileValue; - -signals: - void showErrorPrompt(FileManipulatorThread*, const QString&, const QString&, const int); - void showOverwritePrompt(FileManipulatorThread*, const QString&, const bool); - void showInputFilenamePrompt(FileManipulatorThread*, const QFileInfo&, const bool); - void finished(FileManipulatorThread*); - void setBarSize(FileManipulatorThread*, unsigned int); - void updateProgress(FileManipulatorThread*, int); - void updateFileName(FileManipulatorThread*, QString); - void operationPaused(FileManipulatorThread*); -}; - - -class DeleteThread : public FileManipulatorThread { - Q_OBJECT - -public: - explicit DeleteThread(const QFileInfoList &files); - -protected: - void run(); - virtual void perform(const QFileInfo &file); -}; - - -class CopyThread : public FileManipulatorThread { - Q_OBJECT - -public: - explicit CopyThread(const QFileInfoList &files, QDir &dest); - -protected: - void run(); - virtual void perform(const QFileInfo &file); -}; - - -class MoveThread : public FileManipulatorThread { - Q_OBJECT - -public: - explicit MoveThread(const QFileInfoList &files, QDir &dest); - -protected: - void run(); - virtual void perform(const QFileInfo &file); - void rename(const QFileInfoList &files, const QDir &dest); -}; - - #endif // FILEOPERATOR_H diff --git a/src/operationthread.cpp b/src/operationthread.cpp new file mode 100644 index 0000000..d0cf7e1 --- /dev/null +++ b/src/operationthread.cpp @@ -0,0 +1,601 @@ +// case - file manager for N900 +// Copyright (C) 2010 Lukas Hrazky +// +// 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#include "operationthread.h" + +#include +#include + +#include "utils.h" + + +#define BLOCK_SIZE (256 * 1024) + + +#define PAUSE() \ + if (pause) { \ + emit operationPaused(); \ + waitOnCond(); \ + } + + +#define SHOW_ERROR_PROMPT(promptString, fileName) \ + response = NONE; \ + if (ignoreAll[errno]) { \ + response = IGNORE; \ + } else { \ + char buf[255]; \ + char *realBuf = buf; \ + if (errno == 255) { \ + strcpy(buf, tr("File is sequential").toStdString().c_str()); \ + } else { \ + realBuf = strerror_r(errno, buf, 255); \ + } \ + emit showErrorPrompt(this, promptString + " " + realBuf + ".", fileName, errno); \ + waitOnCond(); \ + } + + +#define ERROR_PROMPT(operation, promptString, fileName) \ +{ \ + response = NONE; \ + while (!abort && operation) { \ + SHOW_ERROR_PROMPT(promptString, fileName) \ + if (response == IGNORE) { \ + break; \ + } \ + PAUSE() \ + } \ +} + + +#define SPECIAL_COPY_ERROR_PROMPT(operation, promptString, fileName) \ +{ \ + ERROR_PROMPT(operation, promptString, fileName) \ + if (abort || response == IGNORE) { \ + if (!abort) { \ + updateProgress(fileSizeMap[path]); \ + removeExcludeFiles.insert(path); \ + } \ + return; \ + } \ +} + + +#define OVERWRITE_PROMPT(file, newFile) \ +{ \ + response = NONE; \ + \ + while (!abort && response == NONE && newFile.exists()) { \ + if (overwriteAll != NONE) { \ + response = overwriteAll; \ + } else { \ + emit showOverwritePrompt(this, newFile.absoluteFilePath(), \ + newFile.isDir() && file.isDir()); \ + waitOnCond(); \ + \ + PAUSE() \ + else if (response == NONE) { \ + emit showInputFilenamePrompt(this, newFile, file.isDir()); \ + waitOnCond(); \ + if (newNameFromDialog.size()) { \ + newFile.setFile(newNameFromDialog); \ + } \ + } \ + } \ + } \ + if (response == ASK) response = NONE; \ +} + + +OperationThread::OperationThread(const QFileInfoList files, QDir dest) : + abort(false), + pause(false), + files(files), + dest(dest), + response(NONE), + overwriteAll(NONE), + totalSize(0), + totalValue(0), + fileValue(0) +{ + memset(ignoreAll, false, sizeof(ignoreAll)); +} + + +void OperationThread::setResponse( + const Response response, + const bool applyToAll, + const int err) +{ + mutex.lock(); + + this->response = response; + + if (applyToAll) { + if (response == KEEP + || response == OVERWRITE + || response == NONE) + { + overwriteAll = response; + } + + if (response == IGNORE) { + ignoreAll[err] = true; + } + } + + if (response == ABORT) abort = true; + + mutex.unlock(); + waitCond.wakeAll(); +} + + +void OperationThread::processFiles(const QFileInfoList &files) { + for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) { + PAUSE(); + if (abort) break; + perform(*it); + } +} + + +bool OperationThread::remove(QString &fileName, const bool doUpdates) { + return remove(QFileInfo(fileName), doUpdates); +} + + +bool OperationThread::remove(const QFileInfoList &files, const bool doUpdates) { + bool res = true; + for (QFileInfoList::const_iterator it = files.begin(); it != files.end(); ++it) { + if (!remove(*it, doUpdates)) res = false; + PAUSE(); + if (abort) break; + } + return res; +} + + +bool OperationThread::remove(const QFileInfo &file, const bool doUpdates) { + 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)) { + 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."), path) + } + + if (!abort && doUpdates) updateProgress(1); + + PAUSE(); + if (abort || response == IGNORE) return false; + return true; +} + + +void OperationThread::copy(const QFileInfo &file) { + QString path(file.absoluteFilePath()); + QFSFileEngine engine(path); + QFileInfo newFile(dest.absolutePath() + "/" + file.fileName()); + + updateFile(path); + + // hack to prevent asking about the same file if we already asked in the rename(...) function + if (overwriteAll == DONT_ASK_ONCE) { + overwriteAll = NONE; + } else { + OVERWRITE_PROMPT(file, newFile) + } + + QString newPath(newFile.absoluteFilePath()); + QFSFileEngine newEngine(newPath); + + PAUSE(); + if (abort) return; + + if (file.isDir()) { + // save the overwrite response, because the response variable will get ovewritten in remove(...) + Response overwriteResponse = response; + + if (newFile.exists() && !newFile.isDir()) { + // overwriting a file, so check for KEEP and handle it + if (response == 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 == SKIP_DIR) { + updateProgress(fileSizeMap[path]); + removeExcludeFiles.insert(path); + return; + } + } + + if (!newFile.exists()) { + 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 + Response tmpResp = overwriteAll; + overwriteAll = overwriteResponse; + + processFiles(listDirFiles(path)); + + overwriteAll = tmpResp; + + ERROR_PROMPT(!newEngine.setPermissions(file.permissions()), + tr("Error setting permissions for directory %1."), newPath) + + PAUSE(); + if (abort) return; + + dest = destBackup; + } else { + if (response == KEEP) { + updateProgress(fileSizeMap[path]); + removeExcludeFiles.insert(path); + return; + } + + SPECIAL_COPY_ERROR_PROMPT(checkSequentialFile(engine), tr("Cannot copy file %1."), path) + + if (newFile.exists() && newFile.isDir()) { + SPECIAL_COPY_ERROR_PROMPT(!remove(newPath), + tr("Cannot replace directory %1 due to previous errors."), newPath) + } + + 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 == IGNORE) { + if (response == IGNORE) { + updateProgress(fileSizeMap[path]); + removeExcludeFiles.insert(path); + ignore = true; + } + break; + } + + 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 == IGNORE) { + updateProgress(fileSizeMap[path] - fileValue); + removeExcludeFiles.insert(path); + ignore = true; + } else { + updateProgress(-fileValue); + } + } + error = true; + break; + } + + PAUSE(); + if (abort) break; + + updateProgress(1); + } + + if (!error) break; + PAUSE(); + } + + engine.close(); + newEngine.close(); + + PAUSE(); + if (abort || ignore) { + if (newFileWritten) { + newEngine.remove(); + } + } else { + ERROR_PROMPT(!newEngine.setPermissions(file.permissions()), + tr("Error setting permissions for file %1."), newPath) + } + } +} + + +unsigned int OperationThread::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 = 0; + + PAUSE(); + if (abort) break; + + if (it->isDir()) { + size += calculateFileSize(listDirFiles(it->absoluteFilePath()), count, addSize); + } + + if (addSize) { + if (it->isDir()) { + ++size; + } else { + size += ceil(static_cast(it->size()) / BLOCK_SIZE); + } + fileSizeMap[it->absoluteFilePath()] = size; + } + + if (count) { + ++size; + } + + res += size; + } + + return res; +} + + +QFileInfoList OperationThread::listDirFiles(const QString &dirPath) { + QDir dir = dirPath; + return dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllEntries | QDir::System | QDir::Hidden); +} + + +void OperationThread::setTotalSize(unsigned int size) { + totalSize = size; + emit totalSizeChanged(size); +} + + +void OperationThread::updateProgress(int value) { + totalValue += value; + fileValue += value; + emit progressUpdate(value); +} + + +void OperationThread::updateFile(const QString &name) { + fileValue = 0; + emit fileNameUpdated(shortenPath(name)); +} + + +void OperationThread::waitOnCond() { + time_t waitTime = time(0); + waitCond.wait(&mutex); + emit operationResumed(time(0) - waitTime); +} + + +bool OperationThread::checkSequentialFile(const QFSFileEngine &engine) { + errno = 0; + if (engine.isSequential()) { + if (!errno) errno = 255; + return true; + } + + return false; +} + + +void OperationThread::wake() { + pause = false; + waitCond.wakeAll(); +} + + +void DeleteThread::run() { + mutex.lock(); + + setTotalSize(calculateFileSize(files, true)); + emit operationStarted(time(0)); + + processFiles(files); + + sleep(0.5); + emit finished(this); +} + + +void DeleteThread::perform(const QFileInfo &file) { + remove(file, true); +} + + +void CopyThread::run() { + mutex.lock(); + + setTotalSize(calculateFileSize(files, false, true)); + emit operationStarted(time(0)); + + processFiles(files); + + sleep(0.5); + emit finished(this); +} + + +void CopyThread::perform(const QFileInfo &file) { + copy(file); +} + + +void MoveThread::run() { + mutex.lock(); + + rename(files, dest); + + sleep(0.5); + emit finished(this); +} + + +void MoveThread::rename(const QFileInfoList &files, const QDir &dest) { + setTotalSize(totalSize + files.size()); + emit operationStarted(time(0)); + + for (int i = 0; i < files.size(); ++i) { + QString path = files[i].absoluteFilePath(); + QFSFileEngine engine(path); + QFileInfo newFile(dest.absolutePath() + "/" + files[i].fileName()); + + updateFile(path); + + OVERWRITE_PROMPT(files[i], newFile) + + // 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 == SKIP_DIR) { + PAUSE(); + if (abort) break; + updateProgress(1); + removeExcludeFiles.insert(path); + continue; + } + } else { + if (response == KEEP) { + PAUSE(); + if (abort) break; + updateProgress(1); + removeExcludeFiles.insert(path); + continue; + } + } + + QString newPath(newFile.absoluteFilePath()); + QFSFileEngine newEngine(newPath); + + bool done = false; + + 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) { + 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 == NONE) overwriteAll = DONT_ASK_ONCE; + + QFileInfoList remainingFiles = files.mid(i); + + setTotalSize(totalValue + calculateFileSize(remainingFiles, true, true)); + + processFiles(remainingFiles); + + emit removeAfterCopy(); + + remove(remainingFiles, true); + + done = true; + break; + // the target is nonempty dir. lets call this recursively and rename the contents one by one + } else if (errno == ENOTEMPTY || errno == EEXIST) { + Response tmpResp = overwriteAll; + overwriteAll = response; + + rename(listDirFiles(path), QDir(newPath)); + PAUSE(); + if (abort) break; + + overwriteAll = tmpResp; + + remove(files[i]); + + break; + // source and target are nonmatching types(file and dir) + // remove the target and let it loop once again + } else if (errno == ENOTDIR || errno == EISDIR) { + if (!remove(newPath)) break; + } else { + SHOW_ERROR_PROMPT(tr("Error moving %1."), path) + + if (response == IGNORE) { + break; + } + } + PAUSE(); + } + + if (done) break; + + PAUSE(); + if (abort) break; + updateProgress(1); + } +} + + +void MoveThread::perform(const QFileInfo &file) { + copy(file); +} diff --git a/src/operationthread.h b/src/operationthread.h new file mode 100644 index 0000000..bb9d95a --- /dev/null +++ b/src/operationthread.h @@ -0,0 +1,152 @@ +// case - file manager for N900 +// Copyright (C) 2010 Lukas Hrazky +// +// 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + + +#ifndef OPERATIONTHREAD_H +#define OPERATIONTHREAD_H + +#include +#include +#include +#include +#include +#include +#include +#include + + +class OperationThread : public QThread { + Q_OBJECT + +public: + // DONT_ASK_ONCE is a hackish way to avoid asking twice to overwrite the same directory when moving + enum Response{NONE, ABORT, RETRY, IGNORE, KEEP, OVERWRITE, SKIP_DIR, ASK, DONT_ASK_ONCE}; + + explicit OperationThread(const QFileInfoList files, QDir dest = QDir()); + void setResponse(const Response response, const bool appyToAll = false, const int err = 0); + + void wake(); + + // the new name entered from the overwrite dialog + QString newNameFromDialog; + // flags to abort/pause the operation + bool abort, pause; + +protected: + void processFiles(const QFileInfoList &files); + virtual void perform(const QFileInfo &file) = 0; + + bool remove(QString &fileName, const bool doUpdates = false); + bool remove(const QFileInfoList &files, const bool doUpdates = false); + bool remove(const QFileInfo &file, const bool doUpdates = false); + + void copy(const QFileInfo &file); + + unsigned int calculateFileSize(const QFileInfoList &files, + const bool count = false, + const bool addSize = false); + + QFileInfoList listDirFiles(const QString &dirPath); + + void setTotalSize(unsigned int size); + void updateProgress(int value); + void updateFile(const QString &name); + + void waitOnCond(); + + bool checkSequentialFile(const QFSFileEngine &engine); + + QMutex mutex; + QWaitCondition waitCond; + + // files to process by the operation + const QFileInfoList files; + // destination for files - changes as the operation recurses into directories + QDir dest; + + // responses from the dialog prompts (error and overwrite) + Response response; + Response overwriteAll; + // an array indicating whether to always ignore the error of index errno + bool ignoreAll[256]; + + // set of files that won't be deleted by the remove(...) functions + // used when move(...) would not overwrite target file to ensure the source file doesn't get deleted + QSet removeExcludeFiles; + + // A map of file paths to their size. Not the actual size, but what is calculated for the + // purpose of the progressbar for the given operation. So either fileSize/BLOCK_SIZE or simply + // 1 for a file and file count for dirs (or both for copy&delete) + QMap fileSizeMap; + + // progress information of the bar and for the current file + unsigned int totalSize, totalValue, fileValue; + +signals: + void showErrorPrompt(OperationThread*, const QString&, const QString&, const int); + void showOverwritePrompt(OperationThread*, const QString&, const bool); + void showInputFilenamePrompt(OperationThread*, const QFileInfo&, const bool); + void finished(OperationThread*); + + void totalSizeChanged(int); + void progressUpdate(int); + void fileNameUpdated(QString); + void operationStarted(time_t); + void operationPaused(); + void operationResumed(time_t); + // special signal emitted when operation has to copy files between partitions + // to notify the user it is deleting files after succesful copy (which can take some time) + void removeAfterCopy(); +}; + + +class DeleteThread : public OperationThread { + Q_OBJECT + +public: + explicit DeleteThread(const QFileInfoList &files) : OperationThread(files) {} + +protected: + void run(); + virtual void perform(const QFileInfo &file); +}; + + +class CopyThread : public OperationThread { + Q_OBJECT + +public: + explicit CopyThread(const QFileInfoList &files, QDir &dest) : OperationThread(files, dest) {} + +protected: + void run(); + virtual void perform(const QFileInfo &file); +}; + + +class MoveThread : public OperationThread { + Q_OBJECT + +public: + explicit MoveThread(const QFileInfoList &files, QDir &dest) : OperationThread(files, dest) {} + +protected: + void run(); + virtual void perform(const QFileInfo &file); + void rename(const QFileInfoList &files, const QDir &dest); +}; + +#endif // OPERATIONTHREAD_H diff --git a/src/progressbar.cpp b/src/progressbar.cpp index 29d68a6..7780b04 100644 --- a/src/progressbar.cpp +++ b/src/progressbar.cpp @@ -23,12 +23,16 @@ #include -ProgressBar::ProgressBar(FileManipulatorThread *thread, QWidget *parent) : - QProgressBar(parent), + +ProgressBar::ProgressBar(const QPixmap &icon, const QPixmap &inverseIcon) : + bgIcon(icon), + fgIcon(inverseIcon), paused(false), contextEvent(false), - thread(thread) + lastTimeUpdate(0), + startTime(0) { + timeBuf[0] = 0; setMaximum(1); setValue(0); setMinimum(0); @@ -41,19 +45,84 @@ ProgressBar::ProgressBar(FileManipulatorThread *thread, QWidget *parent) : } -void ProgressBar::setIcons(const QPixmap &icon, const QPixmap &inverseIcon) { - bgIcon = icon; - fgIcon = inverseIcon; +void ProgressBar::updateProgress(int val) { + if (value() + val > maximum()) { + std::cout << "WARNING: exceeding progressbar maximum (" << maximum() << ") by " << val << std::endl; + } + + setValue(value() + val); + + time_t now = time(0); + if (lastTimeUpdate < now) { + lastTimeUpdate = now; + + time_t elapsed = now - startTime; + time_t remaining = (time_t) ((float) elapsed / value() * (maximum() - value())); + struct tm *ts = gmtime(&remaining); + + if (remaining == 0) { + timeBuf[0] = 0; + } else 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); + } + } + + setFormat(QString("%p%") + timeBuf); +} + + +void ProgressBar::updateMainText(const QString &text) { + mainText = text; + if (fromText.size()) { + mainText.remove(0, fromText.size() + 1); + } + repaint(); +} + + +void ProgressBar::setBottomTexts(const QString &left, const QString &right) { + fromText = left; + toText = right; +} + + +void ProgressBar::setStartTime(time_t t) { + startTime = t; + lastTimeUpdate = time(0); + updateProgress(0); +} + + +void ProgressBar::pause() { + paused = true; + repaint(); +} + + +void ProgressBar::resume(time_t stallTime) { + startTime += stallTime; + paused = false; + repaint(); +} + + +void ProgressBar::showRemoveNotice() { + toText = "<" + tr("deleting") + ">"; + repaint(); } void ProgressBar::mouseReleaseEvent(QMouseEvent *) { - if (!contextEvent) emit togglePauseOperation(thread); + if (!contextEvent) emit togglePauseOperation(this); contextEvent = false; } void ProgressBar::contextMenuEvent(QContextMenuEvent *) { contextEvent = true; - emit abortOperation(thread); + emit abortOperation(this); } diff --git a/src/progressbar.h b/src/progressbar.h index fe67dfd..1c63b0d 100644 --- a/src/progressbar.h +++ b/src/progressbar.h @@ -22,31 +22,39 @@ #include -class FileManipulatorThread; - class ProgressBar : public QProgressBar { Q_OBJECT; -signals: - void abortOperation(FileManipulatorThread*); - void togglePauseOperation(FileManipulatorThread*); - public: - explicit ProgressBar(FileManipulatorThread *thread, QWidget *parent = 0); - - void setIcons(const QPixmap &icon, const QPixmap &inverseIcon); + explicit ProgressBar(const QPixmap &icon, const QPixmap &inverseIcon); QString mainText, fromText, toText; QPixmap bgIcon, fgIcon; bool paused; +public slots: + void updateProgress(int val); + void updateMainText(const QString &text); + void setBottomTexts(const QString &left, const QString &right); + void setStartTime(time_t t); + void pause(); + void resume(time_t stallTime); + void showRemoveNotice(); protected: void mouseReleaseEvent(QMouseEvent *); void contextMenuEvent(QContextMenuEvent *); bool contextEvent; - FileManipulatorThread *thread; + + // stamp of the last ETA recalculation - done every second + time_t lastTimeUpdate; + time_t startTime; + char timeBuf[10]; + +signals: + void abortOperation(ProgressBar*); + void togglePauseOperation(ProgressBar*); }; #endif // PROGRESSBAR_H diff --git a/src/utils.cpp b/src/utils.cpp index bf082e0..8a50e16 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -17,6 +17,37 @@ #include "utils.h" +#include + + +QString shortenPath(const QString &path) { + QString homePath = QFSFileEngine::homePath(); + + if (path.indexOf(homePath, 0) == 0) { + QString result = path; + + result.replace(0, homePath.size(), "~"); + return result; + } + + return path; +} + + +QString unwindPath(const QString &path) { + QString result = path; + // if ~ is the first character and / or nothing follows it, replace with home dir + if (path == "~" || path.indexOf("~/", 0) == 0) { + QString homePath = QFSFileEngine::homePath(); + result.replace(0, 1, homePath); + // in case someone wants to enter a dir called ~ in the current dir, he can escape it with \~ + } else if (path == "\\~" || path.indexOf("\\~/", 0) == 0) { + result.replace(0, 2, "~"); + } + + return result; +} + void themeImage(const QPalette &p, QImage &image, const bool inverse) { image = image.convertToFormat(QImage::Format_Indexed8); diff --git a/src/utils.h b/src/utils.h index b3e1654..c1a5671 100644 --- a/src/utils.h +++ b/src/utils.h @@ -27,6 +27,9 @@ #define ICON_SET "default" +QString shortenPath(const QString &path); +QString unwindPath(const QString &path); + bool loadOperationIcons(const QPalette &p, const QString &name, QPixmap &normal, QPixmap &inverse); bool loadMiddleButtonIcons(const QPalette &p, const QString &name, QIcon &normal, QIcon &mirrored); -- 1.7.9.5