File indexing completed on 2024-04-21 05:49:59
0001 //************************************************************************** 0002 // Copyright 2006 - 2022 Martin Koller, kollix@aon.at 0003 // 0004 // This program is free software; you can redistribute it and/or modify 0005 // it under the terms of the GNU General Public License as published by 0006 // the Free Software Foundation, version 2 of the License 0007 // 0008 //************************************************************************** 0009 0010 #include <Archiver.hxx> 0011 #include <archive.h> 0012 #include <archive_entry.h> 0013 0014 #include <KFilterBase> 0015 #include <kio/job.h> 0016 #include <kio/jobuidelegate.h> 0017 #include <KProcess> 0018 #include <KMountPoint> 0019 #include <KLocalizedString> 0020 #include <KMessageBox> 0021 #include <KIO/ListJob> 0022 0023 #include <QApplication> 0024 #include <QDir> 0025 #include <QFileInfo> 0026 #include <QCursor> 0027 #include <QTextStream> 0028 #include <QFileDialog> 0029 #include <QTemporaryFile> 0030 #include <QTimer> 0031 #include <QElapsedTimer> 0032 0033 #include <sys/types.h> 0034 #include <sys/stat.h> 0035 #include <unistd.h> 0036 #include <string.h> 0037 #include <cerrno> 0038 #include <sys/statvfs.h> 0039 0040 // For INT64_MAX: 0041 // The ISO C99 standard specifies that in C++ implementations these 0042 // macros (stdint.h,inttypes.h) should only be defined if explicitly requested. 0043 0044 // ISO C99: 7.18 Integer types 0045 #ifndef __STDC_LIMIT_MACROS 0046 #define __STDC_LIMIT_MACROS 0047 #endif 0048 #include <stdint.h> 0049 0050 0051 #include <iostream> 0052 0053 //-------------------------------------------------------------------------------- 0054 0055 QString Archiver::sliceScript; 0056 Archiver *Archiver::instance; 0057 0058 const KIO::filesize_t MAX_SLICE = INT64_MAX; // 64bit max value 0059 0060 //-------------------------------------------------------------------------------- 0061 0062 Archiver::Archiver(QWidget *parent) 0063 : QObject(parent), 0064 sliceCapacity(MAX_SLICE), interactive(parent != nullptr) 0065 { 0066 instance = this; 0067 0068 setCompressFiles(false); 0069 0070 if ( !interactive ) 0071 { 0072 connect(this, &Archiver::logging, this, &Archiver::loggingSlot); 0073 connect(this, &Archiver::warning, this, &Archiver::warningSlot); 0074 } 0075 } 0076 0077 //-------------------------------------------------------------------------------- 0078 0079 void Archiver::setCompressFiles(bool b) 0080 { 0081 if ( b ) 0082 { 0083 ext = QStringLiteral(".xz"); 0084 compressionType = KCompressionDevice::Xz; 0085 KFilterBase *base = KCompressionDevice::filterForCompressionType(compressionType); 0086 if ( !base ) 0087 { 0088 ext = QStringLiteral(".bz2"); 0089 compressionType = KCompressionDevice::BZip2; 0090 base = KCompressionDevice::filterForCompressionType(compressionType); 0091 if ( !base ) 0092 { 0093 ext = QStringLiteral(".gz"); 0094 compressionType = KCompressionDevice::GZip; 0095 } 0096 } 0097 0098 delete base; 0099 } 0100 else 0101 { 0102 ext.clear(); 0103 compressionType = KCompressionDevice::None; 0104 } 0105 } 0106 0107 //-------------------------------------------------------------------------------- 0108 0109 void Archiver::setTarget(const QUrl &target) 0110 { 0111 targetURL = target; 0112 calculateCapacity(); 0113 } 0114 0115 //-------------------------------------------------------------------------------- 0116 0117 void Archiver::setMaxSliceMBs(int mbs) 0118 { 0119 maxSliceMBs = mbs; 0120 calculateCapacity(); 0121 } 0122 0123 //-------------------------------------------------------------------------------- 0124 0125 void Archiver::setKeptBackups(int num) 0126 { 0127 numKeptBackups = num; 0128 } 0129 0130 //-------------------------------------------------------------------------------- 0131 0132 void Archiver::setFilter(const QString &filter) 0133 { 0134 filters.clear(); 0135 const QStringList list = filter.split(QLatin1Char(' '), Qt::SkipEmptyParts); 0136 filters.reserve(list.count()); 0137 for (const QString &str : list) 0138 filters.append(QRegExp(str, Qt::CaseSensitive, QRegExp::Wildcard)); 0139 } 0140 0141 //-------------------------------------------------------------------------------- 0142 0143 QString Archiver::getFilter() const 0144 { 0145 QString filter; 0146 for (const QRegExp ® : std::as_const(filters)) 0147 { 0148 filter += reg.pattern(); 0149 filter += QLatin1Char(' '); 0150 } 0151 return filter; 0152 } 0153 0154 //-------------------------------------------------------------------------------- 0155 0156 void Archiver::setDirFilter(const QString &filter) 0157 { 0158 dirFilters.clear(); 0159 const QStringList list = filter.split(QLatin1Char('\n'), Qt::SkipEmptyParts); 0160 for (const QString &str : list) 0161 { 0162 QString expr = str.trimmed(); 0163 if ( !expr.isEmpty() ) 0164 dirFilters.append(QRegExp(expr, Qt::CaseSensitive, QRegExp::Wildcard)); 0165 } 0166 } 0167 0168 //-------------------------------------------------------------------------------- 0169 0170 QString Archiver::getDirFilter() const 0171 { 0172 QString filter; 0173 for (const QRegExp ® : std::as_const(dirFilters)) 0174 { 0175 filter += reg.pattern(); 0176 filter += QLatin1Char('\n'); 0177 } 0178 return filter; 0179 } 0180 0181 //-------------------------------------------------------------------------------- 0182 0183 void Archiver::setFullBackupInterval(int days) 0184 { 0185 fullBackupInterval = days; 0186 0187 if ( fullBackupInterval == 1 ) 0188 { 0189 setIncrementalBackup(false); 0190 lastFullBackup = QDateTime(); 0191 lastBackup = QDateTime(); 0192 } 0193 } 0194 0195 //-------------------------------------------------------------------------------- 0196 0197 void Archiver::setForceFullBackup(bool force) 0198 { 0199 forceFullBackup = force; 0200 Q_EMIT backupTypeChanged(isIncrementalBackup()); 0201 } 0202 0203 //-------------------------------------------------------------------------------- 0204 0205 void Archiver::setIncrementalBackup(bool inc) 0206 { 0207 incrementalBackup = inc; 0208 Q_EMIT backupTypeChanged(isIncrementalBackup()); 0209 } 0210 0211 //-------------------------------------------------------------------------------- 0212 0213 void Archiver::resetBackupCycle() 0214 { 0215 lastFullBackup = QDateTime(); 0216 lastBackup = QDateTime(); 0217 setIncrementalBackup(false); 0218 } 0219 0220 //-------------------------------------------------------------------------------- 0221 0222 void Archiver::setFilePrefix(const QString &prefix) 0223 { 0224 filePrefix = prefix; 0225 } 0226 0227 //-------------------------------------------------------------------------------- 0228 0229 void Archiver::calculateCapacity() 0230 { 0231 if ( targetURL.isEmpty() ) return; 0232 0233 // calculate how large a slice can actually be 0234 // - limited by the target directory (when we store directly into a local dir) 0235 // - limited by the "tmp" dir when we create a tmp file for later upload via KIO 0236 // - limited by Qt (64bit int) 0237 // - limited by user defined maxSliceMBs 0238 0239 KIO::filesize_t totalBytes = 0; 0240 0241 if ( targetURL.isLocalFile() ) 0242 { 0243 if ( ! getDiskFree(targetURL.path(), totalBytes, sliceCapacity) ) 0244 return; 0245 } 0246 else 0247 { 0248 getDiskFree(QDir::tempPath() + QLatin1Char('/'), totalBytes, sliceCapacity); 0249 // as "tmp" is also used by others and by us when compressing a file, 0250 // don't eat it up completely. Reserve 10% 0251 sliceCapacity = sliceCapacity * 9 / 10; 0252 } 0253 0254 // limit to what Qt can handle 0255 sliceCapacity = qMin(sliceCapacity, MAX_SLICE); 0256 0257 if ( maxSliceMBs != UNLIMITED ) 0258 { 0259 KIO::filesize_t max = static_cast<KIO::filesize_t>(maxSliceMBs) * 1024 * 1024; 0260 sliceCapacity = qMin(sliceCapacity, max); 0261 } 0262 0263 sliceBytes = 0; 0264 0265 // if the disk is full (capacity == 0), don't tell the user "unlimited" 0266 // sliceCapacity == 0 has a special meaning as "unlimited"; see MainWidget.cxx 0267 if ( sliceCapacity == 0 ) sliceCapacity = 1; 0268 Q_EMIT targetCapacity(sliceCapacity); 0269 } 0270 0271 //-------------------------------------------------------------------------------- 0272 0273 bool Archiver::loadProfile(const QString &fileName, QStringList &includes, QStringList &excludes, QString &error) 0274 { 0275 QFile file(fileName); 0276 if ( ! file.open(QIODevice::ReadOnly) ) 0277 { 0278 error = file.errorString(); 0279 return false; 0280 } 0281 0282 loadedProfile = fileName; 0283 0284 QString target; 0285 QChar type, blank; 0286 QTextStream stream(&file); 0287 0288 // back to default (in case old profile read which does not include these) 0289 setFilePrefix(QString()); 0290 setMaxSliceMBs(Archiver::UNLIMITED); 0291 setFullBackupInterval(1); // default as in previous versions 0292 filters.clear(); 0293 dirFilters.clear(); 0294 0295 while ( ! stream.atEnd() ) 0296 { 0297 stream.skipWhiteSpace(); 0298 stream >> type; // read a QChar without skipping whitespace 0299 stream >> blank; // read a QChar without skipping whitespace 0300 0301 if ( type == QLatin1Char('M') ) 0302 { 0303 target = stream.readLine(); // include white space 0304 } 0305 else if ( type == QLatin1Char('P') ) 0306 { 0307 QString prefix = stream.readLine(); // include white space 0308 setFilePrefix(prefix); 0309 } 0310 else if ( type == QLatin1Char('R') ) 0311 { 0312 int max; 0313 stream >> max; 0314 setKeptBackups(max); 0315 } 0316 else if ( type == QLatin1Char('F') ) 0317 { 0318 int days; 0319 stream >> days; 0320 setFullBackupInterval(days); 0321 } 0322 else if ( type == QLatin1Char('B') ) // last dateTime for backup 0323 { 0324 QString dateTime; 0325 stream >> dateTime; 0326 lastBackup = QDateTime::fromString(dateTime, Qt::ISODate); 0327 } 0328 else if ( type == QLatin1Char('L') ) // last dateTime for full backup 0329 { 0330 QString dateTime; 0331 stream >> dateTime; 0332 lastFullBackup = QDateTime::fromString(dateTime, Qt::ISODate); 0333 } 0334 else if ( type == QLatin1Char('S') ) 0335 { 0336 int max; 0337 stream >> max; 0338 setMaxSliceMBs(max); 0339 } 0340 else if ( type == QLatin1Char('C') ) 0341 { 0342 int change; 0343 stream >> change; 0344 setMediaNeedsChange(change); 0345 } 0346 else if ( type == QLatin1Char('X') ) 0347 { 0348 setFilter(stream.readLine()); // include white space 0349 } 0350 else if ( type == QLatin1Char('x') ) 0351 { 0352 dirFilters.append(QRegExp(stream.readLine(), Qt::CaseSensitive, QRegExp::Wildcard)); 0353 } 0354 else if ( type == QLatin1Char('Z') ) 0355 { 0356 int compress; 0357 stream >> compress; 0358 setCompressFiles(compress); 0359 } 0360 else if ( type == QLatin1Char('I') ) 0361 { 0362 includes.append(stream.readLine()); 0363 } 0364 else if ( type == QLatin1Char('E') ) 0365 { 0366 excludes.append(stream.readLine()); 0367 } 0368 else 0369 stream.readLine(); // skip unknown key and rest of line 0370 } 0371 0372 file.close(); 0373 0374 setTarget(QUrl::fromUserInput(target)); 0375 0376 setIncrementalBackup( 0377 (fullBackupInterval > 1) && lastFullBackup.isValid() && 0378 (lastFullBackup.daysTo(QDateTime::currentDateTime()) < fullBackupInterval)); 0379 0380 return true; 0381 } 0382 0383 //-------------------------------------------------------------------------------- 0384 0385 bool Archiver::saveProfile(const QString &fileName, const QStringList &includes, const QStringList &excludes, QString &error) 0386 { 0387 QFile file(fileName); 0388 0389 if ( ! file.open(QIODevice::WriteOnly) ) 0390 { 0391 error = file.errorString(); 0392 return false; 0393 } 0394 0395 QTextStream stream(&file); 0396 0397 stream << "M " << targetURL.toString(QUrl::PreferLocalFile) << QLatin1Char('\n'); 0398 stream << "P " << getFilePrefix() << QLatin1Char('\n'); 0399 stream << "S " << getMaxSliceMBs() << QLatin1Char('\n'); 0400 stream << "R " << getKeptBackups() << QLatin1Char('\n'); 0401 stream << "F " << getFullBackupInterval() << QLatin1Char('\n'); 0402 0403 if ( getLastFullBackup().isValid() ) 0404 stream << "L " << getLastFullBackup().toString(Qt::ISODate) << QLatin1Char('\n'); 0405 0406 if ( getLastBackup().isValid() ) 0407 stream << "B " << getLastBackup().toString(Qt::ISODate) << QLatin1Char('\n'); 0408 0409 stream << "C " << static_cast<int>(getMediaNeedsChange()) << QLatin1Char('\n'); 0410 stream << "Z " << static_cast<int>(getCompressFiles()) << QLatin1Char('\n'); 0411 0412 if ( !filters.isEmpty() ) 0413 stream << "X " << getFilter() << QLatin1Char('\n'); 0414 0415 for (const QRegExp &exp : std::as_const(dirFilters)) 0416 stream << "x " << exp.pattern() << QLatin1Char('\n'); 0417 0418 for (const QString &str : includes) 0419 stream << "I " << str << QLatin1Char('\n'); 0420 0421 for (const QString &str : excludes) 0422 stream << "E " << str << QLatin1Char('\n'); 0423 0424 file.close(); 0425 return true; 0426 } 0427 0428 //-------------------------------------------------------------------------------- 0429 0430 bool Archiver::createArchive(const QStringList &includes, const QStringList &excludes) 0431 { 0432 if ( includes.isEmpty() ) 0433 { 0434 Q_EMIT warning(i18n("Nothing selected for backup")); 0435 return false; 0436 } 0437 0438 if ( !targetURL.isValid() ) 0439 { 0440 Q_EMIT warning(i18n("The target dir '%1' is not valid", targetURL.toString())); 0441 return false; 0442 } 0443 0444 // non-interactive mode only allows local targets as KIO needs $DISPLAY 0445 if ( !interactive && !targetURL.isLocalFile() ) 0446 { 0447 Q_EMIT warning(i18n("The target dir '%1' must be a local file system dir and no remote URL", 0448 targetURL.toString())); 0449 return false; 0450 } 0451 0452 // check if the target dir exists and optionally create it 0453 if ( targetURL.isLocalFile() ) 0454 { 0455 QDir dir(targetURL.path()); 0456 if ( !dir.exists() ) 0457 { 0458 if ( !interactive || 0459 (KMessageBox::warningTwoActions(static_cast<QWidget*>(parent()), 0460 i18n("The target directory '%1' does not exist.\n\n" 0461 "Shall I create it?", dir.absolutePath()), 0462 i18nc("@title", "Create Directory"), 0463 KGuiItem(i18nc("@action:button", "Create")), 0464 KStandardGuiItem::cancel()) == KMessageBox::PrimaryAction) ) 0465 { 0466 if ( !dir.mkpath(QStringLiteral(".")) ) 0467 { 0468 Q_EMIT warning(i18n("Could not create the target directory '%1'.\n" 0469 "The operating system reports: %2", dir.absolutePath(), 0470 QString::fromLocal8Bit(strerror(errno)))); 0471 return false; 0472 } 0473 } 0474 else 0475 { 0476 Q_EMIT warning(i18n("The target dir does not exist")); 0477 return false; 0478 } 0479 } 0480 } 0481 0482 excludeDirs.clear(); 0483 excludeFiles.clear(); 0484 0485 // build map for directories and files to be excluded for fast lookup 0486 for (const QString &name : excludes) 0487 { 0488 QFileInfo info(name); 0489 0490 if ( !info.isSymLink() && info.isDir() ) 0491 excludeDirs.insert(name); 0492 else 0493 excludeFiles.insert(name); 0494 } 0495 0496 baseName = QString(); 0497 sliceNum = 0; 0498 totalBytes = 0; 0499 totalFiles = 0; 0500 filteredFiles = 0; 0501 cancelled = false; 0502 skippedFiles = false; 0503 sliceList.clear(); 0504 0505 QDateTime startTime = QDateTime::currentDateTime(); 0506 0507 runs = true; 0508 Q_EMIT inProgress(true); 0509 0510 QTimer runTimer; 0511 if ( interactive ) // else we do not need to be interrupted during the backup 0512 { 0513 connect(&runTimer, &QTimer::timeout, this, &Archiver::updateElapsed); 0514 runTimer.start(1000); 0515 } 0516 elapsed.start(); 0517 0518 if ( ! getNextSlice() ) 0519 { 0520 runs = false; 0521 Q_EMIT inProgress(false); 0522 0523 return false; 0524 } 0525 0526 for (QStringList::const_iterator it = includes.constBegin(); !cancelled && (it != includes.constEnd()); ++it) 0527 { 0528 QString entry = *it; 0529 0530 if ( (entry.length() > 1) && entry.endsWith(QLatin1Char('/')) ) 0531 entry.chop(1); 0532 0533 QFileInfo info(entry); 0534 0535 if ( !info.isSymLink() && info.isDir() ) 0536 { 0537 QDir dir(info.absoluteFilePath()); 0538 addDirFiles(dir); 0539 } 0540 else 0541 addFile(QFileInfo(info.absoluteFilePath())); 0542 } 0543 0544 finishSlice(); 0545 0546 // reduce the number of old backups to the defined number 0547 if ( !cancelled && (numKeptBackups != UNLIMITED) ) 0548 { 0549 Q_EMIT logging(i18n("...reducing number of kept archives to max. %1", numKeptBackups)); 0550 0551 if ( !targetURL.isLocalFile() ) // KIO needs $DISPLAY; non-interactive only allowed for local targets 0552 { 0553 QPointer<KIO::ListJob> listJob; 0554 listJob = KIO::listDir(targetURL, KIO::DefaultFlags, KIO::ListJob::ListFlag{}); 0555 0556 connect(listJob.data(), &KIO::ListJob::entries, 0557 this, &Archiver::slotListResult); 0558 0559 while ( listJob ) 0560 qApp->processEvents(QEventLoop::WaitForMoreEvents); 0561 } 0562 else // non-intercative. create UDSEntryList on our own 0563 { 0564 QDir dir(targetURL.path()); 0565 targetDirList.clear(); 0566 const auto entryList = dir.entryList(); 0567 for (const QString &fileName : entryList) 0568 { 0569 KIO::UDSEntry entry; 0570 entry.fastInsert(KIO::UDSEntry::UDS_NAME, fileName); 0571 targetDirList.append(entry); 0572 } 0573 jobResult = 0; 0574 } 0575 0576 if ( jobResult == 0 ) 0577 { 0578 std::sort(targetDirList.begin(), targetDirList.end(), Archiver::UDSlessThan); 0579 QString prefix = filePrefix.isEmpty() ? QStringLiteral("backup_") : (filePrefix + QLatin1String("_")); 0580 0581 QString sliceName; 0582 int num = 0; 0583 0584 for (const KIO::UDSEntry &entry : std::as_const(targetDirList)) 0585 { 0586 QString entryName = entry.stringValue(KIO::UDSEntry::UDS_NAME); 0587 0588 if ( entryName.startsWith(prefix) && // only matching current profile 0589 entryName.endsWith(QLatin1String(".tar")) ) // just to be sure 0590 { 0591 if ( (num < numKeptBackups) && 0592 (sliceName.isEmpty() || 0593 !entryName.startsWith(sliceName)) ) // whenever a new backup set (different time) is found 0594 { 0595 sliceName = entryName.left(prefix.length() + strlen("yyyy.MM.dd-hh.mm.ss_")); 0596 if ( !entryName.endsWith(QLatin1String("_inc.tar")) ) // do not count partial (differential) backup files 0597 num++; 0598 if ( num == numKeptBackups ) num++; // from here on delete all others 0599 } 0600 0601 if ( (num > numKeptBackups) && // delete all other files 0602 !entryName.startsWith(sliceName) ) // keep complete last matching archive set 0603 { 0604 QUrl url = targetURL; 0605 url = url.adjusted(QUrl::StripTrailingSlash); 0606 url.setPath(url.path() + QLatin1Char('/') + entryName); 0607 Q_EMIT logging(i18n("...deleting %1", entryName)); 0608 0609 // delete the file using KIO 0610 if ( !targetURL.isLocalFile() ) // KIO needs $DISPLAY; non-interactive only allowed for local targets 0611 { 0612 QPointer<KIO::SimpleJob> delJob; 0613 delJob = KIO::file_delete(url, KIO::DefaultFlags); 0614 0615 connect(delJob.data(), &KJob::result, this, &Archiver::slotResult); 0616 0617 while ( delJob ) 0618 qApp->processEvents(QEventLoop::WaitForMoreEvents); 0619 } 0620 else 0621 { 0622 QDir dir(targetURL.path()); 0623 dir.remove(entryName); 0624 } 0625 } 0626 } 0627 } 0628 } 0629 else 0630 { 0631 Q_EMIT warning(i18n("fetching directory listing of target failed. Can not reduce kept archives.")); 0632 } 0633 } 0634 0635 runs = false; 0636 Q_EMIT inProgress(false); 0637 runTimer.stop(); 0638 updateElapsed(); // to catch the last partly second 0639 0640 if ( !cancelled ) 0641 { 0642 lastBackup = startTime; 0643 if ( !isIncrementalBackup() ) 0644 { 0645 lastFullBackup = lastBackup; 0646 setIncrementalBackup(fullBackupInterval > 1); // after a full backup, the next will be incremental 0647 } 0648 0649 if ( (fullBackupInterval > 1) && !loadedProfile.isEmpty() ) 0650 { 0651 QString error; 0652 if ( !saveProfile(loadedProfile, includes, excludes, error) ) 0653 { 0654 Q_EMIT warning(i18n("Could not write backup timestamps into profile %1: %2", loadedProfile, error)); 0655 } 0656 } 0657 0658 Q_EMIT logging(i18n("-- Filtered Files: %1", filteredFiles)); 0659 0660 if ( skippedFiles ) 0661 Q_EMIT logging(i18n("!! Backup finished <b>but files were skipped</b> !!")); 0662 else 0663 Q_EMIT logging(i18n("-- Backup successfully finished --")); 0664 0665 if ( interactive ) 0666 { 0667 int ret = KMessageBox::questionTwoActionsList(static_cast<QWidget*>(parent()), 0668 skippedFiles ? 0669 i18n("The backup has finished but files were skipped.\n" 0670 "What do you want to do now?") : 0671 i18n("The backup has finished successfully.\n" 0672 "What do you want to do now?"), 0673 sliceList, 0674 QString(), 0675 KStandardGuiItem::cont(), KStandardGuiItem::quit(), 0676 QStringLiteral("showDoneInfo")); 0677 0678 if ( ret == KMessageBox::SecondaryAction ) // quit 0679 qApp->quit(); 0680 } 0681 else 0682 { 0683 std::cerr << "-------" << std::endl; 0684 for (const QString &slice : std::as_const(sliceList)) { 0685 std::cerr << slice.toUtf8().constData() << std::endl; 0686 } 0687 std::cerr << "-------" << std::endl; 0688 0689 std::cerr << i18n("Totals: Files: %1, Size: %2, Duration: %3", 0690 totalFiles, 0691 KIO::convertSize(totalBytes), 0692 QTime(0, 0).addMSecs(elapsed.elapsed()).toString(QStringLiteral("HH:mm:ss"))) 0693 .toUtf8().constData() << std::endl; 0694 } 0695 0696 return true; 0697 } 0698 else 0699 { 0700 Q_EMIT logging(i18n("...Backup aborted!")); 0701 return false; 0702 } 0703 } 0704 0705 //-------------------------------------------------------------------------------- 0706 0707 void Archiver::cancel() 0708 { 0709 if ( !runs ) return; 0710 0711 if ( job ) 0712 { 0713 job->kill(); 0714 job = nullptr; 0715 } 0716 if ( !cancelled ) 0717 { 0718 cancelled = true; 0719 0720 if ( archive ) 0721 { 0722 archive_write_free(archive); 0723 archive = nullptr; 0724 } 0725 0726 QFile(archiveName).remove(); // remove the unfinished tar file (which is now corrupted) 0727 Q_EMIT warning(i18n("Backup cancelled")); 0728 } 0729 } 0730 0731 //-------------------------------------------------------------------------------- 0732 0733 void Archiver::finishSlice() 0734 { 0735 if ( archive ) 0736 { 0737 archive_write_free(archive); 0738 archive = nullptr; 0739 } 0740 0741 if ( ! cancelled ) 0742 { 0743 runScript(QStringLiteral("slice_closed")); 0744 0745 if ( targetURL.isLocalFile() ) 0746 { 0747 Q_EMIT logging(i18n("...finished slice %1", archiveName)); 0748 sliceList << archiveName; // store name for display at the end 0749 } 0750 else 0751 { 0752 QUrl source = QUrl::fromLocalFile(archiveName); 0753 QUrl target = targetURL; 0754 0755 while ( true ) 0756 { 0757 // copy to have the archive for the script later down 0758 job = KIO::copy(source, target, KIO::DefaultFlags); 0759 0760 connect(job.data(), &KJob::result, this, &Archiver::slotResult); 0761 0762 Q_EMIT logging(i18n("...uploading archive %1 to %2", source.fileName(), target.toString())); 0763 0764 while ( job ) 0765 qApp->processEvents(QEventLoop::WaitForMoreEvents); 0766 0767 if ( jobResult == 0 ) 0768 { 0769 target = target.adjusted(QUrl::StripTrailingSlash); 0770 target.setPath(target.path() + QLatin1Char('/') + source.fileName()); 0771 sliceList << target.toLocalFile(); // store name for display at the end 0772 break; 0773 } 0774 else 0775 { 0776 enum { ASK, CANCEL, RETRY } action = ASK; 0777 while ( action == ASK ) 0778 { 0779 int ret = KMessageBox::warningTwoActionsCancel(static_cast<QWidget*>(parent()), 0780 i18n("How shall we proceed with the upload?"), i18n("Upload Failed"), 0781 KGuiItem(i18n("Retry")), KGuiItem(i18n("Change Target"))); 0782 0783 if ( ret == KMessageBox::Cancel ) 0784 { 0785 action = CANCEL; 0786 break; 0787 } 0788 else if ( ret == KMessageBox::SecondaryAction ) // change target 0789 { 0790 target = QFileDialog::getExistingDirectoryUrl(static_cast<QWidget*>(parent())); 0791 if ( target.isEmpty() ) 0792 action = ASK; 0793 else 0794 action = RETRY; 0795 } 0796 else 0797 action = RETRY; 0798 } 0799 0800 if ( action == CANCEL ) 0801 break; 0802 } 0803 } 0804 0805 if ( jobResult != 0 ) 0806 cancel(); 0807 } 0808 } 0809 0810 if ( ! cancelled ) 0811 runScript(QStringLiteral("slice_finished")); 0812 0813 if ( !targetURL.isLocalFile() ) 0814 QFile(archiveName).remove(); // remove the tmp file 0815 } 0816 0817 //-------------------------------------------------------------------------------- 0818 0819 void Archiver::slotResult(KJob *theJob) 0820 { 0821 if ( (jobResult = theJob->error()) ) 0822 { 0823 theJob->uiDelegate()->showErrorMessage(); 0824 0825 Q_EMIT warning(theJob->errorString()); 0826 } 0827 } 0828 0829 //-------------------------------------------------------------------------------- 0830 0831 void Archiver::slotListResult(KIO::Job *theJob, const KIO::UDSEntryList &entries) 0832 { 0833 if ( (jobResult = theJob->error()) ) 0834 { 0835 theJob->uiDelegate()->showErrorMessage(); 0836 0837 Q_EMIT warning(theJob->errorString()); 0838 } 0839 0840 targetDirList = entries; 0841 } 0842 0843 //-------------------------------------------------------------------------------- 0844 0845 void Archiver::runScript(const QString &mode) 0846 { 0847 // do some extra action via external script (program) 0848 if ( sliceScript.length() ) 0849 { 0850 QString mountPoint; 0851 if ( targetURL.isLocalFile() ) 0852 { 0853 KMountPoint::Ptr ptr = KMountPoint::currentMountPoints().findByPath(targetURL.path()); 0854 if ( ptr ) 0855 mountPoint = ptr->mountPoint(); 0856 } 0857 0858 KProcess proc; 0859 proc << sliceScript 0860 << mode 0861 << archiveName 0862 << targetURL.toString(QUrl::PreferLocalFile) 0863 << mountPoint; 0864 0865 connect(&proc, &KProcess::readyReadStandardOutput, 0866 this, &Archiver::receivedOutput); 0867 0868 proc.setOutputChannelMode(KProcess::MergedChannels); 0869 0870 if ( proc.execute() == -2 ) 0871 { 0872 QString message = i18n("The script '%1' could not be started.", sliceScript); 0873 if ( interactive ) 0874 KMessageBox::error(static_cast<QWidget*>(parent()), message); 0875 else 0876 Q_EMIT warning(message); 0877 } 0878 } 0879 } 0880 0881 //-------------------------------------------------------------------------------- 0882 0883 void Archiver::receivedOutput() 0884 { 0885 KProcess *proc = static_cast<KProcess*>(sender()); 0886 0887 QByteArray buffer = proc->readAllStandardOutput(); 0888 0889 QString msg = QString::fromUtf8(buffer); 0890 if ( msg.endsWith(QLatin1Char('\n')) ) 0891 msg.chop(1); 0892 0893 Q_EMIT warning(msg); 0894 } 0895 0896 //-------------------------------------------------------------------------------- 0897 0898 bool Archiver::getNextSlice() 0899 { 0900 sliceNum++; 0901 0902 if ( archive ) 0903 { 0904 Q_EMIT sliceProgress(100); 0905 0906 finishSlice(); 0907 if ( cancelled ) return false; 0908 0909 if ( interactive && mediaNeedsChange && 0910 KMessageBox::warningContinueCancel(static_cast<QWidget*>(parent()), 0911 i18n("The medium is full. Please insert medium Nr. %1", sliceNum)) == 0912 KMessageBox::Cancel ) 0913 { 0914 cancel(); 0915 return false; 0916 } 0917 } 0918 0919 Q_EMIT newSlice(sliceNum); 0920 0921 if ( baseName.isEmpty() ) 0922 { 0923 QString prefix = filePrefix.isEmpty() ? QStringLiteral("backup") : filePrefix; 0924 0925 if ( targetURL.isLocalFile() ) 0926 { 0927 baseName = targetURL.path() + QLatin1Char('/') + prefix + 0928 QDateTime::currentDateTime().toString(QStringLiteral("_yyyy.MM.dd-hh.mm.ss")); 0929 } 0930 else 0931 { 0932 baseName = QDir::tempPath() + QLatin1Char('/') + prefix + 0933 QDateTime::currentDateTime().toString(QStringLiteral("_yyyy.MM.dd-hh.mm.ss")); 0934 } 0935 } 0936 0937 archiveName = baseName + QStringLiteral("_%1").arg(sliceNum); 0938 if ( isIncrementalBackup() ) 0939 archiveName += QStringLiteral("_inc.tar"); // mark the file as being not a full backup 0940 else 0941 archiveName += QStringLiteral(".tar"); 0942 0943 runScript(QStringLiteral("slice_init")); 0944 0945 calculateCapacity(); 0946 0947 // don't create a bz2 compressed file as we compress each file on its own 0948 archive = archive_write_new(); 0949 archive_write_set_format_pax_restricted(archive); // uses pax extensions only when absolutely necessary 0950 0951 while ( (sliceCapacity < 1024) || // disk full ? 0952 (archive_write_open_filename(archive, QFile::encodeName(archiveName).constData()) != ARCHIVE_OK) ) 0953 { 0954 if ( !interactive ) 0955 Q_EMIT warning(i18n("The file '%1' can not be opened for writing.", archiveName)); 0956 0957 if ( !interactive || 0958 (KMessageBox::warningTwoActions(static_cast<QWidget*>(parent()), 0959 i18n("The file '%1' can not be opened for writing.\n\n" 0960 "Do you want to retry?", archiveName), i18nc("@title", "Open File"), 0961 KGuiItem(i18nc("@action:button", "Retry")), 0962 KStandardGuiItem::cancel()) == KMessageBox::SecondaryAction) ) 0963 { 0964 cancel(); 0965 return false; 0966 } 0967 calculateCapacity(); // try again; maybe the user freed up some space 0968 } 0969 0970 return true; 0971 } 0972 0973 //-------------------------------------------------------------------------------- 0974 0975 void Archiver::emitArchiveError() 0976 { 0977 QString err = QString::fromLocal8Bit(archive_error_string(archive)); 0978 0979 if ( err.isEmpty() ) 0980 { 0981 Q_EMIT warning(i18n("Could not write to archive. Maybe the medium is full.")); 0982 } 0983 else 0984 { 0985 Q_EMIT warning(i18n("Could not write to archive.\n" 0986 "The operating system reports: %1", err)); 0987 } 0988 } 0989 0990 //-------------------------------------------------------------------------------- 0991 0992 void Archiver::addDirFiles(QDir &dir) 0993 { 0994 QString absolutePath = dir.absolutePath(); 0995 0996 if ( excludeDirs.contains(absolutePath) ) 0997 return; 0998 0999 for (const QRegExp &exp : std::as_const(dirFilters)) 1000 { 1001 if ( exp.exactMatch(absolutePath) ) 1002 { 1003 if ( interactive || verbose ) 1004 Q_EMIT logging(i18n("...skipping filtered directory %1", absolutePath)); 1005 1006 return; 1007 } 1008 } 1009 1010 // add the dir itself 1011 struct stat status; 1012 memset(&status, 0, sizeof(status)); 1013 if ( ::stat(QFile::encodeName(absolutePath).constData(), &status) == -1 ) 1014 { 1015 Q_EMIT warning(i18n("Could not get information of directory: %1\n" 1016 "The operating system reports: %2", 1017 absolutePath, 1018 QString::fromLocal8Bit(strerror(errno)))); 1019 return; 1020 } 1021 QFileInfo dirInfo(absolutePath); 1022 1023 if ( ! dirInfo.isReadable() ) 1024 { 1025 Q_EMIT warning(i18n("Directory '%1' is not readable. Skipping.", absolutePath)); 1026 skippedFiles = true; 1027 return; 1028 } 1029 1030 totalFiles++; 1031 Q_EMIT totalFilesChanged(totalFiles); 1032 if ( interactive || verbose ) 1033 Q_EMIT logging(absolutePath); 1034 1035 qApp->processEvents(QEventLoop::AllEvents, 5); 1036 if ( cancelled ) return; 1037 1038 archive_entry *dirEntry = archive_entry_new(); 1039 archive_entry_copy_stat(dirEntry, &status); 1040 archive_entry_copy_pathname_w(dirEntry, QString(QStringLiteral(".") + absolutePath).toStdWString().c_str()); 1041 1042 if ( archive_write_header(archive, dirEntry) != ARCHIVE_OK ) 1043 { 1044 emitArchiveError(); 1045 archive_entry_free(dirEntry); 1046 return; 1047 } 1048 1049 archive_entry_free(dirEntry); 1050 1051 dir.setFilter(QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot); 1052 1053 const QFileInfoList list = dir.entryInfoList(); 1054 1055 for (int i = 0; !cancelled && (i < list.count()); i++) 1056 { 1057 if ( !list[i].isSymLink() && list[i].isDir() ) 1058 { 1059 QDir dir(list[i].absoluteFilePath()); 1060 addDirFiles(dir); 1061 } 1062 else 1063 addFile(QFileInfo(list[i].absoluteFilePath())); 1064 } 1065 } 1066 1067 //-------------------------------------------------------------------------------- 1068 1069 bool Archiver::fileIsFiltered(const QString &fileName) const 1070 { 1071 for (const QRegExp &exp : std::as_const(filters)) 1072 if ( exp.exactMatch(fileName) ) 1073 return true; 1074 1075 return false; 1076 } 1077 1078 //-------------------------------------------------------------------------------- 1079 1080 void Archiver::addFile(const QFileInfo &info) 1081 { 1082 if ( (isIncrementalBackup() && (info.lastModified() < lastBackup)) || 1083 fileIsFiltered(info.fileName()) ) 1084 { 1085 filteredFiles++; 1086 return; 1087 } 1088 1089 if ( excludeFiles.contains(info.absoluteFilePath()) ) 1090 return; 1091 1092 // avoid including my own archive file 1093 // (QFileInfo to have correct path comparison even in case archiveName contains // etc.) 1094 // startsWith() is needed as KDE4 KTar does not create directly the .tar file but until it's closed 1095 // the file is named "...tarXXXX.new" 1096 if ( info.absoluteFilePath().startsWith(QFileInfo(archiveName).absoluteFilePath()) ) 1097 return; 1098 1099 if ( cancelled ) return; 1100 1101 /* don't skip. We probably do not need to read it anyway, since it might be empty 1102 if ( ! info.isReadable() ) 1103 { 1104 Q_EMIT warning(i18n("File '%1' is not readable. Skipping.").arg(info.absoluteFilePath())); 1105 skippedFiles = true; 1106 return; 1107 } 1108 */ 1109 1110 // Q_EMIT before we do the compression, so that the receiver can already show 1111 // with which file we work 1112 1113 // show filename + size 1114 if ( interactive || verbose ) 1115 Q_EMIT logging(info.absoluteFilePath() + QStringLiteral(" (%1)").arg(KIO::convertSize(info.size()))); 1116 1117 qApp->processEvents(QEventLoop::AllEvents, 5); 1118 if ( cancelled ) return; 1119 1120 struct stat status; 1121 memset(&status, 0, sizeof(status)); 1122 1123 if ( ::lstat(QFile::encodeName(info.absoluteFilePath()).constData(), &status) == -1 ) 1124 { 1125 Q_EMIT warning(i18n("Could not get information of file: %1\n" 1126 "The operating system reports: %2", 1127 info.absoluteFilePath(), 1128 QString::fromLocal8Bit(strerror(errno)))); 1129 1130 skippedFiles = true; 1131 return; 1132 } 1133 1134 if ( S_ISSOCK(status.st_mode) ) // tar format does not support this 1135 { 1136 Q_EMIT warning(i18n("Can not archive file type 'socket': %1\n").arg(info.absoluteFilePath())); 1137 1138 skippedFiles = true; 1139 return; 1140 } 1141 1142 archive_entry *entry = archive_entry_new(); 1143 archive_entry_copy_stat(entry, &status); 1144 archive_entry_copy_pathname_w(entry, QString(QStringLiteral(".") + info.absoluteFilePath()).toStdWString().c_str()); 1145 1146 if ( info.isSymLink() ) 1147 { 1148 archive_entry_copy_symlink_w(entry, info.symLinkTarget().toStdWString().c_str()); 1149 1150 if ( archive_write_header(archive, entry) != ARCHIVE_OK ) 1151 { 1152 emitArchiveError(); 1153 archive_entry_free(entry); 1154 return; 1155 } 1156 1157 archive_entry_free(entry); 1158 totalFiles++; 1159 Q_EMIT totalFilesChanged(totalFiles); 1160 return; 1161 } 1162 1163 if ( !getCompressFiles() ) 1164 { 1165 AddFileStatus ret = addLocalFile(info, entry); // this also increases totalBytes 1166 archive_entry_free(entry); 1167 1168 if ( ret == Error ) 1169 { 1170 cancel(); // we must cancel as the tar-file is now corrupt (file was only partly written) 1171 return; 1172 } 1173 else if ( ret == Skipped ) 1174 { 1175 skippedFiles = true; 1176 return; 1177 } 1178 } 1179 else // add the file compressed 1180 { 1181 // as we can't know which size the file will have after compression, 1182 // we create a compressed file and put this into the archive 1183 QTemporaryFile tmpFile; 1184 1185 if ( ! compressFile(info.absoluteFilePath(), tmpFile) || cancelled ) 1186 return; 1187 1188 // here we have the compressed file in tmpFile 1189 1190 tmpFile.open(); // size() only works if open 1191 1192 if ( (sliceBytes + tmpFile.size()) > sliceCapacity ) 1193 if ( ! getNextSlice() ) return; 1194 1195 // to be able to create the exact same metadata (permission, date, owner) we need 1196 // to fill the file into the archive with the following: 1197 { 1198 archive_entry_copy_pathname_w(entry, QString(QStringLiteral(".") + info.absoluteFilePath() + ext).toStdWString().c_str()); 1199 archive_entry_set_size(entry, tmpFile.size()); 1200 1201 if ( archive_write_header(archive, entry) != ARCHIVE_OK ) 1202 { 1203 archive_entry_free(entry); 1204 emitArchiveError(); 1205 cancel(); 1206 return; 1207 } 1208 archive_entry_free(entry); 1209 1210 const int BUFFER_SIZE = 8*1024; 1211 static char buffer[BUFFER_SIZE]; 1212 qint64 len; 1213 int count = 0; 1214 while ( ! tmpFile.atEnd() ) 1215 { 1216 len = tmpFile.read(buffer, BUFFER_SIZE); 1217 1218 if ( len < 0 ) // error in reading 1219 { 1220 Q_EMIT warning(i18n("Could not read from file '%1'\n" 1221 "The operating system reports: %2", 1222 info.absoluteFilePath(), 1223 tmpFile.errorString())); 1224 cancel(); 1225 return; 1226 } 1227 1228 if ( archive_write_data(archive, buffer, len) < 0 ) 1229 { 1230 emitArchiveError(); 1231 cancel(); 1232 return; 1233 } 1234 1235 count = (count + 1) % 50; 1236 if ( count == 0 ) 1237 { 1238 qApp->processEvents(QEventLoop::AllEvents, 5); 1239 if ( cancelled ) return; 1240 } 1241 } 1242 } 1243 1244 // get filesize 1245 sliceBytes = archive_filter_bytes(archive, -1); // account for tar overhead 1246 totalBytes += tmpFile.size(); 1247 1248 Q_EMIT sliceProgress(static_cast<int>(sliceBytes * 100 / sliceCapacity)); 1249 } 1250 1251 totalFiles++; 1252 Q_EMIT totalFilesChanged(totalFiles); 1253 Q_EMIT totalBytesChanged(totalBytes); 1254 1255 qApp->processEvents(QEventLoop::AllEvents, 5); 1256 } 1257 1258 //-------------------------------------------------------------------------------- 1259 1260 Archiver::AddFileStatus Archiver::addLocalFile(const QFileInfo &info, struct archive_entry *entry) 1261 { 1262 QFile sourceFile(info.absoluteFilePath()); 1263 1264 // if the size is 0 (e.g. a pipe), don't open it since we will not read any content 1265 // and Qt hangs when opening a pipe 1266 if ( (info.size() > 0) && !sourceFile.open(QIODevice::ReadOnly) ) 1267 { 1268 Q_EMIT warning(i18n("Could not open file '%1' for reading.", info.absoluteFilePath())); 1269 return Skipped; 1270 } 1271 1272 if ( (sliceBytes + info.size()) > sliceCapacity ) 1273 if ( ! getNextSlice() ) return Error; 1274 1275 if ( archive_write_header(archive, entry) != ARCHIVE_OK ) 1276 { 1277 emitArchiveError(); 1278 return Error; 1279 } 1280 1281 const int BUFFER_SIZE = 8*1024; 1282 static char buffer[BUFFER_SIZE]; 1283 qint64 len; 1284 int count = 0, progress; 1285 QElapsedTimer timer; 1286 timer.start(); 1287 bool msgShown = false; 1288 qint64 written = 0; 1289 1290 while ( info.size() && !sourceFile.atEnd() && !cancelled ) 1291 { 1292 len = sourceFile.read(buffer, BUFFER_SIZE); 1293 1294 if ( len < 0 ) // error in reading 1295 { 1296 if ( msgShown && interactive ) 1297 QApplication::restoreOverrideCursor(); 1298 1299 Q_EMIT warning(i18n("Could not read from file '%1'\n" 1300 "The operating system reports: %2", 1301 info.absoluteFilePath(), 1302 sourceFile.errorString())); 1303 return Error; 1304 } 1305 1306 if ( archive_write_data(archive, buffer, len) < 0 ) 1307 { 1308 if ( msgShown && interactive ) 1309 QApplication::restoreOverrideCursor(); 1310 1311 emitArchiveError(); 1312 return Error; 1313 } 1314 1315 totalBytes += len; 1316 written += len; 1317 1318 progress = static_cast<int>(written * 100 / info.size()); 1319 1320 // stay responsive 1321 count = (count + 1) % 50; 1322 if ( count == 0 ) 1323 { 1324 if ( msgShown ) 1325 Q_EMIT fileProgress(progress); 1326 1327 Q_EMIT totalBytesChanged(totalBytes); 1328 qApp->processEvents(QEventLoop::AllEvents, 5); 1329 } 1330 1331 if ( !msgShown && (timer.elapsed() > 3000) && (progress < 50) ) 1332 { 1333 Q_EMIT fileProgress(progress); 1334 if ( interactive || verbose ) 1335 Q_EMIT logging(i18n("...archiving file %1", info.absoluteFilePath())); 1336 1337 if ( interactive ) 1338 QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); 1339 1340 qApp->processEvents(QEventLoop::AllEvents, 5); 1341 msgShown = true; 1342 } 1343 } 1344 Q_EMIT fileProgress(100); 1345 sourceFile.close(); 1346 1347 if ( !cancelled ) 1348 { 1349 // get filesize 1350 sliceBytes = archive_filter_bytes(archive, -1); // account for tar overhead 1351 1352 Q_EMIT sliceProgress(static_cast<int>(sliceBytes * 100 / sliceCapacity)); 1353 } 1354 1355 if ( msgShown && interactive ) 1356 QApplication::restoreOverrideCursor(); 1357 1358 return cancelled ? Error : Added; 1359 } 1360 1361 //-------------------------------------------------------------------------------- 1362 1363 bool Archiver::compressFile(const QString &origName, QFile &comprFile) 1364 { 1365 QFile origFile(origName); 1366 if ( ! origFile.open(QIODevice::ReadOnly) ) 1367 { 1368 Q_EMIT warning(i18n("Could not read file: %1\n" 1369 "The operating system reports: %2", 1370 origName, 1371 origFile.errorString())); 1372 1373 skippedFiles = true; 1374 return false; 1375 } 1376 else 1377 { 1378 KCompressionDevice filter(&comprFile, false, compressionType); 1379 1380 if ( !filter.open(QIODevice::WriteOnly) ) 1381 { 1382 Q_EMIT warning(i18n("Could not create temporary file for compressing: %1\n" 1383 "The operating system reports: %2", 1384 origName, 1385 filter.errorString())); 1386 return false; 1387 } 1388 1389 const int BUFFER_SIZE = 8*1024; 1390 static char buffer[BUFFER_SIZE]; 1391 qint64 len; 1392 int count = 0, progress; 1393 QElapsedTimer timer; 1394 timer.start(); 1395 bool msgShown = false; 1396 1397 KIO::filesize_t fileSize = origFile.size(); 1398 KIO::filesize_t written = 0; 1399 1400 while ( fileSize && !origFile.atEnd() && !cancelled ) 1401 { 1402 len = origFile.read(buffer, BUFFER_SIZE); 1403 qint64 wrote = filter.write(buffer, len); 1404 1405 if ( len != wrote ) 1406 { 1407 if ( msgShown && interactive ) 1408 QApplication::restoreOverrideCursor(); 1409 1410 Q_EMIT warning(i18n("Could not write to temporary file")); 1411 return false; 1412 } 1413 1414 written += len; 1415 1416 progress = static_cast<int>(written * 100 / fileSize); 1417 1418 // keep the ui responsive 1419 count = (count + 1) % 50; 1420 if ( count == 0 ) 1421 { 1422 if ( msgShown ) 1423 Q_EMIT fileProgress(progress); 1424 1425 qApp->processEvents(QEventLoop::AllEvents, 5); 1426 } 1427 1428 if ( !msgShown && (timer.elapsed() > 3000) && (progress < 50) ) 1429 { 1430 Q_EMIT fileProgress(progress); 1431 Q_EMIT logging(i18n("...compressing file %1", origName)); 1432 if ( interactive ) 1433 QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); 1434 1435 qApp->processEvents(QEventLoop::AllEvents, 5); 1436 msgShown = true; 1437 } 1438 } 1439 Q_EMIT fileProgress(100); 1440 origFile.close(); 1441 1442 if ( msgShown && interactive ) 1443 QApplication::restoreOverrideCursor(); 1444 } 1445 1446 return true; 1447 } 1448 1449 //-------------------------------------------------------------------------------- 1450 1451 bool Archiver::getDiskFree(const QString &path, KIO::filesize_t &capacityB, KIO::filesize_t &freeB) 1452 { 1453 struct statvfs vfs; 1454 memset(&vfs, 0, sizeof(vfs)); 1455 1456 if ( ::statvfs(QFile::encodeName(path).constData(), &vfs) == -1 ) 1457 return false; 1458 1459 capacityB = static_cast<KIO::filesize_t>(vfs.f_blocks) * static_cast<KIO::filesize_t>(vfs.f_frsize); 1460 freeB = static_cast<KIO::filesize_t>(vfs.f_bavail) * static_cast<KIO::filesize_t>(vfs.f_frsize); 1461 1462 return true; 1463 } 1464 1465 //-------------------------------------------------------------------------------- 1466 1467 void Archiver::loggingSlot(const QString &message) 1468 { 1469 std::cerr << message.toUtf8().constData() << std::endl; 1470 } 1471 1472 //-------------------------------------------------------------------------------- 1473 1474 void Archiver::warningSlot(const QString &message) 1475 { 1476 std::cerr << i18n("WARNING:").toUtf8().constData() << message.toUtf8().constData() << std::endl; 1477 } 1478 1479 //-------------------------------------------------------------------------------- 1480 1481 void Archiver::updateElapsed() 1482 { 1483 Q_EMIT elapsedChanged(QTime(0, 0).addMSecs(elapsed.elapsed())); 1484 } 1485 1486 //-------------------------------------------------------------------------------- 1487 // sort by name of entries in descending order (younger names are first) 1488 1489 bool Archiver::UDSlessThan(const KIO::UDSEntry &left, const KIO::UDSEntry &right) 1490 { 1491 return left.stringValue(KIO::UDSEntry::UDS_NAME) > right.stringValue(KIO::UDSEntry::UDS_NAME); 1492 } 1493 1494 //-------------------------------------------------------------------------------- 1495 1496 #include "moc_Archiver.cpp"