summaryrefslogtreecommitdiffstats
path: root/xbmc/interfaces/generic
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/interfaces/generic')
-rw-r--r--xbmc/interfaces/generic/CMakeLists.txt15
-rw-r--r--xbmc/interfaces/generic/ILanguageInvocationHandler.h31
-rw-r--r--xbmc/interfaces/generic/ILanguageInvoker.cpp96
-rw-r--r--xbmc/interfaces/generic/ILanguageInvoker.h77
-rw-r--r--xbmc/interfaces/generic/LanguageInvokerThread.cpp133
-rw-r--r--xbmc/interfaces/generic/LanguageInvokerThread.h56
-rw-r--r--xbmc/interfaces/generic/RunningScriptObserver.cpp42
-rw-r--r--xbmc/interfaces/generic/RunningScriptObserver.h34
-rw-r--r--xbmc/interfaces/generic/RunningScriptsHandler.h104
-rw-r--r--xbmc/interfaces/generic/ScriptInvocationManager.cpp423
-rw-r--r--xbmc/interfaces/generic/ScriptInvocationManager.h156
-rw-r--r--xbmc/interfaces/generic/ScriptRunner.cpp177
-rw-r--r--xbmc/interfaces/generic/ScriptRunner.h49
13 files changed, 1393 insertions, 0 deletions
diff --git a/xbmc/interfaces/generic/CMakeLists.txt b/xbmc/interfaces/generic/CMakeLists.txt
new file mode 100644
index 0000000..8fd742d
--- /dev/null
+++ b/xbmc/interfaces/generic/CMakeLists.txt
@@ -0,0 +1,15 @@
+set(SOURCES ILanguageInvoker.cpp
+ LanguageInvokerThread.cpp
+ RunningScriptObserver.cpp
+ ScriptInvocationManager.cpp
+ ScriptRunner.cpp)
+
+set(HEADERS ILanguageInvocationHandler.h
+ ILanguageInvoker.h
+ LanguageInvokerThread.h
+ RunningScriptsHandler.h
+ RunningScriptObserver.h
+ ScriptInvocationManager.h
+ ScriptRunner.h)
+
+core_add_library(generic_interface)
diff --git a/xbmc/interfaces/generic/ILanguageInvocationHandler.h b/xbmc/interfaces/generic/ILanguageInvocationHandler.h
new file mode 100644
index 0000000..233e20d
--- /dev/null
+++ b/xbmc/interfaces/generic/ILanguageInvocationHandler.h
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+class ILanguageInvoker;
+
+class ILanguageInvocationHandler
+{
+public:
+ ILanguageInvocationHandler() = default;
+ virtual ~ILanguageInvocationHandler() = default;
+
+ virtual bool Initialize() { return true; }
+ virtual void Process() { }
+ virtual void PulseGlobalEvent() { }
+ virtual void Uninitialize() { }
+
+ virtual bool OnScriptInitialized(ILanguageInvoker *invoker) { return true; }
+ virtual void OnScriptStarted(ILanguageInvoker *invoker) { }
+ virtual void NotifyScriptAborting(ILanguageInvoker *invoker) { }
+ virtual void OnExecutionEnded(ILanguageInvoker* invoker) {}
+ virtual void OnScriptFinalized(ILanguageInvoker *invoker) { }
+
+ virtual ILanguageInvoker* CreateInvoker() = 0;
+};
diff --git a/xbmc/interfaces/generic/ILanguageInvoker.cpp b/xbmc/interfaces/generic/ILanguageInvoker.cpp
new file mode 100644
index 0000000..df1193e
--- /dev/null
+++ b/xbmc/interfaces/generic/ILanguageInvoker.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2015-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 "ILanguageInvoker.h"
+
+#include "interfaces/generic/ILanguageInvocationHandler.h"
+
+#include <string>
+#include <vector>
+
+ILanguageInvoker::ILanguageInvoker(ILanguageInvocationHandler *invocationHandler)
+ : m_id(-1),
+ m_state(InvokerStateUninitialized),
+ m_invocationHandler(invocationHandler)
+{ }
+
+ILanguageInvoker::~ILanguageInvoker() = default;
+
+bool ILanguageInvoker::Execute(const std::string &script, const std::vector<std::string> &arguments /* = std::vector<std::string>() */)
+{
+ if (m_invocationHandler)
+ m_invocationHandler->OnScriptStarted(this);
+
+ return execute(script, arguments);
+}
+
+bool ILanguageInvoker::Stop(bool abort /* = false */)
+{
+ return stop(abort);
+}
+
+bool ILanguageInvoker::IsActive() const
+{
+ return GetState() > InvokerStateUninitialized && GetState() < InvokerStateScriptDone;
+}
+
+bool ILanguageInvoker::IsRunning() const
+{
+ return GetState() == InvokerStateRunning;
+}
+
+bool ILanguageInvoker::IsStopping() const
+{
+ return GetState() == InvokerStateStopping;
+}
+
+void ILanguageInvoker::pulseGlobalEvent()
+{
+ if (m_invocationHandler)
+ m_invocationHandler->PulseGlobalEvent();
+}
+
+bool ILanguageInvoker::onExecutionInitialized()
+{
+ if (m_invocationHandler == NULL)
+ return false;
+
+ return m_invocationHandler->OnScriptInitialized(this);
+}
+
+void ILanguageInvoker::AbortNotification()
+{
+ if (m_invocationHandler)
+ m_invocationHandler->NotifyScriptAborting(this);
+}
+
+void ILanguageInvoker::onExecutionFailed()
+{
+ if (m_invocationHandler)
+ m_invocationHandler->OnExecutionEnded(this);
+}
+
+void ILanguageInvoker::onExecutionDone()
+{
+ if (m_invocationHandler)
+ m_invocationHandler->OnExecutionEnded(this);
+}
+
+void ILanguageInvoker::onExecutionFinalized()
+{
+ if (m_invocationHandler)
+ m_invocationHandler->OnScriptFinalized(this);
+}
+
+void ILanguageInvoker::setState(InvokerState state)
+{
+ if (state <= m_state)
+ return;
+
+ m_state = state;
+}
diff --git a/xbmc/interfaces/generic/ILanguageInvoker.h b/xbmc/interfaces/generic/ILanguageInvoker.h
new file mode 100644
index 0000000..da4001e
--- /dev/null
+++ b/xbmc/interfaces/generic/ILanguageInvoker.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "addons/IAddon.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+class CLanguageInvokerThread;
+class ILanguageInvocationHandler;
+
+typedef enum
+{
+ InvokerStateUninitialized,
+ InvokerStateInitialized,
+ InvokerStateRunning,
+ InvokerStateStopping,
+ InvokerStateScriptDone,
+ InvokerStateExecutionDone,
+ InvokerStateFailed
+} InvokerState;
+
+class ILanguageInvoker
+{
+public:
+ explicit ILanguageInvoker(ILanguageInvocationHandler *invocationHandler);
+ virtual ~ILanguageInvoker();
+
+ virtual bool Execute(const std::string &script, const std::vector<std::string> &arguments = std::vector<std::string>());
+ virtual bool Stop(bool abort = false);
+ virtual bool IsStopping() const;
+
+ void SetId(int id) { m_id = id; }
+ int GetId() const { return m_id; }
+ const ADDON::AddonPtr& GetAddon() const { return m_addon; }
+ void SetAddon(const ADDON::AddonPtr &addon) { m_addon = addon; }
+ InvokerState GetState() const { return m_state; }
+ bool IsActive() const;
+ bool IsRunning() const;
+ void Reset() { m_state = InvokerStateUninitialized; }
+
+protected:
+ friend class CLanguageInvokerThread;
+
+ /**
+ * Called to notify the script is aborting.
+ */
+ virtual void AbortNotification();
+
+ virtual bool execute(const std::string &script, const std::vector<std::string> &arguments) = 0;
+ virtual bool stop(bool abort) = 0;
+
+ virtual void pulseGlobalEvent();
+ virtual bool onExecutionInitialized();
+ virtual void onExecutionFailed();
+ virtual void onExecutionDone();
+ virtual void onExecutionFinalized();
+
+ void setState(InvokerState state);
+
+ ADDON::AddonPtr m_addon;
+
+private:
+ int m_id;
+ InvokerState m_state;
+ ILanguageInvocationHandler *m_invocationHandler;
+};
+
+typedef std::shared_ptr<ILanguageInvoker> LanguageInvokerPtr;
diff --git a/xbmc/interfaces/generic/LanguageInvokerThread.cpp b/xbmc/interfaces/generic/LanguageInvokerThread.cpp
new file mode 100644
index 0000000..67f3d14
--- /dev/null
+++ b/xbmc/interfaces/generic/LanguageInvokerThread.cpp
@@ -0,0 +1,133 @@
+/*
+ * 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 "LanguageInvokerThread.h"
+
+#include "ScriptInvocationManager.h"
+
+#include <utility>
+
+CLanguageInvokerThread::CLanguageInvokerThread(LanguageInvokerPtr invoker,
+ CScriptInvocationManager* invocationManager,
+ bool reuseable)
+ : ILanguageInvoker(NULL),
+ CThread("LanguageInvoker"),
+ m_invoker(std::move(invoker)),
+ m_invocationManager(invocationManager),
+ m_reusable(reuseable)
+{ }
+
+CLanguageInvokerThread::~CLanguageInvokerThread()
+{
+ Stop(true);
+}
+
+InvokerState CLanguageInvokerThread::GetState() const
+{
+ if (m_invoker == NULL)
+ return InvokerStateFailed;
+
+ return m_invoker->GetState();
+}
+
+void CLanguageInvokerThread::Release()
+{
+ m_bStop = true;
+ m_condition.notify_one();
+}
+
+bool CLanguageInvokerThread::execute(const std::string &script, const std::vector<std::string> &arguments)
+{
+ if (m_invoker == NULL || script.empty())
+ return false;
+
+ m_script = script;
+ m_args = arguments;
+
+ if (CThread::IsRunning())
+ {
+ std::unique_lock<std::mutex> lck(m_mutex);
+ m_restart = true;
+ m_condition.notify_one();
+ }
+ else
+ Create();
+
+ //Todo wait until running
+
+ return true;
+}
+
+bool CLanguageInvokerThread::stop(bool wait)
+{
+ if (m_invoker == NULL)
+ return false;
+
+ if (!CThread::IsRunning())
+ return false;
+
+ Release();
+
+ bool result = true;
+ if (m_invoker->GetState() < InvokerStateExecutionDone)
+ {
+ // stop the language-specific invoker
+ result = m_invoker->Stop(wait);
+ }
+ // stop the thread
+ CThread::StopThread(wait);
+
+ return result;
+}
+
+void CLanguageInvokerThread::OnStartup()
+{
+ if (m_invoker == NULL)
+ return;
+
+ m_invoker->SetId(GetId());
+ if (m_addon != NULL)
+ m_invoker->SetAddon(m_addon);
+}
+
+void CLanguageInvokerThread::Process()
+{
+ if (m_invoker == NULL)
+ return;
+
+ std::unique_lock<std::mutex> lckdl(m_mutex);
+ do
+ {
+ m_restart = false;
+ m_invoker->Execute(m_script, m_args);
+
+ if (m_invoker->GetState() != InvokerStateScriptDone)
+ m_reusable = false;
+
+ m_condition.wait(lckdl, [this] { return m_bStop || m_restart || !m_reusable; });
+
+ } while (m_reusable && !m_bStop);
+}
+
+void CLanguageInvokerThread::OnExit()
+{
+ if (m_invoker == NULL)
+ return;
+
+ m_invoker->onExecutionDone();
+ m_invocationManager->OnExecutionDone(GetId());
+}
+
+void CLanguageInvokerThread::OnException()
+{
+ if (m_invoker == NULL)
+ return;
+
+ m_invoker->onExecutionFailed();
+ m_invocationManager->OnExecutionDone(GetId());
+} \ No newline at end of file
diff --git a/xbmc/interfaces/generic/LanguageInvokerThread.h b/xbmc/interfaces/generic/LanguageInvokerThread.h
new file mode 100644
index 0000000..a1844ba
--- /dev/null
+++ b/xbmc/interfaces/generic/LanguageInvokerThread.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "interfaces/generic/ILanguageInvoker.h"
+#include "threads/Thread.h"
+
+#include <string>
+#include <vector>
+
+class CScriptInvocationManager;
+
+class CLanguageInvokerThread : public ILanguageInvoker, protected CThread
+{
+public:
+ CLanguageInvokerThread(LanguageInvokerPtr invoker,
+ CScriptInvocationManager* invocationManager,
+ bool reuseable);
+ ~CLanguageInvokerThread() override;
+
+ virtual InvokerState GetState() const;
+
+ const std::string& GetScript() const { return m_script; }
+ LanguageInvokerPtr GetInvoker() const { return m_invoker; }
+ bool Reuseable(const std::string& script) const
+ {
+ return !m_bStop && m_reusable && GetState() == InvokerStateScriptDone && m_script == script;
+ };
+ virtual void Release();
+
+protected:
+ bool execute(const std::string &script, const std::vector<std::string> &arguments) override;
+ bool stop(bool wait) override;
+
+ void OnStartup() override;
+ void Process() override;
+ void OnExit() override;
+ void OnException() override;
+
+private:
+ LanguageInvokerPtr m_invoker;
+ CScriptInvocationManager *m_invocationManager;
+ std::string m_script;
+ std::vector<std::string> m_args;
+
+ std::mutex m_mutex;
+ std::condition_variable m_condition;
+ bool m_restart = false;
+ bool m_reusable = false;
+};
diff --git a/xbmc/interfaces/generic/RunningScriptObserver.cpp b/xbmc/interfaces/generic/RunningScriptObserver.cpp
new file mode 100644
index 0000000..c89c9f7
--- /dev/null
+++ b/xbmc/interfaces/generic/RunningScriptObserver.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017-2021 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 "RunningScriptObserver.h"
+
+#include "interfaces/generic/ScriptInvocationManager.h"
+
+using namespace std::chrono_literals;
+
+CRunningScriptObserver::CRunningScriptObserver(int scriptId, CEvent& evt)
+ : m_scriptId(scriptId), m_event(evt), m_stopEvent(true), m_thread(this, "ScriptObs")
+{
+ m_thread.Create();
+}
+
+CRunningScriptObserver::~CRunningScriptObserver()
+{
+ Abort();
+}
+
+void CRunningScriptObserver::Run()
+{
+ do
+ {
+ if (!CScriptInvocationManager::GetInstance().IsRunning(m_scriptId))
+ {
+ m_event.Set();
+ break;
+ }
+ } while (!m_stopEvent.Wait(20ms));
+}
+
+void CRunningScriptObserver::Abort()
+{
+ m_stopEvent.Set();
+ m_thread.StopThread();
+}
diff --git a/xbmc/interfaces/generic/RunningScriptObserver.h b/xbmc/interfaces/generic/RunningScriptObserver.h
new file mode 100644
index 0000000..2622411
--- /dev/null
+++ b/xbmc/interfaces/generic/RunningScriptObserver.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017-2021 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.
+ */
+
+#pragma once
+
+#include "threads/Event.h"
+#include "threads/IRunnable.h"
+#include "threads/Thread.h"
+
+#include <atomic>
+
+class CRunningScriptObserver : public IRunnable
+{
+public:
+ CRunningScriptObserver(int scriptId, CEvent& evt);
+ ~CRunningScriptObserver();
+
+ void Abort();
+
+protected:
+ // implementation of IRunnable
+ void Run() override;
+
+ int m_scriptId;
+ CEvent& m_event;
+
+ CEvent m_stopEvent;
+ CThread m_thread;
+};
diff --git a/xbmc/interfaces/generic/RunningScriptsHandler.h b/xbmc/interfaces/generic/RunningScriptsHandler.h
new file mode 100644
index 0000000..59f18bb
--- /dev/null
+++ b/xbmc/interfaces/generic/RunningScriptsHandler.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017-2021 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.
+ */
+
+#pragma once
+
+#include "interfaces/generic/ScriptInvocationManager.h"
+#include "interfaces/generic/ScriptRunner.h"
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+
+#include <cstdint>
+#include <map>
+#include <mutex>
+
+template<class TScript>
+class CRunningScriptsHandler : protected CScriptRunner
+{
+protected:
+ using HandleType = int;
+
+ CRunningScriptsHandler() = default;
+ virtual ~CRunningScriptsHandler() = default;
+
+ using CScriptRunner::ExecuteScript;
+ using CScriptRunner::GetAddon;
+ using CScriptRunner::SetDone;
+ using CScriptRunner::StartScript;
+
+ bool RunScript(TScript* script,
+ const ADDON::AddonPtr& addon,
+ const std::string& path,
+ bool resume)
+ {
+ if (script == nullptr || addon == nullptr || path.empty())
+ return false;
+
+ // reuse an existing script handle or get a new one if necessary
+ int handle = CScriptInvocationManager::GetInstance().GetReusablePluginHandle(addon->LibPath());
+ if (handle < 0)
+ handle = GetNewScriptHandle(script);
+ else
+ ReuseScriptHandle(handle, script);
+
+ // run the script
+ auto result = CScriptRunner::RunScript(addon, path, handle, resume);
+
+ // remove the script handle if necessary
+ RemoveScriptHandle(handle);
+
+ return result;
+ }
+
+ static HandleType GetNewScriptHandle(TScript* script)
+ {
+ std::unique_lock<CCriticalSection> lock(s_critical);
+ uint32_t handle = ++s_scriptHandleCounter;
+ s_scriptHandles[handle] = script;
+
+ return handle;
+ }
+
+ static void ReuseScriptHandle(HandleType handle, TScript* script)
+ {
+ std::unique_lock<CCriticalSection> lock(s_critical);
+ s_scriptHandles[handle] = script;
+ }
+
+ static void RemoveScriptHandle(HandleType handle)
+ {
+ std::unique_lock<CCriticalSection> lock(s_critical);
+ s_scriptHandles.erase(handle);
+ }
+
+ static TScript* GetScriptFromHandle(HandleType handle)
+ {
+ std::unique_lock<CCriticalSection> lock(s_critical);
+ auto scriptHandle = s_scriptHandles.find(handle);
+ if (scriptHandle == s_scriptHandles.end())
+ return nullptr;
+
+ return scriptHandle->second;
+ }
+
+ static inline CCriticalSection& GetScriptsLock() { return s_critical; }
+
+private:
+ static std::map<HandleType, TScript*> s_scriptHandles;
+ static CCriticalSection s_critical;
+ static HandleType s_scriptHandleCounter;
+};
+
+template<class TScript>
+std::map<typename CRunningScriptsHandler<TScript>::HandleType, TScript*>
+ CRunningScriptsHandler<TScript>::s_scriptHandles;
+template<class TScript>
+CCriticalSection CRunningScriptsHandler<TScript>::s_critical;
+template<class TScript>
+typename CRunningScriptsHandler<TScript>::HandleType
+ CRunningScriptsHandler<TScript>::s_scriptHandleCounter = 0;
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;
+}
diff --git a/xbmc/interfaces/generic/ScriptInvocationManager.h b/xbmc/interfaces/generic/ScriptInvocationManager.h
new file mode 100644
index 0000000..6e409b8
--- /dev/null
+++ b/xbmc/interfaces/generic/ScriptInvocationManager.h
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "addons/IAddon.h"
+#include "interfaces/generic/ILanguageInvoker.h"
+#include "threads/CriticalSection.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <vector>
+
+class CLanguageInvokerThread;
+typedef std::shared_ptr<CLanguageInvokerThread> CLanguageInvokerThreadPtr;
+
+class CScriptInvocationManager
+{
+public:
+ static CScriptInvocationManager& GetInstance();
+
+ void Process();
+ void Uninitialize();
+
+ void RegisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler, const std::string &extension);
+ void RegisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler, const std::set<std::string> &extensions);
+ void UnregisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler);
+ bool HasLanguageInvoker(const std::string &script) const;
+ LanguageInvokerPtr GetLanguageInvoker(const std::string& script);
+
+ /*!
+ * \brief Returns addon_handle if last reusable invoker is ready to use.
+ */
+ int GetReusablePluginHandle(const std::string& script);
+
+ /*!
+ * \brief Executes the given script asynchronously in a separate thread.
+ *
+ * \param script Path to the script to be executed
+ * \param addon (Optional) Addon to which the script belongs
+ * \param arguments (Optional) List of arguments passed to the script
+ * \return -1 if an error occurred, otherwise the ID of the script
+ */
+ int 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);
+ /*!
+ * \brief Executes the given script asynchronously in a separate thread.
+ *
+ * \param script Path to the script to be executed
+ * \param languageInvoker Language invoker to be used to execute the script
+ * \param addon (Optional) Addon to which the script belongs
+ * \param arguments (Optional) List of arguments passed to the script
+ * \return -1 if an error occurred, otherwise the ID of the script
+ */
+ int 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);
+
+ /*!
+ * \brief Executes the given script synchronously.
+ *
+ * \details The script is actually executed asynchronously but the calling
+ * thread is blocked until either the script has finished or the given timeout
+ * has expired. If the given timeout has expired the script's execution is
+ * stopped and depending on the specified wait behaviour we wait for the
+ * script's execution to finish or not.
+ *
+ * \param script Path to the script to be executed
+ * \param addon (Optional) Addon to which the script belongs
+ * \param arguments (Optional) List of arguments passed to the script
+ * \param timeout (Optional) Timeout (in milliseconds) for the script's execution
+ * \param waitShutdown (Optional) Whether to wait when having to forcefully stop the script's execution or not.
+ * \return -1 if an error occurred, 0 if the script terminated or ETIMEDOUT if the given timeout expired
+ */
+ int 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);
+ /*!
+ * \brief Executes the given script synchronously.
+ *
+ * \details The script is actually executed asynchronously but the calling
+ * thread is blocked until either the script has finished or the given timeout
+ * has expired. If the given timeout has expired the script's execution is
+ * stopped and depending on the specified wait behaviour we wait for the
+ * script's execution to finish or not.
+ *
+ * \param script Path to the script to be executed
+ * \param languageInvoker Language invoker to be used to execute the script
+ * \param addon (Optional) Addon to which the script belongs
+ * \param arguments (Optional) List of arguments passed to the script
+ * \param timeout (Optional) Timeout (in milliseconds) for the script's execution
+ * \param waitShutdown (Optional) Whether to wait when having to forcefully stop the script's execution or not.
+ * \return -1 if an error occurred, 0 if the script terminated or ETIMEDOUT if the given timeout expired
+ */
+ int 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);
+ bool Stop(int scriptId, bool wait = false);
+ bool Stop(const std::string &scriptPath, bool wait = false);
+
+ /*!
+ *\brief Stop all running scripts
+ *\param wait if kodi should wait for each script to finish (default false)
+ */
+ void StopRunningScripts(bool wait = false);
+
+ bool IsRunning(int scriptId) const;
+ bool IsRunning(const std::string& scriptPath) const;
+
+protected:
+ friend class CLanguageInvokerThread;
+
+ void OnExecutionDone(int scriptId);
+
+private:
+ CScriptInvocationManager() = default;
+ CScriptInvocationManager(const CScriptInvocationManager&) = delete;
+ CScriptInvocationManager const& operator=(CScriptInvocationManager const&) = delete;
+ virtual ~CScriptInvocationManager();
+
+ typedef struct {
+ CLanguageInvokerThreadPtr thread;
+ std::string script;
+ bool done;
+ } LanguageInvokerThread;
+ typedef std::map<int, LanguageInvokerThread> LanguageInvokerThreadMap;
+ typedef std::map<std::string, ILanguageInvocationHandler*> LanguageInvocationHandlerMap;
+
+ LanguageInvokerThread getInvokerThread(int scriptId) const;
+
+ LanguageInvocationHandlerMap m_invocationHandlers;
+ LanguageInvokerThreadMap m_scripts;
+ CLanguageInvokerThreadPtr m_lastInvokerThread;
+ int m_lastPluginHandle = -1;
+
+ std::map<std::string, int> m_scriptPaths;
+ int m_nextId = 0;
+ mutable CCriticalSection m_critSection;
+};
diff --git a/xbmc/interfaces/generic/ScriptRunner.cpp b/xbmc/interfaces/generic/ScriptRunner.cpp
new file mode 100644
index 0000000..b88dbac
--- /dev/null
+++ b/xbmc/interfaces/generic/ScriptRunner.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017-2021 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 "ScriptRunner.h"
+
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "dialogs/GUIDialogBusy.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "interfaces/generic/RunningScriptObserver.h"
+#include "interfaces/generic/ScriptInvocationManager.h"
+#include "messaging/ApplicationMessenger.h"
+#include "threads/SystemClock.h"
+#include "utils/StringUtils.h"
+#include "utils/log.h"
+
+#include <vector>
+
+using namespace std::chrono_literals;
+
+ADDON::AddonPtr CScriptRunner::GetAddon() const
+{
+ return m_addon;
+}
+
+CScriptRunner::CScriptRunner() : m_scriptDone(true)
+{ }
+
+bool CScriptRunner::StartScript(const ADDON::AddonPtr& addon, const std::string& path)
+{
+ return RunScriptInternal(addon, path, 0, false);
+}
+
+bool CScriptRunner::RunScript(const ADDON::AddonPtr& addon,
+ const std::string& path,
+ int handle,
+ bool resume)
+{
+ return RunScriptInternal(addon, path, handle, resume, true);
+}
+
+void CScriptRunner::SetDone()
+{
+ m_scriptDone.Set();
+}
+
+int CScriptRunner::ExecuteScript(const ADDON::AddonPtr& addon, const std::string& path, bool resume)
+{
+ return ExecuteScript(addon, path, -1, resume);
+}
+
+int CScriptRunner::ExecuteScript(const ADDON::AddonPtr& addon,
+ const std::string& path,
+ int handle,
+ bool resume)
+{
+ if (addon == nullptr || path.empty())
+ return false;
+
+ CURL url(path);
+
+ // get options and remove them from the URL because we can then use the url
+ // to generate the base path which is passed to the add-on script
+ auto options = url.GetOptions();
+ url.SetOptions("");
+
+ // setup our parameters to send the script
+ std::vector<std::string> argv = {url.Get(), // base path
+ StringUtils::Format("{:d}", handle), options,
+ StringUtils::Format("resume:{}", resume)};
+
+ bool reuseLanguageInvoker = false;
+ const auto reuseLanguageInvokerIt = addon->ExtraInfo().find("reuselanguageinvoker");
+ if (reuseLanguageInvokerIt != addon->ExtraInfo().end())
+ reuseLanguageInvoker = reuseLanguageInvokerIt->second == "true";
+
+ // run the script
+ CLog::Log(LOGDEBUG, "CScriptRunner: running add-on script {:s}('{:s}', '{:s}', '{:s}')",
+ addon->Name(), argv[0], argv[1], argv[2]);
+ int scriptId = CScriptInvocationManager::GetInstance().ExecuteAsync(addon->LibPath(), addon, argv,
+ reuseLanguageInvoker, handle);
+ if (scriptId < 0)
+ CLog::Log(LOGERROR, "CScriptRunner: unable to run add-on script {:s}", addon->Name());
+
+ return scriptId;
+}
+
+bool CScriptRunner::RunScriptInternal(const ADDON::AddonPtr& addon,
+ const std::string& path,
+ int handle,
+ bool resume,
+ bool wait /* = true */)
+{
+ if (addon == nullptr || path.empty())
+ return false;
+
+ // reset our wait event
+ m_scriptDone.Reset();
+
+ // store the add-on
+ m_addon = addon;
+
+ int scriptId = ExecuteScript(addon, path, handle, resume);
+ if (scriptId < 0)
+ return false;
+
+ // we don't need to wait for the script to end
+ if (!wait)
+ return true;
+
+ // wait for our script to finish
+ return WaitOnScriptResult(scriptId, addon->LibPath(), addon->Name());
+}
+
+bool CScriptRunner::WaitOnScriptResult(int scriptId,
+ const std::string& path,
+ const std::string& name)
+{
+ bool cancelled = false;
+
+ // Add-on scripts can be called from the main and other threads. If called
+ // form the main thread, we need to bring up the BusyDialog in order to
+ // keep the render loop alive
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ if (!m_scriptDone.Wait(20ms))
+ {
+ // observe the script until it's finished while showing the busy dialog
+ CRunningScriptObserver scriptObs(scriptId, m_scriptDone);
+
+ auto& wm = CServiceBroker::GetGUI()->GetWindowManager();
+ if (wm.IsModalDialogTopmost(WINDOW_DIALOG_PROGRESS))
+ {
+ auto progress = wm.GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+ if (!progress->WaitOnEvent(m_scriptDone))
+ cancelled = true;
+ }
+ else if (!CGUIDialogBusy::WaitOnEvent(m_scriptDone, 200))
+ cancelled = true;
+
+ scriptObs.Abort();
+ }
+ }
+ else
+ {
+ // wait for the script to finish or be cancelled
+ while (!IsCancelled() && CScriptInvocationManager::GetInstance().IsRunning(scriptId) &&
+ !m_scriptDone.Wait(20ms))
+ ;
+
+ // give the script 30 seconds to exit before we attempt to stop it
+ XbmcThreads::EndTime<> timer(30s);
+ while (!timer.IsTimePast() && CScriptInvocationManager::GetInstance().IsRunning(scriptId) &&
+ !m_scriptDone.Wait(20ms))
+ ;
+ }
+
+ if (cancelled || IsCancelled())
+ {
+ // cancel the script
+ if (scriptId != -1 && CScriptInvocationManager::GetInstance().IsRunning(scriptId))
+ {
+ CLog::Log(LOGDEBUG, "CScriptRunner: cancelling add-on script {:s} (id = {:d})", name,
+ scriptId);
+ CScriptInvocationManager::GetInstance().Stop(scriptId);
+ }
+ }
+
+ return !cancelled && !IsCancelled() && IsSuccessful();
+}
diff --git a/xbmc/interfaces/generic/ScriptRunner.h b/xbmc/interfaces/generic/ScriptRunner.h
new file mode 100644
index 0000000..77d511d
--- /dev/null
+++ b/xbmc/interfaces/generic/ScriptRunner.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017-2021 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.
+ */
+
+#pragma once
+
+#include "addons/IAddon.h"
+#include "threads/Event.h"
+
+#include <string>
+
+class CScriptRunner
+{
+protected:
+ CScriptRunner();
+ virtual ~CScriptRunner() = default;
+
+ virtual bool IsSuccessful() const = 0;
+ virtual bool IsCancelled() const = 0;
+
+ ADDON::AddonPtr GetAddon() const;
+
+ bool StartScript(const ADDON::AddonPtr& addon, const std::string& path);
+ bool RunScript(const ADDON::AddonPtr& addon, const std::string& path, int handle, bool resume);
+
+ void SetDone();
+
+ static int ExecuteScript(const ADDON::AddonPtr& addon, const std::string& path, bool resume);
+ static int ExecuteScript(const ADDON::AddonPtr& addon,
+ const std::string& path,
+ int handle,
+ bool resume);
+
+private:
+ bool RunScriptInternal(const ADDON::AddonPtr& addon,
+ const std::string& path,
+ int handle,
+ bool resume,
+ bool wait = true);
+ bool WaitOnScriptResult(int scriptId, const std::string& path, const std::string& name);
+
+ ADDON::AddonPtr m_addon;
+
+ CEvent m_scriptDone;
+};