1 /* 2 * Copyright (C) 2005-2018 Team Kodi 3 * This file is part of Kodi - https://kodi.tv 4 * 5 * SPDX-License-Identifier: GPL-2.0-or-later 6 * See LICENSES/README.md for more information. 7 */ 8 9 #include "StackDirectory.h" 10 11 #include "FileItem.h" 12 #include "ServiceBroker.h" 13 #include "URL.h" 14 #include "settings/AdvancedSettings.h" 15 #include "settings/SettingsComponent.h" 16 #include "utils/StringUtils.h" 17 #include "utils/URIUtils.h" 18 #include "utils/log.h" 19 20 #include <stdlib.h> 21 22 namespace XFILE 23 { 24 CStackDirectory::CStackDirectory() = default; 25 26 CStackDirectory::~CStackDirectory() = default; 27 GetDirectory(const CURL & url,CFileItemList & items)28 bool CStackDirectory::GetDirectory(const CURL& url, CFileItemList& items) 29 { 30 items.Clear(); 31 std::vector<std::string> files; 32 const std::string pathToUrl(url.Get()); 33 if (!GetPaths(pathToUrl, files)) 34 return false; // error in path 35 36 for (const std::string& i : files) 37 { 38 CFileItemPtr item(new CFileItem(i)); 39 item->SetPath(i); 40 item->m_bIsFolder = false; 41 items.Add(item); 42 } 43 return true; 44 } 45 GetStackedTitlePath(const std::string & strPath)46 std::string CStackDirectory::GetStackedTitlePath(const std::string &strPath) 47 { 48 // Load up our REs 49 VECCREGEXP RegExps; 50 CRegExp tempRE(true, CRegExp::autoUtf8); 51 const std::vector<std::string>& strRegExps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoStackRegExps; 52 std::vector<std::string>::const_iterator itRegExp = strRegExps.begin(); 53 while (itRegExp != strRegExps.end()) 54 { 55 (void)tempRE.RegComp(*itRegExp); 56 if (tempRE.GetCaptureTotal() == 4) 57 RegExps.push_back(tempRE); 58 else 59 CLog::Log(LOGERROR, "Invalid video stack RE (%s). Must have exactly 4 captures.", itRegExp->c_str()); 60 ++itRegExp; 61 } 62 return GetStackedTitlePath(strPath, RegExps); 63 } 64 GetStackedTitlePath(const std::string & strPath,VECCREGEXP & RegExps)65 std::string CStackDirectory::GetStackedTitlePath(const std::string &strPath, VECCREGEXP& RegExps) 66 { 67 CStackDirectory stack; 68 CFileItemList files; 69 std::string strStackTitlePath, 70 strCommonDir = URIUtils::GetParentPath(strPath); 71 72 const CURL pathToUrl(strPath); 73 stack.GetDirectory(pathToUrl, files); 74 75 if (files.Size() > 1) 76 { 77 std::string strStackTitle; 78 79 std::string File1 = URIUtils::GetFileName(files[0]->GetPath()); 80 std::string File2 = URIUtils::GetFileName(files[1]->GetPath()); 81 // Check if source path uses URL encoding 82 if (URIUtils::HasEncodedFilename(CURL(strCommonDir))) 83 { 84 File1 = CURL::Decode(File1); 85 File2 = CURL::Decode(File2); 86 } 87 88 std::vector<CRegExp>::iterator itRegExp = RegExps.begin(); 89 int offset = 0; 90 91 while (itRegExp != RegExps.end()) 92 { 93 if (itRegExp->RegFind(File1, offset) != -1) 94 { 95 std::string Title1 = itRegExp->GetMatch(1), 96 Volume1 = itRegExp->GetMatch(2), 97 Ignore1 = itRegExp->GetMatch(3), 98 Extension1 = itRegExp->GetMatch(4); 99 if (offset) 100 Title1 = File1.substr(0, itRegExp->GetSubStart(2)); 101 if (itRegExp->RegFind(File2, offset) != -1) 102 { 103 std::string Title2 = itRegExp->GetMatch(1), 104 Volume2 = itRegExp->GetMatch(2), 105 Ignore2 = itRegExp->GetMatch(3), 106 Extension2 = itRegExp->GetMatch(4); 107 if (offset) 108 Title2 = File2.substr(0, itRegExp->GetSubStart(2)); 109 if (StringUtils::EqualsNoCase(Title1, Title2)) 110 { 111 if (!StringUtils::EqualsNoCase(Volume1, Volume2)) 112 { 113 if (StringUtils::EqualsNoCase(Ignore1, Ignore2) && 114 StringUtils::EqualsNoCase(Extension1, Extension2)) 115 { 116 // got it 117 strStackTitle = Title1 + Ignore1 + Extension1; 118 // Check if source path uses URL encoding 119 if (URIUtils::HasEncodedFilename(CURL(strCommonDir))) 120 strStackTitle = CURL::Encode(strStackTitle); 121 122 itRegExp = RegExps.end(); 123 break; 124 } 125 else // Invalid stack 126 break; 127 } 128 else // Early match, retry with offset 129 { 130 offset = itRegExp->GetSubStart(3); 131 continue; 132 } 133 } 134 } 135 } 136 offset = 0; 137 ++itRegExp; 138 } 139 if (!strCommonDir.empty() && !strStackTitle.empty()) 140 strStackTitlePath = strCommonDir + strStackTitle; 141 } 142 143 return strStackTitlePath; 144 } 145 GetFirstStackedFile(const std::string & strPath)146 std::string CStackDirectory::GetFirstStackedFile(const std::string &strPath) 147 { 148 // the stacked files are always in volume order, so just get up to the first filename 149 // occurence of " , " 150 std::string file, folder; 151 size_t pos = strPath.find(" , "); 152 if (pos != std::string::npos) 153 URIUtils::Split(strPath.substr(0, pos), folder, file); 154 else 155 URIUtils::Split(strPath, folder, file); // single filed stacks - should really not happen 156 157 // remove "stack://" from the folder 158 folder = folder.substr(8); 159 StringUtils::Replace(file, ",,", ","); 160 161 return URIUtils::AddFileToFolder(folder, file); 162 } 163 GetPaths(const std::string & strPath,std::vector<std::string> & vecPaths)164 bool CStackDirectory::GetPaths(const std::string& strPath, std::vector<std::string>& vecPaths) 165 { 166 // format is: 167 // stack://file1 , file2 , file3 , file4 168 // filenames with commas are double escaped (ie replaced with ,,), thus the " , " separator used. 169 std::string path = strPath; 170 // remove stack:// from the beginning 171 path = path.substr(8); 172 173 vecPaths = StringUtils::Split(path, " , "); 174 if (vecPaths.empty()) 175 return false; 176 177 // because " , " is used as a separator any "," in the real paths are double escaped 178 for (std::string& itPath : vecPaths) 179 StringUtils::Replace(itPath, ",,", ","); 180 181 return true; 182 } 183 ConstructStackPath(const CFileItemList & items,const std::vector<int> & stack)184 std::string CStackDirectory::ConstructStackPath(const CFileItemList &items, const std::vector<int> &stack) 185 { 186 // no checks on the range of stack here. 187 // we replace all instances of comma's with double comma's, then separate 188 // the files using " , ". 189 std::string stackedPath = "stack://"; 190 std::string folder, file; 191 URIUtils::Split(items[stack[0]]->GetPath(), folder, file); 192 stackedPath += folder; 193 // double escape any occurence of commas 194 StringUtils::Replace(file, ",", ",,"); 195 stackedPath += file; 196 for (unsigned int i = 1; i < stack.size(); ++i) 197 { 198 stackedPath += " , "; 199 file = items[stack[i]]->GetPath(); 200 201 // double escape any occurence of commas 202 StringUtils::Replace(file, ",", ",,"); 203 stackedPath += file; 204 } 205 return stackedPath; 206 } 207 ConstructStackPath(const std::vector<std::string> & paths,std::string & stackedPath)208 bool CStackDirectory::ConstructStackPath(const std::vector<std::string> &paths, std::string& stackedPath) 209 { 210 if (paths.size() < 2) 211 return false; 212 stackedPath = "stack://"; 213 std::string folder, file; 214 URIUtils::Split(paths[0], folder, file); 215 stackedPath += folder; 216 // double escape any occurence of commas 217 StringUtils::Replace(file, ",", ",,"); 218 stackedPath += file; 219 for (unsigned int i = 1; i < paths.size(); ++i) 220 { 221 stackedPath += " , "; 222 file = paths[i]; 223 224 // double escape any occurence of commas 225 StringUtils::Replace(file, ",", ",,"); 226 stackedPath += file; 227 } 228 return true; 229 } 230 } 231 232