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