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 "FileItemList.h"
14 #include "ServiceBroker.h"
17 #include "dialogs/GUIDialogProgress.h"
18 #include "guilib/GUIComponent.h"
19 #include "guilib/GUIWindowManager.h"
20 #include "threads/SystemClock.h"
21 #include "utils/StringUtils.h"
22 #include "utils/URIUtils.h"
23 #include "utils/Variant.h"
24 #include "utils/log.h"
26 using namespace XFILE
;
28 using namespace std::chrono_literals
;
31 // multipath://{path1}/{path2}/{path3}/.../{path-N}
33 // unlike the older virtualpath:// protocol, sub-folders are combined together into a new
34 // multipath:// style url.
37 CMultiPathDirectory::CMultiPathDirectory() = default;
39 CMultiPathDirectory::~CMultiPathDirectory() = default;
41 bool CMultiPathDirectory::GetDirectory(const CURL
& url
, CFileItemList
&items
)
43 CLog::Log(LOGDEBUG
, "CMultiPathDirectory::GetDirectory({})", url
.GetRedacted());
45 std::vector
<std::string
> vecPaths
;
46 if (!GetPaths(url
, vecPaths
))
49 XbmcThreads::EndTime
<> progressTime(3000ms
); // 3 seconds before showing progress bar
50 CGUIDialogProgress
* dlgProgress
= NULL
;
52 unsigned int iFailures
= 0;
53 for (unsigned int i
= 0; i
< vecPaths
.size(); ++i
)
55 // show the progress dialog if we have passed our time limit
56 if (progressTime
.IsTimePast() && !dlgProgress
)
58 dlgProgress
= CServiceBroker::GetGUI()->GetWindowManager().GetWindow
<CGUIDialogProgress
>(WINDOW_DIALOG_PROGRESS
);
61 dlgProgress
->SetHeading(CVariant
{15310});
62 dlgProgress
->SetLine(0, CVariant
{15311});
63 dlgProgress
->SetLine(1, CVariant
{""});
64 dlgProgress
->SetLine(2, CVariant
{""});
66 dlgProgress
->ShowProgressBar(true);
67 dlgProgress
->SetProgressMax((int)vecPaths
.size()*2);
68 dlgProgress
->Progress();
73 CURL
url(vecPaths
[i
]);
74 dlgProgress
->SetLine(1, CVariant
{url
.GetWithoutUserDetails()});
75 dlgProgress
->SetProgressAdvance();
76 dlgProgress
->Progress();
79 CFileItemList tempItems
;
80 CLog::Log(LOGDEBUG
, "Getting Directory ({})", CURL::GetRedacted(vecPaths
[i
]));
81 if (CDirectory::GetDirectory(vecPaths
[i
], tempItems
, m_strFileMask
, m_flags
))
82 items
.Append(tempItems
);
85 CLog::Log(LOGERROR
, "Error Getting Directory ({})", CURL::GetRedacted(vecPaths
[i
]));
91 dlgProgress
->SetProgressAdvance();
92 dlgProgress
->Progress();
99 if (iFailures
== vecPaths
.size())
102 // merge like-named folders into a sub multipath:// style url
108 bool CMultiPathDirectory::Exists(const CURL
& url
)
110 CLog::Log(LOGDEBUG
, "Testing Existence ({})", url
.GetRedacted());
112 std::vector
<std::string
> vecPaths
;
113 if (!GetPaths(url
, vecPaths
))
116 for (unsigned int i
= 0; i
< vecPaths
.size(); ++i
)
118 CLog::Log(LOGDEBUG
, "Testing Existence ({})", CURL::GetRedacted(vecPaths
[i
]));
119 if (CDirectory::Exists(vecPaths
[i
]))
125 bool CMultiPathDirectory::Remove(const CURL
& url
)
127 std::vector
<std::string
> vecPaths
;
128 if (!GetPaths(url
, vecPaths
))
131 bool success
= false;
132 for (unsigned int i
= 0; i
< vecPaths
.size(); ++i
)
134 if (CDirectory::Remove(vecPaths
[i
]))
140 std::string
CMultiPathDirectory::GetFirstPath(const std::string
&strPath
)
142 size_t pos
= strPath
.find('/', 12);
143 if (pos
!= std::string::npos
)
144 return CURL::Decode(strPath
.substr(12, pos
- 12));
148 bool CMultiPathDirectory::GetPaths(const CURL
& url
, std::vector
<std::string
>& vecPaths
)
150 const std::string
pathToUrl(url
.Get());
151 return GetPaths(pathToUrl
, vecPaths
);
154 bool CMultiPathDirectory::GetPaths(const std::string
& path
, std::vector
<std::string
>& paths
)
158 // remove multipath:// from path and any trailing / (so that the last path doesn't get any more than it originally had)
159 std::string path1
= path
.substr(12);
160 path1
.erase(path1
.find_last_not_of('/')+1);
163 std::vector
<std::string
> temp
= StringUtils::Split(path1
, '/');
167 // URL decode each item
168 paths
.resize(temp
.size());
169 std::transform(temp
.begin(), temp
.end(), paths
.begin(), CURL::Decode
);
173 bool CMultiPathDirectory::HasPath(const std::string
& strPath
, const std::string
& strPathToFind
)
175 // remove multipath:// from path and any trailing / (so that the last path doesn't get any more than it originally had)
176 std::string strPath1
= strPath
.substr(12);
177 URIUtils::RemoveSlashAtEnd(strPath1
);
180 std::vector
<std::string
> vecTemp
= StringUtils::Split(strPath1
, '/');
185 for (unsigned int i
= 0; i
< vecTemp
.size(); i
++)
187 if (CURL::Decode(vecTemp
[i
]) == strPathToFind
)
193 std::string
CMultiPathDirectory::ConstructMultiPath(const CFileItemList
& items
, const std::vector
<int> &stack
)
195 // we replace all instances of comma's with double comma's, then separate
196 // the paths using " , "
197 //CLog::Log(LOGDEBUG, "Building multipath");
198 std::string newPath
= "multipath://";
199 //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
200 for (unsigned int i
= 0; i
< stack
.size(); ++i
)
201 AddToMultiPath(newPath
, items
[stack
[i
]]->GetPath());
203 //CLog::Log(LOGDEBUG, "Final path: {}", newPath);
207 void CMultiPathDirectory::AddToMultiPath(std::string
& strMultiPath
, const std::string
& strPath
)
209 URIUtils::AddSlashAtEnd(strMultiPath
);
210 //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
211 strMultiPath
+= CURL::Encode(strPath
);
215 std::string
CMultiPathDirectory::ConstructMultiPath(const std::vector
<std::string
> &vecPaths
)
217 // we replace all instances of comma's with double comma's, then separate
218 // the paths using " , "
219 //CLog::Log(LOGDEBUG, "Building multipath");
220 std::string newPath
= "multipath://";
221 //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
222 for (std::vector
<std::string
>::const_iterator path
= vecPaths
.begin(); path
!= vecPaths
.end(); ++path
)
223 AddToMultiPath(newPath
, *path
);
224 //CLog::Log(LOGDEBUG, "Final path: {}", newPath);
228 std::string
CMultiPathDirectory::ConstructMultiPath(const std::set
<std::string
> &setPaths
)
230 std::string newPath
= "multipath://";
231 for (const std::string
& path
: setPaths
)
232 AddToMultiPath(newPath
, path
);
237 void CMultiPathDirectory::MergeItems(CFileItemList
&items
)
239 CLog::Log(LOGDEBUG
, "CMultiPathDirectory::MergeItems, items = {}", items
.Size());
240 auto start
= std::chrono::steady_clock::now();
241 if (items
.Size() == 0)
243 // sort items by label
244 // folders are before files in this sort method
245 items
.Sort(SortByLabel
, SortOrderAscending
);
248 // if first item in the sorted list is a file, just abort
249 if (!items
.Get(i
)->m_bIsFolder
)
252 while (i
+ 1 < items
.Size())
254 // there are no more folders left, so exit the loop
255 CFileItemPtr pItem1
= items
.Get(i
);
256 if (!pItem1
->m_bIsFolder
)
259 std::vector
<int> stack
;
261 CLog::Log(LOGDEBUG
, "Testing path: [{:03}] {}", i
, CURL::GetRedacted(pItem1
->GetPath()));
266 CFileItemPtr pItem2
= items
.Get(j
);
267 if (pItem2
->GetLabel() != pItem1
->GetLabel())
270 // ignore any filefolders which may coincidently have
271 // the same label as a true folder
272 if (!pItem2
->IsFileFolder())
275 CLog::Log(LOGDEBUG
, " Adding path: [{:03}] {}", j
, CURL::GetRedacted(pItem2
->GetPath()));
279 while (j
< items
.Size());
281 // do we have anything to combine?
282 if (stack
.size() > 1)
284 // we have a multipath so remove the items and add the new item
285 std::string newPath
= ConstructMultiPath(items
, stack
);
286 for (unsigned int k
= stack
.size() - 1; k
> 0; --k
)
287 items
.Remove(stack
[k
]);
288 pItem1
->SetPath(newPath
);
289 CLog::Log(LOGDEBUG
, " New path: {}", CURL::GetRedacted(pItem1
->GetPath()));
295 auto end
= std::chrono::steady_clock::now();
296 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(end
- start
);
298 CLog::Log(LOGDEBUG
, "CMultiPathDirectory::MergeItems, items = {}, took {} ms", items
.Size(),
302 bool CMultiPathDirectory::SupportsWriteFileOperations(const std::string
&strPath
)
304 std::vector
<std::string
> paths
;
305 GetPaths(strPath
, paths
);
306 for (unsigned int i
= 0; i
< paths
.size(); ++i
)
307 if (CUtil::SupportsWriteFileOperations(paths
[i
]))