diff --git a/autotests/data/servicetypes/bad-groups-input.desktop b/autotests/data/servicetypes/bad-groups-input.desktop index 6b17103..48da13f 100644 --- a/autotests/data/servicetypes/bad-groups-input.desktop +++ b/autotests/data/servicetypes/bad-groups-input.desktop @@ -1,46 +1,46 @@ [Desktop Entry] Name=Bad Groups Name[ca]=Grups dolents Name[ca@valencia]=Grups dolents Name[da]=Dårlige grupper Name[de]=Schlechte Gruppen Name[el]=Κακές ομάδες Name[en_GB]=Bad Groups Name[es]=Grupos incorrectos Name[fi]=Huonot ryhmät Name[gl]=Grupos malos Name[it]=Gruppi errati Name[ko]=불량 그룹 Name[nl]=Foute groepen Name[pl]=Złe grupy Name[pt]=Grupos Inválidos Name[pt_BR]=Grupos inválidos Name[sk]=Zlé skupiny Name[sl]=Slabe skupine Name[sr]=Лоше групе Name[sr@ijekavian]=Лоше групе Name[sr@ijekavianlatin]=Loše grupe Name[sr@latin]=Loše grupe Name[sv]=Felaktiga grupper Name[uk]=Погані групи Name[x-test]=xxBad Groupsxx Name[zh_CN]=坏分组 Type=Service -# one value for every property defintion in bad-groups-servicetype.desktop +# one value for every property definition in bad-groups-servicetype.desktop ThisIsOkay=10 #empty =11 #missing terminator MissingTerminator=12 # empty and missing terminator =13 # completely empty =14 SomeOtherProperty=15 # extra spaces in group name (should be okay) TrailingSpacesAreOkay=16 #missing type MissingType=17 InvalidType=18 # ok again after invalid ones ThisIsOkayAgain=19 diff --git a/autotests/kpluginmetadatatest.cpp b/autotests/kpluginmetadatatest.cpp index fb6d10f..145051e 100644 --- a/autotests/kpluginmetadatatest.cpp +++ b/autotests/kpluginmetadatatest.cpp @@ -1,351 +1,351 @@ /* * Copyright 2014 Alex Richardson * * 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.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include #include #include #include namespace QTest { template<> inline char *toString(const QJsonValue &val) { // simply reuse the QDebug representation QString result; QDebug(&result) << val; return QTest::toString(result); } } class KPluginMetaDataTest : public QObject { Q_OBJECT private Q_SLOTS: void testFromPluginLoader() { QString location = KPluginLoader::findPlugin(QStringLiteral("jsonplugin")); QVERIFY2(!location.isEmpty(),"Could not find jsonplugin"); // now that this file is translated we need to read it instead of hardcoding the contents here QString jsonLocation = QFINDTESTDATA("jsonplugin.json"); QVERIFY2(!jsonLocation.isEmpty(), "Could not find jsonplugin.json"); QFile jsonFile(jsonLocation); QVERIFY(jsonFile.open(QFile::ReadOnly)); QJsonParseError e; QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &e); QCOMPARE(e.error, QJsonParseError::NoError); location = QFileInfo(location).absoluteFilePath(); KPluginMetaData fromQPluginLoader(QPluginLoader(QStringLiteral("jsonplugin"))); KPluginMetaData fromKPluginLoader(KPluginLoader(QStringLiteral("jsonplugin"))); KPluginMetaData fromFullPath(location); KPluginMetaData fromRelativePath(QStringLiteral("jsonplugin")); KPluginMetaData fromRawData(jsonDoc.object(), location); auto description = QStringLiteral("This is a plugin"); QVERIFY(fromQPluginLoader.isValid()); QCOMPARE(fromQPluginLoader.description(), description); QVERIFY(fromKPluginLoader.isValid()); QCOMPARE(fromKPluginLoader.description(), description); QVERIFY(fromFullPath.isValid()); QCOMPARE(fromFullPath.description(), description); QVERIFY(fromRelativePath.isValid()); QCOMPARE(fromRelativePath.description(), description); QVERIFY(fromRawData.isValid()); QCOMPARE(fromRawData.description(), description); // check operator== QCOMPARE(fromRawData, fromRawData); QCOMPARE(fromQPluginLoader, fromQPluginLoader); QCOMPARE(fromKPluginLoader, fromKPluginLoader); QCOMPARE(fromFullPath, fromFullPath); QCOMPARE(fromQPluginLoader, fromKPluginLoader); QCOMPARE(fromQPluginLoader, fromFullPath); QCOMPARE(fromQPluginLoader, fromRawData); QCOMPARE(fromKPluginLoader, fromQPluginLoader); QCOMPARE(fromKPluginLoader, fromFullPath); QCOMPARE(fromKPluginLoader, fromRawData); QCOMPARE(fromFullPath, fromQPluginLoader); QCOMPARE(fromFullPath, fromKPluginLoader); QCOMPARE(fromFullPath, fromRawData); QCOMPARE(fromRawData, fromQPluginLoader); QCOMPARE(fromRawData, fromKPluginLoader); QCOMPARE(fromRawData, fromFullPath); } void testAllKeys() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson("{\n" " \"KPlugin\": {\n" " \"Name\": \"Date and Time\",\n" " \"Description\": \"Date and time by timezone\",\n" " \"Icon\": \"preferences-system-time\",\n" " \"Authors\": { \"Name\": \"Aaron Seigo\", \"Email\": \"aseigo@kde.org\" },\n" " \"Translators\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n" " \"OtherContributors\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n" " \"Category\": \"Date and Time\",\n" " \"Dependencies\": [ \"foo\", \"bar\"],\n" " \"EnabledByDefault\": \"true\",\n" " \"ExtraInformation\": \"Something else\",\n" " \"License\": \"LGPL\",\n" " \"Copyright\": \"(c) Alex Richardson 2015\",\n" " \"Id\": \"time\",\n" " \"Version\": \"1.0\",\n" " \"Website\": \"https://plasma.kde.org/\",\n" " \"MimeTypes\": [ \"image/png\" ],\n" " \"ServiceTypes\": [\"Plasma/DataEngine\"]\n" " }\n}\n", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); KPluginMetaData m(jo, QString()); QVERIFY(m.isValid()); QCOMPARE(m.pluginId(), QStringLiteral("time")); QCOMPARE(m.name(), QStringLiteral("Date and Time")); QCOMPARE(m.description(), QStringLiteral("Date and time by timezone")); QCOMPARE(m.extraInformation(), QStringLiteral("Something else")); QCOMPARE(m.iconName(), QStringLiteral("preferences-system-time")); QCOMPARE(m.category(), QStringLiteral("Date and Time")); QCOMPARE(m.dependencies(), QStringList() << QStringLiteral("foo") << QStringLiteral("bar")); QCOMPARE(m.authors().size(), 1); QCOMPARE(m.authors()[0].name(), QStringLiteral("Aaron Seigo")); QCOMPARE(m.authors()[0].emailAddress(), QStringLiteral("aseigo@kde.org")); QCOMPARE(m.translators().size(), 1); QCOMPARE(m.translators()[0].name(), QStringLiteral("No One")); QCOMPARE(m.translators()[0].emailAddress(), QStringLiteral("no.one@kde.org")); QCOMPARE(m.otherContributors().size(), 1); QCOMPARE(m.otherContributors()[0].name(), QStringLiteral("No One")); QCOMPARE(m.otherContributors()[0].emailAddress(), QStringLiteral("no.one@kde.org")); QVERIFY(m.isEnabledByDefault()); QCOMPARE(m.license(), QStringLiteral("LGPL")); QCOMPARE(m.copyrightText(), QStringLiteral("(c) Alex Richardson 2015")); QCOMPARE(m.version(), QStringLiteral("1.0")); QCOMPARE(m.website(), QStringLiteral("https://plasma.kde.org/")); QCOMPARE(m.serviceTypes(), QStringList() << QStringLiteral("Plasma/DataEngine")); QCOMPARE(m.mimeTypes(), QStringList() << QStringLiteral("image/png")); } void testTranslations() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson("{ \"KPlugin\": {\n" "\"Name\": \"Name\",\n" "\"Name[de]\": \"Name (de)\",\n" "\"Name[de_DE]\": \"Name (de_DE)\",\n" "\"Description\": \"Description\",\n" "\"Description[de]\": \"Beschreibung (de)\",\n" "\"Description[de_DE]\": \"Beschreibung (de_DE)\"\n" "}\n}", &e).object(); KPluginMetaData m(jo, QString()); QLocale::setDefault(QLocale::c()); QCOMPARE(m.name(), QStringLiteral("Name")); QCOMPARE(m.description(), QStringLiteral("Description")); QLocale::setDefault(QLocale(QStringLiteral("de_DE"))); QCOMPARE(m.name(), QStringLiteral("Name (de_DE)")); QCOMPARE(m.description(), QStringLiteral("Beschreibung (de_DE)")); QLocale::setDefault(QLocale(QStringLiteral("de_CH"))); QCOMPARE(m.name(), QStringLiteral("Name (de)")); QCOMPARE(m.description(), QStringLiteral("Beschreibung (de)")); QLocale::setDefault(QLocale(QStringLiteral("fr_FR"))); QCOMPARE(m.name(), QStringLiteral("Name")); QCOMPARE(m.description(), QStringLiteral("Description")); } void testReadStringList() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson("{\n" "\"String\": \"foo\",\n" "\"OneArrayEntry\": [ \"foo\" ],\n" "\"Bool\": true,\n" // make sure booleans are accepted "\"QuotedBool\": \"true\",\n" // make sure booleans are accepted "\"Number\": 12345,\n" // number should also work "\"QuotedNumber\": \"12345\",\n" // number should also work "\"EmptyArray\": [],\n" "\"NumberArray\": [1, 2, 3],\n" "\"BoolArray\": [true, false, true],\n" "\"StringArray\": [\"foo\", \"bar\"],\n" "\"Null\": null,\n" // should return empty list "\"QuotedNull\": \"null\",\n" // this is okay, it is a string "\"ArrayWithNull\": [ \"foo\", null, \"bar\"],\n" // TODO: null is converted to empty string, is this okay? "\"Object\": { \"foo\": \"bar\" }\n" // should return empty list "}", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"String\" to be a string list. Treating it as a list with a single entry: \"foo\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("String")), QStringList(QStringLiteral("foo")));; QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("OneArrayEntry")), QStringList(QStringLiteral("foo"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"Bool\" to be a string list. Treating it as a list with a single entry: \"true\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Bool")), QStringList(QStringLiteral("true"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"QuotedBool\" to be a string list. Treating it as a list with a single entry: \"true\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("QuotedBool")), QStringList(QStringLiteral("true"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"Number\" to be a string list. Treating it as a list with a single entry: \"12345\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Number")), QStringList(QStringLiteral("12345"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"QuotedNumber\" to be a string list. Treating it as a list with a single entry: \"12345\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("QuotedNumber")), QStringList(QStringLiteral("12345"))); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("EmptyArray")), QStringList()); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("NumberArray")), QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("BoolArray")), QStringList() << QStringLiteral("true") << QStringLiteral("false") << QStringLiteral("true")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("StringArray")), QStringList() << QStringLiteral("foo") << QStringLiteral("bar")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Null")), QStringList()); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"QuotedNull\" to be a string list. Treating it as a list with a single entry: \"null\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("QuotedNull")), QStringList(QStringLiteral("null"))); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("ArrayWithNull")), QStringList() << QStringLiteral("foo") << QStringLiteral("") << QStringLiteral("bar")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Object")), QStringList()); } void testFromDesktopFile() { const QString dfile = QFINDTESTDATA("data/fakeplugin.desktop"); KPluginMetaData md(dfile); QVERIFY(md.isValid()); QCOMPARE(md.pluginId(), QStringLiteral("fakeplugin")); QCOMPARE(md.fileName(), QStringLiteral("fakeplugin")); QCOMPARE(md.metaDataFileName(), dfile); QCOMPARE(md.iconName(), QStringLiteral("preferences-system-time")); QCOMPARE(md.license(), QStringLiteral("LGPL")); QCOMPARE(md.website(), QStringLiteral("https://kde.org/")); QCOMPARE(md.category(), QStringLiteral("Examples")); QCOMPARE(md.version(), QStringLiteral("1.0")); QCOMPARE(md.dependencies(), QStringList()); QCOMPARE(md.isHidden(), false); QCOMPARE(md.serviceTypes(), QStringList(QStringLiteral("KService/NSA"))); QCOMPARE(md.mimeTypes(), QStringList() << QStringLiteral("image/png") << QStringLiteral("application/pdf")); auto kp = md.rawData()[QStringLiteral("KPlugin")].toObject(); QStringList formFactors = KPluginMetaData::readStringList(kp, QStringLiteral("FormFactors")); QCOMPARE(formFactors, QStringList() << QStringLiteral("mediacenter") << QStringLiteral("desktop")); QCOMPARE(md.formFactors(), QStringList() << QStringLiteral("mediacenter") << QStringLiteral("desktop")); const QString dfilehidden = QFINDTESTDATA("data/hiddenplugin.desktop"); KPluginMetaData mdhidden(dfilehidden); QVERIFY(mdhidden.isValid()); QCOMPARE(mdhidden.isHidden(), true); } void twoStepsParseTest() { QStandardPaths::setTestModeEnabled(true); const QString dfile = QFINDTESTDATA("data/twostepsparsetest.desktop"); const QString typesPath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); KPluginMetaData md = KPluginMetaData::fromDesktopFile(dfile, QStringList() << typesPath); QVERIFY(md.isValid()); QStringList list = KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Test-List")); QCOMPARE(list, QStringList({QStringLiteral("first"), QStringLiteral("second")})); } void testServiceTypes_data() { const QString kdevServiceTypePath = QFINDTESTDATA("data/servicetypes/fake-kdevelopplugin.desktop"); const QString invalidServiceTypePath = QFINDTESTDATA("data/servicetypes/invalid-servicetype.desktop"); const QString exampleServiceTypePath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); QVERIFY(!kdevServiceTypePath.isEmpty()); QVERIFY(!invalidServiceTypePath.isEmpty()); QVERIFY(!exampleServiceTypePath.isEmpty()); } void testServiceType() { const QString typesPath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); QVERIFY(!typesPath.isEmpty()); const QString inputPath = QFINDTESTDATA("data/servicetypes/example-input.desktop"); QVERIFY(!inputPath.isEmpty()); KPluginMetaData md = KPluginMetaData::fromDesktopFile(inputPath, QStringList() << typesPath); QVERIFY(md.isValid()); QCOMPARE(md.name(), QStringLiteral("Example")); QCOMPARE(md.serviceTypes(), QStringList() << QStringLiteral("foo/bar") << QStringLiteral("bar/foo")); // qDebug().noquote() << QJsonDocument(md.rawData()).toJson(); QCOMPARE(md.rawData().size(), 8); QVERIFY(md.rawData().value(QStringLiteral("KPlugin")).isObject()); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Integer")), QJsonValue(42)); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Bool")), QJsonValue(true)); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Double")), QJsonValue(42.42)); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-String")), QJsonValue(QStringLiteral("foobar"))); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-List")), QJsonValue(QJsonArray::fromStringList(QStringList() << QStringLiteral("a") << QStringLiteral("b") << QStringLiteral("c") << QStringLiteral("def")))); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Size")), QJsonValue(QStringLiteral("10,20"))); // QSize no longer supported (and also no longer used) QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Unknown")), QJsonValue(QStringLiteral("true"))); // unknown property -> string } void testBadGroupsInServiceType() { const QString typesPath = QFINDTESTDATA("data/servicetypes/bad-groups-servicetype.desktop"); QVERIFY(!typesPath.isEmpty()); const QString inputPath = QFINDTESTDATA("data/servicetypes/bad-groups-input.desktop"); QVERIFY(!inputPath.isEmpty()); QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[PropertyDef::MissingTerminator\""); QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[PropertyDef::\""); QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[\""); QTest::ignoreMessage(QtWarningMsg, "Read empty .desktop file group name! Invalid file?"); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Skipping invalid group \"\" in service type \".*/bad-groups-servicetype.desktop\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Skipping invalid group \"DoesNotStartWithPropertyDef::SomeOtherProperty\" in service type \".+/data/servicetypes/bad-groups-servicetype.desktop\""))); QTest::ignoreMessage(QtWarningMsg, "Could not find Type= key in group \"PropertyDef::MissingType\""); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Property type \"integer\" is not a known QVariant type. Found while parsing property definition for \"InvalidType\" in \".+/data/servicetypes/bad-groups-servicetype.desktop\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=11\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=13\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=14\""))); KPluginMetaData md = KPluginMetaData::fromDesktopFile(inputPath, QStringList() << typesPath); QVERIFY(md.isValid()); QCOMPARE(md.name(), QStringLiteral("Bad Groups")); // qDebug().noquote() << QJsonDocument(md.rawData()).toJson(); QCOMPARE(md.rawData().size(), 8); QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkay")), QJsonValue(10)); // integer // 11 is empty group QCOMPARE(md.rawData().value(QStringLiteral("MissingTerminator")), QJsonValue(12)); // accept missing group terminator (for now) -> integer // 13 is empty group name // 14 is empty group name QCOMPARE(md.rawData().value(QStringLiteral("SomeOtherProperty")), QJsonValue(QStringLiteral("15"))); // does not start with PropertyDef:: -> fall back to string QCOMPARE(md.rawData().value(QStringLiteral("TrailingSpacesAreOkay")), QJsonValue(16)); // accept trailing spaces in group name -> integer QCOMPARE(md.rawData().value(QStringLiteral("MissingType")), QJsonValue(QStringLiteral("17"))); // Type= missing -> fall back to string QCOMPARE(md.rawData().value(QStringLiteral("InvalidType")), QJsonValue(QStringLiteral("18"))); // Type= is invalid -> fall back to string - QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkayAgain")), QJsonValue(19)); // valid defintion after invalid ones should still work -> integer + QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkayAgain")), QJsonValue(19)); // valid definition after invalid ones should still work -> integer } void testJSONMetadata() { const QString inputPath = QFINDTESTDATA("data/testmetadata.json"); KPluginMetaData md(inputPath); QVERIFY(md.isValid()); QCOMPARE(md.name(), QStringLiteral("Test")); QCOMPARE(md.value(QStringLiteral("X-Plasma-MainScript")), QStringLiteral("ui/main.qml")); QJsonArray expected; expected.append(QStringLiteral("Export")); QCOMPARE(md.rawData().value(QStringLiteral("X-Purpose-PluginTypes")).toArray(), expected); } }; QTEST_MAIN(KPluginMetaDataTest) #include "kpluginmetadatatest.moc" diff --git a/src/desktoptojson/main.cpp b/src/desktoptojson/main.cpp index 1bb8c9c..2c50a8b 100644 --- a/src/desktoptojson/main.cpp +++ b/src/desktoptojson/main.cpp @@ -1,89 +1,89 @@ /****************************************************************************** * Copyright 2013 Sebastian Kügler * * Copyright 2014 Alex Richardson * * 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.1 of the License, or (at your option) version 3, or any * * later version accepted by the membership of KDE e.V. (or its * * successor approved by the membership of KDE e.V.), which shall * * act as a proxy defined in Section 6 of version 3 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library. If not, see * * . * * * ******************************************************************************/ #include "desktoptojson.h" #include static void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QByteArray localMsg = msg.toLocal8Bit(); switch (type) { case QtDebugMsg: fprintf(stdout, "%s\n", localMsg.constData()); break; case QtInfoMsg: fprintf(stdout, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); break; case QtWarningMsg: fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); break; case QtCriticalMsg: fprintf(stderr, "Error: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); break; case QtFatalMsg: fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); abort(); } } int main(int argc, char** argv) { qInstallMessageHandler(messageOutput); QCoreApplication app(argc, argv); const QString description = QStringLiteral("Converts desktop files to json"); app.setApplicationVersion(QStringLiteral("1.0")); const static auto _i = QStringLiteral("input"); const static auto _o = QStringLiteral("output"); const static auto _n = QStringLiteral("name"); const static auto _c = QStringLiteral("compat"); const static auto _s = QStringLiteral("serviceType"); QCommandLineOption input = QCommandLineOption(QStringList() << QStringLiteral("i") << _i, QStringLiteral("Read input from file"), _n); QCommandLineOption output = QCommandLineOption(QStringList() << QStringLiteral("o") << _o, QStringLiteral("Write output to file"), _n); QCommandLineOption verbose = QCommandLineOption(QStringList() << QStringLiteral("verbose"), QStringLiteral("Enable verbose (debug) output")); QCommandLineOption compat = QCommandLineOption(QStringList() << QStringLiteral("c") << _c, QStringLiteral("Generate JSON that is compatible with KPluginInfo instead of the new KPluginMetaData")); QCommandLineOption serviceTypes = QCommandLineOption(QStringList() << QStringLiteral("s") << _s, - QStringLiteral("The name or full path of a KServiceType defintion .desktop file. Can be passed multiple times"), _s); + QStringLiteral("The name or full path of a KServiceType definition .desktop file. Can be passed multiple times"), _s); QCommandLineParser parser; parser.addVersionOption(); parser.setApplicationDescription(description); parser.addHelpOption(); parser.addOption(input); parser.addOption(output); parser.addOption(verbose); parser.addOption(compat); parser.addOption(serviceTypes); DesktopToJson dtj(&parser, input, output, verbose, compat, serviceTypes); parser.process(app); return dtj.runMain(); } diff --git a/src/lib/caching/posix_fallocate_mac.h b/src/lib/caching/posix_fallocate_mac.h index e752db5..b4dfe07 100644 --- a/src/lib/caching/posix_fallocate_mac.h +++ b/src/lib/caching/posix_fallocate_mac.h @@ -1,113 +1,118 @@ /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla code. * * The Initial Developer of the Original Code is * Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Taras Glek * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ +#ifndef POSIX_FALLOCATE_MAC_H +#define POSIX_FALLOCATE_MAC_H + #include #include #include #include // created from the OSX-specific code from Mozilla's mozilla::fallocation() function // of which the licensing information is copied above. // Adaptation (C) 2015,2016 R.J.V. Bertin // From Linux `man posix_fallocate`: // DESCRIPTION // The function posix_fallocate() ensures that disk space is allocated for // the file referred to by the descriptor fd for the bytes in the range // starting at offset and continuing for len bytes. After a successful // call to posix_fallocate(), subsequent writes to bytes in the specified // range are guaranteed not to fail because of lack of disk space. // // If the size of the file is less than offset+len, then the file is // increased to this size; otherwise the file size is left unchanged. // From OS X man fcntl: // F_PREALLOCATE Preallocate file storage space. Note: upon success, the space // that is allocated can be the same size or larger than the space // requested. // The F_PREALLOCATE command operates on the following structure: // typedef struct fstore { // u_int32_t fst_flags; /* IN: flags word */ // int fst_posmode; /* IN: indicates offset field */ // off_t fst_offset; /* IN: start of the region */ // off_t fst_length; /* IN: size of the region */ // off_t fst_bytesalloc; /* OUT: number of bytes allocated */ // } fstore_t; // The flags (fst_flags) for the F_PREALLOCATE command are as follows: // F_ALLOCATECONTIG Allocate contiguous space. // F_ALLOCATEALL Allocate all requested space or no space at all. // The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use // the offset field. The modes are as follows: // F_PEOFPOSMODE Allocate from the physical end of file. // F_VOLPOSMODE Allocate from the volume offset. // From OS X man ftruncate: // DESCRIPTION // ftruncate() and truncate() cause the file named by path, or referenced by fildes, to // be truncated (or extended) to length bytes in size. If the file size exceeds length, // any extra data is discarded. If the file size is smaller than length, the file // extended and filled with zeros to the indicated length. The ftruncate() form requires // the file to be open for writing. // Note: ftruncate() and truncate() do not modify the current file offset for any open // file descriptions associated with the file. static int posix_fallocate(int fd, off_t offset, off_t len) { off_t c_test; int ret; if (!__builtin_saddll_overflow(offset, len, &c_test)) { fstore_t store = {F_ALLOCATECONTIG, F_PEOFPOSMODE, 0, offset + len}; - // Try to get a continous chunk of disk space + // Try to get a continuous chunk of disk space fcntl(fd, F_PREALLOCATE, &store); if (ret < 0) { // OK, perhaps we are too fragmented, allocate non-continuous store.fst_flags = F_ALLOCATEALL; ret = fcntl(fd, F_PREALLOCATE, &store); if (ret < 0) { return ret; } } ret = ftruncate(fd, offset + len); } else { // offset+len would overflow. ret = -1; } return ret; } + +#endif diff --git a/src/lib/io/kbackup.cpp b/src/lib/io/kbackup.cpp index 08d39c1..dc27218 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 "kbackup.h" + #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/kdirwatch.cpp b/src/lib/io/kdirwatch.cpp index bfbba80..b61ee1d 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 +// February 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 // 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 + * level daemon (fam), to which applications connect for + * notification of file changes. For NFS, the fam daemon * 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) + // Emit the signals delayed, to avoid unexpected re-entrance 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) + // propagate dirty flag to dependent 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. + // Report and clear 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 f7bb3ee..8455208 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 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 @c nullptr for parent-less KDataTools) */ explicit 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 + * @param skippedToo if true, the skipped directories (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 24ad770..541bb06 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 class QSocketNotifier; #if HAVE_FAM #include #include #endif #include // time_t, ino_t #include #define invalid_ctime (static_cast(-1)) #if HAVE_QFILESYSTEMWATCHER #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 + // can safely be reported as they occur. File changes i.e. those that emit "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/kprocess.h b/src/lib/io/kprocess.h index 103aa43..40e3a9b 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 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 + * shell, it will transparently 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/kjob.h b/src/lib/jobs/kjob.h index 1540b24..9a0f571 100644 --- a/src/lib/jobs/kjob.h +++ b/src/lib/jobs/kjob.h @@ -1,655 +1,655 @@ /* 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 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 @c nullptr 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 + * your user interface will effectively 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. + * accidental reentrance 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: // These signals must be connected from KIO::KCoreDirLister (among others), // therefore they must be public. /** * 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. * * @note This is a private signal, it shouldn'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. * * @note This is a private signal, it shouldn'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). * * @note This is a private signal, it shouldn'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.). * * @note This is a private signal, it shouldn'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. * * @note This is a private signal, it shouldn'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. * * @note This is a private signal, it shouldn'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/kaboutdata.cpp b/src/lib/kaboutdata.cpp index 76471e6..85f67c7 100644 --- a/src/lib/kaboutdata.cpp +++ b/src/lib/kaboutdata.cpp @@ -1,1208 +1,1208 @@ /* * 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 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; } Q_FALLTHROUGH(); // 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 + // see KAboutData::desktopFileName() for details // 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 + // see KAboutData::desktopFileName() for details 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 desktopFileName has been explicitly 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 a140f07..b733982 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 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 + * information or by using a complete SPDX license expression parser. + * @note SPDX identifiers are case-insensitive. Do not use case-sensitive 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" + * "org.bar.foo.service" or 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/kcoreaddons.cpp b/src/lib/kcoreaddons.cpp index 2e8d876..4b9ffc9 100644 --- a/src/lib/kcoreaddons.cpp +++ b/src/lib/kcoreaddons.cpp @@ -1,35 +1,36 @@ /* * This file is part of the KDE Libraries * Copyright (C) 2016 David Edmundson * * 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 "kcoreaddons.h" + #include -#include "kcoreaddons.h" #include "kcoreaddons_version.h" QString KCoreAddons::versionString() { return QStringLiteral(KCOREADDONS_VERSION_STRING); } uint KCoreAddons::version() { return KCOREADDONS_VERSION; } diff --git a/src/lib/plugin/kpluginfactory.h b/src/lib/plugin/kpluginfactory.h index ab5418a..1d5b248 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 // 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 @c nullptr 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 @c nullptr 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 @c nullptr 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. + * provide generic factories for plugins implemented 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/randomness/krandomsequence.cpp b/src/lib/randomness/krandomsequence.cpp index dc4393c..de8293e 100644 --- a/src/lib/randomness/krandomsequence.cpp +++ b/src/lib/randomness/krandomsequence.cpp @@ -1,217 +1,217 @@ /* 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 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 "krandomsequence.h" #include "krandom.h" class Q_DECL_HIDDEN KRandomSequence::Private { public: enum {SHUFFLE_TABLE_SIZE = 32}; void draw(); // Generate the random number int lngSeed1; int lngSeed2; int lngShufflePos; int shuffleArray[SHUFFLE_TABLE_SIZE]; }; ////////////////////////////////////////////////////////////////////////////// // Construction / Destruction ////////////////////////////////////////////////////////////////////////////// KRandomSequence::KRandomSequence(long lngSeed1) : d(new Private) { // Seed the generator setSeed(lngSeed1); } KRandomSequence::KRandomSequence(int lngSeed1) : d(new Private) { // Seed the generator setSeed(lngSeed1); } KRandomSequence::~KRandomSequence() { delete d; } KRandomSequence::KRandomSequence(const KRandomSequence &a) : d(new Private) { *d = *a.d; } KRandomSequence &KRandomSequence::operator=(const KRandomSequence &a) { if (this != &a) { *d = *a.d; } return *this; } ////////////////////////////////////////////////////////////////////////////// // Member Functions ////////////////////////////////////////////////////////////////////////////// void KRandomSequence::setSeed(long lngSeed1) { setSeed(static_cast(lngSeed1)); } void KRandomSequence::setSeed(int lngSeed1) { // Convert the positive seed number to a negative one so that the draw() - // function can intialise itself the first time it is called. We just have + // function can initialise itself the first time it is called. We just have // to make sure that the seed used != 0 as zero perpetuates itself in a // sequence of random numbers. if (lngSeed1 < 0) { d->lngSeed1 = -1; } else if (lngSeed1 == 0) { d->lngSeed1 = -((KRandom::random() & ~1) + 1); } else { d->lngSeed1 = -lngSeed1; } } static const int sMod1 = 2147483563; static const int sMod2 = 2147483399; void KRandomSequence::Private::draw() { static const int sMM1 = sMod1 - 1; static const int sA1 = 40014; static const int sA2 = 40692; static const int sQ1 = 53668; static const int sQ2 = 52774; static const int sR1 = 12211; static const int sR2 = 3791; static const int sDiv = 1 + sMM1 / SHUFFLE_TABLE_SIZE; // Long period (>2 * 10^18) random number generator of L'Ecuyer with // Bayes-Durham shuffle and added safeguards. Returns a uniform random // deviate between 0.0 and 1.0 (exclusive of the endpoint values). Call // with a negative number to initialize; thereafter, do not alter idum // between successive deviates in a sequence. RNMX should approximate // the largest floating point value that is less than 1. int j; // Index for the shuffle table int k; // Initialise if (lngSeed1 <= 0) { lngSeed2 = lngSeed1; // Load the shuffle table after 8 warm-ups for (j = SHUFFLE_TABLE_SIZE + 7; j >= 0; --j) { k = lngSeed1 / sQ1; lngSeed1 = sA1 * (lngSeed1 - k * sQ1) - k * sR1; if (lngSeed1 < 0) { lngSeed1 += sMod1; } if (j < SHUFFLE_TABLE_SIZE) { shuffleArray[j] = lngSeed1; } } lngShufflePos = shuffleArray[0]; } // Start here when not initializing // Compute lngSeed1 = ( lngIA1*lngSeed1 ) % lngIM1 without overflows // by Schrage's method k = lngSeed1 / sQ1; lngSeed1 = sA1 * (lngSeed1 - k * sQ1) - k * sR1; if (lngSeed1 < 0) { lngSeed1 += sMod1; } // Compute lngSeed2 = ( lngIA2*lngSeed2 ) % lngIM2 without overflows // by Schrage's method k = lngSeed2 / sQ2; lngSeed2 = sA2 * (lngSeed2 - k * sQ2) - k * sR2; if (lngSeed2 < 0) { lngSeed2 += sMod2; } j = lngShufflePos / sDiv; lngShufflePos = shuffleArray[j] - lngSeed2; shuffleArray[j] = lngSeed1; if (lngShufflePos < 1) { lngShufflePos += sMM1; } } void KRandomSequence::modulate(int i) { d->lngSeed2 -= i; if (d->lngSeed2 < 0) { d->lngShufflePos += sMod2; } d->draw(); d->lngSeed1 -= i; if (d->lngSeed1 < 0) { d->lngSeed1 += sMod1; } d->draw(); } double KRandomSequence::getDouble() { static const double finalAmp = 1.0 / double(sMod1); static const double epsilon = 1.2E-7; static const double maxRand = 1.0 - epsilon; double temp; d->draw(); // Return a value that is not one of the endpoints if ((temp = finalAmp * d->lngShufflePos) > maxRand) { // We don't want to return 1.0 return maxRand; } else { return temp; } } unsigned long KRandomSequence::getLong(unsigned long max) { return getInt(static_cast(max)); } unsigned int KRandomSequence::getInt(unsigned int max) { d->draw(); return max ? ((static_cast(d->lngShufflePos)) % max) : 0; } bool KRandomSequence::getBool() { d->draw(); return ((static_cast(d->lngShufflePos)) & 1); } diff --git a/src/lib/text/ktexttohtml.cpp b/src/lib/text/ktexttohtml.cpp index 14bc8ef..28e629e 100644 --- a/src/lib/text/ktexttohtml.cpp +++ b/src/lib/text/ktexttohtml.cpp @@ -1,527 +1,527 @@ /* Copyright (c) 2002 Dave Corrie Copyright (c) 2014 Daniel Vrátil 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 "ktexttohtml.h" #include "ktexttohtml_p.h" #include "ktexttohtmlemoticonsinterface.h" #include #include #include #include #include #include #include #include #include "kcoreaddons_debug.h" static KTextToHTMLEmoticonsInterface *s_emoticonsInterface = nullptr; static void loadEmoticonsPlugin() { static bool triedLoadPlugin = false; if (!triedLoadPlugin) { triedLoadPlugin = true; // Check if QGuiApplication::platformName property exists. This is a // hackish way of determining whether we are running QGuiApplication, // because we cannot load the FrameworkIntegration plugin into a - // QCoreApplication, as it would crash immediatelly + // QCoreApplication, as it would crash immediately if (qApp->metaObject()->indexOfProperty("platformName") > -1) { QPluginLoader lib(QStringLiteral("kf5/KEmoticonsIntegrationPlugin")); QObject *rootObj = lib.instance(); if (rootObj) { s_emoticonsInterface = rootObj->property(KTEXTTOHTMLEMOTICONS_PROPERTY).value(); } } } if (!s_emoticonsInterface) { s_emoticonsInterface = new KTextToHTMLEmoticonsDummy(); } } KTextToHTMLHelper::KTextToHTMLHelper(const QString &plainText, int pos, int maxUrlLen, int maxAddressLen) : mText(plainText) , mMaxUrlLen(maxUrlLen) , mMaxAddressLen(maxAddressLen) , mPos(pos) { } KTextToHTMLEmoticonsInterface* KTextToHTMLHelper::emoticonsInterface() const { if (!s_emoticonsInterface) { loadEmoticonsPlugin(); } return s_emoticonsInterface; } QString KTextToHTMLHelper::getEmailAddress() { QString address; if (mText[mPos] == QLatin1Char('@')) { // the following characters are allowed in a dot-atom (RFC 2822): // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~ static const QString allowedSpecialChars = QStringLiteral(".!#$%&'*+-/=?^_`{|}~"); // determine the local part of the email address int start = mPos - 1; while (start >= 0 && mText[start].unicode() < 128 && (mText[start].isLetterOrNumber() || mText[start] == QLatin1Char('@') || // allow @ to find invalid email addresses allowedSpecialChars.indexOf(mText[start]) != -1)) { if (mText[start] == QLatin1Char('@')) { return QString(); // local part contains '@' -> no email address } --start; } ++start; // we assume that an email address starts with a letter or a digit while ((start < mPos) && !mText[start].isLetterOrNumber()) { ++start; } if (start == mPos) { return QString(); // local part is empty -> no email address } // determine the domain part of the email address int dotPos = INT_MAX; int end = mPos + 1; while (end < mText.length() && (mText[end].isLetterOrNumber() || mText[end] == QLatin1Char('@') || // allow @ to find invalid email addresses mText[end] == QLatin1Char('.') || mText[end] == QLatin1Char('-'))) { if (mText[end] == QLatin1Char('@')) { return QString(); // domain part contains '@' -> no email address } if (mText[end] == QLatin1Char('.')) { dotPos = qMin(dotPos, end); // remember index of first dot in domain } ++end; } // we assume that an email address ends with a letter or a digit while ((end > mPos) && !mText[end - 1].isLetterOrNumber()) { --end; } if (end == mPos) { return QString(); // domain part is empty -> no email address } if (dotPos >= end) { return QString(); // domain part doesn't contain a dot } if (end - start > mMaxAddressLen) { return QString(); // too long -> most likely no email address } address = mText.mid(start, end - start); mPos = end - 1; } return address; } bool KTextToHTMLHelper::atUrl() const { // the following characters are allowed in a dot-atom (RFC 2822): // a-z A-Z 0-9 . ! # $ % & ' * + - / = ? ^ _ ` { | } ~ static const QString allowedSpecialChars = QStringLiteral(".!#$%&'*+-/=?^_`{|}~"); // the character directly before the URL must not be a letter, a number or // any other character allowed in a dot-atom (RFC 2822). if ((mPos > 0) && (mText[mPos - 1].isLetterOrNumber() || (allowedSpecialChars.indexOf(mText[mPos - 1]) != -1))) { return false; } QChar ch = mText[mPos]; return (ch == QLatin1Char('h') && (mText.midRef(mPos, 7) == QLatin1String("http://") || mText.midRef(mPos, 8) == QLatin1String("https://"))) || (ch == QLatin1Char('v') && mText.midRef(mPos, 6) == QLatin1String("vnc://")) || (ch == QLatin1Char('f') && (mText.midRef(mPos, 7) == QLatin1String("fish://") || mText.midRef(mPos, 6) == QLatin1String("ftp://") || mText.midRef(mPos, 7) == QLatin1String("ftps://"))) || (ch == QLatin1Char('s') && (mText.midRef(mPos, 7) == QLatin1String("sftp://") || mText.midRef(mPos, 6) == QLatin1String("smb://"))) || (ch == QLatin1Char('m') && mText.midRef(mPos, 7) == QLatin1String("mailto:")) || (ch == QLatin1Char('w') && mText.midRef(mPos, 4) == QLatin1String("www.")) || (ch == QLatin1Char('f') && (mText.midRef(mPos, 4) == QLatin1String("ftp.") || mText.midRef(mPos, 7) == QLatin1String("file://"))) || (ch == QLatin1Char('n') && mText.midRef(mPos, 5) == QLatin1String("news:")); } bool KTextToHTMLHelper::isEmptyUrl(const QString &url) const { return url.isEmpty() || url == QLatin1String("http://") || url == QLatin1String("https://") || url == QLatin1String("fish://") || url == QLatin1String("ftp://") || url == QLatin1String("ftps://") || url == QLatin1String("sftp://") || url == QLatin1String("smb://") || url == QLatin1String("vnc://") || url == QLatin1String("mailto") || url == QLatin1String("www") || url == QLatin1String("ftp") || url == QLatin1String("news") || url == QLatin1String("news://"); } QString KTextToHTMLHelper::getUrl(bool *badurl) { QString url; if (atUrl()) { // NOTE: see http://tools.ietf.org/html/rfc3986#appendix-A and especially appendix-C // Appendix-C mainly says, that when extracting URLs from plain text, line breaks shall // be allowed and should be ignored when the URI is extracted. // This implementation follows this recommendation and // allows the URL to be enclosed within different kind of brackets/quotes // If an URL is enclosed, whitespace characters are allowed and removed, otherwise // the URL ends with the first whitespace // Also, if the URL is enclosed in brackets, the URL itself is not allowed // to contain the closing bracket, as this would be detected as the end of the URL QChar beforeUrl, afterUrl; // detect if the url has been surrounded by brackets or quotes if (mPos > 0) { beforeUrl = mText[mPos - 1]; /*if ( beforeUrl == '(' ) { afterUrl = ')'; } else */if (beforeUrl == QLatin1Char('[')) { afterUrl = QLatin1Char(']'); } else if (beforeUrl == QLatin1Char('<')) { afterUrl = QLatin1Char('>'); } else if (beforeUrl == QLatin1Char('>')) { // for e.g. http://..... afterUrl = QLatin1Char('<'); } else if (beforeUrl == QLatin1Char('"')) { afterUrl = QLatin1Char('"'); } } url.reserve(mMaxUrlLen); // avoid allocs int start = mPos; bool previousCharIsSpace = false; bool previousCharIsADoubleQuote = false; bool previousIsAnAnchor = false; while ((mPos < mText.length()) && (mText[mPos].isPrint() || mText[mPos].isSpace()) && ((afterUrl.isNull() && !mText[mPos].isSpace()) || (!afterUrl.isNull() && mText[mPos] != afterUrl))) { if (!previousCharIsSpace && (mText[mPos] == QLatin1Char('<')) && ((mPos + 1) < mText.length())) { // Fix Bug #346132: allow "http://www.foo.bar" // < inside a URL is not allowed, however there is a test which // checks that "http://some/path" should be allowed // Therefore: check if what follows is another URL and if so, stop here mPos++; if (atUrl()) { mPos--; break; } mPos--; } if (!previousCharIsSpace && (mText[mPos] == QLatin1Char(' ')) && ((mPos + 1) < mText.length())) { // Fix kmail bug: allow "http://www.foo.bar http://foo.bar/" // Therefore: check if what follows is another URL and if so, stop here mPos++; if (atUrl()) { mPos--; break; } mPos--; } if (mText[mPos].isSpace()) { previousCharIsSpace = true; } else if (!previousIsAnAnchor && mText[mPos] == QLatin1Char('[')) { break; } else if (!previousIsAnAnchor && mText[mPos] == QLatin1Char(']')) { break; } else { // skip whitespace if (previousCharIsSpace && mText[mPos] == QLatin1Char('<')) { url.append(QLatin1Char(' ')); break; } previousCharIsSpace = false; if (mText[mPos] == QLatin1Char('>') && previousCharIsADoubleQuote) { //it's an invalid url if (badurl) { *badurl = true; } return QString(); } if (mText[mPos] == QLatin1Char('"')) { previousCharIsADoubleQuote = true; } else { previousCharIsADoubleQuote = false; } if (mText[mPos] == QLatin1Char('#')) { previousIsAnAnchor = true; } url.append(mText[mPos]); if (url.length() > mMaxUrlLen) { break; } } ++mPos; } if (isEmptyUrl(url) || (url.length() > mMaxUrlLen)) { mPos = start; url.clear(); return url; } else { --mPos; } } // HACK: This is actually against the RFC. However, most people don't properly escape the URL in - // their text with "" or <>. That leads to people writing an url, followed immediatley by + // their text with "" or <>. That leads to people writing an url, followed immediately by // a dot to finish the sentence. That would lead the parser to include the dot in the url, // even though that is not wanted. So work around that here. // Most real-life URLs hopefully don't end with dots or commas. static const QString wordBoundaries = QStringLiteral(".,:!?)>"); if (url.length() > 1) { do { if (wordBoundaries.contains(url.at(url.length() - 1))) { url.chop(1); --mPos; } else { break; } } while (url.length() > 1); } return url; } QString KTextToHTMLHelper::highlightedText() { // formating symbols must be prepended with a whitespace if ((mPos > 0) && !mText[mPos - 1].isSpace()) { return QString(); } const QChar ch = mText[mPos]; if (ch != QLatin1Char('/') && ch != QLatin1Char('*') && ch != QLatin1Char('_') && ch != QLatin1Char('-')) { return QString(); } QRegExp re = QRegExp(QStringLiteral("\\%1((\\w+)([\\s-']\\w+)*( ?[,.:\\?!;])?)\\%2").arg(ch).arg(ch)); re.setMinimal(true); if (re.indexIn(mText, mPos) == mPos) { int length = re.matchedLength(); // there must be a whitespace after the closing formating symbol if (mPos + length < mText.length() && !mText[mPos + length].isSpace()) { return QString(); } mPos += length - 1; switch (ch.toLatin1()) { case '*': return QLatin1String("*") + re.cap(1) + QLatin1String("*"); case '_': return QLatin1String("_") + re.cap(1) + QLatin1String("_"); case '/': return QLatin1String("/") + re.cap(1) + QLatin1String("/"); case '-': return QLatin1String("-") + re.cap(1) + QLatin1String("-"); } } return QString(); } QString KTextToHTMLHelper::pngToDataUrl(const QString &iconPath) const { if (iconPath.isEmpty()) { return QString(); } QFile pngFile(iconPath); if (!pngFile.open(QIODevice::ReadOnly | QIODevice::Unbuffered)) { return QString(); } QByteArray ba = pngFile.readAll(); pngFile.close(); return QStringLiteral("data:image/png;base64,%1").arg(QLatin1String(ba.toBase64().constData())); } QString KTextToHTML::convertToHtml(const QString &plainText, const KTextToHTML::Options &flags, int maxUrlLen, int maxAddressLen) { KTextToHTMLHelper helper(plainText, maxUrlLen, maxAddressLen); QString str; QString result(static_cast(nullptr), helper.mText.length() * 2); QChar ch; int x; bool startOfLine = true; for (helper.mPos = 0, x = 0; helper.mPos < helper.mText.length(); ++helper.mPos, ++x) { ch = helper.mText[helper.mPos]; if (flags & PreserveSpaces) { if (ch == QLatin1Char(' ')) { if (helper.mPos + 1 < helper.mText.length()) { if (helper.mText[helper.mPos + 1] != QLatin1Char(' ')) { // A single space, make it breaking if not at the start or end of the line const bool endOfLine = helper.mText[helper.mPos + 1] == QLatin1Char('\n'); if (!startOfLine && !endOfLine) { result += QLatin1Char(' '); } else { result += QLatin1String(" "); } } else { // Whitespace of more than one space, make it all non-breaking while (helper.mPos < helper.mText.length() && helper.mText[helper.mPos] == QLatin1Char(' ')) { result += QLatin1String(" "); ++helper.mPos; ++x; } // We incremented once to often, undo that --helper.mPos; --x; } } else { // Last space in the text, it is non-breaking result += QLatin1String(" "); } if (startOfLine) { startOfLine = false; } continue; } else if (ch == QLatin1Char('\t')) { do { result += QLatin1String(" "); ++x; } while ((x & 7) != 0); --x; startOfLine = false; continue; } } if (ch == QLatin1Char('\n')) { result += QLatin1String("
\n"); // Keep the \n, so apps can figure out the quoting levels correctly. startOfLine = true; x = -1; continue; } startOfLine = false; if (ch == QLatin1Char('&')) { result += QLatin1String("&"); } else if (ch == QLatin1Char('"')) { result += QLatin1String("""); } else if (ch == QLatin1Char('<')) { result += QLatin1String("<"); } else if (ch == QLatin1Char('>')) { result += QLatin1String(">"); } else { const int start = helper.mPos; if (!(flags & IgnoreUrls)) { bool badUrl = false; str = helper.getUrl(&badUrl); if (badUrl) { QString resultBadUrl; const int helperTextSize(helper.mText.count()); for (int i = 0; i < helperTextSize; ++i) { const QChar chBadUrl = helper.mText[i]; if (chBadUrl == QLatin1Char('&')) { resultBadUrl += QLatin1String("&"); } else if (chBadUrl == QLatin1Char('"')) { resultBadUrl += QLatin1String("""); } else if (chBadUrl == QLatin1Char('<')) { resultBadUrl += QLatin1String("<"); } else if (chBadUrl == QLatin1Char('>')) { resultBadUrl += QLatin1String(">"); } else { resultBadUrl += chBadUrl; } } return resultBadUrl; } if (!str.isEmpty()) { QString hyperlink; if (str.left(4) == QLatin1String("www.")) { hyperlink = QLatin1String("http://") + str; } else if (str.left(4) == QLatin1String("ftp.")) { hyperlink = QLatin1String("ftp://") + str; } else { hyperlink = str; } result += QLatin1String("") + str.toHtmlEscaped() + QLatin1String(""); x += helper.mPos - start; continue; } str = helper.getEmailAddress(); if (!str.isEmpty()) { // len is the length of the local part int len = str.indexOf(QLatin1Char('@')); QString localPart = str.left(len); // remove the local part from the result (as '&'s have been expanded to // & we have to take care of the 4 additional characters per '&') result.truncate(result.length() - len - (localPart.count(QLatin1Char('&')) * 4)); x -= len; result += QLatin1String("") + str + QLatin1String(""); x += str.length() - 1; continue; } } if (flags & HighlightText) { str = helper.highlightedText(); if (!str.isEmpty()) { result += str; x += helper.mPos - start; continue; } } result += ch; } } if (flags & ReplaceSmileys) { const QStringList exclude = { QStringLiteral("(c)"), QStringLiteral("(C)"), QStringLiteral(">:-("), QStringLiteral(">:("), QStringLiteral("(B)"), QStringLiteral("(b)"), QStringLiteral("(P)"), QStringLiteral("(p)") , QStringLiteral("(O)"), QStringLiteral("(o)"), QStringLiteral("(D)"), QStringLiteral("(d)"), QStringLiteral("(E)"), QStringLiteral("(e)"), QStringLiteral("(K)"), QStringLiteral("(k)") , QStringLiteral("(I)"), QStringLiteral("(i)"), QStringLiteral("(L)"), QStringLiteral("(l)"), QStringLiteral("(8)"), QStringLiteral("(T)"), QStringLiteral("(t)"), QStringLiteral("(G)") , QStringLiteral("(g)"), QStringLiteral("(F)"), QStringLiteral("(f)"), QStringLiteral("(H)") , QStringLiteral("8)"), QStringLiteral("(N)"), QStringLiteral("(n)"), QStringLiteral("(Y)"), QStringLiteral("(y)"), QStringLiteral("(U)"), QStringLiteral("(u)"), QStringLiteral("(W)"), QStringLiteral("(w)") , QStringLiteral("(6)")}; result = helper.emoticonsInterface()->parseEmoticons(result, true, exclude); } return result; } diff --git a/src/lib/util/kformat.h b/src/lib/util/kformat.h index c9cf1f7..44dea35 100644 --- a/src/lib/util/kformat.h +++ b/src/lib/util/kformat.h @@ -1,407 +1,407 @@ /* This file is part of the KDE Frameworks Copyright (C) 2013 Alex Merry 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 KFORMAT_H #define KFORMAT_H #include #include #include #include class QDate; class QDateTime; class KFormatPrivate; /** * \file kformat.h */ /* The code in this class was copied from the old KLocale and modified by John Layt (and also Alex Merry) in the KDELIBS 4 to KDE Frameworks 5 transition in 2013. Albert Astals Cid is the original author of formatSpelloutDuration() originally named KLocale::prettyFormatDuration(). Michael Pyne is the original author of formatByteSize(). Michael Leupold is the original author of formatRelativeDate(() originally part of KFormat::formatDate(). */ /** * @class KFormat kformat.h KFormat * * KFormat provides support for formatting numbers and datetimes in * formats that are not supported by QLocale. * * @author John Layt , * Michael Pyne , * Albert Astals Cid , * * @short Class for formatting numbers and datetimes. * @since 5.0 */ class KCOREADDONS_EXPORT KFormat Q_DECL_FINAL { Q_GADGET public: /** * These binary units are used in KDE by the formatByteSize() * function. * * NOTE: There are several different units standards: * 1) SI (i.e. metric), powers-of-10. * 2) IEC, powers-of-2, with specific units KiB, MiB, etc. * 3) JEDEC, powers-of-2, used for solid state memory sizing which * is why you see flash cards labels as e.g. 4GB. These (ab)use * the metric units. Although JEDEC only defines KB, MB, GB, if * JEDEC is selected all units will be powers-of-2 with metric * prefixes for clarity in the event of sizes larger than 1024 GB. * * Although 3 different dialects are possible this enum only uses * metric names since adding all 3 different names of essentially the same * unit would be pointless. Use BinaryUnitDialect to control the exact * units returned. * * @see BinaryUnitDialect * @see formatByteSize */ enum BinarySizeUnits { /// Auto-choose a unit such that the result is in the range [0, 1000 or 1024) DefaultBinaryUnits = -1, // The first real unit must be 0 for the current implementation! UnitByte, ///< B 1 byte UnitKiloByte, ///< KiB/KB/kB 1024/1000 bytes. UnitMegaByte, ///< MiB/MB/MB 2^20/10^06 bytes. UnitGigaByte, ///< GiB/GB/GB 2^30/10^09 bytes. UnitTeraByte, ///< TiB/TB/TB 2^40/10^12 bytes. UnitPetaByte, ///< PiB/PB/PB 2^50/10^15 bytes. UnitExaByte, ///< EiB/EB/EB 2^60/10^18 bytes. UnitZettaByte, ///< ZiB/ZB/ZB 2^70/10^21 bytes. UnitYottaByte, ///< YiB/YB/YB 2^80/10^24 bytes. UnitLastUnit = UnitYottaByte }; /** * These units are used in KDE by the formatValue() function. * * @see formatValue * @since 5.49 */ enum class Unit { Other, Bit, ///< "bit" Byte, ///< "B" Meter, ///< "m" Hertz, ///< "Hz" }; /** * These prefixes are used in KDE by the formatValue() * function. * * IEC prefixes are only defined for integral units of information, e.g. * bits and bytes. * * @see BinarySizeUnits * @see formatValue * @since 5.49 */ enum class UnitPrefix { /// Auto-choose a unit such that the result is in the range [0, 1000 or 1024) AutoAdjust = -128, Yocto = 0, ///< --/-/y 10^-24 Zepto, ///< --/-/z 10^-21 Atto, ///< --/-/a 10^-18 Femto, ///< --/-/f 10^-15 Pico, ///< --/-/p 10^-12 Nano, ///< --/-/n 10^-9 Micro, ///< --/-/µ 10^-6 Milli, ///< --/-/m 10^-3 Centi, ///< --/-/c 0.01 Deci, ///< --/-/d 0.1 Unity, ///< "" 1 Deca, ///< --/-/da 10 Hecto, ///< --/-/h 100 Kilo, ///< Ki/K/k 1024/1000 Mega, ///< Mi/M/M 2^20/10^06 Giga, ///< Gi/G/G 2^30/10^09 Tera, ///< Ti/T/T 2^40/10^12 Peta, ///< Pi/P/P 2^50/10^15 Exa, ///< Ei/E/E 2^60/10^18 Zetta, ///< Zi/Z/Z 2^70/10^21 Yotta, ///< Yi/Y/Y 2^80/10^24 }; /** * This enum chooses what dialect is used for binary units. * * Note: Although JEDEC abuses the metric prefixes and can therefore be * confusing, it has been used to describe *memory* sizes for quite some time * and programs should therefore use either Default, JEDEC, or IEC 60027-2 * for memory sizes. * * On the other hand network transmission rates are typically in metric so * Default, Metric, or IEC (which is unambiguous) should be chosen. * * Normally choosing DefaultBinaryDialect is the best option as that uses * the user's selection for units. If the user has not selected a preference, * IECBinaryDialect will typically be used. * * @see BinarySizeUnits * @see formatByteSize */ enum BinaryUnitDialect { DefaultBinaryDialect = -1, ///< Used if no specific preference IECBinaryDialect, ///< KiB, MiB, etc. 2^(10*n) JEDECBinaryDialect, ///< KB, MB, etc. 2^(10*n) MetricBinaryDialect, ///< SI Units, kB, MB, etc. 10^(3*n) LastBinaryDialect = MetricBinaryDialect }; /** * Format flags for formatDuration() */ enum DurationFormatOption { DefaultDuration = 0x0, ///< Default formatting in localized 1:23:45 format InitialDuration = 0x1, ///< Default formatting in localized 1h23m45s format ShowMilliseconds = 0x2, ///< Include milliseconds in format, e.g. 1:23:45.678 HideSeconds = 0x4, ///< Hide the seconds, e.g. 1:23 or 1h23m, overrides ShowMilliseconds FoldHours = 0x8 ///< Fold the hours into the minutes, e.g. 83:45 or 83m45s, overrides HideSeconds }; Q_DECLARE_FLAGS(DurationFormatOptions, DurationFormatOption) Q_FLAG(DurationFormatOption) /** * Constructs a KFormat. * * @param locale the locale to use, defaults to the system locale */ explicit KFormat(const QLocale &locale = QLocale()); /** * Copy constructor */ KFormat(const KFormat &other); KFormat& operator=(const KFormat &other); /** * Destructor */ ~KFormat(); /** * Converts @p size from bytes to the appropriate string representation * using the binary unit dialect @p dialect and the specific units @p units. * * Example: * @code * QString metric, iec, jedec, small; * metric = formatByteSize(1000, 1, KFormat::MetricBinaryDialect, KFormat::UnitKiloByte); * iec = formatByteSize(1000, 1, KFormat::IECBinaryDialect, KFormat::UnitKiloByte); * jedec = formatByteSize(1000, 1, KFormat::JEDECBinaryDialect, KFormat::UnitKiloByte); * small = formatByteSize(100); * // metric == "1.0 kB", iec == "1.0 KiB", jedec == "1.0 KB", small == "100 B" * @endcode * * @param size size in bytes * @param precision number of places after the decimal point to use. KDE uses * 1 by default so when in doubt use 1. Whenever KFormat::UnitByte is used * (either explicitly or autoselected from KFormat::DefaultBinaryUnits), * the fractional part is always omitted. * @param dialect binary unit standard to use. Use DefaultBinaryDialect to * use the localized user selection unless you need to use a specific * unit type (such as displaying a flash memory size in JEDEC). * @param units specific unit size to use in result. Use * DefaultBinaryUnits to automatically select a unit that will return * a sanely-sized number. * @return converted size as a translated string including the units. * E.g. "1.23 KiB", "2 GB" (JEDEC), "4.2 kB" (Metric). * @see BinarySizeUnits * @see BinaryUnitDialect */ QString formatByteSize(double size, int precision = 1, KFormat::BinaryUnitDialect dialect = KFormat::DefaultBinaryDialect, KFormat::BinarySizeUnits units = KFormat::DefaultBinaryUnits) const; /** * Given a number of milliseconds, converts that to a string containing * the localized equivalent, e.g. 1:23:45 * * @param msecs Time duration in milliseconds * @param options options to use in the duration format * @return converted duration as a string - e.g. "1:23:45" "1h23m" */ QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const; /** * Given a number of milliseconds, converts that to a string containing * the localized equivalent to the requested decimal places. * * e.g. given formatDuration(60000), returns "1.0 minutes" * * @param msecs Time duration in milliseconds - * @param decimalPlaces Deciaml places to round off to, defaults to 2 + * @param decimalPlaces Decimal places to round off to, defaults to 2 * @return converted duration as a string - e.g. "5.5 seconds" "23.0 minutes" */ QString formatDecimalDuration(quint64 msecs, int decimalPlaces = 2) const; /** * Given a number of milliseconds, converts that to a spell-out string containing * the localized equivalent. * * e.g. given formatSpelloutDuration(60001) returns "1 minute" * given formatSpelloutDuration(62005) returns "1 minute and 2 seconds" * given formatSpelloutDuration(90060000) returns "1 day and 1 hour" * * @param msecs Time duration in milliseconds * @return converted duration as a string. * Units not interesting to the user, for example seconds or minutes when the first * unit is day, are not returned because they are irrelevant. The same applies for * seconds when the first unit is hour. */ QString formatSpelloutDuration(quint64 msecs) const; /** * Returns a string formatted to a relative date style. * * If the @p date falls within one week before or after the current date * then a relative date string will be returned, such as: * * Yesterday * * Today * * Tomorrow * * Last Tuesday * * Next Wednesday * * If the @p date falls outside this period then the @p format is used. * * @param date the date to be formatted * @param format the date format to use * * @return the date as a string */ QString formatRelativeDate(const QDate &date, QLocale::FormatType format) const; /** * Returns a string formatted to a relative datetime style. * * If the @p dateTime falls within one week before or after the current date * then a relative date string will be returned, such as: * * Yesterday, 3:00pm * * Today, 3:00pm * * Tomorrow, 3:00pm * * Last Tuesday, 3:00pm * * Next Wednesday, 3:00pm * * If the @p dateTime falls outside this period then the @p format is used. * * @param dateTime the date to be formatted * @param format the date format to use * * @return the date as a string */ QString formatRelativeDateTime(const QDateTime &dateTime, QLocale::FormatType format) const; /** * Converts @p value to the appropriate string representation * * Example: * @code * // sets formatted to "1.0 kbit" * auto formatted = format.formatValue(1000, KFormat::Unit::Bit, 1, KFormat::UnitPrefix::Kilo); * @endcode * * @param value value to be formatted * @param precision number of places after the decimal point to use. KDE uses * 1 by default so when in doubt use 1. * @param unit unit to use in result. * @param prefix specific prefix to use in result. Use UnitPrefix::AutoAdjust * to automatically select an appropriate prefix. * @param dialect prefix standard to use. Use DefaultBinaryDialect to * use the localized user selection unless you need to use a specific * unit type. Only meaningful for KFormat::Unit::Byte, and ignored for * all other units. * @return converted size as a translated string including prefix and unit. * E.g. "1.23 KiB", "2 GB" (JEDEC), "4.2 kB" (Metric), "1.2 kbit". * @see Unit * @see UnitPrefix * @see BinaryUnitDialect * @since 5.49 */ QString formatValue(double value, KFormat::Unit unit, int precision = 1, KFormat::UnitPrefix prefix = KFormat::UnitPrefix::AutoAdjust, KFormat::BinaryUnitDialect dialect = KFormat::DefaultBinaryDialect) const; /** * Converts @p value to the appropriate string representation * * Example: * @code * QString bits, slow, fast; * // sets bits to "1.0 kbit", slow to "1.0 kbit/s" and fast to "12.3 Mbit/s". * bits = format.formatValue(1000, QStringLiteral("bit"), 1, KFormat::UnitPrefix::Kilo); * slow = format.formatValue(1000, QStringLiteral("bit/s"); * fast = format.formatValue(12.3e6, QStringLiteral("bit/s"); * @endcode * * @param value value to be formatted * @param precision number of places after the decimal point to use. KDE uses * 1 by default so when in doubt use 1. * @param unit unit to use in result. * @param prefix specific prefix to use in result. Use UnitPrefix::AutoAdjust * to automatically select an appropriate prefix. * @return converted size as a translated string including prefix and unit. * E.g. "1.2 kbit", "2.4 kB", "12.3 Mbit/s" * @see UnitPrefix * @since 5.49 */ QString formatValue(double value, const QString& unit, int precision = 1, KFormat::UnitPrefix prefix = KFormat::UnitPrefix::AutoAdjust) const; private: QSharedDataPointer d; }; #endif // KFORMAT_H diff --git a/src/lib/util/kuser_win.cpp b/src/lib/util/kuser_win.cpp index bb38845..9fa0f18 100644 --- a/src/lib/util/kuser_win.cpp +++ b/src/lib/util/kuser_win.cpp @@ -1,919 +1,919 @@ /* * KUser - represent a user/account (Windows) * Copyright (C) 2007 Bernhard Loos * 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 "kcoreaddons_debug.h" #include #include #include // unique_ptr #include #include #include //Net* #include //GetProfilesDirectoryW #include //ConvertSidToStringSidW #include // this can't be a lambda due to a MSVC2012 bug // (works fine in 2010 and 2013) struct netApiBufferDeleter { void operator()(void *buffer) { if (buffer) { NetApiBufferFree(buffer); } } }; template class ScopedNetApiBuffer : public std::unique_ptr { public: // explicit scope resolution operator needed in ::netApiBufferDeleter // because of *another* MSVC bug :( inline explicit ScopedNetApiBuffer(T *data) : std::unique_ptr(data, ::netApiBufferDeleter()) {} }; const auto handleCloser = [](HANDLE h) { if (h != INVALID_HANDLE_VALUE) { CloseHandle(h); } }; typedef std::unique_ptr::type, decltype(handleCloser)> ScopedHANDLE; /** Make sure the NetApi functions are called with the correct level argument (for template functions) * This argument can be retrieved by using NetApiTypeInfo::level. In order to do so the type must be * registered by writing e.g. NETAPI_TYPE_INFO(GROUP_INFO, 0) for GROUP_INFO_0 */ template struct NetApiTypeInfo {}; #define NETAPI_TYPE_INFO(prefix, n) template<> struct NetApiTypeInfo { enum { level = n }; }; NETAPI_TYPE_INFO(GROUP_INFO, 0) NETAPI_TYPE_INFO(GROUP_INFO, 3) NETAPI_TYPE_INFO(USER_INFO, 0) NETAPI_TYPE_INFO(USER_INFO, 4) NETAPI_TYPE_INFO(USER_INFO, 11) NETAPI_TYPE_INFO(GROUP_USERS_INFO, 0) // T must be a USER_INFO_* structure template ScopedNetApiBuffer getUserInfo(LPCWSTR server, const QString &userName, NET_API_STATUS *errCode) { LPBYTE userInfoTmp = nullptr; // if level = 11 a USER_INFO_11 structure gets filled in and allocated by NetUserGetInfo(), etc. NET_API_STATUS status = NetUserGetInfo(server, (LPCWSTR)userName.utf16(), NetApiTypeInfo::level, &userInfoTmp); if (status != NERR_Success) { userInfoTmp = nullptr; } if (errCode) { *errCode = status; } return ScopedNetApiBuffer((T*)userInfoTmp); } //enumeration functions /** simplify calling the Net*Enum functions to prevent copy and paste for allUsers(), allUserNames(), allGroups(), allGroupNames() * @tparam T The type that is enumerated (e.g. USER_INFO_11) Must be registered using NETAPI_TYPE_INFO. * @param callback Callback for each listed object. Signature: void(const T&) * @param enumFunc This function enumerates the data using a Net* function. * It will be called in a loop as long as it returns ERROR_MORE_DATA. * */ template static void netApiEnumerate(uint maxCount, Callback callback, EnumFunction enumFunc) { NET_API_STATUS nStatus = NERR_Success; DWORD_PTR resumeHandle = 0; uint total = 0; int level = NetApiTypeInfo::level; do { LPBYTE buffer = nullptr; DWORD entriesRead = 0; DWORD totalEntries = 0; nStatus = enumFunc(level, &buffer, &entriesRead, &totalEntries, &resumeHandle); //qDebug("Net*Enum(level = %d) returned %d entries, total was (%d), status = %d, resume handle = %llx", // level, entriesRead, totalEntries, nStatus, resumeHandle); // buffer must always be freed, even if Net*Enum fails ScopedNetApiBuffer groupInfo((T*)buffer); if (nStatus == NERR_Success || nStatus == ERROR_MORE_DATA) { for (DWORD i = 0; total < maxCount && i < entriesRead; i++, total++) { callback(groupInfo.get()[i]); } } else { qWarning("NetApi enumerate function failed: status = %d", (int)nStatus); } } while (nStatus == ERROR_MORE_DATA); } template void enumerateAllUsers(uint maxCount, Callback callback) { netApiEnumerate(maxCount, callback, [](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) { // pass 0 as filter -> get all users // Why does this function take a DWORD* as resume handle and NetUserEnum/NetGroupGetUsers a UINT64* // Great API design by Microsoft... //casting the uint64* to uint32* is fine, it just writes to the first 32 bits return NetUserEnum(nullptr, level, 0, buffer, MAX_PREFERRED_LENGTH, count, total, (PDWORD)resumeHandle); }); } template void enumerateAllGroups(uint maxCount, Callback callback) { netApiEnumerate(maxCount, callback, [](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) { return NetGroupEnum(nullptr, level, buffer, MAX_PREFERRED_LENGTH, count, total, resumeHandle); }); } template void enumerateGroupsForUser(uint maxCount, const QString &name, Callback callback) { LPCWSTR nameStr = (LPCWSTR)name.utf16(); netApiEnumerate(maxCount, callback, [&](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) -> NET_API_STATUS { Q_UNUSED(resumeHandle); NET_API_STATUS ret = NetUserGetGroups(nullptr, nameStr, level, buffer, MAX_PREFERRED_LENGTH, count, total); - // if we return ERROR_MORE_DATA here it will result in an enless loop + // if we return ERROR_MORE_DATA here it will result in an endless loop if (ret == ERROR_MORE_DATA) { qCWarning(KCOREADDONS_DEBUG) << "NetUserGetGroups for user" << name << "returned ERROR_MORE_DATA. This should not happen!"; ret = NERR_Success; } return ret; }); } template void enumerateUsersForGroup(const QString &name, uint maxCount, Callback callback) { LPCWSTR nameStr = (LPCWSTR)name.utf16(); netApiEnumerate(maxCount, callback, [nameStr](int level, LPBYTE *buffer, DWORD *count, DWORD *total, PDWORD_PTR resumeHandle) { return NetGroupGetUsers(nullptr, nameStr, level, buffer, MAX_PREFERRED_LENGTH, count, total, resumeHandle); }); } class Q_DECL_HIDDEN KUser::Private : public QSharedData { typedef QExplicitlySharedDataPointer Ptr; Private() : isAdmin(false) {} //takes ownership over userInfo_ Private(KUserId uid, KGroupId gid, const QString &loginName, const QString &fullName, const QString &domain, const QString &homeDir, bool isAdmin) : uid(uid), gid(gid), loginName(loginName), fullName(fullName), domain(domain), homeDir(homeDir), isAdmin(isAdmin) { Q_ASSERT(uid.isValid()); } static QString guessHomeDir(const QString &username, KUserId uid) { // usri11_home_dir/usri4_home_dir is often empty // check whether it is the homedir for the current user and if not then fall back to "\" if (uid == KUserId::currentUserId()) { return QDir::homePath(); } QString homeDir; WCHAR profileDirPath[MAX_PATH]; DWORD bufSize = MAX_PATH; BOOL result = GetProfilesDirectoryW(profileDirPath, &bufSize); if (result) { // This might not be correct: e.g. with local user and domain user with same // In that case it could be C:\Users\Foo (local user) vs C:\Users\Foo.DOMAIN (domain user) // However it is still much better than the previous code which just returned the current users home dir homeDir = QString::fromWCharArray(profileDirPath) + QLatin1Char('\\') + username; } return homeDir; } public: static Ptr sharedNull; KUserId uid; KGroupId gid; QString loginName; QString fullName; QString domain; QString homeDir; bool isAdmin; /** Creates a user info from a SID (never returns null) */ static Ptr create(KUserId uid) { if (!uid.isValid()) { return sharedNull; } // now find the fully qualified name for the user DWORD nameBufferLen = UNLEN + 1; WCHAR nameBuffer[UNLEN + 1]; DWORD domainBufferLen = UNLEN + 1; WCHAR domainBuffer[UNLEN + 1]; SID_NAME_USE use; if (!LookupAccountSidW(nullptr, uid.nativeId(), nameBuffer, &nameBufferLen, domainBuffer, &domainBufferLen, &use)) { qCWarning(KCOREADDONS_DEBUG) << "Could not lookup user " << uid.toString() << "error =" << GetLastError(); return sharedNull; } QString loginName = QString::fromWCharArray(nameBuffer); QString domainName = QString::fromWCharArray(domainBuffer); if (use != SidTypeUser && use != SidTypeDeletedAccount) { qCWarning(KCOREADDONS_DEBUG).nospace() << "SID for " << domainName << "\\" << loginName << " (" << uid.toString() << ") is not of type user (" << SidTypeUser << " or " << SidTypeDeletedAccount << "). Got type " << use << " instead."; return sharedNull; } // now get the server name to query (could be null for local machine) LPWSTR servernameTmp = nullptr; NET_API_STATUS status = NetGetAnyDCName(nullptr, 0, (LPBYTE *)&servernameTmp); if (status != NERR_Success) { // this always fails on my desktop system, don't spam the output // qDebug("NetGetAnyDCName failed with error %d", status); } ScopedNetApiBuffer servername(servernameTmp); QString fullName; QString homeDir; KGroupId group; bool isAdmin = false; // must NOT pass the qualified name ("domain\user") here or lookup fails -> just the name // try USER_INFO_4 first, MSDN says it is valid only on servers (whatever that means), it works on my desktop system // If it fails fall back to USER_INFO11, which has all the needed information except primary group if (auto userInfo4 = getUserInfo(servername.get(), loginName, &status)) { Q_ASSERT(KUserId(userInfo4->usri4_user_sid) == uid); // if this is not the same we have a logic error fullName = QString::fromWCharArray(userInfo4->usri4_full_name); homeDir = QString::fromWCharArray(userInfo4->usri4_home_dir); isAdmin = userInfo4->usri4_priv == USER_PRIV_ADMIN; // now determine the primary group: const DWORD primaryGroup = userInfo4->usri4_primary_group_id; // primary group is a relative identifier, i.e. in order to get the SID for that group // we have to take the user SID and replace the last subauthority value with the relative identifier group = KGroupId(uid.nativeId()); // constructor does not check whether the sid refers to a group Q_ASSERT(group.isValid()); UCHAR numSubauthorities = *GetSidSubAuthorityCount(group.nativeId()); PDWORD lastSubAutority = GetSidSubAuthority(group.nativeId(), numSubauthorities - 1); *lastSubAutority = primaryGroup; } else if (auto userInfo11 = getUserInfo(servername.get(), loginName, &status)) { fullName = QString::fromWCharArray(userInfo11->usri11_full_name); homeDir = QString::fromWCharArray(userInfo11->usri11_home_dir); isAdmin = userInfo11->usri11_priv == USER_PRIV_ADMIN; } else { qCWarning(KCOREADDONS_DEBUG).nospace() << "Could not get information for user " << domainName << "\\" << loginName << ": error code = " << status; return sharedNull; } if (homeDir.isEmpty()) { homeDir = guessHomeDir(loginName, uid); } //if we couldn't find a primary group just take the first group found for this user if (!group.isValid()) { enumerateGroupsForUser(1, loginName, [&](const GROUP_USERS_INFO_0 &info) { group = KGroupId::fromName(QString::fromWCharArray(info.grui0_name)); }); } return Ptr(new Private(uid, group, loginName, fullName, domainName, homeDir, isAdmin)); } }; KUser::Private::Ptr KUser::Private::sharedNull(new KUser::Private()); KUser::KUser(UIDMode mode) { if (mode == UseEffectiveUID) { d = Private::create(KUserId::currentEffectiveUserId()); } else if (mode == UseRealUserID) { d = Private::create(KUserId::currentUserId()); } else { d = Private::sharedNull; } } KUser::KUser(K_UID uid) : d(Private::create(KUserId(uid))) { } KUser::KUser(KUserId uid) : d(Private::create(uid)) { } KUser::KUser(const QString &name) : d(Private::create(KUserId::fromName(name))) { } KUser::KUser(const char *name) : d(Private::create(KUserId::fromName(QString::fromLocal8Bit(name)))) { } 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.isValid(); } bool KUser::isSuperUser() const { return d->isAdmin; } QString KUser::loginName() const { return d->loginName; } QString KUser::homeDir() const { return d->homeDir; } // Some RAII objects to help uninitializing/destroying WinAPI stuff // used in faceIconPath. class COMInitializer { public: COMInitializer() : result(CoInitialize(nullptr)) {} ~COMInitializer() { if (SUCCEEDED(result)) { CoUninitialize(); } } HRESULT result; }; class W32Library { public: W32Library(HMODULE h): h(h) {} ~W32Library() { if (h) { FreeLibrary(h); } } operator HMODULE() { return h; } HMODULE h; }; // faceIconPath uses undocumented Windows API known as SHGetUserPicturePath, // only accessible by ordinal, unofficially documented at // http://undoc.airesoft.co.uk/shell32.dll/SHGetUserPicturePath.php // The function has a different ordinal and parameters on Windows XP and Vista/7. // These structs encapsulate the differences. struct FaceIconPath_XP { typedef HRESULT (WINAPI *funcptr_t)(LPCWSTR, DWORD, LPWSTR); static const int ordinal = 233; static HRESULT getPicturePath(funcptr_t SHGetUserPicturePathXP, LPCWSTR username, LPWSTR buf, UINT bufsize) { Q_UNUSED(bufsize); // assumes the buffer is MAX_PATH in size return SHGetUserPicturePathXP(username, 0, buf); } }; struct FaceIconPath_Vista { typedef HRESULT (WINAPI *funcptr_t)(LPCWSTR, DWORD, LPWSTR, UINT); static const int ordinal = 261; static HRESULT getPicturePath(funcptr_t SHGetUserPicturePathV, LPCWSTR username, LPWSTR buf, UINT bufsize) { return SHGetUserPicturePathV(username, 0, buf, bufsize); } }; template static QString faceIconPathImpl(LPCWSTR username) { static COMInitializer COMinit; static W32Library shellMod = LoadLibraryA("shell32.dll"); if (!shellMod) { return QString(); } static typename Platform::funcptr_t sgupp_ptr = reinterpret_cast( GetProcAddress(shellMod, MAKEINTRESOURCEA(Platform::ordinal))); if (!sgupp_ptr) { return QString(); } WCHAR pathBuf[MAX_PATH]; HRESULT res = Platform::getPicturePath(sgupp_ptr, username, pathBuf, MAX_PATH); if (res != S_OK) { return QString(); } return QString::fromWCharArray(pathBuf); } QString KUser::faceIconPath() const { if (!isValid()) { return QString(); } LPCWSTR username = reinterpret_cast(d->loginName.utf16()); if (QSysInfo::windowsVersion() == QSysInfo::WV_XP || QSysInfo::windowsVersion() == QSysInfo::WV_2003) { return faceIconPathImpl(username); } else if (QSysInfo::windowsVersion() >= QSysInfo::WV_VISTA) { return faceIconPathImpl(username); } return QString(); } QString KUser::shell() const { return isValid() ? QStringLiteral("cmd.exe") : QString(); } KUserId KUser::userId() const { return d->uid; } KGroupId KUser::groupId() const { return d->gid; } QVariant KUser::property(UserProperty which) const { if (which == FullName) { return QVariant(d->fullName); } return QVariant(); } KUser::~KUser() { } class Q_DECL_HIDDEN KUserGroup::Private : public QSharedData { public: QString name; KGroupId gid; Private() {} Private(const QString &name, KGroupId id) : name(name), gid(id) { if (!name.isEmpty()) { PBYTE groupInfoTmp = nullptr; NET_API_STATUS status = NetGroupGetInfo(nullptr, (LPCWSTR)name.utf16(), 0, &groupInfoTmp); // must always be freed, even on error ScopedNetApiBuffer groupInfo((GROUP_INFO_0 *)groupInfoTmp); if (status != NERR_Success) { qCWarning(KCOREADDONS_DEBUG) << "Failed to find group with name" << name << "error =" << status; groupInfo.reset(); } if (!id.isValid()) { gid = KGroupId::fromName(name); } } } }; KUserGroup::KUserGroup(const QString &_name) : d(new Private(_name, KGroupId())) { } KUserGroup::KUserGroup(const char *_name) : d(new Private(QLatin1String(_name), KGroupId())) { } static QString nameFromGroupId(KGroupId gid) { if (!gid.isValid()) { return QString(); } DWORD bufferLen = UNLEN + 1; WCHAR buffer[UNLEN + 1]; DWORD domainBufferLen = UNLEN + 1; WCHAR domainBuffer[UNLEN + 1]; SID_NAME_USE eUse; QString name; if (LookupAccountSidW(NULL, gid.nativeId(), buffer, &bufferLen, domainBuffer, &domainBufferLen, &eUse)) { if (eUse == SidTypeGroup || eUse == SidTypeWellKnownGroup) { name = QString::fromWCharArray(buffer); } else { qCWarning(KCOREADDONS_DEBUG) << QString::fromWCharArray(buffer) << "is not a group, SID type is" << eUse; } } return name; } KUserGroup::KUserGroup(KGroupId gid) : d(new Private(nameFromGroupId(gid), gid)) { } KUserGroup::KUserGroup(K_GID gid) { KGroupId groupId(gid); d = new Private(nameFromGroupId(groupId), groupId); } KUserGroup::KUserGroup(KUser::UIDMode mode) { KGroupId gid; if (mode == KUser::UseEffectiveUID) { gid = KGroupId::currentGroupId(); } else if (mode == KUser::UseRealUserID) { gid = KGroupId::currentEffectiveGroupId(); } d = new Private(nameFromGroupId(gid), gid); } 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 && d->name == group.d->name; } bool KUserGroup::isValid() const { return d->gid.isValid() && !d->name.isEmpty(); } QString KUserGroup::name() const { return d->name; } KGroupId KUserGroup::groupId() const { return d->gid; } KUserGroup::~KUserGroup() { } QList KUser::allUsers(uint maxCount) { QList result; // No advantage if we take a USER_INFO_11, since there is no way of copying it // and it is not owned by this function! // -> get a USER_INFO_0 instead and then use KUser(QString) // USER_INFO_23 or USER_INFO_23 would be ideal here since they contains a SID, // but that fails with error code 0x7c (bad level) enumerateAllUsers(maxCount, [&result](const USER_INFO_0 &info) { result.append(KUser(QString::fromWCharArray(info.usri0_name))); }); return result; } QStringList KUser::allUserNames(uint maxCount) { QStringList result; enumerateAllUsers(maxCount, [&result](const USER_INFO_0 &info) { result.append(QString::fromWCharArray(info.usri0_name)); }); return result; } QList KUserGroup::allGroups(uint maxCount) { QList result; // MSDN documentation say 3 is a valid level, however the function fails with invalid level!!! // User GROUP_INFO_0 instead... enumerateAllGroups(maxCount, [&result](const GROUP_INFO_0 &groupInfo) { result.append(KUserGroup(QString::fromWCharArray(groupInfo.grpi0_name))); }); return result; } QStringList KUserGroup::allGroupNames(uint maxCount) { QStringList result; enumerateAllGroups(maxCount, [&result](const GROUP_INFO_0 &groupInfo) { result.append(QString::fromWCharArray(groupInfo.grpi0_name)); }); return result; } QList KUser::groups(uint maxCount) const { QList result; if (!isValid()) { return result; } enumerateGroupsForUser(maxCount, d->loginName, [&result](const GROUP_USERS_INFO_0 &info) { result.append(KUserGroup(QString::fromWCharArray(info.grui0_name))); }); return result; } QStringList KUser::groupNames(uint maxCount) const { QStringList result; if (!isValid()) { return result; } enumerateGroupsForUser(maxCount, d->loginName, [&result](const GROUP_USERS_INFO_0 &info) { result.append(QString::fromWCharArray(info.grui0_name)); }); return result; } QList KUserGroup::users(uint maxCount) const { QList result; if (!isValid()) { return result; } enumerateGroupsForUser(maxCount, d->name, [&result](const GROUP_USERS_INFO_0 &info) { result.append(KUser(QString::fromWCharArray(info.grui0_name))); }); return result; } QStringList KUserGroup::userNames(uint maxCount) const { QStringList result; if (!isValid()) { return result; } enumerateGroupsForUser(maxCount, d->name, [&result](const GROUP_USERS_INFO_0 &info) { result.append(QString::fromWCharArray(info.grui0_name)); }); return result; } static const auto invalidSidString = QStringLiteral(""); static QString sidToString(void *sid) { if (!sid || !IsValidSid(sid)) { return invalidSidString; } WCHAR *sidStr; // allocated by ConvertStringSidToSidW, must be freed using LocalFree() if (!ConvertSidToStringSidW(sid, &sidStr)) { return invalidSidString; } QString ret = QString::fromWCharArray(sidStr); LocalFree(sidStr); return ret; } struct WindowsSIDWrapper : public QSharedData { char sidBuffer[SECURITY_MAX_SID_SIZE]; /** @return a copy of @p sid or null if sid is not valid or an error occurs */ static WindowsSIDWrapper *copySid(PSID sid) { if (!sid || !IsValidSid(sid)) { return nullptr; } //create a copy of sid WindowsSIDWrapper *copy = new WindowsSIDWrapper(); bool success = CopySid(SECURITY_MAX_SID_SIZE, copy->sidBuffer, sid); if (!success) { QString sidString = sidToString(sid); qWarning("Failed to copy SID %s, error = %d", qPrintable(sidString), (int)GetLastError()); delete copy; return nullptr; } return copy; } }; template<> KUserOrGroupId::KUserOrGroupId() { } template<> KUserOrGroupId::~KUserOrGroupId() { } template<> KUserOrGroupId::KUserOrGroupId(const KUserOrGroupId &other) : data(other.data) { } template<> inline KUserOrGroupId &KUserOrGroupId::operator=(const KUserOrGroupId &other) { data = other.data; return *this; } template<> KUserOrGroupId::KUserOrGroupId(void *nativeId) : data(WindowsSIDWrapper::copySid(nativeId)) { } template<> bool KUserOrGroupId::isValid() const { return data; } template<> void *KUserOrGroupId::nativeId() const { if (!data) { return nullptr; } return data->sidBuffer; } template<> bool KUserOrGroupId::operator==(const KUserOrGroupId &other) const { if (data) { if (!other.data) { return false; } return EqualSid(data->sidBuffer, other.data->sidBuffer); } return !other.data; //only equal if other data is also invalid } template<> bool KUserOrGroupId::operator!=(const KUserOrGroupId &other) const { return !(*this == other); } template<> QString KUserOrGroupId::toString() const { return sidToString(data ? data->sidBuffer : nullptr); } /** T must be either KUserId or KGroupId, Callback has signature T(PSID, SID_NAME_USE) */ template static T sidFromName(const QString &name, Callback callback) { if (name.isEmpty()) { // for some reason empty string will always return S-1-5-32 which is of type domain // we only want users or groups -> return invalid return T(); } char buffer[SECURITY_MAX_SID_SIZE]; DWORD sidLength = SECURITY_MAX_SID_SIZE; // ReferencedDomainName must be passed or LookupAccountNameW fails // Documentation says it is optional, however if not passed the function fails and returns the required size WCHAR domainBuffer[1024]; DWORD domainBufferSize = 1024; SID_NAME_USE sidType; bool ok = LookupAccountNameW(nullptr, (LPCWSTR)name.utf16(), buffer, &sidLength, domainBuffer, &domainBufferSize, &sidType); if (!ok) { qCWarning(KCOREADDONS_DEBUG) << "Failed to lookup account" << name << "error code =" << GetLastError(); return T(); } return callback(buffer, sidType); } KUserId KUserId::fromName(const QString &name) { return sidFromName(name, [&](PSID sid, SID_NAME_USE sidType) -> KUserId { if (sidType != SidTypeUser && sidType != SidTypeDeletedAccount) { qCWarning(KCOREADDONS_DEBUG).nospace() << "Failed to lookup user name " << name << ": resulting SID " << sidToString(sid) << " is not a user." " Got SID type " << sidType << " instead."; return KUserId(); } return KUserId(sid); }); } KGroupId KGroupId::fromName(const QString &name) { return sidFromName(name, [&](PSID sid, SID_NAME_USE sidType) -> KGroupId { if (sidType != SidTypeGroup && sidType != SidTypeWellKnownGroup) { qCWarning(KCOREADDONS_DEBUG).nospace() << "Failed to lookup user name " << name << ": resulting SID " << sidToString(sid) << " is not a group." " Got SID type " << sidType << " instead."; return KGroupId(); } return KGroupId(sid); }); } static std::unique_ptr queryProcessInformation(TOKEN_INFORMATION_CLASS type) { HANDLE _token; if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &_token)) { qWarning("Failed to get the token for the current process: %d", (int)GetLastError()); return nullptr; } ScopedHANDLE token(_token, handleCloser); // query required size DWORD requiredSize; if (!GetTokenInformation(token.get(), type, nullptr, 0, &requiredSize)) { if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { qWarning("Failed to get the required size for the token information %d: %d", type, (int)GetLastError()); return nullptr; } } std::unique_ptr buffer(new char[requiredSize]); if (!GetTokenInformation(token.get(), type, buffer.get(), requiredSize, &requiredSize)) { qWarning("Failed to get token information %d from current process: %d", type, (int)GetLastError()); return nullptr; } return buffer; } KUserId KUserId::currentUserId() { std::unique_ptr userTokenBuffer = queryProcessInformation(TokenUser); TOKEN_USER *userToken = (TOKEN_USER *)userTokenBuffer.get(); return KUserId(userToken->User.Sid); } KGroupId KGroupId::currentGroupId() { std::unique_ptr primaryGroupBuffer = queryProcessInformation(TokenPrimaryGroup); TOKEN_PRIMARY_GROUP *primaryGroup = (TOKEN_PRIMARY_GROUP *)primaryGroupBuffer.get(); return KGroupId(primaryGroup->PrimaryGroup); } KUserId KUserId::currentEffectiveUserId() { return currentUserId(); } KGroupId KGroupId::currentEffectiveGroupId() { return currentGroupId(); } KCOREADDONS_EXPORT uint qHash(const KUserId &id, uint seed) { if (!id.isValid()) { return seed; } // we can't just hash the pointer since equal object must have the same hash -> hash contents char *sid = (char *)id.nativeId(); return qHash(QByteArray::fromRawData(sid, GetLengthSid(sid)), seed); } KCOREADDONS_EXPORT uint qHash(const KGroupId &id, uint seed) { if (!id.isValid()) { return seed; } // we can't just hash the pointer since equal object must have the same hash -> hash contents char *sid = (char *)id.nativeId(); return qHash(QByteArray::fromRawData(sid, GetLengthSid(sid)), seed); }