diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/Util.cpp | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/Util.cpp')
-rw-r--r-- | xbmc/Util.cpp | 2350 |
1 files changed, 2350 insertions, 0 deletions
diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp new file mode 100644 index 0000000..50e7987 --- /dev/null +++ b/xbmc/Util.cpp @@ -0,0 +1,2350 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "network/Network.h" +#if defined(TARGET_DARWIN) +#include <sys/param.h> +#include <mach-o/dyld.h> +#endif + +#if defined(TARGET_FREEBSD) +#include <sys/param.h> +#include <sys/sysctl.h> +#endif + +#ifdef TARGET_POSIX +#include <sys/types.h> +#include <dirent.h> +#include <unistd.h> +#include <sys/wait.h> +#endif +#if defined(TARGET_ANDROID) +#include <androidjni/ApplicationInfo.h> +#include "platform/android/bionic_supplement/bionic_supplement.h" +#include "platform/android/activity/XBMCApp.h" +#include "CompileInfo.h" +#endif +#include "ServiceBroker.h" +#include "Util.h" +#include "addons/VFSEntry.h" +#include "filesystem/Directory.h" +#include "filesystem/MultiPathDirectory.h" +#include "filesystem/PVRDirectory.h" +#include "filesystem/RSSDirectory.h" +#include "filesystem/SpecialProtocol.h" +#include "filesystem/StackDirectory.h" + +#include <algorithm> +#include <array> +#include <stdlib.h> +#ifdef HAS_UPNP +#include "filesystem/UPnPDirectory.h" +#endif +#include "profiles/ProfileManager.h" +#include "utils/RegExp.h" +#include "windowing/GraphicContext.h" +#include "guilib/TextureManager.h" +#include "storage/MediaManager.h" +#ifdef TARGET_WINDOWS +#include "utils/CharsetConverter.h" +#include "WIN32Util.h" +#endif +#if defined(TARGET_DARWIN) +#include "CompileInfo.h" +#include "platform/darwin/DarwinUtils.h" +#endif +#include "URL.h" +#include "cores/VideoPlayer/DVDSubtitles/DVDSubtitleStream.h" +#include "cores/VideoPlayer/DVDSubtitles/DVDSubtitleTagSami.h" +#include "filesystem/File.h" +#include "guilib/LocalizeStrings.h" +#include "platform/Environment.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/Digest.h" +#include "utils/FileExtensionProvider.h" +#include "utils/FontUtils.h" +#include "utils/LangCodeExpander.h" +#include "utils/StringUtils.h" +#include "utils/TimeUtils.h" +#include "utils/URIUtils.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/VideoInfoTag.h" +#ifdef HAVE_LIBCAP + #include <sys/capability.h> +#endif + +#include "cores/VideoPlayer/DVDDemuxers/DVDDemux.h" + +#include <fstrcmp.h> + +#ifdef HAS_DVD_DRIVE +using namespace MEDIA_DETECT; +#endif + +using namespace XFILE; +using namespace PLAYLIST; +using KODI::UTILITY::CDigest; + +#if !defined(TARGET_WINDOWS) +unsigned int CUtil::s_randomSeed = time(NULL); +#endif + +namespace +{ +#ifdef TARGET_WINDOWS +bool IsDirectoryValidRoot(std::wstring path) +{ + path += L"\\system\\settings\\settings.xml"; +#if defined(TARGET_WINDOWS_STORE) + auto h = CreateFile2(path.c_str(), GENERIC_READ, 0, OPEN_EXISTING, NULL); +#else + auto h = CreateFileW(path.c_str(), GENERIC_READ, 0, nullptr, + OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); +#endif + if (h != INVALID_HANDLE_VALUE) + { + CloseHandle(h); + return true; + } + + return false; +} + +std::string GetHomePath(const std::string& strTarget, std::string strPath) +{ + std::wstring strPathW; + + // Environment variable was set and we have a path + // Let's make sure it's not relative and test it + // so it's actually pointing to a directory containing + // our stuff + if (strPath.find("..") != std::string::npos) + { + //expand potential relative path to full path + g_charsetConverter.utf8ToW(strPath, strPathW, false); + CWIN32Util::AddExtraLongPathPrefix(strPathW); + auto bufSize = GetFullPathNameW(strPathW.c_str(), 0, nullptr, nullptr); + if (bufSize != 0) + { + auto buf = std::make_unique<wchar_t[]>(bufSize); + if (GetFullPathNameW(strPathW.c_str(), bufSize, buf.get(), nullptr) <= bufSize - 1) + { + strPathW = buf.get(); + CWIN32Util::RemoveExtraLongPathPrefix(strPathW); + + if (IsDirectoryValidRoot(strPathW)) + { + g_charsetConverter.wToUTF8(strPathW, strPath); + return strPath; + } + } + } + } + + // Okay se no environment variable is set, let's + // grab the executable path and check if it's being + // run from a directory containing our stuff + strPath = CUtil::ResolveExecutablePath(); + auto last_sep = strPath.find_last_of(PATH_SEPARATOR_CHAR); + if (last_sep != std::string::npos) + strPath = strPath.substr(0, last_sep); + + g_charsetConverter.utf8ToW(strPath, strPathW); + if (IsDirectoryValidRoot(strPathW)) + return strPath; + + // Still nothing, let's check the current working + // directory and see if it points to a directory + // with our stuff in it. This bit should never be + // needed when running on a users system, it's intended + // to make our dev environment easier. + auto bufSize = GetCurrentDirectoryW(0, nullptr); + if (bufSize > 0) + { + auto buf = std::make_unique<wchar_t[]>(bufSize); + if (0 != GetCurrentDirectoryW(bufSize, buf.get())) + { + std::string currentDirectory; + std::wstring currentDirectoryW(buf.get()); + CWIN32Util::RemoveExtraLongPathPrefix(currentDirectoryW); + + if (IsDirectoryValidRoot(currentDirectoryW)) + { + g_charsetConverter.wToUTF8(currentDirectoryW, currentDirectory); + return currentDirectory; + } + } + } + + // If we ended up here we're most likely screwed + // we will crash in a few seconds + return strPath; +} +#endif +#if defined(TARGET_DARWIN) +#if !defined(TARGET_DARWIN_EMBEDDED) +bool IsDirectoryValidRoot(std::string path) +{ + path += "/system/settings/settings.xml"; + return CFile::Exists(path); +} +#endif + +std::string GetHomePath(const std::string& strTarget, std::string strPath) +{ + if (strPath.empty()) + { + auto strHomePath = CUtil::ResolveExecutablePath(); + int result = -1; + char given_path[2 * MAXPATHLEN]; + size_t path_size = 2 * MAXPATHLEN; + + result = CDarwinUtils::GetExecutablePath(given_path, &path_size); + if (result == 0) + { + // Move backwards to last /. + for (int n = strlen(given_path) - 1; given_path[n] != '/'; n--) + given_path[n] = '\0'; + +#if defined(TARGET_DARWIN_EMBEDDED) + strcat(given_path, "/AppData/AppHome/"); +#else + // Assume local path inside application bundle. + strcat(given_path, "../Resources/"); + strcat(given_path, CCompileInfo::GetAppName()); + strcat(given_path, "/"); + + // if this path doesn't exist we + // might not be started from the app bundle + // but from the debugger/xcode. Lets + // see if this assumption is valid + if (!CDirectory::Exists(given_path)) + { + std::string given_path_stdstr = CUtil::ResolveExecutablePath(); + // try to find the correct folder by going back + // in the executable path until settings.xml was found + bool validRoot = false; + do + { + given_path_stdstr = URIUtils::GetParentPath(given_path_stdstr); + validRoot = IsDirectoryValidRoot(given_path_stdstr); + } + while(given_path_stdstr.length() > 0 && !validRoot); + strncpy(given_path, given_path_stdstr.c_str(), sizeof(given_path)-1); + } + +#endif + + // Convert to real path. + char real_path[2 * MAXPATHLEN]; + if (realpath(given_path, real_path) != NULL) + { + strPath = real_path; + return strPath; + } + } + size_t last_sep = strHomePath.find_last_of(PATH_SEPARATOR_CHAR); + if (last_sep != std::string::npos) + strPath = strHomePath.substr(0, last_sep); + else + strPath = strHomePath; + + } + return strPath; +} +#endif +#if defined(TARGET_POSIX) && !defined(TARGET_DARWIN) +std::string GetHomePath(const std::string& strTarget, std::string strPath) +{ + if (strPath.empty()) + { + auto strHomePath = CUtil::ResolveExecutablePath(); + size_t last_sep = strHomePath.find_last_of(PATH_SEPARATOR_CHAR); + if (last_sep != std::string::npos) + strPath = strHomePath.substr(0, last_sep); + else + strPath = strHomePath; + } + /* Change strPath accordingly when target is KODI_HOME and when INSTALL_PATH + * and BIN_INSTALL_PATH differ + */ + std::string installPath = INSTALL_PATH; + std::string binInstallPath = BIN_INSTALL_PATH; + + if (strTarget.empty() && installPath.compare(binInstallPath)) + { + int pos = strPath.length() - binInstallPath.length(); + std::string tmp = strPath; + tmp.erase(0, pos); + if (!tmp.compare(binInstallPath)) + { + strPath.erase(pos, strPath.length()); + strPath.append(installPath); + } + } + + return strPath; +} +#endif +} + +std::string CUtil::GetTitleFromPath(const std::string& strFileNameAndPath, bool bIsFolder /* = false */) +{ + CURL pathToUrl(strFileNameAndPath); + return GetTitleFromPath(pathToUrl, bIsFolder); +} + +std::string CUtil::GetTitleFromPath(const CURL& url, bool bIsFolder /* = false */) +{ + // use above to get the filename + std::string path(url.Get()); + URIUtils::RemoveSlashAtEnd(path); + std::string strFilename = URIUtils::GetFileName(path); + +#ifdef HAS_UPNP + // UPNP + if (url.IsProtocol("upnp")) + strFilename = CUPnPDirectory::GetFriendlyName(url); +#endif + + if (url.IsProtocol("rss") || url.IsProtocol("rsss")) + { + CRSSDirectory dir; + CFileItemList items; + if(dir.GetDirectory(url, items) && !items.m_strTitle.empty()) + return items.m_strTitle; + } + + // Shoutcast + else if (url.IsProtocol("shout")) + { + const std::string strFileNameAndPath = url.Get(); + const size_t genre = strFileNameAndPath.find_first_of('='); + if(genre == std::string::npos) + strFilename = g_localizeStrings.Get(260); + else + strFilename = g_localizeStrings.Get(260) + " - " + strFileNameAndPath.substr(genre+1).c_str(); + } + + // Windows SMB Network (SMB) + else if (url.IsProtocol("smb") && strFilename.empty()) + { + if (url.GetHostName().empty()) + { + strFilename = g_localizeStrings.Get(20171); + } + else + { + strFilename = url.GetHostName(); + } + } + + // Root file views + else if (url.IsProtocol("sources")) + strFilename = g_localizeStrings.Get(744); + + // Music Playlists + else if (StringUtils::StartsWith(path, "special://musicplaylists")) + strFilename = g_localizeStrings.Get(136); + + // Video Playlists + else if (StringUtils::StartsWith(path, "special://videoplaylists")) + strFilename = g_localizeStrings.Get(136); + + else if (URIUtils::HasParentInHostname(url) && strFilename.empty()) + strFilename = URIUtils::GetFileName(url.GetHostName()); + + // now remove the extension if needed + if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWEXTENSIONS) && !bIsFolder) + { + URIUtils::RemoveExtension(strFilename); + return strFilename; + } + + // URLDecode since the original path may be an URL + strFilename = CURL::Decode(strFilename); + return strFilename; +} + +void CUtil::CleanString(const std::string& strFileName, + std::string& strTitle, + std::string& strTitleAndYear, + std::string& strYear, + bool bRemoveExtension /* = false */, + bool bCleanChars /* = true */) +{ + strTitleAndYear = strFileName; + + if (strFileName == "..") + return; + + const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + const std::vector<std::string> ®exps = advancedSettings->m_videoCleanStringRegExps; + + CRegExp reTags(true, CRegExp::autoUtf8); + CRegExp reYear(false, CRegExp::autoUtf8); + + if (!reYear.RegComp(advancedSettings->m_videoCleanDateTimeRegExp)) + { + CLog::Log(LOGERROR, "{}: Invalid datetime clean RegExp:'{}'", __FUNCTION__, + advancedSettings->m_videoCleanDateTimeRegExp); + } + else + { + if (reYear.RegFind(strTitleAndYear.c_str()) >= 0) + { + strTitleAndYear = reYear.GetMatch(1); + strYear = reYear.GetMatch(2); + } + } + + URIUtils::RemoveExtension(strTitleAndYear); + + for (const auto ®exp : regexps) + { + if (!reTags.RegComp(regexp.c_str())) + { // invalid regexp - complain in logs + CLog::Log(LOGERROR, "{}: Invalid string clean RegExp:'{}'", __FUNCTION__, regexp); + continue; + } + int j=0; + if ((j=reTags.RegFind(strTitleAndYear.c_str())) > 0) + strTitleAndYear = strTitleAndYear.substr(0, j); + } + + // final cleanup - special characters used instead of spaces: + // all '_' tokens should be replaced by spaces + // if the file contains no spaces, all '.' tokens should be replaced by + // spaces - one possibility of a mistake here could be something like: + // "Dr..StrangeLove" - hopefully no one would have anything like this. + if (bCleanChars) + { + bool initialDots = true; + bool alreadyContainsSpace = (strTitleAndYear.find(' ') != std::string::npos); + + for (char &c : strTitleAndYear) + { + if (c != '.') + initialDots = false; + + if ((c == '_') || ((!alreadyContainsSpace) && !initialDots && (c == '.'))) + { + c = ' '; + } + } + } + + StringUtils::Trim(strTitleAndYear); + strTitle = strTitleAndYear; + + // append year + if (!strYear.empty()) + strTitleAndYear = strTitle + " (" + strYear + ")"; + + // restore extension if needed + if (!bRemoveExtension) + strTitleAndYear += URIUtils::GetExtension(strFileName); +} + +void CUtil::GetQualifiedFilename(const std::string &strBasePath, std::string &strFilename) +{ + // Check if the filename is a fully qualified URL such as protocol://path/to/file + CURL plItemUrl(strFilename); + if (!plItemUrl.GetProtocol().empty()) + return; + + // If the filename starts "x:", "\\" or "/" it's already fully qualified so return + if (strFilename.size() > 1) +#ifdef TARGET_POSIX + if ( (strFilename[1] == ':') || (strFilename[0] == '/') ) +#else + if ( strFilename[1] == ':' || (strFilename[0] == '\\' && strFilename[1] == '\\')) +#endif + return; + + // add to base path and then clean + strFilename = URIUtils::AddFileToFolder(strBasePath, strFilename); + + // get rid of any /./ or \.\ that happen to be there + StringUtils::Replace(strFilename, "\\.\\", "\\"); + StringUtils::Replace(strFilename, "/./", "/"); + + // now find any "\\..\\" and remove them via GetParentPath + size_t pos; + while ((pos = strFilename.find("/../")) != std::string::npos) + { + std::string basePath = strFilename.substr(0, pos + 1); + strFilename.erase(0, pos + 4); + basePath = URIUtils::GetParentPath(basePath); + strFilename = URIUtils::AddFileToFolder(basePath, strFilename); + } + while ((pos = strFilename.find("\\..\\")) != std::string::npos) + { + std::string basePath = strFilename.substr(0, pos + 1); + strFilename.erase(0, pos + 4); + basePath = URIUtils::GetParentPath(basePath); + strFilename = URIUtils::AddFileToFolder(basePath, strFilename); + } +} + +void CUtil::RunShortcut(const char* szShortcutPath) +{ +} + +std::string CUtil::GetHomePath(const std::string& strTarget) +{ + auto strPath = CEnvironment::getenv(strTarget); + + return ::GetHomePath(strTarget, strPath); +} + +bool CUtil::IsPicture(const std::string& strFile) +{ + return URIUtils::HasExtension(strFile, + CServiceBroker::GetFileExtensionProvider().GetPictureExtensions()+ "|.tbn|.dds"); +} + +std::string CUtil::GetSplashPath() +{ + 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" }}; + auto it = std::find_if(candidates.begin(), candidates.end(), [](std::string const& file) { return XFILE::CFile::Exists(file); }); + if (it == candidates.end()) + throw std::runtime_error("No splash image found"); + return CSpecialProtocol::TranslatePathConvertCase(*it); +} + +bool CUtil::ExcludeFileOrFolder(const std::string& strFileOrFolder, const std::vector<std::string>& regexps) +{ + if (strFileOrFolder.empty()) + return false; + + CRegExp regExExcludes(true, CRegExp::autoUtf8); // case insensitive regex + + for (const auto ®exp : regexps) + { + if (!regExExcludes.RegComp(regexp.c_str())) + { // invalid regexp - complain in logs + CLog::Log(LOGERROR, "{}: Invalid exclude RegExp:'{}'", __FUNCTION__, regexp); + continue; + } + if (regExExcludes.RegFind(strFileOrFolder) > -1) + { + CLog::LogF(LOGDEBUG, "File '{}' excluded. (Matches exclude rule RegExp: '{}')", CURL::GetRedacted(strFileOrFolder), regexp); + return true; + } + } + return false; +} + +void CUtil::GetFileAndProtocol(const std::string& strURL, std::string& strDir) +{ + strDir = strURL; + if (!URIUtils::IsRemote(strURL)) return ; + if (URIUtils::IsDVD(strURL)) return ; + + CURL url(strURL); + strDir = StringUtils::Format("{}://{}", url.GetProtocol(), url.GetFileName()); +} + +int CUtil::GetDVDIfoTitle(const std::string& strFile) +{ + std::string strFilename = URIUtils::GetFileName(strFile); + if (StringUtils::EqualsNoCase(strFilename, "video_ts.ifo")) return 0; + //VTS_[TITLE]_0.IFO + return atoi(strFilename.substr(4, 2).c_str()); +} + +std::string CUtil::GetFileDigest(const std::string& strPath, KODI::UTILITY::CDigest::Type type) +{ + CFile file; + std::string result; + if (file.Open(strPath)) + { + CDigest digest{type}; + char temp[1024]; + while (true) + { + ssize_t read = file.Read(temp,1024); + if (read <= 0) + break; + digest.Update(temp,read); + } + result = digest.Finalize(); + file.Close(); + } + + return result; +} + +bool CUtil::GetDirectoryName(const std::string& strFileName, std::string& strDescription) +{ + std::string strFName = URIUtils::GetFileName(strFileName); + strDescription = URIUtils::GetDirectory(strFileName); + URIUtils::RemoveSlashAtEnd(strDescription); + + size_t iPos = strDescription.find_last_of("/\\"); + if (iPos != std::string::npos) + strDescription = strDescription.substr(iPos + 1); + else if (strDescription.size() <= 0) + strDescription = strFName; + return true; +} + +void CUtil::GetDVDDriveIcon(const std::string& strPath, std::string& strIcon) +{ + if (!CServiceBroker::GetMediaManager().IsDiscInDrive(strPath)) + { + strIcon = "DefaultDVDEmpty.png"; + return ; + } + + CFileItem item = CFileItem(strPath, false); + + if (item.IsBluray()) + { + strIcon = "DefaultBluray.png"; + return; + } + + if ( URIUtils::IsDVD(strPath) ) + { + strIcon = "DefaultDVDFull.png"; + return ; + } + + if ( URIUtils::IsISO9660(strPath) ) + { +#ifdef HAS_DVD_DRIVE + CCdInfo* pInfo = CServiceBroker::GetMediaManager().GetCdInfo(); + if ( pInfo != NULL && pInfo->IsVideoCd( 1 ) ) + { + strIcon = "DefaultVCD.png"; + return ; + } +#endif + strIcon = "DefaultDVDRom.png"; + return ; + } + + if ( URIUtils::IsCDDA(strPath) ) + { + strIcon = "DefaultCDDA.png"; + return ; + } +} + +void CUtil::RemoveTempFiles() +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + std::string searchPath = profileManager->GetDatabaseFolder(); + CFileItemList items; + if (!XFILE::CDirectory::GetDirectory(searchPath, items, ".tmp", DIR_FLAG_NO_FILE_DIRS)) + return; + + for (const auto &item : items) + { + if (item->m_bIsFolder) + continue; + XFILE::CFile::Delete(item->GetPath()); + } +} + +void CUtil::ClearSubtitles() +{ + //delete cached subs + CFileItemList items; + CDirectory::GetDirectory("special://temp/",items, "", DIR_FLAG_DEFAULTS); + for (const auto &item : items) + { + if (!item->m_bIsFolder) + { + if (item->GetPath().find("subtitle") != std::string::npos || + item->GetPath().find("vobsub_queue") != std::string::npos) + { + CLog::Log(LOGDEBUG, "{} - Deleting temporary subtitle {}", __FUNCTION__, item->GetPath()); + CFile::Delete(item->GetPath()); + } + } + } +} + +int64_t CUtil::ToInt64(uint32_t high, uint32_t low) +{ + int64_t n; + n = high; + n <<= 32; + n += low; + return n; +} + +/*! + \brief Finds next unused filename that matches padded int format identifier provided + \param[in] fn_template filename template consisting of a padded int format identifier (eg screenshot%03d) + \param[in] max maximum number to search for available name + \return "" on failure, string next available name matching format identifier on success +*/ + +std::string CUtil::GetNextFilename(const std::string &fn_template, int max) +{ + std::string searchPath = URIUtils::GetDirectory(fn_template); + std::string mask = URIUtils::GetExtension(fn_template); + std::string name = StringUtils::Format(fn_template, 0); + + CFileItemList items; + if (!CDirectory::GetDirectory(searchPath, items, mask, DIR_FLAG_NO_FILE_DIRS)) + return name; + + items.SetFastLookup(true); + for (int i = 0; i <= max; i++) + { + std::string name = StringUtils::Format(fn_template, i); + if (!items.Get(name)) + return name; + } + return ""; +} + +std::string CUtil::GetNextPathname(const std::string &path_template, int max) +{ + if (path_template.find("%04d") == std::string::npos) + return ""; + + for (int i = 0; i <= max; i++) + { + std::string name = StringUtils::Format(path_template, i); + if (!CFile::Exists(name) && !CDirectory::Exists(name)) + return name; + } + return ""; +} + +void CUtil::StatToStatI64(struct _stati64 *result, struct stat *stat) +{ + result->st_dev = stat->st_dev; + result->st_ino = stat->st_ino; + result->st_mode = stat->st_mode; + result->st_nlink = stat->st_nlink; + result->st_uid = stat->st_uid; + result->st_gid = stat->st_gid; + result->st_rdev = stat->st_rdev; + result->st_size = (int64_t)stat->st_size; + +#ifndef TARGET_POSIX + result->st_atime = (long)(stat->st_atime & 0xFFFFFFFF); + result->st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF); + result->st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF); +#else + result->_st_atime = (long)(stat->st_atime & 0xFFFFFFFF); + result->_st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF); + result->_st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF); +#endif +} + +void CUtil::Stat64ToStatI64(struct _stati64 *result, struct __stat64 *stat) +{ + result->st_dev = stat->st_dev; + result->st_ino = stat->st_ino; + result->st_mode = stat->st_mode; + result->st_nlink = stat->st_nlink; + result->st_uid = stat->st_uid; + result->st_gid = stat->st_gid; + result->st_rdev = stat->st_rdev; + result->st_size = stat->st_size; +#ifndef TARGET_POSIX + result->st_atime = (long)(stat->st_atime & 0xFFFFFFFF); + result->st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF); + result->st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF); +#else + result->_st_atime = (long)(stat->st_atime & 0xFFFFFFFF); + result->_st_mtime = (long)(stat->st_mtime & 0xFFFFFFFF); + result->_st_ctime = (long)(stat->st_ctime & 0xFFFFFFFF); +#endif +} + +void CUtil::StatI64ToStat64(struct __stat64 *result, struct _stati64 *stat) +{ + result->st_dev = stat->st_dev; + result->st_ino = stat->st_ino; + result->st_mode = stat->st_mode; + result->st_nlink = stat->st_nlink; + result->st_uid = stat->st_uid; + result->st_gid = stat->st_gid; + result->st_rdev = stat->st_rdev; + result->st_size = stat->st_size; +#ifndef TARGET_POSIX + result->st_atime = stat->st_atime; + result->st_mtime = stat->st_mtime; + result->st_ctime = stat->st_ctime; +#else + result->st_atime = stat->_st_atime; + result->st_mtime = stat->_st_mtime; + result->st_ctime = stat->_st_ctime; +#endif +} + +void CUtil::StatToStat64(struct __stat64 *result, const struct stat *stat) +{ + memset(result, 0, sizeof(*result)); + result->st_dev = stat->st_dev; + result->st_ino = stat->st_ino; + result->st_mode = stat->st_mode; + result->st_nlink = stat->st_nlink; + result->st_uid = stat->st_uid; + result->st_gid = stat->st_gid; + result->st_rdev = stat->st_rdev; + result->st_size = stat->st_size; + result->st_atime = stat->st_atime; + result->st_mtime = stat->st_mtime; + result->st_ctime = stat->st_ctime; +} + +void CUtil::Stat64ToStat(struct stat *result, struct __stat64 *stat) +{ + result->st_dev = stat->st_dev; + result->st_ino = stat->st_ino; + result->st_mode = stat->st_mode; + result->st_nlink = stat->st_nlink; + result->st_uid = stat->st_uid; + result->st_gid = stat->st_gid; + result->st_rdev = stat->st_rdev; +#ifndef TARGET_POSIX + if (stat->st_size <= LONG_MAX) + result->st_size = (_off_t)stat->st_size; +#else + if (sizeof(stat->st_size) <= sizeof(result->st_size) ) + result->st_size = stat->st_size; +#endif + else + { + result->st_size = 0; + CLog::Log(LOGWARNING, "WARNING: File is larger than 32bit stat can handle, file size will be reported as 0 bytes"); + } + result->st_atime = (time_t)(stat->st_atime & 0xFFFFFFFF); + result->st_mtime = (time_t)(stat->st_mtime & 0xFFFFFFFF); + result->st_ctime = (time_t)(stat->st_ctime & 0xFFFFFFFF); +} + +#ifdef TARGET_WINDOWS +void CUtil::Stat64ToStat64i32(struct _stat64i32 *result, struct __stat64 *stat) +{ + result->st_dev = stat->st_dev; + result->st_ino = stat->st_ino; + result->st_mode = stat->st_mode; + result->st_nlink = stat->st_nlink; + result->st_uid = stat->st_uid; + result->st_gid = stat->st_gid; + result->st_rdev = stat->st_rdev; +#ifndef TARGET_POSIX + if (stat->st_size <= LONG_MAX) + result->st_size = (_off_t)stat->st_size; +#else + if (sizeof(stat->st_size) <= sizeof(result->st_size) ) + result->st_size = stat->st_size; +#endif + else + { + result->st_size = 0; + CLog::Log(LOGWARNING, "WARNING: File is larger than 32bit stat can handle, file size will be reported as 0 bytes"); + } +#ifndef TARGET_POSIX + result->st_atime = stat->st_atime; + result->st_mtime = stat->st_mtime; + result->st_ctime = stat->st_ctime; +#else + result->st_atime = stat->_st_atime; + result->st_mtime = stat->_st_mtime; + result->st_ctime = stat->_st_ctime; +#endif +} +#endif + +bool CUtil::CreateDirectoryEx(const std::string& strPath) +{ + // Function to create all directories at once instead + // of calling CreateDirectory for every subdir. + // Creates the directory and subdirectories if needed. + + // return true if directory already exist + if (CDirectory::Exists(strPath)) return true; + + // we currently only allow HD and smb and nfs paths + if (!URIUtils::IsHD(strPath) && !URIUtils::IsSmb(strPath) && !URIUtils::IsNfs(strPath)) + { + CLog::Log(LOGERROR, "{} called with an unsupported path: {}", __FUNCTION__, strPath); + return false; + } + + std::vector<std::string> dirs = URIUtils::SplitPath(strPath); + if (dirs.empty()) + return false; + std::string dir(dirs.front()); + URIUtils::AddSlashAtEnd(dir); + for (std::vector<std::string>::const_iterator it = dirs.begin() + 1; it != dirs.end(); ++it) + { + dir = URIUtils::AddFileToFolder(dir, *it); + CDirectory::Create(dir); + } + + // was the final destination directory successfully created ? + return CDirectory::Exists(strPath); +} + +std::string CUtil::MakeLegalFileName(const std::string &strFile, int LegalType) +{ + std::string result = strFile; + + StringUtils::Replace(result, '/', '_'); + StringUtils::Replace(result, '\\', '_'); + StringUtils::Replace(result, '?', '_'); + + if (LegalType == LEGAL_WIN32_COMPAT) + { + // just filter out some illegal characters on windows + StringUtils::Replace(result, ':', '_'); + StringUtils::Replace(result, '*', '_'); + StringUtils::Replace(result, '?', '_'); + StringUtils::Replace(result, '\"', '_'); + StringUtils::Replace(result, '<', '_'); + StringUtils::Replace(result, '>', '_'); + StringUtils::Replace(result, '|', '_'); + StringUtils::TrimRight(result, ". "); + } + return result; +} + +// legalize entire path +std::string CUtil::MakeLegalPath(const std::string &strPathAndFile, int LegalType) +{ + if (URIUtils::IsStack(strPathAndFile)) + return MakeLegalPath(CStackDirectory::GetFirstStackedFile(strPathAndFile)); + if (URIUtils::IsMultiPath(strPathAndFile)) + return MakeLegalPath(CMultiPathDirectory::GetFirstPath(strPathAndFile)); + if (!URIUtils::IsHD(strPathAndFile) && !URIUtils::IsSmb(strPathAndFile) && !URIUtils::IsNfs(strPathAndFile)) + return strPathAndFile; // we don't support writing anywhere except HD, SMB and NFS - no need to legalize path + + bool trailingSlash = URIUtils::HasSlashAtEnd(strPathAndFile); + std::vector<std::string> dirs = URIUtils::SplitPath(strPathAndFile); + if (dirs.empty()) + return strPathAndFile; + // we just add first token to path and don't legalize it - possible values: + // "X:" (local win32), "" (local unix - empty string before '/') or + // "protocol://domain" + std::string dir(dirs.front()); + URIUtils::AddSlashAtEnd(dir); + for (std::vector<std::string>::const_iterator it = dirs.begin() + 1; it != dirs.end(); ++it) + dir = URIUtils::AddFileToFolder(dir, MakeLegalFileName(*it, LegalType)); + if (trailingSlash) URIUtils::AddSlashAtEnd(dir); + return dir; +} + +std::string CUtil::ValidatePath(const std::string &path, bool bFixDoubleSlashes /* = false */) +{ + std::string result = path; + + // Don't do any stuff on URLs containing %-characters or protocols that embed + // filenames. NOTE: Don't use IsInZip or IsInRar here since it will infinitely + // recurse and crash XBMC + if (URIUtils::IsURL(path) && + (path.find('%') != std::string::npos || + StringUtils::StartsWithNoCase(path, "apk:") || + StringUtils::StartsWithNoCase(path, "zip:") || + StringUtils::StartsWithNoCase(path, "rar:") || + StringUtils::StartsWithNoCase(path, "stack:") || + StringUtils::StartsWithNoCase(path, "bluray:") || + StringUtils::StartsWithNoCase(path, "multipath:") )) + return result; + + // check the path for incorrect slashes +#ifdef TARGET_WINDOWS + if (URIUtils::IsDOSPath(path)) + { + StringUtils::Replace(result, '/', '\\'); + /* The double slash correction should only be used when *absolutely* + necessary! This applies to certain DLLs or use from Python DLLs/scripts + that incorrectly generate double (back) slashes. + */ + if (bFixDoubleSlashes && !result.empty()) + { + // Fixup for double back slashes (but ignore the \\ of unc-paths) + for (size_t x = 1; x < result.size() - 1; x++) + { + if (result[x] == '\\' && result[x+1] == '\\') + result.erase(x, 1); + } + } + } + else if (path.find("://") != std::string::npos || path.find(":\\\\") != std::string::npos) +#endif + { + StringUtils::Replace(result, '\\', '/'); + /* The double slash correction should only be used when *absolutely* + necessary! This applies to certain DLLs or use from Python DLLs/scripts + that incorrectly generate double (back) slashes. + */ + if (bFixDoubleSlashes && !result.empty()) + { + // Fixup for double forward slashes(/) but don't touch the :// of URLs + for (size_t x = 2; x < result.size() - 1; x++) + { + if ( result[x] == '/' && result[x + 1] == '/' && !(result[x - 1] == ':' || (result[x - 1] == '/' && result[x - 2] == ':')) ) + result.erase(x, 1); + } + } + } + return result; +} + +void CUtil::SplitParams(const std::string ¶mString, std::vector<std::string> ¶meters) +{ + bool inQuotes = false; + bool lastEscaped = false; // only every second character can be escaped + int inFunction = 0; + size_t whiteSpacePos = 0; + std::string parameter; + parameters.clear(); + for (size_t pos = 0; pos < paramString.size(); pos++) + { + char ch = paramString[pos]; + bool escaped = (pos > 0 && paramString[pos - 1] == '\\' && !lastEscaped); + lastEscaped = escaped; + if (inQuotes) + { // if we're in a quote, we accept everything until the closing quote + if (ch == '"' && !escaped) + { // finished a quote - no need to add the end quote to our string + inQuotes = false; + } + } + else + { // not in a quote, so check if we should be starting one + if (ch == '"' && !escaped) + { // start of quote - no need to add the quote to our string + inQuotes = true; + } + if (inFunction && ch == ')') + { // end of a function + inFunction--; + } + if (ch == '(') + { // start of function + inFunction++; + } + if (!inFunction && ch == ',') + { // not in a function, so a comma signifies the end of this parameter + if (whiteSpacePos) + parameter = parameter.substr(0, whiteSpacePos); + // trim off start and end quotes + if (parameter.length() > 1 && parameter[0] == '"' && parameter[parameter.length() - 1] == '"') + parameter = parameter.substr(1, parameter.length() - 2); + else if (parameter.length() > 3 && parameter[parameter.length() - 1] == '"') + { + // check name="value" style param. + size_t quotaPos = parameter.find('"'); + if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=') + { + parameter.erase(parameter.length() - 1); + parameter.erase(quotaPos); + } + } + parameters.push_back(parameter); + parameter.clear(); + whiteSpacePos = 0; + continue; + } + } + if ((ch == '"' || ch == '\\') && escaped) + { // escaped quote or backslash + parameter[parameter.size()-1] = ch; + continue; + } + // whitespace handling - we skip any whitespace at the left or right of an unquoted parameter + if (ch == ' ' && !inQuotes) + { + if (parameter.empty()) // skip whitespace on left + continue; + if (!whiteSpacePos) // make a note of where whitespace starts on the right + whiteSpacePos = parameter.size(); + } + else + whiteSpacePos = 0; + parameter += ch; + } + if (inFunction || inQuotes) + CLog::Log(LOGWARNING, "{}({}) - end of string while searching for ) or \"", __FUNCTION__, + paramString); + if (whiteSpacePos) + parameter.erase(whiteSpacePos); + // trim off start and end quotes + if (parameter.size() > 1 && parameter[0] == '"' && parameter[parameter.size() - 1] == '"') + parameter = parameter.substr(1,parameter.size() - 2); + else if (parameter.size() > 3 && parameter[parameter.size() - 1] == '"') + { + // check name="value" style param. + size_t quotaPos = parameter.find('"'); + if (quotaPos > 1 && quotaPos < parameter.length() - 1 && parameter[quotaPos - 1] == '=') + { + parameter.erase(parameter.length() - 1); + parameter.erase(quotaPos); + } + } + if (!parameter.empty() || parameters.size()) + parameters.push_back(parameter); +} + +int CUtil::GetMatchingSource(const std::string& strPath1, VECSOURCES& VECSOURCES, bool& bIsSourceName) +{ + if (strPath1.empty()) + return -1; + + // copy as we may change strPath + std::string strPath = strPath1; + + // Check for special protocols + CURL checkURL(strPath); + + if (StringUtils::StartsWith(strPath, "special://skin/")) + return 1; + + // do not return early if URL protocol is "plugin" + // since video- and/or audio-plugins can be configured as mediasource + + // stack:// + if (checkURL.IsProtocol("stack")) + strPath.erase(0, 8); // remove the stack protocol + + if (checkURL.IsProtocol("shout")) + strPath = checkURL.GetHostName(); + + if (checkURL.IsProtocol("multipath")) + strPath = CMultiPathDirectory::GetFirstPath(strPath); + + bIsSourceName = false; + int iIndex = -1; + + // we first test the NAME of a source + for (int i = 0; i < (int)VECSOURCES.size(); ++i) + { + const CMediaSource &share = VECSOURCES[i]; + std::string strName = share.strName; + + // special cases for dvds + if (URIUtils::IsOnDVD(share.strPath)) + { + if (URIUtils::IsOnDVD(strPath)) + return i; + + // not a path, so we need to modify the source name + // since we add the drive status and disc name to the source + // "Name (Drive Status/Disc Name)" + size_t iPos = strName.rfind('('); + if (iPos != std::string::npos && iPos > 1) + strName = strName.substr(0, iPos - 1); + } + if (StringUtils::EqualsNoCase(strPath, strName)) + { + bIsSourceName = true; + return i; + } + } + + // now test the paths + + // remove user details, and ensure path only uses forward slashes + // and ends with a trailing slash so as not to match a substring + CURL urlDest(strPath); + urlDest.SetOptions(""); + urlDest.SetProtocolOptions(""); + std::string strDest = urlDest.GetWithoutUserDetails(); + ForceForwardSlashes(strDest); + if (!URIUtils::HasSlashAtEnd(strDest)) + strDest += "/"; + + size_t iLength = 0; + size_t iLenPath = strDest.size(); + for (int i = 0; i < (int)VECSOURCES.size(); ++i) + { + const CMediaSource &share = VECSOURCES[i]; + + // does it match a source name? + if (share.strPath.substr(0,8) == "shout://") + { + CURL url(share.strPath); + if (strPath == url.GetHostName()) + return i; + } + + // doesn't match a name, so try the source path + std::vector<std::string> vecPaths; + + // add any concatenated paths if they exist + if (!share.vecPaths.empty()) + vecPaths = share.vecPaths; + + // add the actual share path at the front of the vector + vecPaths.insert(vecPaths.begin(), share.strPath); + + // test each path + for (const auto &path : vecPaths) + { + // remove user details, and ensure path only uses forward slashes + // and ends with a trailing slash so as not to match a substring + CURL urlShare(path); + urlShare.SetOptions(""); + urlShare.SetProtocolOptions(""); + std::string strShare = urlShare.GetWithoutUserDetails(); + ForceForwardSlashes(strShare); + if (!URIUtils::HasSlashAtEnd(strShare)) + strShare += "/"; + size_t iLenShare = strShare.size(); + + if ((iLenPath >= iLenShare) && StringUtils::StartsWithNoCase(strDest, strShare) && (iLenShare > iLength)) + { + // if exact match, return it immediately + if (iLenPath == iLenShare) + { + // if the path EXACTLY matches an item in a concatenated path + // set source name to true to load the full virtualpath + bIsSourceName = false; + if (vecPaths.size() > 1) + bIsSourceName = true; + return i; + } + iIndex = i; + iLength = iLenShare; + } + } + } + + // return the index of the share with the longest match + if (iIndex == -1) + { + + // rar:// and zip:// + // if archive wasn't mounted, look for a matching share for the archive instead + if( StringUtils::StartsWithNoCase(strPath, "rar://") || StringUtils::StartsWithNoCase(strPath, "zip://") ) + { + // get the hostname portion of the url since it contains the archive file + strPath = checkURL.GetHostName(); + + bIsSourceName = false; + bool bDummy; + return GetMatchingSource(strPath, VECSOURCES, bDummy); + } + + CLog::Log(LOGDEBUG, "CUtil::GetMatchingSource: no matching source found for [{}]", strPath1); + } + return iIndex; +} + +std::string CUtil::TranslateSpecialSource(const std::string &strSpecial) +{ + if (!strSpecial.empty() && strSpecial[0] == '$') + { + if (StringUtils::StartsWithNoCase(strSpecial, "$home")) + return URIUtils::AddFileToFolder("special://home/", strSpecial.substr(5)); + else if (StringUtils::StartsWithNoCase(strSpecial, "$subtitles")) + return URIUtils::AddFileToFolder("special://subtitles/", strSpecial.substr(10)); + else if (StringUtils::StartsWithNoCase(strSpecial, "$userdata")) + return URIUtils::AddFileToFolder("special://userdata/", strSpecial.substr(9)); + else if (StringUtils::StartsWithNoCase(strSpecial, "$database")) + return URIUtils::AddFileToFolder("special://database/", strSpecial.substr(9)); + else if (StringUtils::StartsWithNoCase(strSpecial, "$thumbnails")) + return URIUtils::AddFileToFolder("special://thumbnails/", strSpecial.substr(11)); + else if (StringUtils::StartsWithNoCase(strSpecial, "$recordings")) + return URIUtils::AddFileToFolder("special://recordings/", strSpecial.substr(11)); + else if (StringUtils::StartsWithNoCase(strSpecial, "$screenshots")) + return URIUtils::AddFileToFolder("special://screenshots/", strSpecial.substr(12)); + else if (StringUtils::StartsWithNoCase(strSpecial, "$musicplaylists")) + return URIUtils::AddFileToFolder("special://musicplaylists/", strSpecial.substr(15)); + else if (StringUtils::StartsWithNoCase(strSpecial, "$videoplaylists")) + return URIUtils::AddFileToFolder("special://videoplaylists/", strSpecial.substr(15)); + else if (StringUtils::StartsWithNoCase(strSpecial, "$cdrips")) + return URIUtils::AddFileToFolder("special://cdrips/", strSpecial.substr(7)); + // this one will be removed post 2.0 + else if (StringUtils::StartsWithNoCase(strSpecial, "$playlists")) + return URIUtils::AddFileToFolder(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), strSpecial.substr(10)); + } + return strSpecial; +} + +std::string CUtil::MusicPlaylistsLocation() +{ + const std::string path = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH); + std::vector<std::string> vec; + vec.push_back(URIUtils::AddFileToFolder(path, "music")); + vec.push_back(URIUtils::AddFileToFolder(path, "mixed")); + return XFILE::CMultiPathDirectory::ConstructMultiPath(vec); +} + +std::string CUtil::VideoPlaylistsLocation() +{ + const std::string path = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH); + std::vector<std::string> vec; + vec.push_back(URIUtils::AddFileToFolder(path, "video")); + vec.push_back(URIUtils::AddFileToFolder(path, "mixed")); + return XFILE::CMultiPathDirectory::ConstructMultiPath(vec); +} + +void CUtil::DeleteMusicDatabaseDirectoryCache() +{ + CUtil::DeleteDirectoryCache("mdb-"); + CUtil::DeleteDirectoryCache("sp-"); // overkill as it will delete video smartplaylists, but as we can't differentiate based on URL... +} + +void CUtil::DeleteVideoDatabaseDirectoryCache() +{ + CUtil::DeleteDirectoryCache("vdb-"); + CUtil::DeleteDirectoryCache("sp-"); // overkill as it will delete music smartplaylists, but as we can't differentiate based on URL... +} + +void CUtil::DeleteDirectoryCache(const std::string &prefix) +{ + std::string searchPath = "special://temp/"; + CFileItemList items; + if (!XFILE::CDirectory::GetDirectory(searchPath, items, ".fi", DIR_FLAG_NO_FILE_DIRS)) + return; + + for (const auto &item : items) + { + if (item->m_bIsFolder) + continue; + std::string fileName = URIUtils::GetFileName(item->GetPath()); + if (StringUtils::StartsWith(fileName, prefix)) + XFILE::CFile::Delete(item->GetPath()); + } +} + + +void CUtil::GetRecursiveListing(const std::string& strPath, CFileItemList& items, const std::string& strMask, unsigned int flags /* = DIR_FLAG_DEFAULTS */) +{ + CFileItemList myItems; + CDirectory::GetDirectory(strPath,myItems,strMask,flags); + for (const auto &item : myItems) + { + if (item->m_bIsFolder) + CUtil::GetRecursiveListing(item->GetPath(),items,strMask,flags); + else + items.Add(item); + } +} + +void CUtil::GetRecursiveDirsListing(const std::string& strPath, CFileItemList& item, unsigned int flags /* = DIR_FLAG_DEFAULTS */) +{ + CFileItemList myItems; + CDirectory::GetDirectory(strPath,myItems,"",flags); + for (const auto &i : myItems) + { + if (i->m_bIsFolder && !i->IsPath("..")) + { + item.Add(i); + CUtil::GetRecursiveDirsListing(i->GetPath(),item,flags); + } + } +} + +void CUtil::ForceForwardSlashes(std::string& strPath) +{ + size_t iPos = strPath.rfind('\\'); + while (iPos != std::string::npos) + { + strPath.at(iPos) = '/'; + iPos = strPath.rfind('\\'); + } +} + +double CUtil::AlbumRelevance(const std::string& strAlbumTemp1, const std::string& strAlbum1, const std::string& strArtistTemp1, const std::string& strArtist1) +{ + // case-insensitive fuzzy string comparison on the album and artist for relevance + // weighting is identical, both album and artist are 50% of the total relevance + // a missing artist means the maximum relevance can only be 0.50 + std::string strAlbumTemp = strAlbumTemp1; + StringUtils::ToLower(strAlbumTemp); + std::string strAlbum = strAlbum1; + StringUtils::ToLower(strAlbum); + double fAlbumPercentage = fstrcmp(strAlbumTemp.c_str(), strAlbum.c_str()); + double fArtistPercentage = 0.0; + if (!strArtist1.empty()) + { + std::string strArtistTemp = strArtistTemp1; + StringUtils::ToLower(strArtistTemp); + std::string strArtist = strArtist1; + StringUtils::ToLower(strArtist); + fArtistPercentage = fstrcmp(strArtistTemp.c_str(), strArtist.c_str()); + } + double fRelevance = fAlbumPercentage * 0.5 + fArtistPercentage * 0.5; + return fRelevance; +} + +bool CUtil::MakeShortenPath(std::string StrInput, std::string& StrOutput, size_t iTextMaxLength) +{ + size_t iStrInputSize = StrInput.size(); + if(iStrInputSize <= 0 || iTextMaxLength >= iStrInputSize) + { + StrOutput = StrInput; + return true; + } + + char cDelim = '\0'; + size_t nGreaterDelim, nPos; + + nPos = StrInput.find_last_of( '\\' ); + if (nPos != std::string::npos) + cDelim = '\\'; + else + { + nPos = StrInput.find_last_of( '/' ); + if (nPos != std::string::npos) + cDelim = '/'; + } + if ( cDelim == '\0' ) + return false; + + if (nPos == StrInput.size() - 1) + { + StrInput.erase(StrInput.size() - 1); + nPos = StrInput.find_last_of(cDelim); + } + while( iTextMaxLength < iStrInputSize ) + { + nPos = StrInput.find_last_of( cDelim, nPos ); + nGreaterDelim = nPos; + + if (nPos == std::string::npos || nPos == 0) + break; + + nPos = StrInput.find_last_of( cDelim, nPos - 1 ); + + if ( nPos == std::string::npos ) + break; + if ( nGreaterDelim > nPos ) + StrInput.replace( nPos + 1, nGreaterDelim - nPos - 1, ".." ); + iStrInputSize = StrInput.size(); + } + // replace any additional /../../ with just /../ if necessary + std::string replaceDots = StringUtils::Format("..{}..", cDelim); + while (StrInput.size() > (unsigned int)iTextMaxLength) + if (!StringUtils::Replace(StrInput, replaceDots, "..")) + break; + // finally, truncate our string to force inside our max text length, + // replacing the last 2 characters with ".." + + // eg end up with: + // "smb://../Playboy Swimsuit Cal.." + if (iTextMaxLength > 2 && StrInput.size() > (unsigned int)iTextMaxLength) + { + StrInput.erase(iTextMaxLength - 2); + StrInput += ".."; + } + StrOutput = StrInput; + return true; +} + +bool CUtil::SupportsWriteFileOperations(const std::string& strPath) +{ + // currently only hd, smb, nfs and dav support delete and rename + if (URIUtils::IsHD(strPath)) + return true; + if (URIUtils::IsSmb(strPath)) + return true; + if (URIUtils::IsPVRRecording(strPath)) + return CPVRDirectory::SupportsWriteFileOperations(strPath); + if (URIUtils::IsNfs(strPath)) + return true; + if (URIUtils::IsDAV(strPath)) + return true; + if (URIUtils::IsStack(strPath)) + return SupportsWriteFileOperations(CStackDirectory::GetFirstStackedFile(strPath)); + if (URIUtils::IsMultiPath(strPath)) + return CMultiPathDirectory::SupportsWriteFileOperations(strPath); + + if (CServiceBroker::IsAddonInterfaceUp()) + { + CURL url(strPath); + for (const auto& addon : CServiceBroker::GetVFSAddonCache().GetAddonInstances()) + { + const auto& info = addon->GetProtocolInfo(); + auto prots = StringUtils::Split(info.type, "|"); + if (info.supportWrite && + std::find(prots.begin(), prots.end(), url.GetProtocol()) != prots.end()) + return true; + } + } + + return false; +} + +bool CUtil::SupportsReadFileOperations(const std::string& strPath) +{ + return !URIUtils::IsVideoDb(strPath); +} + +std::string CUtil::GetDefaultFolderThumb(const std::string &folderThumb) +{ + if (CServiceBroker::GetGUI()->GetTextureManager().HasTexture(folderThumb)) + return folderThumb; + return ""; +} + +void CUtil::GetSkinThemes(std::vector<std::string>& vecTheme) +{ + static const std::string TexturesXbt = "Textures.xbt"; + + std::string strPath = URIUtils::AddFileToFolder(CServiceBroker::GetWinSystem()->GetGfxContext().GetMediaDir(), "media"); + CFileItemList items; + CDirectory::GetDirectory(strPath, items, "", DIR_FLAG_DEFAULTS); + // Search for Themes in the Current skin! + for (const auto &pItem : items) + { + if (!pItem->m_bIsFolder) + { + std::string strExtension = URIUtils::GetExtension(pItem->GetPath()); + std::string strLabel = pItem->GetLabel(); + if ((strExtension == ".xbt" && !StringUtils::EqualsNoCase(strLabel, TexturesXbt))) + vecTheme.push_back(StringUtils::Left(strLabel, strLabel.size() - strExtension.size())); + } + else + { + // check if this is an xbt:// VFS path + CURL itemUrl(pItem->GetPath()); + if (!itemUrl.IsProtocol("xbt") || !itemUrl.GetFileName().empty()) + continue; + + std::string strLabel = URIUtils::GetFileName(itemUrl.GetHostName()); + if (!StringUtils::EqualsNoCase(strLabel, TexturesXbt)) + vecTheme.push_back(StringUtils::Left(strLabel, strLabel.size() - URIUtils::GetExtension(strLabel).size())); + } + } + std::sort(vecTheme.begin(), vecTheme.end(), sortstringbyname()); +} + +void CUtil::InitRandomSeed() +{ + // Init random seed + auto now = std::chrono::steady_clock::now(); + auto seed = now.time_since_epoch(); + + srand(static_cast<unsigned int>(seed.count())); +} + +#if defined(TARGET_POSIX) && !defined(TARGET_DARWIN_TVOS) +bool CUtil::RunCommandLine(const std::string& cmdLine, bool waitExit) +{ + std::vector<std::string> args = StringUtils::Split(cmdLine, ","); + + // Strip quotes and whitespace around the arguments, or exec will fail. + // This allows the python invocation to be written more naturally with any amount of whitespace around the args. + // But it's still limited, for example quotes inside the strings are not expanded, etc. + //! @todo Maybe some python library routine can parse this more properly ? + for (std::vector<std::string>::iterator it = args.begin(); it != args.end(); ++it) + { + size_t pos; + pos = it->find_first_not_of(" \t\n\"'"); + if (pos != std::string::npos) + { + it->erase(0, pos); + } + + pos = it->find_last_not_of(" \t\n\"'"); // if it returns npos we'll end up with an empty string which is OK + { + it->erase(++pos, it->size()); + } + } + + return Command(args, waitExit); +} + +bool CUtil::Command(const std::vector<std::string>& arrArgs, bool waitExit) +{ +#ifdef _DEBUG + printf("Executing: "); + for (const auto &arg : arrArgs) + printf("%s ", arg.c_str()); + printf("\n"); +#endif + + pid_t child = fork(); + int n = 0; + if (child == 0) + { + if (!waitExit) + { + // fork again in order not to leave a zombie process + child = fork(); + if (child == -1) + _exit(2); + else if (child != 0) + _exit(0); + } + close(0); + close(1); + close(2); + if (!arrArgs.empty()) + { + char **args = (char **)alloca(sizeof(char *) * (arrArgs.size() + 3)); + memset(args, 0, (sizeof(char *) * (arrArgs.size() + 3))); + for (size_t i=0; i<arrArgs.size(); i++) + args[i] = const_cast<char *>(arrArgs[i].c_str()); + execvp(args[0], args); + } + } + else + { + waitpid(child, &n, 0); + } + + return (waitExit) ? (WEXITSTATUS(n) == 0) : true; +} +#endif + +int CUtil::LookupRomanDigit(char roman_digit) +{ + switch (roman_digit) + { + case 'i': + case 'I': + return 1; + case 'v': + case 'V': + return 5; + case 'x': + case 'X': + return 10; + case 'l': + case 'L': + return 50; + case 'c': + case 'C': + return 100; + case 'd': + case 'D': + return 500; + case 'm': + case 'M': + return 1000; + default: + return 0; + } +} + +int CUtil::TranslateRomanNumeral(const char* roman_numeral) +{ + + int decimal = -1; + + if (roman_numeral && roman_numeral[0]) + { + int temp_sum = 0, + last = 0, + repeat = 0, + trend = 1; + decimal = 0; + while (*roman_numeral) + { + int digit = CUtil::LookupRomanDigit(*roman_numeral); + int test = last; + + // General sanity checks + + // numeral not in LUT + if (!digit) + return -1; + + while (test > 5) + test /= 10; + + // N = 10^n may not precede (N+1) > 10^(N+1) + if (test == 1 && digit > last * 10) + return -1; + + // N = 5*10^n may not precede (N+1) >= N + if (test == 5 && digit >= last) + return -1; + + // End general sanity checks + + if (last < digit) + { + // smaller numerals may not repeat before a larger one + if (repeat) + return -1; + + temp_sum += digit; + + repeat = 0; + trend = 0; + } + else if (last == digit) + { + temp_sum += digit; + repeat++; + trend = 1; + } + else + { + if (!repeat) + decimal += 2 * last - temp_sum; + else + decimal += temp_sum; + + temp_sum = digit; + + trend = 1; + repeat = 0; + } + // Post general sanity checks + + // numerals may not repeat more than thrice + if (repeat == 3) + return -1; + + last = digit; + roman_numeral++; + } + + if (trend) + decimal += temp_sum; + else + decimal += 2 * last - temp_sum; + } + return decimal; +} + +std::string CUtil::ResolveExecutablePath() +{ + std::string strExecutablePath; +#ifdef TARGET_WINDOWS + static const size_t bufSize = MAX_PATH * 2; + wchar_t* buf = new wchar_t[bufSize]; + buf[0] = 0; + ::GetModuleFileNameW(0, buf, bufSize); + buf[bufSize-1] = 0; + g_charsetConverter.wToUTF8(buf,strExecutablePath); + delete[] buf; +#elif defined(TARGET_DARWIN) + char given_path[2*MAXPATHLEN]; + size_t path_size =2*MAXPATHLEN; + + CDarwinUtils::GetExecutablePath(given_path, &path_size); + strExecutablePath = given_path; +#elif defined(TARGET_FREEBSD) + char buf[PATH_MAX]; + size_t buflen; + int mib[4]; + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = getpid(); + + buflen = sizeof(buf) - 1; + if(sysctl(mib, 4, buf, &buflen, NULL, 0) < 0) + strExecutablePath = ""; + else + strExecutablePath = buf; +#elif defined(TARGET_ANDROID) + strExecutablePath = CXBMCApp::getApplicationInfo().nativeLibraryDir; + + std::string appName = CCompileInfo::GetAppName(); + std::string libName = "lib" + appName + ".so"; + StringUtils::ToLower(libName); + strExecutablePath += "/" + libName; +#else + /* Get our PID and build the name of the link in /proc */ + pid_t pid = getpid(); + char linkname[64]; /* /proc/<pid>/exe */ + snprintf(linkname, sizeof(linkname), "/proc/%i/exe", pid); + + /* Now read the symbolic link */ + char buf[PATH_MAX + 1]; + buf[0] = 0; + + int ret = readlink(linkname, buf, sizeof(buf) - 1); + if (ret != -1) + buf[ret] = 0; + + strExecutablePath = buf; +#endif + return strExecutablePath; +} + +std::string CUtil::GetFrameworksPath(bool forPython) +{ + std::string strFrameworksPath; +#if defined(TARGET_DARWIN) + strFrameworksPath = CDarwinUtils::GetFrameworkPath(forPython); +#endif + return strFrameworksPath; +} + +void CUtil::GetVideoBasePathAndFileName(const std::string& videoPath, std::string& basePath, std::string& videoFileName) +{ + CFileItem item(videoPath, false); + videoFileName = URIUtils::ReplaceExtension(URIUtils::GetFileName(videoPath), ""); + + if (item.HasVideoInfoTag()) + basePath = item.GetVideoInfoTag()->m_basePath; + + if (basePath.empty() && item.IsOpticalMediaFile()) + basePath = item.GetLocalMetadataPath(); + + CURL url(videoPath); + if (basePath.empty() && url.IsProtocol("bluray")) + { + basePath = url.GetHostName(); + videoFileName = URIUtils::ReplaceExtension(GetTitleFromPath(url.GetHostName()), ""); + + url = CURL(url.GetHostName()); + if (url.IsProtocol("udf")) + basePath = URIUtils::GetParentPath(url.GetHostName()); + } + + if (basePath.empty()) + basePath = URIUtils::GetBasePath(videoPath); +} + +void CUtil::GetItemsToScan(const std::string& videoPath, + const std::string& item_exts, + const std::vector<std::string>& sub_dirs, + CFileItemList& items) +{ + int flags = DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO; + + if (!videoPath.empty()) + CDirectory::GetDirectory(videoPath, items, item_exts, flags); + + std::vector<std::string> additionalPaths; + for (const auto &item : items) + { + for (const auto& subdir : sub_dirs) + { + if (StringUtils::EqualsNoCase(item->GetLabel(), subdir)) + additionalPaths.push_back(item->GetPath()); + } + } + + for (std::vector<std::string>::const_iterator it = additionalPaths.begin(); it != additionalPaths.end(); ++it) + { + CFileItemList moreItems; + CDirectory::GetDirectory(*it, moreItems, item_exts, flags); + items.Append(moreItems); + } +} + + +void CUtil::ScanPathsForAssociatedItems(const std::string& videoName, + const CFileItemList& items, + const std::vector<std::string>& item_exts, + std::vector<std::string>& associatedFiles) +{ + for (const auto &pItem : items) + { + if (pItem->m_bIsFolder) + continue; + + std::string strCandidate = URIUtils::GetFileName(pItem->GetPath()); + + // skip duplicates + if (std::find(associatedFiles.begin(), associatedFiles.end(), pItem->GetPath()) != associatedFiles.end()) + continue; + + URIUtils::RemoveExtension(strCandidate); + // NOTE: We don't know if one of videoName or strCandidate is URL-encoded and the other is not, so try both + if (StringUtils::StartsWithNoCase(strCandidate, videoName) || (StringUtils::StartsWithNoCase(strCandidate, CURL::Decode(videoName)))) + { + if (URIUtils::IsRAR(pItem->GetPath()) || URIUtils::IsZIP(pItem->GetPath())) + CUtil::ScanArchiveForAssociatedItems(pItem->GetPath(), "", item_exts, associatedFiles); + else + { + associatedFiles.push_back(pItem->GetPath()); + CLog::Log(LOGINFO, "{}: found associated file {}", __FUNCTION__, + CURL::GetRedacted(pItem->GetPath())); + } + } + else + { + if (URIUtils::IsRAR(pItem->GetPath()) || URIUtils::IsZIP(pItem->GetPath())) + CUtil::ScanArchiveForAssociatedItems(pItem->GetPath(), videoName, item_exts, associatedFiles); + } + } +} + +int CUtil::ScanArchiveForAssociatedItems(const std::string& strArchivePath, + const std::string& videoNameNoExt, + const std::vector<std::string>& item_exts, + std::vector<std::string>& associatedFiles) +{ + CLog::LogF(LOGDEBUG, "Scanning archive {}", CURL::GetRedacted(strArchivePath)); + int nItemsAdded = 0; + CFileItemList ItemList; + + // zip only gets the root dir + if (URIUtils::HasExtension(strArchivePath, ".zip")) + { + CURL pathToUrl(strArchivePath); + CURL zipURL = URIUtils::CreateArchivePath("zip", pathToUrl, ""); + if (!CDirectory::GetDirectory(zipURL, ItemList, "", DIR_FLAG_NO_FILE_DIRS)) + return false; + } + else if (URIUtils::HasExtension(strArchivePath, ".rar")) + { + CURL pathToUrl(strArchivePath); + CURL rarURL = URIUtils::CreateArchivePath("rar", pathToUrl, ""); + if (!CDirectory::GetDirectory(rarURL, ItemList, "", DIR_FLAG_NO_FILE_DIRS)) + return false; + } + for (const auto &item : ItemList) + { + std::string strPathInRar = item->GetPath(); + std::string strExt = URIUtils::GetExtension(strPathInRar); + + // Check another archive in archive + if (strExt == ".zip" || strExt == ".rar") + { + nItemsAdded += + ScanArchiveForAssociatedItems(strPathInRar, videoNameNoExt, item_exts, associatedFiles); + continue; + } + + // check that the found filename matches the movie filename + size_t fnl = videoNameNoExt.size(); + // NOTE: We don't know if videoNameNoExt is URL-encoded, so try both + if (fnl && + !(StringUtils::StartsWithNoCase(URIUtils::GetFileName(strPathInRar), videoNameNoExt) || + StringUtils::StartsWithNoCase(URIUtils::GetFileName(strPathInRar), CURL::Decode(videoNameNoExt)))) + continue; + + for (const auto& ext : item_exts) + { + if (StringUtils::EqualsNoCase(strExt, ext)) + { + CLog::Log(LOGINFO, "{}: found associated file {}", __FUNCTION__, + CURL::GetRedacted(strPathInRar)); + associatedFiles.push_back(strPathInRar); + nItemsAdded++; + break; + } + } + } + + return nItemsAdded; +} + +void CUtil::ScanForExternalSubtitles(const std::string& strMovie, std::vector<std::string>& vecSubtitles) +{ + auto start = std::chrono::steady_clock::now(); + + CFileItem item(strMovie, false); + if ((item.IsInternetStream() && !URIUtils::IsOnLAN(item.GetDynPath())) + || item.IsPlayList() + || item.IsLiveTV() + || !item.IsVideo()) + return; + + CLog::Log(LOGDEBUG, "{}: Searching for subtitles...", __FUNCTION__); + + std::string strBasePath; + std::string strSubtitle; + + GetVideoBasePathAndFileName(strMovie, strBasePath, strSubtitle); + + CFileItemList items; + const std::vector<std::string> common_sub_dirs = { "subs", "subtitles", "vobsubs", "sub", "vobsub", "subtitle" }; + const std::string subtitleExtensions = CServiceBroker::GetFileExtensionProvider().GetSubtitleExtensions(); + GetItemsToScan(strBasePath, subtitleExtensions, common_sub_dirs, items); + + const std::string customPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SUBTITLES_CUSTOMPATH); + + if (!CMediaSettings::GetInstance().GetAdditionalSubtitleDirectoryChecked() && !customPath.empty()) // to avoid checking non-existent directories (network) every time.. + { + if (!CServiceBroker::GetNetwork().IsAvailable() && !URIUtils::IsHD(customPath)) + { + CLog::Log(LOGINFO, "CUtil::CacheSubtitles: disabling alternate subtitle directory for this session, it's inaccessible"); + CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(-1); // disabled + } + else if (!CDirectory::Exists(customPath)) + { + CLog::Log(LOGINFO, "CUtil::CacheSubtitles: disabling alternate subtitle directory for this session, it's nonexistent"); + CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(-1); // disabled + } + + CMediaSettings::GetInstance().SetAdditionalSubtitleDirectoryChecked(1); + } + + std::vector<std::string> strLookInPaths; + // this is last because we dont want to check any common subdirs or cd-dirs in the alternate <subtitles> dir. + if (CMediaSettings::GetInstance().GetAdditionalSubtitleDirectoryChecked() == 1) + { + std::string strPath2 = customPath; + URIUtils::AddSlashAtEnd(strPath2); + strLookInPaths.push_back(strPath2); + } + + int flags = DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO; + for (const std::string& path : strLookInPaths) + { + CFileItemList moreItems; + CDirectory::GetDirectory(path, moreItems, subtitleExtensions, flags); + items.Append(moreItems); + } + + std::vector<std::string> exts = StringUtils::Split(subtitleExtensions, '|'); + exts.erase(std::remove(exts.begin(), exts.end(), ".zip"), exts.end()); + exts.erase(std::remove(exts.begin(), exts.end(), ".rar"), exts.end()); + + ScanPathsForAssociatedItems(strSubtitle, items, exts, vecSubtitles); + + size_t iSize = vecSubtitles.size(); + for (size_t i = 0; i < iSize; i++) + { + if (URIUtils::HasExtension(vecSubtitles[i], ".smi")) + { + //Cache multi-language sami subtitle + CDVDSubtitleStream stream; + if (stream.Open(vecSubtitles[i])) + { + CDVDSubtitleTagSami TagConv; + TagConv.LoadHead(&stream); + if (TagConv.m_Langclass.size() >= 2) + { + for (const auto &lang : TagConv.m_Langclass) + { + std::string strDest = + StringUtils::Format("special://temp/subtitle.{}.{}.smi", lang.Name, i); + if (CFile::Copy(vecSubtitles[i], strDest)) + { + CLog::Log(LOGINFO, " cached subtitle {}->{}", CURL::GetRedacted(vecSubtitles[i]), + strDest); + vecSubtitles.push_back(strDest); + } + } + } + } + } + } + + auto end = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start); + CLog::Log(LOGDEBUG, "{}: END (total time: {} ms)", __FUNCTION__, duration.count()); +} + +ExternalStreamInfo CUtil::GetExternalStreamDetailsFromFilename(const std::string& videoPath, const std::string& associatedFile) +{ + ExternalStreamInfo info; + + std::string videoBaseName = URIUtils::GetFileName(videoPath); + URIUtils::RemoveExtension(videoBaseName); + + std::string toParse = URIUtils::GetFileName(associatedFile); + URIUtils::RemoveExtension(toParse); + + // we check left part - if it's same as video base name - strip it + if (StringUtils::StartsWithNoCase(toParse, videoBaseName)) + toParse = toParse.substr(videoBaseName.length()); + else if (URIUtils::GetExtension(associatedFile) == ".sub" && URIUtils::IsInArchive(associatedFile)) + { + // exclude parsing of vobsub file names that are embedded in an archive + CLog::Log(LOGDEBUG, "{} - skipping archived vobsub filename parsing: {}", __FUNCTION__, + CURL::GetRedacted(associatedFile)); + toParse.clear(); + } + + // trim any non-alphanumeric char in the beginning + std::string::iterator result = std::find_if(toParse.begin(), toParse.end(), StringUtils::isasciialphanum); + + std::string name; + if (result != toParse.end()) // if we have anything to parse + { + std::string inputString(result, toParse.end()); + std::string delimiters(" .-"); + std::vector<std::string> tokens; + StringUtils::Tokenize(inputString, tokens, delimiters); + + for (auto it = tokens.rbegin(); it != tokens.rend(); ++it) + { + // try to recognize a flag + std::string flag_tmp(*it); + StringUtils::ToLower(flag_tmp); + if (!flag_tmp.compare("none")) + { + info.flag |= StreamFlags::FLAG_NONE; + continue; + } + else if (!flag_tmp.compare("default")) + { + info.flag |= StreamFlags::FLAG_DEFAULT; + continue; + } + else if (!flag_tmp.compare("forced")) + { + info.flag |= StreamFlags::FLAG_FORCED; + continue; + } + + if (info.language.empty()) + { + std::string langCode; + // try to recognize language + if (g_LangCodeExpander.ConvertToISO6392B(*it, langCode)) + { + info.language = langCode; + continue; + } + } + + name = (*it) + " " + name; + } + } + name += " "; + name += g_localizeStrings.Get(21602); // External + StringUtils::Trim(name); + info.name = StringUtils::RemoveDuplicatedSpacesAndTabs(name); + if (info.flag == 0) + info.flag = StreamFlags::FLAG_NONE; + + CLog::Log(LOGDEBUG, "{} - Language = '{}' / Name = '{}' / Flag = '{}' from {}", __FUNCTION__, + info.language, info.name, info.flag, CURL::GetRedacted(associatedFile)); + + return info; +} + +/*! \brief in a vector of subtitles finds the corresponding .sub file for a given .idx file + */ +bool CUtil::FindVobSubPair(const std::vector<std::string>& vecSubtitles, const std::string& strIdxPath, std::string& strSubPath) +{ + if (URIUtils::HasExtension(strIdxPath, ".idx")) + { + std::string strIdxFile; + std::string strIdxDirectory; + URIUtils::Split(strIdxPath, strIdxDirectory, strIdxFile); + for (const auto &subtitlePath : vecSubtitles) + { + std::string strSubFile; + std::string strSubDirectory; + URIUtils::Split(subtitlePath, strSubDirectory, strSubFile); + if (URIUtils::IsInArchive(subtitlePath)) + strSubDirectory = CURL::Decode(strSubDirectory); + if (URIUtils::HasExtension(strSubFile, ".sub") && + (URIUtils::PathEquals(URIUtils::ReplaceExtension(strIdxPath,""), + URIUtils::ReplaceExtension(subtitlePath,"")) || + (strSubDirectory.size() >= 11 && + StringUtils::EqualsNoCase(strSubDirectory.substr(6, strSubDirectory.length()-11), URIUtils::ReplaceExtension(strIdxPath,""))))) + { + strSubPath = subtitlePath; + return true; + } + } + } + return false; +} + +/*! \brief checks if in the vector of subtitles the given .sub file has a corresponding idx and hence is a vobsub file + */ +bool CUtil::IsVobSub(const std::vector<std::string>& vecSubtitles, const std::string& strSubPath) +{ + if (URIUtils::HasExtension(strSubPath, ".sub")) + { + std::string strSubFile; + std::string strSubDirectory; + URIUtils::Split(strSubPath, strSubDirectory, strSubFile); + if (URIUtils::IsInArchive(strSubPath)) + strSubDirectory = CURL::Decode(strSubDirectory); + for (const auto &subtitlePath : vecSubtitles) + { + std::string strIdxFile; + std::string strIdxDirectory; + URIUtils::Split(subtitlePath, strIdxDirectory, strIdxFile); + if (URIUtils::HasExtension(strIdxFile, ".idx") && + (URIUtils::PathEquals(URIUtils::ReplaceExtension(subtitlePath,""), + URIUtils::ReplaceExtension(strSubPath,"")) || + (strSubDirectory.size() >= 11 && + StringUtils::EqualsNoCase(strSubDirectory.substr(6, strSubDirectory.length()-11), URIUtils::ReplaceExtension(subtitlePath,""))))) + return true; + } + } + return false; +} + +/*! \brief find a plain or archived vobsub .sub file corresponding to an .idx file + */ +std::string CUtil::GetVobSubSubFromIdx(const std::string& vobSubIdx) +{ + std::string vobSub = URIUtils::ReplaceExtension(vobSubIdx, ".sub"); + + // check if a .sub file exists in the same directory + if (CFile::Exists(vobSub)) + { + return vobSub; + } + + // look inside a .rar or .zip in the same directory + const std::string archTypes[] = { "rar", "zip" }; + std::string vobSubFilename = URIUtils::GetFileName(vobSub); + for (const std::string& archType : archTypes) + { + vobSub = URIUtils::CreateArchivePath(archType, + CURL(URIUtils::ReplaceExtension(vobSubIdx, std::string(".") + archType)), + vobSubFilename).Get(); + if (CFile::Exists(vobSub)) + return vobSub; + } + + return std::string(); +} + +/*! \brief find a .idx file from a path of a plain or archived vobsub .sub file + */ +std::string CUtil::GetVobSubIdxFromSub(const std::string& vobSub) +{ + std::string vobSubIdx = URIUtils::ReplaceExtension(vobSub, ".idx"); + + // check if a .idx file exists in the same directory + if (CFile::Exists(vobSubIdx)) + { + return vobSubIdx; + } + + // look outside archive (usually .rar) if the .sub is inside one + if (URIUtils::IsInArchive(vobSub)) + { + + std::string archiveFile = URIUtils::GetDirectory(vobSub); + std::string vobSubIdxDir = URIUtils::GetParentPath(archiveFile); + + if (!vobSubIdxDir.empty()) + { + std::string vobSubIdxFilename = URIUtils::GetFileName(vobSubIdx); + std::string vobSubIdx = URIUtils::AddFileToFolder(vobSubIdxDir, vobSubIdxFilename); + + if (CFile::Exists(vobSubIdx)) + return vobSubIdx; + } + } + + return std::string(); +} + +void CUtil::ScanForExternalAudio(const std::string& videoPath, std::vector<std::string>& vecAudio) +{ + CFileItem item(videoPath, false); + if ( item.IsInternetStream() + || item.IsPlayList() + || item.IsLiveTV() + || item.IsPVR() + || !item.IsVideo()) + return; + + std::string strBasePath; + std::string strAudio; + + GetVideoBasePathAndFileName(videoPath, strBasePath, strAudio); + + CFileItemList items; + const std::vector<std::string> common_sub_dirs = { "audio", "tracks"}; + GetItemsToScan(strBasePath, CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), common_sub_dirs, items); + + std::vector<std::string> exts = StringUtils::Split(CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), "|"); + + CVideoDatabase database; + database.Open(); + bool useAllExternalAudio = database.GetUseAllExternalAudioForVideo(videoPath); + + if (useAllExternalAudio) + { + for (const auto& audioItem : items.GetList()) + { + vecAudio.push_back(audioItem.get()->GetPath()); + } + } + else + ScanPathsForAssociatedItems(strAudio, items, exts, vecAudio); +} + +bool CUtil::CanBindPrivileged() +{ +#ifdef TARGET_POSIX + + if (geteuid() == 0) + return true; //root user can always bind to privileged ports + +#ifdef HAVE_LIBCAP + + //check if CAP_NET_BIND_SERVICE is enabled, this allows non-root users to bind to privileged ports + bool canbind = false; + cap_t capabilities = cap_get_proc(); + if (capabilities) + { + cap_flag_value_t value; + if (cap_get_flag(capabilities, CAP_NET_BIND_SERVICE, CAP_EFFECTIVE, &value) == 0) + canbind = value; + + cap_free(capabilities); + } + + return canbind; + +#else //HAVE_LIBCAP + + return false; + +#endif //HAVE_LIBCAP + +#else //TARGET_POSIX + + return true; + +#endif //TARGET_POSIX +} + +bool CUtil::ValidatePort(int port) +{ + // check that it's a valid port +#ifdef TARGET_POSIX + if (!CUtil::CanBindPrivileged() && (port < 1024 || port > 65535)) + return false; + else +#endif + if (port <= 0 || port > 65535) + return false; + + return true; +} + +int CUtil::GetRandomNumber() +{ +#if !defined(TARGET_WINDOWS) + return rand_r(&s_randomSeed); +#else + unsigned int number; + if (rand_s(&number) == 0) + return (int)number; + + return rand(); +#endif +} + +void CUtil::CopyUserDataIfNeeded(const std::string& strPath, + const std::string& file, + const std::string& destname) +{ + std::string destPath; + if (destname.empty()) + destPath = URIUtils::AddFileToFolder(strPath, file); + else + destPath = URIUtils::AddFileToFolder(strPath, destname); + + if (!CFile::Exists(destPath)) + { + // need to copy it across + std::string srcPath = URIUtils::AddFileToFolder("special://xbmc/userdata/", file); + CFile::Copy(srcPath, destPath); + } +} |