[PVR][Estuary] Timer settings dialog: Show client name in timer type selection dialog...
[xbmc.git] / xbmc / Util.cpp
blob50e7987d0a92f06728da983096dd69c55f8ea1b9
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/bionic_supplement/bionic_supplement.h"
29 #include "platform/android/activity/XBMCApp.h"
30 #include "CompileInfo.h"
31 #endif
32 #include "ServiceBroker.h"
33 #include "Util.h"
34 #include "addons/VFSEntry.h"
35 #include "filesystem/Directory.h"
36 #include "filesystem/MultiPathDirectory.h"
37 #include "filesystem/PVRDirectory.h"
38 #include "filesystem/RSSDirectory.h"
39 #include "filesystem/SpecialProtocol.h"
40 #include "filesystem/StackDirectory.h"
42 #include <algorithm>
43 #include <array>
44 #include <stdlib.h>
45 #ifdef HAS_UPNP
46 #include "filesystem/UPnPDirectory.h"
47 #endif
48 #include "profiles/ProfileManager.h"
49 #include "utils/RegExp.h"
50 #include "windowing/GraphicContext.h"
51 #include "guilib/TextureManager.h"
52 #include "storage/MediaManager.h"
53 #ifdef TARGET_WINDOWS
54 #include "utils/CharsetConverter.h"
55 #include "WIN32Util.h"
56 #endif
57 #if defined(TARGET_DARWIN)
58 #include "CompileInfo.h"
59 #include "platform/darwin/DarwinUtils.h"
60 #endif
61 #include "URL.h"
62 #include "cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.h"
63 #include "cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.h"
64 #include "filesystem/File.h"
65 #include "guilib/LocalizeStrings.h"
66 #include "platform/Environment.h"
67 #include "settings/AdvancedSettings.h"
68 #include "settings/MediaSettings.h"
69 #include "settings/Settings.h"
70 #include "settings/SettingsComponent.h"
71 #include "utils/Digest.h"
72 #include "utils/FileExtensionProvider.h"
73 #include "utils/FontUtils.h"
74 #include "utils/LangCodeExpander.h"
75 #include "utils/StringUtils.h"
76 #include "utils/TimeUtils.h"
77 #include "utils/URIUtils.h"
78 #include "utils/log.h"
79 #include "video/VideoDatabase.h"
80 #include "video/VideoInfoTag.h"
81 #ifdef HAVE_LIBCAP
82 #include <sys/capability.h>
83 #endif
85 #include "cores/VideoPlayer/DVDDemuxers/DVDDemux.h"
87 #include <fstrcmp.h>
89 #ifdef HAS_DVD_DRIVE
90 using namespace MEDIA_DETECT;
91 #endif
93 using namespace XFILE;
94 using namespace PLAYLIST;
95 using KODI::UTILITY::CDigest;
97 #if !defined(TARGET_WINDOWS)
98 unsigned int CUtil::s_randomSeed = time(NULL);
99 #endif
101 namespace
103 #ifdef TARGET_WINDOWS
104 bool IsDirectoryValidRoot(std::wstring path)
106 path += L"\\system\\settings\\settings.xml";
107 #if defined(TARGET_WINDOWS_STORE)
108 auto h = CreateFile2(path.c_str(), GENERIC_READ, 0, OPEN_EXISTING, NULL);
109 #else
110 auto h = CreateFileW(path.c_str(), GENERIC_READ, 0, nullptr,
111 OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
112 #endif
113 if (h != INVALID_HANDLE_VALUE)
115 CloseHandle(h);
116 return true;
119 return false;
122 std::string GetHomePath(const std::string& strTarget, std::string strPath)
124 std::wstring strPathW;
126 // Environment variable was set and we have a path
127 // Let's make sure it's not relative and test it
128 // so it's actually pointing to a directory containing
129 // our stuff
130 if (strPath.find("..") != std::string::npos)
132 //expand potential relative path to full path
133 g_charsetConverter.utf8ToW(strPath, strPathW, false);
134 CWIN32Util::AddExtraLongPathPrefix(strPathW);
135 auto bufSize = GetFullPathNameW(strPathW.c_str(), 0, nullptr, nullptr);
136 if (bufSize != 0)
138 auto buf = std::make_unique<wchar_t[]>(bufSize);
139 if (GetFullPathNameW(strPathW.c_str(), bufSize, buf.get(), nullptr) <= bufSize - 1)
141 strPathW = buf.get();
142 CWIN32Util::RemoveExtraLongPathPrefix(strPathW);
144 if (IsDirectoryValidRoot(strPathW))
146 g_charsetConverter.wToUTF8(strPathW, strPath);
147 return strPath;
153 // Okay se no environment variable is set, let's
154 // grab the executable path and check if it's being
155 // run from a directory containing our stuff
156 strPath = CUtil::ResolveExecutablePath();
157 auto last_sep = strPath.find_last_of(PATH_SEPARATOR_CHAR);
158 if (last_sep != std::string::npos)
159 strPath = strPath.substr(0, last_sep);
161 g_charsetConverter.utf8ToW(strPath, strPathW);
162 if (IsDirectoryValidRoot(strPathW))
163 return strPath;
165 // Still nothing, let's check the current working
166 // directory and see if it points to a directory
167 // with our stuff in it. This bit should never be
168 // needed when running on a users system, it's intended
169 // to make our dev environment easier.
170 auto bufSize = GetCurrentDirectoryW(0, nullptr);
171 if (bufSize > 0)
173 auto buf = std::make_unique<wchar_t[]>(bufSize);
174 if (0 != GetCurrentDirectoryW(bufSize, buf.get()))
176 std::string currentDirectory;
177 std::wstring currentDirectoryW(buf.get());
178 CWIN32Util::RemoveExtraLongPathPrefix(currentDirectoryW);
180 if (IsDirectoryValidRoot(currentDirectoryW))
182 g_charsetConverter.wToUTF8(currentDirectoryW, currentDirectory);
183 return currentDirectory;
188 // If we ended up here we're most likely screwed
189 // we will crash in a few seconds
190 return strPath;
192 #endif
193 #if defined(TARGET_DARWIN)
194 #if !defined(TARGET_DARWIN_EMBEDDED)
195 bool IsDirectoryValidRoot(std::string path)
197 path += "/system/settings/settings.xml";
198 return CFile::Exists(path);
200 #endif
202 std::string GetHomePath(const std::string& strTarget, std::string strPath)
204 if (strPath.empty())
206 auto strHomePath = CUtil::ResolveExecutablePath();
207 int result = -1;
208 char given_path[2 * MAXPATHLEN];
209 size_t path_size = 2 * MAXPATHLEN;
211 result = CDarwinUtils::GetExecutablePath(given_path, &path_size);
212 if (result == 0)
214 // Move backwards to last /.
215 for (int n = strlen(given_path) - 1; given_path[n] != '/'; n--)
216 given_path[n] = '\0';
218 #if defined(TARGET_DARWIN_EMBEDDED)
219 strcat(given_path, "/AppData/AppHome/");
220 #else
221 // Assume local path inside application bundle.
222 strcat(given_path, "../Resources/");
223 strcat(given_path, CCompileInfo::GetAppName());
224 strcat(given_path, "/");
226 // if this path doesn't exist we
227 // might not be started from the app bundle
228 // but from the debugger/xcode. Lets
229 // see if this assumption is valid
230 if (!CDirectory::Exists(given_path))
232 std::string given_path_stdstr = CUtil::ResolveExecutablePath();
233 // try to find the correct folder by going back
234 // in the executable path until settings.xml was found
235 bool validRoot = false;
238 given_path_stdstr = URIUtils::GetParentPath(given_path_stdstr);
239 validRoot = IsDirectoryValidRoot(given_path_stdstr);
241 while(given_path_stdstr.length() > 0 && !validRoot);
242 strncpy(given_path, given_path_stdstr.c_str(), sizeof(given_path)-1);
245 #endif
247 // Convert to real path.
248 char real_path[2 * MAXPATHLEN];
249 if (realpath(given_path, real_path) != NULL)
251 strPath = real_path;
252 return strPath;
255 size_t last_sep = strHomePath.find_last_of(PATH_SEPARATOR_CHAR);
256 if (last_sep != std::string::npos)
257 strPath = strHomePath.substr(0, last_sep);
258 else
259 strPath = strHomePath;
262 return strPath;
264 #endif
265 #if defined(TARGET_POSIX) && !defined(TARGET_DARWIN)
266 std::string GetHomePath(const std::string& strTarget, std::string strPath)
268 if (strPath.empty())
270 auto strHomePath = CUtil::ResolveExecutablePath();
271 size_t last_sep = strHomePath.find_last_of(PATH_SEPARATOR_CHAR);
272 if (last_sep != std::string::npos)
273 strPath = strHomePath.substr(0, last_sep);
274 else
275 strPath = strHomePath;
277 /* Change strPath accordingly when target is KODI_HOME and when INSTALL_PATH
278 * and BIN_INSTALL_PATH differ
280 std::string installPath = INSTALL_PATH;
281 std::string binInstallPath = BIN_INSTALL_PATH;
283 if (strTarget.empty() && installPath.compare(binInstallPath))
285 int pos = strPath.length() - binInstallPath.length();
286 std::string tmp = strPath;
287 tmp.erase(0, pos);
288 if (!tmp.compare(binInstallPath))
290 strPath.erase(pos, strPath.length());
291 strPath.append(installPath);
295 return strPath;
297 #endif
300 std::string CUtil::GetTitleFromPath(const std::string& strFileNameAndPath, bool bIsFolder /* = false */)
302 CURL pathToUrl(strFileNameAndPath);
303 return GetTitleFromPath(pathToUrl, bIsFolder);
306 std::string CUtil::GetTitleFromPath(const CURL& url, bool bIsFolder /* = false */)
308 // use above to get the filename
309 std::string path(url.Get());
310 URIUtils::RemoveSlashAtEnd(path);
311 std::string strFilename = URIUtils::GetFileName(path);
313 #ifdef HAS_UPNP
314 // UPNP
315 if (url.IsProtocol("upnp"))
316 strFilename = CUPnPDirectory::GetFriendlyName(url);
317 #endif
319 if (url.IsProtocol("rss") || url.IsProtocol("rsss"))
321 CRSSDirectory dir;
322 CFileItemList items;
323 if(dir.GetDirectory(url, items) && !items.m_strTitle.empty())
324 return items.m_strTitle;
327 // Shoutcast
328 else if (url.IsProtocol("shout"))
330 const std::string strFileNameAndPath = url.Get();
331 const size_t genre = strFileNameAndPath.find_first_of('=');
332 if(genre == std::string::npos)
333 strFilename = g_localizeStrings.Get(260);
334 else
335 strFilename = g_localizeStrings.Get(260) + " - " + strFileNameAndPath.substr(genre+1).c_str();
338 // Windows SMB Network (SMB)
339 else if (url.IsProtocol("smb") && strFilename.empty())
341 if (url.GetHostName().empty())
343 strFilename = g_localizeStrings.Get(20171);
345 else
347 strFilename = url.GetHostName();
351 // Root file views
352 else if (url.IsProtocol("sources"))
353 strFilename = g_localizeStrings.Get(744);
355 // Music Playlists
356 else if (StringUtils::StartsWith(path, "special://musicplaylists"))
357 strFilename = g_localizeStrings.Get(136);
359 // Video Playlists
360 else if (StringUtils::StartsWith(path, "special://videoplaylists"))
361 strFilename = g_localizeStrings.Get(136);
363 else if (URIUtils::HasParentInHostname(url) && strFilename.empty())
364 strFilename = URIUtils::GetFileName(url.GetHostName());
366 // now remove the extension if needed
367 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWEXTENSIONS) && !bIsFolder)
369 URIUtils::RemoveExtension(strFilename);
370 return strFilename;
373 // URLDecode since the original path may be an URL
374 strFilename = CURL::Decode(strFilename);
375 return strFilename;
378 void CUtil::CleanString(const std::string& strFileName,
379 std::string& strTitle,
380 std::string& strTitleAndYear,
381 std::string& strYear,
382 bool bRemoveExtension /* = false */,
383 bool bCleanChars /* = true */)
385 strTitleAndYear = strFileName;
387 if (strFileName == "..")
388 return;
390 const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
391 const std::vector<std::string> &regexps = advancedSettings->m_videoCleanStringRegExps;
393 CRegExp reTags(true, CRegExp::autoUtf8);
394 CRegExp reYear(false, CRegExp::autoUtf8);
396 if (!reYear.RegComp(advancedSettings->m_videoCleanDateTimeRegExp))
398 CLog::Log(LOGERROR, "{}: Invalid datetime clean RegExp:'{}'", __FUNCTION__,
399 advancedSettings->m_videoCleanDateTimeRegExp);
401 else
403 if (reYear.RegFind(strTitleAndYear.c_str()) >= 0)
405 strTitleAndYear = reYear.GetMatch(1);
406 strYear = reYear.GetMatch(2);
410 URIUtils::RemoveExtension(strTitleAndYear);
412 for (const auto &regexp : regexps)
414 if (!reTags.RegComp(regexp.c_str()))
415 { // invalid regexp - complain in logs
416 CLog::Log(LOGERROR, "{}: Invalid string clean RegExp:'{}'", __FUNCTION__, regexp);
417 continue;
419 int j=0;
420 if ((j=reTags.RegFind(strTitleAndYear.c_str())) > 0)
421 strTitleAndYear = strTitleAndYear.substr(0, j);
424 // final cleanup - special characters used instead of spaces:
425 // all '_' tokens should be replaced by spaces
426 // if the file contains no spaces, all '.' tokens should be replaced by
427 // spaces - one possibility of a mistake here could be something like:
428 // "Dr..StrangeLove" - hopefully no one would have anything like this.
429 if (bCleanChars)
431 bool initialDots = true;
432 bool alreadyContainsSpace = (strTitleAndYear.find(' ') != std::string::npos);
434 for (char &c : strTitleAndYear)
436 if (c != '.')
437 initialDots = false;
439 if ((c == '_') || ((!alreadyContainsSpace) && !initialDots && (c == '.')))
441 c = ' ';
446 StringUtils::Trim(strTitleAndYear);
447 strTitle = strTitleAndYear;
449 // append year
450 if (!strYear.empty())
451 strTitleAndYear = strTitle + " (" + strYear + ")";
453 // restore extension if needed
454 if (!bRemoveExtension)
455 strTitleAndYear += URIUtils::GetExtension(strFileName);
458 void CUtil::GetQualifiedFilename(const std::string &strBasePath, std::string &strFilename)
460 // Check if the filename is a fully qualified URL such as protocol://path/to/file
461 CURL plItemUrl(strFilename);
462 if (!plItemUrl.GetProtocol().empty())
463 return;
465 // If the filename starts "x:", "\\" or "/" it's already fully qualified so return
466 if (strFilename.size() > 1)
467 #ifdef TARGET_POSIX
468 if ( (strFilename[1] == ':') || (strFilename[0] == '/') )
469 #else
470 if ( strFilename[1] == ':' || (strFilename[0] == '\\' && strFilename[1] == '\\'))
471 #endif
472 return;
474 // add to base path and then clean
475 strFilename = URIUtils::AddFileToFolder(strBasePath, strFilename);
477 // get rid of any /./ or \.\ that happen to be there
478 StringUtils::Replace(strFilename, "\\.\\", "\\");
479 StringUtils::Replace(strFilename, "/./", "/");
481 // now find any "\\..\\" and remove them via GetParentPath
482 size_t pos;
483 while ((pos = strFilename.find("/../")) != std::string::npos)
485 std::string basePath = strFilename.substr(0, pos + 1);
486 strFilename.erase(0, pos + 4);
487 basePath = URIUtils::GetParentPath(basePath);
488 strFilename = URIUtils::AddFileToFolder(basePath, strFilename);
490 while ((pos = strFilename.find("\\..\\")) != std::string::npos)
492 std::string basePath = strFilename.substr(0, pos + 1);
493 strFilename.erase(0, pos + 4);
494 basePath = URIUtils::GetParentPath(basePath);
495 strFilename = URIUtils::AddFileToFolder(basePath, strFilename);
499 void CUtil::RunShortcut(const char* szShortcutPath)
503 std::string CUtil::GetHomePath(const std::string& strTarget)
505 auto strPath = CEnvironment::getenv(strTarget);
507 return ::GetHomePath(strTarget, strPath);
510 bool CUtil::IsPicture(const std::string& strFile)
512 return URIUtils::HasExtension(strFile,
513 CServiceBroker::GetFileExtensionProvider().GetPictureExtensions()+ "|.tbn|.dds");
516 std::string CUtil::GetSplashPath()
518 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" }};
519 auto it = std::find_if(candidates.begin(), candidates.end(), [](std::string const& file) { return XFILE::CFile::Exists(file); });
520 if (it == candidates.end())
521 throw std::runtime_error("No splash image found");
522 return CSpecialProtocol::TranslatePathConvertCase(*it);
525 bool CUtil::ExcludeFileOrFolder(const std::string& strFileOrFolder, const std::vector<std::string>& regexps)
527 if (strFileOrFolder.empty())
528 return false;
530 CRegExp regExExcludes(true, CRegExp::autoUtf8); // case insensitive regex
532 for (const auto &regexp : regexps)
534 if (!regExExcludes.RegComp(regexp.c_str()))
535 { // invalid regexp - complain in logs
536 CLog::Log(LOGERROR, "{}: Invalid exclude RegExp:'{}'", __FUNCTION__, regexp);
537 continue;
539 if (regExExcludes.RegFind(strFileOrFolder) > -1)
541 CLog::LogF(LOGDEBUG, "File '{}' excluded. (Matches exclude rule RegExp: '{}')", CURL::GetRedacted(strFileOrFolder), regexp);
542 return true;
545 return false;
548 void CUtil::GetFileAndProtocol(const std::string& strURL, std::string& strDir)
550 strDir = strURL;
551 if (!URIUtils::IsRemote(strURL)) return ;
552 if (URIUtils::IsDVD(strURL)) return ;
554 CURL url(strURL);
555 strDir = StringUtils::Format("{}://{}", url.GetProtocol(), url.GetFileName());
558 int CUtil::GetDVDIfoTitle(const std::string& strFile)
560 std::string strFilename = URIUtils::GetFileName(strFile);
561 if (StringUtils::EqualsNoCase(strFilename, "video_ts.ifo")) return 0;
562 //VTS_[TITLE]_0.IFO
563 return atoi(strFilename.substr(4, 2).c_str());
566 std::string CUtil::GetFileDigest(const std::string& strPath, KODI::UTILITY::CDigest::Type type)
568 CFile file;
569 std::string result;
570 if (file.Open(strPath))
572 CDigest digest{type};
573 char temp[1024];
574 while (true)
576 ssize_t read = file.Read(temp,1024);
577 if (read <= 0)
578 break;
579 digest.Update(temp,read);
581 result = digest.Finalize();
582 file.Close();
585 return result;
588 bool CUtil::GetDirectoryName(const std::string& strFileName, std::string& strDescription)
590 std::string strFName = URIUtils::GetFileName(strFileName);
591 strDescription = URIUtils::GetDirectory(strFileName);
592 URIUtils::RemoveSlashAtEnd(strDescription);
594 size_t iPos = strDescription.find_last_of("/\\");
595 if (iPos != std::string::npos)
596 strDescription = strDescription.substr(iPos + 1);
597 else if (strDescription.size() <= 0)
598 strDescription = strFName;
599 return true;
602 void CUtil::GetDVDDriveIcon(const std::string& strPath, std::string& strIcon)
604 if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath))
606 strIcon = "DefaultDVDEmpty.png";
607 return ;
610 CFileItem item = CFileItem(strPath, false);
612 if (item.IsBluray())
614 strIcon = "DefaultBluray.png";
615 return;
618 if ( URIUtils::IsDVD(strPath) )
620 strIcon = "DefaultDVDFull.png";
621 return ;
624 if ( URIUtils::IsISO9660(strPath) )
626 #ifdef HAS_DVD_DRIVE
627 CCdInfo* pInfo = CServiceBroker::GetMediaManager().GetCdInfo();
628 if ( pInfo != NULL && pInfo->IsVideoCd( 1 ) )
630 strIcon = "DefaultVCD.png";
631 return ;
633 #endif
634 strIcon = "DefaultDVDRom.png";
635 return ;
638 if ( URIUtils::IsCDDA(strPath) )
640 strIcon = "DefaultCDDA.png";
641 return ;
645 void CUtil::RemoveTempFiles()
647 const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager();
649 std::string searchPath = profileManager->GetDatabaseFolder();
650 CFileItemList items;
651 if (!XFILE::CDirectory::GetDirectory(searchPath, items, ".tmp", DIR_FLAG_NO_FILE_DIRS))
652 return;
654 for (const auto &item : items)
656 if (item->m_bIsFolder)
657 continue;
658 XFILE::CFile::Delete(item->GetPath());
662 void CUtil::ClearSubtitles()
664 //delete cached subs
665 CFileItemList items;
666 CDirectory::GetDirectory("special://temp/",items, "", DIR_FLAG_DEFAULTS);
667 for (const auto &item : items)
669 if (!item->m_bIsFolder)
671 if (item->GetPath().find("subtitle") != std::string::npos ||
672 item->GetPath().find("vobsub_queue") != std::string::npos)
674 CLog::Log(LOGDEBUG, "{} - Deleting temporary subtitle {}", __FUNCTION__, item->GetPath());
675 CFile::Delete(item->GetPath());
681 int64_t CUtil::ToInt64(uint32_t high, uint32_t low)
683 int64_t n;
684 n = high;
685 n <<= 32;
686 n += low;
687 return n;
691 \brief Finds next unused filename that matches padded int format identifier provided
692 \param[in] fn_template filename template consisting of a padded int format identifier (eg screenshot%03d)
693 \param[in] max maximum number to search for available name
694 \return "" on failure, string next available name matching format identifier on success
697 std::string CUtil::GetNextFilename(const std::string &fn_template, int max)
699 std::string searchPath = URIUtils::GetDirectory(fn_template);
700 std::string mask = URIUtils::GetExtension(fn_template);
701 std::string name = StringUtils::Format(fn_template, 0);
703 CFileItemList items;
704 if (!CDirectory::GetDirectory(searchPath, items, mask, DIR_FLAG_NO_FILE_DIRS))
705 return name;
707 items.SetFastLookup(true);
708 for (int i = 0; i <= max; i++)
710 std::string name = StringUtils::Format(fn_template, i);
711 if (!items.Get(name))
712 return name;
714 return "";
717 std::string CUtil::GetNextPathname(const std::string &path_template, int max)
719 if (path_template.find("%04d") == std::string::npos)
720 return "";
722 for (int i = 0; i <= max; i++)
724 std::string name = StringUtils::Format(path_template, i);
725 if (!CFile::Exists(name) && !CDirectory::Exists(name))
726 return name;
728 return "";
731 void CUtil::StatToStatI64(struct _stati64 *result, struct stat *stat)
733 result->st_dev = stat->st_dev;
734 result->st_ino = stat->st_ino;
735 result->st_mode = stat->st_mode;
736 result->st_nlink = stat->st_nlink;
737 result->st_uid = stat->st_uid;
738 result->st_gid = stat->st_gid;
739 result->st_rdev = stat->st_rdev;
740 result->st_size = (int64_t)stat->st_size;
742 #ifndef TARGET_POSIX
743 result->st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
744 result->st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
745 result->st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
746 #else
747 result->_st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
748 result->_st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
749 result->_st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
750 #endif
753 void CUtil::Stat64ToStatI64(struct _stati64 *result, struct __stat64 *stat)
755 result->st_dev = stat->st_dev;
756 result->st_ino = stat->st_ino;
757 result->st_mode = stat->st_mode;
758 result->st_nlink = stat->st_nlink;
759 result->st_uid = stat->st_uid;
760 result->st_gid = stat->st_gid;
761 result->st_rdev = stat->st_rdev;
762 result->st_size = stat->st_size;
763 #ifndef TARGET_POSIX
764 result->st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
765 result->st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
766 result->st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
767 #else
768 result->_st_atime = (long)(stat->st_atime & 0xFFFFFFFF);
769 result->_st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF);
770 result->_st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF);
771 #endif
774 void CUtil::StatI64ToStat64(struct __stat64 *result, struct _stati64 *stat)
776 result->st_dev = stat->st_dev;
777 result->st_ino = stat->st_ino;
778 result->st_mode = stat->st_mode;
779 result->st_nlink = stat->st_nlink;
780 result->st_uid = stat->st_uid;
781 result->st_gid = stat->st_gid;
782 result->st_rdev = stat->st_rdev;
783 result->st_size = stat->st_size;
784 #ifndef TARGET_POSIX
785 result->st_atime = stat->st_atime;
786 result->st_mtime = stat->st_mtime;
787 result->st_ctime = stat->st_ctime;
788 #else
789 result->st_atime = stat->_st_atime;
790 result->st_mtime = stat->_st_mtime;
791 result->st_ctime = stat->_st_ctime;
792 #endif
795 void CUtil::StatToStat64(struct __stat64 *result, const struct stat *stat)
797 memset(result, 0, sizeof(*result));
798 result->st_dev = stat->st_dev;
799 result->st_ino = stat->st_ino;
800 result->st_mode = stat->st_mode;
801 result->st_nlink = stat->st_nlink;
802 result->st_uid = stat->st_uid;
803 result->st_gid = stat->st_gid;
804 result->st_rdev = stat->st_rdev;
805 result->st_size = stat->st_size;
806 result->st_atime = stat->st_atime;
807 result->st_mtime = stat->st_mtime;
808 result->st_ctime = stat->st_ctime;
811 void CUtil::Stat64ToStat(struct stat *result, struct __stat64 *stat)
813 result->st_dev = stat->st_dev;
814 result->st_ino = stat->st_ino;
815 result->st_mode = stat->st_mode;
816 result->st_nlink = stat->st_nlink;
817 result->st_uid = stat->st_uid;
818 result->st_gid = stat->st_gid;
819 result->st_rdev = stat->st_rdev;
820 #ifndef TARGET_POSIX
821 if (stat->st_size <= LONG_MAX)
822 result->st_size = (_off_t)stat->st_size;
823 #else
824 if (sizeof(stat->st_size) <= sizeof(result->st_size) )
825 result->st_size = stat->st_size;
826 #endif
827 else
829 result->st_size = 0;
830 CLog::Log(LOGWARNING, "WARNING: File is larger than 32bit stat can handle, file size will be reported as 0 bytes");
832 result->st_atime = (time_t)(stat->st_atime & 0xFFFFFFFF);
833 result->st_mtime = (time_t)(stat->st_mtime & 0xFFFFFFFF);
834 result->st_ctime = (time_t)(stat->st_ctime & 0xFFFFFFFF);
837 #ifdef TARGET_WINDOWS
838 void CUtil::Stat64ToStat64i32(struct _stat64i32 *result, struct __stat64 *stat)
840 result->st_dev = stat->st_dev;
841 result->st_ino = stat->st_ino;
842 result->st_mode = stat->st_mode;
843 result->st_nlink = stat->st_nlink;
844 result->st_uid = stat->st_uid;
845 result->st_gid = stat->st_gid;
846 result->st_rdev = stat->st_rdev;
847 #ifndef TARGET_POSIX
848 if (stat->st_size <= LONG_MAX)
849 result->st_size = (_off_t)stat->st_size;
850 #else
851 if (sizeof(stat->st_size) <= sizeof(result->st_size) )
852 result->st_size = stat->st_size;
853 #endif
854 else
856 result->st_size = 0;
857 CLog::Log(LOGWARNING, "WARNING: File is larger than 32bit stat can handle, file size will be reported as 0 bytes");
859 #ifndef TARGET_POSIX
860 result->st_atime = stat->st_atime;
861 result->st_mtime = stat->st_mtime;
862 result->st_ctime = stat->st_ctime;
863 #else
864 result->st_atime = stat->_st_atime;
865 result->st_mtime = stat->_st_mtime;
866 result->st_ctime = stat->_st_ctime;
867 #endif
869 #endif
871 bool CUtil::CreateDirectoryEx(const std::string& strPath)
873 // Function to create all directories at once instead
874 // of calling CreateDirectory for every subdir.
875 // Creates the directory and subdirectories if needed.
877 // return true if directory already exist
878 if (CDirectory::Exists(strPath)) return true;
880 // we currently only allow HD and smb and nfs paths
881 if (!URIUtils::IsHD(strPath) && !URIUtils::IsSmb(strPath) && !URIUtils::IsNfs(strPath))
883 CLog::Log(LOGERROR, "{} called with an unsupported path: {}", __FUNCTION__, strPath);
884 return false;
887 std::vector<std::string> dirs = URIUtils::SplitPath(strPath);
888 if (dirs.empty())
889 return false;
890 std::string dir(dirs.front());
891 URIUtils::AddSlashAtEnd(dir);
892 for (std::vector<std::string>::const_iterator it = dirs.begin() + 1; it != dirs.end(); ++it)
894 dir = URIUtils::AddFileToFolder(dir, *it);
895 CDirectory::Create(dir);
898 // was the final destination directory successfully created ?
899 return CDirectory::Exists(strPath);
902 std::string CUtil::MakeLegalFileName(const std::string &strFile, int LegalType)
904 std::string result = strFile;
906 StringUtils::Replace(result, '/', '_');
907 StringUtils::Replace(result, '\\', '_');
908 StringUtils::Replace(result, '?', '_');
910 if (LegalType == LEGAL_WIN32_COMPAT)
912 // just filter out some illegal characters on windows
913 StringUtils::Replace(result, ':', '_');
914 StringUtils::Replace(result, '*', '_');
915 StringUtils::Replace(result, '?', '_');
916 StringUtils::Replace(result, '\"', '_');
917 StringUtils::Replace(result, '<', '_');
918 StringUtils::Replace(result, '>', '_');
919 StringUtils::Replace(result, '|', '_');
920 StringUtils::TrimRight(result, ". ");
922 return result;
925 // legalize entire path
926 std::string CUtil::MakeLegalPath(const std::string &strPathAndFile, int LegalType)
928 if (URIUtils::IsStack(strPathAndFile))
929 return MakeLegalPath(CStackDirectory::GetFirstStackedFile(strPathAndFile));
930 if (URIUtils::IsMultiPath(strPathAndFile))
931 return MakeLegalPath(CMultiPathDirectory::GetFirstPath(strPathAndFile));
932 if (!URIUtils::IsHD(strPathAndFile) && !URIUtils::IsSmb(strPathAndFile) && !URIUtils::IsNfs(strPathAndFile))
933 return strPathAndFile; // we don't support writing anywhere except HD, SMB and NFS - no need to legalize path
935 bool trailingSlash = URIUtils::HasSlashAtEnd(strPathAndFile);
936 std::vector<std::string> dirs = URIUtils::SplitPath(strPathAndFile);
937 if (dirs.empty())
938 return strPathAndFile;
939 // we just add first token to path and don't legalize it - possible values:
940 // "X:" (local win32), "" (local unix - empty string before '/') or
941 // "protocol://domain"
942 std::string dir(dirs.front());
943 URIUtils::AddSlashAtEnd(dir);
944 for (std::vector<std::string>::const_iterator it = dirs.begin() + 1; it != dirs.end(); ++it)
945 dir = URIUtils::AddFileToFolder(dir, MakeLegalFileName(*it, LegalType));
946 if (trailingSlash) URIUtils::AddSlashAtEnd(dir);
947 return dir;
950 std::string CUtil::ValidatePath(const std::string &path, bool bFixDoubleSlashes /* = false */)
952 std::string result = path;
954 // Don't do any stuff on URLs containing %-characters or protocols that embed
955 // filenames. NOTE: Don't use IsInZip or IsInRar here since it will infinitely
956 // recurse and crash XBMC
957 if (URIUtils::IsURL(path) &&
958 (path.find('%') != std::string::npos ||
959 StringUtils::StartsWithNoCase(path, "apk:") ||
960 StringUtils::StartsWithNoCase(path, "zip:") ||
961 StringUtils::StartsWithNoCase(path, "rar:") ||
962 StringUtils::StartsWithNoCase(path, "stack:") ||
963 StringUtils::StartsWithNoCase(path, "bluray:") ||
964 StringUtils::StartsWithNoCase(path, "multipath:") ))
965 return result;
967 // check the path for incorrect slashes
968 #ifdef TARGET_WINDOWS
969 if (URIUtils::IsDOSPath(path))
971 StringUtils::Replace(result, '/', '\\');
972 /* The double slash correction should only be used when *absolutely*
973 necessary! This applies to certain DLLs or use from Python DLLs/scripts
974 that incorrectly generate double (back) slashes.
976 if (bFixDoubleSlashes && !result.empty())
978 // Fixup for double back slashes (but ignore the \\ of unc-paths)
979 for (size_t x = 1; x < result.size() - 1; x++)
981 if (result[x] == '\\' && result[x+1] == '\\')
982 result.erase(x, 1);
986 else if (path.find("://") != std::string::npos || path.find(":\\\\") != std::string::npos)
987 #endif
989 StringUtils::Replace(result, '\\', '/');
990 /* The double slash correction should only be used when *absolutely*
991 necessary! This applies to certain DLLs or use from Python DLLs/scripts
992 that incorrectly generate double (back) slashes.
994 if (bFixDoubleSlashes && !result.empty())
996 // Fixup for double forward slashes(/) but don't touch the :// of URLs
997 for (size_t x = 2; x < result.size() - 1; x++)
999 if ( result[x] == '/' && result[x + 1] == '/' && !(result[x - 1] == ':' || (result[x - 1] == '/' && result[x - 2] == ':')) )
1000 result.erase(x, 1);
1004 return result;
1007 void CUtil::SplitParams(const std::string &paramString, std::vector<std::string> &parameters)
1009 bool inQuotes = false;
1010 bool lastEscaped = false; // only every second character can be escaped
1011 int inFunction = 0;
1012 size_t whiteSpacePos = 0;
1013 std::string parameter;
1014 parameters.clear();
1015 for (size_t pos = 0; pos < paramString.size(); pos++)
1017 char ch = paramString[pos];
1018 bool escaped = (pos > 0 && paramString[pos - 1] == '\\' && !lastEscaped);
1019 lastEscaped = escaped;
1020 if (inQuotes)
1021 { // if we're in a quote, we accept everything until the closing quote
1022 if (ch == '"' && !escaped)
1023 { // finished a quote - no need to add the end quote to our string
1024 inQuotes = false;
1027 else
1028 { // not in a quote, so check if we should be starting one
1029 if (ch == '"' && !escaped)
1030 { // start of quote - no need to add the quote to our string
1031 inQuotes = true;
1033 if (inFunction && ch == ')')
1034 { // end of a function
1035 inFunction--;
1037 if (ch == '(')
1038 { // start of function
1039 inFunction++;
1041 if (!inFunction && ch == ',')
1042 { // not in a function, so a comma signifies the end of this parameter
1043 if (whiteSpacePos)
1044 parameter = parameter.substr(0, whiteSpacePos);
1045 // trim off start and end quotes
1046 if (parameter.length() > 1 && parameter[0] == '"' && parameter[parameter.length() - 1] == '"')
1047 parameter = parameter.substr(1, parameter.length() - 2);
1048 else if (parameter.length() > 3 && parameter[parameter.length() - 1] == '"')
1050 // check name="value" style param.
1051 size_t quotaPos = parameter.find('"');
1052 if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=')
1054 parameter.erase(parameter.length() - 1);
1055 parameter.erase(quotaPos);
1058 parameters.push_back(parameter);
1059 parameter.clear();
1060 whiteSpacePos = 0;
1061 continue;
1064 if ((ch == '"' || ch == '\\') && escaped)
1065 { // escaped quote or backslash
1066 parameter[parameter.size()-1] = ch;
1067 continue;
1069 // whitespace handling - we skip any whitespace at the left or right of an unquoted parameter
1070 if (ch == ' ' && !inQuotes)
1072 if (parameter.empty()) // skip whitespace on left
1073 continue;
1074 if (!whiteSpacePos) // make a note of where whitespace starts on the right
1075 whiteSpacePos = parameter.size();
1077 else
1078 whiteSpacePos = 0;
1079 parameter += ch;
1081 if (inFunction || inQuotes)
1082 CLog::Log(LOGWARNING, "{}({}) - end of string while searching for ) or \"", __FUNCTION__,
1083 paramString);
1084 if (whiteSpacePos)
1085 parameter.erase(whiteSpacePos);
1086 // trim off start and end quotes
1087 if (parameter.size() > 1 && parameter[0] == '"' && parameter[parameter.size() - 1] == '"')
1088 parameter = parameter.substr(1,parameter.size() - 2);
1089 else if (parameter.size() > 3 && parameter[parameter.size() - 1] == '"')
1091 // check name="value" style param.
1092 size_t quotaPos = parameter.find('"');
1093 if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=')
1095 parameter.erase(parameter.length() - 1);
1096 parameter.erase(quotaPos);
1099 if (!parameter.empty() || parameters.size())
1100 parameters.push_back(parameter);
1103 int CUtil::GetMatchingSource(const std::string& strPath1, VECSOURCES& VECSOURCES, bool& bIsSourceName)
1105 if (strPath1.empty())
1106 return -1;
1108 // copy as we may change strPath
1109 std::string strPath = strPath1;
1111 // Check for special protocols
1112 CURL checkURL(strPath);
1114 if (StringUtils::StartsWith(strPath, "special://skin/"))
1115 return 1;
1117 // do not return early if URL protocol is "plugin"
1118 // since video- and/or audio-plugins can be configured as mediasource
1120 // stack://
1121 if (checkURL.IsProtocol("stack"))
1122 strPath.erase(0, 8); // remove the stack protocol
1124 if (checkURL.IsProtocol("shout"))
1125 strPath = checkURL.GetHostName();
1127 if (checkURL.IsProtocol("multipath"))
1128 strPath = CMultiPathDirectory::GetFirstPath(strPath);
1130 bIsSourceName = false;
1131 int iIndex = -1;
1133 // we first test the NAME of a source
1134 for (int i = 0; i < (int)VECSOURCES.size(); ++i)
1136 const CMediaSource &share = VECSOURCES[i];
1137 std::string strName = share.strName;
1139 // special cases for dvds
1140 if (URIUtils::IsOnDVD(share.strPath))
1142 if (URIUtils::IsOnDVD(strPath))
1143 return i;
1145 // not a path, so we need to modify the source name
1146 // since we add the drive status and disc name to the source
1147 // "Name (Drive Status/Disc Name)"
1148 size_t iPos = strName.rfind('(');
1149 if (iPos != std::string::npos && iPos > 1)
1150 strName = strName.substr(0, iPos - 1);
1152 if (StringUtils::EqualsNoCase(strPath, strName))
1154 bIsSourceName = true;
1155 return i;
1159 // now test the paths
1161 // remove user details, and ensure path only uses forward slashes
1162 // and ends with a trailing slash so as not to match a substring
1163 CURL urlDest(strPath);
1164 urlDest.SetOptions("");
1165 urlDest.SetProtocolOptions("");
1166 std::string strDest = urlDest.GetWithoutUserDetails();
1167 ForceForwardSlashes(strDest);
1168 if (!URIUtils::HasSlashAtEnd(strDest))
1169 strDest += "/";
1171 size_t iLength = 0;
1172 size_t iLenPath = strDest.size();
1173 for (int i = 0; i < (int)VECSOURCES.size(); ++i)
1175 const CMediaSource &share = VECSOURCES[i];
1177 // does it match a source name?
1178 if (share.strPath.substr(0,8) == "shout://")
1180 CURL url(share.strPath);
1181 if (strPath == url.GetHostName())
1182 return i;
1185 // doesn't match a name, so try the source path
1186 std::vector<std::string> vecPaths;
1188 // add any concatenated paths if they exist
1189 if (!share.vecPaths.empty())
1190 vecPaths = share.vecPaths;
1192 // add the actual share path at the front of the vector
1193 vecPaths.insert(vecPaths.begin(), share.strPath);
1195 // test each path
1196 for (const auto &path : vecPaths)
1198 // remove user details, and ensure path only uses forward slashes
1199 // and ends with a trailing slash so as not to match a substring
1200 CURL urlShare(path);
1201 urlShare.SetOptions("");
1202 urlShare.SetProtocolOptions("");
1203 std::string strShare = urlShare.GetWithoutUserDetails();
1204 ForceForwardSlashes(strShare);
1205 if (!URIUtils::HasSlashAtEnd(strShare))
1206 strShare += "/";
1207 size_t iLenShare = strShare.size();
1209 if ((iLenPath >= iLenShare) && StringUtils::StartsWithNoCase(strDest, strShare) && (iLenShare > iLength))
1211 // if exact match, return it immediately
1212 if (iLenPath == iLenShare)
1214 // if the path EXACTLY matches an item in a concatenated path
1215 // set source name to true to load the full virtualpath
1216 bIsSourceName = false;
1217 if (vecPaths.size() > 1)
1218 bIsSourceName = true;
1219 return i;
1221 iIndex = i;
1222 iLength = iLenShare;
1227 // return the index of the share with the longest match
1228 if (iIndex == -1)
1231 // rar:// and zip://
1232 // if archive wasn't mounted, look for a matching share for the archive instead
1233 if( StringUtils::StartsWithNoCase(strPath, "rar://") || StringUtils::StartsWithNoCase(strPath, "zip://") )
1235 // get the hostname portion of the url since it contains the archive file
1236 strPath = checkURL.GetHostName();
1238 bIsSourceName = false;
1239 bool bDummy;
1240 return GetMatchingSource(strPath, VECSOURCES, bDummy);
1243 CLog::Log(LOGDEBUG, "CUtil::GetMatchingSource: no matching source found for [{}]", strPath1);
1245 return iIndex;
1248 std::string CUtil::TranslateSpecialSource(const std::string &strSpecial)
1250 if (!strSpecial.empty() && strSpecial[0] == '$')
1252 if (StringUtils::StartsWithNoCase(strSpecial, "$home"))
1253 return URIUtils::AddFileToFolder("special://home/", strSpecial.substr(5));
1254 else if (StringUtils::StartsWithNoCase(strSpecial, "$subtitles"))
1255 return URIUtils::AddFileToFolder("special://subtitles/", strSpecial.substr(10));
1256 else if (StringUtils::StartsWithNoCase(strSpecial, "$userdata"))
1257 return URIUtils::AddFileToFolder("special://userdata/", strSpecial.substr(9));
1258 else if (StringUtils::StartsWithNoCase(strSpecial, "$database"))
1259 return URIUtils::AddFileToFolder("special://database/", strSpecial.substr(9));
1260 else if (StringUtils::StartsWithNoCase(strSpecial, "$thumbnails"))
1261 return URIUtils::AddFileToFolder("special://thumbnails/", strSpecial.substr(11));
1262 else if (StringUtils::StartsWithNoCase(strSpecial, "$recordings"))
1263 return URIUtils::AddFileToFolder("special://recordings/", strSpecial.substr(11));
1264 else if (StringUtils::StartsWithNoCase(strSpecial, "$screenshots"))
1265 return URIUtils::AddFileToFolder("special://screenshots/", strSpecial.substr(12));
1266 else if (StringUtils::StartsWithNoCase(strSpecial, "$musicplaylists"))
1267 return URIUtils::AddFileToFolder("special://musicplaylists/", strSpecial.substr(15));
1268 else if (StringUtils::StartsWithNoCase(strSpecial, "$videoplaylists"))
1269 return URIUtils::AddFileToFolder("special://videoplaylists/", strSpecial.substr(15));
1270 else if (StringUtils::StartsWithNoCase(strSpecial, "$cdrips"))
1271 return URIUtils::AddFileToFolder("special://cdrips/", strSpecial.substr(7));
1272 // this one will be removed post 2.0
1273 else if (StringUtils::StartsWithNoCase(strSpecial, "$playlists"))
1274 return URIUtils::AddFileToFolder(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), strSpecial.substr(10));
1276 return strSpecial;
1279 std::string CUtil::MusicPlaylistsLocation()
1281 const std::string path = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH);
1282 std::vector<std::string> vec;
1283 vec.push_back(URIUtils::AddFileToFolder(path, "music"));
1284 vec.push_back(URIUtils::AddFileToFolder(path, "mixed"));
1285 return XFILE::CMultiPathDirectory::ConstructMultiPath(vec);
1288 std::string CUtil::VideoPlaylistsLocation()
1290 const std::string path = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH);
1291 std::vector<std::string> vec;
1292 vec.push_back(URIUtils::AddFileToFolder(path, "video"));
1293 vec.push_back(URIUtils::AddFileToFolder(path, "mixed"));
1294 return XFILE::CMultiPathDirectory::ConstructMultiPath(vec);
1297 void CUtil::DeleteMusicDatabaseDirectoryCache()
1299 CUtil::DeleteDirectoryCache("mdb-");
1300 CUtil::DeleteDirectoryCache("sp-"); // overkill as it will delete video smartplaylists, but as we can't differentiate based on URL...
1303 void CUtil::DeleteVideoDatabaseDirectoryCache()
1305 CUtil::DeleteDirectoryCache("vdb-");
1306 CUtil::DeleteDirectoryCache("sp-"); // overkill as it will delete music smartplaylists, but as we can't differentiate based on URL...
1309 void CUtil::DeleteDirectoryCache(const std::string &prefix)
1311 std::string searchPath = "special://temp/";
1312 CFileItemList items;
1313 if (!XFILE::CDirectory::GetDirectory(searchPath, items, ".fi", DIR_FLAG_NO_FILE_DIRS))
1314 return;
1316 for (const auto &item : items)
1318 if (item->m_bIsFolder)
1319 continue;
1320 std::string fileName = URIUtils::GetFileName(item->GetPath());
1321 if (StringUtils::StartsWith(fileName, prefix))
1322 XFILE::CFile::Delete(item->GetPath());
1327 void CUtil::GetRecursiveListing(const std::string& strPath, CFileItemList& items, const std::string& strMask, unsigned int flags /* = DIR_FLAG_DEFAULTS */)
1329 CFileItemList myItems;
1330 CDirectory::GetDirectory(strPath,myItems,strMask,flags);
1331 for (const auto &item : myItems)
1333 if (item->m_bIsFolder)
1334 CUtil::GetRecursiveListing(item->GetPath(),items,strMask,flags);
1335 else
1336 items.Add(item);
1340 void CUtil::GetRecursiveDirsListing(const std::string& strPath, CFileItemList& item, unsigned int flags /* = DIR_FLAG_DEFAULTS */)
1342 CFileItemList myItems;
1343 CDirectory::GetDirectory(strPath,myItems,"",flags);
1344 for (const auto &i : myItems)
1346 if (i->m_bIsFolder && !i->IsPath(".."))
1348 item.Add(i);
1349 CUtil::GetRecursiveDirsListing(i->GetPath(),item,flags);
1354 void CUtil::ForceForwardSlashes(std::string& strPath)
1356 size_t iPos = strPath.rfind('\\');
1357 while (iPos != std::string::npos)
1359 strPath.at(iPos) = '/';
1360 iPos = strPath.rfind('\\');
1364 double CUtil::AlbumRelevance(const std::string& strAlbumTemp1, const std::string& strAlbum1, const std::string& strArtistTemp1, const std::string& strArtist1)
1366 // case-insensitive fuzzy string comparison on the album and artist for relevance
1367 // weighting is identical, both album and artist are 50% of the total relevance
1368 // a missing artist means the maximum relevance can only be 0.50
1369 std::string strAlbumTemp = strAlbumTemp1;
1370 StringUtils::ToLower(strAlbumTemp);
1371 std::string strAlbum = strAlbum1;
1372 StringUtils::ToLower(strAlbum);
1373 double fAlbumPercentage = fstrcmp(strAlbumTemp.c_str(), strAlbum.c_str());
1374 double fArtistPercentage = 0.0;
1375 if (!strArtist1.empty())
1377 std::string strArtistTemp = strArtistTemp1;
1378 StringUtils::ToLower(strArtistTemp);
1379 std::string strArtist = strArtist1;
1380 StringUtils::ToLower(strArtist);
1381 fArtistPercentage = fstrcmp(strArtistTemp.c_str(), strArtist.c_str());
1383 double fRelevance = fAlbumPercentage * 0.5 + fArtistPercentage * 0.5;
1384 return fRelevance;
1387 bool CUtil::MakeShortenPath(std::string StrInput, std::string& StrOutput, size_t iTextMaxLength)
1389 size_t iStrInputSize = StrInput.size();
1390 if(iStrInputSize <= 0 || iTextMaxLength >= iStrInputSize)
1392 StrOutput = StrInput;
1393 return true;
1396 char cDelim = '\0';
1397 size_t nGreaterDelim, nPos;
1399 nPos = StrInput.find_last_of( '\\' );
1400 if (nPos != std::string::npos)
1401 cDelim = '\\';
1402 else
1404 nPos = StrInput.find_last_of( '/' );
1405 if (nPos != std::string::npos)
1406 cDelim = '/';
1408 if ( cDelim == '\0' )
1409 return false;
1411 if (nPos == StrInput.size() - 1)
1413 StrInput.erase(StrInput.size() - 1);
1414 nPos = StrInput.find_last_of(cDelim);
1416 while( iTextMaxLength < iStrInputSize )
1418 nPos = StrInput.find_last_of( cDelim, nPos );
1419 nGreaterDelim = nPos;
1421 if (nPos == std::string::npos || nPos == 0)
1422 break;
1424 nPos = StrInput.find_last_of( cDelim, nPos - 1 );
1426 if ( nPos == std::string::npos )
1427 break;
1428 if ( nGreaterDelim > nPos )
1429 StrInput.replace( nPos + 1, nGreaterDelim - nPos - 1, ".." );
1430 iStrInputSize = StrInput.size();
1432 // replace any additional /../../ with just /../ if necessary
1433 std::string replaceDots = StringUtils::Format("..{}..", cDelim);
1434 while (StrInput.size() > (unsigned int)iTextMaxLength)
1435 if (!StringUtils::Replace(StrInput, replaceDots, ".."))
1436 break;
1437 // finally, truncate our string to force inside our max text length,
1438 // replacing the last 2 characters with ".."
1440 // eg end up with:
1441 // "smb://../Playboy Swimsuit Cal.."
1442 if (iTextMaxLength > 2 && StrInput.size() > (unsigned int)iTextMaxLength)
1444 StrInput.erase(iTextMaxLength - 2);
1445 StrInput += "..";
1447 StrOutput = StrInput;
1448 return true;
1451 bool CUtil::SupportsWriteFileOperations(const std::string& strPath)
1453 // currently only hd, smb, nfs and dav support delete and rename
1454 if (URIUtils::IsHD(strPath))
1455 return true;
1456 if (URIUtils::IsSmb(strPath))
1457 return true;
1458 if (URIUtils::IsPVRRecording(strPath))
1459 return CPVRDirectory::SupportsWriteFileOperations(strPath);
1460 if (URIUtils::IsNfs(strPath))
1461 return true;
1462 if (URIUtils::IsDAV(strPath))
1463 return true;
1464 if (URIUtils::IsStack(strPath))
1465 return SupportsWriteFileOperations(CStackDirectory::GetFirstStackedFile(strPath));
1466 if (URIUtils::IsMultiPath(strPath))
1467 return CMultiPathDirectory::SupportsWriteFileOperations(strPath);
1469 if (CServiceBroker::IsAddonInterfaceUp())
1471 CURL url(strPath);
1472 for (const auto& addon : CServiceBroker::GetVFSAddonCache().GetAddonInstances())
1474 const auto& info = addon->GetProtocolInfo();
1475 auto prots = StringUtils::Split(info.type, "|");
1476 if (info.supportWrite &&
1477 std::find(prots.begin(), prots.end(), url.GetProtocol()) != prots.end())
1478 return true;
1482 return false;
1485 bool CUtil::SupportsReadFileOperations(const std::string& strPath)
1487 return !URIUtils::IsVideoDb(strPath);
1490 std::string CUtil::GetDefaultFolderThumb(const std::string &folderThumb)
1492 if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(folderThumb))
1493 return folderThumb;
1494 return "";
1497 void CUtil::GetSkinThemes(std::vector<std::string>& vecTheme)
1499 static const std::string TexturesXbt = "Textures.xbt";
1501 std::string strPath = URIUtils::AddFileToFolder(CServiceBroker::GetWinSystem()->GetGfxContext().GetMediaDir(), "media");
1502 CFileItemList items;
1503 CDirectory::GetDirectory(strPath, items, "", DIR_FLAG_DEFAULTS);
1504 // Search for Themes in the Current skin!
1505 for (const auto &pItem : items)
1507 if (!pItem->m_bIsFolder)
1509 std::string strExtension = URIUtils::GetExtension(pItem->GetPath());
1510 std::string strLabel = pItem->GetLabel();
1511 if ((strExtension == ".xbt" && !StringUtils::EqualsNoCase(strLabel, TexturesXbt)))
1512 vecTheme.push_back(StringUtils::Left(strLabel, strLabel.size() - strExtension.size()));
1514 else
1516 // check if this is an xbt:// VFS path
1517 CURL itemUrl(pItem->GetPath());
1518 if (!itemUrl.IsProtocol("xbt") || !itemUrl.GetFileName().empty())
1519 continue;
1521 std::string strLabel = URIUtils::GetFileName(itemUrl.GetHostName());
1522 if (!StringUtils::EqualsNoCase(strLabel, TexturesXbt))
1523 vecTheme.push_back(StringUtils::Left(strLabel, strLabel.size() - URIUtils::GetExtension(strLabel).size()));
1526 std::sort(vecTheme.begin(), vecTheme.end(), sortstringbyname());
1529 void CUtil::InitRandomSeed()
1531 // Init random seed
1532 auto now = std::chrono::steady_clock::now();
1533 auto seed = now.time_since_epoch();
1535 srand(static_cast<unsigned int>(seed.count()));
1538 #if defined(TARGET_POSIX) && !defined(TARGET_DARWIN_TVOS)
1539 bool CUtil::RunCommandLine(const std::string& cmdLine, bool waitExit)
1541 std::vector<std::string> args = StringUtils::Split(cmdLine, ",");
1543 // Strip quotes and whitespace around the arguments, or exec will fail.
1544 // This allows the python invocation to be written more naturally with any amount of whitespace around the args.
1545 // But it's still limited, for example quotes inside the strings are not expanded, etc.
1546 //! @todo Maybe some python library routine can parse this more properly ?
1547 for (std::vector<std::string>::iterator it = args.begin(); it != args.end(); ++it)
1549 size_t pos;
1550 pos = it->find_first_not_of(" \t\n\"'");
1551 if (pos != std::string::npos)
1553 it->erase(0, pos);
1556 pos = it->find_last_not_of(" \t\n\"'"); // if it returns npos we'll end up with an empty string which is OK
1558 it->erase(++pos, it->size());
1562 return Command(args, waitExit);
1565 bool CUtil::Command(const std::vector<std::string>& arrArgs, bool waitExit)
1567 #ifdef _DEBUG
1568 printf("Executing: ");
1569 for (const auto &arg : arrArgs)
1570 printf("%s ", arg.c_str());
1571 printf("\n");
1572 #endif
1574 pid_t child = fork();
1575 int n = 0;
1576 if (child == 0)
1578 if (!waitExit)
1580 // fork again in order not to leave a zombie process
1581 child = fork();
1582 if (child == -1)
1583 _exit(2);
1584 else if (child != 0)
1585 _exit(0);
1587 close(0);
1588 close(1);
1589 close(2);
1590 if (!arrArgs.empty())
1592 char **args = (char **)alloca(sizeof(char *) * (arrArgs.size() + 3));
1593 memset(args, 0, (sizeof(char *) * (arrArgs.size() + 3)));
1594 for (size_t i=0; i<arrArgs.size(); i++)
1595 args[i] = const_cast<char *>(arrArgs[i].c_str());
1596 execvp(args[0], args);
1599 else
1601 waitpid(child, &n, 0);
1604 return (waitExit) ? (WEXITSTATUS(n) == 0) : true;
1606 #endif
1608 int CUtil::LookupRomanDigit(char roman_digit)
1610 switch (roman_digit)
1612 case 'i':
1613 case 'I':
1614 return 1;
1615 case 'v':
1616 case 'V':
1617 return 5;
1618 case 'x':
1619 case 'X':
1620 return 10;
1621 case 'l':
1622 case 'L':
1623 return 50;
1624 case 'c':
1625 case 'C':
1626 return 100;
1627 case 'd':
1628 case 'D':
1629 return 500;
1630 case 'm':
1631 case 'M':
1632 return 1000;
1633 default:
1634 return 0;
1638 int CUtil::TranslateRomanNumeral(const char* roman_numeral)
1641 int decimal = -1;
1643 if (roman_numeral && roman_numeral[0])
1645 int temp_sum = 0,
1646 last = 0,
1647 repeat = 0,
1648 trend = 1;
1649 decimal = 0;
1650 while (*roman_numeral)
1652 int digit = CUtil::LookupRomanDigit(*roman_numeral);
1653 int test = last;
1655 // General sanity checks
1657 // numeral not in LUT
1658 if (!digit)
1659 return -1;
1661 while (test > 5)
1662 test /= 10;
1664 // N = 10^n may not precede (N+1) > 10^(N+1)
1665 if (test == 1 && digit > last * 10)
1666 return -1;
1668 // N = 5*10^n may not precede (N+1) >= N
1669 if (test == 5 && digit >= last)
1670 return -1;
1672 // End general sanity checks
1674 if (last < digit)
1676 // smaller numerals may not repeat before a larger one
1677 if (repeat)
1678 return -1;
1680 temp_sum += digit;
1682 repeat = 0;
1683 trend = 0;
1685 else if (last == digit)
1687 temp_sum += digit;
1688 repeat++;
1689 trend = 1;
1691 else
1693 if (!repeat)
1694 decimal += 2 * last - temp_sum;
1695 else
1696 decimal += temp_sum;
1698 temp_sum = digit;
1700 trend = 1;
1701 repeat = 0;
1703 // Post general sanity checks
1705 // numerals may not repeat more than thrice
1706 if (repeat == 3)
1707 return -1;
1709 last = digit;
1710 roman_numeral++;
1713 if (trend)
1714 decimal += temp_sum;
1715 else
1716 decimal += 2 * last - temp_sum;
1718 return decimal;
1721 std::string CUtil::ResolveExecutablePath()
1723 std::string strExecutablePath;
1724 #ifdef TARGET_WINDOWS
1725 static const size_t bufSize = MAX_PATH * 2;
1726 wchar_t* buf = new wchar_t[bufSize];
1727 buf[0] = 0;
1728 ::GetModuleFileNameW(0, buf, bufSize);
1729 buf[bufSize-1] = 0;
1730 g_charsetConverter.wToUTF8(buf,strExecutablePath);
1731 delete[] buf;
1732 #elif defined(TARGET_DARWIN)
1733 char given_path[2*MAXPATHLEN];
1734 size_t path_size =2*MAXPATHLEN;
1736 CDarwinUtils::GetExecutablePath(given_path, &path_size);
1737 strExecutablePath = given_path;
1738 #elif defined(TARGET_FREEBSD)
1739 char buf[PATH_MAX];
1740 size_t buflen;
1741 int mib[4];
1743 mib[0] = CTL_KERN;
1744 mib[1] = KERN_PROC;
1745 mib[2] = KERN_PROC_PATHNAME;
1746 mib[3] = getpid();
1748 buflen = sizeof(buf) - 1;
1749 if(sysctl(mib, 4, buf, &buflen, NULL, 0) < 0)
1750 strExecutablePath = "";
1751 else
1752 strExecutablePath = buf;
1753 #elif defined(TARGET_ANDROID)
1754 strExecutablePath = CXBMCApp::getApplicationInfo().nativeLibraryDir;
1756 std::string appName = CCompileInfo::GetAppName();
1757 std::string libName = "lib" + appName + ".so";
1758 StringUtils::ToLower(libName);
1759 strExecutablePath += "/" + libName;
1760 #else
1761 /* Get our PID and build the name of the link in /proc */
1762 pid_t pid = getpid();
1763 char linkname[64]; /* /proc/<pid>/exe */
1764 snprintf(linkname, sizeof(linkname), "/proc/%i/exe", pid);
1766 /* Now read the symbolic link */
1767 char buf[PATH_MAX + 1];
1768 buf[0] = 0;
1770 int ret = readlink(linkname, buf, sizeof(buf) - 1);
1771 if (ret != -1)
1772 buf[ret] = 0;
1774 strExecutablePath = buf;
1775 #endif
1776 return strExecutablePath;
1779 std::string CUtil::GetFrameworksPath(bool forPython)
1781 std::string strFrameworksPath;
1782 #if defined(TARGET_DARWIN)
1783 strFrameworksPath = CDarwinUtils::GetFrameworkPath(forPython);
1784 #endif
1785 return strFrameworksPath;
1788 void CUtil::GetVideoBasePathAndFileName(const std::string& videoPath, std::string& basePath, std::string& videoFileName)
1790 CFileItem item(videoPath, false);
1791 videoFileName = URIUtils::ReplaceExtension(URIUtils::GetFileName(videoPath), "");
1793 if (item.HasVideoInfoTag())
1794 basePath = item.GetVideoInfoTag()->m_basePath;
1796 if (basePath.empty() && item.IsOpticalMediaFile())
1797 basePath = item.GetLocalMetadataPath();
1799 CURL url(videoPath);
1800 if (basePath.empty() && url.IsProtocol("bluray"))
1802 basePath = url.GetHostName();
1803 videoFileName = URIUtils::ReplaceExtension(GetTitleFromPath(url.GetHostName()), "");
1805 url = CURL(url.GetHostName());
1806 if (url.IsProtocol("udf"))
1807 basePath = URIUtils::GetParentPath(url.GetHostName());
1810 if (basePath.empty())
1811 basePath = URIUtils::GetBasePath(videoPath);
1814 void CUtil::GetItemsToScan(const std::string& videoPath,
1815 const std::string& item_exts,
1816 const std::vector<std::string>& sub_dirs,
1817 CFileItemList& items)
1819 int flags = DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO;
1821 if (!videoPath.empty())
1822 CDirectory::GetDirectory(videoPath, items, item_exts, flags);
1824 std::vector<std::string> additionalPaths;
1825 for (const auto &item : items)
1827 for (const auto& subdir : sub_dirs)
1829 if (StringUtils::EqualsNoCase(item->GetLabel(), subdir))
1830 additionalPaths.push_back(item->GetPath());
1834 for (std::vector<std::string>::const_iterator it = additionalPaths.begin(); it != additionalPaths.end(); ++it)
1836 CFileItemList moreItems;
1837 CDirectory::GetDirectory(*it, moreItems, item_exts, flags);
1838 items.Append(moreItems);
1843 void CUtil::ScanPathsForAssociatedItems(const std::string& videoName,
1844 const CFileItemList& items,
1845 const std::vector<std::string>& item_exts,
1846 std::vector<std::string>& associatedFiles)
1848 for (const auto &pItem : items)
1850 if (pItem->m_bIsFolder)
1851 continue;
1853 std::string strCandidate = URIUtils::GetFileName(pItem->GetPath());
1855 // skip duplicates
1856 if (std::find(associatedFiles.begin(), associatedFiles.end(), pItem->GetPath()) != associatedFiles.end())
1857 continue;
1859 URIUtils::RemoveExtension(strCandidate);
1860 // NOTE: We don't know if one of videoName or strCandidate is URL-encoded and the other is not, so try both
1861 if (StringUtils::StartsWithNoCase(strCandidate, videoName) || (StringUtils::StartsWithNoCase(strCandidate, CURL::Decode(videoName))))
1863 if (URIUtils::IsRAR(pItem->GetPath()) || URIUtils::IsZIP(pItem->GetPath()))
1864 CUtil::ScanArchiveForAssociatedItems(pItem->GetPath(), "", item_exts, associatedFiles);
1865 else
1867 associatedFiles.push_back(pItem->GetPath());
1868 CLog::Log(LOGINFO, "{}: found associated file {}", __FUNCTION__,
1869 CURL::GetRedacted(pItem->GetPath()));
1872 else
1874 if (URIUtils::IsRAR(pItem->GetPath()) || URIUtils::IsZIP(pItem->GetPath()))
1875 CUtil::ScanArchiveForAssociatedItems(pItem->GetPath(), videoName, item_exts, associatedFiles);
1880 int CUtil::ScanArchiveForAssociatedItems(const std::string& strArchivePath,
1881 const std::string& videoNameNoExt,
1882 const std::vector<std::string>& item_exts,
1883 std::vector<std::string>& associatedFiles)
1885 CLog::LogF(LOGDEBUG, "Scanning archive {}", CURL::GetRedacted(strArchivePath));
1886 int nItemsAdded = 0;
1887 CFileItemList ItemList;
1889 // zip only gets the root dir
1890 if (URIUtils::HasExtension(strArchivePath, ".zip"))
1892 CURL pathToUrl(strArchivePath);
1893 CURL zipURL = URIUtils::CreateArchivePath("zip", pathToUrl, "");
1894 if (!CDirectory::GetDirectory(zipURL, ItemList, "", DIR_FLAG_NO_FILE_DIRS))
1895 return false;
1897 else if (URIUtils::HasExtension(strArchivePath, ".rar"))
1899 CURL pathToUrl(strArchivePath);
1900 CURL rarURL = URIUtils::CreateArchivePath("rar", pathToUrl, "");
1901 if (!CDirectory::GetDirectory(rarURL, ItemList, "", DIR_FLAG_NO_FILE_DIRS))
1902 return false;
1904 for (const auto &item : ItemList)
1906 std::string strPathInRar = item->GetPath();
1907 std::string strExt = URIUtils::GetExtension(strPathInRar);
1909 // Check another archive in archive
1910 if (strExt == ".zip" || strExt == ".rar")
1912 nItemsAdded +=
1913 ScanArchiveForAssociatedItems(strPathInRar, videoNameNoExt, item_exts, associatedFiles);
1914 continue;
1917 // check that the found filename matches the movie filename
1918 size_t fnl = videoNameNoExt.size();
1919 // NOTE: We don't know if videoNameNoExt is URL-encoded, so try both
1920 if (fnl &&
1921 !(StringUtils::StartsWithNoCase(URIUtils::GetFileName(strPathInRar), videoNameNoExt) ||
1922 StringUtils::StartsWithNoCase(URIUtils::GetFileName(strPathInRar), CURL::Decode(videoNameNoExt))))
1923 continue;
1925 for (const auto& ext : item_exts)
1927 if (StringUtils::EqualsNoCase(strExt, ext))
1929 CLog::Log(LOGINFO, "{}: found associated file {}", __FUNCTION__,
1930 CURL::GetRedacted(strPathInRar));
1931 associatedFiles.push_back(strPathInRar);
1932 nItemsAdded++;
1933 break;
1938 return nItemsAdded;
1941 void CUtil::ScanForExternalSubtitles(const std::string& strMovie, std::vector<std::string>& vecSubtitles)
1943 auto start = std::chrono::steady_clock::now();
1945 CFileItem item(strMovie, false);
1946 if ((item.IsInternetStream() && !URIUtils::IsOnLAN(item.GetDynPath()))
1947 || item.IsPlayList()
1948 || item.IsLiveTV()
1949 || !item.IsVideo())
1950 return;
1952 CLog::Log(LOGDEBUG, "{}: Searching for subtitles...", __FUNCTION__);
1954 std::string strBasePath;
1955 std::string strSubtitle;
1957 GetVideoBasePathAndFileName(strMovie, strBasePath, strSubtitle);
1959 CFileItemList items;
1960 const std::vector<std::string> common_sub_dirs = { "subs", "subtitles", "vobsubs", "sub", "vobsub", "subtitle" };
1961 const std::string subtitleExtensions = CServiceBroker::GetFileExtensionProvider().GetSubtitleExtensions();
1962 GetItemsToScan(strBasePath, subtitleExtensions, common_sub_dirs, items);
1964 const std::string customPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SUBTITLES_CUSTOMPATH);
1966 if (!CMediaSettings::GetInstance().GetAdditionalSubtitleDirectoryChecked() && !customPath.empty()) // to avoid checking non-existent directories (network) every time..
1968 if (!CServiceBroker::GetNetwork().IsAvailable() && !URIUtils::IsHD(customPath))
1970 CLog::Log(LOGINFO, "CUtil::CacheSubtitles: disabling alternate subtitle directory for this session, it's inaccessible");
1971 CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(-1); // disabled
1973 else if (!CDirectory::Exists(customPath))
1975 CLog::Log(LOGINFO, "CUtil::CacheSubtitles: disabling alternate subtitle directory for this session, it's nonexistent");
1976 CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(-1); // disabled
1979 CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(1);
1982 std::vector<std::string> strLookInPaths;
1983 // this is last because we dont want to check any common subdirs or cd-dirs in the alternate <subtitles> dir.
1984 if (CMediaSettings::GetInstance().GetAdditionalSubtitleDirectoryChecked() == 1)
1986 std::string strPath2 = customPath;
1987 URIUtils::AddSlashAtEnd(strPath2);
1988 strLookInPaths.push_back(strPath2);
1991 int flags = DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO;
1992 for (const std::string& path : strLookInPaths)
1994 CFileItemList moreItems;
1995 CDirectory::GetDirectory(path, moreItems, subtitleExtensions, flags);
1996 items.Append(moreItems);
1999 std::vector<std::string> exts = StringUtils::Split(subtitleExtensions, '|');
2000 exts.erase(std::remove(exts.begin(), exts.end(), ".zip"), exts.end());
2001 exts.erase(std::remove(exts.begin(), exts.end(), ".rar"), exts.end());
2003 ScanPathsForAssociatedItems(strSubtitle, items, exts, vecSubtitles);
2005 size_t iSize = vecSubtitles.size();
2006 for (size_t i = 0; i < iSize; i++)
2008 if (URIUtils::HasExtension(vecSubtitles[i], ".smi"))
2010 //Cache multi-language sami subtitle
2011 CDVDSubtitleStream stream;
2012 if (stream.Open(vecSubtitles[i]))
2014 CDVDSubtitleTagSami TagConv;
2015 TagConv.LoadHead(&stream);
2016 if (TagConv.m_Langclass.size() >= 2)
2018 for (const auto &lang : TagConv.m_Langclass)
2020 std::string strDest =
2021 StringUtils::Format("special://temp/subtitle.{}.{}.smi", lang.Name, i);
2022 if (CFile::Copy(vecSubtitles[i], strDest))
2024 CLog::Log(LOGINFO, " cached subtitle {}->{}", CURL::GetRedacted(vecSubtitles[i]),
2025 strDest);
2026 vecSubtitles.push_back(strDest);
2034 auto end = std::chrono::steady_clock::now();
2035 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
2036 CLog::Log(LOGDEBUG, "{}: END (total time: {} ms)", __FUNCTION__, duration.count());
2039 ExternalStreamInfo CUtil::GetExternalStreamDetailsFromFilename(const std::string& videoPath, const std::string& associatedFile)
2041 ExternalStreamInfo info;
2043 std::string videoBaseName = URIUtils::GetFileName(videoPath);
2044 URIUtils::RemoveExtension(videoBaseName);
2046 std::string toParse = URIUtils::GetFileName(associatedFile);
2047 URIUtils::RemoveExtension(toParse);
2049 // we check left part - if it's same as video base name - strip it
2050 if (StringUtils::StartsWithNoCase(toParse, videoBaseName))
2051 toParse = toParse.substr(videoBaseName.length());
2052 else if (URIUtils::GetExtension(associatedFile) == ".sub" && URIUtils::IsInArchive(associatedFile))
2054 // exclude parsing of vobsub file names that are embedded in an archive
2055 CLog::Log(LOGDEBUG, "{} - skipping archived vobsub filename parsing: {}", __FUNCTION__,
2056 CURL::GetRedacted(associatedFile));
2057 toParse.clear();
2060 // trim any non-alphanumeric char in the beginning
2061 std::string::iterator result = std::find_if(toParse.begin(), toParse.end(), StringUtils::isasciialphanum);
2063 std::string name;
2064 if (result != toParse.end()) // if we have anything to parse
2066 std::string inputString(result, toParse.end());
2067 std::string delimiters(" .-");
2068 std::vector<std::string> tokens;
2069 StringUtils::Tokenize(inputString, tokens, delimiters);
2071 for (auto it = tokens.rbegin(); it != tokens.rend(); ++it)
2073 // try to recognize a flag
2074 std::string flag_tmp(*it);
2075 StringUtils::ToLower(flag_tmp);
2076 if (!flag_tmp.compare("none"))
2078 info.flag |= StreamFlags::FLAG_NONE;
2079 continue;
2081 else if (!flag_tmp.compare("default"))
2083 info.flag |= StreamFlags::FLAG_DEFAULT;
2084 continue;
2086 else if (!flag_tmp.compare("forced"))
2088 info.flag |= StreamFlags::FLAG_FORCED;
2089 continue;
2092 if (info.language.empty())
2094 std::string langCode;
2095 // try to recognize language
2096 if (g_LangCodeExpander.ConvertToISO6392B(*it, langCode))
2098 info.language = langCode;
2099 continue;
2103 name = (*it) + " " + name;
2106 name += " ";
2107 name += g_localizeStrings.Get(21602); // External
2108 StringUtils::Trim(name);
2109 info.name = StringUtils::RemoveDuplicatedSpacesAndTabs(name);
2110 if (info.flag == 0)
2111 info.flag = StreamFlags::FLAG_NONE;
2113 CLog::Log(LOGDEBUG, "{} - Language = '{}' / Name = '{}' / Flag = '{}' from {}", __FUNCTION__,
2114 info.language, info.name, info.flag, CURL::GetRedacted(associatedFile));
2116 return info;
2119 /*! \brief in a vector of subtitles finds the corresponding .sub file for a given .idx file
2121 bool CUtil::FindVobSubPair(const std::vector<std::string>& vecSubtitles, const std::string& strIdxPath, std::string& strSubPath)
2123 if (URIUtils::HasExtension(strIdxPath, ".idx"))
2125 std::string strIdxFile;
2126 std::string strIdxDirectory;
2127 URIUtils::Split(strIdxPath, strIdxDirectory, strIdxFile);
2128 for (const auto &subtitlePath : vecSubtitles)
2130 std::string strSubFile;
2131 std::string strSubDirectory;
2132 URIUtils::Split(subtitlePath, strSubDirectory, strSubFile);
2133 if (URIUtils::IsInArchive(subtitlePath))
2134 strSubDirectory = CURL::Decode(strSubDirectory);
2135 if (URIUtils::HasExtension(strSubFile, ".sub") &&
2136 (URIUtils::PathEquals(URIUtils::ReplaceExtension(strIdxPath,""),
2137 URIUtils::ReplaceExtension(subtitlePath,"")) ||
2138 (strSubDirectory.size() >= 11 &&
2139 StringUtils::EqualsNoCase(strSubDirectory.substr(6, strSubDirectory.length()-11), URIUtils::ReplaceExtension(strIdxPath,"")))))
2141 strSubPath = subtitlePath;
2142 return true;
2146 return false;
2149 /*! \brief checks if in the vector of subtitles the given .sub file has a corresponding idx and hence is a vobsub file
2151 bool CUtil::IsVobSub(const std::vector<std::string>& vecSubtitles, const std::string& strSubPath)
2153 if (URIUtils::HasExtension(strSubPath, ".sub"))
2155 std::string strSubFile;
2156 std::string strSubDirectory;
2157 URIUtils::Split(strSubPath, strSubDirectory, strSubFile);
2158 if (URIUtils::IsInArchive(strSubPath))
2159 strSubDirectory = CURL::Decode(strSubDirectory);
2160 for (const auto &subtitlePath : vecSubtitles)
2162 std::string strIdxFile;
2163 std::string strIdxDirectory;
2164 URIUtils::Split(subtitlePath, strIdxDirectory, strIdxFile);
2165 if (URIUtils::HasExtension(strIdxFile, ".idx") &&
2166 (URIUtils::PathEquals(URIUtils::ReplaceExtension(subtitlePath,""),
2167 URIUtils::ReplaceExtension(strSubPath,"")) ||
2168 (strSubDirectory.size() >= 11 &&
2169 StringUtils::EqualsNoCase(strSubDirectory.substr(6, strSubDirectory.length()-11), URIUtils::ReplaceExtension(subtitlePath,"")))))
2170 return true;
2173 return false;
2176 /*! \brief find a plain or archived vobsub .sub file corresponding to an .idx file
2178 std::string CUtil::GetVobSubSubFromIdx(const std::string& vobSubIdx)
2180 std::string vobSub = URIUtils::ReplaceExtension(vobSubIdx, ".sub");
2182 // check if a .sub file exists in the same directory
2183 if (CFile::Exists(vobSub))
2185 return vobSub;
2188 // look inside a .rar or .zip in the same directory
2189 const std::string archTypes[] = { "rar", "zip" };
2190 std::string vobSubFilename = URIUtils::GetFileName(vobSub);
2191 for (const std::string& archType : archTypes)
2193 vobSub = URIUtils::CreateArchivePath(archType,
2194 CURL(URIUtils::ReplaceExtension(vobSubIdx, std::string(".") + archType)),
2195 vobSubFilename).Get();
2196 if (CFile::Exists(vobSub))
2197 return vobSub;
2200 return std::string();
2203 /*! \brief find a .idx file from a path of a plain or archived vobsub .sub file
2205 std::string CUtil::GetVobSubIdxFromSub(const std::string& vobSub)
2207 std::string vobSubIdx = URIUtils::ReplaceExtension(vobSub, ".idx");
2209 // check if a .idx file exists in the same directory
2210 if (CFile::Exists(vobSubIdx))
2212 return vobSubIdx;
2215 // look outside archive (usually .rar) if the .sub is inside one
2216 if (URIUtils::IsInArchive(vobSub))
2219 std::string archiveFile = URIUtils::GetDirectory(vobSub);
2220 std::string vobSubIdxDir = URIUtils::GetParentPath(archiveFile);
2222 if (!vobSubIdxDir.empty())
2224 std::string vobSubIdxFilename = URIUtils::GetFileName(vobSubIdx);
2225 std::string vobSubIdx = URIUtils::AddFileToFolder(vobSubIdxDir, vobSubIdxFilename);
2227 if (CFile::Exists(vobSubIdx))
2228 return vobSubIdx;
2232 return std::string();
2235 void CUtil::ScanForExternalAudio(const std::string& videoPath, std::vector<std::string>& vecAudio)
2237 CFileItem item(videoPath, false);
2238 if ( item.IsInternetStream()
2239 || item.IsPlayList()
2240 || item.IsLiveTV()
2241 || item.IsPVR()
2242 || !item.IsVideo())
2243 return;
2245 std::string strBasePath;
2246 std::string strAudio;
2248 GetVideoBasePathAndFileName(videoPath, strBasePath, strAudio);
2250 CFileItemList items;
2251 const std::vector<std::string> common_sub_dirs = { "audio", "tracks"};
2252 GetItemsToScan(strBasePath, CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), common_sub_dirs, items);
2254 std::vector<std::string> exts = StringUtils::Split(CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), "|");
2256 CVideoDatabase database;
2257 database.Open();
2258 bool useAllExternalAudio = database.GetUseAllExternalAudioForVideo(videoPath);
2260 if (useAllExternalAudio)
2262 for (const auto& audioItem : items.GetList())
2264 vecAudio.push_back(audioItem.get()->GetPath());
2267 else
2268 ScanPathsForAssociatedItems(strAudio, items, exts, vecAudio);
2271 bool CUtil::CanBindPrivileged()
2273 #ifdef TARGET_POSIX
2275 if (geteuid() == 0)
2276 return true; //root user can always bind to privileged ports
2278 #ifdef HAVE_LIBCAP
2280 //check if CAP_NET_BIND_SERVICE is enabled, this allows non-root users to bind to privileged ports
2281 bool canbind = false;
2282 cap_t capabilities = cap_get_proc();
2283 if (capabilities)
2285 cap_flag_value_t value;
2286 if (cap_get_flag(capabilities, CAP_NET_BIND_SERVICE, CAP_EFFECTIVE, &value) == 0)
2287 canbind = value;
2289 cap_free(capabilities);
2292 return canbind;
2294 #else //HAVE_LIBCAP
2296 return false;
2298 #endif //HAVE_LIBCAP
2300 #else //TARGET_POSIX
2302 return true;
2304 #endif //TARGET_POSIX
2307 bool CUtil::ValidatePort(int port)
2309 // check that it's a valid port
2310 #ifdef TARGET_POSIX
2311 if (!CUtil::CanBindPrivileged() && (port < 1024 || port > 65535))
2312 return false;
2313 else
2314 #endif
2315 if (port <= 0 || port > 65535)
2316 return false;
2318 return true;
2321 int CUtil::GetRandomNumber()
2323 #if !defined(TARGET_WINDOWS)
2324 return rand_r(&s_randomSeed);
2325 #else
2326 unsigned int number;
2327 if (rand_s(&number) == 0)
2328 return (int)number;
2330 return rand();
2331 #endif
2334 void CUtil::CopyUserDataIfNeeded(const std::string& strPath,
2335 const std::string& file,
2336 const std::string& destname)
2338 std::string destPath;
2339 if (destname.empty())
2340 destPath = URIUtils::AddFileToFolder(strPath, file);
2341 else
2342 destPath = URIUtils::AddFileToFolder(strPath, destname);
2344 if (!CFile::Exists(destPath))
2346 // need to copy it across
2347 std::string srcPath = URIUtils::AddFileToFolder("special://xbmc/userdata/", file);
2348 CFile::Copy(srcPath, destPath);