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 "Directory.h"
10
11 #include "Application.h"
12 #include "DirectoryCache.h"
13 #include "DirectoryFactory.h"
14 #include "FileDirectoryFactory.h"
15 #include "FileItem.h"
16 #include "PasswordManager.h"
17 #include "ServiceBroker.h"
18 #include "URL.h"
19 #include "commons/Exception.h"
20 #include "dialogs/GUIDialogBusy.h"
21 #include "guilib/GUIWindowManager.h"
22 #include "settings/Settings.h"
23 #include "settings/SettingsComponent.h"
24 #include "utils/Job.h"
25 #include "utils/JobManager.h"
26 #include "utils/URIUtils.h"
27 #include "utils/log.h"
28
29 using namespace XFILE;
30
31 #define TIME_TO_BUSY_DIALOG 500
32
33 class CGetDirectory
34 {
35 private:
36
37 struct CResult
38 {
CResultCGetDirectory::CResult39 CResult(const CURL& dir, const CURL& listDir) : m_event(true), m_dir(dir), m_listDir(listDir), m_result(false) {}
40 CEvent m_event;
41 CFileItemList m_list;
42 CURL m_dir;
43 CURL m_listDir;
44 bool m_result;
45 };
46
47 struct CGetJob
48 : CJob
49 {
CGetJobCGetDirectory::CGetJob50 CGetJob(std::shared_ptr<IDirectory>& imp
51 , std::shared_ptr<CResult>& result)
52 : m_result(result)
53 , m_imp(imp)
54 {}
55 public:
DoWorkCGetDirectory::CGetJob56 bool DoWork() override
57 {
58 m_result->m_list.SetURL(m_result->m_listDir);
59 m_result->m_result = m_imp->GetDirectory(m_result->m_dir, m_result->m_list);
60 m_result->m_event.Set();
61 return m_result->m_result;
62 }
63
64 std::shared_ptr<CResult> m_result;
65 std::shared_ptr<IDirectory> m_imp;
66 };
67
68 public:
69
CGetDirectory(std::shared_ptr<IDirectory> & imp,const CURL & dir,const CURL & listDir)70 CGetDirectory(std::shared_ptr<IDirectory>& imp, const CURL& dir, const CURL& listDir)
71 : m_result(new CResult(dir, listDir))
72 {
73 m_id = CJobManager::GetInstance().AddJob(new CGetJob(imp, m_result)
74 , NULL
75 , CJob::PRIORITY_HIGH);
76 if (m_id == 0)
77 {
78 CGetJob job(imp, m_result);
79 job.DoWork();
80 }
81 }
~CGetDirectory()82 ~CGetDirectory()
83 {
84 CJobManager::GetInstance().CancelJob(m_id);
85 }
86
GetEvent()87 CEvent& GetEvent()
88 {
89 return m_result->m_event;
90 }
91
Wait(unsigned int timeout)92 bool Wait(unsigned int timeout)
93 {
94 return m_result->m_event.WaitMSec(timeout);
95 }
96
GetDirectory(CFileItemList & list)97 bool GetDirectory(CFileItemList& list)
98 {
99 /* if it was not finished or failed, return failure */
100 if(!m_result->m_event.WaitMSec(0) || !m_result->m_result)
101 {
102 list.Clear();
103 return false;
104 }
105
106 list.Copy(m_result->m_list);
107 return true;
108 }
109 std::shared_ptr<CResult> m_result;
110 unsigned int m_id;
111 };
112
113
114 CDirectory::CDirectory() = default;
115
116 CDirectory::~CDirectory() = default;
117
GetDirectory(const std::string & strPath,CFileItemList & items,const std::string & strMask,int flags)118 bool CDirectory::GetDirectory(const std::string& strPath, CFileItemList &items, const std::string &strMask, int flags)
119 {
120 CHints hints;
121 hints.flags = flags;
122 hints.mask = strMask;
123 const CURL pathToUrl(strPath);
124 return GetDirectory(pathToUrl, items, hints);
125 }
126
GetDirectory(const std::string & strPath,const std::shared_ptr<IDirectory> & pDirectory,CFileItemList & items,const std::string & strMask,int flags)127 bool CDirectory::GetDirectory(const std::string& strPath,
128 const std::shared_ptr<IDirectory>& pDirectory,
129 CFileItemList& items,
130 const std::string& strMask,
131 int flags)
132 {
133 CHints hints;
134 hints.flags = flags;
135 hints.mask = strMask;
136 const CURL pathToUrl(strPath);
137 return GetDirectory(pathToUrl, pDirectory, items, hints);
138 }
139
GetDirectory(const std::string & strPath,CFileItemList & items,const CHints & hints)140 bool CDirectory::GetDirectory(const std::string& strPath, CFileItemList &items, const CHints &hints)
141 {
142 const CURL pathToUrl(strPath);
143 return GetDirectory(pathToUrl, items, hints);
144 }
145
GetDirectory(const CURL & url,CFileItemList & items,const std::string & strMask,int flags)146 bool CDirectory::GetDirectory(const CURL& url, CFileItemList &items, const std::string &strMask, int flags)
147 {
148 CHints hints;
149 hints.flags = flags;
150 hints.mask = strMask;
151 return GetDirectory(url, items, hints);
152 }
153
GetDirectory(const CURL & url,CFileItemList & items,const CHints & hints)154 bool CDirectory::GetDirectory(const CURL& url, CFileItemList &items, const CHints &hints)
155 {
156 CURL realURL = URIUtils::SubstitutePath(url);
157 std::shared_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
158 return CDirectory::GetDirectory(url, pDirectory, items, hints);
159 }
160
GetDirectory(const CURL & url,const std::shared_ptr<IDirectory> & pDirectory,CFileItemList & items,const CHints & hints)161 bool CDirectory::GetDirectory(const CURL& url,
162 const std::shared_ptr<IDirectory>& pDirectory,
163 CFileItemList& items,
164 const CHints& hints)
165 {
166 try
167 {
168 CURL realURL = URIUtils::SubstitutePath(url);
169 if (!pDirectory)
170 return false;
171
172 // check our cache for this path
173 if (g_directoryCache.GetDirectory(realURL.Get(), items, (hints.flags & DIR_FLAG_READ_CACHE) == DIR_FLAG_READ_CACHE))
174 items.SetURL(url);
175 else
176 {
177 // need to clear the cache (in case the directory fetch fails)
178 // and (re)fetch the folder
179 if (!(hints.flags & DIR_FLAG_BYPASS_CACHE))
180 g_directoryCache.ClearDirectory(realURL.Get());
181
182 pDirectory->SetFlags(hints.flags);
183
184 bool result = false, cancel = false;
185 CURL authUrl = realURL;
186
187 while (!result && !cancel)
188 {
189 const std::string pathToUrl(url.Get());
190
191 // don't change auth if it's set explicitly
192 if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
193 CPasswordManager::GetInstance().AuthenticateURL(authUrl);
194
195 items.SetURL(url);
196 result = pDirectory->GetDirectory(authUrl, items);
197
198 if (!result)
199 {
200 if (!cancel)
201 {
202 // @TODO ProcessRequirements() can bring up the keyboard input dialog
203 // filesystem must not depend on GUI
204 if (g_application.IsCurrentThread() && pDirectory->ProcessRequirements())
205 {
206 authUrl.SetDomain("");
207 authUrl.SetUserName("");
208 authUrl.SetPassword("");
209 continue;
210 }
211 }
212 CLog::Log(LOGERROR, "%s - Error getting %s", __FUNCTION__, url.GetRedacted().c_str());
213 return false;
214 }
215 }
216
217 // hide credentials if necessary
218 if (CPasswordManager::GetInstance().IsURLSupported(realURL))
219 {
220 bool hide = false;
221 // for explicitly credentials
222 if (!realURL.GetUserName().empty())
223 {
224 // credentials was changed i.e. were stored in the password
225 // manager, in this case we can hide them from an item URL,
226 // otherwise we have to keep credentials in an item URL
227 if ( realURL.GetUserName() != authUrl.GetUserName()
228 || realURL.GetPassWord() != authUrl.GetPassWord()
229 || realURL.GetDomain() != authUrl.GetDomain())
230 {
231 hide = true;
232 }
233 }
234 else
235 {
236 // hide credentials in any other cases
237 hide = true;
238 }
239
240 if (hide)
241 {
242 for (int i = 0; i < items.Size(); ++i)
243 {
244 CFileItemPtr item = items[i];
245 CURL itemUrl = item->GetURL();
246 itemUrl.SetDomain("");
247 itemUrl.SetUserName("");
248 itemUrl.SetPassword("");
249 item->SetPath(itemUrl.Get());
250 }
251 }
252 }
253
254 // cache the directory, if necessary
255 if (!(hints.flags & DIR_FLAG_BYPASS_CACHE))
256 g_directoryCache.SetDirectory(realURL.Get(), items, pDirectory->GetCacheType(url));
257 }
258
259 // now filter for allowed files
260 if (!pDirectory->AllowAll())
261 {
262 pDirectory->SetMask(hints.mask);
263 for (int i = 0; i < items.Size(); ++i)
264 {
265 CFileItemPtr item = items[i];
266 if (!item->m_bIsFolder && !pDirectory->IsAllowed(item->GetURL()))
267 {
268 items.Remove(i);
269 i--; // don't confuse loop
270 }
271 }
272 }
273 // filter hidden files
274 //! @todo we shouldn't be checking the gui setting here, callers should use getHidden instead
275 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWHIDDEN) && !(hints.flags & DIR_FLAG_GET_HIDDEN))
276 {
277 for (int i = 0; i < items.Size(); ++i)
278 {
279 if (items[i]->GetProperty("file:hidden").asBoolean())
280 {
281 items.Remove(i);
282 i--; // don't confuse loop
283 }
284 }
285 }
286
287 // Should any of the files we read be treated as a directory?
288 // Disable for database folders, as they already contain the extracted items
289 if (!(hints.flags & DIR_FLAG_NO_FILE_DIRS) && !items.IsMusicDb() && !items.IsVideoDb() && !items.IsSmartPlayList())
290 FilterFileDirectories(items, hints.mask);
291
292 // Correct items for path substitution
293 const std::string pathToUrl(url.Get());
294 const std::string pathToUrl2(realURL.Get());
295 if (pathToUrl != pathToUrl2)
296 {
297 for (int i = 0; i < items.Size(); ++i)
298 {
299 CFileItemPtr item = items[i];
300 item->SetPath(URIUtils::SubstitutePath(item->GetPath(), true));
301 }
302 }
303
304 return true;
305 }
306 XBMCCOMMONS_HANDLE_UNCHECKED
307 catch (...)
308 {
309 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
310 }
311 CLog::Log(LOGERROR, "%s - Error getting %s", __FUNCTION__, url.GetRedacted().c_str());
312 return false;
313 }
314
Create(const std::string & strPath)315 bool CDirectory::Create(const std::string& strPath)
316 {
317 const CURL pathToUrl(strPath);
318 return Create(pathToUrl);
319 }
320
Create(const CURL & url)321 bool CDirectory::Create(const CURL& url)
322 {
323 try
324 {
325 CURL realURL = URIUtils::SubstitutePath(url);
326
327 if (CPasswordManager::GetInstance().IsURLSupported(realURL) && realURL.GetUserName().empty())
328 CPasswordManager::GetInstance().AuthenticateURL(realURL);
329
330 std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
331 if (pDirectory)
332 if(pDirectory->Create(realURL))
333 return true;
334 }
335 XBMCCOMMONS_HANDLE_UNCHECKED
336 catch (...)
337 {
338 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
339 }
340 CLog::Log(LOGERROR, "%s - Error creating %s", __FUNCTION__, url.GetRedacted().c_str());
341 return false;
342 }
343
Exists(const std::string & strPath,bool bUseCache)344 bool CDirectory::Exists(const std::string& strPath, bool bUseCache /* = true */)
345 {
346 const CURL pathToUrl(strPath);
347 return Exists(pathToUrl, bUseCache);
348 }
349
Exists(const CURL & url,bool bUseCache)350 bool CDirectory::Exists(const CURL& url, bool bUseCache /* = true */)
351 {
352 try
353 {
354 CURL realURL = URIUtils::SubstitutePath(url);
355 if (bUseCache)
356 {
357 bool bPathInCache;
358 std::string realPath(realURL.Get());
359 URIUtils::AddSlashAtEnd(realPath);
360 if (g_directoryCache.FileExists(realPath, bPathInCache))
361 return true;
362 if (bPathInCache)
363 return false;
364 }
365
366 if (CPasswordManager::GetInstance().IsURLSupported(realURL) && realURL.GetUserName().empty())
367 CPasswordManager::GetInstance().AuthenticateURL(realURL);
368
369 std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
370 if (pDirectory)
371 return pDirectory->Exists(realURL);
372 }
373 XBMCCOMMONS_HANDLE_UNCHECKED
374 catch (...)
375 {
376 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
377 }
378 CLog::Log(LOGERROR, "%s - Error checking for %s", __FUNCTION__, url.GetRedacted().c_str());
379 return false;
380 }
381
Remove(const std::string & strPath)382 bool CDirectory::Remove(const std::string& strPath)
383 {
384 const CURL pathToUrl(strPath);
385 return Remove(pathToUrl);
386 }
387
RemoveRecursive(const std::string & strPath)388 bool CDirectory::RemoveRecursive(const std::string& strPath)
389 {
390 return RemoveRecursive(CURL{ strPath });
391 }
392
Remove(const CURL & url)393 bool CDirectory::Remove(const CURL& url)
394 {
395 try
396 {
397 CURL realURL = URIUtils::SubstitutePath(url);
398 CURL authUrl = realURL;
399 if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
400 CPasswordManager::GetInstance().AuthenticateURL(authUrl);
401
402 std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
403 if (pDirectory)
404 if(pDirectory->Remove(authUrl))
405 {
406 g_directoryCache.ClearFile(realURL.Get());
407 return true;
408 }
409 }
410 XBMCCOMMONS_HANDLE_UNCHECKED
411 catch (...)
412 {
413 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
414 }
415 CLog::Log(LOGERROR, "%s - Error removing %s", __FUNCTION__, url.GetRedacted().c_str());
416 return false;
417 }
418
RemoveRecursive(const CURL & url)419 bool CDirectory::RemoveRecursive(const CURL& url)
420 {
421 try
422 {
423 CURL realURL = URIUtils::SubstitutePath(url);
424 CURL authUrl = realURL;
425 if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
426 CPasswordManager::GetInstance().AuthenticateURL(authUrl);
427
428 std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
429 if (pDirectory)
430 if(pDirectory->RemoveRecursive(authUrl))
431 {
432 g_directoryCache.ClearFile(realURL.Get());
433 return true;
434 }
435 }
436 XBMCCOMMONS_HANDLE_UNCHECKED
437 catch (...)
438 {
439 CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
440 }
441 CLog::Log(LOGERROR, "%s - Error removing %s", __FUNCTION__, url.GetRedacted().c_str());
442 return false;
443 }
444
FilterFileDirectories(CFileItemList & items,const std::string & mask,bool expandImages)445 void CDirectory::FilterFileDirectories(CFileItemList &items, const std::string &mask,
446 bool expandImages)
447 {
448 for (int i=0; i< items.Size(); ++i)
449 {
450 CFileItemPtr pItem=items[i];
451 auto mode = expandImages && pItem->IsDiscImage() ? EFILEFOLDER_TYPE_ONBROWSE : EFILEFOLDER_TYPE_ALWAYS;
452 if (!pItem->m_bIsFolder && pItem->IsFileFolder(mode))
453 {
454 std::unique_ptr<IFileDirectory> pDirectory(CFileDirectoryFactory::Create(pItem->GetURL(),pItem.get(),mask));
455 if (pDirectory)
456 pItem->m_bIsFolder = true;
457 else
458 if (pItem->m_bIsFolder)
459 {
460 items.Remove(i);
461 i--; // don't confuse loop
462 }
463 }
464 }
465 }
466