File indexing completed on 2024-05-05 10:16:00
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()), ×tamp); 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()), ×tamp); 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 }