summaryrefslogtreecommitdiffstats
path: root/xbmc/interfaces/generic/ScriptRunner.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/interfaces/generic/ScriptRunner.cpp')
-rw-r--r--xbmc/interfaces/generic/ScriptRunner.cpp177
1 files changed, 177 insertions, 0 deletions
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();
+}