1 /*****************************************************************************
2 * Copyright (C) 2006 Csaba Karai <krusader@users.sourceforge.net> *
3 * Copyright (C) 2006-2019 Krusader Krew [https://krusader.org] *
4 * *
5 * This file is part of Krusader [https://krusader.org]. *
6 * *
7 * Krusader is free software: you can redistribute it and/or modify *
8 * it under the terms of the GNU General Public License as published by *
9 * the Free Software Foundation, either version 2 of the License, or *
10 * (at your option) any later version. *
11 * *
12 * Krusader is distributed in the hope that it will be useful, *
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 * GNU General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with Krusader. If not, see [http://www.gnu.org/licenses/]. *
19 *****************************************************************************/
20
21 #include "synchronizertask.h"
22
23 // QtCore
24 #include <QTimer>
25 #include <QFile>
26
27 #include <KI18n/KLocalizedString>
28 #include <KWidgetsAddons/KMessageBox>
29
30 #include "synchronizer.h"
31 #include "synchronizerfileitem.h"
32 #include "synchronizerdirlist.h"
33 #include "../FileSystem/filesystem.h"
34
CompareTask(SynchronizerFileItem * parentIn,const QString & leftURL,const QString & rightURL,const QString & leftDir,const QString & rightDir,bool hidden)35 CompareTask::CompareTask(SynchronizerFileItem *parentIn, const QString &leftURL,
36 const QString &rightURL, const QString &leftDir,
37 const QString &rightDir, bool hidden) : SynchronizerTask(), m_parent(parentIn),
38 m_url(leftURL), m_dir(leftDir), m_otherUrl(rightURL),
39 m_otherDir(rightDir), m_duplicate(true),
40 m_dirList(0), m_otherDirList(0)
41 {
42 ignoreHidden = hidden;
43 }
44
CompareTask(SynchronizerFileItem * parentIn,const QString & urlIn,const QString & dirIn,bool isLeftIn,bool hidden)45 CompareTask::CompareTask(SynchronizerFileItem *parentIn, const QString &urlIn,
46 const QString &dirIn, bool isLeftIn, bool hidden) : SynchronizerTask(),
47 m_parent(parentIn), m_url(urlIn), m_dir(dirIn),
48 m_isLeft(isLeftIn), m_duplicate(false),
49 m_dirList(0), m_otherDirList(0)
50 {
51 ignoreHidden = hidden;
52 }
53
~CompareTask()54 CompareTask::~CompareTask()
55 {
56 if (m_dirList) {
57 delete m_dirList;
58 m_dirList = 0;
59 }
60 if (m_otherDirList) {
61 delete m_otherDirList;
62 m_otherDirList = 0;
63 }
64 }
65
start()66 void CompareTask::start()
67 {
68 if (m_state == ST_STATE_NEW) {
69 m_state = ST_STATE_PENDING;
70 m_loadFinished = m_otherLoadFinished = false;
71
72 m_dirList = new SynchronizerDirList(parentWidget, ignoreHidden);
73 connect(m_dirList, SIGNAL(finished(bool)), this, SLOT(slotFinished(bool)));
74 m_dirList->load(m_url, false);
75
76 if (m_duplicate) {
77 m_otherDirList = new SynchronizerDirList(parentWidget, ignoreHidden);
78 connect(m_otherDirList, SIGNAL(finished(bool)), this, SLOT(slotOtherFinished(bool)));
79 m_otherDirList->load(m_otherUrl, false);
80 }
81 }
82 }
83
slotFinished(bool result)84 void CompareTask::slotFinished(bool result)
85 {
86 if (!result) {
87 m_state = ST_STATE_ERROR;
88 return;
89 }
90 m_loadFinished = true;
91
92 if (m_otherLoadFinished || !m_duplicate)
93 m_state = ST_STATE_READY;
94 }
95
96
slotOtherFinished(bool result)97 void CompareTask::slotOtherFinished(bool result)
98 {
99 if (!result) {
100 m_state = ST_STATE_ERROR;
101 return;
102 }
103 m_otherLoadFinished = true;
104
105 if (m_loadFinished)
106 m_state = ST_STATE_READY;
107 }
108
CompareContentTask(Synchronizer * syn,SynchronizerFileItem * itemIn,const QUrl & leftURLIn,const QUrl & rightURLIn,KIO::filesize_t sizeIn)109 CompareContentTask::CompareContentTask(Synchronizer *syn, SynchronizerFileItem *itemIn, const QUrl &leftURLIn,
110 const QUrl &rightURLIn, KIO::filesize_t sizeIn) : SynchronizerTask(),
111 leftURL(leftURLIn), rightURL(rightURLIn),
112 size(sizeIn), errorPrinted(false), leftReadJob(0),
113 rightReadJob(0), compareArray(), owner(-1), item(itemIn), timer(0),
114 leftFile(0), rightFile(0), received(0), sync(syn)
115 {
116 }
117
~CompareContentTask()118 CompareContentTask::~CompareContentTask()
119 {
120 abortContentComparing();
121
122 if (timer)
123 delete timer;
124 if (leftFile)
125 delete leftFile;
126 if (rightFile)
127 delete rightFile;
128 }
129
start()130 void CompareContentTask::start()
131 {
132 m_state = ST_STATE_PENDING;
133
134 if (leftURL.isLocalFile() && rightURL.isLocalFile()) {
135 leftFile = new QFile(leftURL.path());
136 if (!leftFile->open(QIODevice::ReadOnly)) {
137 KMessageBox::error(parentWidget, i18n("Error at opening %1.", leftURL.path()));
138 m_state = ST_STATE_ERROR;
139 return;
140 }
141
142 rightFile = new QFile(rightURL.path());
143 if (!rightFile->open(QIODevice::ReadOnly)) {
144 KMessageBox::error(parentWidget, i18n("Error at opening %1.", rightURL.path()));
145 m_state = ST_STATE_ERROR;
146 return;
147 }
148
149 timer = new QTimer(this);
150 connect(timer, SIGNAL(timeout()), this, SLOT(sendStatusMessage()));
151 timer->setSingleShot(true);
152 timer->start(1000);
153
154 localFileCompareCycle();
155 } else {
156 leftReadJob = KIO::get(leftURL, KIO::NoReload, KIO::HideProgressInfo);
157 rightReadJob = KIO::get(rightURL, KIO::NoReload, KIO::HideProgressInfo);
158
159 connect(leftReadJob, SIGNAL(data(KIO::Job*,QByteArray)),
160 this, SLOT(slotDataReceived(KIO::Job*,QByteArray)));
161 connect(rightReadJob, SIGNAL(data(KIO::Job*,QByteArray)),
162 this, SLOT(slotDataReceived(KIO::Job*,QByteArray)));
163 connect(leftReadJob, SIGNAL(result(KJob*)),
164 this, SLOT(slotFinished(KJob*)));
165 connect(rightReadJob, SIGNAL(result(KJob*)),
166 this, SLOT(slotFinished(KJob*)));
167
168 rightReadJob->suspend();
169
170 timer = new QTimer(this);
171 connect(timer, SIGNAL(timeout()), this, SLOT(sendStatusMessage()));
172 timer->setSingleShot(true);
173 timer->start(1000);
174 }
175 }
176
localFileCompareCycle()177 void CompareContentTask::localFileCompareCycle()
178 {
179
180 bool different = false;
181
182 char leftBuffer[ 1440 ];
183 char rightBuffer[ 1440 ];
184
185 QTime timer;
186 timer.start();
187
188 int cnt = 0;
189
190 while (!leftFile->atEnd() && !rightFile->atEnd()) {
191 int leftBytes = leftFile->read(leftBuffer, sizeof(leftBuffer));
192 int rightBytes = rightFile->read(rightBuffer, sizeof(rightBuffer));
193
194 if (leftBytes != rightBytes) {
195 different = true;
196 break;
197 }
198
199 if (leftBytes <= 0)
200 break;
201
202 received += leftBytes;
203
204 if (memcmp(leftBuffer, rightBuffer, leftBytes)) {
205 different = true;
206 break;
207 }
208
209 if ((++cnt % 16) == 0 && timer.elapsed() >= 250)
210 break;
211 }
212
213 if (different) {
214 sync->compareContentResult(item, false);
215 m_state = ST_STATE_READY;
216 return;
217 }
218
219 if (leftFile->atEnd() && rightFile->atEnd()) {
220 sync->compareContentResult(item, true);
221 m_state = ST_STATE_READY;
222 return;
223 }
224
225 QTimer::singleShot(0, this, SLOT(localFileCompareCycle()));
226 }
227
228
slotDataReceived(KIO::Job * job,const QByteArray & data)229 void CompareContentTask::slotDataReceived(KIO::Job *job, const QByteArray &data)
230 {
231 int jobowner = (job == leftReadJob) ? 1 : 0;
232 int bufferLen = compareArray.size();
233 int dataLen = data.size();
234
235 if (job == leftReadJob)
236 received += dataLen;
237
238 if (jobowner == owner) {
239 compareArray.append(data);
240 return;
241 }
242
243 do {
244 if (bufferLen == 0) {
245 compareArray = QByteArray(data.data(), dataLen);
246 owner = jobowner;
247 break;
248 }
249
250 int minSize = (dataLen < bufferLen) ? dataLen : bufferLen;
251
252 for (int i = 0; i != minSize; i++)
253 if (data[i] != compareArray[i]) {
254 abortContentComparing();
255 return;
256 }
257
258 if (minSize == bufferLen) {
259 compareArray = QByteArray(data.data() + bufferLen, dataLen - bufferLen);
260 if (dataLen == bufferLen) {
261 owner = -1;
262 return;
263 }
264 owner = jobowner;
265 break;
266 } else {
267 compareArray = QByteArray(compareArray.data() + dataLen, bufferLen - dataLen);
268 return;
269 }
270
271 } while (false);
272
273 KIO::TransferJob *otherJob = (job == leftReadJob) ? rightReadJob : leftReadJob;
274
275 if (otherJob == 0) {
276 if (compareArray.size())
277 abortContentComparing();
278 } else {
279 if (!((KIO::TransferJob *)job)->isSuspended()) {
280 ((KIO::TransferJob *)job)->suspend();
281 otherJob->resume();
282 }
283 }
284 }
285
slotFinished(KJob * job)286 void CompareContentTask::slotFinished(KJob *job)
287 {
288 KIO::TransferJob *otherJob = (job == leftReadJob) ? rightReadJob : leftReadJob;
289
290 if (job == leftReadJob)
291 leftReadJob = 0;
292 else
293 rightReadJob = 0;
294
295 if (otherJob)
296 otherJob->resume();
297
298 if (job->error()) {
299 timer->stop();
300 abortContentComparing();
301 }
302
303 if (job->error() && job->error() != KIO::ERR_USER_CANCELED && !errorPrinted) {
304 errorPrinted = true;
305 KMessageBox::error(parentWidget, i18n("I/O error while comparing file %1 with %2.",
306 leftURL.toDisplayString(QUrl::PreferLocalFile),
307 rightURL.toDisplayString(QUrl::PreferLocalFile)));
308 }
309
310 if (leftReadJob == 0 && rightReadJob == 0) {
311 if (!compareArray.size())
312 sync->compareContentResult(item, true);
313 else
314 sync->compareContentResult(item, false);
315
316 m_state = ST_STATE_READY;
317 }
318 }
319
abortContentComparing()320 void CompareContentTask::abortContentComparing()
321 {
322 if (timer)
323 timer->stop();
324
325 if (leftReadJob)
326 leftReadJob->kill(KJob::EmitResult);
327 if (rightReadJob)
328 rightReadJob->kill(KJob::EmitResult);
329
330 if (item->task() >= TT_UNKNOWN)
331 sync->compareContentResult(item, false);
332
333 m_state = ST_STATE_READY;
334 }
335
sendStatusMessage()336 void CompareContentTask::sendStatusMessage()
337 {
338 double perc = (size == 0) ? 1. : (double)received / (double)size;
339 int percent = (int)(perc * 10000. + 0.5);
340 QString statstr = QString("%1.%2%3").arg(percent / 100).arg((percent / 10) % 10).arg(percent % 10) + '%';
341 setStatusMessage(i18n("Comparing file %1 (%2)...", leftURL.fileName(), statstr));
342 timer->setSingleShot(true);
343 timer->start(500);
344 }
345
346