1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "threads/SystemClock.h"
10 #include "CompileInfo.h"
11 #include "ExternalPlayer.h"
12 #include "windowing/WinSystem.h"
13 #include "dialogs/GUIDialogOK.h"
14 #include "guilib/GUIComponent.h"
15 #include "guilib/GUIWindowManager.h"
16 #include "Application.h"
17 #include "filesystem/MusicDatabaseFile.h"
18 #include "FileItem.h"
19 #include "utils/RegExp.h"
20 #include "utils/StringUtils.h"
21 #include "utils/URIUtils.h"
22 #include "URL.h"
23 #include "utils/XMLUtils.h"
24 #include "utils/log.h"
25 #include "utils/Variant.h"
26 #include "video/Bookmark.h"
27 #include "ServiceBroker.h"
28 #include "cores/AudioEngine/Interfaces/AE.h"
29 #include "cores/DataCacheCore.h"
30 #if defined(TARGET_WINDOWS)
31 #include "utils/CharsetConverter.h"
32 #include <Windows.h>
33 #endif
34 #if defined(TARGET_ANDROID)
35 #include "platform/android/activity/XBMCApp.h"
36 #endif
37
38 // If the process ends in less than this time (ms), we assume it's a launcher
39 // and wait for manual intervention before continuing
40 #define LAUNCHER_PROCESS_TIME 2000
41 // Time (ms) we give a process we sent a WM_QUIT to close before terminating
42 #define PROCESS_GRACE_TIME 3000
43 // Default time after which the item's playcount is incremented
44 #define DEFAULT_PLAYCOUNT_MIN_TIME 10
45
46 using namespace XFILE;
47
48 #if defined(TARGET_WINDOWS_DESKTOP)
49 extern HWND g_hWnd;
50 #endif
51
CExternalPlayer(IPlayerCallback & callback)52 CExternalPlayer::CExternalPlayer(IPlayerCallback& callback)
53 : IPlayer(callback),
54 CThread("ExternalPlayer")
55 {
56 m_bAbortRequest = false;
57 m_bIsPlaying = false;
58 m_playbackStartTime = 0;
59 m_speed = 1;
60 m_time = 0;
61
62 m_hideconsole = false;
63 m_warpcursor = WARP_NONE;
64 m_hidexbmc = false;
65 m_islauncher = false;
66 m_playCountMinTime = DEFAULT_PLAYCOUNT_MIN_TIME;
67 m_playOneStackItem = false;
68
69 m_dialog = NULL;
70 #if defined(TARGET_WINDOWS_DESKTOP)
71 m_xPos = 0;
72 m_yPos = 0;
73
74 memset(&m_processInfo, 0, sizeof(m_processInfo));
75 #endif
76 }
77
~CExternalPlayer()78 CExternalPlayer::~CExternalPlayer()
79 {
80 CloseFile();
81 }
82
OpenFile(const CFileItem & file,const CPlayerOptions & options)83 bool CExternalPlayer::OpenFile(const CFileItem& file, const CPlayerOptions &options)
84 {
85 try
86 {
87 m_file = file;
88 m_bIsPlaying = true;
89 m_time = 0;
90 m_playbackStartTime = XbmcThreads::SystemClockMillis();
91 m_launchFilename = file.GetDynPath();
92 CLog::Log(LOGINFO, "%s: %s", __FUNCTION__, m_launchFilename.c_str());
93 Create();
94
95 return true;
96 }
97 catch(...)
98 {
99 m_bIsPlaying = false;
100 CLog::Log(LOGERROR,"%s - Exception thrown", __FUNCTION__);
101 return false;
102 }
103 }
104
CloseFile(bool reopen)105 bool CExternalPlayer::CloseFile(bool reopen)
106 {
107 m_bAbortRequest = true;
108
109 if (m_dialog && m_dialog->IsActive()) m_dialog->Close();
110
111 #if defined(TARGET_WINDOWS_DESKTOP)
112 if (m_bIsPlaying && m_processInfo.hProcess)
113 {
114 TerminateProcess(m_processInfo.hProcess, 1);
115 }
116 #endif
117
118 return true;
119 }
120
IsPlaying() const121 bool CExternalPlayer::IsPlaying() const
122 {
123 return m_bIsPlaying;
124 }
125
Process()126 void CExternalPlayer::Process()
127 {
128 std::string mainFile = m_launchFilename;
129 std::string archiveContent;
130
131 if (m_args.find("{0}") == std::string::npos)
132 {
133 // Unwind archive names
134 CURL url(m_launchFilename);
135 if (url.IsProtocol("zip") || url.IsProtocol("rar") /* || url.IsProtocol("iso9660") ??*/ || url.IsProtocol("udf"))
136 {
137 mainFile = url.GetHostName();
138 archiveContent = url.GetFileName();
139 }
140 if (url.IsProtocol("musicdb"))
141 mainFile = CMusicDatabaseFile::TranslateUrl(url);
142 if (url.IsProtocol("bluray"))
143 {
144 CURL base(url.GetHostName());
145 if (base.IsProtocol("udf"))
146 {
147 mainFile = base.GetHostName(); /* image file */
148 archiveContent = base.GetFileName();
149 }
150 else
151 mainFile = URIUtils::AddFileToFolder(base.Get(), url.GetFileName());
152 }
153 }
154
155 if (!m_filenameReplacers.empty())
156 {
157 for (unsigned int i = 0; i < m_filenameReplacers.size(); i++)
158 {
159 std::vector<std::string> vecSplit = StringUtils::Split(m_filenameReplacers[i], " , ");
160
161 // something is wrong, go to next substitution
162 if (vecSplit.size() != 4)
163 continue;
164
165 std::string strMatch = vecSplit[0];
166 StringUtils::Replace(strMatch, ",,",",");
167 bool bCaseless = vecSplit[3].find('i') != std::string::npos;
168 CRegExp regExp(bCaseless, CRegExp::autoUtf8);
169
170 if (!regExp.RegComp(strMatch.c_str()))
171 { // invalid regexp - complain in logs
172 CLog::Log(LOGERROR, "%s: Invalid RegExp:'%s'", __FUNCTION__, strMatch.c_str());
173 continue;
174 }
175
176 if (regExp.RegFind(mainFile) > -1)
177 {
178 std::string strPat = vecSplit[1];
179 StringUtils::Replace(strPat, ",,",",");
180
181 if (!regExp.RegComp(strPat.c_str()))
182 { // invalid regexp - complain in logs
183 CLog::Log(LOGERROR, "%s: Invalid RegExp:'%s'", __FUNCTION__, strPat.c_str());
184 continue;
185 }
186
187 std::string strRep = vecSplit[2];
188 StringUtils::Replace(strRep, ",,",",");
189 bool bGlobal = vecSplit[3].find('g') != std::string::npos;
190 bool bStop = vecSplit[3].find('s') != std::string::npos;
191 int iStart = 0;
192 while ((iStart = regExp.RegFind(mainFile, iStart)) > -1)
193 {
194 int iLength = regExp.GetFindLen();
195 mainFile = mainFile.substr(0, iStart) + regExp.GetReplaceString(strRep) + mainFile.substr(iStart + iLength);
196 if (!bGlobal)
197 break;
198 }
199 CLog::Log(LOGINFO, "%s: File matched:'%s' (RE='%s',Rep='%s') new filename:'%s'.", __FUNCTION__, strMatch.c_str(), strPat.c_str(), strRep.c_str(), mainFile.c_str());
200 if (bStop) break;
201 }
202 }
203 }
204
205 CLog::Log(LOGINFO, "%s: Player : %s", __FUNCTION__, m_filename.c_str());
206 CLog::Log(LOGINFO, "%s: File : %s", __FUNCTION__, mainFile.c_str());
207 CLog::Log(LOGINFO, "%s: Content: %s", __FUNCTION__, archiveContent.c_str());
208 CLog::Log(LOGINFO, "%s: Args : %s", __FUNCTION__, m_args.c_str());
209 CLog::Log(LOGINFO, "%s: Start", __FUNCTION__);
210
211 // make sure we surround the arguments with quotes where necessary
212 std::string strFName;
213 std::string strFArgs;
214 #if defined(TARGET_WINDOWS_DESKTOP)
215 // W32 batch-file handline
216 if (StringUtils::EndsWith(m_filename, ".bat") || StringUtils::EndsWith(m_filename, ".cmd"))
217 {
218 // MSDN says you just need to do this, but cmd's handing of spaces and
219 // quotes is soo broken it seems to work much better if you just omit
220 // lpApplicationName and enclose the module in lpCommandLine in quotes
221 //strFName = "cmd.exe";
222 //strFArgs = "/c ";
223 }
224 else
225 #endif
226 strFName = m_filename;
227
228 strFArgs.append("\"");
229 strFArgs.append(m_filename);
230 strFArgs.append("\" ");
231 strFArgs.append(m_args);
232
233 int nReplaced = StringUtils::Replace(strFArgs, "{0}", mainFile);
234
235 if (!nReplaced)
236 nReplaced = StringUtils::Replace(strFArgs, "{1}", mainFile) + StringUtils::Replace(strFArgs, "{2}", archiveContent);
237
238 if (!nReplaced)
239 {
240 strFArgs.append(" \"");
241 strFArgs.append(mainFile);
242 strFArgs.append("\"");
243 }
244
245 #if defined(TARGET_WINDOWS_DESKTOP)
246 if (m_warpcursor)
247 {
248 GetCursorPos(&m_ptCursorpos);
249 int x = 0;
250 int y = 0;
251 switch (m_warpcursor)
252 {
253 case WARP_BOTTOM_RIGHT:
254 x = GetSystemMetrics(SM_CXSCREEN);
255 case WARP_BOTTOM_LEFT:
256 y = GetSystemMetrics(SM_CYSCREEN);
257 break;
258 case WARP_TOP_RIGHT:
259 x = GetSystemMetrics(SM_CXSCREEN);
260 break;
261 case WARP_CENTER:
262 x = GetSystemMetrics(SM_CXSCREEN) / 2;
263 y = GetSystemMetrics(SM_CYSCREEN) / 2;
264 break;
265 }
266 CLog::Log(LOGINFO, "%s: Warping cursor to (%d,%d)", __FUNCTION__, x, y);
267 SetCursorPos(x,y);
268 }
269
270 LONG currentStyle = GetWindowLong(g_hWnd, GWL_EXSTYLE);
271 #endif
272
273 if (m_hidexbmc && !m_islauncher)
274 {
275 CLog::Log(LOGINFO, "%s: Hiding %s window", __FUNCTION__, CCompileInfo::GetAppName());
276 CServiceBroker::GetWinSystem()->Hide();
277 }
278 #if defined(TARGET_WINDOWS_DESKTOP)
279 else if (currentStyle & WS_EX_TOPMOST)
280 {
281 CLog::Log(LOGINFO, "%s: Lowering %s window", __FUNCTION__, CCompileInfo::GetAppName());
282 SetWindowPos(g_hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW | SWP_ASYNCWINDOWPOS);
283 }
284
285 CLog::Log(LOGDEBUG, "%s: Unlocking foreground window", __FUNCTION__);
286 LockSetForegroundWindow(LSFW_UNLOCK);
287 #endif
288
289 m_playbackStartTime = XbmcThreads::SystemClockMillis();
290
291 /* Suspend AE temporarily so exclusive or hog-mode sinks */
292 /* don't block external player's access to audio device */
293 CServiceBroker::GetActiveAE()->Suspend();
294 // wait for AE has completed suspended
295 XbmcThreads::EndTime timer(2000);
296 while (!timer.IsTimePast() && !CServiceBroker::GetActiveAE()->IsSuspended())
297 {
298 CThread::Sleep(50);
299 }
300 if (timer.IsTimePast())
301 {
302 CLog::Log(LOGERROR,"%s: AudioEngine did not suspend before launching external player", __FUNCTION__);
303 }
304
305 m_callback.OnPlayBackStarted(m_file);
306 m_callback.OnAVStarted(m_file);
307
308 bool ret = true;
309 #if defined(TARGET_WINDOWS_DESKTOP)
310 ret = ExecuteAppW32(strFName.c_str(),strFArgs.c_str());
311 #elif defined(TARGET_ANDROID)
312 ret = ExecuteAppAndroid(m_filename.c_str(), mainFile.c_str());
313 #elif defined(TARGET_POSIX) && !defined(TARGET_DARWIN_EMBEDDED)
314 ret = ExecuteAppLinux(strFArgs.c_str());
315 #endif
316 int64_t elapsedMillis = XbmcThreads::SystemClockMillis() - m_playbackStartTime;
317
318 if (ret && (m_islauncher || elapsedMillis < LAUNCHER_PROCESS_TIME))
319 {
320 if (m_hidexbmc)
321 {
322 CLog::Log(LOGINFO, "%s: %s cannot stay hidden for a launcher process", __FUNCTION__,
323 CCompileInfo::GetAppName());
324 CServiceBroker::GetWinSystem()->Show(false);
325 }
326
327 {
328 m_dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogOK>(WINDOW_DIALOG_OK);
329 m_dialog->SetHeading(CVariant{23100});
330 m_dialog->SetLine(1, CVariant{23104});
331 m_dialog->SetLine(2, CVariant{23105});
332 m_dialog->SetLine(3, CVariant{23106});
333 }
334
335 if (!m_bAbortRequest)
336 m_dialog->Open();
337 }
338
339 m_bIsPlaying = false;
340 CLog::Log(LOGINFO, "%s: Stop", __FUNCTION__);
341
342 #if defined(TARGET_WINDOWS_DESKTOP)
343 CServiceBroker::GetWinSystem()->Restore();
344
345 if (currentStyle & WS_EX_TOPMOST)
346 {
347 CLog::Log(LOGINFO, "%s: Showing %s window TOPMOST", __FUNCTION__, CCompileInfo::GetAppName());
348 SetWindowPos(g_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_ASYNCWINDOWPOS);
349 SetForegroundWindow(g_hWnd);
350 }
351 else
352 #endif
353 {
354 CLog::Log(LOGINFO, "%s: Showing %s window", __FUNCTION__, CCompileInfo::GetAppName());
355 CServiceBroker::GetWinSystem()->Show();
356 }
357
358 #if defined(TARGET_WINDOWS_DESKTOP)
359 if (m_warpcursor)
360 {
361 m_xPos = 0;
362 m_yPos = 0;
363 if (&m_ptCursorpos != 0)
364 {
365 m_xPos = (m_ptCursorpos.x);
366 m_yPos = (m_ptCursorpos.y);
367 }
368 CLog::Log(LOGINFO, "%s: Restoring cursor to (%d,%d)", __FUNCTION__, m_xPos, m_yPos);
369 SetCursorPos(m_xPos,m_yPos);
370 }
371 #endif
372
373 CBookmark bookmark;
374 bookmark.totalTimeInSeconds = 1;
375 bookmark.timeInSeconds = (elapsedMillis / 1000 >= m_playCountMinTime) ? 1 : 0;
376 bookmark.player = m_name;
377 m_callback.OnPlayerCloseFile(m_file, bookmark);
378
379 /* Resume AE processing of XBMC native audio */
380 if (!CServiceBroker::GetActiveAE()->Resume())
381 {
382 CLog::Log(LOGFATAL, "%s: Failed to restart AudioEngine after return from external player",__FUNCTION__);
383 }
384
385 // We don't want to come back to an active screensaver
386 g_application.ResetScreenSaver();
387 g_application.WakeUpScreenSaverAndDPMS();
388
389 if (!ret || (m_playOneStackItem && g_application.CurrentFileItem().IsStack()))
390 m_callback.OnPlayBackStopped();
391 else
392 m_callback.OnPlayBackEnded();
393 }
394
395 #if defined(TARGET_WINDOWS_DESKTOP)
ExecuteAppW32(const char * strPath,const char * strSwitches)396 bool CExternalPlayer::ExecuteAppW32(const char* strPath, const char* strSwitches)
397 {
398 CLog::Log(LOGINFO, "%s: %s %s", __FUNCTION__, strPath, strSwitches);
399
400 STARTUPINFOW si;
401 memset(&si, 0, sizeof(si));
402 si.cb = sizeof(si);
403 si.dwFlags = STARTF_USESHOWWINDOW;
404 si.wShowWindow = m_hideconsole ? SW_HIDE : SW_SHOW;
405
406 std::wstring WstrPath, WstrSwitches;
407 g_charsetConverter.utf8ToW(strPath, WstrPath, false);
408 g_charsetConverter.utf8ToW(strSwitches, WstrSwitches, false);
409
410 if (m_bAbortRequest) return false;
411
412 BOOL ret = CreateProcessW(WstrPath.empty() ? NULL : WstrPath.c_str(),
413 (LPWSTR) WstrSwitches.c_str(), NULL, NULL, FALSE, NULL,
414 NULL, NULL, &si, &m_processInfo);
415
416 if (ret == FALSE)
417 {
418 DWORD lastError = GetLastError();
419 CLog::Log(LOGINFO, "%s - Failure: %d", __FUNCTION__, lastError);
420 }
421 else
422 {
423 int res = WaitForSingleObject(m_processInfo.hProcess, INFINITE);
424
425 switch (res)
426 {
427 case WAIT_OBJECT_0:
428 CLog::Log(LOGINFO, "%s: WAIT_OBJECT_0", __FUNCTION__);
429 break;
430 case WAIT_ABANDONED:
431 CLog::Log(LOGINFO, "%s: WAIT_ABANDONED", __FUNCTION__);
432 break;
433 case WAIT_TIMEOUT:
434 CLog::Log(LOGINFO, "%s: WAIT_TIMEOUT", __FUNCTION__);
435 break;
436 case WAIT_FAILED:
437 CLog::Log(LOGINFO, "%s: WAIT_FAILED (%d)", __FUNCTION__, GetLastError());
438 ret = FALSE;
439 break;
440 }
441
442 CloseHandle(m_processInfo.hThread);
443 m_processInfo.hThread = 0;
444 CloseHandle(m_processInfo.hProcess);
445 m_processInfo.hProcess = 0;
446 }
447 return (ret == TRUE);
448 }
449 #endif
450
451 #if !defined(TARGET_ANDROID) && !defined(TARGET_DARWIN_EMBEDDED) && defined(TARGET_POSIX)
ExecuteAppLinux(const char * strSwitches)452 bool CExternalPlayer::ExecuteAppLinux(const char* strSwitches)
453 {
454 CLog::Log(LOGINFO, "%s: %s", __FUNCTION__, strSwitches);
455
456 int ret = system(strSwitches);
457 if (ret != 0)
458 {
459 CLog::Log(LOGINFO, "%s: Failure: %d", __FUNCTION__, ret);
460 }
461
462 return (ret == 0);
463 }
464 #endif
465
466 #if defined(TARGET_ANDROID)
ExecuteAppAndroid(const char * strSwitches,const char * strPath)467 bool CExternalPlayer::ExecuteAppAndroid(const char* strSwitches,const char* strPath)
468 {
469 CLog::Log(LOGINFO, "%s: %s", __FUNCTION__, strSwitches);
470
471 bool ret = CXBMCApp::StartActivity(strSwitches, "android.intent.action.VIEW", "video/*", strPath);
472
473 if (!ret)
474 {
475 CLog::Log(LOGINFO, "%s: Failure", __FUNCTION__);
476 }
477
478 return (ret == 0);
479 }
480 #endif
481
Pause()482 void CExternalPlayer::Pause()
483 {
484 }
485
HasVideo() const486 bool CExternalPlayer::HasVideo() const
487 {
488 return true;
489 }
490
HasAudio() const491 bool CExternalPlayer::HasAudio() const
492 {
493 return false;
494 }
495
CanSeek()496 bool CExternalPlayer::CanSeek()
497 {
498 return false;
499 }
500
Seek(bool bPlus,bool bLargeStep,bool bChapterOverride)501 void CExternalPlayer::Seek(bool bPlus, bool bLargeStep, bool bChapterOverride)
502 {
503 }
504
SeekPercentage(float iPercent)505 void CExternalPlayer::SeekPercentage(float iPercent)
506 {
507 }
508
SetAVDelay(float fValue)509 void CExternalPlayer::SetAVDelay(float fValue)
510 {
511 }
512
GetAVDelay()513 float CExternalPlayer::GetAVDelay()
514 {
515 return 0.0f;
516 }
517
SetSubTitleDelay(float fValue)518 void CExternalPlayer::SetSubTitleDelay(float fValue)
519 {
520 }
521
GetSubTitleDelay()522 float CExternalPlayer::GetSubTitleDelay()
523 {
524 return 0.0;
525 }
526
SeekTime(int64_t iTime)527 void CExternalPlayer::SeekTime(int64_t iTime)
528 {
529 }
530
SetSpeed(float speed)531 void CExternalPlayer::SetSpeed(float speed)
532 {
533 m_speed = speed;
534 CDataCacheCore::GetInstance().SetSpeed(1.0, speed);
535 }
536
GetPlayerState()537 std::string CExternalPlayer::GetPlayerState()
538 {
539 return "";
540 }
541
SetPlayerState(const std::string & state)542 bool CExternalPlayer::SetPlayerState(const std::string& state)
543 {
544 return true;
545 }
546
Initialize(TiXmlElement * pConfig)547 bool CExternalPlayer::Initialize(TiXmlElement* pConfig)
548 {
549 XMLUtils::GetString(pConfig, "filename", m_filename);
550 if (m_filename.length() > 0)
551 {
552 CLog::Log(LOGINFO, "ExternalPlayer Filename: %s", m_filename.c_str());
553 }
554 else
555 {
556 std::string xml;
557 xml<<*pConfig;
558 CLog::Log(LOGERROR, "ExternalPlayer Error: filename element missing from: %s", xml.c_str());
559 return false;
560 }
561
562 XMLUtils::GetString(pConfig, "args", m_args);
563 XMLUtils::GetBoolean(pConfig, "playonestackitem", m_playOneStackItem);
564 XMLUtils::GetBoolean(pConfig, "islauncher", m_islauncher);
565 XMLUtils::GetBoolean(pConfig, "hidexbmc", m_hidexbmc);
566 if (!XMLUtils::GetBoolean(pConfig, "hideconsole", m_hideconsole))
567 {
568 #ifdef TARGET_WINDOWS_DESKTOP
569 // Default depends on whether player is a batch file
570 m_hideconsole = StringUtils::EndsWith(m_filename, ".bat");
571 #endif
572 }
573
574 bool bHideCursor;
575 if (XMLUtils::GetBoolean(pConfig, "hidecursor", bHideCursor) && bHideCursor)
576 m_warpcursor = WARP_BOTTOM_RIGHT;
577
578 std::string warpCursor;
579 if (XMLUtils::GetString(pConfig, "warpcursor", warpCursor) && !warpCursor.empty())
580 {
581 if (warpCursor == "bottomright") m_warpcursor = WARP_BOTTOM_RIGHT;
582 else if (warpCursor == "bottomleft") m_warpcursor = WARP_BOTTOM_LEFT;
583 else if (warpCursor == "topleft") m_warpcursor = WARP_TOP_LEFT;
584 else if (warpCursor == "topright") m_warpcursor = WARP_TOP_RIGHT;
585 else if (warpCursor == "center") m_warpcursor = WARP_CENTER;
586 else
587 {
588 warpCursor = "none";
589 CLog::Log(LOGWARNING, "ExternalPlayer: invalid value for warpcursor: %s", warpCursor.c_str());
590 }
591 }
592
593 XMLUtils::GetInt(pConfig, "playcountminimumtime", m_playCountMinTime, 1, INT_MAX);
594
595 CLog::Log(
596 LOGINFO,
597 "ExternalPlayer Tweaks: hideconsole (%s), hidexbmc (%s), islauncher (%s), warpcursor (%s)",
598 m_hideconsole ? "true" : "false", m_hidexbmc ? "true" : "false",
599 m_islauncher ? "true" : "false", warpCursor.c_str());
600
601 #ifdef TARGET_WINDOWS_DESKTOP
602 m_filenameReplacers.push_back("^smb:// , / , \\\\ , g");
603 m_filenameReplacers.push_back("^smb:\\\\\\\\ , smb:(\\\\\\\\[^\\\\]*\\\\) , \\1 , ");
604 #endif
605
606 TiXmlElement* pReplacers = pConfig->FirstChildElement("replacers");
607 while (pReplacers)
608 {
609 GetCustomRegexpReplacers(pReplacers, m_filenameReplacers);
610 pReplacers = pReplacers->NextSiblingElement("replacers");
611 }
612
613 return true;
614 }
615
GetCustomRegexpReplacers(TiXmlElement * pRootElement,std::vector<std::string> & settings)616 void CExternalPlayer::GetCustomRegexpReplacers(TiXmlElement *pRootElement,
617 std::vector<std::string>& settings)
618 {
619 int iAction = 0; // overwrite
620 // for backward compatibility
621 const char* szAppend = pRootElement->Attribute("append");
622 if ((szAppend && StringUtils::CompareNoCase(szAppend, "yes") == 0))
623 iAction = 1;
624 // action takes precedence if both attributes exist
625 const char* szAction = pRootElement->Attribute("action");
626 if (szAction)
627 {
628 iAction = 0; // overwrite
629 if (StringUtils::CompareNoCase(szAction, "append") == 0)
630 iAction = 1; // append
631 else if (StringUtils::CompareNoCase(szAction, "prepend") == 0)
632 iAction = 2; // prepend
633 }
634 if (iAction == 0)
635 settings.clear();
636
637 TiXmlElement* pReplacer = pRootElement->FirstChildElement("replacer");
638 int i = 0;
639 while (pReplacer)
640 {
641 if (pReplacer->FirstChild())
642 {
643 const char* szGlobal = pReplacer->Attribute("global");
644 const char* szStop = pReplacer->Attribute("stop");
645 bool bGlobal = szGlobal && StringUtils::CompareNoCase(szGlobal, "true") == 0;
646 bool bStop = szStop && StringUtils::CompareNoCase(szStop, "true") == 0;
647
648 std::string strMatch;
649 std::string strPat;
650 std::string strRep;
651 XMLUtils::GetString(pReplacer,"match",strMatch);
652 XMLUtils::GetString(pReplacer,"pat",strPat);
653 XMLUtils::GetString(pReplacer,"rep",strRep);
654
655 if (!strPat.empty() && !strRep.empty())
656 {
657 CLog::Log(LOGDEBUG," Registering replacer:");
658 CLog::Log(LOGDEBUG," Match:[%s] Pattern:[%s] Replacement:[%s]", strMatch.c_str(), strPat.c_str(), strRep.c_str());
659 CLog::Log(LOGDEBUG," Global:[%s] Stop:[%s]", bGlobal?"true":"false", bStop?"true":"false");
660 // keep literal commas since we use comma as a separator
661 StringUtils::Replace(strMatch, ",",",,");
662 StringUtils::Replace(strPat, ",",",,");
663 StringUtils::Replace(strRep, ",",",,");
664
665 std::string strReplacer = strMatch + " , " + strPat + " , " + strRep + " , " + (bGlobal ? "g" : "") + (bStop ? "s" : "");
666 if (iAction == 2)
667 settings.insert(settings.begin() + i++, 1, strReplacer);
668 else
669 settings.push_back(strReplacer);
670 }
671 else
672 {
673 // error message about missing tag
674 if (strPat.empty())
675 CLog::Log(LOGERROR," Missing <Pat> tag");
676 else
677 CLog::Log(LOGERROR," Missing <Rep> tag");
678 }
679 }
680
681 pReplacer = pReplacer->NextSiblingElement("replacer");
682 }
683 }
684