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.
9 #include "MultiPathDirectory.h"
11 #include "Directory.h"
13 #include "ServiceBroker.h"
16 #include "dialogs/GUIDialogProgress.h"
17 #include "guilib/GUIComponent.h"
18 #include "guilib/GUIWindowManager.h"
19 #include "threads/SystemClock.h"
20 #include "utils/StringUtils.h"
21 #include "utils/URIUtils.h"
22 #include "utils/Variant.h"
23 #include "utils/log.h"
25 using namespace XFILE
;
27 using namespace std::chrono_literals
;
30 // multipath://{path1}/{path2}/{path3}/.../{path-N}
32 // unlike the older virtualpath:// protocol, sub-folders are combined together into a new
33 // multipath:// style url.
36 CMultiPathDirectory::CMultiPathDirectory() = default;
38 CMultiPathDirectory::~CMultiPathDirectory() = default;
40 bool CMultiPathDirectory::GetDirectory(const CURL
& url
, CFileItemList
&items
)
42 CLog::Log(LOGDEBUG
, "CMultiPathDirectory::GetDirectory({})", url
.GetRedacted());
44 std::vector
<std::string
> vecPaths
;
45 if (!GetPaths(url
, vecPaths
))
48 XbmcThreads::EndTime
<> progressTime(3000ms
); // 3 seconds before showing progress bar
49 CGUIDialogProgress
* dlgProgress
= NULL
;
51 unsigned int iFailures
= 0;
52 for (unsigned int i
= 0; i
< vecPaths
.size(); ++i
)
54 // show the progress dialog if we have passed our time limit
55 if (progressTime
.IsTimePast() && !dlgProgress
)
57 dlgProgress
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogProgress
>(WINDOW_DIALOG_PROGRESS
);
60 dlgProgress
->SetHeading(CVariant
{15310});
61 dlgProgress
->SetLine(0, CVariant
{15311});
62 dlgProgress
->SetLine(1, CVariant
{""});
63 dlgProgress
->SetLine(2, CVariant
{""});
65 dlgProgress
->ShowProgressBar(true);
66 dlgProgress
->SetProgressMax((int)vecPaths
.size()*2);
67 dlgProgress
->Progress();
72 CURL
url(vecPaths
[i
]);
73 dlgProgress
->SetLine(1, CVariant
{url
.GetWithoutUserDetails()});
74 dlgProgress
->SetProgressAdvance();
75 dlgProgress
->Progress();
78 CFileItemList tempItems
;
79 CLog::Log(LOGDEBUG
, "Getting Directory ({})", CURL::GetRedacted(vecPaths
[i
]));
80 if (CDirectory::GetDirectory(vecPaths
[i
], tempItems
, m_strFileMask
, m_flags
))
81 items
.Append(tempItems
);
84 CLog::Log(LOGERROR
, "Error Getting Directory ({})", CURL::GetRedacted(vecPaths
[i
]));
90 dlgProgress
->SetProgressAdvance();
91 dlgProgress
->Progress();
98 if (iFailures
== vecPaths
.size())
101 // merge like-named folders into a sub multipath:// style url
107 bool CMultiPathDirectory::Exists(const CURL
& url
)
109 CLog::Log(LOGDEBUG
, "Testing Existence ({})", url
.GetRedacted());
111 std::vector
<std::string
> vecPaths
;
112 if (!GetPaths(url
, vecPaths
))
115 for (unsigned int i
= 0; i
< vecPaths
.size(); ++i
)
117 CLog::Log(LOGDEBUG
, "Testing Existence ({})", CURL::GetRedacted(vecPaths
[i
]));
118 if (CDirectory::Exists(vecPaths
[i
]))
124 bool CMultiPathDirectory::Remove(const CURL
& url
)
126 std::vector
<std::string
> vecPaths
;
127 if (!GetPaths(url
, vecPaths
))
130 bool success
= false;
131 for (unsigned int i
= 0; i
< vecPaths
.size(); ++i
)
133 if (CDirectory::Remove(vecPaths
[i
]))
139 std::string
CMultiPathDirectory::GetFirstPath(const std::string
&strPath
)
141 size_t pos
= strPath
.find('/', 12);
142 if (pos
!= std::string::npos
)
143 return CURL::Decode(strPath
.substr(12, pos
- 12));
147 bool CMultiPathDirectory::GetPaths(const CURL
& url
, std::vector
<std::string
>& vecPaths
)
149 const std::string
pathToUrl(url
.Get());
150 return GetPaths(pathToUrl
, vecPaths
);
153 bool CMultiPathDirectory::GetPaths(const std::string
& path
, std::vector
<std::string
>& paths
)
157 // remove multipath:// from path and any trailing / (so that the last path doesn't get any more than it originally had)
158 std::string path1
= path
.substr(12);
159 path1
.erase(path1
.find_last_not_of('/')+1);
162 std::vector
<std::string
> temp
= StringUtils::Split(path1
, '/');
166 // URL decode each item
167 paths
.resize(temp
.size());
168 std::transform(temp
.begin(), temp
.end(), paths
.begin(), CURL::Decode
);
172 bool CMultiPathDirectory::HasPath(const std::string
& strPath
, const std::string
& strPathToFind
)
174 // remove multipath:// from path and any trailing / (so that the last path doesn't get any more than it originally had)
175 std::string strPath1
= strPath
.substr(12);
176 URIUtils::RemoveSlashAtEnd(strPath1
);
179 std::vector
<std::string
> vecTemp
= StringUtils::Split(strPath1
, '/');
184 for (unsigned int i
= 0; i
< vecTemp
.size(); i
++)
186 if (CURL::Decode(vecTemp
[i
]) == strPathToFind
)
192 std::string
CMultiPathDirectory::ConstructMultiPath(const CFileItemList
& items
, const std::vector
<int> &stack
)
194 // we replace all instances of comma's with double comma's, then separate
195 // the paths using " , "
196 //CLog::Log(LOGDEBUG, "Building multipath");
197 std::string newPath
= "multipath://";
198 //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
199 for (unsigned int i
= 0; i
< stack
.size(); ++i
)
200 AddToMultiPath(newPath
, items
[stack
[i
]]->GetPath());
202 //CLog::Log(LOGDEBUG, "Final path: {}", newPath);
206 void CMultiPathDirectory::AddToMultiPath(std::string
& strMultiPath
, const std::string
& strPath
)
208 URIUtils::AddSlashAtEnd(strMultiPath
);
209 //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
210 strMultiPath
+= CURL::Encode(strPath
);
214 std::string
CMultiPathDirectory::ConstructMultiPath(const std::vector
<std::string
> &vecPaths
)
216 // we replace all instances of comma's with double comma's, then separate
217 // the paths using " , "
218 //CLog::Log(LOGDEBUG, "Building multipath");
219 std::string newPath
= "multipath://";
220 //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
221 for (std::vector
<std::string
>::const_iterator path
= vecPaths
.begin(); path
!= vecPaths
.end(); ++path
)
222 AddToMultiPath(newPath
, *path
);
223 //CLog::Log(LOGDEBUG, "Final path: {}", newPath);
227 std::string
CMultiPathDirectory::ConstructMultiPath(const std::set
<std::string
> &setPaths
)
229 std::string newPath
= "multipath://";
230 for (const std::string
& path
: setPaths
)
231 AddToMultiPath(newPath
, path
);
236 void CMultiPathDirectory::MergeItems(CFileItemList
&items
)
238 CLog::Log(LOGDEBUG
, "CMultiPathDirectory::MergeItems, items = {}", items
.Size());
239 auto start
= std::chrono::steady_clock::now();
240 if (items
.Size() == 0)
242 // sort items by label
243 // folders are before files in this sort method
244 items
.Sort(SortByLabel
, SortOrderAscending
);
247 // if first item in the sorted list is a file, just abort
248 if (!items
.Get(i
)->m_bIsFolder
)
251 while (i
+ 1 < items
.Size())
253 // there are no more folders left, so exit the loop
254 CFileItemPtr pItem1
= items
.Get(i
);
255 if (!pItem1
->m_bIsFolder
)
258 std::vector
<int> stack
;
260 CLog::Log(LOGDEBUG
, "Testing path: [{:03}] {}", i
, CURL::GetRedacted(pItem1
->GetPath()));
265 CFileItemPtr pItem2
= items
.Get(j
);
266 if (pItem2
->GetLabel() != pItem1
->GetLabel())
269 // ignore any filefolders which may coincidently have
270 // the same label as a true folder
271 if (!pItem2
->IsFileFolder())
274 CLog::Log(LOGDEBUG
, " Adding path: [{:03}] {}", j
, CURL::GetRedacted(pItem2
->GetPath()));
278 while (j
< items
.Size());
280 // do we have anything to combine?
281 if (stack
.size() > 1)
283 // we have a multipath so remove the items and add the new item
284 std::string newPath
= ConstructMultiPath(items
, stack
);
285 for (unsigned int k
= stack
.size() - 1; k
> 0; --k
)
286 items
.Remove(stack
[k
]);
287 pItem1
->SetPath(newPath
);
288 CLog::Log(LOGDEBUG
, " New path: {}", CURL::GetRedacted(pItem1
->GetPath()));
294 auto end
= std::chrono::steady_clock::now();
295 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(end
- start
);
297 CLog::Log(LOGDEBUG
, "CMultiPathDirectory::MergeItems, items = {}, took {} ms", items
.Size(),
301 bool CMultiPathDirectory::SupportsWriteFileOperations(const std::string
&strPath
)
303 std::vector
<std::string
> paths
;
304 GetPaths(strPath
, paths
);
305 for (unsigned int i
= 0; i
< paths
.size(); ++i
)
306 if (CUtil::SupportsWriteFileOperations(paths
[i
]))