File indexing completed on 2024-04-21 05:01:40
0001 /* 0002 This file contains private helper classes for the Smb4KSynchronizer 0003 class. 0004 0005 SPDX-FileCopyrightText: 2008-2023 Alexander Reinholdt <alexander.reinholdt@kdemail.net> 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 // application specific includes 0010 #include "smb4ksynchronizer_p.h" 0011 #include "smb4kglobal.h" 0012 #include "smb4knotification.h" 0013 #include "smb4ksettings.h" 0014 0015 // Qt includes 0016 #include <QLocale> 0017 #include <QRegularExpression> 0018 #include <QStandardPaths> 0019 #include <QTimer> 0020 0021 // KDE includes 0022 #include <KLocalizedString> 0023 0024 using namespace Smb4KGlobal; 0025 0026 Smb4KSyncJob::Smb4KSyncJob(QObject *parent) 0027 : KJob(parent) 0028 , m_process(nullptr) 0029 { 0030 setCapabilities(KJob::Killable); 0031 0032 m_terminated = false; 0033 m_jobTracker = new KUiServerJobTracker(this); 0034 } 0035 0036 Smb4KSyncJob::~Smb4KSyncJob() 0037 { 0038 } 0039 0040 void Smb4KSyncJob::start() 0041 { 0042 QTimer::singleShot(0, this, SLOT(slotStartSynchronization())); 0043 } 0044 0045 void Smb4KSyncJob::setupSynchronization(const QUrl &sourceUrl, const QUrl &destinationUrl) 0046 { 0047 if (sourceUrl.isValid() && !sourceUrl.isEmpty() && destinationUrl.isValid() && !destinationUrl.isEmpty()) { 0048 m_sourceUrl = sourceUrl; 0049 m_destinationUrl = destinationUrl; 0050 } 0051 } 0052 0053 bool Smb4KSyncJob::doKill() 0054 { 0055 if (m_process && m_process->state() != KProcess::NotRunning) { 0056 m_process->terminate(); 0057 m_terminated = true; 0058 } 0059 0060 return KJob::doKill(); 0061 } 0062 0063 void Smb4KSyncJob::slotStartSynchronization() 0064 { 0065 if (m_sourceUrl.isEmpty() || m_destinationUrl.isEmpty()) { 0066 emitResult(); 0067 return; 0068 } 0069 0070 QString rsync = QStandardPaths::findExecutable(QStringLiteral("rsync")); 0071 0072 if (rsync.isEmpty()) { 0073 Smb4KNotification::commandNotFound(QStringLiteral("rsync")); 0074 emitResult(); 0075 return; 0076 } 0077 0078 QDir destinationDirectory(m_destinationUrl.path()); 0079 0080 if (!destinationDirectory.exists()) { 0081 if (!QDir().mkpath(destinationDirectory.path())) { 0082 Smb4KNotification::mkdirFailed(destinationDirectory); 0083 emitResult(); 0084 return; 0085 } 0086 } 0087 0088 // 0089 // The command 0090 // 0091 QStringList command; 0092 command << rsync; 0093 command << QStringLiteral("--progress"); 0094 command << QStringLiteral("--info=progress2"); 0095 0096 // 0097 // Basic settings 0098 // 0099 if (Smb4KSettings::archiveMode()) { 0100 command << QStringLiteral("--archive"); 0101 } 0102 0103 if (Smb4KSettings::recurseIntoDirectories()) { 0104 command << QStringLiteral("--recursive"); 0105 } 0106 0107 if (Smb4KSettings::relativePathNames()) { 0108 command << QStringLiteral("--relative"); 0109 } 0110 0111 if (Smb4KSettings::noImpliedDirectories()) { 0112 command << QStringLiteral("--no-implied-dirs"); 0113 } 0114 0115 if (Smb4KSettings::transferDirectories()) { 0116 command << QStringLiteral("--dirs"); 0117 } 0118 0119 if (Smb4KSettings::makeBackups()) { 0120 command << QStringLiteral("--backup"); 0121 0122 if (Smb4KSettings::useBackupDirectory()) { 0123 command << QStringLiteral("--backup-dir=") + Smb4KSettings::backupDirectory().path(); 0124 } 0125 0126 if (Smb4KSettings::useBackupSuffix()) { 0127 command << QStringLiteral("--suffix=") + Smb4KSettings::backupSuffix(); 0128 } 0129 } 0130 0131 // 0132 // File handling 0133 // 0134 if (Smb4KSettings::updateTarget()) { 0135 command << QStringLiteral("--update"); 0136 } 0137 0138 if (Smb4KSettings::updateInPlace()) { 0139 command << QStringLiteral("--inplace"); 0140 } 0141 0142 if (Smb4KSettings::efficientSparseFileHandling()) { 0143 command << QStringLiteral("--sparse"); 0144 } 0145 0146 if (Smb4KSettings::copyFilesWhole()) { 0147 command << QStringLiteral("--whole-file"); 0148 } 0149 0150 if (Smb4KSettings::updateExisting()) { 0151 command << QStringLiteral("--existing"); 0152 } 0153 0154 if (Smb4KSettings::ignoreExisting()) { 0155 command << QStringLiteral("--ignore-existing"); 0156 } 0157 0158 if (Smb4KSettings::preserveSymlinks()) { 0159 command << QStringLiteral("--links"); 0160 } 0161 0162 if (Smb4KSettings::transformSymlinks()) { 0163 command << QStringLiteral("--copy-links"); 0164 } 0165 0166 if (Smb4KSettings::transformUnsafeSymlinks()) { 0167 command << QStringLiteral("--copy-unsafe-links"); 0168 } 0169 0170 if (Smb4KSettings::ignoreUnsafeSymlinks()) { 0171 command << QStringLiteral("--safe-links"); 0172 } 0173 0174 if (Smb4KSettings::mungeSymlinks()) { 0175 command << QStringLiteral("--munge-links"); 0176 } 0177 0178 if (Smb4KSettings::preserveHardLinks()) { 0179 command << QStringLiteral("--hard-links"); 0180 } 0181 0182 if (Smb4KSettings::copyDirectorySymlinks()) { 0183 command << QStringLiteral("--copy-dirlinks"); 0184 } 0185 0186 if (Smb4KSettings::keepDirectorySymlinks()) { 0187 command << QStringLiteral("--keep-dirlinks"); 0188 } 0189 0190 if (Smb4KSettings::preservePermissions()) { 0191 command << QStringLiteral("--perms"); 0192 } 0193 0194 if (Smb4KSettings::preserveACLs()) { 0195 command << QStringLiteral("--acls"); 0196 } 0197 0198 if (Smb4KSettings::preserveExtendedAttributes()) { 0199 command << QStringLiteral("--xattrs"); 0200 } 0201 0202 if (Smb4KSettings::preserveAccessTimes()) { 0203 command << QStringLiteral("--atimes"); 0204 } 0205 0206 if (Smb4KSettings::preserveCreateTimes()) { 0207 command << QStringLiteral("--crtimes"); 0208 } 0209 0210 if (Smb4KSettings::preserveOwner()) { 0211 command << QStringLiteral("--owner"); 0212 } 0213 0214 if (Smb4KSettings::preserveGroup()) { 0215 command << QStringLiteral("--group"); 0216 } 0217 0218 if (Smb4KSettings::preserveDevicesAndSpecials()) { 0219 // Alias -D 0220 command << QStringLiteral("--devices"); 0221 command << QStringLiteral("--specials"); 0222 } 0223 0224 if (Smb4KSettings::preserveTimes()) { 0225 command << QStringLiteral("--times"); 0226 } 0227 0228 if (Smb4KSettings::omitDirectoryTimes()) { 0229 command << QStringLiteral("--omit-dir-times"); 0230 } 0231 0232 // 0233 // File transfer 0234 // 0235 if (Smb4KSettings::compressData()) { 0236 command << QStringLiteral("--compress"); 0237 0238 if (Smb4KSettings::useCompressionLevel()) { 0239 command << QStringLiteral("--compress-level=") + QString::number(Smb4KSettings::compressionLevel()); 0240 } 0241 0242 if (Smb4KSettings::useSkipCompression()) { 0243 command << QStringLiteral("--skip-compress=") + Smb4KSettings::skipCompression(); 0244 } 0245 } 0246 0247 if (Smb4KSettings::self()->useMaximalTransferSize()) { 0248 command << QStringLiteral("--max-size=") + QString::number(Smb4KSettings::self()->maximalTransferSize()) + QStringLiteral("kB"); 0249 } 0250 0251 if (Smb4KSettings::self()->useMinimalTransferSize()) { 0252 command << QStringLiteral("--min-size=") + QString::number(Smb4KSettings::self()->minimalTransferSize()) + QStringLiteral("kB"); 0253 } 0254 0255 if (Smb4KSettings::keepPartial()) { 0256 command << QStringLiteral(" --partial"); 0257 0258 if (Smb4KSettings::usePartialDirectory()) { 0259 command << QStringLiteral("--partial-dir=") + Smb4KSettings::partialDirectory().path(); 0260 } 0261 } 0262 0263 if (Smb4KSettings::useBandwidthLimit()) { 0264 command << QStringLiteral("--bwlimit=") + QString::number(Smb4KSettings::bandwidthLimit()) + QStringLiteral("kB"); 0265 } 0266 0267 // 0268 // File deletion 0269 // 0270 if (Smb4KSettings::removeSourceFiles()) { 0271 command << QStringLiteral("--remove-source-files"); 0272 } 0273 0274 if (Smb4KSettings::deleteExtraneous()) { 0275 command << QStringLiteral("--delete"); 0276 } 0277 0278 if (Smb4KSettings::deleteBefore()) { 0279 command << QStringLiteral("--delete-before"); 0280 } 0281 0282 if (Smb4KSettings::deleteDuring()) { 0283 command << QStringLiteral("--delete-during"); 0284 } 0285 0286 if (Smb4KSettings::deleteAfter()) { 0287 command << QStringLiteral("--delete-after"); 0288 } 0289 0290 if (Smb4KSettings::deleteExcluded()) { 0291 command << QStringLiteral("--delete-excluded"); 0292 } 0293 0294 if (Smb4KSettings::ignoreErrors()) { 0295 command << QStringLiteral("--ignore-errors"); 0296 } 0297 0298 if (Smb4KSettings::forceDirectoryDeletion()) { 0299 command << QStringLiteral("--force"); 0300 } 0301 0302 if (Smb4KSettings::useMaximumDelete()) { 0303 command << QStringLiteral("--max-delete=") + QString::number(Smb4KSettings::maximumDeleteValue()); 0304 } 0305 0306 // 0307 // Filtering 0308 // 0309 if (Smb4KSettings::useCVSExclude()) { 0310 command << QStringLiteral("--cvs-exclude"); 0311 } 0312 0313 if (Smb4KSettings::useExcludePattern()) { 0314 command << QStringLiteral("--exclude=") + Smb4KSettings::excludePattern(); 0315 } 0316 0317 if (Smb4KSettings::useExcludeFrom()) { 0318 command << QStringLiteral("--exclude-from=") + Smb4KSettings::excludeFrom().path(); 0319 } 0320 0321 if (Smb4KSettings::useIncludePattern()) { 0322 command << QStringLiteral("--include=") + Smb4KSettings::includePattern(); 0323 } 0324 0325 if (Smb4KSettings::useIncludeFrom()) { 0326 command << QStringLiteral("--include-from=") + Smb4KSettings::includeFrom().path(); 0327 } 0328 0329 if (Smb4KSettings::useCustomFilteringRules()) { 0330 if (!Smb4KSettings::customFilteringRules().isEmpty()) { 0331 qDebug() << "Do we need to spilt the filtering rules into a list?"; 0332 command << Smb4KSettings::customFilteringRules(); 0333 } 0334 } 0335 0336 if (Smb4KSettings::useFFilterRule()) { 0337 command << QStringLiteral("-F"); 0338 } 0339 0340 if (Smb4KSettings::useFFFilterRule()) { 0341 command << QStringLiteral("-F"); 0342 command << QStringLiteral("-F"); 0343 } 0344 0345 // 0346 // Miscellaneous 0347 // 0348 if (Smb4KSettings::useBlockSize()) { 0349 command << QStringLiteral("--block-size=") + QString::number(Smb4KSettings::blockSize()); 0350 } 0351 0352 if (Smb4KSettings::useChecksumSeed()) { 0353 command << QStringLiteral("--checksum-seed=") + QString::number(Smb4KSettings::checksumSeed()); 0354 } 0355 0356 if (Smb4KSettings::useChecksum()) { 0357 command << QStringLiteral("--checksum"); 0358 } 0359 0360 if (Smb4KSettings::oneFileSystem()) { 0361 command << QStringLiteral("--one-file-system"); 0362 } 0363 0364 if (Smb4KSettings::delayUpdates()) { 0365 command << QStringLiteral("--delay-updates"); 0366 } 0367 0368 // Make sure that the trailing slash is present. rsync is very 0369 // picky regarding it. 0370 QString source = m_sourceUrl.path() + (!m_sourceUrl.path().endsWith(QStringLiteral("/")) ? QStringLiteral("/") : QStringLiteral("")); 0371 QString destination = m_destinationUrl.path() + (!m_destinationUrl.path().endsWith(QStringLiteral("/")) ? QStringLiteral("/") : QStringLiteral("")); 0372 0373 command << source; 0374 command << destination; 0375 0376 // 0377 // The job tracker 0378 // 0379 m_jobTracker->registerJob(this); 0380 connect(this, &Smb4KSyncJob::result, m_jobTracker, &KUiServerJobTracker::unregisterJob); 0381 0382 // 0383 // The process 0384 // 0385 m_process = new KProcess(this); 0386 m_process->setOutputChannelMode(KProcess::SeparateChannels); 0387 m_process->setProgram(command); 0388 0389 connect(m_process, SIGNAL(readyReadStandardOutput()), SLOT(slotReadStandardOutput())); 0390 connect(m_process, SIGNAL(readyReadStandardError()), SLOT(slotReadStandardError())); 0391 connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(slotProcessFinished(int, QProcess::ExitStatus))); 0392 0393 // Start the synchronization process 0394 Q_EMIT aboutToStart(m_destinationUrl.path()); 0395 0396 // Send description to the GUI 0397 Q_EMIT description(this, i18n("Synchronizing"), qMakePair(i18n("Source"), source), qMakePair(i18n("Destination"), destination)); 0398 0399 // Dummy to show 0 % 0400 emitPercent(0, 100); 0401 0402 m_terminated = false; 0403 m_process->start(); 0404 } 0405 0406 void Smb4KSyncJob::slotReadStandardOutput() 0407 { 0408 QStringList stdOut = QString::fromUtf8(m_process->readAllStandardOutput()).split(QStringLiteral("\r"), Qt::SkipEmptyParts); 0409 0410 for (const QString &line : stdOut) { 0411 if (line.contains(QStringLiteral("%"))) { 0412 bool success = true; 0413 QString transferInfo = line.trimmed().simplified(); 0414 0415 // Overall progress 0416 QString progressString = transferInfo.section(QStringLiteral(" "), 1, 1).replace(QStringLiteral("%"), QStringLiteral("")); 0417 0418 if (!progressString.isEmpty()) { 0419 qulonglong progress = progressString.toLongLong(&success); 0420 0421 if (success) { 0422 setPercent(progress); 0423 } 0424 } 0425 0426 // Speed 0427 QRegularExpression expression(QStringLiteral("../s")); 0428 QString speedString = transferInfo.section(QStringLiteral(" "), 2, 2).section(expression, 0, 0); 0429 0430 if (!speedString.isEmpty()) { 0431 QLocale locale; 0432 double speed = locale.toDouble(speedString, &success); 0433 0434 if (success) { 0435 // MB == 1000000 B and kB == 1000 B per definition! 0436 if (transferInfo.contains(QStringLiteral("MB/s"))) { 0437 speed *= 1e6; 0438 } else if (transferInfo.contains(QStringLiteral("kB/s"))) { 0439 speed *= 1e3; 0440 } 0441 0442 emitSpeed((ulong)speed); 0443 } 0444 } 0445 0446 // Transfered files 0447 QString transferedFilesString = transferInfo.section(QStringLiteral("xfr#"), 1, 1).section(QStringLiteral(","), 0, 0); 0448 0449 if (!transferedFilesString.isEmpty()) { 0450 qulonglong transferedFiles = transferedFilesString.toULongLong(&success); 0451 0452 if (success) { 0453 setProcessedAmount(KJob::Files, transferedFiles); 0454 } 0455 } 0456 0457 // Total amount of files 0458 QString totalFilesString = transferInfo.section(QStringLiteral("/"), -1, -1).section(QStringLiteral(")"), 0, 0); 0459 0460 if (!totalFilesString.isEmpty()) { 0461 qulonglong totalFiles = totalFilesString.toULongLong(&success); 0462 0463 if (success) { 0464 setTotalAmount(KJob::Files, totalFiles); 0465 } 0466 } 0467 0468 } else if (!line.contains(QStringLiteral("sending incremental file list"))) { 0469 QString relativePath = line.trimmed().simplified(); 0470 0471 QUrl sourceUrl = m_sourceUrl; 0472 sourceUrl.setPath(QDir::cleanPath(sourceUrl.path() + QStringLiteral("/") + relativePath)); 0473 0474 QUrl destinationUrl = m_destinationUrl; 0475 destinationUrl.setPath(QDir::cleanPath(destinationUrl.path() + QStringLiteral("/") + relativePath)); 0476 0477 // Send description to the GUI 0478 Q_EMIT description(this, i18n("Synchronizing"), qMakePair(i18n("Source"), sourceUrl.path()), qMakePair(i18n("Destination"), destinationUrl.path())); 0479 } 0480 } 0481 } 0482 0483 void Smb4KSyncJob::slotReadStandardError() 0484 { 0485 if (!m_terminated) { 0486 QString stdErr = QString::fromUtf8(m_process->readAllStandardError()).trimmed(); 0487 Smb4KNotification::synchronizationFailed(m_sourceUrl, m_destinationUrl, stdErr); 0488 } 0489 } 0490 0491 void Smb4KSyncJob::slotProcessFinished(int, QProcess::ExitStatus status) 0492 { 0493 // Dummy to show 100 % 0494 emitPercent(100, 100); 0495 0496 // Handle error. 0497 switch (status) { 0498 case QProcess::CrashExit: { 0499 Smb4KNotification::processError(m_process->error()); 0500 break; 0501 } 0502 default: { 0503 break; 0504 } 0505 } 0506 0507 // Finish job 0508 emitResult(); 0509 Q_EMIT finished(m_destinationUrl.path()); 0510 }