summaryrefslogtreecommitdiffstats
path: root/xbmc/interfaces/generic/ScriptRunner.cpp
blob: b88dbac34b62263ff1c1c2b798414cf20cde5ddd (plain)
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();
}