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