[video] Fix the refresh of movies with additional versions or extras
[xbmc.git] / xbmc / Util.cpp
blob93b6dce9e92caff046e54e8f3727dd926b926785
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 #if defined(TARGET_DARWIN)
11 #include <sys/param.h>
12 #include <mach-o/dyld.h>
13 #endif
15 #if defined(TARGET_FREEBSD)
16 #include <sys/param.h>
17 #include <sys/sysctl.h>
18 #endif
20 #ifdef TARGET_POSIX
21 #include <sys/types.h>
22 #include <dirent.h>
23 #include <unistd.h>
24 #include <sys/wait.h>
25 #endif
26 #if defined(TARGET_ANDROID)
27 #include <androidjni/ApplicationInfo.h>
28 #include "platform/android/activity/XBMCApp.h"
29 #include "CompileInfo.h"
30 #endif
31 #include "ServiceBroker.h"
32 #include "Util.h"
33 #include "addons/VFSEntry.h"
34 #include "filesystem/Directory.h"
35 #include "filesystem/MultiPathDirectory.h"
36 #include "filesystem/PVRDirectory.h"
37 #include "filesystem/RSSDirectory.h"
38 #include "filesystem/SpecialProtocol.h"
39 #include "filesystem/StackDirectory.h"
41 #include <algorithm>
42 #include <array>
43 #include <stdlib.h>
44 #ifdef HAS_UPNP
45 #include "filesystem/UPnPDirectory.h"
46 #endif
47 #include "profiles/ProfileManager.h"
48 #include "utils/RegExp.h"
49 #include "windowing/GraphicContext.h"
50 #include "guilib/TextureManager.h"
51 #include "storage/MediaManager.h"
52 #ifdef TARGET_WINDOWS
53 #include "utils/CharsetConverter.h"
54 #include "WIN32Util.h"
55 #endif
56 #if defined(TARGET_DARWIN)
57 #include "CompileInfo.h"
58 #include "platform/darwin/DarwinUtils.h"
59 #endif
60 #include "URL.h"
61 #include "cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.h"
62 #include "cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.h"
63 #include "filesystem/File.h"
64 #include "guilib/LocalizeStrings.h"
65 #include "platform/Environment.h"
66 #include "settings/AdvancedSettings.h"
67 #include "settings/MediaSettings.h"
68 #include "settings/Settings.h"
69 #include "settings/SettingsComponent.h"
70 #include "utils/Digest.h"
71 #include "utils/FileExtensionProvider.h"
72 #include "utils/FontUtils.h"
73 #include "utils/LangCodeExpander.h"
74 #include "utils/StringUtils.h"
75 #include "utils/TimeUtils.h"
76 #include "utils/URIUtils.h"
77 #include "utils/log.h"
78 #include "video/VideoDatabase.h"
79 #include "video/VideoInfoTag.h"
80 #ifdef HAVE_LIBCAP
81 #include <sys/capability.h>
82 #endif
84 #include "cores/VideoPlayer/DVDDemuxers/DVDDemux.h"
86 #include <fstrcmp.h>
88 #ifdef HAS_OPTICAL_DRIVE
89 using namespace MEDIA_DETECT;
90 #endif
92 using namespace XFILE;
93 using namespace PLAYLIST;
94 using KODI::UTILITY::CDigest;
96 #if !defined(TARGET_WINDOWS)
97 unsigned int CUtil::s_randomSeed = time(NULL);
98 #endif
100 namespace
102 #ifdef TARGET_WINDOWS
103 bool IsDirectoryValidRoot(std::wstring path)
105 path += L"\\system\\settings\\settings.xml";
106 #if defined(TARGET_WINDOWS_STORE)
107 auto h = CreateFile2(path.c_str(), GENERIC_READ, 0, OPEN_EXISTING, NULL);
108 #else
109 auto h = CreateFileW(path.c_str(), GENERIC_READ, 0, nullptr,
110 OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
111 #endif
112 if (h != INVALID_HANDLE_VALUE)
114 CloseHandle(h);
115 return true;
118 return false;
121 std::string GetHomePath(const std::string& strTarget, std::string strPath)
123 std::wstring strPathW;
125 // Environment variable was set and we have a path
126 // Let's make sure it's not relative and test it
127 // so it's actually pointing to a directory containing
128 // our stuff
129 if (strPath.find("..") != std::string::npos)
131 //expand potential relative path to full path
132 g_charsetConverter.utf8ToW(strPath, strPathW, false);
133 CWIN32Util::AddExtraLongPathPrefix(strPathW);
134 auto bufSize = GetFullPathNameW(strPathW.c_str(), 0, nullptr, nullptr);
135 if (bufSize != 0)
137 auto buf = std::make_unique<wchar_t[]>(bufSize);
138 if (GetFullPathNameW(strPathW.c_str(), bufSize, buf.get(), nullptr) <= bufSize - 1)
140 strPathW = buf.get();
141 CWIN32Util::RemoveExtraLongPathPrefix(strPathW);
143 if (IsDirectoryValidRoot(strPathW))
145 g_charsetConverter.wToUTF8(strPathW, strPath);
146 return strPath;
152 // Okay se no environment variable is set, let's
153 // grab the executable path and check if it's being
154 // run from a directory containing our stuff
155 strPath = CUtil::ResolveExecutablePath();
156 auto last_sep = strPath.find_last_of(PATH_SEPARATOR_CHAR);
157 if (last_sep != std::string::npos)
158 strPath = strPath.substr(0, last_sep);
160 g_charsetConverter.utf8ToW(strPath, strPathW);
161 if (IsDirectoryValidRoot(strPathW))
162 return strPath;
164 // Still nothing, let's check the current working
165 // directory and see if it points to a directory
166 // with our stuff in it. This bit should never be
167 // needed when running on a users system, it's intended
168 // to make our dev environment easier.
169 auto bufSize = GetCurrentDirectoryW(0, nullptr);
170 if (bufSize > 0)
172 auto buf = std::make_unique<wchar_t[]>(bufSize);
173 if (0 != GetCurrentDirectoryW(bufSize, buf.get()))
175 std::string currentDirectory;
176 std::wstring currentDirectoryW(buf.get());
177 CWIN32Util::RemoveExtraLongPathPrefix(currentDirectoryW);
179 if (IsDirectoryValidRoot(currentDirectoryW))
181 g_charsetConverter.wToUTF8(currentDirectoryW, currentDirectory);
182 return currentDirectory;
187 // If we ended up here we're most likely screwed
188 // we will crash in a few seconds
189 return strPath;
191 #endif
192 #if defined(TARGET_DARWIN)
193 #if !defined(TARGET_DARWIN_EMBEDDED)
194 bool IsDirectoryValidRoot(std::string path)
196 path += "/system/settings/settings.xml";
197 return CFile::Exists(path);
199 #endif
201 std::string GetHomePath(const std::string& strTarget, std::string strPath)
203 if (strPath.empty())
205 auto strHomePath = CUtil::ResolveExecutablePath();
206 int result = -1;
207 char given_path[2 * MAXPATHLEN];
208 size_t path_size = 2 * MAXPATHLEN;
210 result = CDarwinUtils::GetExecutablePath(given_path, &path_size);
211 if (result == 0)
213 // Move backwards to last /.
214 for (int n = strlen(given_path) - 1; given_path[n] != '/'; n--)
215 given_path[n] = '\0';
217 #if defined(TARGET_DARWIN_EMBEDDED)
218 strcat(given_path, "/AppData/AppHome/");
219 #else
220 // Assume local path inside application bundle.
221 strcat(given_path, "../Resources/");
222 strcat(given_path, CCompileInfo::GetAppName());
223 strcat(given_path, "/");
225 // if this path doesn't exist we
226 // might not be started from the app bundle
227 // but from the debugger/xcode. Lets
228 // see if this assumption is valid
229 if (!CDirectory::Exists(given_path))
231 std::string given_path_stdstr = CUtil::ResolveExecutablePath();
232 // try to find the correct folder by going back
233 // in the executable path until settings.xml was found
234 bool validRoot = false;
237 given_path_stdstr = URIUtils::GetParentPath(given_path_stdstr);
238 validRoot = IsDirectoryValidRoot(given_path_stdstr);
240 while(given_path_stdstr.length() > 0 && !validRoot);
241 strncpy(given_path, given_path_stdstr.c_str(), sizeof(given_path)-1);
244 #endif
246 // Convert to real path.
247 char real_path[2 * MAXPATHLEN];
248 if (realpath(given_path, real_path) != NULL)
250 strPath = real_path;
251 return strPath;
254 size_t last_sep = strHomePath.find_last_of(PATH_SEPARATOR_CHAR);
255 if (last_sep != std::string::npos)
256 strPath = strHomePath.substr(0, last_sep);
257 else
258 strPath = strHomePath;
261 return strPath;
263 #endif
264 #if defined(TARGET_POSIX) && !defined(TARGET_DARWIN)
265 std::string GetHomePath(const std::string& strTarget, std::string strPath)
267 if (strPath.empty())
269 auto strHomePath = CUtil::ResolveExecutablePath();
270 size_t last_sep = strHomePath.find_last_of(PATH_SEPARATOR_CHAR);
271 if (last_sep != std::string::npos)
272 strPath = strHomePath.substr(0, last_sep);
273 else
274 strPath = strHomePath;
276 /* Change strPath accordingly when target is KODI_HOME and when INSTALL_PATH
277 * and BIN_INSTALL_PATH differ
279 std::string installPath = INSTALL_PATH;
280 std::string binInstallPath = BIN_INSTALL_PATH;
282 if (strTarget.empty() && installPath.compare(binInstallPath))
284 int pos = strPath.length() - binInstallPath.length();
285 std::string tmp = strPath;
286 tmp.erase(0, pos);
287 if (!tmp.compare(binInstallPath))
289 strPath.erase(pos, strPath.length());
290 strPath.append(installPath);
294 return strPath;
296 #endif
299 std::string CUtil::GetTitleFromPath(const std::string& strFileNameAndPath, bool bIsFolder /* = false */)
301 CURL pathToUrl(strFileNameAndPath);
302 return GetTitleFromPath(pathToUrl, bIsFolder);
305 std::string CUtil::GetTitleFromPath(const CURL& url, bool bIsFolder /* = false */)
307 // use above to get the filename
308 std::string path(url.Get());
309 URIUtils::RemoveSlashAtEnd(path);
310 std::string strFilename = URIUtils::GetFileName(path);
312 #ifdef HAS_UPNP
313 // UPNP
314 if (url.IsProtocol("upnp"))
315 strFilename = CUPnPDirectory::GetFriendlyName(url);
316 #endif
318 if (url.IsProtocol("rss") || url.IsProtocol("rsss"))
320 CRSSDirectory dir;
321 CFileItemList items;
322 if(dir.GetDirectory(url, items) && !items.m_strTitle.empty())
323 return items.m_strTitle;
326 // Shoutcast
327 else if (url.IsProtocol("shout"))
329 const std::string strFileNameAndPath = url.Get();
330 const size_t genre = strFileNameAndPath.find_first_of('=');
331 if(genre == std::string::npos)
332 strFilename = g_localizeStrings.Get(260);
333 else
334 strFilename = g_localizeStrings.Get(260) + " - " + strFileNameAndPath.substr(genre+1).c_str();
337 // Windows SMB Network (SMB)
338 else if (url.IsProtocol("smb") && strFilename.empty())
340 if (url.GetHostName().empty())
342 strFilename = g_localizeStrings.Get(20171);
344 else
346 strFilename = url.GetHostName();
350 // Root file views
351 else if (url.IsProtocol("sources"))
352 strFilename = g_localizeStrings.Get(744);
354 // Music Playlists
355 else if (StringUtils::StartsWith(path, "special://musicplaylists"))
356 strFilename = g_localizeStrings.Get(136);
358 // Video Playlists
359 else if (StringUtils::StartsWith(path, "special://videoplaylists"))
360 strFilename = g_localizeStrings.Get(136);
362 else if (URIUtils::HasParentInHostname(url) && strFilename.empty())
363 strFilename = URIUtils::GetFileName(url.GetHostName());
365 // now remove the extension if needed
366 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWEXTENSIONS) && !bIsFolder)
368 URIUtils::RemoveExtension(strFilename);
369 return strFilename;
372 // URLDecode since the original path may be an URL
373 strFilename = CURL::Decode(strFilename);
374 return strFilename;
377 namespace
379 void GetTrailingDiscNumberSegmentInfoFromPath(const std::string& pathIn,
380 size_t& pos,
381 std::string& number)
383 std::string path{pathIn};
384 URIUtils::RemoveSlashAtEnd(path);
386 pos = std::string::npos;
387 number.clear();
389 // Handle Disc, Disk and locale specific spellings
390 std::string discStr{StringUtils::Format("/{} ", g_localizeStrings.Get(427))};
391 size_t discPos = path.rfind(discStr);
393 if (discPos == std::string::npos)
395 discStr = "/Disc ";
396 discPos = path.rfind(discStr);
399 if (discPos == std::string::npos)
401 discStr = "/Disk ";
402 discPos = path.rfind(discStr);
405 if (discPos != std::string::npos)
407 // Check remainder of path is numeric (eg. Disc 1)
408 const std::string discNum{path.substr(discPos + discStr.size())};
409 if (discNum.find_first_not_of("0123456789") == std::string::npos)
411 pos = discPos;
412 number = discNum;
416 } // unnamed namespace
418 std::string CUtil::RemoveTrailingDiscNumberSegmentFromPath(std::string path)
420 size_t discPos{std::string::npos};
421 std::string discNum;
422 GetTrailingDiscNumberSegmentInfoFromPath(path, discPos, discNum);
424 if (discPos != std::string::npos)
425 path.erase(discPos);
427 return path;
430 std::string CUtil::GetDiscNumberFromPath(const std::string& path)
432 size_t discPos{std::string::npos};
433 std::string discNum;
434 GetTrailingDiscNumberSegmentInfoFromPath(path, discPos, discNum);
435 return discNum;
438 bool CUtil::GetFilenameIdentifier(const std::string& fileName,
439 std::string& identifierType,
440 std::string& identifier)
442 std::string match;
443 return GetFilenameIdentifier(fileName, identifierType, identifier, match);
446 bool CUtil::GetFilenameIdentifier(const std::string& fileName,
447 std::string& identifierType,
448 std::string& identifier,
449 std::string& match)
451 CRegExp reIdentifier(true, CRegExp::autoUtf8);
453 const std::shared_ptr<CAdvancedSettings> advancedSettings =
454 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
455 if (!reIdentifier.RegComp(advancedSettings->m_videoFilenameIdentifierRegExp))
457 CLog::LogF(LOGERROR, "Invalid filename identifier RegExp:'{}'",
458 advancedSettings->m_videoFilenameIdentifierRegExp);
459 return false;
461 else
463 if (reIdentifier.RegComp(advancedSettings->m_videoFilenameIdentifierRegExp))
465 if (reIdentifier.RegFind(fileName) >= 0)
467 match = reIdentifier.GetMatch(0);
468 identifierType = reIdentifier.GetMatch(1);
469 identifier = reIdentifier.GetMatch(2);
470 StringUtils::ToLower(identifierType);
471 return true;
475 return false;
478 bool CUtil::HasFilenameIdentifier(const std::string& fileName)
480 std::string identifierType;
481 std::string identifier;
482 return GetFilenameIdentifier(fileName, identifierType, identifier);
485 void CUtil::CleanString(const std::string& strFileName,
486 std::string& strTitle,
487 std::string& strTitleAndYear,
488 std::string& strYear,
489 bool bRemoveExtension /* = false */,
490 bool bCleanChars /* = true */)
492 strTitleAndYear = strFileName;
494 if (strFileName == "..")
495 return;
497 std::string identifier;
498 std::string identifierType;
499 std::string identifierMatch;
500 if (GetFilenameIdentifier(strFileName, identifierType, identifier, identifierMatch))
501 StringUtils::Replace(strTitleAndYear, identifierMatch, "");
503 const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
504 const std::vector<std::string> &regexps = advancedSettings->m_videoCleanStringRegExps;
506 CRegExp reTags(true, CRegExp::autoUtf8);
507 CRegExp reYear(false, CRegExp::autoUtf8);
509 if (!reYear.RegComp(advancedSettings->m_videoCleanDateTimeRegExp))
511 CLog::Log(LOGERROR, "{}: Invalid datetime clean RegExp:'{}'", __FUNCTION__,
512 advancedSettings->m_videoCleanDateTimeRegExp);
514 else
516 if (reYear.RegFind(strTitleAndYear.c_str()) >= 0)
518 strTitleAndYear = reYear.GetMatch(1);
519 strYear = reYear.GetMatch(2);
523 URIUtils::RemoveExtension(strTitleAndYear);
525 for (const auto &regexp : regexps)
527 if (!reTags.RegComp(regexp.c_str()))
528 { // invalid regexp - complain in logs
529 CLog::Log(LOGERROR, "{}: Invalid string clean RegExp:'{}'", __FUNCTION__, regexp);
530 continue;
532 int j=0;
533 if ((j=reTags.RegFind(strTitleAndYear.c_str())) > 0)
534 strTitleAndYear.resize(j);
537 // final cleanup - special characters used instead of spaces:
538 // all '_' tokens should be replaced by spaces
539 // if the file contains no spaces, all '.' tokens should be replaced by
540 // spaces - one possibility of a mistake here could be something like:
541 // "Dr..StrangeLove" - hopefully no one would have anything like this.
542 if (bCleanChars)
544 bool initialDots = true;
545 bool alreadyContainsSpace = (strTitleAndYear.find(' ') != std::string::npos);
547 for (char &c : strTitleAndYear)
549 if (c != '.')
550 initialDots = false;
552 if ((c == '_') || ((!alreadyContainsSpace) && !initialDots && (c == '.')))
554 c = ' ';
559 StringUtils::Trim(strTitleAndYear);
560 strTitle = strTitleAndYear;
562 // append year
563 if (!strYear.empty())
564 strTitleAndYear = strTitle + " (" + strYear + ")";
566 // restore extension if needed
567 if (!bRemoveExtension)
568 strTitleAndYear += URIUtils::GetExtension(strFileName);
571 void CUtil::GetQualifiedFilename(const std::string &strBasePath, std::string &strFilename)
573 // Check if the filename is a fully qualified URL such as protocol://path/to/file
574 CURL plItemUrl(strFilename);
575 if (!plItemUrl.GetProtocol().empty())
576 return;
578 // If the filename starts "x:", "\\" or "/" it's already fully qualified so return
579 if (strFilename.size() > 1)
580 #ifdef TARGET_POSIX
581 if ( (strFilename[1] == ':') || (strFilename[0] == '/') )
582 #else
583 if ( strFilename[1] == ':' || (strFilename[0] == '\\' && strFilename[1] == '\\'))
584 #endif
585 return;
587 // add to base path and then clean
588 strFilename = URIUtils::AddFileToFolder(strBasePath, strFilename);
590 // get rid of any /./ or \.\ that happen to be there
591 StringUtils::Replace(strFilename, "\\.\\", "\\");
592 StringUtils::Replace(strFilename, "/./", "/");
594 // now find any "\\..\\" and remove them via GetParentPath
595 size_t pos;
596 while ((pos = strFilename.find("/../")) != std::string::npos)
598 std::string basePath = strFilename.substr(0, pos + 1);
599 strFilename.erase(0, pos + 4);
600 basePath = URIUtils::GetParentPath(basePath);
601 strFilename = URIUtils::AddFileToFolder(basePath, strFilename);
603 while ((pos = strFilename.find("\\..\\")) != std::string::npos)
605 std::string basePath = strFilename.substr(0, pos + 1);
606 strFilename.erase(0, pos + 4);
607 basePath = URIUtils::GetParentPath(basePath);
608 strFilename = URIUtils::AddFileToFolder(basePath, strFilename);
612 void CUtil::RunShortcut(const char* szShortcutPath)
616 std::string CUtil::GetHomePath(const std::string& strTarget)
618 auto strPath = CEnvironment::getenv(strTarget);
620 return ::GetHomePath(strTarget, strPath);
623 bool CUtil::IsPicture(const std::string& strFile)
625 return URIUtils::HasExtension(strFile,
626 CServiceBroker::GetFileExtensionProvider().GetPictureExtensions()+ "|.tbn|.dds");
629 std::string CUtil::GetSplashPath()
631 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" }};
632 auto it = std::find_if(candidates.begin(), candidates.end(), [](std::string const& file) { return XFILE::CFile::Exists(file); });
633 if (it == candidates.end())
634 throw std::runtime_error("No splash image found");
635 return CSpecialProtocol::TranslatePathConvertCase(*it);
638 bool CUtil::ExcludeFileOrFolder(const std::string& strFileOrFolder, const std::vector<std::string>& regexps)
640 if (strFileOrFolder.empty())
641 return false;
643 CRegExp regExExcludes(true, CRegExp::autoUtf8); // case insensitive regex
645 for (const auto &regexp : regexps)
647 if (!regExExcludes.RegComp(regexp.c_str()))
648 { // invalid regexp - complain in logs
649 CLog::Log(LOGERROR, "{}: Invalid exclude RegExp:'{}'", __FUNCTION__, regexp);
650 continue;
652 if (regExExcludes.RegFind(strFileOrFolder) > -1)
654 CLog::LogF(LOGDEBUG, "File '{}' excluded. (Matches exclude rule RegExp: '{}')", CURL::GetRedacted(strFileOrFolder), regexp);
655 return true;
658 return false;
661 void CUtil::GetFileAndProtocol(const std::string& strURL, std::string& strDir)
663 strDir = strURL;
664 if (!URIUtils::IsRemote(strURL)) return ;
665 if (URIUtils::IsDVD(strURL)) return ;
667 CURL url(strURL);
668 strDir = StringUtils::Format("{}://{}", url.GetProtocol(), url.GetFileName());
671 int CUtil::GetDVDIfoTitle(const std::string& strFile)
673 std::string strFilename = URIUtils::GetFileName(strFile);
674 if (StringUtils::EqualsNoCase(strFilename, "video_ts.ifo")) return 0;
675 //VTS_[TITLE]_0.IFO
676 return atoi(strFilename.substr(4, 2).c_str());
679 std::string CUtil::GetFileDigest(const std::string& strPath, KODI::UTILITY::CDigest::Type type)
681 CFile file;
682 std::string result;
683 if (file.Open(strPath))
685 CDigest digest{type};
686 char temp[1024];
687 while (true)
689 ssize_t read = file.Read(temp,1024);
690 if (read <= 0)
691 break;
692 digest.Update(temp,read);
694 result = digest.Finalize();
695 file.Close();
698 return result;
701 bool CUtil::GetDirectoryName(const std::string& strFileName, std::string& strDescription)
703 std::string strFName = URIUtils::GetFileName(strFileName);
704 strDescription = URIUtils::GetDirectory(strFileName);
705 URIUtils::RemoveSlashAtEnd(strDescription);
707 size_t iPos = strDescription.find_last_of("/\\");
708 if (iPos != std::string::npos)
709 strDescription = strDescription.substr(iPos + 1);
710 else if (strDescription.size() <= 0)
711 strDescription = strFName;
712 return true;
715 void CUtil::GetDVDDriveIcon(const std::string& strPath, std::string& strIcon)
717 if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath))
719 strIcon = "DefaultDVDEmpty.png";
720 return ;
723 CFileItem item = CFileItem(strPath, false);
725 if (item.IsBluray())
727 strIcon = "DefaultBluray.png";
728 return;
731 if ( URIUtils::IsDVD(strPath) )
733 strIcon = "DefaultDVDFull.png";
734 return ;
737 if ( URIUtils::IsISO9660(strPath) )
739 #ifdef HAS_OPTICAL_DRIVE
740 CCdInfo* pInfo = CServiceBroker::GetMediaManager().GetCdInfo();
741 if ( pInfo != NULL && pInfo->IsVideoCd( 1 ) )
743 strIcon = "DefaultVCD.png";
744 return ;
746 #endif
747 strIcon = "DefaultDVDRom.png";
748 return ;
751 if ( URIUtils::IsCDDA(strPath) )
753 strIcon = "DefaultCDDA.png";
754 return ;
758 void CUtil::RemoveTempFiles()
760 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
762 std::string searchPath = profileManager->GetDatabaseFolder();
763 CFileItemList items;
764 if (!XFILE::CDirectory::GetDirectory(searchPath, items, ".tmp", DIR_FLAG_NO_FILE_DIRS))
765 return;
767 for (const auto &item : items)
769 if (item->m_bIsFolder)
770 continue;
771 XFILE::CFile::Delete(item->GetPath());
775 void CUtil::ClearSubtitles()
777 //delete cached subs
778 CFileItemList items;
779 CDirectory::GetDirectory("special://temp/",items, "", DIR_FLAG_DEFAULTS);
780 for (const auto &item : items)
782 if (!item->m_bIsFolder)
784 if (item->GetPath().find("subtitle") != std::string::npos ||
785 item->GetPath().find("vobsub_queue") != std::string::npos)
787 CLog::Log(LOGDEBUG, "{} - Deleting temporary subtitle {}", __FUNCTION__, item->GetPath());
788 CFile::Delete(item->GetPath());
794 int64_t CUtil::ToInt64(uint32_t high, uint32_t low)
796 int64_t n;
797 n = high;
798 n <<= 32;
799 n += low;
800 return n;
804 \brief Finds next unused filename that matches padded int format identifier provided
805 \param[in] fn_template filename template consisting of a padded int format identifier (eg screenshot%03d)
806 \param[in] max maximum number to search for available name
807 \return "" on failure, string next available name matching format identifier on success
810 std::string CUtil::GetNextFilename(const std::string &fn_template, int max)
812 std::string searchPath = URIUtils::GetDirectory(fn_template);
813 std::string mask = URIUtils::GetExtension(fn_template);
814 std::string name = StringUtils::Format(fn_template, 0);
816 CFileItemList items;
817 if (!CDirectory::GetDirectory(searchPath, items, mask, DIR_FLAG_NO_FILE_DIRS))
818 return name;
820 items.SetFastLookup(true);
821 for (int i = 0; i <= max; i++)
823 std::string name = StringUtils::Format(fn_template, i);
824 if (!items.Get(name))
825 return name;
827 return "";
830 std::string CUtil::GetNextPathname(const std::string &path_template, int max)
832 if (path_template.find("%04d") == std::string::npos)
833 return "";
835 for (int i = 0; i <= max; i++)
837 std::string name = StringUtils::Format(path_template, i);
838 if (!CFile::Exists(name) && !CDirectory::Exists(name))
839 return name;
841 return "";
844 void CUtil::StatToStatI64(struct _stati64 *result, struct stat *stat)
846 result->st_dev = stat->st_dev;
847 result->st_ino = stat->st_ino;
848 result->st_mode = stat->st_mode;
849 result->st_nlink = stat->st_nlink;
850 result->st_uid = stat->st_uid;
851 result->st_gid = stat->st_gid;
852 result->st_rdev = stat->st_rdev;
853 result->st_size = (int64_t)stat->st_size;
855 #ifndef TARGET_POSIX
856 result->st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
857 result->st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
858 result->st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
859 #else
860 result->_st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
861 result->_st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
862 result->_st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
863 #endif
866 void CUtil::Stat64ToStatI64(struct _stati64 *result, struct __stat64 *stat)
868 result->st_dev = stat->st_dev;
869 result->st_ino = stat->st_ino;
870 result->st_mode = stat->st_mode;
871 result->st_nlink = stat->st_nlink;
872 result->st_uid = stat->st_uid;
873 result->st_gid = stat->st_gid;
874 result->st_rdev = stat->st_rdev;
875 result->st_size = stat->st_size;
876 #ifndef TARGET_POSIX
877 result->st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
878 result->st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
879 result->st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
880 #else
881 result->_st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
882 result->_st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
883 result->_st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
884 #endif
887 void CUtil::StatI64ToStat64(struct __stat64 *result, struct _stati64 *stat)
889 result->st_dev = stat->st_dev;
890 result->st_ino = stat->st_ino;
891 result->st_mode = stat->st_mode;
892 result->st_nlink = stat->st_nlink;
893 result->st_uid = stat->st_uid;
894 result->st_gid = stat->st_gid;
895 result->st_rdev = stat->st_rdev;
896 result->st_size = stat->st_size;
897 #ifndef TARGET_POSIX
898 result->st_atime = stat->st_atime;
899 result->st_mtime = stat->st_mtime;
900 result->st_ctime = stat->st_ctime;
901 #else
902 result->st_atime = stat->_st_atime;
903 result->st_mtime = stat->_st_mtime;
904 result->st_ctime = stat->_st_ctime;
905 #endif
908 void CUtil::StatToStat64(struct __stat64 *result, const struct stat *stat)
910 memset(result, 0, sizeof(*result));
911 result->st_dev = stat->st_dev;
912 result->st_ino = stat->st_ino;
913 result->st_mode = stat->st_mode;
914 result->st_nlink = stat->st_nlink;
915 result->st_uid = stat->st_uid;
916 result->st_gid = stat->st_gid;
917 result->st_rdev = stat->st_rdev;
918 result->st_size = stat->st_size;
919 result->st_atime = stat->st_atime;
920 result->st_mtime = stat->st_mtime;
921 result->st_ctime = stat->st_ctime;
924 void CUtil::Stat64ToStat(struct stat *result, struct __stat64 *stat)
926 result->st_dev = stat->st_dev;
927 result->st_ino = stat->st_ino;
928 result->st_mode = stat->st_mode;
929 result->st_nlink = stat->st_nlink;
930 result->st_uid = stat->st_uid;
931 result->st_gid = stat->st_gid;
932 result->st_rdev = stat->st_rdev;
933 #ifndef TARGET_POSIX
934 if (stat->st_size <= LONG_MAX)
935 result->st_size = (_off_t)stat->st_size;
936 #else
937 if (sizeof(stat->st_size) <= sizeof(result->st_size) )
938 result->st_size = stat->st_size;
939 #endif
940 else
942 result->st_size = 0;
943 CLog::Log(LOGWARNING, "WARNING: File is larger than 32bit stat can handle, file size will be reported as 0 bytes");
945 result->st_atime = (time_t)(stat->st_atime & 0xFFFFFFFF);
946 result->st_mtime = (time_t)(stat->st_mtime & 0xFFFFFFFF);
947 result->st_ctime = (time_t)(stat->st_ctime & 0xFFFFFFFF);
950 #ifdef TARGET_WINDOWS
951 void CUtil::Stat64ToStat64i32(struct _stat64i32 *result, struct __stat64 *stat)
953 result->st_dev = stat->st_dev;
954 result->st_ino = stat->st_ino;
955 result->st_mode = stat->st_mode;
956 result->st_nlink = stat->st_nlink;
957 result->st_uid = stat->st_uid;
958 result->st_gid = stat->st_gid;
959 result->st_rdev = stat->st_rdev;
960 #ifndef TARGET_POSIX
961 if (stat->st_size <= LONG_MAX)
962 result->st_size = (_off_t)stat->st_size;
963 #else
964 if (sizeof(stat->st_size) <= sizeof(result->st_size) )
965 result->st_size = stat->st_size;
966 #endif
967 else
969 result->st_size = 0;
970 CLog::Log(LOGWARNING, "WARNING: File is larger than 32bit stat can handle, file size will be reported as 0 bytes");
972 #ifndef TARGET_POSIX
973 result->st_atime = stat->st_atime;
974 result->st_mtime = stat->st_mtime;
975 result->st_ctime = stat->st_ctime;
976 #else
977 result->st_atime = stat->_st_atime;
978 result->st_mtime = stat->_st_mtime;
979 result->st_ctime = stat->_st_ctime;
980 #endif
982 #endif
984 bool CUtil::CreateDirectoryEx(const std::string& strPath)
986 // Function to create all directories at once instead
987 // of calling CreateDirectory for every subdir.
988 // Creates the directory and subdirectories if needed.
990 // return true if directory already exist
991 if (CDirectory::Exists(strPath)) return true;
993 // we currently only allow HD and smb and nfs paths
994 if (!URIUtils::IsHD(strPath) && !URIUtils::IsSmb(strPath) && !URIUtils::IsNfs(strPath))
996 CLog::Log(LOGERROR, "{} called with an unsupported path: {}", __FUNCTION__, strPath);
997 return false;
1000 std::vector<std::string> dirs = URIUtils::SplitPath(strPath);
1001 if (dirs.empty())
1002 return false;
1003 std::string dir(dirs.front());
1004 URIUtils::AddSlashAtEnd(dir);
1005 for (std::vector<std::string>::const_iterator it = dirs.begin() + 1; it != dirs.end(); ++it)
1007 dir = URIUtils::AddFileToFolder(dir, *it);
1008 CDirectory::Create(dir);
1011 // was the final destination directory successfully created ?
1012 return CDirectory::Exists(strPath);
1015 std::string CUtil::MakeLegalFileName(std::string strFile, int LegalType)
1017 StringUtils::Replace(strFile, '/', '_');
1018 StringUtils::Replace(strFile, '\\', '_');
1019 StringUtils::Replace(strFile, '?', '_');
1021 if (LegalType == LEGAL_WIN32_COMPAT)
1023 // just filter out some illegal characters on windows
1024 StringUtils::Replace(strFile, ':', '_');
1025 StringUtils::Replace(strFile, '*', '_');
1026 StringUtils::Replace(strFile, '?', '_');
1027 StringUtils::Replace(strFile, '\"', '_');
1028 StringUtils::Replace(strFile, '<', '_');
1029 StringUtils::Replace(strFile, '>', '_');
1030 StringUtils::Replace(strFile, '|', '_');
1031 StringUtils::TrimRight(strFile, ". ");
1033 return strFile;
1036 // legalize entire path
1037 std::string CUtil::MakeLegalPath(std::string strPathAndFile, int LegalType)
1039 if (URIUtils::IsStack(strPathAndFile))
1040 return MakeLegalPath(CStackDirectory::GetFirstStackedFile(strPathAndFile));
1041 if (URIUtils::IsMultiPath(strPathAndFile))
1042 return MakeLegalPath(CMultiPathDirectory::GetFirstPath(strPathAndFile));
1043 if (!URIUtils::IsHD(strPathAndFile) && !URIUtils::IsSmb(strPathAndFile) && !URIUtils::IsNfs(strPathAndFile))
1044 return strPathAndFile; // we don't support writing anywhere except HD, SMB and NFS - no need to legalize path
1046 bool trailingSlash = URIUtils::HasSlashAtEnd(strPathAndFile);
1047 std::vector<std::string> dirs = URIUtils::SplitPath(strPathAndFile);
1048 if (dirs.empty())
1049 return strPathAndFile;
1050 // we just add first token to path and don't legalize it - possible values:
1051 // "X:" (local win32), "" (local unix - empty string before '/') or
1052 // "protocol://domain"
1053 std::string dir(dirs.front());
1054 URIUtils::AddSlashAtEnd(dir);
1055 for (std::vector<std::string>::const_iterator it = dirs.begin() + 1; it != dirs.end(); ++it)
1056 dir = URIUtils::AddFileToFolder(dir, MakeLegalFileName(*it, LegalType));
1057 if (trailingSlash) URIUtils::AddSlashAtEnd(dir);
1058 return dir;
1061 std::string CUtil::ValidatePath(std::string path, bool bFixDoubleSlashes /* = false */)
1064 // Don't do any stuff on URLs containing %-characters or protocols that embed
1065 // filenames. NOTE: Don't use IsInZip or IsInRar here since it will infinitely
1066 // recurse and crash XBMC
1067 if (URIUtils::IsURL(path) &&
1068 (path.find('%') != std::string::npos ||
1069 StringUtils::StartsWithNoCase(path, "apk:") ||
1070 StringUtils::StartsWithNoCase(path, "zip:") ||
1071 StringUtils::StartsWithNoCase(path, "rar:") ||
1072 StringUtils::StartsWithNoCase(path, "stack:") ||
1073 StringUtils::StartsWithNoCase(path, "bluray:") ||
1074 StringUtils::StartsWithNoCase(path, "multipath:") ))
1075 return path;
1077 // check the path for incorrect slashes
1078 #ifdef TARGET_WINDOWS
1079 if (URIUtils::IsDOSPath(path))
1081 StringUtils::Replace(path, '/', '\\');
1082 /* The double slash correction should only be used when *absolutely*
1083 necessary! This applies to certain DLLs or use from Python DLLs/scripts
1084 that incorrectly generate double (back) slashes.
1086 if (bFixDoubleSlashes && !path.empty())
1088 // Fixup for double back slashes (but ignore the \\ of unc-paths)
1089 for (size_t x = 1; x < path.size() - 1; x++)
1091 if (path[x] == '\\' && path[x + 1] == '\\')
1092 path.erase(x, 1);
1096 else if (path.find("://") != std::string::npos || path.find(":\\\\") != std::string::npos)
1097 #endif
1099 StringUtils::Replace(path, '\\', '/');
1100 /* The double slash correction should only be used when *absolutely*
1101 necessary! This applies to certain DLLs or use from Python DLLs/scripts
1102 that incorrectly generate double (back) slashes.
1104 if (bFixDoubleSlashes && !path.empty())
1106 // Fixup for double forward slashes(/) but don't touch the :// of URLs
1107 for (size_t x = 2; x < path.size() - 1; x++)
1109 if (path[x] == '/' && path[x + 1] == '/' &&
1110 !(path[x - 1] == ':' || (path[x - 1] == '/' && path[x - 2] == ':')))
1111 path.erase(x, 1);
1115 return path;
1118 void CUtil::SplitParams(const std::string &paramString, std::vector<std::string> &parameters)
1120 bool inQuotes = false;
1121 bool lastEscaped = false; // only every second character can be escaped
1122 int inFunction = 0;
1123 size_t whiteSpacePos = 0;
1124 std::string parameter;
1125 parameters.clear();
1126 for (size_t pos = 0; pos < paramString.size(); pos++)
1128 char ch = paramString[pos];
1129 bool escaped = (pos > 0 && paramString[pos - 1] == '\\' && !lastEscaped);
1130 lastEscaped = escaped;
1131 if (inQuotes)
1132 { // if we're in a quote, we accept everything until the closing quote
1133 if (ch == '"' && !escaped)
1134 { // finished a quote - no need to add the end quote to our string
1135 inQuotes = false;
1138 else
1139 { // not in a quote, so check if we should be starting one
1140 if (ch == '"' && !escaped)
1141 { // start of quote - no need to add the quote to our string
1142 inQuotes = true;
1144 if (inFunction && ch == ')')
1145 { // end of a function
1146 inFunction--;
1148 if (ch == '(')
1149 { // start of function
1150 inFunction++;
1152 if (!inFunction && ch == ',')
1153 { // not in a function, so a comma signifies the end of this parameter
1154 if (whiteSpacePos)
1155 parameter.resize(whiteSpacePos);
1156 // trim off start and end quotes
1157 if (parameter.length() > 1 && parameter[0] == '"' && parameter[parameter.length() - 1] == '"')
1158 parameter = parameter.substr(1, parameter.length() - 2);
1159 else if (parameter.length() > 3 && parameter[parameter.length() - 1] == '"')
1161 // check name="value" style param.
1162 size_t quotaPos = parameter.find('"');
1163 if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=')
1165 parameter.erase(parameter.length() - 1);
1166 parameter.erase(quotaPos);
1169 parameters.push_back(parameter);
1170 parameter.clear();
1171 whiteSpacePos = 0;
1172 continue;
1175 if ((ch == '"' || ch == '\\') && escaped)
1176 { // escaped quote or backslash
1177 parameter[parameter.size()-1] = ch;
1178 continue;
1180 // whitespace handling - we skip any whitespace at the left or right of an unquoted parameter
1181 if (ch == ' ' && !inQuotes)
1183 if (parameter.empty()) // skip whitespace on left
1184 continue;
1185 if (!whiteSpacePos) // make a note of where whitespace starts on the right
1186 whiteSpacePos = parameter.size();
1188 else
1189 whiteSpacePos = 0;
1190 parameter += ch;
1192 if (inFunction || inQuotes)
1193 CLog::Log(LOGWARNING, "{}({}) - end of string while searching for ) or \"", __FUNCTION__,
1194 paramString);
1195 if (whiteSpacePos)
1196 parameter.erase(whiteSpacePos);
1197 // trim off start and end quotes
1198 if (parameter.size() > 1 && parameter[0] == '"' && parameter[parameter.size() - 1] == '"')
1199 parameter = parameter.substr(1,parameter.size() - 2);
1200 else if (parameter.size() > 3 && parameter[parameter.size() - 1] == '"')
1202 // check name="value" style param.
1203 size_t quotaPos = parameter.find('"');
1204 if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=')
1206 parameter.erase(parameter.length() - 1);
1207 parameter.erase(quotaPos);
1210 if (!parameter.empty() || parameters.size())
1211 parameters.push_back(parameter);
1214 int CUtil::GetMatchingSource(const std::string& strPath1, VECSOURCES& VECSOURCES, bool& bIsSourceName)
1216 if (strPath1.empty())
1217 return -1;
1219 // copy as we may change strPath
1220 std::string strPath = strPath1;
1222 // Check for special protocols
1223 CURL checkURL(strPath);
1225 if (StringUtils::StartsWith(strPath, "special://skin/"))
1226 return 1;
1228 // do not return early if URL protocol is "plugin"
1229 // since video- and/or audio-plugins can be configured as mediasource
1231 // stack://
1232 if (checkURL.IsProtocol("stack"))
1233 strPath.erase(0, 8); // remove the stack protocol
1235 if (checkURL.IsProtocol("shout"))
1236 strPath = checkURL.GetHostName();
1238 if (checkURL.IsProtocol("multipath"))
1239 strPath = CMultiPathDirectory::GetFirstPath(strPath);
1241 bIsSourceName = false;
1242 int iIndex = -1;
1244 // we first test the NAME of a source
1245 for (int i = 0; i < (int)VECSOURCES.size(); ++i)
1247 const CMediaSource &share = VECSOURCES[i];
1248 std::string strName = share.strName;
1250 // special cases for dvds
1251 if (URIUtils::IsOnDVD(share.strPath))
1253 if (URIUtils::IsOnDVD(strPath))
1254 return i;
1256 // not a path, so we need to modify the source name
1257 // since we add the drive status and disc name to the source
1258 // "Name (Drive Status/Disc Name)"
1259 size_t iPos = strName.rfind('(');
1260 if (iPos != std::string::npos && iPos > 1)
1261 strName.resize(iPos - 1);
1263 if (StringUtils::EqualsNoCase(strPath, strName))
1265 bIsSourceName = true;
1266 return i;
1270 // now test the paths
1272 // remove user details, and ensure path only uses forward slashes
1273 // and ends with a trailing slash so as not to match a substring
1274 CURL urlDest(strPath);
1275 urlDest.SetOptions("");
1276 urlDest.SetProtocolOptions("");
1277 std::string strDest = urlDest.GetWithoutUserDetails();
1278 ForceForwardSlashes(strDest);
1279 if (!URIUtils::HasSlashAtEnd(strDest))
1280 strDest += "/";
1282 size_t iLength = 0;
1283 size_t iLenPath = strDest.size();
1284 for (int i = 0; i < (int)VECSOURCES.size(); ++i)
1286 const CMediaSource &share = VECSOURCES[i];
1288 // does it match a source name?
1289 if (share.strPath.substr(0,8) == "shout://")
1291 CURL url(share.strPath);
1292 if (strPath == url.GetHostName())
1293 return i;
1296 // doesn't match a name, so try the source path
1297 std::vector<std::string> vecPaths;
1299 // add any concatenated paths if they exist
1300 if (!share.vecPaths.empty())
1301 vecPaths = share.vecPaths;
1303 // add the actual share path at the front of the vector
1304 vecPaths.insert(vecPaths.begin(), share.strPath);
1306 // test each path
1307 for (const auto &path : vecPaths)
1309 // remove user details, and ensure path only uses forward slashes
1310 // and ends with a trailing slash so as not to match a substring
1311 CURL urlShare(path);
1312 urlShare.SetOptions("");
1313 urlShare.SetProtocolOptions("");
1314 std::string strShare = urlShare.GetWithoutUserDetails();
1315 ForceForwardSlashes(strShare);
1316 if (!URIUtils::HasSlashAtEnd(strShare))
1317 strShare += "/";
1318 size_t iLenShare = strShare.size();
1320 if ((iLenPath >= iLenShare) && StringUtils::StartsWithNoCase(strDest, strShare) && (iLenShare > iLength))
1322 // if exact match, return it immediately
1323 if (iLenPath == iLenShare)
1325 // if the path EXACTLY matches an item in a concatenated path
1326 // set source name to true to load the full virtualpath
1327 bIsSourceName = false;
1328 if (vecPaths.size() > 1)
1329 bIsSourceName = true;
1330 return i;
1332 iIndex = i;
1333 iLength = iLenShare;
1338 // return the index of the share with the longest match
1339 if (iIndex == -1)
1342 // rar:// and zip://
1343 // if archive wasn't mounted, look for a matching share for the archive instead
1344 if( StringUtils::StartsWithNoCase(strPath, "rar://") || StringUtils::StartsWithNoCase(strPath, "zip://") )
1346 // get the hostname portion of the url since it contains the archive file
1347 strPath = checkURL.GetHostName();
1349 bIsSourceName = false;
1350 bool bDummy;
1351 return GetMatchingSource(strPath, VECSOURCES, bDummy);
1354 CLog::Log(LOGDEBUG, "CUtil::GetMatchingSource: no matching source found for [{}]", strPath1);
1356 return iIndex;
1359 std::string CUtil::TranslateSpecialSource(const std::string &strSpecial)
1361 if (!strSpecial.empty() && strSpecial[0] == '$')
1363 if (StringUtils::StartsWithNoCase(strSpecial, "$home"))
1364 return URIUtils::AddFileToFolder("special://home/", strSpecial.substr(5));
1365 else if (StringUtils::StartsWithNoCase(strSpecial, "$subtitles"))
1366 return URIUtils::AddFileToFolder("special://subtitles/", strSpecial.substr(10));
1367 else if (StringUtils::StartsWithNoCase(strSpecial, "$userdata"))
1368 return URIUtils::AddFileToFolder("special://userdata/", strSpecial.substr(9));
1369 else if (StringUtils::StartsWithNoCase(strSpecial, "$database"))
1370 return URIUtils::AddFileToFolder("special://database/", strSpecial.substr(9));
1371 else if (StringUtils::StartsWithNoCase(strSpecial, "$thumbnails"))
1372 return URIUtils::AddFileToFolder("special://thumbnails/", strSpecial.substr(11));
1373 else if (StringUtils::StartsWithNoCase(strSpecial, "$recordings"))
1374 return URIUtils::AddFileToFolder("special://recordings/", strSpecial.substr(11));
1375 else if (StringUtils::StartsWithNoCase(strSpecial, "$screenshots"))
1376 return URIUtils::AddFileToFolder("special://screenshots/", strSpecial.substr(12));
1377 else if (StringUtils::StartsWithNoCase(strSpecial, "$musicplaylists"))
1378 return URIUtils::AddFileToFolder("special://musicplaylists/", strSpecial.substr(15));
1379 else if (StringUtils::StartsWithNoCase(strSpecial, "$videoplaylists"))
1380 return URIUtils::AddFileToFolder("special://videoplaylists/", strSpecial.substr(15));
1381 else if (StringUtils::StartsWithNoCase(strSpecial, "$cdrips"))
1382 return URIUtils::AddFileToFolder("special://cdrips/", strSpecial.substr(7));
1383 // this one will be removed post 2.0
1384 else if (StringUtils::StartsWithNoCase(strSpecial, "$playlists"))
1385 return URIUtils::AddFileToFolder(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), strSpecial.substr(10));
1387 return strSpecial;
1390 std::string CUtil::MusicPlaylistsLocation()
1392 const std::string path = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH);
1393 std::vector<std::string> vec;
1394 vec.push_back(URIUtils::AddFileToFolder(path, "music"));
1395 vec.push_back(URIUtils::AddFileToFolder(path, "mixed"));
1396 return XFILE::CMultiPathDirectory::ConstructMultiPath(vec);
1399 std::string CUtil::VideoPlaylistsLocation()
1401 const std::string path = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH);
1402 std::vector<std::string> vec;
1403 vec.push_back(URIUtils::AddFileToFolder(path, "video"));
1404 vec.push_back(URIUtils::AddFileToFolder(path, "mixed"));
1405 return XFILE::CMultiPathDirectory::ConstructMultiPath(vec);
1408 void CUtil::DeleteMusicDatabaseDirectoryCache()
1410 CUtil::DeleteDirectoryCache("mdb-");
1411 CUtil::DeleteDirectoryCache("sp-"); // overkill as it will delete video smartplaylists, but as we can't differentiate based on URL...
1414 void CUtil::DeleteVideoDatabaseDirectoryCache()
1416 CUtil::DeleteDirectoryCache("vdb-");
1417 CUtil::DeleteDirectoryCache("sp-"); // overkill as it will delete music smartplaylists, but as we can't differentiate based on URL...
1420 void CUtil::DeleteDirectoryCache(const std::string &prefix)
1422 std::string searchPath = "special://temp/";
1423 CFileItemList items;
1424 if (!XFILE::CDirectory::GetDirectory(searchPath, items, ".fi", DIR_FLAG_NO_FILE_DIRS))
1425 return;
1427 for (const auto &item : items)
1429 if (item->m_bIsFolder)
1430 continue;
1431 std::string fileName = URIUtils::GetFileName(item->GetPath());
1432 if (StringUtils::StartsWith(fileName, prefix))
1433 XFILE::CFile::Delete(item->GetPath());
1438 void CUtil::GetRecursiveListing(const std::string& strPath, CFileItemList& items, const std::string& strMask, unsigned int flags /* = DIR_FLAG_DEFAULTS */)
1440 CFileItemList myItems;
1441 CDirectory::GetDirectory(strPath,myItems,strMask,flags);
1442 for (const auto &item : myItems)
1444 if (item->m_bIsFolder)
1445 CUtil::GetRecursiveListing(item->GetPath(),items,strMask,flags);
1446 else
1447 items.Add(item);
1451 void CUtil::GetRecursiveDirsListing(const std::string& strPath, CFileItemList& item, unsigned int flags /* = DIR_FLAG_DEFAULTS */)
1453 CFileItemList myItems;
1454 CDirectory::GetDirectory(strPath,myItems,"",flags);
1455 for (const auto &i : myItems)
1457 if (i->m_bIsFolder && !i->IsPath(".."))
1459 item.Add(i);
1460 CUtil::GetRecursiveDirsListing(i->GetPath(),item,flags);
1465 void CUtil::ForceForwardSlashes(std::string& strPath)
1467 size_t iPos = strPath.rfind('\\');
1468 while (iPos != std::string::npos)
1470 strPath.at(iPos) = '/';
1471 iPos = strPath.rfind('\\');
1475 double CUtil::AlbumRelevance(const std::string& strAlbumTemp1, const std::string& strAlbum1, const std::string& strArtistTemp1, const std::string& strArtist1)
1477 // case-insensitive fuzzy string comparison on the album and artist for relevance
1478 // weighting is identical, both album and artist are 50% of the total relevance
1479 // a missing artist means the maximum relevance can only be 0.50
1480 std::string strAlbumTemp = strAlbumTemp1;
1481 StringUtils::ToLower(strAlbumTemp);
1482 std::string strAlbum = strAlbum1;
1483 StringUtils::ToLower(strAlbum);
1484 double fAlbumPercentage = fstrcmp(strAlbumTemp.c_str(), strAlbum.c_str());
1485 double fArtistPercentage = 0.0;
1486 if (!strArtist1.empty())
1488 std::string strArtistTemp = strArtistTemp1;
1489 StringUtils::ToLower(strArtistTemp);
1490 std::string strArtist = strArtist1;
1491 StringUtils::ToLower(strArtist);
1492 fArtistPercentage = fstrcmp(strArtistTemp.c_str(), strArtist.c_str());
1494 double fRelevance = fAlbumPercentage * 0.5 + fArtistPercentage * 0.5;
1495 return fRelevance;
1498 bool CUtil::MakeShortenPath(std::string StrInput, std::string& StrOutput, size_t iTextMaxLength)
1500 size_t iStrInputSize = StrInput.size();
1501 if(iStrInputSize <= 0 || iTextMaxLength >= iStrInputSize)
1503 StrOutput = StrInput;
1504 return true;
1507 char cDelim = '\0';
1508 size_t nGreaterDelim, nPos;
1510 nPos = StrInput.find_last_of( '\\' );
1511 if (nPos != std::string::npos)
1512 cDelim = '\\';
1513 else
1515 nPos = StrInput.find_last_of( '/' );
1516 if (nPos != std::string::npos)
1517 cDelim = '/';
1519 if ( cDelim == '\0' )
1520 return false;
1522 if (nPos == StrInput.size() - 1)
1524 StrInput.erase(StrInput.size() - 1);
1525 nPos = StrInput.find_last_of(cDelim);
1527 while( iTextMaxLength < iStrInputSize )
1529 nPos = StrInput.find_last_of( cDelim, nPos );
1530 nGreaterDelim = nPos;
1532 if (nPos == std::string::npos || nPos == 0)
1533 break;
1535 nPos = StrInput.find_last_of( cDelim, nPos - 1 );
1537 if ( nPos == std::string::npos )
1538 break;
1539 if ( nGreaterDelim > nPos )
1540 StrInput.replace( nPos + 1, nGreaterDelim - nPos - 1, ".." );
1541 iStrInputSize = StrInput.size();
1543 // replace any additional /../../ with just /../ if necessary
1544 std::string replaceDots = StringUtils::Format("..{}..", cDelim);
1545 while (StrInput.size() > (unsigned int)iTextMaxLength)
1546 if (!StringUtils::Replace(StrInput, replaceDots, ".."))
1547 break;
1548 // finally, truncate our string to force inside our max text length,
1549 // replacing the last 2 characters with ".."
1551 // eg end up with:
1552 // "smb://../Playboy Swimsuit Cal.."
1553 if (iTextMaxLength > 2 && StrInput.size() > (unsigned int)iTextMaxLength)
1555 StrInput.erase(iTextMaxLength - 2);
1556 StrInput += "..";
1558 StrOutput = StrInput;
1559 return true;
1562 bool CUtil::SupportsWriteFileOperations(const std::string& strPath)
1564 // currently only hd, smb, nfs and dav support delete and rename
1565 if (URIUtils::IsHD(strPath))
1566 return true;
1567 if (URIUtils::IsSmb(strPath))
1568 return true;
1569 if (URIUtils::IsPVRRecording(strPath))
1570 return CPVRDirectory::SupportsWriteFileOperations(strPath);
1571 if (URIUtils::IsNfs(strPath))
1572 return true;
1573 if (URIUtils::IsDAV(strPath))
1574 return true;
1575 if (URIUtils::IsStack(strPath))
1576 return SupportsWriteFileOperations(CStackDirectory::GetFirstStackedFile(strPath));
1577 if (URIUtils::IsMultiPath(strPath))
1578 return CMultiPathDirectory::SupportsWriteFileOperations(strPath);
1580 if (CServiceBroker::IsAddonInterfaceUp())
1582 CURL url(strPath);
1583 for (const auto& addon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
1585 const auto& info = addon->GetProtocolInfo();
1586 auto prots = StringUtils::Split(info.type, "|");
1587 if (info.supportWrite &&
1588 std::find(prots.begin(), prots.end(), url.GetProtocol()) != prots.end())
1589 return true;
1593 return false;
1596 bool CUtil::SupportsReadFileOperations(const std::string& strPath)
1598 return !URIUtils::IsVideoDb(strPath);
1601 std::string CUtil::GetDefaultFolderThumb(const std::string &folderThumb)
1603 if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(folderThumb))
1604 return folderThumb;
1605 return "";
1608 void CUtil::GetSkinThemes(std::vector<std::string>& vecTheme)
1610 static const std::string TexturesXbt = "Textures.xbt";
1612 std::string strPath = URIUtils::AddFileToFolder(CServiceBroker::GetWinSystem()->GetGfxContext().GetMediaDir(), "media");
1613 CFileItemList items;
1614 CDirectory::GetDirectory(strPath, items, "", DIR_FLAG_DEFAULTS);
1615 // Search for Themes in the Current skin!
1616 for (const auto &pItem : items)
1618 if (!pItem->m_bIsFolder)
1620 std::string strExtension = URIUtils::GetExtension(pItem->GetPath());
1621 std::string strLabel = pItem->GetLabel();
1622 if ((strExtension == ".xbt" && !StringUtils::EqualsNoCase(strLabel, TexturesXbt)))
1623 vecTheme.push_back(StringUtils::Left(strLabel, strLabel.size() - strExtension.size()));
1625 else
1627 // check if this is an xbt:// VFS path
1628 CURL itemUrl(pItem->GetPath());
1629 if (!itemUrl.IsProtocol("xbt") || !itemUrl.GetFileName().empty())
1630 continue;
1632 std::string strLabel = URIUtils::GetFileName(itemUrl.GetHostName());
1633 if (!StringUtils::EqualsNoCase(strLabel, TexturesXbt))
1634 vecTheme.push_back(StringUtils::Left(strLabel, strLabel.size() - URIUtils::GetExtension(strLabel).size()));
1637 std::sort(vecTheme.begin(), vecTheme.end(), sortstringbyname());
1640 void CUtil::InitRandomSeed()
1642 // Init random seed
1643 auto now = std::chrono::steady_clock::now();
1644 auto seed = now.time_since_epoch();
1646 srand(static_cast<unsigned int>(seed.count()));
1649 #if defined(TARGET_POSIX) && !defined(TARGET_DARWIN_TVOS)
1650 bool CUtil::RunCommandLine(const std::string& cmdLine, bool waitExit)
1652 std::vector<std::string> args = StringUtils::Split(cmdLine, ",");
1654 // Strip quotes and whitespace around the arguments, or exec will fail.
1655 // This allows the python invocation to be written more naturally with any amount of whitespace around the args.
1656 // But it's still limited, for example quotes inside the strings are not expanded, etc.
1657 //! @todo Maybe some python library routine can parse this more properly ?
1658 for (std::vector<std::string>::iterator it = args.begin(); it != args.end(); ++it)
1660 size_t pos;
1661 pos = it->find_first_not_of(" \t\n\"'");
1662 if (pos != std::string::npos)
1664 it->erase(0, pos);
1667 pos = it->find_last_not_of(" \t\n\"'"); // if it returns npos we'll end up with an empty string which is OK
1669 it->erase(++pos, it->size());
1673 return Command(args, waitExit);
1676 bool CUtil::Command(const std::vector<std::string>& arrArgs, bool waitExit)
1678 #ifdef _DEBUG
1679 printf("Executing: ");
1680 for (const auto &arg : arrArgs)
1681 printf("%s ", arg.c_str());
1682 printf("\n");
1683 #endif
1685 pid_t child = fork();
1686 int n = 0;
1687 if (child == 0)
1689 if (!waitExit)
1691 // fork again in order not to leave a zombie process
1692 child = fork();
1693 if (child == -1)
1694 _exit(2);
1695 else if (child != 0)
1696 _exit(0);
1698 close(0);
1699 close(1);
1700 close(2);
1701 if (!arrArgs.empty())
1703 char **args = (char **)alloca(sizeof(char *) * (arrArgs.size() + 3));
1704 memset(args, 0, (sizeof(char *) * (arrArgs.size() + 3)));
1705 for (size_t i=0; i<arrArgs.size(); i++)
1706 args[i] = const_cast<char *>(arrArgs[i].c_str());
1707 execvp(args[0], args);
1710 else
1712 waitpid(child, &n, 0);
1715 return (waitExit) ? (WEXITSTATUS(n) == 0) : true;
1717 #endif
1719 int CUtil::LookupRomanDigit(char roman_digit)
1721 switch (roman_digit)
1723 case 'i':
1724 case 'I':
1725 return 1;
1726 case 'v':
1727 case 'V':
1728 return 5;
1729 case 'x':
1730 case 'X':
1731 return 10;
1732 case 'l':
1733 case 'L':
1734 return 50;
1735 case 'c':
1736 case 'C':
1737 return 100;
1738 case 'd':
1739 case 'D':
1740 return 500;
1741 case 'm':
1742 case 'M':
1743 return 1000;
1744 default:
1745 return 0;
1749 int CUtil::TranslateRomanNumeral(const char* roman_numeral)
1752 int decimal = -1;
1754 if (roman_numeral && roman_numeral[0])
1756 int temp_sum = 0,
1757 last = 0,
1758 repeat = 0,
1759 trend = 1;
1760 decimal = 0;
1761 while (*roman_numeral)
1763 int digit = CUtil::LookupRomanDigit(*roman_numeral);
1764 int test = last;
1766 // General sanity checks
1768 // numeral not in LUT
1769 if (!digit)
1770 return -1;
1772 while (test > 5)
1773 test /= 10;
1775 // N = 10^n may not precede (N+1) > 10^(N+1)
1776 if (test == 1 && digit > last * 10)
1777 return -1;
1779 // N = 5*10^n may not precede (N+1) >= N
1780 if (test == 5 && digit >= last)
1781 return -1;
1783 // End general sanity checks
1785 if (last < digit)
1787 // smaller numerals may not repeat before a larger one
1788 if (repeat)
1789 return -1;
1791 temp_sum += digit;
1793 repeat = 0;
1794 trend = 0;
1796 else if (last == digit)
1798 temp_sum += digit;
1799 repeat++;
1800 trend = 1;
1802 else
1804 if (!repeat)
1805 decimal += 2 * last - temp_sum;
1806 else
1807 decimal += temp_sum;
1809 temp_sum = digit;
1811 trend = 1;
1812 repeat = 0;
1814 // Post general sanity checks
1816 // numerals may not repeat more than thrice
1817 if (repeat == 3)
1818 return -1;
1820 last = digit;
1821 roman_numeral++;
1824 if (trend)
1825 decimal += temp_sum;
1826 else
1827 decimal += 2 * last - temp_sum;
1829 return decimal;
1832 std::string CUtil::ResolveExecutablePath()
1834 std::string strExecutablePath;
1835 #ifdef TARGET_WINDOWS
1836 static const size_t bufSize = MAX_PATH * 2;
1837 wchar_t* buf = new wchar_t[bufSize];
1838 buf[0] = 0;
1839 ::GetModuleFileNameW(0, buf, bufSize);
1840 buf[bufSize-1] = 0;
1841 g_charsetConverter.wToUTF8(buf,strExecutablePath);
1842 delete[] buf;
1843 #elif defined(TARGET_DARWIN)
1844 char given_path[2*MAXPATHLEN];
1845 size_t path_size =2*MAXPATHLEN;
1847 CDarwinUtils::GetExecutablePath(given_path, &path_size);
1848 strExecutablePath = given_path;
1849 #elif defined(TARGET_FREEBSD)
1850 char buf[PATH_MAX];
1851 size_t buflen;
1852 int mib[4];
1854 mib[0] = CTL_KERN;
1855 mib[1] = KERN_PROC;
1856 mib[2] = KERN_PROC_PATHNAME;
1857 mib[3] = getpid();
1859 buflen = sizeof(buf) - 1;
1860 if(sysctl(mib, 4, buf, &buflen, NULL, 0) < 0)
1861 strExecutablePath = "";
1862 else
1863 strExecutablePath = buf;
1864 #elif defined(TARGET_ANDROID)
1865 strExecutablePath = CXBMCApp::getApplicationInfo().nativeLibraryDir;
1867 std::string appName = CCompileInfo::GetAppName();
1868 std::string libName = "lib" + appName + ".so";
1869 StringUtils::ToLower(libName);
1870 strExecutablePath += "/" + libName;
1871 #else
1872 /* Get our PID and build the name of the link in /proc */
1873 pid_t pid = getpid();
1874 char linkname[64]; /* /proc/<pid>/exe */
1875 snprintf(linkname, sizeof(linkname), "/proc/%i/exe", pid);
1877 /* Now read the symbolic link */
1878 char buf[PATH_MAX + 1];
1879 buf[0] = 0;
1881 int ret = readlink(linkname, buf, sizeof(buf) - 1);
1882 if (ret != -1)
1883 buf[ret] = 0;
1885 strExecutablePath = buf;
1886 #endif
1887 return strExecutablePath;
1890 std::string CUtil::GetFrameworksPath(bool forPython)
1892 std::string strFrameworksPath;
1893 #if defined(TARGET_DARWIN)
1894 strFrameworksPath = CDarwinUtils::GetFrameworkPath(forPython);
1895 #endif
1896 return strFrameworksPath;
1899 void CUtil::GetVideoBasePathAndFileName(const std::string& videoPath, std::string& basePath, std::string& videoFileName)
1901 CFileItem item(videoPath, false);
1902 videoFileName = URIUtils::ReplaceExtension(URIUtils::GetFileName(videoPath), "");
1904 if (item.HasVideoInfoTag())
1905 basePath = item.GetVideoInfoTag()->m_basePath;
1907 if (basePath.empty() && item.IsOpticalMediaFile())
1908 basePath = item.GetLocalMetadataPath();
1910 CURL url(videoPath);
1911 if (basePath.empty() && url.IsProtocol("bluray"))
1913 basePath = url.GetHostName();
1914 videoFileName = URIUtils::ReplaceExtension(GetTitleFromPath(url.GetHostName()), "");
1916 url = CURL(url.GetHostName());
1917 if (url.IsProtocol("udf"))
1918 basePath = URIUtils::GetParentPath(url.GetHostName());
1921 if (basePath.empty())
1922 basePath = URIUtils::GetBasePath(videoPath);
1925 void CUtil::GetItemsToScan(const std::string& videoPath,
1926 const std::string& item_exts,
1927 const std::vector<std::string>& sub_dirs,
1928 CFileItemList& items)
1930 int flags = DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO;
1932 if (!videoPath.empty())
1933 CDirectory::GetDirectory(videoPath, items, item_exts, flags);
1935 std::vector<std::string> additionalPaths;
1936 for (const auto &item : items)
1938 for (const auto& subdir : sub_dirs)
1940 if (StringUtils::EqualsNoCase(item->GetLabel(), subdir))
1941 additionalPaths.push_back(item->GetPath());
1945 for (std::vector<std::string>::const_iterator it = additionalPaths.begin(); it != additionalPaths.end(); ++it)
1947 CFileItemList moreItems;
1948 CDirectory::GetDirectory(*it, moreItems, item_exts, flags);
1949 items.Append(moreItems);
1954 void CUtil::ScanPathsForAssociatedItems(const std::string& videoName,
1955 const CFileItemList& items,
1956 const std::vector<std::string>& item_exts,
1957 std::vector<std::string>& associatedFiles)
1959 for (const auto &pItem : items)
1961 if (pItem->m_bIsFolder)
1962 continue;
1964 std::string strCandidate = URIUtils::GetFileName(pItem->GetPath());
1966 // skip duplicates
1967 if (std::find(associatedFiles.begin(), associatedFiles.end(), pItem->GetPath()) != associatedFiles.end())
1968 continue;
1970 URIUtils::RemoveExtension(strCandidate);
1971 // NOTE: We don't know if one of videoName or strCandidate is URL-encoded and the other is not, so try both
1972 if (StringUtils::StartsWithNoCase(strCandidate, videoName) || (StringUtils::StartsWithNoCase(strCandidate, CURL::Decode(videoName))))
1974 if (URIUtils::IsRAR(pItem->GetPath()) || URIUtils::IsZIP(pItem->GetPath()))
1975 CUtil::ScanArchiveForAssociatedItems(pItem->GetPath(), "", item_exts, associatedFiles);
1976 else
1978 associatedFiles.push_back(pItem->GetPath());
1979 CLog::Log(LOGINFO, "{}: found associated file {}", __FUNCTION__,
1980 CURL::GetRedacted(pItem->GetPath()));
1983 else
1985 if (URIUtils::IsRAR(pItem->GetPath()) || URIUtils::IsZIP(pItem->GetPath()))
1986 CUtil::ScanArchiveForAssociatedItems(pItem->GetPath(), videoName, item_exts, associatedFiles);
1991 int CUtil::ScanArchiveForAssociatedItems(const std::string& strArchivePath,
1992 const std::string& videoNameNoExt,
1993 const std::vector<std::string>& item_exts,
1994 std::vector<std::string>& associatedFiles)
1996 CLog::LogF(LOGDEBUG, "Scanning archive {}", CURL::GetRedacted(strArchivePath));
1997 int nItemsAdded = 0;
1998 CFileItemList ItemList;
2000 // zip only gets the root dir
2001 if (URIUtils::HasExtension(strArchivePath, ".zip"))
2003 CURL pathToUrl(strArchivePath);
2004 CURL zipURL = URIUtils::CreateArchivePath("zip", pathToUrl, "");
2005 if (!CDirectory::GetDirectory(zipURL, ItemList, "", DIR_FLAG_NO_FILE_DIRS))
2006 return false;
2008 else if (URIUtils::HasExtension(strArchivePath, ".rar"))
2010 CURL pathToUrl(strArchivePath);
2011 CURL rarURL = URIUtils::CreateArchivePath("rar", pathToUrl, "");
2012 if (!CDirectory::GetDirectory(rarURL, ItemList, "", DIR_FLAG_NO_FILE_DIRS))
2013 return false;
2015 for (const auto &item : ItemList)
2017 std::string strPathInRar = item->GetPath();
2018 std::string strExt = URIUtils::GetExtension(strPathInRar);
2020 // Check another archive in archive
2021 if (strExt == ".zip" || strExt == ".rar")
2023 nItemsAdded +=
2024 ScanArchiveForAssociatedItems(strPathInRar, videoNameNoExt, item_exts, associatedFiles);
2025 continue;
2028 // check that the found filename matches the movie filename
2029 size_t fnl = videoNameNoExt.size();
2030 // NOTE: We don't know if videoNameNoExt is URL-encoded, so try both
2031 if (fnl &&
2032 !(StringUtils::StartsWithNoCase(URIUtils::GetFileName(strPathInRar), videoNameNoExt) ||
2033 StringUtils::StartsWithNoCase(URIUtils::GetFileName(strPathInRar), CURL::Decode(videoNameNoExt))))
2034 continue;
2036 for (const auto& ext : item_exts)
2038 if (StringUtils::EqualsNoCase(strExt, ext))
2040 CLog::Log(LOGINFO, "{}: found associated file {}", __FUNCTION__,
2041 CURL::GetRedacted(strPathInRar));
2042 associatedFiles.push_back(strPathInRar);
2043 nItemsAdded++;
2044 break;
2049 return nItemsAdded;
2052 void CUtil::ScanForExternalSubtitles(const std::string& strMovie, std::vector<std::string>& vecSubtitles)
2054 auto start = std::chrono::steady_clock::now();
2056 CFileItem item(strMovie, false);
2057 if ((item.IsInternetStream() && !URIUtils::IsOnLAN(item.GetDynPath()))
2058 || item.IsPlayList()
2059 || item.IsLiveTV()
2060 || !item.IsVideo())
2061 return;
2063 CLog::Log(LOGDEBUG, "{}: Searching for subtitles...", __FUNCTION__);
2065 std::string strBasePath;
2066 std::string strSubtitle;
2068 GetVideoBasePathAndFileName(strMovie, strBasePath, strSubtitle);
2070 CFileItemList items;
2071 const std::vector<std::string> common_sub_dirs = { "subs", "subtitles", "vobsubs", "sub", "vobsub", "subtitle" };
2072 const std::string subtitleExtensions = CServiceBroker::GetFileExtensionProvider().GetSubtitleExtensions();
2073 GetItemsToScan(strBasePath, subtitleExtensions, common_sub_dirs, items);
2075 const std::string customPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SUBTITLES_CUSTOMPATH);
2077 if (!CMediaSettings::GetInstance().GetAdditionalSubtitleDirectoryChecked() && !customPath.empty()) // to avoid checking non-existent directories (network) every time..
2079 if (!CServiceBroker::GetNetwork().IsAvailable() && !URIUtils::IsHD(customPath))
2081 CLog::Log(LOGINFO, "CUtil::CacheSubtitles: disabling alternate subtitle directory for this session, it's inaccessible");
2082 CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(-1); // disabled
2084 else if (!CDirectory::Exists(customPath))
2086 CLog::Log(LOGINFO, "CUtil::CacheSubtitles: disabling alternate subtitle directory for this session, it's nonexistent");
2087 CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(-1); // disabled
2090 CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(1);
2093 std::vector<std::string> strLookInPaths;
2094 // this is last because we dont want to check any common subdirs or cd-dirs in the alternate <subtitles> dir.
2095 if (CMediaSettings::GetInstance().GetAdditionalSubtitleDirectoryChecked() == 1)
2097 std::string strPath2 = customPath;
2098 URIUtils::AddSlashAtEnd(strPath2);
2099 strLookInPaths.push_back(strPath2);
2102 int flags = DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO;
2103 for (const std::string& path : strLookInPaths)
2105 CFileItemList moreItems;
2106 CDirectory::GetDirectory(path, moreItems, subtitleExtensions, flags);
2107 items.Append(moreItems);
2110 std::vector<std::string> exts = StringUtils::Split(subtitleExtensions, '|');
2111 exts.erase(std::remove(exts.begin(), exts.end(), ".zip"), exts.end());
2112 exts.erase(std::remove(exts.begin(), exts.end(), ".rar"), exts.end());
2114 ScanPathsForAssociatedItems(strSubtitle, items, exts, vecSubtitles);
2116 size_t iSize = vecSubtitles.size();
2117 for (size_t i = 0; i < iSize; i++)
2119 if (URIUtils::HasExtension(vecSubtitles[i], ".smi"))
2121 //Cache multi-language sami subtitle
2122 CDVDSubtitleStream stream;
2123 if (stream.Open(vecSubtitles[i]))
2125 CDVDSubtitleTagSami TagConv;
2126 TagConv.LoadHead(&stream);
2127 if (TagConv.m_Langclass.size() >= 2)
2129 for (const auto &lang : TagConv.m_Langclass)
2131 std::string strDest =
2132 StringUtils::Format("special://temp/subtitle.{}.{}.smi", lang.Name, i);
2133 if (CFile::Copy(vecSubtitles[i], strDest))
2135 CLog::Log(LOGINFO, " cached subtitle {}->{}", CURL::GetRedacted(vecSubtitles[i]),
2136 strDest);
2137 vecSubtitles.push_back(strDest);
2145 auto end = std::chrono::steady_clock::now();
2146 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
2147 CLog::Log(LOGDEBUG, "{}: END (total time: {} ms)", __FUNCTION__, duration.count());
2150 ExternalStreamInfo CUtil::GetExternalStreamDetailsFromFilename(const std::string& videoPath, const std::string& associatedFile)
2152 ExternalStreamInfo info;
2154 std::string videoBaseName = URIUtils::GetFileName(videoPath);
2155 URIUtils::RemoveExtension(videoBaseName);
2157 std::string toParse = URIUtils::GetFileName(associatedFile);
2158 URIUtils::RemoveExtension(toParse);
2160 // we check left part - if it's same as video base name - strip it
2161 if (StringUtils::StartsWithNoCase(toParse, videoBaseName))
2162 toParse = toParse.substr(videoBaseName.length());
2163 else if (URIUtils::GetExtension(associatedFile) == ".sub" && URIUtils::IsInArchive(associatedFile))
2165 // exclude parsing of vobsub file names that are embedded in an archive
2166 CLog::Log(LOGDEBUG, "{} - skipping archived vobsub filename parsing: {}", __FUNCTION__,
2167 CURL::GetRedacted(associatedFile));
2168 toParse.clear();
2171 // trim any non-alphanumeric char in the beginning
2172 std::string::iterator result = std::find_if(toParse.begin(), toParse.end(), StringUtils::isasciialphanum);
2174 std::string name;
2175 if (result != toParse.end()) // if we have anything to parse
2177 std::string inputString(result, toParse.end());
2178 std::string delimiters(" .-");
2179 std::vector<std::string> tokens;
2180 StringUtils::Tokenize(inputString, tokens, delimiters);
2182 for (auto it = tokens.rbegin(); it != tokens.rend(); ++it)
2184 // try to recognize a flag
2185 std::string flag_tmp(*it);
2186 StringUtils::ToLower(flag_tmp);
2187 if (!flag_tmp.compare("none"))
2189 info.flag |= StreamFlags::FLAG_NONE;
2190 continue;
2192 else if (!flag_tmp.compare("default"))
2194 info.flag |= StreamFlags::FLAG_DEFAULT;
2195 continue;
2197 else if (!flag_tmp.compare("forced"))
2199 info.flag |= StreamFlags::FLAG_FORCED;
2200 continue;
2203 if (info.language.empty())
2205 std::string langCode;
2206 // try to recognize language
2207 if (g_LangCodeExpander.ConvertToISO6392B(*it, langCode))
2209 info.language = langCode;
2210 continue;
2214 name = (*it) + " " + name;
2217 name += " ";
2218 name += g_localizeStrings.Get(21602); // External
2219 StringUtils::Trim(name);
2220 info.name = StringUtils::RemoveDuplicatedSpacesAndTabs(name);
2221 if (info.flag == 0)
2222 info.flag = StreamFlags::FLAG_NONE;
2224 CLog::Log(LOGDEBUG, "{} - Language = '{}' / Name = '{}' / Flag = '{}' from {}", __FUNCTION__,
2225 info.language, info.name, info.flag, CURL::GetRedacted(associatedFile));
2227 return info;
2230 /*! \brief in a vector of subtitles finds the corresponding .sub file for a given .idx file
2232 bool CUtil::FindVobSubPair(const std::vector<std::string>& vecSubtitles, const std::string& strIdxPath, std::string& strSubPath)
2234 if (URIUtils::HasExtension(strIdxPath, ".idx"))
2236 std::string strIdxFile;
2237 std::string strIdxDirectory;
2238 URIUtils::Split(strIdxPath, strIdxDirectory, strIdxFile);
2239 for (const auto &subtitlePath : vecSubtitles)
2241 std::string strSubFile;
2242 std::string strSubDirectory;
2243 URIUtils::Split(subtitlePath, strSubDirectory, strSubFile);
2244 if (URIUtils::IsInArchive(subtitlePath))
2245 strSubDirectory = CURL::Decode(strSubDirectory);
2246 if (URIUtils::HasExtension(strSubFile, ".sub") &&
2247 (URIUtils::PathEquals(URIUtils::ReplaceExtension(strIdxPath,""),
2248 URIUtils::ReplaceExtension(subtitlePath,"")) ||
2249 (strSubDirectory.size() >= 11 &&
2250 StringUtils::EqualsNoCase(strSubDirectory.substr(6, strSubDirectory.length()-11), URIUtils::ReplaceExtension(strIdxPath,"")))))
2252 strSubPath = subtitlePath;
2253 return true;
2257 return false;
2260 /*! \brief checks if in the vector of subtitles the given .sub file has a corresponding idx and hence is a vobsub file
2262 bool CUtil::IsVobSub(const std::vector<std::string>& vecSubtitles, const std::string& strSubPath)
2264 if (URIUtils::HasExtension(strSubPath, ".sub"))
2266 std::string strSubFile;
2267 std::string strSubDirectory;
2268 URIUtils::Split(strSubPath, strSubDirectory, strSubFile);
2269 if (URIUtils::IsInArchive(strSubPath))
2270 strSubDirectory = CURL::Decode(strSubDirectory);
2271 for (const auto &subtitlePath : vecSubtitles)
2273 std::string strIdxFile;
2274 std::string strIdxDirectory;
2275 URIUtils::Split(subtitlePath, strIdxDirectory, strIdxFile);
2276 if (URIUtils::HasExtension(strIdxFile, ".idx") &&
2277 (URIUtils::PathEquals(URIUtils::ReplaceExtension(subtitlePath,""),
2278 URIUtils::ReplaceExtension(strSubPath,"")) ||
2279 (strSubDirectory.size() >= 11 &&
2280 StringUtils::EqualsNoCase(strSubDirectory.substr(6, strSubDirectory.length()-11), URIUtils::ReplaceExtension(subtitlePath,"")))))
2281 return true;
2284 return false;
2287 /*! \brief find a plain or archived vobsub .sub file corresponding to an .idx file
2289 std::string CUtil::GetVobSubSubFromIdx(const std::string& vobSubIdx)
2291 std::string vobSub = URIUtils::ReplaceExtension(vobSubIdx, ".sub");
2293 // check if a .sub file exists in the same directory
2294 if (CFile::Exists(vobSub))
2296 return vobSub;
2299 // look inside a .rar or .zip in the same directory
2300 const std::string archTypes[] = { "rar", "zip" };
2301 std::string vobSubFilename = URIUtils::GetFileName(vobSub);
2302 for (const std::string& archType : archTypes)
2304 vobSub = URIUtils::CreateArchivePath(archType,
2305 CURL(URIUtils::ReplaceExtension(vobSubIdx, std::string(".") + archType)),
2306 vobSubFilename).Get();
2307 if (CFile::Exists(vobSub))
2308 return vobSub;
2311 return std::string();
2314 /*! \brief find a .idx file from a path of a plain or archived vobsub .sub file
2316 std::string CUtil::GetVobSubIdxFromSub(const std::string& vobSub)
2318 std::string vobSubIdx = URIUtils::ReplaceExtension(vobSub, ".idx");
2320 // check if a .idx file exists in the same directory
2321 if (CFile::Exists(vobSubIdx))
2323 return vobSubIdx;
2326 // look outside archive (usually .rar) if the .sub is inside one
2327 if (URIUtils::IsInArchive(vobSub))
2330 std::string archiveFile = URIUtils::GetDirectory(vobSub);
2331 std::string vobSubIdxDir = URIUtils::GetParentPath(archiveFile);
2333 if (!vobSubIdxDir.empty())
2335 std::string vobSubIdxFilename = URIUtils::GetFileName(vobSubIdx);
2336 std::string vobSubIdx = URIUtils::AddFileToFolder(vobSubIdxDir, vobSubIdxFilename);
2338 if (CFile::Exists(vobSubIdx))
2339 return vobSubIdx;
2343 return std::string();
2346 void CUtil::ScanForExternalAudio(const std::string& videoPath, std::vector<std::string>& vecAudio)
2348 CFileItem item(videoPath, false);
2349 if ( item.IsInternetStream()
2350 || item.IsPlayList()
2351 || item.IsLiveTV()
2352 || item.IsPVR()
2353 || !item.IsVideo())
2354 return;
2356 std::string strBasePath;
2357 std::string strAudio;
2359 GetVideoBasePathAndFileName(videoPath, strBasePath, strAudio);
2361 CFileItemList items;
2362 const std::vector<std::string> common_sub_dirs = { "audio", "tracks"};
2363 GetItemsToScan(strBasePath, CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), common_sub_dirs, items);
2365 std::vector<std::string> exts = StringUtils::Split(CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), "|");
2367 CVideoDatabase database;
2368 database.Open();
2369 bool useAllExternalAudio = database.GetUseAllExternalAudioForVideo(videoPath);
2371 if (useAllExternalAudio)
2373 for (const auto& audioItem : items.GetList())
2375 vecAudio.push_back(audioItem.get()->GetPath());
2378 else
2379 ScanPathsForAssociatedItems(strAudio, items, exts, vecAudio);
2382 bool CUtil::CanBindPrivileged()
2384 #ifdef TARGET_POSIX
2386 if (geteuid() == 0)
2387 return true; //root user can always bind to privileged ports
2389 #ifdef HAVE_LIBCAP
2391 //check if CAP_NET_BIND_SERVICE is enabled, this allows non-root users to bind to privileged ports
2392 bool canbind = false;
2393 cap_t capabilities = cap_get_proc();
2394 if (capabilities)
2396 cap_flag_value_t value;
2397 if (cap_get_flag(capabilities, CAP_NET_BIND_SERVICE, CAP_EFFECTIVE, &value) == 0)
2398 canbind = value;
2400 cap_free(capabilities);
2403 return canbind;
2405 #else //HAVE_LIBCAP
2407 return false;
2409 #endif //HAVE_LIBCAP
2411 #else //TARGET_POSIX
2413 return true;
2415 #endif //TARGET_POSIX
2418 bool CUtil::ValidatePort(int port)
2420 // check that it's a valid port
2421 #ifdef TARGET_POSIX
2422 if (!CUtil::CanBindPrivileged() && (port < 1024 || port > 65535))
2423 return false;
2424 else
2425 #endif
2426 if (port <= 0 || port > 65535)
2427 return false;
2429 return true;
2432 int CUtil::GetRandomNumber()
2434 #if !defined(TARGET_WINDOWS)
2435 return rand_r(&s_randomSeed);
2436 #else
2437 unsigned int number;
2438 if (rand_s(&number) == 0)
2439 return (int)number;
2441 return rand();
2442 #endif
2445 void CUtil::CopyUserDataIfNeeded(const std::string& strPath,
2446 const std::string& file,
2447 const std::string& destname)
2449 std::string destPath;
2450 if (destname.empty())
2451 destPath = URIUtils::AddFileToFolder(strPath, file);
2452 else
2453 destPath = URIUtils::AddFileToFolder(strPath, destname);
2455 if (!CFile::Exists(destPath))
2457 // need to copy it across
2458 std::string srcPath = URIUtils::AddFileToFolder("special://xbmc/userdata/", file);
2459 CFile::Copy(srcPath, destPath);