Add test, fix some issues with loading internal and built-in plugin translations

This commit is contained in:
Michael Zanetti 2019-03-18 22:29:45 +01:00
parent 8ebd1ddc4e
commit 50aa691087
12 changed files with 1390 additions and 858 deletions

View File

@ -46,7 +46,7 @@ QJsonObject CloudNotifications::metaData() const
{ {
QVariantMap pluginMetaData; QVariantMap pluginMetaData;
pluginMetaData.insert("id", "ccc6dbc8-e352-48a1-8e87-3c89a4669fc2"); pluginMetaData.insert("id", "ccc6dbc8-e352-48a1-8e87-3c89a4669fc2");
pluginMetaData.insert("name", "cloudNotifications"); pluginMetaData.insert("name", "CloudNotifications");
pluginMetaData.insert("displayName", tr("Cloud Notifications")); pluginMetaData.insert("displayName", tr("Cloud Notifications"));
QVariantList interfaces; QVariantList interfaces;
@ -131,6 +131,10 @@ QJsonObject CloudNotifications::metaData() const
QVariantList vendors; QVariantList vendors;
vendors.append(guhVendor); vendors.append(guhVendor);
pluginMetaData.insert("vendors", vendors); pluginMetaData.insert("vendors", vendors);
// Mark this plugin as built-in
pluginMetaData.insert("builtIn", true);
return QJsonObject::fromVariantMap(pluginMetaData); return QJsonObject::fromVariantMap(pluginMetaData);
} }

View File

