summaryrefslogtreecommitdiffstats
path: root/xbmc/interfaces/generic/ScriptInvocationManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/interfaces/generic/ScriptInvocationManager.cpp')
-rw-r--r--xbmc/interfaces/generic/ScriptInvocationManager.cpp423
1 files changed, 423 insertions, 0 deletions
diff --git a/xbmc/interfaces/generic/ScriptInvocationManager.cpp b/xbmc/interfaces/generic/ScriptInvocationManager.cpp
new file mode 100644
index 0000000..9daae0f
--- /dev/null
+++ b/xbmc/interfaces/generic/ScriptInvocationManager.cpp
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "ScriptInvocationManager.h"
+
+#include "interfaces/generic/ILanguageInvocationHandler.h"
+#include "interfaces/generic/ILanguageInvoker.h"
+#include "interfaces/generic/LanguageInvokerThread.h"
+#include "utils/FileUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <cerrno>
+#include <mutex>
+#include <utility>
+#include <vector>
+
+CScriptInvocationManager::~CScriptInvocationManager()
+{
+ Uninitialize();
+}
+
+CScriptInvocationManager& CScriptInvocationManager::GetInstance()
+{
+ static CScriptInvocationManager s_instance;
+ return s_instance;
+}
+
+void CScriptInvocationManager::Process()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ // go through all active threads and find and remove all which are done
+ std::vector<LanguageInvokerThread> tempList;
+ for (LanguageInvokerThreadMap::iterator it = m_scripts.begin(); it != m_scripts.end(); )
+ {
+ if (it->second.done)
+ {
+ tempList.push_back(it->second);
+ m_scripts.erase(it++);
+ }
+ else
+ ++it;
+ }
+
+ // remove the finished scripts from the script path map as well
+ for (const auto& it : tempList)
+ m_scriptPaths.erase(it.script);
+
+ // we can leave the lock now
+ lock.unlock();
+
+ // finally remove the finished threads but we do it outside of any locks in
+ // case of any callbacks from the destruction of the CLanguageInvokerThread
+ tempList.clear();
+
+ // let the invocation handlers do their processing
+ for (auto& it : m_invocationHandlers)
+ it.second->Process();
+}
+
+void CScriptInvocationManager::Uninitialize()
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ // execute Process() once more to handle the remaining scripts
+ Process();
+
+ // it is safe to release early, thread must be in m_scripts too
+ m_lastInvokerThread = nullptr;
+
+ // make sure all scripts are done
+ std::vector<LanguageInvokerThread> tempList;
+ for (const auto& script : m_scripts)
+ tempList.push_back(script.second);
+
+ m_scripts.clear();
+ m_scriptPaths.clear();
+
+ // we can leave the lock now
+ lock.unlock();
+
+ // finally stop and remove the finished threads but we do it outside of any
+ // locks in case of any callbacks from the stop or destruction logic of
+ // CLanguageInvokerThread or the ILanguageInvoker implementation
+ for (auto& it : tempList)
+ {
+ if (!it.done)
+ it.thread->Stop(true);
+ }
+
+ lock.lock();
+
+ tempList.clear();
+
+ // uninitialize all invocation handlers and then remove them
+ for (auto& it : m_invocationHandlers)
+ it.second->Uninitialize();
+
+ m_invocationHandlers.clear();
+}
+
+void CScriptInvocationManager::RegisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler, const std::string &extension)
+{
+ if (invocationHandler == NULL || extension.empty())
+ return;
+
+ std::string ext = extension;
+ StringUtils::ToLower(ext);
+ if (!StringUtils::StartsWithNoCase(ext, "."))
+ ext = "." + ext;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ if (m_invocationHandlers.find(ext) != m_invocationHandlers.end())
+ return;
+
+ m_invocationHandlers.insert(std::make_pair(extension, invocationHandler));
+
+ bool known = false;
+ for (const auto& it : m_invocationHandlers)
+ {
+ if (it.second == invocationHandler)
+ {
+ known = true;
+ break;
+ }
+ }
+
+ // automatically initialize the invocation handler if it's a new one
+ if (!known)
+ invocationHandler->Initialize();
+}
+
+void CScriptInvocationManager::RegisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler, const std::set<std::string> &extensions)
+{
+ if (invocationHandler == NULL || extensions.empty())
+ return;
+
+ for (const auto& extension : extensions)
+ RegisterLanguageInvocationHandler(invocationHandler, extension);
+}
+
+void CScriptInvocationManager::UnregisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler)
+{
+ if (invocationHandler == NULL)
+ return;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ // get all extensions of the given language invoker
+ for (std::map<std::string, ILanguageInvocationHandler*>::iterator it = m_invocationHandlers.begin(); it != m_invocationHandlers.end(); )
+ {
+ if (it->second == invocationHandler)
+ m_invocationHandlers.erase(it++);
+ else
+ ++it;
+ }
+
+ // automatically uninitialize the invocation handler
+ invocationHandler->Uninitialize();
+}
+
+bool CScriptInvocationManager::HasLanguageInvoker(const std::string &script) const
+{
+ std::string extension = URIUtils::GetExtension(script);
+ StringUtils::ToLower(extension);
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ std::map<std::string, ILanguageInvocationHandler*>::const_iterator it = m_invocationHandlers.find(extension);
+ return it != m_invocationHandlers.end() && it->second != NULL;
+}
+
+int CScriptInvocationManager::GetReusablePluginHandle(const std::string& script)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_lastInvokerThread)
+ {
+ if (m_lastInvokerThread->Reuseable(script))
+ return m_lastPluginHandle;
+ m_lastInvokerThread->Release();
+ m_lastInvokerThread = nullptr;
+ }
+ return -1;
+}
+
+LanguageInvokerPtr CScriptInvocationManager::GetLanguageInvoker(const std::string& script)
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_lastInvokerThread)
+ {
+ if (m_lastInvokerThread->Reuseable(script))
+ {
+ CLog::Log(LOGDEBUG, "{} - Reusing LanguageInvokerThread {} for script {}", __FUNCTION__,
+ m_lastInvokerThread->GetId(), script);
+ m_lastInvokerThread->GetInvoker()->Reset();
+ return m_lastInvokerThread->GetInvoker();
+ }
+ m_lastInvokerThread->Release();
+ m_lastInvokerThread = nullptr;
+ }
+
+ std::string extension = URIUtils::GetExtension(script);
+ StringUtils::ToLower(extension);
+
+ std::map<std::string, ILanguageInvocationHandler*>::const_iterator it = m_invocationHandlers.find(extension);
+ if (it != m_invocationHandlers.end() && it->second != NULL)
+ return LanguageInvokerPtr(it->second->CreateInvoker());
+
+ return LanguageInvokerPtr();
+}
+
+int CScriptInvocationManager::ExecuteAsync(
+ const std::string& script,
+ const ADDON::AddonPtr& addon /* = ADDON::AddonPtr() */,
+ const std::vector<std::string>& arguments /* = std::vector<std::string>() */,
+ bool reuseable /* = false */,
+ int pluginHandle /* = -1 */)
+{
+ if (script.empty())
+ return -1;
+
+ if (!CFileUtils::Exists(script, false))
+ {
+ CLog::Log(LOGERROR, "{} - Not executing non-existing script {}", __FUNCTION__, script);
+ return -1;
+ }
+
+ LanguageInvokerPtr invoker = GetLanguageInvoker(script);
+ return ExecuteAsync(script, invoker, addon, arguments, reuseable, pluginHandle);
+}
+
+int CScriptInvocationManager::ExecuteAsync(
+ const std::string& script,
+ const LanguageInvokerPtr& languageInvoker,
+ const ADDON::AddonPtr& addon /* = ADDON::AddonPtr() */,
+ const std::vector<std::string>& arguments /* = std::vector<std::string>() */,
+ bool reuseable /* = false */,
+ int pluginHandle /* = -1 */)
+{
+ if (script.empty() || languageInvoker == NULL)
+ return -1;
+
+ if (!CFileUtils::Exists(script, false))
+ {
+ CLog::Log(LOGERROR, "{} - Not executing non-existing script {}", __FUNCTION__, script);
+ return -1;
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+
+ if (m_lastInvokerThread && m_lastInvokerThread->GetInvoker() == languageInvoker)
+ {
+ if (addon != NULL)
+ m_lastInvokerThread->SetAddon(addon);
+
+ // After we leave the lock, m_lastInvokerThread can be released -> copy!
+ CLanguageInvokerThreadPtr invokerThread = m_lastInvokerThread;
+ lock.unlock();
+ invokerThread->Execute(script, arguments);
+
+ return invokerThread->GetId();
+ }
+
+ m_lastInvokerThread =
+ CLanguageInvokerThreadPtr(new CLanguageInvokerThread(languageInvoker, this, reuseable));
+ if (m_lastInvokerThread == NULL)
+ return -1;
+
+ if (addon != NULL)
+ m_lastInvokerThread->SetAddon(addon);
+
+ m_lastInvokerThread->SetId(m_nextId++);
+ m_lastPluginHandle = pluginHandle;
+
+ LanguageInvokerThread thread = {m_lastInvokerThread, script, false};
+ m_scripts.insert(std::make_pair(m_lastInvokerThread->GetId(), thread));
+ m_scriptPaths.insert(std::make_pair(script, m_lastInvokerThread->GetId()));
+ // After we leave the lock, m_lastInvokerThread can be released -> copy!
+ CLanguageInvokerThreadPtr invokerThread = m_lastInvokerThread;
+ lock.unlock();
+ invokerThread->Execute(script, arguments);
+
+ return invokerThread->GetId();
+}
+
+int CScriptInvocationManager::ExecuteSync(
+ const std::string& script,
+ const ADDON::AddonPtr& addon /* = ADDON::AddonPtr() */,
+ const std::vector<std::string>& arguments /* = std::vector<std::string>() */,
+ uint32_t timeoutMs /* = 0 */,
+ bool waitShutdown /* = false */)
+{
+ if (script.empty())
+ return -1;
+
+ if (!CFileUtils::Exists(script, false))
+ {
+ CLog::Log(LOGERROR, "{} - Not executing non-existing script {}", __FUNCTION__, script);
+ return -1;
+ }
+
+ LanguageInvokerPtr invoker = GetLanguageInvoker(script);
+ return ExecuteSync(script, invoker, addon, arguments, timeoutMs, waitShutdown);
+}
+
+int CScriptInvocationManager::ExecuteSync(
+ const std::string& script,
+ const LanguageInvokerPtr& languageInvoker,
+ const ADDON::AddonPtr& addon /* = ADDON::AddonPtr() */,
+ const std::vector<std::string>& arguments /* = std::vector<std::string>() */,
+ uint32_t timeoutMs /* = 0 */,
+ bool waitShutdown /* = false */)
+{
+ int scriptId = ExecuteAsync(script, languageInvoker, addon, arguments);
+ if (scriptId < 0)
+ return -1;
+
+ bool timeout = timeoutMs > 0;
+ while ((!timeout || timeoutMs > 0) && IsRunning(scriptId))
+ {
+ unsigned int sleepMs = 100U;
+ if (timeout && timeoutMs < sleepMs)
+ sleepMs = timeoutMs;
+
+ KODI::TIME::Sleep(std::chrono::milliseconds(sleepMs));
+
+ if (timeout)
+ timeoutMs -= sleepMs;
+ }
+
+ if (IsRunning(scriptId))
+ {
+ Stop(scriptId, waitShutdown);
+ return ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+bool CScriptInvocationManager::Stop(int scriptId, bool wait /* = false */)
+{
+ if (scriptId < 0)
+ return false;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ CLanguageInvokerThreadPtr invokerThread = getInvokerThread(scriptId).thread;
+ if (invokerThread == NULL)
+ return false;
+
+ return invokerThread->Stop(wait);
+}
+
+void CScriptInvocationManager::StopRunningScripts(bool wait /* = false */)
+{
+ for (auto& it : m_scripts)
+ {
+ if (!it.second.done)
+ Stop(it.second.script, wait);
+ }
+}
+
+bool CScriptInvocationManager::Stop(const std::string &scriptPath, bool wait /* = false */)
+{
+ if (scriptPath.empty())
+ return false;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ std::map<std::string, int>::const_iterator script = m_scriptPaths.find(scriptPath);
+ if (script == m_scriptPaths.end())
+ return false;
+
+ return Stop(script->second, wait);
+}
+
+bool CScriptInvocationManager::IsRunning(int scriptId) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ LanguageInvokerThread invokerThread = getInvokerThread(scriptId);
+ if (invokerThread.thread == NULL)
+ return false;
+
+ return !invokerThread.done;
+}
+
+bool CScriptInvocationManager::IsRunning(const std::string& scriptPath) const
+{
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ auto it = m_scriptPaths.find(scriptPath);
+ if (it == m_scriptPaths.end())
+ return false;
+
+ return IsRunning(it->second);
+}
+
+void CScriptInvocationManager::OnExecutionDone(int scriptId)
+{
+ if (scriptId < 0)
+ return;
+
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ LanguageInvokerThreadMap::iterator script = m_scripts.find(scriptId);
+ if (script != m_scripts.end())
+ script->second.done = true;
+}
+
+CScriptInvocationManager::LanguageInvokerThread CScriptInvocationManager::getInvokerThread(int scriptId) const
+{
+ if (scriptId < 0)
+ return LanguageInvokerThread();
+
+ LanguageInvokerThreadMap::const_iterator script = m_scripts.find(scriptId);
+ if (script == m_scripts.end())
+ return LanguageInvokerThread();
+
+ return script->second;
+}