[XAudio2] avoid leak + fix voice creation for closest match
[xbmc.git] / xbmc / Util.cpp
blobcd761165e3c2c3ee13dfacca03ad09d7c246a0aa
1 /*
2 * Copyright (C) 2005-2018 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 "network/Network.h"
10 #include "network/NetworkFileItemClassify.h"
11 #include "playlists/PlayListFileItemClassify.h"
12 #include "video/VideoFileItemClassify.h"
13 #if defined(TARGET_DARWIN)
14 #include <sys/param.h>
15 #include <mach-o/dyld.h>
16 #endif
18 #if defined(TARGET_FREEBSD)
19 #include <sys/param.h>
20 #include <sys/sysctl.h>
21 #endif
23 #ifdef TARGET_POSIX
24 #include <sys/types.h>
25 #include <dirent.h>
26 #include <unistd.h>
27 #include <sys/wait.h>
28 #endif
29 #if defined(TARGET_ANDROID)
30 #include <androidjni/ApplicationInfo.h>
31 #include "platform/android/activity/XBMCApp.h"
32 #include "CompileInfo.h"
33 #endif
34 #include "ServiceBroker.h"
35 #include "Util.h"
36 #include "addons/VFSEntry.h"
37 #include "filesystem/Directory.h"
38 #include "filesystem/MultiPathDirectory.h"
39 #include "filesystem/PVRDirectory.h"
40 #include "filesystem/RSSDirectory.h"
41 #include "filesystem/SpecialProtocol.h"
42 #include "filesystem/StackDirectory.h"
44 #include <algorithm>
45 #include <array>
46 #include <stdlib.h>
47 #ifdef HAS_UPNP
48 #include "filesystem/UPnPDirectory.h"
49 #endif
50 #include "profiles/ProfileManager.h"
51 #include "utils/RegExp.h"
52 #include "windowing/GraphicContext.h"
53 #include "guilib/TextureManager.h"
54 #include "storage/MediaManager.h"
55 #ifdef TARGET_WINDOWS
56 #include "utils/CharsetConverter.h"
57 #include "WIN32Util.h"
58 #endif
59 #if defined(TARGET_DARWIN)
60 #include "CompileInfo.h"
61 #include "platform/darwin/DarwinUtils.h"
62 #endif
63 #include "URL.h"
64 #include "cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.h"
65 #include "cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.h"
66 #include "filesystem/File.h"
67 #include "guilib/LocalizeStrings.h"
68 #include "platform/Environment.h"
69 #include "settings/AdvancedSettings.h"
70 #include "settings/MediaSettings.h"
71 #include "settings/Settings.h"
72 #include "settings/SettingsComponent.h"
73 #include "utils/Digest.h"
74 #include "utils/FileExtensionProvider.h"
75 #include "utils/FontUtils.h"
76 #include "utils/LangCodeExpander.h"
77 #include "utils/StringUtils.h"
78 #include "utils/TimeUtils.h"
79 #include "utils/URIUtils.h"
80 #include "utils/log.h"
81 #include "video/VideoDatabase.h"
82 #include "video/VideoInfoTag.h"
83 #ifdef HAVE_LIBCAP
84 #include <sys/capability.h>
85 #endif
87 #include "cores/VideoPlayer/DVDDemuxers/DVDDemux.h"
89 #include <fstrcmp.h>
91 #ifdef HAS_OPTICAL_DRIVE
92 using namespace MEDIA_DETECT;
93 #endif
95 using namespace XFILE;
96 using KODI::UTILITY::CDigest;
97 using namespace KODI;
99 #if !defined(TARGET_WINDOWS)
100 unsigned int CUtil::s_randomSeed = time(NULL);
101 #endif
103 namespace
105 #ifdef TARGET_WINDOWS
106 bool IsDirectoryValidRoot(std::wstring path)
108 path += L"\\system\\settings\\settings.xml";
109 #if defined(TARGET_WINDOWS_STORE)
110 auto h = CreateFile2(path.c_str(), GENERIC_READ, 0, OPEN_EXISTING, NULL);
111 #else
112 auto h = CreateFileW(path.c_str(), GENERIC_READ, 0, nullptr,
113 OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
114 #endif
115 if (h != INVALID_HANDLE_VALUE)
117 CloseHandle(h);
118 return true;
121 return false;
124 std::string GetHomePath(const std::string& strTarget, std::string strPath)
126 std::wstring strPathW;
128 // Environment variable was set and we have a path
129 // Let's make sure it's not relative and test it
130 // so it's actually pointing to a directory containing
131 // our stuff
132 if (strPath.find("..") != std::string::npos)
134 //expand potential relative path to full path
135 g_charsetConverter.utf8ToW(strPath, strPathW, false);
136 CWIN32Util::AddExtraLongPathPrefix(strPathW);
137 auto bufSize = GetFullPathNameW(strPathW.c_str(), 0, nullptr, nullptr);
138 if (bufSize != 0)
140 auto buf = std::make_unique<wchar_t[]>(bufSize);
141 if (GetFullPathNameW(strPathW.c_str(), bufSize, buf.get(), nullptr) <= bufSize - 1)
143 strPathW = buf.get();
144 CWIN32Util::RemoveExtraLongPathPrefix(strPathW);
146 if (IsDirectoryValidRoot(strPathW))
148 g_charsetConverter.wToUTF8(strPathW, strPath);
149 return strPath;
155 // Okay se no environment variable is set, let's
156 // grab the executable path and check if it's being
157 // run from a directory containing our stuff
158 strPath = CUtil::ResolveExecutablePath();
159 auto last_sep = strPath.find_last_of(PATH_SEPARATOR_CHAR);
160 if (last_sep != std::string::npos)
161 strPath.resize(last_sep);
163 g_charsetConverter.utf8ToW(strPath, strPathW);
164 if (IsDirectoryValidRoot(strPathW))
165 return strPath;
167 // Still nothing, let's check the current working
168 // directory and see if it points to a directory
169 // with our stuff in it. This bit should never be
170 // needed when running on a users system, it's intended
171 // to make our dev environment easier.
172 auto bufSize = GetCurrentDirectoryW(0, nullptr);
173 if (bufSize > 0)
175 auto buf = std::make_unique<wchar_t[]>(bufSize);
176 if (0 != GetCurrentDirectoryW(bufSize, buf.get()))
178 std::string currentDirectory;
179 std::wstring currentDirectoryW(buf.get());
180 CWIN32Util::RemoveExtraLongPathPrefix(currentDirectoryW);
182 if (IsDirectoryValidRoot(currentDirectoryW))
184 g_charsetConverter.wToUTF8(currentDirectoryW, currentDirectory);
185 return currentDirectory;
190 // If we ended up here we're most likely screwed
191 // we will crash in a few seconds
192 return strPath;
194 #endif
195 #if defined(TARGET_DARWIN)
196 #if !defined(TARGET_DARWIN_EMBEDDED)
197 bool IsDirectoryValidRoot(std::string path)
199 path += "/system/settings/settings.xml";
200 return CFile::Exists(path);
202 #endif
204 std::string GetHomePath(const std::string& strTarget, std::string strPath)
206 if (strPath.empty())
208 auto strHomePath = CUtil::ResolveExecutablePath();
209 int result = -1;
210 char given_path[2 * MAXPATHLEN];
211 size_t path_size = 2 * MAXPATHLEN;
213 result = CDarwinUtils::GetExecutablePath(given_path, &path_size);
214 if (result == 0)
216 // Move backwards to last /.
217 for (int n = strlen(given_path) - 1; given_path[n] != '/'; n--)
218 given_path[n] = '\0';
220 #if defined(TARGET_DARWIN_EMBEDDED)
221 strcat(given_path, "/AppData/AppHome/");
222 #else
223 // Assume local path inside application bundle.
224 strcat(given_path, "../Resources/");
225 strcat(given_path, CCompileInfo::GetAppName());
226 strcat(given_path, "/");
228 // if this path doesn't exist we
229 // might not be started from the app bundle
230 // but from the debugger/xcode. Lets
231 // see if this assumption is valid
232 if (!CDirectory::Exists(given_path))
234 std::string given_path_stdstr = CUtil::ResolveExecutablePath();
235 // try to find the correct folder by going back
236 // in the executable path until settings.xml was found
237 bool validRoot = false;
240 given_path_stdstr = URIUtils::GetParentPath(given_path_stdstr);
241 validRoot = IsDirectoryValidRoot(given_path_stdstr);
243 while(given_path_stdstr.length() > 0 && !validRoot);
244 strncpy(given_path, given_path_stdstr.c_str(), sizeof(given_path)-1);
247 #endif
249 // Convert to real path.
250 char real_path[2 * MAXPATHLEN];
251 if (realpath(given_path, real_path) != NULL)
253 strPath = real_path;
254 return strPath;
257 size_t last_sep = strHomePath.find_last_of(PATH_SEPARATOR_CHAR);
258 if (last_sep != std::string::npos)
259 strPath = strHomePath.substr(0, last_sep);
260 else
261 strPath = strHomePath;
264 return strPath;
266 #endif
267 #if defined(TARGET_POSIX) && !defined(TARGET_DARWIN)
268 std::string GetHomePath(const std::string& strTarget, std::string strPath)
270 if (strPath.empty())
272 auto strHomePath = CUtil::ResolveExecutablePath();
273 size_t last_sep = strHomePath.find_last_of(PATH_SEPARATOR_CHAR);
274 if (last_sep != std::string::npos)
275 strPath = strHomePath.substr(0, last_sep);
276 else
277 strPath = strHomePath;
279 /* Change strPath accordingly when target is KODI_HOME and when INSTALL_PATH
280 * and BIN_INSTALL_PATH differ
282 std::string installPath = INSTALL_PATH;
283 std::string binInstallPath = BIN_INSTALL_PATH;
285 if (strTarget.empty() && installPath.compare(binInstallPath))
287 int pos = strPath.length() - binInstallPath.length();
288 std::string tmp = strPath;
289 tmp.erase(0, pos);
290 if (!tmp.compare(binInstallPath))
292 strPath.erase(pos, strPath.length());
293 strPath.append(installPath);
297 return strPath;
299 #endif
302 std::string CUtil::GetTitleFromPath(const std::string& strFileNameAndPath, bool bIsFolder /* = false */)
304 CURL pathToUrl(strFileNameAndPath);
305 return GetTitleFromPath(pathToUrl, bIsFolder);
308 std::string CUtil::GetTitleFromPath(const CURL& url, bool bIsFolder /* = false */)
310 // use above to get the filename
311 std::string path(url.Get());
312 URIUtils::RemoveSlashAtEnd(path);
313 std::string strFilename = URIUtils::GetFileName(path);
315 #ifdef HAS_UPNP
316 // UPNP
317 if (url.IsProtocol("upnp"))
318 strFilename = CUPnPDirectory::GetFriendlyName(url);
319 #endif
321 if (url.IsProtocol("rss") || url.IsProtocol("rsss"))
323 CRSSDirectory dir;
324 CFileItemList items;
325 if(dir.GetDirectory(url, items) && !items.m_strTitle.empty())
326 return items.m_strTitle;
329 // Shoutcast
330 else if (url.IsProtocol("shout"))
332 const std::string strFileNameAndPath = url.Get();
333 const size_t genre = strFileNameAndPath.find_first_of('=');
334 if(genre == std::string::npos)
335 strFilename = g_localizeStrings.Get(260);
336 else
337 strFilename = g_localizeStrings.Get(260) + " - " + strFileNameAndPath.substr(genre+1).c_str();
340 // Windows SMB Network (SMB)
341 else if (url.IsProtocol("smb") && strFilename.empty())
343 if (url.GetHostName().empty())
345 strFilename = g_localizeStrings.Get(20171);
347 else
349 strFilename = url.GetHostName();
353 // Root file views
354 else if (url.IsProtocol("sources"))
355 strFilename = g_localizeStrings.Get(744);
357 // Music Playlists
358 else if (StringUtils::StartsWith(path, "special://musicplaylists"))
359 strFilename = g_localizeStrings.Get(136);
361 // Video Playlists
362 else if (StringUtils::StartsWith(path, "special://videoplaylists"))
363 strFilename = g_localizeStrings.Get(136);
365 else if (URIUtils::HasParentInHostname(url) && strFilename.empty())
366 strFilename = URIUtils::GetFileName(url.GetHostName());
368 // now remove the extension if needed
369 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWEXTENSIONS) && !bIsFolder)
371 URIUtils::RemoveExtension(strFilename);
372 return strFilename;
375 // URLDecode since the original path may be an URL
376 strFilename = CURL::Decode(strFilename);
377 return strFilename;
380 namespace
382 void GetTrailingDiscNumberSegmentInfoFromPath(const std::string& pathIn,
383 size_t& pos,
384 std::string& number)
386 std::string path{pathIn};
387 URIUtils::RemoveSlashAtEnd(path);
389 pos = std::string::npos;
390 number.clear();
392 // Handle Disc, Disk and locale specific spellings
393 std::string discStr{StringUtils::Format("/{} ", g_localizeStrings.Get(427))};
394 size_t discPos = path.rfind(discStr);
396 if (discPos == std::string::npos)
398 discStr = "/Disc ";
399 discPos = path.rfind(discStr);
402 if (discPos == std::string::npos)
404 discStr = "/Disk ";
405 discPos = path.rfind(discStr);
408 if (discPos != std::string::npos)
410 // Check remainder of path is numeric (eg. Disc 1)
411 const std::string discNum{path.substr(discPos + discStr.size())};
412 if (discNum.find_first_not_of("0123456789") == std::string::npos)
414 pos = discPos;
415 number = discNum;
419 } // unnamed namespace
421 std::string CUtil::RemoveTrailingDiscNumberSegmentFromPath(std::string path)
423 size_t discPos{std::string::npos};
424 std::string discNum;
425 GetTrailingDiscNumberSegmentInfoFromPath(path, discPos, discNum);
427 if (discPos != std::string::npos)
428 path.erase(discPos);
430 return path;
433 std::string CUtil::GetDiscNumberFromPath(const std::string& path)
435 size_t discPos{std::string::npos};
436 std::string discNum;
437 GetTrailingDiscNumberSegmentInfoFromPath(path, discPos, discNum);
438 return discNum;
441 bool CUtil::GetFilenameIdentifier(const std::string& fileName,
442 std::string& identifierType,
443 std::string& identifier)
445 std::string match;
446 return GetFilenameIdentifier(fileName, identifierType, identifier, match);
449 bool CUtil::GetFilenameIdentifier(const std::string& fileName,
450 std::string& identifierType,
451 std::string& identifier,
452 std::string& match)
454 CRegExp reIdentifier(true, CRegExp::autoUtf8);
456 const std::shared_ptr<CAdvancedSettings> advancedSettings =
457 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
458 if (!reIdentifier.RegComp(advancedSettings->m_videoFilenameIdentifierRegExp))
460 CLog::LogF(LOGERROR, "Invalid filename identifier RegExp:'{}'",
461 advancedSettings->m_videoFilenameIdentifierRegExp);
462 return false;
464 else
466 if (reIdentifier.RegComp(advancedSettings->m_videoFilenameIdentifierRegExp))
468 if (reIdentifier.RegFind(fileName) >= 0)
470 match = reIdentifier.GetMatch(0);
471 identifierType = reIdentifier.GetMatch(1);
472 identifier = reIdentifier.GetMatch(2);
473 StringUtils::ToLower(identifierType);
474 return true;
478 return false;
481 bool CUtil::HasFilenameIdentifier(const std::string& fileName)
483 std::string identifierType;
484 std::string identifier;
485 return GetFilenameIdentifier(fileName, identifierType, identifier);
488 void CUtil::CleanString(const std::string& strFileName,
489 std::string& strTitle,
490 std::string& strTitleAndYear,
491 std::string& strYear,
492 bool bRemoveExtension /* = false */,
493 bool bCleanChars /* = true */)
495 strTitleAndYear = strFileName;
497 if (strFileName == "..")
498 return;
500 std::string identifier;
501 std::string identifierType;
502 std::string identifierMatch;
503 if (GetFilenameIdentifier(strFileName, identifierType, identifier, identifierMatch))
504 StringUtils::Replace(strTitleAndYear, identifierMatch, "");
506 const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
507 const std::vector<std::string> &regexps = advancedSettings->m_videoCleanStringRegExps;
509 CRegExp reTags(true, CRegExp::autoUtf8);
510 CRegExp reYear(false, CRegExp::autoUtf8);
512 if (!reYear.RegComp(advancedSettings->m_videoCleanDateTimeRegExp))
514 CLog::Log(LOGERROR, "{}: Invalid datetime clean RegExp:'{}'", __FUNCTION__,
515 advancedSettings->m_videoCleanDateTimeRegExp);
517 else
519 if (reYear.RegFind(strTitleAndYear.c_str()) >= 0)
521 strTitleAndYear = reYear.GetMatch(1);
522 strYear = reYear.GetMatch(2);
526 URIUtils::RemoveExtension(strTitleAndYear);
528 for (const auto &regexp : regexps)
530 if (!reTags.RegComp(regexp.c_str()))
531 { // invalid regexp - complain in logs
532 CLog::Log(LOGERROR, "{}: Invalid string clean RegExp:'{}'", __FUNCTION__, regexp);
533 continue;
535 int j=0;
536 if ((j=reTags.RegFind(strTitleAndYear.c_str())) > 0)
537 strTitleAndYear.resize(j);
540 // final cleanup - special characters used instead of spaces:
541 // all '_' tokens should be replaced by spaces
542 // if the file contains no spaces, all '.' tokens should be replaced by
543 // spaces - one possibility of a mistake here could be something like:
544 // "Dr..StrangeLove" - hopefully no one would have anything like this.
545 if (bCleanChars)
547 bool initialDots = true;
548 bool alreadyContainsSpace = (strTitleAndYear.find(' ') != std::string::npos);
550 for (char &c : strTitleAndYear)
552 if (c != '.')
553 initialDots = false;
555 if ((c == '_') || ((!alreadyContainsSpace) && !initialDots && (c == '.')))
557 c = ' ';
562 StringUtils::Trim(strTitleAndYear);
563 strTitle = strTitleAndYear;
565 // append year
566 if (!strYear.empty())
567 strTitleAndYear = strTitle + " (" + strYear + ")";
569 // restore extension if needed
570 if (!bRemoveExtension)
571 strTitleAndYear += URIUtils::GetExtension(strFileName);
574 void CUtil::GetQualifiedFilename(const std::string &strBasePath, std::string &strFilename)
576 // Check if the filename is a fully qualified URL such as protocol://path/to/file
577 CURL plItemUrl(strFilename);
578 if (!plItemUrl.GetProtocol().empty())
579 return;
581 // If the filename starts "x:", "\\" or "/" it's already fully qualified so return
582 if (strFilename.size() > 1)
583 #ifdef TARGET_POSIX
584 if ( (strFilename[1] == ':') || (strFilename[0] == '/') )
585 #else
586 if ( strFilename[1] == ':' || (strFilename[0] == '\\' && strFilename[1] == '\\'))
587 #endif
588 return;
590 // add to base path and then clean
591 strFilename = URIUtils::AddFileToFolder(strBasePath, strFilename);
593 // get rid of any /./ or \.\ that happen to be there
594 StringUtils::Replace(strFilename, "\\.\\", "\\");
595 StringUtils::Replace(strFilename, "/./", "/");
597 // now find any "\\..\\" and remove them via GetParentPath
598 size_t pos;
599 while ((pos = strFilename.find("/../")) != std::string::npos)
601 std::string basePath = strFilename.substr(0, pos + 1);
602 strFilename.erase(0, pos + 4);
603 basePath = URIUtils::GetParentPath(basePath);
604 strFilename = URIUtils::AddFileToFolder(basePath, strFilename);
606 while ((pos = strFilename.find("\\..\\")) != std::string::npos)
608 std::string basePath = strFilename.substr(0, pos + 1);
609 strFilename.erase(0, pos + 4);
610 basePath = URIUtils::GetParentPath(basePath);
611 strFilename = URIUtils::AddFileToFolder(basePath, strFilename);
615 void CUtil::RunShortcut(const char* szShortcutPath)
619 std::string CUtil::GetHomePath(const std::string& strTarget)
621 auto strPath = CEnvironment::getenv(strTarget);
623 return ::GetHomePath(strTarget, strPath);
626 bool CUtil::IsPicture(const std::string& strFile)
628 return URIUtils::HasExtension(strFile,
629 CServiceBroker::GetFileExtensionProvider().GetPictureExtensions()+ "|.tbn|.dds");
632 std::string CUtil::GetSplashPath()
634 std::array<std::string, 4> candidates {{ "special://home/media/splash.jpg", "special://home/media/splash.png", "special://xbmc/media/splash.jpg", "special://xbmc/media/splash.png" }};
635 auto it = std::find_if(candidates.begin(), candidates.end(), [](std::string const& file) { return XFILE::CFile::Exists(file); });
636 if (it == candidates.end())
637 throw std::runtime_error("No splash image found");
638 return CSpecialProtocol::TranslatePathConvertCase(*it);
641 bool CUtil::ExcludeFileOrFolder(const std::string& strFileOrFolder, const std::vector<std::string>& regexps)
643 if (strFileOrFolder.empty())
644 return false;
646 CRegExp regExExcludes(true, CRegExp::autoUtf8); // case insensitive regex
648 for (const auto &regexp : regexps)
650 if (!regExExcludes.RegComp(regexp.c_str()))
651 { // invalid regexp - complain in logs
652 CLog::Log(LOGERROR, "{}: Invalid exclude RegExp:'{}'", __FUNCTION__, regexp);
653 continue;
655 if (regExExcludes.RegFind(strFileOrFolder) > -1)
657 CLog::LogF(LOGDEBUG, "File '{}' excluded. (Matches exclude rule RegExp: '{}')", CURL::GetRedacted(strFileOrFolder), regexp);
658 return true;
661 return false;
664 void CUtil::GetFileAndProtocol(const std::string& strURL, std::string& strDir)
666 strDir = strURL;
667 if (!URIUtils::IsRemote(strURL)) return ;
668 if (URIUtils::IsDVD(strURL)) return ;
670 CURL url(strURL);
671 strDir = StringUtils::Format("{}://{}", url.GetProtocol(), url.GetFileName());
674 int CUtil::GetDVDIfoTitle(const std::string& strFile)
676 std::string strFilename = URIUtils::GetFileName(strFile);
677 if (StringUtils::EqualsNoCase(strFilename, "video_ts.ifo")) return 0;
678 //VTS_[TITLE]_0.IFO
679 return atoi(strFilename.substr(4, 2).c_str());
682 std::string CUtil::GetFileDigest(const std::string& strPath, KODI::UTILITY::CDigest::Type type)
684 CFile file;
685 std::string result;
686 if (file.Open(strPath))
688 CDigest digest{type};
689 char temp[1024];
690 while (true)
692 ssize_t read = file.Read(temp,1024);
693 if (read <= 0)
694 break;
695 digest.Update(temp,read);
697 result = digest.Finalize();
698 file.Close();
701 return result;
704 bool CUtil::GetDirectoryName(const std::string& strFileName, std::string& strDescription)
706 std::string strFName = URIUtils::GetFileName(strFileName);
707 strDescription = URIUtils::GetDirectory(strFileName);
708 URIUtils::RemoveSlashAtEnd(strDescription);
710 size_t iPos = strDescription.find_last_of("/\\");
711 if (iPos != std::string::npos)
712 strDescription = strDescription.substr(iPos + 1);
713 else if (strDescription.size() <= 0)
714 strDescription = strFName;
715 return true;
718 void CUtil::GetDVDDriveIcon(const std::string& strPath, std::string& strIcon)
720 if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath))
722 strIcon = "DefaultDVDEmpty.png";
723 return ;
726 CFileItem item = CFileItem(strPath, false);
728 if (item.IsBluray())
730 strIcon = "DefaultBluray.png";
731 return;
734 if ( URIUtils::IsDVD(strPath) )
736 strIcon = "DefaultDVDFull.png";
737 return ;
740 if ( URIUtils::IsISO9660(strPath) )
742 #ifdef HAS_OPTICAL_DRIVE
743 CCdInfo* pInfo = CServiceBroker::GetMediaManager().GetCdInfo();
744 if ( pInfo != NULL && pInfo->IsVideoCd( 1 ) )
746 strIcon = "DefaultVCD.png";
747 return ;
749 #endif
750 strIcon = "DefaultDVDRom.png";
751 return ;
754 if ( URIUtils::IsCDDA(strPath) )
756 strIcon = "DefaultCDDA.png";
757 return ;
761 void CUtil::RemoveTempFiles()
763 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
765 std::string searchPath = profileManager->GetDatabaseFolder();
766 CFileItemList items;
767 if (!XFILE::CDirectory::GetDirectory(searchPath, items, ".tmp", DIR_FLAG_NO_FILE_DIRS))
768 return;
770 for (const auto &item : items)
772 if (item->m_bIsFolder)
773 continue;
774 XFILE::CFile::Delete(item->GetPath());
778 void CUtil::ClearSubtitles()
780 //delete cached subs
781 CFileItemList items;
782 CDirectory::GetDirectory("special://temp/",items, "", DIR_FLAG_DEFAULTS);
783 for (const auto &item : items)
785 if (!item->m_bIsFolder)
787 if (item->GetPath().find("subtitle") != std::string::npos ||
788 item->GetPath().find("vobsub_queue") != std::string::npos)
790 CLog::Log(LOGDEBUG, "{} - Deleting temporary subtitle {}", __FUNCTION__, item->GetPath());
791 CFile::Delete(item->GetPath());
797 int64_t CUtil::ToInt64(uint32_t high, uint32_t low)
799 int64_t n;
800 n = high;
801 n <<= 32;
802 n += low;
803 return n;
807 \brief Finds next unused filename that matches padded int format identifier provided
808 \param[in] fn_template filename template consisting of a padded int format identifier (eg screenshot%03d)
809 \param[in] max maximum number to search for available name
810 \return "" on failure, string next available name matching format identifier on success
813 std::string CUtil::GetNextFilename(const std::string &fn_template, int max)
815 std::string searchPath = URIUtils::GetDirectory(fn_template);
816 std::string mask = URIUtils::GetExtension(fn_template);
817 std::string name = StringUtils::Format(fn_template, 0);
819 CFileItemList items;
820 if (!CDirectory::GetDirectory(searchPath, items, mask, DIR_FLAG_NO_FILE_DIRS))
821 return name;
823 items.SetFastLookup(true);
824 for (int i = 0; i <= max; i++)
826 std::string name = StringUtils::Format(fn_template, i);
827 if (!items.Get(name))
828 return name;
830 return "";
833 std::string CUtil::GetNextPathname(const std::string &path_template, int max)
835 if (path_template.find("%04d") == std::string::npos)
836 return "";
838 for (int i = 0; i <= max; i++)
840 std::string name = StringUtils::Format(path_template, i);
841 if (!CFile::Exists(name) && !CDirectory::Exists(name))
842 return name;
844 return "";
847 void CUtil::StatToStatI64(struct _stati64 *result, struct stat *stat)
849 result->st_dev = stat->st_dev;
850 result->st_ino = stat->st_ino;
851 result->st_mode = stat->st_mode;
852 result->st_nlink = stat->st_nlink;
853 result->st_uid = stat->st_uid;
854 result->st_gid = stat->st_gid;
855 result->st_rdev = stat->st_rdev;
856 result->st_size = (int64_t)stat->st_size;
858 #ifndef TARGET_POSIX
859 result->st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
860 result->st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
861 result->st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
862 #else
863 result->_st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
864 result->_st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
865 result->_st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
866 #endif
869 void CUtil::Stat64ToStatI64(struct _stati64 *result, struct __stat64 *stat)
871 result->st_dev = stat->st_dev;
872 result->st_ino = stat->st_ino;
873 result->st_mode = stat->st_mode;
874 result->st_nlink = stat->st_nlink;
875 result->st_uid = stat->st_uid;
876 result->st_gid = stat->st_gid;
877 result->st_rdev = stat->st_rdev;
878 result->st_size = stat->st_size;
879 #ifndef TARGET_POSIX
880 result->st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
881 result->st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
882 result->st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
883 #else
884 result->_st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
885 result->_st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
886 result->_st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
887 #endif
890 void CUtil::StatI64ToStat64(struct __stat64 *result, struct _stati64 *stat)
892 result->st_dev = stat->st_dev;
893 result->st_ino = stat->st_ino;
894 result->st_mode = stat->st_mode;
895 result->st_nlink = stat->st_nlink;
896 result->st_uid = stat->st_uid;
897 result->st_gid = stat->st_gid;
898 result->st_rdev = stat->st_rdev;
899 result->st_size = stat->st_size;
900 #ifndef TARGET_POSIX
901 result->st_atime = stat->st_atime;
902 result->st_mtime = stat->st_mtime;
903 result->st_ctime = stat->st_ctime;
904 #else
905 result->st_atime = stat->_st_atime;
906 result->st_mtime = stat->_st_mtime;
907 result->st_ctime = stat->_st_ctime;
908 #endif
911 void CUtil::StatToStat64(struct __stat64 *result, const struct stat *stat)
913 memset(result, 0, sizeof(*result));
914 result->st_dev = stat->st_dev;
915 result->st_ino = stat->st_ino;
916 result->st_mode = stat->st_mode;
917 result->st_nlink = stat->st_nlink;
918 result->st_uid = stat->st_uid;
919 result->st_gid = stat->st_gid;
920 result->st_rdev = stat->st_rdev;
921 result->st_size = stat->st_size;
922 result->st_atime = stat->st_atime;
923 result->st_mtime = stat->st_mtime;
924 result->st_ctime = stat->st_ctime;
927 void CUtil::Stat64ToStat(struct stat *result, struct __stat64 *stat)
929 result->st_dev = stat->st_dev;
930 result->st_ino = stat->st_ino;
931 result->st_mode = stat->st_mode;
932 result->st_nlink = stat->st_nlink;
933 result->st_uid = stat->st_uid;
934 result->st_gid = stat->st_gid;
935 result->st_rdev = stat->st_rdev;
936 #ifndef TARGET_POSIX
937 if (stat->st_size <= LONG_MAX)
938 result->st_size = (_off_t)stat->st_size;
939 #else
940 if (sizeof(stat->st_size) <= sizeof(result->st_size) )
941 result->st_size = stat->st_size;
942 #endif
943 else
945 result->st_size = 0;
946 CLog::Log(LOGWARNING, "WARNING: File is larger than 32bit stat can handle, file size will be reported as 0 bytes");
948 result->st_atime = (time_t)(stat->st_atime & 0xFFFFFFFF);
949 result->st_mtime = (time_t)(stat->st_mtime & 0xFFFFFFFF);
950 result->st_ctime = (time_t)(stat->st_ctime & 0xFFFFFFFF);
953 #ifdef TARGET_WINDOWS
954 void CUtil::Stat64ToStat64i32(struct _stat64i32 *result, struct __stat64 *stat)
956 result->st_dev = stat->st_dev;
957 result->st_ino = stat->st_ino;
958 result->st_mode = stat->st_mode;
959 result->st_nlink = stat->st_nlink;
960 result->st_uid = stat->st_uid;
961 result->st_gid = stat->st_gid;
962 result->st_rdev = stat->st_rdev;
963 #ifndef TARGET_POSIX
964 if (stat->st_size <= LONG_MAX)
965 result->st_size = (_off_t)stat->st_size;
966 #else
967 if (sizeof(stat->st_size) <= sizeof(result->st_size) )
968 result->st_size = stat->st_size;
969 #endif
970 else
972 result->st_size = 0;
973 CLog::Log(LOGWARNING, "WARNING: File is larger than 32bit stat can handle, file size will be reported as 0 bytes");
975 #ifndef TARGET_POSIX
976 result->st_atime = stat->st_atime;
977 result->st_mtime = stat->st_mtime;
978 result->st_ctime = stat->st_ctime;
979 #else
980 result->st_atime = stat->_st_atime;
981 result->st_mtime = stat->_st_mtime;
982 result->st_ctime = stat->_st_ctime;
983 #endif
985 #endif
987 bool CUtil::CreateDirectoryEx(const std::string& strPath)
989 // Function to create all directories at once instead
990 // of calling CreateDirectory for every subdir.
991 // Creates the directory and subdirectories if needed.
993 // return true if directory already exist
994 if (CDirectory::Exists(strPath)) return true;
996 // we currently only allow HD and smb and nfs paths
997 if (!URIUtils::IsHD(strPath) && !URIUtils::IsSmb(strPath) && !URIUtils::IsNfs(strPath))
999 CLog::Log(LOGERROR, "{} called with an unsupported path: {}", __FUNCTION__, strPath);
1000 return false;
1003 std::vector<std::string> dirs = URIUtils::SplitPath(strPath);
1004 if (dirs.empty())
1005 return false;
1006 std::string dir(dirs.front());
1007 URIUtils::AddSlashAtEnd(dir);
1008 for (std::vector<std::string>::const_iterator it = dirs.begin() + 1; it != dirs.end(); ++it)
1010 dir = URIUtils::AddFileToFolder(dir, *it);
1011 CDirectory::Create(dir);
1014 // was the final destination directory successfully created ?
1015 return CDirectory::Exists(strPath);
1018 std::string CUtil::MakeLegalFileName(std::string strFile, int LegalType)
1020 StringUtils::Replace(strFile, '/', '_');
1021 StringUtils::Replace(strFile, '\\', '_');
1022 StringUtils::Replace(strFile, '?', '_');
1024 if (LegalType == LEGAL_WIN32_COMPAT)
1026 // just filter out some illegal characters on windows
1027 StringUtils::Replace(strFile, ':', '_');
1028 StringUtils::Replace(strFile, '*', '_');
1029 StringUtils::Replace(strFile, '?', '_');
1030 StringUtils::Replace(strFile, '\"', '_');
1031 StringUtils::Replace(strFile, '<', '_');
1032 StringUtils::Replace(strFile, '>', '_');
1033 StringUtils::Replace(strFile, '|', '_');
1034 StringUtils::TrimRight(strFile, ". ");
1036 return strFile;
1039 // legalize entire path
1040 std::string CUtil::MakeLegalPath(std::string strPathAndFile, int LegalType)
1042 if (URIUtils::IsStack(strPathAndFile))
1043 return MakeLegalPath(CStackDirectory::GetFirstStackedFile(strPathAndFile));
1044 if (URIUtils::IsMultiPath(strPathAndFile))
1045 return MakeLegalPath(CMultiPathDirectory::GetFirstPath(strPathAndFile));
1046 if (!URIUtils::IsHD(strPathAndFile) && !URIUtils::IsSmb(strPathAndFile) && !URIUtils::IsNfs(strPathAndFile))
1047 return strPathAndFile; // we don't support writing anywhere except HD, SMB and NFS - no need to legalize path
1049 bool trailingSlash = URIUtils::HasSlashAtEnd(strPathAndFile);
1050 std::vector<std::string> dirs = URIUtils::SplitPath(strPathAndFile);
1051 if (dirs.empty())
1052 return strPathAndFile;
1053 // we just add first token to path and don't legalize it - possible values:
1054 // "X:" (local win32), "" (local unix - empty string before '/') or
1055 // "protocol://domain"
1056 std::string dir(dirs.front());
1057 URIUtils::AddSlashAtEnd(dir);
1058 for (std::vector<std::string>::const_iterator it = dirs.begin() + 1; it != dirs.end(); ++it)
1059 dir = URIUtils::AddFileToFolder(dir, MakeLegalFileName(*it, LegalType));
1060 if (trailingSlash) URIUtils::AddSlashAtEnd(dir);
1061 return dir;
1064 std::string CUtil::ValidatePath(std::string path, bool bFixDoubleSlashes /* = false */)
1067 // Don't do any stuff on URLs containing %-characters or protocols that embed
1068 // filenames. NOTE: Don't use IsInZip or IsInRar here since it will infinitely
1069 // recurse and crash XBMC
1070 if (URIUtils::IsURL(path) &&
1071 (path.find('%') != std::string::npos ||
1072 StringUtils::StartsWithNoCase(path, "apk:") ||
1073 StringUtils::StartsWithNoCase(path, "zip:") ||
1074 StringUtils::StartsWithNoCase(path, "rar:") ||
1075 StringUtils::StartsWithNoCase(path, "stack:") ||
1076 StringUtils::StartsWithNoCase(path, "bluray:") ||
1077 StringUtils::StartsWithNoCase(path, "multipath:") ))
1078 return path;
1080 // check the path for incorrect slashes
1081 #ifdef TARGET_WINDOWS
1082 if (URIUtils::IsDOSPath(path))
1084 StringUtils::Replace(path, '/', '\\');
1085 /* The double slash correction should only be used when *absolutely*
1086 necessary! This applies to certain DLLs or use from Python DLLs/scripts
1087 that incorrectly generate double (back) slashes.
1089 if (bFixDoubleSlashes && !path.empty())
1091 // Fixup for double back slashes (but ignore the \\ of unc-paths)
1092 for (size_t x = 1; x < path.size() - 1; x++)
1094 if (path[x] == '\\' && path[x + 1] == '\\')
1095 path.erase(x, 1);
1099 else if (path.find("://") != std::string::npos || path.find(":\\\\") != std::string::npos)
1100 #endif
1102 StringUtils::Replace(path, '\\', '/');
1103 /* The double slash correction should only be used when *absolutely*
1104 necessary! This applies to certain DLLs or use from Python DLLs/scripts
1105 that incorrectly generate double (back) slashes.
1107 if (bFixDoubleSlashes && !path.empty())
1109 // Fixup for double forward slashes(/) but don't touch the :// of URLs
1110 for (size_t x = 2; x < path.size() - 1; x++)
1112 if (path[x] == '/' && path[x + 1] == '/' &&
1113 !(path[x - 1] == ':' || (path[x - 1] == '/' && path[x - 2] == ':')))
1114 path.erase(x, 1);
1118 return path;
1121 void CUtil::SplitParams(const std::string &paramString, std::vector<std::string> &parameters)
1123 bool inQuotes = false;
1124 bool lastEscaped = false; // only every second character can be escaped
1125 int inFunction = 0;
1126 size_t whiteSpacePos = 0;
1127 std::string parameter;
1128 parameters.clear();
1129 for (size_t pos = 0; pos < paramString.size(); pos++)
1131 char ch = paramString[pos];
1132 bool escaped = (pos > 0 && paramString[pos - 1] == '\\' && !lastEscaped);
1133 lastEscaped = escaped;
1134 if (inQuotes)
1135 { // if we're in a quote, we accept everything until the closing quote
1136 if (ch == '"' && !escaped)
1137 { // finished a quote - no need to add the end quote to our string
1138 inQuotes = false;
1141 else
1142 { // not in a quote, so check if we should be starting one
1143 if (ch == '"' && !escaped)
1144 { // start of quote - no need to add the quote to our string
1145 inQuotes = true;
1147 if (inFunction && ch == ')')
1148 { // end of a function
1149 inFunction--;
1151 if (ch == '(')
1152 { // start of function
1153 inFunction++;
1155 if (!inFunction && ch == ',')
1156 { // not in a function, so a comma signifies the end of this parameter
1157 if (whiteSpacePos)
1158 parameter.resize(whiteSpacePos);
1159 // trim off start and end quotes
1160 if (parameter.length() > 1 && parameter[0] == '"' && parameter[parameter.length() - 1] == '"')
1161 parameter = parameter.substr(1, parameter.length() - 2);
1162 else if (parameter.length() > 3 && parameter[parameter.length() - 1] == '"')
1164 // check name="value" style param.
1165 size_t quotaPos = parameter.find('"');
1166 if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=')
1168 parameter.erase(parameter.length() - 1);
1169 parameter.erase(quotaPos, 1);
1172 parameters.push_back(parameter);
1173 parameter.clear();
1174 whiteSpacePos = 0;
1175 continue;
1178 if ((ch == '"' || ch == '\\') && escaped)
1179 { // escaped quote or backslash
1180 parameter[parameter.size()-1] = ch;
1181 continue;
1183 // whitespace handling - we skip any whitespace at the left or right of an unquoted parameter
1184 if (ch == ' ' && !inQuotes)
1186 if (parameter.empty()) // skip whitespace on left
1187 continue;
1188 if (!whiteSpacePos) // make a note of where whitespace starts on the right
1189 whiteSpacePos = parameter.size();
1191 else
1192 whiteSpacePos = 0;
1193 parameter += ch;
1195 if (inFunction || inQuotes)
1196 CLog::Log(LOGWARNING, "{}({}) - end of string while searching for ) or \"", __FUNCTION__,
1197 paramString);
1198 if (whiteSpacePos)
1199 parameter.erase(whiteSpacePos);
1200 // trim off start and end quotes
1201 if (parameter.size() > 1 && parameter[0] == '"' && parameter[parameter.size() - 1] == '"')
1202 parameter = parameter.substr(1,parameter.size() - 2);
1203 else if (parameter.size() > 3 && parameter[parameter.size() - 1] == '"')
1205 // check name="value" style param.
1206 size_t quotaPos = parameter.find('"');
1207 if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=')
1209 parameter.erase(parameter.length() - 1);
1210 parameter.erase(quotaPos, 1);
1213 if (!parameter.empty() || parameters.size())
1214 parameters.push_back(parameter);
1217 int CUtil::GetMatchingSource(const std::string& strPath1, VECSOURCES& VECSOURCES, bool& bIsSourceName)
1219 if (strPath1.empty())
1220 return -1;
1222 // copy as we may change strPath
1223 std::string strPath = strPath1;
1225 // Check for special protocols
1226 CURL checkURL(strPath);
1228 if (StringUtils::StartsWith(strPath, "special://skin/"))
1229 return 1;
1231 // do not return early if URL protocol is "plugin"
1232 // since video- and/or audio-plugins can be configured as mediasource
1234 // stack://
1235 if (checkURL.IsProtocol("stack"))
1236 strPath.erase(0, 8); // remove the stack protocol
1238 if (checkURL.IsProtocol("shout"))
1239 strPath = checkURL.GetHostName();
1241 if (checkURL.IsProtocol("multipath"))
1242 strPath = CMultiPathDirectory::GetFirstPath(strPath);
1244 bIsSourceName = false;
1245 int iIndex = -1;
1247 // we first test the NAME of a source
1248 for (int i = 0; i < (int)VECSOURCES.size(); ++i)
1250 const CMediaSource &share = VECSOURCES[i];
1251 std::string strName = share.strName;
1253 // special cases for dvds
1254 if (URIUtils::IsOnDVD(share.strPath))
1256 if (URIUtils::IsOnDVD(strPath))
1257 return i;
1259 // not a path, so we need to modify the source name
1260 // since we add the drive status and disc name to the source
1261 // "Name (Drive Status/Disc Name)"
1262 size_t iPos = strName.rfind('(');
1263 if (iPos != std::string::npos && iPos > 1)
1264 strName.resize(iPos - 1);
1266 if (StringUtils::EqualsNoCase(strPath, strName))
1268 bIsSourceName = true;
1269 return i;
1273 // now test the paths
1275 // remove user details, and ensure path only uses forward slashes
1276 // and ends with a trailing slash so as not to match a substring
1277 CURL urlDest(strPath);
1278 urlDest.SetOptions("");
1279 urlDest.SetProtocolOptions("");
1280 std::string strDest = urlDest.GetWithoutUserDetails();
1281 ForceForwardSlashes(strDest);
1282 if (!URIUtils::HasSlashAtEnd(strDest))
1283 strDest += "/";
1285 size_t iLength = 0;
1286 size_t iLenPath = strDest.size();
1287 for (int i = 0; i < (int)VECSOURCES.size(); ++i)
1289 const CMediaSource &share = VECSOURCES[i];
1291 // does it match a source name?
1292 if (share.strPath.substr(0,8) == "shout://")
1294 CURL url(share.strPath);
1295 if (strPath == url.GetHostName())
1296 return i;
1299 // doesn't match a name, so try the source path
1300 std::vector<std::string> vecPaths;
1302 // add any concatenated paths if they exist
1303 if (!share.vecPaths.empty())
1304 vecPaths = share.vecPaths;
1306 // add the actual share path at the front of the vector
1307 vecPaths.insert(vecPaths.begin(), share.strPath);
1309 // test each path
1310 for (const auto &path : vecPaths)
1312 // remove user details, and ensure path only uses forward slashes
1313 // and ends with a trailing slash so as not to match a substring
1314 CURL urlShare(path);
1315 urlShare.SetOptions("");
1316 urlShare.SetProtocolOptions("");
1317 std::string strShare = urlShare.GetWithoutUserDetails();
1318 ForceForwardSlashes(strShare);
1319 if (!URIUtils::HasSlashAtEnd(strShare))
1320 strShare += "/";
1321 size_t iLenShare = strShare.size();
1323 if ((iLenPath >= iLenShare) && StringUtils::StartsWithNoCase(strDest, strShare) && (iLenShare > iLength))
1325 // if exact match, return it immediately
1326 if (iLenPath == iLenShare)
1328 // if the path EXACTLY matches an item in a concatenated path
1329 // set source name to true to load the full virtualpath
1330 bIsSourceName = false;
1331 if (vecPaths.size() > 1)
1332 bIsSourceName = true;
1333 return i;
1335 iIndex = i;
1336 iLength = iLenShare;
1341 // return the index of the share with the longest match
1342 if (iIndex == -1)
1345 // rar:// and zip://
1346 // if archive wasn't mounted, look for a matching share for the archive instead
1347 if( StringUtils::StartsWithNoCase(strPath, "rar://") || StringUtils::StartsWithNoCase(strPath, "zip://") )
1349 // get the hostname portion of the url since it contains the archive file
1350 strPath = checkURL.GetHostName();
1352 bIsSourceName = false;
1353 bool bDummy;
1354 return GetMatchingSource(strPath, VECSOURCES, bDummy);
1357 CLog::Log(LOGDEBUG, "CUtil::GetMatchingSource: no matching source found for [{}]", strPath1);
1359 return iIndex;
1362 std::string CUtil::TranslateSpecialSource(const std::string &strSpecial)
1364 if (!strSpecial.empty() && strSpecial[0] == '$')
1366 if (StringUtils::StartsWithNoCase(strSpecial, "$home"))
1367 return URIUtils::AddFileToFolder("special://home/", strSpecial.substr(5));
1368 else if (StringUtils::StartsWithNoCase(strSpecial, "$subtitles"))
1369 return URIUtils::AddFileToFolder("special://subtitles/", strSpecial.substr(10));
1370 else if (StringUtils::StartsWithNoCase(strSpecial, "$userdata"))
1371 return URIUtils::AddFileToFolder("special://userdata/", strSpecial.substr(9));
1372 else if (StringUtils::StartsWithNoCase(strSpecial, "$database"))
1373 return URIUtils::AddFileToFolder("special://database/", strSpecial.substr(9));
1374 else if (StringUtils::StartsWithNoCase(strSpecial, "$thumbnails"))
1375 return URIUtils::AddFileToFolder("special://thumbnails/", strSpecial.substr(11));
1376 else if (StringUtils::StartsWithNoCase(strSpecial, "$recordings"))
1377 return URIUtils::AddFileToFolder("special://recordings/", strSpecial.substr(11));
1378 else if (StringUtils::StartsWithNoCase(strSpecial, "$screenshots"))
1379 return URIUtils::AddFileToFolder("special://screenshots/", strSpecial.substr(12));
1380 else if (StringUtils::StartsWithNoCase(strSpecial, "$musicplaylists"))
1381 return URIUtils::AddFileToFolder("special://musicplaylists/", strSpecial.substr(15));
1382 else if (StringUtils::StartsWithNoCase(strSpecial, "$videoplaylists"))
1383 return URIUtils::AddFileToFolder("special://videoplaylists/", strSpecial.substr(15));
1384 else if (StringUtils::StartsWithNoCase(strSpecial, "$cdrips"))
1385 return URIUtils::AddFileToFolder("special://cdrips/", strSpecial.substr(7));
1386 // this one will be removed post 2.0
1387 else if (StringUtils::StartsWithNoCase(strSpecial, "$playlists"))
1388 return URIUtils::AddFileToFolder(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), strSpecial.substr(10));
1390 return strSpecial;
1393 std::string CUtil::MusicPlaylistsLocation()
1395 const std::string path = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH);
1396 std::vector<std::string> vec;
1397 vec.push_back(URIUtils::AddFileToFolder(path, "music"));
1398 vec.push_back(URIUtils::AddFileToFolder(path, "mixed"));
1399 return XFILE::CMultiPathDirectory::ConstructMultiPath(vec);
1402 std::string CUtil::VideoPlaylistsLocation()
1404 const std::string path = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH);
1405 std::vector<std::string> vec;
1406 vec.push_back(URIUtils::AddFileToFolder(path, "video"));
1407 vec.push_back(URIUtils::AddFileToFolder(path, "mixed"));
1408 return XFILE::CMultiPathDirectory::ConstructMultiPath(vec);
1411 void CUtil::DeleteMusicDatabaseDirectoryCache()
1413 CUtil::DeleteDirectoryCache("mdb-");
1414 CUtil::DeleteDirectoryCache("sp-"); // overkill as it will delete video smartplaylists, but as we can't differentiate based on URL...
1417 void CUtil::DeleteVideoDatabaseDirectoryCache()
1419 CUtil::DeleteDirectoryCache("vdb-");
1420 CUtil::DeleteDirectoryCache("sp-"); // overkill as it will delete music smartplaylists, but as we can't differentiate based on URL...
1423 void CUtil::DeleteDirectoryCache(const std::string &prefix)
1425 std::string searchPath = "special://temp/";
1426 CFileItemList items;
1427 if (!XFILE::CDirectory::GetDirectory(searchPath, items, ".fi", DIR_FLAG_NO_FILE_DIRS))
1428 return;
1430 for (const auto &item : items)
1432 if (item->m_bIsFolder)
1433 continue;
1434 std::string fileName = URIUtils::GetFileName(item->GetPath());
1435 if (StringUtils::StartsWith(fileName, prefix))
1436 XFILE::CFile::Delete(item->GetPath());
1441 void CUtil::GetRecursiveListing(const std::string& strPath, CFileItemList& items, const std::string& strMask, unsigned int flags /* = DIR_FLAG_DEFAULTS */)
1443 CFileItemList myItems;
1444 CDirectory::GetDirectory(strPath,myItems,strMask,flags);
1445 for (const auto &item : myItems)
1447 if (item->m_bIsFolder)
1448 CUtil::GetRecursiveListing(item->GetPath(),items,strMask,flags);
1449 else
1450 items.Add(item);
1454 void CUtil::GetRecursiveDirsListing(const std::string& strPath, CFileItemList& item, unsigned int flags /* = DIR_FLAG_DEFAULTS */)
1456 CFileItemList myItems;
1457 CDirectory::GetDirectory(strPath,myItems,"",flags);
1458 for (const auto &i : myItems)
1460 if (i->m_bIsFolder && !i->IsPath(".."))
1462 item.Add(i);
1463 CUtil::GetRecursiveDirsListing(i->GetPath(),item,flags);
1468 void CUtil::ForceForwardSlashes(std::string& strPath)
1470 size_t iPos = strPath.rfind('\\');
1471 while (iPos != std::string::npos)
1473 strPath.at(iPos) = '/';
1474 iPos = strPath.rfind('\\');
1478 double CUtil::AlbumRelevance(const std::string& strAlbumTemp1, const std::string& strAlbum1, const std::string& strArtistTemp1, const std::string& strArtist1)
1480 // case-insensitive fuzzy string comparison on the album and artist for relevance
1481 // weighting is identical, both album and artist are 50% of the total relevance
1482 // a missing artist means the maximum relevance can only be 0.50
1483 std::string strAlbumTemp = strAlbumTemp1;
1484 StringUtils::ToLower(strAlbumTemp);
1485 std::string strAlbum = strAlbum1;
1486 StringUtils::ToLower(strAlbum);
1487 double fAlbumPercentage = fstrcmp(strAlbumTemp.c_str(), strAlbum.c_str());
1488 double fArtistPercentage = 0.0;
1489 if (!strArtist1.empty())
1491 std::string strArtistTemp = strArtistTemp1;
1492 StringUtils::ToLower(strArtistTemp);
1493 std::string strArtist = strArtist1;
1494 StringUtils::ToLower(strArtist);
1495 fArtistPercentage = fstrcmp(strArtistTemp.c_str(), strArtist.c_str());
1497 double fRelevance = fAlbumPercentage * 0.5 + fArtistPercentage * 0.5;
1498 return fRelevance;
1501 bool CUtil::MakeShortenPath(std::string StrInput, std::string& StrOutput, size_t iTextMaxLength)
1503 size_t iStrInputSize = StrInput.size();
1504 if(iStrInputSize <= 0 || iTextMaxLength >= iStrInputSize)
1506 StrOutput = StrInput;
1507 return true;
1510 char cDelim = '\0';
1511 size_t nGreaterDelim, nPos;
1513 nPos = StrInput.find_last_of( '\\' );
1514 if (nPos != std::string::npos)
1515 cDelim = '\\';
1516 else
1518 nPos = StrInput.find_last_of( '/' );
1519 if (nPos != std::string::npos)
1520 cDelim = '/';
1522 if ( cDelim == '\0' )
1523 return false;
1525 if (nPos == StrInput.size() - 1)
1527 StrInput.erase(StrInput.size() - 1);
1528 nPos = StrInput.find_last_of(cDelim);
1530 while( iTextMaxLength < iStrInputSize )
1532 nPos = StrInput.find_last_of( cDelim, nPos );
1533 nGreaterDelim = nPos;
1535 if (nPos == std::string::npos || nPos == 0)
1536 break;
1538 nPos = StrInput.find_last_of( cDelim, nPos - 1 );
1540 if ( nPos == std::string::npos )
1541 break;
1542 if ( nGreaterDelim > nPos )
1543 StrInput.replace( nPos + 1, nGreaterDelim - nPos - 1, ".." );
1544 iStrInputSize = StrInput.size();
1546 // replace any additional /../../ with just /../ if necessary
1547 std::string replaceDots = StringUtils::Format("..{}..", cDelim);
1548 while (StrInput.size() > (unsigned int)iTextMaxLength)
1549 if (!StringUtils::Replace(StrInput, replaceDots, ".."))
1550 break;
1551 // finally, truncate our string to force inside our max text length,
1552 // replacing the last 2 characters with ".."
1554 // eg end up with:
1555 // "smb://../Playboy Swimsuit Cal.."
1556 if (iTextMaxLength > 2 && StrInput.size() > (unsigned int)iTextMaxLength)
1558 StrInput.erase(iTextMaxLength - 2);
1559 StrInput += "..";
1561 StrOutput = StrInput;
1562 return true;
1565 bool CUtil::SupportsWriteFileOperations(const std::string& strPath)
1567 // currently only hd, smb, nfs and dav support delete and rename
1568 if (URIUtils::IsHD(strPath))
1569 return true;
1570 if (URIUtils::IsSmb(strPath))
1571 return true;
1572 if (URIUtils::IsPVRRecording(strPath))
1573 return CPVRDirectory::SupportsWriteFileOperations(strPath);
1574 if (URIUtils::IsNfs(strPath))
1575 return true;
1576 if (URIUtils::IsDAV(strPath))
1577 return true;
1578 if (URIUtils::IsStack(strPath))
1579 return SupportsWriteFileOperations(CStackDirectory::GetFirstStackedFile(strPath));
1580 if (URIUtils::IsMultiPath(strPath))
1581 return CMultiPathDirectory::SupportsWriteFileOperations(strPath);
1583 if (CServiceBroker::IsAddonInterfaceUp())
1585 CURL url(strPath);
1586 for (const auto& addon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
1588 const auto& info = addon->GetProtocolInfo();
1589 auto prots = StringUtils::Split(info.type, "|");
1590 if (info.supportWrite &&
1591 std::find(prots.begin(), prots.end(), url.GetProtocol()) != prots.end())
1592 return true;
1596 return false;
1599 bool CUtil::SupportsReadFileOperations(const std::string& strPath)
1601 return !URIUtils::IsVideoDb(strPath);
1604 std::string CUtil::GetDefaultFolderThumb(const std::string &folderThumb)
1606 if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(folderThumb))
1607 return folderThumb;
1608 return "";
1611 void CUtil::GetSkinThemes(std::vector<std::string>& vecTheme)
1613 static const std::string TexturesXbt = "Textures.xbt";
1615 std::string strPath = URIUtils::AddFileToFolder(CServiceBroker::GetWinSystem()->GetGfxContext().GetMediaDir(), "media");
1616 CFileItemList items;
1617 CDirectory::GetDirectory(strPath, items, "", DIR_FLAG_DEFAULTS);
1618 // Search for Themes in the Current skin!
1619 for (const auto &pItem : items)
1621 if (!pItem->m_bIsFolder)
1623 std::string strExtension = URIUtils::GetExtension(pItem->GetPath());
1624 std::string strLabel = pItem->GetLabel();
1625 if ((strExtension == ".xbt" && !StringUtils::EqualsNoCase(strLabel, TexturesXbt)))
1626 vecTheme.push_back(StringUtils::Left(strLabel, strLabel.size() - strExtension.size()));
1628 else
1630 // check if this is an xbt:// VFS path
1631 CURL itemUrl(pItem->GetPath());
1632 if (!itemUrl.IsProtocol("xbt") || !itemUrl.GetFileName().empty())
1633 continue;
1635 std::string strLabel = URIUtils::GetFileName(itemUrl.GetHostName());
1636 if (!StringUtils::EqualsNoCase(strLabel, TexturesXbt))
1637 vecTheme.push_back(StringUtils::Left(strLabel, strLabel.size() - URIUtils::GetExtension(strLabel).size()));
1640 std::sort(vecTheme.begin(), vecTheme.end(), sortstringbyname());
1643 void CUtil::InitRandomSeed()
1645 // Init random seed
1646 auto now = std::chrono::steady_clock::now();
1647 auto seed = now.time_since_epoch();
1649 srand(static_cast<unsigned int>(seed.count()));
1652 #if defined(TARGET_POSIX) && !defined(TARGET_DARWIN_TVOS)
1653 bool CUtil::RunCommandLine(const std::string& cmdLine, bool waitExit)
1655 std::vector<std::string> args = StringUtils::Split(cmdLine, ",");
1657 // Strip quotes and whitespace around the arguments, or exec will fail.
1658 // This allows the python invocation to be written more naturally with any amount of whitespace around the args.
1659 // But it's still limited, for example quotes inside the strings are not expanded, etc.
1660 //! @todo Maybe some python library routine can parse this more properly ?
1661 for (std::vector<std::string>::iterator it = args.begin(); it != args.end(); ++it)
1663 size_t pos;
1664 pos = it->find_first_not_of(" \t\n\"'");
1665 if (pos != std::string::npos)
1667 it->erase(0, pos);
1670 pos = it->find_last_not_of(" \t\n\"'"); // if it returns npos we'll end up with an empty string which is OK
1672 it->erase(++pos, it->size());
1676 return Command(args, waitExit);
1679 bool CUtil::Command(const std::vector<std::string>& arrArgs, bool waitExit)
1681 #ifdef _DEBUG
1682 printf("Executing: ");
1683 for (const auto &arg : arrArgs)
1684 printf("%s ", arg.c_str());
1685 printf("\n");
1686 #endif
1688 pid_t child = fork();
1689 int n = 0;
1690 if (child == 0)
1692 if (!waitExit)
1694 // fork again in order not to leave a zombie process
1695 child = fork();
1696 if (child == -1)
1697 _exit(2);
1698 else if (child != 0)
1699 _exit(0);
1701 close(0);
1702 close(1);
1703 close(2);
1704 if (!arrArgs.empty())
1706 char **args = (char **)alloca(sizeof(char *) * (arrArgs.size() + 3));
1707 memset(args, 0, (sizeof(char *) * (arrArgs.size() + 3)));
1708 for (size_t i=0; i<arrArgs.size(); i++)
1709 args[i] = const_cast<char *>(arrArgs[i].c_str());
1710 execvp(args[0], args);
1713 else
1715 waitpid(child, &n, 0);
1718 return (waitExit) ? (WEXITSTATUS(n) == 0) : true;
1720 #endif
1722 int CUtil::LookupRomanDigit(char roman_digit)
1724 switch (roman_digit)
1726 case 'i':
1727 case 'I':
1728 return 1;
1729 case 'v':
1730 case 'V':
1731 return 5;
1732 case 'x':
1733 case 'X':
1734 return 10;
1735 case 'l':
1736 case 'L':
1737 return 50;
1738 case 'c':
1739 case 'C':
1740 return 100;
1741 case 'd':
1742 case 'D':
1743 return 500;
1744 case 'm':
1745 case 'M':
1746 return 1000;
1747 default:
1748 return 0;
1752 int CUtil::TranslateRomanNumeral(const char* roman_numeral)
1755 int decimal = -1;
1757 if (roman_numeral && roman_numeral[0])
1759 int temp_sum = 0,
1760 last = 0,
1761 repeat = 0,
1762 trend = 1;
1763 decimal = 0;
1764 while (*roman_numeral)
1766 int digit = CUtil::LookupRomanDigit(*roman_numeral);
1767 int test = last;
1769 // General sanity checks
1771 // numeral not in LUT
1772 if (!digit)
1773 return -1;
1775 while (test > 5)
1776 test /= 10;
1778 // N = 10^n may not precede (N+1) > 10^(N+1)
1779 if (test == 1 && digit > last * 10)
1780 return -1;
1782 // N = 5*10^n may not precede (N+1) >= N
1783 if (test == 5 && digit >= last)
1784 return -1;
1786 // End general sanity checks
1788 if (last < digit)
1790 // smaller numerals may not repeat before a larger one
1791 if (repeat)
1792 return -1;
1794 temp_sum += digit;
1796 repeat = 0;
1797 trend = 0;
1799 else if (last == digit)
1801 temp_sum += digit;
1802 repeat++;
1803 trend = 1;
1805 else
1807 if (!repeat)
1808 decimal += 2 * last - temp_sum;
1809 else
1810 decimal += temp_sum;
1812 temp_sum = digit;
1814 trend = 1;
1815 repeat = 0;
1817 // Post general sanity checks
1819 // numerals may not repeat more than thrice
1820 if (repeat == 3)
1821 return -1;
1823 last = digit;
1824 roman_numeral++;
1827 if (trend)
1828 decimal += temp_sum;
1829 else
1830 decimal += 2 * last - temp_sum;
1832 return decimal;
1835 std::string CUtil::ResolveExecutablePath()
1837 std::string strExecutablePath;
1838 #ifdef TARGET_WINDOWS
1839 static const size_t bufSize = MAX_PATH * 2;
1840 wchar_t* buf = new wchar_t[bufSize];
1841 buf[0] = 0;
1842 ::GetModuleFileNameW(0, buf, bufSize);
1843 buf[bufSize-1] = 0;
1844 g_charsetConverter.wToUTF8(buf,strExecutablePath);
1845 delete[] buf;
1846 #elif defined(TARGET_DARWIN)
1847 char given_path[2*MAXPATHLEN];
1848 size_t path_size =2*MAXPATHLEN;
1850 CDarwinUtils::GetExecutablePath(given_path, &path_size);
1851 strExecutablePath = given_path;
1852 #elif defined(TARGET_FREEBSD)
1853 char buf[PATH_MAX];
1854 size_t buflen;
1855 int mib[4];
1857 mib[0] = CTL_KERN;
1858 mib[1] = KERN_PROC;
1859 mib[2] = KERN_PROC_PATHNAME;
1860 mib[3] = getpid();
1862 buflen = sizeof(buf) - 1;
1863 if(sysctl(mib, 4, buf, &buflen, NULL, 0) < 0)
1864 strExecutablePath = "";
1865 else
1866 strExecutablePath = buf;
1867 #elif defined(TARGET_ANDROID)
1868 strExecutablePath = CXBMCApp::getApplicationInfo().nativeLibraryDir;
1870 std::string appName = CCompileInfo::GetAppName();
1871 std::string libName = "lib" + appName + ".so";
1872 StringUtils::ToLower(libName);
1873 strExecutablePath += "/" + libName;
1874 #else
1875 /* Get our PID and build the name of the link in /proc */
1876 pid_t pid = getpid();
1877 char linkname[64]; /* /proc/<pid>/exe */
1878 snprintf(linkname, sizeof(linkname), "/proc/%i/exe", pid);
1880 /* Now read the symbolic link */
1881 char buf[PATH_MAX + 1];
1882 buf[0] = 0;
1884 int ret = readlink(linkname, buf, sizeof(buf) - 1);
1885 if (ret != -1)
1886 buf[ret] = 0;
1888 strExecutablePath = buf;
1889 #endif
1890 return strExecutablePath;
1893 std::string CUtil::GetFrameworksPath(bool forPython)
1895 std::string strFrameworksPath;
1896 #if defined(TARGET_DARWIN)
1897 strFrameworksPath = CDarwinUtils::GetFrameworkPath(forPython);
1898 #endif
1899 return strFrameworksPath;
1902 void CUtil::GetVideoBasePathAndFileName(const std::string& videoPath, std::string& basePath, std::string& videoFileName)
1904 CFileItem item(videoPath, false);
1905 videoFileName = URIUtils::ReplaceExtension(URIUtils::GetFileName(videoPath), "");
1907 if (item.HasVideoInfoTag())
1908 basePath = item.GetVideoInfoTag()->m_basePath;
1910 if (basePath.empty() && item.IsOpticalMediaFile())
1911 basePath = item.GetLocalMetadataPath();
1913 CURL url(videoPath);
1914 if (basePath.empty() && url.IsProtocol("bluray"))
1916 basePath = url.GetHostName();
1917 videoFileName = URIUtils::ReplaceExtension(GetTitleFromPath(url.GetHostName()), "");
1919 url = CURL(url.GetHostName());
1920 if (url.IsProtocol("udf"))
1921 basePath = URIUtils::GetParentPath(url.GetHostName());
1924 if (basePath.empty())
1925 basePath = URIUtils::GetBasePath(videoPath);
1928 void CUtil::GetItemsToScan(const std::string& videoPath,
1929 const std::string& item_exts,
1930 const std::vector<std::string>& sub_dirs,
1931 CFileItemList& items)
1933 int flags = DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO;
1935 if (!videoPath.empty())
1936 CDirectory::GetDirectory(videoPath, items, item_exts, flags);
1938 std::vector<std::string> additionalPaths;
1939 for (const auto &item : items)
1941 for (const auto& subdir : sub_dirs)
1943 if (StringUtils::EqualsNoCase(item->GetLabel(), subdir))
1944 additionalPaths.push_back(item->GetPath());
1948 for (std::vector<std::string>::const_iterator it = additionalPaths.begin(); it != additionalPaths.end(); ++it)
1950 CFileItemList moreItems;
1951 CDirectory::GetDirectory(*it, moreItems, item_exts, flags);
1952 items.Append(moreItems);
1957 void CUtil::ScanPathsForAssociatedItems(const std::string& videoName,
1958 const CFileItemList& items,
1959 const std::vector<std::string>& item_exts,
1960 std::vector<std::string>& associatedFiles)
1962 for (const auto &pItem : items)
1964 if (pItem->m_bIsFolder)
1965 continue;
1967 std::string strCandidate = URIUtils::GetFileName(pItem->GetPath());
1969 // skip duplicates
1970 if (std::find(associatedFiles.begin(), associatedFiles.end(), pItem->GetPath()) != associatedFiles.end())
1971 continue;
1973 URIUtils::RemoveExtension(strCandidate);
1974 // NOTE: We don't know if one of videoName or strCandidate is URL-encoded and the other is not, so try both
1975 if (StringUtils::StartsWithNoCase(strCandidate, videoName) || (StringUtils::StartsWithNoCase(strCandidate, CURL::Decode(videoName))))
1977 if (URIUtils::IsRAR(pItem->GetPath()) || URIUtils::IsZIP(pItem->GetPath()))
1978 CUtil::ScanArchiveForAssociatedItems(pItem->GetPath(), "", item_exts, associatedFiles);
1979 else
1981 associatedFiles.push_back(pItem->GetPath());
1982 CLog::Log(LOGINFO, "{}: found associated file {}", __FUNCTION__,
1983 CURL::GetRedacted(pItem->GetPath()));
1986 else
1988 if (URIUtils::IsRAR(pItem->GetPath()) || URIUtils::IsZIP(pItem->GetPath()))
1989 CUtil::ScanArchiveForAssociatedItems(pItem->GetPath(), videoName, item_exts, associatedFiles);
1994 int CUtil::ScanArchiveForAssociatedItems(const std::string& strArchivePath,
1995 const std::string& videoNameNoExt,
1996 const std::vector<std::string>& item_exts,
1997 std::vector<std::string>& associatedFiles)
1999 CLog::LogF(LOGDEBUG, "Scanning archive {}", CURL::GetRedacted(strArchivePath));
2000 int nItemsAdded = 0;
2001 CFileItemList ItemList;
2003 // zip only gets the root dir
2004 if (URIUtils::HasExtension(strArchivePath, ".zip"))
2006 CURL pathToUrl(strArchivePath);
2007 CURL zipURL = URIUtils::CreateArchivePath("zip", pathToUrl, "");
2008 if (!CDirectory::GetDirectory(zipURL, ItemList, "", DIR_FLAG_NO_FILE_DIRS))
2009 return false;
2011 else if (URIUtils::HasExtension(strArchivePath, ".rar"))
2013 CURL pathToUrl(strArchivePath);
2014 CURL rarURL = URIUtils::CreateArchivePath("rar", pathToUrl, "");
2015 if (!CDirectory::GetDirectory(rarURL, ItemList, "", DIR_FLAG_NO_FILE_DIRS))
2016 return false;
2018 for (const auto &item : ItemList)
2020 std::string strPathInRar = item->GetPath();
2021 std::string strExt = URIUtils::GetExtension(strPathInRar);
2023 // Check another archive in archive
2024 if (strExt == ".zip" || strExt == ".rar")
2026 nItemsAdded +=
2027 ScanArchiveForAssociatedItems(strPathInRar, videoNameNoExt, item_exts, associatedFiles);
2028 continue;
2031 // check that the found filename matches the movie filename
2032 size_t fnl = videoNameNoExt.size();
2033 // NOTE: We don't know if videoNameNoExt is URL-encoded, so try both
2034 if (fnl &&
2035 !(StringUtils::StartsWithNoCase(URIUtils::GetFileName(strPathInRar), videoNameNoExt) ||
2036 StringUtils::StartsWithNoCase(URIUtils::GetFileName(strPathInRar), CURL::Decode(videoNameNoExt))))
2037 continue;
2039 for (const auto& ext : item_exts)
2041 if (StringUtils::EqualsNoCase(strExt, ext))
2043 CLog::Log(LOGINFO, "{}: found associated file {}", __FUNCTION__,
2044 CURL::GetRedacted(strPathInRar));
2045 associatedFiles.push_back(strPathInRar);
2046 nItemsAdded++;
2047 break;
2052 return nItemsAdded;
2055 void CUtil::ScanForExternalSubtitles(const std::string& strMovie, std::vector<std::string>& vecSubtitles)
2057 auto start = std::chrono::steady_clock::now();
2059 CFileItem item(strMovie, false);
2060 if ((NETWORK::IsInternetStream(item) && !URIUtils::IsOnLAN(item.GetDynPath())) ||
2061 PLAYLIST::IsPlayList(item) || item.IsLiveTV() || !VIDEO::IsVideo(item))
2062 return;
2064 CLog::Log(LOGDEBUG, "{}: Searching for subtitles...", __FUNCTION__);
2066 std::string strBasePath;
2067 std::string strSubtitle;
2069 GetVideoBasePathAndFileName(strMovie, strBasePath, strSubtitle);
2071 CFileItemList items;
2072 const std::vector<std::string> common_sub_dirs = { "subs", "subtitles", "vobsubs", "sub", "vobsub", "subtitle" };
2073 const std::string subtitleExtensions = CServiceBroker::GetFileExtensionProvider().GetSubtitleExtensions();
2074 GetItemsToScan(strBasePath, subtitleExtensions, common_sub_dirs, items);
2076 const std::string customPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SUBTITLES_CUSTOMPATH);
2078 if (!CMediaSettings::GetInstance().GetAdditionalSubtitleDirectoryChecked() && !customPath.empty()) // to avoid checking non-existent directories (network) every time..
2080 if (!CServiceBroker::GetNetwork().IsAvailable() && !URIUtils::IsHD(customPath))
2082 CLog::Log(LOGINFO, "CUtil::CacheSubtitles: disabling alternate subtitle directory for this session, it's inaccessible");
2083 CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(-1); // disabled
2085 else if (!CDirectory::Exists(customPath))
2087 CLog::Log(LOGINFO, "CUtil::CacheSubtitles: disabling alternate subtitle directory for this session, it's nonexistent");
2088 CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(-1); // disabled
2091 CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(1);
2094 std::vector<std::string> strLookInPaths;
2095 // this is last because we dont want to check any common subdirs or cd-dirs in the alternate <subtitles> dir.
2096 if (CMediaSettings::GetInstance().GetAdditionalSubtitleDirectoryChecked() == 1)
2098 std::string strPath2 = customPath;
2099 URIUtils::AddSlashAtEnd(strPath2);
2100 strLookInPaths.push_back(strPath2);
2103 int flags = DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO;
2104 for (const std::string& path : strLookInPaths)
2106 CFileItemList moreItems;
2107 CDirectory::GetDirectory(path, moreItems, subtitleExtensions, flags);
2108 items.Append(moreItems);
2111 std::vector<std::string> exts = StringUtils::Split(subtitleExtensions, '|');
2112 exts.erase(std::remove(exts.begin(), exts.end(), ".zip"), exts.end());
2113 exts.erase(std::remove(exts.begin(), exts.end(), ".rar"), exts.end());
2115 ScanPathsForAssociatedItems(strSubtitle, items, exts, vecSubtitles);
2117 size_t iSize = vecSubtitles.size();
2118 for (size_t i = 0; i < iSize; i++)
2120 if (URIUtils::HasExtension(vecSubtitles[i], ".smi"))
2122 //Cache multi-language sami subtitle
2123 CDVDSubtitleStream stream;
2124 if (stream.Open(vecSubtitles[i]))
2126 CDVDSubtitleTagSami TagConv;
2127 TagConv.LoadHead(&stream);
2128 if (TagConv.m_Langclass.size() >= 2)
2130 for (const auto &lang : TagConv.m_Langclass)
2132 std::string strDest =
2133 StringUtils::Format("special://temp/subtitle.{}.{}.smi", lang.Name, i);
2134 if (CFile::Copy(vecSubtitles[i], strDest))
2136 CLog::Log(LOGINFO, " cached subtitle {}->{}", CURL::GetRedacted(vecSubtitles[i]),
2137 strDest);
2138 vecSubtitles.push_back(strDest);
2146 auto end = std::chrono::steady_clock::now();
2147 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
2148 CLog::Log(LOGDEBUG, "{}: END (total time: {} ms)", __FUNCTION__, duration.count());
2151 ExternalStreamInfo CUtil::GetExternalStreamDetailsFromFilename(const std::string& videoPath, const std::string& associatedFile)
2153 ExternalStreamInfo info;
2155 std::string videoBaseName = URIUtils::GetFileName(videoPath);
2156 URIUtils::RemoveExtension(videoBaseName);
2158 std::string toParse = URIUtils::GetFileName(associatedFile);
2159 URIUtils::RemoveExtension(toParse);
2161 // we check left part - if it's same as video base name - strip it
2162 if (StringUtils::StartsWithNoCase(toParse, videoBaseName))
2163 toParse = toParse.substr(videoBaseName.length());
2164 else if (URIUtils::GetExtension(associatedFile) == ".sub" && URIUtils::IsInArchive(associatedFile))
2166 // exclude parsing of vobsub file names that are embedded in an archive
2167 CLog::Log(LOGDEBUG, "{} - skipping archived vobsub filename parsing: {}", __FUNCTION__,
2168 CURL::GetRedacted(associatedFile));
2169 toParse.clear();
2172 // trim any non-alphanumeric char in the beginning
2173 std::string::iterator result = std::find_if(toParse.begin(), toParse.end(), StringUtils::isasciialphanum);
2175 std::string name;
2176 if (result != toParse.end()) // if we have anything to parse
2178 std::string inputString(result, toParse.end());
2179 std::string delimiters(" .-");
2180 std::vector<std::string> tokens;
2181 StringUtils::Tokenize(inputString, tokens, delimiters);
2183 for (auto it = tokens.rbegin(); it != tokens.rend(); ++it)
2185 // try to recognize a flag
2186 std::string flag_tmp(*it);
2187 StringUtils::ToLower(flag_tmp);
2188 if (!flag_tmp.compare("none"))
2190 info.flag |= StreamFlags::FLAG_NONE;
2191 continue;
2193 else if (!flag_tmp.compare("default"))
2195 info.flag |= StreamFlags::FLAG_DEFAULT;
2196 continue;
2198 else if (!flag_tmp.compare("forced"))
2200 info.flag |= StreamFlags::FLAG_FORCED;
2201 continue;
2204 if (info.language.empty())
2206 std::string langCode;
2207 // try to recognize language
2208 if (g_LangCodeExpander.ConvertToISO6392B(*it, langCode))
2210 info.language = langCode;
2211 continue;
2215 name = (*it) + " " + name;
2218 name += " ";
2219 name += g_localizeStrings.Get(21602); // External
2220 StringUtils::Trim(name);
2221 info.name = StringUtils::RemoveDuplicatedSpacesAndTabs(name);
2222 if (info.flag == 0)
2223 info.flag = StreamFlags::FLAG_NONE;
2225 CLog::Log(LOGDEBUG, "{} - Language = '{}' / Name = '{}' / Flag = '{}' from {}", __FUNCTION__,
2226 info.language, info.name, info.flag, CURL::GetRedacted(associatedFile));
2228 return info;
2231 /*! \brief in a vector of subtitles finds the corresponding .sub file for a given .idx file
2233 bool CUtil::FindVobSubPair(const std::vector<std::string>& vecSubtitles, const std::string& strIdxPath, std::string& strSubPath)
2235 if (URIUtils::HasExtension(strIdxPath, ".idx"))
2237 std::string strIdxFile;
2238 std::string strIdxDirectory;
2239 URIUtils::Split(strIdxPath, strIdxDirectory, strIdxFile);
2240 for (const auto &subtitlePath : vecSubtitles)
2242 std::string strSubFile;
2243 std::string strSubDirectory;
2244 URIUtils::Split(subtitlePath, strSubDirectory, strSubFile);
2245 if (URIUtils::IsInArchive(subtitlePath))
2246 strSubDirectory = CURL::Decode(strSubDirectory);
2247 if (URIUtils::HasExtension(strSubFile, ".sub") &&
2248 (URIUtils::PathEquals(URIUtils::ReplaceExtension(strIdxPath,""),
2249 URIUtils::ReplaceExtension(subtitlePath,"")) ||
2250 (strSubDirectory.size() >= 11 &&
2251 StringUtils::EqualsNoCase(strSubDirectory.substr(6, strSubDirectory.length()-11), URIUtils::ReplaceExtension(strIdxPath,"")))))
2253 strSubPath = subtitlePath;
2254 return true;
2258 return false;
2261 /*! \brief checks if in the vector of subtitles the given .sub file has a corresponding idx and hence is a vobsub file
2263 bool CUtil::IsVobSub(const std::vector<std::string>& vecSubtitles, const std::string& strSubPath)
2265 if (URIUtils::HasExtension(strSubPath, ".sub"))
2267 std::string strSubFile;
2268 std::string strSubDirectory;
2269 URIUtils::Split(strSubPath, strSubDirectory, strSubFile);
2270 if (URIUtils::IsInArchive(strSubPath))
2271 strSubDirectory = CURL::Decode(strSubDirectory);
2272 for (const auto &subtitlePath : vecSubtitles)
2274 std::string strIdxFile;
2275 std::string strIdxDirectory;
2276 URIUtils::Split(subtitlePath, strIdxDirectory, strIdxFile);
2277 if (URIUtils::HasExtension(strIdxFile, ".idx") &&
2278 (URIUtils::PathEquals(URIUtils::ReplaceExtension(subtitlePath,""),
2279 URIUtils::ReplaceExtension(strSubPath,"")) ||
2280 (strSubDirectory.size() >= 11 &&
2281 StringUtils::EqualsNoCase(strSubDirectory.substr(6, strSubDirectory.length()-11), URIUtils::ReplaceExtension(subtitlePath,"")))))
2282 return true;
2285 return false;
2288 /*! \brief find a plain or archived vobsub .sub file corresponding to an .idx file
2290 std::string CUtil::GetVobSubSubFromIdx(const std::string& vobSubIdx)
2292 std::string vobSub = URIUtils::ReplaceExtension(vobSubIdx, ".sub");
2294 // check if a .sub file exists in the same directory
2295 if (CFile::Exists(vobSub))
2297 return vobSub;
2300 // look inside a .rar or .zip in the same directory
2301 const std::string archTypes[] = { "rar", "zip" };
2302 std::string vobSubFilename = URIUtils::GetFileName(vobSub);
2303 for (const std::string& archType : archTypes)
2305 vobSub = URIUtils::CreateArchivePath(archType,
2306 CURL(URIUtils::ReplaceExtension(vobSubIdx, std::string(".") + archType)),
2307 vobSubFilename).Get();
2308 if (CFile::Exists(vobSub))
2309 return vobSub;
2312 return std::string();
2315 /*! \brief find a .idx file from a path of a plain or archived vobsub .sub file
2317 std::string CUtil::GetVobSubIdxFromSub(const std::string& vobSub)
2319 std::string vobSubIdx = URIUtils::ReplaceExtension(vobSub, ".idx");
2321 // check if a .idx file exists in the same directory
2322 if (CFile::Exists(vobSubIdx))
2324 return vobSubIdx;
2327 // look outside archive (usually .rar) if the .sub is inside one
2328 if (URIUtils::IsInArchive(vobSub))
2331 std::string archiveFile = URIUtils::GetDirectory(vobSub);
2332 std::string vobSubIdxDir = URIUtils::GetParentPath(archiveFile);
2334 if (!vobSubIdxDir.empty())
2336 std::string vobSubIdxFilename = URIUtils::GetFileName(vobSubIdx);
2337 std::string vobSubIdx = URIUtils::AddFileToFolder(vobSubIdxDir, vobSubIdxFilename);
2339 if (CFile::Exists(vobSubIdx))
2340 return vobSubIdx;
2344 return std::string();
2347 void CUtil::ScanForExternalAudio(const std::string& videoPath, std::vector<std::string>& vecAudio)
2349 CFileItem item(videoPath, false);
2350 if (NETWORK::IsInternetStream(item) || PLAYLIST::IsPlayList(item) || item.IsLiveTV() ||
2351 item.IsPVR() || !VIDEO::IsVideo(item))
2352 return;
2354 std::string strBasePath;
2355 std::string strAudio;
2357 GetVideoBasePathAndFileName(videoPath, strBasePath, strAudio);
2359 CFileItemList items;
2360 const std::vector<std::string> common_sub_dirs = { "audio", "tracks"};
2361 GetItemsToScan(strBasePath, CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), common_sub_dirs, items);
2363 std::vector<std::string> exts = StringUtils::Split(CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), "|");
2365 CVideoDatabase database;
2366 database.Open();
2367 bool useAllExternalAudio = database.GetUseAllExternalAudioForVideo(videoPath);
2369 if (useAllExternalAudio)
2371 for (const auto& audioItem : items.GetList())
2373 vecAudio.push_back(audioItem.get()->GetPath());
2376 else
2377 ScanPathsForAssociatedItems(strAudio, items, exts, vecAudio);
2380 bool CUtil::CanBindPrivileged()
2382 #ifdef TARGET_POSIX
2384 if (geteuid() == 0)
2385 return true; //root user can always bind to privileged ports
2387 #ifdef HAVE_LIBCAP
2389 //check if CAP_NET_BIND_SERVICE is enabled, this allows non-root users to bind to privileged ports
2390 bool canbind = false;
2391 cap_t capabilities = cap_get_proc();
2392 if (capabilities)
2394 cap_flag_value_t value;
2395 if (cap_get_flag(capabilities, CAP_NET_BIND_SERVICE, CAP_EFFECTIVE, &value) == 0)
2396 canbind = value;
2398 cap_free(capabilities);
2401 return canbind;
2403 #else //HAVE_LIBCAP
2405 return false;
2407 #endif //HAVE_LIBCAP
2409 #else //TARGET_POSIX
2411 return true;
2413 #endif //TARGET_POSIX
2416 bool CUtil::ValidatePort(int port)
2418 // check that it's a valid port
2419 #ifdef TARGET_POSIX
2420 if (!CUtil::CanBindPrivileged() && (port < 1024 || port > 65535))
2421 return false;
2422 else
2423 #endif
2424 if (port <= 0 || port > 65535)
2425 return false;
2427 return true;
2430 int CUtil::GetRandomNumber()
2432 #if !defined(TARGET_WINDOWS)
2433 return rand_r(&s_randomSeed);
2434 #else
2435 unsigned int number;
2436 if (rand_s(&number) == 0)
2437 return (int)number;
2439 return rand();
2440 #endif
2443 void CUtil::CopyUserDataIfNeeded(const std::string& strPath,
2444 const std::string& file,
2445 const std::string& destname)
2447 std::string destPath;
2448 if (destname.empty())
2449 destPath = URIUtils::AddFileToFolder(strPath, file);
2450 else
2451 destPath = URIUtils::AddFileToFolder(strPath, destname);
2453 if (!CFile::Exists(destPath))
2455 // need to copy it across
2456 std::string srcPath = URIUtils::AddFileToFolder("special://xbmc/userdata/", file);
2457 CFile::Copy(srcPath, destPath);