diff --git a/autotests/kautosavefiletest.cpp b/autotests/kautosavefiletest.cpp index 1b09cb4..eb5cd75 100644 --- a/autotests/kautosavefiletest.cpp +++ b/autotests/kautosavefiletest.cpp @@ -1,129 +1,129 @@ /* This file is part of the KDE libraries Copyright (c) 2006 Jacob R Rideout This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kautosavefiletest.h" -#include -#include +#include +#include #include #include -#include +#include QTEST_MAIN(KAutoSaveFileTest) void KAutoSaveFileTest::initTestCase() { QCoreApplication::instance()->setApplicationName(QLatin1String("qttest")); // TODO do this in qtestlib itself } void KAutoSaveFileTest::cleanupTestCase() { Q_FOREACH (const QString &fileToRemove, filesToRemove) { QFile::remove(fileToRemove); } } void KAutoSaveFileTest::test_readWrite() { QTemporaryFile file; QVERIFY(file.open()); QUrl normalFile = QUrl::fromLocalFile(QFileInfo(file).absoluteFilePath()); //Test basic functionality KAutoSaveFile saveFile(normalFile); QVERIFY(!QFile::exists(saveFile.fileName())); QVERIFY(saveFile.open(QIODevice::ReadWrite)); QString inText = QString::fromLatin1("This is test data one.\n"); { QTextStream ts(&saveFile); ts << inText; ts.flush(); } saveFile.close(); { QFile testReader(saveFile.fileName()); testReader.open(QIODevice::ReadWrite); QTextStream ts(&testReader); QString outText = ts.readAll(); QCOMPARE(outText, inText); } filesToRemove << file.fileName(); } void KAutoSaveFileTest::test_fileStaleFiles() { QUrl normalFile = QUrl::fromLocalFile(QDir::temp().absoluteFilePath(QStringLiteral("test directory/tîst me.txt"))); KAutoSaveFile saveFile(normalFile); QVERIFY(saveFile.open(QIODevice::ReadWrite)); saveFile.write("testdata"); // Make sure the stale file is found QVERIFY(saveFile.staleFiles(normalFile, QStringLiteral("qttest")).count() == 1); saveFile.releaseLock(); // Make sure the stale file is deleted QVERIFY(saveFile.staleFiles(normalFile, QStringLiteral("qttest")).isEmpty()); } void KAutoSaveFileTest::test_applicationStaleFiles() { // TODO } void KAutoSaveFileTest::test_locking() { QUrl normalFile(QString::fromLatin1("fish://user@example.com/home/remote/test.txt")); KAutoSaveFile saveFile(normalFile); QVERIFY(!QFile::exists(saveFile.fileName())); QVERIFY(saveFile.open(QIODevice::ReadWrite)); const QList staleFiles(KAutoSaveFile::staleFiles(normalFile)); QVERIFY(!staleFiles.isEmpty()); KAutoSaveFile *saveFile2 = staleFiles.at(0); const QString fn = saveFile2->fileName(); // It looks like $XDG_DATA_HOME/stalefiles/qttest/test.txtXXXfish_%2Fhome%2FremoteXXXXXXX QVERIFY2(fn.contains(QLatin1String("stalefiles/qttest/test.txt")), qPrintable(fn)); QVERIFY2(fn.contains(QLatin1String("fish_%2Fhome%2Fremote")), qPrintable(fn)); QVERIFY(QFile::exists(saveFile2->fileName())); QVERIFY(!saveFile2->open(QIODevice::ReadWrite)); saveFile.releaseLock(); QVERIFY(saveFile2->open(QIODevice::ReadWrite)); delete saveFile2; } diff --git a/autotests/kautosavefiletest.h b/autotests/kautosavefiletest.h index cf70f4c..ca7077c 100644 --- a/autotests/kautosavefiletest.h +++ b/autotests/kautosavefiletest.h @@ -1,42 +1,42 @@ /* This file is part of the KDE libraries Copyright (c) 2006 Jacob R Rideout This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef kautosavefiletest_h #define kautosavefiletest_h -#include -#include +#include +#include class KAutoSaveFileTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void test_readWrite(); void test_fileStaleFiles(); void test_applicationStaleFiles(); void test_locking(); void cleanupTestCase(); private: QStringList filesToRemove; }; #endif diff --git a/autotests/kcompositejobtest.cpp b/autotests/kcompositejobtest.cpp index b37e4ad..a270ba1 100644 --- a/autotests/kcompositejobtest.cpp +++ b/autotests/kcompositejobtest.cpp @@ -1,106 +1,106 @@ /* This file is part of the KDE project Copyright (C) 2013 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcompositejobtest.h" -#include +#include #include TestJob::TestJob(QObject *parent) : KJob(parent) { } void TestJob::start() { QTimer::singleShot(1000, this, SLOT(doEmit())); } void TestJob::doEmit() { emitResult(); } void CompositeJob::start() { if (hasSubjobs()) { subjobs().first()->start(); } else { emitResult(); } } bool CompositeJob::addSubjob(KJob *job) { return KCompositeJob::addSubjob(job); } void CompositeJob::slotResult(KJob *job) { KCompositeJob::slotResult(job); if (!error() && hasSubjobs()) { // start next subjobs().first()->start(); } else { setError(job->error()); setErrorText(job->errorText()); emitResult(); } } KCompositeJobTest::KCompositeJobTest() : loop(this) { } /** * In case a composite job is deleted during execution * we still want to assure that we don't crash * * see bug: https://bugs.kde.org/show_bug.cgi?id=230692 */ void KCompositeJobTest::testDeletionDuringExecution() { QObject *someParent = new QObject; KJob *job = new TestJob(someParent); CompositeJob *compositeJob = new CompositeJob; compositeJob->setAutoDelete(false); QVERIFY(compositeJob->addSubjob(job)); QCOMPARE(job->parent(), compositeJob); QSignalSpy destroyed_spy(job, SIGNAL(destroyed(QObject*))); // check if job got reparented properly delete someParent; someParent = nullptr; // the job should still exist, because it is a child of KCompositeJob now QCOMPARE(destroyed_spy.size(), 0); // start async, the subjob takes 1 second to finish compositeJob->start(); // delete the job during the execution delete compositeJob; compositeJob = nullptr; // at this point, the subjob should be deleted, too QCOMPARE(destroyed_spy.size(), 1); } QTEST_GUILESS_MAIN(KCompositeJobTest) diff --git a/autotests/kformattest.cpp b/autotests/kformattest.cpp index fc29cda..15080be 100644 --- a/autotests/kformattest.cpp +++ b/autotests/kformattest.cpp @@ -1,350 +1,350 @@ /* This file is part of the KDE Frameworks Copyright (C) 2013 John Layt , Copyright (C) 2010 Michael Leupold Copyright (C) 2009 Michael Pyne Copyright (C) 2008 Albert Astals Cid This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kformattest.h" -#include +#include #include "kformat.h" void KFormatTest::formatByteSize() { QLocale locale(QLocale::c()); locale.setNumberOptions(QLocale::DefaultNumberOptions); // Qt >= 5.6 sets QLocale::OmitGroupSeparator for the C locale KFormat format(locale); QCOMPARE(format.formatByteSize(0), QStringLiteral("0 B")); QCOMPARE(format.formatByteSize(50), QStringLiteral("50 B")); QCOMPARE(format.formatByteSize(500), QStringLiteral("500 B")); QCOMPARE(format.formatByteSize(5000), QStringLiteral("4.9 KiB")); QCOMPARE(format.formatByteSize(50000), QStringLiteral("48.8 KiB")); QCOMPARE(format.formatByteSize(500000), QStringLiteral("488.3 KiB")); QCOMPARE(format.formatByteSize(5000000), QStringLiteral("4.8 MiB")); QCOMPARE(format.formatByteSize(50000000), QStringLiteral("47.7 MiB")); QCOMPARE(format.formatByteSize(500000000), QStringLiteral("476.8 MiB")); #if (defined(__WORDSIZE) && (__WORDSIZE == 64)) || defined (_LP64) || defined(__LP64__) || defined(__ILP64__) QCOMPARE(format.formatByteSize(5000000000), QStringLiteral("4.7 GiB")); QCOMPARE(format.formatByteSize(50000000000), QStringLiteral("46.6 GiB")); QCOMPARE(format.formatByteSize(500000000000), QStringLiteral("465.7 GiB")); QCOMPARE(format.formatByteSize(5000000000000), QStringLiteral("4.5 TiB")); QCOMPARE(format.formatByteSize(50000000000000), QStringLiteral("45.5 TiB")); QCOMPARE(format.formatByteSize(500000000000000), QStringLiteral("454.7 TiB")); #endif QCOMPARE(format.formatByteSize(1024.0, 1, KFormat::IECBinaryDialect), QStringLiteral("1.0 KiB")); QCOMPARE(format.formatByteSize(1023.0, 1, KFormat::IECBinaryDialect), QStringLiteral("1,023 B")); QCOMPARE(format.formatByteSize(1163000.0, 1, KFormat::IECBinaryDialect), QStringLiteral("1.1 MiB")); // 1.2 metric QCOMPARE(format.formatByteSize(-1024.0, 1, KFormat::IECBinaryDialect), QStringLiteral("-1.0 KiB")); QCOMPARE(format.formatByteSize(-1023.0, 1, KFormat::IECBinaryDialect), QStringLiteral("-1,023 B")); QCOMPARE(format.formatByteSize(-1163000.0, 1, KFormat::IECBinaryDialect), QStringLiteral("-1.1 MiB")); // 1.2 metric QCOMPARE(format.formatByteSize(1024.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("1.0 KB")); QCOMPARE(format.formatByteSize(1023.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("1,023 B")); QCOMPARE(format.formatByteSize(1163000.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("1.1 MB")); QCOMPARE(format.formatByteSize(-1024.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("-1.0 KB")); QCOMPARE(format.formatByteSize(-1023.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("-1,023 B")); QCOMPARE(format.formatByteSize(-1163000.0, 1, KFormat::JEDECBinaryDialect), QStringLiteral("-1.1 MB")); QCOMPARE(format.formatByteSize(1024.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("1.0 kB")); QCOMPARE(format.formatByteSize(1023.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("1.0 kB")); QCOMPARE(format.formatByteSize(1163000.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("1.2 MB")); QCOMPARE(format.formatByteSize(-1024.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("-1.0 kB")); QCOMPARE(format.formatByteSize(-1023.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("-1.0 kB")); QCOMPARE(format.formatByteSize(-1163000.0, 1, KFormat::MetricBinaryDialect), QStringLiteral("-1.2 MB")); // Ensure all units are represented QCOMPARE(format.formatByteSize(2.0e9, 1, KFormat::MetricBinaryDialect), QStringLiteral("2.0 GB")); QCOMPARE(format.formatByteSize(3.2e12, 1, KFormat::MetricBinaryDialect), QStringLiteral("3.2 TB")); QCOMPARE(format.formatByteSize(4.1e15, 1, KFormat::MetricBinaryDialect), QStringLiteral("4.1 PB")); QCOMPARE(format.formatByteSize(6.7e18, 2, KFormat::MetricBinaryDialect), QStringLiteral("6.70 EB")); QCOMPARE(format.formatByteSize(5.6e20, 2, KFormat::MetricBinaryDialect), QStringLiteral("560.00 EB")); QCOMPARE(format.formatByteSize(2.3e22, 2, KFormat::MetricBinaryDialect), QStringLiteral("23.00 ZB")); QCOMPARE(format.formatByteSize(1.0e27, 1, KFormat::MetricBinaryDialect), QStringLiteral("1,000.0 YB")); // Spattering of specific units QCOMPARE(format.formatByteSize(823000, 3, KFormat::IECBinaryDialect, KFormat::UnitMegaByte), QStringLiteral("0.785 MiB")); QCOMPARE(format.formatByteSize(1234034.0, 4, KFormat::JEDECBinaryDialect, KFormat::UnitByte), QStringLiteral("1,234,034 B")); } enum TimeConstants { MSecsInDay = 86400000, MSecsInHour = 3600000, MSecsInMinute = 60000, MSecsInSecond = 1000 }; void KFormatTest::formatDuration() { KFormat format(QLocale::c()); quint64 singleSecond = 3 * MSecsInSecond + 700; quint64 doubleSecond = 33 * MSecsInSecond + 700; quint64 singleMinute = 8 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 doubleMinute = 38 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 singleHour = 5 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 doubleHour = 15 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 singleDay = 1 * MSecsInDay + 5 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 doubleDay = 10 * MSecsInDay + 5 * MSecsInHour + 8 * MSecsInMinute + 3 * MSecsInSecond + 700; quint64 roundingIssues = 2* MSecsInHour + 59 * MSecsInMinute + 59 * MSecsInSecond + 900; // Default format QCOMPARE(format.formatDuration(singleSecond), QStringLiteral("0:00:04")); QCOMPARE(format.formatDuration(doubleSecond), QStringLiteral("0:00:34")); QCOMPARE(format.formatDuration(singleMinute), QStringLiteral("0:08:04")); QCOMPARE(format.formatDuration(doubleMinute), QStringLiteral("0:38:04")); QCOMPARE(format.formatDuration(singleHour), QStringLiteral("5:08:04")); QCOMPARE(format.formatDuration(doubleHour), QStringLiteral("15:08:04")); QCOMPARE(format.formatDuration(singleDay), QStringLiteral("29:08:04")); QCOMPARE(format.formatDuration(doubleDay), QStringLiteral("245:08:04")); QCOMPARE(format.formatDuration(roundingIssues), QStringLiteral("3:00:00")); // ShowMilliseconds format KFormat::DurationFormatOptions options = KFormat::ShowMilliseconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:00:03.700")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:00:33.700")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0:08:03.700")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0:38:03.700")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5:08:03.700")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15:08:03.700")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29:08:03.700")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245:08:03.700")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("2:59:59.900")); // HideSeconds format options = KFormat::HideSeconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:00")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:01")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0:08")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0:38")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5:08")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15:08")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29:08")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245:08")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("3:00")); // FoldHours format options = KFormat::FoldHours; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:04")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:34")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8:04")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38:04")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308:04")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908:04")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748:04")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708:04")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("180:00")); // FoldHours ShowMilliseconds format options = KFormat::FoldHours; options = options | KFormat::ShowMilliseconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0:03.700")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0:33.700")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8:03.700")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38:03.700")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308:03.700")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908:03.700")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748:03.700")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708:03.700")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("179:59.900")); // InitialDuration format options = KFormat::InitialDuration; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0h00m04s")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0h00m34s")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0h08m04s")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0h38m04s")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5h08m04s")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15h08m04s")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29h08m04s")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245h08m04s")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("3h00m00s")); // InitialDuration and ShowMilliseconds format options = KFormat::InitialDuration; options = options | KFormat::ShowMilliseconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0h00m03.700s")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0h00m33.700s")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0h08m03.700s")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0h38m03.700s")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5h08m03.700s")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15h08m03.700s")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29h08m03.700s")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245h08m03.700s")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("2h59m59.900s")); // InitialDuration and HideSeconds format options = KFormat::InitialDuration; options = options | KFormat::HideSeconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0h00m")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0h01m")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("0h08m")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("0h38m")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("5h08m")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("15h08m")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("29h08m")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("245h08m")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("3h00m")); // InitialDuration and FoldHours format options = KFormat::InitialDuration; options = options | KFormat::FoldHours; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0m04s")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0m34s")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8m04s")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38m04s")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308m04s")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908m04s")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748m04s")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708m04s")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("180m00s")); // InitialDuration and FoldHours and ShowMilliseconds format options = KFormat::InitialDuration; options = options | KFormat::FoldHours | KFormat::ShowMilliseconds; QCOMPARE(format.formatDuration(singleSecond, options), QStringLiteral("0m03.700s")); QCOMPARE(format.formatDuration(doubleSecond, options), QStringLiteral("0m33.700s")); QCOMPARE(format.formatDuration(singleMinute, options), QStringLiteral("8m03.700s")); QCOMPARE(format.formatDuration(doubleMinute, options), QStringLiteral("38m03.700s")); QCOMPARE(format.formatDuration(singleHour, options), QStringLiteral("308m03.700s")); QCOMPARE(format.formatDuration(doubleHour, options), QStringLiteral("908m03.700s")); QCOMPARE(format.formatDuration(singleDay, options), QStringLiteral("1748m03.700s")); QCOMPARE(format.formatDuration(doubleDay, options), QStringLiteral("14708m03.700s")); QCOMPARE(format.formatDuration(roundingIssues, options), QStringLiteral("179m59.900s")); } void KFormatTest::formatDecimalDuration() { KFormat format(QLocale::c()); QCOMPARE(format.formatDecimalDuration(10), QStringLiteral("10 millisecond(s)")); QCOMPARE(format.formatDecimalDuration(10, 3), QStringLiteral("10 millisecond(s)")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInSecond + 10), QStringLiteral("1.01 seconds")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInSecond + 1, 3), QStringLiteral("1.001 seconds")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInMinute + 10 * MSecsInSecond), QStringLiteral("1.17 minutes")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInMinute + 10 * MSecsInSecond, 3), QStringLiteral("1.167 minutes")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInHour + 10 * MSecsInMinute), QStringLiteral("1.17 hours")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInHour + 10 * MSecsInMinute, 3), QStringLiteral("1.167 hours")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInDay + 10 * MSecsInHour), QStringLiteral("1.42 days")); QCOMPARE(format.formatDecimalDuration(1 * MSecsInDay + 10 * MSecsInHour, 3), QStringLiteral("1.417 days")); } void KFormatTest::formatSpelloutDuration() { KFormat format(QLocale::c()); QCOMPARE(format.formatSpelloutDuration(1000), QStringLiteral("1 second(s)")); QCOMPARE(format.formatSpelloutDuration(5000), QStringLiteral("5 second(s)")); QCOMPARE(format.formatSpelloutDuration(60000), QStringLiteral("1 minute(s)")); QCOMPARE(format.formatSpelloutDuration(300000), QStringLiteral("5 minute(s)")); QCOMPARE(format.formatSpelloutDuration(3600000), QStringLiteral("1 hour(s)")); QCOMPARE(format.formatSpelloutDuration(18000000), QStringLiteral("5 hour(s)")); QCOMPARE(format.formatSpelloutDuration(75000), QStringLiteral("1 minute(s) and 15 second(s)")); // Problematic case #1 (there is a reference to this case on kformat.cpp) QCOMPARE(format.formatSpelloutDuration(119999), QStringLiteral("2 minute(s)")); // This case is strictly 2 hours, 15 minutes and 59 seconds. However, since the range is // pretty high between hours and seconds, formatSpelloutDuration always omits seconds when there // are hours in scene. QCOMPARE(format.formatSpelloutDuration(8159000), QStringLiteral("2 hour(s) and 15 minute(s)")); // This case is strictly 1 hour and 10 seconds. For the same reason, formatSpelloutDuration // detects that 10 seconds is just garbage compared to 1 hour, and omits it on the result. QCOMPARE(format.formatSpelloutDuration(3610000), QStringLiteral("1 hour(s)")); } void KFormatTest::formatRelativeDate() { KFormat format(QLocale::c()); QDate testDate = QDate::currentDate(); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Today")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Today")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Today")); testDate = QDate::currentDate().addDays(1); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Tomorrow")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Tomorrow")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Tomorrow")); testDate = QDate::currentDate().addDays(-1); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Yesterday")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Yesterday")); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Yesterday")); // Relative dates within a week are up to translators but there's no // expectation that day names are shortened -- per the API docs, the date // format is only used when the date is outside the relative window testDate = QDate::currentDate().addDays(-7); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Last %1").arg(QLocale::c().dayName(testDate.dayOfWeek(), QLocale::LongFormat))); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Last %1").arg(QLocale::c().dayName(testDate.dayOfWeek(), QLocale::LongFormat))); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Last %1").arg(QLocale::c().dayName(testDate.dayOfWeek(), QLocale::LongFormat))); testDate = QDate::currentDate().addDays(7); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Next %1").arg(QLocale::c().dayName(testDate.dayOfWeek(), QLocale::LongFormat))); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QStringLiteral("Next %1").arg(QLocale::c().dayName(testDate.dayOfWeek(), QLocale::LongFormat))); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QStringLiteral("Next %1").arg(QLocale::c().dayName(testDate.dayOfWeek(), QLocale::LongFormat))); testDate = QDate::currentDate().addDays(-8); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QLocale::c().toString(testDate, QLocale::LongFormat)); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QLocale::c().toString(testDate, QLocale::ShortFormat)); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QLocale::c().toString(testDate, QLocale::NarrowFormat)); testDate = QDate::currentDate().addDays(8); QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QLocale::c().toString(testDate, QLocale::LongFormat)); QCOMPARE(format.formatRelativeDate(testDate, QLocale::ShortFormat), QLocale::c().toString(testDate, QLocale::ShortFormat)); QCOMPARE(format.formatRelativeDate(testDate, QLocale::NarrowFormat), QLocale::c().toString(testDate, QLocale::NarrowFormat)); testDate = QDate(); // invalid date QCOMPARE(format.formatRelativeDate(testDate, QLocale::LongFormat), QStringLiteral("Invalid date")); QDateTime testDateTime = QDateTime(QDate::currentDate(), QTime(3, 0, 0)); QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::ShortFormat), QStringLiteral("Today, 03:00:00")); testDateTime = QDateTime(QDate::currentDate().addDays(8), QTime(3, 0, 0)); QCOMPARE(format.formatRelativeDateTime(testDateTime, QLocale::LongFormat), QLocale::c().toString(testDateTime, QLocale::LongFormat)); } QTEST_MAIN(KFormatTest) diff --git a/autotests/kformattest.h b/autotests/kformattest.h index 9d207a1..1e23cbc 100644 --- a/autotests/kformattest.h +++ b/autotests/kformattest.h @@ -1,42 +1,42 @@ /* This file is part of the KDE Frameworks Copyright (C) 2013 John Layt , Copyright (C) 2010 Michael Leupold Copyright (C) 2009 Michael Pyne Copyright (C) 2008 Albert Astals Cid This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KFORMATTEST_H #define KFORMATTEST_H -#include +#include class KFormatTest : public QObject { Q_OBJECT private Q_SLOTS: void formatByteSize(); void formatDuration(); void formatDecimalDuration(); void formatSpelloutDuration(); void formatRelativeDate(); }; #endif // KFORMATTEST_H diff --git a/autotests/kjobtest.cpp b/autotests/kjobtest.cpp index 368101b..651b651 100644 --- a/autotests/kjobtest.cpp +++ b/autotests/kjobtest.cpp @@ -1,411 +1,411 @@ /* This file is part of the KDE project Copyright (C) 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kjobtest.h" -#include -#include -#include +#include +#include +#include QTEST_MAIN(KJobTest) KJobTest::KJobTest() : loop(this) { } void KJobTest::testEmitResult_data() { QTest::addColumn("errorCode"); QTest::addColumn("errorText"); QTest::newRow("no error") << int(KJob::NoError) << QString(); QTest::newRow("error no text") << 2 << QString(); QTest::newRow("error with text") << 6 << "oops! an error? naaah, really?"; } void KJobTest::testEmitResult() { TestJob *job = new TestJob; connect(job, &KJob::result, this, &KJobTest::slotResult); QFETCH(int, errorCode); QFETCH(QString, errorText); job->setError(errorCode); job->setErrorText(errorText); QSignalSpy destroyed_spy(job, SIGNAL(destroyed(QObject*))); job->start(); loop.exec(); QCOMPARE(m_lastError, errorCode); QCOMPARE(m_lastErrorText, errorText); // Verify that the job is not deleted immediately... QCOMPARE(destroyed_spy.size(), 0); QTimer::singleShot(0, &loop, SLOT(quit())); // ... but when we enter the event loop again. loop.exec(); QCOMPARE(destroyed_spy.size(), 1); } void KJobTest::testProgressTracking() { TestJob *testJob = new TestJob; KJob *job = testJob; qRegisterMetaType("KJob*"); qRegisterMetaType("qulonglong"); QSignalSpy processed_spy(job, SIGNAL(processedAmount(KJob*,KJob::Unit,qulonglong))); QSignalSpy total_spy(job, SIGNAL(totalAmount(KJob*,KJob::Unit,qulonglong))); QSignalSpy percent_spy(job, SIGNAL(percent(KJob*,ulong))); /* Process a first item. Corresponding signal should be emitted. * Total size didn't change. * Since the total size is unknown, no percent signal is emitted. */ testJob->setProcessedSize(1); QCOMPARE(processed_spy.size(), 1); QCOMPARE(processed_spy.at(0).at(0).value(), static_cast(job)); QCOMPARE(processed_spy.at(0).at(2).value(), qulonglong(1)); QCOMPARE(total_spy.size(), 0); QCOMPARE(percent_spy.size(), 0); /* Now, we know the total size. It's signaled. * The new percentage is signaled too. */ testJob->setTotalSize(10); QCOMPARE(processed_spy.size(), 1); QCOMPARE(total_spy.size(), 1); QCOMPARE(total_spy.at(0).at(0).value(), job); QCOMPARE(total_spy.at(0).at(2).value(), qulonglong(10)); QCOMPARE(percent_spy.size(), 1); QCOMPARE(percent_spy.at(0).at(0).value(), job); QCOMPARE(percent_spy.at(0).at(1).value(), static_cast(10)); /* We announce a new percentage by hand. * Total, and processed didn't change, so no signal is emitted for them. */ testJob->setPercent(15); QCOMPARE(processed_spy.size(), 1); QCOMPARE(total_spy.size(), 1); QCOMPARE(percent_spy.size(), 2); QCOMPARE(percent_spy.at(1).at(0).value(), job); QCOMPARE(percent_spy.at(1).at(1).value(), static_cast(15)); /* We make some progress. * Processed size and percent are signaled. */ testJob->setProcessedSize(3); QCOMPARE(processed_spy.size(), 2); QCOMPARE(processed_spy.at(1).at(0).value(), job); QCOMPARE(processed_spy.at(1).at(2).value(), qulonglong(3)); QCOMPARE(total_spy.size(), 1); QCOMPARE(percent_spy.size(), 3); QCOMPARE(percent_spy.at(2).at(0).value(), job); QCOMPARE(percent_spy.at(2).at(1).value(), static_cast(30)); /* We set a new total size, but equals to the previous one. * No signal is emitted. */ testJob->setTotalSize(10); QCOMPARE(processed_spy.size(), 2); QCOMPARE(total_spy.size(), 1); QCOMPARE(percent_spy.size(), 3); /* We 'lost' the previous work done. * Signals both percentage and new processed size. */ testJob->setProcessedSize(0); QCOMPARE(processed_spy.size(), 3); QCOMPARE(processed_spy.at(2).at(0).value(), job); QCOMPARE(processed_spy.at(2).at(2).value(), qulonglong(0)); QCOMPARE(total_spy.size(), 1); QCOMPARE(percent_spy.size(), 4); QCOMPARE(percent_spy.at(3).at(0).value(), job); QCOMPARE(percent_spy.at(3).at(1).value(), static_cast(0)); /* We process more than the total size!? * Signals both percentage and new processed size. * Percentage is 150% * * Might sounds weird, but verify that this case is handled gracefully. */ testJob->setProcessedSize(15); QCOMPARE(processed_spy.size(), 4); QCOMPARE(processed_spy.at(3).at(0).value(), job); QCOMPARE(processed_spy.at(3).at(2).value(), qulonglong(15)); QCOMPARE(total_spy.size(), 1); QCOMPARE(percent_spy.size(), 5); QCOMPARE(percent_spy.at(4).at(0).value(), job); QCOMPARE(percent_spy.at(4).at(1).value(), static_cast(150)); delete job; } void KJobTest::testExec_data() { QTest::addColumn("errorCode"); QTest::addColumn("errorText"); QTest::newRow("no error") << int(KJob::NoError) << QString(); QTest::newRow("error no text") << 2 << QString(); QTest::newRow("error with text") << 6 << "oops! an error? naaah, really?"; } void KJobTest::testExec() { TestJob *job = new TestJob; QFETCH(int, errorCode); QFETCH(QString, errorText); job->setError(errorCode); job->setErrorText(errorText); int resultEmitted = 0; // Prove to Kai Uwe that one can connect a job to a lambdas, despite the "private" signal connect(job, &KJob::result, this, [&resultEmitted](KJob *) { ++resultEmitted; }); QSignalSpy destroyed_spy(job, SIGNAL(destroyed(QObject*))); bool status = job->exec(); QCOMPARE(resultEmitted, 1); QCOMPARE(status, (errorCode == KJob::NoError)); QCOMPARE(job->error(), errorCode); QCOMPARE(job->errorText(), errorText); // Verify that the job is not deleted immediately... QCOMPARE(destroyed_spy.size(), 0); QTimer::singleShot(0, &loop, SLOT(quit())); // ... but when we enter the event loop again. loop.exec(); QCOMPARE(destroyed_spy.size(), 1); } void KJobTest::testKill_data() { QTest::addColumn("killVerbosity"); QTest::addColumn("errorCode"); QTest::addColumn("errorText"); QTest::addColumn("resultEmitCount"); QTest::addColumn("finishedEmitCount"); QTest::newRow("killed with result") << int(KJob::EmitResult) << int(KJob::KilledJobError) << QString() << 1 << 1; QTest::newRow("killed quietly") << int(KJob::Quietly) << int(KJob::KilledJobError) << QString() << 0 << 1; } void KJobTest::testKill() { TestJob *job = new TestJob; connect(job, &KJob::result, this, &KJobTest::slotResult); connect(job, &KJob::finished, this, &KJobTest::slotFinished); m_lastError = KJob::NoError; m_lastErrorText.clear(); m_resultCount = 0; m_finishedCount = 0; QFETCH(int, killVerbosity); QFETCH(int, errorCode); QFETCH(QString, errorText); QFETCH(int, resultEmitCount); QFETCH(int, finishedEmitCount); QSignalSpy destroyed_spy(job, SIGNAL(destroyed(QObject*))); job->kill(static_cast(killVerbosity)); loop.processEvents(QEventLoop::AllEvents, 2000); QCOMPARE(m_lastError, errorCode); QCOMPARE(m_lastErrorText, errorText); QCOMPARE(job->error(), errorCode); QCOMPARE(job->errorText(), errorText); QCOMPARE(m_resultCount, resultEmitCount); QCOMPARE(m_finishedCount, finishedEmitCount); // Verify that the job is not deleted immediately... QCOMPARE(destroyed_spy.size(), 0); QTimer::singleShot(0, &loop, SLOT(quit())); // ... but when we enter the event loop again. loop.exec(); QCOMPARE(destroyed_spy.size(), 1); } void KJobTest::testDelegateUsage() { TestJob *job1 = new TestJob; TestJob *job2 = new TestJob; TestJobUiDelegate *delegate = new TestJobUiDelegate; QPointer guard(delegate); QVERIFY(job1->uiDelegate() == nullptr); job1->setUiDelegate(delegate); QVERIFY(job1->uiDelegate() == delegate); QVERIFY(job2->uiDelegate() == nullptr); job2->setUiDelegate(delegate); QVERIFY(job2->uiDelegate() == nullptr); delete job1; delete job2; QVERIFY(guard.isNull()); // deleted by job1 } void KJobTest::testNestedExec() { m_innerJob = nullptr; QTimer::singleShot(100, this, SLOT(slotStartInnerJob())); m_outerJob = new WaitJob(); m_outerJob->exec(); } void KJobTest::slotStartInnerJob() { QTimer::singleShot(100, this, SLOT(slotFinishOuterJob())); m_innerJob = new WaitJob(); m_innerJob->exec(); } void KJobTest::slotFinishOuterJob() { QTimer::singleShot(100, this, SLOT(slotFinishInnerJob())); m_outerJob->makeItFinish(); } void KJobTest::slotFinishInnerJob() { m_innerJob->makeItFinish(); } void KJobTest::slotResult(KJob *job) { if (job->error()) { m_lastError = job->error(); m_lastErrorText = job->errorText(); } else { m_lastError = KJob::NoError; m_lastErrorText.clear(); } m_resultCount++; loop.quit(); } void KJobTest::slotFinished(KJob *job) { if (job->error()) { m_lastError = job->error(); m_lastErrorText = job->errorText(); } else { m_lastError = KJob::NoError; m_lastErrorText.clear(); } m_finishedCount++; } TestJob::TestJob() : KJob() { } TestJob::~TestJob() { } void TestJob::start() { QTimer::singleShot(0, this, SLOT(doEmit())); } bool TestJob::doKill() { return true; } void TestJob::setError(int errorCode) { KJob::setError(errorCode); } void TestJob::setErrorText(const QString &errorText) { KJob::setErrorText(errorText); } void TestJob::setProcessedSize(qulonglong size) { KJob::setProcessedAmount(KJob::Bytes, size); } void TestJob::setTotalSize(qulonglong size) { KJob::setTotalAmount(KJob::Bytes, size); } void TestJob::setPercent(unsigned long percentage) { KJob::setPercent(percentage); } void TestJob::doEmit() { emitResult(); } void WaitJob::start() { } void WaitJob::makeItFinish() { emitResult(); } void TestJobUiDelegate::connectJob(KJob *job) { QVERIFY(job->uiDelegate() != nullptr); } #include "moc_kjobtest.cpp" diff --git a/autotests/kjobtest.h b/autotests/kjobtest.h index 1a22bcc..08f351a 100644 --- a/autotests/kjobtest.h +++ b/autotests/kjobtest.h @@ -1,108 +1,108 @@ /* This file is part of the KDE project Copyright (C) 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KJOBTEST_H #define KJOBTEST_H -#include -#include +#include +#include #include "kjob.h" #include "kjobuidelegate.h" class TestJob : public KJob { Q_OBJECT public: TestJob(); ~TestJob() override; void start() Q_DECL_OVERRIDE; protected: bool doKill() Q_DECL_OVERRIDE; public: void setError(int errorCode); void setErrorText(const QString &errorText); void setProcessedSize(qulonglong size); void setTotalSize(qulonglong size); void setPercent(unsigned long percentage); private Q_SLOTS: void doEmit(); }; class TestJobUiDelegate : public KJobUiDelegate { Q_OBJECT protected: virtual void connectJob(KJob *job); }; class WaitJob; class KJobTest : public QObject { Q_OBJECT public: KJobTest(); public Q_SLOTS: // These slots need to be public, otherwise qtestlib calls them as part of the test void slotStartInnerJob(); void slotFinishOuterJob(); void slotFinishInnerJob(); private Q_SLOTS: void testEmitResult_data(); void testEmitResult(); void testProgressTracking(); void testExec_data(); void testExec(); void testKill_data(); void testKill(); void testDelegateUsage(); void testNestedExec(); void slotResult(KJob *job); void slotFinished(KJob *job); private: QEventLoop loop; int m_lastError; QString m_lastErrorText; int m_resultCount; int m_finishedCount; WaitJob *m_outerJob; WaitJob *m_innerJob; }; class WaitJob : public KJob { Q_OBJECT public: void start() Q_DECL_OVERRIDE; void makeItFinish(); }; #endif diff --git a/autotests/kmacroexpandertest.cpp b/autotests/kmacroexpandertest.cpp index 7705927..b587f3b 100644 --- a/autotests/kmacroexpandertest.cpp +++ b/autotests/kmacroexpandertest.cpp @@ -1,287 +1,287 @@ /* This file is part of the KDE libraries Copyright (c) 2003,2008 Oswald Buddenhagen Copyright (c) 2005 Thomas Braxton This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include -#include -#include +#include +#include class KMacroExpanderTest : public QObject { Q_OBJECT private Q_SLOTS: void expandMacros(); void expandMacrosShellQuote(); void expandMacrosShellQuoteParens(); void expandMacrosSubClass(); }; class MyCExpander : public KCharMacroExpander { QString exp; public: MyCExpander() : KCharMacroExpander(), exp("expanded") { } protected: bool expandMacro(QChar ch, QStringList &ret) { if (ch == 'm') { ret = QStringList(exp); return true; } return false; } }; class MyWExpander : public KWordMacroExpander { QString exp; public: MyWExpander() : KWordMacroExpander(), exp("expanded") { } protected: bool expandMacro(const QString &str, QStringList &ret) { if (str == QLatin1String("macro")) { ret = QStringList(exp); return true; } return false; } }; void KMacroExpanderTest::expandMacros() { QHash map; QStringList list; QString s; list << QString("Restaurant \"Chew It\""); map.insert('n', list); list.clear(); list << QString("element1") << QString("'element2'"); map.insert('l', list); s = "%% text %l text %n"; QCOMPARE(KMacroExpander::expandMacros(s, map), QLatin1String("% text element1 'element2' text Restaurant \"Chew It\"")); s = "text \"%l %n\" text"; QCOMPARE(KMacroExpander::expandMacros(s, map), QLatin1String("text \"element1 'element2' Restaurant \"Chew It\"\" text")); QHash map2; map2.insert('a', "%n"); map2.insert('f', "filename.txt"); map2.insert('u', "https://www.kde.org/index.html"); map2.insert('n', "Restaurant \"Chew It\""); s = "Title: %a - %f - %u - %n - %%"; QCOMPARE(KMacroExpander::expandMacros(s, map2), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\" - %")); QHash smap; smap.insert("foo", "%n"); smap.insert("file", "filename.txt"); smap.insert("url", "https://www.kde.org/index.html"); smap.insert("name", "Restaurant \"Chew It\""); s = "Title: %foo - %file - %url - %name - %"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\" - %")); s = "%foo - %file - %url - %name"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("%n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\"")); s = "Title: %{foo} - %{file} - %{url} - %{name} - %"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\" - %")); s = "%{foo} - %{file} - %{url} - %{name}"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("%n - filename.txt - https://www.kde.org/index.html - Restaurant \"Chew It\"")); s = "Title: %foo-%file-%url-%name-%"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: %n-filename.txt-https://www.kde.org/index.html-Restaurant \"Chew It\"-%")); s = "Title: %{file} %{url"; QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String("Title: filename.txt %{url")); s = " * Copyright (C) 2008 %{AUTHOR}"; smap.clear(); QCOMPARE(KMacroExpander::expandMacros(s, smap), QLatin1String(" * Copyright (C) 2008 %{AUTHOR}")); } void KMacroExpanderTest::expandMacrosShellQuote() { QHash map; QStringList list; QString s; list << QString("Restaurant \"Chew It\""); map.insert('n', list); list.clear(); list << QString("element1") << QString("'element2'") << QString("\"element3\""); map.insert('l', list); #ifdef Q_OS_WIN s = "text %l %n text"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text element1 'element2' \\^\"element3\\^\" \"Restaurant \"\\^\"\"Chew It\"\\^\" text")); s = "text \"%l %n\" text"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text \"element1 'element2' \"\\^\"\"element3\"\\^\"\" Restaurant \"\\^\"\"Chew It\"\\^\"\"\" text")); #else s = "text %l %n text"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text element1 ''\\''element2'\\''' '\"element3\"' 'Restaurant \"Chew It\"' text")); s = "text \"%l %n\" text"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map), QLatin1String("text \"element1 'element2' \\\"element3\\\" Restaurant \\\"Chew It\\\"\" text")); #endif QHash map2; map2.insert('a', "%n"); map2.insert('f', "filename.txt"); map2.insert('u', "https://www.kde.org/index.html"); map2.insert('n', "Restaurant \"Chew It\""); #ifdef Q_OS_WIN s = "Title: %a - %f - %u - %n - %% - %VARIABLE% foo"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("Title: %PERCENT_SIGN%n - filename.txt - https://www.kde.org/index.html - \"Restaurant \"\\^\"\"Chew It\"\\^\" - %PERCENT_SIGN% - %VARIABLE% foo")); s = "kedit --caption %n %f"; map2.insert('n', "Restaurant 'Chew It'"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt")); s = "kedit --caption \"%n\" %f"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt")); map2.insert('n', "Restaurant \"Chew It\""); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \"\\^\"\"Chew It\"\\^\"\"\" filename.txt")); map2.insert('n', "Restaurant %HOME%"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant %PERCENT_SIGN%HOME%PERCENT_SIGN%\" filename.txt")); s = "kedit c:\\%f"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit c:\\filename.txt")); s = "kedit \"c:\\%f\""; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\filename.txt\"")); map2.insert('f', "\"filename.txt\""); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\\\\"\\^\"\"filename.txt\"\\^\"\"\"")); map2.insert('f', "path\\"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit \"c:\\path\\\\\"\"\"")); #else s = "Title: %a - %f - %u - %n - %%"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("Title: %n - filename.txt - https://www.kde.org/index.html - 'Restaurant \"Chew It\"' - %")); s = "kedit --caption %n %f"; map2.insert('n', "Restaurant 'Chew It'"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption 'Restaurant '\\''Chew It'\\''' filename.txt")); s = "kedit --caption \"%n\" %f"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant 'Chew It'\" filename.txt")); map2.insert('n', "Restaurant \"Chew It\""); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\\"Chew It\\\"\" filename.txt")); map2.insert('n', "Restaurant $HOME"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\$HOME\" filename.txt")); map2.insert('n', "Restaurant `echo hello`"); QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"Restaurant \\`echo hello\\`\" filename.txt")); s = "kedit --caption \"`echo %n`\" %f"; QCOMPARE(KMacroExpander::expandMacrosShellQuote(s, map2), QLatin1String("kedit --caption \"$( echo 'Restaurant `echo hello`')\" filename.txt")); #endif } class DummyMacroExpander : public KMacroExpanderBase { public: DummyMacroExpander() : KMacroExpanderBase(QChar(0x4567)) { } protected: int expandPlainMacro(const QString &, int, QStringList &) { return 0; } int expandEscapedMacro(const QString &, int, QStringList &) { return 0; } }; void KMacroExpanderTest::expandMacrosShellQuoteParens() { QHash map; QStringList list; QString s; s = "( echo \"just testing (parens)\" ) ) after"; int pos = 0; DummyMacroExpander kmx; QVERIFY(kmx.expandMacrosShellQuote(s, pos)); QCOMPARE(s.mid(pos), QLatin1String(") after")); QVERIFY(!kmx.expandMacrosShellQuote(s)); } void KMacroExpanderTest::expandMacrosSubClass() { QString s; MyCExpander mx1; s = "subst %m but not %n equ %%"; mx1.expandMacros(s); QCOMPARE(s, QLatin1String("subst expanded but not %n equ %")); MyWExpander mx2; s = "subst %macro but not %not equ %%"; mx2.expandMacros(s); QCOMPARE(s, QLatin1String("subst expanded but not %not equ %")); } QTEST_MAIN(KMacroExpanderTest) #include "kmacroexpandertest.moc" diff --git a/autotests/kprocesstest.cpp b/autotests/kprocesstest.cpp index 121513b..4ef8687 100644 --- a/autotests/kprocesstest.cpp +++ b/autotests/kprocesstest.cpp @@ -1,118 +1,118 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Oswald Buddenhagen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include -#include +#include #include #include #include #include class KProcessTest : public QObject { Q_OBJECT private Q_SLOTS: void test_channels(); void test_setShellCommand(); }; // IOCCC nomination pending static char **gargv; static QString recurse(KProcess::OutputChannelMode how) { QProcess p; p.setProcessChannelMode(QProcess::MergedChannels); p.start(QString::fromLatin1(gargv[0]), QStringList() << QString::number(how) << QStringLiteral("--nocrashhandler")); p.waitForFinished(); return QString::fromLatin1(p.readAllStandardOutput()); } #define EOUT "foo - stdout" #define EERR "bar - stderr" #define POUT "program output:\n" #define ROUT "received stdout:\n" #define RERR "received stderr:\n" #define EO EOUT "\n" #define EE EERR "\n" #define TESTCHAN(me,ms,pout,rout,rerr) \ e = QStringLiteral("mode: " ms "\n" POUT pout ROUT rout RERR rerr); \ a = QStringLiteral("mode: " ms "\n") + recurse(KProcess::me); \ QCOMPARE(a, e) void KProcessTest::test_channels() { #ifdef Q_OS_UNIX QString e, a; TESTCHAN(SeparateChannels, "separate", "", EO, EE); TESTCHAN(ForwardedChannels, "forwarded", EO EE, "", ""); TESTCHAN(OnlyStderrChannel, "forwarded stdout", EO, "", EE); TESTCHAN(OnlyStdoutChannel, "forwarded stderr", EE, EO, ""); TESTCHAN(MergedChannels, "merged", "", EO EE, ""); #else Q_UNUSED(recurse); QSKIP("This test needs a UNIX system"); #endif } void KProcessTest::test_setShellCommand() { // Condition copied from kprocess.cpp #if !defined(__linux__) && !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) && !defined(__GNU__) QSKIP("This test needs a free UNIX system"); #else KProcess p; p.setShellCommand(QStringLiteral("cat")); QCOMPARE(p.program().count(), 1); QCOMPARE(p.program().at(0), QStandardPaths::findExecutable(QStringLiteral("cat"))); QVERIFY(p.program().at(0).endsWith(QStringLiteral("/bin/cat"))); p.setShellCommand(QStringLiteral("true || false")); QCOMPARE(p.program(), QStringList() << QStringLiteral("/bin/sh") << QStringLiteral("-c") << QString::fromLatin1("true || false")); #endif } static void recursor(char **argv) { if (argv[1]) { KProcess p; p.setShellCommand(QString::fromLatin1("echo " EOUT "; echo " EERR " >&2")); p.setOutputChannelMode(static_cast(atoi(argv[1]))); fputs(POUT, stdout); fflush(stdout); p.execute(); fputs(ROUT, stdout); fputs(p.readAllStandardOutput().constData(), stdout); fputs(RERR, stdout); fputs(p.readAllStandardError().constData(), stdout); exit(0); } gargv = argv; } QTEST_MAIN(recursor(argv); KProcessTest) #include "kprocesstest.moc" diff --git a/autotests/kshareddatacachetest.cpp b/autotests/kshareddatacachetest.cpp index 8f0b54b..ac163aa 100644 --- a/autotests/kshareddatacachetest.cpp +++ b/autotests/kshareddatacachetest.cpp @@ -1,78 +1,78 @@ /* This file is part of the KDE libraries * Copyright (c) 2012 David Faure * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License or ( at * your option ) version 3 or, at the discretion of KDE e.V. ( which shall * act as a proxy as in section 14 of the GPLv3 ), any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include // strcpy class KSharedDataCacheTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void simpleInsert(); }; void KSharedDataCacheTest::initTestCase() { } void KSharedDataCacheTest::simpleInsert() { const QLatin1String cacheName("myTestCache"); const QLatin1String key("mypic"); // clear the cache QString cacheFile = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/") + cacheName + QLatin1String(".kcache"); QFile file(cacheFile); if (file.exists()) { QVERIFY(file.remove()); } // insert something into it KSharedDataCache cache(cacheName, 5 * 1024 * 1024); #ifndef Q_OS_WIN // the windows implementation is currently only memory based and not really shared QVERIFY(file.exists()); // make sure we got the cache filename right #endif QByteArray data; data.resize(9228); strcpy(data.data(), "Hello world"); QVERIFY(cache.insert(key, data)); // read it out again QByteArray result; QVERIFY(cache.find(key, &result)); QCOMPARE(result, data); // another insert strcpy(data.data(), "Hello KDE"); QVERIFY(cache.insert(key, data)); // and another read QVERIFY(cache.find(key, &result)); QCOMPARE(result, data); } QTEST_MAIN(KSharedDataCacheTest) #include "kshareddatacachetest.moc" diff --git a/autotests/kshelltest.cpp b/autotests/kshelltest.cpp index 2147022..c465e3b 100644 --- a/autotests/kshelltest.cpp +++ b/autotests/kshelltest.cpp @@ -1,253 +1,253 @@ /* This file is part of the KDE libraries Copyright (c) 2003,2007-2008 Oswald Buddenhagen Copyright (c) 2005 Thomas Braxton This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include -#include +#include -#include -#include -#include -#include +#include +#include +#include +#include class KShellTest : public QObject { Q_OBJECT private Q_SLOTS: void tildeExpand(); void quoteArg(); void joinArgs(); void splitJoin(); void quoteSplit(); void quoteSplit_data(); void abortOnMeta(); }; // The expansion of ~me isn't exactly QDir::homePath(), in case $HOME has a trailing slash, it's kept. static QString myHomePath() { #ifdef Q_OS_WIN return QDir::homePath(); #else return QString::fromLocal8Bit(qgetenv("HOME")); #endif } void KShellTest::tildeExpand() { QString me(KUser().loginName()); QCOMPARE(KShell::tildeExpand(QStringLiteral("~")), QDir::homePath()); QCOMPARE(KShell::tildeExpand(QStringLiteral("~/dir")), QString(QDir::homePath() + QStringLiteral("/dir"))); QCOMPARE(KShell::tildeExpand(QLatin1Char('~') + me), myHomePath()); QCOMPARE(KShell::tildeExpand(QLatin1Char('~') + me + QStringLiteral("/dir")), QString(myHomePath() + QStringLiteral("/dir"))); #ifdef Q_OS_WIN QCOMPARE(KShell::tildeExpand(QStringLiteral("^~") + me), QString(QLatin1Char('~') + me)); #else QCOMPARE(KShell::tildeExpand(QStringLiteral("\\~") + me), QString(QStringLiteral("~") + me)); #endif } void KShellTest::quoteArg() { #ifdef Q_OS_WIN QCOMPARE(KShell::quoteArg(QStringLiteral("a space")), QStringLiteral("\"a space\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("fds\\\"")), QStringLiteral("fds\\\\\\^\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("\\\\foo")), QStringLiteral("\\\\foo")); QCOMPARE(KShell::quoteArg(QStringLiteral("\"asdf\"")), QStringLiteral("\\^\"asdf\\^\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("with\\")), QStringLiteral("\"with\\\\\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("\\\\")), QStringLiteral("\"\\\\\\\\\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("\"a space\\\"")), QStringLiteral("\\^\"\"a space\"\\\\\\^\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("as df\\")), QStringLiteral("\"as df\\\\\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("foo bar\"\\\"bla")), QStringLiteral("\"foo bar\"\\^\"\\\\\\^\"\"bla\"")); QCOMPARE(KShell::quoteArg(QStringLiteral("a % space")), QStringLiteral("\"a %PERCENT_SIGN% space\"")); #else QCOMPARE(KShell::quoteArg(QStringLiteral("a space")), QStringLiteral("'a space'")); #endif } void KShellTest::joinArgs() { QStringList list; list << QStringLiteral("this") << QStringLiteral("is") << QStringLiteral("a") << QStringLiteral("test"); QCOMPARE(KShell::joinArgs(list), QStringLiteral("this is a test")); } static QString sj(const QString &str, KShell::Options flags, KShell::Errors *ret) { return KShell::joinArgs(KShell::splitArgs(str, flags, ret)); } void KShellTest::splitJoin() { KShell::Errors err = KShell::NoError; #ifdef Q_OS_WIN QCOMPARE(sj(QStringLiteral("\"(sulli)\" text"), KShell::NoOptions, &err), QStringLiteral("\"(sulli)\" text")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral(" ha\\ lo "), KShell::NoOptions, &err), QStringLiteral("\"ha\\\\\" lo")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("say \" error"), KShell::NoOptions, &err), QString()); QVERIFY(err == KShell::BadQuoting); QCOMPARE(sj(QStringLiteral("no \" error\""), KShell::NoOptions, &err), QStringLiteral("no \" error\"")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("say \" still error"), KShell::NoOptions, &err), QString()); QVERIFY(err == KShell::BadQuoting); QCOMPARE(sj(QStringLiteral("BLA;asdf sdfess d"), KShell::NoOptions, &err), QStringLiteral("\"BLA;asdf\" sdfess d")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("B\"L\"A&sdf FOO|bar sdf wer "), KShell::NoOptions, &err), QStringLiteral("\"BLA&sdf\" \"FOO|bar\" sdf wer")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("\"\"\"just \"\" fine\"\"\""), KShell::NoOptions, &err), QStringLiteral("\\^\"\"just \"\\^\"\" fine\"\\^\"")); QVERIFY(err == KShell::NoError); #else QCOMPARE(sj(QString::fromUtf8("\"~qU4rK\" 'text' 'jo'\"jo\" $'crap' $'\\\\\\'\\e\\x21' ha\\ lo \\a"), KShell::NoOptions, &err), QString::fromUtf8("'~qU4rK' text jojo crap '\\'\\''\x1b!' 'ha lo' a")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("\"~qU4rK\" 'text'"), KShell::TildeExpand, &err), QStringLiteral("'~qU4rK' text")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("~\"qU4rK\" 'text'"), KShell::TildeExpand, &err), QStringLiteral("'~qU4rK' text")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("~/\"dir\" 'text'"), KShell::TildeExpand, &err), QString(QDir::homePath() + QStringLiteral("/dir text"))); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("~ 'text' ~"), KShell::TildeExpand, &err), QString(QDir::homePath() + QStringLiteral(" text ") + QDir::homePath())); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("\\~ blah"), KShell::TildeExpand, &err), QStringLiteral("'~' blah")); QVERIFY(err == KShell::NoError); QCOMPARE(sj(QStringLiteral("~qU4rK ~") + KUser().loginName(), KShell::TildeExpand, &err), QString(QStringLiteral("'~qU4rK' ") + myHomePath())); QVERIFY(err == KShell::NoError); const QString unicodeSpaceFileName = QStringLiteral("test テスト.txt"); // #345140 QCOMPARE(sj(unicodeSpaceFileName, KShell::AbortOnMeta | KShell::TildeExpand, &err), unicodeSpaceFileName); QVERIFY(err == KShell::NoError); #endif } void KShellTest::quoteSplit_data() { QTest::addColumn("string"); QTest::newRow("no space") << QStringLiteral("hiho"); QTest::newRow("regular space") << QStringLiteral("hi there"); QTest::newRow("special space") << QString::fromUtf8("如何定期清潔典型的電風扇 講義.pdf"); } void KShellTest::quoteSplit() { QFETCH(QString, string); // Splitting a quote arg should always just return one argument const QStringList args = KShell::splitArgs(KShell::quoteArg(string)); QCOMPARE(args.count(), 1); } void KShellTest::abortOnMeta() { KShell::Errors err1 = KShell::NoError, err2 = KShell::NoError; QCOMPARE(sj(QStringLiteral("text"), KShell::AbortOnMeta, &err1), QStringLiteral("text")); QVERIFY(err1 == KShell::NoError); #ifdef Q_OS_WIN QVERIFY(KShell::splitArgs(QStringLiteral("BLA & asdf sdfess d"), KShell::AbortOnMeta, &err1).isEmpty()); QVERIFY(err1 == KShell::FoundMeta); QVERIFY(KShell::splitArgs(QStringLiteral("foo %PATH% bar"), KShell::AbortOnMeta, &err1).isEmpty()); QVERIFY(err1 == KShell::FoundMeta); QCOMPARE(sj(QStringLiteral("foo %PERCENT_SIGN% bar"), KShell::AbortOnMeta, &err1), QStringLiteral("foo %PERCENT_SIGN% bar")); QVERIFY(err1 == KShell::NoError); QCOMPARE(sj(QStringLiteral("@foo ^& bar"), KShell::AbortOnMeta, &err1), QStringLiteral("foo \"&\" bar")); QVERIFY(err1 == KShell::NoError); QCOMPARE(sj(QStringLiteral("\"BLA|asdf\" sdfess d"), KShell::AbortOnMeta, &err1), QStringLiteral("\"BLA|asdf\" sdfess d")); QVERIFY(err1 == KShell::NoError); QCOMPARE(sj(QStringLiteral("B\"L\"A\"|\"sdf \"FOO | bar\" sdf wer"), KShell::AbortOnMeta, &err1), QStringLiteral("\"BLA|sdf\" \"FOO | bar\" sdf wer")); QVERIFY(err1 == KShell::NoError); QCOMPARE(sj(QStringLiteral("b-q me \\\\^|\\\\\\^\""), KShell::AbortOnMeta, &err1), QStringLiteral("b-q me \"\\\\|\"\\\\\\^\"")); QVERIFY(err1 == KShell::NoError); #else QCOMPARE(sj(QStringLiteral("say \" error"), KShell::NoOptions, &err1), QString()); QVERIFY(err1 != KShell::NoError); QCOMPARE(sj(QStringLiteral("say \" still error"), KShell::AbortOnMeta, &err1), QString()); QVERIFY(err1 != KShell::NoError); QVERIFY(sj(QStringLiteral("say `echo no error`"), KShell::NoOptions, &err1) != sj(QStringLiteral("say `echo no error`"), KShell::AbortOnMeta, &err2)); QVERIFY(err1 != err2); QVERIFY(sj(QStringLiteral("BLA=say echo meta"), KShell::NoOptions, &err1) != sj(QStringLiteral("BLA=say echo meta"), KShell::AbortOnMeta, &err2)); QVERIFY(err1 != err2); QVERIFY(sj(QStringLiteral("B\"L\"A=say FOO=bar echo meta"), KShell::NoOptions, &err1) == sj(QStringLiteral("B\"L\"A=say FOO=bar echo meta"), KShell::AbortOnMeta, &err2)); #endif } QTEST_MAIN(KShellTest) #include "kshelltest.moc" diff --git a/autotests/kstringhandlertest.h b/autotests/kstringhandlertest.h index 6b0f986..e016f01 100644 --- a/autotests/kstringhandlertest.h +++ b/autotests/kstringhandlertest.h @@ -1,24 +1,24 @@ #ifndef KSTRINGHANDLERTEST_H #define KSTRINGHANDLERTEST_H -#include +#include class KStringHandlerTest : public QObject { Q_OBJECT private Q_SLOTS: void capwords(); void tagURLs(); void perlSplit(); void obscure(); void preProcessWrap_data(); void preProcessWrap(); void logicalLength_data(); void logicalLength(); private: static QString test; }; #endif diff --git a/autotests/ktexttohtmltest.h b/autotests/ktexttohtmltest.h index b299df9..f1a3d88 100644 --- a/autotests/ktexttohtmltest.h +++ b/autotests/ktexttohtmltest.h @@ -1,41 +1,41 @@ /* Copyright (c) 2007 Allen Winter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KTEXTTOHTMLTEST_H #define KTEXTTOHTMLTEST_H -#include +#include class KTextToHTMLTest : public QObject { Q_OBJECT private Q_SLOTS: void testGetEmailAddress(); void testGetUrl(); void testHtmlConvert(); void testHtmlConvert_data(); void benchHtmlConvert(); void benchHtmlConvert_data(); private: void testGetUrl2(const QString &left, const QString &right); }; #endif diff --git a/autotests/kurlmimedatatest.h b/autotests/kurlmimedatatest.h index db34551..1c90b16 100644 --- a/autotests/kurlmimedatatest.h +++ b/autotests/kurlmimedatatest.h @@ -1,36 +1,36 @@ /* This file is part of the KDE libraries Copyright (c) 2005 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KURLMIMEDATATEST_H #define KURLMIMEDATATEST_H -#include +#include class KUrlMimeDataTest : public QObject { Q_OBJECT private Q_SLOTS: void testURLList(); void testOneURL(); void testFromQUrl(); void testMostLocalUrlList_data(); void testMostLocalUrlList(); }; #endif diff --git a/src/lib/caching/kshareddatacache.cpp b/src/lib/caching/kshareddatacache.cpp index 5b0b958..76be9ee 100644 --- a/src/lib/caching/kshareddatacache.cpp +++ b/src/lib/caching/kshareddatacache.cpp @@ -1,1717 +1,1717 @@ /* * This file is part of the KDE project. * Copyright © 2010, 2012 Michael Pyne * Copyright © 2012 Ralf Jung * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library includes "MurmurHash" code from Austin Appleby, which is * placed in the public domain. See http://sites.google.com/site/murmurhash/ * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kshareddatacache.h" #include "kshareddatacache_p.h" // Various auxiliary support code #include "kcoreaddons_debug.h" #include "qstandardpaths.h" #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include /// The maximum number of probes to make while searching for a bucket in /// the presence of collisions in the cache index table. static const uint MAX_PROBE_COUNT = 6; /** * A very simple class whose only purpose is to be thrown as an exception from * underlying code to indicate that the shared cache is apparently corrupt. * This must be caught by top-level library code and used to unlink the cache * in this circumstance. * * @internal */ class KSDCCorrupted { public: KSDCCorrupted() { qCritical() << "Error detected in cache, re-generating"; } }; //----------------------------------------------------------------------------- // MurmurHashAligned, by Austin Appleby // (Released to the public domain, or licensed under the MIT license where // software may not be released to the public domain. See // http://sites.google.com/site/murmurhash/) // Same algorithm as MurmurHash, but only does aligned reads - should be safer // on certain platforms. static unsigned int MurmurHashAligned(const void *key, int len, unsigned int seed) { const unsigned int m = 0xc6a4a793; const int r = 16; const unsigned char *data = reinterpret_cast(key); unsigned int h = seed ^ (len * m); int align = reinterpret_cast(data) & 3; if (align && len >= 4) { // Pre-load the temp registers unsigned int t = 0, d = 0; switch (align) { case 1: t |= data[2] << 16; case 2: t |= data[1] << 8; case 3: t |= data[0]; } t <<= (8 * align); data += 4 - align; len -= 4 - align; int sl = 8 * (4 - align); int sr = 8 * align; // Mix while (len >= 4) { d = *reinterpret_cast(data); t = (t >> sr) | (d << sl); h += t; h *= m; h ^= h >> r; t = d; data += 4; len -= 4; } // Handle leftover data in temp registers int pack = len < align ? len : align; d = 0; switch (pack) { case 3: d |= data[2] << 16; case 2: d |= data[1] << 8; case 1: d |= data[0]; case 0: h += (t >> sr) | (d << sl); h *= m; h ^= h >> r; } data += pack; len -= pack; } else { while (len >= 4) { h += *reinterpret_cast(data); h *= m; h ^= h >> r; data += 4; len -= 4; } } //---------- // Handle tail bytes switch (len) { case 3: h += data[2] << 16; case 2: h += data[1] << 8; case 1: h += data[0]; h *= m; h ^= h >> r; }; h *= m; h ^= h >> 10; h *= m; h ^= h >> 17; return h; } /** * This is the hash function used for our data to hopefully make the * hashing used to place the QByteArrays as efficient as possible. */ static quint32 generateHash(const QByteArray &buffer) { // The final constant is the "seed" for MurmurHash. Do *not* change it // without incrementing the cache version. return MurmurHashAligned(buffer.data(), buffer.size(), 0xF0F00F0F); } // Alignment concerns become a big deal when we're dealing with shared memory, // since trying to access a structure sized at, say 8 bytes at an address that // is not evenly divisible by 8 is a crash-inducing error on some // architectures. The compiler would normally take care of this, but with // shared memory the compiler will not necessarily know the alignment expected, // so make sure we account for this ourselves. To do so we need a way to find // out the expected alignment. Enter ALIGNOF... #ifndef ALIGNOF #if defined(Q_CC_GNU) || defined(Q_CC_SUN) #define ALIGNOF(x) (__alignof__ (x)) // GCC provides what we want directly #else #include // offsetof template struct __alignmentHack { char firstEntry; T obj; static const size_t size = offsetof(__alignmentHack, obj); }; #define ALIGNOF(x) (__alignmentHack::size) #endif // Non gcc #endif // ALIGNOF undefined // Returns a pointer properly aligned to handle size alignment. // size should be a power of 2. start is assumed to be the lowest // permissible address, therefore the return value will be >= start. template T *alignTo(const void *start, uint size = ALIGNOF(T)) { quintptr mask = size - 1; // Cast to int-type to handle bit-twiddling quintptr basePointer = reinterpret_cast(start); // If (and only if) we are already aligned, adding mask into basePointer // will not increment any of the bits in ~mask and we get the right answer. basePointer = (basePointer + mask) & ~mask; return reinterpret_cast(basePointer); } /** * Returns a pointer to a const object of type T, assumed to be @p offset * *BYTES* greater than the base address. Note that in order to meet alignment * requirements for T, it is possible that the returned pointer points greater * than @p offset into @p base. */ template const T *offsetAs(const void *const base, qint32 offset) { const char *ptr = reinterpret_cast(base); return alignTo(ptr + offset); } // Same as above, but for non-const objects template T *offsetAs(void *const base, qint32 offset) { char *ptr = reinterpret_cast(base); return alignTo(ptr + offset); } /** * @return the smallest integer greater than or equal to (@p a / @p b). * @param a Numerator, should be ≥ 0. * @param b Denominator, should be > 0. */ static unsigned intCeil(unsigned a, unsigned b) { // The overflow check is unsigned and so is actually defined behavior. if (Q_UNLIKELY(b == 0 || ((a + b) < a))) { throw KSDCCorrupted(); } return (a + b - 1) / b; } /** * @return number of set bits in @p value (see also "Hamming weight") */ static unsigned countSetBits(unsigned value) { // K&R / Wegner's algorithm used. GCC supports __builtin_popcount but we // expect there to always be only 1 bit set so this should be perhaps a bit // faster 99.9% of the time. unsigned count = 0; for (count = 0; value != 0; count++) { value &= (value - 1); // Clears least-significant set bit. } return count; } typedef qint32 pageID; // ========================================================================= // Description of the cache: // // The shared memory cache is designed to be handled as two separate objects, // all contained in the same global memory segment. First off, there is the // basic header data, consisting of the global header followed by the // accounting data necessary to hold items (described in more detail // momentarily). Following the accounting data is the start of the "page table" // (essentially just as you'd see it in an Operating Systems text). // // The page table contains shared memory split into fixed-size pages, with a // configurable page size. In the event that the data is too large to fit into // a single logical page, it will need to occupy consecutive pages of memory. // // The accounting data that was referenced earlier is split into two: // // 1. index table, containing a fixed-size list of possible cache entries. // Each index entry is of type IndexTableEntry (below), and holds the various // accounting data and a pointer to the first page. // // 2. page table, which is used to speed up the process of searching for // free pages of memory. There is one entry for every page in the page table, // and it contains the index of the one entry in the index table actually // holding the page (or <0 if the page is free). // // The entire segment looks like so: // ?════════?═════════════?════════════?═══════?═══════?═══════?═══════?═══? // ? Header │ Index Table │ Page Table ? Pages │ │ │ │...? // ?════════?═════════════?════════════?═══════?═══════?═══════?═══════?═══? // ========================================================================= // All elements of this struct must be "plain old data" (POD) types since it // will be in shared memory. In addition, no pointers! To point to something // you must use relative offsets since the pointer start addresses will be // different in each process. struct IndexTableEntry { uint fileNameHash; uint totalItemSize; // in bytes mutable uint useCount; time_t addTime; mutable time_t lastUsedTime; pageID firstPage; }; // Page table entry struct PageTableEntry { // int so we can use values <0 for unassigned pages. qint32 index; }; // Each individual page contains the cached data. The first page starts off with // the utf8-encoded key, a null '\0', and then the data follows immediately // from the next byte, possibly crossing consecutive page boundaries to hold // all of the data. // There is, however, no specific struct for a page, it is simply a location in // memory. // This is effectively the layout of the shared memory segment. The variables // contained within form the header, data contained afterwards is pointed to // by using special accessor functions. struct SharedMemory { /** * Note to downstream packagers: This version flag is intended to be * machine-specific. The KDE-provided source code will not set the lower * two bits to allow for distribution-specific needs, with the exception * of version 1 which was already defined in KDE Platform 4.5. * e.g. the next version bump will be from 4 to 8, then 12, etc. */ enum { PIXMAP_CACHE_VERSION = 12, MINIMUM_CACHE_SIZE = 4096 }; // Note to those who follow me. You should not, under any circumstances, ever // re-arrange the following two fields, even if you change the version number // for later revisions of this code. QAtomicInt ready; ///< DO NOT INITIALIZE quint8 version; // See kshareddatacache_p.h SharedLock shmLock; uint cacheSize; uint cacheAvail; QAtomicInt evictionPolicy; // pageSize and cacheSize determine the number of pages. The number of // pages determine the page table size and (indirectly) the index table // size. QAtomicInt pageSize; // This variable is added to reserve space for later cache timestamping // support. The idea is this variable will be updated when the cache is // written to, to allow clients to detect a changed cache quickly. QAtomicInt cacheTimestamp; /** * Converts the given average item size into an appropriate page size. */ static unsigned equivalentPageSize(unsigned itemSize) { if (itemSize == 0) { return 4096; // Default average item size. } int log2OfSize = 0; while ((itemSize >>= 1) != 0) { log2OfSize++; } // Bound page size between 512 bytes and 256 KiB. // If this is adjusted, also alter validSizeMask in cachePageSize log2OfSize = qBound(9, log2OfSize, 18); return (1 << log2OfSize); } // Returns pageSize in unsigned format. unsigned cachePageSize() const { unsigned _pageSize = static_cast(pageSize.load()); // bits 9-18 may be set. static const unsigned validSizeMask = 0x7FE00u; // Check for page sizes that are not a power-of-2, or are too low/high. if (Q_UNLIKELY(countSetBits(_pageSize) != 1 || (_pageSize & ~validSizeMask))) { throw KSDCCorrupted(); } return _pageSize; } /** * This is effectively the class ctor. But since we're in shared memory, * there's a few rules: * * 1. To allow for some form of locking in the initial-setup case, we * use an atomic int, which will be initialized to 0 by mmap(). Then to * take the lock we atomically increment the 0 to 1. If we end up calling * the QAtomicInt constructor we can mess that up, so we can't use a * constructor for this class either. * 2. Any member variable you add takes up space in shared memory as well, * so make sure you need it. */ bool performInitialSetup(uint _cacheSize, uint _pageSize) { if (_cacheSize < MINIMUM_CACHE_SIZE) { qCritical() << "Internal error: Attempted to create a cache sized < " << MINIMUM_CACHE_SIZE; return false; } if (_pageSize == 0) { qCritical() << "Internal error: Attempted to create a cache with 0-sized pages."; return false; } shmLock.type = findBestSharedLock(); if (shmLock.type == LOCKTYPE_INVALID) { qCritical() << "Unable to find an appropriate lock to guard the shared cache. " << "This *should* be essentially impossible. :("; return false; } bool isProcessShared = false; QSharedPointer tempLock(createLockFromId(shmLock.type, shmLock)); if (!tempLock->initialize(isProcessShared)) { qCritical() << "Unable to initialize the lock for the cache!"; return false; } if (!isProcessShared) { qCWarning(KCOREADDONS_DEBUG) << "Cache initialized, but does not support being" << "shared across processes."; } // These must be updated to make some of our auxiliary functions // work right since their values will be based on the cache size. cacheSize = _cacheSize; pageSize = _pageSize; version = PIXMAP_CACHE_VERSION; cacheTimestamp = static_cast(::time(nullptr)); clearInternalTables(); // Unlock the mini-lock, and introduce a total memory barrier to make // sure all changes have propagated even without a mutex. ready.ref(); return true; } void clearInternalTables() { // Assumes we're already locked somehow. cacheAvail = pageTableSize(); // Setup page tables to point nowhere PageTableEntry *table = pageTable(); for (uint i = 0; i < pageTableSize(); ++i) { table[i].index = -1; } // Setup index tables to be accurate. IndexTableEntry *indices = indexTable(); for (uint i = 0; i < indexTableSize(); ++i) { indices[i].firstPage = -1; indices[i].useCount = 0; indices[i].fileNameHash = 0; indices[i].totalItemSize = 0; indices[i].addTime = 0; indices[i].lastUsedTime = 0; } } const IndexTableEntry *indexTable() const { // Index Table goes immediately after this struct, at the first byte // where alignment constraints are met (accounted for by offsetAs). return offsetAs(this, sizeof(*this)); } const PageTableEntry *pageTable() const { const IndexTableEntry *base = indexTable(); base += indexTableSize(); // Let's call wherever we end up the start of the page table... return alignTo(base); } const void *cachePages() const { const PageTableEntry *tableStart = pageTable(); tableStart += pageTableSize(); // Let's call wherever we end up the start of the data... return alignTo(tableStart, cachePageSize()); } const void *page(pageID at) const { if (static_cast(at) >= pageTableSize()) { return nullptr; } // We must manually calculate this one since pageSize varies. const char *pageStart = reinterpret_cast(cachePages()); pageStart += (at * cachePageSize()); return reinterpret_cast(pageStart); } // The following are non-const versions of some of the methods defined // above. They use const_cast<> because I feel that is better than // duplicating the code. I suppose template member functions (?) // may work, may investigate later. IndexTableEntry *indexTable() { const SharedMemory *that = const_cast(this); return const_cast(that->indexTable()); } PageTableEntry *pageTable() { const SharedMemory *that = const_cast(this); return const_cast(that->pageTable()); } void *cachePages() { const SharedMemory *that = const_cast(this); return const_cast(that->cachePages()); } void *page(pageID at) { const SharedMemory *that = const_cast(this); return const_cast(that->page(at)); } uint pageTableSize() const { return cacheSize / cachePageSize(); } uint indexTableSize() const { // Assume 2 pages on average are needed -> the number of entries // would be half of the number of pages. return pageTableSize() / 2; } /** * @return the index of the first page, for the set of contiguous * pages that can hold @p pagesNeeded PAGES. */ pageID findEmptyPages(uint pagesNeeded) const { if (Q_UNLIKELY(pagesNeeded > pageTableSize())) { return pageTableSize(); } // Loop through the page table, find the first empty page, and just // makes sure that there are enough free pages. const PageTableEntry *table = pageTable(); uint contiguousPagesFound = 0; pageID base = 0; for (pageID i = 0; i < static_cast(pageTableSize()); ++i) { if (table[i].index < 0) { if (contiguousPagesFound == 0) { base = i; } contiguousPagesFound++; } else { contiguousPagesFound = 0; } if (contiguousPagesFound == pagesNeeded) { return base; } } return pageTableSize(); } // left < right? static bool lruCompare(const IndexTableEntry &l, const IndexTableEntry &r) { // Ensure invalid entries migrate to the end if (l.firstPage < 0 && r.firstPage >= 0) { return false; } if (l.firstPage >= 0 && r.firstPage < 0) { return true; } // Most recently used will have the highest absolute time => // least recently used (lowest) should go first => use left < right return l.lastUsedTime < r.lastUsedTime; } // left < right? static bool seldomUsedCompare(const IndexTableEntry &l, const IndexTableEntry &r) { // Ensure invalid entries migrate to the end if (l.firstPage < 0 && r.firstPage >= 0) { return false; } if (l.firstPage >= 0 && r.firstPage < 0) { return true; } // Put lowest use count at start by using left < right return l.useCount < r.useCount; } // left < right? static bool ageCompare(const IndexTableEntry &l, const IndexTableEntry &r) { // Ensure invalid entries migrate to the end if (l.firstPage < 0 && r.firstPage >= 0) { return false; } if (l.firstPage >= 0 && r.firstPage < 0) { return true; } // Oldest entries die first -- they have the lowest absolute add time, // so just like the others use left < right return l.addTime < r.addTime; } void defragment() { if (cacheAvail * cachePageSize() == cacheSize) { return; // That was easy } qCDebug(KCOREADDONS_DEBUG) << "Defragmenting the shared cache"; // Just do a linear scan, and anytime there is free space, swap it // with the pages to its right. In order to meet the precondition // we need to skip any used pages first. pageID currentPage = 0; pageID idLimit = static_cast(pageTableSize()); PageTableEntry *pages = pageTable(); if (Q_UNLIKELY(!pages || idLimit <= 0)) { throw KSDCCorrupted(); } // Skip used pages while (currentPage < idLimit && pages[currentPage].index >= 0) { ++currentPage; } pageID freeSpot = currentPage; // Main loop, starting from a free page, skip to the used pages and // move them back. while (currentPage < idLimit) { // Find the next used page while (currentPage < idLimit && pages[currentPage].index < 0) { ++currentPage; } if (currentPage >= idLimit) { break; } // Found an entry, move it. qint32 affectedIndex = pages[currentPage].index; if (Q_UNLIKELY(affectedIndex < 0 || affectedIndex >= idLimit || indexTable()[affectedIndex].firstPage != currentPage)) { throw KSDCCorrupted(); } indexTable()[affectedIndex].firstPage = freeSpot; // Moving one page at a time guarantees we can use memcpy safely // (in other words, the source and destination will not overlap). while (currentPage < idLimit && pages[currentPage].index >= 0) { const void *const sourcePage = page(currentPage); void *const destinationPage = page(freeSpot); // We always move used pages into previously-found empty spots, // so check ordering as well for logic errors. if (Q_UNLIKELY(!sourcePage || !destinationPage || sourcePage < destinationPage)) { throw KSDCCorrupted(); } ::memcpy(destinationPage, sourcePage, cachePageSize()); pages[freeSpot].index = affectedIndex; pages[currentPage].index = -1; ++currentPage; ++freeSpot; // If we've just moved the very last page and it happened to // be at the very end of the cache then we're done. if (currentPage >= idLimit) { break; } // We're moving consecutive used pages whether they belong to // our affected entry or not, so detect if we've started moving // the data for a different entry and adjust if necessary. if (affectedIndex != pages[currentPage].index) { indexTable()[pages[currentPage].index].firstPage = freeSpot; } affectedIndex = pages[currentPage].index; } // At this point currentPage is on a page that is unused, and the // cycle repeats. However, currentPage is not the first unused // page, freeSpot is, so leave it alone. } } /** * Finds the index entry for a given key. * @param key UTF-8 encoded key to search for. * @return The index of the entry in the cache named by @p key. Returns * <0 if no such entry is present. */ qint32 findNamedEntry(const QByteArray &key) const { uint keyHash = generateHash(key); uint position = keyHash % indexTableSize(); uint probeNumber = 1; // See insert() for description // Imagine 3 entries A, B, C in this logical probing chain. If B is // later removed then we can't find C either. So, we must keep // searching for probeNumber number of tries (or we find the item, // obviously). while (indexTable()[position].fileNameHash != keyHash && probeNumber < MAX_PROBE_COUNT) { position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2) % indexTableSize(); probeNumber++; } if (indexTable()[position].fileNameHash == keyHash) { pageID firstPage = indexTable()[position].firstPage; if (firstPage < 0 || static_cast(firstPage) >= pageTableSize()) { return -1; } const void *resultPage = page(firstPage); if (Q_UNLIKELY(!resultPage)) { throw KSDCCorrupted(); } const char *utf8FileName = reinterpret_cast(resultPage); if (qstrncmp(utf8FileName, key.constData(), cachePageSize()) == 0) { return position; } } return -1; // Not found, or a different one found. } // Function to use with QSharedPointer in removeUsedPages below... static void deleteTable(IndexTableEntry *table) { delete [] table; } /** * Removes the requested number of pages. * * @param numberNeeded the number of pages required to fulfill a current request. * This number should be <0 and <= the number of pages in the cache. * @return The identifier of the beginning of a consecutive block of pages able * to fill the request. Returns a value >= pageTableSize() if no such * request can be filled. * @internal */ uint removeUsedPages(uint numberNeeded) { if (numberNeeded == 0) { qCritical() << "Internal error: Asked to remove exactly 0 pages for some reason."; throw KSDCCorrupted(); } if (numberNeeded > pageTableSize()) { qCritical() << "Internal error: Requested more space than exists in the cache."; qCritical() << numberNeeded << "requested, " << pageTableSize() << "is the total possible."; throw KSDCCorrupted(); } // If the cache free space is large enough we will defragment first // instead since it's likely we're highly fragmented. // Otherwise, we will (eventually) simply remove entries per the // eviction order set for the cache until there is enough room // available to hold the number of pages we need. qCDebug(KCOREADDONS_DEBUG) << "Removing old entries to free up" << numberNeeded << "pages," << cacheAvail << "are already theoretically available."; if (cacheAvail > 3 * numberNeeded) { defragment(); uint result = findEmptyPages(numberNeeded); if (result < pageTableSize()) { return result; } else { qCritical() << "Just defragmented a locked cache, but still there" << "isn't enough room for the current request."; } } // At this point we know we'll have to free some space up, so sort our // list of entries by whatever the current criteria are and start // killing expired entries. QSharedPointer tablePtr(new IndexTableEntry[indexTableSize()], deleteTable); if (!tablePtr) { qCritical() << "Unable to allocate temporary memory for sorting the cache!"; clearInternalTables(); throw KSDCCorrupted(); } // We use tablePtr to ensure the data is destroyed, but do the access // via a helper pointer to allow for array ops. IndexTableEntry *table = tablePtr.data(); ::memcpy(table, indexTable(), sizeof(IndexTableEntry) * indexTableSize()); // Our entry ID is simply its index into the // index table, which qSort will rearrange all willy-nilly, so first // we'll save the *real* entry ID into firstPage (which is useless in // our copy of the index table). On the other hand if the entry is not // used then we note that with -1. for (uint i = 0; i < indexTableSize(); ++i) { table[i].firstPage = table[i].useCount > 0 ? static_cast(i) : -1; } // Declare the comparison function that we'll use to pass to qSort, // based on our cache eviction policy. bool (*compareFunction)(const IndexTableEntry &, const IndexTableEntry &); switch (evictionPolicy.load()) { case KSharedDataCache::EvictLeastOftenUsed: case KSharedDataCache::NoEvictionPreference: default: compareFunction = seldomUsedCompare; break; case KSharedDataCache::EvictLeastRecentlyUsed: compareFunction = lruCompare; break; case KSharedDataCache::EvictOldest: compareFunction = ageCompare; break; } qSort(table, table + indexTableSize(), compareFunction); // Least recently used entries will be in the front. // Start killing until we have room. // Note on removeEntry: It expects an index into the index table, // but our sorted list is all jumbled. But we stored the real index // in the firstPage member. // Remove entries until we've removed at least the required number // of pages. uint i = 0; while (i < indexTableSize() && numberNeeded > cacheAvail) { int curIndex = table[i++].firstPage; // Really an index, not a page // Removed everything, still no luck (or curIndex is set but too high). if (curIndex < 0 || static_cast(curIndex) >= indexTableSize()) { qCritical() << "Trying to remove index" << curIndex << "out-of-bounds for index table of size" << indexTableSize(); throw KSDCCorrupted(); } qCDebug(KCOREADDONS_DEBUG) << "Removing entry of" << indexTable()[curIndex].totalItemSize << "size"; removeEntry(curIndex); } // At this point let's see if we have freed up enough data by // defragmenting first and seeing if we can find that free space. defragment(); pageID result = pageTableSize(); while (i < indexTableSize() && (static_cast(result = findEmptyPages(numberNeeded))) >= pageTableSize()) { int curIndex = table[i++].firstPage; if (curIndex < 0) { // One last shot. defragment(); return findEmptyPages(numberNeeded); } if (Q_UNLIKELY(static_cast(curIndex) >= indexTableSize())) { throw KSDCCorrupted(); } removeEntry(curIndex); } // Whew. return result; } // Returns the total size required for a given cache size. static uint totalSize(uint cacheSize, uint effectivePageSize) { uint numberPages = intCeil(cacheSize, effectivePageSize); uint indexTableSize = numberPages / 2; // Knowing the number of pages, we can determine what addresses we'd be // using (properly aligned), and from there determine how much memory // we'd use. IndexTableEntry *indexTableStart = offsetAs(static_cast(nullptr), sizeof(SharedMemory)); indexTableStart += indexTableSize; PageTableEntry *pageTableStart = reinterpret_cast(indexTableStart); pageTableStart = alignTo(pageTableStart); pageTableStart += numberPages; // The weird part, we must manually adjust the pointer based on the page size. char *cacheStart = reinterpret_cast(pageTableStart); cacheStart += (numberPages * effectivePageSize); // ALIGNOF gives pointer alignment cacheStart = alignTo(cacheStart, ALIGNOF(void *)); // We've traversed the header, index, page table, and cache. // Wherever we're at now is the size of the enchilada. return static_cast(reinterpret_cast(cacheStart)); } uint fileNameHash(const QByteArray &utf8FileName) const { return generateHash(utf8FileName) % indexTableSize(); } void clear() { clearInternalTables(); } void removeEntry(uint index); }; // The per-instance private data, such as map size, whether // attached or not, pointer to shared memory, etc. class Q_DECL_HIDDEN KSharedDataCache::Private { public: Private(const QString &name, unsigned defaultCacheSize, unsigned expectedItemSize ) : m_cacheName(name) , shm(nullptr) , m_lock() , m_mapSize(0) , m_defaultCacheSize(defaultCacheSize) , m_expectedItemSize(expectedItemSize) , m_expectedType(LOCKTYPE_INVALID) { mapSharedMemory(); } // Put the cache in a condition to be able to call mapSharedMemory() by // completely detaching from shared memory (such as to respond to an // unrecoverable error). // m_mapSize must already be set to the amount of memory mapped to shm. void detachFromSharedMemory() { // The lock holds a reference into shared memory, so this must be // cleared before shm is removed. m_lock.clear(); if (shm && 0 != ::munmap(shm, m_mapSize)) { qCritical() << "Unable to unmap shared memory segment" << static_cast(shm) << ":" << ::strerror(errno); } shm = nullptr; m_mapSize = 0; } // This function does a lot of the important work, attempting to connect to shared // memory, a private anonymous mapping if that fails, and failing that, nothing (but // the cache remains "valid", we just don't actually do anything). void mapSharedMemory() { // 0-sized caches are fairly useless. unsigned cacheSize = qMax(m_defaultCacheSize, uint(SharedMemory::MINIMUM_CACHE_SIZE)); unsigned pageSize = SharedMemory::equivalentPageSize(m_expectedItemSize); // Ensure that the cache is sized such that there is a minimum number of // pages available. (i.e. a cache consisting of only 1 page is fairly // useless and probably crash-prone). cacheSize = qMax(pageSize * 256, cacheSize); // The m_cacheName is used to find the file to store the cache in. const QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); QString cacheName = cacheDir + QLatin1String("/") + m_cacheName + QLatin1String(".kcache"); QFile file(cacheName); QFileInfo fileInfo(file); if (!QDir().mkpath(fileInfo.absolutePath())) { return; } // The basic idea is to open the file that we want to map into shared // memory, and then actually establish the mapping. Once we have mapped the // file into shared memory we can close the file handle, the mapping will // still be maintained (unless the file is resized to be shorter than // expected, which we don't handle yet :-( ) // size accounts for the overhead over the desired cacheSize uint size = SharedMemory::totalSize(cacheSize, pageSize); void *mapAddress = MAP_FAILED; if (size < cacheSize) { qCritical() << "Asked for a cache size less than requested size somehow -- Logic Error :("; return; } // We establish the shared memory mapping here, only if we will have appropriate // mutex support (systemSupportsProcessSharing), then we: // Open the file and resize to some sane value if the file is too small. if (file.open(QIODevice::ReadWrite) && (file.size() >= size || (file.resize(size) && ensureFileAllocated(file.handle(), size)))) { // Use mmap directly instead of QFile::map since the QFile (and its // shared mapping) will disappear unless we hang onto the QFile for no // reason (see the note below, we don't care about the file per se...) mapAddress = QT_MMAP(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, file.handle(), 0); // So... it is possible that someone else has mapped this cache already // with a larger size. If that's the case we need to at least match // the size to be able to access every entry, so fixup the mapping. if (mapAddress != MAP_FAILED) { SharedMemory *mapped = reinterpret_cast(mapAddress); // First make sure that the version of the cache on disk is // valid. We also need to check that version != 0 to // disambiguate against an uninitialized cache. if (mapped->version != SharedMemory::PIXMAP_CACHE_VERSION && mapped->version > 0) { qCWarning(KCOREADDONS_DEBUG) << "Deleting wrong version of cache" << cacheName; // CAUTION: Potentially recursive since the recovery // involves calling this function again. m_mapSize = size; shm = mapped; recoverCorruptedCache(); return; } else if (mapped->cacheSize > cacheSize) { // This order is very important. We must save the cache size // before we remove the mapping, but unmap before overwriting // the previous mapping size... cacheSize = mapped->cacheSize; unsigned actualPageSize = mapped->cachePageSize(); ::munmap(mapAddress, size); size = SharedMemory::totalSize(cacheSize, actualPageSize); mapAddress = QT_MMAP(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, file.handle(), 0); } } } // We could be here without the mapping established if: // 1) Process-shared synchronization is not supported, either at compile or run time, // 2) Unable to open the required file. // 3) Unable to resize the file to be large enough. // 4) Establishing the mapping failed. // 5) The mapping succeeded, but the size was wrong and we were unable to map when // we tried again. // 6) The incorrect version of the cache was detected. // 7) The file could be created, but posix_fallocate failed to commit it fully to disk. // In any of these cases, attempt to fallback to the // better-supported anonymous private page style of mmap. This memory won't // be shared, but our code will still work the same. // NOTE: We never use the on-disk representation independently of the // shared memory. If we don't get shared memory the disk info is ignored, // if we do get shared memory we never look at disk again. if (mapAddress == MAP_FAILED) { qCWarning(KCOREADDONS_DEBUG) << "Failed to establish shared memory mapping, will fallback" << "to private memory -- memory usage will increase"; mapAddress = QT_MMAP(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); } // Well now we're really hosed. We can still work, but we can't even cache // data. if (mapAddress == MAP_FAILED) { qCritical() << "Unable to allocate shared memory segment for shared data cache" << cacheName << "of size" << cacheSize; return; } m_mapSize = size; // We never actually construct shm, but we assign it the same address as the // shared memory we just mapped, so effectively shm is now a SharedMemory that // happens to be located at mapAddress. shm = reinterpret_cast(mapAddress); // If we were first to create this memory map, all data will be 0. // Therefore if ready == 0 we're not initialized. A fully initialized // header will have ready == 2. Why? // Because 0 means "safe to initialize" // 1 means "in progress of initing" // 2 means "ready" uint usecSleepTime = 8; // Start by sleeping for 8 microseconds while (shm->ready.load() != 2) { if (Q_UNLIKELY(usecSleepTime >= (1 << 21))) { // Didn't acquire within ~8 seconds? Assume an issue exists qCritical() << "Unable to acquire shared lock, is the cache corrupt?"; file.remove(); // Unlink the cache in case it's corrupt. detachFromSharedMemory(); return; // Fallback to QCache (later) } if (shm->ready.testAndSetAcquire(0, 1)) { if (!shm->performInitialSetup(cacheSize, pageSize)) { qCritical() << "Unable to perform initial setup, this system probably " "does not really support process-shared pthreads or " "semaphores, even though it claims otherwise."; file.remove(); detachFromSharedMemory(); return; } } else { usleep(usecSleepTime); // spin // Exponential fallback as in Ethernet and similar collision resolution methods usecSleepTime *= 2; } } m_expectedType = shm->shmLock.type; m_lock = QSharedPointer(createLockFromId(m_expectedType, shm->shmLock)); bool isProcessSharingSupported = false; if (!m_lock->initialize(isProcessSharingSupported)) { qCritical() << "Unable to setup shared cache lock, although it worked when created."; detachFromSharedMemory(); } } // Called whenever the cache is apparently corrupt (for instance, a timeout trying to // lock the cache). In this situation it is safer just to destroy it all and try again. void recoverCorruptedCache() { KSharedDataCache::deleteCache(m_cacheName); detachFromSharedMemory(); // Do this even if we weren't previously cached -- it might work now. mapSharedMemory(); } // This should be called for any memory access to shared memory. This // function will verify that the bytes [base, base+accessLength) are // actually mapped to d->shm. The cache itself may have incorrect cache // page sizes, incorrect cache size, etc. so this function should be called // despite the cache data indicating it should be safe. // // If the access is /not/ safe then a KSDCCorrupted exception will be // thrown, so be ready to catch that. void verifyProposedMemoryAccess(const void *base, unsigned accessLength) const { quintptr startOfAccess = reinterpret_cast(base); quintptr startOfShm = reinterpret_cast(shm); if (Q_UNLIKELY(startOfAccess < startOfShm)) { throw KSDCCorrupted(); } quintptr endOfShm = startOfShm + m_mapSize; quintptr endOfAccess = startOfAccess + accessLength; // Check for unsigned integer wraparound, and then // bounds access if (Q_UNLIKELY((endOfShm < startOfShm) || (endOfAccess < startOfAccess) || (endOfAccess > endOfShm))) { throw KSDCCorrupted(); } } bool lock() const { if (Q_LIKELY(shm && shm->shmLock.type == m_expectedType)) { return m_lock->lock(); } // No shm or wrong type --> corrupt! throw KSDCCorrupted(); } void unlock() const { m_lock->unlock(); } class CacheLocker { mutable Private *d; bool cautiousLock() { int lockCount = 0; // Locking can fail due to a timeout. If it happens too often even though // we're taking corrective action assume there's some disastrous problem // and give up. while (!d->lock() && !isLockedCacheSafe()) { d->recoverCorruptedCache(); if (!d->shm) { qCWarning(KCOREADDONS_DEBUG) << "Lost the connection to shared memory for cache" << d->m_cacheName; return false; } if (lockCount++ > 4) { qCritical() << "There is a very serious problem with the KDE data cache" << d->m_cacheName << "giving up trying to access cache."; d->detachFromSharedMemory(); return false; } } return true; } // Runs a quick battery of tests on an already-locked cache and returns // false as soon as a sanity check fails. The cache remains locked in this // situation. bool isLockedCacheSafe() const { // Note that cachePageSize() itself runs a check that can throw. uint testSize = SharedMemory::totalSize(d->shm->cacheSize, d->shm->cachePageSize()); if (Q_UNLIKELY(d->m_mapSize != testSize)) { return false; } if (Q_UNLIKELY(d->shm->version != SharedMemory::PIXMAP_CACHE_VERSION)) { return false; } switch (d->shm->evictionPolicy.load()) { case NoEvictionPreference: // fallthrough case EvictLeastRecentlyUsed: // fallthrough case EvictLeastOftenUsed: // fallthrough case EvictOldest: break; default: return false; } return true; } public: CacheLocker(const Private *_d) : d(const_cast(_d)) { if (Q_UNLIKELY(!d || !d->shm || !cautiousLock())) { d = nullptr; } } ~CacheLocker() { if (d && d->shm) { d->unlock(); } } bool failed() const { return !d || d->shm == nullptr; } }; QString m_cacheName; SharedMemory *shm; QSharedPointer m_lock; uint m_mapSize; uint m_defaultCacheSize; uint m_expectedItemSize; SharedLockId m_expectedType; }; // Must be called while the lock is already held! void SharedMemory::removeEntry(uint index) { if (index >= indexTableSize() || cacheAvail > pageTableSize()) { throw KSDCCorrupted(); } PageTableEntry *pageTableEntries = pageTable(); IndexTableEntry *entriesIndex = indexTable(); // Update page table first pageID firstPage = entriesIndex[index].firstPage; if (firstPage < 0 || static_cast(firstPage) >= pageTableSize()) { qCDebug(KCOREADDONS_DEBUG) << "Trying to remove an entry which is already invalid. This " << "cache is likely corrupt."; throw KSDCCorrupted(); } if (index != static_cast(pageTableEntries[firstPage].index)) { qCritical() << "Removing entry" << index << "but the matching data" << "doesn't link back -- cache is corrupt, clearing."; throw KSDCCorrupted(); } uint entriesToRemove = intCeil(entriesIndex[index].totalItemSize, cachePageSize()); uint savedCacheSize = cacheAvail; for (uint i = firstPage; i < pageTableSize() && static_cast(pageTableEntries[i].index) == index; ++i) { pageTableEntries[i].index = -1; cacheAvail++; } if ((cacheAvail - savedCacheSize) != entriesToRemove) { qCritical() << "We somehow did not remove" << entriesToRemove << "when removing entry" << index << ", instead we removed" << (cacheAvail - savedCacheSize); throw KSDCCorrupted(); } // For debugging #ifdef NDEBUG void *const startOfData = page(firstPage); if (startOfData) { QByteArray str((const char *) startOfData); str.prepend(" REMOVED: "); str.prepend(QByteArray::number(index)); str.prepend("ENTRY "); ::memcpy(startOfData, str.constData(), str.size() + 1); } #endif // Update the index entriesIndex[index].fileNameHash = 0; entriesIndex[index].totalItemSize = 0; entriesIndex[index].useCount = 0; entriesIndex[index].lastUsedTime = 0; entriesIndex[index].addTime = 0; entriesIndex[index].firstPage = -1; } KSharedDataCache::KSharedDataCache(const QString &cacheName, unsigned defaultCacheSize, unsigned expectedItemSize) : d(nullptr) { try { d = new Private(cacheName, defaultCacheSize, expectedItemSize); } catch (KSDCCorrupted) { KSharedDataCache::deleteCache(cacheName); // Try only once more try { d = new Private(cacheName, defaultCacheSize, expectedItemSize); } catch (KSDCCorrupted) { qCritical() << "Even a brand-new cache starts off corrupted, something is" << "seriously wrong. :-("; d = nullptr; // Just in case } } } KSharedDataCache::~KSharedDataCache() { // Note that there is no other actions required to separate from the // shared memory segment, simply unmapping is enough. This makes things // *much* easier so I'd recommend maintaining this ideal. if (!d) { return; } if (d->shm) { #ifdef KSDC_MSYNC_SUPPORTED ::msync(d->shm, d->m_mapSize, MS_INVALIDATE | MS_ASYNC); #endif ::munmap(d->shm, d->m_mapSize); } // Do not delete d->shm, it was never constructed, it's just an alias. d->shm = nullptr; delete d; } bool KSharedDataCache::insert(const QString &key, const QByteArray &data) { try { Private::CacheLocker lock(d); if (lock.failed()) { return false; } QByteArray encodedKey = key.toUtf8(); uint keyHash = generateHash(encodedKey); uint position = keyHash % d->shm->indexTableSize(); // See if we're overwriting an existing entry. IndexTableEntry *indices = d->shm->indexTable(); // In order to avoid the issue of a very long-lived cache having items // with a use count of 1 near-permanently, we attempt to artifically // reduce the use count of long-lived items when there is high load on // the cache. We do this randomly, with a weighting that makes the event // impossible if load < 0.5, and guaranteed if load >= 0.96. const static double startCullPoint = 0.5l; const static double mustCullPoint = 0.96l; // cacheAvail is in pages, cacheSize is in bytes. double loadFactor = 1.0 - (1.0l * d->shm->cacheAvail * d->shm->cachePageSize() / d->shm->cacheSize); bool cullCollisions = false; if (Q_UNLIKELY(loadFactor >= mustCullPoint)) { cullCollisions = true; } else if (loadFactor > startCullPoint) { const int tripWireValue = RAND_MAX * (loadFactor - startCullPoint) / (mustCullPoint - startCullPoint); if (KRandom::random() >= tripWireValue) { cullCollisions = true; } } // In case of collisions in the index table (i.e. identical positions), use // quadratic chaining to attempt to find an empty slot. The equation we use // is: // position = (hash + (i + i*i) / 2) % size, where i is the probe number. uint probeNumber = 1; while (indices[position].useCount > 0 && probeNumber < MAX_PROBE_COUNT) { // If we actually stumbled upon an old version of the key we are // overwriting, then use that position, do not skip over it. if (Q_UNLIKELY(indices[position].fileNameHash == keyHash)) { break; } // If we are "culling" old entries, see if this one is old and if so // reduce its use count. If it reduces to zero then eliminate it and // use its old spot. if (cullCollisions && (::time(nullptr) - indices[position].lastUsedTime) > 60) { indices[position].useCount >>= 1; if (indices[position].useCount == 0) { qCDebug(KCOREADDONS_DEBUG) << "Overwriting existing old cached entry due to collision."; d->shm->removeEntry(position); // Remove it first break; } } position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2) % d->shm->indexTableSize(); probeNumber++; } if (indices[position].useCount > 0 && indices[position].firstPage >= 0) { //qCDebug(KCOREADDONS_DEBUG) << "Overwriting existing cached entry due to collision."; d->shm->removeEntry(position); // Remove it first } // Data will be stored as fileNamefoo\0PNGimagedata..... // So total size required is the length of the encoded file name + 1 // for the trailing null, and then the length of the image data. uint fileNameLength = 1 + encodedKey.length(); uint requiredSize = fileNameLength + data.size(); uint pagesNeeded = intCeil(requiredSize, d->shm->cachePageSize()); uint firstPage(-1); if (pagesNeeded >= d->shm->pageTableSize()) { qCWarning(KCOREADDONS_DEBUG) << key << "is too large to be cached."; return false; } // If the cache has no room, or the fragmentation is too great to find // the required number of consecutive free pages, take action. if (pagesNeeded > d->shm->cacheAvail || (firstPage = d->shm->findEmptyPages(pagesNeeded)) >= d->shm->pageTableSize()) { // If we have enough free space just defragment uint freePagesDesired = 3 * qMax(1u, pagesNeeded / 2); if (d->shm->cacheAvail > freePagesDesired) { // TODO: How the hell long does this actually take on real // caches? d->shm->defragment(); firstPage = d->shm->findEmptyPages(pagesNeeded); } else { // If we already have free pages we don't want to remove a ton // extra. However we can't rely on the return value of // removeUsedPages giving us a good location since we're not // passing in the actual number of pages that we need. d->shm->removeUsedPages(qMin(2 * freePagesDesired, d->shm->pageTableSize()) - d->shm->cacheAvail); firstPage = d->shm->findEmptyPages(pagesNeeded); } if (firstPage >= d->shm->pageTableSize() || d->shm->cacheAvail < pagesNeeded) { qCritical() << "Unable to free up memory for" << key; return false; } } // Update page table PageTableEntry *table = d->shm->pageTable(); for (uint i = 0; i < pagesNeeded; ++i) { table[firstPage + i].index = position; } // Update index indices[position].fileNameHash = keyHash; indices[position].totalItemSize = requiredSize; indices[position].useCount = 1; indices[position].addTime = ::time(nullptr); indices[position].lastUsedTime = indices[position].addTime; indices[position].firstPage = firstPage; // Update cache d->shm->cacheAvail -= pagesNeeded; // Actually move the data in place void *dataPage = d->shm->page(firstPage); if (Q_UNLIKELY(!dataPage)) { throw KSDCCorrupted(); } // Verify it will all fit d->verifyProposedMemoryAccess(dataPage, requiredSize); // Cast for byte-sized pointer arithmetic uchar *startOfPageData = reinterpret_cast(dataPage); ::memcpy(startOfPageData, encodedKey.constData(), fileNameLength); ::memcpy(startOfPageData + fileNameLength, data.constData(), data.size()); return true; } catch (KSDCCorrupted) { d->recoverCorruptedCache(); return false; } } bool KSharedDataCache::find(const QString &key, QByteArray *destination) const { try { Private::CacheLocker lock(d); if (lock.failed()) { return false; } // Search in the index for our data, hashed by key; QByteArray encodedKey = key.toUtf8(); qint32 entry = d->shm->findNamedEntry(encodedKey); if (entry >= 0) { const IndexTableEntry *header = &d->shm->indexTable()[entry]; const void *resultPage = d->shm->page(header->firstPage); if (Q_UNLIKELY(!resultPage)) { throw KSDCCorrupted(); } d->verifyProposedMemoryAccess(resultPage, header->totalItemSize); header->useCount++; header->lastUsedTime = ::time(nullptr); // Our item is the key followed immediately by the data, so skip // past the key. const char *cacheData = reinterpret_cast(resultPage); cacheData += encodedKey.size(); cacheData++; // Skip trailing null -- now we're pointing to start of data if (destination) { *destination = QByteArray(cacheData, header->totalItemSize - encodedKey.size() - 1); } return true; } } catch (KSDCCorrupted) { d->recoverCorruptedCache(); } return false; } void KSharedDataCache::clear() { try { Private::CacheLocker lock(d); if (!lock.failed()) { d->shm->clear(); } } catch (KSDCCorrupted) { d->recoverCorruptedCache(); } } bool KSharedDataCache::contains(const QString &key) const { try { Private::CacheLocker lock(d); if (lock.failed()) { return false; } return d->shm->findNamedEntry(key.toUtf8()) >= 0; } catch (KSDCCorrupted) { d->recoverCorruptedCache(); return false; } } void KSharedDataCache::deleteCache(const QString &cacheName) { QString cachePath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/") + cacheName + QLatin1String(".kcache"); // Note that it is important to simply unlink the file, and not truncate it // smaller first to avoid SIGBUS errors and similar with shared memory // attached to the underlying inode. qCDebug(KCOREADDONS_DEBUG) << "Removing cache at" << cachePath; QFile::remove(cachePath); } unsigned KSharedDataCache::totalSize() const { try { Private::CacheLocker lock(d); if (lock.failed()) { return 0u; } return d->shm->cacheSize; } catch (KSDCCorrupted) { d->recoverCorruptedCache(); return 0u; } } unsigned KSharedDataCache::freeSize() const { try { Private::CacheLocker lock(d); if (lock.failed()) { return 0u; } return d->shm->cacheAvail * d->shm->cachePageSize(); } catch (KSDCCorrupted) { d->recoverCorruptedCache(); return 0u; } } KSharedDataCache::EvictionPolicy KSharedDataCache::evictionPolicy() const { if (d && d->shm) { return static_cast(d->shm->evictionPolicy.fetchAndAddAcquire(0)); } return NoEvictionPreference; } void KSharedDataCache::setEvictionPolicy(EvictionPolicy newPolicy) { if (d && d->shm) { d->shm->evictionPolicy.fetchAndStoreRelease(static_cast(newPolicy)); } } unsigned KSharedDataCache::timestamp() const { if (d && d->shm) { return static_cast(d->shm->cacheTimestamp.fetchAndAddAcquire(0)); } return 0; } void KSharedDataCache::setTimestamp(unsigned newTimestamp) { if (d && d->shm) { d->shm->cacheTimestamp.fetchAndStoreRelease(static_cast(newTimestamp)); } } diff --git a/src/lib/caching/kshareddatacache_p.h b/src/lib/caching/kshareddatacache_p.h index e244d46..094ac51 100644 --- a/src/lib/caching/kshareddatacache_p.h +++ b/src/lib/caching/kshareddatacache_p.h @@ -1,498 +1,498 @@ /* * This file is part of the KDE project. * Copyright © 2010 Michael Pyne * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KSHAREDDATACACHE_P_H #define KSHAREDDATACACHE_P_H #include // HAVE_SYS_MMAN_H -#include -#include +#include +#include #include #include "kcoreaddons_debug.h" #include // Check for sched_yield #include // sched_yield #include #include #include // Mac OS X, for all its POSIX compliance, does not support timeouts on its // mutexes, which is kind of a disaster for cross-process support. However // synchronization primitives still work, they just might hang if the cache is // corrupted, so keep going. #if defined(_POSIX_TIMEOUTS) && ((_POSIX_TIMEOUTS == 0) || (_POSIX_TIMEOUTS >= 200112L)) #define KSDC_TIMEOUTS_SUPPORTED 1 #endif #if defined(__GNUC__) && !defined(KSDC_TIMEOUTS_SUPPORTED) #warning "No support for POSIX timeouts -- application hangs are possible if the cache is corrupt" #endif #if defined(_POSIX_THREAD_PROCESS_SHARED) && ((_POSIX_THREAD_PROCESS_SHARED == 0) || (_POSIX_THREAD_PROCESS_SHARED >= 200112L)) && !defined(__APPLE__) #include #define KSDC_THREAD_PROCESS_SHARED_SUPPORTED 1 #endif #if defined(_POSIX_SEMAPHORES) && ((_POSIX_SEMAPHORES == 0) || (_POSIX_SEMAPHORES >= 200112L)) #include #define KSDC_SEMAPHORES_SUPPORTED 1 #endif #if defined(__GNUC__) && !defined(KSDC_SEMAPHORES_SUPPORTED) && !defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) #warning "No system support claimed for process-shared synchronization, KSharedDataCache will be mostly useless." #endif #if defined(_POSIX_MAPPED_FILES) && ((_POSIX_MAPPED_FILES == 0) || (_POSIX_MAPPED_FILES >= 200112L)) #define KSDC_MAPPED_FILES_SUPPORTED 1 #endif #if defined(_POSIX_SYNCHRONIZED_IO) && ((_POSIX_SYNCHRONIZED_IO == 0) || (_POSIX_SYNCHRONIZED_IO >= 200112L)) #define KSDC_SYNCHRONIZED_IO_SUPPORTED 1 #endif // msync(2) requires both MAPPED_FILES and SYNCHRONIZED_IO POSIX options #if defined(KSDC_MAPPED_FILES_SUPPORTED) && defined(KSDC_SYNCHRONIZED_IO_SUPPORTED) #define KSDC_MSYNC_SUPPORTED #endif // posix_fallocate is used to ensure that the file used for the cache is // actually fully committed to disk before attempting to use the file. #if defined(_POSIX_ADVISORY_INFO) && ((_POSIX_ADVISORY_INFO == 0) || (_POSIX_ADVISORY_INFO >= 200112L)) #define KSDC_POSIX_FALLOCATE_SUPPORTED 1 #endif #ifdef Q_OS_OSX #include "posix_fallocate_mac.h" #define KSDC_POSIX_FALLOCATE_SUPPORTED 1 #endif // BSD/Mac OS X compat #if HAVE_SYS_MMAN_H #include #endif #if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) #define MAP_ANONYMOUS MAP_ANON #endif /** * This class defines an interface used by KSharedDataCache::Private to offload * proper locking and unlocking depending on what the platform supports at * runtime and compile-time. */ class KSDCLock { public: virtual ~KSDCLock() { } // Return value indicates if the mutex was properly initialized (including // threads-only as a fallback). virtual bool initialize(bool &processSharingSupported) { processSharingSupported = false; return false; } virtual bool lock() { return false; } virtual void unlock() { } }; /** * This is a very basic lock that should work on any system where GCC atomic * intrinsics are supported. It can waste CPU so better primitives should be * used if available on the system. */ class simpleSpinLock : public KSDCLock { public: simpleSpinLock(QBasicAtomicInt &spinlock) : m_spinlock(spinlock) { } bool initialize(bool &processSharingSupported) Q_DECL_OVERRIDE { // Clear the spinlock m_spinlock.store(0); processSharingSupported = true; return true; } bool lock() Q_DECL_OVERRIDE { // Spin a few times attempting to gain the lock, as upper-level code won't // attempt again without assuming the cache is corrupt. for (unsigned i = 50; i > 0; --i) { if (m_spinlock.testAndSetAcquire(0, 1)) { return true; } // Don't steal the processor and starve the thread we're waiting // on. loopSpinPause(); } return false; } void unlock() Q_DECL_OVERRIDE { m_spinlock.testAndSetRelease(1, 0); } private: #ifdef Q_CC_GNU __attribute__((always_inline, gnu_inline, artificial)) #endif static inline void loopSpinPause() { // TODO: Spinning might be better in multi-core systems... but that means // figuring how to find numbers of CPUs in a cross-platform way. #ifdef _POSIX_PRIORITY_SCHEDULING sched_yield(); #else // Sleep for shortest possible time (nanosleep should round-up). struct timespec wait_time = { 0 /* sec */, 100 /* ns */ }; ::nanosleep(&wait_time, static_cast(0)); #endif } QBasicAtomicInt &m_spinlock; }; #ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED class pthreadLock : public KSDCLock { public: pthreadLock(pthread_mutex_t &mutex) : m_mutex(mutex) { } bool initialize(bool &processSharingSupported) Q_DECL_OVERRIDE { // Setup process-sharing. pthread_mutexattr_t mutexAttr; processSharingSupported = false; // Initialize attributes, enable process-shared primitives, and setup // the mutex. if (::sysconf(_SC_THREAD_PROCESS_SHARED) >= 200112L && pthread_mutexattr_init(&mutexAttr) == 0) { if (pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED) == 0 && pthread_mutex_init(&m_mutex, &mutexAttr) == 0) { processSharingSupported = true; } pthread_mutexattr_destroy(&mutexAttr); } // Attempt to setup for thread-only synchronization. if (!processSharingSupported && pthread_mutex_init(&m_mutex, nullptr) != 0) { return false; } return true; } bool lock() Q_DECL_OVERRIDE { return pthread_mutex_lock(&m_mutex) == 0; } void unlock() Q_DECL_OVERRIDE { pthread_mutex_unlock(&m_mutex); } protected: pthread_mutex_t &m_mutex; }; #endif #if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED) class pthreadTimedLock : public pthreadLock { public: pthreadTimedLock(pthread_mutex_t &mutex) : pthreadLock(mutex) { } bool lock() Q_DECL_OVERRIDE { struct timespec timeout; // Long timeout, but if we fail to meet this timeout it's probably a cache // corruption (and if we take 8 seconds then it should be much much quicker // the next time anyways since we'd be paged back in from disk) timeout.tv_sec = 10 + ::time(nullptr); // Absolute time, so 10 seconds from now timeout.tv_nsec = 0; return pthread_mutex_timedlock(&m_mutex, &timeout) == 0; } }; #endif #ifdef KSDC_SEMAPHORES_SUPPORTED class semaphoreLock : public KSDCLock { public: semaphoreLock(sem_t &semaphore) : m_semaphore(semaphore) { } bool initialize(bool &processSharingSupported) Q_DECL_OVERRIDE { processSharingSupported = false; if (::sysconf(_SC_SEMAPHORES) < 200112L) { return false; } // sem_init sets up process-sharing for us. if (sem_init(&m_semaphore, 1, 1) == 0) { processSharingSupported = true; } // If not successful try falling back to thread-shared. else if (sem_init(&m_semaphore, 0, 1) != 0) { return false; } return true; } bool lock() Q_DECL_OVERRIDE { return sem_wait(&m_semaphore) == 0; } void unlock() Q_DECL_OVERRIDE { sem_post(&m_semaphore); } protected: sem_t &m_semaphore; }; #endif #if defined(KSDC_SEMAPHORES_SUPPORTED) && defined(KSDC_TIMEOUTS_SUPPORTED) class semaphoreTimedLock : public semaphoreLock { public: semaphoreTimedLock(sem_t &semaphore) : semaphoreLock(semaphore) { } bool lock() Q_DECL_OVERRIDE { struct timespec timeout; // Long timeout, but if we fail to meet this timeout it's probably a cache // corruption (and if we take 8 seconds then it should be much much quicker // the next time anyways since we'd be paged back in from disk) timeout.tv_sec = 10 + ::time(nullptr); // Absolute time, so 10 seconds from now timeout.tv_nsec = 0; return sem_timedwait(&m_semaphore, &timeout) == 0; } }; #endif // This enum controls the type of the locking used for the cache to allow // for as much portability as possible. This value will be stored in the // cache and used by multiple processes, therefore you should consider this // a versioned field, do not re-arrange. enum SharedLockId { LOCKTYPE_INVALID = 0, LOCKTYPE_MUTEX = 1, // pthread_mutex LOCKTYPE_SEMAPHORE = 2, // sem_t LOCKTYPE_SPINLOCK = 3 // atomic int in shared memory }; // This type is a union of all possible lock types, with a SharedLockId used // to choose which one is actually in use. struct SharedLock { union { #if defined(KSDC_THREAD_PROCESS_SHARED_SUPPORTED) pthread_mutex_t mutex; #endif #if defined(KSDC_SEMAPHORES_SUPPORTED) sem_t semaphore; #endif QBasicAtomicInt spinlock; // It would be highly unfortunate if a simple glibc upgrade or kernel // addition caused this structure to change size when an existing // lock was thought present, so reserve enough size to cover any // reasonable locking structure char unused[64]; }; SharedLockId type; }; /** * This is a method to determine the best lock type to use for a * shared cache, based on local support. An identifier to the appropriate * SharedLockId is returned, which can be passed to createLockFromId(). */ static SharedLockId findBestSharedLock() { // We would prefer a process-shared capability that also supports // timeouts. Failing that, process-shared is preferred over timeout // support. Failing that we'll go thread-local bool timeoutsSupported = false; bool pthreadsProcessShared = false; bool semaphoresProcessShared = false; #ifdef KSDC_TIMEOUTS_SUPPORTED timeoutsSupported = ::sysconf(_SC_TIMEOUTS) >= 200112L; #endif // Now that we've queried timeouts, try actually creating real locks and // seeing if there's issues with that. #ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED { pthread_mutex_t tempMutex; QSharedPointer tempLock; if (timeoutsSupported) { #ifdef KSDC_TIMEOUTS_SUPPORTED tempLock = QSharedPointer(new pthreadTimedLock(tempMutex)); #endif } else { tempLock = QSharedPointer(new pthreadLock(tempMutex)); } tempLock->initialize(pthreadsProcessShared); } #endif // Our first choice is pthread_mutex_t for compatibility. if (timeoutsSupported && pthreadsProcessShared) { return LOCKTYPE_MUTEX; } #ifdef KSDC_SEMAPHORES_SUPPORTED { sem_t tempSemaphore; QSharedPointer tempLock; if (timeoutsSupported) { tempLock = QSharedPointer(new semaphoreTimedLock(tempSemaphore)); } else { tempLock = QSharedPointer(new semaphoreLock(tempSemaphore)); } tempLock->initialize(semaphoresProcessShared); } #endif if (timeoutsSupported && semaphoresProcessShared) { return LOCKTYPE_SEMAPHORE; } else if (pthreadsProcessShared) { return LOCKTYPE_MUTEX; } else if (semaphoresProcessShared) { return LOCKTYPE_SEMAPHORE; } // Fallback to a dumb-simple but possibly-CPU-wasteful solution. return LOCKTYPE_SPINLOCK; } static KSDCLock *createLockFromId(SharedLockId id, SharedLock &lock) { switch (id) { #ifdef KSDC_THREAD_PROCESS_SHARED_SUPPORTED case LOCKTYPE_MUTEX: #ifdef KSDC_TIMEOUTS_SUPPORTED if (::sysconf(_SC_TIMEOUTS) >= 200112L) { return new pthreadTimedLock(lock.mutex); } #endif return new pthreadLock(lock.mutex); break; #endif #ifdef KSDC_SEMAPHORES_SUPPORTED case LOCKTYPE_SEMAPHORE: #ifdef KSDC_TIMEOUTS_SUPPORTED if (::sysconf(_SC_SEMAPHORES) >= 200112L) { return new semaphoreTimedLock(lock.semaphore); } #endif return new semaphoreLock(lock.semaphore); break; #endif case LOCKTYPE_SPINLOCK: return new simpleSpinLock(lock.spinlock); break; default: qCritical() << "Creating shell of a lock!"; return new KSDCLock; } } static bool ensureFileAllocated(int fd, size_t fileSize) { #ifdef KSDC_POSIX_FALLOCATE_SUPPORTED int result; while ((result = ::posix_fallocate(fd, 0, fileSize)) == EINTR) { ; } if (result < 0) { qCritical() << "The operating system is unable to promise" << fileSize << "bytes for mapped cache, " "abandoning the cache for crash-safety."; return false; } return true; #else #ifdef __GNUC__ #warning "This system does not seem to support posix_fallocate, which is needed to ensure KSharedDataCache's underlying files are fully committed to disk to avoid crashes with low disk space." #endif qCWarning(KCOREADDONS_DEBUG) << "This system misses support for posix_fallocate()" " -- ensure this partition has room for at least" << fileSize << "bytes."; // TODO: It's possible to emulate the functionality, but doing so // overwrites the data in the file so we don't do this. If you were to add // this emulation, you must ensure it only happens on initial creation of a // new file and not just mapping an existing cache. return true; #endif } #endif /* KSHAREDDATACACHE_P_H */ diff --git a/src/lib/caching/kshareddatacache_win.cpp b/src/lib/caching/kshareddatacache_win.cpp index 872cd2d..2b173d3 100644 --- a/src/lib/caching/kshareddatacache_win.cpp +++ b/src/lib/caching/kshareddatacache_win.cpp @@ -1,119 +1,119 @@ /* * This file is part of the KDE project. * Copyright 2010 Michael Pyne * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /** * This is a horrifically simple implementation of KSharedDataCache that is * basically missing the "shared" part to it, for use on Windows or other platforms * that don't support POSIX. */ #include "kshareddatacache.h" -#include -#include -#include +#include +#include +#include class Q_DECL_HIDDEN KSharedDataCache::Private { public: KSharedDataCache::EvictionPolicy evictionPolicy; QCache cache; }; KSharedDataCache::KSharedDataCache(const QString &cacheName, unsigned defaultCacheSize, unsigned expectedItemSize) : d(new Private) { d->cache.setMaxCost(defaultCacheSize); Q_UNUSED(cacheName); Q_UNUSED(expectedItemSize); } KSharedDataCache::~KSharedDataCache() { delete d; } KSharedDataCache::EvictionPolicy KSharedDataCache::evictionPolicy() const { return d->evictionPolicy; } void KSharedDataCache::setEvictionPolicy(KSharedDataCache::EvictionPolicy newPolicy) { d->evictionPolicy = newPolicy; } bool KSharedDataCache::insert(const QString &key, const QByteArray &data) { return d->cache.insert(key, new QByteArray(data)); } bool KSharedDataCache::find(const QString &key, QByteArray *destination) const { QByteArray *value = d->cache.object(key); if (value) { if (destination) { *destination = *value; } return true; } else { return false; } } void KSharedDataCache::clear() { d->cache.clear(); } void KSharedDataCache::deleteCache(const QString &cacheName) { Q_UNUSED(cacheName); } bool KSharedDataCache::contains(const QString &key) const { return d->cache.contains(key); } unsigned KSharedDataCache::totalSize() const { return static_cast(d->cache.maxCost()); } unsigned KSharedDataCache::freeSize() const { if (d->cache.totalCost() < d->cache.maxCost()) { return static_cast(d->cache.maxCost() - d->cache.totalCost()); } else { return 0; } } unsigned KSharedDataCache::timestamp() const { return 0; } void KSharedDataCache::setTimestamp(unsigned newTimestamp) { } diff --git a/src/lib/io/kautosavefile.cpp b/src/lib/io/kautosavefile.cpp index 84059cf..cd057c0 100644 --- a/src/lib/io/kautosavefile.cpp +++ b/src/lib/io/kautosavefile.cpp @@ -1,217 +1,217 @@ /* This file is part of the KDE libraries Copyright (c) 2006 Jacob R Rideout Copyright (c) 2015 Nick Shaforostoff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kautosavefile.h" #include // for FILENAME_MAX -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include "krandom.h" #include "kcoreaddons_debug.h" class KAutoSaveFilePrivate { public: enum {NamePadding=8}; KAutoSaveFilePrivate() : lock(nullptr), managedFileNameChanged(false) {} QString tempFileName(); QUrl managedFile; QLockFile *lock; bool managedFileNameChanged; }; static QStringList findAllStales(const QString &appName) { const QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); QStringList files; for (const QString &dir : dirs) { QDir appDir(dir + QStringLiteral("/stalefiles/") + appName); //qCDebug(KCOREADDONS_DEBUG) << "Looking in" << appDir.absolutePath(); Q_FOREACH (const QString &file, appDir.entryList(QDir::Files)) { files << (appDir.absolutePath() + QLatin1Char('/') + file); } } return files; } QString KAutoSaveFilePrivate::tempFileName() { // Note: we drop any query string and user/pass info const QString protocol(managedFile.scheme()); const QString path(managedFile.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path()); QString name(managedFile.fileName()); // Remove any part of the path to the right if it is longer than the max file size and // ensure that the max filesize takes into account the other parts of the tempFileName // Subtract 1 for the _ char, 3 for the padding separator, 5 is for the .lock int pathLengthLimit = FILENAME_MAX - NamePadding - name.size() - protocol.size() - 9; QString junk = KRandom::randomString(NamePadding); // tempName = fileName + junk.truncated + protocol + _ + path.truncated + junk // This is done so that the separation between the filename and path can be determined name += junk.rightRef(3) + protocol + QLatin1Char('_') + path.leftRef(pathLengthLimit) + junk; return QString::fromLatin1(QUrl::toPercentEncoding(name).constData()); } KAutoSaveFile::KAutoSaveFile(const QUrl &filename, QObject *parent) : QFile(parent), d(new KAutoSaveFilePrivate) { setManagedFile(filename); } KAutoSaveFile::KAutoSaveFile(QObject *parent) : QFile(parent), d(new KAutoSaveFilePrivate) { } KAutoSaveFile::~KAutoSaveFile() { releaseLock(); delete d->lock; delete d; } QUrl KAutoSaveFile::managedFile() const { return d->managedFile; } void KAutoSaveFile::setManagedFile(const QUrl &filename) { releaseLock(); d->managedFile = filename; d->managedFileNameChanged = true; } void KAutoSaveFile::releaseLock() { if (d->lock && d->lock->isLocked()) { delete d->lock; d->lock = nullptr; if (!fileName().isEmpty()) { remove(); } } } bool KAutoSaveFile::open(OpenMode openmode) { if (d->managedFile.isEmpty()) { return false; } QString tempFile; if (d->managedFileNameChanged) { QString staleFilesDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/stalefiles/") + QCoreApplication::instance()->applicationName(); if (!QDir().mkpath(staleFilesDir)) { return false; } tempFile = staleFilesDir + QChar::fromLatin1('/') + d->tempFileName(); } else { tempFile = fileName(); } d->managedFileNameChanged = false; setFileName(tempFile); if (QFile::open(openmode)) { if (!d->lock) { d->lock = new QLockFile(tempFile + QStringLiteral(".lock")); d->lock->setStaleLockTime(60 * 1000); // HARDCODE, 1 minute } if (d->lock->isLocked() || d->lock->tryLock()) { return true; } else { qCWarning(KCOREADDONS_DEBUG)<<"Could not lock file:"< KAutoSaveFile::staleFiles(const QUrl &filename, const QString &applicationName) { QString appName(applicationName); if (appName.isEmpty()) { appName = QCoreApplication::instance()->applicationName(); } // get stale files const QStringList files = findAllStales(appName); QList list; // contruct a KAutoSaveFile for stale files corresponding given filename for (const QString &file : files) { if (file.endsWith(QLatin1String(".lock")) || (!filename.isEmpty() && extractManagedFilePath(file).path()!=filename.path())) { continue; } // sets managedFile KAutoSaveFile *asFile = new KAutoSaveFile(filename.isEmpty()?extractManagedFilePath(file):filename); asFile->setFileName(file); asFile->d->managedFileNameChanged = false; // do not regenerate tempfile name list.append(asFile); } return list; } QList KAutoSaveFile::allStaleFiles(const QString &applicationName) { return staleFiles(QUrl(), applicationName); } #include "moc_kautosavefile.cpp" diff --git a/src/lib/io/kautosavefile.h b/src/lib/io/kautosavefile.h index 70fc624..9b51c78 100644 --- a/src/lib/io/kautosavefile.h +++ b/src/lib/io/kautosavefile.h @@ -1,248 +1,248 @@ /* This file is part of the KDE libraries Copyright (c) 2006 Jacob R Rideout This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KAUTOSAVEFILE_H #define KAUTOSAVEFILE_H #include -#include -#include -#include +#include +#include +#include class KAutoSaveFilePrivate; /** * \class KAutoSaveFile kautosavefile.h * * @brief Creates and manages a temporary "auto-save" file. * Autosave files are temporary files that applications use to store * the unsaved data in a file they have open for * editing. KAutoSaveFile allows you to easily create and manage such * files, as well as to recover the unsaved data left over by a * crashed or otherwise gone process. * * Each KAutoSaveFile object is associated with one specific file that * the application holds open. KAutoSaveFile is also a QObject, so it * can be reparented to the actual opened file object, so as to manage * the lifetime of the temporary file. * * Typical use consists of: * - verifying whether stale autosave files exist for the opened file * - deciding whether to recover the old, autosaved data * - if not recovering, creating a KAutoSaveFile object for the opened file * - during normal execution of the program, periodically save unsaved * data into the KAutoSaveFile file. * * KAutoSaveFile holds a lock on the autosave file, so it's safe to * delete the file and recreate it later. Because of that, disposing * of stale autosave files should be done with releaseLock(). No lock is * held on the managed file. * * Examples: * Opening a new file: * @code * void Document::open(const QUrl &url) * { * // check whether autosave files exist: * QList staleFiles = KAutoSaveFile::staleFiles(url); * if (!staleFiles.isEmpty()) { * if (KMessageBox::questionYesNo(parent, * "Auto-saved files exist. Do you want to recover them now?", * "File Recovery", * "Recover", "Don't recover") == KMessage::Yes) { * recoverFiles(staleFiles); * return; * } else { * // remove the stale files * foreach (KAutoSaveFile *stale, staleFiles) { * stale->open(QIODevice::ReadWrite); * delete stale; * } * } * } * * // create new autosave object * m_autosave = new KAutoSaveFile(url, this); * * // continue the process of opening file 'url' * ... * } * @endcode * * The function recoverFiles could loop over the list of files and do this: * @code * foreach (KAutoSaveFile *stale, staleFiles) { * if (!stale->open(QIODevice::ReadWrite)) { * // show an error message; we could not steal the lockfile * // maybe another application got to the file before us? * delete stale; * continue; * } * Document *doc = new Document; * doc->m_autosave = stale; * stale->setParent(doc); // reparent * * doc->setUrl(stale->managedFile()); * doc->setContents(stale->readAll()); * doc->setState(Document::Modified); // mark it as modified and unsaved * * documentManager->addDocument(doc); * } * @endcode * * If the file is unsaved, periodically write the contents to the save file: * @code * if (!m_autosave->isOpen() && !m_autosave->open(QIODevice::ReadWrite)) { * // show error: could not open the autosave file * } * m_autosave->write(contents()); * @endcode * * When the user saves the file, the autosaved file is no longer * necessary and can be removed or emptied. * @code * m_autosave->resize(0); // leaves the file open * @endcode * * @code * m_autosave->remove(); // closes the file * @endcode * * @author Jacob R Rideout */ class KCOREADDONS_EXPORT KAutoSaveFile : public QFile { Q_OBJECT public: /** * Constructs a KAutoSaveFile for file @p filename. The temporary * file is not opened or created until actually needed. The file * @p filename does not have to exist for KAutoSaveFile to be * constructed (if it exists, it will not be touched). * * @param filename the filename that this KAutoSaveFile refers to * @param parent the parent object */ explicit KAutoSaveFile(const QUrl &filename, QObject *parent = nullptr); /** * @overload * Constructs a KAutoSaveFile object. Note that you need to call * setManagedFile() before calling open(). * * @param parent the parent object */ explicit KAutoSaveFile(QObject *parent = nullptr); /** * Destroys the KAutoSaveFile object, removes the autosave * file and drops the lock being held (if any). */ ~KAutoSaveFile(); /** * Retrieves the URL of the file managed by KAutoSaveFile. This * is the same URL that was given to setManagedFile() or the * KAutoSaveFile constructor. * * This is the name of the real file being edited by the * application. To get the name of the temporary file where data * can be saved, use fileName() (after you have called open()). */ QUrl managedFile() const; /** * Sets the URL of the file managed by KAutoSaveFile. This should * be the name of the real file being edited by the application. * If the file was previously set, this function calls releaseLock(). * * @param filename the filename that this KAutoSaveFile refers to */ void setManagedFile(const QUrl &filename); /** * Closes the autosave file resource and removes the lock * file. The file name returned by fileName() will no longer be * protected and can be overwritten by another application at any * time. To obtain a new lock, call open() again. * * This function calls remove(), so the autosave temporary file * will be removed too. */ virtual void releaseLock(); /** * Opens the autosave file and locks it if it wasn't already * locked. The name of the temporary file where data can be saved * to will be set by this function and can be retrieved with * fileName(). It will not change unless releaseLock() is called. No * other application will attempt to edit such a file either while * the lock is held. * * @param openmode the mode that should be used to open the file, * probably QIODevice::ReadWrite * @returns true if the file could be opened (= locked and * created), false if the operation failed */ bool open(OpenMode openmode) Q_DECL_OVERRIDE; /** * Checks for stale autosave files for the file @p url. Returns a list * of autosave files that contain autosaved data left behind by * other instances of the application, due to crashing or * otherwise uncleanly exiting. * * It is the application's job to determine what to do with such * unsaved data. Generally, this is done by asking the user if he * wants to see the recovered data, and then allowing the user to * save if he wants to. * * If not given, the application name is obtained from * QCoreApplication, so be sure to have set it correctly before * calling this function. * * This function returns a list of unopened KAutoSaveFile * objects. By calling open() on them, the application will steal * the lock. Subsequent releaseLock() or deleting of the object will * then erase the stale autosave file. */ static QList staleFiles(const QUrl &url, const QString &applicationName = QString()); /** * Returns all stale autosave files left behind by crashed or * otherwise gone instances of this application. * * If not given, the application name is obtained from * QCoreApplication, so be sure to have set it correctly before * calling this function. * * See staleFiles() for information on the returned objects. */ static QList allStaleFiles(const QString &applicationName = QString()); private: Q_DISABLE_COPY(KAutoSaveFile) friend class KAutoSaveFilePrivate; KAutoSaveFilePrivate *const d; }; #endif // KAUTOSAVEFILE_H diff --git a/src/lib/io/kbackup.cpp b/src/lib/io/kbackup.cpp index 6cc7b88..08d39c1 100644 --- a/src/lib/io/kbackup.cpp +++ b/src/lib/io/kbackup.cpp @@ -1,207 +1,207 @@ /* This file is part of the KDE libraries Copyright 1999 Waldo Bastian Copyright 2006 Allen Winter Copyright 2006 Gregory S. Hayes Copyright 2006 Jaison Lee Copyright 2011 Romain Perier This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include -#include +#include +#include +#include #include #include "kbackup.h" namespace KBackup { bool backupFile(const QString &qFilename, const QString &backupDir) { // get backup type from config, by default use "simple" // get extension from config, by default use "~" // get max number of backups from config, by default set to 10 #pragma message("KDE5 TODO: Remove KConfig correctly") #if 0 KConfigGroup g(KSharedConfig::openConfig(), "Backups"); // look in the Backups section QString type = g.readEntry("Type", "simple"); QString extension = g.readEntry("Extension", "~"); QString message = g.readEntry("Message", "Automated KDE Commit"); int maxnum = g.readEntry("MaxBackups", 10); if (type.toLower() == QLatin1String("numbered")) { return (numberedBackupFile(qFilename, backupDir, extension, maxnum)); } else if (type.toLower() == QLatin1String("rcs")) { return (rcsBackupFile(qFilename, backupDir, message)); } else { return (simpleBackupFile(qFilename, backupDir, extension)); } #endif return (simpleBackupFile(qFilename, backupDir, QStringLiteral("~"))); } bool simpleBackupFile(const QString &qFilename, const QString &backupDir, const QString &backupExtension) { QString backupFileName = qFilename + backupExtension; if (!backupDir.isEmpty()) { QFileInfo fileInfo(qFilename); backupFileName = backupDir + QLatin1Char('/') + fileInfo.fileName() + backupExtension; } // qCDebug(KCOREADDONS_DEBUG) << "KBackup copying " << qFilename << " to " << backupFileName; QFile::remove(backupFileName); return QFile::copy(qFilename, backupFileName); } bool rcsBackupFile(const QString &qFilename, const QString &backupDir, const QString &backupMessage) { QFileInfo fileInfo(qFilename); QString qBackupFilename; if (backupDir.isEmpty()) { qBackupFilename = qFilename; } else { qBackupFilename = backupDir + fileInfo.fileName(); } qBackupFilename += QString::fromLatin1(",v"); // If backupDir is specified, copy qFilename to the // backupDir and perform the commit there, unlinking // backupDir/qFilename when finished. if (!backupDir.isEmpty()) { if (!QFile::copy(qFilename, backupDir + fileInfo.fileName())) { return false; } fileInfo.setFile(backupDir + QLatin1Char('/') + fileInfo.fileName()); } const QString cipath = QStandardPaths::findExecutable(QStringLiteral("ci")); const QString copath = QStandardPaths::findExecutable(QStringLiteral("co")); const QString rcspath = QStandardPaths::findExecutable(QStringLiteral("rcs")); if (cipath.isEmpty() || copath.isEmpty() || rcspath.isEmpty()) { return false; } // Check in the file unlocked with 'ci' QProcess ci; if (!backupDir.isEmpty()) { ci.setWorkingDirectory(backupDir); } ci.start(cipath, QStringList() << QStringLiteral("-u") << fileInfo.filePath()); if (!ci.waitForStarted()) { return false; } ci.write(backupMessage.toLocal8Bit()); ci.write("."); ci.closeWriteChannel(); if (!ci.waitForFinished()) { return false; } // Use 'rcs' to unset strict locking QProcess rcs; if (!backupDir.isEmpty()) { rcs.setWorkingDirectory(backupDir); } rcs.start(rcspath, QStringList() << QStringLiteral("-U") << qBackupFilename); if (!rcs.waitForFinished()) { return false; } // Use 'co' to checkout the current revision and restore permissions QProcess co; if (!backupDir.isEmpty()) { co.setWorkingDirectory(backupDir); } co.start(copath, QStringList() << qBackupFilename); if (!co.waitForFinished()) { return false; } if (!backupDir.isEmpty()) { return QFile::remove(fileInfo.filePath()); } else { return true; } } bool numberedBackupFile(const QString &qFilename, const QString &backupDir, const QString &backupExtension, const uint maxBackups) { QFileInfo fileInfo(qFilename); // The backup file name template. QString sTemplate; if (backupDir.isEmpty()) { sTemplate = qFilename + QLatin1String(".%1") + backupExtension; } else { sTemplate = backupDir + QLatin1Char('/') + fileInfo.fileName() + QLatin1String(".%1") + backupExtension; } // First, search backupDir for numbered backup files to remove. // Remove all with number 'maxBackups' and greater. QDir d = backupDir.isEmpty() ? fileInfo.dir() : backupDir; d.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); const QStringList nameFilters = QStringList(fileInfo.fileName() + QLatin1String(".*") + backupExtension); d.setNameFilters(nameFilters); d.setSorting(QDir::Name); uint maxBackupFound = 0; Q_FOREACH (const QFileInfo &fi, d.entryInfoList()) { if (fi.fileName().endsWith(backupExtension)) { // sTemp holds the file name, without the ending backupExtension QString sTemp = fi.fileName(); sTemp.truncate(fi.fileName().length() - backupExtension.length()); // compute the backup number int idex = sTemp.lastIndexOf(QLatin1Char('.')); if (idex > 0) { bool ok; uint num = sTemp.midRef(idex + 1).toUInt(&ok); if (ok) { if (num >= maxBackups) { QFile::remove(fi.filePath()); } else { maxBackupFound = qMax(maxBackupFound, num); } } } } } // Next, rename max-1 to max, max-2 to max-1, etc. QString to = sTemplate.arg(maxBackupFound + 1); for (int i = maxBackupFound; i > 0; i--) { QString from = sTemplate.arg(i); // qCDebug(KCOREADDONS_DEBUG) << "KBackup renaming " << from << " to " << to; QFile::rename(from, to); to = from; } // Finally create most recent backup by copying the file to backup number 1. // qCDebug(KCOREADDONS_DEBUG) << "KBackup copying " << qFilename << " to " << sTemplate.arg(1); return QFile::copy(qFilename, sTemplate.arg(1)); } } diff --git a/src/lib/io/kbackup.h b/src/lib/io/kbackup.h index 323062c..84b85ac 100644 --- a/src/lib/io/kbackup.h +++ b/src/lib/io/kbackup.h @@ -1,126 +1,126 @@ /* This file is part of the KDE libraries Copyright 1999 Waldo Bastian Copyright 2006 Jaison Lee Copyright 2011 Romain Perier This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KBACKUP_H #define KBACKUP_H #include -#include +#include /** * @namespace KBackup * Provides utility functions for backup of files. */ namespace KBackup { /** * @brief Function to create a backup file before saving. * * If empty (the default), the backup will be in the same directory as @p filename. * The backup type (simple, rcs, or numbered), extension string, and maximum * number of backup files are read from the user's global configuration. * Use simpleBackupFile() or numberedBackupFile() to force one of these * specific backup styles. * You can use this method even if you don't use KSaveFile. * @param filename the file to backup * @param backupDir optional directory where to save the backup file in. * @return true if successful, or false if an error has occurred. */ KCOREADDONS_EXPORT bool backupFile(const QString &filename, const QString &backupDir = QString()); /** * @brief Function to create a backup file for a given filename. * * This function creates a backup file from the given filename. * You can use this method even if you don't use KSaveFile. * @param filename the file to backup * @param backupDir optional directory where to save the backup file in. * If empty (the default), the backup will be in the same directory as @p filename. * @param backupExtension the extension to append to @p filename, "~" by default. * @return true if successful, or false if an error has occurred. */ KCOREADDONS_EXPORT bool simpleBackupFile(const QString &filename, const QString &backupDir = QString(), const QString &backupExtension = QStringLiteral("~")); /** * @brief Function to create a backup file for a given filename. * * This function creates a series of numbered backup files from the * given filename. * * The backup file names will be of the form: * \.\\ * for instance * \verbatim chat.3.log \endverbatim * * The new backup file will be have the backup number 1. * Each existing backup file will have its number incremented by 1. * Any backup files with numbers greater than the maximum number * permitted (@p maxBackups) will be removed. * You can use this method even if you don't use KSaveFile. * * @param filename the file to backup * @param backupDir optional directory where to save the backup file in. * If empty (the default), the backup will be in the same directory as * @p filename. * @param backupExtension the extension to append to @p filename, * which is "~" by default. Do not use an extension containing digits. * @param maxBackups the maximum number of backup files permitted. * For best performance a small number (10) is recommended. * @return true if successful, or false if an error has occurred. */ KCOREADDONS_EXPORT bool numberedBackupFile(const QString &filename, const QString &backupDir = QString(), const QString &backupExtension = QStringLiteral("~"), const uint maxBackups = 10 ); /** * @brief Function to create an rcs backup file for a given filename. * * This function creates a rcs-formatted backup file from the * given filename. * * The backup file names will be of the form: * \,v * for instance * \verbatim photo.jpg,v \endverbatim * * The new backup file will be in RCS format. * Each existing backup file will be committed as a new revision. * You can use this method even if you don't use KSaveFile. * * @param filename the file to backup * @param backupDir optional directory where to save the backup file in. * If empty (the default), the backup will be in the same directory as * @p filename. * @param backupMessage is the RCS commit message for this revision. * @return true if successful, or false if an error has occurred. */ KCOREADDONS_EXPORT bool rcsBackupFile(const QString &filename, const QString &backupDir = QString(), const QString &backupMessage = QString() ); } #endif diff --git a/src/lib/io/kdirwatch.cpp b/src/lib/io/kdirwatch.cpp index a2e15e1..bfbba80 100644 --- a/src/lib/io/kdirwatch.cpp +++ b/src/lib/io/kdirwatch.cpp @@ -1,2066 +1,2066 @@ /* This file is part of the KDE libraries Copyright (C) 1998 Sven Radej Copyright (C) 2006 Dirk Mueller Copyright (C) 2007 Flavio Castelli Copyright (C) 2008 Rafal Rzepecki Copyright (C) 2010 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // CHANGES: // Jul 30, 2008 - Don't follow symlinks when recursing to avoid loops (Rafal) // Aug 6, 2007 - KDirWatch::WatchModes support complete, flags work fine also // when using FAMD (Flavio Castelli) // Aug 3, 2007 - Handled KDirWatch::WatchModes flags when using inotify, now // recursive and file monitoring modes are implemented (Flavio Castelli) // Jul 30, 2007 - Substituted addEntry boolean params with KDirWatch::WatchModes // flag (Flavio Castelli) // Oct 4, 2005 - Inotify support (Dirk Mueller) // Februar 2002 - Add file watching and remote mount check for STAT // Mar 30, 2001 - Native support for Linux dir change notification. // Jan 28, 2000 - Usage of FAM service on IRIX (Josef.Weidendorfer@in.tum.de) // May 24. 1998 - List of times introduced, and some bugs are fixed. (sven) // May 23. 1998 - Removed static pointer - you can have more instances. // It was Needed for KRegistry. KDirWatch now emits signals and doesn't // call (or need) KFM. No more URL's - just plain paths. (sven) // Mar 29. 1998 - added docs, stop/restart for particular Dirs and // deep copies for list of dirs. (sven) // Mar 28. 1998 - Created. (sven) #include "kdirwatch.h" #include "kdirwatch_p.h" #include "kfilesystemtype.h" #include "kcoreaddons_debug.h" #include #include #include #include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include // QT_LSTAT, QT_STAT, QT_STATBUF #include #include #if HAVE_SYS_INOTIFY_H #include #include #include #ifndef IN_DONT_FOLLOW #define IN_DONT_FOLLOW 0x02000000 #endif #ifndef IN_ONLYDIR #define IN_ONLYDIR 0x01000000 #endif // debug #include #include #endif // HAVE_SYS_INOTIFY_H Q_DECLARE_LOGGING_CATEGORY(KDIRWATCH) // logging category for this framework, default: log stuff >= warning Q_LOGGING_CATEGORY(KDIRWATCH, "kf5.kcoreaddons.kdirwatch", QtWarningMsg) // set this to true for much more verbose debug output static bool s_verboseDebug = false; static QThreadStorage dwp_self; static KDirWatchPrivate *createPrivate() { if (!dwp_self.hasLocalData()) { dwp_self.setLocalData(new KDirWatchPrivate); } return dwp_self.localData(); } // Convert a string into a watch Method static KDirWatch::Method methodFromString(const QByteArray &method) { if (method == "Fam") { return KDirWatch::FAM; } else if (method == "Stat") { return KDirWatch::Stat; } else if (method == "QFSWatch") { return KDirWatch::QFSWatch; } else { #if defined(HAVE_SYS_INOTIFY_H) // inotify supports delete+recreate+modify, which QFSWatch doesn't support return KDirWatch::INotify; #else return KDirWatch::QFSWatch; #endif } } static const char *methodToString(KDirWatch::Method method) { switch (method) { case KDirWatch::FAM: return "Fam"; case KDirWatch::INotify: return "INotify"; case KDirWatch::Stat: return "Stat"; case KDirWatch::QFSWatch: return "QFSWatch"; } // not reached return nullptr; } static const char s_envNfsPoll[] = "KDIRWATCH_NFSPOLLINTERVAL"; static const char s_envPoll[] = "KDIRWATCH_POLLINTERVAL"; static const char s_envMethod[] = "KDIRWATCH_METHOD"; static const char s_envNfsMethod[] = "KDIRWATCH_NFSMETHOD"; // // Class KDirWatchPrivate (singleton) // /* All entries (files/directories) to be watched in the * application (coming from multiple KDirWatch instances) * are registered in a single KDirWatchPrivate instance. * * At the moment, the following methods for file watching * are supported: * - Polling: All files to be watched are polled regularly * using stat (more precise: QFileInfo.lastModified()). * The polling frequency is determined from global kconfig * settings, defaulting to 500 ms for local directories * and 5000 ms for remote mounts * - FAM (File Alternation Monitor): first used on IRIX, SGI * has ported this method to LINUX. It uses a kernel part * (IMON, sending change events to /dev/imon) and a user * level damon (fam), to which applications connect for * notification of file changes. For NFS, the fam damon * on the NFS server machine is used; if IMON is not built * into the kernel, fam uses polling for local files. * - INOTIFY: In LINUX 2.6.13, inode change notification was * introduced. You're now able to watch arbitrary inode's * for changes, and even get notification when they're * unmounted. */ KDirWatchPrivate::KDirWatchPrivate() : timer(), freq(3600000), // 1 hour as upper bound statEntries(0), delayRemove(false), rescan_all(false), rescan_timer(), #if HAVE_SYS_INOTIFY_H mSn(nullptr), #endif _isStopped(false) { // Debug unittest on CI if (qAppName() == QLatin1String("kservicetest") || qAppName() == QLatin1String("filetypestest")) { s_verboseDebug = true; } timer.setObjectName(QStringLiteral("KDirWatchPrivate::timer")); connect(&timer, SIGNAL(timeout()), this, SLOT(slotRescan())); m_nfsPollInterval = qEnvironmentVariableIsSet(s_envNfsPoll) ? qgetenv(s_envNfsPoll).toInt() : 5000; m_PollInterval = qEnvironmentVariableIsSet(s_envPoll) ? qgetenv(s_envPoll).toInt() : 500; m_preferredMethod = methodFromString(qEnvironmentVariableIsSet(s_envMethod) ? qgetenv(s_envMethod) : "inotify"); // The nfs method defaults to the normal (local) method m_nfsPreferredMethod = methodFromString(qEnvironmentVariableIsSet(s_envNfsMethod) ? qgetenv(s_envNfsMethod) : "Fam"); QList availableMethods; availableMethods << "Stat"; // used for FAM and inotify rescan_timer.setObjectName(QStringLiteral("KDirWatchPrivate::rescan_timer")); rescan_timer.setSingleShot(true); connect(&rescan_timer, SIGNAL(timeout()), this, SLOT(slotRescan())); #if HAVE_FAM availableMethods << "FAM"; use_fam = true; sn = 0; #endif #if HAVE_SYS_INOTIFY_H supports_inotify = true; m_inotify_fd = inotify_init(); if (m_inotify_fd <= 0) { qCDebug(KDIRWATCH) << "Can't use Inotify, kernel doesn't support it"; supports_inotify = false; } //qCDebug(KDIRWATCH) << "INotify available: " << supports_inotify; if (supports_inotify) { availableMethods << "INotify"; (void)fcntl(m_inotify_fd, F_SETFD, FD_CLOEXEC); mSn = new QSocketNotifier(m_inotify_fd, QSocketNotifier::Read, this); connect(mSn, SIGNAL(activated(int)), this, SLOT(inotifyEventReceived())); } #endif #if HAVE_QFILESYSTEMWATCHER availableMethods << "QFileSystemWatcher"; fsWatcher = nullptr; #endif if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Available methods: " << availableMethods << "preferred=" << methodToString(m_preferredMethod); } } // This is called on app exit (deleted by QThreadStorage) KDirWatchPrivate::~KDirWatchPrivate() { timer.stop(); #if HAVE_FAM if (use_fam && sn) { FAMClose(&fc); } #endif #if HAVE_SYS_INOTIFY_H if (supports_inotify) { QT_CLOSE(m_inotify_fd); } #endif #if HAVE_QFILESYSTEMWATCHER delete fsWatcher; #endif } void KDirWatchPrivate::inotifyEventReceived() { //qCDebug(KDIRWATCH); #if HAVE_SYS_INOTIFY_H if (!supports_inotify) { return; } int pending = -1; int offsetStartRead = 0; // where we read into buffer char buf[8192]; assert(m_inotify_fd > -1); ioctl(m_inotify_fd, FIONREAD, &pending); while (pending > 0) { const int bytesToRead = qMin(pending, sizeof(buf) - offsetStartRead); int bytesAvailable = read(m_inotify_fd, &buf[offsetStartRead], bytesToRead); pending -= bytesAvailable; bytesAvailable += offsetStartRead; offsetStartRead = 0; int offsetCurrent = 0; while (bytesAvailable >= int(sizeof(struct inotify_event))) { const struct inotify_event *const event = reinterpret_cast(&buf[offsetCurrent]); const int eventSize = sizeof(struct inotify_event) + event->len; if (bytesAvailable < eventSize) { break; } bytesAvailable -= eventSize; offsetCurrent += eventSize; QString path; // strip trailing null chars, see inotify_event documentation // these must not end up in the final QString version of path int len = event->len; while (len > 1 && !event->name[len - 1]) { --len; } QByteArray cpath(event->name, len); if (len) { path = QFile::decodeName(cpath); } if (!path.isEmpty() && isNoisyFile(cpath.data())) { continue; } // Is set to true if the new event is a directory, false otherwise. This prevents a stat call in clientsForFileOrDir const bool isDir = (event->mask & (IN_ISDIR)); Entry *e = m_inotify_wd_to_entry.value(event->wd); if (!e) { continue; } const bool wasDirty = e->dirty; e->dirty = true; const QString tpath = e->path + QLatin1Char('/') + path; if (s_verboseDebug) { qCDebug(KDIRWATCH).nospace() << "got event 0x" << qPrintable(QString::number(event->mask, 16)) << " for " << e->path; } if (event->mask & IN_DELETE_SELF) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "-->got deleteself signal for" << e->path; } e->m_status = NonExistent; m_inotify_wd_to_entry.remove(e->wd); e->wd = -1; e->m_ctime = invalid_ctime; emitEvent(e, Deleted, e->path); // If the parent dir was already watched, tell it something changed Entry *parentEntry = entry(e->parentDirectory()); if (parentEntry) { parentEntry->dirty = true; } // Add entry to parent dir to notice if the entry gets recreated addEntry(nullptr, e->parentDirectory(), e, true /*isDir*/); } if (event->mask & IN_IGNORED) { // Causes bug #207361 with kernels 2.6.31 and 2.6.32! //e->wd = -1; } if (event->mask & (IN_CREATE | IN_MOVED_TO)) { Entry *sub_entry = e->findSubEntry(tpath); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "-->got CREATE signal for" << (tpath) << "sub_entry=" << sub_entry; qCDebug(KDIRWATCH) << *e; } // The code below is very similar to the one in checkFAMEvent... if (sub_entry) { // We were waiting for this new file/dir to be created sub_entry->dirty = true; rescan_timer.start(0); // process this asap, to start watching that dir } else if (e->isDir && !e->m_clients.empty()) { const QList clients = e->inotifyClientsForFileOrDir(isDir); // See discussion in addEntry for why we don't addEntry for individual // files in WatchFiles mode with inotify. if (isDir) { for (const Client *client : clients) { addEntry(client->instance, tpath, nullptr, isDir, isDir ? client->m_watchModes : KDirWatch::WatchDirOnly); } } if (!clients.isEmpty()) { emitEvent(e, Created, tpath); qCDebug(KDIRWATCH).nospace() << clients.count() << " instance(s) monitoring the new " << (isDir ? "dir " : "file ") << tpath; } e->m_pendingFileChanges.append(e->path); if (!rescan_timer.isActive()) { rescan_timer.start(m_PollInterval); // singleshot } } } if (event->mask & (IN_DELETE | IN_MOVED_FROM)) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "-->got DELETE signal for" << tpath; } if ((e->isDir) && (!e->m_clients.empty())) { // A file in this directory has been removed. It wasn't an explicitly // watched file as it would have its own watch descriptor, so // no addEntry/ removeEntry bookkeeping should be required. Emit // the event immediately if any clients are interested. KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; int counter = 0; for (const Client &client : e->m_clients) { if (client.m_watchModes & flag) { counter++; } } if (counter != 0) { emitEvent(e, Deleted, tpath); } } } if (event->mask & (IN_MODIFY | IN_ATTRIB)) { if ((e->isDir) && (!e->m_clients.empty())) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "-->got MODIFY signal for" << (tpath); } // A file in this directory has been changed. No // addEntry/ removeEntry bookkeeping should be required. // Add the path to the list of pending file changes if // there are any interested clients. //QT_STATBUF stat_buf; //QByteArray tpath = QFile::encodeName(e->path+'/'+path); //QT_STAT(tpath, &stat_buf); //bool isDir = S_ISDIR(stat_buf.st_mode); // The API doc is somewhat vague as to whether we should emit // dirty() for implicitly watched files when WatchFiles has // not been specified - we'll assume they are always interested, // regardless. // Don't worry about duplicates for the time // being; this is handled in slotRescan. e->m_pendingFileChanges.append(tpath); // Avoid stat'ing the directory if only an entry inside it changed. e->dirty = (wasDirty || (path.isEmpty() && (event->mask & IN_ATTRIB))); } } if (!rescan_timer.isActive()) { rescan_timer.start(m_PollInterval); // singleshot } } if (bytesAvailable > 0) { // copy partial event to beginning of buffer memmove(buf, &buf[offsetCurrent], bytesAvailable); offsetStartRead = bytesAvailable; } } #endif } KDirWatchPrivate::Entry::~Entry() { } /* In FAM mode, only entries which are marked dirty are scanned. * We first need to mark all yet nonexistent, but possible created * entries as dirty... */ void KDirWatchPrivate::Entry::propagate_dirty() { Q_FOREACH (Entry *sub_entry, m_entries) { if (!sub_entry->dirty) { sub_entry->dirty = true; sub_entry->propagate_dirty(); } } } /* A KDirWatch instance is interested in getting events for * this file/Dir entry. */ void KDirWatchPrivate::Entry::addClient(KDirWatch *instance, KDirWatch::WatchModes watchModes) { if (instance == nullptr) { return; } for (Client &client : m_clients) { if (client.instance == instance) { client.count++; client.m_watchModes = watchModes; return; } } m_clients.emplace_back(instance, watchModes); } void KDirWatchPrivate::Entry::removeClient(KDirWatch *instance) { auto it = m_clients.begin(); const auto end = m_clients.end(); for (; it != end; ++it) { Client &client = *it; if (client.instance == instance) { client.count--; if (client.count == 0) { m_clients.erase(it); } return; } } } /* get number of clients */ int KDirWatchPrivate::Entry::clientCount() const { int clients = 0; for (const Client &client : m_clients) { clients += client.count; } return clients; } QString KDirWatchPrivate::Entry::parentDirectory() const { return QDir::cleanPath(path + QLatin1String("/..")); } QList KDirWatchPrivate::Entry::clientsForFileOrDir(const QString &tpath, bool *isDir) const { QList ret; QFileInfo fi(tpath); if (fi.exists()) { *isDir = fi.isDir(); const KDirWatch::WatchModes flag = *isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; for (const Client &client : m_clients) { if (client.m_watchModes & flag) { ret.append(&client); } } } else { // Happens frequently, e.g. ERROR: couldn't stat "/home/dfaure/.viminfo.tmp" //qCDebug(KDIRWATCH) << "ERROR: couldn't stat" << tpath; // In this case isDir is not set, but ret is empty anyway // so isDir won't be used. } return ret; } // inotify specific function that doesn't call KDE::stat to figure out if we have a file or folder. // isDir is determined through inotify's "IN_ISDIR" flag in KDirWatchPrivate::inotifyEventReceived QList KDirWatchPrivate::Entry::inotifyClientsForFileOrDir(bool isDir) const { QList ret; const KDirWatch::WatchModes flag = isDir ? KDirWatch::WatchSubDirs : KDirWatch::WatchFiles; for (const Client &client : m_clients) { if (client.m_watchModes & flag) { ret.append(&client); } } return ret; } QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry) { debug.nospace() << "[ Entry for " << entry.path << ", " << (entry.isDir ? "dir" : "file"); if (entry.m_status == KDirWatchPrivate::NonExistent) { debug << ", non-existent"; } debug << ", using " << ((entry.m_mode == KDirWatchPrivate::FAMMode) ? "FAM" : (entry.m_mode == KDirWatchPrivate::INotifyMode) ? "INotify" : (entry.m_mode == KDirWatchPrivate::QFSWatchMode) ? "QFSWatch" : (entry.m_mode == KDirWatchPrivate::StatMode) ? "Stat" : "Unknown Method"); #if HAVE_SYS_INOTIFY_H if (entry.m_mode == KDirWatchPrivate::INotifyMode) { debug << " inotify_wd=" << entry.wd; } #endif debug << ", has " << entry.m_clients.size() << " clients"; debug.space(); if (!entry.m_entries.isEmpty()) { debug << ", nonexistent subentries:"; Q_FOREACH (KDirWatchPrivate::Entry *subEntry, entry.m_entries) { debug << subEntry << subEntry->path; } } debug << ']'; return debug; } KDirWatchPrivate::Entry *KDirWatchPrivate::entry(const QString &_path) { // we only support absolute paths if (_path.isEmpty() || QDir::isRelativePath(_path)) { return nullptr; } QString path(_path); if (path.length() > 1 && path.endsWith(QLatin1Char('/'))) { path.truncate(path.length() - 1); } EntryMap::Iterator it = m_mapEntries.find(path); if (it == m_mapEntries.end()) { return nullptr; } else { return &(*it); } } // set polling frequency for a entry and adjust global freq if needed void KDirWatchPrivate::useFreq(Entry *e, int newFreq) { e->freq = newFreq; // a reasonable frequency for the global polling timer if (e->freq < freq) { freq = e->freq; if (timer.isActive()) { timer.start(freq); } qCDebug(KDIRWATCH) << "Global Poll Freq is now" << freq << "msec"; } } #if HAVE_FAM // setup FAM notification, returns false if not possible bool KDirWatchPrivate::useFAM(Entry *e) { if (!use_fam) { return false; } if (!sn) { if (FAMOpen(&fc) == 0) { sn = new QSocketNotifier(FAMCONNECTION_GETFD(&fc), QSocketNotifier::Read, this); connect(sn, SIGNAL(activated(int)), this, SLOT(famEventReceived())); } else { use_fam = false; return false; } } // handle FAM events to avoid deadlock // (FAM sends back all files in a directory when monitoring) famEventReceived(); e->m_mode = FAMMode; e->dirty = false; e->m_famReportedSeen = false; bool startedFAMMonitor = false; if (e->isDir) { if (e->m_status == NonExistent) { // If the directory does not exist we watch the parent directory addEntry(0, e->parentDirectory(), e, true); } else { int res = FAMMonitorDirectory(&fc, QFile::encodeName(e->path).data(), &(e->fr), e); startedFAMMonitor = true; if (res < 0) { e->m_mode = UnknownMode; use_fam = false; delete sn; sn = 0; return false; } qCDebug(KDIRWATCH).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path; } } else { if (e->m_status == NonExistent) { // If the file does not exist we watch the directory addEntry(0, QFileInfo(e->path).absolutePath(), e, true); } else { int res = FAMMonitorFile(&fc, QFile::encodeName(e->path).data(), &(e->fr), e); startedFAMMonitor = true; if (res < 0) { e->m_mode = UnknownMode; use_fam = false; delete sn; sn = 0; return false; } qCDebug(KDIRWATCH).nospace() << " Setup FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path; } } // handle FAM events to avoid deadlock // (FAM sends back all files in a directory when monitoring) do { famEventReceived(); if (startedFAMMonitor && !e->m_famReportedSeen) { // 50 is ~half the time it takes to setup a watch. If gamin's latency // gets better, this can be reduced. QThread::msleep(50); } } while (startedFAMMonitor &&!e->m_famReportedSeen); return true; } #endif #if HAVE_SYS_INOTIFY_H // setup INotify notification, returns false if not possible bool KDirWatchPrivate::useINotify(Entry *e) { //qCDebug(KDIRWATCH) << "trying to use inotify for monitoring"; e->wd = -1; e->dirty = false; if (!supports_inotify) { return false; } e->m_mode = INotifyMode; if (e->m_status == NonExistent) { addEntry(nullptr, e->parentDirectory(), e, true); return true; } // May as well register for almost everything - it's free! int mask = IN_DELETE | IN_DELETE_SELF | IN_CREATE | IN_MOVE | IN_MOVE_SELF | IN_DONT_FOLLOW | IN_MOVED_FROM | IN_MODIFY | IN_ATTRIB; if ((e->wd = inotify_add_watch(m_inotify_fd, QFile::encodeName(e->path).data(), mask)) >= 0) { m_inotify_wd_to_entry.insert(e->wd, e); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "inotify successfully used for monitoring" << e->path << "wd=" << e->wd; } return true; } qCDebug(KDIRWATCH) << "inotify failed for monitoring" << e->path << ":" << strerror(errno); return false; } #endif #if HAVE_QFILESYSTEMWATCHER bool KDirWatchPrivate::useQFSWatch(Entry *e) { e->m_mode = QFSWatchMode; e->dirty = false; if (e->m_status == NonExistent) { addEntry(nullptr, e->parentDirectory(), e, true /*isDir*/); return true; } qCDebug(KDIRWATCH) << "fsWatcher->addPath" << e->path; if (!fsWatcher) { fsWatcher = new QFileSystemWatcher(); connect(fsWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(fswEventReceived(QString))); connect(fsWatcher, SIGNAL(fileChanged(QString)), this, SLOT(fswEventReceived(QString))); } fsWatcher->addPath(e->path); return true; } #endif bool KDirWatchPrivate::useStat(Entry *e) { if (KFileSystemType::fileSystemType(e->path) == KFileSystemType::Nfs) { // TODO: or Smbfs? useFreq(e, m_nfsPollInterval); } else { useFreq(e, m_PollInterval); } if (e->m_mode != StatMode) { e->m_mode = StatMode; statEntries++; if (statEntries == 1) { // if this was first STAT entry (=timer was stopped) timer.start(freq); // then start the timer qCDebug(KDIRWATCH) << " Started Polling Timer, freq " << freq; } } qCDebug(KDIRWATCH) << " Setup Stat (freq " << e->freq << ") for " << e->path; return true; } /* If !=0, this KDirWatch instance wants to watch at <_path>, * providing in the type of the entry to be watched. * Sometimes, entries are dependent on each other: if !=0, * this entry needs another entry to watch itself (when notExistent). */ void KDirWatchPrivate::addEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry, bool isDir, KDirWatch::WatchModes watchModes) { QString path(_path); if (path.startsWith(QLatin1String(":/"))) { qCWarning(KDIRWATCH) << "Cannot watch QRC-like path" << path; return; } if (path.isEmpty() #ifndef Q_OS_WIN || path == QLatin1String("/dev") || (path.startsWith(QLatin1String("/dev/")) && !path.startsWith(QLatin1String("/dev/.")) && !path.startsWith(QLatin1String("/dev/shm"))) #endif ) { return; // Don't even go there. } if (path.length() > 1 && path.endsWith(QLatin1Char('/'))) { path.truncate(path.length() - 1); } EntryMap::Iterator it = m_mapEntries.find(path); if (it != m_mapEntries.end()) { if (sub_entry) { (*it).m_entries.append(sub_entry); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Added already watched Entry" << path << "(for" << sub_entry->path << ")"; } } else { (*it).addClient(instance, watchModes); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Added already watched Entry" << path << "(now" << (*it).clientCount() << "clients)" << QStringLiteral("[%1]").arg(instance->objectName()); } } return; } // we have a new path to watch QT_STATBUF stat_buf; bool exists = (QT_STAT(QFile::encodeName(path).constData(), &stat_buf) == 0); EntryMap::iterator newIt = m_mapEntries.insert(path, Entry()); // the insert does a copy, so we have to use now Entry *e = &(*newIt); if (exists) { e->isDir = (stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_DIR; #ifndef Q_OS_WIN if (e->isDir && !isDir) { if (QT_LSTAT(QFile::encodeName(path).constData(), &stat_buf) == 0) { if ((stat_buf.st_mode & QT_STAT_MASK) == QT_STAT_LNK) { // if it's a symlink, don't follow it e->isDir = false; } } } #endif if (e->isDir && !isDir) { qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a directory. Use addDir!"; } else if (!e->isDir && isDir) { qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a file. Use addFile!"; } if (!e->isDir && (watchModes != KDirWatch::WatchDirOnly)) { qCWarning(KCOREADDONS_DEBUG) << "KDirWatch:" << path << "is a file. You can't use recursive or " "watchFiles options"; watchModes = KDirWatch::WatchDirOnly; } #ifdef Q_OS_WIN // ctime is the 'creation time' on windows - use mtime instead e->m_ctime = stat_buf.st_mtime; #else e->m_ctime = stat_buf.st_ctime; #endif e->m_status = Normal; e->m_nlink = stat_buf.st_nlink; e->m_ino = stat_buf.st_ino; } else { e->isDir = isDir; e->m_ctime = invalid_ctime; e->m_status = NonExistent; e->m_nlink = 0; e->m_ino = 0; } e->path = path; if (sub_entry) { e->m_entries.append(sub_entry); } else { e->addClient(instance, watchModes); } if (s_verboseDebug) { qCDebug(KDIRWATCH).nospace() << "Added " << (e->isDir ? "Dir " : "File ") << path << (e->m_status == NonExistent ? " NotExisting" : "") << " for " << (sub_entry ? sub_entry->path : QString()) << " [" << (instance ? instance->objectName() : QString()) << "]"; } // now setup the notification method e->m_mode = UnknownMode; e->msecLeft = 0; if (isNoisyFile(QFile::encodeName(path).data())) { return; } if (exists && e->isDir && (watchModes != KDirWatch::WatchDirOnly)) { QFlags filters = QDir::NoDotAndDotDot; if ((watchModes & KDirWatch::WatchSubDirs) && (watchModes & KDirWatch::WatchFiles)) { filters |= (QDir::Dirs | QDir::Files); } else if (watchModes & KDirWatch::WatchSubDirs) { filters |= QDir::Dirs; } else if (watchModes & KDirWatch::WatchFiles) { filters |= QDir::Files; } #if HAVE_SYS_INOTIFY_H if (e->m_mode == INotifyMode || (e->m_mode == UnknownMode && m_preferredMethod == KDirWatch::INotify)) { //qCDebug(KDIRWATCH) << "Ignoring WatchFiles directive - this is implicit with inotify"; // Placing a watch on individual files is redundant with inotify // (inotify gives us WatchFiles functionality "for free") and indeed // actively harmful, so prevent it. WatchSubDirs is necessary, though. filters &= ~QDir::Files; } #endif QDir basedir(e->path); const QFileInfoList contents = basedir.entryInfoList(filters); for (QFileInfoList::const_iterator iter = contents.constBegin(); iter != contents.constEnd(); ++iter) { const QFileInfo &fileInfo = *iter; // treat symlinks as files--don't follow them. bool isDir = fileInfo.isDir() && !fileInfo.isSymLink(); addEntry(instance, fileInfo.absoluteFilePath(), nullptr, isDir, isDir ? watchModes : KDirWatch::WatchDirOnly); } } addWatch(e); } void KDirWatchPrivate::addWatch(Entry *e) { // If the watch is on a network filesystem use the nfsPreferredMethod as the // default, otherwise use preferredMethod as the default, if the methods are // the same we can skip the mountpoint check // This allows to configure a different method for NFS mounts, since inotify // cannot detect changes made by other machines. However as a default inotify // is fine, since the most common case is a NFS-mounted home, where all changes // are made locally. #177892. KDirWatch::Method preferredMethod = m_preferredMethod; if (m_nfsPreferredMethod != m_preferredMethod) { if (KFileSystemType::fileSystemType(e->path) == KFileSystemType::Nfs) { preferredMethod = m_nfsPreferredMethod; } } // Try the appropriate preferred method from the config first bool entryAdded = false; switch (preferredMethod) { #if HAVE_FAM case KDirWatch::FAM: entryAdded = useFAM(e); break; #endif #if HAVE_SYS_INOTIFY_H case KDirWatch::INotify: entryAdded = useINotify(e); break; #endif #if HAVE_QFILESYSTEMWATCHER case KDirWatch::QFSWatch: entryAdded = useQFSWatch(e); break; #endif case KDirWatch::Stat: entryAdded = useStat(e); break; } // Failing that try in order INotify, FAM, QFSWatch, Stat if (!entryAdded) { #if HAVE_SYS_INOTIFY_H if (useINotify(e)) { return; } #endif #if HAVE_FAM if (useFAM(e)) { return; } #endif #if HAVE_QFILESYSTEMWATCHER if (useQFSWatch(e)) { return; } #endif useStat(e); } } void KDirWatchPrivate::removeWatch(Entry *e) { #if HAVE_FAM if (e->m_mode == FAMMode) { FAMCancelMonitor(&fc, &(e->fr)); qCDebug(KDIRWATCH).nospace() << "Cancelled FAM (Req " << FAMREQUEST_GETREQNUM(&(e->fr)) << ") for " << e->path; } #endif #if HAVE_SYS_INOTIFY_H if (e->m_mode == INotifyMode) { m_inotify_wd_to_entry.remove(e->wd); (void) inotify_rm_watch(m_inotify_fd, e->wd); if (s_verboseDebug) { qCDebug(KDIRWATCH).nospace() << "Cancelled INotify (fd " << m_inotify_fd << ", " << e->wd << ") for " << e->path; } } #endif #if HAVE_QFILESYSTEMWATCHER if (e->m_mode == QFSWatchMode && fsWatcher) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "fsWatcher->removePath" << e->path; } fsWatcher->removePath(e->path); } #endif } void KDirWatchPrivate::removeEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "path=" << _path << "sub_entry:" << sub_entry; } Entry *e = entry(_path); if (e) { removeEntry(instance, e, sub_entry); } } void KDirWatchPrivate::removeEntry(KDirWatch *instance, Entry *e, Entry *sub_entry) { removeList.remove(e); if (sub_entry) { e->m_entries.removeAll(sub_entry); } else { e->removeClient(instance); } if (!e->m_clients.empty() || !e->m_entries.empty()) { return; } if (delayRemove) { removeList.insert(e); // now e->isValid() is false return; } if (e->m_status == Normal) { removeWatch(e); } else { // Removed a NonExistent entry - we just remove it from the parent if (e->isDir) { removeEntry(nullptr, e->parentDirectory(), e); } else { removeEntry(nullptr, QFileInfo(e->path).absolutePath(), e); } } if (e->m_mode == StatMode) { statEntries--; if (statEntries == 0) { timer.stop(); // stop timer if lists are empty qCDebug(KDIRWATCH) << " Stopped Polling Timer"; } } if (s_verboseDebug) { qCDebug(KDIRWATCH).nospace() << "Removed " << (e->isDir ? "Dir " : "File ") << e->path << " for " << (sub_entry ? sub_entry->path : QString()) << " [" << (instance ? instance->objectName() : QString()) << "]"; } QString p = e->path; // take a copy, QMap::remove takes a reference and deletes, since e points into the map #if HAVE_SYS_INOTIFY_H m_inotify_wd_to_entry.remove(e->wd); #endif m_mapEntries.remove(p); // not valid any more } /* Called from KDirWatch destructor: * remove as client from all entries */ void KDirWatchPrivate::removeEntries(KDirWatch *instance) { int minfreq = 3600000; QStringList pathList; // put all entries where instance is a client in list EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { Client *c = nullptr; for (Client &client : (*it).m_clients) { if (client.instance == instance) { c = &client; break; } } if (c) { c->count = 1; // forces deletion of instance as client pathList.append((*it).path); } else if ((*it).m_mode == StatMode && (*it).freq < minfreq) { minfreq = (*it).freq; } } Q_FOREACH (const QString &path, pathList) { removeEntry(instance, path, nullptr); } if (minfreq > freq) { // we can decrease the global polling frequency freq = minfreq; if (timer.isActive()) { timer.start(freq); } qCDebug(KDIRWATCH) << "Poll Freq now" << freq << "msec"; } } // instance ==0: stop scanning for all instances bool KDirWatchPrivate::stopEntryScan(KDirWatch *instance, Entry *e) { int stillWatching = 0; for (Client &client : e->m_clients) { if (!instance || instance == client.instance) { client.watchingStopped = true; } else if (!client.watchingStopped) { stillWatching += client.count; } } qCDebug(KDIRWATCH) << (instance ? instance->objectName() : QStringLiteral("all")) << "stopped scanning" << e->path << "(now" << stillWatching << "watchers)"; if (stillWatching == 0) { // if nobody is interested, we don't watch, and we don't report // changes that happened while not watching e->m_ctime = invalid_ctime; // invalid // Changing m_status like this would create wrong "created" events in stat mode. // To really "stop watching" we would need to determine 'stillWatching==0' in scanEntry... //e->m_status = NonExistent; } return true; } // instance ==0: start scanning for all instances bool KDirWatchPrivate::restartEntryScan(KDirWatch *instance, Entry *e, bool notify) { int wasWatching = 0, newWatching = 0; for (Client &client : e->m_clients) { if (!client.watchingStopped) { wasWatching += client.count; } else if (!instance || instance == client.instance) { client.watchingStopped = false; newWatching += client.count; } } if (newWatching == 0) { return false; } qCDebug(KDIRWATCH) << (instance ? instance->objectName() : QStringLiteral("all")) << "restarted scanning" << e->path << "(now" << wasWatching + newWatching << "watchers)"; // restart watching and emit pending events int ev = NoChange; if (wasWatching == 0) { if (!notify) { QT_STATBUF stat_buf; bool exists = (QT_STAT(QFile::encodeName(e->path).constData(), &stat_buf) == 0); if (exists) { // ctime is the 'creation time' on windows, but with qMax // we get the latest change of any kind, on any platform. e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime); e->m_status = Normal; if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Setting status to Normal for" << e << e->path; } e->m_nlink = stat_buf.st_nlink; e->m_ino = stat_buf.st_ino; // Same as in scanEntry: ensure no subentry in parent dir removeEntry(nullptr, e->parentDirectory(), e); } else { e->m_ctime = invalid_ctime; e->m_status = NonExistent; e->m_nlink = 0; if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Setting status to NonExistent for" << e << e->path; } } } e->msecLeft = 0; ev = scanEntry(e); } emitEvent(e, ev); return true; } // instance ==0: stop scanning for all instances void KDirWatchPrivate::stopScan(KDirWatch *instance) { EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { stopEntryScan(instance, &(*it)); } } void KDirWatchPrivate::startScan(KDirWatch *instance, bool notify, bool skippedToo) { if (!notify) { resetList(instance, skippedToo); } EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { restartEntryScan(instance, &(*it), notify); } // timer should still be running when in polling mode } // clear all pending events, also from stopped void KDirWatchPrivate::resetList(KDirWatch *instance, bool skippedToo) { Q_UNUSED(instance); EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { for (Client &client : (*it).m_clients) { if (!client.watchingStopped || skippedToo) { client.pending = NoChange; } } } } // Return event happened on // int KDirWatchPrivate::scanEntry(Entry *e) { // Shouldn't happen: Ignore "unknown" notification method if (e->m_mode == UnknownMode) { return NoChange; } if (e->m_mode == FAMMode || e->m_mode == INotifyMode) { // we know nothing has changed, no need to stat if (!e->dirty) { return NoChange; } e->dirty = false; } if (e->m_mode == StatMode) { // only scan if timeout on entry timer happens; // e.g. when using 500msec global timer, a entry // with freq=5000 is only watched every 10th time e->msecLeft -= freq; if (e->msecLeft > 0) { return NoChange; } e->msecLeft += e->freq; } QT_STATBUF stat_buf; const bool exists = (QT_STAT(QFile::encodeName(e->path).constData(), &stat_buf) == 0); if (exists) { if (e->m_status == NonExistent) { // ctime is the 'creation time' on windows, but with qMax // we get the latest change of any kind, on any platform. e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime); e->m_status = Normal; e->m_ino = stat_buf.st_ino; if (s_verboseDebug) { qCDebug(KDIRWATCH) << "Setting status to Normal for just created" << e << e->path; } // We need to make sure the entry isn't listed in its parent's subentries... (#222974, testMoveTo) removeEntry(nullptr, e->parentDirectory(), e); return Created; } #if 1 // for debugging the if() below if (s_verboseDebug) { struct tm *tmp = localtime(&e->m_ctime); char outstr[200]; strftime(outstr, sizeof(outstr), "%H:%M:%S", tmp); qCDebug(KDIRWATCH) << e->path << "e->m_ctime=" << e->m_ctime << outstr << "stat_buf.st_ctime=" << stat_buf.st_ctime << "stat_buf.st_mtime=" << stat_buf.st_mtime << "e->m_nlink=" << e->m_nlink << "stat_buf.st_nlink=" << stat_buf.st_nlink << "e->m_ino=" << e->m_ino << "stat_buf.st_ino=" << stat_buf.st_ino; } #endif if ((e->m_ctime != invalid_ctime) && (qMax(stat_buf.st_ctime, stat_buf.st_mtime) != e->m_ctime || stat_buf.st_ino != e->m_ino || int(stat_buf.st_nlink) != int(e->m_nlink) #ifdef Q_OS_WIN // on Windows, we trust QFSW to get it right, the ctime comparisons above // fail for example when adding files to directories on Windows // which doesn't change the mtime of the directory || e->m_mode == QFSWatchMode #endif )) { e->m_ctime = qMax(stat_buf.st_ctime, stat_buf.st_mtime); e->m_nlink = stat_buf.st_nlink; if (e->m_ino != stat_buf.st_ino) { // The file got deleted and recreated. We need to watch it again. removeWatch(e); addWatch(e); e->m_ino = stat_buf.st_ino; return (Deleted|Created); } else { return Changed; } } return NoChange; } // dir/file doesn't exist e->m_nlink = 0; e->m_ino = 0; e->m_status = NonExistent; if (e->m_ctime == invalid_ctime) { return NoChange; } e->m_ctime = invalid_ctime; return Deleted; } /* Notify all interested KDirWatch instances about a given event on an entry * and stored pending events. When watching is stopped, the event is * added to the pending events. */ void KDirWatchPrivate::emitEvent(Entry *e, int event, const QString &fileName) { QString path(e->path); if (!fileName.isEmpty()) { if (!QDir::isRelativePath(fileName)) { path = fileName; } else { #ifdef Q_OS_UNIX path += QLatin1Char('/') + fileName; #elif defined(Q_OS_WIN) //current drive is passed instead of / path += QDir::currentPath().left(2) + QLatin1Char('/') + fileName; #endif } } if (s_verboseDebug) { qCDebug(KDIRWATCH) << event << path << e->m_clients.size() << "clients"; } for (Client &c : e->m_clients) { if (c.instance == nullptr || c.count == 0) { continue; } if (c.watchingStopped) { // Do not add event to a list of pending events, the docs say restartDirScan won't emit! #if 0 if (event == Changed) { c.pending |= event; } else if (event == Created || event == Deleted) { c.pending = event; } #endif continue; } // not stopped if (event == NoChange || event == Changed) { event |= c.pending; } c.pending = NoChange; if (event == NoChange) { continue; } // Emit the signals delayed, to avoid unexpected re-entrancy from the slots (#220153) if (event & Deleted) { #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(c.instance, "setDeleted", Qt::QueuedConnection, Q_ARG(QString, path)); #else QMetaObject::invokeMethod(c.instance, [c, path]() { c.instance->setDeleted(path); }, Qt::QueuedConnection); #endif } if (event & Created) { #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(c.instance, "setCreated", Qt::QueuedConnection, Q_ARG(QString, path)); #else QMetaObject::invokeMethod(c.instance, [c, path]() { c.instance->setCreated(path); }, Qt::QueuedConnection); #endif // possible emit Change event after creation } if (event & Changed) { #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) QMetaObject::invokeMethod(c.instance, "setDirty", Qt::QueuedConnection, Q_ARG(QString, path)); #else QMetaObject::invokeMethod(c.instance, [c, path]() { c.instance->setDirty(path); }, Qt::QueuedConnection); #endif } } } // Remove entries which were marked to be removed void KDirWatchPrivate::slotRemoveDelayed() { delayRemove = false; // Removing an entry could also take care of removing its parent // (e.g. in FAM or inotify mode), which would remove other entries in removeList, // so don't use Q_FOREACH or iterators here... while (!removeList.isEmpty()) { Entry *entry = *removeList.begin(); removeEntry(nullptr, entry, nullptr); // this will remove entry from removeList } } /* Scan all entries to be watched for changes. This is done regularly * when polling. FAM and inotify use a single-shot timer to call this slot delayed. */ void KDirWatchPrivate::slotRescan() { if (s_verboseDebug) { qCDebug(KDIRWATCH); } EntryMap::Iterator it; // People can do very long things in the slot connected to dirty(), // like showing a message box. We don't want to keep polling during // that time, otherwise the value of 'delayRemove' will be reset. // ### TODO: now the emitEvent delays emission, this can be cleaned up bool timerRunning = timer.isActive(); if (timerRunning) { timer.stop(); } // We delay deletions of entries this way. // removeDir(), when called in slotDirty(), can cause a crash otherwise // ### TODO: now the emitEvent delays emission, this can be cleaned up delayRemove = true; if (rescan_all) { // mark all as dirty it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { (*it).dirty = true; } rescan_all = false; } else { // progate dirty flag to dependant entries (e.g. file watches) it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) if (((*it).m_mode == INotifyMode || (*it).m_mode == QFSWatchMode) && (*it).dirty) { (*it).propagate_dirty(); } } #if HAVE_SYS_INOTIFY_H QList cList; #endif it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { // we don't check invalid entries (i.e. remove delayed) Entry *entry = &(*it); if (!entry->isValid()) { continue; } const int ev = scanEntry(entry); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "scanEntry for" << entry->path << "says" << ev; } switch (entry->m_mode) { #if HAVE_SYS_INOTIFY_H case INotifyMode: if (ev == Deleted) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "scanEntry says" << entry->path << "was deleted"; } addEntry(nullptr, entry->parentDirectory(), entry, true); } else if (ev == Created) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "scanEntry says" << entry->path << "was created. wd=" << entry->wd; } if (entry->wd < 0) { cList.append(entry); addWatch(entry); } } break; #endif case FAMMode: case QFSWatchMode: if (ev == Created) { addWatch(entry); } break; default: // dunno about StatMode... break; } #if HAVE_SYS_INOTIFY_H if (entry->isDir) { // Report and clear the the list of files that have changed in this directory. // Remove duplicates by changing to set and back again: // we don't really care about preserving the order of the // original changes. QStringList pendingFileChanges = entry->m_pendingFileChanges; pendingFileChanges.removeDuplicates(); Q_FOREACH (const QString &changedFilename, pendingFileChanges) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << "processing pending file change for" << changedFilename; } emitEvent(entry, Changed, changedFilename); } entry->m_pendingFileChanges.clear(); } #endif if (ev != NoChange) { emitEvent(entry, ev); } } if (timerRunning) { timer.start(freq); } #if HAVE_SYS_INOTIFY_H // Remove watch of parent of new created directories Q_FOREACH (Entry *e, cList) { removeEntry(nullptr, e->parentDirectory(), e); } #endif QTimer::singleShot(0, this, SLOT(slotRemoveDelayed())); } bool KDirWatchPrivate::isNoisyFile(const char *filename) { // $HOME/.X.err grows with debug output, so don't notify change if (*filename == '.') { if (strncmp(filename, ".X.err", 6) == 0) { return true; } if (strncmp(filename, ".xsession-errors", 16) == 0) { return true; } // fontconfig updates the cache on every KDE app start // (inclusive kio_thumbnail slaves) if (strncmp(filename, ".fonts.cache", 12) == 0) { return true; } } return false; } #if HAVE_FAM void KDirWatchPrivate::famEventReceived() { static FAMEvent fe; delayRemove = true; //qCDebug(KDIRWATCH) << "Fam event received"; while (use_fam && FAMPending(&fc)) { if (FAMNextEvent(&fc, &fe) == -1) { qCWarning(KCOREADDONS_DEBUG) << "FAM connection problem, switching to polling."; use_fam = false; delete sn; sn = 0; // Replace all FAMMode entries with INotify/Stat EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) if ((*it).m_mode == FAMMode && !(*it).m_clients.empty()) { Entry *e = &(*it); addWatch(e); } } else { checkFAMEvent(&fe); } } QTimer::singleShot(0, this, SLOT(slotRemoveDelayed())); } void KDirWatchPrivate::checkFAMEvent(FAMEvent *fe) { //qCDebug(KDIRWATCH); Entry *e = 0; EntryMap::Iterator it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) if (FAMREQUEST_GETREQNUM(&((*it).fr)) == FAMREQUEST_GETREQNUM(&(fe->fr))) { e = &(*it); break; } // Don't be too verbose ;-) if ((fe->code == FAMExists) || (fe->code == FAMEndExist) || (fe->code == FAMAcknowledge)) { if (e) { e->m_famReportedSeen = true; } return; } if (isNoisyFile(fe->filename)) { return; } // Entry *e = static_cast(fe->userdata); if (s_verboseDebug) { // don't enable this except when debugging, see #88538 qCDebug(KDIRWATCH) << "Processing FAM event (" << ((fe->code == FAMChanged) ? "FAMChanged" : (fe->code == FAMDeleted) ? "FAMDeleted" : (fe->code == FAMStartExecuting) ? "FAMStartExecuting" : (fe->code == FAMStopExecuting) ? "FAMStopExecuting" : (fe->code == FAMCreated) ? "FAMCreated" : (fe->code == FAMMoved) ? "FAMMoved" : (fe->code == FAMAcknowledge) ? "FAMAcknowledge" : (fe->code == FAMExists) ? "FAMExists" : (fe->code == FAMEndExist) ? "FAMEndExist" : "Unknown Code") << ", " << fe->filename << ", Req " << FAMREQUEST_GETREQNUM(&(fe->fr)) << ") e=" << e; } if (!e) { // this happens e.g. for FAMAcknowledge after deleting a dir... // qCDebug(KDIRWATCH) << "No entry for FAM event ?!"; return; } if (e->m_status == NonExistent) { qCDebug(KDIRWATCH) << "FAM event for nonExistent entry " << e->path; return; } // Delayed handling. This rechecks changes with own stat calls. e->dirty = true; if (!rescan_timer.isActive()) { rescan_timer.start(m_PollInterval); // singleshot } // needed FAM control actions on FAM events switch (fe->code) { case FAMDeleted: // fe->filename is an absolute path when a watched file-or-dir is deleted if (!QDir::isRelativePath(QFile::decodeName(fe->filename))) { FAMCancelMonitor(&fc, &(e->fr)); // needed ? qCDebug(KDIRWATCH) << "Cancelled FAMReq" << FAMREQUEST_GETREQNUM(&(e->fr)) << "for" << e->path; e->m_status = NonExistent; e->m_ctime = invalid_ctime; emitEvent(e, Deleted, e->path); // If the parent dir was already watched, tell it something changed Entry *parentEntry = entry(e->parentDirectory()); if (parentEntry) { parentEntry->dirty = true; } // Add entry to parent dir to notice if the entry gets recreated addEntry(0, e->parentDirectory(), e, true /*isDir*/); } else { // A file in this directory has been removed, and wasn't explicitly watched. // We could still inform clients, like inotify does? But stat can't. // For now we just marked e dirty and slotRescan will emit the dir as dirty. //qCDebug(KDIRWATCH) << "Got FAMDeleted for" << QFile::decodeName(fe->filename) << "in" << e->path << ". Absolute path -> NOOP!"; } break; case FAMCreated: { // check for creation of a directory we have to watch QString tpath(e->path + QLatin1Char('/') + QFile::decodeName(fe->filename)); // This code is very similar to the one in inotifyEventReceived... Entry *sub_entry = e->findSubEntry(tpath); if (sub_entry /*&& sub_entry->isDir*/) { // We were waiting for this new file/dir to be created. We don't actually // emit an event here, as the rescan_timer will re-detect the creation and // do the signal emission there. sub_entry->dirty = true; rescan_timer.start(0); // process this asap, to start watching that dir } else if (e->isDir && !e->m_clients.empty()) { bool isDir = false; const QList clients = e->clientsForFileOrDir(tpath, &isDir); Q_FOREACH (const Client *client, clients) { addEntry(client->instance, tpath, 0, isDir, isDir ? client->m_watchModes : KDirWatch::WatchDirOnly); } if (!clients.isEmpty()) { emitEvent(e, Created, tpath); qCDebug(KDIRWATCH).nospace() << clients.count() << " instance(s) monitoring the new " << (isDir ? "dir " : "file ") << tpath; } } } break; default: break; } } #else void KDirWatchPrivate::famEventReceived() { qCWarning(KCOREADDONS_DEBUG) << "Fam event received but FAM is not supported"; } #endif void KDirWatchPrivate::statistics() { EntryMap::Iterator it; qCDebug(KDIRWATCH) << "Entries watched:"; if (m_mapEntries.count() == 0) { qCDebug(KDIRWATCH) << " None."; } else { it = m_mapEntries.begin(); for (; it != m_mapEntries.end(); ++it) { Entry *e = &(*it); qCDebug(KDIRWATCH) << " " << *e; for (const Client &c : e->m_clients) { QByteArray pending; if (c.watchingStopped) { if (c.pending & Deleted) { pending += "deleted "; } if (c.pending & Created) { pending += "created "; } if (c.pending & Changed) { pending += "changed "; } if (!pending.isEmpty()) { pending = " (pending: " + pending + ')'; } pending = ", stopped" + pending; } qCDebug(KDIRWATCH) << " by " << c.instance->objectName() << " (" << c.count << " times)" << pending; } if (e->m_entries.count() > 0) { qCDebug(KDIRWATCH) << " dependent entries:"; Q_FOREACH (Entry *d, e->m_entries) { qCDebug(KDIRWATCH) << " " << d << d->path << (d->m_status == NonExistent ? "NonExistent" : "EXISTS!!! ERROR!"); if (s_verboseDebug) { Q_ASSERT(d->m_status == NonExistent); // it doesn't belong here otherwise } } } } } } #if HAVE_QFILESYSTEMWATCHER // Slot for QFileSystemWatcher void KDirWatchPrivate::fswEventReceived(const QString &path) { if (s_verboseDebug) { qCDebug(KDIRWATCH) << path; } EntryMap::Iterator it = m_mapEntries.find(path); if (it != m_mapEntries.end()) { Entry *e = &(*it); e->dirty = true; const int ev = scanEntry(e); if (s_verboseDebug) { qCDebug(KDIRWATCH) << "scanEntry for" << e->path << "says" << ev; } if (ev != NoChange) { emitEvent(e, ev); } if (ev == Deleted) { if (e->isDir) { addEntry(nullptr, e->parentDirectory(), e, true); } else { addEntry(nullptr, QFileInfo(e->path).absolutePath(), e, true); } } else if (ev == Created) { // We were waiting for it to appear; now watch it addWatch(e); } else if (e->isDir) { // Check if any file or dir was created under this directory, that we were waiting for Q_FOREACH (Entry *sub_entry, e->m_entries) { fswEventReceived(sub_entry->path); // recurse, to call scanEntry and see if something changed } } else { /* Even though QFileSystemWatcher only reported the file as modified, it is possible that the file * was in fact just deleted and then immediately recreated. If the file was deleted, QFileSystemWatcher * will delete the watch, and will ignore the file, even after it is recreated. Since it is impossible * to reliably detect this case, always re-request the watch on a dirty signal, to avoid losing the * underlying OS monitor. */ fsWatcher->addPath(e->path); } } } #else void KDirWatchPrivate::fswEventReceived(const QString &path) { Q_UNUSED(path); qCWarning(KCOREADDONS_DEBUG) << "QFileSystemWatcher event received but QFileSystemWatcher is not supported"; } #endif // HAVE_QFILESYSTEMWATCHER // // Class KDirWatch // Q_GLOBAL_STATIC(KDirWatch, s_pKDirWatchSelf) KDirWatch *KDirWatch::self() { return s_pKDirWatchSelf(); } // is this used anywhere? // yes, see kio/src/core/kcoredirlister_p.h:328 bool KDirWatch::exists() { return s_pKDirWatchSelf.exists() && dwp_self.hasLocalData(); } static void postRoutine_KDirWatch() { if (s_pKDirWatchSelf.exists()) { s_pKDirWatchSelf()->deleteQFSWatcher(); } } KDirWatch::KDirWatch(QObject *parent) : QObject(parent), d(createPrivate()) { static QBasicAtomicInt nameCounter = Q_BASIC_ATOMIC_INITIALIZER(1); const int counter = nameCounter.fetchAndAddRelaxed(1); // returns the old value setObjectName(QStringLiteral("KDirWatch-%1").arg(counter)); if (counter == 1) { // very first KDirWatch instance // Must delete QFileSystemWatcher before qApp is gone - bug 261541 qAddPostRoutine(postRoutine_KDirWatch); } } KDirWatch::~KDirWatch() { if (d && dwp_self.hasLocalData()) { // skip this after app destruction d->removeEntries(this); } } void KDirWatch::addDir(const QString &_path, WatchModes watchModes) { if (d) { d->addEntry(this, _path, nullptr, true, watchModes); } } void KDirWatch::addFile(const QString &_path) { if (!d) { return; } d->addEntry(this, _path, nullptr, false); } QDateTime KDirWatch::ctime(const QString &_path) const { KDirWatchPrivate::Entry *e = d->entry(_path); if (!e) { return QDateTime(); } return QDateTime::fromTime_t(e->m_ctime); } void KDirWatch::removeDir(const QString &_path) { if (d) { d->removeEntry(this, _path, nullptr); } } void KDirWatch::removeFile(const QString &_path) { if (d) { d->removeEntry(this, _path, nullptr); } } bool KDirWatch::stopDirScan(const QString &_path) { if (d) { KDirWatchPrivate::Entry *e = d->entry(_path); if (e && e->isDir) { return d->stopEntryScan(this, e); } } return false; } bool KDirWatch::restartDirScan(const QString &_path) { if (d) { KDirWatchPrivate::Entry *e = d->entry(_path); if (e && e->isDir) // restart without notifying pending events { return d->restartEntryScan(this, e, false); } } return false; } void KDirWatch::stopScan() { if (d) { d->stopScan(this); d->_isStopped = true; } } bool KDirWatch::isStopped() { return d->_isStopped; } void KDirWatch::startScan(bool notify, bool skippedToo) { if (d) { d->_isStopped = false; d->startScan(this, notify, skippedToo); } } bool KDirWatch::contains(const QString &_path) const { KDirWatchPrivate::Entry *e = d->entry(_path); if (!e) { return false; } for (const KDirWatchPrivate::Client &client : e->m_clients) { if (client.instance == this) { return true; } } return false; } void KDirWatch::deleteQFSWatcher() { delete d->fsWatcher; d->fsWatcher = nullptr; d = nullptr; } void KDirWatch::statistics() { if (!dwp_self.hasLocalData()) { qCDebug(KDIRWATCH) << "KDirWatch not used"; return; } dwp_self.localData()->statistics(); } void KDirWatch::setCreated(const QString &_file) { qCDebug(KDIRWATCH) << objectName() << "emitting created" << _file; emit created(_file); } void KDirWatch::setDirty(const QString &_file) { //qCDebug(KDIRWATCH) << objectName() << "emitting dirty" << _file; emit dirty(_file); } void KDirWatch::setDeleted(const QString &_file) { qCDebug(KDIRWATCH) << objectName() << "emitting deleted" << _file; emit deleted(_file); } KDirWatch::Method KDirWatch::internalMethod() const { // This reproduces the logic in KDirWatchPrivate::addWatch switch (d->m_preferredMethod) { #if HAVE_FAM case KDirWatch::FAM: if (d->use_fam) { return KDirWatch::FAM; } break; #endif #if HAVE_SYS_INOTIFY_H case KDirWatch::INotify: if (d->supports_inotify) { return KDirWatch::INotify; } break; #endif #if HAVE_QFILESYSTEMWATCHER case KDirWatch::QFSWatch: return KDirWatch::QFSWatch; #endif case KDirWatch::Stat: return KDirWatch::Stat; } #if HAVE_SYS_INOTIFY_H if (d->supports_inotify) { return KDirWatch::INotify; } #endif #if HAVE_FAM if (d->use_fam) { return KDirWatch::FAM; } #endif #if HAVE_QFILESYSTEMWATCHER return KDirWatch::QFSWatch; #else return KDirWatch::Stat; #endif } #include "moc_kdirwatch.cpp" #include "moc_kdirwatch_p.cpp" //sven diff --git a/src/lib/io/kdirwatch.h b/src/lib/io/kdirwatch.h index 2270387..e0de0b6 100644 --- a/src/lib/io/kdirwatch.h +++ b/src/lib/io/kdirwatch.h @@ -1,311 +1,311 @@ /* This file is part of the KDE libraries Copyright (C) 1998 Sven Radej This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KDIRWATCH_H #define _KDIRWATCH_H -#include -#include -#include +#include +#include +#include #include class KDirWatchPrivate; /** * @class KDirWatch kdirwatch.h KDirWatch * * @short Class for watching directory and file changes. * * Watch directories and files for changes. * The watched directories or files don't have to exist yet. * * When a watched directory is changed, i.e. when files therein are * created or deleted, KDirWatch will emit the signal dirty(). * * When a watched, but previously not existing directory gets created, * KDirWatch will emit the signal created(). * * When a watched directory gets deleted, KDirWatch will emit the * signal deleted(). The directory is still watched for new * creation. * * When a watched file is changed, i.e. attributes changed or written * to, KDirWatch will emit the signal dirty(). * * Scanning of particular directories or files can be stopped temporarily * and restarted. The whole class can be stopped and restarted. * Directories and files can be added/removed from the list in any state. * * The implementation uses the INOTIFY functionality on LINUX. * Otherwise the FAM service is used, when available. * As a last resort, a regular polling for change of modification times * is done; the polling interval is a global config option: * DirWatch/PollInterval and DirWatch/NFSPollInterval for NFS mounted * directories. * The choice of implementation can be adjusted by the user, with the key * [DirWatch] PreferredMethod={Fam|Stat|QFSWatch|inotify} * * @see self() * @author Sven Radej (in 1998) */ class KCOREADDONS_EXPORT KDirWatch : public QObject { Q_OBJECT public: /** * Available watch modes for directory monitoring **/ enum WatchMode { WatchDirOnly = 0, ///< Watch just the specified directory WatchFiles = 0x01, ///< Watch also all files contained by the directory WatchSubDirs = 0x02 ///< Watch also all the subdirs contained by the directory }; Q_DECLARE_FLAGS(WatchModes, WatchMode) /** * Constructor. * * Scanning begins immediately when a dir/file watch * is added. * @param parent the parent of the QObject (or 0 for parent-less KDataTools) */ KDirWatch(QObject *parent = nullptr); /** * Destructor. * * Stops scanning and cleans up. */ ~KDirWatch(); /** * Adds a directory to be watched. * * The directory does not have to exist. When @p watchModes is set to * WatchDirOnly (the default), the signals dirty(), created(), deleted() * can be emitted, all for the watched directory. * When @p watchModes is set to WatchFiles, all files in the watched * directory are watched for changes, too. Thus, the signals dirty(), * created(), deleted() can be emitted. * When @p watchModes is set to WatchSubDirs, all subdirs are watched using * the same flags specified in @p watchModes (symlinks aren't followed). * If the @p path points to a symlink to a directory, the target directory * is watched instead. If you want to watch the link, use @p addFile(). * * @param path the path to watch * @param watchModes watch modes * * @sa KDirWatch::WatchMode */ void addDir(const QString &path, WatchModes watchModes = WatchDirOnly); /** * Adds a file to be watched. * If it's a symlink to a directory, it watches the symlink itself. * @param file the file to watch */ void addFile(const QString &file); /** * Returns the time the directory/file was last changed. * @param path the file to check * @return the date of the last modification */ QDateTime ctime(const QString &path) const; /** * Removes a directory from the list of scanned directories. * * If specified path is not in the list this does nothing. * @param path the path of the dir to be removed from the list */ void removeDir(const QString &path); /** * Removes a file from the list of watched files. * * If specified path is not in the list this does nothing. * @param file the file to be removed from the list */ void removeFile(const QString &file); /** * Stops scanning the specified path. * * The @p path is not deleted from the internal list, it is just skipped. * Call this function when you perform an huge operation * on this directory (copy/move big files or many files). When finished, * call restartDirScan(path). * * @param path the path to skip * @return true if the @p path is being watched, otherwise false * @see restartDirScan() */ bool stopDirScan(const QString &path); /** * Restarts scanning for specified path. * * It doesn't notify about the changes (by emitting a signal). * The ctime value is reset. * * Call it when you are finished with big operations on that path, * @em and when @em you have refreshed that path. * * @param path the path to restart scanning * @return true if the @p path is being watched, otherwise false * @see stopDirScan() */ bool restartDirScan(const QString &path); /** * Starts scanning of all dirs in list. * * @param notify If true, all changed directories (since * stopScan() call) will be notified for refresh. If notify is * false, all ctimes will be reset (except those who are stopped, * but only if @p skippedToo is false) and changed dirs won't be * notified. You can start scanning even if the list is * empty. First call should be called with @p false or else all * directories * in list will be notified. * @param skippedToo if true, the skipped directoris (scanning of which was * stopped with stopDirScan() ) will be reset and notified * for change. Otherwise, stopped directories will continue to be * unnotified. */ void startScan(bool notify = false, bool skippedToo = false); /** * Stops scanning of all directories in internal list. * * The timer is stopped, but the list is not cleared. */ void stopScan(); /** * Is scanning stopped? * After creation of a KDirWatch instance, this is false. * @return true when scanning stopped */ bool isStopped(); /** * Check if a directory is being watched by this KDirWatch instance * @param path the directory to check * @return true if the directory is being watched */ bool contains(const QString &path) const; void deleteQFSWatcher(); // KF6 TODO: remove from public API /** * Dump statistic information about the KDirWatch::self() instance. * This checks for consistency, too. */ static void statistics(); // TODO implement a QDebug operator for KDirWatch instead. enum Method { FAM, INotify, Stat, QFSWatch }; /** * Returns the preferred internal method to * watch for changes. */ Method internalMethod() const; /** * The KDirWatch instance usually globally used in an application. * It is automatically deleted when the application exits. * * However, you can create an arbitrary number of KDirWatch instances * aside from this one - for those you have to take care of memory management. * * This function returns an instance of KDirWatch. If there is none, it * will be created. * * @return a KDirWatch instance */ static KDirWatch *self(); /** * Returns true if there is an instance of KDirWatch. * @return true if there is an instance of KDirWatch. * @see KDirWatch::self() */ static bool exists(); public Q_SLOTS: /** * Emits created(). * @param path the path of the file or directory */ void setCreated(const QString &path); /** * Emits dirty(). * @param path the path of the file or directory */ void setDirty(const QString &path); /** * Emits deleted(). * @param path the path of the file or directory */ void setDeleted(const QString &path); Q_SIGNALS: /** * Emitted when a watched object is changed. * For a directory this signal is emitted when files * therein are created or deleted. * For a file this signal is emitted when its size or attributes change. * * When you watch a directory, changes in the size or attributes of * contained files may or may not trigger this signal to be emitted * depending on which backend is used by KDirWatch. * * The new ctime is set before the signal is emitted. * @param path the path of the file or directory */ void dirty(const QString &path); /** * Emitted when a file or directory (being watched explicitly) is created. * This is not emitted when creating a file is created in a watched directory. * @param path the path of the file or directory */ void created(const QString &path); /** * Emitted when a file or directory is deleted. * * The object is still watched for new creation. * @param path the path of the file or directory */ void deleted(const QString &path); private: KDirWatchPrivate *d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KDirWatch::WatchModes) #endif diff --git a/src/lib/io/kdirwatch_p.h b/src/lib/io/kdirwatch_p.h index 4a55c18..24ad770 100644 --- a/src/lib/io/kdirwatch_p.h +++ b/src/lib/io/kdirwatch_p.h @@ -1,239 +1,239 @@ /* Private Header for class of KDirWatchPrivate * * this separate header file is needed for MOC processing * because KDirWatchPrivate has signals and slots * * This file is part of the KDE libraries * Copyright (C) 1998 Sven Radej * Copyright (C) 2006 Dirk Mueller * Copyright (C) 2007 Flavio Castelli * Copyright (C) 2008 Jarosław Staniek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KDIRWATCH_P_H #define KDIRWATCH_P_H #include #include "kdirwatch.h" #ifndef QT_NO_FILESYSTEMWATCHER #define HAVE_QFILESYSTEMWATCHER 1 #else #define HAVE_QFILESYSTEMWATCHER 0 #endif -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include class QSocketNotifier; #if HAVE_FAM #include #include #endif #include // time_t, ino_t #include #define invalid_ctime (static_cast(-1)) #if HAVE_QFILESYSTEMWATCHER -#include +#include #endif // HAVE_QFILESYSTEMWATCHER /* KDirWatchPrivate is a singleton and does the watching * for every KDirWatch instance in the application. */ class KDirWatchPrivate : public QObject { Q_OBJECT public: enum entryStatus { Normal = 0, NonExistent }; enum entryMode { UnknownMode = 0, StatMode, INotifyMode, FAMMode, QFSWatchMode }; enum { NoChange = 0, Changed = 1, Created = 2, Deleted = 4 }; struct Client { Client(KDirWatch *inst, KDirWatch::WatchModes watchModes) : instance(inst), count(1), watchingStopped(inst->isStopped()), pending(NoChange), m_watchModes(watchModes) {} // The compiler needs a copy ctor for Client when Entry is inserted into m_mapEntries // (even though the vector of clients is empty at that point, so no performance penalty there) //Client(const Client &) = delete; //Client &operator=(const Client &) = delete; //Client(Client &&) = default; //Client &operator=(Client &&) = default; KDirWatch *instance; int count; // did the instance stop watching bool watchingStopped; // events blocked when stopped int pending; KDirWatch::WatchModes m_watchModes; }; class Entry { public: ~Entry(); // instances interested in events std::vector m_clients; // nonexistent entries of this directory QList m_entries; QString path; // the last observed modification time time_t m_ctime; // last observed inode ino_t m_ino; // the last observed link count int m_nlink; entryStatus m_status; entryMode m_mode; int msecLeft, freq; bool isDir; QString parentDirectory() const; void addClient(KDirWatch *, KDirWatch::WatchModes); void removeClient(KDirWatch *); int clientCount() const; bool isValid() { return !m_clients.empty() || !m_entries.empty(); } Entry *findSubEntry(const QString &path) const { Q_FOREACH (Entry *sub_entry, m_entries) { if (sub_entry->path == path) { return sub_entry; } } return nullptr; } bool dirty; void propagate_dirty(); QList clientsForFileOrDir(const QString &tpath, bool *isDir) const; QList inotifyClientsForFileOrDir(bool isDir) const; #if HAVE_FAM FAMRequest fr; bool m_famReportedSeen; #endif #if HAVE_SYS_INOTIFY_H int wd; // Creation and Deletion of files happens infrequently, so // can safely be reported as they occur. File changes i.e. those that emity "dirty()" can // happen many times per second, though, so maintain a list of files in this directory // that can be emitted and flushed at the next slotRescan(...). // This will be unused if the Entry is not a directory. QList m_pendingFileChanges; #endif }; typedef QMap EntryMap; KDirWatchPrivate(); ~KDirWatchPrivate(); void resetList(KDirWatch *instance, bool skippedToo); void useFreq(Entry *e, int newFreq); void addEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry, bool isDir, KDirWatch::WatchModes watchModes = KDirWatch::WatchDirOnly); void removeEntry(KDirWatch *instance, const QString &path, Entry *sub_entry); void removeEntry(KDirWatch *instance, Entry *e, Entry *sub_entry); bool stopEntryScan(KDirWatch *instance, Entry *e); bool restartEntryScan(KDirWatch *instance, Entry *e, bool notify); void stopScan(KDirWatch *instance); void startScan(KDirWatch *instance, bool notify, bool skippedToo); void removeEntries(KDirWatch *instance); void statistics(); void addWatch(Entry *entry); void removeWatch(Entry *entry); Entry *entry(const QString &_path); int scanEntry(Entry *e); void emitEvent(Entry *e, int event, const QString &fileName = QString()); static bool isNoisyFile(const char *filename); public Q_SLOTS: void slotRescan(); void famEventReceived(); // for FAM void inotifyEventReceived(); // for inotify void slotRemoveDelayed(); void fswEventReceived(const QString &path); // for QFileSystemWatcher public: QTimer timer; EntryMap m_mapEntries; KDirWatch::Method m_preferredMethod, m_nfsPreferredMethod; int freq; int statEntries; int m_nfsPollInterval, m_PollInterval; bool useStat(Entry *e); // removeList is allowed to contain any entry at most once QSet removeList; bool delayRemove; bool rescan_all; QTimer rescan_timer; #if HAVE_FAM QSocketNotifier *sn; FAMConnection fc; bool use_fam; void checkFAMEvent(FAMEvent *fe); bool useFAM(Entry *e); #endif #if HAVE_SYS_INOTIFY_H QSocketNotifier *mSn; bool supports_inotify; int m_inotify_fd; QHash m_inotify_wd_to_entry; bool useINotify(Entry *e); #endif #if HAVE_QFILESYSTEMWATCHER QFileSystemWatcher *fsWatcher; bool useQFSWatch(Entry *e); #endif bool _isStopped; }; QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry); #endif // KDIRWATCH_P_H diff --git a/src/lib/io/kfilesystemtype.h b/src/lib/io/kfilesystemtype.h index 65e1fec..f7e0e07 100644 --- a/src/lib/io/kfilesystemtype.h +++ b/src/lib/io/kfilesystemtype.h @@ -1,49 +1,49 @@ /* This file is part of the KDE libraries Copyright (c) 2011 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2.1 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KFILESYSTEMTYPE_P_H #define KFILESYSTEMTYPE_P_H -#include +#include #include /** * @namespace KFileSystemType * Provides utility functions for the type of file systems. */ namespace KFileSystemType { enum Type { Unknown, Nfs, ///< NFS or other full-featured networked filesystems (autofs, subfs, cachefs, sshfs) Smb, ///< SMB/CIFS mount (networked but with some FAT-like behavior) Fat, ///< FAT or similar (msdos, fat, vfat) Ramfs, ///< RAMDISK mount Other ///< ext, reiser, and so on. "Normal" local filesystems. }; /** * Returns the file system type at a given path, as much as we are able to figure it out. * @since 5.0 */ KCOREADDONS_EXPORT Type fileSystemType(const QString &path); } #endif diff --git a/src/lib/io/kmessage.cpp b/src/lib/io/kmessage.cpp index d0e9e24..cf1910f 100644 --- a/src/lib/io/kmessage.cpp +++ b/src/lib/io/kmessage.cpp @@ -1,99 +1,99 @@ /* This file is part of the KDE libraries Copyright (C) 2006 Michaël Larouche This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; version 2 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmessage.h" -#include +#include #include class StaticMessageHandler { public: StaticMessageHandler() : m_handler(nullptr) {} ~StaticMessageHandler() { delete m_handler; } /* Sets the new message handler and deletes the old one */ void setHandler(KMessageHandler *handler) { delete m_handler; m_handler = handler; } KMessageHandler *handler() const { return m_handler; } protected: KMessageHandler *m_handler; }; Q_GLOBAL_STATIC(StaticMessageHandler, s_messageHandler) static void internalMessageFallback(KMessage::MessageType messageType, const QString &text, const QString &caption) { QString prefix; switch (messageType) { case KMessage::Error: prefix = QStringLiteral("ERROR: "); break; case KMessage::Fatal: prefix = QStringLiteral("FATAL: "); break; case KMessage::Information: prefix = QStringLiteral("INFORMATION: "); break; case KMessage::Sorry: prefix = QStringLiteral("SORRY: "); break; case KMessage::Warning: prefix = QStringLiteral("WARNING: "); break; } QString message; if (!caption.isEmpty()) { message += QLatin1Char('(') + caption + QLatin1Char(')'); } message += prefix + text; // Show a message to the developer to setup a KMessageHandler std::cerr << "WARNING: Please setup an KMessageHandler with KMessage::setMessageHandler to display message propertly." << std::endl; // Show message to stdout std::cerr << qPrintable(message) << std::endl; } void KMessage::setMessageHandler(KMessageHandler *handler) { // Delete old message handler. s_messageHandler()->setHandler(handler); } void KMessage::message(KMessage::MessageType messageType, const QString &text, const QString &caption) { // Use current message handler if available, else use stdout if (s_messageHandler()->handler()) { s_messageHandler()->handler()->message(messageType, text, caption); } else { internalMessageFallback(messageType, text, caption); } } diff --git a/src/lib/io/kmessage.h b/src/lib/io/kmessage.h index 3f7dcf0..036e688 100644 --- a/src/lib/io/kmessage.h +++ b/src/lib/io/kmessage.h @@ -1,127 +1,127 @@ /* This file is part of the KDE libraries Copyright (C) 2006 Michaël Larouche This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; version 2 of the License. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDECORE_KMESSAGE_H #define KDECORE_KMESSAGE_H #include -#include +#include class KMessageHandler; /** * @brief Display an informative message using a KMessageHandler. * * This class does not define how to display a message, it is just * a clean interface for developers to use. * The job is done by the current KMessageHandler set in the class. * * If no KMessageHandler is currently registered in KMessage, * the message will be outputed to stderr. * * Use KMessage::setMessageHandler() to use a KMessageHandler. * * @code * KMessage::setMessageHandler( new KMessageBoxHandler(this) ); * // some operation * * KMessage::message( KMessage::Error, i18n("Could not load service. Use kbuildsycoca to fix the service database."), i18n("KService") ); * @endcode * * Some KMessageHandler are already done such as KMessageBoxMessageHandler and KPassivePopupMessageHandler. * @author Michaël Larouche */ namespace KMessage { enum MessageType { /** * Error message. * Display critical information that affect the behavior of the application. */ Error, /** * Information message. * Display useful information to the user. */ Information, /** * Warning message. * Display a message that could affect the behavior of the application. */ Warning, /** * Sorry message. * Display a message explaining that a task couldn't be accomplished. */ Sorry, /** * Fatal message. * Display a message before the application fail and close itself. */ Fatal }; /** * @brief Display a long message of a certain type. * A long message span on multiple lines and can have a caption. * * @param messageType Currrent type of message. See MessageType enum. * @param text Long message to be displayed. * @param caption Caption to be used. This is optional. */ KCOREADDONS_EXPORT void message(KMessage::MessageType messageType, const QString &text, const QString &caption = QString()); /** * @brief Set the current KMessageHandler * Note that this method takes ownership of the KMessageHandler. * @param handler Instance of a real KMessageHandler. * * @warning This function isn't thread-safe. You don't want to * change the message handler during the program's * execution anyways. Do so only at start-up. */ KCOREADDONS_EXPORT void setMessageHandler(KMessageHandler *handler); } /** * \class KMessageHandler kmessage.h * * @brief Abstract class for KMessage handler. * This class define how KMessage display a message. * * Reimplement the virtual methods then set your custom * KMessageHandler using KMessage::setMessageHandler() * * @author Michaël Larouche */ class KCOREADDONS_EXPORT KMessageHandler { public: virtual ~KMessageHandler() {} // KF6 TODO: de-inline (-Wweak-vtables) /** * @brief Display a long message of a certain type. * A long message span on multiple lines and can have a caption. * * @param type Currrent type of message. See MessageType enum. * @param text Long message to be displayed. * @param caption Caption to be used. This is optional. */ virtual void message(KMessage::MessageType type, const QString &text, const QString &caption) = 0; }; #endif diff --git a/src/lib/io/kprocess.h b/src/lib/io/kprocess.h index 5036971..103aa43 100644 --- a/src/lib/io/kprocess.h +++ b/src/lib/io/kprocess.h @@ -1,339 +1,339 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Oswald Buddenhagen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KPROCESS_H #define KPROCESS_H #include -#include +#include class KProcessPrivate; /** * \class KProcess kprocess.h * * Child process invocation, monitoring and control. * * This class extends QProcess by some useful functionality, overrides * some defaults with saner values and wraps parts of the API into a more * accessible one. * Only use KProcess if you need the extra features, otherwise QProcess * is the preferred way of spawning child processes. * * @author Oswald Buddenhagen **/ class KCOREADDONS_EXPORT KProcess : public QProcess { Q_OBJECT Q_DECLARE_PRIVATE(KProcess) public: /** * Modes in which the output channels can be opened. */ enum OutputChannelMode { SeparateChannels = QProcess::SeparateChannels, /**< Standard output and standard error are handled by KProcess as separate channels */ MergedChannels = QProcess::MergedChannels, /**< Standard output and standard error are handled by KProcess as one channel */ ForwardedChannels = QProcess::ForwardedChannels, /**< Both standard output and standard error are forwarded to the parent process' respective channel */ OnlyStdoutChannel = QProcess::ForwardedErrorChannel, /**< Only standard output is handled; standard error is forwarded */ OnlyStderrChannel = QProcess::ForwardedOutputChannel /**< Only standard error is handled; standard output is forwarded */ }; /** * Constructor */ explicit KProcess(QObject *parent = nullptr); /** * Destructor */ ~KProcess() override; /** * Set how to handle the output channels of the child process. * * The default is ForwardedChannels, which is unlike in QProcess. * Do not request more than you actually handle, as this output is * simply lost otherwise. * * This function must be called before starting the process. * * @param mode the output channel handling mode */ void setOutputChannelMode(OutputChannelMode mode); /** * Query how the output channels of the child process are handled. * * @return the output channel handling mode */ OutputChannelMode outputChannelMode() const; /** * Set the QIODevice open mode the process will be opened in. * * This function must be called before starting the process, obviously. * * @param mode the open mode. Note that this mode is automatically * "reduced" according to the channel modes and redirections. * The default is QIODevice::ReadWrite. */ void setNextOpenMode(QIODevice::OpenMode mode); /** * Adds the variable @p name to the process' environment. * * This function must be called before starting the process. * * @param name the name of the environment variable * @param value the new value for the environment variable * @param overwrite if @c false and the environment variable is already * set, the old value will be preserved */ void setEnv(const QString &name, const QString &value, bool overwrite = true); /** * Removes the variable @p name from the process' environment. * * This function must be called before starting the process. * * @param name the name of the environment variable */ void unsetEnv(const QString &name); /** * Empties the process' environment. * * Note that LD_LIBRARY_PATH/DYLD_LIBRARY_PATH is automatically added * on *NIX. * * This function must be called before starting the process. */ void clearEnvironment(); /** * Set the program and the command line arguments. * * This function must be called before starting the process, obviously. * * @param exe the program to execute * @param args the command line arguments for the program, * one per list element */ void setProgram(const QString &exe, const QStringList &args = QStringList()); /** * @overload * * @param argv the program to execute and the command line arguments * for the program, one per list element */ void setProgram(const QStringList &argv); /** * Append an element to the command line argument list for this process. * * If no executable is set yet, it will be set instead. * * For example, doing an "ls -l /usr/local/bin" can be achieved by: * \code * KProcess p; * p << "ls" << "-l" << "/usr/local/bin"; * ... * \endcode * * This function must be called before starting the process, obviously. * * @param arg the argument to add * @return a reference to this KProcess */ KProcess &operator<<(const QString &arg); /** * @overload * * @param args the arguments to add * @return a reference to this KProcess */ KProcess &operator<<(const QStringList &args); /** * Clear the program and command line argument list. */ void clearProgram(); /** * Set a command to execute through a shell (a POSIX sh on *NIX * and cmd.exe on Windows). * * Using this for anything but user-supplied commands is usually a bad * idea, as the command's syntax depends on the platform. * Redirections including pipes, etc. are better handled by the * respective functions provided by QProcess. * * If KProcess determines that the command does not really need a * shell, it will trasparently execute it without one for performance * reasons. * * This function must be called before starting the process, obviously. * * @param cmd the command to execute through a shell. * The caller must make sure that all filenames etc. are properly * quoted when passed as argument. Failure to do so often results in * serious security holes. See KShell::quoteArg(). */ void setShellCommand(const QString &cmd); /** * Obtain the currently set program and arguments. * * @return a list, the first element being the program, the remaining ones * being command line arguments to the program. */ QStringList program() const; /** * Start the process. * * @see QProcess::start(const QString &, const QStringList &, OpenMode) */ void start(); /** * Start the process, wait for it to finish, and return the exit code. * * This method is roughly equivalent to the sequence: * * start(); * waitForFinished(msecs); * return exitCode(); * * * Unlike the other execute() variants this method is not static, * so the process can be parametrized properly and talked to. * * @param msecs time to wait for process to exit before killing it * @return -2 if the process could not be started, -1 if it crashed, * otherwise its exit code */ int execute(int msecs = -1); /** * @overload * * @param exe the program to execute * @param args the command line arguments for the program, * one per list element * @param msecs time to wait for process to exit before killing it * @return -2 if the process could not be started, -1 if it crashed, * otherwise its exit code */ static int execute(const QString &exe, const QStringList &args = QStringList(), int msecs = -1); /** * @overload * * @param argv the program to execute and the command line arguments * for the program, one per list element * @param msecs time to wait for process to exit before killing it * @return -2 if the process could not be started, -1 if it crashed, * otherwise its exit code */ static int execute(const QStringList &argv, int msecs = -1); /** * Start the process and detach from it. See QProcess::startDetached() * for details. * * Unlike the other startDetached() variants this method is not static, * so the process can be parametrized properly. * @note Currently, only the setProgram()/setShellCommand() and * setWorkingDirectory() parametrizations are supported. * * The KProcess object may be re-used immediately after calling this * function. * * @return the PID of the started process or 0 on error */ int startDetached(); /** * @overload * * @param exe the program to start * @param args the command line arguments for the program, * one per list element * @return the PID of the started process or 0 on error */ static int startDetached(const QString &exe, const QStringList &args = QStringList()); /** * @overload * * @param argv the program to start and the command line arguments * for the program, one per list element * @return the PID of the started process or 0 on error */ static int startDetached(const QStringList &argv); /** * Obtain the process' ID as known to the system. * * Unlike with QProcess::pid(), this is a real PID also on Windows. * * This function can be called only while the process is running. * It cannot be applied to detached processes. * * @return the process ID */ int pid() const; protected: /** * @internal */ KProcess(KProcessPrivate *d, QObject *parent); /** * @internal */ KProcessPrivate *const d_ptr; private: // hide those using QProcess::setReadChannelMode; using QProcess::readChannelMode; using QProcess::setProcessChannelMode; using QProcess::processChannelMode; }; #endif diff --git a/src/lib/jobs/kcompositejob.h b/src/lib/jobs/kcompositejob.h index 734d220..1944496 100644 --- a/src/lib/jobs/kcompositejob.h +++ b/src/lib/jobs/kcompositejob.h @@ -1,123 +1,123 @@ /* This file is part of the KDE project Copyright (C) 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KCOMPOSITEJOB_H #define KCOMPOSITEJOB_H #include #include -#include +#include class KCompositeJobPrivate; /** * @class KCompositeJob kcompositejob.h KCompositeJob * * The base class for all jobs able to be composed of one * or more subjobs. */ class KCOREADDONS_EXPORT KCompositeJob : public KJob { Q_OBJECT public: /** * Creates a new KCompositeJob object. * * @param parent the parent QObject */ explicit KCompositeJob(QObject *parent = nullptr); /** * Destroys a KCompositeJob object. */ ~KCompositeJob() override; protected: /** * Add a job that has to be finished before a result * is emitted. This has obviously to be called before * the result has been emitted by the job. * * Note that the composite job takes ownership of @p job * * @param job the subjob to add * @return true if the job has been added correctly, false otherwise */ virtual bool addSubjob(KJob *job); /** * Mark a sub job as being done. * * The ownership of @p job is passed on to the caller. * * @param job the subjob to remove * @return true if the job has been removed correctly, false otherwise */ virtual bool removeSubjob(KJob *job); /** * Checks if this job has subjobs running. * * @return true if we still have subjobs running, false otherwise */ bool hasSubjobs() const; /** * Retrieves the list of the subjobs. * * @return the full list of sub jobs */ const QList &subjobs() const; /** * Clears the list of subjobs. * * Note that this will *not* delete the subjobs. * Ownership of the subjobs is passed on to the caller. */ void clearSubjobs(); protected Q_SLOTS: /** * Called whenever a subjob finishes. * Default implementation checks for errors and propagates * to parent job, and in all cases it calls removeSubjob. * * @param job the subjob */ virtual void slotResult(KJob *job); /** * Forward signal from subjob. * * @param job the subjob * @param plain the info message in plain text version * @param rich the info message in rich text version * @see infoMessage() */ virtual void slotInfoMessage(KJob *job, const QString &plain, const QString &rich); protected: KCompositeJob(KCompositeJobPrivate &dd, QObject *parent); private: Q_DECLARE_PRIVATE(KCompositeJob) }; #endif diff --git a/src/lib/jobs/kjob.h b/src/lib/jobs/kjob.h index 1a7c461..85e6df3 100644 --- a/src/lib/jobs/kjob.h +++ b/src/lib/jobs/kjob.h @@ -1,657 +1,657 @@ /* This file is part of the KDE project Copyright (C) 2000 Stephan Kulow David Faure Copyright (C) 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KJOB_H #define KJOB_H #include -#include -#include +#include +#include class KJobUiDelegate; class KJobPrivate; /** * @class KJob kjob.h KJob * * The base class for all jobs. * For all jobs created in an application, the code looks like * * \code * void SomeClass::methodWithAsynchronousJobCall() * { * KJob* job = someoperation(some parameters); * connect(job, SIGNAL(result(KJob*)), * this, SLOT(handleResult(KJob*))); * job->start(); * } * \endcode * (other connects, specific to the job) * * And handleResult is usually at least: * * \code * void SomeClass::handleResult( KJob *job ) * { * if (job->error()) { * doSomething(); * } * } * \endcode * * With the synchronous interface the code looks like * * \code * void SomeClass::methodWithSynchronousJobCall() * { * KJob *job = someoperation( some parameters ); * if (!job->exec()) { * // An error occurred * } else { * // Do something * } * } * \endcode * * Subclasses must implement start(), which should trigger * the execution of the job (although the work should be * done asynchronously). errorString() should also be * reimplemented by any subclasses that introduce new * error codes. * * @note: KJob and its subclasses are meant to be used * in a fire-and-forget way. Jobs will delete themselves * when they finish using deleteLater() (although this * behaviour can be changed), so a job instance will * disappear after the next event loop run. */ class KCOREADDONS_EXPORT KJob : public QObject { Q_OBJECT Q_PROPERTY(int error READ error NOTIFY result) Q_PROPERTY(QString errorText READ errorText NOTIFY result) Q_PROPERTY(QString errorString READ errorString NOTIFY result) Q_PROPERTY(unsigned long percent READ percent NOTIFY percent) Q_PROPERTY(Capabilities capabilities READ capabilities CONSTANT) public: enum Unit { Bytes, Files, Directories }; Q_ENUM(Unit) enum Capability { NoCapabilities = 0x0000, Killable = 0x0001, Suspendable = 0x0002 }; Q_ENUM(Capability) Q_DECLARE_FLAGS(Capabilities, Capability) Q_FLAG(Capabilities) /** * Creates a new KJob object. * * @param parent the parent QObject */ explicit KJob(QObject *parent = nullptr); /** * Destroys a KJob object. */ ~KJob() override; /** * Attach a UI delegate to this job. * * If the job had another UI delegate, it's automatically deleted. Once * attached to the job, the UI delegate will be deleted with the job. * * @param delegate the new UI delegate to use * @see KJobUiDelegate */ void setUiDelegate(KJobUiDelegate *delegate); /** * Retrieves the delegate attached to this job. * * @return the delegate attached to this job, or 0 if there's no such delegate */ KJobUiDelegate *uiDelegate() const; /** * Returns the capabilities of this job. * * @return the capabilities that this job supports * @see setCapabilities() */ Capabilities capabilities() const; /** * Returns if the job was suspended with the suspend() call. * * @return if the job was suspended * @see suspend() resume() */ bool isSuspended() const; /** * Starts the job asynchronously. * * When the job is finished, result() is emitted. * * Warning: Never implement any synchronous workload in this method. This method * should just trigger the job startup, not do any work itself. It is expected to * be non-blocking. * * This is the method all subclasses need to implement. * It should setup and trigger the workload of the job. It should not do any * work itself. This includes all signals and terminating the job, e.g. by * emitResult(). The workload, which could be another method of the * subclass, is to be triggered using the event loop, e.g. by code like: * \code * void ExampleJob::start() * { * QTimer::singleShot(0, this, SLOT(doWork())); * } * \endcode */ Q_SCRIPTABLE virtual void start() = 0; enum KillVerbosity { Quietly, EmitResult }; Q_ENUM(KillVerbosity) public Q_SLOTS: /** * Aborts this job. * * This kills and deletes the job. * * @param verbosity if equals to EmitResult, Job will emit signal result * and ask uiserver to close the progress window. * @p verbosity is set to EmitResult for subjobs. Whether applications * should call with Quietly or EmitResult depends on whether they rely * on result being emitted or not. Please notice that if @p verbosity is * set to Quietly, signal result will NOT be emitted. * @return true if the operation is supported and succeeded, false otherwise */ bool kill(KillVerbosity verbosity = Quietly); /** * Suspends this job. * The job should be kept in a state in which it is possible to resume it. * * @return true if the operation is supported and succeeded, false otherwise */ bool suspend(); /** * Resumes this job. * * @return true if the operation is supported and succeeded, false otherwise */ bool resume(); protected: /** * Aborts this job quietly. * * This simply kills the job, no error reporting or job deletion should be involved. * * @return true if the operation is supported and succeeded, false otherwise */ virtual bool doKill(); /** * Suspends this job. * * @return true if the operation is supported and succeeded, false otherwise */ virtual bool doSuspend(); /** * Resumes this job. * * @return true if the operation is supported and succeeded, false otherwise */ virtual bool doResume(); /** * Sets the capabilities for this job. * * @param capabilities are the capabilities supported by this job * @see capabilities() */ void setCapabilities(Capabilities capabilities); public: /** * Executes the job synchronously. * * This will start a nested QEventLoop internally. Nested event loop can be dangerous and * can have unintended side effects, you should avoid calling exec() whenever you can and use the * asynchronous interface of KJob instead. * * Should you indeed call this method, you need to make sure that all callers are reentrant, * so that events delivered by the inner event loop don't cause non-reentrant functions to be * called, which usually wreaks havoc. * * Note that the event loop started by this method does not process user input events, which means * your user interface will effectivly be blocked. Other events like paint or network events are * still being processed. The advantage of not processing user input events is that the chance of * accidental reentrancy is greatly reduced. Still you should avoid calling this function. * * @return true if the job has been executed without error, false otherwise */ bool exec(); enum { /*** Indicates there is no error */ NoError = 0, /*** Indicates the job was killed */ KilledJobError = 1, /*** Subclasses should define error codes starting at this value */ UserDefinedError = 100 }; /** * Returns the error code, if there has been an error. * * Only call this method from the slot connected to result(). * * @return the error code for this job, 0 if no error. */ int error() const; /** * Returns the error text if there has been an error. * * Only call if error is not 0. * * This is usually some extra data associated with the error, * such as a URL. Use errorString() to get a human-readable, * translated message. * * @return a string to help understand the error */ QString errorText() const; /** * A human-readable error message. * * This provides a translated, human-readable description of the * error. Only call if error is not 0. * * Subclasses should implement this to create a translated * error message from the error code and error text. * For example: * \code * if (error() == ReadFailed) { * i18n("Could not read \"%1\"", errorText()); * } * \endcode * * @return a translated error message, providing error() is 0 */ virtual QString errorString() const; /** * Returns the processed amount of a given unit for this job. * * @param unit the unit of the requested amount * @return the processed size */ Q_SCRIPTABLE qulonglong processedAmount(Unit unit) const; /** * Returns the total amount of a given unit for this job. * * @param unit the unit of the requested amount * @return the total size */ Q_SCRIPTABLE qulonglong totalAmount(Unit unit) const; /** * Returns the overall progress of this job. * * @return the overall progress of this job */ unsigned long percent() const; /** * set the auto-delete property of the job. If @p autodelete is * set to false the job will not delete itself once it is finished. * * The default for any KJob is to automatically delete itself. * * @param autodelete set to false to disable automatic deletion * of the job. */ void setAutoDelete(bool autodelete); /** * Returns whether this job automatically deletes itself once * the job is finished. * * @return whether the job is deleted automatically after * finishing. */ bool isAutoDelete() const; Q_SIGNALS: /** * Emitted when the job is finished, in any case. It is used to notify * observers that the job is terminated and that progress can be hidden. * * This is a private signal, it can't be emitted directly by subclasses of * KJob, use emitResult() instead. * * In general, to be notified of a job's completion, client code should connect to result() * rather than finished(), so that kill(Quietly) is indeed quiet. * However if you store a list of jobs and they might get killed silently, * then you must connect to this instead of result(), to avoid dangling pointers in your list. * * @param job the job that emitted this signal * @internal * * @see result */ void finished(KJob *job #if !defined(DOXYGEN_SHOULD_SKIP_THIS) , QPrivateSignal #endif ); /** * Emitted when the job is suspended. * * This is a private signal, it can't be emitted directly by subclasses of * KJob. * * @param job the job that emitted this signal */ void suspended(KJob *job #if !defined(DOXYGEN_SHOULD_SKIP_THIS) , QPrivateSignal #endif ); /** * Emitted when the job is resumed. * * This is a private signal, it can't be emitted directly by subclasses of * KJob. * * @param job the job that emitted this signal */ void resumed(KJob *job #if !defined(DOXYGEN_SHOULD_SKIP_THIS) , QPrivateSignal #endif ); /** * Emitted when the job is finished (except when killed with KJob::Quietly). * * Use error to know if the job was finished with error. * * This is a private signal, it can't be emitted directly by subclasses of * KJob, use emitResult() instead. * * Please connect to this signal instead of finished. * * @param job the job that emitted this signal * * @see kill */ void result(KJob *job #if !defined(DOXYGEN_SHOULD_SKIP_THIS) , QPrivateSignal #endif ); /** * Emitted to display general description of this job. A description has * a title and two optional fields which can be used to complete the * description. * * Examples of titles are "Copying", "Creating resource", etc. * The fields of the description can be "Source" with an URL, and, * "Destination" with an URL for a "Copying" description. * @param job the job that emitted this signal * @param title the general description of the job * @param field1 first field (localized name and value) * @param field2 second field (localized name and value) */ void description(KJob *job, const QString &title, const QPair &field1 = QPair(), const QPair &field2 = QPair()); /** * Emitted to display state information about this job. * Examples of message are "Resolving host", "Connecting to host...", etc. * * @param job the job that emitted this signal * @param plain the info message * @param rich the rich text version of the message, or QString() is none is available */ void infoMessage(KJob *job, const QString &plain, const QString &rich = QString()); /** * Emitted to display a warning about this job. * * @param job the job that emitted this signal * @param plain the warning message * @param rich the rich text version of the message, or QString() is none is available */ void warning(KJob *job, const QString &plain, const QString &rich = QString()); Q_SIGNALS: #if !defined(Q_MOC_RUN) && !defined(DOXYGEN_SHOULD_SKIP_THIS) && !defined(IN_IDE_PARSER) // TODO KF6: Use QPrivateSignal instead, to allow for new signal-slot syntax private: // don't tell moc, doxygen or kdevelop, but those signals are in fact private #endif /** * Emitted when we know the amount the job will have to process. The unit of this * amount is sent too. It can be emitted several times if the job manages several * different units. * * This is a private signal, it can't be emitted directly by subclasses of * KJob, use setTotalAmount() instead. * * @param job the job that emitted this signal * @param unit the unit of the total amount * @param amount the total amount */ void totalAmount(KJob *job, KJob::Unit unit, qulonglong amount); /** * Regularly emitted to show the progress of this job by giving the current amount. * The unit of this amount is sent too. It can be emitted several times if the job * manages several different units. * * This is a private signal, it can't be emitted directly by subclasses of * KJob, use setProcessedAmount() instead. * * @param job the job that emitted this signal * @param unit the unit of the processed amount * @param amount the processed amount */ void processedAmount(KJob *job, KJob::Unit unit, qulonglong amount); /** * Emitted when we know the size of this job (data size in bytes for transfers, * number of entries for listings, etc). * * This is a private signal, it can't be emitted directly by subclasses of * KJob, use setTotalAmount() instead. * * @param job the job that emitted this signal * @param size the total size */ void totalSize(KJob *job, qulonglong size); /** * Regularly emitted to show the progress of this job * (current data size in bytes for transfers, entries listed, etc.). * * This is a private signal, it can't be emitted directly by subclasses of * KJob, use setProcessedAmount() instead. * * @param job the job that emitted this signal * @param size the processed size */ void processedSize(KJob *job, qulonglong size); /** * Progress signal showing the overall progress of the job * This is valid for any kind of job, and allows using a * a progress bar very easily. (see KProgressBar). * Note that this signal is not emitted for finished jobs. * * This is a private signal, it can't be emitted directly by subclasses of * KJob, use emitPercent(), setPercent() setTotalAmount() or * setProcessedAmount() instead. * * @param job the job that emitted this signal * @param percent the percentage */ void percent(KJob *job, unsigned long percent); /** * Emitted to display information about the speed of this job. * * This is a private signal, it can't be emitted directly by subclasses of * KJob, use emitSpeed() instead. * * @param job the job that emitted this signal * @param speed the speed in bytes/s */ void speed(KJob *job, unsigned long speed); protected: /** * Sets the error code. * * It should be called when an error * is encountered in the job, just before calling emitResult(). * * You should define an (anonymous) enum of error codes, * with values starting at KJob::UserDefinedError, and use * those. For example, * @code * enum { * InvalidFoo = UserDefinedError, * BarNotFound * }; * @endcode * * @param errorCode the error code * @see emitResult() */ void setError(int errorCode); /** * Sets the error text. * * It should be called when an error * is encountered in the job, just before calling emitResult(). * * Provides extra information about the error that cannot be * determined directly from the error code. For example, a * URL or filename. This string is not normally translatable. * * @param errorText the error text * @see emitResult(), errorString(), setError() */ void setErrorText(const QString &errorText); /** * Sets the processed size. The processedAmount() and percent() signals * are emitted if the values changed. The percent() signal is emitted * only for the progress unit. * * @param unit the unit of the new processed amount * @param amount the new processed amount */ void setProcessedAmount(Unit unit, qulonglong amount); /** * Sets the total size. The totalSize() and percent() signals * are emitted if the values changed. The percent() signal is emitted * only for the progress unit. * * @param unit the unit of the new total amount * @param amount the new total amount */ void setTotalAmount(Unit unit, qulonglong amount); /** * Sets the overall progress of the job. The percent() signal * is emitted if the value changed. * * @param percentage the new overall progress */ void setPercent(unsigned long percentage); /** * Utility function to emit the result signal, and suicide this job. * It first notifies the observers to hide the progress for this job using * the finished() signal. * * @note: Deletes this job using deleteLater(). * * @see result() * @see finished() */ void emitResult(); /** * Utility function for inherited jobs. * Emits the percent signal if bigger than previous value, * after calculating it from the parameters. * * @param processedAmount the processed amount * @param totalAmount the total amount * @see percent() */ void emitPercent(qulonglong processedAmount, qulonglong totalAmount); /** * Utility function for inherited jobs. * Emits the speed signal and starts the timer for removing that info * * @param speed the speed in bytes/s */ void emitSpeed(unsigned long speed); protected: KJobPrivate *const d_ptr; KJob(KJobPrivate &dd, QObject *parent); private: void finishJob(bool emitResult); Q_PRIVATE_SLOT(d_func(), void _k_speedTimeout()) Q_DECLARE_PRIVATE(KJob) }; Q_DECLARE_METATYPE(KJob::Unit) Q_DECLARE_OPERATORS_FOR_FLAGS(KJob::Capabilities) #endif diff --git a/src/lib/jobs/kjobtrackerinterface.h b/src/lib/jobs/kjobtrackerinterface.h index eae180b..f47dfc9 100644 --- a/src/lib/jobs/kjobtrackerinterface.h +++ b/src/lib/jobs/kjobtrackerinterface.h @@ -1,193 +1,193 @@ /* This file is part of the KDE project Copyright (C) 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KJOBTRACKERINTERFACE_H #define KJOBTRACKERINTERFACE_H #include #include -#include -#include +#include +#include /** * @class KJobTrackerInterface kjobtrackerinterface.h KJobTrackerInterface * * The interface to implement to track the progresses of a job. */ class KCOREADDONS_EXPORT KJobTrackerInterface : public QObject { Q_OBJECT public: /** * Creates a new KJobTrackerInterface * * @param parent the parent object */ KJobTrackerInterface(QObject *parent = nullptr); /** * Destroys a KJobTrackerInterface */ ~KJobTrackerInterface() override; public Q_SLOTS: /** * Register a new job in this tracker. * The default implementation connects the following KJob signals * to the respective protected slots of this class: * - finished() (also connected to the unregisterJob() slot) * - suspended() * - resumed() * - description() * - infoMessage() * - totalAmount() * - processedAmount() * - percent() * - speed() * * If you re-implement this method, you may want to call the default * implementation or add at least: * * @code * connect(job, &KJob::finished, this, &MyJobTracker::unregisterJob); * @endcode * * so that you won't have to manually call unregisterJob(). * * @param job the job to register * @see unregisterJob() */ virtual void registerJob(KJob *job); /** * Unregister a job from this tracker. * @note You need to manually call this method only if you re-implemented * registerJob() without connecting KJob::finished to this slot. * * @param job the job to unregister * @see registerJob() */ virtual void unregisterJob(KJob *job); protected Q_SLOTS: /** * Called when a job is finished, in any case. It is used to notify * that the job is terminated and that progress UI (if any) can be hidden. * * @param job the job that emitted this signal */ virtual void finished(KJob *job); /** * Called when a job is suspended. * * @param job the job that emitted this signal */ virtual void suspended(KJob *job); /** * Called when a job is resumed. * * @param job the job that emitted this signal */ virtual void resumed(KJob *job); /** * Called to display general description of a job. A description has * a title and two optional fields which can be used to complete the * description. * * Examples of titles are "Copying", "Creating resource", etc. * The fields of the description can be "Source" with an URL, and, * "Destination" with an URL for a "Copying" description. * @param job the job that emitted this signal * @param title the general description of the job * @param field1 first field (localized name and value) * @param field2 second field (localized name and value) */ virtual void description(KJob *job, const QString &title, const QPair &field1, const QPair &field2); /** * Called to display state information about a job. * Examples of message are "Resolving host", "Connecting to host...", etc. * * @param job the job that emitted this signal * @param plain the info message * @param rich the rich text version of the message, or QString() is none is available */ virtual void infoMessage(KJob *job, const QString &plain, const QString &rich); /** * Emitted to display a warning about a job. * * @param job the job that emitted this signal * @param plain the warning message * @param rich the rich text version of the message, or QString() is none is available */ virtual void warning(KJob *job, const QString &plain, const QString &rich); /** * Called when we know the amount a job will have to process. The unit of this * amount is provided too. It can be called several times for a given job if the job * manages several different units. * * @param job the job that emitted this signal * @param unit the unit of the total amount * @param amount the total amount */ virtual void totalAmount(KJob *job, KJob::Unit unit, qulonglong amount); /** * Regularly called to show the progress of a job by giving the current amount. * The unit of this amount is provided too. It can be called several times for a given * job if the job manages several different units. * * @param job the job that emitted this signal * @param unit the unit of the processed amount * @param amount the processed amount */ virtual void processedAmount(KJob *job, KJob::Unit unit, qulonglong amount); /** * Called to show the overall progress of the job. * Note that this is not called for finished jobs. * * @param job the job that emitted this signal * @param percent the percentage */ virtual void percent(KJob *job, unsigned long percent); /** * Called to show the speed of the job. * * @param job the job that emitted this signal * @param value the current speed of the job */ virtual void speed(KJob *job, unsigned long value); private: class Private; Private *const d; }; #endif diff --git a/src/lib/jobs/kjobuidelegate.h b/src/lib/jobs/kjobuidelegate.h index 33b31d3..2d140a1 100644 --- a/src/lib/jobs/kjobuidelegate.h +++ b/src/lib/jobs/kjobuidelegate.h @@ -1,140 +1,140 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow David Faure Copyright (C) 2006 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KJOBUIDELEGATE_H #define KJOBUIDELEGATE_H #include -#include +#include class KJob; /** * @class KJobUiDelegate kjobuidelegate.h KJobUiDelegate * * The base class for all KJob UI delegate. * * A UI delegate is responsible for the events of a * job and provides a UI for them (an error message * box or warning etc.). * * @see KJob */ class KCOREADDONS_EXPORT KJobUiDelegate : public QObject { Q_OBJECT public: /** * Constructs a new KJobUiDelegate. */ KJobUiDelegate(); /** * Destroys a KJobUiDelegate. */ ~KJobUiDelegate() override; protected: /** * Attach this UI delegate to a job. Once attached it'll track the job events. * * @return true if the job we're correctly attached to the job, false otherwise. */ virtual bool setJob(KJob *job); protected: /** * Retrieves the current job this UI delegate is attached to. * * @return current job this UI delegate is attached to, or 0 if * this UI delegate is not tracking any job */ KJob *job() const; friend class KJob; public: /** * Display a dialog box to inform the user of the error given by * this job. * Only call if error is not 0, and only in the slot connected * to result. */ virtual void showErrorMessage(); /** * Enable or disable the automatic error handling. When automatic * error handling is enabled and an error occurs, then showErrorDialog() * is called, right before the emission of the result signal. * * The default is false. * * See also isAutoErrorHandlingEnabled , showErrorDialog * * @param enable enable or disable automatic error handling * @see isAutoErrorHandlingEnabled() */ void setAutoErrorHandlingEnabled(bool enable); /** * Returns whether automatic error handling is enabled or disabled. * See also setAutoErrorHandlingEnabled . * @return true if automatic error handling is enabled * @see setAutoErrorHandlingEnabled() */ bool isAutoErrorHandlingEnabled() const; /** * Enable or disable the automatic warning handling. When automatic * warning handling is enabled and an error occurs, then a message box * is displayed with the warning message * * The default is true. * * See also isAutoWarningHandlingEnabled , showErrorDialog * * @param enable enable or disable automatic warning handling * @see isAutoWarningHandlingEnabled() */ void setAutoWarningHandlingEnabled(bool enable); /** * Returns whether automatic warning handling is enabled or disabled. * See also setAutoWarningHandlingEnabled . * @return true if automatic warning handling is enabled * @see setAutoWarningHandlingEnabled() */ bool isAutoWarningHandlingEnabled() const; protected Q_SLOTS: virtual void slotWarning(KJob *job, const QString &plain, const QString &rich); private: void connectJob(KJob *job); Q_PRIVATE_SLOT(d, void _k_result(KJob *)) class Private; Private *const d; }; #endif // KJOBUIDELEGATE_H diff --git a/src/lib/kaboutdata.cpp b/src/lib/kaboutdata.cpp index fed7176..194becf 100644 --- a/src/lib/kaboutdata.cpp +++ b/src/lib/kaboutdata.cpp @@ -1,1207 +1,1207 @@ /* * This file is part of the KDE Libraries * Copyright (C) 2000 Espen Sand (espen@kde.org) * Copyright (C) 2006 Nicolas GOUTTE * Copyright (C) 2008 Friedrich W. H. Kossebau * Copyright (C) 2010 Teo Mrnjavac * Copyright (C) 2017 Harald Sitter * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "kaboutdata.h" #include "kpluginmetadata.h" #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(KABOUTDATA) // logging category for this framework, default: log stuff >= warning Q_LOGGING_CATEGORY(KABOUTDATA, "kf5.kcoreaddons.kaboutdata", QtWarningMsg) class Q_DECL_HIDDEN KAboutPerson::Private { public: QString _name; QString _task; QString _emailAddress; QString _webAddress; QString _ocsUsername; }; KAboutPerson::KAboutPerson(const QString &_name, const QString &_task, const QString &_emailAddress, const QString &_webAddress, const QString &_ocsUsername) : d(new Private) { d->_name = _name; d->_task = _task; d->_emailAddress = _emailAddress; d->_webAddress = _webAddress; d->_ocsUsername = _ocsUsername; } KAboutPerson::KAboutPerson(const QString &_name, const QString &_email, bool) : d(new Private) { d->_name = _name; d->_emailAddress = _email; } KAboutPerson::KAboutPerson(const KAboutPerson &other): d(new Private) { *d = *other.d; } KAboutPerson::~KAboutPerson() { delete d; } QString KAboutPerson::name() const { return d->_name; } QString KAboutPerson::task() const { return d->_task; } QString KAboutPerson::emailAddress() const { return d->_emailAddress; } QString KAboutPerson::webAddress() const { return d->_webAddress; } QString KAboutPerson::ocsUsername() const { return d->_ocsUsername; } KAboutPerson &KAboutPerson::operator=(const KAboutPerson &other) { *d = *other.d; return *this; } KAboutPerson KAboutPerson::fromJSON(const QJsonObject &obj) { const QString name = KPluginMetaData::readTranslatedString(obj, QStringLiteral("Name")); const QString task = KPluginMetaData::readTranslatedString(obj, QStringLiteral("Task")); const QString email = obj[QStringLiteral("Email")].toString(); const QString website = obj[QStringLiteral("Website")].toString(); const QString userName = obj[QStringLiteral("UserName")].toString(); return KAboutPerson(name, task, email, website, userName); } class Q_DECL_HIDDEN KAboutLicense::Private : public QSharedData { public: Private(LicenseKey licenseType, VersionRestriction versionRestriction, const KAboutData *aboutData); Private(const Private &other); QString spdxID() const; LicenseKey _licenseKey; QString _licenseText; QString _pathToLicenseTextFile; VersionRestriction _versionRestriction; // needed for access to the possibly changing copyrightStatement() const KAboutData *_aboutData; }; KAboutLicense::Private::Private(LicenseKey licenseType, VersionRestriction versionRestriction, const KAboutData *aboutData) : QSharedData(), _licenseKey(licenseType), _versionRestriction(versionRestriction), _aboutData(aboutData) { } KAboutLicense::Private::Private(const KAboutLicense::Private &other) : QSharedData(other), _licenseKey(other._licenseKey), _licenseText(other._licenseText), _pathToLicenseTextFile(other._pathToLicenseTextFile), _versionRestriction(other._versionRestriction), _aboutData(other._aboutData) {} QString KAboutLicense::Private::spdxID() const { switch (_licenseKey) { case KAboutLicense::GPL_V2: return QStringLiteral("GPL-2.0"); case KAboutLicense::LGPL_V2: return QStringLiteral("LGPL-2.0"); case KAboutLicense::BSDL: return QStringLiteral("BSD-2-Clause"); case KAboutLicense::Artistic: return QStringLiteral("Artistic-1.0"); case KAboutLicense::QPL_V1_0: return QStringLiteral("QPL-1.0"); case KAboutLicense::GPL_V3: return QStringLiteral("GPL-3.0"); case KAboutLicense::LGPL_V3: return QStringLiteral("LGPL-3.0"); case KAboutLicense::LGPL_V2_1: return QStringLiteral("LGPL-2.1"); case KAboutLicense::Custom: case KAboutLicense::File: case KAboutLicense::Unknown: return QString(); } return QString(); } KAboutLicense::KAboutLicense(LicenseKey licenseType, VersionRestriction versionRestriction, const KAboutData *aboutData) : d(new Private(licenseType, versionRestriction, aboutData)) { } KAboutLicense::KAboutLicense(LicenseKey licenseType, const KAboutData *aboutData) : d(new Private(licenseType, OnlyThisVersion, aboutData)) { } KAboutLicense::KAboutLicense(const KAboutData *aboutData) : d(new Private(Unknown, OnlyThisVersion, aboutData)) { } KAboutLicense::KAboutLicense(const KAboutLicense &other) : d(other.d) { } KAboutLicense::~KAboutLicense() {} void KAboutLicense::setLicenseFromPath(const QString &pathToFile) { d->_licenseKey = KAboutLicense::File; d->_pathToLicenseTextFile = pathToFile; } void KAboutLicense::setLicenseFromText(const QString &licenseText) { d->_licenseKey = KAboutLicense::Custom; d->_licenseText = licenseText; } QString KAboutLicense::text() const { QString result; const QString lineFeed = QStringLiteral("\n\n"); if (d->_aboutData && !d->_aboutData->copyrightStatement().isEmpty()) { result = d->_aboutData->copyrightStatement() + lineFeed; } bool knownLicense = false; QString pathToFile; // rel path if known license switch (d->_licenseKey) { case KAboutLicense::File: pathToFile = d->_pathToLicenseTextFile; break; case KAboutLicense::GPL_V2: knownLicense = true; pathToFile = QStringLiteral("GPL_V2"); break; case KAboutLicense::LGPL_V2: knownLicense = true; pathToFile = QStringLiteral("LGPL_V2"); break; case KAboutLicense::BSDL: knownLicense = true; pathToFile = QStringLiteral("BSD"); break; case KAboutLicense::Artistic: knownLicense = true; pathToFile = QStringLiteral("ARTISTIC"); break; case KAboutLicense::QPL_V1_0: knownLicense = true; pathToFile = QStringLiteral("QPL_V1.0"); break; case KAboutLicense::GPL_V3: knownLicense = true; pathToFile = QStringLiteral("GPL_V3"); break; case KAboutLicense::LGPL_V3: knownLicense = true; pathToFile = QStringLiteral("LGPL_V3"); break; case KAboutLicense::LGPL_V2_1: knownLicense = true; pathToFile = QStringLiteral("LGPL_V21"); break; case KAboutLicense::Custom: if (!d->_licenseText.isEmpty()) { result = d->_licenseText; break; } // fall through default: result += QCoreApplication::translate( "KAboutLicense", "No licensing terms for this program have been specified.\n" "Please check the documentation or the source for any\n" "licensing terms.\n"); } if (knownLicense) { pathToFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString::fromLatin1("kf5/licenses/") + pathToFile); result += QCoreApplication::translate( "KAboutLicense", "This program is distributed under the terms of the %1.").arg(name(KAboutLicense::ShortName)); if (!pathToFile.isEmpty()) { result += lineFeed; } } if (!pathToFile.isEmpty()) { QFile file(pathToFile); if (file.open(QIODevice::ReadOnly)) { QTextStream str(&file); result += str.readAll(); } } return result; } QString KAboutLicense::spdx() const { // SPDX licenses are comprised of an identifier (e.g. GPL-2.0), an optional + to denote 'or // later versions' and optional ' WITH $exception' to denote standardized exceptions from the // core license. As we do not offer exceptions we effectively only return GPL-2.0 or GPL-2.0+, // this may change in the future. To that end the documentation makes no assertations about the // actual content of the SPDX license expression we return. // Expressions can in theory also contain AND, OR and () to build constructs involving more than // one license. As this is outside the scope of a single license object we'll ignore this here // for now. // The expecation is that the return value is only run through spec-compliant parsers, so this // can potentially be changed. auto id = d->spdxID(); if (id.isNull()) { // Guard against potential future changes which would allow 'Foo+' as input. return id; } return d->_versionRestriction == OrLaterVersions ? id.append(QLatin1Char('+')) : id; } QString KAboutLicense::name(KAboutLicense::NameFormat formatName) const { QString licenseShort; QString licenseFull; switch (d->_licenseKey) { case KAboutLicense::GPL_V2: licenseShort = QCoreApplication::translate("KAboutLicense", "GPL v2", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "GNU General Public License Version 2", "@item license"); break; case KAboutLicense::LGPL_V2: licenseShort = QCoreApplication::translate("KAboutLicense", "LGPL v2", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "GNU Lesser General Public License Version 2", "@item license"); break; case KAboutLicense::BSDL: licenseShort = QCoreApplication::translate("KAboutLicense", "BSD License", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "BSD License", "@item license"); break; case KAboutLicense::Artistic: licenseShort = QCoreApplication::translate("KAboutLicense", "Artistic License", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "Artistic License", "@item license"); break; case KAboutLicense::QPL_V1_0: licenseShort = QCoreApplication::translate("KAboutLicense", "QPL v1.0", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "Q Public License", "@item license"); break; case KAboutLicense::GPL_V3: licenseShort = QCoreApplication::translate("KAboutLicense", "GPL v3", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "GNU General Public License Version 3", "@item license"); break; case KAboutLicense::LGPL_V3: licenseShort = QCoreApplication::translate("KAboutLicense", "LGPL v3", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "GNU Lesser General Public License Version 3", "@item license"); break; case KAboutLicense::LGPL_V2_1: licenseShort = QCoreApplication::translate("KAboutLicense", "LGPL v2.1", "@item license (short name)"); licenseFull = QCoreApplication::translate("KAboutLicense", "GNU Lesser General Public License Version 2.1", "@item license"); break; case KAboutLicense::Custom: case KAboutLicense::File: licenseShort = licenseFull = QCoreApplication::translate("KAboutLicense", "Custom", "@item license"); break; default: licenseShort = licenseFull = QCoreApplication::translate("KAboutLicense", "Not specified", "@item license"); } const QString result = (formatName == KAboutLicense::ShortName) ? licenseShort : (formatName == KAboutLicense::FullName) ? licenseFull : QString(); return result; } KAboutLicense &KAboutLicense::operator=(const KAboutLicense &other) { d = other.d; return *this; } KAboutLicense::LicenseKey KAboutLicense::key() const { return d->_licenseKey; } KAboutLicense KAboutLicense::byKeyword(const QString &rawKeyword) { // Setup keyword->enum dictionary on first call. // Use normalized keywords, by the algorithm below. static const QHash licenseDict { { "gpl", KAboutLicense::GPL }, { "gplv2", KAboutLicense::GPL_V2 }, { "gplv2+", KAboutLicense::GPL_V2 }, { "gpl20", KAboutLicense::GPL_V2 }, { "gpl20+", KAboutLicense::GPL_V2 }, { "lgpl", KAboutLicense::LGPL }, { "lgplv2", KAboutLicense::LGPL_V2 }, { "lgplv2+", KAboutLicense::LGPL_V2 }, { "lgpl20", KAboutLicense::LGPL_V2 }, { "lgpl20+", KAboutLicense::LGPL_V2 }, { "bsd", KAboutLicense::BSDL }, { "bsd2clause", KAboutLicense::BSDL }, { "artistic", KAboutLicense::Artistic }, { "artistic10", KAboutLicense::Artistic }, { "qpl", KAboutLicense::QPL }, { "qplv1", KAboutLicense::QPL_V1_0 }, { "qplv10", KAboutLicense::QPL_V1_0 }, { "qpl10", KAboutLicense::QPL_V1_0 }, { "gplv3", KAboutLicense::GPL_V3 }, { "gplv3+", KAboutLicense::GPL_V3 }, { "gpl30", KAboutLicense::GPL_V3 }, { "gpl30+", KAboutLicense::GPL_V3 }, { "lgplv3", KAboutLicense::LGPL_V3 }, { "lgplv3+", KAboutLicense::LGPL_V3 }, { "lgpl30", KAboutLicense::LGPL_V3 }, { "lgpl30+", KAboutLicense::LGPL_V3 }, { "lgplv21", KAboutLicense::LGPL_V2_1 }, { "lgplv21+", KAboutLicense::LGPL_V2_1 }, { "lgpl21", KAboutLicense::LGPL_V2_1 }, { "lgpl21+", KAboutLicense::LGPL_V2_1 }, }; // Normalize keyword. QString keyword = rawKeyword; keyword = keyword.toLower(); keyword.remove(QLatin1Char(' ')); keyword.remove(QLatin1Char('.')); keyword.remove(QLatin1Char('-')); LicenseKey license = licenseDict.value(keyword.toLatin1(), KAboutLicense::Custom); auto restriction = keyword.endsWith(QLatin1Char('+')) ? OrLaterVersions : OnlyThisVersion; return KAboutLicense(license, restriction, nullptr); } class Q_DECL_HIDDEN KAboutData::Private { public: Private() : customAuthorTextEnabled(false) {} QString _componentName; QString _displayName; QString _shortDescription; QString _copyrightStatement; QString _otherText; QString _homepageAddress; QList _authorList; QList _creditList; QList _translatorList; QList _licenseList; QString productName; QString programIconName; QVariant programLogo; QString customAuthorPlainText, customAuthorRichText; bool customAuthorTextEnabled; QString organizationDomain; QString _ocsProviderUrl; QString desktopFileName; // Everything dr.konqi needs, we store as utf-8, so we // can just give it a pointer, w/o any allocations. QByteArray _internalProgramName; QByteArray _version; QByteArray _bugAddress; static QList parseTranslators(const QString &translatorName, const QString &translatorEmail); }; KAboutData::KAboutData(const QString &_componentName, const QString &_displayName, const QString &_version, const QString &_shortDescription, enum KAboutLicense::LicenseKey licenseType, const QString &_copyrightStatement, const QString &text, const QString &homePageAddress, const QString &bugAddress ) : d(new Private) { d->_componentName = _componentName; int p = d->_componentName.indexOf(QLatin1Char('/')); if (p >= 0) { d->_componentName = d->_componentName.mid(p + 1); } d->_displayName = _displayName; if (!d->_displayName.isEmpty()) { // KComponentData("klauncher") gives empty program name d->_internalProgramName = _displayName.toUtf8(); } d->_version = _version.toUtf8(); d->_shortDescription = _shortDescription; d->_licenseList.append(KAboutLicense(licenseType, this)); d->_copyrightStatement = _copyrightStatement; d->_otherText = text; d->_homepageAddress = homePageAddress; d->_bugAddress = bugAddress.toUtf8(); QUrl homePageUrl(homePageAddress); if (!homePageUrl.isValid() || homePageUrl.scheme().isEmpty()) { // Default domain if nothing else is better homePageUrl.setUrl(QStringLiteral("https://kde.org/")); } const QChar dotChar(QLatin1Char('.')); QStringList hostComponents = homePageUrl.host().split(dotChar); // Remove leading component unless 2 (or less) components are present if (hostComponents.size() > 2) { hostComponents.removeFirst(); } d->organizationDomain = hostComponents.join(dotChar); // KF6: do not set a default desktopFileName value here, but remove this code and leave it empty // see KAboutData::desktopFileName() for detals // desktop file name is reverse domain name std::reverse(hostComponents.begin(), hostComponents.end()); hostComponents.append(_componentName); d->desktopFileName = hostComponents.join(dotChar); } KAboutData::KAboutData(const QString &_componentName, const QString &_displayName, const QString &_version ) : d(new Private) { d->_componentName = _componentName; int p = d->_componentName.indexOf(QLatin1Char('/')); if (p >= 0) { d->_componentName = d->_componentName.mid(p + 1); } d->_displayName = _displayName; if (!d->_displayName.isEmpty()) { // KComponentData("klauncher") gives empty program name d->_internalProgramName = _displayName.toUtf8(); } d->_version = _version.toUtf8(); // match behaviour of other constructors d->_licenseList.append(KAboutLicense(KAboutLicense::Unknown, this)); d->_bugAddress = "submit@bugs.kde.org"; d->organizationDomain = QStringLiteral("kde.org"); // KF6: do not set a default desktopFileName value here, but remove this code and leave it empty // see KAboutData::desktopFileName() for detals d->desktopFileName = QStringLiteral("org.kde.%1").arg(d->_componentName); } KAboutData::~KAboutData() { delete d; } KAboutData::KAboutData(const KAboutData &other): d(new Private) { *d = *other.d; QList::iterator it = d->_licenseList.begin(), itEnd = d->_licenseList.end(); for (; it != itEnd; ++it) { KAboutLicense &al = *it; al.d.detach(); al.d->_aboutData = this; } } KAboutData &KAboutData::operator=(const KAboutData &other) { if (this != &other) { *d = *other.d; QList::iterator it = d->_licenseList.begin(), itEnd = d->_licenseList.end(); for (; it != itEnd; ++it) { KAboutLicense &al = *it; al.d.detach(); al.d->_aboutData = this; } } return *this; } KAboutData KAboutData::fromPluginMetaData(const KPluginMetaData &plugin) { KAboutData ret(plugin.pluginId(), plugin.name(), plugin.version(), plugin.description(), KAboutLicense::byKeyword(plugin.license()).key(), plugin.copyrightText(), plugin.extraInformation(), plugin.website()); ret.d->programIconName = plugin.iconName(); ret.d->_authorList = plugin.authors(); ret.d->_translatorList = plugin.translators(); ret.d->_creditList = plugin.otherContributors(); return ret; } KAboutData &KAboutData::addAuthor(const QString &name, const QString &task, const QString &emailAddress, const QString &webAddress, const QString &ocsUsername) { d->_authorList.append(KAboutPerson(name, task, emailAddress, webAddress, ocsUsername)); return *this; } KAboutData &KAboutData::addCredit(const QString &name, const QString &task, const QString &emailAddress, const QString &webAddress, const QString &ocsUsername) { d->_creditList.append(KAboutPerson(name, task, emailAddress, webAddress, ocsUsername)); return *this; } KAboutData &KAboutData::setTranslator(const QString &name, const QString &emailAddress) { d->_translatorList = Private::parseTranslators(name, emailAddress); return *this; } KAboutData &KAboutData::setLicenseText(const QString &licenseText) { d->_licenseList[0] = KAboutLicense(this); d->_licenseList[0].setLicenseFromText(licenseText); return *this; } KAboutData &KAboutData::addLicenseText(const QString &licenseText) { // if the default license is unknown, overwrite instead of append KAboutLicense &firstLicense = d->_licenseList[0]; KAboutLicense newLicense(this); newLicense.setLicenseFromText(licenseText); if (d->_licenseList.count() == 1 && firstLicense.d->_licenseKey == KAboutLicense::Unknown) { firstLicense = newLicense; } else { d->_licenseList.append(newLicense); } return *this; } KAboutData &KAboutData::setLicenseTextFile(const QString &pathToFile) { d->_licenseList[0] = KAboutLicense(this); d->_licenseList[0].setLicenseFromPath(pathToFile); return *this; } KAboutData &KAboutData::addLicenseTextFile(const QString &pathToFile) { // if the default license is unknown, overwrite instead of append KAboutLicense &firstLicense = d->_licenseList[0]; KAboutLicense newLicense(this); newLicense.setLicenseFromPath(pathToFile); if (d->_licenseList.count() == 1 && firstLicense.d->_licenseKey == KAboutLicense::Unknown) { firstLicense = newLicense; } else { d->_licenseList.append(newLicense); } return *this; } KAboutData &KAboutData::setComponentName(const QString &componentName) { d->_componentName = componentName; return *this; } KAboutData &KAboutData::setDisplayName(const QString &_displayName) { d->_displayName = _displayName; d->_internalProgramName = _displayName.toUtf8(); return *this; } KAboutData &KAboutData::setOcsProvider(const QString &_ocsProviderUrl) { d->_ocsProviderUrl = _ocsProviderUrl; return *this; } KAboutData &KAboutData::setVersion(const QByteArray &_version) { d->_version = _version; return *this; } KAboutData &KAboutData::setShortDescription(const QString &_shortDescription) { d->_shortDescription = _shortDescription; return *this; } KAboutData &KAboutData::setLicense(KAboutLicense::LicenseKey licenseKey) { return setLicense(licenseKey, KAboutLicense::OnlyThisVersion); } KAboutData &KAboutData::setLicense(KAboutLicense::LicenseKey licenseKey, KAboutLicense::VersionRestriction versionRestriction) { d->_licenseList[0] = KAboutLicense(licenseKey, versionRestriction, this); return *this; } KAboutData &KAboutData::addLicense(KAboutLicense::LicenseKey licenseKey) { return addLicense(licenseKey, KAboutLicense::OnlyThisVersion); } KAboutData &KAboutData::addLicense(KAboutLicense::LicenseKey licenseKey, KAboutLicense::VersionRestriction versionRestriction) { // if the default license is unknown, overwrite instead of append KAboutLicense &firstLicense = d->_licenseList[0]; if (d->_licenseList.count() == 1 && firstLicense.d->_licenseKey == KAboutLicense::Unknown) { firstLicense = KAboutLicense(licenseKey, versionRestriction, this); } else { d->_licenseList.append(KAboutLicense(licenseKey, versionRestriction, this)); } return *this; } KAboutData &KAboutData::setCopyrightStatement(const QString &_copyrightStatement) { d->_copyrightStatement = _copyrightStatement; return *this; } KAboutData &KAboutData::setOtherText(const QString &_otherText) { d->_otherText = _otherText; return *this; } KAboutData &KAboutData::setHomepage(const QString &homepage) { d->_homepageAddress = homepage; return *this; } KAboutData &KAboutData::setBugAddress(const QByteArray &_bugAddress) { d->_bugAddress = _bugAddress; return *this; } KAboutData &KAboutData::setOrganizationDomain(const QByteArray &domain) { d->organizationDomain = QString::fromLatin1(domain.data()); return *this; } KAboutData &KAboutData::setProductName(const QByteArray &_productName) { d->productName = QString::fromUtf8(_productName.data()); return *this; } QString KAboutData::componentName() const { return d->_componentName; } QString KAboutData::productName() const { if (!d->productName.isEmpty()) { return d->productName; } return componentName(); } QString KAboutData::displayName() const { if (!d->_displayName.isEmpty()) { return d->_displayName; } return componentName(); } /// @internal /// Return the program name. It is always pre-allocated. /// Needed for KCrash in particular. const char *KAboutData::internalProgramName() const { return d->_internalProgramName.constData(); } QString KAboutData::programIconName() const { return d->programIconName.isEmpty() ? componentName() : d->programIconName; } KAboutData &KAboutData::setProgramIconName(const QString &iconName) { d->programIconName = iconName; return *this; } QVariant KAboutData::programLogo() const { return d->programLogo; } KAboutData &KAboutData::setProgramLogo(const QVariant &image) { d->programLogo = image; return *this; } QString KAboutData::ocsProviderUrl() const { return d->_ocsProviderUrl; } QString KAboutData::version() const { return QString::fromUtf8(d->_version.data()); } /// @internal /// Return the untranslated and uninterpreted (to UTF8) string /// for the version information. Used in particular for KCrash. const char *KAboutData::internalVersion() const { return d->_version.constData(); } QString KAboutData::shortDescription() const { return d->_shortDescription; } QString KAboutData::homepage() const { return d->_homepageAddress; } QString KAboutData::bugAddress() const { return QString::fromUtf8(d->_bugAddress.constData()); } QString KAboutData::organizationDomain() const { return d->organizationDomain; } /// @internal /// Return the untranslated and uninterpreted (to UTF8) string /// for the bug mail address. Used in particular for KCrash. const char *KAboutData::internalBugAddress() const { if (d->_bugAddress.isEmpty()) { return nullptr; } return d->_bugAddress.constData(); } QList KAboutData::authors() const { return d->_authorList; } QList KAboutData::credits() const { return d->_creditList; } QList KAboutData::Private::parseTranslators(const QString &translatorName, const QString &translatorEmail) { QList personList; if (translatorName.isEmpty() || translatorName == QStringLiteral("Your names")) { return personList; } const QStringList nameList(translatorName.split(QLatin1Char(','))); QStringList emailList; if (!translatorEmail.isEmpty() && translatorEmail != QStringLiteral("Your emails")) { emailList = translatorEmail.split(QLatin1Char(','), QString::KeepEmptyParts); } QStringList::const_iterator nit; QStringList::const_iterator eit = emailList.constBegin(); for (nit = nameList.constBegin(); nit != nameList.constEnd(); ++nit) { QString email; if (eit != emailList.constEnd()) { email = *eit; ++eit; } personList.append(KAboutPerson((*nit).trimmed(), email.trimmed(), true)); } return personList; } QList KAboutData::translators() const { return d->_translatorList; } QString KAboutData::aboutTranslationTeam() { return QCoreApplication::translate( "KAboutData", "

KDE is translated into many languages thanks to the work " "of the translation teams all over the world.

" "

For more information on KDE internationalization " "visit https://l10n.kde.org

", "replace this with information about your translation team" ); } QString KAboutData::otherText() const { return d->_otherText; } QList KAboutData::licenses() const { return d->_licenseList; } QString KAboutData::copyrightStatement() const { return d->_copyrightStatement; } QString KAboutData::customAuthorPlainText() const { return d->customAuthorPlainText; } QString KAboutData::customAuthorRichText() const { return d->customAuthorRichText; } bool KAboutData::customAuthorTextEnabled() const { return d->customAuthorTextEnabled; } KAboutData &KAboutData::setCustomAuthorText(const QString &plainText, const QString &richText) { d->customAuthorPlainText = plainText; d->customAuthorRichText = richText; d->customAuthorTextEnabled = true; return *this; } KAboutData &KAboutData::unsetCustomAuthorText() { d->customAuthorPlainText = QString(); d->customAuthorRichText = QString(); d->customAuthorTextEnabled = false; return *this; } KAboutData &KAboutData::setDesktopFileName(const QString &desktopFileName) { d->desktopFileName = desktopFileName; return *this; } QString KAboutData::desktopFileName() const { return d->desktopFileName; // KF6: switch to this code and adapt API dox #if 0 // if desktopFileName has been explicitely set, use that value if (!d->desktopFileName.isEmpty()) { return d->desktopFileName; } // return a string calculated on-the-fly from the current org domain & component name const QChar dotChar(QLatin1Char('.')); QStringList hostComponents = d->organizationDomain.split(dotChar); // desktop file name is reverse domain name std::reverse(hostComponents.begin(), hostComponents.end()); hostComponents.append(componentName()); return hostComponents.join(dotChar); #endif } class KAboutDataRegistry { public: KAboutDataRegistry() : m_appData(nullptr) {} ~KAboutDataRegistry() { delete m_appData; qDeleteAll(m_pluginData); } KAboutData *m_appData; QHash m_pluginData; }; Q_GLOBAL_STATIC(KAboutDataRegistry, s_registry) namespace { void warnIfOutOfSync(const char *aboutDataString, const QString &aboutDataValue, const char *appDataString, const QString &appDataValue) { if (aboutDataValue != appDataValue) { qCWarning(KABOUTDATA) << appDataString <m_appData; // not yet existing if (!aboutData) { // init from current Q*Application data aboutData = new KAboutData(QCoreApplication::applicationName(), QString(), QString()); // For applicationDisplayName & desktopFileName, which are only properties of QGuiApplication, // we have to try to get them via the property system, as the static getter methods are // part of QtGui only. Disadvantage: requires an app instance. // Either get all or none of the properties & warn about it if (app) { aboutData->setOrganizationDomain(QCoreApplication::organizationDomain().toUtf8()); aboutData->setVersion(QCoreApplication::applicationVersion().toUtf8()); aboutData->setDisplayName(app->property("applicationDisplayName").toString()); aboutData->setDesktopFileName(app->property("desktopFileName").toString()); } else { qCWarning(KABOUTDATA) << "Could not initialize the properties of KAboutData::applicationData by the equivalent properties from Q*Application: no app instance (yet) existing."; } s_registry->m_appData = aboutData; } else { // check if in-sync with Q*Application metadata, as their setters could have been called // after the last KAboutData::setApplicationData, with different values warnIfOutOfSync("KAboutData::applicationData().componentName", aboutData->componentName(), "QCoreApplication::applicationName", QCoreApplication::applicationName()); warnIfOutOfSync("KAboutData::applicationData().version", aboutData->version(), "QCoreApplication::applicationVersion", QCoreApplication::applicationVersion()); warnIfOutOfSync("KAboutData::applicationData().organizationDomain", aboutData->organizationDomain(), "QCoreApplication::organizationDomain", QCoreApplication::organizationDomain()); if (app) { warnIfOutOfSync("KAboutData::applicationData().displayName", aboutData->displayName(), "QGuiApplication::applicationDisplayName", app->property("applicationDisplayName").toString()); warnIfOutOfSync("KAboutData::applicationData().desktopFileName", aboutData->desktopFileName(), "QGuiApplication::desktopFileName", app->property("desktopFileName").toString()); } } return *aboutData; } void KAboutData::setApplicationData(const KAboutData &aboutData) { if (s_registry->m_appData) { *s_registry->m_appData = aboutData; } else { s_registry->m_appData = new KAboutData(aboutData); } // For applicationDisplayName & desktopFileName, which are only properties of QGuiApplication, // we have to try to set them via the property system, as the static getter methods are // part of QtGui only. Disadvantage: requires an app instance. // So set either all or none of the properties & warn about it QCoreApplication *app = QCoreApplication::instance(); if (app) { app->setApplicationVersion(aboutData.version()); app->setApplicationName(aboutData.componentName()); app->setOrganizationDomain(aboutData.organizationDomain()); app->setProperty("applicationDisplayName", aboutData.displayName()); app->setProperty("desktopFileName", aboutData.desktopFileName()); } else { qCWarning(KABOUTDATA) << "Could not initialize the equivalent properties of Q*Application: no instance (yet) existing."; } // KF6: Rethink the current relation between KAboutData::applicationData and the Q*Application metadata // Always overwriting the Q*Application metadata here, but not updating back the KAboutData // in applicationData() is unbalanced and can result in out-of-sync data if the Q*Application // setters have been called meanwhile // Options are to remove the overlapping properties of KAboutData for cleancode, or making the // overlapping properties official shadow properties of their Q*Application countparts, though // that increases behavioural complexity a little. } void KAboutData::registerPluginData(const KAboutData &aboutData) { s_registry->m_pluginData.insert(aboutData.componentName(), new KAboutData(aboutData)); } KAboutData *KAboutData::pluginData(const QString &componentName) { KAboutData *ad = s_registry->m_pluginData.value(componentName); return ad; } // only for KCrash (no memory allocation allowed) const KAboutData *KAboutData::applicationDataPointer() { if (s_registry.exists()) { return s_registry->m_appData; } return nullptr; } bool KAboutData::setupCommandLine(QCommandLineParser *parser) { if (!d->_shortDescription.isEmpty()) { parser->setApplicationDescription(d->_shortDescription); } parser->addHelpOption(); QCoreApplication *app = QCoreApplication::instance(); if (app && !app->applicationVersion().isEmpty()) { parser->addVersionOption(); } return parser->addOption(QCommandLineOption(QStringLiteral("author"), QCoreApplication::translate("KAboutData CLI", "Show author information."))) && parser->addOption(QCommandLineOption(QStringLiteral("license"), QCoreApplication::translate("KAboutData CLI", "Show license information."))) && parser->addOption(QCommandLineOption(QStringLiteral("desktopfile"), QCoreApplication::translate("KAboutData CLI", "The base file name of the desktop entry for this application."), QCoreApplication::translate("KAboutData CLI", "file name"))); } void KAboutData::processCommandLine(QCommandLineParser *parser) { bool foundArgument = false; if (parser->isSet(QStringLiteral("author"))) { foundArgument = true; if (d->_authorList.isEmpty()) { printf("%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "This application was written by somebody who wants to remain anonymous."))); } else { printf("%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "%1 was written by:").arg(qAppName()))); Q_FOREACH (const KAboutPerson &person, d->_authorList) { QString authorData = QStringLiteral(" ") + person.name(); if (!person.emailAddress().isEmpty()) { authorData.append(QStringLiteral(" <") + person.emailAddress() + QStringLiteral(">")); } printf("%s\n", qPrintable(authorData)); } } if (!customAuthorTextEnabled()) { if (bugAddress() == QLatin1String("submit@bugs.kde.org") ) { printf("%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "Please use https://bugs.kde.org to report bugs."))); } else if (!bugAddress().isEmpty()) { printf("%s\n", qPrintable(QCoreApplication::translate("KAboutData CLI", "Please report bugs to %1.").arg(bugAddress()))); } } else { printf("%s\n", qPrintable(customAuthorPlainText())); } } else if (parser->isSet(QStringLiteral("license"))) { foundArgument = true; Q_FOREACH (const KAboutLicense &license, d->_licenseList) { printf("%s\n", qPrintable(license.text())); } } const QString desktopFileName = parser->value(QStringLiteral("desktopfile")); if (!desktopFileName.isEmpty()) { d->desktopFileName = desktopFileName; } if (foundArgument) { ::exit(EXIT_SUCCESS); } } diff --git a/src/lib/kaboutdata.h b/src/lib/kaboutdata.h index 0dc796e..b3dd135 100644 --- a/src/lib/kaboutdata.h +++ b/src/lib/kaboutdata.h @@ -1,1127 +1,1127 @@ /* * This file is part of the KDE Libraries * Copyright (C) 2000 Espen Sand (espen@kde.org) * Copyright (C) 2008 Friedrich W. H. Kossebau * Copyright (C) 2010 Teo Mrnjavac * Copyright (C) 2013 David Faure * Copyright (C) 2017 Harald Sitter * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #ifndef KABOUTDATA_H #define KABOUTDATA_H #include -#include -#include +#include +#include template class QList; class QCommandLineParser; class QJsonObject; class QVariant; class KAboutData; class KPluginMetaData; namespace KCrash { Q_DECL_IMPORT void defaultCrashHandler(int sig); } /** * This class is used to store information about a person or developer. * It can store the person's name, a task, an email address and a * link to a home page. This class is intended for use in the * KAboutData class, but it can be used elsewhere as well. * Normally you should at least define the person's name. * Creating a KAboutPerson object by yourself is relatively useless, * but the KAboutData methods KAboutData::authors() and KAboutData::credits() * return lists of KAboutPerson data objects which you can examine. * * Example usage within a main(), retrieving the list of people involved * with a program and re-using data from one of them: * * @code * KAboutData about("khello", i18n("KHello"), "0.1", * i18n("A KDE version of Hello, world!"), * KAboutLicense::LGPL, * i18n("Copyright (C) 2014 Developer")); * * about.addAuthor(i18n("Joe Developer"), i18n("developer"), "joe@host.com", 0); * QList people = about.authors(); * about.addCredit(people[0].name(), people[0].task()); * @endcode */ class KCOREADDONS_EXPORT KAboutPerson { friend class KAboutData; public: /** * Convenience constructor * * @param name The name of the person. * * @param task The task of this person. * * @param emailAddress The email address of the person. * * @param webAddress Home page of the person. * * @param ocsUsername Open Collaboration Services username of the person. */ explicit KAboutPerson(const QString &name, const QString &task = QString(), const QString &emailAddress = QString(), const QString &webAddress = QString(), const QString &ocsUsername = QString()); /** * Copy constructor. Performs a deep copy. * @param other object to copy */ KAboutPerson(const KAboutPerson &other); ~KAboutPerson(); /** * Assignment operator. Performs a deep copy. * @param other object to copy */ KAboutPerson &operator=(const KAboutPerson &other); /** * The person's name * @return the person's name (can be QString(), if it has been * constructed with an empty name) */ QString name() const; /** * The person's task * @return the person's task (can be QString(), if it has been * constructed with an empty task) */ QString task() const; /** * The person's email address * @return the person's email address (can be QString(), if it has been * constructed with an empty email) */ QString emailAddress() const; /** * The home page or a relevant link * @return the persons home page (can be QString(), if it has been * constructed with an empty home page) */ QString webAddress() const; /** * The person's Open Collaboration Services username * @return the persons OCS username (can be QString(), if it has been * constructed with an empty username) */ QString ocsUsername() const; /** * Creates a @c KAboutPerson from a JSON object with the following structure: * * Key | Accessor * -----------| ---------------------------- * Name | name() * Email | emailAddress() * Task | task() * Website | webAddress() * UserName | ocsUsername() * * The @c Name and @c Task key are translatable (by using e.g. a "Task[de_DE]" key) * * @since 5.18 */ static KAboutPerson fromJSON(const QJsonObject &obj); private: /** * @internal Used by KAboutData to construct translator data. */ explicit KAboutPerson(const QString &name, const QString &email, bool disambiguation); class Private; Private *const d; }; /** * This class is used to store information about a license. * The license can be one of some predefined, one given as text or one * that can be loaded from a file. This class is used in the KAboutData class. * Explicitly creating a KAboutLicense object is not possible. * If the license is wanted for a KDE component having KAboutData object, * use KAboutData::licenses() to get the licenses for that component. * If the license is for a non-code resource and given by a keyword * (e.g. in .desktop files), try using KAboutLicense::byKeyword(). */ class KCOREADDONS_EXPORT KAboutLicense { friend class KAboutData; public: /** * Describes the license of the software. */ enum LicenseKey { Custom = -2, File = -1, Unknown = 0, GPL = 1, GPL_V2 = 1, LGPL = 2, LGPL_V2 = 2, BSDL = 3, Artistic = 4, QPL = 5, QPL_V1_0 = 5, GPL_V3 = 6, LGPL_V3 = 7, LGPL_V2_1 = 8 ///< @since 5.25 }; /** * Format of the license name. */ enum NameFormat { ShortName, FullName }; /** * Whether later versions of the license are allowed. */ enum VersionRestriction { OnlyThisVersion, OrLaterVersions }; /** * Copy constructor. Performs a deep copy. * @param other object to copy */ KAboutLicense(const KAboutLicense &other); ~KAboutLicense(); /** * Assignment operator. Performs a deep copy. * @param other object to copy */ KAboutLicense &operator=(const KAboutLicense &other); /** * Returns the full license text. If the licenseType argument of the * constructor has been used, any text defined by setLicenseText is ignored, * and the standard text for the chosen license will be returned. * * @return The license text. */ QString text() const; /** * Returns the license name. * * @return The license name as a string. */ QString name(KAboutLicense::NameFormat formatName) const; /** * Returns the license key. * * @return The license key as element of KAboutLicense::LicenseKey enum. */ KAboutLicense::LicenseKey key() const; /** * Returns the SPDX license expression of this license. * If the underlying license cannot be expressed as a SPDX expression a null string is returned. * * @note SPDX expression are expansive constructs. If you parse the return value, do it in a * SPDX specification compliant manner by splitting on whitespaces to discard unwanted * informationor or by using a complete SPDX license expression parser. * @note SPDX identifiers are case-insensitive. Do not use case-senstivie checks on the return * value. * @see https://spdx.org/licenses * @return SPDX license expression or QString() if the license has no identifier. Compliant * with SPDX 2.1. * * @since 5.37 */ QString spdx() const; /** * Fetch a known license by a keyword/spdx ID * * Frequently the license data is provided by a terse keyword-like string, * e.g. by a field in a .desktop file. Using this method, an application * can get hold of a proper KAboutLicense object, providing that the * license is one of the several known to KDE, and use it to present * more human-readable information to the user. * * Keywords are matched by stripping all whitespace and lowercasing. * The known keywords correspond to the KAboutLicense::LicenseKey enumeration, * e.g. any of "LGPLV3", "LGPLv3", "LGPL v3" would match KAboutLicense::LGPL_V3. * If there is no match for the keyword, a valid license object is still * returned, with its name and text informing about a custom license, * and its key equal to KAboutLicense::Custom. * * @param keyword The license keyword. * @return The license object. * * @see KAboutLicense::LicenseKey */ static KAboutLicense byKeyword(const QString &keyword); private: /** * @internal Used by KAboutData to construct a predefined license. */ explicit KAboutLicense(enum KAboutLicense::LicenseKey licenseType, enum KAboutLicense::VersionRestriction versionRestriction, const KAboutData *aboutData); /** * @internal Used by KAboutData to construct a predefined license. */ explicit KAboutLicense(enum KAboutLicense::LicenseKey licenseType, const KAboutData *aboutData); /** * @internal Used by KAboutData to construct a KAboutLicense */ explicit KAboutLicense(const KAboutData *aboutData); /** * @internal Used by KAboutData to construct license by given text */ void setLicenseFromPath(const QString &pathToFile); /** * @internal Used by KAboutData to construct license by given text */ void setLicenseFromText(const QString &licenseText); class Private; QSharedDataPointer d; }; /** * @class KAboutData kaboutdata.h KAboutData * * This class is used to store information about a program or plugin. * It can store such values as version number, program name, home page, address * for bug reporting, multiple authors and contributors * (using KAboutPerson), license and copyright information. * * Currently, the values set here are shown by the "About" box * (see KAboutDialog), used by the bug report dialog (see KBugReport), * and by the help shown on command line (see KAboutData::setupCommandLine()). * * Porting Notes: Since KDE Frameworks 5.0, the translation catalog mechanism * must provided by your translation framework to load the correct catalog * instead (eg: KLocalizedString::setApplicationDomain() for KI18n, or * QCoreApplication::installTranslator() for Qt's translation system). This * applies to the old setCatalogName() and catalogName() members. But see also * K4AboutData in kde4support as a compatibility class. * * Example: * Setting the metadata of an application using KAboutData in code also relying * on the KDE Framework modules KI18n and KDBusAddons: * @code * // create QApplication instance * QApplication app(argc, argv); * // setup translation string domain for the i18n calls * KLocalizedString::setApplicationDomain("foo"); * // create a KAboutData object to use for setting the application metadata * KAboutData aboutData("foo", i18n("Foo"), "0.1", * i18n("To Foo or not To Foo"), * KAboutLicense::LGPL, * i18n("Copyright 2017 Bar Foundation"), QString(), * "https://www.foo-the-app.net"); * // overwrite default-generated values of organizationDomain & desktopFileName * aboutData.setOrganizationDomain("barfoundation.org"); * aboutData.setDesktopFileName("org.barfoundation.foo"); * * // set the application metadata * KAboutData::setApplicationData(aboutData); * // in GUI apps set the window icon manually, not covered by KAboutData * // needed for environments where the icon name is not extracted from * // the information in the application's desktop file * QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("foo"))); * * // integrate with commandline argument handling * QCommandLineParser parser; * aboutData.setupCommandLine(&parser); * // setup of app specific commandline args * [...] * parser.process(app); * aboutData.processCommandLine(&parser); * * // with the application metadata set, register to the D-Bus session * KDBusService programDBusService(KDBusService::Multiple | KDBusService::NoExitOnFailure); * @endcode * * @short Holds information needed by the "About" box and other * classes. * @author Espen Sand (espen@kde.org), David Faure (faure@kde.org) * */ class KCOREADDONS_EXPORT KAboutData { public: /** * Returns the KAboutData for the application. * * This contains information such as authors, license, etc., * provided that setApplicationData has been called before. * If not called before, the returned KAboutData will be initialized from the * equivalent properties of QCoreApplication (and its subclasses), * if an instance of that already exists. * For the list of such properties see setApplicationData * (before 5.22: limited to QCoreApplication::applicationName). * @see setApplicationData */ static KAboutData applicationData(); /** * Sets the application data for this application. * * In addition to changing the result of applicationData(), this initializes * the equivalent properties of QCoreApplication (and its subclasses) with * information from @p aboutData, if an instance of that already exists. * Those properties are:
  • QCoreApplication::applicationName
  • QCoreApplication::applicationVersion
  • QCoreApplication::organizationDomain
  • QGuiApplication::applicationDisplayName
  • QGuiApplication::desktopFileName (since 5.16)
* @see applicationData */ static void setApplicationData(const KAboutData &aboutData); /** * Register the KAboutData information for a plugin. * Call this from the constructor of the plugin. * This will register the plugin's @p aboutData under the component name * that was set in @p aboutData. */ static void registerPluginData(const KAboutData &aboutData); /** * Return the KAboutData for the given plugin identified by @p componentName. */ static KAboutData *pluginData(const QString &componentName); /** * Creates a @c KAboutData from the given @p plugin metadata * * @since 5.18 */ static KAboutData fromPluginMetaData(const KPluginMetaData &plugin); public: /** * Constructor. * * Porting Note: The @p catalogName parameter present in KDE4 was * deprecated and removed. See also K4AboutData * in kde4support if this feature is needed for compatibility purposes, or * consider using componentName() instead. * * @param componentName The program name or plugin name used internally. * Example: QStringLiteral("kwrite"). This should never be translated. * * @param displayName A displayable name for the program or plugin. This string * should be translated. Example: i18n("KWrite") * * @param version The component version string. Example: QStringLiteral("1.0"). * * @param shortDescription A short description of what the component does. * This string should be translated. * Example: i18n("A simple text editor.") * * @param licenseType The license identifier. Use setLicenseText or setLicenseTextFile if you use a license not predefined here. * * @param copyrightStatement A copyright statement, that can look like this: * i18n("Copyright (C) 1999-2000 Name"). The string specified here is * taken verbatim; the author information from addAuthor is not used. * * @param otherText Some free form text, that can contain any kind of * information. The text can contain newlines. This string * should be translated. * * @param homePageAddress The URL to the component's homepage, including * URL scheme. "http://some.domain" is correct, "some.domain" is * not. Since KDE Frameworks 5.17, https and other valid URL schemes * are also valid. See also the note below. * * @param bugAddress The bug report address string, an email address or a URL. * This defaults to the kde.org bug system. * * @note The @p homePageAddress argument is used to derive a default organization * domain for the application (which is used to register on the session D-Bus, * locate the appropriate desktop file, etc.), by taking the host name and dropping * the first component, unless there are less than three (e.g. "www.kde.org" -> "kde.org"). * Use both setOrganizationDomain(const QByteArray&) and setDesktopFileName() if their default values * do not have proper values. * * @see setOrganizationDomain(const QByteArray&), setDesktopFileName(const QString&) */ // KF6: remove constructor that includes catalogName, and put default // values back in for shortDescription and licenseType KAboutData(const QString &componentName, const QString &displayName, const QString &version, const QString &shortDescription, enum KAboutLicense::LicenseKey licenseType, const QString ©rightStatement = QString(), const QString &otherText = QString(), const QString &homePageAddress = QString(), const QString &bugAddress = QStringLiteral("submit@bugs.kde.org") ); /** * Constructor. * * @param componentName The program name or plugin name used internally. * Example: "kwrite". * * @param displayName A displayable name for the program or plugin. This string * should be translated. Example: i18n("KWrite") * * @param version The component version string. * * Sets the property desktopFileName to "org.kde."+componentName and * the property organizationDomain to "kde.org". * * @see setOrganizationDomain(const QByteArray&), setDesktopFileName(const QString&) */ KAboutData(const QString &componentName, const QString &displayName, const QString &version ); /** * Copy constructor. Performs a deep copy. * @param other object to copy */ KAboutData(const KAboutData &other); /** * Assignment operator. Performs a deep copy. * @param other object to copy */ KAboutData &operator=(const KAboutData &other); ~KAboutData(); /** * Defines an author. * * You can call this function as many times as you need. Each entry is * appended to a list. The person in the first entry is assumed to be * the leader of the project. * * @param name The developer's name. It should be translated. * * @param task What the person is responsible for. This text can contain * newlines. It should be translated. * Can be left empty. * * @param emailAddress An Email address where the person can be reached. * Can be left empty. * * @param webAddress The person's homepage or a relevant link. * Start the address with "http://". "http://some.domain" is * correct, "some.domain" is not. Can be left empty. * * @param ocsUsername The person's Open Collaboration Services username. * The provider can be optionally specified with @see setOcsProvider. * */ KAboutData &addAuthor(const QString &name, const QString &task = QString(), const QString &emailAddress = QString(), const QString &webAddress = QString(), const QString &ocsUsername = QString()); /** * Defines a person that deserves credit. * * You can call this function as many times as you need. Each entry * is appended to a list. * * @param name The person's name. It should be translated. * * @param task What the person has done to deserve the honor. The * text can contain newlines. It should be translated. * Can be left empty. * * @param emailAddress An email address when the person can be reached. * Can be left empty. * * @param webAddress The person's homepage or a relevant link. * Start the address with "http://". "http://some.domain" is * is correct, "some.domain" is not. Can be left empty. * * @param ocsUsername The person's Open Collaboration Services username. * The provider can be optionally specified with @see setOcsProvider. * */ KAboutData &addCredit(const QString &name, const QString &task = QString(), const QString &emailAddress = QString(), const QString &webAddress = QString(), const QString &ocsUsername = QString()); /** * @brief Sets the name(s) of the translator(s) of the GUI. * * The canonical use with the ki18n framework is: * * \code * setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), * i18nc("EMAIL OF TRANSLATORS", "Your emails")); * \endcode * * If you are using a KMainWindow this is done for you automatically. * * The name and emailAddress are treated as lists separated with ",". * * If the strings are empty or "Your names"/"Your emails" * respectively they will be ignored. * * @param name the name(s) of the translator(s) * @param emailAddress the email address(es) of the translator(s) * @see KAboutTranslator */ KAboutData &setTranslator(const QString &name, const QString &emailAddress); /** * Defines a license text, which is translated. * * Example: * \code * setLicenseText( i18n("This is my license") ); * \endcode * * @param license The license text. */ KAboutData &setLicenseText(const QString &license); /** * Adds a license text, which is translated. * * If there is only one unknown license set, e.g. by using the default * parameter in the constructor, that one is replaced. * * Example: * \code * addLicenseText( i18n("This is my license") ); * \endcode * * @param license The license text. * @see setLicenseText, addLicense, addLicenseTextFile */ KAboutData &addLicenseText(const QString &license); /** * Defines a license text by pointing to a file where it resides. * The file format has to be plain text in an encoding compatible to the locale. * * @param file Path to the file in the local filesystem containing the license text. */ KAboutData &setLicenseTextFile(const QString &file); /** * Adds a license text by pointing to a file where it resides. * The file format has to be plain text in an encoding compatible to the locale. * * If there is only one unknown license set, e.g. by using the default * parameter in the constructor, that one is replaced. * * @param file Path to the file in the local filesystem containing the license text. * @see addLicenseText, addLicense, setLicenseTextFile */ KAboutData &addLicenseTextFile(const QString &file); /** * Defines the component name used internally. * * @param componentName The application or plugin name. Example: "kate". */ KAboutData &setComponentName(const QString &componentName); /** * Defines the displayable component name string. * * @param displayName The display name. This string should be * translated. * Example: i18n("Advanced Text Editor"). */ KAboutData &setDisplayName(const QString &displayName); /** * Obsolete method * * This method used to set the icon name but this is no longer * possible in KDE Frameworks 5 because KCoreAddons does not * depend on QtGui. * * @param iconName name of the icon. Example: "accessories-text-editor" * @see programIconName() * * @deprecated since 5.2, use QApplication::setWindowIcon(QIcon::fromTheme()) instead. */ KCOREADDONS_DEPRECATED KAboutData &setProgramIconName(const QString &iconName); // KF6 remove this /** * Defines the program logo. * * Use this if you need to have an application logo * in AboutData other than the application icon. * * Because KAboutData is a core class it cannot use QImage directly, * so this is a QVariant that should contain a QImage. * * @param image logo image. * @see programLogo() */ KAboutData &setProgramLogo(const QVariant &image); /** * Specifies an Open Collaboration Services provider by URL. * A provider file must be available for the chosen provider. * * Use this if you need to override the default provider. * * If this method is not used, all the KAboutPerson OCS usernames * will be used with the openDesktop.org entry from the default * provider file. * * @param providerUrl The provider URL as defined in the provider file. */ KAboutData &setOcsProvider(const QString &providerUrl); /** * Defines the program version string. * * @param version The program version. */ KAboutData &setVersion(const QByteArray &version); /** * Defines a short description of what the program does. * * @param shortDescription The program description. This string should * be translated. Example: i18n("An advanced text * editor with syntax highlighting support."). */ KAboutData &setShortDescription(const QString &shortDescription); /** * Defines the license identifier. * * @param licenseKey The license identifier. * @see addLicenseText, setLicenseText, setLicenseTextFile */ KAboutData &setLicense(KAboutLicense::LicenseKey licenseKey); /** * Defines the license identifier. * * @param licenseKey The license identifier. * @param versionRestriction Whether later versions of the license are also allowed. * e.g. licensed under "GPL 2.0 or at your option later versions" would be OrLaterVersions. * @see addLicenseText, setLicenseText, setLicenseTextFile * * @since 5.37 */ KAboutData &setLicense(KAboutLicense::LicenseKey licenseKey, KAboutLicense::VersionRestriction versionRestriction); /** * Adds a license identifier. * * If there is only one unknown license set, e.g. by using the default * parameter in the constructor, that one is replaced. * * @param licenseKey The license identifier. * @see setLicenseText, addLicenseText, addLicenseTextFile */ KAboutData &addLicense(KAboutLicense::LicenseKey licenseKey); /** * Adds a license identifier. * * If there is only one unknown license set, e.g. by using the default * parameter in the constructor, that one is replaced. * * @param licenseKey The license identifier. * @param versionRestriction Whether later versions of the license are also allowed. * e.g. licensed under "GPL 2.0 or at your option later versions" would be OrLaterVersions. * @see setLicenseText, addLicenseText, addLicenseTextFile * * @since 5.37 */ KAboutData &addLicense(KAboutLicense::LicenseKey licenseKey, KAboutLicense::VersionRestriction versionRestriction); /** * Defines the copyright statement to show when displaying the license. * * @param copyrightStatement A copyright statement, that can look like * this: i18n("Copyright (C) 1999-2000 Name"). The string specified here is * taken verbatim; the author information from addAuthor is not used. */ KAboutData &setCopyrightStatement(const QString ©rightStatement); /** * Defines the additional text to show in the about dialog. * * @param otherText Some free form text, that can contain any kind of * information. The text can contain newlines. This string * should be translated. */ KAboutData &setOtherText(const QString &otherText); /** * Defines the program homepage. * * @param homepage The program homepage string. * Start the address with "http://". "http://kate.kde.org" * is correct but "kate.kde.org" is not. */ KAboutData &setHomepage(const QString &homepage); /** * Defines the address where bug reports should be sent. * * @param bugAddress The bug report email address or URL. * This defaults to the kde.org bug system. */ KAboutData &setBugAddress(const QByteArray &bugAddress); /** * Defines the domain of the organization that wrote this application. * The domain is set to kde.org by default, or the domain of the homePageAddress constructor argument, * if set. * * Make sure to call setOrganizationDomain(const QByteArray&) if your product * is not developed inside the KDE community. * * Used e.g. for the registration to D-Bus done by KDBusService * from the KDE Frameworks KDBusAddons module. * * Calling this method has no effect on the value of the desktopFileName property. * * @note: If your program should work as a D-Bus activatable service, the base name * of the D-Bus service description file or of the desktop file you install must match * the D-Bus "well-known name" for which the program will register. * For example, KDBusService will use a name created from the reversed organization domain * with the component name attached, so for an organization domain "bar.org" and a * component name "foo" the name of an installed D-Bus service file needs to be * "org.bar.foo.service" or the the name of the installed desktop file "org.bar.foo.desktop" * (and the desktopFileName property accordingly set to "org.bar.foo"). * For still supporting the deprecated start of services via KToolInvocation, * the desktop file needs to have an entry with the key "X-DBUS-ServiceName" * and a value which matches the used D-Bus "well-known name" as just described, * so with the above used values it needs a line "X-DBUS-ServiceName=org.bar.foo" * * @param domain the domain name, for instance kde.org, koffice.org, etc. * * @see setDesktopFileName(const QString&) */ KAboutData &setOrganizationDomain(const QByteArray &domain); /** * Defines the product name which will be used in the KBugReport dialog. * By default it's the componentName, but you can overwrite it here to provide * support for special components e.g. in the form 'product/component', * such as 'kontact/summary'. * * @param name The name of product */ KAboutData &setProductName(const QByteArray &name); /** * Returns the application's internal name. * @return the internal program name. */ QString componentName() const; /** * Returns the application's product name, which will be used in KBugReport * dialog. By default it returns componentName(), otherwise the one which is set * with setProductName() * * @return the product name. */ QString productName() const; /** * Returns the translated program name. * @return the program name (translated). */ QString displayName() const; /** * Returns the domain name of the organization that wrote this application. * * @see setOrganizationDomain(const QByteArray&) */ QString organizationDomain() const; /** * @internal * Provided for use by KCrash */ const char *internalProgramName() const; /** * Returns the program's icon name. * * The default value is componentName(). * @return the program's icon name. * * This is mostly for compatibility, given that setProgramIconName is deprecated. */ QString programIconName() const; /** * Returns the program logo image. * * Because KAboutData is a core class it cannot use QImage directly, * so this is a QVariant containing a QImage. * * @return the program logo data, or a null image if there is * no custom application logo defined. */ QVariant programLogo() const; /** * Returns the chosen Open Collaboration Services provider URL. * @return the provider URL. */ QString ocsProviderUrl() const; /** * Returns the program's version. * @return the version string. */ QString version() const; /** * @internal * Provided for use by KCrash */ const char *internalVersion() const; /** * Returns a short, translated description. * @return the short description (translated). Can be * QString() if not set. */ QString shortDescription() const; /** * Returns the application homepage. * @return the application homepage URL. Can be QString() if * not set. */ QString homepage() const; /** * Returns the email address or URL for bugs. * @return the address where to report bugs. */ QString bugAddress() const; /** * @internal * Provided for use by KCrash */ const char *internalBugAddress() const; /** * Returns a list of authors. * @return author information (list of persons). */ QList authors() const; /** * Returns a list of persons who contributed. * @return credit information (list of persons). */ QList credits() const; /** * Returns a list of translators. * @return translators information (list of persons) */ QList translators() const; /** * Returns a message about the translation team. * @return a message about the translation team */ static QString aboutTranslationTeam(); /** * Returns a translated, free form text. * @return the free form text (translated). Can be QString() if not set. */ QString otherText() const; /** * Returns a list of licenses. * * @return licenses information (list of licenses) */ QList licenses() const; /** * Returns the copyright statement. * @return the copyright statement. Can be QString() if not set. */ QString copyrightStatement() const; /** * Returns the plain text displayed around the list of authors instead * of the default message telling users to send bug reports to bugAddress(). * * @return the plain text displayed around the list of authors instead * of the default message. Can be QString(). */ QString customAuthorPlainText() const; /** * Returns the rich text displayed around the list of authors instead * of the default message telling users to send bug reports to bugAddress(). * * @return the rich text displayed around the list of authors instead * of the default message. Can be QString(). */ QString customAuthorRichText() const; /** * Returns whether custom text should be displayed around the list of * authors. * * @return whether custom text should be displayed around the list of * authors. */ bool customAuthorTextEnabled() const; /** * Sets the custom text displayed around the list of authors instead * of the default message telling users to send bug reports to bugAddress(). * * @param plainText The plain text. * @param richText The rich text. * * Setting both to parameters to QString() will cause no message to be * displayed at all. Call unsetCustomAuthorText() to revert to the default * message. */ KAboutData &setCustomAuthorText(const QString &plainText, const QString &richText); /** * Clears any custom text displayed around the list of authors and falls * back to the default message telling users to send bug reports to * bugAddress(). */ KAboutData &unsetCustomAuthorText(); /** * Configures the @p parser command line parser to provide an authors entry with * information about the developers of the application and an entry specifying the license. * * Additionally, it will set the description to the command line parser, will add the help * option and if the QApplication has a version set (e.g. via KAboutData::setApplicationData) * it will also add the version option. * * Since 5.16 it also adds an option to set the desktop file name. * * @returns true if adding the options was successful; otherwise returns false. * * @sa processCommandLine() */ bool setupCommandLine(QCommandLineParser *parser); /** * Reads the processed @p parser and sees if any of the arguments are the ones set * up from setupCommandLine(). * * @sa setupCommandLine() */ void processCommandLine(QCommandLineParser *parser); /** * Sets the base name of the desktop entry for this application. * * This is the file name, without the full path and without extension, * of the desktop entry that represents this application according to * the freedesktop desktop entry specification (e.g. "org.kde.foo"). * * A default desktop file name is constructed when the KAboutData * object is created, using the reverse domain name of the * organizationDomain() and the componentName() as they are at the time * of the KAboutData object creation. * Call this method to override that default name. Typically this is * done when also setOrganizationDomain(const QByteArray&) or setComponentName(const QString&) * need to be called to override the initial values. * * The desktop file name can also be passed to the application at runtime through * the @c desktopfile command line option which is added by setupCommandLine(QCommandLineParser*). * This is useful if an application supports multiple desktop files with different runtime * settings. * * @param desktopFileName The desktop file name of this application * * @sa desktopFileName() * @sa organizationDomain() * @sa componentName() * @sa setupCommandLine(QCommandLineParser*) * @since 5.16 **/ KAboutData &setDesktopFileName(const QString &desktopFileName); /** * @returns The desktop file name of this application (e.g. "org.kde.foo") * @sa setDesktopFileName(const QString&) * @since 5.16 **/ QString desktopFileName() const; private: friend void KCrash::defaultCrashHandler(int sig); static const KAboutData *applicationDataPointer(); class Private; Private *const d; }; #endif diff --git a/src/lib/plugin/kexportplugin.h b/src/lib/plugin/kexportplugin.h index 937dae9..4306be9 100644 --- a/src/lib/plugin/kexportplugin.h +++ b/src/lib/plugin/kexportplugin.h @@ -1,57 +1,57 @@ /* This file is part of the KDE project Copyright (C) 2007 Bernhard Loos This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KEXPORTPLUGIN_H #define KEXPORTPLUGIN_H -#include -#include +#include +#include #include /** * \relates KPluginLoader * Use this macro if you want to give your plugin a version number. * You can later access the version number with KPluginLoader::pluginVersion() */ #define K_EXPORT_PLUGIN_VERSION(version) \ Q_EXTERN_C Q_DECL_EXPORT const quint32 kde_plugin_version = version; /** * \relates KPluginLoader * This macro exports the main object of the plugin. Most times, this will be a KPluginFactory * or derived class, but any QObject derived class can be used. * Take a look at the documentation of Q_EXPORT_PLUGIN2 for some details. */ #if defined (Q_OS_WIN32) && defined(Q_CC_BOR) #define Q_STANDARD_CALL __stdcall #else #define Q_STANDARD_CALL #ifndef KCOREADDONS_NO_DEPRECATED class KCOREADDONS_DEPRECATED_EXPORT K_EXPORT_PLUGIN_is_deprecated_see_KDE5PORTING { }; #define K_EXPORT_PLUGIN(factory) \ K_EXPORT_PLUGIN_is_deprecated_see_KDE5PORTING dummy; #endif #endif #endif // KEXPORTPLUGIN_H diff --git a/src/lib/plugin/kpluginfactory.h b/src/lib/plugin/kpluginfactory.h index 44c0e38..cc696bc 100644 --- a/src/lib/plugin/kpluginfactory.h +++ b/src/lib/plugin/kpluginfactory.h @@ -1,586 +1,586 @@ /* This file is part of the KDE project Copyright (C) 2007 Matthias Kretz Copyright (C) 2007 Bernhard Loos This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KPLUGINFACTORY_H #define KPLUGINFACTORY_H #include "kcoreaddons_export.h" -#include -#include -#include +#include +#include +#include #include // for source compat class QWidget; class KPluginFactoryPrivate; namespace KParts { class Part; } #define KPluginFactory_iid "org.kde.KPluginFactory" #define K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_SKEL(name, baseFactory, ...) \ class name : public KPluginFactory \ { \ Q_OBJECT \ Q_INTERFACES(KPluginFactory) \ __VA_ARGS__ \ public: \ explicit name(); \ ~name(); \ }; #define K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_JSON(name, baseFactory, json) \ K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_SKEL(name, baseFactory, Q_PLUGIN_METADATA(IID KPluginFactory_iid FILE json)) #define K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY(name, baseFactory) \ K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_SKEL(name, baseFactory, Q_PLUGIN_METADATA(IID KPluginFactory_iid)) #define K_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) \ name::name() \ { \ pluginRegistrations \ } \ name::~name() {} #define K_PLUGIN_FACTORY_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) \ K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY(name, baseFactory) \ K_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) #define K_PLUGIN_FACTORY_WITH_BASEFACTORY_JSON(name, baseFactory, jsonFile, pluginRegistrations) \ K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY_JSON(name, baseFactory, jsonFile) \ K_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, baseFactory, pluginRegistrations) /** * \relates KPluginFactory * * Create a KPluginFactory subclass and export it as the root plugin object. * * \param name The name of the KPluginFactory derived class. * * \param pluginRegistrations Code to be inserted into the constructor of the * class. Usually a series of registerPlugin() calls. * * @note K_PLUGIN_FACTORY declares the subclass including a Q_OBJECT macro. * So you need to make sure to have Qt's moc run also for the source file * where you use the macro. E.g. in projects using CMake and it's automoc feature, * as usual you need to have a line * @code * #include * @endcode * in the same source file when that one has the name "myplugin.cpp". * * Example: * \code * #include * #include * * class MyPlugin : public PluginInterface * { * public: * MyPlugin(QObject *parent, const QVariantList &args) * : PluginInterface(parent) * {} * }; * * K_PLUGIN_FACTORY(MyPluginFactory, * registerPlugin(); * ) * * #include * \endcode * * If you want to compile a .json file into the plugin, use K_PLUGIN_FACTORY_WITH_JSON. * * \see K_PLUGIN_FACTORY_WITH_JSON * \see K_PLUGIN_FACTORY_DECLARATION * \see K_PLUGIN_FACTORY_DEFINITION */ #define K_PLUGIN_FACTORY(name, pluginRegistrations) K_PLUGIN_FACTORY_WITH_BASEFACTORY(name, KPluginFactory, pluginRegistrations) /** * \relates KPluginFactory * * Create a KPluginFactory subclass and export it as the root plugin object with * JSON metadata. * * This macro does the same as K_PLUGIN_FACTORY, but adds a JSON file as plugin * metadata. See Q_PLUGIN_METADATA() for more information. * * \param name The name of the KPluginFactory derived class. * * \param pluginRegistrations Code to be inserted into the constructor of the * class. Usually a series of registerPlugin() calls. * * \param jsonFile Name of the json file to be compiled into the plugin as metadata * * @note K_PLUGIN_FACTORY_WITH_JSON declares the subclass including a Q_OBJECT macro. * So you need to make sure to have Qt's moc run also for the source file * where you use the macro. E.g. in projects using CMake and it's automoc feature, * as usual you need to have a line * @code * #include * @endcode * in the same source file when that one has the name "myplugin.cpp". * * Example: * \code * #include * #include * * class MyPlugin : public PluginInterface * { * public: * MyPlugin(QObject *parent, const QVariantList &args) * : PluginInterface(parent) * {} * }; * * K_PLUGIN_FACTORY_WITH_JSON(MyPluginFactory, * "metadata.json", * registerPlugin(); * ) * * #include * \endcode * * \see K_PLUGIN_FACTORY * \see K_PLUGIN_FACTORY_DECLARATION * \see K_PLUGIN_FACTORY_DEFINITION * * @since 5.0 */ #define K_PLUGIN_FACTORY_WITH_JSON(name, jsonFile, pluginRegistrations) K_PLUGIN_FACTORY_WITH_BASEFACTORY_JSON(name, KPluginFactory, jsonFile, pluginRegistrations) /** * \relates KPluginFactory * * Create a KPluginFactory subclass and export it as the root plugin object with * JSON metadata. * * This macro does the same as K_PLUGIN_FACTORY_WITH_JSON, but you only have to pass the class name and the json file. * The factory name and registerPlugin call are deduced from the class name. * * @code * #include * @endcode * in the same source file when that one has the name "myplugin.cpp". * * Example: * \code * #include * #include * * class MyPlugin : public PluginInterface * { * public: * MyPlugin(QObject *parent, const QVariantList &args) * : PluginInterface(parent) * {} * }; * * K_PLUGIN_CLASS_WITH_JSON(MyPlugin, "metadata.json") * * #include * \endcode * * \see K_PLUGIN_FACTORY_WITH_JSON * * @since 5.44 */ #define K_PLUGIN_CLASS_WITH_JSON(classname, jsonFile) K_PLUGIN_FACTORY_WITH_JSON(classname ## Factory, jsonFile, registerPlugin();) /** * \relates KPluginFactory * * K_PLUGIN_FACTORY_DECLARATION declares the KPluginFactory subclass. This macro * can be used in a header file. * * \param name The name of the KPluginFactory derived class. * * \see K_PLUGIN_FACTORY * \see K_PLUGIN_FACTORY_DEFINITION */ #define K_PLUGIN_FACTORY_DECLARATION(name) K_PLUGIN_FACTORY_DECLARATION_WITH_BASEFACTORY(name, KPluginFactory) /** * \relates KPluginFactory * K_PLUGIN_FACTORY_DEFINITION defines the KPluginFactory subclass. This macro * can not be used in a header file. * * \param name The name of the KPluginFactory derived class. * * \param pluginRegistrations Code to be inserted into the constructor of the * class. Usually a series of registerPlugin() calls. * * \see K_PLUGIN_FACTORY * \see K_PLUGIN_FACTORY_DECLARATION */ #define K_PLUGIN_FACTORY_DEFINITION(name, pluginRegistrations) K_PLUGIN_FACTORY_DEFINITION_WITH_BASEFACTORY(name, KPluginFactory, pluginRegistrations) /** * \class KPluginFactory kpluginfactory.h * * KPluginFactory provides a convenient way to provide factory-style plugins. * Qt plugins provide a singleton object, but a common pattern is for plugins * to generate as many objects of a particular type as the application requires. * By using KPluginFactory, you can avoid implementing the factory pattern * yourself. * * KPluginFactory also allows plugins to provide multiple different object * types, indexed by keywords. * * The objects created by KPluginFactory must inherit QObject, and must have a * standard constructor pattern: * \li if the object is a KPart::Part, it must be of the form * \code * T(QWidget *parentWidget, QObject *parent, const QVariantList &args) * \endcode * \li if it is a QWidget, it must be of the form * \code * T(QWidget *parent, const QVariantList &args) * \endcode * \li otherwise it must be of the form * \code * T(QObject *parent, const QVariantList &args) * \endcode * * You should typically use either K_PLUGIN_FACTORY() or * K_PLUGIN_FACTORY_WITH_JSON() in your plugin code to create the factory. The * typical pattern is * * \code * #include * #include * * class MyPlugin : public PluginInterface * { * public: * MyPlugin(QObject *parent, const QVariantList &args) * : PluginInterface(parent) * {} * }; * * K_PLUGIN_FACTORY(MyPluginFactory, * registerPlugin(); * ) * #include * \endcode * * If you want to write a custom KPluginFactory not using the standard macro(s) * you can reimplement the * create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword) * method. * * Example: * \code * class SomeScriptLanguageFactory : public KPluginFactory * { * Q_OBJECT * public: * SomeScriptLanguageFactory() * {} * * protected: * virtual QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword) * { * const QString identifier = QLatin1String(iface) + QLatin1Char('_') + keyword; * // load scripting language module from the information in identifier * // and return it: * return object; * } * }; * \endcode * * If you want to load a library use KPluginLoader. * The application that wants to instantiate plugin classes can do the following: * \code * KPluginFactory *factory = KPluginLoader("libraryname").factory(); * if (factory) { * PluginInterface *p1 = factory->create(parent); * OtherInterface *p2 = factory->create(parent); * NextInterface *p3 = factory->create("keyword1", parent); * NextInterface *p3 = factory->create("keyword2", parent); * } * \endcode * * \author Matthias Kretz * \author Bernhard Loos */ class KCOREADDONS_EXPORT KPluginFactory : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(KPluginFactory) public: /** * This constructor creates a factory for a plugin. */ explicit KPluginFactory(); /** * This destroys the PluginFactory. */ ~KPluginFactory() override; /** * Use this method to create an object. It will try to create an object which inherits * \p T. If it has multiple choices, you will get a fatal error (kFatal()), so be careful * to request a unique interface or use keywords. * * \tparam T The interface for which an object should be created. The object will inherit \p T. * \param parent The parent of the object. If \p parent is a widget type, it will also passed * to the parentWidget argument of the CreateInstanceFunction for the object. * \param args Additional arguments which will be passed to the object. * \returns A pointer to the created object is returned, or 0 if an error occurred. */ template T *create(QObject *parent = nullptr, const QVariantList &args = QVariantList()); /** * Use this method to create an object. It will try to create an object which inherits * \p T and was registered with \p keyword. * * \tparam T The interface for which an object should be created. The object will inherit \p T. * \param keyword The keyword of the object. * \param parent The parent of the object. If \p parent is a widget type, it will also passed * to the parentWidget argument of the CreateInstanceFunction for the object. * \param args Additional arguments which will be passed to the object. * \returns A pointer to the created object is returned, or 0 if an error occurred. */ template T *create(const QString &keyword, QObject *parent = nullptr, const QVariantList &args = QVariantList()); /** * Use this method to create an object. It will try to create an object which inherits * \p T and was registered with \p keyword. * This overload has an additional \p parentWidget argument, which is used by some plugins (e.g. Parts). * \tparam T The interface for which an object should be created. The object will inherit \p T. * \param parentWidget An additional parent widget. * \param parent The parent of the object. If \p parent is a widget type, it will also passed * to the parentWidget argument of the CreateInstanceFunction for the object. * \param keyword The keyword of the object. * \param args Additional arguments which will be passed to the object. * \returns A pointer to the created object is returned, or 0 if an error occurred. */ template T *create(QWidget *parentWidget, QObject *parent, const QString &keyword = QString(), const QVariantList &args = QVariantList()); /** * @deprecated since 4.0 use create(QObject *parent, const QVariantList &args) */ #ifndef KCOREADDONS_NO_DEPRECATED template KCOREADDONS_DEPRECATED T *create(QObject *parent, const QStringList &args) { return create(parent, stringListToVariantList(args)); } #endif /** * @deprecated since 4.0 use create(QObject *parent, const QVariantList &args) */ #ifndef KCOREADDONS_NO_DEPRECATED KCOREADDONS_DEPRECATED QObject *create(QObject *parent = nullptr, const char *classname = "QObject", const QStringList &args = QStringList()) { return create(classname, nullptr, parent, stringListToVariantList(args), QString()); } #endif /** * \internal * Converts a QStringList to a QVariantList */ static QVariantList stringListToVariantList(const QStringList &list); /** * \internal * Converts a QVariantList of strings to a QStringList */ static QStringList variantListToStringList(const QVariantList &list); Q_SIGNALS: void objectCreated(QObject *object); protected: /** * Function pointer type to a function that instantiates a plugin. */ typedef QObject *(*CreateInstanceFunction)(QWidget *, QObject *, const QVariantList &); /** * This is used to detect the arguments need for the constructor of plugin classes. * You can inherit it, if you want to add new classes and still keep support for the old ones. */ template struct InheritanceChecker { CreateInstanceFunction createInstanceFunction(KParts::Part *) { return &createPartInstance; } CreateInstanceFunction createInstanceFunction(QWidget *) { return &createInstance; } CreateInstanceFunction createInstanceFunction(...) { return &createInstance; } }; explicit KPluginFactory(KPluginFactoryPrivate &dd); /** * Registers a plugin with the factory. Call this function from the constructor of the * KPluginFactory subclass to make the create function able to instantiate the plugin when asked * for an interface the plugin implements. * * You can register as many plugin classes as you want as long as either the plugin interface or * the \p keyword makes it unique. E.g. it is possible to register a KCModule and a * KParts::Part without having to specify keywords since their interfaces differ. * * \tparam T the name of the plugin class * * \param keyword An optional keyword as unique identifier for the plugin. This allows you to * put more than one plugin with the same interface into the same library using the same * factory. X-KDE-PluginKeyword is a convenient way to specify the keyword in a desktop file. * * \param instanceFunction A function pointer to a function that creates an instance of the * plugin. The default function that will be used depends on the type of interface. If the * interface inherits from * \li \c KParts::Part the function will call * \code * new T(QWidget *parentWidget, QObject *parent, const QVariantList &args) * \endcode * \li \c QWidget the function will call * \code * new T(QWidget *parent, const QVariantList &args) * \endcode * \li else the function will call * \code * new T(QObject *parent, const QVariantList &args) * \endcode */ template void registerPlugin(const QString &keyword = QString(), CreateInstanceFunction instanceFunction = InheritanceChecker().createInstanceFunction(reinterpret_cast(0))) { registerPlugin(keyword, &T::staticMetaObject, instanceFunction); } KPluginFactoryPrivate *const d_ptr; /** * @deprecated since 4.0 use create(QObject *parent, const QVariantList &args) */ #ifndef KCOREADDONS_NO_DEPRECATED virtual KCOREADDONS_DEPRECATED QObject *createObject(QObject *parent, const char *className, const QStringList &args); #endif /** * @deprecated since 4.0 use create(QWidget *parentWidget, QObject *parent, const QString &keyword, const QVariantList &args) */ #ifndef KCOREADDONS_NO_DEPRECATED virtual KCOREADDONS_DEPRECATED KParts::Part *createPartObject(QWidget *parentWidget, QObject *parent, const char *classname, const QStringList &args); #endif /** * This function is called when the factory asked to create an Object. * * You may reimplement it to provide a very flexible factory. This is especially useful to * provide generic factories for plugins implemeted using a scripting language. * * \param iface The staticMetaObject::className() string identifying the plugin interface that * was requested. E.g. for KCModule plugins this string will be "KCModule". * \param parentWidget Only used if the requested plugin is a KPart. * \param parent The parent object for the plugin object. * \param args A plugin specific list of arbitrary arguments. * \param keyword A string that uniquely identifies the plugin. If a KService is used this * keyword is read from the X-KDE-PluginKeyword entry in the .desktop file. */ virtual QObject *create(const char *iface, QWidget *parentWidget, QObject *parent, const QVariantList &args, const QString &keyword); template static QObject *createInstance(QWidget *parentWidget, QObject *parent, const QVariantList &args) { Q_UNUSED(parentWidget); ParentType *p = nullptr; if (parent) { p = qobject_cast(parent); Q_ASSERT(p); } return new impl(p, args); } template static QObject *createPartInstance(QWidget *parentWidget, QObject *parent, const QVariantList &args) { return new impl(parentWidget, parent, args); } private: void registerPlugin(const QString &keyword, const QMetaObject *metaObject, CreateInstanceFunction instanceFunction); }; typedef KPluginFactory KLibFactory; template inline T *KPluginFactory::create(QObject *parent, const QVariantList &args) { QObject *o = create(T::staticMetaObject.className(), parent && parent->isWidgetType() ? reinterpret_cast(parent) : nullptr, parent, args, QString()); T *t = qobject_cast(o); if (!t) { delete o; } return t; } template inline T *KPluginFactory::create(const QString &keyword, QObject *parent, const QVariantList &args) { QObject *o = create(T::staticMetaObject.className(), parent && parent->isWidgetType() ? reinterpret_cast(parent) : nullptr, parent, args, keyword); T *t = qobject_cast(o); if (!t) { delete o; } return t; } template inline T *KPluginFactory::create(QWidget *parentWidget, QObject *parent, const QString &keyword, const QVariantList &args) { QObject *o = create(T::staticMetaObject.className(), parentWidget, parent, args, keyword); T *t = qobject_cast(o); if (!t) { delete o; } return t; } Q_DECLARE_INTERFACE(KPluginFactory, KPluginFactory_iid) #endif // KPLUGINFACTORY_H diff --git a/src/lib/plugin/kpluginfactory_p.h b/src/lib/plugin/kpluginfactory_p.h index 3afcaa3..4dff2e0 100644 --- a/src/lib/plugin/kpluginfactory_p.h +++ b/src/lib/plugin/kpluginfactory_p.h @@ -1,47 +1,47 @@ /* This file is part of the KDE project Copyright (C) 2007 Matthias Kretz Copyright (C) 2007 Bernhard Loos This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KPLUGINFACTORY_P_H #define KPLUGINFACTORY_P_H #include "kpluginfactory.h" -#include +#include class KPluginFactoryPrivate { Q_DECLARE_PUBLIC(KPluginFactory) protected: typedef QPair Plugin; KPluginFactoryPrivate() : catalogInitialized(false) {} ~KPluginFactoryPrivate() { } QHash createInstanceHash; QString catalogName; bool catalogInitialized; KPluginFactory *q_ptr; }; #endif // KPLUGINFACTORY_P_H diff --git a/src/lib/plugin/kpluginloader.cpp b/src/lib/plugin/kpluginloader.cpp index edfc3f1..b7fe090 100644 --- a/src/lib/plugin/kpluginloader.cpp +++ b/src/lib/plugin/kpluginloader.cpp @@ -1,305 +1,305 @@ /* This file is part of the KDE project Copyright (C) 2007 Bernhard Loos This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kpluginloader.h" #include "kpluginfactory.h" #include "kpluginmetadata.h" -#include -#include +#include +#include #include -#include +#include #include "kcoreaddons_debug.h" #include #include // TODO: Upstream the versioning stuff to Qt // TODO: Patch for Qt to expose plugin-finding code directly // TODO: Add a convenience method to KFactory to replace KPluginLoader::factory() // TODO: (after the above) deprecate this class class KPluginLoaderPrivate { Q_DECLARE_PUBLIC(KPluginLoader) protected: KPluginLoaderPrivate(const QString &libname) : name(libname), loader(nullptr), pluginVersion(~0U), pluginVersionResolved(false) {} ~KPluginLoaderPrivate() {} KPluginLoader *q_ptr; const QString name; QString errorString; QPluginLoader *loader; quint32 pluginVersion; bool pluginVersionResolved; }; QString KPluginLoader::findPlugin(const QString &name) { // We just defer to Qt; unfortunately, QPluginLoader's searching code is not // accessible without creating a QPluginLoader object. // Workaround for QTBUG-39642 static QMutex s_qtWorkaroundMutex; QMutexLocker lock(&s_qtWorkaroundMutex); QPluginLoader loader(name); return loader.fileName(); } KPluginLoader::KPluginLoader(const QString &plugin, QObject *parent) : QObject(parent), d_ptr(new KPluginLoaderPrivate(plugin)) { d_ptr->q_ptr = this; Q_D(KPluginLoader); d->loader = new QPluginLoader(plugin, this); } KPluginLoader::KPluginLoader(const KPluginName &pluginName, QObject *parent) : QObject(parent), d_ptr(new KPluginLoaderPrivate(pluginName.name())) { d_ptr->q_ptr = this; Q_D(KPluginLoader); d->loader = new QPluginLoader(this); if (pluginName.isValid()) { d->loader->setFileName(pluginName.name()); if (d->loader->fileName().isEmpty()) { qCWarning(KCOREADDONS_DEBUG) << "Error loading plugin" << pluginName.name() << d->loader->errorString() << endl << "Plugin search paths are" << QCoreApplication::libraryPaths() << endl << "The environment variable QT_PLUGIN_PATH might be not correctly set"; } } else { d->errorString = pluginName.errorString(); } } KPluginLoader::~KPluginLoader() { delete d_ptr; } KPluginFactory *KPluginLoader::factory() { Q_D(KPluginLoader); QObject *obj = instance(); if (!obj) { return nullptr; } KPluginFactory *factory = qobject_cast(obj); if (factory == nullptr) { qCDebug(KCOREADDONS_DEBUG) << "Expected a KPluginFactory, got a" << obj->metaObject()->className(); delete obj; d->errorString = tr("The library %1 does not offer a KPluginFactory.").arg(d->name); } return factory; } quint32 KPluginLoader::pluginVersion() { Q_D(const KPluginLoader); if (!load()) { return qint32(-1); } return d->pluginVersion; } QString KPluginLoader::pluginName() const { Q_D(const KPluginLoader); return d->name; } QString KPluginLoader::errorString() const { Q_D(const KPluginLoader); if (!d->errorString.isEmpty()) { return d->errorString; } return d->loader->errorString(); } QString KPluginLoader::fileName() const { Q_D(const KPluginLoader); return d->loader->fileName(); } QObject *KPluginLoader::instance() { Q_D(const KPluginLoader); if (!load()) { return nullptr; } return d->loader->instance(); } bool KPluginLoader::isLoaded() const { Q_D(const KPluginLoader); return d->loader->isLoaded() && d->pluginVersionResolved; } bool KPluginLoader::load() { Q_D(KPluginLoader); if (!d->loader->load()) { return false; } if (d->pluginVersionResolved) { return true; } Q_ASSERT(!fileName().isEmpty()); QLibrary lib(fileName()); Q_ASSERT(lib.isLoaded()); // already loaded by QPluginLoader::load() // TODO: this messes up KPluginLoader::errorString(): it will change from unknown error to could not resolve kde_plugin_version quint32 *version = reinterpret_cast(lib.resolve("kde_plugin_version")); if (version) { d->pluginVersion = *version; } else { d->pluginVersion = ~0U; } d->pluginVersionResolved = true; return true; } QLibrary::LoadHints KPluginLoader::loadHints() const { Q_D(const KPluginLoader); return d->loader->loadHints(); } QJsonObject KPluginLoader::metaData() const { Q_D(const KPluginLoader); return d->loader->metaData(); } void KPluginLoader::setLoadHints(QLibrary::LoadHints loadHints) { Q_D(KPluginLoader); d->loader->setLoadHints(loadHints); } bool KPluginLoader::unload() { Q_D(KPluginLoader); // Even if *this* call does not unload it, another might, // so we err on the side of re-resolving the version. d->pluginVersionResolved = false; return d->loader->unload(); } void KPluginLoader::forEachPlugin(const QString &directory, std::function callback) { QStringList dirsToCheck; if (QDir::isAbsolutePath(directory)) { dirsToCheck << directory; } else { foreach (const QString &libDir, QCoreApplication::libraryPaths()) { dirsToCheck << libDir + QDir::separator() + directory; } } foreach (const QString &dir, dirsToCheck) { QDirIterator it(dir, QDir::Files); while (it.hasNext()) { it.next(); if (QLibrary::isLibrary(it.fileName())) { callback(it.fileInfo().absoluteFilePath()); } } } } QVector KPluginLoader::findPlugins(const QString &directory, std::function filter) { QVector ret; forEachPlugin(directory, [&](const QString &pluginPath) { KPluginMetaData metadata(pluginPath); if (!metadata.isValid()) { return; } if (filter && !filter(metadata)) { return; } ret.append(metadata); }); return ret; } QVector< KPluginMetaData > KPluginLoader::findPluginsById(const QString& directory, const QString& pluginId) { auto filter = [&pluginId](const KPluginMetaData &md) -> bool { return md.pluginId() == pluginId; }; return KPluginLoader::findPlugins(directory, filter); } QList KPluginLoader::instantiatePlugins(const QString &directory, std::function filter, QObject* parent) { QList ret; QPluginLoader loader; foreach (const KPluginMetaData &metadata, findPlugins(directory, filter)) { loader.setFileName(metadata.fileName()); QObject* obj = loader.instance(); if (!obj) { qCWarning(KCOREADDONS_DEBUG).nospace() << "Could not instantiate plugin \"" << metadata.fileName() << "\": " << loader.errorString(); continue; } obj->setParent(parent); ret.append(obj); } return ret; } diff --git a/src/lib/plugin/kpluginloader.h b/src/lib/plugin/kpluginloader.h index e5869b0..bc60b70 100644 --- a/src/lib/plugin/kpluginloader.h +++ b/src/lib/plugin/kpluginloader.h @@ -1,476 +1,476 @@ /* This file is part of the KDE project Copyright (C) 2007 Bernhard Loos This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KPLUGINLOADER_H #define KPLUGINLOADER_H #include -#include -#include +#include +#include #include class KPluginFactory; class KPluginMetaData; class KPluginLoaderPrivate; class KPluginName; /** * \class KPluginLoader kpluginloader.h * * This class behaves largely like QPluginLoader (and, indeed, uses it * internally), but additionally reads the plugin version, as provided by the * K_EXPORT_PLUGIN_VERSION macro (see pluginVersion()) and provides access to a * KPluginFactory instance if the plugin provides one (see factory()) * * Note that the factory() is a typesafe convenience method that just wraps a * qobject_cast on the result of QPluginLoader::instance(). Therefore, if you do * not need the plugin version feature, you can (and should) just use * QPluginLoader instead. * * Unlike QPluginLoader, it is not possible to re-use KPluginLoader for more * than one plugin (it provides no setFileName method). * * The same notes and caveats that apply to QPluginLoader also apply to * KPluginLoader. * * Sample code: * \code * KPluginLoader loader( ...library or kservice... ); * KPluginFactory* factory = loader.factory(); * if (!factory) { * qWarning() << "Error loading plugin:" << loader.errorString(); * } else { * MyInterface* obj = factory->create(); * if (!obj) { * qWarning() << "Error creating object"; * } * } * \endcode * * \see KPluginFactory * * \author Bernhard Loos */ class KCOREADDONS_EXPORT KPluginLoader : public QObject { Q_OBJECT Q_PROPERTY(QString fileName READ fileName) Q_PROPERTY(QLibrary::LoadHints loadHints READ loadHints WRITE setLoadHints) Q_PROPERTY(QString pluginName READ pluginName) Q_PROPERTY(quint32 pluginVersion READ pluginVersion) public: /** * Load a plugin by name. * * This should be the name of the plugin object file, without any suffix * (like .so or .dll). Plugin object files should not have a 'lib' prefix. * * fileName() will be empty if the plugin could not be found. * * \param plugin The name of the plugin. * \param parent A parent object. */ explicit KPluginLoader(const QString &plugin, QObject *parent = nullptr); /** * Load a plugin by name. * * This constructor behaves exactly the same as * KPluginLoader(const QString&,QObject*). It allows any class that can be * cast to KPluginName (such as KService) to be passed to KPluginLoader. * * \param name The name of the plugin. * \param parent A parent object. */ explicit KPluginLoader(const KPluginName &name, QObject *parent = nullptr); /** * Destroys the plugin loader. * * Unless unload() was called explicitly, the plugin stays in memory until * the application terminates. */ ~KPluginLoader(); /** * Returns the factory object of the plugin. * * This is typically created by one of the KPluginFactory macros. * Internally, this uses QPluginLoader::instance(), and the same * behaviours apply. * * \returns The factory of the plugin or 0 on error. */ KPluginFactory *factory(); /** * Returns the name of this plugin as given to the constructor. * * If the KService constructor was used, this is the name of the library * provided by the service. * * \returns The plugin name. * * \see fileName() */ QString pluginName() const; /** * Returns the plugin version. * * This will load the plugin if it is not already loaded. * * \returns The version given to K_EXPORT_PLUGIN_VERSION, or (quint32) -1 if * the macro was not used or the plugin could not be loaded. */ quint32 pluginVersion(); /** * Locates a plugin. * * Searches for a dynamic object file in the locations KPluginLoader and * QPluginLoader would search (ie: the current directory and * QCoreApplication::libraryPaths()). * * This can be useful if you wish to use a plugin that does not conform to * the Qt plugin scheme of providing a QObject that declares * Q_PLUGIN_METADATA. In this case, you can find the plugin with this * method, and load it with QLibrary. * * Note that the path is not necessarily absolute. In particular, if the * plugin is found in the current directory, it will be a relative path. * * \param name The name of the plugin (can be a relative path; see above). * This should not include a file extension (like .so or .dll). * \returns The path to the plugin if it was found, or QString() if it * could not be found. * * @since 5.0 */ static QString findPlugin(const QString &name); /** * Returns the last error. * * \returns The description of the last error. * * \see QPluginLoader::errorString() */ QString errorString() const; /** * Returns the path of the plugin. * * This will be the full path of the plugin if it was found, and empty if * it could not be found. * * \returns The full path of the plugin, or the null string if it could * not be found. * * \see QPluginLoader::fileName(), pluginName() */ QString fileName() const; /** * Returns the root object of the plugin. * * The plugin will be loaded if necessary. If the plugin used one of the * KPluginFactory macros, you should use factory() instead. * * \returns The plugin's root object. * * \see QPluginLoader::instance() */ QObject *instance(); /** * Determines whether the plugin is loaded. * * \returns @c True if the plugin is loaded, @c false otherwise. * * \see QPluginLoader::isLoaded() */ bool isLoaded() const; /** * Loads the plugin. * * It is safe to call this multiple times; if the plugin was already loaded, * it will just return @c true. * * Methods that require the plugin to be loaded will load it as necessary * anyway, so you do not normally need to call this method. * * \returns @c True if the plugin was loaded successfully, @c false * otherwise. * * \see QPluginLoader::load() */ bool load(); /** * Returns the load hints for the plugin. * * Determines how load() should work. See QLibrary::loadHints for more * information. * * \returns The load hints for the plugin. * * \see QPluginLoader::loadHints(), setLoadHints() */ QLibrary::LoadHints loadHints() const; /** * Returns the meta data for the plugin. * * \returns A JSON object containing the plugin's metadata, if found. * * \see QPluginLoader::metaData() */ QJsonObject metaData() const; /** * Set the load hints for the plugin. * * Determines how load() should work. See QLibrary::loadHints for more * information. * * \param loadHints The load hints for the plugin. * * \see QPluginLoader::setLoadHints(), loadHints() */ void setLoadHints(QLibrary::LoadHints loadHints); /** * Attempts to unload the plugin. * * If other instances of KPluginLoader or QPluginLoader are using the same * plugin, this will fail; unloading will only happen when every instance * has called unload(). * * \returns @c True if the plugin was unloaded, @c false otherwise. * * \see QPluginLoader::unload(), load(), instance(), factory() */ bool unload(); /** * Finds and instantiates (by calling QPluginLoader::instance()) all plugins from a given * directory. Only plugins which have JSON metadata will be considered. A filter can be passed * which determines which of the found plugins should actually be loaded. * * If you use KConfig you could have a group "Plugins" in your configuration file with the * plugin name as the key and true/false as the value to indicate whether the plugin should * be loaded. In order to easily load all the enable plugins you could use the following code: * @code * KConfigGroup pluginGroup = KSharedConfig::openConfig()->group("Plugins"); * auto filter = [&](const KPluginMetaData &md) { * if (!pluginGroup.hasKey(md.pluginName())) { * return md.isEnabledByDefault(); * } else { * return pluginGroup.readEntry(md.pluginName(), false); * } * }; * QList plugins = KPluginLoader::instantiatePlugins("myapp", filter); * @endcode * * @param directory the directory to search for plugins. If a relative path is given for @p directory, * all entries of QCoreApplication::libraryPaths() will be checked with @p directory appended as a * subdirectory. If an absolute path is given only that directory will be searched. * * @param filter a callback function that returns @c true if the found plugin should be loaded * and @c false if it should be skipped. If this argument is omitted all plugins will be loaded. * * @param parent the parent to set for the instantiated plugins, if the * plugins were not already loaded. * * @note If the plugins have been previously loaded (via QPluginLoader, * directly or due to this class) without being deleted in the meantime * then they are not re-created or re-parented and will be returned using * the parent they were originally created with. @sa * QPluginLoader::instance(). * * @return a list containing an instantiation of each plugin that met the filter criteria * * @see KPluginLoader::findPlugins() * * @since 5.1 */ static QList instantiatePlugins(const QString &directory, std::function filter = std::function(), QObject* parent = nullptr); /** * Find all plugins inside @p directory. Only plugins which have JSON metadata will be considered. * * @param directory The directory to search for plugins. If a relative path is given for @p directory, * all entries of QCoreApplication::libraryPaths() will be checked with @p directory appended as a * subdirectory. If an absolute path is given only that directory will be searched. * * @param filter a callback function that returns @c true if the found plugin should be loaded * and @c false if it should be skipped. If this argument is omitted all plugins will be loaded. * * @return all plugins found in @p directory that fulfil the constraints of @p filter * * @see KPluginLoader::instantiatePlugins() * * @since 5.1 */ static QVector findPlugins(const QString &directory, std::function filter = std::function()); /** * Find all plugins inside @p directory with a given pluginId. Only plugins which have JSON metadata will be considered. * * @param directory The directory to search for plugins. If a relative path is given for @p directory, * all entries of QCoreApplication::libraryPaths() will be checked with @p directory appended as a * subdirectory. If an absolute path is given only that directory will be searched. * * @param pluginId The Id of the plugin, for example KPluginMetaData.pluginId(). * * @return all plugins found in @p directory with the given pluginId. * * @see KPluginLoader::instantiatePlugins() * * @since 5.11 */ static QVector findPluginsById(const QString &directory, const QString &pluginId); /** * Invokes @p callback for each valid plugin found inside @p directory. This is useful if * your application needs to customize the behaviour of KPluginLoader::findPlugins() or * KPluginLoader::instantiatePlugins(). * * @note The files found do not necessarily contain JSON metadata and may not be loadable using K/QPluginLoader. * The only guarantee made is that they are valid library file names as determined by QLibrary::isLibrary(). * * @param directory The directory to search for plugins. If a relative path is given for @p directory, * all entries of QCoreApplication::libraryPaths() will be checked with @p directory appended as a * subdirectory. If an absolute path is given only that directory will be searched. * * @param callback This function will be invoked for each valid plugin that is found. It will receive * the absolute path to the plugin as an argument * * @see KPluginLoader::findPlugins(), KPluginLoader::instantiatePlugins() * * @since 5.1 */ static void forEachPlugin(const QString &directory, std::function callback = std::function()); private: Q_DECLARE_PRIVATE(KPluginLoader) Q_DISABLE_COPY(KPluginLoader) KPluginLoaderPrivate *const d_ptr; }; /** * Represents the name of a plugin intended for KPluginLoader. * * This exists only so that classes such as KService can provide a cast * operator to allow them to be used as arguments to KPluginLoader. * Unless you are implementing such a cast operator, you should never * need to use this class directly. */ // NOTE: this class is all inline, as it mainly exists for typing reasons // (ie: to prevent the issues that would be caused by adding an // operator QString() method to KService) class KCOREADDONS_EXPORT KPluginName { public: /** * Construct a (valid) plugin name from a string. * * If there was an error and the name could not be determined, * fromErrorString() should be used instead to construct an * appropriate error message. * * @param name The name of the plugin; this should not be empty. */ inline explicit KPluginName(const QString &name); /** * The name of the plugin. * * @returns The string passed to the constructor if isValid() is * @c true, QString() otherwise. */ inline QString name() const; /** * Whether the name is valid. * * Note that this only determines how the KPluginName was * constructed, not anything about the value of the string. * * @returns @c true if the KPluginName(const QString&) constructor * was used, @c false if fromErrorString() was used. */ inline bool isValid() const; /** * The error string if no name could be determined. * * @returns The string passed to fromErrorString() if isValid() is * @c false, QString() otherwise. */ inline QString errorString() const; /** * Construct an invalid plugin name with an error message. * * When this object is passed to KPluginLoader, @p errorString * will be used for KPluginLoader::errorString(). * * @param errorString The (translated) error string. */ static inline KPluginName fromErrorString(const QString &errorString); private: inline KPluginName(const QString &name, bool isError); QString m_name; bool m_isError; }; inline KPluginName::KPluginName(const QString &name) : m_name(name), m_isError(false) {} inline KPluginName::KPluginName(const QString &name, bool isError) : m_name(name), m_isError(isError) {} inline QString KPluginName::name() const { return m_isError ? QString() : m_name; } inline bool KPluginName::isValid() const { return !m_isError; } inline QString KPluginName::errorString() const { return m_isError ? m_name : QString(); } inline KPluginName KPluginName::fromErrorString(const QString &errorString) { return KPluginName(errorString, true); } #endif diff --git a/src/lib/randomness/krandom.cpp b/src/lib/randomness/krandom.cpp index 5bda4b0..85cb5a3 100644 --- a/src/lib/randomness/krandom.cpp +++ b/src/lib/randomness/krandom.cpp @@ -1,80 +1,80 @@ /* This file is part of the KDE libraries Copyright (c) 1999 Matthias Kalle Dalheimer Copyright (c) 2000 Charles Samuels Copyright (c) 2005 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "krandom.h" #include #ifdef Q_OS_WIN #include #else // Q_OS_WIN #include #endif // Q_OS_WIN #include #include #ifndef Q_OS_WIN #include #endif // Q_OS_WIN #include -#include +#include #include #include int KRandom::random() { static QThreadStorage initialized_threads; if (!initialized_threads.localData()) { unsigned int seed; initialized_threads.setLocalData(true); QFile urandom(QStringLiteral("/dev/urandom")); bool opened = urandom.open(QIODevice::ReadOnly | QIODevice::Unbuffered); if (!opened || urandom.read(reinterpret_cast(&seed), sizeof(seed)) != sizeof(seed)) { // No /dev/urandom... try something else. qsrand(getpid()); seed = qrand() ^ time(nullptr) ^ reinterpret_cast(QThread::currentThread()); } qsrand(seed); } return qrand(); } QString KRandom::randomString(int length) { if (length <= 0) { return QString(); } QString str; str.resize(length); int i = 0; while (length--) { int r = random() % 62; r += 48; if (r > 57) { r += 7; } if (r > 90) { r += 6; } str[i++] = char(r); // so what if I work backwards? } return str; } diff --git a/src/lib/randomness/krandom.h b/src/lib/randomness/krandom.h index 232b983..656495d 100644 --- a/src/lib/randomness/krandom.h +++ b/src/lib/randomness/krandom.h @@ -1,56 +1,56 @@ /* This file is part of the KDE libraries Copyright (c) 1999 Matthias Kalle Dalheimer Copyright (c) 2000 Charles Samuels Copyright (c) 2005 Joseph Wenninger This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KRANDOM_H #define KRANDOM_H #include -#include +#include /** * \headerfile krandom.h * * @short Helper class to create random data * * This namespace provides methods which generate random data. * KRandom is not recommended for serious random-number generation needs, * like cryptography. */ namespace KRandom { /** * Generates a uniform random number. * @return A random number in the range [0, RAND_MAX). The RNG is seeded * on first use. */ KCOREADDONS_EXPORT int random(); /** * Generates a random string. It operates in the range [A-Za-z0-9] * @param length Generate a string of this length. * @return the random string */ KCOREADDONS_EXPORT QString randomString(int length); } #endif diff --git a/src/lib/randomness/krandomsequence.h b/src/lib/randomness/krandomsequence.h index 7b5caf5..3d374fe 100644 --- a/src/lib/randomness/krandomsequence.h +++ b/src/lib/randomness/krandomsequence.h @@ -1,158 +1,158 @@ /* This file is part of the KDE libraries Copyright (c) 1999 Sean Harmer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef K_RANDOM_SEQUENCE_H #define K_RANDOM_SEQUENCE_H #include -#include +#include /** * \class KRandomSequence krandomsequence.h * * A class to create a pseudo-random sequence * * Given a seed number, this class will produce a sequence of * pseudo-random numbers. This would typically be used in * applications like games. * * In general, you should instantiate a KRandomSequence object and * pass along your seed number in the constructor. From then on, * simply call getDouble or getLong to obtain the next * number in the sequence. * * @author Sean Harmer */ class KCOREADDONS_EXPORT KRandomSequence { public: /** * Creates a pseudo-random sequence based on the seed lngSeed. * * A Pseudo-random sequence is different for each seed but can be * reproduced by starting the sequence with the same seed. * * If you need a single value which needs to be unpredictable, * you need to use KRandom::random() instead. * * @param intSeed Seed to initialize the sequence with. * If lngSeed is 0, the sequence is initialized with a value from * KRandom::random(). * * Do not use methods working with long type because on 64-bit * their size is different. */ explicit KRandomSequence(int intSeed = 0); explicit KRandomSequence(long lngSeed); /** * Standard destructor */ virtual ~KRandomSequence(); /** * Copy constructor */ KRandomSequence(const KRandomSequence &a); /** * Assignment */ KRandomSequence &operator=(const KRandomSequence &a); /** * Restart the sequence based on lngSeed. * @param intSeed Seed to initialize the sequence with. * If lngSeed is 0, the sequence is initialized with a value from * KRandom::random(). */ void setSeed(int intSeed = 0); void setSeed(long lngSeed = 0); /** * Get the next number from the pseudo-random sequence. * * @return a pseudo-random double value between [0,1) */ double getDouble(); /** * Get the next number from the pseudo-random sequence. * * @return a pseudo-random integer value between [0, max) * with 0 <= max < 1.000.000 */ unsigned int getInt(unsigned int max); unsigned long getLong(unsigned long max); /** * Get a boolean from the pseudo-random sequence. * * @return a boolean which is either true or false */ bool getBool(); /** * Put a list in random order. * * Since kdelibs 4.11, this function uses a more efficient * algorithm (Fisher-Yates). Therefore, the order of the items in * the randomized list is different from the one in earlier * versions if the same seed value is used for the random * sequence. * * @param list the list whose order will be modified * @note modifies the list in place */ template void randomize(QList &list) { // Fisher-Yates algorithm for (int index = list.count() - 1; index > 0; --index) { const int swapIndex = getInt(index + 1); qSwap(list[index], list[swapIndex]); } } /** * Modulate the random sequence. * * If S(i) is the sequence of numbers that will follow * given the current state after calling modulate(i), * then S(i) != S(j) for i != j and * S(i) == S(j) for i == j. * * This can be useful in game situation where "undo" restores * the state of the random sequence. If the game modulates the * random sequence with the move chosen by the player, the * random sequence will be identical whenever the player "redo"-s * his or hers original move, but different when the player * chooses another move. * * With this scenario "undo" can no longer be used to repeat a * certain move over and over again until the computer reacts * with a favorable response or to predict the response for a * certain move based on the response to another move. * @param i the sequence identified */ void modulate(int i); private: class Private; Private *const d; }; #endif diff --git a/src/lib/text/kmacroexpander.cpp b/src/lib/text/kmacroexpander.cpp index 16c1579..c5d1c8c 100644 --- a/src/lib/text/kmacroexpander.cpp +++ b/src/lib/text/kmacroexpander.cpp @@ -1,403 +1,403 @@ /* This file is part of the KDE libraries Copyright (c) 2002-2003 Oswald Buddenhagen Copyright (c) 2003 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmacroexpander_p.h" -#include -#include +#include +#include KMacroExpanderBase::KMacroExpanderBase(QChar c) : d(new KMacroExpanderBasePrivate(c)) { } KMacroExpanderBase::~KMacroExpanderBase() { delete d; } void KMacroExpanderBase::setEscapeChar(QChar c) { d->escapechar = c; } QChar KMacroExpanderBase::escapeChar() const { return d->escapechar; } void KMacroExpanderBase::expandMacros(QString &str) { int pos; int len; ushort ec = d->escapechar.unicode(); QStringList rst; QString rsts; for (pos = 0; pos < str.length();) { if (ec != 0) { if (str.unicode()[pos].unicode() != ec) { goto nohit; } if (!(len = expandEscapedMacro(str, pos, rst))) { goto nohit; } } else { if (!(len = expandPlainMacro(str, pos, rst))) { goto nohit; } } if (len < 0) { pos -= len; continue; } rsts = rst.join(QLatin1Char(' ')); rst.clear(); str.replace(pos, len, rsts); pos += rsts.length(); continue; nohit: pos++; } } bool KMacroExpanderBase::expandMacrosShellQuote(QString &str) { int pos = 0; return expandMacrosShellQuote(str, pos) && pos == str.length(); } int KMacroExpanderBase::expandPlainMacro(const QString &, int, QStringList &) { qFatal("KMacroExpanderBase::expandPlainMacro called!"); return 0; } int KMacroExpanderBase::expandEscapedMacro(const QString &, int, QStringList &) { qFatal("KMacroExpanderBase::expandEscapedMacro called!"); return 0; } ////////////////////////////////////////////////// template class KMacroMapExpander : public KMacroExpanderBase { public: KMacroMapExpander(const QHash &map, QChar c = QLatin1Char('%')) : KMacroExpanderBase(c), macromap(map) {} protected: int expandPlainMacro(const QString &str, int pos, QStringList &ret) Q_DECL_OVERRIDE; int expandEscapedMacro(const QString &str, int pos, QStringList &ret) Q_DECL_OVERRIDE; private: QHash macromap; }; static QStringList &operator+=(QStringList &s, const QString &n) { s << n; return s; } //////// static bool isIdentifier(ushort c) { return c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); } //////// template class KMacroMapExpander : public KMacroExpanderBase { public: KMacroMapExpander(const QHash &map, QChar c = QLatin1Char('%')) : KMacroExpanderBase(c), macromap(map) {} protected: int expandPlainMacro(const QString &str, int pos, QStringList &ret) Q_DECL_OVERRIDE; int expandEscapedMacro(const QString &str, int pos, QStringList &ret) Q_DECL_OVERRIDE; private: QHash macromap; }; template int KMacroMapExpander::expandPlainMacro(const QString &str, int pos, QStringList &ret) { typename QHash::const_iterator it = macromap.constFind(str.unicode()[pos]); if (it != macromap.constEnd()) { ret += it.value(); return 1; } return 0; } template int KMacroMapExpander::expandEscapedMacro(const QString &str, int pos, QStringList &ret) { if (str.length() <= pos + 1) { return 0; } if (str.unicode()[pos + 1] == escapeChar()) { ret += QString(escapeChar()); return 2; } typename QHash::const_iterator it = macromap.constFind(str.unicode()[pos + 1]); if (it != macromap.constEnd()) { ret += it.value(); return 2; } return 0; } template class KMacroMapExpander : public KMacroExpanderBase { public: KMacroMapExpander(const QHash &map, QChar c = QLatin1Char('%')) : KMacroExpanderBase(c), macromap(map) {} protected: int expandPlainMacro(const QString &str, int pos, QStringList &ret) Q_DECL_OVERRIDE; int expandEscapedMacro(const QString &str, int pos, QStringList &ret) Q_DECL_OVERRIDE; private: QHash macromap; }; template int KMacroMapExpander::expandPlainMacro(const QString &str, int pos, QStringList &ret) { if (pos && isIdentifier(str.unicode()[pos - 1].unicode())) { return 0; } int sl; for (sl = 0; isIdentifier(str.unicode()[pos + sl].unicode()); sl++) ; if (!sl) { return 0; } typename QHash::const_iterator it = macromap.constFind(str.mid(pos, sl)); if (it != macromap.constEnd()) { ret += it.value(); return sl; } return 0; } template int KMacroMapExpander::expandEscapedMacro(const QString &str, int pos, QStringList &ret) { if (str.length() <= pos + 1) { return 0; } if (str.unicode()[pos + 1] == escapeChar()) { ret += QString(escapeChar()); return 2; } int sl, rsl, rpos; if (str.unicode()[pos + 1].unicode() == '{') { rpos = pos + 2; if ((sl = str.indexOf(QLatin1Char('}'), rpos)) < 0) { return 0; } sl -= rpos; rsl = sl + 3; } else { rpos = pos + 1; for (sl = 0; isIdentifier(str.unicode()[rpos + sl].unicode()); ++sl) ; rsl = sl + 1; } if (!sl) { return 0; } typename QHash::const_iterator it = macromap.constFind(str.mid(rpos, sl)); if (it != macromap.constEnd()) { ret += it.value(); return rsl; } return 0; } //////////// int KCharMacroExpander::expandPlainMacro(const QString &str, int pos, QStringList &ret) { if (expandMacro(str.unicode()[pos], ret)) { return 1; } return 0; } int KCharMacroExpander::expandEscapedMacro(const QString &str, int pos, QStringList &ret) { if (str.length() <= pos + 1) { return 0; } if (str.unicode()[pos + 1] == escapeChar()) { ret += QString(escapeChar()); return 2; } if (expandMacro(str.unicode()[pos + 1], ret)) { return 2; } return 0; } int KWordMacroExpander::expandPlainMacro(const QString &str, int pos, QStringList &ret) { if (pos && isIdentifier(str.unicode()[pos - 1].unicode())) { return 0; } int sl; for (sl = 0; isIdentifier(str.unicode()[pos + sl].unicode()); sl++) ; if (!sl) { return 0; } if (expandMacro(str.mid(pos, sl), ret)) { return sl; } return 0; } int KWordMacroExpander::expandEscapedMacro(const QString &str, int pos, QStringList &ret) { if (str.length() <= pos + 1) { return 0; } if (str.unicode()[pos + 1] == escapeChar()) { ret += QString(escapeChar()); return 2; } int sl, rsl, rpos; if (str.unicode()[pos + 1].unicode() == '{') { rpos = pos + 2; if ((sl = str.indexOf(QLatin1Char('}'), rpos)) < 0) { return 0; } sl -= rpos; rsl = sl + 3; } else { rpos = pos + 1; for (sl = 0; isIdentifier(str.unicode()[rpos + sl].unicode()); ++sl) ; rsl = sl + 1; } if (!sl) { return 0; } if (expandMacro(str.mid(rpos, sl), ret)) { return rsl; } return 0; } //////////// template inline QString TexpandMacros(const QString &ostr, const QHash &map, QChar c) { QString str(ostr); KMacroMapExpander kmx(map, c); kmx.expandMacros(str); return str; } template inline QString TexpandMacrosShellQuote(const QString &ostr, const QHash &map, QChar c) { QString str(ostr); KMacroMapExpander kmx(map, c); if (!kmx.expandMacrosShellQuote(str)) { return QString(); } return str; } // public API namespace KMacroExpander { QString expandMacros(const QString &ostr, const QHash &map, QChar c) { return TexpandMacros(ostr, map, c); } QString expandMacrosShellQuote(const QString &ostr, const QHash &map, QChar c) { return TexpandMacrosShellQuote(ostr, map, c); } QString expandMacros(const QString &ostr, const QHash &map, QChar c) { return TexpandMacros(ostr, map, c); } QString expandMacrosShellQuote(const QString &ostr, const QHash &map, QChar c) { return TexpandMacrosShellQuote(ostr, map, c); } QString expandMacros(const QString &ostr, const QHash &map, QChar c) { return TexpandMacros(ostr, map, c); } QString expandMacrosShellQuote(const QString &ostr, const QHash &map, QChar c) { return TexpandMacrosShellQuote(ostr, map, c); } QString expandMacros(const QString &ostr, const QHash &map, QChar c) { return TexpandMacros(ostr, map, c); } QString expandMacrosShellQuote(const QString &ostr, const QHash &map, QChar c) { return TexpandMacrosShellQuote(ostr, map, c); } } // namespace diff --git a/src/lib/text/kmacroexpander.h b/src/lib/text/kmacroexpander.h index 13c6252..1a795f5 100644 --- a/src/lib/text/kmacroexpander.h +++ b/src/lib/text/kmacroexpander.h @@ -1,412 +1,412 @@ /* This file is part of the KDE libraries Copyright (c) 2002-2003 Oswald Buddenhagen Copyright (c) 2003 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KMACROEXPANDER_H #define KMACROEXPANDER_H #include -#include +#include class QString; class QStringList; template class QHash; class KMacroExpanderBasePrivate; /** * \class KMacroExpanderBase kmacroexpander.h * * Abstract base class for the worker classes behind the KMacroExpander namespace * and the KCharMacroExpander and KWordMacroExpander classes. * * @author Oswald Buddenhagen */ class KCOREADDONS_EXPORT KMacroExpanderBase { public: /** * Constructor. * @param c escape char indicating start of macros, or QChar::null for none */ explicit KMacroExpanderBase(QChar c = QLatin1Char('%')); /** * Destructor. */ virtual ~KMacroExpanderBase(); /** * Perform safe macro expansion (substitution) on a string. * * @param str the string in which macros are expanded in-place */ void expandMacros(QString &str); // TODO: This documentation is relevant for end-users. Where to put it? /** * Perform safe macro expansion (substitution) on a string for use * in shell commands. * *

*NIX notes

* * Explicitly supported shell constructs: * \ '' "" $'' $"" {} () $(()) ${} $() `` * * Implicitly supported shell constructs: * (()) * * Unsupported shell constructs that will cause problems: * Shortened "case $v in pat)" syntax. Use * "case $v in (pat)" instead. * * The rest of the shell (incl. bash) syntax is simply ignored, * as it is not expected to cause problems. * * Note that bash contains a bug which makes macro expansion within * double quoted substitutions ("${VAR:-%macro}") inherently * insecure. * * For security reasons, @em never put expandos in command line arguments * that are shell commands by themselves - * "sh -c 'foo \%f'" is taboo. * "file=\%f sh -c 'foo "$file"'" is OK. * *

Windows notes

* * All quoting syntax supported by KShell is supported here as well. * Additionally, command grouping via parentheses is recognized - note * however, that the parser is much stricter about unquoted parentheses * than cmd itself. * The rest of the cmd syntax is simply ignored, as it is not expected * to cause problems - do not use commands that embed other commands, * though - "for /f ..." is taboo. * * @param str the string in which macros are expanded in-place * @param pos the position inside the string at which parsing/substitution * should start, and upon exit where processing stopped * @return false if the string could not be parsed and therefore no safe * substitution was possible. Note that macros will have been processed * up to the point where the error occurred. An unmatched closing paren * or brace outside any shell construct is @em not an error (unlike in * the function below), but still prematurely terminates processing. */ bool expandMacrosShellQuote(QString &str, int &pos); /** * Same as above, but always starts at position 0, and unmatched closing * parens and braces are treated as errors. */ bool expandMacrosShellQuote(QString &str); /** * Set the macro escape character. * @param c escape char indicating start of macros, or QChar::null if none */ void setEscapeChar(QChar c); /** * Obtain the macro escape character. * @return escape char indicating start of macros, or QChar::null if none */ QChar escapeChar() const; protected: /** * This function is called for every single char within the string if * the escape char is QChar::null. It should determine whether the * string starting at @p pos within @p str is a valid macro and return * the substitution value for it if so. * @param str the input string * @param pos the offset within @p str * @param ret return value: the string to substitute for the macro * @return If greater than zero, the number of chars at @p pos in @p str * to substitute with @p ret (i.e., a valid macro was found). If less * than zero, subtract this value from @p pos (to skip a macro, i.e., * substitute it with itself). If zero, no macro starts at @p pos. */ virtual int expandPlainMacro(const QString &str, int pos, QStringList &ret); /** * This function is called every time the escape char is found if it is * not QChar::null. It should determine whether the * string starting at @p pos witin @p str is a valid macro and return * the substitution value for it if so. * @param str the input string * @param pos the offset within @p str. Note that this is the position of * the occurrence of the escape char * @param ret return value: the string to substitute for the macro * @return If greater than zero, the number of chars at @p pos in @p str * to substitute with @p ret (i.e., a valid macro was found). If less * than zero, subtract this value from @p pos (to skip a macro, i.e., * substitute it with itself). If zero, scanning continues as if no * escape char was encountered at all. */ virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret); private: KMacroExpanderBasePrivate *const d; }; /** * \class KWordMacroExpander kmacroexpander.h * * Abstract base class for simple word macro substitutors. Use this instead of * the functions in the KMacroExpander namespace if speculatively pre-filling * the substitution map would be too expensive. * * A typical application: * * \code * class MyClass { * ... * private: * QString m_str; * ... * friend class MyExpander; * }; * * class MyExpander : public KWordMacroExpander { * public: * MyExpander( MyClass *_that ) : KWordMacroExpander(), that( _that ) {} * protected: * virtual bool expandMacro( const QString &str, QStringList &ret ); * private: * MyClass *that; * }; * * bool MyExpander::expandMacro( const QString &str, QStringList &ret ) * { * if (str == "macro") { * ret += complexOperation( that->m_str ); * return true; * } * return false; * } * * ... MyClass::...(...) * { * QString str; * ... * MyExpander mx( this ); * mx.expandMacrosShellQuote( str ); * ... * } * \endcode * * Alternatively MyClass could inherit from KWordMacroExpander directly. * * @author Oswald Buddenhagen */ class KCOREADDONS_EXPORT KWordMacroExpander : public KMacroExpanderBase { public: /** * Constructor. * @param c escape char indicating start of macros, or QChar::null for none */ explicit KWordMacroExpander(QChar c = QLatin1Char('%')) : KMacroExpanderBase(c) {} protected: /** \internal Not to be called or reimplemented. */ int expandPlainMacro(const QString &str, int pos, QStringList &ret) Q_DECL_OVERRIDE; /** \internal Not to be called or reimplemented. */ int expandEscapedMacro(const QString &str, int pos, QStringList &ret) Q_DECL_OVERRIDE; /** * Return substitution list @p ret for string macro @p str. * @param str the macro to expand * @param ret return variable reference. It is guaranteed to be empty * when expandMacro is entered. * @return @c true iff @p chr was a recognized macro name */ virtual bool expandMacro(const QString &str, QStringList &ret) = 0; }; /** * \class KCharMacroExpander kmacroexpander.h * * Abstract base class for single char macro substitutors. Use this instead of * the functions in the KMacroExpander namespace if speculatively pre-filling * the substitution map would be too expensive. * * See KWordMacroExpander for a sample application. * * @author Oswald Buddenhagen */ class KCOREADDONS_EXPORT KCharMacroExpander : public KMacroExpanderBase { public: /** * Constructor. * @param c escape char indicating start of macros, or QChar::null for none */ explicit KCharMacroExpander(QChar c = QLatin1Char('%')) : KMacroExpanderBase(c) {} protected: /** \internal Not to be called or reimplemented. */ int expandPlainMacro(const QString &str, int pos, QStringList &ret) Q_DECL_OVERRIDE; /** \internal Not to be called or reimplemented. */ int expandEscapedMacro(const QString &str, int pos, QStringList &ret) Q_DECL_OVERRIDE; /** * Return substitution list @p ret for single-character macro @p chr. * @param chr the macro to expand * @param ret return variable reference. It is guaranteed to be empty * when expandMacro is entered. * @return @c true iff @p chr was a recognized macro name */ virtual bool expandMacro(QChar chr, QStringList &ret) = 0; }; /** * A group of functions providing macro expansion (substitution) in strings, * optionally with quoting appropriate for shell execution. */ namespace KMacroExpander { /** * Perform safe macro expansion (substitution) on a string. * The escape char must be quoted with itself to obtain its literal * representation in the resulting string. * * @param str The string to expand * @param map map with substitutions * @param c escape char indicating start of macro, or QChar::null if none * @return the string with all valid macros expanded * * \code * // Code example * QHash map; * map.insert('u', "/tmp/myfile.txt"); * map.insert('n', "My File"); * QString s = "%% Title: %u:%n"; * s = KMacroExpander::expandMacros(s, map); * // s is now "% Title: /tmp/myfile.txt:My File"; * \endcode */ KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); /** * Perform safe macro expansion (substitution) on a string for use * in shell commands. * The escape char must be quoted with itself to obtain its literal * representation in the resulting string. * * @param str The string to expand * @param map map with substitutions * @param c escape char indicating start of macro, or QChar::null if none * @return the string with all valid macros expanded, or a null string * if a shell syntax error was detected in the command * * \code * // Code example * QHash map; * map.insert('u', "/tmp/myfile.txt"); * map.insert('n', "My File"); * QString s = "kedit --caption %n %u"; * s = KMacroExpander::expandMacrosShellQuote(s, map); * // s is now "kedit --caption 'My File' '/tmp/myfile.txt'"; * system(QFile::encodeName(s)); * \endcode */ KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); /** * Perform safe macro expansion (substitution) on a string. * The escape char must be quoted with itself to obtain its literal * representation in the resulting string. * Macro names can consist of chars in the range [A-Za-z0-9_]; * use braces to delimit macros from following words starting * with these chars, or to use other chars for macro names. * * @param str The string to expand * @param map map with substitutions * @param c escape char indicating start of macro, or QChar::null if none * @return the string with all valid macros expanded * * \code * // Code example * QHash map; * map.insert("url", "/tmp/myfile.txt"); * map.insert("name", "My File"); * QString s = "Title: %{url}-%name"; * s = KMacroExpander::expandMacros(s, map); * // s is now "Title: /tmp/myfile.txt-My File"; * \endcode */ KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); /** * Perform safe macro expansion (substitution) on a string for use * in shell commands. See KMacroExpanderBase::expandMacrosShellQuote() * for the exact semantics. * The escape char must be quoted with itself to obtain its literal * representation in the resulting string. * Macro names can consist of chars in the range [A-Za-z0-9_]; * use braces to delimit macros from following words starting * with these chars, or to use other chars for macro names. * * @param str The string to expand * @param map map with substitutions * @param c escape char indicating start of macro, or QChar::null if none * @return the string with all valid macros expanded, or a null string * if a shell syntax error was detected in the command * * \code * // Code example * QHash map; * map.insert("url", "/tmp/myfile.txt"); * map.insert("name", "My File"); * QString s = "kedit --caption %name %{url}"; * s = KMacroExpander::expandMacrosShellQuote(s, map); * // s is now "kedit --caption 'My File' '/tmp/myfile.txt'"; * system(QFile::encodeName(s)); * \endcode */ KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); /** * Same as above, except that the macros expand to string lists that * are simply join(" ")ed together. */ KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); KCOREADDONS_EXPORT QString expandMacros(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); /** * Same as above, except that the macros expand to string lists. * If the macro appears inside a quoted string, the list is simply * join(" ")ed together; otherwise every element expands to a separate * quoted string. */ KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); KCOREADDONS_EXPORT QString expandMacrosShellQuote(const QString &str, const QHash &map, QChar c = QLatin1Char('%')); } #endif /* KMACROEXPANDER_H */ diff --git a/src/lib/text/kmacroexpander_unix.cpp b/src/lib/text/kmacroexpander_unix.cpp index a111035..d8ad103 100644 --- a/src/lib/text/kmacroexpander_unix.cpp +++ b/src/lib/text/kmacroexpander_unix.cpp @@ -1,260 +1,260 @@ /* This file is part of the KDE libraries Copyright (c) 2002-2003 Oswald Buddenhagen Copyright (c) 2003 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmacroexpander_p.h" -#include -#include -#include +#include +#include +#include namespace KMacroExpander { enum Quoting { noquote, singlequote, doublequote, dollarquote, paren, subst, group, math }; typedef struct { Quoting current; bool dquote; } State; typedef struct { QString str; int pos; } Save; } using namespace KMacroExpander; #pragma message("TODO: Import these methods into Qt") inline static bool isSpecial(QChar cUnicode) { static const uchar iqm[] = { 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 }; // 0-32 \'"$`<>|;&(){}*?#!~[] uint c = cUnicode.unicode(); return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); } static QString quoteArg(const QString &arg) { if (!arg.length()) { return QString::fromLatin1("''"); } for (int i = 0; i < arg.length(); i++) if (isSpecial(arg.unicode()[i])) { QChar q(QLatin1Char('\'')); return QString(arg).replace(q, QLatin1String("'\\''")).prepend(q).append(q); } return arg; } static QString joinArgs(const QStringList &args) { QString ret; for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) { if (!ret.isEmpty()) { ret.append(QLatin1Char(' ')); } ret.append(quoteArg(*it)); } return ret; } bool KMacroExpanderBase::expandMacrosShellQuote(QString &str, int &pos) { int len; int pos2; ushort ec = d->escapechar.unicode(); State state = { noquote, false }; QStack sstack; QStack ostack; QStringList rst; QString rsts; while (pos < str.length()) { ushort cc = str.unicode()[pos].unicode(); if (ec != 0) { if (cc != ec) { goto nohit; } if (!(len = expandEscapedMacro(str, pos, rst))) { goto nohit; } } else { if (!(len = expandPlainMacro(str, pos, rst))) { goto nohit; } } if (len < 0) { pos -= len; continue; } if (state.dquote) { rsts = rst.join(QLatin1String(" ")); rsts.replace(QRegExp(QLatin1String("([$`\"\\\\])")), QLatin1String("\\\\1")); } else if (state.current == dollarquote) { rsts = rst.join(QLatin1String(" ")); rsts.replace(QRegExp(QLatin1String("(['\\\\])")), QLatin1String("\\\\1")); } else if (state.current == singlequote) { rsts = rst.join(QLatin1String(" ")); rsts.replace(QLatin1Char('\''), QLatin1String("'\\''")); } else { if (rst.isEmpty()) { str.remove(pos, len); continue; } else { rsts = joinArgs(rst); } } rst.clear(); str.replace(pos, len, rsts); pos += rsts.length(); continue; nohit: if (state.current == singlequote) { if (cc == '\'') { state = sstack.pop(); } } else if (cc == '\\') { // always swallow the char -> prevent anomalies due to expansion pos += 2; continue; } else if (state.current == dollarquote) { if (cc == '\'') { state = sstack.pop(); } } else if (cc == '$') { cc = str.unicode()[++pos].unicode(); if (cc == '(') { sstack.push(state); if (str.unicode()[pos + 1].unicode() == '(') { Save sav = { str, pos + 2 }; ostack.push(sav); state.current = math; pos += 2; continue; } else { state.current = paren; state.dquote = false; } } else if (cc == '{') { sstack.push(state); state.current = subst; } else if (!state.dquote) { if (cc == '\'') { sstack.push(state); state.current = dollarquote; } else if (cc == '"') { sstack.push(state); state.current = doublequote; state.dquote = true; } } // always swallow the char -> prevent anomalies due to expansion } else if (cc == '`') { str.replace(pos, 1, QLatin1String("$( ")); // add space -> avoid creating $(( pos2 = pos += 3; for (;;) { if (pos2 >= str.length()) { pos = pos2; return false; } cc = str.unicode()[pos2].unicode(); if (cc == '`') { break; } if (cc == '\\') { cc = str.unicode()[++pos2].unicode(); if (cc == '$' || cc == '`' || cc == '\\' || (cc == '"' && state.dquote)) { str.remove(pos2 - 1, 1); continue; } } pos2++; } str[pos2] = QLatin1Char(')'); sstack.push(state); state.current = paren; state.dquote = false; continue; } else if (state.current == doublequote) { if (cc == '"') { state = sstack.pop(); } } else if (cc == '\'') { if (!state.dquote) { sstack.push(state); state.current = singlequote; } } else if (cc == '"') { if (!state.dquote) { sstack.push(state); state.current = doublequote; state.dquote = true; } } else if (state.current == subst) { if (cc == '}') { state = sstack.pop(); } } else if (cc == ')') { if (state.current == math) { if (str.unicode()[pos + 1].unicode() == ')') { state = sstack.pop(); pos += 2; } else { // false hit: the $(( was a $( ( in fact // ash does not care, but bash does pos = ostack.top().pos; str = ostack.top().str; ostack.pop(); state.current = paren; state.dquote = false; sstack.push(state); } continue; } else if (state.current == paren) { state = sstack.pop(); } else { break; } } else if (cc == '}') { if (state.current == KMacroExpander::group) { state = sstack.pop(); } else { break; } } else if (cc == '(') { sstack.push(state); state.current = paren; } else if (cc == '{') { sstack.push(state); state.current = KMacroExpander::group; } pos++; } return sstack.empty(); } diff --git a/src/lib/text/kstringhandler.cpp b/src/lib/text/kstringhandler.cpp index 8437b4f..cd0a837 100644 --- a/src/lib/text/kstringhandler.cpp +++ b/src/lib/text/kstringhandler.cpp @@ -1,361 +1,361 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Ian Zepp (icszepp@islc.net) Copyright (C) 2006 by Dominic Battre Copyright (C) 2006 by Martin Pool This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kstringhandler.h" #include // random() -#include // for the word ranges -#include -#include -#include +#include // for the word ranges +#include +#include +#include // // Capitalization routines // QString KStringHandler::capwords(const QString &text) { if (text.isEmpty()) { return text; } const QString strippedText = text.trimmed(); const QString space = QString(QLatin1Char(' ')); const QStringList words = capwords(strippedText.split(space)); QString result = text; result.replace(strippedText, words.join(space)); return result; } QStringList KStringHandler::capwords(const QStringList &list) { QStringList tmp = list; for (QStringList::Iterator it = tmp.begin(); it != tmp.end(); ++it) { *it = (*it)[ 0 ].toUpper() + (*it).midRef(1); } return tmp; } QString KStringHandler::lsqueeze(const QString &str, int maxlen) { if (str.length() > maxlen) { int part = maxlen - 3; return QStringLiteral("...") + str.rightRef(part); } else { return str; } } QString KStringHandler::csqueeze(const QString &str, int maxlen) { if (str.length() > maxlen && maxlen > 3) { const int part = (maxlen - 3) / 2; return str.leftRef(part) + QStringLiteral("...") + str.rightRef(part); } else { return str; } } QString KStringHandler::rsqueeze(const QString &str, int maxlen) { if (str.length() > maxlen) { int part = maxlen - 3; return str.leftRef(part) + QStringLiteral("..."); } else { return str; } } QStringList KStringHandler::perlSplit(const QString &sep, const QString &s, int max) { bool ignoreMax = 0 == max; QStringList l; int searchStart = 0; int tokenStart = s.indexOf(sep, searchStart); while (-1 != tokenStart && (ignoreMax || l.count() < max - 1)) { if (!s.midRef(searchStart, tokenStart - searchStart).isEmpty()) { l << s.mid(searchStart, tokenStart - searchStart); } searchStart = tokenStart + sep.length(); tokenStart = s.indexOf(sep, searchStart); } if (!s.midRef(searchStart, s.length() - searchStart).isEmpty()) { l << s.mid(searchStart, s.length() - searchStart); } return l; } QStringList KStringHandler::perlSplit(const QChar &sep, const QString &s, int max) { bool ignoreMax = 0 == max; QStringList l; int searchStart = 0; int tokenStart = s.indexOf(sep, searchStart); while (-1 != tokenStart && (ignoreMax || l.count() < max - 1)) { if (!s.midRef(searchStart, tokenStart - searchStart).isEmpty()) { l << s.mid(searchStart, tokenStart - searchStart); } searchStart = tokenStart + 1; tokenStart = s.indexOf(sep, searchStart); } if (!s.midRef(searchStart, s.length() - searchStart).isEmpty()) { l << s.mid(searchStart, s.length() - searchStart); } return l; } QStringList KStringHandler::perlSplit(const QRegExp &sep, const QString &s, int max) { bool ignoreMax = 0 == max; QStringList l; int searchStart = 0; int tokenStart = sep.indexIn(s, searchStart); int len = sep.matchedLength(); while (-1 != tokenStart && (ignoreMax || l.count() < max - 1)) { if (!s.midRef(searchStart, tokenStart - searchStart).isEmpty()) { l << s.mid(searchStart, tokenStart - searchStart); } searchStart = tokenStart + len; tokenStart = sep.indexIn(s, searchStart); len = sep.matchedLength(); } if (!s.midRef(searchStart, s.length() - searchStart).isEmpty()) { l << s.mid(searchStart, s.length() - searchStart); } return l; } QString KStringHandler::tagUrls(const QString &text) { /*static*/ QRegExp urlEx(QStringLiteral("(www\\.(?!\\.)|(fish|(f|ht)tp(|s))://)[\\d\\w\\./,:_~\\?=&;#@\\-\\+\\%\\$]+[\\d\\w/]")); QString richText(text); int urlPos = 0, urlLen; while ((urlPos = urlEx.indexIn(richText, urlPos)) >= 0) { urlLen = urlEx.matchedLength(); QString href = richText.mid(urlPos, urlLen); // Qt doesn't support (?<=pattern) so we do it here if ((urlPos > 0) && richText[urlPos - 1].isLetterOrNumber()) { urlPos++; continue; } // Don't use QString::arg since %01, %20, etc could be in the string QString anchor = QStringLiteral("") + href + QStringLiteral(""); richText.replace(urlPos, urlLen, anchor); urlPos += anchor.length(); } return richText; } QString KStringHandler::obscure(const QString &str) { QString result; const QChar *unicode = str.unicode(); for (int i = 0; i < str.length(); ++i) // yes, no typo. can't encode ' ' or '!' because // they're the unicode BOM. stupid scrambling. stupid. result += (unicode[ i ].unicode() <= 0x21) ? unicode[ i ] : QChar(0x1001F - unicode[ i ].unicode()); return result; } bool KStringHandler::isUtf8(const char *buf) { int i, n; unsigned char c; bool gotone = false; if (!buf) { return true; // whatever, just don't crash } #define F 0 /* character never appears in text */ #define T 1 /* character appears in plain ASCII text */ #define I 2 /* character appears in ISO-8859 text */ #define X 3 /* character appears in non-ISO extended ASCII (Mac, IBM PC) */ static const unsigned char text_chars[256] = { /* BEL BS HT LF FF CR */ F, F, F, F, F, F, F, T, T, T, T, F, T, T, F, F, /* 0x0X */ /* ESC */ F, F, F, F, F, F, F, F, F, F, F, T, F, F, F, F, /* 0x1X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x2X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x3X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x4X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x5X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, /* 0x6X */ T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, F, /* 0x7X */ /* NEL */ X, X, X, X, X, T, X, X, X, X, X, X, X, X, X, X, /* 0x8X */ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 0x9X */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xaX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xbX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xcX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xdX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, /* 0xeX */ I, I, I, I, I, I, I, I, I, I, I, I, I, I, I, I /* 0xfX */ }; /* *ulen = 0; */ for (i = 0; (c = buf[i]); ++i) { if ((c & 0x80) == 0) { /* 0xxxxxxx is plain ASCII */ /* * Even if the whole file is valid UTF-8 sequences, * still reject it if it uses weird control characters. */ if (text_chars[c] != T) { return false; } } else if ((c & 0x40) == 0) { /* 10xxxxxx never 1st byte */ return false; } else { /* 11xxxxxx begins UTF-8 */ int following; if ((c & 0x20) == 0) { /* 110xxxxx */ following = 1; } else if ((c & 0x10) == 0) { /* 1110xxxx */ following = 2; } else if ((c & 0x08) == 0) { /* 11110xxx */ following = 3; } else if ((c & 0x04) == 0) { /* 111110xx */ following = 4; } else if ((c & 0x02) == 0) { /* 1111110x */ following = 5; } else { return false; } for (n = 0; n < following; ++n) { i++; if (!(c = buf[i])) { goto done; } if ((c & 0x80) == 0 || (c & 0x40)) { return false; } } gotone = true; } } done: return gotone; /* don't claim it's UTF-8 if it's all 7-bit */ } #undef F #undef T #undef I #undef X QString KStringHandler::from8Bit(const char *str) { if (!str) { return QString(); } if (!*str) { static const QLatin1String emptyString(""); return emptyString; } return KStringHandler::isUtf8(str) ? QString::fromUtf8(str) : QString::fromLocal8Bit(str); } QString KStringHandler::preProcessWrap(const QString &text) { const QChar zwsp(0x200b); QString result; result.reserve(text.length()); for (int i = 0; i < text.length(); i++) { const QChar c = text[i]; bool openingParens = (c == QLatin1Char('(') || c == QLatin1Char('{') || c == QLatin1Char('[')); bool singleQuote = (c == QLatin1Char('\'')); bool closingParens = (c == QLatin1Char(')') || c == QLatin1Char('}') || c == QLatin1Char(']')); bool breakAfter = (closingParens || c.isPunct() || c.isSymbol()); bool nextIsSpace = (i == (text.length() - 1) || text[i + 1].isSpace()); bool prevIsSpace = (i == 0 || text[i - 1].isSpace() || result[result.length() - 1] == zwsp); // Provide a breaking opportunity before opening parenthesis if (openingParens && !prevIsSpace) { result += zwsp; } // Provide a word joiner before the single quote if (singleQuote && !prevIsSpace) { result += QChar(0x2060); } result += c; if (breakAfter && !openingParens && !nextIsSpace && !singleQuote) { result += zwsp; } } return result; } int KStringHandler::logicalLength(const QString& text) { int length = 0; auto chrs = text.toUcs4(); for (auto chr : chrs) { auto script = QChar::script(chr); if (script == QChar::Script_Han || script == QChar::Script_Hangul || script == QChar::Script_Hiragana || script == QChar::Script_Katakana || script == QChar::Script_Yi || QChar::isHighSurrogate(chr)) { length += 2; } else { length += 1; } } return length; } diff --git a/src/lib/text/kstringhandler.h b/src/lib/text/kstringhandler.h index b1bbb35..b428ac6 100644 --- a/src/lib/text/kstringhandler.h +++ b/src/lib/text/kstringhandler.h @@ -1,229 +1,229 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Ian Zepp (icszepp@islc.net) Copyright (C) 2000 Rik Hemsley (rikkus) Copyright (C) 2006 by Dominic Battre Copyright (C) 2006 by Martin Pool This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSTRINGHANDLER_H #define KSTRINGHANDLER_H #include -#include +#include class QChar; class QRegExp; class QString; class QStringList; /** * This namespace contains utility functions for handling strings. * * The functions here are intended to provide an easy way to * cut/slice/splice words inside sentences in whatever order desired. * While the main focus of KStringHandler is words (ie characters * separated by spaces/tabs), the two core functions here (split() * and join()) will allow you to use any character as a separator * This will make it easy to redefine what a 'word' means in the * future if needed. * * The function names and calling styles are based on python and mIRC's * scripting support. * * The ranges are a fairly powerful way of getting/stripping words from * a string. These ranges function, for the large part, as they would in * python. See the word(const QString&, int) and remword(const QString&, int) * functions for more detail. * * The methods here are completely stateless. All strings are cut * on the fly and returned as new qstrings/qstringlists. * * @short Namespace for manipulating words and sentences in strings * @author Ian Zepp * @see KShell */ namespace KStringHandler { /** Capitalizes each word in the string * "hello there" becomes "Hello There" (string) * @param text the text to capitalize * @return the resulting string */ KCOREADDONS_EXPORT QString capwords(const QString &text); /** Capitalizes each word in the list * [hello, there] becomes [Hello, There] (list) * @param list the list to capitalize * @return the resulting list */ KCOREADDONS_EXPORT QStringList capwords(const QStringList &list); /** Substitute characters at the beginning of a string by "...". * @param str is the string to modify * @param maxlen is the maximum length the modified string will have * If the original string is shorter than "maxlen", it is returned verbatim * @return the modified string */ KCOREADDONS_EXPORT QString lsqueeze(const QString &str, int maxlen = 40); /** Substitute characters at the middle of a string by "...". * @param str is the string to modify * @param maxlen is the maximum length the modified string will have * If the original string is shorter than "maxlen", it is returned verbatim * @return the modified string */ KCOREADDONS_EXPORT QString csqueeze(const QString &str, int maxlen = 40); /** Substitute characters at the end of a string by "...". * @param str is the string to modify * @param maxlen is the maximum length the modified string will have * If the original string is shorter than "maxlen", it is returned verbatim * @return the modified string */ KCOREADDONS_EXPORT QString rsqueeze(const QString &str, int maxlen = 40); /** * Split a QString into a QStringList in a similar fashion to the static * QStringList function in Qt, except you can specify a maximum number * of tokens. If max is specified (!= 0) then only that number of tokens * will be extracted. The final token will be the remainder of the string. * * Example: * \code * perlSplit("__", "some__string__for__you__here", 4) * QStringList contains: "some", "string", "for", "you__here" * \endcode * * @param sep is the string to use to delimit s. * @param s is the input string * @param max is the maximum number of extractions to perform, or 0. * @return A QStringList containing tokens extracted from s. */ KCOREADDONS_EXPORT QStringList perlSplit(const QString &sep, const QString &s, int max = 0); /** * Split a QString into a QStringList in a similar fashion to the static * QStringList function in Qt, except you can specify a maximum number * of tokens. If max is specified (!= 0) then only that number of tokens * will be extracted. The final token will be the remainder of the string. * * Example: * \code * perlSplit(' ', "kparts reaches the parts other parts can't", 3) * QStringList contains: "kparts", "reaches", "the parts other parts can't" * \endcode * * @param sep is the character to use to delimit s. * @param s is the input string * @param max is the maximum number of extractions to perform, or 0. * @return A QStringList containing tokens extracted from s. */ KCOREADDONS_EXPORT QStringList perlSplit(const QChar &sep, const QString &s, int max = 0); /** * Split a QString into a QStringList in a similar fashion to the static * QStringList function in Qt, except you can specify a maximum number * of tokens. If max is specified (!= 0) then only that number of tokens * will be extracted. The final token will be the remainder of the string. * * Example: * \code * perlSplit(QRegExp("[! ]"), "Split me up ! I'm bored ! OK ?", 3) * QStringList contains: "Split", "me", "up ! I'm bored ! OK ?" * \endcode * * @param sep is the regular expression to use to delimit s. * @param s is the input string * @param max is the maximum number of extractions to perform, or 0. * @return A QStringList containing tokens extracted from s. */ KCOREADDONS_EXPORT QStringList perlSplit(const QRegExp &sep, const QString &s, int max = 0); /** * This method auto-detects URLs in strings, and adds HTML markup to them * so that richtext or HTML-enabled widgets will display the URL correctly. * @param text the string which may contain URLs * @return the resulting text */ KCOREADDONS_EXPORT QString tagUrls(const QString &text); /** Obscure string by using a simple symmetric encryption. Applying the function to a string obscured by this function will result in the original string. The function can be used to obscure passwords stored to configuration files. Note that this won't give you any more security than preventing that the password is directly copied and pasted. @param str string to be obscured @return obscured string */ KCOREADDONS_EXPORT QString obscure(const QString &str); /** Guess whether a string is UTF8 encoded. @param str the string to check @return true if UTF8. If false, the string is probably in Local8Bit. */ KCOREADDONS_EXPORT bool isUtf8(const char *str); /** Construct QString from a c string, guessing whether it is UTF8- or Local8Bit-encoded. @param str the input string @return the (hopefully correctly guessed) QString representation of @p str @see KEncodingProber */ KCOREADDONS_EXPORT QString from8Bit(const char *str); /** Preprocesses the given string in order to provide additional line breaking opportunities for QTextLayout. This is done by inserting ZWSP (Zero-width space) characters in the string at points that wouldn't normally be considered word boundaries by QTextLayout, but where wrapping the text will produce good results. Examples of such points includes after punctuation signs, underscores and dashes, that aren't followed by spaces. @since 4.4 */ KCOREADDONS_EXPORT QString preProcessWrap(const QString &text); /** Returns the length that reflects the density of information in the text. In general the character from CJK languages are assigned with weight 2, while other Latin characters are assigned with 1. @since 5.41 */ KCOREADDONS_EXPORT int logicalLength(const QString &text); } #endif diff --git a/src/lib/util/kshell.cpp b/src/lib/util/kshell.cpp index 93f28a7..c30d711 100644 --- a/src/lib/util/kshell.cpp +++ b/src/lib/util/kshell.cpp @@ -1,70 +1,70 @@ /* This file is part of the KDE libraries Copyright (c) 2003,2007 Oswald Buddenhagen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kshell.h" #include "kshell_p.h" #include "kuser.h" -#include +#include QString KShell::homeDir(const QString &user) { if (user.isEmpty()) { return QDir::homePath(); } return KUser(user).homeDir(); } QString KShell::joinArgs(const QStringList &args) { QString ret; for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it) { if (!ret.isEmpty()) { ret.append(QLatin1Char(' ')); } ret.append(quoteArg(*it)); } return ret; } #ifdef Q_OS_WIN # define ESCAPE '^' #else # define ESCAPE '\\' #endif QString KShell::tildeExpand(const QString &fname) { if (!fname.isEmpty() && fname[0] == QLatin1Char('~')) { int pos = fname.indexOf(QLatin1Char('/')); if (pos < 0) { return homeDir(fname.mid(1)); } QString ret = homeDir(fname.mid(1, pos - 1)); if (!ret.isNull()) { ret += fname.midRef(pos); } return ret; } else if (fname.length() > 1 && fname[0] == QLatin1Char(ESCAPE) && fname[1] == QLatin1Char('~')) { return fname.mid(1); } return fname; } diff --git a/src/lib/util/kshell_unix.cpp b/src/lib/util/kshell_unix.cpp index 6b71336..6089b0c 100644 --- a/src/lib/util/kshell_unix.cpp +++ b/src/lib/util/kshell_unix.cpp @@ -1,321 +1,321 @@ /* This file is part of the KDE libraries Copyright (c) 2003,2007 Oswald Buddenhagen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kshell.h" #include "kshell_p.h" #include -#include -#include +#include +#include static int fromHex(QChar cUnicode) { char c = cUnicode.toLatin1(); if (c >= '0' && c <= '9') { return c - '0'; } else if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } else if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } return -1; } inline static bool isQuoteMeta(QChar cUnicode) { #if 0 // it's not worth it, especially after seeing gcc's asm output ... static const uchar iqm[] = { 0x00, 0x00, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00 }; // \'"$ return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); #else char c = cUnicode.toLatin1(); return c == '\\' || c == '\'' || c == '"' || c == '$'; #endif } inline static bool isMeta(QChar cUnicode) { static const uchar iqm[] = { 0x00, 0x00, 0x00, 0x00, 0xdc, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x38 }; // \'"$`<>|;&(){}*?#[] uint c = cUnicode.unicode(); return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); } QStringList KShell::splitArgs(const QString &args, Options flags, Errors *err) { QStringList ret; bool firstword = flags & AbortOnMeta; for (int pos = 0;;) { QChar c; do { if (pos >= args.length()) { goto okret; } c = args.unicode()[pos++]; } while (c == QLatin1Char(' ')); QString cret; if ((flags & TildeExpand) && c == QLatin1Char('~')) { int opos = pos; for (;; pos++) { if (pos >= args.length()) { break; } c = args.unicode()[pos]; if (c == QLatin1Char('/') || c == QLatin1Char(' ')) { break; } if (isQuoteMeta(c)) { pos = opos; c = QLatin1Char('~'); goto notilde; } if ((flags & AbortOnMeta) && isMeta(c)) { goto metaerr; } } QString ccret = homeDir(args.mid(opos, pos - opos)); if (ccret.isEmpty()) { pos = opos; c = QLatin1Char('~'); goto notilde; } if (pos >= args.length()) { ret += ccret; goto okret; } pos++; if (c == QLatin1Char(' ')) { ret += ccret; firstword = false; continue; } cret = ccret; } // before the notilde label, as a tilde does not match anyway if (firstword) { if (c == QLatin1Char('_') || (c >= QLatin1Char('A') && c <= QLatin1Char('Z')) || (c >= QLatin1Char('a') && c <= QLatin1Char('z'))) { int pos2 = pos; QChar cc; do { if (pos2 >= args.length()) { // Exactly one word ret += args.mid(pos - 1); goto okret; } cc = args.unicode()[pos2++]; } while (cc == QLatin1Char('_') || (cc >= QLatin1Char('A') && cc <= QLatin1Char('Z')) || (cc >= QLatin1Char('a') && cc <= QLatin1Char('z')) || (cc >= QLatin1Char('0') && cc <= QLatin1Char('9'))); if (cc == QLatin1Char('=')) { goto metaerr; } } } notilde: do { if (c == QLatin1Char('\'')) { int spos = pos; do { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; } while (c != QLatin1Char('\'')); cret += args.midRef(spos, pos - spos - 1); } else if (c == QLatin1Char('"')) { for (;;) { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; if (c == QLatin1Char('"')) { break; } if (c == QLatin1Char('\\')) { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; if (c != QLatin1Char('"') && c != QLatin1Char('\\') && !((flags & AbortOnMeta) && (c == QLatin1Char('$') || c == QLatin1Char('`')))) { cret += QLatin1Char('\\'); } } else if ((flags & AbortOnMeta) && (c == QLatin1Char('$') || c == QLatin1Char('`'))) { goto metaerr; } cret += c; } } else if (c == QLatin1Char('$') && pos < args.length() && args.unicode()[pos] == QLatin1Char('\'')) { pos++; for (;;) { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; if (c == QLatin1Char('\'')) { break; } if (c == QLatin1Char('\\')) { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; switch (c.toLatin1()) { case 'a': cret += QLatin1Char('\a'); break; case 'b': cret += QLatin1Char('\b'); break; case 'e': cret += QLatin1Char('\033'); break; case 'f': cret += QLatin1Char('\f'); break; case 'n': cret += QLatin1Char('\n'); break; case 'r': cret += QLatin1Char('\r'); break; case 't': cret += QLatin1Char('\t'); break; case '\\': cret += QLatin1Char('\\'); break; case '\'': cret += QLatin1Char('\''); break; case 'c': if (pos >= args.length()) { goto quoteerr; } cret += args.unicode()[pos++].toLatin1() & 31; break; case 'x': { if (pos >= args.length()) { goto quoteerr; } int hv = fromHex(args.unicode()[pos++]); if (hv < 0) { goto quoteerr; } if (pos < args.length()) { int hhv = fromHex(args.unicode()[pos]); if (hhv > 0) { hv = hv * 16 + hhv; pos++; } cret += QChar(hv); } break; } default: if (c.toLatin1() >= '0' && c.toLatin1() <= '7') { char cAscii = c.toLatin1(); int hv = cAscii - '0'; for (int i = 0; i < 2; i++) { if (pos >= args.length()) { break; } c = args.unicode()[pos]; if (c.toLatin1() < '0' || c.toLatin1() > '7') { break; } hv = hv * 8 + (c.toLatin1() - '0'); pos++; } cret += QChar(hv); } else { cret += QLatin1Char('\\'); cret += c; } break; } } else { cret += c; } } } else { if (c == QLatin1Char('\\')) { if (pos >= args.length()) { goto quoteerr; } c = args.unicode()[pos++]; } else if ((flags & AbortOnMeta) && isMeta(c)) { goto metaerr; } cret += c; } if (pos >= args.length()) { break; } c = args.unicode()[pos++]; } while (c != QLatin1Char(' ')); ret += cret; firstword = false; } okret: if (err) { *err = NoError; } return ret; quoteerr: if (err) { *err = BadQuoting; } return QStringList(); metaerr: if (err) { *err = FoundMeta; } return QStringList(); } inline static bool isSpecial(QChar cUnicode) { static const uchar iqm[] = { 0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78 }; // 0-32 \'"$`<>|;&(){}*?#!~[] uint c = cUnicode.unicode(); return ((c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)))); } QString KShell::quoteArg(const QString &arg) { if (!arg.length()) { return QString::fromLatin1("''"); } for (int i = 0; i < arg.length(); i++) if (isSpecial(arg.unicode()[i])) { QChar q(QLatin1Char('\'')); return QString(arg).replace(q, QLatin1String("'\\''")).prepend(q).append(q); } return arg; } diff --git a/src/lib/util/kshell_win.cpp b/src/lib/util/kshell_win.cpp index 179ed6f..13cf553 100644 --- a/src/lib/util/kshell_win.cpp +++ b/src/lib/util/kshell_win.cpp @@ -1,276 +1,276 @@ /* This file is part of the KDE libraries Copyright (c) 2007 Bernhard Loos Copyright (c) 2007,2008 Oswald Buddenhagen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kshell.h" #include "kshell_p.h" #include #include -#include +#include /* * A short introduction into cmd semantics: * - Variable expansion is done first, without regard to *any* escaping - * if something looks like an existing variable, it is replaced. * - Then follows regular tokenization by the shell. &, &&, | and || are * command delimiters. ( and ) are command grouping operators; they are * recognized only a the start resp. end of a command; mismatched )s are * an error if any (s are present. <, > are just like under UNIX - they can * appear *anywhere* in a command, perform their function and are cut out. * @ at the start of a command is eaten (local echo off - no function as * far as cmd /c is concerned). : at the start of a command declares a label, * which effectively means the remainder of the line is a comment - note that * command separators are not recognized past that point. * ^ is the escape char for everything including itself. * cmd ignores *all* special chars between double quotes, so there is no * way to escape the closing quote. Note that the quotes are *not* removed * from the resulting command line. * - Then follows delayed variable expansion if it is enabled and at least * one exclamation mark is present. This involves another layer of ^ * escaping, regardless of quotes. (Win2k+) * - Then follows argument splitting as described in * http://msdn2.microsoft.com/en-us/library/ms880421.aspx . * Note that this is done by the called application and therefore might * be subject to completely different semantics, in fact. */ inline static bool isMetaChar(ushort c) { static const uchar iqm[] = { 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 }; // &()<>| return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); } inline static bool isSpecialChar(ushort c) { // Chars that should be quoted (TM). This includes: // - control chars & space // - the shell meta chars &()<>^| // - the potential separators ,;= static const uchar iqm[] = { 0xff, 0xff, 0xff, 0xff, 0x41, 0x13, 0x00, 0x78, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10 }; return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7))); } inline static bool isWhiteSpace(ushort c) { return c == ' ' || c == '\t'; } QStringList KShell::splitArgs(const QString &_args, Options flags, Errors *err) { QString args(_args); QStringList ret; const QLatin1Char bs('\\'), dq('\"'); if (flags & AbortOnMeta) { args.remove(PERCENT_ESCAPE); if (args.indexOf(QLatin1Char('%')) >= 0) { if (err) { *err = FoundMeta; } return QStringList(); } args = _args; args.replace(PERCENT_ESCAPE, QLatin1String("%")); if (!args.isEmpty() && args[0].unicode() == '@') { args.remove(0, 1); } for (int p = 0; p < args.length(); p++) { ushort c = args[p].unicode(); if (c == '^') { args.remove(p, 1); } else if (c == '"') { while (++p < args.length() && args[p].unicode() != '"') ; } else if (isMetaChar(c)) { if (err) { *err = FoundMeta; } return QStringList(); } } } if (err) { *err = NoError; } int p = 0; const int length = args.length(); forever { while (p < length && isWhiteSpace(args[p].unicode())) { ++p; } if (p == length) { return ret; } QString arg; bool inquote = false; forever { bool copy = true; // copy this char int bslashes = 0; // number of preceding backslashes to insert while (p < length && args[p] == bs) { ++p; ++bslashes; } if (p < length && args[p] == dq) { if (bslashes % 2 == 0) { // Even number of backslashes, so the quote is not escaped. if (inquote) { if (p + 1 < length && args[p + 1] == dq) { // Two consecutive quotes make a literal quote. // This is not documented on MSDN. ++p; } else { // Closing quote copy = false; inquote = !inquote; } } else { // Opening quote copy = false; inquote = !inquote; } } bslashes /= 2; } while (--bslashes >= 0) { arg.append(bs); } if (p == length || (!inquote && isWhiteSpace(args[p].unicode()))) { ret.append(arg); if (inquote) { if (err) { *err = BadQuoting; } return QStringList(); } break; } if (copy) { arg.append(args[p]); } ++p; } } //not reached } QString KShell::quoteArgInternal(const QString &arg, bool _inquote) { // Escape quotes, preceding backslashes are doubled. Surround with quotes. // Note that cmd does not understand quote escapes in quoted strings, // so the quoting needs to be "suspended". const QLatin1Char bs('\\'), dq('\"'); QString ret; bool inquote = _inquote; int bslashes = 0; for (int p = 0; p < arg.length(); p++) { if (arg[p] == bs) { bslashes++; } else if (arg[p] == dq) { if (inquote) { ret.append(dq); inquote = false; } for (; bslashes; bslashes--) { ret.append(QLatin1String("\\\\")); } ret.append(QLatin1String("\\^\"")); } else { if (!inquote) { ret.append(dq); inquote = true; } for (; bslashes; bslashes--) { ret.append(bs); } ret.append(arg[p]); } } ret.replace(QLatin1Char('%'), PERCENT_ESCAPE); if (bslashes) { // Ensure that we don't have directly trailing backslashes, // so concatenating with another string won't cause surprises. if (!inquote && !_inquote) { ret.append(dq); } for (; bslashes; bslashes--) { ret.append(QLatin1String("\\\\")); } ret.append(dq); if (inquote && _inquote) { ret.append(dq); } } else if (inquote != _inquote) { ret.append(dq); } return ret; } QString KShell::quoteArg(const QString &arg) { if (arg.isEmpty()) { return QString::fromLatin1("\"\""); } // Ensure that we don't have directly trailing backslashes, // so concatenating with another string won't cause surprises. if (arg.endsWith(QLatin1Char('\\'))) { return quoteArgInternal(arg, false); } for (int x = arg.length() - 1; x >= 0; --x) if (isSpecialChar(arg[x].unicode())) { return quoteArgInternal(arg, false); } // Escape quotes. Preceding backslashes are doubled. // Note that the remaining string is not quoted. QString ret(arg); ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\\1\\1\\^\"")); ret.replace(QLatin1Char('%'), PERCENT_ESCAPE); return ret; } diff --git a/src/lib/util/kuser.h b/src/lib/util/kuser.h index d21c65f..785f5e5 100644 --- a/src/lib/util/kuser.h +++ b/src/lib/util/kuser.h @@ -1,639 +1,639 @@ /* * KUser - represent a user/account * Copyright (C) 2002-2003 Tim Jansen * Copyright (C) 2003 Oswald Buddenhagen * Copyright (C) 2004 Jan Schaefer * Copyright (C) 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KUSER_H #define KUSER_H #include #include -#include +#include class KUserGroup; class QString; class QStringList; template class QList; #ifdef Q_OS_WIN typedef void *K_UID; typedef void *K_GID; struct WindowsSIDWrapper; #else #include typedef uid_t K_UID; typedef gid_t K_GID; struct passwd; struct group; #endif // The following is to avoid compile errors using msvc, and it is done // using a common #define to avoid helpful people accidentally cleaning this // not quite pretty thing and breaking it for people on windows. // See https://git.reviewboard.kde.org/r/127598/ for details #define KCOREADDONS_UINT_MAX (std::numeric_limits::max)() /** A platform independent user or group ID. * * * This struct is required since Windows does not have an integer uid_t/gid_t type * but instead uses an opaque binary blob (SID) which must free allocated memory. * On UNIX this is simply a uid_t/gid_t and all operations are inline, so there is * no runtime overhead over using the uid_t/gid_t directly. On Windows this is an implicitly * shared class that frees the underlying SID once no more references remain. * * Unlike KUser/KUserGroup this does not query additional information, it is simply * an abstraction over the native user/group ID type. If more information is necessary, a * KUser or KUserGroup instance can be constructed from this ID * * @internal * @author Alex Richardson */ template struct KCOREADDONS_EXPORT KUserOrGroupId { typedef T NativeType; protected: /** Creates an invalid KUserOrGroupId */ KUserOrGroupId(); /** Creates a KUserOrGroupId from a native user/group ID. On windows this will not take * ownership over the passed SID, a copy will be created instead. */ explicit KUserOrGroupId(NativeType nativeId); /** Copy constructor. This is very fast, objects can be passed by value */ KUserOrGroupId(const KUserOrGroupId &other); KUserOrGroupId &operator=(const KUserOrGroupId &other); ~KUserOrGroupId(); public: /** @return true if this object references a valid user/group ID. * @note If this returns true it doesn't necessarily mean that the referenced user/group exists, * it only checks whether this value could be a valid user/group ID. */ bool isValid() const; /** * @return A user/group ID that can be used in operating system specific functions * @note On Windows the returned pointer will be freed once the last KUserOrGroupId referencing * this user/group ID is deleted. Make sure that the KUserOrGroupId object remains valid as * long as the native pointer is needed. */ NativeType nativeId() const; /** @return A string representation of this user ID, not the name of the user * On UNIX this is a simple integer, e.g. "0" for root. On Windows this is a string * like e.g. "S-1-5-32-544" for the Administrators group */ QString toString() const; /** @return whether this KUserOrGroupId is equal to @p other */ bool operator==(const KUserOrGroupId &other) const; /** @return whether this KUserOrGroupId is not equal to @p other */ bool operator!=(const KUserOrGroupId &other) const; private: #ifdef Q_OS_WIN QExplicitlySharedDataPointer data; #else NativeType id; #endif }; #ifdef Q_OS_WIN template<> KUserOrGroupId::KUserOrGroupId(); template<> KUserOrGroupId::~KUserOrGroupId(); template<> KUserOrGroupId::KUserOrGroupId(KUserOrGroupId::NativeType nativeId); template<> KUserOrGroupId::KUserOrGroupId(const KUserOrGroupId &other); template<> KUserOrGroupId& KUserOrGroupId::operator=(const KUserOrGroupId &other); template<> bool KUserOrGroupId::isValid() const; template<> KUserOrGroupId::NativeType KUserOrGroupId::nativeId() const; template<> QString KUserOrGroupId::toString() const; template<> bool KUserOrGroupId::operator==(const KUserOrGroupId &other) const; template<> bool KUserOrGroupId::operator!=(const KUserOrGroupId &other) const; #endif /** A platform independent user ID. * @see KUserOrGroupId * @since 5.0 * @author Alex Richardson */ struct KCOREADDONS_EXPORT KUserId : public KUserOrGroupId { /** Creates an invalid KUserId */ KUserId() {} /** Creates an KUserId from the native user ID type */ explicit KUserId(K_UID uid) : KUserOrGroupId(uid) {} KUserId(const KUserId &other) : KUserOrGroupId(other) {} ~KUserId() {} /** @return a KUserId for the user with name @p name, or an invalid KUserId if no * user with this name was found on the system */ static KUserId fromName(const QString &name); /** @return a KUserId for the current user. This is like ::getuid() on UNIX. */ static KUserId currentUserId(); /** @return a KUserId for the current effective user. This is like ::geteuid() on UNIX. * @note Windows does not have setuid binaries, so on Windows this will always be the same * as KUserId::currentUserId() */ static KUserId currentEffectiveUserId(); }; /** A platform independent group ID. * @see KUserOrGroupId * @since 5.0 * @author Alex Richardson */ struct KCOREADDONS_EXPORT KGroupId : public KUserOrGroupId { /** Creates an invalid KGroupId */ KGroupId() {} /** Creates an KGroupId from the native group ID type */ explicit KGroupId(K_GID gid) : KUserOrGroupId(gid) {} KGroupId(const KGroupId &other) : KUserOrGroupId(other) {} ~KGroupId() {} /** @return A KGroupId for the user with name @p name, or an invalid KGroupId if no * user with this name was found on the system */ static KGroupId fromName(const QString &name); /** @return a KGroupId for the current user. This is like ::getgid() on UNIX. */ static KGroupId currentGroupId(); /** @return a KGroupId for the current effective user. This is like ::getegid() on UNIX. * @note Windows does not have setuid binaries, so on Windows this will always be the same * as KGroupId::currentGroupId() */ static KGroupId currentEffectiveGroupId(); }; #ifndef Q_OS_WIN inline uint qHash(const KUserId &id, uint seed = 0) { return qHash(id.nativeId(), seed); } inline uint qHash(const KGroupId &id, uint seed = 0) { return qHash(id.nativeId(), seed); } #else // can't be inline on windows, because we would need to include windows.h (which can break code) KCOREADDONS_EXPORT uint qHash(const KUserId &id, uint seed = 0); KCOREADDONS_EXPORT uint qHash(const KGroupId &id, uint seed = 0); #endif /** * \class KUser kuser.h * * @short Represents a user on your system * * This class represents a user on your system. You can either get * information about the current user, of fetch information about * a user on the system. Instances of this class will be explicitly shared, * so copying objects is very cheap and you can safely pass objects by value. * * @author Tim Jansen */ class KCOREADDONS_EXPORT KUser { public: enum UIDMode { UseEffectiveUID, ///< Use the effective user id. UseRealUserID ///< Use the real user id. }; /** * Creates an object that contains information about the current user. * (as returned by getuid(2) or geteuid(2), taking $LOGNAME/$USER into * account). * @param mode if #UseEffectiveUID is passed the effective * user is returned. * If #UseRealUserID is passed the real user will be * returned. * The real UID will be different than the effective UID in setuid * programs; in * such a case use the effective UID for checking permissions, and * the real UID for displaying information about the user. */ explicit KUser(UIDMode mode = UseEffectiveUID); /** * Creates an object for the user with the given user id. * If the user does not exist isValid() will return false. * @param uid the user id */ explicit KUser(K_UID uid); /** * Creates an object for the user with the given user id. * If the KUserId object is invalid this one will be, too. * @param uid the user id */ explicit KUser(KUserId uid); /** * Creates an object that contains information about the user with the given * name. If the user does not exist isValid() will return false. * * @param name the name of the user */ explicit KUser(const QString &name); /** * Creates an object that contains information about the user with the given * name. If the user does not exist isValid() will return false. * * @param name the name of the user */ explicit KUser(const char *name); #ifndef Q_OS_WIN /** * Creates an object from a passwd structure. * If the pointer is null isValid() will return false. * * @param p the passwd structure to create the user from */ explicit KUser(const passwd *p); #endif /** * Creates an object from another KUser object * @param user the user to create the new object from */ KUser(const KUser &user); /** * Copies a user * @param user the user to copy * @return this object */ KUser &operator =(const KUser &user); /** * Two KUser objects are equal if the userId() are identical. * Invalid users never compare equal. */ bool operator ==(const KUser &user) const; /** * Two KUser objects are not equal if userId() are not identical. * Invalid users always compare unequal. */ bool operator !=(const KUser &user) const; /** * Returns true if the user is valid. A KUser object can be invalid if * you created it with an non-existing uid or name. * @return true if the user is valid */ bool isValid() const; /** @return the native user id of the user. */ KUserId userId() const; /** @return the native user id of the user. */ KGroupId groupId() const; #ifndef KCOREADDONS_NO_DEPRECATED /** * Returns the group id of the user. * @return the id of the group or -1 if user is invalid * @deprecated since 5.0 use KUser::groupId() */ KCOREADDONS_DEPRECATED K_GID gid() const { return groupId().nativeId(); } #endif /** * Checks whether the user is the super user (root). * @return true if the user is root */ bool isSuperUser() const; /** * The login name of the user. * @return the login name of the user or QString() if user is invalid */ QString loginName() const; /** * The full name of the user. * @return the full name of the user or QString() if user is invalid * @deprecated use property(KUser::FullName) instead */ #ifndef KCOREADDONS_NO_DEPRECATED KCOREADDONS_DEPRECATED QString fullName() const { return property(FullName).toString(); } /** * Returns the user id of the user. * @return the id of the user or -1 (UNIX)/ null(Windows) if user is invalid * @deprecated since 5.0 use KUser::userId() */ KCOREADDONS_DEPRECATED K_UID uid() const { return userId().nativeId(); } #endif /** * The path to the user's home directory. * @return the home directory of the user or QString() if the * user is invalid */ QString homeDir() const; /** * The path to the user's face file. * @return the path to the user's face file or QString() if no * face has been set */ QString faceIconPath() const; /** * The path to the user's login shell. * @return the login shell of the user or QString() if the * user is invalid */ QString shell() const; /** * @param maxCount the maximum number of groups to return * @return all groups of the user */ QList groups(uint maxCount = KCOREADDONS_UINT_MAX) const; /** * @param maxCount the maximum number of groups to return * @return all group names of the user */ QStringList groupNames(uint maxCount = KCOREADDONS_UINT_MAX) const; enum UserProperty { FullName, RoomNumber, WorkPhone, HomePhone }; /** * Returns an extended property. * * Under Windows, @p RoomNumber, @p WorkPhone and @p HomePhone are unsupported. * * @return a QVariant with the value of the property or an invalid QVariant, * if the property is not set */ QVariant property(UserProperty which) const; /** * Destructor. */ ~KUser(); /** * @param maxCount the maximum number of users to return * @return all users of the system. */ static QList allUsers(uint maxCount = KCOREADDONS_UINT_MAX); /** * @param maxCount the maximum number of users to return * @return all user names of the system. */ static QStringList allUserNames(uint maxCount = KCOREADDONS_UINT_MAX); private: class Private; QExplicitlySharedDataPointer d; }; /** * \class KUserGroup kuser.h * * @short Represents a group on your system * * This class represents a group on your system. You can either get * information about the group of the current user, of fetch information about * a group on the system. Instances of this class will be explicitly shared, * so copying objects is very cheap and you can safely pass objects by value. * * @author Jan Schaefer */ class KCOREADDONS_EXPORT KUserGroup { public: /** * Create an object from a group name. * If the group does not exist, isValid() will return false. * @param name the name of the group */ explicit KUserGroup(const QString &name); /** * Create an object from a group name. * If the group does not exist, isValid() will return false. * @param name the name of the group */ explicit KUserGroup(const char *name); /** * Creates an object for the group with the given group id. * If the KGroupId object is invalid this one will be, too. * @param gid the group id */ explicit KUserGroup(KGroupId gid); /** * Create an object from the group of the current user. * @param mode if #KUser::UseEffectiveUID is passed the effective user * will be used. If #KUser::UseRealUserID is passed the real user * will be used. * The real UID will be different than the effective UID in setuid * programs; in such a case use the effective UID for checking * permissions, and the real UID for displaying information about * the group associated with the user. */ explicit KUserGroup(KUser::UIDMode mode = KUser::UseEffectiveUID); /** * Create an object from a group id. * If the group does not exist, isValid() will return false. * @param gid the group id */ explicit KUserGroup(K_GID gid); #ifndef Q_OS_WIN /** * Creates an object from a group structure. * If the pointer is null, isValid() will return false. * @param g the group structure to create the group from. */ explicit KUserGroup(const group *g); #endif /** * Creates a new KUserGroup instance from another KUserGroup object * @param group the KUserGroup to copy */ KUserGroup(const KUserGroup &group); /** * Copies a group * @param group the group that should be copied * @return this group */ KUserGroup &operator =(const KUserGroup &group); /** * Two KUserGroup objects are equal if their gid()s are identical. * Invalid groups never compare equal. * @return true if the groups are identical */ bool operator ==(const KUserGroup &group) const; /** * Two KUserGroup objects are not equal if their gid()s are not identical. * Invalid groups always compare unequal. * @return true if the groups are not identical */ bool operator !=(const KUserGroup &group) const; /** * Returns whether the group is valid. * A KUserGroup object can be invalid if it is * created with a non-existing gid or name. * @return true if the group is valid */ bool isValid() const; #ifndef KCOREADDONS_NO_DEPRECATED /** * Returns the group id of the group. * @return the group id of the group or -1 if the group is invalid * @deprecated since 5.0 use KUserGroup::groupId() */ KCOREADDONS_DEPRECATED K_GID gid() const { return groupId().nativeId(); } #endif /** @return the native group id of the user. */ KGroupId groupId() const; /** * The name of the group. * @return the name of the group */ QString name() const; /** * @param maxCount the maximum number of users to return * @return a list of all users of the group */ QList users(uint maxCount = KCOREADDONS_UINT_MAX) const; /** * @param maxCount the maximum number of groups to return * @return a list of all user login names of the group */ QStringList userNames(uint maxCount = KCOREADDONS_UINT_MAX) const; /** * Destructor. */ ~KUserGroup(); /** * @param maxCount the maximum number of groups to return * @return a list of all groups on this system */ static QList allGroups(uint maxCount = KCOREADDONS_UINT_MAX); /** * @param maxCount the maximum number of groups to return * @return a list of all group names on this system */ static QStringList allGroupNames(uint maxCount = KCOREADDONS_UINT_MAX); private: class Private; QSharedDataPointer d; }; #if !defined(Q_OS_WIN) // inline UNIX implementation of KUserOrGroupId template inline bool KUserOrGroupId::isValid() const { return id != NativeType(-1); } template inline bool KUserOrGroupId::operator==(const KUserOrGroupId &other) const { return id == other.id; } template inline bool KUserOrGroupId::operator!=(const KUserOrGroupId &other) const { return id != other.id; } template inline typename KUserOrGroupId::NativeType KUserOrGroupId::nativeId() const { return id; } template inline QString KUserOrGroupId::toString() const { return QString::number(id); } template inline KUserOrGroupId::KUserOrGroupId() : id(-1) { } template inline KUserOrGroupId::KUserOrGroupId(KUserOrGroupId::NativeType nativeId) : id(nativeId) { } template inline KUserOrGroupId::KUserOrGroupId(const KUserOrGroupId &other) : id(other.id) { } template inline KUserOrGroupId &KUserOrGroupId::operator=(const KUserOrGroupId &other) { id = other.id; return *this; } template inline KUserOrGroupId::~KUserOrGroupId() { } #endif // !defined(Q_OS_WIN) inline bool KUser::operator!=(const KUser &other) const { return !operator==(other); } inline bool KUserGroup::operator!=(const KUserGroup &other) const { return !operator==(other); } #endif diff --git a/src/lib/util/kuser_unix.cpp b/src/lib/util/kuser_unix.cpp index c6da2c3..c4e780c 100644 --- a/src/lib/util/kuser_unix.cpp +++ b/src/lib/util/kuser_unix.cpp @@ -1,563 +1,563 @@ /* * KUser - represent a user/account * Copyright (C) 2002 Tim Jansen * Copyright (C) 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kuser.h" #include "config-getgrouplist.h" #include "config-accountsservice.h" -#include -#include +#include +#include #include #include #include #include #include #include // std::find #include // std::function #if defined(__BIONIC__) static inline struct passwd * getpwent() { return nullptr; } inline void setpwent() { } static inline void setgrent() { } static inline struct group * getgrent() { return nullptr; } inline void endpwent() { } static inline void endgrent() { } #endif class Q_DECL_HIDDEN KUser::Private : public QSharedData { public: uid_t uid; gid_t gid; QString loginName; QString homeDir, shell; QMap properties; Private() : uid(uid_t(-1)), gid(gid_t(-1)) {} Private(const char *name) : uid(uid_t(-1)), gid(gid_t(-1)) { fillPasswd(name ? ::getpwnam(name) : nullptr); } Private(const passwd *p) : uid(uid_t(-1)), gid(gid_t(-1)) { fillPasswd(p); } void fillPasswd(const passwd *p) { if (p) { #ifndef __BIONIC__ QString gecos = QString::fromLocal8Bit(p->pw_gecos); #else QString gecos = QString(); #endif QStringList gecosList = gecos.split(QLatin1Char(',')); // fill up the list, should be at least 4 entries while (gecosList.size() < 4) { gecosList << QString(); } uid = p->pw_uid; gid = p->pw_gid; loginName = QString::fromLocal8Bit(p->pw_name); properties[KUser::FullName] = QVariant(gecosList[0]); properties[KUser::RoomNumber] = QVariant(gecosList[1]); properties[KUser::WorkPhone] = QVariant(gecosList[2]); properties[KUser::HomePhone] = QVariant(gecosList[3]); if (uid == ::getuid() && uid == ::geteuid()) { homeDir = QFile::decodeName(qgetenv("HOME")); } if (homeDir.isEmpty()) { homeDir = QFile::decodeName(p->pw_dir); } shell = QString::fromLocal8Bit(p->pw_shell); } } }; KUser::KUser(UIDMode mode) { uid_t _uid = ::getuid(), _euid; if (mode == UseEffectiveUID && (_euid = ::geteuid()) != _uid) { d = new Private(::getpwuid(_euid)); } else { d = new Private(qgetenv("LOGNAME").constData()); if (d->uid != _uid) { d = new Private(qgetenv("USER").constData()); if (d->uid != _uid) { d = new Private(::getpwuid(_uid)); } } } } KUser::KUser(K_UID _uid) : d(new Private(::getpwuid(_uid))) { } KUser::KUser(KUserId _uid) : d(new Private(::getpwuid(_uid.nativeId()))) { } KUser::KUser(const QString &name) : d(new Private(name.toLocal8Bit().data())) { } KUser::KUser(const char *name) : d(new Private(name)) { } KUser::KUser(const passwd *p) : d(new Private(p)) { } KUser::KUser(const KUser &user) : d(user.d) { } KUser &KUser::operator =(const KUser &user) { d = user.d; return *this; } bool KUser::operator ==(const KUser &user) const { return isValid() && (d->uid == user.d->uid); } bool KUser::isValid() const { return d->uid != uid_t(-1); } KUserId KUser::userId() const { return KUserId(d->uid); } KGroupId KUser::groupId() const { return KGroupId(d->gid); } bool KUser::isSuperUser() const { return d->uid == 0; } QString KUser::loginName() const { return d->loginName; } QString KUser::homeDir() const { return d->homeDir; } QString KUser::faceIconPath() const { QString pathToFaceIcon; if (!d->loginName.isEmpty()) { pathToFaceIcon = QStringLiteral(ACCOUNTS_SERVICE_ICON_DIR) + QLatin1Char('/') + d->loginName; } if (QFile::exists(pathToFaceIcon)) { return pathToFaceIcon; } pathToFaceIcon = QString(homeDir() + QLatin1Char('/') + QStringLiteral(".face.icon")); if (QFile::exists(pathToFaceIcon)) { return pathToFaceIcon; } return QString(); } QString KUser::shell() const { return d->shell; } template static void listGroupsForUser(const char *name, gid_t gid, uint maxCount, Func handleNextGroup) { if (Q_UNLIKELY(maxCount == 0)) { return; } uint found = 0; #if HAVE_GETGROUPLIST QVarLengthArray gid_buffer; gid_buffer.resize(100); int numGroups = gid_buffer.size(); int result = getgrouplist(name, gid, gid_buffer.data(), &numGroups); if (result < 0 && uint(numGroups) < maxCount) { // getgrouplist returns -1 if the buffer was too small to store all entries, the required size is in numGroups qDebug("Buffer was too small: %d, need %d", gid_buffer.size(), numGroups); gid_buffer.resize(numGroups); numGroups = gid_buffer.size(); getgrouplist(name, gid, gid_buffer.data(), &numGroups); } for (int i = 0; i < numGroups && found < maxCount; ++i) { struct group *g = getgrgid(gid_buffer[i]); // should never be null, but better be safe than crash if (g) { found++; handleNextGroup(g); } } #else // fall back to getgrent() and reading gr->gr_mem // This is slower than getgrouplist, but works as well // add the current gid, this is often not part of g->gr_mem (e.g. build.kde.org or my openSuSE 13.1 system) struct group *g = getgrgid(gid); if (g) { handleNextGroup(g); found++; if (found >= maxCount) { return; } } static const auto groupContainsUser = [](struct group * g, const char *name) -> bool { for (char **user = g->gr_mem; *user; user++) { if (strcmp(name, *user) == 0) { return true; } } return false; }; setgrent(); while ((g = getgrent())) { // don't add the current gid again if (g->gr_gid != gid && groupContainsUser(g, name)) { handleNextGroup(g); found++; if (found >= maxCount) { break; } } } endgrent(); #endif } QList KUser::groups(uint maxCount) const { QList result; listGroupsForUser( d->loginName.toLocal8Bit().constData(), d->gid, maxCount, [&](const group * g) { result.append(KUserGroup(g)); } ); return result; } QStringList KUser::groupNames(uint maxCount) const { QStringList result; listGroupsForUser( d->loginName.toLocal8Bit().constData(), d->gid, maxCount, [&](const group * g) { result.append(QString::fromLocal8Bit(g->gr_name)); } ); return result; } QVariant KUser::property(UserProperty which) const { return d->properties.value(which); } QList KUser::allUsers(uint maxCount) { QList result; passwd *p; setpwent(); for (uint i = 0; i < maxCount && (p = getpwent()); ++i) { result.append(KUser(p)); } endpwent(); return result; } QStringList KUser::allUserNames(uint maxCount) { QStringList result; passwd *p; setpwent(); for (uint i = 0; i < maxCount && (p = getpwent()); ++i) { result.append(QString::fromLocal8Bit(p->pw_name)); } endpwent(); return result; } KUser::~KUser() { } class Q_DECL_HIDDEN KUserGroup::Private : public QSharedData { public: gid_t gid; QString name; Private() : gid(gid_t(-1)) {} Private(const char *_name) : gid(gid_t(-1)) { fillGroup(_name ? ::getgrnam(_name) : nullptr); } Private(const ::group *p) : gid(gid_t(-1)) { fillGroup(p); } void fillGroup(const ::group *p) { if (p) { gid = p->gr_gid; name = QString::fromLocal8Bit(p->gr_name); } } }; KUserGroup::KUserGroup(KUser::UIDMode mode) { d = new Private(getgrgid(KUser(mode).groupId().nativeId())); } KUserGroup::KUserGroup(K_GID _gid) : d(new Private(getgrgid(_gid))) { } KUserGroup::KUserGroup(KGroupId _gid) : d(new Private(getgrgid(_gid.nativeId()))) { } KUserGroup::KUserGroup(const QString &_name) : d(new Private(_name.toLocal8Bit().data())) { } KUserGroup::KUserGroup(const char *_name) : d(new Private(_name)) { } KUserGroup::KUserGroup(const ::group *g) : d(new Private(g)) { } KUserGroup::KUserGroup(const KUserGroup &group) : d(group.d) { } KUserGroup &KUserGroup::operator =(const KUserGroup &group) { d = group.d; return *this; } bool KUserGroup::operator ==(const KUserGroup &group) const { return isValid() && (d->gid == group.d->gid); } bool KUserGroup::isValid() const { return d->gid != gid_t(-1); } KGroupId KUserGroup::groupId() const { return KGroupId(d->gid); } QString KUserGroup::name() const { return d->name; } static void listGroupMembers(gid_t gid, uint maxCount, std::function handleNextGroupUser) { if (maxCount == 0) { return; } struct group *g = getgrgid(gid); if (!g) { return; } uint found = 0; QVarLengthArray addedUsers; struct passwd *p = nullptr; for (char **user = g->gr_mem; *user; user++) { if ((p = getpwnam(*user))) { addedUsers.append(p->pw_uid); handleNextGroupUser(p); found++; if (found >= maxCount) { break; } } } //gr_mem doesn't contain users where the primary group == gid -> we have to iterate over all users setpwent(); while ((p = getpwent()) && found < maxCount) { if (p->pw_gid != gid) { continue; // only need primary gid since otherwise gr_mem already contains this user } // make sure we don't list a user twice if (std::find(addedUsers.cbegin(), addedUsers.cend(), p->pw_uid) == addedUsers.cend()) { handleNextGroupUser(p); found++; } } endpwent(); } QList KUserGroup::users(uint maxCount) const { QList result; listGroupMembers(d->gid, maxCount, [&](const passwd *p) { result.append(KUser(p)); }); return result; } QStringList KUserGroup::userNames(uint maxCount) const { QStringList result; listGroupMembers(d->gid, maxCount, [&](const passwd *p) { result.append(QString::fromLocal8Bit(p->pw_name)); }); return result; } QList KUserGroup::allGroups(uint maxCount) { QList result; ::group *g; setgrent(); for (uint i = 0; i < maxCount && (g = getgrent()); ++i) { result.append(KUserGroup(g)); } endgrent(); return result; } QStringList KUserGroup::allGroupNames(uint maxCount) { QStringList result; ::group *g; setgrent(); for (uint i = 0; i < maxCount && (g = getgrent()); ++i) { result.append(QString::fromLocal8Bit(g->gr_name)); } endgrent(); return result; } KUserGroup::~KUserGroup() { } KUserId KUserId::fromName(const QString &name) { if (name.isEmpty()) { return KUserId(); } QByteArray name8Bit = name.toLocal8Bit(); struct passwd *p = ::getpwnam(name8Bit.constData()); if (!p) { qWarning("Failed to lookup user %s: %s", name8Bit.constData(), strerror(errno)); return KUserId(); } return KUserId(p->pw_uid); } KGroupId KGroupId::fromName(const QString &name) { if (name.isEmpty()) { return KGroupId(); } QByteArray name8Bit = name.toLocal8Bit(); struct group *g = ::getgrnam(name8Bit.constData()); if (!g) { qWarning("Failed to lookup group %s: %s", name8Bit.constData(), strerror(errno)); return KGroupId(); } return KGroupId(g->gr_gid); } KUserId KUserId::currentUserId() { return KUserId(getuid()); } KUserId KUserId::currentEffectiveUserId() { return KUserId(geteuid()); } KGroupId KGroupId::currentGroupId() { return KGroupId(getgid()); } KGroupId KGroupId::currentEffectiveGroupId() { return KGroupId(getegid()); } diff --git a/tests/kdirwatchtest.cpp b/tests/kdirwatchtest.cpp index b0ba108..ef958dc 100644 --- a/tests/kdirwatchtest.cpp +++ b/tests/kdirwatchtest.cpp @@ -1,87 +1,87 @@ /* This file is part of the KDE libraries Copyright 1998 Sven Radej This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdirwatchtest.h" -#include -#include +#include +#include #include #include // TODO debug crash when calling "./kdirwatchtest ./kdirwatchtest" int main(int argc, char **argv) { // TODO port to QCommandLineArguments once it exists //options.add("+[directory ...]", qi18n("Directory(ies) to watch")); QCoreApplication a(argc, argv); myTest testObject; KDirWatch *dirwatch1 = KDirWatch::self(); KDirWatch *dirwatch2 = new KDirWatch; testObject.connect(dirwatch1, SIGNAL(dirty(QString)), SLOT(dirty(QString))); testObject.connect(dirwatch1, SIGNAL(created(QString)), SLOT(created(QString))); testObject.connect(dirwatch1, SIGNAL(deleted(QString)), SLOT(deleted(QString))); // TODO port to QCommandLineArguments once it exists const QStringList args = a.arguments(); for (int i = 1; i < args.count(); ++i) { const QString arg = args.at(i); if (!arg.startsWith("-")) { qDebug() << "Watching: " << arg; dirwatch2->addDir(arg); } } QString home = QString(getenv("HOME")) + '/'; QString desk = home + "Desktop/"; qDebug() << "Watching: " << home; dirwatch1->addDir(home); qDebug() << "Watching file: " << home << "foo "; dirwatch1->addFile(home + "foo"); qDebug() << "Watching: " << desk; dirwatch1->addDir(desk); QString test = home + "test/"; qDebug() << "Watching: (but skipped) " << test; dirwatch1->addDir(test); dirwatch1->startScan(); dirwatch2->startScan(); if (!dirwatch1->stopDirScan(home)) { qDebug() << "stopDirscan: " << home << " error!"; } if (!dirwatch1->restartDirScan(home)) { qDebug() << "restartDirScan: " << home << "error!"; } if (!dirwatch1->stopDirScan(test)) { qDebug() << "stopDirScan: error"; } KDirWatch::statistics(); delete dirwatch2; KDirWatch::statistics(); return a.exec(); } diff --git a/tests/kdirwatchtest.h b/tests/kdirwatchtest.h index 892b3d5..5509408 100644 --- a/tests/kdirwatchtest.h +++ b/tests/kdirwatchtest.h @@ -1,48 +1,48 @@ /* This file is part of the KDE libraries Copyright 1998 Sven Radej This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KDIRWATCHTEST_H_ #define _KDIRWATCHTEST_H_ #include #include -#include +#include #include "kdirwatch.h" class myTest : public QObject { Q_OBJECT public: myTest() { } public Q_SLOTS: void dirty(const QString &a) { printf("Dirty: %s\n", a.toLocal8Bit().constData()); } void created(const QString &f) { printf("Created: %s\n", f.toLocal8Bit().constData()); } void deleted(const QString &f) { printf("Deleted: %s\n", f.toLocal8Bit().constData()); } }; #endif diff --git a/tests/krandomsequencetest.cpp b/tests/krandomsequencetest.cpp index 8afea9d..297e867 100644 --- a/tests/krandomsequencetest.cpp +++ b/tests/krandomsequencetest.cpp @@ -1,97 +1,97 @@ /* This file is part of the KDE libraries Copyright (c) 1999 Waldo Bastian This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include +#include +#include #include "krandomsequence.h" #include "krandom.h" #include int main(/*int argc, char *argv[]*/) { long seed; KRandomSequence seq; seed = 2; seq.setSeed(seed); printf("Seed = %4ld :", seed); for (int i = 0; i < 20; i++) { printf("%3ld ", seq.getLong(100)); } printf("\n"); seed = 0; seq.setSeed(seed); printf("Seed = %4ld :", seed); for (int i = 0; i < 20; i++) { printf("%3ld ", seq.getLong(100)); } printf("\n"); seed = 0; seq.setSeed(seed); printf("Seed = %4ld :", seed); for (int i = 0; i < 20; i++) { printf("%3ld ", seq.getLong(100)); } printf("\n"); seed = 2; seq.setSeed(seed); printf("Seed = %4ld :", seed); for (int i = 0; i < 20; i++) { printf("%3ld ", seq.getLong(100)); } printf("\n"); seq.setSeed(KRandom::random()); QList list; list.append(QLatin1String("A")); list.append(QLatin1String("B")); list.append(QLatin1String("C")); list.append(QLatin1String("D")); list.append(QLatin1String("E")); list.append(QLatin1String("F")); list.append(QLatin1String("G")); for (QList::Iterator str = list.begin(); str != list.end(); ++str) { printf("%s", str->toLatin1().data()); } printf("\n"); seq.randomize(list); for (QList::Iterator str = list.begin(); str != list.end(); ++str) { printf("%s", str->toLatin1().data()); } printf("\n"); seq.randomize(list); for (QList::Iterator str = list.begin(); str != list.end(); ++str) { printf("%s", str->toLatin1().data()); } printf("\n"); seq.randomize(list); for (QList::Iterator str = list.begin(); str != list.end(); ++str) { printf("%s", str->toLatin1().data()); } printf("\n"); }