@ -75,7 +75,15 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject
QVariantMap params; QVariantMap params;
params.clear(); returns.clear(); params.clear(); returns.clear();
setDescription("Hello", "Upon first connection, nymea will automatically send a welcome message containing information about the setup. If this message is lost for whatever reason (connections with multiple hops might drop this if nymea sends it too early), the exact same message can be retrieved multiple times by calling this Hello method. Note that the contents might change if the system changed its state in the meantime, e.g. initialSetupRequired might turn false if the initial setup has been performed in the meantime."); setDescription("Hello", "Initiates a connection. Use this method to perform an initial handshake of the "
"connection. Optionally, a parameter \"locale\" is can be passed to set up the used "
"locale for this connection. Strings such as DeviceClass displayNames etc will be "
"localized to this locale. If this parameter is omitted, the default system locale "
"(depending on the configuration) is used. The reply of this method contains information "
"about this core instance such as version information, uuid and its name. The locale value"
"indicates the locale used for this connection. Note: This method can be called multiple "
"times. The locale used in the last call for this connection will be used. Other values, "
"like initialSetupRequired might change if the setup has been performed in the meantime.");
params.insert("o:locale", JsonTypes::basicTypeToString(JsonTypes::String)); params.insert("o:locale", JsonTypes::basicTypeToString(JsonTypes::String));
setParams("Hello", params); setParams("Hello", params);
returns.insert("id", JsonTypes::basicTypeToString(JsonTypes::Int)); returns.insert("id", JsonTypes::basicTypeToString(JsonTypes::Int));
@ -84,6 +92,7 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject
returns.insert("version", JsonTypes::basicTypeToString(JsonTypes::String)); returns.insert("version", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("uuid", JsonTypes::basicTypeToString(JsonTypes::Uuid)); returns.insert("uuid", JsonTypes::basicTypeToString(JsonTypes::Uuid));
returns.insert("language", JsonTypes::basicTypeToString(JsonTypes::String)); returns.insert("language", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("locale", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("protocol version", JsonTypes::basicTypeToString(JsonTypes::String)); returns.insert("protocol version", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("initialSetupRequired", JsonTypes::basicTypeToString(JsonTypes::Bool)); returns.insert("initialSetupRequired", JsonTypes::basicTypeToString(JsonTypes::Bool));
returns.insert("authenticationRequired", JsonTypes::basicTypeToString(JsonTypes::Bool)); returns.insert("authenticationRequired", JsonTypes::basicTypeToString(JsonTypes::Bool));
@ -239,7 +248,7 @@ JsonReply *JsonRPCServer::Hello(const QVariantMap &params)
qCDebug(dcJsonRpc()) << "Client" << clientId << "initiated handshake." << m_clientLocales.value(clientId); qCDebug(dcJsonRpc()) << "Client" << clientId << "initiated handshake." << m_clientLocales.value(clientId);
return createReply(createWelcomeMessage(interface)); return createReply(createWelcomeMessage(interface, clientId));
} }
JsonReply* JsonRPCServer::Introspect(const QVariantMap &params) const JsonReply* JsonRPCServer::Introspect(const QVariantMap &params) const
@ -475,7 +484,7 @@ void JsonRPCServer::sendUnauthorizedResponse(TransportInterface *interface, cons
interface->sendData(clientId, data); interface->sendData(clientId, data);
} }
QVariantMap JsonRPCServer::createWelcomeMessage(TransportInterface *interface) const QVariantMap JsonRPCServer::createWelcomeMessage(TransportInterface *interface, const QUuid &clientId) const
{ {
QVariantMap handshake; QVariantMap handshake;
handshake.insert("id", 0); handshake.insert("id", 0);
@ -483,7 +492,9 @@ QVariantMap JsonRPCServer::createWelcomeMessage(TransportInterface *interface) c
handshake.insert("name", NymeaCore::instance()->configuration()->serverName()); handshake.insert("name", NymeaCore::instance()->configuration()->serverName());
handshake.insert("version", NYMEA_VERSION_STRING); handshake.insert("version", NYMEA_VERSION_STRING);
handshake.insert("uuid", NymeaCore::instance()->configuration()->serverUuid().toString()); handshake.insert("uuid", NymeaCore::instance()->configuration()->serverUuid().toString());
handshake.insert("language", NymeaCore::instance()->configuration()->locale().name()); // "language" is deprecated
handshake.insert("language", m_clientLocales.value(clientId).name());
handshake.insert("locale", m_clientLocales.value(clientId).name());
handshake.insert("protocol version", JSON_PROTOCOL_VERSION); handshake.insert("protocol version", JSON_PROTOCOL_VERSION);
handshake.insert("initialSetupRequired", (interface->configuration().authenticationEnabled ? NymeaCore::instance()->userManager()->initRequired() : false)); handshake.insert("initialSetupRequired", (interface->configuration().authenticationEnabled ? NymeaCore::instance()->userManager()->initRequired() : false));
handshake.insert("authenticationRequired", interface->configuration().authenticationEnabled); handshake.insert("authenticationRequired", interface->configuration().authenticationEnabled);
@ -612,14 +623,10 @@ void JsonRPCServer::processJsonPacket(TransportInterface *interface, const QUuid
qCDebug(dcJsonRpc()) << "Invoking method" << targetNamespace << method.toLatin1().data(); qCDebug(dcJsonRpc()) << "Invoking method" << targetNamespace << method.toLatin1().data();
if (targetNamespace != "JSONRPC" || method != "Hello") { if (targetNamespace != "JSONRPC" || method != "Hello") {
// Unless this is the Hello message, which allows setting the locale explicity, attach the locale
// for this connection
// If the client did request a locale in the Hello message, use that locale // If the client did request a locale in the Hello message, use that locale
if (m_clientLocales.contains(clientId)) { params.insert("locale", m_clientLocales.value(clientId));
params.insert("locale", m_clientLocales.value(clientId));
}
// Otherwise fall back to the locale set in the configuration.
else {
params.insert("locale", NymeaCore::instance()->configuration()->locale());
}
} }
JsonReply *reply; JsonReply *reply;
@ -757,7 +764,10 @@ void JsonRPCServer::clientConnected(const QUuid &clientId)
// If authentication is required, notifications are disabled by default. Clients must enable them with a valid token // If authentication is required, notifications are disabled by default. Clients must enable them with a valid token
m_clientNotifications.insert(clientId, !interface->configuration().authenticationEnabled); m_clientNotifications.insert(clientId, !interface->configuration().authenticationEnabled);
interface->sendData(clientId, QJsonDocument::fromVariant(createWelcomeMessage(interface)).toJson(QJsonDocument::Compact)); // Initialize the connection locale to the settings default
m_clientLocales.insert(clientId, NymeaCore::instance()->configuration()->locale());
interface->sendData(clientId, QJsonDocument::fromVariant(createWelcomeMessage(interface, clientId)).toJson(QJsonDocument::Compact));
} }
void JsonRPCServer::clientDisconnected(const QUuid &clientId) void JsonRPCServer::clientDisconnected(const QUuid &clientId)

View File

@ -77,7 +77,7 @@ private:
void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap &params = QVariantMap()); void sendResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QVariantMap &params = QVariantMap());
void sendErrorResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error); void sendErrorResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error);
void sendUnauthorizedResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error); void sendUnauthorizedResponse(TransportInterface *interface, const QUuid &clientId, int commandId, const QString &error);
QVariantMap createWelcomeMessage(TransportInterface *interface) const; QVariantMap createWelcomeMessage(TransportInterface *interface, const QUuid &clientId) const;
void processJsonPacket(TransportInterface *interface, const QUuid &clientId, const QByteArray &data); void processJsonPacket(TransportInterface *interface, const QUuid &clientId, const QByteArray &data);

