Merge pull request #26278 from basilgello/taglib2-fix-piers
[xbmc.git] / xbmc / FileItemList.cpp
blob7580549d1f881b3edb648e35dbe72b159dd7aef5
1 /*
2 * Copyright (C) 2005-2020 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.
7 */
9 #include "FileItemList.h"
11 #include "CueDocument.h"
12 #include "ServiceBroker.h"
13 #include "URL.h"
14 #include "filesystem/Directory.h"
15 #include "filesystem/File.h"
16 #include "filesystem/MusicDatabaseDirectory.h"
17 #include "filesystem/StackDirectory.h"
18 #include "filesystem/VideoDatabaseDirectory.h"
19 #include "music/MusicFileItemClassify.h"
20 #include "network/NetworkFileItemClassify.h"
21 #include "playlists/PlayListFileItemClassify.h"
22 #include "settings/AdvancedSettings.h"
23 #include "settings/Settings.h"
24 #include "settings/SettingsComponent.h"
25 #include "utils/Archive.h"
26 #include "utils/ArtUtils.h"
27 #include "utils/Crc32.h"
28 #include "utils/FileExtensionProvider.h"
29 #include "utils/Random.h"
30 #include "utils/RegExp.h"
31 #include "utils/URIUtils.h"
32 #include "utils/log.h"
33 #include "video/VideoFileItemClassify.h"
34 #include "video/VideoUtils.h"
36 #include <algorithm>
38 using namespace KODI;
39 using namespace XFILE;
41 CFileItemList::CFileItemList() : CFileItem("", true)
45 CFileItemList::CFileItemList(const std::string& strPath) : CFileItem(strPath, true)
49 CFileItemList::~CFileItemList()
51 Clear();
54 CFileItemPtr CFileItemList::operator[](int iItem)
56 return Get(iItem);
59 const CFileItemPtr CFileItemList::operator[](int iItem) const
61 return Get(iItem);
64 CFileItemPtr CFileItemList::operator[](const std::string& strPath)
66 return Get(strPath);
69 const CFileItemPtr CFileItemList::operator[](const std::string& strPath) const
71 return Get(strPath);
74 void CFileItemList::SetIgnoreURLOptions(bool ignoreURLOptions)
76 m_ignoreURLOptions = ignoreURLOptions;
78 if (m_fastLookup)
80 m_fastLookup = false; // Force SetFastlookup to clear map
81 SetFastLookup(true); // and regenerate map
85 void CFileItemList::SetFastLookup(bool fastLookup)
87 std::unique_lock<CCriticalSection> lock(m_lock);
89 if (fastLookup && !m_fastLookup)
90 { // generate the map
91 m_map.clear();
92 for (unsigned int i = 0; i < m_items.size(); i++)
94 CFileItemPtr pItem = m_items[i];
95 m_map.insert(MAPFILEITEMSPAIR(m_ignoreURLOptions ? CURL(pItem->GetPath()).GetWithoutOptions()
96 : pItem->GetPath(),
97 pItem));
100 if (!fastLookup && m_fastLookup)
101 m_map.clear();
102 m_fastLookup = fastLookup;
105 bool CFileItemList::Contains(const std::string& fileName) const
107 std::unique_lock<CCriticalSection> lock(m_lock);
109 if (m_fastLookup)
110 return m_map.find(m_ignoreURLOptions ? CURL(fileName).GetWithoutOptions() : fileName) !=
111 m_map.end();
113 // slow method...
114 for (unsigned int i = 0; i < m_items.size(); i++)
116 const CFileItemPtr pItem = m_items[i];
117 if (pItem->IsPath(m_ignoreURLOptions ? CURL(fileName).GetWithoutOptions() : fileName))
118 return true;
120 return false;
123 void CFileItemList::Clear()
125 std::unique_lock<CCriticalSection> lock(m_lock);
127 ClearItems();
128 m_sortDescription.sortBy = SortByNone;
129 m_sortDescription.sortOrder = SortOrderNone;
130 m_sortDescription.sortAttributes = SortAttributeNone;
131 m_sortIgnoreFolders = false;
132 m_cacheToDisc = CacheType::IF_SLOW;
133 m_sortDetails.clear();
134 m_replaceListing = false;
135 m_content.clear();
138 void CFileItemList::ClearItems()
140 std::unique_lock<CCriticalSection> lock(m_lock);
141 // make sure we free the memory of the items (these are GUIControls which may have allocated resources)
142 FreeMemory();
143 for (unsigned int i = 0; i < m_items.size(); i++)
145 CFileItemPtr item = m_items[i];
146 item->FreeMemory();
148 m_items.clear();
149 m_map.clear();
152 void CFileItemList::Add(CFileItemPtr pItem)
154 std::unique_lock<CCriticalSection> lock(m_lock);
155 if (m_fastLookup)
156 m_map.insert(MAPFILEITEMSPAIR(
157 m_ignoreURLOptions ? CURL(pItem->GetPath()).GetWithoutOptions() : pItem->GetPath(), pItem));
158 m_items.emplace_back(std::move(pItem));
161 void CFileItemList::Add(CFileItem&& item)
163 std::unique_lock<CCriticalSection> lock(m_lock);
164 auto ptr = std::make_shared<CFileItem>(std::move(item));
165 if (m_fastLookup)
166 m_map.insert(MAPFILEITEMSPAIR(
167 m_ignoreURLOptions ? CURL(ptr->GetPath()).GetWithoutOptions() : ptr->GetPath(), ptr));
168 m_items.emplace_back(std::move(ptr));
171 void CFileItemList::AddFront(const CFileItemPtr& pItem, int itemPosition)
173 std::unique_lock<CCriticalSection> lock(m_lock);
175 if (itemPosition >= 0)
177 m_items.insert(m_items.begin() + itemPosition, pItem);
179 else
181 m_items.insert(m_items.begin() + (m_items.size() + itemPosition), pItem);
183 if (m_fastLookup)
185 m_map.insert(MAPFILEITEMSPAIR(
186 m_ignoreURLOptions ? CURL(pItem->GetPath()).GetWithoutOptions() : pItem->GetPath(), pItem));
190 void CFileItemList::Remove(CFileItem* pItem)
192 std::unique_lock<CCriticalSection> lock(m_lock);
194 for (IVECFILEITEMS it = m_items.begin(); it != m_items.end(); ++it)
196 if (pItem == it->get())
198 m_items.erase(it);
199 if (m_fastLookup)
201 m_map.erase(m_ignoreURLOptions ? CURL(pItem->GetPath()).GetWithoutOptions()
202 : pItem->GetPath());
204 break;
209 VECFILEITEMS::iterator CFileItemList::erase(VECFILEITEMS::iterator first,
210 VECFILEITEMS::iterator last)
212 std::unique_lock<CCriticalSection> lock(m_lock);
213 return m_items.erase(first, last);
216 void CFileItemList::Remove(int iItem)
218 std::unique_lock<CCriticalSection> lock(m_lock);
220 if (iItem >= 0 && iItem < Size())
222 CFileItemPtr pItem = *(m_items.begin() + iItem);
223 if (m_fastLookup)
225 m_map.erase(m_ignoreURLOptions ? CURL(pItem->GetPath()).GetWithoutOptions()
226 : pItem->GetPath());
228 m_items.erase(m_items.begin() + iItem);
232 void CFileItemList::Append(const CFileItemList& itemlist)
234 std::unique_lock<CCriticalSection> lock(m_lock);
236 for (int i = 0; i < itemlist.Size(); ++i)
237 Add(itemlist[i]);
240 void CFileItemList::Assign(const CFileItemList& itemlist, bool append)
242 std::unique_lock<CCriticalSection> lock(m_lock);
243 if (!append)
244 Clear();
245 Append(itemlist);
246 SetPath(itemlist.GetPath());
247 SetLabel(itemlist.GetLabel());
248 m_sortDetails = itemlist.m_sortDetails;
249 m_sortDescription = itemlist.m_sortDescription;
250 m_replaceListing = itemlist.m_replaceListing;
251 m_content = itemlist.m_content;
252 m_mapProperties = itemlist.m_mapProperties;
253 m_cacheToDisc = itemlist.m_cacheToDisc;
256 bool CFileItemList::Copy(const CFileItemList& items, bool copyItems /* = true */)
258 // assign all CFileItem parts
259 *static_cast<CFileItem*>(this) = static_cast<const CFileItem&>(items);
261 // assign the rest of the CFileItemList properties
262 m_replaceListing = items.m_replaceListing;
263 m_content = items.m_content;
264 m_mapProperties = items.m_mapProperties;
265 m_cacheToDisc = items.m_cacheToDisc;
266 m_sortDetails = items.m_sortDetails;
267 m_sortDescription = items.m_sortDescription;
268 m_sortIgnoreFolders = items.m_sortIgnoreFolders;
270 if (copyItems)
272 // make a copy of each item
273 for (int i = 0; i < items.Size(); i++)
275 CFileItemPtr newItem(new CFileItem(*items[i]));
276 Add(newItem);
280 return true;
283 CFileItemPtr CFileItemList::Get(int iItem) const
285 std::unique_lock<CCriticalSection> lock(m_lock);
287 if (iItem > -1 && iItem < (int)m_items.size())
288 return m_items[iItem];
290 return CFileItemPtr();
293 CFileItemPtr CFileItemList::Get(const std::string& strPath) const
295 std::unique_lock<CCriticalSection> lock(m_lock);
297 if (m_fastLookup)
299 MAPFILEITEMS::const_iterator it =
300 m_map.find(m_ignoreURLOptions ? CURL(strPath).GetWithoutOptions() : strPath);
301 if (it != m_map.end())
302 return it->second;
304 return CFileItemPtr();
306 // slow method...
307 for (unsigned int i = 0; i < m_items.size(); i++)
309 CFileItemPtr pItem = m_items[i];
310 if (pItem->IsPath(m_ignoreURLOptions ? CURL(strPath).GetWithoutOptions() : strPath))
311 return pItem;
314 return CFileItemPtr();
317 int CFileItemList::Size() const
319 std::unique_lock<CCriticalSection> lock(m_lock);
320 return (int)m_items.size();
323 bool CFileItemList::IsEmpty() const
325 std::unique_lock<CCriticalSection> lock(m_lock);
326 return m_items.empty();
329 void CFileItemList::Reserve(size_t iCount)
331 std::unique_lock<CCriticalSection> lock(m_lock);
332 m_items.reserve(iCount);
335 void CFileItemList::Sort(FILEITEMLISTCOMPARISONFUNC func)
337 std::unique_lock<CCriticalSection> lock(m_lock);
338 std::stable_sort(m_items.begin(), m_items.end(), func);
341 void CFileItemList::FillSortFields(FILEITEMFILLFUNC func)
343 std::unique_lock<CCriticalSection> lock(m_lock);
344 std::for_each(m_items.begin(), m_items.end(), func);
347 void CFileItemList::Sort(SortBy sortBy,
348 SortOrder sortOrder,
349 SortAttribute sortAttributes /* = SortAttributeNone */)
351 if (sortBy == SortByNone ||
352 (m_sortDescription.sortBy == sortBy && m_sortDescription.sortOrder == sortOrder &&
353 m_sortDescription.sortAttributes == sortAttributes))
354 return;
356 SortDescription sorting;
357 sorting.sortBy = sortBy;
358 sorting.sortOrder = sortOrder;
359 sorting.sortAttributes = sortAttributes;
361 Sort(sorting);
362 m_sortDescription = sorting;
365 void CFileItemList::Sort(SortDescription sortDescription)
367 if (sortDescription.sortBy == SortByFile || sortDescription.sortBy == SortBySortTitle ||
368 sortDescription.sortBy == SortByOriginalTitle || sortDescription.sortBy == SortByDateAdded ||
369 sortDescription.sortBy == SortByRating || sortDescription.sortBy == SortByYear ||
370 sortDescription.sortBy == SortByPlaylistOrder || sortDescription.sortBy == SortByLastPlayed ||
371 sortDescription.sortBy == SortByPlaycount)
372 sortDescription.sortAttributes =
373 (SortAttribute)((int)sortDescription.sortAttributes | SortAttributeIgnoreFolders);
375 if (sortDescription.sortBy == SortByNone ||
376 (m_sortDescription.sortBy == sortDescription.sortBy &&
377 m_sortDescription.sortOrder == sortDescription.sortOrder &&
378 m_sortDescription.sortAttributes == sortDescription.sortAttributes))
379 return;
381 if (m_sortIgnoreFolders)
382 sortDescription.sortAttributes =
383 (SortAttribute)((int)sortDescription.sortAttributes | SortAttributeIgnoreFolders);
385 const Fields fields = SortUtils::GetFieldsForSorting(sortDescription.sortBy);
386 SortItems sortItems((size_t)Size());
387 for (int index = 0; index < Size(); index++)
389 sortItems[index] = std::make_shared<SortItem>();
390 m_items[index]->ToSortable(*sortItems[index], fields);
391 (*sortItems[index])[FieldId] = index;
394 // do the sorting
395 SortUtils::Sort(sortDescription, sortItems);
397 // apply the new order to the existing CFileItems
398 VECFILEITEMS sortedFileItems;
399 sortedFileItems.reserve(Size());
400 for (SortItems::const_iterator it = sortItems.begin(); it != sortItems.end(); ++it)
402 CFileItemPtr item = m_items[(int)(*it)->at(FieldId).asInteger()];
403 // Set the sort label in the CFileItem
404 item->SetSortLabel((*it)->at(FieldSort).asWideString());
406 sortedFileItems.push_back(item);
409 // replace the current list with the re-ordered one
410 m_items = std::move(sortedFileItems);
413 void CFileItemList::Randomize()
415 std::unique_lock<CCriticalSection> lock(m_lock);
416 KODI::UTILS::RandomShuffle(m_items.begin(), m_items.end());
419 void CFileItemList::Archive(CArchive& ar)
421 std::unique_lock<CCriticalSection> lock(m_lock);
422 if (ar.IsStoring())
424 CFileItem::Archive(ar);
426 int i = 0;
427 if (!m_items.empty() && m_items[0]->IsParentFolder())
428 i = 1;
430 ar << (int)(m_items.size() - i);
432 ar << m_ignoreURLOptions;
434 ar << m_fastLookup;
436 ar << (int)m_sortDescription.sortBy;
437 ar << (int)m_sortDescription.sortOrder;
438 ar << (int)m_sortDescription.sortAttributes;
439 ar << m_sortIgnoreFolders;
440 ar << (int)m_cacheToDisc;
442 ar << (int)m_sortDetails.size();
443 for (unsigned int j = 0; j < m_sortDetails.size(); ++j)
445 const GUIViewSortDetails& details = m_sortDetails[j];
446 ar << (int)details.m_sortDescription.sortBy;
447 ar << (int)details.m_sortDescription.sortOrder;
448 ar << (int)details.m_sortDescription.sortAttributes;
449 ar << details.m_buttonLabel;
450 ar << details.m_labelMasks.m_strLabelFile;
451 ar << details.m_labelMasks.m_strLabelFolder;
452 ar << details.m_labelMasks.m_strLabel2File;
453 ar << details.m_labelMasks.m_strLabel2Folder;
456 ar << m_content;
458 for (; i < (int)m_items.size(); ++i)
460 CFileItemPtr pItem = m_items[i];
461 ar << *pItem;
464 else
466 CFileItemPtr pParent;
467 if (!IsEmpty())
469 CFileItemPtr pItem = m_items[0];
470 if (pItem->IsParentFolder())
471 pParent = std::make_shared<CFileItem>(*pItem);
474 SetIgnoreURLOptions(false);
475 SetFastLookup(false);
476 Clear();
478 CFileItem::Archive(ar);
480 int iSize = 0;
481 ar >> iSize;
482 if (iSize <= 0)
483 return;
485 if (pParent)
487 m_items.reserve(iSize + 1);
488 m_items.push_back(pParent);
490 else
491 m_items.reserve(iSize);
493 bool ignoreURLOptions = false;
494 ar >> ignoreURLOptions;
496 bool fastLookup = false;
497 ar >> fastLookup;
499 int tempint;
500 ar >> tempint;
501 m_sortDescription.sortBy = (SortBy)tempint;
502 ar >> tempint;
503 m_sortDescription.sortOrder = (SortOrder)tempint;
504 ar >> tempint;
505 m_sortDescription.sortAttributes = (SortAttribute)tempint;
506 ar >> m_sortIgnoreFolders;
507 ar >> tempint;
508 m_cacheToDisc = CacheType(tempint);
510 unsigned int detailSize = 0;
511 ar >> detailSize;
512 for (unsigned int j = 0; j < detailSize; ++j)
514 GUIViewSortDetails details;
515 ar >> tempint;
516 details.m_sortDescription.sortBy = (SortBy)tempint;
517 ar >> tempint;
518 details.m_sortDescription.sortOrder = (SortOrder)tempint;
519 ar >> tempint;
520 details.m_sortDescription.sortAttributes = (SortAttribute)tempint;
521 ar >> details.m_buttonLabel;
522 ar >> details.m_labelMasks.m_strLabelFile;
523 ar >> details.m_labelMasks.m_strLabelFolder;
524 ar >> details.m_labelMasks.m_strLabel2File;
525 ar >> details.m_labelMasks.m_strLabel2Folder;
526 m_sortDetails.push_back(details);
529 ar >> m_content;
531 for (int i = 0; i < iSize; ++i)
533 CFileItemPtr pItem(new CFileItem);
534 ar >> *pItem;
535 Add(pItem);
538 SetIgnoreURLOptions(ignoreURLOptions);
539 SetFastLookup(fastLookup);
543 void CFileItemList::FillInDefaultIcons()
545 std::unique_lock<CCriticalSection> lock(m_lock);
546 for (int i = 0; i < (int)m_items.size(); ++i)
548 CFileItemPtr pItem = m_items[i];
549 ART::FillInDefaultIcon(*pItem);
553 int CFileItemList::GetFolderCount() const
555 std::unique_lock<CCriticalSection> lock(m_lock);
556 int nFolderCount = 0;
557 for (int i = 0; i < (int)m_items.size(); i++)
559 CFileItemPtr pItem = m_items[i];
560 if (pItem->m_bIsFolder)
561 nFolderCount++;
564 return nFolderCount;
567 int CFileItemList::GetObjectCount() const
569 std::unique_lock<CCriticalSection> lock(m_lock);
571 int numObjects = (int)m_items.size();
572 if (numObjects && m_items[0]->IsParentFolder())
573 numObjects--;
575 return numObjects;
578 int CFileItemList::GetFileCount() const
580 std::unique_lock<CCriticalSection> lock(m_lock);
581 int nFileCount = 0;
582 for (int i = 0; i < (int)m_items.size(); i++)
584 CFileItemPtr pItem = m_items[i];
585 if (!pItem->m_bIsFolder)
586 nFileCount++;
589 return nFileCount;
592 int CFileItemList::GetSelectedCount() const
594 std::unique_lock<CCriticalSection> lock(m_lock);
595 int count = 0;
596 for (int i = 0; i < (int)m_items.size(); i++)
598 CFileItemPtr pItem = m_items[i];
599 if (pItem->IsSelected())
600 count++;
603 return count;
606 void CFileItemList::FilterCueItems()
608 std::unique_lock<CCriticalSection> lock(m_lock);
609 // Handle .CUE sheet files...
610 std::vector<std::string> itemstodelete;
611 for (int i = 0; i < (int)m_items.size(); i++)
613 CFileItemPtr pItem = m_items[i];
614 if (!pItem->m_bIsFolder)
615 { // see if it's a .CUE sheet
616 if (MUSIC::IsCUESheet(*pItem))
618 CCueDocumentPtr cuesheet(new CCueDocument);
619 if (cuesheet->ParseFile(pItem->GetPath()))
621 std::vector<std::string> MediaFileVec;
622 cuesheet->GetMediaFiles(MediaFileVec);
624 // queue the cue sheet and the underlying media file for deletion
625 for (std::vector<std::string>::iterator itMedia = MediaFileVec.begin();
626 itMedia != MediaFileVec.end(); ++itMedia)
628 std::string strMediaFile = *itMedia;
629 std::string fileFromCue =
630 strMediaFile; // save the file from the cue we're matching against,
631 // as we're going to search for others here...
632 bool bFoundMediaFile = CFile::Exists(strMediaFile);
633 if (!bFoundMediaFile)
635 // try file in same dir, not matching case...
636 if (Contains(strMediaFile))
638 bFoundMediaFile = true;
640 else
642 // try removing the .cue extension...
643 strMediaFile = pItem->GetPath();
644 URIUtils::RemoveExtension(strMediaFile);
645 CFileItem item(strMediaFile, false);
646 if (MUSIC::IsAudio(item) && Contains(strMediaFile))
648 bFoundMediaFile = true;
650 else
651 { // try replacing the extension with one of our allowed ones.
652 std::vector<std::string> extensions = StringUtils::Split(
653 CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(), "|");
654 for (std::vector<std::string>::const_iterator i = extensions.begin();
655 i != extensions.end(); ++i)
657 strMediaFile = URIUtils::ReplaceExtension(pItem->GetPath(), *i);
658 CFileItem item(strMediaFile, false);
659 if (!MUSIC::IsCUESheet(item) && !PLAYLIST::IsPlayList(item) &&
660 Contains(strMediaFile))
662 bFoundMediaFile = true;
663 break;
669 if (bFoundMediaFile)
671 cuesheet->UpdateMediaFile(fileFromCue, strMediaFile);
672 // apply CUE for later processing
673 for (int j = 0; j < (int)m_items.size(); j++)
675 CFileItemPtr pItem = m_items[j];
676 if (StringUtils::CompareNoCase(pItem->GetPath(), strMediaFile) == 0)
677 pItem->SetCueDocument(cuesheet);
682 itemstodelete.push_back(pItem->GetPath());
686 // now delete the .CUE files.
687 for (int i = 0; i < (int)itemstodelete.size(); i++)
689 for (int j = 0; j < (int)m_items.size(); j++)
691 CFileItemPtr pItem = m_items[j];
692 if (StringUtils::CompareNoCase(pItem->GetPath(), itemstodelete[i]) == 0)
693 { // delete this item
694 m_items.erase(m_items.begin() + j);
695 break;
701 // Remove the extensions from the filenames
702 void CFileItemList::RemoveExtensions()
704 std::unique_lock<CCriticalSection> lock(m_lock);
705 for (int i = 0; i < Size(); ++i)
706 m_items[i]->RemoveExtension();
709 void CFileItemList::Stack(bool stackFiles /* = true */)
711 std::unique_lock<CCriticalSection> lock(m_lock);
713 // not allowed here
714 if (IsVirtualDirectoryRoot() || IsLiveTV() || IsSourcesPath() || IsLibraryFolder())
715 return;
717 SetProperty("isstacked", true);
719 // items needs to be sorted for stuff below to work properly
720 Sort(SortByLabel, SortOrderAscending);
722 StackFolders();
724 if (stackFiles)
725 StackFiles();
728 void CFileItemList::StackFolders()
730 // Precompile our REs
731 VECCREGEXP folderRegExps;
732 CRegExp folderRegExp(true, CRegExp::autoUtf8);
733 const std::vector<std::string>& strFolderRegExps =
734 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_folderStackRegExps;
736 std::vector<std::string>::const_iterator strExpression = strFolderRegExps.begin();
737 while (strExpression != strFolderRegExps.end())
739 if (!folderRegExp.RegComp(*strExpression))
740 CLog::Log(LOGERROR, "{}: Invalid folder stack RegExp:'{}'", __FUNCTION__,
741 strExpression->c_str());
742 else
743 folderRegExps.push_back(folderRegExp);
745 ++strExpression;
748 if (!folderRegExp.IsCompiled())
750 CLog::Log(LOGDEBUG, "{}: No stack expressions available. Skipping folder stacking",
751 __FUNCTION__);
752 return;
755 // stack folders
756 for (int i = 0; i < Size(); i++)
758 CFileItemPtr item = Get(i);
759 // combined the folder checks
760 if (item->m_bIsFolder)
762 // only check known fast sources?
763 // NOTES:
764 // 1. rars and zips may be on slow sources? is this supposed to be allowed?
765 if (!NETWORK::IsRemote(*item) || item->IsSmb() || item->IsNfs() ||
766 URIUtils::IsInRAR(item->GetPath()) || URIUtils::IsInZIP(item->GetPath()) ||
767 URIUtils::IsOnLAN(item->GetPath()))
769 // stack cd# folders if contains only a single video file
771 bool bMatch(false);
773 VECCREGEXP::iterator expr = folderRegExps.begin();
774 while (!bMatch && expr != folderRegExps.end())
776 //CLog::Log(LOGDEBUG,"{}: Running expression {} on {}", __FUNCTION__, expr->GetPattern(), item->GetLabel());
777 bMatch = (expr->RegFind(item->GetLabel().c_str()) != -1);
778 if (bMatch)
780 CFileItemList items;
781 CDirectory::GetDirectory(
782 item->GetPath(), items,
783 CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(), DIR_FLAG_DEFAULTS);
784 // optimized to only traverse listing once by checking for filecount
785 // and recording last file item for later use
786 int nFiles = 0;
787 int index = -1;
788 for (int j = 0; j < items.Size(); j++)
790 if (!items[j]->m_bIsFolder)
792 nFiles++;
793 index = j;
796 if (nFiles > 1)
797 break;
800 if (nFiles == 1)
801 *item = *items[index];
803 ++expr;
806 // check for dvd folders
807 if (!bMatch)
809 std::string dvdPath = VIDEO::UTILS::GetOpticalMediaPath(*item);
811 if (!dvdPath.empty())
813 // NOTE: should this be done for the CD# folders too?
814 item->m_bIsFolder = false;
815 item->SetPath(dvdPath);
816 item->SetLabel2("");
817 item->SetLabelPreformatted(true);
818 m_sortDescription.sortBy = SortByNone; /* sorting is now broken */
826 void CFileItemList::StackFiles()
828 // Precompile our REs
829 VECCREGEXP stackRegExps;
830 CRegExp tmpRegExp(true, CRegExp::autoUtf8);
831 const std::vector<std::string>& strStackRegExps =
832 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoStackRegExps;
833 std::vector<std::string>::const_iterator strRegExp = strStackRegExps.begin();
834 while (strRegExp != strStackRegExps.end())
836 if (tmpRegExp.RegComp(*strRegExp))
838 if (tmpRegExp.GetCaptureTotal() == 4)
839 stackRegExps.push_back(tmpRegExp);
840 else
841 CLog::Log(LOGERROR, "Invalid video stack RE ({}). Must have 4 captures.", *strRegExp);
843 ++strRegExp;
846 // now stack the files, some of which may be from the previous stack iteration
847 int i = 0;
848 while (i < Size())
850 CFileItemPtr item1 = Get(i);
852 // skip folders, nfo files, playlists
853 if (item1->m_bIsFolder || item1->IsParentFolder() || item1->IsNFO() ||
854 PLAYLIST::IsPlayList(*item1))
856 // increment index
857 i++;
858 continue;
861 int64_t size = 0;
862 size_t offset = 0;
863 std::string stackName;
864 std::string file1;
865 std::string filePath;
866 std::vector<int> stack;
867 VECCREGEXP::iterator expr = stackRegExps.begin();
869 URIUtils::Split(item1->GetPath(), filePath, file1);
870 if (URIUtils::HasEncodedFilename(CURL(filePath)))
871 file1 = CURL::Decode(file1);
873 int j;
874 while (expr != stackRegExps.end())
876 if (expr->RegFind(file1, offset) != -1)
878 std::string Title1 = expr->GetMatch(1), Volume1 = expr->GetMatch(2),
879 Ignore1 = expr->GetMatch(3), Extension1 = expr->GetMatch(4);
880 if (offset)
881 Title1 = file1.substr(0, expr->GetSubStart(2));
882 j = i + 1;
883 while (j < Size())
885 CFileItemPtr item2 = Get(j);
887 // skip folders, nfo files, playlists
888 if (item2->m_bIsFolder || item2->IsParentFolder() || item2->IsNFO() ||
889 PLAYLIST::IsPlayList(*item2))
891 // increment index
892 j++;
893 continue;
896 std::string file2, filePath2;
897 URIUtils::Split(item2->GetPath(), filePath2, file2);
898 if (URIUtils::HasEncodedFilename(CURL(filePath2)))
899 file2 = CURL::Decode(file2);
901 if (expr->RegFind(file2, offset) != -1)
903 std::string Title2 = expr->GetMatch(1), Volume2 = expr->GetMatch(2),
904 Ignore2 = expr->GetMatch(3), Extension2 = expr->GetMatch(4);
905 if (offset)
906 Title2 = file2.substr(0, expr->GetSubStart(2));
907 if (StringUtils::EqualsNoCase(Title1, Title2))
909 if (!StringUtils::EqualsNoCase(Volume1, Volume2))
911 if (StringUtils::EqualsNoCase(Ignore1, Ignore2) &&
912 StringUtils::EqualsNoCase(Extension1, Extension2))
914 if (stack.empty())
916 stackName = Title1 + Ignore1 + Extension1;
917 stack.push_back(i);
918 size += item1->m_dwSize;
920 stack.push_back(j);
921 size += item2->m_dwSize;
923 else // Sequel
925 offset = 0;
926 ++expr;
927 break;
930 else if (!StringUtils::EqualsNoCase(Ignore1,
931 Ignore2)) // False positive, try again with offset
933 offset = expr->GetSubStart(3);
934 break;
936 else // Extension mismatch
938 offset = 0;
939 ++expr;
940 break;
943 else // Title mismatch
945 offset = 0;
946 ++expr;
947 break;
950 else // No match 2, next expression
952 offset = 0;
953 ++expr;
954 break;
956 j++;
958 if (j == Size())
959 expr = stackRegExps.end();
961 else // No match 1
963 offset = 0;
964 ++expr;
966 if (stack.size() > 1)
968 // have a stack, remove the items and add the stacked item
969 // dont actually stack a multipart rar set, just remove all items but the first
970 std::string stackPath;
971 if (Get(stack[0])->IsRAR())
972 stackPath = Get(stack[0])->GetPath();
973 else
975 CStackDirectory dir;
976 stackPath = dir.ConstructStackPath(*this, stack);
978 item1->SetPath(stackPath);
979 // clean up list
980 for (unsigned k = 1; k < stack.size(); k++)
981 Remove(i + 1);
982 // item->m_bIsFolder = true; // don't treat stacked files as folders
983 // the label may be in a different char set from the filename (eg over smb
984 // the label is converted from utf8, but the filename is not)
985 if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
986 CSettings::SETTING_FILELISTS_SHOWEXTENSIONS))
987 URIUtils::RemoveExtension(stackName);
989 item1->SetLabel(stackName);
990 item1->m_dwSize = size;
991 break;
994 i++;
998 bool CFileItemList::Load(int windowID)
1000 CFile file;
1001 auto path = GetDiscFileCache(windowID);
1004 if (file.Open(path))
1006 CArchive ar(&file, CArchive::load);
1007 ar >> *this;
1008 CLog::Log(LOGDEBUG, "Loading items: {}, directory: {} sort method: {}, ascending: {}", Size(),
1009 CURL::GetRedacted(GetPath()), m_sortDescription.sortBy,
1010 m_sortDescription.sortOrder == SortOrderAscending ? "true" : "false");
1011 ar.Close();
1012 file.Close();
1013 return true;
1016 catch (const std::out_of_range&)
1018 CLog::Log(LOGERROR, "Corrupt archive: {}", CURL::GetRedacted(path));
1021 return false;
1024 bool CFileItemList::Save(int windowID)
1026 int iSize = Size();
1027 if (iSize <= 0)
1028 return false;
1030 CLog::Log(LOGDEBUG, "Saving fileitems [{}]", CURL::GetRedacted(GetPath()));
1032 CFile file;
1033 std::string cachefile = GetDiscFileCache(windowID);
1034 if (file.OpenForWrite(cachefile, true)) // overwrite always
1036 // Before caching save simplified cache file name in every item so the cache file can be
1037 // identifed and removed if the item is updated. List path and options (used for file
1038 // name when list cached) can not be accurately derived from item path.
1039 StringUtils::Replace(cachefile, "special://temp/archive_cache/", "");
1040 StringUtils::Replace(cachefile, ".fi", "");
1041 for (const auto& item : m_items)
1042 item->SetProperty("cachefilename", cachefile);
1044 CArchive ar(&file, CArchive::store);
1045 ar << *this;
1046 CLog::Log(LOGDEBUG, " -- items: {}, sort method: {}, ascending: {}", iSize,
1047 m_sortDescription.sortBy,
1048 m_sortDescription.sortOrder == SortOrderAscending ? "true" : "false");
1049 ar.Close();
1050 file.Close();
1051 return true;
1054 return false;
1057 void CFileItemList::RemoveDiscCache(int windowID) const
1059 RemoveDiscCache(GetDiscFileCache(windowID));
1062 void CFileItemList::RemoveDiscCache(const std::string& cacheFile) const
1064 if (CFile::Exists(cacheFile))
1066 CLog::Log(LOGDEBUG, "Clearing cached fileitems [{}]", CURL::GetRedacted(GetPath()));
1067 CFile::Delete(cacheFile);
1071 void CFileItemList::RemoveDiscCacheCRC(const std::string& crc) const
1073 std::string cachefile = StringUtils::Format("special://temp/archive_cache/{}.fi", crc);
1074 RemoveDiscCache(cachefile);
1077 std::string CFileItemList::GetDiscFileCache(int windowID) const
1079 std::string strPath(GetPath());
1080 URIUtils::RemoveSlashAtEnd(strPath);
1082 uint32_t crc = Crc32::ComputeFromLowerCase(strPath);
1084 if (MUSIC::IsCDDA(*this) || IsOnDVD())
1085 return StringUtils::Format("special://temp/archive_cache/r-{:08x}.fi", crc);
1087 if (MUSIC::IsMusicDb(*this))
1088 return StringUtils::Format("special://temp/archive_cache/mdb-{:08x}.fi", crc);
1090 if (VIDEO::IsVideoDb(*this))
1091 return StringUtils::Format("special://temp/archive_cache/vdb-{:08x}.fi", crc);
1093 if (PLAYLIST::IsSmartPlayList(*this))
1094 return StringUtils::Format("special://temp/archive_cache/sp-{:08x}.fi", crc);
1096 if (windowID)
1097 return StringUtils::Format("special://temp/archive_cache/{}-{:08x}.fi", windowID, crc);
1099 return StringUtils::Format("special://temp/archive_cache/{:08x}.fi", crc);
1102 bool CFileItemList::AlwaysCache() const
1104 // some database folders are always cached
1105 if (MUSIC::IsMusicDb(*this))
1106 return CMusicDatabaseDirectory::CanCache(GetPath());
1107 if (VIDEO::IsVideoDb(*this))
1108 return CVideoDatabaseDirectory::CanCache(GetPath());
1109 if (IsEPG())
1110 return true; // always cache
1111 return false;
1114 void CFileItemList::Swap(unsigned int item1, unsigned int item2)
1116 if (item1 != item2 && item1 < m_items.size() && item2 < m_items.size())
1117 std::swap(m_items[item1], m_items[item2]);
1120 bool CFileItemList::UpdateItem(const CFileItem* item)
1122 if (!item)
1123 return false;
1125 std::unique_lock<CCriticalSection> lock(m_lock);
1126 for (unsigned int i = 0; i < m_items.size(); i++)
1128 CFileItemPtr pItem = m_items[i];
1129 if (pItem->IsSamePath(item))
1131 pItem->UpdateInfo(*item);
1132 return true;
1135 return false;
1138 void CFileItemList::AddSortMethod(SortBy sortBy,
1139 int buttonLabel,
1140 const LABEL_MASKS& labelMasks,
1141 SortAttribute sortAttributes /* = SortAttributeNone */)
1143 AddSortMethod(sortBy, sortAttributes, buttonLabel, labelMasks);
1146 void CFileItemList::AddSortMethod(SortBy sortBy,
1147 SortAttribute sortAttributes,
1148 int buttonLabel,
1149 const LABEL_MASKS& labelMasks)
1151 SortDescription sorting;
1152 sorting.sortBy = sortBy;
1153 sorting.sortAttributes = sortAttributes;
1155 AddSortMethod(sorting, buttonLabel, labelMasks);
1158 void CFileItemList::AddSortMethod(const SortDescription& sortDescription,
1159 int buttonLabel,
1160 const LABEL_MASKS& labelMasks)
1162 GUIViewSortDetails sort;
1163 sort.m_sortDescription = sortDescription;
1164 sort.m_buttonLabel = buttonLabel;
1165 sort.m_labelMasks = labelMasks;
1167 m_sortDetails.push_back(sort);
1170 void CFileItemList::SetReplaceListing(bool replace)
1172 m_replaceListing = replace;
1175 void CFileItemList::ClearSortState()
1177 m_sortDescription.sortBy = SortByNone;
1178 m_sortDescription.sortOrder = SortOrderNone;
1179 m_sortDescription.sortAttributes = SortAttributeNone;