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>
26 #include <QMutexLocker>
27 #include <QDirIterator>
30 #if (QT_VERSION < QT_VERSION_CHECK(5, 5, 0))
31 #define QtInfoMsg QtMsgType(4)
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
):
48 direction((SyncDirection
)syncDirection
),
49 ctype((SyncCompareType
)compareType
),
50 maxFileSize(qMax
<qint64
>(0, maxFileSize
)),
54 if (direction
== SYNC_B2A_A2B
) {
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>;");
67 testRunStr
= tr("[TEST RUN] ");
70 void SyncProcess::stop()
72 QMutexLocker
locker(&stopReqMutex
);
76 bool SyncProcess::isStopRequsted()
78 QMutexLocker
locker(&stopReqMutex
);
82 void SyncProcess::run()
84 count
= index
= created
= updated
= skipped
= errored
= 0;
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()) {
98 if (direction
== SYNC_A2B_B2A
|| direction
== SYNC_B2A
)
99 count
+= getFilesCount(folder2
);
101 if (isStopRequsted()) {
106 emit
fileCountChanged(count
);
109 QString nf
= tr("Synchronization failed, nothing found to copy.");
110 emit
statusMessage(nf
);
116 if (direction
== SYNC_A2B_B2A
|| direction
== SYNC_A2B
)
117 updateDir(folder1
, folder2
);
119 if (isStopRequsted()) {
124 if (direction
== SYNC_A2B_B2A
|| direction
== SYNC_B2A
)
125 updateDir(folder2
, folder1
);
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"));
137 int SyncProcess::getFilesCount(const QString
& directory
)
139 if (!QFile::exists(directory
))
143 QDirIterator
it(directory
, dirFilters
, QDirIterator::Subdirectories
| QDirIterator::FollowSymlinks
);
144 while (it
.hasNext() && !isStopRequsted()) {
147 QApplication::processEvents();
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 -> %2: %3").arg(source
, destination
, "<b>%1</b>|<b>%2</b> (%3)");
157 PRINT_INFO(testRunStr
% tr("Starting synchronization: %1 -> %2<br>").arg(source
, destination
));
159 QDirIterator
it(source
, dirFilters
, QDirIterator::Subdirectories
| QDirIterator::FollowSymlinks
);
160 while (it
.hasNext() && !isStopRequsted()) {
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)));
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>"));
176 QApplication::processEvents();
179 QString endStr
= "<br>" % testRunStr
% tr("Finished synchronizing %1 -> %2 :<br> %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"));
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
));
203 PRINT_SKIP(tr("Destination directory exists: %1").arg(destPath
));
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
));
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()));
231 if (!destinationFile
.open(QFile::ReadOnly
)) {
232 PRINT_ERROR(tr("Could not open destination file '%1': %2").arg(destPath
, destinationFile
.errorString()));
237 bool skip
= QCryptographicHash::hash(sourceFile
.readAll(), QCryptographicHash::Md5
) == QCryptographicHash::hash(destinationFile
.readAll(), QCryptographicHash::Md5
);
239 destinationFile
.close();
241 PRINT_SKIP(tr("Skipping identical file: %1").arg(srcPath
));
245 checkContent
= false;
248 if (!destExists
|| (!checkDate
&& !checkContent
)) {
249 if (destInfo
.exists()) {
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()));
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()));