View File

@ -430,6 +430,11 @@ DeviceManager::DeviceError DevicePlugin::setConfigValue(const ParamTypeId &param
return DeviceManager::DeviceErrorNoError; return DeviceManager::DeviceErrorNoError;
} }
bool DevicePlugin::isBuiltIn() const
{
return m_metaData.value("builtIn").toBool();
}
/*! Returns a pointer to the \l{DeviceManager}. /*! Returns a pointer to the \l{DeviceManager}.
When implementing a plugin, use this to find the \l{Device}{Devices} you need. When implementing a plugin, use this to find the \l{Device}{Devices} you need.
*/ */
@ -501,7 +506,7 @@ void DevicePlugin::loadMetaData()
// Note: The DevicePlugin has no type class, so we define the json properties here // Note: The DevicePlugin has no type class, so we define the json properties here
QStringList pluginMandatoryJsonProperties = QStringList() << "id" << "name" << "displayName" << "vendors"; QStringList pluginMandatoryJsonProperties = QStringList() << "id" << "name" << "displayName" << "vendors";
QStringList pluginJsonProperties = QStringList() << "id" << "name" << "displayName" << "vendors" << "paramTypes"; QStringList pluginJsonProperties = QStringList() << "id" << "name" << "displayName" << "vendors" << "paramTypes" << "builtIn";
QPair<QStringList, QStringList> verificationResult = verifyFields(pluginJsonProperties, pluginMandatoryJsonProperties, m_metaData); QPair<QStringList, QStringList> verificationResult = verifyFields(pluginJsonProperties, pluginMandatoryJsonProperties, m_metaData);

View File

@ -82,6 +82,8 @@ public:
QVariant configValue(const ParamTypeId &paramTypeId) const; QVariant configValue(const ParamTypeId &paramTypeId) const;
DeviceManager::DeviceError setConfigValue(const ParamTypeId &paramTypeId, const QVariant &value); DeviceManager::DeviceError setConfigValue(const ParamTypeId &paramTypeId, const QVariant &value);
bool isBuiltIn() const;
signals: signals:
void emitEvent(const Event &event); void emitEvent(const Event &event);
void devicesDiscovered(const DeviceClassId &deviceClassId, const QList<DeviceDescriptor> &deviceDescriptors); void devicesDiscovered(const DeviceClassId &deviceClassId, const QList<DeviceDescriptor> &deviceDescriptors);

