From 68de6a8b286af4b3f4b54a8be6aea2b6a4db6bbb Mon Sep 17 00:00:00 2001 From: hama Date: Sun, 7 Feb 2016 22:58:58 +0100 Subject: [PATCH] rebackgui: implentation of "clean" --- appl/rebackgui/BackupEngine.cpp | 604 ++++++++++++++++++++++++-------- appl/rebackgui/BackupEngine.hpp | 50 +++ appl/rebackgui/mainwindow.cpp | 70 +++- appl/rebackgui/mainwindow.hpp | 6 +- appl/rebackgui/mainwindow.ui | 16 + appl/rebackgui/osconnect.pl | 87 +++-- 6 files changed, 627 insertions(+), 206 deletions(-) diff --git a/appl/rebackgui/BackupEngine.cpp b/appl/rebackgui/BackupEngine.cpp index 27aae20..dbab03f 100644 --- a/appl/rebackgui/BackupEngine.cpp +++ b/appl/rebackgui/BackupEngine.cpp @@ -21,6 +21,7 @@ bool BackupEngine::m_searchReady = false; QMutex BackupEngine::m_mutex; QChar BackupEngine::m_separator = '\t'; QString BackupEngine::m_separatorString = "\t"; + QStringList ChecksumTask::m_checksumInfo; bool ChecksumTask::m_sourceProcessingReady = false; @@ -39,6 +40,8 @@ BackupEngine::BackupEngine(const QString& name, m_sourceDirs(sourceDirs), m_targetBaseDir(targetDir), m_targetDirs(), + m_shadowBaseDir(), + m_shadowDirs(), m_mainWindow(mainWindow), m_name(name) { @@ -65,6 +68,33 @@ bool BackupEngine::error(const QString& message){ return false; } +/** + * Initializes the shadow directories. + * + * All not too old superflous files will be moved to the shadow directory. + * Note: the length of the shadow directory is equal to + * the length of the target dir: Otherwise full path length could be too long. + */ +void BackupEngine::initializeShadowDir(){ + if (m_shadowBaseDir.isEmpty()){ + int fullLength = m_targetBaseDir.length(); + QString node = ReFileUtils::nodeOf(m_targetBaseDir); + int nodeLength = node.length(); + if (nodeLength <= 2) + m_shadowBaseDir = ReFileUtils::parentOf(m_targetBaseDir) + "$"; + else { + int middle = nodeLength / 2; + m_shadowBaseDir = "." + m_targetBaseDir; + m_shadowBaseDir.remove(fullLength - middle, 1); + } + } + m_shadowDirs.clear(); + for (int ix = 0; ix < m_sourceDirs.size(); ix++){ + m_shadowDirs.append(m_shadowBaseDir + OS_SEPARATOR_STR + + ReFileUtils::nodeOf(m_sourceDirs.at(ix))); + } +} + /** * @brief Logs a message. * @@ -78,83 +108,32 @@ bool BackupEngine::log(const QString& message){ } /** - * Constructor. + * Inserts a remove command into queue for too old files in the shadow. * - * @param compareWithTarget false: all files will be found
- * otherwise: only not existing or newer files will - * be found - * @param name name of the task - * @param filePatterns only files which match the patterns will be found - * @param dirPatterns only subdirectories which matches the patterns will be entered - * @param source the list of base directories to search - * @param targetDir the base target directory - * @param mainWindow the GUI module, the parent - */ -SearchTask::SearchTask(bool compareWithTarget, const QString& name, - const QString& filePatterns, const QString& dirPatterns, - const QStringList& sourceDirs, const QString& targetDir, - MainWindow* mainWindow) : - BackupEngine(name, sourceDirs, targetDir, mainWindow), - m_fileMatcher(filePatterns), - m_dirMatcher(dirPatterns), - m_compareWithTarget(compareWithTarget) -{ -} - -/** - * Runs the task. - */ -void SearchTask::run() -{ - qint64 start = QDateTime::currentMSecsSinceEpoch(); - m_searchReady = false; - QString targetDir, sourceDir; - for (int ix = 0; ix < m_sourceDirs.size(); ix++){ - sourceDir = m_sourceDirs.at(ix); - if (m_compareWithTarget) - targetDir = m_targetDirs.at(ix) + ReFileUtils::nodeOf(sourceDir); - searchOneDirectory(sourceDir, targetDir, ix); - } - m_searchReady = true; - m_mainWindow->externalLog(tr( - "Search finished: to process: %1 with %2 matching: %3 total: %4 " - "subdirs: %5 runtime: %6") - .arg(m_hotFiles) - .arg(ReQStringUtils::readableSize(m_hotBytes)) - .arg(m_matchedFiles).arg(m_totalFiles) - .arg(m_totalDirs) - .arg(ReQStringUtils::readableDuration( - QDateTime::currentMSecsSinceEpoch() - start))); -} -/** - * Search the files to backup and write it to a list. + * Note: this method is recursive. * - * This method is recursive. - * - * @param source the directory to inspect - * @param target "": no target directory exists
- * otherwise: only files will be written if the file found in - * the source directory does not exists in this target or - * it is newer or has another size - * @param index the index of the base directory in m_sourceDirs + * @param directory directory to inspect + * @param maxAge all files older than this time point will be deleted + * @param index the index in the shadow directories */ -void SearchTask::searchOneDirectory(const QString& source, - const QString& target, int index){ - QDirIterator it(source); +void BackupEngine::removeOlder(const QString& directory, const QDateTime& time, + int index){ + QDirIterator it(directory); QString info, node; - int lengthBase = m_sourceDirs.at(index).length(); + int lengthBase = m_shadowDirs.at(index).length(); assert(index < 0x7fff); QString prefix; m_mutex.lock(); m_totalDirs++; m_mutex.unlock(); - if (source.length() > lengthBase){ - prefix = QChar(1 + index) - + ReFileUtils::nativePath(source.mid(lengthBase)) + OS_SEPARATOR_STR + if (directory.length() > lengthBase){ + prefix = QChar(1 + index) + + ReFileUtils::nativePath(directory.mid(lengthBase)) + OS_SEPARATOR_STR + m_separator; } else { prefix = QChar(1 + index) + m_separatorString; } + bool isEmpty = true; while (it.hasNext()){ if (m_shouldStop){ break; @@ -162,70 +141,29 @@ void SearchTask::searchOneDirectory(const QString& source, it.next(); node = it.fileName(); if (it.fileInfo().isDir()){ - // nothing to do - } else if (! m_fileMatcher.matches(node)){ - m_mutex.lock(); - m_totalFiles++; - m_mutex.unlock(); - } else { - qint64 diff = 0; - bool doCopy = false; - if (! m_compareWithTarget) - doCopy = true; - else if (target.isEmpty()) - doCopy = true; - else { - QFileInfo trg(target + it.fileName()); - if (! trg.exists()) - doCopy = true; - else { - const QFileInfo src = it.fileInfo(); - if (trg.size() != src.size()) - doCopy = true; - else if ((diff = src.lastModified().toMSecsSinceEpoch() - - trg.lastModified().toMSecsSinceEpoch()) > 2000) - doCopy = true; - } - } - if (doCopy){ - info = prefix + it.fileName(); + if (node != "." && node != ".."){ + removeOlder(it.filePath(), time, index); + isEmpty = false; } + } else if (it.fileInfo().lastModified() < time){ + isEmpty = false; + info = prefix + QChar(CmdRemove) + it.fileName(); m_mutex.lock(); - if (doCopy){ - m_files.append(info); - m_hotFiles++; - m_hotBytes += it.fileInfo().size(); - } + m_files.append(info); + m_hotFiles++; + m_hotBytes += it.fileInfo().size(); m_totalFiles++; - m_matchedFiles++; m_mutex.unlock(); } } - if (! m_shouldStop){ - QDirIterator it2(source); - QString node; - QString subTarget; - while (it2.hasNext()){ - if (m_shouldStop){ - break; - } - it2.next(); - - if (it2.fileInfo().isDir() && (node = it2.fileName()) != "." && node != ".." - && m_dirMatcher.matches(node)){ - if (target.isEmpty()) - subTarget.clear(); - else{ - subTarget = target + it2.fileName(); - if (! ReFileUtils::isDirectory(subTarget)) - subTarget.clear(); - else - subTarget += OS_SEPARATOR_STR; - } - searchOneDirectory(it2.filePath(), subTarget, index); - } - } + if (isEmpty){ + info = prefix + QChar(CmdRemoveDir) + it.fileName(); + m_mutex.lock(); + m_files.append(info); + m_matchedFiles++; + m_mutex.unlock(); } + } /** @@ -317,7 +255,7 @@ void BackupTask::run() node = info.mid(pos + 1); copyFile(index, relPath, node); qint64 now = QDateTime::currentMSecsSinceEpoch(); - qint64 estimated = qint64(double(now - start) / max(1LL, m_processedBytes) * m_hotBytes); + qint64 estimated = qint64(double(now - start) / max(1LL, m_processedBytes) * m_hotBytes); m_mainWindow->externalAppend(ReGuiQueueItem::StatusLine, NULL, tr("%1 of %2 (%3 of %4) %5 MB/sec Remaining: %6 of %7") .arg(m_processedFiles).arg(m_hotFiles) @@ -507,26 +445,28 @@ void ChecksumOfTargetTask::run() error(QObject::tr("checksum differs: ") + relPath + node + " [" + sourceChecksum + "/" + checksum + "]"); } - qint64 processedBytes, hotBytes; - int hotFiles, processedFiles; - m_mutex.lock(); - hotFiles = m_hotFiles; - hotBytes = m_hotBytes; - processedBytes = m_processedBytes; - processedFiles = m_processedFiles; - m_mutex.unlock(); - now = QDateTime::currentMSecsSinceEpoch(); - qint64 duration = (now - start); - qint64 estimated = estimated = qint64(double(now - start) / max(1LL, m_processedBytes) * m_hotBytes * 2); - qint64 rest = estimated - duration; + qint64 processedBytes, hotBytes; + int hotFiles, processedFiles; + m_mutex.lock(); + hotFiles = m_hotFiles; + hotBytes = m_hotBytes; + processedBytes = m_processedBytes; + processedFiles = m_processedFiles; + m_mutex.unlock(); + now = QDateTime::currentMSecsSinceEpoch(); + double duration = (now - start); + double estimated = qint64(double(now - start)); + estimated /= max(1LL, m_processedBytes); + estimated *= m_hotBytes * 2; + qint64 rest = qint64(estimated - duration); m_mainWindow->externalAppend(ReGuiQueueItem::StatusLine, NULL, tr("%1 of %2 (%3 of %4) %5 MB/sec Remaining: %6 of %7") - .arg(m_processedFiles).arg(hotFiles * 2) - .arg(ReQStringUtils::readableSize(processedBytes)) - .arg(ReQStringUtils::readableSize(hotBytes * 2)) - .arg(processedBytes / 1024.0 / 1024 / duration * 1000.0, 0, 'f', 3) - .arg(ReQStringUtils::readableDuration(rest)) - .arg(ReQStringUtils::readableDuration(estimated))); + .arg(processedFiles).arg(hotFiles * 2) + .arg(ReQStringUtils::readableSize(processedBytes)) + .arg(ReQStringUtils::readableSize(hotBytes * 2)) + .arg(processedBytes / 1024.0 / 1024 / duration * 1000.0, 0, 'f', 3) + .arg(ReQStringUtils::readableDuration(rest)) + .arg(ReQStringUtils::readableDuration(qint64(estimated)))); } } @@ -537,4 +477,388 @@ void ChecksumOfTargetTask::run() .arg(m_mainWindow->errors())); } +/** + Constructor. + + * @param name name of the task + * @param sourceDirs the list of source directories to inspect + * @param targetDir the base of the target directories + * @param maxAgeSec superflous file which is older will be moved to the + * shadow, the files which are younger will be deleted + * @param mainWindow the GUI module + */ + +CleanTask::CleanTask(const QString& name, const QStringList& sourceDirs, + const QString& targetDir, MainWindow* mainWindow) : + BackupEngine(name, sourceDirs, targetDir, mainWindow) +{ + +} + +/** + * Runs the task. + */ +void CleanTask::run() +{ + QString relPath, node; + QString info; + qint64 start = QDateTime::currentMSecsSinceEpoch(); + while (true){ + m_mutex.lock(); + if (m_files.size() == 0) + info.clear(); + else{ + info = m_files.first(); + m_files.removeFirst(); + } + m_processedFiles++; + m_mutex.unlock(); + if (info.isEmpty()){ + if (m_searchReady) + break; + else + QThread::msleep(50); + } else { + int index = int(info.at(0).unicode()) - 1; + int pos = info.indexOf(m_separator, 1); + if (pos == 1) + relPath.clear(); + else + relPath = info.mid(1, pos - 1); + Command command = (Command) info.at(pos + 1).unicode(); + node = info.mid(pos + 2); + switch(command){ + case CmdRemove: + m_mainWindow->addToFileList("-" + relPath); + break; + case CmdRemoveDir: + m_mainWindow->addToFileList("/" + relPath); + break; + case CmdMove: + { + QString target = m_targetDirs.at(index) + relPath + node; + QString shadowDir = m_shadowDirs.at(index) + relPath; + QString shadow = shadowDir + node; + if (! ReFileUtils::makeDirWithParents(shadowDir)) + error(QObject::tr("cannot create the shadow directory: %1") + .arg(shadowDir)); + m_mainWindow->addToFileList(">" + target + " -> " + shadow); + break; + } + default: + error("unknown command: " + QString::number(command)); + break; + } + qint64 now = QDateTime::currentMSecsSinceEpoch(); + qint64 estimated = qint64(double(now - start) / max(1, m_processedFiles) * m_hotFiles); + m_mainWindow->externalAppend(ReGuiQueueItem::StatusLine, NULL, + tr("%1 of %2 (%3 of %4) %5 MB/sec Remaining: %6 of %7") + .arg(m_processedFiles).arg(m_hotFiles) + .arg(ReQStringUtils::readableSize(m_processedBytes)) + .arg(ReQStringUtils::readableSize(m_hotBytes)) + .arg(m_processedBytes / 1024.0 / 1024 / (now - start) * 1000, 0, 'f', 3) + .arg(ReQStringUtils::readableDuration(estimated - (now - start))) + .arg(ReQStringUtils::readableDuration(estimated))); + } + } + m_mainWindow->externalTaskFinished(tr("backup complete after %1. Errors: %2") + .arg(ReQStringUtils::readableDuration( + QDateTime::currentMSecsSinceEpoch() - start)) + .arg(m_mainWindow->errors())); +} + + +/** + * Constructor. + * + * @param compareWithTarget false: all files will be found
+ * otherwise: only not existing or newer files will + * be found + * @param name name of the task + * @param filePatterns only files which match the patterns will be found + * @param dirPatterns only subdirectories which matches the patterns will be entered + * @param source the list of base directories to search + * @param targetDir the base target directory + * @param mainWindow the GUI module, the parent + */ +SearchTask::SearchTask(bool compareWithTarget, const QString& name, + const QString& filePatterns, const QString& dirPatterns, + const QStringList& sourceDirs, const QString& targetDir, + MainWindow* mainWindow) : + BackupEngine(name, sourceDirs, targetDir, mainWindow), + m_fileMatcher(filePatterns), + m_dirMatcher(dirPatterns), + m_compareWithTarget(compareWithTarget) +{ +} + +/** + * Runs the task. + */ +void SearchTask::run() +{ + qint64 start = QDateTime::currentMSecsSinceEpoch(); + m_searchReady = false; + QString targetDir, sourceDir; + for (int ix = 0; ix < m_sourceDirs.size(); ix++){ + sourceDir = m_sourceDirs.at(ix); + if (m_compareWithTarget) + targetDir = m_targetDirs.at(ix) + ReFileUtils::nodeOf(sourceDir); + searchOneDirectory(sourceDir, targetDir, ix); + } + m_searchReady = true; + m_mainWindow->externalLog(tr( + "Search finished: to process: %1 with %2 matching: %3 total: %4 " + "subdirs: %5 runtime: %6") + .arg(m_hotFiles) + .arg(ReQStringUtils::readableSize(m_hotBytes)) + .arg(m_matchedFiles).arg(m_totalFiles) + .arg(m_totalDirs) + .arg(ReQStringUtils::readableDuration( + QDateTime::currentMSecsSinceEpoch() - start))); +} +/** + * Search the files to backup and write it to a list. + * + * This method is recursive. + * + * @param source the directory to inspect + * @param target "": no target directory exists
+ * otherwise: only files will be written if the file found in + * the source directory does not exists in this target or + * it is newer or has another size + * @param index the index of the base directory in m_sourceDirs + */ +void SearchTask::searchOneDirectory(const QString& source, + const QString& target, int index){ + QDirIterator it(source); + QString info, node; + int lengthBase = m_sourceDirs.at(index).length(); + assert(index < 0x7fff); + QString prefix; + m_mutex.lock(); + m_totalDirs++; + m_mutex.unlock(); + if (source.length() > lengthBase){ + prefix = QChar(1 + index) + + ReFileUtils::nativePath(source.mid(lengthBase)) + OS_SEPARATOR_STR + + m_separator; + } else { + prefix = QChar(1 + index) + m_separatorString; + } + while (it.hasNext()){ + if (m_shouldStop){ + break; + } + it.next(); + node = it.fileName(); + if (it.fileInfo().isDir()){ + // nothing to do + } else if (! m_fileMatcher.matches(node)){ + m_mutex.lock(); + m_totalFiles++; + m_mutex.unlock(); + } else { + qint64 diff = 0; + bool doCopy = false; + if (! m_compareWithTarget) + doCopy = true; + else if (target.isEmpty()) + doCopy = true; + else { + QFileInfo trg(target + it.fileName()); + if (! trg.exists()) + doCopy = true; + else { + const QFileInfo src = it.fileInfo(); + if (trg.size() != src.size()) + doCopy = true; + else if ((diff = src.lastModified().toMSecsSinceEpoch() + - trg.lastModified().toMSecsSinceEpoch()) > 2000) + doCopy = true; + } + } + if (doCopy){ + info = prefix + it.fileName(); + } + m_mutex.lock(); + if (doCopy){ + m_files.append(info); + m_hotFiles++; + m_hotBytes += it.fileInfo().size(); + } + m_totalFiles++; + m_matchedFiles++; + m_mutex.unlock(); + } + } + if (! m_shouldStop){ + QDirIterator it2(source); + QString node; + QString subTarget; + while (it2.hasNext()){ + if (m_shouldStop){ + break; + } + it2.next(); + + if (it2.fileInfo().isDir() && (node = it2.fileName()) != "." && node != ".." + && m_dirMatcher.matches(node)){ + if (target.isEmpty()) + subTarget.clear(); + else{ + subTarget = target + it2.fileName(); + if (! ReFileUtils::isDirectory(subTarget)) + subTarget.clear(); + else + subTarget += OS_SEPARATOR_STR; + } + searchOneDirectory(it2.filePath(), subTarget, index); + } + } + } +} + +/** + Constructor. + + * @param name name of the task + * @param sourceDirs the list of source directories to inspect + * @param targetDir the base of the target directories + * @param maxAgeSec superflous file which is older will be moved to the + * shadow, the files which are younger will be deleted + * @param mainWindow the GUI module + */ + +SearchTargetTask::SearchTargetTask(const QString& name, const QStringList& sourceDirs, + const QString& targetDir, qint64 maxAgeSec, MainWindow* mainWindow) : + BackupEngine(name, sourceDirs, targetDir, mainWindow), + m_maxAge() +{ + m_maxAge.setMSecsSinceEpoch(maxAgeSec * 1000); + initializeShadowDir(); +} + +/** + * Search the files to clean. + * + * This method is recursive. + * + * @param target the directory to inspect + * @param source the source directory to compare + * @param index the index of the base directory in m_targetDirs + */ +void SearchTargetTask::searchOneDirectory(const QString& target, + const QString& source, int index){ + QDirIterator it(target); + QString info, node; + int lengthBase = m_targetDirs.at(index).length(); + assert(index < 0x7fff); + QString prefix; + m_mutex.lock(); + m_totalDirs++; + m_mutex.unlock(); + if (source.length() > lengthBase){ + prefix = QChar(1 + index) + + ReFileUtils::nativePath(source.mid(lengthBase)) + OS_SEPARATOR_STR + + m_separator; + } else { + prefix = QChar(1 + index) + m_separatorString; + } + while (it.hasNext()){ + if (m_shouldStop){ + break; + } + it.next(); + node = it.fileName(); + if (it.fileInfo().isDir()){ + // nothing to do + } else{ + Command command = CmdUndef; + QFileInfo src(source + it.fileName()); + if (! src.exists()){ + const QFileInfo trg = it.fileInfo(); + command = trg.lastModified() < m_maxAge ? CmdRemove : CmdMove; + info = prefix + QChar(command) + it.fileName(); + } + m_mutex.lock(); + if (command != CmdUndef){ + m_files.append(info); + m_hotFiles++; + m_hotBytes += it.fileInfo().size(); + } + m_totalFiles++; + m_mutex.unlock(); + } + } + if (! m_shouldStop){ + QDirIterator it2(target); + QString node; + QString source; + while (it2.hasNext()){ + if (m_shouldStop){ + break; + } + it2.next(); + + if (it2.fileInfo().isDir() && (node = it2.fileName()) != "." && node != ".."){ + QString newSource = source + it2.fileName(); + QFileInfo src(newSource); + if (src.exists() && src.isDir()) + searchOneDirectory(it2.filePath(), newSource + OS_SEPARATOR_STR, index); + else{ + moveToShadow(it2.filePath(), prefix, index); + } + } + } + } +} + +/** + * Moves a target directory to the shadow directory and delegate the removing files. + * + * @param target the target directory + * @param relPath the relative path of the parent of target + * @param index the index of the target in m_targetDirs + */ +void SearchTargetTask::moveToShadow(const QString& target, const QString& relPath, int index){ + QString shadowDir = m_shadowDirs.at(index) + relPath; + if (! ReFileUtils::makeDirWithParents(shadowDir)) + error(QObject::tr("cannot create shadow directory (%1): %2") + .arg(errno).arg(shadowDir)); + else { + QString shadow = shadowDir + ReFileUtils::nodeOf(target); + if (rename(I18N::s2b(target).constData(), I18N::s2b(shadow).constData()) != 0){ + error(QObject::tr("cannot move to shadow directory (%1): %2 -> %3") + .arg(errno).arg(target).arg(shadow)); + } else { + removeOlder(shadowDir, m_maxAge, index); + } + } +} + +/** + * Executes the task "search superflous files/dirs in the target directory". + */ +void SearchTargetTask::run() +{ + qint64 start = QDateTime::currentMSecsSinceEpoch(); + m_searchReady = false; + QString targetDir, sourceDir; + for (int ix = 0; ix < m_sourceDirs.size(); ix++){ + sourceDir = m_sourceDirs.at(ix); + targetDir = m_targetDirs.at(ix); + searchOneDirectory(targetDir, sourceDir, ix); + } + m_searchReady = true; + m_mainWindow->externalLog(tr( + "Search in target finished: to process: %1 with %2 dirs to delete: %3 total: %4 " + "subdirs: %5 runtime: %6") + .arg(m_hotFiles) + .arg(ReQStringUtils::readableSize(m_hotBytes)) + .arg(m_matchedFiles).arg(m_totalFiles) + .arg(m_totalDirs) + .arg(ReQStringUtils::readableDuration( + QDateTime::currentMSecsSinceEpoch() - start))); +} diff --git a/appl/rebackgui/BackupEngine.hpp b/appl/rebackgui/BackupEngine.hpp index 5171ee1..2b148e4 100644 --- a/appl/rebackgui/BackupEngine.hpp +++ b/appl/rebackgui/BackupEngine.hpp @@ -15,6 +15,14 @@ */ class BackupEngine : public QThread { +public: + enum Command { + CmdUndef, + CmdRemove, + CmdMove, + CmdRemoveDir + }; + public: BackupEngine(const QString& name, const QStringList& sourceDirs, const QString& targetDir, @@ -23,6 +31,8 @@ public: bool error(const QString& message); bool log(const QString& message); protected: + void initializeShadowDir(); + void removeOlder(const QString& directory, const QDateTime& time, int index); virtual void run() = 0; protected: // list of source dirs, trailing with separator @@ -31,6 +41,10 @@ protected: QString m_targetBaseDir; // list of target dirs, trailing with separator. Same node as m_sourceDirs QStringList m_targetDirs; + // Superflous files will moved from target to these dirs + QString m_shadowBaseDir; + // list of shadow dirs. + QStringList m_shadowDirs; MainWindow* m_mainWindow; QString m_name; public: @@ -121,6 +135,24 @@ public: virtual void run(); }; +/** + Handles the clean action. + + * Compares each of the files of in the target directory with the source directory: + * If it exists nothing is done. + * If not and the target file is older than a given date it is moved to a + * shadow directory beneath the target directory. + * If is younger the target file is deleted. + */ +class CleanTask : public BackupEngine +{ +public: + CleanTask(const QString& name, const QStringList& sourceDirs, const QString& targetDir, + MainWindow* mainWindow); +public: + virtual void run(); +}; + /** * Searches the files matching the file patterns and writes them into the file list. */ @@ -144,5 +176,23 @@ private: bool m_compareWithTarget; }; +/** + * Searches the files matching the file patterns and writes them into the file list. + */ +class SearchTargetTask : public BackupEngine +{ +public: + SearchTargetTask(const QString& name, + const QStringList& sourceDirs, const QString& targetDir, + qint64 maxAgeSec, MainWindow* mainWindow); +public: + virtual void run(); +private: + void moveToShadow(const QString& target, const QString& relPath, int index); + void searchOneDirectory(const QString& target, const QString& source, + int index); +protected: + QDateTime m_maxAge; +}; #endif // BACKUPPROCESSOR_HPP diff --git a/appl/rebackgui/mainwindow.cpp b/appl/rebackgui/mainwindow.cpp index 7f421f4..63970b1 100644 --- a/appl/rebackgui/mainwindow.cpp +++ b/appl/rebackgui/mainwindow.cpp @@ -30,8 +30,10 @@ MainWindow::MainWindow(const QString& homeDir, QWidget *parent) : m_backupTask(NULL), m_checksumOfSourceTask(NULL), m_checksumOfTargetTask(NULL), - m_errors(0), - m_maxListSize(1000) + m_searchTargetTask(NULL), + m_cleanTask(NULL), + m_errors(0), + m_maxListSize(1000) { ui->setupUi(this); initializeGuiElements(); @@ -67,6 +69,7 @@ MainWindow::MainWindow(const QString& homeDir, QWidget *parent) : connect(ui->pushButtonClearFileList, SIGNAL(clicked()), this, SLOT(onClearFileList())); connect(ui->pushButtonClearErrorList, SIGNAL(clicked()), this, SLOT(onClearErrorList())); connect(ui->pushButtonClear, SIGNAL(clicked()), this, SLOT(onClearLog())); + connect(ui->pushButtonClean, SIGNAL(clicked()), this, SLOT(onClean())); m_configuration.load(""); ui->tableWidgetConfiguration->selectRow(0); updateTable(); @@ -162,14 +165,14 @@ QString MainWindow::extractTarget(const QString& dir){ void MainWindow::onGuiTimerUpdate() { int count = m_guiQueue.count(); - QListWidget* list; + QListWidget* list; while(count-- > 0){ m_mutexGuiQueue.lock(); ReGuiQueueItem item = m_guiQueue.popFront(); m_mutexGuiQueue.unlock(); - if (item.m_type == ReGuiQueueItem::ListEnd - && (list = reinterpret_cast(item.m_widget))->count() >= m_maxListSize) - delete list->takeItem(0); + if (item.m_type == ReGuiQueueItem::ListEnd + && (list = reinterpret_cast(item.m_widget))->count() >= m_maxListSize) + delete list->takeItem(0); if (item.m_type == ReGuiQueueItem::Undef) break; if (! item.apply()){ @@ -206,7 +209,7 @@ void MainWindow::onGuiTimerUpdate() * otherwise: error occurred */ bool MainWindow::initializeStart(){ - bool rc = true; + bool rc = true; BackupEngine::m_searchReady = false; BackupEngine::m_hotBytes = 0; BackupEngine::m_hotFiles = 0; @@ -216,18 +219,18 @@ bool MainWindow::initializeStart(){ BackupEngine::m_processedFiles = 0; BackupEngine::m_processedBytes = 0; ChecksumTask::m_sourceProcessingReady = false; - if ( (m_maxListSize = comboInt(ui->comboBoxMaxListLength, -999)) == -999){ - rc = false; - m_maxListSize = 100; - ui->comboBoxMaxListLength->setEditText(QString::number(m_maxListSize)); - } + if ( (m_maxListSize = comboInt(ui->comboBoxMaxListLength, -999)) == -999){ + rc = false; + m_maxListSize = 100; + ui->comboBoxMaxListLength->setEditText(QString::number(m_maxListSize)); + } m_errors = 0; startStop(true); - if (ui->checkBoxAutoClean->isChecked()){ - onClearFileList(); - onClearErrorList(); - } - return rc; + if (ui->checkBoxAutoClean->isChecked()){ + onClearFileList(); + onClearErrorList(); + } + return rc; } /** @@ -360,7 +363,36 @@ QStringList dirs = ReGuiUtils::selectDirectories(tr("Select Source Directory"), } /** - * Remove the lines of hte file list. + * Remove the lines of the log list. + */ +void MainWindow::onClean() +{ + int row = ui->tableWidget->currentRow(); + if (row < 0){ + say(LOG_ERROR, tr("no backup item selected")); + } else { + BackupItem& item = m_configuration.items()[row]; + QString target = BackupUtils::findTarget(item, &m_logger); + if (target.isEmpty()){ + say(LOG_ERROR, tr("Target not available")); + } else { + ReQStringUtils::ensureLastChar(target, OS_SEPARATOR); + //writeTargetConfiguration(item, target); + initializeStart(); + delete m_searchTargetTask; + m_searchTargetTask = new SearchTargetTask(item.m_name, + item.m_sources, target, this); + m_searchTargetTask->start(); + delete m_cleanTask; + m_cleanTask = new CleanTask(item.m_name, item.m_sources, target, + this); + m_backupTask->start(); + } + } +} + +/** + * Removes the lines of the file list. */ void MainWindow::onClearErrorList() { @@ -368,7 +400,7 @@ void MainWindow::onClearErrorList() } /** - * Remove the lines of the log list. + * Removes the lines of the log list. */ void MainWindow::onClearLog() { diff --git a/appl/rebackgui/mainwindow.hpp b/appl/rebackgui/mainwindow.hpp index 2913641..664e7ae 100644 --- a/appl/rebackgui/mainwindow.hpp +++ b/appl/rebackgui/mainwindow.hpp @@ -35,7 +35,6 @@ public: void restoreState(); void saveState(); -public slots: private: QString extractTarget(const QString& dir); bool initializeStart(); @@ -46,6 +45,7 @@ private slots: void onAddItem(); void onAddSource(); void onChecksums(); + void onClean(); void onClearFileList(); void onClearErrorList(); void onClearLog(); @@ -73,8 +73,10 @@ private: BackupTask* m_backupTask; ChecksumOfSourceTask* m_checksumOfSourceTask; ChecksumOfTargetTask* m_checksumOfTargetTask; + SearchTargetTask* m_searchTargetTask; + CleanTask* m_cleanTask; int m_errors; - int m_maxListSize; + int m_maxListSize; }; #endif // MAINWINDOW_HPP diff --git a/appl/rebackgui/mainwindow.ui b/appl/rebackgui/mainwindow.ui index 627650b..b85e259 100644 --- a/appl/rebackgui/mainwindow.ui +++ b/appl/rebackgui/mainwindow.ui @@ -214,6 +214,22 @@ + + + + + 125 + 0 + + + + Detects files/dirs not found in the source directory. Old files will be remove, the other will be moved to the shadow directory + + + Clean + + + diff --git a/appl/rebackgui/osconnect.pl b/appl/rebackgui/osconnect.pl index 99c5700..04f24c7 100644 --- a/appl/rebackgui/osconnect.pl +++ b/appl/rebackgui/osconnect.pl @@ -4,48 +4,28 @@ use strict; my $mode = shift; my %devs; +my (%g_devOfPoint, %g_pointOfDev, %g_devOfLabel, %g_labelOfDev); + +&FindLabels; +&FindMountPoints; if ($mode eq "find-label"){ my $targetPath = shift; if (! -d $targetPath){ &Usage("not a directory: $targetPath"); } - %devs = &LabelOfDevs; - open($CMD, "mount|") || die "cannot execute mount: $!"; - my ($label, $relPath); - while(<$CMD>){ - # /dev/sdb1 on /media/src type vfat (rw,...) - if(/(\S+) on (\S+)/){ - my ($dev, $path) = ($1, $2); - if (index($targetPath, $path) == 0){ - if ($devs{$dev} ne ""){ - $label = $devs{$dev}; - $relPath = substr($targetPath, length($path) + 1); - } - } - } - } - close $CMD; - if ($label eq ""){ - &Usage("no mounted device found"); - } else { - print "L=\"$label\"\n$relPath\n"; - } + &PrintLabelAndRelPath($targetPath); } elsif ($mode eq "search-target"){ my $label = shift; my $relPath = shift; &Usage("missing label") unless $label; &Usage("missing relative path") unless $relPath; - - %devs = &LabelOfDevs; - if ( - # /dev/sdb1: LABEL="EFI system" UUID="80F7-47BB" TYPE="vfat" ... - open(my $CMD, "blkid|") || die "cannot execute blkid: $!"; - while(<$CMD>){ - if(/(\S+): LABEL="(.*?)"/){ - $devs{$1} = $2; + my $dev = %g_devOfLabel{$label}; + if ($dev ne ""){ + my $mountPoint = $g_pointOfDev{$dev}; + if ($mountPoint ne ""){ + print "$mountPoint/$relPath\n"; } } -close $CMD; } else { &Usage("unknown mode"); } @@ -76,31 +56,48 @@ EOS # Returns an associative array with (dev, label) entries # @return an associative array with (dev, label) entries -sub LabelOfDev{ +sub FindLabels{ - my %devs; # /dev/sdb1: LABEL="EFI system" UUID="80F7-47BB" TYPE="vfat" ... open(my $CMD, "blkid|") || die "cannot execute blkid: $!"; while(<$CMD>){ if(/(\S+): LABEL="(.*?)"/){ - $devs{$1} = $2; + $g_devOfLabel{$2} = $1; + $g_labelOfDev{$1} = $2; } } close $CMD; return %devs; } -# Search the device of a given label. -# @param $label label to search -# @return: "": not found -# otherwise: the device with the given label -sub DeviceOfLabel{ - my $label = shift; - my $dev; - for (keys %devs){ - if ($devs{$_} eq $label){ - $dev = $_; - last; +# stores the relation of devices and mount points. +sub FindMountPoints{ + my $dev = shift; + open(my $CMD, "mount|") || die "cannot execute mount: $!"; + my ($label, $relPath); + while(<$CMD>){ + # /dev/sdb1 on /media/src type vfat (rw,...) + if(/(\S+) on (\S+)/){ + $g_devOfPoint{$2} = $1; + $g_pointOfDev{$1} = $2; + } + } + close $CMD; +} + +# Find the label and relative path given by a subdirectory of the mountpoint +# @param subdirOfMountpoint +sub PrintLabelAndRelPath{ + my $subdirOfMountpoint = shift; + my ($relPath, $label); + for my $path (keys %g_pointOfDev){ + if (index($subdirOfMountpoint, $path) == 0){ + my $dev = $g_devOfPoint{$path}; + if ($g_labelOfDev{$dev} ne ""){ + $label = $g_labelOfDev{$dev}; + $relPath = substr($subdirOfMountpoint, length($path) + 1); + print "L=$label\n$relPath\n"; + } + } } - return $rc; } -- 2.39.5