summaryrefslogtreecommitdiffstats
path: root/xbmc/filesystem/MultiPathDirectory.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/filesystem/MultiPathDirectory.cpp')
-rw-r--r--xbmc/filesystem/MultiPathDirectory.cpp309
1 files changed, 309 insertions, 0 deletions
diff --git a/xbmc/filesystem/MultiPathDirectory.cpp b/xbmc/filesystem/MultiPathDirectory.cpp
new file mode 100644
index 0000000..4cdcfcc
--- /dev/null
+++ b/xbmc/filesystem/MultiPathDirectory.cpp
@@ -0,0 +1,309 @@
+/*
+ * 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 "MultiPathDirectory.h"
+
+#include "Directory.h"
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "threads/SystemClock.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+
+using namespace XFILE;
+
+using namespace std::chrono_literals;
+
+//
+// multipath://{path1}/{path2}/{path3}/.../{path-N}
+//
+// unlike the older virtualpath:// protocol, sub-folders are combined together into a new
+// multipath:// style url.
+//
+
+CMultiPathDirectory::CMultiPathDirectory() = default;
+
+CMultiPathDirectory::~CMultiPathDirectory() = default;
+
+bool CMultiPathDirectory::GetDirectory(const CURL& url, CFileItemList &items)
+{
+ CLog::Log(LOGDEBUG, "CMultiPathDirectory::GetDirectory({})", url.GetRedacted());
+
+ std::vector<std::string> vecPaths;
+ if (!GetPaths(url, vecPaths))
+ return false;
+
+ XbmcThreads::EndTime<> progressTime(3000ms); // 3 seconds before showing progress bar
+ CGUIDialogProgress* dlgProgress = NULL;
+
+ unsigned int iFailures = 0;
+ for (unsigned int i = 0; i < vecPaths.size(); ++i)
+ {
+ // show the progress dialog if we have passed our time limit
+ if (progressTime.IsTimePast() && !dlgProgress)
+ {
+ dlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+ if (dlgProgress)
+ {
+ dlgProgress->SetHeading(CVariant{15310});
+ dlgProgress->SetLine(0, CVariant{15311});
+ dlgProgress->SetLine(1, CVariant{""});
+ dlgProgress->SetLine(2, CVariant{""});
+ dlgProgress->Open();
+ dlgProgress->ShowProgressBar(true);
+ dlgProgress->SetProgressMax((int)vecPaths.size()*2);
+ dlgProgress->Progress();
+ }
+ }
+ if (dlgProgress)
+ {
+ CURL url(vecPaths[i]);
+ dlgProgress->SetLine(1, CVariant{url.GetWithoutUserDetails()});
+ dlgProgress->SetProgressAdvance();
+ dlgProgress->Progress();
+ }
+
+ CFileItemList tempItems;
+ CLog::Log(LOGDEBUG, "Getting Directory ({})", CURL::GetRedacted(vecPaths[i]));
+ if (CDirectory::GetDirectory(vecPaths[i], tempItems, m_strFileMask, m_flags))
+ items.Append(tempItems);
+ else
+ {
+ CLog::Log(LOGERROR, "Error Getting Directory ({})", CURL::GetRedacted(vecPaths[i]));
+ iFailures++;
+ }
+
+ if (dlgProgress)
+ {
+ dlgProgress->SetProgressAdvance();
+ dlgProgress->Progress();
+ }
+ }
+
+ if (dlgProgress)
+ dlgProgress->Close();
+
+ if (iFailures == vecPaths.size())
+ return false;
+
+ // merge like-named folders into a sub multipath:// style url
+ MergeItems(items);
+
+ return true;
+}
+
+bool CMultiPathDirectory::Exists(const CURL& url)
+{
+ CLog::Log(LOGDEBUG, "Testing Existence ({})", url.GetRedacted());
+
+ std::vector<std::string> vecPaths;
+ if (!GetPaths(url, vecPaths))
+ return false;
+
+ for (unsigned int i = 0; i < vecPaths.size(); ++i)
+ {
+ CLog::Log(LOGDEBUG, "Testing Existence ({})", CURL::GetRedacted(vecPaths[i]));
+ if (CDirectory::Exists(vecPaths[i]))
+ return true;
+ }
+ return false;
+}
+
+bool CMultiPathDirectory::Remove(const CURL& url)
+{
+ std::vector<std::string> vecPaths;
+ if (!GetPaths(url, vecPaths))
+ return false;
+
+ bool success = false;
+ for (unsigned int i = 0; i < vecPaths.size(); ++i)
+ {
+ if (CDirectory::Remove(vecPaths[i]))
+ success = true;
+ }
+ return success;
+}
+
+std::string CMultiPathDirectory::GetFirstPath(const std::string &strPath)
+{
+ size_t pos = strPath.find('/', 12);
+ if (pos != std::string::npos)
+ return CURL::Decode(strPath.substr(12, pos - 12));
+ return "";
+}
+
+bool CMultiPathDirectory::GetPaths(const CURL& url, std::vector<std::string>& vecPaths)
+{
+ const std::string pathToUrl(url.Get());
+ return GetPaths(pathToUrl, vecPaths);
+}
+
+bool CMultiPathDirectory::GetPaths(const std::string& path, std::vector<std::string>& paths)
+{
+ paths.clear();
+
+ // remove multipath:// from path and any trailing / (so that the last path doesn't get any more than it originally had)
+ std::string path1 = path.substr(12);
+ path1.erase(path1.find_last_not_of('/')+1);
+
+ // split on "/"
+ std::vector<std::string> temp = StringUtils::Split(path1, '/');
+ if (temp.empty())
+ return false;
+
+ // URL decode each item
+ paths.resize(temp.size());
+ std::transform(temp.begin(), temp.end(), paths.begin(), CURL::Decode);
+ return true;
+}
+
+bool CMultiPathDirectory::HasPath(const std::string& strPath, const std::string& strPathToFind)
+{
+ // remove multipath:// from path and any trailing / (so that the last path doesn't get any more than it originally had)
+ std::string strPath1 = strPath.substr(12);
+ URIUtils::RemoveSlashAtEnd(strPath1);
+
+ // split on "/"
+ std::vector<std::string> vecTemp = StringUtils::Split(strPath1, '/');
+ if (vecTemp.empty())
+ return false;
+
+ // check each item
+ for (unsigned int i = 0; i < vecTemp.size(); i++)
+ {
+ if (CURL::Decode(vecTemp[i]) == strPathToFind)
+ return true;
+ }
+ return false;
+}
+
+std::string CMultiPathDirectory::ConstructMultiPath(const CFileItemList& items, const std::vector<int> &stack)
+{
+ // we replace all instances of comma's with double comma's, then separate
+ // the paths using " , "
+ //CLog::Log(LOGDEBUG, "Building multipath");
+ std::string newPath = "multipath://";
+ //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
+ for (unsigned int i = 0; i < stack.size(); ++i)
+ AddToMultiPath(newPath, items[stack[i]]->GetPath());
+
+ //CLog::Log(LOGDEBUG, "Final path: {}", newPath);
+ return newPath;
+}
+
+void CMultiPathDirectory::AddToMultiPath(std::string& strMultiPath, const std::string& strPath)
+{
+ URIUtils::AddSlashAtEnd(strMultiPath);
+ //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
+ strMultiPath += CURL::Encode(strPath);
+ strMultiPath += "/";
+}
+
+std::string CMultiPathDirectory::ConstructMultiPath(const std::vector<std::string> &vecPaths)
+{
+ // we replace all instances of comma's with double comma's, then separate
+ // the paths using " , "
+ //CLog::Log(LOGDEBUG, "Building multipath");
+ std::string newPath = "multipath://";
+ //CLog::Log(LOGDEBUG, "-- adding path: {}", strPath);
+ for (std::vector<std::string>::const_iterator path = vecPaths.begin(); path != vecPaths.end(); ++path)
+ AddToMultiPath(newPath, *path);
+ //CLog::Log(LOGDEBUG, "Final path: {}", newPath);
+ return newPath;
+}
+
+std::string CMultiPathDirectory::ConstructMultiPath(const std::set<std::string> &setPaths)
+{
+ std::string newPath = "multipath://";
+ for (const std::string& path : setPaths)
+ AddToMultiPath(newPath, path);
+
+ return newPath;
+}
+
+void CMultiPathDirectory::MergeItems(CFileItemList &items)
+{
+ CLog::Log(LOGDEBUG, "CMultiPathDirectory::MergeItems, items = {}", items.Size());
+ auto start = std::chrono::steady_clock::now();
+ if (items.Size() == 0)
+ return;
+ // sort items by label
+ // folders are before files in this sort method
+ items.Sort(SortByLabel, SortOrderAscending);
+ int i = 0;
+
+ // if first item in the sorted list is a file, just abort
+ if (!items.Get(i)->m_bIsFolder)
+ return;
+
+ while (i + 1 < items.Size())
+ {
+ // there are no more folders left, so exit the loop
+ CFileItemPtr pItem1 = items.Get(i);
+ if (!pItem1->m_bIsFolder)
+ break;
+
+ std::vector<int> stack;
+ stack.push_back(i);
+ CLog::Log(LOGDEBUG, "Testing path: [{:03}] {}", i, CURL::GetRedacted(pItem1->GetPath()));
+
+ int j = i + 1;
+ do
+ {
+ CFileItemPtr pItem2 = items.Get(j);
+ if (pItem2->GetLabel() != pItem1->GetLabel())
+ break;
+
+ // ignore any filefolders which may coincidently have
+ // the same label as a true folder
+ if (!pItem2->IsFileFolder())
+ {
+ stack.push_back(j);
+ CLog::Log(LOGDEBUG, " Adding path: [{:03}] {}", j, CURL::GetRedacted(pItem2->GetPath()));
+ }
+ j++;
+ }
+ while (j < items.Size());
+
+ // do we have anything to combine?
+ if (stack.size() > 1)
+ {
+ // we have a multipath so remove the items and add the new item
+ std::string newPath = ConstructMultiPath(items, stack);
+ for (unsigned int k = stack.size() - 1; k > 0; --k)
+ items.Remove(stack[k]);
+ pItem1->SetPath(newPath);
+ CLog::Log(LOGDEBUG, " New path: {}", CURL::GetRedacted(pItem1->GetPath()));
+ }
+
+ i++;
+ }
+
+ auto end = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
+
+ CLog::Log(LOGDEBUG, "CMultiPathDirectory::MergeItems, items = {}, took {} ms", items.Size(),
+ duration.count());
+}
+
+bool CMultiPathDirectory::SupportsWriteFileOperations(const std::string &strPath)
+{
+ std::vector<std::string> paths;
+ GetPaths(strPath, paths);
+ for (unsigned int i = 0; i < paths.size(); ++i)
+ if (CUtil::SupportsWriteFileOperations(paths[i]))
+ return true;
+ return false;
+}