View File

@ -137,37 +137,56 @@ void Translator::loadTranslator(DevicePlugin *plugin, const QLocale &locale)
} }
} }
bool loaded = false;
// check if there are local translations // check if there are local translations
QTranslator* translator = new QTranslator(); QTranslator* translator = new QTranslator();
QString pluginId = plugin->pluginId().toString().remove(QRegExp("[{}]"));
bool loaded = false; if (plugin->isBuiltIn()) {
foreach (const QString &pluginPath, qgetenv("NYMEA_PLUGINS_PATH").split(':')) { if (translator->load(locale, QCoreApplication::instance()->applicationName(), "-", QDir(QCoreApplication::applicationDirPath() + "../../translations/").absolutePath(), ".qm")) {
if (translator->load(locale, pluginId, "-", QDir(pluginPath + "/translations/").absolutePath(), ".qm")) { qCDebug(dcTranslations()) << "* Loaded translation" << locale.name() << "for plugin" << plugin->pluginName() << "from" << QDir(QCoreApplication::applicationDirPath() + "../../translations/").absolutePath() + "/" + QCoreApplication::applicationName() + "-[" + locale.name() + "].qm";
qCDebug(dcTranslations()) << "* Loaded translation" << locale.name() << "for plugin" << plugin->pluginName() << "from" << QDir(pluginPath + "/translations/").absolutePath(); loaded = true;
} else if (translator->load(locale, QCoreApplication::instance()->applicationName(), "-", NymeaSettings::translationsPath(), ".qm")) {
qCDebug(dcTranslations()) << "* Loaded translation" << locale.name() << "for plugin" << plugin->pluginName() << "from" << NymeaSettings::translationsPath()+ "/" + QCoreApplication::applicationName() + "-[" + locale.name() + "].qm";
loaded = true; loaded = true;
break;
} }
foreach (const QString &subdir, QDir(pluginPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { } else {
if (translator->load(locale, pluginId, "-", QDir(pluginPath + "/" + subdir + "/translations/").absolutePath(), ".qm")) { QString pluginId = plugin->pluginId().toString().remove(QRegExp("[{}]"));
qCDebug(dcTranslations()) << "* Loaded translation" << locale.name() << "for plugin" << plugin->pluginName() << "from" << QDir(pluginPath + "/" + subdir + "/translations/").absolutePath();
QStringList searchDirs = QString(qgetenv("NYMEA_PLUGINS_PATH")).split(':');
searchDirs << QCoreApplication::applicationDirPath() + "/../lib/nymea/plugins";
searchDirs << QCoreApplication::applicationDirPath() + "/../plugins/";
searchDirs << QCoreApplication::applicationDirPath() + "/../../../plugins/";
searchDirs << QString("%1").arg(NYMEA_PLUGINS_PATH);
foreach (const QString &pluginPath, searchDirs) {
if (translator->load(locale, pluginId, "-", QDir(pluginPath + "/translations/").absolutePath(), ".qm")) {
qCDebug(dcTranslations()) << "* Loaded translation" << locale.name() << "for plugin" << plugin->pluginName() << "from" << QDir(pluginPath + "/translations/").absolutePath();
loaded = true; loaded = true;
break; break;
} }
foreach (const QString &subdir, QDir(pluginPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
if (translator->load(locale, pluginId, "-", QDir(pluginPath + "/" + subdir + "/translations/").absolutePath(), ".qm")) {
qCDebug(dcTranslations()) << "* Loaded translation" << locale.name() << "for plugin" << plugin->pluginName() << "from" << QDir(pluginPath + "/" + subdir + "/translations/").absolutePath() + "/" + pluginId + "-[" + locale.name() + "].qm";
loaded = true;
break;
}
}
if (loaded) {
break;
}
} }
if (loaded) {
break; // otherwise use the system translations
if (!loaded && translator->load(locale, pluginId, "-", NymeaSettings::translationsPath(), ".qm")) {
qCDebug(dcTranslations()) << "* Load translation" << locale.name() << "for" << plugin->pluginName() << "from" << NymeaSettings::translationsPath() + "/" + pluginId + "-[" + locale.name() + "].qm";
loaded = true;
}
if (!loaded && locale.name() != "en_US") {
qCWarning(dcTranslations()) << "* Could not load translation" << locale.name() << "for plugin" << plugin->pluginName() << "(" << pluginId << ")";
} }
} }
// otherwise use the system translations
if (!loaded && translator->load(locale, pluginId, "-", NymeaSettings::translationsPath(), ".qm")) {
qCDebug(dcTranslations()) << "* Load translation" << locale.name() << "for" << plugin->pluginName() << "from" << NymeaSettings::translationsPath();
loaded = true;
}
if (!loaded && locale.name() != "en_US")
qCWarning(dcTranslations()) << "* Could not load translation" << locale.name() << "for plugin" << plugin->pluginName();
if (!loaded) { if (!loaded) {
translator = m_translatorContexts.value(plugin->pluginId()).translators.value(QLocale("en_US")); translator = m_translatorContexts.value(plugin->pluginId()).translators.value(QLocale("en_US"));

View File

@ -23,20 +23,22 @@ test.commands = LD_LIBRARY_PATH=$$top_builddir/libnymea-core:$$top_builddir/libn
# Translations: # Translations:
# make lupdate to update .ts files # make lupdate to update .ts files
TRANSLATIONS += $$files(translations/*.ts, true) CORE_TRANSLATIONS += $$files($${top_srcdir}/translations/*.ts, true)
TRANSLATIONS += $$files(plugins/mock/translations/*.ts, true) lupdate.commands = lupdate -recursive -no-obsolete $${top_srcdir} -ts $${CORE_TRANSLATIONS};
PLUGIN_TRANSLATIONS += $$files($${top_srcdir}/plugins/mock/translations/*.ts, true)
lupdate.commands += lupdate -recursive -no-obsolete $${top_builddir}/plugins/mock/ -ts $${PLUGIN_TRANSLATIONS};
lupdate.depends = FORCE lupdate.depends = FORCE
lupdate.commands = lupdate -recursive -no-obsolete $$_FILE_; TRANSLATIONS = $${CORE_TRANSLATIONS} $${PLUGIN_TRANSLATIONS}
# make lrelease to compile .ts to .qm # make lrelease to compile .ts to .qm
lrelease.depends = FORCE lrelease.depends = FORCE
lrelease.commands = lrelease $$_FILE_; \ lrelease.commands = lrelease $$_FILE_; \
rsync -a $$top_srcdir/translations/*.qm $$top_builddir/translations/; rsync -a $$top_srcdir/translations/*.qm $$top_builddir/translations/; \
rsync -a $$top_srcdir/plugins/mock/translations/*.qm $$top_builddir/plugins/mock/translations/;
first.depends = $(first) lrelease first.depends = $(first) lrelease
# Install translation files # Install translation files
translations.path = /usr/share/nymea/translations translations.path = /usr/share/nymea/translations
translations.files = $$[QT_SOURCE_TREE]/translations/*.qm
translations.depends = lrelease translations.depends = lrelease
INSTALLS += translations INSTALLS += translations

View File

@ -3,7 +3,7 @@ include(../nymea.pri)
TEMPLATE = lib TEMPLATE = lib
CONFIG += plugin CONFIG += plugin
QT += network bluetooth QT += network
INCLUDEPATH += $$top_srcdir/libnymea INCLUDEPATH += $$top_srcdir/libnymea
LIBS += -L../../libnymea -lnymea LIBS += -L../../libnymea -lnymea

View File

@ -146,7 +146,7 @@ int main(int argc, char *argv[])
// check if there are local translations // check if there are local translations
if (!translator.load(QLocale::system(), application.applicationName(), "-", QDir(QCoreApplication::applicationDirPath() + "../../translations/").absolutePath(), ".qm")) if (!translator.load(QLocale::system(), application.applicationName(), "-", QDir(QCoreApplication::applicationDirPath() + "../../translations/").absolutePath(), ".qm"))
if (!translator.load(QLocale::system(), application.applicationName(), "-", NymeaSettings::translationsPath(), ".qm")) if (!translator.load(QLocale::system(), application.applicationName(), "-", NymeaSettings::translationsPath(), ".qm"))
qWarning(dcApplication()) << "Could not find nymead translations for" << QLocale::system().name() << endl << (QDir(QCoreApplication::applicationDirPath() + "../../translations/").absolutePath()) << endl << NymeaSettings::translationsPath(); qWarning(dcTranslations()) << "Could not find nymead translations for" << QLocale::system().name() << endl << (QDir(QCoreApplication::applicationDirPath() + "../../translations/").absolutePath()) << endl << NymeaSettings::translationsPath();

View File

@ -35,6 +35,8 @@ private slots:
void testHandshake(); void testHandshake();
void testHandshakeLocale();
void testInitialSetup(); void testInitialSetup();
void testRevokeToken(); void testRevokeToken();
@ -131,7 +133,9 @@ void TestJSONRPC::initTestCase()
{ {
NymeaTestBase::initTestCase(); NymeaTestBase::initTestCase();
QLoggingCategory::setFilterRules("*.debug=false\n" QLoggingCategory::setFilterRules("*.debug=false\n"
"JsonRpc*.debug=true"); // "JsonRpc*.debug=true\n"
"Translations.debug=true\n"
"Tests.debug=true");
} }
void TestJSONRPC::testHandshake() void TestJSONRPC::testHandshake()
@ -170,6 +174,42 @@ void TestJSONRPC::testHandshake()
QCOMPARE(handShake.value("params").toMap().value("version").toString(), nymeaVersionString); QCOMPARE(handShake.value("params").toMap().value("version").toString(), nymeaVersionString);
} }
void TestJSONRPC::testHandshakeLocale()
{
// first test if the handshake message is auto-sent upon connecting
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
// Test withouth locale data
QVariantMap handShake = injectAndWait("JSONRPC.Hello").toMap();
QCOMPARE(handShake.value("params").toMap().value("locale").toString(), QString("en_US"));
QVariantMap supportedDevices = injectAndWait("Devices.GetSupportedDevices").toMap();
bool found = false;
foreach (const QVariant &dcMap, supportedDevices.value("params").toMap().value("deviceClasses").toList()) {
if (dcMap.toMap().value("id").toUuid() == mockDeviceAutoClassId) {
QCOMPARE(dcMap.toMap().value("displayName").toString(), QString("Mock Device (Auto created)"));
found = true;
}
}
QVERIFY(found);
// And now with locale info
QVariantMap params;
params.insert("locale", "de_DE");
handShake = injectAndWait("JSONRPC.Hello", params).toMap();
QCOMPARE(handShake.value("params").toMap().value("locale").toString(), QString("de_DE"));
supportedDevices = injectAndWait("Devices.GetSupportedDevices").toMap();
found = false;
foreach (const QVariant &dcMap, supportedDevices.value("params").toMap().value("deviceClasses").toList()) {
if (dcMap.toMap().value("id").toUuid() == mockDeviceAutoClassId) {
QCOMPARE(dcMap.toMap().value("displayName").toString(), QString("Mock Gerät (Automatisch erzeugt)"));
found = true;
}
}
QVERIFY(found);
}
void TestJSONRPC::testInitialSetup() void TestJSONRPC::testInitialSetup()
{ {
foreach (const QString &user, NymeaCore::instance()->userManager()->users()) { foreach (const QString &user, NymeaCore::instance()->userManager()->users()) {