Also play model name on model changes (#5416)
[opentx.git] / companion / src / process_sync.cpp
blob0cfef49dd1cfd38353ea899a7e9bd1669c96c4e3
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "process_sync.h"
23 #include <QApplication>
24 #include <QCryptographicHash>
25 #include <QDateTime>
26 #include <QMutexLocker>
27 #include <QDirIterator>
28 #include <QDebug>
30 #if (QT_VERSION < QT_VERSION_CHECK(5, 5, 0))
31 #define QtInfoMsg QtMsgType(4)
32 #endif
34 #define PRINT_INFO(str) emit progressMessage((str), QtInfoMsg)
35 #define PRINT_CREATE(str) emit progressMessage((str), QtInfoMsg)
36 #define PRINT_REPLACE(str) emit progressMessage((str), QtWarningMsg)
37 #define PRINT_DELETE(str) emit progressMessage((str), QtCriticalMsg)
38 #define PRINT_ERROR(str) emit progressMessage((str), QtFatalMsg)
39 //#define PRINT_SKIP(str) emit progressMessage((str), QtDebugMsg) // mostly useless noise (maybe make an option later)
40 #define PRINT_SKIP(str)
41 #define PRINT_SEP() PRINT_INFO(QString(80, '='))
43 #define SYNC_MAX_ERRORS 50 // give up after this many errors per destination
45 SyncProcess::SyncProcess(const QString & folderA, const QString & folderB, const int & syncDirection, const int & compareType, const qint64 & maxFileSize, const bool dryRun):
46 folder1(folderA),
47 folder2(folderB),
48 direction((SyncDirection)syncDirection),
49 ctype((SyncCompareType)compareType),
50 maxFileSize(qMax<qint64>(0, maxFileSize)),
51 dryRun(dryRun),
52 stopping(false)
54 if (direction == SYNC_B2A_A2B) {
55 folder1 = folderB;
56 folder2 = folderA;
57 direction = SYNC_A2B_B2A;
60 if (ctype == OVERWR_ALWAYS && direction == SYNC_A2B_B2A)
61 ctype = OVERWR_IF_DIFF;
63 dirFilters = QDir::Filters(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
65 reportTemplate = tr("New: <b>%1</b>; Updated: <b>%2</b>; Skipped: <b>%3</b>; Errors: <font color=%5><b>%4</b></font>;");
66 if (dryRun)
67 testRunStr = tr("[TEST RUN] ");
70 void SyncProcess::stop()
72 QMutexLocker locker(&stopReqMutex);
73 stopping = true;
76 bool SyncProcess::isStopRequsted()
78 QMutexLocker locker(&stopReqMutex);
79 return stopping;
82 void SyncProcess::run()
84 count = index = created = updated = skipped = errored = 0;
86 emit started();
87 emit progressStep(index);
88 emit statusMessage(tr("Gathering file information..."));
90 if (direction == SYNC_A2B_B2A || direction == SYNC_A2B)
91 count += getFilesCount(folder1);
93 if (isStopRequsted()) {
94 finish();
95 return;
98 if (direction == SYNC_A2B_B2A || direction == SYNC_B2A)
99 count += getFilesCount(folder2);
101 if (isStopRequsted()) {
102 finish();
103 return;
106 emit fileCountChanged(count);
108 if (!count) {
109 QString nf = tr("Synchronization failed, nothing found to copy.");
110 emit statusMessage(nf);
111 PRINT_ERROR(nf);
112 emit finished();
113 return;
116 if (direction == SYNC_A2B_B2A || direction == SYNC_A2B)
117 updateDir(folder1, folder2);
119 if (isStopRequsted()) {
120 finish();
121 return;
124 if (direction == SYNC_A2B_B2A || direction == SYNC_B2A)
125 updateDir(folder2, folder1);
127 finish();
130 void SyncProcess::finish()
132 QString endStr = testRunStr % tr("Synchronization finished. ") % reportTemplate;
133 emit statusMessage(endStr.arg(created).arg(updated).arg(skipped).arg(errored).arg(errored ? "red" : "black"));
134 emit finished();
137 int SyncProcess::getFilesCount(const QString & directory)
139 if (!QFile::exists(directory))
140 return 0;
142 int result = 0;
143 QDirIterator it(directory, dirFilters, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
144 while (it.hasNext() && !isStopRequsted()) {
145 it.next();
146 result++;
147 QApplication::processEvents();
149 return result;
152 void SyncProcess::updateDir(const QString & source, const QString & destination)
154 int counts[4] = { created, updated, skipped, errored };
155 QString statusStr = testRunStr % tr("Synchronizing %1 -&gt; %2: %3").arg(source, destination, "<b>%1</b>|<b>%2</b> (%3)");
157 PRINT_INFO(testRunStr % tr("Starting synchronization: %1 -&gt; %2<br>").arg(source, destination));
159 QDirIterator it(source, dirFilters, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
160 while (it.hasNext() && !isStopRequsted()) {
161 it.next();
162 ++index;
163 emit statusMessage(statusStr.arg(index).arg(count).arg(reportTemplate.arg(created).arg(updated).arg(skipped).arg(errored).arg(errored ? "red" : "black")));
164 emit progressStep(index);
165 if (maxFileSize && it.fileInfo().isFile() && it.fileInfo().size() > maxFileSize) {
166 PRINT_SKIP(tr("Skipping large file: %1 (%2KB)").arg(it.fileName()).arg(int(it.fileInfo().size() / 1024)));
167 ++skipped;
169 else {
170 updateEntry(it.filePath(), source, destination);
171 if (errored - counts[3] > SYNC_MAX_ERRORS) {
172 PRINT_ERROR(tr("<br><b>Too many errors, giving up.<b>"));
173 break;
176 QApplication::processEvents();
179 QString endStr = "<br>" % testRunStr % tr("Finished synchronizing %1 -&gt; %2 :<br>&nbsp;&nbsp;&nbsp;&nbsp; %3").arg(source, destination, reportTemplate);
180 PRINT_INFO(endStr.arg(created-counts[0]).arg(updated-counts[1]).arg(skipped-counts[2]).arg(errored-counts[3]).arg(errored-counts[3] ? "red" : "black"));
181 PRINT_SEP();
184 bool SyncProcess::updateEntry(const QString & entry, const QDir & source, const QDir & destination)
186 QString srcPath = source.toNativeSeparators(source.absoluteFilePath(entry));
187 QString relPath = source.relativeFilePath(entry);
188 QString destPath = destination.toNativeSeparators(destination.absoluteFilePath(relPath));
189 QFileInfo sourceInfo(srcPath);
190 QFileInfo destInfo(destPath);
192 if (sourceInfo.isDir()) {
193 if (!destInfo.exists()) {
194 PRINT_CREATE(tr("Creating directory: %1").arg(destPath));
195 if (!dryRun && !destination.mkpath(destPath)) {
196 PRINT_ERROR(tr("Could not create directory: %1").arg(destPath));
197 ++errored;
198 return false;
200 ++created;
202 else {
203 PRINT_SKIP(tr("Destination directory exists: %1").arg(destPath));
204 ++skipped;
206 return true;
209 QFile sourceFile(srcPath);
210 QFile destinationFile(destPath);
211 bool destExists = destInfo.exists();
212 bool checkDate = (ctype == OVERWR_NEWER_IF_DIFF || ctype == OVERWR_NEWER_ALWAYS);
213 bool checkContent = (ctype == OVERWR_NEWER_IF_DIFF || ctype == OVERWR_IF_DIFF);
214 bool existed = false;
216 if (destExists && checkDate) {
217 if (sourceInfo.lastModified() <= destInfo.lastModified()) {
218 PRINT_SKIP(tr("Skipping older file: %1").arg(srcPath));
219 ++skipped;
220 return true;
222 checkDate = false;
225 if (destExists && checkContent) {
226 if (!sourceFile.open(QFile::ReadOnly)) {
227 PRINT_ERROR(tr("Could not open source file '%1': %2").arg(srcPath, sourceFile.errorString()));
228 ++errored;
229 return false;
231 if (!destinationFile.open(QFile::ReadOnly)) {
232 PRINT_ERROR(tr("Could not open destination file '%1': %2").arg(destPath, destinationFile.errorString()));
233 ++errored;
234 return false;
237 bool skip = QCryptographicHash::hash(sourceFile.readAll(), QCryptographicHash::Md5) == QCryptographicHash::hash(destinationFile.readAll(), QCryptographicHash::Md5);
238 sourceFile.close();
239 destinationFile.close();
240 if (skip) {
241 PRINT_SKIP(tr("Skipping identical file: %1").arg(srcPath));
242 ++skipped;
243 return true;
245 checkContent = false;
248 if (!destExists || (!checkDate && !checkContent)) {
249 if (destInfo.exists()) {
250 existed = true;
251 PRINT_REPLACE(tr("Replacing destination file: %1").arg(destPath));
252 if (!dryRun && !destinationFile.remove()) {
253 PRINT_ERROR(tr("Could not delete destination file '%1': %2").arg(destPath, destinationFile.errorString()));
254 ++errored;
255 return false;
258 else {
259 PRINT_CREATE(tr("Creating destination file: %1").arg(destPath));
261 if (!dryRun && !sourceFile.copy(destPath)) {
262 PRINT_ERROR(tr("Copy failed: '%1' to '%2': %3").arg(srcPath, destPath, sourceFile.errorString()));
263 ++errored;
264 return false;
267 if (existed)
268 ++updated;
269 else
270 ++created;
273 return true;