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