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

View File

@ -75,7 +75,15 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject
QVariantMap params;
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));
setParams("Hello", params);
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("uuid", JsonTypes::basicTypeToString(JsonTypes::Uuid));
returns.insert("language", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("locale", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("protocol version", JsonTypes::basicTypeToString(JsonTypes::String));
returns.insert("initialSetupRequired", 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);
return createReply(createWelcomeMessage(interface));
return createReply(createWelcomeMessage(interface, clientId));
}
JsonReply* JsonRPCServer::Introspect(const QVariantMap &params) const
@ -475,7 +484,7 @@ void JsonRPCServer::sendUnauthorizedResponse(TransportInterface *interface, cons
interface->sendData(clientId, data);
}
QVariantMap JsonRPCServer::createWelcomeMessage(TransportInterface *interface) const
QVariantMap JsonRPCServer::createWelcomeMessage(TransportInterface *interface, const QUuid &clientId) const
{
QVariantMap handshake;
handshake.insert("id", 0);
@ -483,7 +492,9 @@ QVariantMap JsonRPCServer::createWelcomeMessage(TransportInterface *interface) c
handshake.insert("name", NymeaCore::instance()->configuration()->serverName());
handshake.insert("version", NYMEA_VERSION_STRING);
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("initialSetupRequired", (interface->configuration().authenticationEnabled ? NymeaCore::instance()->userManager()->initRequired() : false));
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();
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 (m_clientLocales.contains(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());
}
params.insert("locale", m_clientLocales.value(clientId));
}
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
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)

View File

@ -77,7 +77,7 @@ private:
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 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);

View File

@ -430,6 +430,11 @@ DeviceManager::DeviceError DevicePlugin::setConfigValue(const ParamTypeId &param
return DeviceManager::DeviceErrorNoError;
}
bool DevicePlugin::isBuiltIn() const
{
return m_metaData.value("builtIn").toBool();
}
/*! Returns a pointer to the \l{DeviceManager}.
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
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);

View File

@ -82,6 +82,8 @@ public:
QVariant configValue(const ParamTypeId &paramTypeId) const;
DeviceManager::DeviceError setConfigValue(const ParamTypeId &paramTypeId, const QVariant &value);
bool isBuiltIn() const;
signals:
void emitEvent(const Event &event);
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
QTranslator* translator = new QTranslator();
QString pluginId = plugin->pluginId().toString().remove(QRegExp("[{}]"));
bool loaded = false;
foreach (const QString &pluginPath, qgetenv("NYMEA_PLUGINS_PATH").split(':')) {
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();
if (plugin->isBuiltIn()) {
if (translator->load(locale, QCoreApplication::instance()->applicationName(), "-", QDir(QCoreApplication::applicationDirPath() + "../../translations/").absolutePath(), ".qm")) {
qCDebug(dcTranslations()) << "* Loaded translation" << locale.name() << "for plugin" << plugin->pluginName() << "from" << QDir(QCoreApplication::applicationDirPath() + "../../translations/").absolutePath() + "/" + QCoreApplication::applicationName() + "-[" + locale.name() + "].qm";
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;
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();
} else {
QString pluginId = plugin->pluginId().toString().remove(QRegExp("[{}]"));
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;
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) {
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:
# make lupdate to update .ts files
TRANSLATIONS += $$files(translations/*.ts, true)
TRANSLATIONS += $$files(plugins/mock/translations/*.ts, true)
CORE_TRANSLATIONS += $$files($${top_srcdir}/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.commands = lupdate -recursive -no-obsolete $$_FILE_;
TRANSLATIONS = $${CORE_TRANSLATIONS} $${PLUGIN_TRANSLATIONS}
# make lrelease to compile .ts to .qm
lrelease.depends = FORCE
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
# Install translation files
translations.path = /usr/share/nymea/translations
translations.files = $$[QT_SOURCE_TREE]/translations/*.qm
translations.depends = lrelease
INSTALLS += translations

View File

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

View File

@ -146,7 +146,7 @@ int main(int argc, char *argv[])
// 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(), "-", 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 testHandshakeLocale();
void testInitialSetup();
void testRevokeToken();
@ -131,7 +133,9 @@ void TestJSONRPC::initTestCase()
{
NymeaTestBase::initTestCase();
QLoggingCategory::setFilterRules("*.debug=false\n"
"JsonRpc*.debug=true");
// "JsonRpc*.debug=true\n"
"Translations.debug=true\n"
"Tests.debug=true");
}
void TestJSONRPC::testHandshake()
@ -170,6 +174,42 @@ void TestJSONRPC::testHandshake()
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()
{
foreach (const QString &user, NymeaCore::instance()->userManager()->users()) {