File indexing completed on 2024-04-21 16:33:13

0001 /*
0002     SPDX-FileCopyrightText: 2003 Csaba Karai <krusader@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "synchronizer.h"
0009 #include "../FileSystem/filesystem.h"
0010 #include "../FileSystem/krquery.h"
0011 #include "../krglobal.h"
0012 #include "../krservices.h"
0013 #include "synchronizerdirlist.h"
0014 
0015 #include <utime.h>
0016 
0017 // QtCore
0018 #include <QDateTime>
0019 #include <QDir>
0020 #include <QEventLoop>
0021 #include <QRegExp>
0022 #include <QTime>
0023 #include <QTimer>
0024 #include <QUrl>
0025 // QtWidgets
0026 #include <QApplication>
0027 #include <QDialogButtonBox>
0028 #include <QFrame>
0029 #include <QLabel>
0030 #include <QLayout>
0031 #include <QProgressBar>
0032 #include <QPushButton>
0033 #include <QVBoxLayout>
0034 
0035 #include <KCoreAddons/KProcess>
0036 #include <KI18n/KLocalizedString>
0037 #include <KIO/DeleteJob>
0038 #include <KIO/JobUiDelegate>
0039 #include <KIO/SkipDialog>
0040 #include <KIOWidgets/KUrlCompletion>
0041 #include <KWidgetsAddons/KMessageBox>
0042 
0043 #ifdef HAVE_POSIX_ACL
0044 #include <sys/acl.h>
0045 #ifdef HAVE_NON_POSIX_ACL_EXTENSIONS
0046 #include <acl/libacl.h>
0047 #endif
0048 #endif
0049 
0050 #define DISPLAY_UPDATE_PERIOD 2
0051 
0052 Synchronizer::Synchronizer()
0053     : displayUpdateCount(0)
0054     , markEquals(true)
0055     , markDiffers(true)
0056     , markCopyToLeft(true)
0057     , markCopyToRight(true)
0058     , markDeletable(true)
0059     , parentWidget(nullptr)
0060     , resultListIt(resultList)
0061 {
0062 }
0063 
0064 Synchronizer::~Synchronizer()
0065 {
0066     clearLists();
0067 }
0068 
0069 QUrl Synchronizer::fsUrl(const QString &strUrl)
0070 {
0071     QUrl result = QUrl::fromUserInput(strUrl, QString(), QUrl::AssumeLocalFile);
0072     return KrServices::escapeFileUrl(result);
0073 }
0074 
0075 void Synchronizer::clearLists()
0076 {
0077     QListIterator<SynchronizerFileItem *> i1(resultList);
0078     while (i1.hasNext())
0079         delete i1.next();
0080     resultList.clear();
0081 
0082     QListIterator<SynchronizerTask *> i2(stack);
0083     while (i2.hasNext())
0084         delete i2.next();
0085     stack.clear();
0086 
0087     temporaryList.clear();
0088 }
0089 
0090 void Synchronizer::reset()
0091 {
0092     displayUpdateCount = 0;
0093     markEquals = markDiffers = markCopyToLeft = markCopyToRight = markDeletable = true;
0094     stopped = false;
0095     recurseSubDirs = followSymLinks = ignoreDate = asymmetric = cmpByContent = ignoreCase = autoScroll = false;
0096     markEquals = markDiffers = markCopyToLeft = markCopyToRight = markDeletable = markDuplicates = markSingles = false;
0097     leftCopyEnabled = rightCopyEnabled = deleteEnabled = overWrite = autoSkip = paused = false;
0098     leftCopyNr = rightCopyNr = deleteNr = 0;
0099     leftCopySize = rightCopySize = deleteSize = 0;
0100     comparedDirs = fileCount = 0;
0101     leftBaseDir.clear();
0102     rightBaseDir.clear();
0103     clearLists();
0104 }
0105 
0106 int Synchronizer::compare(QString leftURL,
0107                           QString rightURL,
0108                           KrQuery *query,
0109                           bool subDirs,
0110                           bool symLinks,
0111                           bool igDate,
0112                           bool asymm,
0113                           bool cmpByCnt,
0114                           bool igCase,
0115                           bool autoSc,
0116                           QStringList &selFiles,
0117                           int equThres,
0118                           int timeOffs,
0119                           int parThreads,
0120                           bool hiddenFiles)
0121 {
0122     clearLists();
0123 
0124     recurseSubDirs = subDirs;
0125     followSymLinks = symLinks;
0126     ignoreDate = igDate;
0127     asymmetric = asymm;
0128     cmpByContent = cmpByCnt;
0129     autoScroll = autoSc;
0130     ignoreCase = igCase;
0131     selectedFiles = selFiles;
0132     equalsThreshold = equThres;
0133     timeOffset = timeOffs;
0134     parallelThreads = parThreads;
0135     ignoreHidden = hiddenFiles;
0136 
0137     stopped = false;
0138 
0139     this->query = query;
0140 
0141     leftURL = KUrlCompletion::replacedPath(leftURL, true, true);
0142     rightURL = KUrlCompletion::replacedPath(rightURL, true, true);
0143 
0144     if (!leftURL.endsWith('/'))
0145         leftURL += '/';
0146     if (!rightURL.endsWith('/'))
0147         rightURL += '/';
0148 
0149     excludedPaths = KrServices::toStringList(query->dontSearchInDirs());
0150     for (int i = 0; i != excludedPaths.count(); i++)
0151         if (excludedPaths[i].endsWith('/'))
0152             excludedPaths[i].truncate(excludedPaths[i].length() - 1);
0153 
0154     comparedDirs = fileCount = 0;
0155 
0156     stack.append(new CompareTask(nullptr, leftBaseDir = leftURL, rightBaseDir = rightURL, "", "", ignoreHidden));
0157     compareLoop();
0158 
0159     QListIterator<SynchronizerFileItem *> it(temporaryList);
0160     while (it.hasNext()) {
0161         SynchronizerFileItem *item = it.next();
0162 
0163         if (item->isTemporary())
0164             delete item;
0165     }
0166     temporaryList.clear();
0167 
0168     if (!autoScroll)
0169         refresh(true);
0170 
0171     emit statusInfo(i18n("Number of files: %1", fileCount));
0172     return fileCount;
0173 }
0174 
0175 void Synchronizer::compareLoop()
0176 {
0177     while (!stopped && !stack.isEmpty()) {
0178         for (int thread = 0; thread < (int)stack.count() && thread < parallelThreads; thread++) {
0179             SynchronizerTask *entry = stack.at(thread);
0180 
0181             if (entry->state() == ST_STATE_NEW)
0182                 entry->start(parentWidget);
0183 
0184             if (entry->inherits("CompareTask")) {
0185                 if (entry->state() == ST_STATE_READY) {
0186                     auto *ctentry = qobject_cast<CompareTask *>(entry);
0187                     if (ctentry->isDuplicate())
0188                         compareDirectory(ctentry->parent(), ctentry->leftDirList(), ctentry->rightDirList(), ctentry->leftDir(), ctentry->rightDir());
0189                     else
0190                         addSingleDirectory(ctentry->parent(), ctentry->dirList(), ctentry->dir(), ctentry->isLeft());
0191                 }
0192                 if (entry->state() == ST_STATE_READY || entry->state() == ST_STATE_ERROR)
0193                     comparedDirs++;
0194             }
0195             switch (entry->state()) {
0196             case ST_STATE_STATUS:
0197                 emit statusInfo(entry->status());
0198                 break;
0199             case ST_STATE_READY:
0200             case ST_STATE_ERROR:
0201                 emit statusInfo(i18n("Number of compared folders: %1", comparedDirs));
0202                 stack.removeAll(entry);
0203                 delete entry;
0204                 continue;
0205             default:
0206                 break;
0207             }
0208         }
0209         if (!stack.isEmpty())
0210             qApp->processEvents();
0211     }
0212 
0213     QListIterator<SynchronizerTask *> it(stack);
0214     while (it.hasNext())
0215         delete it.next();
0216     stack.clear();
0217 }
0218 
0219 void Synchronizer::compareDirectory(SynchronizerFileItem *parent,
0220                                     SynchronizerDirList *left_directory,
0221                                     SynchronizerDirList *right_directory,
0222                                     const QString &leftDir,
0223                                     const QString &rightDir)
0224 {
0225     const QString &leftURL = left_directory->url();
0226     const QString &rightURL = right_directory->url();
0227     FileItem *left_file;
0228     FileItem *right_file;
0229 
0230     QString file_name;
0231     bool checkIfSelected = false;
0232 
0233     if (leftDir.isEmpty() && rightDir.isEmpty() && selectedFiles.count())
0234         checkIfSelected = true;
0235 
0236     /* walking through in the left directory */
0237     for (left_file = left_directory->first(); left_file != nullptr && !stopped; left_file = left_directory->next()) {
0238         if (isDir(left_file))
0239             continue;
0240 
0241         file_name = left_file->getName();
0242 
0243         if (checkIfSelected && !selectedFiles.contains(file_name))
0244             continue;
0245 
0246         if (!query->match(left_file))
0247             continue;
0248 
0249         if ((right_file = right_directory->search(file_name, ignoreCase)) == nullptr)
0250             addLeftOnlyItem(parent,
0251                             file_name,
0252                             leftDir,
0253                             left_file->getSize(),
0254                             left_file->getModificationTime(),
0255                             readLink(left_file),
0256                             left_file->getOwner(),
0257                             left_file->getGroup(),
0258                             left_file->getMode(),
0259                             left_file->getACL());
0260         else {
0261             if (isDir(right_file))
0262                 continue;
0263 
0264             addDuplicateItem(parent,
0265                              file_name,
0266                              right_file->getName(),
0267                              leftDir,
0268                              rightDir,
0269                              left_file->getSize(),
0270                              right_file->getSize(),
0271                              left_file->getModificationTime(),
0272                              right_file->getModificationTime(),
0273                              readLink(left_file),
0274                              readLink(right_file),
0275                              left_file->getOwner(),
0276                              right_file->getOwner(),
0277                              left_file->getGroup(),
0278                              right_file->getGroup(),
0279                              left_file->getMode(),
0280                              right_file->getMode(),
0281                              left_file->getACL(),
0282                              right_file->getACL());
0283         }
0284     }
0285 
0286     /* walking through in the right directory */
0287     for (right_file = right_directory->first(); right_file != nullptr && !stopped; right_file = right_directory->next()) {
0288         if (isDir(right_file))
0289             continue;
0290 
0291         file_name = right_file->getName();
0292 
0293         if (checkIfSelected && !selectedFiles.contains(file_name))
0294             continue;
0295 
0296         if (!query->match(right_file))
0297             continue;
0298 
0299         if (left_directory->search(file_name, ignoreCase) == nullptr)
0300             addRightOnlyItem(parent,
0301                              file_name,
0302                              rightDir,
0303                              right_file->getSize(),
0304                              right_file->getModificationTime(),
0305                              readLink(right_file),
0306                              right_file->getOwner(),
0307                              right_file->getGroup(),
0308                              right_file->getMode(),
0309                              right_file->getACL());
0310     }
0311 
0312     /* walking through the subdirectories */
0313     if (recurseSubDirs) {
0314         for (left_file = left_directory->first(); left_file != nullptr && !stopped; left_file = left_directory->next()) {
0315             if (left_file->isDir() && (followSymLinks || !left_file->isSymLink())) {
0316                 QString left_file_name = left_file->getName();
0317 
0318                 if (checkIfSelected && !selectedFiles.contains(left_file_name))
0319                     continue;
0320 
0321                 // Exclude the left-side folder if it's inside the exclusion list
0322                 if (query->isExcluded(left_file->getUrl()))
0323                     continue;
0324 
0325                 if (!query->matchDirName(left_file_name))
0326                     continue;
0327 
0328                 if ((right_file = right_directory->search(left_file_name, ignoreCase)) == nullptr) {
0329                     SynchronizerFileItem *me = addLeftOnlyItem(parent,
0330                                                                left_file_name,
0331                                                                leftDir,
0332                                                                0,
0333                                                                left_file->getModificationTime(),
0334                                                                readLink(left_file),
0335                                                                left_file->getOwner(),
0336                                                                left_file->getGroup(),
0337                                                                left_file->getMode(),
0338                                                                left_file->getACL(),
0339                                                                true,
0340                                                                !query->match(left_file));
0341                     stack.append(new CompareTask(me,
0342                                                  leftURL + left_file_name + '/',
0343                                                  leftDir.isEmpty() ? left_file_name : leftDir + '/' + left_file_name,
0344                                                  true,
0345                                                  ignoreHidden));
0346                 } else {
0347                     QString right_file_name = right_file->getName();
0348                     SynchronizerFileItem *me = addDuplicateItem(parent,
0349                                                                 left_file_name,
0350                                                                 right_file_name,
0351                                                                 leftDir,
0352                                                                 rightDir,
0353                                                                 0,
0354                                                                 0,
0355                                                                 left_file->getModificationTime(),
0356                                                                 right_file->getModificationTime(),
0357                                                                 readLink(left_file),
0358                                                                 readLink(right_file),
0359                                                                 left_file->getOwner(),
0360                                                                 right_file->getOwner(),
0361                                                                 left_file->getGroup(),
0362                                                                 right_file->getGroup(),
0363                                                                 left_file->getMode(),
0364                                                                 right_file->getMode(),
0365                                                                 left_file->getACL(),
0366                                                                 right_file->getACL(),
0367                                                                 true,
0368                                                                 !query->match(left_file));
0369                     stack.append(new CompareTask(me,
0370                                                  leftURL + left_file_name + '/',
0371                                                  rightURL + right_file_name + '/',
0372                                                  leftDir.isEmpty() ? left_file_name : leftDir + '/' + left_file_name,
0373                                                  rightDir.isEmpty() ? right_file_name : rightDir + '/' + right_file_name,
0374                                                  ignoreHidden));
0375                 }
0376             }
0377         }
0378 
0379         /* walking through the right side subdirectories */
0380         for (right_file = right_directory->first(); right_file != nullptr && !stopped; right_file = right_directory->next()) {
0381             if (right_file->isDir() && (followSymLinks || !right_file->isSymLink())) {
0382                 file_name = right_file->getName();
0383 
0384                 if (checkIfSelected && !selectedFiles.contains(file_name))
0385                     continue;
0386 
0387                 // Exclude the right-side folder if it's inside the exclusion list
0388                 if (query->isExcluded(right_file->getUrl()))
0389                     continue;
0390 
0391                 if (!query->matchDirName(file_name))
0392                     continue;
0393 
0394                 if (left_directory->search(file_name, ignoreCase) == nullptr) {
0395                     SynchronizerFileItem *me = addRightOnlyItem(parent,
0396                                                                 file_name,
0397                                                                 rightDir,
0398                                                                 0,
0399                                                                 right_file->getModificationTime(),
0400                                                                 readLink(right_file),
0401                                                                 right_file->getOwner(),
0402                                                                 right_file->getGroup(),
0403                                                                 right_file->getMode(),
0404                                                                 right_file->getACL(),
0405                                                                 true,
0406                                                                 !query->match(right_file));
0407                     stack.append(
0408                         new CompareTask(me, rightURL + file_name + '/', rightDir.isEmpty() ? file_name : rightDir + '/' + file_name, false, ignoreHidden));
0409                 }
0410             }
0411         }
0412     }
0413 }
0414 
0415 QString Synchronizer::getTaskTypeName(TaskType taskType)
0416 {
0417     static QString names[] = {"=", "!=", "<-", "->", "DEL", "?", "?", "?", "?", "?"};
0418 
0419     return names[taskType];
0420 }
0421 
0422 SynchronizerFileItem *Synchronizer::addItem(SynchronizerFileItem *parent,
0423                                             const QString &leftFile,
0424                                             const QString &rightFile,
0425                                             const QString &leftDir,
0426                                             const QString &rightDir,
0427                                             bool existsLeft,
0428                                             bool existsRight,
0429                                             KIO::filesize_t leftSize,
0430                                             KIO::filesize_t rightSize,
0431                                             time_t leftDate,
0432                                             time_t rightDate,
0433                                             const QString &leftLink,
0434                                             const QString &rightLink,
0435                                             const QString &leftOwner,
0436                                             const QString &rightOwner,
0437                                             const QString &leftGroup,
0438                                             const QString &rightGroup,
0439                                             mode_t leftMode,
0440                                             mode_t rightMode,
0441                                             const QString &leftACL,
0442                                             const QString &rightACL,
0443                                             TaskType tsk,
0444                                             bool isDir,
0445                                             bool isTemp)
0446 {
0447     bool marked = autoScroll ? !isTemp && isMarked(tsk, existsLeft && existsRight) : false;
0448     auto *item = new SynchronizerFileItem(leftFile,
0449                                           rightFile,
0450                                           leftDir,
0451                                           rightDir,
0452                                           marked,
0453                                           existsLeft,
0454                                           existsRight,
0455                                           leftSize,
0456                                           rightSize,
0457                                           leftDate,
0458                                           rightDate,
0459                                           leftLink,
0460                                           rightLink,
0461                                           leftOwner,
0462                                           rightOwner,
0463                                           leftGroup,
0464                                           rightGroup,
0465                                           leftMode,
0466                                           rightMode,
0467                                           leftACL,
0468                                           rightACL,
0469                                           tsk,
0470                                           isDir,
0471                                           isTemp,
0472                                           parent);
0473 
0474     if (!isTemp) {
0475         while (parent && parent->isTemporary())
0476             setPermanent(parent);
0477 
0478         bool doRefresh = false;
0479 
0480         if (marked) {
0481             fileCount++;
0482             if (autoScroll && markParentDirectories(item))
0483                 doRefresh = true;
0484         }
0485 
0486         resultList.append(item);
0487         emit comparedFileData(item);
0488 
0489         if (doRefresh)
0490             refresh(true);
0491 
0492         if (marked && (displayUpdateCount++ % DISPLAY_UPDATE_PERIOD == (DISPLAY_UPDATE_PERIOD - 1)))
0493             qApp->processEvents();
0494     } else
0495         temporaryList.append(item);
0496 
0497     return item;
0498 }
0499 
0500 void Synchronizer::compareContentResult(SynchronizerFileItem *item, bool res)
0501 {
0502     item->compareContentResult(res);
0503     bool marked = autoScroll ? isMarked(item->task(), item->existsInLeft() && item->existsInRight()) : false;
0504     item->setMarked(marked);
0505     if (marked) {
0506         markParentDirectories(item);
0507         fileCount++;
0508         emit markChanged(item, true);
0509     }
0510 }
0511 
0512 void Synchronizer::setPermanent(SynchronizerFileItem *item)
0513 {
0514     if (item->parent() && item->parent()->isTemporary())
0515         setPermanent(item->parent());
0516 
0517     item->setPermanent();
0518     resultList.append(item);
0519     emit comparedFileData(item);
0520 }
0521 
0522 SynchronizerFileItem *Synchronizer::addLeftOnlyItem(SynchronizerFileItem *parent,
0523                                                     const QString &file_name,
0524                                                     const QString &dir,
0525                                                     KIO::filesize_t size,
0526                                                     time_t date,
0527                                                     const QString &link,
0528                                                     const QString &owner,
0529                                                     const QString &group,
0530                                                     mode_t mode,
0531                                                     const QString &acl,
0532                                                     bool isDir,
0533                                                     bool isTemp)
0534 {
0535     return addItem(parent,
0536                    file_name,
0537                    file_name,
0538                    dir,
0539                    dir,
0540                    true,
0541                    false,
0542                    size,
0543                    0,
0544                    date,
0545                    0,
0546                    link,
0547                    QString(),
0548                    owner,
0549                    QString(),
0550                    group,
0551                    QString(),
0552                    mode,
0553                    (mode_t)-1,
0554                    acl,
0555                    QString(),
0556                    asymmetric ? TT_DELETE : TT_COPY_TO_RIGHT,
0557                    isDir,
0558                    isTemp);
0559 }
0560 
0561 SynchronizerFileItem *Synchronizer::addRightOnlyItem(SynchronizerFileItem *parent,
0562                                                      const QString &file_name,
0563                                                      const QString &dir,
0564                                                      KIO::filesize_t size,
0565                                                      time_t date,
0566                                                      const QString &link,
0567                                                      const QString &owner,
0568                                                      const QString &group,
0569                                                      mode_t mode,
0570                                                      const QString &acl,
0571                                                      bool isDir,
0572                                                      bool isTemp)
0573 {
0574     return addItem(parent,
0575                    file_name,
0576                    file_name,
0577                    dir,
0578                    dir,
0579                    false,
0580                    true,
0581                    0,
0582                    size,
0583                    0,
0584                    date,
0585                    QString(),
0586                    link,
0587                    QString(),
0588                    owner,
0589                    QString(),
0590                    group,
0591                    (mode_t)-1,
0592                    mode,
0593                    QString(),
0594                    acl,
0595                    TT_COPY_TO_LEFT,
0596                    isDir,
0597                    isTemp);
0598 }
0599 
0600 SynchronizerFileItem *Synchronizer::addDuplicateItem(SynchronizerFileItem *parent,
0601                                                      const QString &leftName,
0602                                                      const QString &rightName,
0603                                                      const QString &leftDir,
0604                                                      const QString &rightDir,
0605                                                      KIO::filesize_t leftSize,
0606                                                      KIO::filesize_t rightSize,
0607                                                      time_t leftDate,
0608                                                      time_t rightDate,
0609                                                      const QString &leftLink,
0610                                                      const QString &rightLink,
0611                                                      const QString &leftOwner,
0612                                                      const QString &rightOwner,
0613                                                      const QString &leftGroup,
0614                                                      const QString &rightGroup,
0615                                                      mode_t leftMode,
0616                                                      mode_t rightMode,
0617                                                      const QString &leftACL,
0618                                                      const QString &rightACL,
0619                                                      bool isDir,
0620                                                      bool isTemp)
0621 {
0622     TaskType task;
0623 
0624     time_t checkedRightDate = rightDate - timeOffset;
0625     int uncertain = 0;
0626 
0627     do {
0628         if (isDir) {
0629             task = TT_EQUALS;
0630             break;
0631         }
0632         if (leftSize == rightSize) {
0633             if (!leftLink.isNull() || !rightLink.isNull()) {
0634                 if (leftLink == rightLink) {
0635                     task = TT_EQUALS;
0636                     break;
0637                 }
0638             } else if (cmpByContent)
0639                 uncertain = TT_UNKNOWN;
0640             else {
0641                 if (ignoreDate || leftDate == checkedRightDate) {
0642                     task = TT_EQUALS;
0643                     break;
0644                 }
0645                 time_t diff = (leftDate > checkedRightDate) ? leftDate - checkedRightDate : checkedRightDate - leftDate;
0646                 if (diff <= equalsThreshold) {
0647                     task = TT_EQUALS;
0648                     break;
0649                 }
0650             }
0651         }
0652 
0653         if (ignoreDate)
0654             task = TT_DIFFERS;
0655         else if (asymmetric) {
0656             // To avoid that the default action is
0657             // to overwrite a file by an old version of it
0658             if (leftDate > checkedRightDate)
0659                 task = TT_DIFFERS;
0660             else
0661                 task = TT_COPY_TO_LEFT;
0662         } else if (leftDate > checkedRightDate)
0663             task = TT_COPY_TO_RIGHT;
0664         else if (leftDate < checkedRightDate)
0665             task = TT_COPY_TO_LEFT;
0666         else
0667             task = TT_DIFFERS;
0668 
0669     } while (false);
0670 
0671     SynchronizerFileItem *item = addItem(parent,
0672                                          leftName,
0673                                          rightName,
0674                                          leftDir,
0675                                          rightDir,
0676                                          true,
0677                                          true,
0678                                          leftSize,
0679                                          rightSize,
0680                                          leftDate,
0681                                          rightDate,
0682                                          leftLink,
0683                                          rightLink,
0684                                          leftOwner,
0685                                          rightOwner,
0686                                          leftGroup,
0687                                          rightGroup,
0688                                          leftMode,
0689                                          rightMode,
0690                                          leftACL,
0691                                          rightACL,
0692                                          (TaskType)(task + uncertain),
0693                                          isDir,
0694                                          isTemp);
0695 
0696     if (uncertain == TT_UNKNOWN) {
0697         QUrl leftURL = Synchronizer::fsUrl(leftDir.isEmpty() ? leftBaseDir + leftName : leftBaseDir + leftDir + '/' + leftName);
0698         QUrl rightURL = Synchronizer::fsUrl(rightDir.isEmpty() ? rightBaseDir + rightName : rightBaseDir + rightDir + '/' + rightName);
0699         stack.append(new CompareContentTask(this, item, leftURL, rightURL, leftSize));
0700     }
0701 
0702     return item;
0703 }
0704 
0705 void Synchronizer::addSingleDirectory(SynchronizerFileItem *parent, SynchronizerDirList *directory, const QString &dirName, bool isLeft)
0706 {
0707     const QString &url = directory->url();
0708     FileItem *file;
0709     QString file_name;
0710 
0711     /* walking through the directory files */
0712     for (file = directory->first(); file != nullptr && !stopped; file = directory->next()) {
0713         if (isDir(file))
0714             continue;
0715 
0716         file_name = file->getName();
0717 
0718         if (!query->match(file))
0719             continue;
0720 
0721         if (isLeft)
0722             addLeftOnlyItem(parent,
0723                             file_name,
0724                             dirName,
0725                             file->getSize(),
0726                             file->getModificationTime(),
0727                             readLink(file),
0728                             file->getOwner(),
0729                             file->getGroup(),
0730                             file->getMode(),
0731                             file->getACL());
0732         else
0733             addRightOnlyItem(parent,
0734                              file_name,
0735                              dirName,
0736                              file->getSize(),
0737                              file->getModificationTime(),
0738                              readLink(file),
0739                              file->getOwner(),
0740                              file->getGroup(),
0741                              file->getMode(),
0742                              file->getACL());
0743     }
0744 
0745     /* walking through the subdirectories */
0746     for (file = directory->first(); file != nullptr && !stopped; file = directory->next()) {
0747         if (file->isDir() && (followSymLinks || !file->isSymLink())) {
0748             file_name = file->getName();
0749 
0750             if (excludedPaths.contains(dirName.isEmpty() ? file_name : dirName + '/' + file_name))
0751                 continue;
0752 
0753             if (!query->matchDirName(file_name))
0754                 continue;
0755 
0756             SynchronizerFileItem *me;
0757 
0758             if (isLeft)
0759                 me = addLeftOnlyItem(parent,
0760                                      file_name,
0761                                      dirName,
0762                                      0,
0763                                      file->getModificationTime(),
0764                                      readLink(file),
0765                                      file->getOwner(),
0766                                      file->getGroup(),
0767                                      file->getMode(),
0768                                      file->getACL(),
0769                                      true,
0770                                      !query->match(file));
0771             else
0772                 me = addRightOnlyItem(parent,
0773                                       file_name,
0774                                       dirName,
0775                                       0,
0776                                       file->getModificationTime(),
0777                                       readLink(file),
0778                                       file->getOwner(),
0779                                       file->getGroup(),
0780                                       file->getMode(),
0781                                       file->getACL(),
0782                                       true,
0783                                       !query->match(file));
0784             stack.append(new CompareTask(me, url + file_name + '/', dirName.isEmpty() ? file_name : dirName + '/' + file_name, isLeft, ignoreHidden));
0785         }
0786     }
0787 }
0788 
0789 void Synchronizer::setMarkFlags(bool left, bool equal, bool differs, bool right, bool dup, bool sing, bool del)
0790 {
0791     markEquals = equal;
0792     markDiffers = differs;
0793     markCopyToLeft = left;
0794     markCopyToRight = right;
0795     markDeletable = del;
0796     markDuplicates = dup;
0797     markSingles = sing;
0798 }
0799 
0800 bool Synchronizer::isMarked(TaskType task, bool isDuplicate)
0801 {
0802     if ((isDuplicate && !markDuplicates) || (!isDuplicate && !markSingles))
0803         return false;
0804 
0805     switch (task) {
0806     case TT_EQUALS:
0807         return markEquals;
0808     case TT_DIFFERS:
0809         return markDiffers;
0810     case TT_COPY_TO_LEFT:
0811         return markCopyToLeft;
0812     case TT_COPY_TO_RIGHT:
0813         return markCopyToRight;
0814     case TT_DELETE:
0815         return markDeletable;
0816     default:
0817         return false;
0818     }
0819 }
0820 
0821 bool Synchronizer::markParentDirectories(SynchronizerFileItem *item)
0822 {
0823     if (item->parent() == nullptr || item->parent()->isMarked())
0824         return false;
0825 
0826     markParentDirectories(item->parent());
0827 
0828     item->parent()->setMarked(true);
0829 
0830     fileCount++;
0831     emit markChanged(item->parent(), false);
0832     return true;
0833 }
0834 
0835 int Synchronizer::refresh(bool nostatus)
0836 {
0837     fileCount = 0;
0838 
0839     QListIterator<SynchronizerFileItem *> it(resultList);
0840     while (it.hasNext()) {
0841         SynchronizerFileItem *item = it.next();
0842 
0843         bool marked = isMarked(item->task(), item->existsInLeft() && item->existsInRight());
0844         item->setMarked(marked);
0845 
0846         if (marked) {
0847             markParentDirectories(item);
0848             fileCount++;
0849         }
0850     }
0851 
0852     it.toFront();
0853     while (it.hasNext()) {
0854         SynchronizerFileItem *item = it.next();
0855         emit markChanged(item, false);
0856     }
0857 
0858     if (!nostatus)
0859         emit statusInfo(i18n("Number of files: %1", fileCount));
0860 
0861     return fileCount;
0862 }
0863 
0864 void Synchronizer::operate(SynchronizerFileItem *item, void (*executeOperation)(SynchronizerFileItem *))
0865 {
0866     executeOperation(item);
0867 
0868     if (item->isDir()) {
0869         QString leftDirName = (item->leftDirectory().isEmpty()) ? item->leftName() : item->leftDirectory() + '/' + item->leftName();
0870         QString rightDirName = (item->rightDirectory().isEmpty()) ? item->rightName() : item->rightDirectory() + '/' + item->rightName();
0871 
0872         QListIterator<SynchronizerFileItem *> it(resultList);
0873         while (it.hasNext()) {
0874             SynchronizerFileItem *item = it.next();
0875 
0876             if (item->leftDirectory() == leftDirName || item->leftDirectory().startsWith(leftDirName + '/') || item->rightDirectory() == rightDirName
0877                 || item->rightDirectory().startsWith(rightDirName + '/'))
0878                 executeOperation(item);
0879         }
0880     }
0881 }
0882 
0883 void Synchronizer::excludeOperation(SynchronizerFileItem *item)
0884 {
0885     item->setTask(TT_DIFFERS);
0886 }
0887 
0888 void Synchronizer::exclude(SynchronizerFileItem *item)
0889 {
0890     if (!item->parent() || item->parent()->task() != TT_DELETE)
0891         operate(item, excludeOperation); /* exclude only if the parent task is not DEL */
0892 }
0893 
0894 void Synchronizer::restoreOperation(SynchronizerFileItem *item)
0895 {
0896     item->restoreOriginalTask();
0897 }
0898 
0899 void Synchronizer::restore(SynchronizerFileItem *item)
0900 {
0901     operate(item, restoreOperation);
0902 
0903     while ((item = item->parent()) != nullptr) /* in case of restore, the parent directories */
0904     { /* must be changed for being consistent */
0905         if (item->task() != TT_DIFFERS)
0906             break;
0907 
0908         if (item->originalTask() == TT_DELETE) /* if the parent original task is delete */
0909             break; /* don't touch it */
0910 
0911         item->restoreOriginalTask(); /* restore */
0912     }
0913 }
0914 
0915 void Synchronizer::reverseDirectionOperation(SynchronizerFileItem *item)
0916 {
0917     if (item->existsInRight() && item->existsInLeft()) {
0918         if (item->task() == TT_COPY_TO_LEFT)
0919             item->setTask(TT_COPY_TO_RIGHT);
0920         else if (item->task() == TT_COPY_TO_RIGHT)
0921             item->setTask(TT_COPY_TO_LEFT);
0922     }
0923 }
0924 
0925 void Synchronizer::reverseDirection(SynchronizerFileItem *item)
0926 {
0927     operate(item, reverseDirectionOperation);
0928 }
0929 
0930 void Synchronizer::deleteLeftOperation(SynchronizerFileItem *item)
0931 {
0932     if (!item->existsInRight() && item->existsInLeft())
0933         item->setTask(TT_DELETE);
0934 }
0935 
0936 void Synchronizer::deleteLeft(SynchronizerFileItem *item)
0937 {
0938     operate(item, deleteLeftOperation);
0939 }
0940 
0941 void Synchronizer::copyToLeftOperation(SynchronizerFileItem *item)
0942 {
0943     if (item->existsInRight()) {
0944         if (!item->isDir())
0945             item->setTask(TT_COPY_TO_LEFT);
0946         else {
0947             if (item->existsInLeft() && item->existsInRight())
0948                 item->setTask(TT_EQUALS);
0949             else if (!item->existsInLeft() && item->existsInRight())
0950                 item->setTask(TT_COPY_TO_LEFT);
0951         }
0952     }
0953 }
0954 
0955 void Synchronizer::copyToLeft(SynchronizerFileItem *item)
0956 {
0957     operate(item, copyToLeftOperation);
0958 
0959     while ((item = item->parent()) != nullptr) {
0960         if (item->task() != TT_DIFFERS)
0961             break;
0962 
0963         if (item->existsInLeft() && item->existsInRight())
0964             item->setTask(TT_EQUALS);
0965         else if (!item->existsInLeft() && item->existsInRight())
0966             item->setTask(TT_COPY_TO_LEFT);
0967     }
0968 }
0969 
0970 void Synchronizer::copyToRightOperation(SynchronizerFileItem *item)
0971 {
0972     if (item->existsInLeft()) {
0973         if (!item->isDir())
0974             item->setTask(TT_COPY_TO_RIGHT);
0975         else {
0976             if (item->existsInLeft() && item->existsInRight())
0977                 item->setTask(TT_EQUALS);
0978             else if (item->existsInLeft() && !item->existsInRight())
0979                 item->setTask(TT_COPY_TO_RIGHT);
0980         }
0981     }
0982 }
0983 
0984 void Synchronizer::copyToRight(SynchronizerFileItem *item)
0985 {
0986     operate(item, copyToRightOperation);
0987 
0988     while ((item = item->parent()) != nullptr) {
0989         if (item->task() != TT_DIFFERS && item->task() != TT_DELETE)
0990             break;
0991 
0992         if (item->existsInLeft() && item->existsInRight())
0993             item->setTask(TT_EQUALS);
0994         else if (item->existsInLeft() && !item->existsInRight())
0995             item->setTask(TT_COPY_TO_RIGHT);
0996     }
0997 }
0998 
0999 bool Synchronizer::totalSizes(int *leftCopyNr,
1000                               KIO::filesize_t *leftCopySize,
1001                               int *rightCopyNr,
1002                               KIO::filesize_t *rightCopySize,
1003                               int *deleteNr,
1004                               KIO::filesize_t *deletableSize)
1005 {
1006     bool hasAnythingToDo = false;
1007 
1008     *leftCopySize = *rightCopySize = *deletableSize = 0;
1009     *leftCopyNr = *rightCopyNr = *deleteNr = 0;
1010 
1011     QListIterator<SynchronizerFileItem *> it(resultList);
1012     while (it.hasNext()) {
1013         SynchronizerFileItem *item = it.next();
1014 
1015         if (item->isMarked()) {
1016             switch (item->task()) {
1017             case TT_COPY_TO_LEFT:
1018                 *leftCopySize += item->rightSize();
1019                 (*leftCopyNr)++;
1020                 hasAnythingToDo = true;
1021                 break;
1022             case TT_COPY_TO_RIGHT:
1023                 *rightCopySize += item->leftSize();
1024                 (*rightCopyNr)++;
1025                 hasAnythingToDo = true;
1026                 break;
1027             case TT_DELETE:
1028                 *deletableSize += item->leftSize();
1029                 (*deleteNr)++;
1030                 hasAnythingToDo = true;
1031                 break;
1032             default:
1033                 break;
1034             }
1035         }
1036     }
1037 
1038     return hasAnythingToDo;
1039 }
1040 
1041 void Synchronizer::swapSides()
1042 {
1043     QString leftTmp = leftBaseDir;
1044     leftBaseDir = rightBaseDir;
1045     rightBaseDir = leftTmp;
1046 
1047     QListIterator<SynchronizerFileItem *> it(resultList);
1048     while (it.hasNext()) {
1049         SynchronizerFileItem *item = it.next();
1050 
1051         item->swap(asymmetric);
1052     }
1053 }
1054 
1055 void Synchronizer::setScrolling(bool scroll)
1056 {
1057     autoScroll = scroll;
1058     if (autoScroll) {
1059         int oldFileCount = fileCount;
1060         refresh(true);
1061         fileCount = oldFileCount;
1062     }
1063 }
1064 
1065 void Synchronizer::synchronize(QWidget *syncWdg, bool leftCopyEnabled, bool rightCopyEnabled, bool deleteEnabled, bool overWrite, int parThreads)
1066 {
1067     this->leftCopyEnabled = leftCopyEnabled;
1068     this->rightCopyEnabled = rightCopyEnabled;
1069     this->deleteEnabled = deleteEnabled;
1070     this->overWrite = overWrite;
1071     this->parallelThreads = parThreads;
1072     this->syncDlgWidget = syncWdg;
1073 
1074     autoSkip = paused = disableNewTasks = false;
1075 
1076     leftCopyNr = rightCopyNr = deleteNr = 0;
1077     leftCopySize = rightCopySize = deleteSize = 0;
1078 
1079     inTaskFinished = 0;
1080     lastTask = nullptr;
1081 
1082     jobMap.clear();
1083     receivedMap.clear();
1084 
1085     resultListIt = resultList;
1086     synchronizeLoop();
1087 }
1088 
1089 void Synchronizer::synchronizeLoop()
1090 {
1091     if (disableNewTasks) {
1092         if (!resultListIt.hasNext() && jobMap.count() == 0)
1093             emit synchronizationFinished();
1094         return;
1095     }
1096 
1097     while ((int)jobMap.count() < parallelThreads) {
1098         SynchronizerFileItem *task = getNextTask();
1099         if (task == nullptr) {
1100             if (jobMap.count() == 0)
1101                 emit synchronizationFinished();
1102             return;
1103         }
1104         executeTask(task);
1105         if (disableNewTasks)
1106             break;
1107     }
1108 }
1109 
1110 SynchronizerFileItem *Synchronizer::getNextTask()
1111 {
1112     TaskType task;
1113     SynchronizerFileItem *currentTask;
1114 
1115     do {
1116         if (!resultListIt.hasNext())
1117             return nullptr;
1118 
1119         currentTask = resultListIt.next();
1120 
1121         if (currentTask->isMarked()) {
1122             task = currentTask->task();
1123 
1124             if (leftCopyEnabled && task == TT_COPY_TO_LEFT)
1125                 break;
1126             else if (rightCopyEnabled && task == TT_COPY_TO_RIGHT)
1127                 break;
1128             else if (deleteEnabled && task == TT_DELETE)
1129                 break;
1130         }
1131     } while (true);
1132 
1133     return lastTask = currentTask;
1134 }
1135 
1136 void Synchronizer::executeTask(SynchronizerFileItem *task)
1137 {
1138     QString leftDirName = task->leftDirectory();
1139     if (!leftDirName.isEmpty())
1140         leftDirName += '/';
1141     QString rightDirName = task->rightDirectory();
1142     if (!rightDirName.isEmpty())
1143         rightDirName += '/';
1144 
1145     QUrl leftURL = Synchronizer::fsUrl(leftBaseDir + leftDirName + task->leftName());
1146     QUrl rightURL = Synchronizer::fsUrl(rightBaseDir + rightDirName + task->rightName());
1147 
1148     switch (task->task()) {
1149     case TT_COPY_TO_LEFT:
1150         if (task->isDir()) {
1151             KIO::SimpleJob *job = KIO::mkdir(leftURL);
1152             connect(job, &KIO::MkdirJob::result, this, &Synchronizer::slotTaskFinished);
1153             jobMap[job] = task;
1154             disableNewTasks = true;
1155         } else {
1156             QUrl destURL(leftURL);
1157             if (!task->destination().isNull())
1158                 destURL = Synchronizer::fsUrl(task->destination());
1159 
1160             if (task->rightLink().isNull()) {
1161                 KIO::FileCopyJob *job =
1162                     KIO::file_copy(rightURL, destURL, -1, ((overWrite || task->overWrite()) ? KIO::Overwrite : KIO::DefaultFlags) | KIO::HideProgressInfo);
1163                 connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotProcessedSize(KJob *, qulonglong)));
1164                 connect(job, &KIO::FileCopyJob::result, this, &Synchronizer::slotTaskFinished);
1165                 jobMap[job] = task;
1166             } else {
1167                 KIO::SimpleJob *job =
1168                     KIO::symlink(task->rightLink(), destURL, ((overWrite || task->overWrite()) ? KIO::Overwrite : KIO::DefaultFlags) | KIO::HideProgressInfo);
1169                 connect(job, &KIO::SimpleJob::result, this, &Synchronizer::slotTaskFinished);
1170                 jobMap[job] = task;
1171             }
1172         }
1173         break;
1174     case TT_COPY_TO_RIGHT:
1175         if (task->isDir()) {
1176             KIO::SimpleJob *job = KIO::mkdir(rightURL);
1177             connect(job, &KIO::SimpleJob::result, this, &Synchronizer::slotTaskFinished);
1178             jobMap[job] = task;
1179             disableNewTasks = true;
1180         } else {
1181             QUrl destURL(rightURL);
1182             if (!task->destination().isNull())
1183                 destURL = Synchronizer::fsUrl(task->destination());
1184 
1185             if (task->leftLink().isNull()) {
1186                 KIO::FileCopyJob *job =
1187                     KIO::file_copy(leftURL, destURL, -1, ((overWrite || task->overWrite()) ? KIO::Overwrite : KIO::DefaultFlags) | KIO::HideProgressInfo);
1188                 connect(job, SIGNAL(processedSize(KJob *, qulonglong)), this, SLOT(slotProcessedSize(KJob *, qulonglong)));
1189                 connect(job, &KIO::FileCopyJob::result, this, &Synchronizer::slotTaskFinished);
1190                 jobMap[job] = task;
1191             } else {
1192                 KIO::SimpleJob *job =
1193                     KIO::symlink(task->leftLink(), destURL, ((overWrite || task->overWrite()) ? KIO::Overwrite : KIO::DefaultFlags) | KIO::HideProgressInfo);
1194                 connect(job, &KIO::SimpleJob::result, this, &Synchronizer::slotTaskFinished);
1195                 jobMap[job] = task;
1196             }
1197         }
1198         break;
1199     case TT_DELETE: {
1200         KIO::DeleteJob *job = KIO::del(leftURL, KIO::DefaultFlags);
1201         connect(job, &KIO::DeleteJob::result, this, &Synchronizer::slotTaskFinished);
1202         jobMap[job] = task;
1203     } break;
1204     default:
1205         break;
1206     }
1207 }
1208 
1209 void Synchronizer::slotTaskFinished(KJob *job)
1210 {
1211     inTaskFinished++;
1212 
1213     SynchronizerFileItem *item = jobMap[job];
1214     jobMap.remove(job);
1215 
1216     KIO::filesize_t receivedSize = 0;
1217 
1218     if (receivedMap.contains(job)) {
1219         receivedSize = receivedMap[job];
1220         receivedMap.remove(job);
1221     }
1222 
1223     if (disableNewTasks && item == lastTask)
1224         disableNewTasks = false; // the blocker task finished
1225 
1226     QString leftDirName = item->leftDirectory().isEmpty() ? "" : item->leftDirectory() + '/';
1227     QString rightDirName = item->rightDirectory().isEmpty() ? "" : item->rightDirectory() + '/';
1228     QUrl leftURL = Synchronizer::fsUrl(leftBaseDir + leftDirName + item->leftName());
1229     QUrl rightURL = Synchronizer::fsUrl(rightBaseDir + rightDirName + item->rightName());
1230 
1231     do {
1232         if (!job->error()) {
1233             switch (item->task()) {
1234             case TT_COPY_TO_LEFT:
1235                 if (leftURL.isLocalFile()) {
1236                     struct utimbuf timestamp;
1237 
1238                     timestamp.actime = time(nullptr);
1239                     timestamp.modtime = item->rightDate() - timeOffset;
1240 
1241                     utime((const char *)(leftURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), &timestamp);
1242 
1243                     auto newOwnerID = (uid_t)-1; // chown(2) : -1 means no change
1244                     if (!item->rightOwner().isEmpty()) {
1245                         struct passwd *pw = getpwnam(QFile::encodeName(item->rightOwner()));
1246                         if (pw != nullptr)
1247                             newOwnerID = pw->pw_uid;
1248                     }
1249                     auto newGroupID = (gid_t)-1; // chown(2) : -1 means no change
1250                     if (!item->rightGroup().isEmpty()) {
1251                         struct group *g = getgrnam(QFile::encodeName(item->rightGroup()));
1252                         if (g != nullptr)
1253                             newGroupID = g->gr_gid;
1254                     }
1255                     int status1 = chown((const char *)(leftURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), newOwnerID, (gid_t)-1);
1256                     int status2 = chown((const char *)(leftURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), (uid_t)-1, newGroupID);
1257                     if (status1 < 0 || status2 < 0) {
1258                         // synchronizer currently ignores chown errors
1259                     }
1260 
1261                     chmod((const char *)(leftURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), item->rightMode() & 07777);
1262 
1263 #ifdef HAVE_POSIX_ACL
1264                     if (!item->rightACL().isNull()) {
1265                         acl_t acl = acl_from_text(item->rightACL().toLatin1());
1266                         if (acl && !acl_valid(acl))
1267                             acl_set_file(leftURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit(), ACL_TYPE_ACCESS, acl);
1268                         if (acl)
1269                             acl_free(acl);
1270                     }
1271 #endif
1272                 }
1273                 break;
1274             case TT_COPY_TO_RIGHT:
1275                 if (rightURL.isLocalFile()) {
1276                     struct utimbuf timestamp;
1277 
1278                     timestamp.actime = time(nullptr);
1279                     timestamp.modtime = item->leftDate() + timeOffset;
1280 
1281                     utime((const char *)(rightURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), &timestamp);
1282 
1283                     auto newOwnerID = (uid_t)-1; // chown(2) : -1 means no change
1284                     if (!item->leftOwner().isEmpty()) {
1285                         struct passwd *pw = getpwnam(QFile::encodeName(item->leftOwner()));
1286                         if (pw != nullptr)
1287                             newOwnerID = pw->pw_uid;
1288                     }
1289                     auto newGroupID = (gid_t)-1; // chown(2) : -1 means no change
1290                     if (!item->leftGroup().isEmpty()) {
1291                         struct group *g = getgrnam(QFile::encodeName(item->leftGroup()));
1292                         if (g != nullptr)
1293                             newGroupID = g->gr_gid;
1294                     }
1295                     int status1 = chown((const char *)(rightURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), newOwnerID, (uid_t)-1);
1296                     int status2 = chown((const char *)(rightURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), (uid_t)-1, newGroupID);
1297                     if (status1 < 0 || status2 < 0) {
1298                         // synchronizer currently ignores chown errors
1299                     }
1300 
1301                     chmod((const char *)(rightURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit()), item->leftMode() & 07777);
1302 
1303 #ifdef HAVE_POSIX_ACL
1304                     if (!item->leftACL().isNull()) {
1305                         acl_t acl = acl_from_text(item->leftACL().toLatin1());
1306                         if (acl && !acl_valid(acl))
1307                             acl_set_file(rightURL.adjusted(QUrl::StripTrailingSlash).path().toLocal8Bit(), ACL_TYPE_ACCESS, acl);
1308                         if (acl)
1309                             acl_free(acl);
1310                     }
1311 #endif
1312                 }
1313                 break;
1314             default:
1315                 break;
1316             }
1317         } else {
1318             if (job->error() == KIO::ERR_FILE_ALREADY_EXIST && item->task() != TT_DELETE) {
1319                 KIO::RenameDialog_Result result;
1320                 QString newDest;
1321 
1322                 if (autoSkip)
1323                     break;
1324 
1325                 auto *ui = dynamic_cast<KIO::JobUiDelegate *>(job->uiDelegate());
1326                 ui->setWindow(syncDlgWidget);
1327 
1328                 if (item->task() == TT_COPY_TO_LEFT) {
1329                     result = ui->askFileRename(job,
1330                                                i18n("File Already Exists"),
1331                                                rightURL,
1332                                                leftURL,
1333                                                KIO::RenameDialog_Overwrite | KIO::RenameDialog_Skip | KIO::RenameDialog_MultipleItems,
1334                                                newDest,
1335                                                item->rightSize(),
1336                                                item->leftSize(),
1337                                                QDateTime(),
1338                                                QDateTime(),
1339                                                QDateTime::fromTime_t(static_cast<uint>(item->rightDate())),
1340                                                QDateTime::fromTime_t(static_cast<uint>(item->leftDate())));
1341                 } else {
1342                     result = ui->askFileRename(job,
1343                                                i18n("File Already Exists"),
1344                                                leftURL,
1345                                                rightURL,
1346                                                KIO::RenameDialog_Overwrite | KIO::RenameDialog_Skip | KIO::RenameDialog_MultipleItems,
1347                                                newDest,
1348                                                item->leftSize(),
1349                                                item->rightSize(),
1350                                                QDateTime(),
1351                                                QDateTime(),
1352                                                QDateTime::fromTime_t(static_cast<uint>(item->leftDate())),
1353                                                QDateTime::fromTime_t(static_cast<uint>(item->rightDate())));
1354                 }
1355 
1356                 switch (result) {
1357                 case KIO::Result_Rename:
1358                     item->setDestination(newDest);
1359                     executeTask(item);
1360                     inTaskFinished--;
1361                     return;
1362                 case KIO::Result_Overwrite:
1363                     item->setOverWrite();
1364                     executeTask(item);
1365                     inTaskFinished--;
1366                     return;
1367                 case KIO::Result_OverwriteAll:
1368                     overWrite = true;
1369                     executeTask(item);
1370                     inTaskFinished--;
1371                     return;
1372                 case KIO::Result_AutoSkip:
1373                     autoSkip = true;
1374                 case KIO::Result_Skip:
1375                 default:
1376                     break;
1377                 }
1378                 break;
1379             }
1380 
1381             if (job->error() != KIO::ERR_DOES_NOT_EXIST || item->task() != TT_DELETE) {
1382                 if (autoSkip)
1383                     break;
1384 
1385                 QString error;
1386 
1387                 switch (item->task()) {
1388                 case TT_COPY_TO_LEFT:
1389                     error = i18n("Error at copying file %1 to %2.",
1390                                  rightURL.toDisplayString(QUrl::PreferLocalFile),
1391                                  leftURL.toDisplayString(QUrl::PreferLocalFile));
1392                     break;
1393                 case TT_COPY_TO_RIGHT:
1394                     error = i18n("Error at copying file %1 to %2.",
1395                                  leftURL.toDisplayString(QUrl::PreferLocalFile),
1396                                  rightURL.toDisplayString(QUrl::PreferLocalFile));
1397                     break;
1398                 case TT_DELETE:
1399                     error = i18n("Error at deleting file %1.", leftURL.toDisplayString(QUrl::PreferLocalFile));
1400                     break;
1401                 default:
1402                     break;
1403                 }
1404 
1405                 auto *ui = dynamic_cast<KIO::JobUiDelegate *>(job->uiDelegate());
1406                 ui->setWindow(syncDlgWidget);
1407 
1408                 KIO::SkipDialog_Result result = ui->askSkip(job, KIO::SkipDialog_MultipleItems, error);
1409 
1410                 switch (result) {
1411                 case KIO::Result_Cancel:
1412                     executeTask(item); /* simply retry */
1413                     inTaskFinished--;
1414                     return;
1415                 case KIO::Result_AutoSkip:
1416                     autoSkip = true;
1417                 default:
1418                     break;
1419                 }
1420             }
1421         }
1422     } while (false);
1423 
1424     switch (item->task()) {
1425     case TT_COPY_TO_LEFT:
1426         leftCopyNr++;
1427         leftCopySize += item->rightSize() - receivedSize;
1428         break;
1429     case TT_COPY_TO_RIGHT:
1430         rightCopyNr++;
1431         rightCopySize += item->leftSize() - receivedSize;
1432         break;
1433     case TT_DELETE:
1434         deleteNr++;
1435         deleteSize += item->leftSize() - receivedSize;
1436         break;
1437     default:
1438         break;
1439     }
1440 
1441     emit processedSizes(leftCopyNr, leftCopySize, rightCopyNr, rightCopySize, deleteNr, deleteSize);
1442 
1443     if (--inTaskFinished == 0) {
1444         if (paused)
1445             emit pauseAccepted();
1446         else
1447             synchronizeLoop();
1448     }
1449 }
1450 
1451 void Synchronizer::slotProcessedSize(KJob *job, qulonglong size)
1452 {
1453     KIO::filesize_t dl = 0, dr = 0, dd = 0;
1454     SynchronizerFileItem *item = jobMap[job];
1455 
1456     KIO::filesize_t lastProcessedSize = 0;
1457     if (receivedMap.contains(job))
1458         lastProcessedSize = receivedMap[job];
1459 
1460     receivedMap[job] = size;
1461 
1462     switch (item->task()) {
1463     case TT_COPY_TO_LEFT:
1464         dl = size - lastProcessedSize;
1465         break;
1466     case TT_COPY_TO_RIGHT:
1467         dr = size - lastProcessedSize;
1468         break;
1469     case TT_DELETE:
1470         dd = size - lastProcessedSize;
1471         break;
1472     default:
1473         break;
1474     }
1475 
1476     emit processedSizes(leftCopyNr, leftCopySize += dl, rightCopyNr, rightCopySize += dr, deleteNr, deleteSize += dd);
1477 }
1478 
1479 void Synchronizer::pause()
1480 {
1481     paused = true;
1482 }
1483 
1484 void Synchronizer::resume()
1485 {
1486     paused = false;
1487     synchronizeLoop();
1488 }
1489 
1490 QString Synchronizer::leftBaseDirectory()
1491 {
1492     return leftBaseDir;
1493 }
1494 
1495 QString Synchronizer::rightBaseDirectory()
1496 {
1497     return rightBaseDir;
1498 }
1499 
1500 KgetProgressDialog::KgetProgressDialog(QWidget *parent, const QString &caption, const QString &text, bool modal)
1501     : QDialog(parent)
1502 {
1503     if (caption.isEmpty())
1504         setWindowTitle(caption);
1505     setModal(modal);
1506 
1507     auto *mainLayout = new QVBoxLayout;
1508     setLayout(mainLayout);
1509 
1510     mainLayout->addWidget(new QLabel(text));
1511 
1512     mProgressBar = new QProgressBar;
1513     mainLayout->addWidget(mProgressBar);
1514 
1515     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel);
1516     mainLayout->addWidget(buttonBox);
1517 
1518     mPauseButton = new QPushButton(i18n("Pause"));
1519     buttonBox->addButton(mPauseButton, QDialogButtonBox::ActionRole);
1520     buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true);
1521 
1522     connect(mPauseButton, &QPushButton::clicked, this, &KgetProgressDialog::slotPause);
1523     connect(buttonBox, &QDialogButtonBox::rejected, this, &KgetProgressDialog::slotCancel);
1524 
1525     mCancelled = mPaused = false;
1526 }
1527 
1528 void KgetProgressDialog::slotPause()
1529 {
1530     if ((mPaused = !mPaused) == false)
1531         mPauseButton->setText(i18n("Pause"));
1532     else
1533         mPauseButton->setText(i18n("Resume"));
1534 }
1535 
1536 void KgetProgressDialog::slotCancel()
1537 {
1538     mCancelled = true;
1539     reject();
1540 }
1541 
1542 void Synchronizer::synchronizeWithKGet()
1543 {
1544     bool isLeftLocal = QUrl::fromUserInput(leftBaseDirectory(), QString(), QUrl::AssumeLocalFile).isLocalFile();
1545     KgetProgressDialog *progDlg = nullptr;
1546     int processedCount = 0, totalCount = 0;
1547 
1548     QListIterator<SynchronizerFileItem *> it(resultList);
1549     while (it.hasNext())
1550         if (it.next()->isMarked())
1551             totalCount++;
1552 
1553     it.toFront();
1554     while (it.hasNext()) {
1555         SynchronizerFileItem *item = it.next();
1556 
1557         if (item->isMarked()) {
1558             QUrl downloadURL;
1559             QUrl destURL;
1560             QString destDir;
1561             QString leftDirName = item->leftDirectory().isEmpty() ? "" : item->leftDirectory() + '/';
1562             QString rightDirName = item->rightDirectory().isEmpty() ? "" : item->rightDirectory() + '/';
1563 
1564             if (progDlg == nullptr) {
1565                 progDlg = new KgetProgressDialog(krMainWindow, i18n("Krusader::Synchronizer"), i18n("Feeding the URLs to KGet"), true);
1566                 progDlg->progressBar()->setMaximum(totalCount);
1567                 progDlg->show();
1568                 qApp->processEvents();
1569             }
1570 
1571             if (item->task() == TT_COPY_TO_RIGHT && !isLeftLocal) {
1572                 downloadURL = Synchronizer::fsUrl(leftBaseDirectory() + leftDirName + item->leftName());
1573                 destDir = rightBaseDirectory() + rightDirName;
1574                 destURL = Synchronizer::fsUrl(destDir + item->rightName());
1575 
1576                 if (item->isDir())
1577                     destDir += item->leftName();
1578             }
1579             if (item->task() == TT_COPY_TO_LEFT && isLeftLocal) {
1580                 downloadURL = Synchronizer::fsUrl(rightBaseDirectory() + rightDirName + item->rightName());
1581                 destDir = leftBaseDirectory() + leftDirName;
1582                 destURL = Synchronizer::fsUrl(destDir + item->leftName());
1583 
1584                 if (item->isDir())
1585                     destDir += item->rightName();
1586             }
1587 
1588             // creating the directory system
1589             for (int i = 0; i >= 0; i = destDir.indexOf('/', i + 1))
1590                 if (!QDir(destDir.left(i)).exists())
1591                     QDir().mkdir(destDir.left(i));
1592 
1593             if (!item->isDir() && !downloadURL.isEmpty()) {
1594                 if (QFile(destURL.path()).exists())
1595                     QFile(destURL.path()).remove();
1596 
1597                 QString source = downloadURL.toDisplayString();
1598                 if (source.indexOf('@') >= 2) { /* is this an ftp proxy URL? */
1599                     int lastAt = source.lastIndexOf('@');
1600                     QString startString = source.left(lastAt);
1601                     QString endString = source.mid(lastAt);
1602                     startString.replace('@', "%40");
1603                     source = startString + endString;
1604                 }
1605 
1606                 KProcess p;
1607 
1608                 p << KrServices::fullPathName("kget") << source << destURL.path();
1609                 if (!p.startDetached())
1610                     KMessageBox::error(parentWidget, i18n("Error executing %1.", KrServices::fullPathName("kget")));
1611             }
1612 
1613             progDlg->progressBar()->setValue(++processedCount);
1614 
1615             QElapsedTimer t;
1616             t.start();
1617             bool canExit = false;
1618 
1619             do {
1620                 qApp->processEvents();
1621 
1622                 if (progDlg->wasCancelled())
1623                     break;
1624 
1625                 canExit = (t.elapsed() > 100);
1626 
1627                 if (progDlg->isPaused() || !canExit)
1628                     usleep(10000);
1629 
1630             } while (progDlg->isPaused() || !canExit);
1631 
1632             if (progDlg->wasCancelled())
1633                 break;
1634         }
1635     }
1636 
1637     if (progDlg)
1638         delete progDlg;
1639 }
1640 
1641 bool Synchronizer::isDir(const FileItem *file)
1642 {
1643     if (followSymLinks) {
1644         return file->isDir();
1645     } else {
1646         return file->isDir() && !file->isSymLink();
1647     }
1648 }
1649 
1650 QString Synchronizer::readLink(const FileItem *file)
1651 {
1652     if (file->isSymLink())
1653         return file->getSymDest();
1654     else
1655         return QString();
1656 }
1657 
1658 SynchronizerFileItem *Synchronizer::getItemAt(unsigned ndx)
1659 {
1660     if (ndx < (unsigned)resultList.count())
1661         return resultList.at(ndx);
1662     else
1663         return nullptr;
1664 }