diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3972090 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Copyright (c) 2015-2018 LG Electronics, Inc. +# +# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +# Common Eclipse project files +.project +.cproject + +#Clion project files +.idea + +# Ignore the build artifacts. +BUILD* +/build +release-*/ +debug-*/ +CMakeCache.txt +cmake_install.cmake +CMakeFiles +conf/*.conf +doc/Doxyfile +doc/html +patches +src/*.pc +*.orig +src/config.h \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1b088cb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,102 @@ +# Copyright (c) 2015-2018 LG Electronics, Inc. +# +# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 2.8.7) + +project(event-monitor CXX) + +include(webOS/webOS) +webos_modules_init(1 6 0) +webos_component(1 1 0) + +webos_add_compiler_flags(ALL -Wall -std=c++11) + +if (_LOCAL_BUILD) + webos_add_compiler_flags(ALL -DSECURITY_COMPATIBILITY) +endif (_LOCAL_BUILD) + +include(FindPkgConfig) + +pkg_check_modules(GLIB2 REQUIRED glib-2.0) +include_directories(${GLIB2_INCLUDE_DIRS}) +webos_add_compiler_flags(ALL ${GLIB2_CFLAGS_OTHER}) + +pkg_check_modules(LUNASERVICE2PP REQUIRED luna-service2++) +include_directories(${LUNASERVICE2PP_INCLUDE_DIRS}) +webos_add_compiler_flags(ALL ${LUNASERVICE2PP_CFLAGS_OTHER}) + +pkg_check_modules(PBNJSON_CPP REQUIRED pbnjson_cpp) +include_directories(${PBNJSON_CPP_INCLUDE_DIRS}) +webos_add_compiler_flags(ALL ${PBNJSON_CPP_CFLAGS_OTHER}) + +pkg_check_modules(PMLOG REQUIRED PmLogLib) +include_directories(${PMLOG_INCLUDE_DIRS}) +webos_add_compiler_flags(ALL ${PMLOG_CFLAGS_OTHER}) + +pkg_check_modules(I18N REQUIRED webosi18n) +include_directories(${I18N_INCLUDE_DIRS}) +webos_add_compiler_flags(ALL ${I18N_CFLAGS_OTHER}) + + +# Require that all undefined symbols are satisfied by the libraries from target_link_libraries() +webos_add_linker_options(ALL --no-undefined) + +include_directories(include/public) + +######## Service ######## + +set(LIBS + ${GLIB2_LDFLAGS} + ${PBNJSON_CPP_LDFLAGS} + ${PMLOG_LDFLAGS} + ${LUNASERVICE2PP_LDFLAGS} + ${I18N_LDFLAGS} + dl + ) + +file(GLOB SOURCES src/service/*.cpp) + +webos_configure_source_files(SOURCES src/config.h) +webos_add_compiler_flags(ALL -I${CMAKE_CURRENT_BINARY_DIR}/Configured/src) + +add_executable(event-monitor ${SOURCES}) +target_link_libraries(event-monitor ${LIBS}) +webos_build_daemon() +webos_build_system_bus_files() + +######## Headers for plugins ######## + +install(DIRECTORY "include/public/" DESTINATION @WEBOS_INSTALL_INCLUDEDIR@ FILES_MATCHING PATTERN "*.h*" PATTERN ".*" EXCLUDE) + +######## Mock plugin ######## +if (BUILD_MOCK_PLUGIN) + + set(MOCK_LIBS + ${GLIB2_LDFLAGS} + ${PBNJSON_CPP_LDFLAGS} + ${PMLOG_LDFLAGS} + ${I18N_LDFLAGS} + ) + + file(GLOB MOCK_SOURCES src/mockplugin/*.cpp) + + set(CMAKE_SHARED_MODULE_PREFIX "") + add_library(mock-plugin MODULE ${MOCK_SOURCES}) + target_link_libraries(mock-plugin ${MOCK_LIBS}) + + install(TARGETS mock-plugin DESTINATION ${WEBOS_EVENT_MONITOR_PLUGIN_PATH}) + +endif (BUILD_MOCK_PLUGIN) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b8353d0 --- /dev/null +++ b/README.md @@ -0,0 +1,89 @@ +event-monitor +============= + +Summary +------- +webOS event monitor service. + +Description +----------- +Event monitor is a service to monitor events from system services and show UI +notifications on relevant events. +Event monitor provides common framework for monitoring events. Actual business +logic is implemented in plugins. A mock plugin is provided for testing. + +How to Build on Linux +===================== + +## Dependencies + +Below are the tools and libraries (and their minimum versions) required to build +event-monitor: + +* cmake (version required by webosose/cmake-modules-webos) +* gcc 4.7.2 +* glib-2.0 2.32.1 +* make (any version) +* webosose/pbnjson_cpp 1.3.0 +* webosose/luna-service2 3.9.5 +* pkg-config 0.26 +* pmloglib + +## Building + +Once you have downloaded the source, enter the following to build it (after +changing into the directory under which it was downloaded): + + $ mkdir BUILD + $ cd BUILD + $ cmake .. + $ make + $ sudo make install + +The directory under which the files are installed defaults to `/usr/local/webos`. +You can install them elsewhere by supplying a value for `WEBOS_INSTALL_ROOT` +when invoking `cmake`. For example: + + $ cmake -D WEBOS_INSTALL_ROOT:PATH=$HOME/projects/webos-pro .. + $ make + $ make install + +will install the files in subdirectories of `$HOME/projects/webos-pro`. + +Specifying `WEBOS_INSTALL_ROOT` also causes `pkg-config` to look in that tree +first before searching the standard locations. You can specify additional +directories to be searched prior to this one by setting the `PKG_CONFIG_PATH` +environment variable. + +If not specified, `WEBOS_INSTALL_ROOT` defaults to `/usr/local/webos`. + +To configure for a debug build, enter: + + $ cmake -D CMAKE_BUILD_TYPE:STRING=Debug .. + +To see a list of the make targets that `cmake` has generated, enter: + + $ make help + +## Uninstalling + +From the directory where you originally ran `make install`, enter: + + $ [sudo] make uninstall + +You will need to use `sudo` if you did not specify `WEBOS_INSTALL_ROOT`. + +# Copyright and License Information + +Unless otherwise specified, all content, including all source code files and +documentation files in this repository are: + +Copyright (c) 2015-2018 LG Electronics, Inc. + +All content, including all source code files and documentation files in this repository except otherwise noted are: Licensed under the Apache License, Version 2.0 (the "License"); you may not use this content except in compliance with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +SPDX-License-Identifier: Apache-2.0 diff --git a/astyle_options b/astyle_options new file mode 100644 index 0000000..a6bb883 --- /dev/null +++ b/astyle_options @@ -0,0 +1,18 @@ +style=ansi +indent=tab=4 +indent-switches +indent-namespaces +indent-col1-comments +break-blocks +pad-oper +pad-header +unpad-paren +align-pointer=name +align-reference=name +add-brackets +convert-tabs +close-templates +break-after-logical +max-code-length=80 +lineend=linux + diff --git a/files/launch/event-monitor.conf.in b/files/launch/event-monitor.conf.in new file mode 100644 index 0000000..cd55517 --- /dev/null +++ b/files/launch/event-monitor.conf.in @@ -0,0 +1,27 @@ +# Copyright (c) 2015-2018 LG Electronics, Inc. +# +# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +description "@WEBOS_PROJECT_SUMMARY@" + +start on settingsservice-ready +stop on started start_update + +respawn + +# Comment this line out to suppress logs on the console +#console output + +exec @WEBOS_INSTALL_SBINDIR@/event-monitor diff --git a/files/sysbus/com.webos.service.eventmonitor.api.json b/files/sysbus/com.webos.service.eventmonitor.api.json new file mode 100644 index 0000000..7cc11fe --- /dev/null +++ b/files/sysbus/com.webos.service.eventmonitor.api.json @@ -0,0 +1,14 @@ +{ + "eventmonitor.plugins": [ + "com.webos.service.eventmonitor/mockPlugin/action", + "com.webos.service.eventmonitor/mockPlugin/getEvents", + "com.webos.service.eventmonitor/network/starfish/enableWifiDirectNotifications", + "com.webos.service.eventmonitor/network/starfish/alertClose" + ], + "private": [ + "com.webos.service.eventmonitor/mockPlugin/action", + "com.webos.service.eventmonitor/mockPlugin/getEvents", + "com.webos.service.eventmonitor/network/starfish/enableWifiDirectNotifications", + "com.webos.service.eventmonitor/network/starfish/alertClose" + ] +} diff --git a/files/sysbus/com.webos.service.eventmonitor.perm.json b/files/sysbus/com.webos.service.eventmonitor.perm.json new file mode 100644 index 0000000..e79b5a8 --- /dev/null +++ b/files/sysbus/com.webos.service.eventmonitor.perm.json @@ -0,0 +1,14 @@ +{ + "com.webos.service.eventmonitor": [ + "settings", + "notifications", + "applications", + "applications.internal", + "networking.query", + "devices", + "nfc.query", + "services", + "networking.internal", + "tv.management" + ] +} diff --git a/files/sysbus/com.webos.service.eventmonitor.role.json.in b/files/sysbus/com.webos.service.eventmonitor.role.json.in new file mode 100644 index 0000000..f95b41d --- /dev/null +++ b/files/sysbus/com.webos.service.eventmonitor.role.json.in @@ -0,0 +1,26 @@ +{ + "exeName":"@WEBOS_INSTALL_SBINDIR@/event-monitor", + "type": "regular", + "allowedNames": ["com.webos.service.eventmonitor"], + "permissions": [ + { + "service":"com.webos.service.eventmonitor", + "outbound":[ + "com.webos.service.eventmonitor", + "com.webos.settingsservice", + "com.webos.service.bus", + "com.webos.notification", + "com.webos.applicationManager", + "com.webos.service.connectionmanager", + "com.webos.service.bluetooth2", + "com.webos.service.nfc", + "com.lge.service.systemui", + "com.webos.service.battery", + "com.palm.display", + "com.lge.settingmanager", + "com.webos.service.wifi", + "com.webos.service.tvpower" + ] + } + ] +} diff --git a/files/sysbus/com.webos.service.eventmonitor.service.in b/files/sysbus/com.webos.service.eventmonitor.service.in new file mode 100644 index 0000000..c124772 --- /dev/null +++ b/files/sysbus/com.webos.service.eventmonitor.service.in @@ -0,0 +1,20 @@ +# Copyright (c) 2015-2018 LG Electronics, Inc. +# +# Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + +[D-BUS Service] +Name=com.webos.service.eventmonitor +Exec=@WEBOS_INSTALL_SBINDIR@/event-monitor +Type=static diff --git a/include/public/event-monitor-api.h b/include/public/event-monitor-api.h new file mode 100644 index 0000000..a7f6024 --- /dev/null +++ b/include/public/event-monitor-api.h @@ -0,0 +1,54 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef EVENT_MONITOR_API_H +#define EVENT_MONITOR_API_H + +#include +#include "event-monitor-api/api.h" +#include "event-monitor-api/logging.h" +#include "event-monitor-api/error.hpp" +#include "event-monitor-api/pluginbase.hpp" + +/** + * Array of luna service paths that are required before the plugin can be instantiated. + * A single plugin can have multiple services as a requirement. + */ +extern "C" const char *requiredServices[]; + +/** + * @brief Factory method to create new plugin instance. + * The result must be an subclass of Plugin. + * This method wil be called only once. + * The plugin will be freed by the caller. + * After that the plugin shared library will be unloaded. + * + * + * @param Version of the API to use, the current api version is available in + * EventMonitor::API_VERSION + */ +extern "C" EventMonitor::Plugin *instantiatePlugin(int version, + EventMonitor::Manager *manager); + + +/** + * Log context forward declaration. To use logging, plugin must declare variable + * for it and call manager->setupLogging. + */ +extern PmLogContext pluginLogContext; + + +#endif //MONITOR_PLUGINAPI_H diff --git a/include/public/event-monitor-api/api.h b/include/public/event-monitor-api/api.h new file mode 100644 index 0000000..cb4b292 --- /dev/null +++ b/include/public/event-monitor-api/api.h @@ -0,0 +1,251 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef EVENT_MONITOR_INTERNAL_API_H +#define EVENT_MONITOR_INTERNAL_API_H + +#include +#include +#include +#include + +#include "error.hpp" + +namespace EventMonitor +{ + + /** + * Current plugin API version. Increment this if any changes are made in this file. + */ + const int API_VERSION = 3; + + class Manager; + class Plugin; + + enum UnloadResult + { + UNLOAD_OK = 0, // Ok to unload + UNLOAD_CANCEL = 1, // Still have unfinished stuff, I will unload myself later. + }; + + typedef std::function + LunaCallHandler; + + typedef std::function LunaCallback; + + typedef std::function TimeoutCallback; + + /** + * Subscribe callback function. + * @param previousResponse - response from previous subscribe response. + * Null JValue if this is the first response. + * @param value - the value from current subscribe response. + */ + typedef std::function + SubscribeCallback; + + /** + * Event monitor public API. + */ + class Manager + { + public: + + /** + * Sets up logging instance for particular plugin. + */ + virtual void setupLogging(PmLogContext *context) = 0; + + /** + * Returns just the UI locale. + */ + virtual const std::string getUILocale() = 0; + + /** + * Returns full JSON locale structure. + */ + virtual const pbnjson::JValue &getLocaleInfo() = 0; + + /** + * Manually unload the plugin. + */ + virtual void unloadPlugin() = 0; + + /** + * Do a synchronous luna call. + * Will throw an exception in case of bus error. + * Will return null object if there is no reply within timeout. + */ + virtual pbnjson::JValue lunaCall(const std::string &serviceUrl, + pbnjson::JValue ¶ms, + unsigned long timeout = 1000) = 0; + + /** + * Do a async luna call. + */ + virtual void lunaCallAsync(const std::string &serviceUrl, + pbnjson::JValue ¶ms, + LunaCallback callback) = 0; + + /** + * Subscribe to luna method. + * @parm subscriptionId - subscription identifier - use to unsubscribe or replace existing subscription. + * @param methodPath - luna service path to method name to subscribe to, including the luna:// prefix. + * @parm params - extra parameters. Subscribe=true will be added automatically. + * Use JObject() to indicate no parameters. + * @param callback - the method to call when + */ + virtual void subscribeToMethod( + const std::string &subscriptionId, + const std::string &methodPath, + pbnjson::JValue ¶ms, + SubscribeCallback callback, + const pbnjson::JSchema &schema = pbnjson::JSchema::AllSchema()) = 0; + + /** + * Unsubscribe from luna method. + * @param subscriptionId - subscription identifier. + * @returns - true if there was a subscription. + */ + virtual bool unsubscribeFromMethod(const std::string &subscriptionId) = 0; + + /** + * Subscribe to luna signal. + * @parm subscriptionId - subscription identifier - use to unsubscribe or replace existing subscription. + * @param category - luna signal category. + * @parm method - luna method in the category. Leave empty ("") to subscribe + * to all methods. + * @param callback - the method to call when signal is fired. + */ + virtual void subscribeToSignal( + const std::string &subscriptionId, + const std::string &category, + const std::string &method, + SubscribeCallback callback, + const pbnjson::JSchema &schema = pbnjson::JSchema::AllSchema()) = 0; + + /** + * Unsubscribe from luna signal. + * @param subscriptionId - subscription identifier. + * @returns - true if there was a subscription. + */ + virtual bool unsubscribeFromSignal(const std::string &subscriptionId) = 0; + + /** + * Call a function after a specified time. + * @param: timeoutId - string identifier. Note tht setting a new timeout + * will automatically cancel previous one with the same id. + * @param timeMs - time in milliseconds to wait before calling the function. + * @param repeat - if true, the callback function will be called repeatedly + * until cancelTimeout is called. + * @param callback - the function to call. + */ + virtual void setTimeout(const std::string &timeoutId, + unsigned int timeMs, + bool repeat, + TimeoutCallback callback) = 0; + + /** + * Cancel a timeout. + * @param - timeout identifier. Same as in setTimeout. + * @returns - true if timeout was present. + */ + virtual bool cancelTimeout(const std::string &timeoutId) = 0; + + /** + * Registers a luna method on bus. + * This method may be called multiple times for same methodName to update + * the handler or schema or simply to retrieve the method path. + * Overriding methods registered by different plugin is not allowed + * and will result in exception. + * @param categoryName - category name. Must start with "/". + * @param methodName - method name. Registering again + * with same name will override previous method. + * @param handler - function to call when method is called, + * @params schema - schema for method parameters. All calls not conforming + * to schema will dropped and logged. + * @return method service URL, eg: luna://com.webos.service.event-monitor/pluginName/methodName + */ + virtual std::string registerMethod(const std::string &categoryName, + const std::string &methodName, + LunaCallHandler handler, + const pbnjson::JSchema &schema = pbnjson::JSchema::AllSchema()) = 0; + + /** + * Convenience method to create a toast. + * Create a toast with optional icon and on click action. + * See createAlert API documentation for details. + */ + virtual void createToast( + const std::string &message, + const std::string &iconUrl = "", + const pbnjson::JValue &onClickAction = pbnjson::JValue()) = 0; + + /** + * Convenience method to create an alert. + * See createAlert API documentation for details. + */ + virtual void createAlert(const std::string &alertId, + const std::string &title, + const std::string &message, + bool modal, + const std::string &iconUrl, + const pbnjson::JValue &buttons, + const pbnjson::JValue &onClose) = 0; + + /** + * Closes the alert specified by id, if open. + * @param alertId - alert identifier. + * @returns - true of alert was open. + */ + virtual bool closeAlert(const std::string &alertId) = 0; + }; + + /** + * Plugin interface with event monitor. + */ + class Plugin + { + public: + /** + * Called when service path goes online, use to subscribe to any relevant services. + */ + virtual void startMonitoring() = 0; + + /** + * Called when required service goes offline. + * @param service - name of the service that went offline. + * @return - UNLOAD_OK: active alerts are removed, + * the plugin instance is freed and plugin is unloaded. + * - UNLOAD_CANCEL: no action is taken, plugin continues to receive, + * callbacks from any active alerts. Should unload manually + * when possible by calling manager->unloadPlugin(this); + * Note that the plugin will NOT be reloaded if it's not + * unloaded and service goes back online. + */ + virtual UnloadResult stopMonitoring(const std::string &service) = 0; + + /** + * Called when system locale is changed. Use to update plugin's resource bundle. + */ + virtual void uiLocaleChanged(const std::string &uiLocale) = 0; + + virtual ~Plugin() {}; + }; + +}; /* Namespace EventMonitor */ + +#endif //MONITOR_INTERNAL_API_H diff --git a/include/public/event-monitor-api/error.hpp b/include/public/event-monitor-api/error.hpp new file mode 100644 index 0000000..f4fe20c --- /dev/null +++ b/include/public/event-monitor-api/error.hpp @@ -0,0 +1,52 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef EVENT_MONITOR_SERVICE_ERROR_HPP +#define EVENT_MONITOR_SERVICE_ERROR_HPP + +#include +#include +#include + +namespace EventMonitor { + +/** + * @brief This class wraps Event monitor errors. + */ + class Error : public std::exception + { + public: + Error(const std::string& _message): + message(_message) + {}; + + virtual ~Error(){} + + /** + * @brief Get text representation of error + * + * @return error text message + */ + const char *what() const noexcept + { return this->message.c_str(); } + + private: + std::string message; + }; + +} //namespace EventMonitor; + +#endif // EVENT_MONITOR_SERVICE_ERROR_HPP diff --git a/include/public/event-monitor-api/logging.h b/include/public/event-monitor-api/logging.h new file mode 100644 index 0000000..099da6e --- /dev/null +++ b/include/public/event-monitor-api/logging.h @@ -0,0 +1,30 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#define LOG_CRITICAL(msgid, kvcount, ...) \ + PmLogCritical(pluginLogContext, msgid, kvcount, ##__VA_ARGS__) + +#define LOG_ERROR(msgid, kvcount, ...) \ + PmLogError(pluginLogContext, msgid, kvcount,##__VA_ARGS__) + +#define LOG_WARNING(msgid, kvcount, ...) \ + PmLogWarning(pluginLogContext, msgid, kvcount, ##__VA_ARGS__) + +#define LOG_INFO(msgid, kvcount, ...) \ + PmLogInfo(pluginLogContext, msgid, kvcount, ##__VA_ARGS__) + +#define LOG_DEBUG(fmt, ...) \ + PmLogDebug(pluginLogContext, "%s:%s() " fmt, __FILE__, __FUNCTION__, ##__VA_ARGS__) diff --git a/include/public/event-monitor-api/pluginbase.hpp b/include/public/event-monitor-api/pluginbase.hpp new file mode 100644 index 0000000..b31cdb3 --- /dev/null +++ b/include/public/event-monitor-api/pluginbase.hpp @@ -0,0 +1,73 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +extern PmLogContext pluginLogContext; + +namespace EventMonitor{ + +/** + * Convenience base class. + * Initializes logging and localization. + * Use this->getLocString for localization. + */ +class PluginBase: public EventMonitor::Plugin +{ +public: + PluginBase(EventMonitor::Manager *_manager, + const char* _localizationPath): + manager(_manager), + resourceBundle(nullptr), + localizationPath(_localizationPath) + { + this->manager->setupLogging(&pluginLogContext); + this->uiLocaleChanged(this->manager->getUILocale()); + } + + virtual ~PluginBase() + { + this->manager = nullptr; + } + + + const std::string getLocString(const std::string& source){ + return this->resourceBundle->getLocString(source); + } + + const std::string getLocString(const std::string& key, + const std::string& source){ + return this->resourceBundle->getLocString(key, source); + } + + void uiLocaleChanged(const std::string &uiLocale){ + this->resourceBundle.reset(new ResBundle( + manager->getUILocale().c_str(), + "cppstrings.json", + this->localizationPath)); + } + +protected: + EventMonitor::Manager *manager; + std::unique_ptr resourceBundle; + const char * localizationPath; +}; + + +} \ No newline at end of file diff --git a/src/config.h.in b/src/config.h.in new file mode 100644 index 0000000..54d217c --- /dev/null +++ b/src/config.h.in @@ -0,0 +1,29 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#define VERSION "@WEBOS_COMPONENT_VERSION@" +#define DESCRIPTION "@WEBOS_PROJECT_SUMMARY@" +#define COMPONENT_NAME "@CMAKE_PROJECT_NAME@" +#define WEBOS_LOCALIZATION_PATH "/usr/share/localization/@CMAKE_PROJECT_NAME@" + +#define SERVICE_BUS_NAME "com.webos.service.eventmonitor" + +#define WEBOS_EVENT_MONITOR_PLUGIN_PATH "@WEBOS_EVENT_MONITOR_PLUGIN_PATH@" + +#endif diff --git a/src/mockplugin/mockplugin.cpp b/src/mockplugin/mockplugin.cpp new file mode 100644 index 0000000..9dd4879 --- /dev/null +++ b/src/mockplugin/mockplugin.cpp @@ -0,0 +1,403 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + + +#include "mockplugin.h" +#include "config.h" + +using namespace pbnjson; +using namespace EventMonitor; + +#define TOAST_SOURCE_ID "com.webos.service.eventmonitor-mock-plugin" +#define ALERT_SOURCE_ID "com.webos.service.eventmonitor" +#define ALERT_SERVICE_URI "luna://com.webos.service.eventmonitor/mockPlugin/" + +std::string g_alertTimeStampID = ""; + +const char *requiredServices[] = {"com.webos.applicationManager","com.webos.notification", + nullptr + }; + +PmLogContext pluginLogContext; + +EventMonitor::Plugin *instantiatePlugin(int version, + EventMonitor::Manager *manager) +{ + if (version != EventMonitor::API_VERSION) + { + return nullptr; + } + + return new MockPlugin(manager); +} + +MockPlugin::MockPlugin(Manager *_manager): + PluginBase(_manager, WEBOS_LOCALIZATION_PATH), + eventsMockPlugin({{"pluginLoaded",false}, + {"subscribedMethod",false}, + {"subscribedSignal",false}, + {"unsubscribed",false}, + {"createdToast",false}, + {"createdAlert",false}, + {"closedAlert",false}, + {"setTimeout",false}}) +{ +} + +MockPlugin::~MockPlugin() +{ + LOG_DEBUG("Destructor called"); +} + +void MockPlugin::uiLocaleChanged(const std::string &locale) +{ + PluginBase::uiLocaleChanged(locale); + + LOG_DEBUG("Locale set to %s", locale.c_str()); + + this->manager->createToast(this->getLocString("Locale set to ") + + locale); +} + +void MockPlugin::startMonitoring() +{ + LOG_DEBUG("Starting to monitor"); + + eventsMockPlugin["pluginLoaded"] = true; + + (void) this->manager->cancelTimeout("unloadTimeout"); + + this->manager->registerMethod( + "/mockPlugin", + "getEvents", + std::bind(&MockPlugin::getEventsCb, this)); + + this->manager->createToast( + this->getLocString("Mock plugin started, will show alert in 2 seconds")); + + this->manager->setTimeout("startAlert", + 2000, + false, + std::bind(&MockPlugin::startAlert, this, std::placeholders::_1)); + + JValue params = JObject(); + this->manager->subscribeToMethod( + "foregroundApp", + "luna://com.webos.applicationManager/getForegroundAppInfo", + params, + std::bind(&MockPlugin::foregroundAppCallback, this, std::placeholders::_1, std::placeholders::_2)); + + this->manager->subscribeToMethod( + "toastNotification", + "luna://com.webos.notification/getToastNotification", + params, + std::bind(&MockPlugin::toastNotificationCallback, this, std::placeholders::_1, std::placeholders::_2)); + + this->manager->subscribeToMethod( + "alertNotification", + "luna://com.webos.notification/getAlertNotification", + params, + std::bind(&MockPlugin::alertNotificationCallback, this, std::placeholders::_1, std::placeholders::_2)); + + // You can subscribe to signals even when the appropriate service is not started. + this->manager->subscribeToSignal( + "batteryStatus", + "/com/palm/power", + "batteryStatus", + std::bind(&MockPlugin::batteryStatusCallback, this, std::placeholders::_1, std::placeholders::_2)); + + this->manager->subscribeToSignal( + "processFinished", + "/booster", + "processFinished", + std::bind(&MockPlugin::boosterFinishedCallback, this, std::placeholders::_1, std::placeholders::_2)); +} + +void MockPlugin::foregroundAppCallback(JValue& previousValue, + JValue& value) +{ + eventsMockPlugin["subscribedMethod"] = true; + + if (previousValue.isNull()) + { + return; + } + + std::string prevApp; + std::string curApp; + previousValue["appId"].asString(prevApp); + value["appId"].asString(curApp); + + LOG_DEBUG("Foreground app callback: %s", value.stringify("").c_str()); + + if (prevApp != curApp) + { + this->manager->createToast(this->getLocString("Active application changed to ") + curApp); + } +} + +UnloadResult MockPlugin::stopMonitoring(const std::string &service) +{ + LOG_DEBUG("Stopping plugin"); + + this->manager->createToast(this->getLocString("Required services unloaded, waiting 5 seconds to unload the plugin.")); + + // Try using a lambda function. + auto timeoutCallback = [this](const std::string & id) + { + LOG_DEBUG("Timout finished, toasting"); + this->manager->createToast(this->getLocString("5 seconds passed, unloading plugin")); + LOG_DEBUG("Timout finished, unloading plugin"); + this->manager->unloadPlugin(); + }; + + this->manager->setTimeout("unloadTimeout", 5000, false, timeoutCallback); + + LOG_DEBUG( "Stopping plugin - done"); + return UNLOAD_CANCEL; +} + +void MockPlugin::startAlert(const std::string &timeoutId) +{ + eventsMockPlugin["setTimeout"] = true; + + std::string actionUrl = this->manager->registerMethod( + "/mockPlugin", + "action", + std::bind(&MockPlugin::actionCallback, this, std::placeholders::_1)); + + JValue buttons = JArray + { + JObject{{"label", "close"}, + {"onclick", actionUrl}, + {"position", "left"}, + {"params", JObject{{"close",true}}}}, + JObject{{"label", "toast"}, + {"onclick", actionUrl}, + {"params", JObject{{"close",false},{"toast",this->getLocString("toast")}}}} + }; + + JValue onClose = JObject(); + + this->manager->createAlert("question", + this->getLocString("Event Monitor Mock plugin started"), + this->getLocString( + "Do you see this alert? " + "I will show toasts whenever active application is changed." + "
Closing the alert in 20 seconds. "), + false, + "", + buttons, + onClose); + + auto timeoutCallback = [this](const std::string & id) + { + this->manager->closeAlert("question"); + this->manager->createToast( + this->getLocString("Alert closed after 20 seconds")); + }; + + this->manager->setTimeout("closeQuestion", + 10000, + false, + timeoutCallback); +} + +pbnjson::JValue MockPlugin::actionCallback(const pbnjson::JValue ¶ms) +{ + (void) this->manager->cancelTimeout("closeQuestion"); + + std::string message = ""; + bool closeAlert = false; + auto wasError = params["close"].asBool(closeAlert); + // Toast is optional parameter. + auto toastMissing = params["toast"].asString(message); + + if (wasError) + { + return JObject{{"returnValue", false}, + {"errorCode", 100}, + {"errorMessage", "Error parsing JSON"}}; + } + + if (toastMissing) + { + this->manager->createToast( + this->getLocString("Button with no message")); + } + else + { + this->manager->createToast( + this->getLocString("Button said ") + message); + } + + if (!closeAlert) + { + this->manager->setTimeout("startAlert", + 100, + false, + std::bind(&MockPlugin::startAlert, this, std::placeholders::_1)); + } + + return JObject{{"returnValue", true}}; +} + +pbnjson::JValue MockPlugin::getEventsCb() +{ + return JObject{{"pluginLoaded", eventsMockPlugin["pluginLoaded"]}, + {"subscribedMethod", eventsMockPlugin["subscribedMethod"]}, + {"subscribedSignal", eventsMockPlugin["subscribedSignal"]}, + {"unsubscribed", eventsMockPlugin["unsubscribed"]}, + {"createdToast", eventsMockPlugin["createdToast"]}, + {"createdAlert", eventsMockPlugin["createdAlert"]}, + {"closedAlert", eventsMockPlugin["closedAlert"]}, + {"setTimeout", eventsMockPlugin["setTimeout"]}, + {"returnValue", true} + }; +} +void MockPlugin::batteryStatusCallback(pbnjson::JValue& previousValue, + pbnjson::JValue& value) +{ + this->manager->createToast("Battery status callback"); + + int percent = 0; + auto wasError = value["percent"].asNumber(percent); + + if (wasError) + { + return; + } + + eventsMockPlugin["subscribedSignal"] = true; + char buf[1000]; + snprintf(buf, + 1000, + this->getLocString("Battery Status update: percent: %d").c_str(), + percent); + this->manager->createToast(buf); +} + +void MockPlugin::boosterFinishedCallback(pbnjson::JValue& previousValue, + pbnjson::JValue& value) +{ + int exitCode = 0; + auto wasError = value["exitCode"].asNumber(exitCode); + + if (wasError) + { + return; + } + + eventsMockPlugin["subscribedSignal"] = true; + auto boosterTimeoutCallback = [this,exitCode](const std::string & id) + { + this->manager->createToast(this->getLocString("Signal received. Boosted QML app terminated with exitcode: ") + to_string(exitCode)); + }; + + this->manager->setTimeout("boosterTimer", + 5000, + false, + boosterTimeoutCallback); + + auto unsubscribeTimeoutCallback = [this](const std::string & id) + { + this->manager->unsubscribeFromMethod("foregroundApp"); + this->manager->unsubscribeFromSignal("batteryStatus"); + this->manager->unsubscribeFromSignal("processFinished"); + this->manager->createToast(this->getLocString("Unsubscribed from signals and methods")); + eventsMockPlugin["subscribedSignal"] = false; + eventsMockPlugin["subscribedMethod"] = false; + eventsMockPlugin["unsubscribed"] = true; + }; + + this->manager->setTimeout("unsubscribeTimer", + 10000, + false, + unsubscribeTimeoutCallback); + +} +void MockPlugin::toastNotificationCallback(pbnjson::JValue& previousValue, + pbnjson::JValue& value) +{ + std::string toastSourceId = ""; + auto wasError = value["sourceId"].asString(toastSourceId); + + if (wasError) + { + return; + } + + if (toastSourceId == TOAST_SOURCE_ID) + eventsMockPlugin["createdToast"] = true; +} + +void MockPlugin::alertNotificationCallback(pbnjson::JValue& previousValue, + pbnjson::JValue& value) +{ + std::string alertSourceID = ""; + std::string alertServiceURI = ""; + std::string alertAction = ""; + std::string alertTimeStamp = ""; + + auto wasError = value["alertAction"].asString(alertAction); + + if (wasError) + { + return; + } + + if (alertAction == "close") + { + wasError |= value["alertInfo"]["timestamp"].asString(alertTimeStamp); + + if (wasError) + { + return; + } + + if (alertTimeStamp == g_alertTimeStampID) + eventsMockPlugin["closedAlert"] = true; + } + + wasError |= value["alertInfo"]["sourceId"].asString(alertSourceID); + + if (wasError) + { + return; + } + + if (value["alertInfo"]["buttons"].isArray()) + { + for (auto buttonInfo: value["alertInfo"]["buttons"].items()) + { + wasError |= buttonInfo["action"]["serviceURI"].asString(alertServiceURI); + if (wasError) + { + return; + } + } + } + + if (alertSourceID == ALERT_SOURCE_ID && alertServiceURI == ALERT_SERVICE_URI && alertAction == "open") + { + eventsMockPlugin["createdAlert"] = true; + wasError |= value["timestamp"].asString(g_alertTimeStampID); + if (wasError) + { + return; + } + } +} diff --git a/src/mockplugin/mockplugin.h b/src/mockplugin/mockplugin.h new file mode 100644 index 0000000..df37c2e --- /dev/null +++ b/src/mockplugin/mockplugin.h @@ -0,0 +1,53 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include +#include + +class MockPlugin: public EventMonitor::PluginBase +{ +public: + MockPlugin(EventMonitor::Manager *manager); + virtual ~MockPlugin(); + void startMonitoring(); + EventMonitor::UnloadResult stopMonitoring(const std::string &service); + virtual void uiLocaleChanged(const std::string &locale); + +private: + + //Map of events. + std::unordered_map eventsMockPlugin; + + void foregroundAppCallback(pbnjson::JValue& previousValue, + pbnjson::JValue& value); + void batteryStatusCallback(pbnjson::JValue& previousValue, + pbnjson::JValue& value); + void boosterFinishedCallback(pbnjson::JValue& previousValue, + pbnjson::JValue& value); + void toastNotificationCallback(pbnjson::JValue& previousValue, + pbnjson::JValue& value); + void alertNotificationCallback(pbnjson::JValue& previousValue, + pbnjson::JValue& value); + void startAlert(const std::string &timeoutId); + pbnjson::JValue actionCallback(const pbnjson::JValue ¶ms); + pbnjson::JValue getEventsCb(); +}; + diff --git a/src/service/logging.h b/src/service/logging.h new file mode 100644 index 0000000..16093d3 --- /dev/null +++ b/src/service/logging.h @@ -0,0 +1,76 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + + +#pragma once + +#include + +extern PmLogContext logContext; + +#define LOG_CRITICAL(msgid, kvcount, ...) \ + PmLogCritical(::logContext, msgid, kvcount, ##__VA_ARGS__) + +#define LOG_ERROR(msgid, kvcount, ...) \ + PmLogError(::logContext, msgid, kvcount,##__VA_ARGS__) + +#define LOG_WARNING(msgid, kvcount, ...) \ + PmLogWarning(::logContext, msgid, kvcount, ##__VA_ARGS__) + +#define LOG_INFO(msgid, kvcount, ...) \ + PmLogInfo(::logContext, msgid, kvcount, ##__VA_ARGS__) + +#define LOG_DEBUG(fmt, ...) \ + PmLogDebug(::logContext, "%s:%s() " fmt, __FILE__, __FUNCTION__, ##__VA_ARGS__) + + +#define MSGID_ERROR_INTERNAL "INTERNAL_ERROR" +#define MSGID_PLUGIN_EXCEPTION "PLUGIN_EXCEPTION" + +#define MSGIC_TERMINATING "TERMINATING" + +#define MSGID_LS2_FAILED_TO_SUBSCRIBE "LS2_FAILED_TO_SUBSCRIBE" +#define MSGID_LS2_FAILED_TO_SEND "LS2_FAILED_TO_SEND" +#define MSGID_LS2_DISCONNECTED "LS2_DISCONNECTED" +#define MSGID_LS2_HUB_ERROR "LS2_HUB_ERROR" +#define MSGID_LS2_NOT_SUBSCRIBED "LS2_NOT_SUBSCRIBED" +#define MSGID_LS2_RESPONSE_PARSE_ERROR "LS2_RESPONSE_PARSE_ERROR" +#define MSGID_LS2_RESPONSE_SCHEMA_ERROR "LS2_RESPONSE_SCHEMA_ERROR" +#define MSGID_LS2_RESPONSE_NOT_AN_OBJECT "LS2_RESPONSE_NOT_AN_OBJECT" +#define MSGID_LS2_FIRST_RESPONSE_ERROR "LS2_FIRST_RESPONSE_ERROR" +#define MSGID_LS2_CALL_NO_REPLY "LS2_CALL_NO_REPLY" + +#define MSGID_SETTINGS_LOCALE_MISSING "SETTINGS_LOCALE_MISSING" + +#define MSGID_PMLOG_GETCONTEXT_FAIL "PMLOG_FAILED_TO_GET_CONTEXT" +#define MSGID_UNLOAD_BAD_PARAMS "UNLOAD_BAD_PARAMS" + +#define MSGID_PLUGIN_LOADER "PLUGIN_LOADER" +#define MSGID_PLUGIN_LOAD_FAILED "LOAD_PLUGIN_FAILED" +#define MSGID_PLUGIN_ADDED "PLUGIN_ADDED" +#define MSGID_PLUGIN_LOADED "PLUGIN_LOADED" +#define MSGID_PLUGIN_UNLOADED "PLUGIN_UNLOADED" + +#define MSGID_SERVICE_STATUS_ERROR "SERVICE_STATUS_ERROR" +#define MSGID_SERVICE_STATUS "SERVICE_STATUS" + +#define MSGID_PLUGIN_INVALID_SUBSCRIBE "PLUGIN_INVALID_SUBSCRIBE" + +#define MSGID_ADD_TIMEOUT_FAILED "ADD_TIMEOUT_FAILED" + +#define MSGID_LOCALE_ERROR "LOCALE_ERROR" + +#define MSGID_CREATE_ALERT_FAILED "CREATE_ALERT_FAILED" diff --git a/src/service/lunaservice.cpp b/src/service/lunaservice.cpp new file mode 100644 index 0000000..782504e --- /dev/null +++ b/src/service/lunaservice.cpp @@ -0,0 +1,485 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "lunaservice.h" +#include "pluginadapter.h" +#include "pluginmanager.h" +#include "logging.h" + +using namespace pbnjson; +using namespace EventMonitor; + +LunaService::LunaService(std::string _servicePath, GMainLoop *mainLoop, + const char *identifier): + LS::Handle(_servicePath.c_str(), identifier), + servicePath(_servicePath) +{ + this->setDisconnectHandler(LunaService::onLunaDisconnect, this); + this->attachToLoop(mainLoop); +} + +LunaService::~LunaService() +{ + //Cleanup the subscriptions + for (auto i : this->subscriptions) + { + SubscriptionInfo* subscription = i.first; + subscription->call.cancel(); + delete subscription; + } + + // Cleanup the methods + for (auto cat : this->categoryMethods) + { + for(auto method: cat.second) + { + delete method.second; + } + } + + this->categoryMethods.clear(); +} + +JValue LunaService::call(const std::string &serviceUrl, + JValue ¶ms, + unsigned long timeout) +{ + std::string paramsStr; + + if (params.getType() != JValueType::JV_OBJECT) + { + paramsStr = R"({})"; + } + else + { + paramsStr = params.stringify(); + } + + LOG_DEBUG("Luna call %s params %s", serviceUrl.c_str(), paramsStr.c_str()); + + try + { + LS::Call call = this->callOneReply(serviceUrl.c_str(), paramsStr.c_str()); + LS::Message reply = call.get(timeout); + + if (!reply) + { + LOG_ERROR(MSGID_LS2_CALL_NO_REPLY, 0, "Luna call %s has no reply within timeout %lu", serviceUrl.c_str(), timeout); + return JValue(); // Return null value. + } + + LOG_DEBUG("Call result %s: %s", serviceUrl.c_str(), reply.getPayload()); + + JValue value = JDomParser::fromString(reply.getPayload(), JSchema::AllSchema()); + + if (!value.isValid()) + { + LOG_ERROR(MSGID_LS2_RESPONSE_PARSE_ERROR, 0, "Failed to parse luna reply: %s", + reply.getPayload()); + } + else if (!value.isObject()) + { + LOG_ERROR(MSGID_LS2_RESPONSE_NOT_AN_OBJECT, 0, + "Luna reply not an JSON object: %s", reply.getPayload()); + } + + return value; + } + catch (const LS::Error &error) + { + LOG_ERROR(MSGID_LS2_FAILED_TO_SUBSCRIBE, 0, "Failed to call %s, params %s" , + serviceUrl.c_str(), paramsStr.c_str()); + throw; + } +} + +CallHandle LunaService::callAsync(const std::string &serviceUrl, + pbnjson::JValue ¶ms, + LunaCallback callback, + PluginAdapter *plugin) +{ + std::string paramsStr; + + if (params.getType() != JValueType::JV_OBJECT) + { + paramsStr = R"({})"; + } + else + { + paramsStr = params.stringify(); + } + + LOG_DEBUG("Call async to %s params %s", serviceUrl.c_str(), paramsStr.c_str()); + + SubscriptionInfo *info = nullptr; + + try + { + if (!callback) + { + //Call and forget + this->callOneReply(serviceUrl.c_str(), paramsStr.c_str()); + return nullptr; + } + else + { + info = new SubscriptionInfo(JSchema::AllSchema()); + this->subscriptions[info] = info; + + info->service = this; + info->call = this->callMultiReply(serviceUrl.c_str(), + paramsStr.c_str()); + info->simpleCallback = callback; + info->counter = 0; + info->plugin = plugin; + info->serviceUrl = serviceUrl; + info->previousValue = JValue(); // Null value + info->call.continueWith(LunaService::callResultHandler, info); + return info; + } + } + catch (const LS::Error &error) + { + LOG_ERROR(MSGID_LS2_FAILED_TO_SUBSCRIBE, 0, "Failed to call %s, params %s" , + serviceUrl.c_str(), paramsStr.c_str()); + delete info; + throw; + } +} + +MethodInfo* LunaService::registerMethod(PluginAdapter *plugin, + const std::string &category, + const std::string &methodName, + EventMonitor::LunaCallHandler handler, + const pbnjson::JSchema &schema) +{ + if (!plugin) + { + throw Error("Plugin == null"); + } + + MethodInfo *info = this->findMethod(category, methodName); + + if (info && info->plugin && info->plugin != plugin) + { + throw Error("Method already registered for different plugin. Cross-plugin method override not allowed."); + } + + if (info == nullptr) + { + /* Register new method */ + LSMethod methods[] = + { + { + methodName.c_str(), + &LS::Handle::methodWraper, + LUNA_METHOD_FLAGS_NONE + }, + nullptr + }; + this->registerCategoryAppend(category.c_str(), methods, nullptr); + this->setCategoryData(category.c_str(), this); + + info = new MethodInfo(); + this->categoryMethods[category][methodName] = info; + info->url = "luna://" + this->servicePath + category + "/" + methodName; + } + + info->plugin = plugin; + info->handler = handler; + info->schema = schema; + return info; +} + +bool LunaService::methodHandler(LSMessage &msg) +{ + LS::Message request{&msg}; + std::string methodName = request.getMethod(); + std::string categoryName = request.getCategory(); + + LOG_DEBUG("Luna method called %s/%s: %s", categoryName.c_str(), methodName.c_str(), + request.getPayload()); + + MethodInfo* method = this->findMethod(categoryName, methodName); + + if (!method || method->plugin == nullptr || method->handler == nullptr) + { + /** Most likely plugin unloaded. */ + LOG_DEBUG("No handler for method call"); + request.respond( + R"({"returnValue":false, "errorCode":1, "errorMessage":"Method removed."})"); + return true; + } + + JValue value = JDomParser::fromString(request.getPayload(), method->schema); + + if (!value.isValid()) + { + LOG_ERROR(MSGID_LS2_RESPONSE_PARSE_ERROR, 0, + "Failed to validate luna request against schema: %s, error: %s", + request.getPayload(), + value.errorString().c_str()); + request.respond( + R"({"returnValue":false, "errorCode":2, "errorMessage":"Failed to validate request against schema"})"); + return true; + } + else + { + LOG_DEBUG("Calling method handler"); + JValue result = method->handler(value); + request.respond(result.stringify("").c_str()); + + //FIXME: plugin unloading should be decoupled from luna service. + method->plugin->manager->processUnload(method->plugin); + return true; + } +} + +SubscribeHandle LunaService::subscribeToMethod(const std::string &serviceUrl, + JValue ¶ms, + SubscribeCallback callback, + const pbnjson::JSchema &schema, + PluginAdapter *plugin, + bool checkFirstResponse) +{ + std::string paramsStr; + + if (params.getType() != JValueType::JV_OBJECT) + { + paramsStr = R"({"subscribe":true})"; + } + else + { + params.put("subscribe", JValue(true)); + paramsStr = params.stringify(); + } + + LOG_DEBUG("Subscribing to %s params %s", serviceUrl.c_str(), paramsStr.c_str()); + + SubscriptionInfo *info = new SubscriptionInfo(schema); + + try + { + this->subscriptions[info] = info; + + info->service = this; + info->call = this->callMultiReply(serviceUrl.c_str(), + paramsStr.c_str()); + + if (checkFirstResponse) + { + LS::Message reply = info->call.get(1000); + + if (!reply) + { + LOG_ERROR(MSGID_LS2_CALL_NO_REPLY, 0, "Luna call %s has no reply within timeout %d", serviceUrl.c_str(), 1000); + delete info; + info = nullptr; + throw Error("SubscribeToMethod: No luna call response within 1000 ms"); + } + + LOG_DEBUG("Subscribe first result %s: %s", serviceUrl.c_str(), reply.getPayload()); + + // First response is not validated against the shcema. + // As it's frequently in different format than the subscribe responses. + JValue value = JDomParser::fromString(reply.getPayload(), JSchema::AllSchema()); + bool success = false; + ConversionResultFlags error = value["returnValue"].asBool(success); + + if (error) + { + LOG_ERROR(MSGID_LS2_RESPONSE_PARSE_ERROR, 0, "Failed to parse returnValue in first response: %s", + reply.getPayload()); + delete info; + info = nullptr; + throw Error("Failed to parse returnValue in first response"); + } + else if (!success) + { + LOG_ERROR(MSGID_LS2_FIRST_RESPONSE_ERROR, 0, "First response failed: %s", + reply.getPayload()); + delete info; + info = nullptr; + throw Error("First response failed"); + } + + LOG_DEBUG("Subscribe first result success"); + } + + info->subscribeCallback = callback; + info->counter = 0; + info->plugin = plugin; + info->serviceUrl = serviceUrl; + info->previousValue = JValue(); // Null value + info->call.continueWith(LunaService::callResultHandler, info); + LOG_DEBUG("Subscribe successful"); + + return info; + } + catch (const LS::Error &error) + { + LOG_ERROR(MSGID_LS2_FAILED_TO_SUBSCRIBE, 0, + "Failed to subscribe %s, params %s" , serviceUrl.c_str(), paramsStr.c_str()); + delete info; + info = nullptr; + throw; + } +} + +void LunaService::cancelSubscribe(SubscriptionInfo *info) +{ + if (this->subscriptions.count(info) > 0) + { + LOG_DEBUG("Canceling subscribe to %s", info->serviceUrl.c_str()); + info->call.cancel(); + delete info; + this->subscriptions.erase(info); + } +} + +void LunaService::cleanupPlugin(PluginAdapter *plugin) +{ + // Erase all subscriptions associated with the plugin + for (auto iter = this->subscriptions.cbegin(); + iter != this->subscriptions.cend(); /* no increment */) + { + if (iter->first->plugin == plugin) + { + SubscriptionInfo *info = iter->first; + info->call.cancel(); + delete info; + this->subscriptions.erase(iter++); + } + else + { + ++iter; + } + } + + // Set plugin to null for all methods associated with the plugin. + // Note that we cannot remove methods from bus, instead the method + // handler will return a generic error - method removed. + for (auto cat : this->categoryMethods) + { + for (auto methodIter: cat.second) + { + if (methodIter.second->plugin == plugin) + { + MethodInfo *info = methodIter.second; + info->plugin = nullptr; + info->handler = nullptr; + } + } + } +} + +bool LunaService::callResultHandler(LSHandle *handle UNUSED_VAR, + LSMessage *message, + void *context) +{ + auto info = reinterpret_cast(context); + + LunaService *service = info->service; + + if (service->subscriptions.count(info) == 0) + { + //Should not ever happen, but anyway. At this point info is most likely + //Freed memory. + LS::Message reply{message}; + LOG_CRITICAL(MSGID_LS2_HUB_ERROR, 0, + "No subscription info for subscription reply from %s", reply.getSender()); + return false; + } + + return info->service->callResult(info, message); +} + +bool LunaService::callResult(SubscriptionInfo *info, LSMessage *message) +{ + LS::Message reply{message}; + + if (reply.isHubError()) + { + LOG_INFO(MSGID_LS2_HUB_ERROR, 0, "Luna hub error, service %s", + info->serviceUrl.c_str()); + this->cancelSubscribe(info); + return false; + } + + LOG_DEBUG("Subscribe callback %s: %s", info->serviceUrl.c_str(), + reply.getPayload()); + + JValue value = JDomParser::fromString(reply.getPayload(), JSchema::AllSchema()); + JResult validation = info->schema.validate(value); + + if (!value.isValid()) + { + LOG_ERROR(MSGID_LS2_RESPONSE_PARSE_ERROR, 0, "Failed to parse luna reply: %s", + reply.getPayload()); + } + else if (!value.isObject()) + { + LOG_ERROR(MSGID_LS2_RESPONSE_NOT_AN_OBJECT, 0, + "Luna reply not an JSON object: %s", reply.getPayload()); + } + else if (validation.isError()) + { + LOG_ERROR(MSGID_LS2_RESPONSE_SCHEMA_ERROR, 0, + "Failed to validate against schema: %s, schema: %s", reply.getPayload(), validation.errorString().c_str()); + return false; + } + else + { + PluginAdapter *plugin = info->plugin; + + if (info->simpleCallback) + { + LunaCallback callback = info->simpleCallback; + this->cancelSubscribe(info); + //Callback always last as it can change the state or even delete everyting + callback(value); + } + else if (info->subscribeCallback) + { + info->counter += 1; + JValue previousValue = info->previousValue; + info->previousValue = value; + //Callback always last as it can change the state or even delete info + info->subscribeCallback(previousValue, value); + } + + //FIXME: not nice calling it from here. Luna service should be decoupled + // from plugins + if (plugin) + { + plugin->manager->processUnload(plugin); + } + } + + return true; +} + + +void LunaService::onLunaDisconnect(LSHandle *handle UNUSED_VAR, void *data) +{ + auto service = reinterpret_cast(data); + + LOG_INFO(MSGID_LS2_DISCONNECTED, 0, "Luna service disconnected."); + std::cout << "LS disconnect"; + service->detach(); //Detaching from mainloop should cause it to stop, terminating the application. +} diff --git a/src/service/lunaservice.h b/src/service/lunaservice.h new file mode 100644 index 0000000..54b2044 --- /dev/null +++ b/src/service/lunaservice.h @@ -0,0 +1,145 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +#include + +class LunaService; +class PluginAdapter; + +class MethodInfo +{ +public: + MethodInfo(): + plugin(nullptr), + schema(pbnjson::JSchema::AllSchema()) + {}; + + PluginAdapter *plugin; // Null if plugin unloaded + EventMonitor::LunaCallHandler handler; + pbnjson::JSchema schema; + std::string url; +}; + +class SubscriptionInfo +{ +public: + SubscriptionInfo(const pbnjson::JSchema &_schema): + service(nullptr), + plugin(nullptr), + schema(_schema), + counter(0) + {}; + + LunaService *service; + PluginAdapter *plugin; + EventMonitor::SubscribeCallback subscribeCallback; + EventMonitor::LunaCallback simpleCallback; + std::string serviceUrl; + pbnjson::JValue previousValue; + pbnjson::JSchema schema; + LS::Call call; + unsigned long long counter; +}; + +typedef SubscriptionInfo *SubscribeHandle; +typedef SubscriptionInfo *CallHandle; + +/** + * Wrapper around the luna service. + * Connects to the bus and provides convenience methods. + */ +class LunaService: private LS::Handle +{ +public: + LunaService(std::string servicePath, GMainLoop *mainLoop, + const char *identifier); + ~LunaService(); + + /** + * Not copyable (because the handle is not copyable). + */ + LunaService(const LunaService &) = delete; + LunaService &operator=(const LunaService &) = delete; + + pbnjson::JValue call(const std::string &serviceUrl, + pbnjson::JValue ¶ms, + unsigned long timeout = 1000); + CallHandle callAsync(const std::string &serviceUrl, + pbnjson::JValue ¶ms, + EventMonitor::LunaCallback callback, + PluginAdapter *plugin); + + /** + * Subscribe to luna method + * @param checkFirstResponse - if true, will wait synchronously for first + * response to arrive and not passs it to the callback method. + * The return value of the resposne will be checked and exception raised if + * it is not successful. + */ + SubscribeHandle subscribeToMethod( + const std::string &serviceUrl, + pbnjson::JValue ¶ms, + EventMonitor::SubscribeCallback callback, + const pbnjson::JSchema &schema, + PluginAdapter *plugin, + bool checkFirstResponse = false); + + void cancelSubscribe(SubscriptionInfo *handle); + + /** + * Cancels all subscriptions and calls associated with this plugin. + */ + void cleanupPlugin(PluginAdapter *plugin); + + MethodInfo* registerMethod(PluginAdapter *plugin, + const std::string &category, + const std::string &methodName, + EventMonitor::LunaCallHandler handler, + const pbnjson::JSchema &schema); + +private: + static bool callResultHandler(LSHandle *handle, LSMessage *message, + void *context); + static void onLunaDisconnect(LSHandle *sh, void *user_data); + bool methodHandler(LSMessage &msg); + bool callResult(SubscriptionInfo *info, LSMessage *message); + + inline MethodInfo* findMethod(const std::string& category, const std::string& name) + { + if (this->categoryMethods.count(category) != 0 && this->categoryMethods[category].count(name) != 0) + { + return this->categoryMethods[category][name]; + } + else + { + return nullptr; + } + } + +public: + const std::string servicePath; + +private: + std::unordered_map subscriptions; + std::unordered_map > categoryMethods; +}; + diff --git a/src/service/main.cpp b/src/service/main.cpp new file mode 100644 index 0000000..245fc1a --- /dev/null +++ b/src/service/main.cpp @@ -0,0 +1,211 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include + +#include "logging.h" +#include "config.h" +#include "pluginloader.h" +#include "pluginmanager.h" +#include "servicemonitor.h" + +static const char *LOG_CONTEXT_NAME = COMPONENT_NAME; + +PmLogContext logContext; +static bool terminated = false; +static GMainLoop *mainLoop = NULL; + +//Option variables +static gboolean option_version = FALSE; + +static GOptionEntry options[] = +{ + { + "version", 'v', 0, G_OPTION_ARG_NONE, &option_version, + "Show version information and exit" + }, + { nullptr }, +}; + + +void processOptions(int argc, char **argv) +{ + GOptionContext *context; + GError *err = nullptr; + + context = g_option_context_new(nullptr); + g_option_context_add_main_entries(context, options, nullptr); + + if (g_option_context_parse(context, &argc, &argv, &err) == FALSE) + { + if (err != nullptr) + { + g_printerr("%s\n", err->message); + g_error_free(err); + } + else + { + g_printerr("An unknown error occurred\n"); + } + + exit(EXIT_FAILURE); + } + + g_option_context_free(context); +} + +void setupLogging() +{ + PmLogErr error = PmLogGetContext(LOG_CONTEXT_NAME, &logContext); + + if (error != kPmLogErr_None) + { + std::cerr << "Failed to setup up log context " << LOG_CONTEXT_NAME << std::endl; + abort(); + } +} + +static gboolean signal_handler(GIOChannel *channel, GIOCondition cond, + gpointer user_data) +{ + struct signalfd_siginfo si; + ssize_t result; + int fd; + + if (cond & (G_IO_NVAL | G_IO_ERR | G_IO_HUP)) + { + return FALSE; + } + + fd = g_io_channel_unix_get_fd(channel); + + result = read(fd, &si, sizeof(si)); + + if (result != sizeof(si)) + { + return FALSE; + } + + switch (si.ssi_signo) + { + case SIGUSR1: + /* This will exit the service without correctly terminating and leaving every + * applied configured as it is. This is needed to simulate various test + * situations. */ + exit(EXIT_FAILURE); + break; + + case SIGINT: + case SIGTERM: + if (terminated == 0) + { + LOG_INFO(MSGIC_TERMINATING, 0, "Terminating"); + g_main_loop_quit(mainLoop); + } + + terminated = 1; + break; + + default: + break; + } + + return TRUE; +} + +static guint setup_signalfd() +{ + GIOChannel *channel; + guint source; + sigset_t mask; + int fd; + + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGUSR1); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) + { + std::cerr << "Failed to set signal mask"; + return 0; + } + + fd = signalfd(-1, &mask, 0); + + if (fd < 0) + { + std::cerr << "Failed to create signal descriptor"; + return 0; + } + + channel = g_io_channel_unix_new(fd); + + g_io_channel_set_close_on_unref(channel, TRUE); + g_io_channel_set_encoding(channel, NULL, NULL); + g_io_channel_set_buffered(channel, FALSE); + + source = g_io_add_watch(channel, + static_cast(G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL), + signal_handler, NULL); + + g_io_channel_unref(channel); + + return source; +} + +int main(int argc, char **argv) +{ + std::unique_ptr> main_loop{g_main_loop_new(nullptr, FALSE), g_main_loop_unref}; + mainLoop = main_loop.get(); + + guint signal = setup_signalfd(); + + processOptions(argc, argv); + + if (option_version == TRUE) + { + std::cout << VERSION << std::endl; + exit(EXIT_SUCCESS); + } + + setupLogging(); + + try + { + //setup the service + LunaService service{SERVICE_BUS_NAME, mainLoop, nullptr}; + PluginLoader loader{WEBOS_EVENT_MONITOR_PLUGIN_PATH}; + PluginManager manager{loader, service, mainLoop}; + + ServiceMonitor monitor{manager, service}; + monitor.startMonitor(loader.getPlugins()); + + g_main_loop_run(mainLoop); + } + catch(...) + { + LOG_ERROR(MSGID_SERVICE_STATUS_ERROR, 0,"startMonitor failure :"); + exit(EXIT_FAILURE); + } + + g_source_remove(signal); + + return EXIT_SUCCESS; +} + diff --git a/src/service/pluginadapter.cpp b/src/service/pluginadapter.cpp new file mode 100644 index 0000000..98b7c99 --- /dev/null +++ b/src/service/pluginadapter.cpp @@ -0,0 +1,525 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "pluginadapter.h" +#include "pluginmanager.h" +#include "logging.h" +#include "utils.h" +#include "config.h" + +using namespace pbnjson; + +PluginAdapter::PluginAdapter(PluginManager *_manager, const PluginInfo *_info): + needUnload(false), + manager(_manager), + info(_info), + plugin(nullptr), + unloadNotified(false) +{ + //Prepare logging context + const std::string name = std::string(COMPONENT_NAME) + "-" + this->info->name; + PmLogErr error = PmLogGetContext(name.c_str(), &this->logContext); + + if (error != kPmLogErr_None) + { + LOG_WARNING(MSGID_PMLOG_GETCONTEXT_FAIL, 0 , + "Failed to setup up log context %s, error %d\n", name.c_str(), error); + //Will use the global context. + this->logContext = ::logContext; + } + + //Plugin instance will now be constructed and pluginLoaded called. +} + +void PluginAdapter::unloadPlugin() +{ + if (!this->plugin) + { + return; + } + + LOG_DEBUG("Preparing to unload plugin %s", this->info->name.c_str()); + + // cleanup pending luna calls + this->manager->lunaService.cleanupPlugin(this); + + // close alerts + while (!this->alerts.empty()) + { + (void) this->closeAlert(this->alerts.begin()->first); + } + + // cleanup timeouts + while (!this->timeouts.empty()) + { + (void) this->cancelTimeout(this->timeouts.cbegin()->first); + } + + //Cannot delete the plugin here, it might still be in our call stack. + //Instead set a flag and process later. + //Need to call manager->processUnload(adapter) when scope out of adapter. + this->needUnload = true; +} + +PluginAdapter::~PluginAdapter() +{ + if (this->plugin) + { + delete this->plugin; + this->plugin = nullptr; + } +} + +void PluginAdapter::pluginLoaded(Plugin *plugin) +{ + if (plugin) + { + //First time + this->plugin = plugin; + //It's unloaded until we have called startMonitoring + this->unloadNotified = true; + } + else + { + //Only if was notified about unloading before + if (!this->unloadNotified) + { + return; + } + } + + this->unloadNotified = false; + + LOG_DEBUG("Calling startMonitoring on plugin %s", this->info->path.c_str()); + + try + { + this->plugin->startMonitoring(); + } + catch (const std::exception &e) + { + LOG_ERROR(MSGID_PLUGIN_EXCEPTION, 0, + "Exception while executing startMonitoring in plugin %s, message: %s", + this->info->path.c_str(), e.what()); + this->unloadPlugin(); + } + catch (...) + { + LOG_ERROR(MSGID_PLUGIN_EXCEPTION, 0, + "Exception while executing startMonitoring in plugin %s", + this->info->path.c_str()); + this->unloadPlugin(); + } + + LOG_DEBUG("Done startMonitoring on plugin %s", this->info->path.c_str()); +} + +void PluginAdapter::setupLogging(PmLogContext *context) +{ + if (!context) + { + LOG_ERROR(MSGID_UNLOAD_BAD_PARAMS, 0, "Bad parameters"); + return; + } + + *context = this->logContext; +} + +void PluginAdapter::notifyLocaleChanged(const std::string &locale) +{ + if (!this->plugin) + { + //Plugin not loaded at this moment. + return; + } + + try + { + this->plugin->uiLocaleChanged(locale); + } + catch (const std::exception &e) + { + LOG_ERROR(MSGID_PLUGIN_EXCEPTION, 0, + "Exception while executing uiLocaleChanged in plugin %s, message: %s", + this->info->path.c_str(), e.what()); + this->unloadPlugin(); + } + catch (...) + { + LOG_ERROR(MSGID_PLUGIN_EXCEPTION, 0, + "Unknown exception while executing uiLocaleChanged in plugin %s", + this->info->path.c_str()); + this->unloadPlugin(); + } +} + +void PluginAdapter::notifyPluginShouldUnload(const std::string &service) +{ + if (!this->plugin) + { + //Ok to unload if not yet loaded. + return; + } + + UnloadResult result; + + this->unloadNotified = true; + + LOG_DEBUG("Calling stopMonitoring on plugin %s", this->info->path.c_str()); + + try + { + result = this->plugin->stopMonitoring(service); + } + catch (const std::exception &e) + { + LOG_ERROR(MSGID_PLUGIN_EXCEPTION, 0, + "Exception while executing stopMonitoring in plugin %s, message: %s", + this->info->path.c_str(), e.what()); + result = UNLOAD_OK; + } + catch (...) + { + LOG_ERROR(MSGID_PLUGIN_EXCEPTION, 0, + "Exception while executing stopMonitoring in plugin %s", + this->info->path.c_str()); + result = UNLOAD_OK; + } + + if (result == UNLOAD_OK) + { + this->unloadPlugin(); + } + + LOG_DEBUG("Done stopMonitoring on plugin %s", this->info->path.c_str()); +} + +void PluginAdapter::subscribeToMethod(const std::string &subscriptionId, + const std::string &serviceName, + JValue ¶ms, + SubscribeCallback callback, + const pbnjson::JSchema &schema) +{ + (void) this->unsubscribeFromMethod(subscriptionId); + + LOG_DEBUG("Plugin %s trying to subscribe to method: %s", + this->info->name.c_str(), + serviceName.c_str()); + + //Do not allow to subscribe if the service is not a requirement. + if (!this->info->containsURI(serviceName)) + { + LOG_ERROR(MSGID_PLUGIN_INVALID_SUBSCRIBE, 0, + "Can only subscribe to services that are in required list, plugin: %s, service: %s", + this->info->name.c_str(), + serviceName.c_str()); + throw Error("Can only subscribe to services that are in required list"); + } + + SubscribeHandle handle = this->manager->lunaService.subscribeToMethod( + serviceName, + params, + callback, + schema, + this, + false); + this->subscriptions[subscriptionId] = handle; +} + +bool PluginAdapter::unsubscribeFromMethod(const std::string &subscriptionId) +{ + if (this->subscriptions.count(subscriptionId) == 0) + { + return false; + } + + SubscribeHandle handle = this->subscriptions[subscriptionId]; + this->manager->lunaService.cancelSubscribe(handle); + this->subscriptions.erase(subscriptionId); + return true; +} + +void PluginAdapter::subscribeToSignal(const std::string &subscriptionId, + const std::string &category, + const std::string &method, + SubscribeCallback callback, + const pbnjson::JSchema &schema) +{ + (void) this->unsubscribeFromSignal(subscriptionId); + + LOG_DEBUG("Plugin %s trying to subscribe to signal: %s, method %s", + this->info->name.c_str(), + category.c_str(), + method.c_str()); + + JObject params = JObject{{"category", category.c_str()}}; + if (method.length() > 0) + { + params.put("method", method.c_str()); + } + + SubscribeHandle handle = this->manager->lunaService.subscribeToMethod( + "luna://com.webos.service.bus/signal/addmatch", + params, + callback, + schema, + this, + true); + this->subscriptions[subscriptionId] = handle; +} + +bool PluginAdapter::unsubscribeFromSignal(const std::string &subscriptionId) +{ + return this->unsubscribeFromMethod(subscriptionId); +} + +pbnjson::JValue PluginAdapter::lunaCall(const std::string &serviceUrl, + pbnjson::JValue ¶ms, + unsigned long timeout) +{ + return this->manager->lunaService.call(serviceUrl, params, timeout); +} + +void PluginAdapter::lunaCallAsync(const std::string &serviceUrl, + pbnjson::JValue ¶ms, + LunaCallback callback) +{ + this->manager->lunaService.callAsync(serviceUrl, params, callback, this); +} + +void PluginAdapter::setTimeout(const std::string &timeoutId, + unsigned int timeMs, + bool repeat, + TimeoutCallback callback) +{ + (void) this->cancelTimeout(timeoutId); + + LOG_DEBUG("Plugin %s set timeout: %s", + this->info->name.c_str(), + timeoutId.c_str()); + + auto timeout = new TimeoutState(); + + guint handle = g_timeout_add(timeMs, PluginAdapter::timeoutCallback, timeout); + + if (handle == 0) + { + LOG_ERROR(MSGID_ADD_TIMEOUT_FAILED, 0, + "Add timeout failed, id: %s, timeout: %u", timeoutId.c_str(), timeMs); + delete timeout; + return; + } + + timeout->adapter = this; + timeout->callback = callback; + timeout->timeoutId = timeoutId; + timeout->repeat = repeat; + timeout->handle = handle; + this->timeouts[timeoutId] = timeout; +} + +bool PluginAdapter::cancelTimeout(const std::string &timeoutId) +{ + if (this->timeouts.count(timeoutId) == 0) + { + return false; + } + + LOG_DEBUG("Plugin %s cancel timeout: %s", + this->info->name.c_str(), + timeoutId.c_str()); + + TimeoutState *timeout = this->timeouts[timeoutId]; + this->timeouts.erase(timeoutId); + g_source_remove(timeout->handle); + delete timeout; + return true; +} + +gboolean PluginAdapter::timeoutCallback(gpointer userData) +{ + TimeoutState *state = reinterpret_cast(userData); + PluginAdapter *adapter = state->adapter; + gboolean continueTimeout; + + { + std::string timeoutId = state->timeoutId; + TimeoutCallback callback = state->callback; + + LOG_DEBUG("Plugin %s timeout happened: %s", + state->adapter->info->name.c_str(), + timeoutId.c_str()); + + //Do any processing before doing the callback, as it might unload the plugin + + if (state->repeat) + { + continueTimeout = G_SOURCE_CONTINUE; + } + else + { + (void) state->adapter->cancelTimeout(timeoutId); + continueTimeout = G_SOURCE_REMOVE; + } + + try + { + callback(timeoutId); + } + catch (const std::exception &e) + { + LOG_ERROR(MSGID_PLUGIN_EXCEPTION, 0, + "Exception while executing timeout callback in plugin %s, message: %s", + adapter->info->path.c_str(), e.what()); + adapter->unloadPlugin(); + } + catch (...) + { + LOG_ERROR(MSGID_PLUGIN_EXCEPTION, 0, + "Exception while executing timeout callback in plugin %s", + adapter->info->path.c_str()); + adapter->unloadPlugin(); + } + } + + adapter->manager->processUnload(adapter); + return continueTimeout; +} + +const std::string PluginAdapter::getUILocale() +{ + return this->manager->getUILocale(); +} + +const pbnjson::JValue &PluginAdapter::getLocaleInfo() +{ + return this->manager->locale; +} + +void PluginAdapter::createToast( + const std::string &message, + const std::string &iconUrl, + const pbnjson::JValue &onClickAction) +{ + JObject params = JObject(); + params.put("message", JValue(message)); + params.put("sourceId", JValue(this->manager->lunaService.servicePath + "-" + + this->info->name)); + + if (iconUrl.length() > 0) + { + params.put("iconUrl", JValue(iconUrl)); + } + + if (!onClickAction.isNull()) + { + params.put("onclick", onClickAction); + } + + this->manager->lunaService.callAsync("luna://com.webos.notification/createToast", + params, + nullptr, + this); +} + +void PluginAdapter::createAlert(const std::string &alertId, + const std::string &title, + const std::string &message, + bool modal, + const std::string &iconUrl, + const pbnjson::JValue &buttons, + const pbnjson::JValue &onClose) +{ + (void) this->closeAlert(alertId); + + JObject params = JObject{{"title", JValue(title)}, + {"modal", JValue(modal)}, + {"message", JValue(message)}, + {"buttons", buttons}}; + + if (!onClose.isNull()) + { + params.put("onclose", onClose); + } + + if (iconUrl.length() > 0) + { + params.put("iconUrl", JValue(iconUrl)); + } + + JValue result = this->manager->lunaService.call( + "luna://com.webos.notification/createAlert", + params); + + bool success = false; + std::string internalId = ""; + ConversionResultFlags jsonError = 0; + + jsonError = result["returnValue"].asBool(success); + jsonError |= result["alertId"].asString(internalId); + + if (jsonError || !success || internalId.length() == 0) + { + LOG_ERROR(MSGID_CREATE_ALERT_FAILED, 0, + "Failed to create alert, plugin %s, params %s, response was %s", + this->info->path.c_str(), + params.stringify("").c_str(), + result.stringify("").c_str()); + throw Error("Failed to create alert"); + } + + this->alerts[alertId] = internalId; +} + +bool PluginAdapter::closeAlert(const std::string &alertId) +{ + if (this->alerts.count(alertId) == 0) + { + return false; + } + + JObject params = JObject{{"alertId", JValue(this->alerts[alertId])}}; + this->alerts.erase(alertId); + this->manager->lunaService.call("luna://com.webos.notification/closeAlert", + params); + return true; +} + +std::string PluginAdapter::registerMethod(const std::string &category, + const std::string &name, + LunaCallHandler handler, + const pbnjson::JSchema &schema) +{ + + if (name.length() == 0) + { + throw Error("Name length = 0"); + } + if (category.length() == 0 || category[0] != '/') + { + throw Error("Category needs to start with /"); + } + + MethodInfo* method; + method = this->manager->lunaService.registerMethod( + this, + category, + name, + handler, + schema); + + return method->url; +} diff --git a/src/service/pluginadapter.h b/src/service/pluginadapter.h new file mode 100644 index 0000000..3617b12 --- /dev/null +++ b/src/service/pluginadapter.h @@ -0,0 +1,168 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include + +#include "pluginloader.h" +#include "lunaservice.h" + +using namespace EventMonitor; + +class PluginManager; +class PluginAdapter; + +class TimeoutState +{ +public: + PluginAdapter *adapter; + std::string timeoutId; + bool repeat; + guint handle; + TimeoutCallback callback; +}; + +/** + * Implement the Manager API and handles all incoming calls from the plugin. + * Each plugin instance has a matching adapter instance that handles calls + * form that particular plugin. + */ +class PluginAdapter: public Manager +{ +public: + PluginAdapter(PluginManager *manager, const PluginInfo *info); + virtual ~PluginAdapter(); + + // Manager methods - called from plugin + void setupLogging(PmLogContext *context); + void unloadPlugin(); + + const std::string getUILocale(); + + const pbnjson::JValue &getLocaleInfo(); + + pbnjson::JValue lunaCall(const std::string &serviceUrl, + pbnjson::JValue ¶ms, + unsigned long timeout = 1000); + + void lunaCallAsync(const std::string &serviceUrl, + pbnjson::JValue ¶ms, + LunaCallback callback); + + /** + * Registers a luna method on bus. + * This method may be called multiple times for same methodName to update + * the handler or schema or simply to retrieve the method path. + * Overriding methods registered by different plugin is not allowed + * and will result in exception. + * @param categoryName - category name. Must start with "/". + * @param methodName - method name. Registering again + * with same name will override previous method. + * @param handler - function to call when method is called, + * @params schema - schema for method parameters. All calls not conforming + * to schema will dropped and logged. + * @return method service URL, eg: luna://com.webos.service.event-monitor/pluginName/methodName + */ + virtual std::string registerMethod(const std::string &categoryName, + const std::string &methodName, + LunaCallHandler handler, + const pbnjson::JSchema &schema = pbnjson::JSchema::AllSchema()); + + void subscribeToMethod( + const std::string &subscriptionId, + const std::string &methodPath, + pbnjson::JValue ¶ms, + SubscribeCallback callback, + const pbnjson::JSchema &schema = pbnjson::JSchema::AllSchema()); + + bool unsubscribeFromMethod(const std::string &subscriptionId); + + + void subscribeToSignal( + const std::string &subscriptionId, + const std::string &category, + const std::string &method, + SubscribeCallback callback, + const pbnjson::JSchema &schema = pbnjson::JSchema::AllSchema()); + + bool unsubscribeFromSignal(const std::string &subscriptionId); + + void setTimeout(const std::string &timeoutId, + unsigned int timeMs, + bool repeat, + TimeoutCallback callback); + + bool cancelTimeout(const std::string &timeoutId); + + void createToast( + const std::string &message, + const std::string &iconUrl = "", + const pbnjson::JValue &onClickAction = pbnjson::JValue()); + + /** + * Convenience method to create an alert. + */ + void createAlert(const std::string &alertId, + const std::string &title, + const std::string &message, + bool modal, + const std::string &iconUrl, + const pbnjson::JValue &buttons, + const pbnjson::JValue &onClose); + + /** + * Closes the alert specified by id, if open. + */ + bool closeAlert(const std::string &alertId); + + // Methods - called from manager + void pluginLoaded(Plugin *plugin); + + void notifyLocaleChanged(const std::string &locale); + void notifyPluginShouldUnload(const std::string &service); + + inline const PluginInfo *getInfo() + { + return this->info; + } + +public: + //Plugin needs to be unloaded + bool needUnload; + PluginManager *manager; + +private: + static gboolean timeoutCallback(gpointer userData); + + const PluginInfo *info; + PmLogContext logContext; + Plugin *plugin; + //Plugin was notified that it should unload + bool unloadNotified; + + // Active subscriptions + std::unordered_map subscriptions; + + // Active alerts + std::unordered_map timeouts; + + // Active alerts + std::unordered_map alerts; +}; diff --git a/src/service/plugininfo.cpp b/src/service/plugininfo.cpp new file mode 100644 index 0000000..9449528 --- /dev/null +++ b/src/service/plugininfo.cpp @@ -0,0 +1,57 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "plugininfo.h" +#include "utils.h" +#include "logging.h" + +using namespace EventMonitor; + +bool PluginInfo::containsURI(const std::string &uri) const +{ + auto parts = splitString(uri, '/'); + + if (parts.size() < 3) + { + LOG_INFO(MSGID_PLUGIN_INVALID_SUBSCRIBE, 0, + "Parts.size < 3, %u", static_cast(parts.size())); + + throw Error("Bad luna URL"); + } + + if (parts[0] != "luna:" || parts[1] != "" || parts[2].length() == 0) + { + + LOG_INFO(MSGID_PLUGIN_INVALID_SUBSCRIBE, 0, + "Parts bad, %s, %s, %s", parts[0].c_str(), parts[1].c_str(), parts[2].c_str()); + + throw Error("Bad luna URL"); + } + + std::string serviceName = parts[2]; + + for (auto s : this->requiredServices) + { + if (s == serviceName) + { + return true; + } + } + + return false; +} diff --git a/src/service/plugininfo.h b/src/service/plugininfo.h new file mode 100644 index 0000000..17edc2b --- /dev/null +++ b/src/service/plugininfo.h @@ -0,0 +1,36 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include +#include + +/** + * Storage class for plugin information. + */ +class PluginInfo +{ +public: + std::string name; + std::string path; + std::vector requiredServices; + void *dlHandle; // handle from dlopen + + bool containsURI(const std::string &uri) const; +}; diff --git a/src/service/pluginloader.cpp b/src/service/pluginloader.cpp new file mode 100644 index 0000000..5d2a6f7 --- /dev/null +++ b/src/service/pluginloader.cpp @@ -0,0 +1,151 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "pluginloader.h" +#include "logging.h" +#include "utils.h" + +PluginLoader::PluginLoader(const std::string &_pluginPath): + pluginPath(_pluginPath) +{ + + LOG_INFO(MSGID_PLUGIN_LOADER, 0 , "Loooking for plugins in %s", + this->pluginPath.c_str()); + + GError *error = nullptr; + std::unique_ptr> dir { g_dir_open(this->pluginPath.c_str(), 0, &error), g_dir_close}; + + if (error) + { + LOG_ERROR(MSGID_PLUGIN_LOADER, 0, + "Failed to open plugin directory: %s, error: %s", this->pluginPath.c_str(), + error->message); + return; // No plugins + } + + while (const gchar *filename = g_dir_read_name(dir.get())) + { + if (!g_str_has_suffix(filename, ".so")) + { + continue; + } + + std::string filePath = g_build_path("/", this->pluginPath.c_str(), filename, + NULL); + LOG_INFO(MSGID_PLUGIN_LOADER, 0, "Loading file: %s", filePath.c_str()); + + std::unique_ptr> handle { dlopen(filePath.c_str(), RTLD_NOW), dlclose}; + + if (!handle.get()) + { + LOG_CRITICAL(MSGID_PLUGIN_LOADER, 0, "Failed to load plugin file: %s", + filePath.c_str()); + continue; + } + + auto services = reinterpret_cast(dlsym(handle.get(), + "requiredServices")); + auto instantiateFunc = + reinterpret_cast(dlsym( + handle.get(), "instantiatePlugin")); + + if (!services || !instantiateFunc) + { + LOG_CRITICAL(MSGID_PLUGIN_LOADER, 0, + "Failed to find plugin methods, requiredServices and instantiatePlugin."); + continue; + } + + //strip the .so part + std::string name{filename, strlen(filename) - 3}; + + PluginInfo info; + info.name = name; + info.path = std::string(filePath); + info.dlHandle = nullptr; + + for (size_t pos = 0; services[pos]; pos += 1) + { + info.requiredServices.push_back(std::string(services[pos])); + } + + this->plugins.push_back(info); + } + +}; + +const std::vector *PluginLoader::getPlugins() +{ + return &this->plugins; +} + +Plugin *PluginLoader::loadPlugin(const PluginInfo *info, Manager *manager) +{ + LOG_INFO(MSGID_PLUGIN_LOADED, 0, "Loading plugin %s", info->path.c_str()); + + void *handle = dlopen(info->path.c_str(), RTLD_NOW); + + if (!handle) + { + LOG_CRITICAL(MSGID_PLUGIN_LOADER, 0, "Failed to load plugin file: %s", + info->path.c_str()); + throw EventMonitor::Error("Failed to load plugin"); + } + + auto instantiateFunc = reinterpret_cast(dlsym( + handle, "instantiatePlugin")); + + if (!instantiateFunc) + { + dlclose(handle); + LOG_CRITICAL(MSGID_PLUGIN_LOADER, 0, + "Failed to find plugin method instantiatePlugin."); + throw EventMonitor::Error("Failed to load plugin"); + } + + Plugin *plugin; + + try + { + plugin = instantiateFunc(API_VERSION, manager); + } + catch (...) + { + //FIXME: should do this with unique_ptr + dlclose(handle); + throw; + } + + const_cast(info)->dlHandle = handle; + + return plugin; +} + +void PluginLoader::unloadPlugin(const PluginInfo *info) +{ + LOG_INFO(MSGID_PLUGIN_UNLOADED, 0, "Unloading plugin %s", info->path.c_str()); + + if (!info->dlHandle) + { + return; + } + + dlclose(info->dlHandle); + const_cast(info)->dlHandle = nullptr; +} diff --git a/src/service/pluginloader.h b/src/service/pluginloader.h new file mode 100644 index 0000000..781c2c5 --- /dev/null +++ b/src/service/pluginloader.h @@ -0,0 +1,42 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include +#include + +#include "plugininfo.h" + +using namespace EventMonitor; + +/** + * Manages list of available plugins and performs the actual loading unloading. + */ +class PluginLoader +{ +public: + PluginLoader(const std::string &pluginPath); + + const std::vector *getPlugins(); + Plugin *loadPlugin(const PluginInfo *info, Manager *manager); + void unloadPlugin(const PluginInfo *info); + +private: + const std::string pluginPath; + std::vector plugins; +}; diff --git a/src/service/pluginmanager.cpp b/src/service/pluginmanager.cpp new file mode 100644 index 0000000..7e33fb9 --- /dev/null +++ b/src/service/pluginmanager.cpp @@ -0,0 +1,142 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "pluginmanager.h" +#include "logging.h" + +PluginManager::PluginManager(PluginLoader &_loader, + LunaService &_lunaService, + GMainLoop *_mainLoop): + lunaService(_lunaService), + mainLoop(_mainLoop), + loader(_loader) +{ +} + +PluginManager::~PluginManager() +{ + while (this->activePlugins.size() > 0) + { + PluginAdapter *adapter = this->activePlugins.begin()->second; + adapter->unloadPlugin(); + this->processUnload(adapter); + } +} + +/** + * Called by lunaMonitor. + */ +void PluginManager::loadPlugin(const PluginInfo *info, + const std::string &service) +{ + if (this->isPluginLoaded(info)) + { + PluginAdapter *adapter = this->activePlugins[info->path]; + adapter->pluginLoaded(nullptr); + this->processUnload(adapter); + } + else // new plugin + { + PluginAdapter *adapter = new PluginAdapter(this, info); + Plugin *plugin = this->loader.loadPlugin(info, adapter); + + if (!plugin) + { + //Plugin loader method successfully called but returned null. + // Most likely API incompatibility. + LOG_ERROR(MSGID_PLUGIN_LOAD_FAILED, + 0, + "Plugin %s instantiatePlugin returned NULL", + info->name.c_str()); + + this->loader.unloadPlugin(info); + delete adapter; + return; + } + + this->activePlugins[info->path] = adapter; + adapter->pluginLoaded(plugin); + this->processUnload(adapter); + } +} + +bool PluginManager::isPluginLoaded(const PluginInfo *pluginInfo) +{ + return this->activePlugins.count(pluginInfo->path) > 0; +} + +void PluginManager::processUnload(PluginAdapter *adapter) +{ + if (!adapter->needUnload) + { + return; + } + + const PluginInfo *info = adapter->getInfo(); + this->activePlugins.erase(info->path); + + adapter->unloadPlugin(); + delete adapter; + + //Unload the shared library, must happen after deleting the adapter + this->loader.unloadPlugin(info); +} + +void PluginManager::notifyPluginShouldUnload(const PluginInfo *pluginInfo, + const std::string &serviceName) +{ + if (!this->isPluginLoaded(pluginInfo)) + { + return; + } + + PluginAdapter *adapter = this->activePlugins[pluginInfo->path]; + adapter->notifyPluginShouldUnload(serviceName); + this->processUnload(adapter); +} + +void PluginManager::notifyLocaleChanged(const pbnjson::JValue &locale) +{ + this->locale = locale; + + std::string localeStr = this->getUILocale(); + locale.asString(localeStr); + + for (auto iter = this->activePlugins.begin(); + iter != this->activePlugins.end(); /* no increment */) + { + PluginAdapter *adapter = iter->second; + adapter->notifyLocaleChanged(localeStr); + + iter ++; + this->processUnload(adapter); + } +} + +const std::string PluginManager::getUILocale() +{ + std::string uiLocaleStr = "en-US"; + ConversionResultFlags resultFail = this->locale["locales"]["UI"].asString( + uiLocaleStr); + + if (resultFail) + { + LOG_ERROR(MSGID_LOCALE_ERROR, 0, "Could not parse ui locale: %s.", + this->locale.stringify().c_str()); + } + + return uiLocaleStr; +} diff --git a/src/service/pluginmanager.h b/src/service/pluginmanager.h new file mode 100644 index 0000000..98db887 --- /dev/null +++ b/src/service/pluginmanager.h @@ -0,0 +1,73 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include "pluginadapter.h" + + +/** + * Manages list of loaded plugins and dispatches common notifications to them. + * Handles plugin loading unloading. + */ +class PluginManager +{ +public: + PluginManager(PluginLoader &loader, LunaService &service, GMainLoop *mainLoop); + + ~PluginManager(); + + /** + * Called by lunaMonitor + */ + void loadPlugin(const PluginInfo *pluginInfo, const std::string &service); + + bool isPluginLoaded(const PluginInfo *pluginInfo); + + // void unloadPlugin(const PluginInfo* pluginInfo); + + /** + * Check if plugin is marked for unload and if so unload it. + */ + void processUnload(PluginAdapter *adapter); + + /** + * Called by lunaMonitor + */ + void notifyPluginShouldUnload(const PluginInfo *pluginInfo, + const std::string &serviceName); + + /** + * Called by lunaMonitor + */ + void notifyLocaleChanged(const pbnjson::JValue &locale); + + const std::string getUILocale(); + +public: + LunaService &lunaService; + pbnjson::JValue locale; + GMainLoop *mainLoop; + +private: + PluginLoader &loader; + + /** + * Map plugin path to plugin adapter. + */ + std::unordered_map activePlugins; +}; diff --git a/src/service/servicemonitor.cpp b/src/service/servicemonitor.cpp new file mode 100644 index 0000000..effaeec --- /dev/null +++ b/src/service/servicemonitor.cpp @@ -0,0 +1,182 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + + +#include "servicemonitor.h" +#include "logging.h" + +using namespace pbnjson; + +ServiceMonitor::ServiceMonitor(PluginManager &_manager, LunaService &_service): + manager(_manager), + service(_service), + plugins(nullptr), + monitorStarted(false) +{ +} + +ServiceMonitor::~ServiceMonitor() +{ + this->stopMonitor(); +} + +void ServiceMonitor::startMonitor(const std::vector *plugins) +{ + this->plugins = plugins; + + JValue params = JObject{{"keys", JArray({JValue("localeInfo")})}}; + + this->service.subscribeToMethod("luna://com.webos.settingsservice/getSystemSettings", + params, + std::bind(&ServiceMonitor::localeCallback, + this, + std::placeholders::_1, + std::placeholders::_2), + pbnjson::JSchema::AllSchema(), + nullptr, + false); + // Start the actual monitor once we have the locale information + // Otherwise plugins will be created with incorrect locale. +} + +void ServiceMonitor::stopMonitor() +{ + +} + +void ServiceMonitor::localeCallback(pbnjson::JValue &previousValue, + pbnjson::JValue &value) +{ + JValue locale = value["settings"]["localeInfo"]; + + if (!locale.isValid()) + { + LOG_ERROR(MSGID_SETTINGS_LOCALE_MISSING, 0, + "settings/localeinfo not found in payload: %s.", value.stringify().c_str()); + return; + } + + this->manager.notifyLocaleChanged(locale); + + if (!this->monitorStarted) + { + for (auto iter : *this->plugins) + { + this->addPlugin(iter); + } + + this->monitorStarted = true; + } +} + +void ServiceMonitor::addPlugin(const PluginInfo &info) +{ + LOG_INFO(MSGID_SERVICE_STATUS, 0, "Adding plugin from %s", info.path.c_str()); + + for (auto service : info.requiredServices) + { + if (this->serviceStatus.count(service) > 0) + { + continue; //already subscribed. + } + + LOG_INFO(MSGID_SERVICE_STATUS, 0, "Monitoring service %s", service.c_str()); + + this->serviceStatus[service] = false; + JValue params = JObject{{"serviceName", JValue(service.c_str())}}; + + this->service.subscribeToMethod( + "luna://com.webos.service.bus/signal/registerServerStatus", + params, + std::bind(&ServiceMonitor::serviceStatusCallback, + this, + std::placeholders::_1, + std::placeholders::_2), + pbnjson::JSchema::AllSchema(), + nullptr, + false); + } +} + +void ServiceMonitor::serviceStatusCallback(pbnjson::JValue &previousValue, + pbnjson::JValue &value) +{ + std::string serviceName; + bool connected; + ConversionResultFlags problems = 0; + + problems |= value["serviceName"].asString(serviceName); + problems |= value["connected"].asBool(connected); + + if (problems) + { + LOG_ERROR(MSGID_SERVICE_STATUS, 0, + "Could not parse registerServerStatus response: %s", value.stringify().c_str()); + throw Error("Could not parse registerServerStatus response"); + } + + if (this->serviceStatus.count(serviceName) == 0) + { + LOG_WARNING(MSGID_SERVICE_STATUS, 0, + "Service status response on unexpected service: %s", value.stringify().c_str()); + return; + } + + bool wasConnected = this->serviceStatus[serviceName]; + this->serviceStatus[serviceName] = connected; + + if (connected) + { + LOG_INFO(MSGID_SERVICE_STATUS, 0, "Service %s is now online", + serviceName.c_str()); + } + else + { + LOG_INFO(MSGID_SERVICE_STATUS, 0, "Service %s is now offline", + serviceName.c_str()); + } + + if (wasConnected != connected) + { + this->updatePlugins(serviceName); + } +} + +void ServiceMonitor::updatePlugins(const std::string &serviceName) +{ + for (const PluginInfo &plugin : *this->plugins) + { + bool dependenciesMet = true; + + for (const std::string &service : plugin.requiredServices) + { + if (!this->serviceStatus[service]) + { + dependenciesMet = false; + break; + } + } + + if (dependenciesMet) + { + this->manager.loadPlugin(&plugin, serviceName); + } + else + { + this->manager.notifyPluginShouldUnload(&plugin, serviceName); + } + } +} diff --git a/src/service/servicemonitor.h b/src/service/servicemonitor.h new file mode 100644 index 0000000..a98aa00 --- /dev/null +++ b/src/service/servicemonitor.h @@ -0,0 +1,54 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include "lunaservice.h" +#include "pluginloader.h" +#include "pluginmanager.h" + +/** + * Monitors all services present in plugin loader's list and notifies + * manager about what plugins need to be loaded/unloaded. + * Also monitors system locale change. + */ +class ServiceMonitor +{ +public: + ServiceMonitor(PluginManager &manager, LunaService &service); + ~ServiceMonitor(); + + void startMonitor(const std::vector *plugins); + void stopMonitor(); + + + +private: + void localeCallback(pbnjson::JValue &previousValue, pbnjson::JValue &value); + void serviceStatusCallback(pbnjson::JValue &previousValue, + pbnjson::JValue &value); + void addPlugin(const PluginInfo &info); + void updatePlugins(const std::string &serviceName); + +private: + PluginManager &manager; + LunaService &service; + const std::vector *plugins; + + std::unordered_map serviceStatus; + bool monitorStarted; +}; diff --git a/src/service/utils.cpp b/src/service/utils.cpp new file mode 100644 index 0000000..8737778 --- /dev/null +++ b/src/service/utils.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#include +#include "utils.h" + +std::vector &splitString(const std::string &s, char delim, + std::vector &elems) +{ + std::stringstream ss(s); + std::string item; + + while (std::getline(ss, item, delim)) + { + elems.push_back(item); + } + + return elems; +} + +std::vector splitString(const std::string &s, char delim) +{ + std::vector elems; + splitString(s, delim, elems); + return elems; +} diff --git a/src/service/utils.h b/src/service/utils.h new file mode 100644 index 0000000..d51d807 --- /dev/null +++ b/src/service/utils.h @@ -0,0 +1,28 @@ +// Copyright (c) 2015-2018 LG Electronics, Inc. +// +// Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#define UNUSED_FUNC __attribute__((unused)) +#define UNUSED_VAR __attribute__((unused)) + +#include +#include +#include + +std::vector &splitString(const std::string &s, char delim, + std::vector &elems); +std::vector splitString(const std::string &s, char delim);