1 /*
2  *  Copyright (C) 2012-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 "GroupUtils.h"
10 
11 #include "FileItem.h"
12 #include "filesystem/MultiPathDirectory.h"
13 #include "utils/StringUtils.h"
14 #include "utils/URIUtils.h"
15 #include "video/VideoDbUrl.h"
16 #include "video/VideoInfoTag.h"
17 
18 #include <map>
19 #include <set>
20 
21 using SetMap = std::map<int, std::set<CFileItemPtr> >;
22 
Group(GroupBy groupBy,const std::string & baseDir,const CFileItemList & items,CFileItemList & groupedItems,GroupAttribute groupAttributes)23 bool GroupUtils::Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, GroupAttribute groupAttributes /* = GroupAttributeNone */)
24 {
25   CFileItemList ungroupedItems;
26   return Group(groupBy, baseDir, items, groupedItems, ungroupedItems, groupAttributes);
27 }
28 
Group(GroupBy groupBy,const std::string & baseDir,const CFileItemList & items,CFileItemList & groupedItems,CFileItemList & ungroupedItems,GroupAttribute groupAttributes)29 bool GroupUtils::Group(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItems, CFileItemList &ungroupedItems, GroupAttribute groupAttributes /* = GroupAttributeNone */)
30 {
31   if (groupBy == GroupByNone)
32     return false;
33 
34   // nothing to do if there are no items to group
35   if (items.Size() <= 0)
36     return true;
37 
38   SetMap setMap;
39   for (int index = 0; index < items.Size(); index++)
40   {
41     bool ungrouped = true;
42     const CFileItemPtr item = items.Get(index);
43 
44     // group by sets
45     if ((groupBy & GroupBySet) &&
46       item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_set.id > 0)
47     {
48       ungrouped = false;
49       setMap[item->GetVideoInfoTag()->m_set.id].insert(item);
50     }
51 
52     if (ungrouped)
53       ungroupedItems.Add(item);
54   }
55 
56   if ((groupBy & GroupBySet) && !setMap.empty())
57   {
58     CVideoDbUrl itemsUrl;
59     if (!itemsUrl.FromString(baseDir))
60       return false;
61 
62     for (SetMap::const_iterator set = setMap.begin(); set != setMap.end(); ++set)
63     {
64       // only one item in the set, so add it to the ungrouped items
65       if (set->second.size() == 1 && (groupAttributes & GroupAttributeIgnoreSingleItems))
66       {
67         ungroupedItems.Add(*set->second.begin());
68         continue;
69       }
70 
71       CFileItemPtr pItem(new CFileItem((*set->second.begin())->GetVideoInfoTag()->m_set.title));
72       pItem->GetVideoInfoTag()->m_iDbId = set->first;
73       pItem->GetVideoInfoTag()->m_type = MediaTypeVideoCollection;
74 
75       std::string basePath = StringUtils::Format("videodb://movies/sets/%i/", set->first);
76       CVideoDbUrl videoUrl;
77       if (!videoUrl.FromString(basePath))
78         pItem->SetPath(basePath);
79       else
80       {
81         videoUrl.AddOptions((*set->second.begin())->GetURL().GetOptions());
82         pItem->SetPath(videoUrl.ToString());
83       }
84       pItem->m_bIsFolder = true;
85 
86       CVideoInfoTag* setInfo = pItem->GetVideoInfoTag();
87       setInfo->m_strPath = pItem->GetPath();
88       setInfo->m_strTitle = pItem->GetLabel();
89       setInfo->m_strPlot = (*set->second.begin())->GetVideoInfoTag()->m_set.overview;
90 
91       int ratings = 0;
92       float totalRatings = 0;
93       int iWatched = 0; // have all the movies been played at least once?
94       std::set<std::string> pathSet;
95       for (std::set<CFileItemPtr>::const_iterator movie = set->second.begin(); movie != set->second.end(); ++movie)
96       {
97         CVideoInfoTag* movieInfo = (*movie)->GetVideoInfoTag();
98         // handle rating
99         if (movieInfo->GetRating().rating > 0.0f)
100         {
101           ratings++;
102           totalRatings += movieInfo->GetRating().rating;
103         }
104 
105         // handle year
106         if (movieInfo->GetYear() > setInfo->GetYear())
107           setInfo->SetYear(movieInfo->GetYear());
108 
109         // handle lastplayed
110         if (movieInfo->m_lastPlayed.IsValid() && movieInfo->m_lastPlayed > setInfo->m_lastPlayed)
111           setInfo->m_lastPlayed = movieInfo->m_lastPlayed;
112 
113         // handle dateadded
114         if (movieInfo->m_dateAdded.IsValid() && movieInfo->m_dateAdded > setInfo->m_dateAdded)
115           setInfo->m_dateAdded = movieInfo->m_dateAdded;
116 
117         // handle playcount/watched
118         setInfo->SetPlayCount(setInfo->GetPlayCount() + movieInfo->GetPlayCount());
119         if (movieInfo->GetPlayCount() > 0)
120           iWatched++;
121 
122         //accumulate the path for a multipath construction
123         CFileItem video(movieInfo->m_basePath, false);
124         if (video.IsVideo())
125           pathSet.insert(URIUtils::GetParentPath(movieInfo->m_basePath));
126         else
127           pathSet.insert(movieInfo->m_basePath);
128       }
129       setInfo->m_basePath = XFILE::CMultiPathDirectory::ConstructMultiPath(pathSet);
130 
131       if (ratings > 0)
132         pItem->GetVideoInfoTag()->SetRating(totalRatings / ratings);
133 
134       setInfo->SetPlayCount(iWatched >= static_cast<int>(set->second.size()) ? (setInfo->GetPlayCount() / set->second.size()) : 0);
135       pItem->SetProperty("total", (int)set->second.size());
136       pItem->SetProperty("watched", iWatched);
137       pItem->SetProperty("unwatched", (int)set->second.size() - iWatched);
138       pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, setInfo->GetPlayCount() > 0);
139 
140       groupedItems.Add(pItem);
141     }
142   }
143 
144   return true;
145 }
146 
GroupAndMix(GroupBy groupBy,const std::string & baseDir,const CFileItemList & items,CFileItemList & groupedItemsMixed,GroupAttribute groupAttributes)147 bool GroupUtils::GroupAndMix(GroupBy groupBy, const std::string &baseDir, const CFileItemList &items, CFileItemList &groupedItemsMixed, GroupAttribute groupAttributes /* = GroupAttributeNone */)
148 {
149   CFileItemList ungroupedItems;
150   if (!Group(groupBy, baseDir, items, groupedItemsMixed, ungroupedItems, groupAttributes))
151     return false;
152 
153   // add all the ungrouped items as well
154   groupedItemsMixed.Append(ungroupedItems);
155 
156   return true;
157 }
158