X-Git-Url: https://vcs.maemo.org/git/?a=blobdiff_plain;f=src%2Foperationthread.cpp;fp=src%2Foperationthread.cpp;h=d0cf7e15104970a08dc49c72012bcfd4b6eea3cf;hb=2edb4f2f10329116492492b442c2957653381c28;hp=0000000000000000000000000000000000000000;hpb=55d99074d0b0b5eca3530d0c0f683faee48f10f9;p=case 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); +}