--- /dev/null
+/*
+ * Licence:
+ * You can use and modify this file without any restriction.
+ * There is no warranty.
+ * You also can use the licence from http://www.wtfpl.net/.
+ * The original sources can be found on https://github.com/republib.
+*/
+
+#include "backupgui.hpp"
+
+bool BackupEngine::m_shouldStop = false;
+QStringList BackupEngine::m_files;
+qint64 BackupEngine::m_matchedBytes = 0;
+int BackupEngine::m_matchedFiles = 0;
+int BackupEngine::m_totalFiles = 0;
+int BackupEngine::m_totalDirs = 0;
+bool BackupEngine::m_searchReady = false;
+QMutex BackupEngine::m_mutex;
+
+/**
+ * Constructor.
+ *
+ * @param sourceDirs the list of source directories to inspect
+ * @param targetDir the base of the target directories
+ * @param mainWindow the GUI module
+ */
+BackupEngine::BackupEngine(const QStringList& sourceDirs, const QString& targetDir,
+ MainWindow* mainWindow) :
+ QThread(),
+ m_sourceDirs(sourceDirs),
+ m_targetDir(targetDir),
+ m_mainWindow(mainWindow)
+{
+
+}
+
+/**
+ * @brief Logs an error message.
+ *
+ * @param message the message to log
+ * @return <code>false</code>
+ * @throws ConverterException
+ */
+bool BackupEngine::error(const QString& message){
+ m_mainWindow->externalError(message);
+ return false;
+}
+
+/**
+ * @brief Logs a message.
+ *
+ * @param message the message to log
+ * @return <code>true</code>
+ */
+bool BackupEngine::log(const QString& message){
+ //printf("%s\n", I18N::s2b(message).constData());
+ m_mainWindow->externalLog(message);
+ return true;
+}
+
+/**
+ * Constructor.
+ *
+ * @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(const QString& filePatterns, const QString& dirPatterns,
+ const QStringList& sourceDirs, const QString& targetDir,
+ MainWindow* mainWindow) :
+ BackupEngine(sourceDirs, targetDir, mainWindow),
+ m_fileMatcher(filePatterns),
+ m_dirMatcher(dirPatterns)
+{
+
+}
+
+/**
+ * Runs the task.
+ */
+void SearchTask::run()
+{
+ qint64 start = QDateTime::currentMSecsSinceEpoch();
+ m_searchReady = false;
+ for (int ix = 0; ix < m_sourceDirs.size(); ix++){
+ processOneDirectory(m_sourceDirs.at(ix), m_targetDir, ix);
+ }
+ m_searchReady = true;
+ m_mainWindow->externalLog(tr("%1 matching file(s) under %2 with %3 in %4 subdirs %5")
+ .arg(m_matchedFiles).arg(m_totalFiles)
+ .arg(ReQStringUtils::readableSize(m_matchedBytes))
+ .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<br>
+ * 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::processOneDirectory(const QString& source,
+ const QString& target, int index){
+ QDirIterator it(source);
+ QString relPath;
+ QString info;
+ int lengthBase = m_sourceDirs.at(index).length();
+ if (source.length() > lengthBase){
+ relPath = source.mid(lengthBase + 1) + OS_SEPARATOR_STR;
+ }
+ while (it.hasNext()){
+ if (m_shouldStop){
+ break;
+ }
+ it.next();
+ if (it.fileInfo().isDir() && m_fileMatcher.matches(it.fileName()) >= 0){
+ m_mutex.lock();
+ m_totalFiles++;
+ m_mutex.unlock();
+ } else {
+ bool doCopy = false;
+ if (! target.isEmpty()){
+ QFileInfo trg(target + it.fileName());
+ doCopy = trg.exists() && (trg.size() != it.fileInfo().size()
+ || trg.lastModified() > it.fileInfo().lastModified());
+ }
+ if (doCopy)
+ info = relPath + "\t" + it.fileName();
+ m_mutex.lock();
+ if (doCopy){
+ m_files.append(info);
+ }
+ m_totalFiles++;
+ m_matchedFiles++;
+ m_matchedBytes += it.fileInfo().size();
+ 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() + OS_2nd_SEPARATOR_STR;
+ if (! ReFileUtils::isDirectory(subTarget))
+ subTarget.clear();
+ processOneDirectory(it2.filePath(), subTarget, index);
+ m_mutex.lock();
+ m_totalDirs++;
+ m_mutex.unlock();
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Constructor.
+ *
+ * @param sourceDirs the list of source directories to inspect
+ * @param targetDir the base of the target directories
+ * @param mainWindow the GUI module
+ */
+BackupTask::BackupTask(const QStringList& sourceDirs, const QString& targetDir,
+ MainWindow* mainWindow) :
+ BackupEngine(sourceDirs, targetDir, mainWindow)
+{
+
+}
+
+/**
+ * Do the backup task.
+ */
+void BackupTask::run()
+{
+ while(! m_searchReady)
+ QThread::sleep(50);
+ m_mainWindow->externalTaskFinished("ready");
+}
--- /dev/null
+/*
+ * Licence:
+ * You can use and modify this file without any restriction.
+ * There is no warranty.
+ * You also can use the licence from http://www.wtfpl.net/.
+ * The original sources can be found on https://github.com/republib.
+*/
+
+#ifndef BACKUPPROCESSOR_HPP
+#define BACKUPPROCESSOR_HPP
+
+
+class BackupEngine : public QThread
+{
+public:
+ BackupEngine(const QStringList& sourceDirs, const QString& targetDir,
+ MainWindow* mainWindow);
+public:
+ bool error(const QString& message);
+ bool log(const QString& message);
+protected:
+ virtual void run() = 0;
+protected:
+ QStringList m_sourceDirs;
+ QString m_targetDir;
+ MainWindow* m_mainWindow;
+public:
+ static bool m_shouldStop;
+ static QStringList m_files;
+ static qint64 m_matchedBytes;
+ static int m_matchedFiles;
+ static int m_totalFiles;
+ static int m_totalDirs;
+ static bool m_searchReady;
+ static QMutex m_mutex;
+};
+
+class SearchTask : public BackupEngine
+{
+public:
+ SearchTask(const QString& filePatterns, const QString& dirPatterns,
+ const QStringList& sourceDirs, const QString& targetDir,
+ MainWindow* mainWindow);
+public:
+ virtual void run();
+private:
+ QString m_filePatterns;
+ QString m_dirPatterns;
+ ReIncludeExcludeMatcher m_fileMatcher;
+ ReIncludeExcludeMatcher m_dirMatcher;
+ void processOneDirectory(const QString& source, const QString& target,
+ int index);
+};
+
+class BackupTask : public BackupEngine
+{
+public:
+ BackupTask(const QStringList& sourceDirs, const QString& targetDir,
+ MainWindow* mainWindow);
+public:
+ virtual void run();
+protected:
+};
+
+#endif // BACKUPPROCESSOR_HPP
// ignore comments and empty lines:
if (line[0] == '#' || line[strspn(line, " \t\r\n")] == '\0')
continue;
- line2.fromUtf8(line);
+ line2 = line2.fromUtf8(line);
ReQStringUtils::chomp(line2);
if (ReQStringUtils::hasPrefixAndNumber("name", line2, no, value)){
getItem(no, map).m_name = value;
} else if (ReQStringUtils::hasPrefixAndNumber("sources", line2, no, value)){
getItem(no, map).m_sources = value.split(';');
} else if (ReQStringUtils::hasPrefixAndNumber("target", line2, no, value)){
- getItem(no, map).m_name = value;
+ getItem(no, map).m_target = value;
+ } else if (ReQStringUtils::hasPrefixAndNumber("filepatterns", line2, no, value)){
+ getItem(no, map).m_filePatterns = value;
+ } else if (ReQStringUtils::hasPrefixAndNumber("dirpatterns", line2, no, value)){
+ getItem(no, map).m_dirPatterns = value;
} else if (ReQStringUtils::hasPrefixAndNumber("lastbackup", line2, no, value)){
- getItem(no, map).m_lastBackup.setMSecsSinceEpoch(value.toLongLong());
+ qint64 time = value.toLongLong();
+ if (time > 24*3600)
+ getItem(no, map).m_lastBackup.setMSecsSinceEpoch(time);
} else {
m_mainWindow->error(QObject::tr("unknown format in %1-%2: %3")
.arg(filename).arg(lineNo)
QByteArray buffer;
for (int ix = 0; ix < m_items.size(); ix++){
BackupItem& item = m_items[ix];
- buffer = "name." + QByteArray::number(ix + 1) + "="
+ buffer = "name." + QByteArray::number(ix) + "="
+ item.m_name.toUtf8() + "\n";
- buffer += "sources." + QByteArray::number(ix + 1) + "="
+ buffer += "sources." + QByteArray::number(ix) + "="
+ item.m_sources.join(';').toUtf8() + "\n";
- buffer += "target." + QByteArray::number(ix + 1) + "="
+ buffer += "target." + QByteArray::number(ix) + "="
+ + item.m_target.toUtf8() + "\n";
+ buffer += "target." + QByteArray::number(ix) + "="
+ item.m_target.toUtf8() + "\n";
- buffer += "lastbackup." + QByteArray::number(ix + 1) + "="
+ buffer += "lastbackup." + QByteArray::number(ix) + "="
+ item.m_lastBackup.toString("yyyy.MM.dd/hh:mm") + "\n";
if (fputs(buffer.constData(), fp) != buffer.length())
m_mainWindow->error(QObject::tr("cannot write (%1): %2").arg(errno)
QStringList m_sources;
QString m_target;
QDateTime m_lastBackup;
+ QString m_filePatterns;
+ QString m_dirPatterns;
};
typedef QMap<int, BackupItem> BackupItemMap;
typedef QList<BackupItem> BackupItemList;
#include "base/rebase.hpp"
#include "gui/regui.hpp"
#include "Configuration.hpp"
+#include "BackupEngine.hpp"
#include "mainwindow.hpp"
#include "ui_mainwindow.h"
MainWindow::MainWindow(const QString& homeDir, QWidget *parent) :
ReGuiApplication("rebackupgui", homeDir, 2, 100100100, parent),
+ ReGuiValidator(),
ui(new Ui::MainWindow),
m_configuration(this),
m_currentRowConfiguration(0),
m_currentRowBackup(0),
- m_lastSource()
+ m_lastSource(),
+ m_searchTask(NULL),
+ m_backupTask(NULL)
{
ui->setupUi(this);
initializeGuiElements();
connect(ui->pushButtonAddItem, SIGNAL(clicked()), this, SLOT(onAddItem()));
connect(ui->pushButtonDeleteItem, SIGNAL(clicked()), this, SLOT(onDeleteItem()));
connect(ui->pushButtonAddSource, SIGNAL(clicked()), this, SLOT(onAddSource()));
+ connect(ui->pushButtonDeleteSource, SIGNAL(clicked()), this, SLOT(onDeleteSource()));
connect(ui->pushButtonSelectTarget, SIGNAL(clicked()), this, SLOT(onSelectTarget()));
connect(ui->pushButtonSaveConfig, SIGNAL(clicked()), this, SLOT(onSaveConfig()));
connect(ui->pushButtonUpdate, SIGNAL(clicked()), this, SLOT(onUpdate()));
*/
void MainWindow::onDeleteSource()
{
-
+ int row = ui->listWidgetSource->selectionModel()->currentIndex().row();
+ if (row >= 0){
+ ui->listWidgetSource->takeItem(row);
+ }
}
void MainWindow::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected){
* @param dir the directory on the external medium (already mounted)
*/
void MainWindow::findTarget(const QString& dir){
+ QString target = ";" + dir;
#if defined __linux__
-
#else
#endif
+ ui->lineEditTarget->setText(target);
}
/**
m_configuration.save("");
}
+/**
+ * Builds the target directory.
+ *
+ * @return the target directory
+ */
+QString MainWindow::buildTargetFile(){
+ QString rc = ui->lineEditTarget->text();
+ int ix = rc.indexOf(rc, ';');
+ if (ix == 0)
+ rc.remove(0, 1);
+ return rc;
+}
+
/**
* Starts the backup.
*/
void MainWindow::onStart(){
startStop(true);
+ int row = ui->tableWidget->currentRow();
+ const BackupItem& item = m_configuration.items().at(row);
+ delete m_searchTask;
+ m_searchTask = new SearchTask(item.m_filePatterns, item.m_dirPatterns,
+ item.m_sources, buildTargetFile(),
+ this);
+ m_searchTask->start();
+ delete m_backupTask;
+ m_backupTask = new BackupTask(item.m_sources, buildTargetFile(),
+ this);
+
}
/**
* Stops the backup.
*/
void MainWindow::onStop(){
- startStop(false);
+ BackupEngine::m_shouldStop = true;
}
/**
BackupItem& item = m_configuration.items()[m_currentRowConfiguration];
item.m_name = ui->lineEditName->text();
item.m_target = ui->lineEditTarget->text();
+ item.m_filePatterns = comboText(ui->comboBoxFilePatterns);
+ item.m_dirPatterns = comboText(ui->comboBoxDirPatterns);
item.m_sources.clear();
for (int ix = 0; ix < ui->listWidgetSource->count(); ix++){
item.m_sources.append(ui->listWidgetSource->item(ix)->text());
}
updateTable();
+ saveState();
+}
+
+
+/**
+ * Show a message.
+ *
+ * @param level LOG_ERROR or LOG_INFO
+ * @param message the message to show
+ * @return <code>false</code>: the message is an error message
+ */
+bool MainWindow::say(ReLoggerLevel level, const QString& message)
+{
+ if (level == LOG_ERROR)
+ error(message);
+ else
+ log(message);
+ setStatusMessage(level, message);
+ return level >= LOG_INFO;
}
/**
m_statusMessage->setText(message);
}
+/**
+ * Reads the history of the widget values and other parameters and set it.
+ */
+void MainWindow::restoreState(){
+ ReStateStorage storage(m_storageFile, &m_logger);
+ storage.setForm("main");
+ storage.restore(ui->comboBoxFilePatterns, "comboBoxFilePatterns", false);
+ storage.restore(ui->comboBoxDirPatterns, "comboBoxDirPatterns", false);
+ storage.close();
+}
+
+/**
+ * Stores the history of the widget values and other parameters.
+ */
+void MainWindow::saveState(){
+ ReStateStorage storage(m_storageFile, &m_logger);
+ storage.setForm("main");
+ storage.store(ui->comboBoxFilePatterns, "comboBoxFilePatterns");
+ storage.store(ui->comboBoxDirPatterns, "comboBoxDirPatterns");
+ storage.close();
+}
/**
* Starts or stops the backup.
*
ui->lineEditName->setText(item.m_name);
ui->lineEditTarget->setText(item.m_target);
ui->listWidgetSource->clear();
+ ui->comboBoxFilePatterns->setEditText(item.m_filePatterns);
+ ui->comboBoxDirPatterns->setEditText(item.m_dirPatterns);
for (int ix = 0; ix < item.m_sources.size(); ix++){
ui->listWidgetSource->insertItem(ix, m_lastSource = item.m_sources.at(ix));
}
}
+
class MainWindow;
}
-class MainWindow : public ReGuiApplication
+class MainWindow : public ReGuiApplication, public ReGuiValidator
{
Q_OBJECT
explicit MainWindow(const QString& homeDir, QWidget *parent = 0);
~MainWindow();
public:
+ QString buildTargetFile();
bool error(const QString& message);
bool log(const QString& message);
+ virtual bool say(ReLoggerLevel level, const QString& message);
void setStatusMessage(bool error, const QString& message);
void startStop(bool isStart);
+ void restoreState();
+ void saveState();
private slots:
virtual void aboutToQuit();
void findTarget(const QString& dir);
int m_currentRowConfiguration;
int m_currentRowBackup;
QString m_lastSource;
+ SearchTask* m_searchTask;
+ BackupTask* m_backupTask;
};
#endif // MAINWINDOW_HPP
<x>0</x>
<y>0</y>
<width>820</width>
- <height>654</height>
+ <height>679</height>
</rect>
</property>
<property name="windowTitle">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
- <number>0</number>
+ <number>1</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
</size>
</property>
<property name="text">
- <string>...</string>
+ <string>Select target</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_10" stretch="0,1,0,1">
+ <item>
+ <widget class="QLabel" name="label_8">
+ <property name="minimumSize">
+ <size>
+ <width>125</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>File patterns:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBoxFilePatterns">
+ <property name="toolTip">
+ <string><html><head/><body><p>A comma (',') separated list of filename patterns. A prefix of '-' means inversion: if a filename matches it will not be found.</p><p>Example: *.txt,*.odt</p></body></html></string>
+ </property>
+ <property name="editable">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_9">
+ <property name="minimumSize">
+ <size>
+ <width>125</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Dir patterns:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="comboBoxDirPatterns">
+ <property name="toolTip">
+ <string><html><head/><body><p>A comma (',') separated list of directory name patterns. A prefix of '-' means inversion: if a directory name matches it will not be found.</p><p>Example: *,-.git,-*cache*</p></body></html></string>
+ </property>
+ <property name="editable">
+ <bool>true</bool>
</property>
</widget>
</item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<item>
<widget class="QLabel" name="label_4">
+ <property name="minimumSize">
+ <size>
+ <width>125</width>
+ <height>0</height>
+ </size>
+ </property>
<property name="text">
- <string>Source directories:</string>
+ <string>Source dirs:</string>
</property>
</widget>
</item>
../../base/ReException.cpp \
../../base/ReQStringUtils.cpp \
../../base/ReFileUtils.cpp \
+ ../../base/ReMatcher.cpp \
../../base/ReLogger.cpp \
../../gui/ReStateStorage.cpp \
../../gui/ReGuiApplication.cpp \
+ ../../gui/ReGuiValidator.cpp \
../../gui/ReGuiQueue.cpp \
mainwindow.cpp \
aboutdialog.cpp \
- Configuration.cpp
+ Configuration.cpp \
+ BackupEngine.cpp
HEADERS += mainwindow.hpp \
../../base/rebase.hpp \
../../gui/regui.hpp \
aboutdialog.hpp \
Configuration.hpp \
- backupgui.hpp
+ backupgui.hpp \
+ BackupEngine.hpp
FORMS += mainwindow.ui \
aboutdialog.ui
int width = 0;
int height = 0;
QString info;
- clock_t start = clock();
+ qint64 start = QDateTime::currentMSecsSinceEpoch();
if (readProperties(source, width, height, info)){
int widthNew = width;
int heightNew = height;
preSize += size;
postSize += fileInfo.st_size;
message += "-> " + info + " " + ReQStringUtils::readableSize(fileInfo.st_size)
- + " " + ReQStringUtils::readableDuration(clock() - start);
+ + " " + ReQStringUtils::readableDuration(
+ QDateTime::currentMSecsSinceEpoch() - start);
log(message);
int percentFiles = int(no * 100 / max(1, m_totalFiles));
int percentSize = int(preSize * 100 / max(1, min(preSize, m_totalBytes)));
return rc;
}
+/**
+ * Tests whether a path is a directory.
+ *
+ * @param path full name of the directory to inspect
+ * @param isFile OUT: <code>true</code>: this is a file (and not a directory)
+ * @return <code>true</code>: path is a directory
+ */
+bool ReFileUtils::isDirectory(const QString& path, bool* isFile)
+{
+ QFileInfo info(path);
+ bool rc = info.exists();
+ if (rc){
+ if (isFile != NULL)
+ *isFile = true;
+ if (! info.isDir())
+ rc = false;
+ }
+ return rc;
+}
+
/**
* Checks whether path is a root directory.
*
static QByteArray extensionOf(const char* filename);
static bool isAbsolutPath(const QString& path);
static bool isAbsolutPath(const char* path);
+ static bool isDirectory(const QString& path, bool* isFile = NULL);
static bool isRootDir(const char* path);
/** Returns a path with native separators.
* QT under windows can operator with 2 separators: '\\' and '/'.
if (line.at(lengthPrefix + 1 + lengthNumber) == '='){
no = number;
rc = true;
- value = line.mid(lengthPrefix + 1 + lengthNumber);
+ value = line.mid(lengthPrefix + 2 + lengthNumber);
}
}
}
/**
* Returns a readable string for a duration given by <code>clock_t</code>.
*
- * @param duration a duration measured by <code>clock()</code>
+ * @param duration a duration in msec
*
* @return a string describing the duration.
*/
-QString ReQStringUtils::readableDuration(clock_t duration){
+QString ReQStringUtils::readableDuration(qint64 duration){
QString rc;
char buffer[128];
- double duration2 = (double) duration / CLOCKS_PER_SEC;
+ double duration2 = (double) duration / 1000;
if (duration2 < 60.0){
rc = QString::number(duration2, 'f', 3) + " sec";
} else if (duration2 < 3600.0){
return path;
#endif
}
- static QString readableDuration(clock_t duration);
+ static QString readableDuration(qint64 duration);
static QString readableSize(int64_t filesize);
static bool replacePlaceholders(QString& text,
const QMap<QString, QString>& placeholders, QString* error);