1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 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();
}
|