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 &reg : 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 &reg : 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"