[PVR][Estuary] Timer settings dialog: Show client name in timer type selection dialog...
[xbmc.git] / xbmc / utils / ExecString.cpp
blob37650e046827a4d91b7367bf7e01d43fdd878880
1 /*
2 * Copyright (C) 2022 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "ExecString.h"
11 #include "FileItem.h"
12 #include "ServiceBroker.h"
13 #include "URL.h"
14 #include "music/tags/MusicInfoTag.h"
15 #include "settings/AdvancedSettings.h"
16 #include "settings/SettingsComponent.h"
17 #include "utils/StringUtils.h"
18 #include "utils/Variant.h"
19 #include "utils/log.h"
20 #include "video/VideoInfoTag.h"
22 CExecString::CExecString(const std::string& execString)
24 m_valid = Parse(execString);
27 CExecString::CExecString(const std::string& function, const std::vector<std::string>& params)
28 : m_function(function), m_params(params)
30 m_valid = !m_function.empty();
32 if (m_valid)
33 SetExecString();
36 CExecString::CExecString(const CFileItem& item, const std::string& contextWindow)
38 m_valid = Parse(item, contextWindow);
41 namespace
43 void SplitParams(const std::string& paramString, std::vector<std::string>& parameters)
45 bool inQuotes = false;
46 bool lastEscaped = false; // only every second character can be escaped
47 int inFunction = 0;
48 size_t whiteSpacePos = 0;
49 std::string parameter;
50 parameters.clear();
51 for (size_t pos = 0; pos < paramString.size(); pos++)
53 char ch = paramString[pos];
54 bool escaped = (pos > 0 && paramString[pos - 1] == '\\' && !lastEscaped);
55 lastEscaped = escaped;
56 if (inQuotes)
57 { // if we're in a quote, we accept everything until the closing quote
58 if (ch == '"' && !escaped)
59 { // finished a quote - no need to add the end quote to our string
60 inQuotes = false;
63 else
64 { // not in a quote, so check if we should be starting one
65 if (ch == '"' && !escaped)
66 { // start of quote - no need to add the quote to our string
67 inQuotes = true;
69 if (inFunction && ch == ')')
70 { // end of a function
71 inFunction--;
73 if (ch == '(')
74 { // start of function
75 inFunction++;
77 if (!inFunction && ch == ',')
78 { // not in a function, so a comma signifies the end of this parameter
79 if (whiteSpacePos)
80 parameter = parameter.substr(0, whiteSpacePos);
81 // trim off start and end quotes
82 if (parameter.length() > 1 && parameter[0] == '"' &&
83 parameter[parameter.length() - 1] == '"')
84 parameter = parameter.substr(1, parameter.length() - 2);
85 else if (parameter.length() > 3 && parameter[parameter.length() - 1] == '"')
87 // check name="value" style param.
88 size_t quotaPos = parameter.find('"');
89 if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=')
91 parameter.erase(parameter.length() - 1);
92 parameter.erase(quotaPos);
95 parameters.push_back(parameter);
96 parameter.clear();
97 whiteSpacePos = 0;
98 continue;
101 if ((ch == '"' || ch == '\\') && escaped)
102 { // escaped quote or backslash
103 parameter[parameter.size() - 1] = ch;
104 continue;
106 // whitespace handling - we skip any whitespace at the left or right of an unquoted parameter
107 if (ch == ' ' && !inQuotes)
109 if (parameter.empty()) // skip whitespace on left
110 continue;
111 if (!whiteSpacePos) // make a note of where whitespace starts on the right
112 whiteSpacePos = parameter.size();
114 else
115 whiteSpacePos = 0;
116 parameter += ch;
118 if (inFunction || inQuotes)
119 CLog::Log(LOGWARNING, "{}({}) - end of string while searching for ) or \"", __FUNCTION__,
120 paramString);
121 if (whiteSpacePos)
122 parameter.erase(whiteSpacePos);
123 // trim off start and end quotes
124 if (parameter.size() > 1 && parameter[0] == '"' && parameter[parameter.size() - 1] == '"')
125 parameter = parameter.substr(1, parameter.size() - 2);
126 else if (parameter.size() > 3 && parameter[parameter.size() - 1] == '"')
128 // check name="value" style param.
129 size_t quotaPos = parameter.find('"');
130 if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=')
132 parameter.erase(parameter.length() - 1);
133 parameter.erase(quotaPos);
136 if (!parameter.empty() || parameters.size())
137 parameters.push_back(parameter);
140 void SplitExecFunction(const std::string& execString,
141 std::string& function,
142 std::vector<std::string>& parameters)
144 std::string paramString;
146 size_t iPos = execString.find('(');
147 size_t iPos2 = execString.rfind(')');
148 if (iPos != std::string::npos && iPos2 != std::string::npos)
150 paramString = execString.substr(iPos + 1, iPos2 - iPos - 1);
151 function = execString.substr(0, iPos);
153 else
154 function = execString;
156 // remove any whitespace, and the standard prefix (if it exists)
157 StringUtils::Trim(function);
159 SplitParams(paramString, parameters);
161 } // namespace
163 bool CExecString::Parse(const std::string& execString)
165 m_execString = execString;
166 SplitExecFunction(m_execString, m_function, m_params);
168 // Keep original function case in execstring, lowercase it in function
169 StringUtils::ToLower(m_function);
170 return true;
173 bool CExecString::Parse(const CFileItem& item, const std::string& contextWindow)
175 if (item.IsFavourite())
177 const CURL url(item.GetPath());
178 Parse(CURL::Decode(url.GetHostName()));
180 else if (item.m_bIsFolder &&
181 (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders ||
182 !(item.IsSmartPlayList() || item.IsPlayList())))
184 if (!contextWindow.empty())
185 Build("ActivateWindow", {contextWindow, StringUtils::Paramify(item.GetPath()), "return"});
187 else if (item.IsScript() && item.GetPath().size() > 9) // script://<foo>
188 Build("RunScript", {StringUtils::Paramify(item.GetPath().substr(9))});
189 else if (item.IsAddonsPath() && item.GetPath().size() > 9) // addons://<foo>
191 const CURL url(item.GetPath());
192 if (url.GetHostName() == "install")
193 Build("InstallFromZip", {});
194 else if (url.GetHostName() == "check_for_updates")
195 Build("UpdateAddonRepos", {"showProgress"});
196 else
197 Build("RunAddon", {StringUtils::Paramify(url.GetFileName())});
199 else if (item.IsAndroidApp() && item.GetPath().size() > 26) // androidapp://sources/apps/<foo>
200 Build("StartAndroidActivity", {StringUtils::Paramify(item.GetPath().substr(26))});
201 else // assume a media file
203 if (item.IsVideoDb() && item.HasVideoInfoTag())
204 BuildPlayMedia(item, StringUtils::Paramify(item.GetVideoInfoTag()->m_strFileNameAndPath));
205 else if (item.IsMusicDb() && item.HasMusicInfoTag())
206 BuildPlayMedia(item, StringUtils::Paramify(item.GetMusicInfoTag()->GetURL()));
207 else if (item.IsPicture())
208 Build("ShowPicture", {StringUtils::Paramify(item.GetPath())});
209 else
211 // Everything else will be treated as PlayMedia for item's path
212 BuildPlayMedia(item, StringUtils::Paramify(item.GetPath()));
215 return true;
218 void CExecString::Build(const std::string& function, const std::vector<std::string>& params)
220 m_function = function;
221 m_params = params;
222 SetExecString();
225 void CExecString::BuildPlayMedia(const CFileItem& item, const std::string& target)
227 std::vector<std::string> params{target};
229 if (item.HasProperty("playlist_type_hint"))
230 params.emplace_back("playlist_type_hint=" + item.GetProperty("playlist_type_hint").asString());
232 Build("PlayMedia", params);
235 void CExecString::SetExecString()
237 if (m_params.empty())
238 m_execString = m_function;
239 else
240 m_execString = StringUtils::Format("{}({})", m_function, StringUtils::Join(m_params, ","));
242 // Keep original function case in execstring, lowercase it in function
243 StringUtils::ToLower(m_function);