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 "SortUtils.h"
10 
11 #include "LangInfo.h"
12 #include "URL.h"
13 #include "Util.h"
14 #include "utils/CharsetConverter.h"
15 #include "utils/StringUtils.h"
16 #include "utils/Variant.h"
17 
18 #include <algorithm>
19 #include <inttypes.h>
20 
ArrayToString(SortAttribute attributes,const CVariant & variant,const std::string & separator=" / ")21 std::string ArrayToString(SortAttribute attributes, const CVariant &variant, const std::string &separator = " / ")
22 {
23   std::vector<std::string> strArray;
24   if (variant.isArray())
25   {
26     for (CVariant::const_iterator_array it = variant.begin_array(); it != variant.end_array(); it++)
27     {
28       if (attributes & SortAttributeIgnoreArticle)
29         strArray.push_back(SortUtils::RemoveArticles(it->asString()));
30       else
31         strArray.push_back(it->asString());
32     }
33 
34     return StringUtils::Join(strArray, separator);
35   }
36   else if (variant.isString())
37   {
38     if (attributes & SortAttributeIgnoreArticle)
39       return SortUtils::RemoveArticles(variant.asString());
40     else
41       return variant.asString();
42   }
43 
44   return "";
45 }
46 
ByLabel(SortAttribute attributes,const SortItem & values)47 std::string ByLabel(SortAttribute attributes, const SortItem &values)
48 {
49   if (attributes & SortAttributeIgnoreArticle)
50     return SortUtils::RemoveArticles(values.at(FieldLabel).asString());
51 
52   return values.at(FieldLabel).asString();
53 }
54 
ByFile(SortAttribute attributes,const SortItem & values)55 std::string ByFile(SortAttribute attributes, const SortItem &values)
56 {
57   CURL url(values.at(FieldPath).asString());
58 
59   return StringUtils::Format("%s %" PRId64, url.GetFileNameWithoutPath().c_str(), values.at(FieldStartOffset).asInteger());
60 }
61 
ByPath(SortAttribute attributes,const SortItem & values)62 std::string ByPath(SortAttribute attributes, const SortItem &values)
63 {
64   return StringUtils::Format("%s %" PRId64, values.at(FieldPath).asString().c_str(), values.at(FieldStartOffset).asInteger());
65 }
66 
ByLastPlayed(SortAttribute attributes,const SortItem & values)67 std::string ByLastPlayed(SortAttribute attributes, const SortItem &values)
68 {
69   if (attributes & SortAttributeIgnoreLabel)
70     return values.at(FieldLastPlayed).asString();
71 
72   return StringUtils::Format("%s %s", values.at(FieldLastPlayed).asString().c_str(), ByLabel(attributes, values).c_str());
73 }
74 
ByPlaycount(SortAttribute attributes,const SortItem & values)75 std::string ByPlaycount(SortAttribute attributes, const SortItem &values)
76 {
77   return StringUtils::Format("%i %s", (int)values.at(FieldPlaycount).asInteger(), ByLabel(attributes, values).c_str());
78 }
79 
ByDate(SortAttribute attributes,const SortItem & values)80 std::string ByDate(SortAttribute attributes, const SortItem &values)
81 {
82   return values.at(FieldDate).asString() + " " + ByLabel(attributes, values);
83 }
84 
ByDateAdded(SortAttribute attributes,const SortItem & values)85 std::string ByDateAdded(SortAttribute attributes, const SortItem &values)
86 {
87   return StringUtils::Format("%s %d", values.at(FieldDateAdded).asString().c_str(), (int)values.at(FieldId).asInteger());
88 }
89 
BySize(SortAttribute attributes,const SortItem & values)90 std::string BySize(SortAttribute attributes, const SortItem &values)
91 {
92   return StringUtils::Format("%" PRId64, values.at(FieldSize).asInteger());
93 }
94 
ByDriveType(SortAttribute attributes,const SortItem & values)95 std::string ByDriveType(SortAttribute attributes, const SortItem &values)
96 {
97   return StringUtils::Format("%d %s", (int)values.at(FieldDriveType).asInteger(), ByLabel(attributes, values).c_str());
98 }
99 
ByTitle(SortAttribute attributes,const SortItem & values)100 std::string ByTitle(SortAttribute attributes, const SortItem &values)
101 {
102   if (attributes & SortAttributeIgnoreArticle)
103     return SortUtils::RemoveArticles(values.at(FieldTitle).asString());
104 
105   return values.at(FieldTitle).asString();
106 }
107 
ByAlbum(SortAttribute attributes,const SortItem & values)108 std::string ByAlbum(SortAttribute attributes, const SortItem &values)
109 {
110   std::string album = values.at(FieldAlbum).asString();
111   if (attributes & SortAttributeIgnoreArticle)
112     album = SortUtils::RemoveArticles(album);
113 
114   std::string label = StringUtils::Format("%s %s", album.c_str(), ArrayToString(attributes, values.at(FieldArtist)).c_str());
115 
116   const CVariant &track = values.at(FieldTrackNumber);
117   if (!track.isNull())
118     label += StringUtils::Format(" %i", (int)track.asInteger());
119 
120   return label;
121 }
122 
ByAlbumType(SortAttribute attributes,const SortItem & values)123 std::string ByAlbumType(SortAttribute attributes, const SortItem &values)
124 {
125   return values.at(FieldAlbumType).asString() + " " + ByLabel(attributes, values);
126 }
127 
ByArtist(SortAttribute attributes,const SortItem & values)128 std::string ByArtist(SortAttribute attributes, const SortItem &values)
129 {
130   std::string label;
131   if (attributes & SortAttributeUseArtistSortName)
132   {
133     const CVariant &artistsort = values.at(FieldArtistSort);
134     if (!artistsort.isNull())
135       label = artistsort.asString();
136   }
137   if (label.empty())
138     label = ArrayToString(attributes, values.at(FieldArtist));
139 
140   const CVariant &album = values.at(FieldAlbum);
141   if (!album.isNull())
142     label += " " + SortUtils::RemoveArticles(album.asString());
143 
144   const CVariant &track = values.at(FieldTrackNumber);
145   if (!track.isNull())
146     label += StringUtils::Format(" %i", (int)track.asInteger());
147 
148   return label;
149 }
150 
ByArtistThenYear(SortAttribute attributes,const SortItem & values)151 std::string ByArtistThenYear(SortAttribute attributes, const SortItem &values)
152 {
153   std::string label;
154   if (attributes & SortAttributeUseArtistSortName)
155   {
156     const CVariant &artistsort = values.at(FieldArtistSort);
157     if (!artistsort.isNull())
158       label = artistsort.asString();
159   }
160   if (label.empty())
161     label = ArrayToString(attributes, values.at(FieldArtist));
162 
163   const CVariant &year = values.at(FieldYear);
164   if (!year.isNull())
165     label += StringUtils::Format(" %i", static_cast<int>(year.asInteger()));
166 
167   const CVariant &album = values.at(FieldAlbum);
168   if (!album.isNull())
169     label += " " + SortUtils::RemoveArticles(album.asString());
170 
171   const CVariant &track = values.at(FieldTrackNumber);
172   if (!track.isNull())
173     label += StringUtils::Format(" %i", (int)track.asInteger());
174 
175   return label;
176 }
177 
ByTrackNumber(SortAttribute attributes,const SortItem & values)178 std::string ByTrackNumber(SortAttribute attributes, const SortItem &values)
179 {
180   return StringUtils::Format("%i", (int)values.at(FieldTrackNumber).asInteger());
181 }
182 
ByTotalDiscs(SortAttribute attributes,const SortItem & values)183 std::string ByTotalDiscs(SortAttribute attributes, const SortItem& values)
184 {
185   return StringUtils::Format("%d %s", static_cast<int>(values.at(FieldTotalDiscs).asInteger()),
186                              ByLabel(attributes, values));
187 }
ByTime(SortAttribute attributes,const SortItem & values)188 std::string ByTime(SortAttribute attributes, const SortItem &values)
189 {
190   std::string label;
191   const CVariant &time = values.at(FieldTime);
192   if (time.isInteger())
193     label = StringUtils::Format("%i", (int)time.asInteger());
194   else
195     label = StringUtils::Format("%s", time.asString().c_str());
196   return label;
197 }
198 
ByProgramCount(SortAttribute attributes,const SortItem & values)199 std::string ByProgramCount(SortAttribute attributes, const SortItem &values)
200 {
201   return StringUtils::Format("%i", (int)values.at(FieldProgramCount).asInteger());
202 }
203 
ByPlaylistOrder(SortAttribute attributes,const SortItem & values)204 std::string ByPlaylistOrder(SortAttribute attributes, const SortItem &values)
205 {
206   //! @todo Playlist order is hacked into program count variable (not nice, but ok until 2.0)
207   return ByProgramCount(attributes, values);
208 }
209 
ByGenre(SortAttribute attributes,const SortItem & values)210 std::string ByGenre(SortAttribute attributes, const SortItem &values)
211 {
212   return ArrayToString(attributes, values.at(FieldGenre));
213 }
214 
ByCountry(SortAttribute attributes,const SortItem & values)215 std::string ByCountry(SortAttribute attributes, const SortItem &values)
216 {
217   return ArrayToString(attributes, values.at(FieldCountry));
218 }
219 
ByYear(SortAttribute attributes,const SortItem & values)220 std::string ByYear(SortAttribute attributes, const SortItem &values)
221 {
222   std::string label;
223   const CVariant &airDate = values.at(FieldAirDate);
224   if (!airDate.isNull() && !airDate.asString().empty())
225     label = airDate.asString() + " ";
226 
227   label += StringUtils::Format("%i", (int)values.at(FieldYear).asInteger());
228 
229   const CVariant &album = values.at(FieldAlbum);
230   if (!album.isNull())
231     label += " " + SortUtils::RemoveArticles(album.asString());
232 
233   const CVariant &track = values.at(FieldTrackNumber);
234   if (!track.isNull())
235     label += StringUtils::Format(" %i", (int)track.asInteger());
236 
237   label += " " + ByLabel(attributes, values);
238 
239   return label;
240 }
241 
ByOrigDate(SortAttribute attributes,const SortItem & values)242 std::string ByOrigDate(SortAttribute attributes, const SortItem& values)
243 {
244   std::string label;
245   label = values.at(FieldOrigDate).asString();
246 
247   const CVariant &album = values.at(FieldAlbum);
248   if (!album.isNull())
249     label += " " + SortUtils::RemoveArticles(album.asString());
250 
251   const CVariant &track = values.at(FieldTrackNumber);
252   if (!track.isNull())
253     label += StringUtils::Format(" %i", static_cast<int>(track.asInteger()));
254 
255   label += " " + ByLabel(attributes, values);
256 
257   return label;
258 }
259 
BySortTitle(SortAttribute attributes,const SortItem & values)260 std::string BySortTitle(SortAttribute attributes, const SortItem &values)
261 {
262   std::string title = values.at(FieldSortTitle).asString();
263   if (title.empty())
264     title = values.at(FieldTitle).asString();
265 
266   if (attributes & SortAttributeIgnoreArticle)
267     title = SortUtils::RemoveArticles(title);
268 
269   return title;
270 }
271 
ByRating(SortAttribute attributes,const SortItem & values)272 std::string ByRating(SortAttribute attributes, const SortItem &values)
273 {
274   return StringUtils::Format("%f %s", values.at(FieldRating).asFloat(), ByLabel(attributes, values).c_str());
275 }
276 
ByUserRating(SortAttribute attributes,const SortItem & values)277 std::string ByUserRating(SortAttribute attributes, const SortItem &values)
278 {
279   return StringUtils::Format("%d %s", static_cast<int>(values.at(FieldUserRating).asInteger()), ByLabel(attributes, values).c_str());
280 }
281 
ByVotes(SortAttribute attributes,const SortItem & values)282 std::string ByVotes(SortAttribute attributes, const SortItem &values)
283 {
284   return StringUtils::Format("%d %s", (int)values.at(FieldVotes).asInteger(), ByLabel(attributes, values).c_str());
285 }
286 
ByTop250(SortAttribute attributes,const SortItem & values)287 std::string ByTop250(SortAttribute attributes, const SortItem &values)
288 {
289   return StringUtils::Format("%d %s", (int)values.at(FieldTop250).asInteger(), ByLabel(attributes, values).c_str());
290 }
291 
ByMPAA(SortAttribute attributes,const SortItem & values)292 std::string ByMPAA(SortAttribute attributes, const SortItem &values)
293 {
294   return values.at(FieldMPAA).asString() + " " + ByLabel(attributes, values);
295 }
296 
ByStudio(SortAttribute attributes,const SortItem & values)297 std::string ByStudio(SortAttribute attributes, const SortItem &values)
298 {
299   return ArrayToString(attributes, values.at(FieldStudio));
300 }
301 
ByEpisodeNumber(SortAttribute attributes,const SortItem & values)302 std::string ByEpisodeNumber(SortAttribute attributes, const SortItem &values)
303 {
304   // we calculate an offset number based on the episode's
305   // sort season and episode values. in addition
306   // we include specials 'episode' numbers to get proper
307   // sorting of multiple specials in a row. each
308   // of these are given their particular ranges to semi-ensure uniqueness.
309   // theoretical problem: if a show has > 2^15 specials and two of these are placed
310   // after each other they will sort backwards. if a show has > 2^32-1 seasons
311   // or if a season has > 2^16-1 episodes strange things will happen (overflow)
312   uint64_t num;
313   const CVariant &episodeSpecial = values.at(FieldEpisodeNumberSpecialSort);
314   const CVariant &seasonSpecial = values.at(FieldSeasonSpecialSort);
315   if (!episodeSpecial.isNull() && !seasonSpecial.isNull() &&
316      (episodeSpecial.asInteger() > 0 || seasonSpecial.asInteger() > 0))
317     num = ((uint64_t)seasonSpecial.asInteger() << 32) + (episodeSpecial.asInteger() << 16) - ((2 << 15) - values.at(FieldEpisodeNumber).asInteger());
318   else
319     num = ((uint64_t)values.at(FieldSeason).asInteger() << 32) + (values.at(FieldEpisodeNumber).asInteger() << 16);
320 
321   std::string title;
322   if (values.find(FieldMediaType) != values.end() && values.at(FieldMediaType).asString() == MediaTypeMovie)
323     title = BySortTitle(attributes, values);
324   if (title.empty())
325     title = ByLabel(attributes, values);
326 
327   return StringUtils::Format("%" PRIu64" %s", num, title.c_str());
328 }
329 
BySeason(SortAttribute attributes,const SortItem & values)330 std::string BySeason(SortAttribute attributes, const SortItem &values)
331 {
332   int season = (int)values.at(FieldSeason).asInteger();
333   const CVariant &specialSeason = values.at(FieldSeasonSpecialSort);
334   if (!specialSeason.isNull())
335     season = (int)specialSeason.asInteger();
336 
337   return StringUtils::Format("%i %s", season, ByLabel(attributes, values).c_str());
338 }
339 
ByNumberOfEpisodes(SortAttribute attributes,const SortItem & values)340 std::string ByNumberOfEpisodes(SortAttribute attributes, const SortItem &values)
341 {
342   return StringUtils::Format("%i %s", (int)values.at(FieldNumberOfEpisodes).asInteger(), ByLabel(attributes, values).c_str());
343 }
344 
ByNumberOfWatchedEpisodes(SortAttribute attributes,const SortItem & values)345 std::string ByNumberOfWatchedEpisodes(SortAttribute attributes, const SortItem &values)
346 {
347   return StringUtils::Format("%i %s", (int)values.at(FieldNumberOfWatchedEpisodes).asInteger(), ByLabel(attributes, values).c_str());
348 }
349 
ByTvShowStatus(SortAttribute attributes,const SortItem & values)350 std::string ByTvShowStatus(SortAttribute attributes, const SortItem &values)
351 {
352   return values.at(FieldTvShowStatus).asString() + " " + ByLabel(attributes, values);
353 }
354 
ByTvShowTitle(SortAttribute attributes,const SortItem & values)355 std::string ByTvShowTitle(SortAttribute attributes, const SortItem &values)
356 {
357   return values.at(FieldTvShowTitle).asString() + " " + ByLabel(attributes, values);
358 }
359 
ByProductionCode(SortAttribute attributes,const SortItem & values)360 std::string ByProductionCode(SortAttribute attributes, const SortItem &values)
361 {
362   return values.at(FieldProductionCode).asString();
363 }
364 
ByVideoResolution(SortAttribute attributes,const SortItem & values)365 std::string ByVideoResolution(SortAttribute attributes, const SortItem &values)
366 {
367   return StringUtils::Format("%i %s", (int)values.at(FieldVideoResolution).asInteger(), ByLabel(attributes, values).c_str());
368 }
369 
ByVideoCodec(SortAttribute attributes,const SortItem & values)370 std::string ByVideoCodec(SortAttribute attributes, const SortItem &values)
371 {
372   return StringUtils::Format("%s %s", values.at(FieldVideoCodec).asString().c_str(), ByLabel(attributes, values).c_str());
373 }
374 
ByVideoAspectRatio(SortAttribute attributes,const SortItem & values)375 std::string ByVideoAspectRatio(SortAttribute attributes, const SortItem &values)
376 {
377   return StringUtils::Format("%.3f %s", values.at(FieldVideoAspectRatio).asFloat(), ByLabel(attributes, values).c_str());
378 }
379 
ByAudioChannels(SortAttribute attributes,const SortItem & values)380 std::string ByAudioChannels(SortAttribute attributes, const SortItem &values)
381 {
382   return StringUtils::Format("%i %s", (int)values.at(FieldAudioChannels).asInteger(), ByLabel(attributes, values).c_str());
383 }
384 
ByAudioCodec(SortAttribute attributes,const SortItem & values)385 std::string ByAudioCodec(SortAttribute attributes, const SortItem &values)
386 {
387   return StringUtils::Format("%s %s", values.at(FieldAudioCodec).asString().c_str(), ByLabel(attributes, values).c_str());
388 }
389 
ByAudioLanguage(SortAttribute attributes,const SortItem & values)390 std::string ByAudioLanguage(SortAttribute attributes, const SortItem &values)
391 {
392   return StringUtils::Format("%s %s", values.at(FieldAudioLanguage).asString().c_str(), ByLabel(attributes, values).c_str());
393 }
394 
BySubtitleLanguage(SortAttribute attributes,const SortItem & values)395 std::string BySubtitleLanguage(SortAttribute attributes, const SortItem &values)
396 {
397   return StringUtils::Format("%s %s", values.at(FieldSubtitleLanguage).asString().c_str(), ByLabel(attributes, values).c_str());
398 }
399 
ByBitrate(SortAttribute attributes,const SortItem & values)400 std::string ByBitrate(SortAttribute attributes, const SortItem &values)
401 {
402   return StringUtils::Format("%" PRId64, values.at(FieldBitrate).asInteger());
403 }
404 
ByListeners(SortAttribute attributes,const SortItem & values)405 std::string ByListeners(SortAttribute attributes, const SortItem &values)
406 {
407   return StringUtils::Format("%" PRId64, values.at(FieldListeners).asInteger());
408 }
409 
ByRandom(SortAttribute attributes,const SortItem & values)410 std::string ByRandom(SortAttribute attributes, const SortItem &values)
411 {
412   return StringUtils::Format("%i", CUtil::GetRandomNumber());
413 }
414 
ByChannel(SortAttribute attributes,const SortItem & values)415 std::string ByChannel(SortAttribute attributes, const SortItem &values)
416 {
417   return values.at(FieldChannelName).asString();
418 }
419 
ByChannelNumber(SortAttribute attributes,const SortItem & values)420 std::string ByChannelNumber(SortAttribute attributes, const SortItem &values)
421 {
422   return values.at(FieldChannelNumber).asString();
423 }
424 
ByClientChannelOrder(SortAttribute attributes,const SortItem & values)425 std::string ByClientChannelOrder(SortAttribute attributes, const SortItem& values)
426 {
427   return values.at(FieldClientChannelOrder).asString();
428 }
429 
ByDateTaken(SortAttribute attributes,const SortItem & values)430 std::string ByDateTaken(SortAttribute attributes, const SortItem &values)
431 {
432   return values.at(FieldDateTaken).asString();
433 }
434 
ByRelevance(SortAttribute attributes,const SortItem & values)435 std::string ByRelevance(SortAttribute attributes, const SortItem &values)
436 {
437   return StringUtils::Format("%i", (int)values.at(FieldRelevance).asInteger());
438 }
439 
ByInstallDate(SortAttribute attributes,const SortItem & values)440 std::string ByInstallDate(SortAttribute attributes, const SortItem &values)
441 {
442   return values.at(FieldInstallDate).asString();
443 }
444 
ByLastUpdated(SortAttribute attributes,const SortItem & values)445 std::string ByLastUpdated(SortAttribute attributes, const SortItem &values)
446 {
447   return values.at(FieldLastUpdated).asString();
448 }
449 
ByLastUsed(SortAttribute attributes,const SortItem & values)450 std::string ByLastUsed(SortAttribute attributes, const SortItem &values)
451 {
452   return values.at(FieldLastUsed).asString();
453 }
454 
ByBPM(SortAttribute attributes,const SortItem & values)455 std::string ByBPM(SortAttribute attributes, const SortItem& values)
456 {
457   return StringUtils::Format("%d %s", static_cast<int>(values.at(FieldBPM).asInteger()),
458                              ByLabel(attributes, values));
459 }
460 
preliminarySort(const SortItem & left,const SortItem & right,bool handleFolder,bool & result,std::wstring & labelLeft,std::wstring & labelRight)461 bool preliminarySort(const SortItem &left, const SortItem &right, bool handleFolder, bool &result, std::wstring &labelLeft, std::wstring &labelRight)
462 {
463   // make sure both items have the necessary data to do the sorting
464   SortItem::const_iterator itLeftSort, itRightSort;
465   if ((itLeftSort = left.find(FieldSort)) == left.end())
466   {
467     result = false;
468     return true;
469   }
470   if ((itRightSort = right.find(FieldSort)) == right.end())
471   {
472     result = true;
473     return true;
474   }
475 
476   // look at special sorting behaviour
477   SortItem::const_iterator itLeft, itRight;
478   SortSpecial leftSortSpecial = SortSpecialNone;
479   SortSpecial rightSortSpecial = SortSpecialNone;
480   if ((itLeft = left.find(FieldSortSpecial)) != left.end() && itLeft->second.asInteger() <= (int64_t)SortSpecialOnBottom)
481     leftSortSpecial = (SortSpecial)itLeft->second.asInteger();
482   if ((itRight = right.find(FieldSortSpecial)) != right.end() && itRight->second.asInteger() <= (int64_t)SortSpecialOnBottom)
483     rightSortSpecial = (SortSpecial)itRight->second.asInteger();
484 
485   // one has a special sort
486   if (leftSortSpecial != rightSortSpecial)
487   {
488     // left should be sorted on top
489     // or right should be sorted on bottom
490     // => left is sorted above right
491     if (leftSortSpecial == SortSpecialOnTop ||
492         rightSortSpecial == SortSpecialOnBottom)
493     {
494       result = true;
495       return true;
496     }
497 
498     // otherwise right is sorted above left
499     result = false;
500     return true;
501   }
502   // both have either sort on top or sort on bottom -> leave as-is
503   else if (leftSortSpecial != SortSpecialNone && leftSortSpecial == rightSortSpecial)
504   {
505     result = false;
506     return true;
507   }
508 
509   if (handleFolder)
510   {
511     itLeft = left.find(FieldFolder);
512     itRight = right.find(FieldFolder);
513     if (itLeft != left.end() && itRight != right.end() &&
514         itLeft->second.asBoolean() != itRight->second.asBoolean())
515     {
516       result = itLeft->second.asBoolean();
517       return true;
518     }
519   }
520 
521   labelLeft = itLeftSort->second.asWideString();
522   labelRight = itRightSort->second.asWideString();
523 
524   return false;
525 }
526 
SorterAscending(const SortItem & left,const SortItem & right)527 bool SorterAscending(const SortItem &left, const SortItem &right)
528 {
529   bool result;
530   std::wstring labelLeft, labelRight;
531   if (preliminarySort(left, right, true, result, labelLeft, labelRight))
532     return result;
533 
534   return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) < 0;
535 }
536 
SorterDescending(const SortItem & left,const SortItem & right)537 bool SorterDescending(const SortItem &left, const SortItem &right)
538 {
539   bool result;
540   std::wstring labelLeft, labelRight;
541   if (preliminarySort(left, right, true, result, labelLeft, labelRight))
542     return result;
543 
544   return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) > 0;
545 }
546 
SorterIgnoreFoldersAscending(const SortItem & left,const SortItem & right)547 bool SorterIgnoreFoldersAscending(const SortItem &left, const SortItem &right)
548 {
549   bool result;
550   std::wstring labelLeft, labelRight;
551   if (preliminarySort(left, right, false, result, labelLeft, labelRight))
552     return result;
553 
554   return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) < 0;
555 }
556 
SorterIgnoreFoldersDescending(const SortItem & left,const SortItem & right)557 bool SorterIgnoreFoldersDescending(const SortItem &left, const SortItem &right)
558 {
559   bool result;
560   std::wstring labelLeft, labelRight;
561   if (preliminarySort(left, right, false, result, labelLeft, labelRight))
562     return result;
563 
564   return StringUtils::AlphaNumericCompare(labelLeft.c_str(), labelRight.c_str()) > 0;
565 }
566 
SorterIndirectAscending(const SortItemPtr & left,const SortItemPtr & right)567 bool SorterIndirectAscending(const SortItemPtr &left, const SortItemPtr &right)
568 {
569   return SorterAscending(*left, *right);
570 }
571 
SorterIndirectDescending(const SortItemPtr & left,const SortItemPtr & right)572 bool SorterIndirectDescending(const SortItemPtr &left, const SortItemPtr &right)
573 {
574   return SorterDescending(*left, *right);
575 }
576 
SorterIndirectIgnoreFoldersAscending(const SortItemPtr & left,const SortItemPtr & right)577 bool SorterIndirectIgnoreFoldersAscending(const SortItemPtr &left, const SortItemPtr &right)
578 {
579   return SorterIgnoreFoldersAscending(*left, *right);
580 }
581 
SorterIndirectIgnoreFoldersDescending(const SortItemPtr & left,const SortItemPtr & right)582 bool SorterIndirectIgnoreFoldersDescending(const SortItemPtr &left, const SortItemPtr &right)
583 {
584   return SorterIgnoreFoldersDescending(*left, *right);
585 }
586 
587 // clang-format off
fillPreparators()588 std::map<SortBy, SortUtils::SortPreparator> fillPreparators()
589 {
590   std::map<SortBy, SortUtils::SortPreparator> preparators;
591 
592   preparators[SortByNone]                     = NULL;
593   preparators[SortByLabel]                    = ByLabel;
594   preparators[SortByDate]                     = ByDate;
595   preparators[SortBySize]                     = BySize;
596   preparators[SortByFile]                     = ByFile;
597   preparators[SortByPath]                     = ByPath;
598   preparators[SortByDriveType]                = ByDriveType;
599   preparators[SortByTitle]                    = ByTitle;
600   preparators[SortByTrackNumber]              = ByTrackNumber;
601   preparators[SortByTime]                     = ByTime;
602   preparators[SortByArtist]                   = ByArtist;
603   preparators[SortByArtistThenYear]           = ByArtistThenYear;
604   preparators[SortByAlbum]                    = ByAlbum;
605   preparators[SortByAlbumType]                = ByAlbumType;
606   preparators[SortByGenre]                    = ByGenre;
607   preparators[SortByCountry]                  = ByCountry;
608   preparators[SortByYear]                     = ByYear;
609   preparators[SortByRating]                   = ByRating;
610   preparators[SortByUserRating]               = ByUserRating;
611   preparators[SortByVotes]                    = ByVotes;
612   preparators[SortByTop250]                   = ByTop250;
613   preparators[SortByProgramCount]             = ByProgramCount;
614   preparators[SortByPlaylistOrder]            = ByPlaylistOrder;
615   preparators[SortByEpisodeNumber]            = ByEpisodeNumber;
616   preparators[SortBySeason]                   = BySeason;
617   preparators[SortByNumberOfEpisodes]         = ByNumberOfEpisodes;
618   preparators[SortByNumberOfWatchedEpisodes]  = ByNumberOfWatchedEpisodes;
619   preparators[SortByTvShowStatus]             = ByTvShowStatus;
620   preparators[SortByTvShowTitle]              = ByTvShowTitle;
621   preparators[SortBySortTitle]                = BySortTitle;
622   preparators[SortByProductionCode]           = ByProductionCode;
623   preparators[SortByMPAA]                     = ByMPAA;
624   preparators[SortByVideoResolution]          = ByVideoResolution;
625   preparators[SortByVideoCodec]               = ByVideoCodec;
626   preparators[SortByVideoAspectRatio]         = ByVideoAspectRatio;
627   preparators[SortByAudioChannels]            = ByAudioChannels;
628   preparators[SortByAudioCodec]               = ByAudioCodec;
629   preparators[SortByAudioLanguage]            = ByAudioLanguage;
630   preparators[SortBySubtitleLanguage]         = BySubtitleLanguage;
631   preparators[SortByStudio]                   = ByStudio;
632   preparators[SortByDateAdded]                = ByDateAdded;
633   preparators[SortByLastPlayed]               = ByLastPlayed;
634   preparators[SortByPlaycount]                = ByPlaycount;
635   preparators[SortByListeners]                = ByListeners;
636   preparators[SortByBitrate]                  = ByBitrate;
637   preparators[SortByRandom]                   = ByRandom;
638   preparators[SortByChannel]                  = ByChannel;
639   preparators[SortByChannelNumber]            = ByChannelNumber;
640   preparators[SortByClientChannelOrder]       = ByClientChannelOrder;
641   preparators[SortByDateTaken]                = ByDateTaken;
642   preparators[SortByRelevance]                = ByRelevance;
643   preparators[SortByInstallDate]              = ByInstallDate;
644   preparators[SortByLastUpdated]              = ByLastUpdated;
645   preparators[SortByLastUsed]                 = ByLastUsed;
646   preparators[SortByTotalDiscs]               = ByTotalDiscs;
647   preparators[SortByOrigDate]                 = ByOrigDate;
648   preparators[SortByBPM]                      = ByBPM;
649 
650   return preparators;
651 }
652 // clang-format on
653 
fillSortingFields()654 std::map<SortBy, Fields> fillSortingFields()
655 {
656   std::map<SortBy, Fields> sortingFields;
657 
658   sortingFields.insert(std::pair<SortBy, Fields>(SortByNone, Fields()));
659 
660   sortingFields[SortByLabel].insert(FieldLabel);
661   sortingFields[SortByDate].insert(FieldDate);
662   sortingFields[SortBySize].insert(FieldSize);
663   sortingFields[SortByFile].insert(FieldPath);
664   sortingFields[SortByFile].insert(FieldStartOffset);
665   sortingFields[SortByPath].insert(FieldPath);
666   sortingFields[SortByPath].insert(FieldStartOffset);
667   sortingFields[SortByDriveType].insert(FieldDriveType);
668   sortingFields[SortByTitle].insert(FieldTitle);
669   sortingFields[SortByTrackNumber].insert(FieldTrackNumber);
670   sortingFields[SortByTime].insert(FieldTime);
671   sortingFields[SortByArtist].insert(FieldArtist);
672   sortingFields[SortByArtist].insert(FieldArtistSort);
673   sortingFields[SortByArtist].insert(FieldYear);
674   sortingFields[SortByArtist].insert(FieldAlbum);
675   sortingFields[SortByArtist].insert(FieldTrackNumber);
676   sortingFields[SortByArtistThenYear].insert(FieldArtist);
677   sortingFields[SortByArtistThenYear].insert(FieldArtistSort);
678   sortingFields[SortByArtistThenYear].insert(FieldYear);
679   sortingFields[SortByArtistThenYear].insert(FieldOrigDate);
680   sortingFields[SortByArtistThenYear].insert(FieldAlbum);
681   sortingFields[SortByArtistThenYear].insert(FieldTrackNumber);
682   sortingFields[SortByAlbum].insert(FieldAlbum);
683   sortingFields[SortByAlbum].insert(FieldArtist);
684   sortingFields[SortByAlbum].insert(FieldArtistSort);
685   sortingFields[SortByAlbum].insert(FieldTrackNumber);
686   sortingFields[SortByAlbumType].insert(FieldAlbumType);
687   sortingFields[SortByGenre].insert(FieldGenre);
688   sortingFields[SortByCountry].insert(FieldCountry);
689   sortingFields[SortByYear].insert(FieldYear);
690   sortingFields[SortByYear].insert(FieldAirDate);
691   sortingFields[SortByYear].insert(FieldAlbum);
692   sortingFields[SortByYear].insert(FieldTrackNumber);
693   sortingFields[SortByYear].insert(FieldOrigDate);
694   sortingFields[SortByRating].insert(FieldRating);
695   sortingFields[SortByUserRating].insert(FieldUserRating);
696   sortingFields[SortByVotes].insert(FieldVotes);
697   sortingFields[SortByTop250].insert(FieldTop250);
698   sortingFields[SortByProgramCount].insert(FieldProgramCount);
699   sortingFields[SortByPlaylistOrder].insert(FieldProgramCount);
700   sortingFields[SortByEpisodeNumber].insert(FieldEpisodeNumber);
701   sortingFields[SortByEpisodeNumber].insert(FieldSeason);
702   sortingFields[SortByEpisodeNumber].insert(FieldEpisodeNumberSpecialSort);
703   sortingFields[SortByEpisodeNumber].insert(FieldSeasonSpecialSort);
704   sortingFields[SortByEpisodeNumber].insert(FieldTitle);
705   sortingFields[SortByEpisodeNumber].insert(FieldSortTitle);
706   sortingFields[SortBySeason].insert(FieldSeason);
707   sortingFields[SortBySeason].insert(FieldSeasonSpecialSort);
708   sortingFields[SortByNumberOfEpisodes].insert(FieldNumberOfEpisodes);
709   sortingFields[SortByNumberOfWatchedEpisodes].insert(FieldNumberOfWatchedEpisodes);
710   sortingFields[SortByTvShowStatus].insert(FieldTvShowStatus);
711   sortingFields[SortByTvShowTitle].insert(FieldTvShowTitle);
712   sortingFields[SortBySortTitle].insert(FieldSortTitle);
713   sortingFields[SortBySortTitle].insert(FieldTitle);
714   sortingFields[SortByProductionCode].insert(FieldProductionCode);
715   sortingFields[SortByMPAA].insert(FieldMPAA);
716   sortingFields[SortByVideoResolution].insert(FieldVideoResolution);
717   sortingFields[SortByVideoCodec].insert(FieldVideoCodec);
718   sortingFields[SortByVideoAspectRatio].insert(FieldVideoAspectRatio);
719   sortingFields[SortByAudioChannels].insert(FieldAudioChannels);
720   sortingFields[SortByAudioCodec].insert(FieldAudioCodec);
721   sortingFields[SortByAudioLanguage].insert(FieldAudioLanguage);
722   sortingFields[SortBySubtitleLanguage].insert(FieldSubtitleLanguage);
723   sortingFields[SortByStudio].insert(FieldStudio);
724   sortingFields[SortByDateAdded].insert(FieldDateAdded);
725   sortingFields[SortByDateAdded].insert(FieldId);
726   sortingFields[SortByLastPlayed].insert(FieldLastPlayed);
727   sortingFields[SortByPlaycount].insert(FieldPlaycount);
728   sortingFields[SortByListeners].insert(FieldListeners);
729   sortingFields[SortByBitrate].insert(FieldBitrate);
730   sortingFields[SortByChannel].insert(FieldChannelName);
731   sortingFields[SortByChannelNumber].insert(FieldChannelNumber);
732   sortingFields[SortByClientChannelOrder].insert(FieldClientChannelOrder);
733   sortingFields[SortByDateTaken].insert(FieldDateTaken);
734   sortingFields[SortByRelevance].insert(FieldRelevance);
735   sortingFields[SortByInstallDate].insert(FieldInstallDate);
736   sortingFields[SortByLastUpdated].insert(FieldLastUpdated);
737   sortingFields[SortByLastUsed].insert(FieldLastUsed);
738   sortingFields[SortByTotalDiscs].insert(FieldTotalDiscs);
739   sortingFields[SortByOrigDate].insert(FieldOrigDate);
740   sortingFields[SortByOrigDate].insert(FieldAlbum);
741   sortingFields[SortByOrigDate].insert(FieldTrackNumber);
742   sortingFields[SortByBPM].insert(FieldBPM);
743   sortingFields.insert(std::pair<SortBy, Fields>(SortByRandom, Fields()));
744 
745   return sortingFields;
746 }
747 
748 std::map<SortBy, SortUtils::SortPreparator> SortUtils::m_preparators = fillPreparators();
749 std::map<SortBy, Fields> SortUtils::m_sortingFields = fillSortingFields();
750 
GetFieldsForSQLSort(const MediaType & mediaType,SortBy sortMethod,FieldList & fields)751 void SortUtils::GetFieldsForSQLSort(const MediaType& mediaType,
752                                     SortBy sortMethod,
753                                     FieldList& fields)
754 {
755   fields.clear();
756   if (mediaType == MediaTypeNone)
757     return;
758 
759   if (mediaType == MediaTypeAlbum)
760   {
761     if (sortMethod == SortByLabel || sortMethod == SortByAlbum || sortMethod == SortByTitle)
762     {
763       fields.emplace_back(FieldAlbum);
764       fields.emplace_back(FieldArtist);
765     }
766     else if (sortMethod == SortByAlbumType)
767     {
768       fields.emplace_back(FieldAlbumType);
769       fields.emplace_back(FieldAlbum);
770       fields.emplace_back(FieldArtist);
771     }
772     else if (sortMethod == SortByArtist)
773     {
774       fields.emplace_back(FieldArtist);
775       fields.emplace_back(FieldAlbum);
776     }
777     else if (sortMethod == SortByArtistThenYear)
778     {
779       fields.emplace_back(FieldArtist);
780       fields.emplace_back(FieldYear);
781       fields.emplace_back(FieldAlbum);
782     }
783     else if (sortMethod == SortByYear)
784     {
785       fields.emplace_back(FieldYear);
786       fields.emplace_back(FieldAlbum);
787     }
788     else if (sortMethod == SortByGenre)
789     {
790       fields.emplace_back(FieldGenre);
791       fields.emplace_back(FieldAlbum);
792     }
793     else if (sortMethod == SortByDateAdded)
794       fields.emplace_back(FieldDateAdded);
795     else if (sortMethod == SortByPlaycount)
796     {
797       fields.emplace_back(FieldPlaycount);
798       fields.emplace_back(FieldAlbum);
799     }
800     else if (sortMethod == SortByLastPlayed)
801     {
802       fields.emplace_back(FieldLastPlayed);
803       fields.emplace_back(FieldAlbum);
804     }
805     else if (sortMethod == SortByRating)
806     {
807       fields.emplace_back(FieldRating);
808       fields.emplace_back(FieldAlbum);
809     }
810     else if (sortMethod == SortByVotes)
811     {
812       fields.emplace_back(FieldVotes);
813       fields.emplace_back(FieldAlbum);
814     }
815     else if (sortMethod == SortByUserRating)
816     {
817       fields.emplace_back(FieldUserRating);
818       fields.emplace_back(FieldAlbum);
819     }
820     else if (sortMethod == SortByTotalDiscs)
821     {
822       fields.emplace_back(FieldTotalDiscs);
823       fields.emplace_back(FieldAlbum);
824     }
825     else if (sortMethod == SortByOrigDate)
826     {
827       fields.emplace_back(FieldOrigDate);
828       fields.emplace_back(FieldAlbum);
829     }
830   }
831   else if (mediaType == MediaTypeSong)
832   {
833     if (sortMethod == SortByLabel || sortMethod == SortByTrackNumber)
834       fields.emplace_back(FieldTrackNumber);
835     else if (sortMethod == SortByTitle)
836       fields.emplace_back(FieldTitle);
837     else if (sortMethod == SortByAlbum)
838     {
839       fields.emplace_back(FieldAlbum);
840       fields.emplace_back(FieldAlbumArtist);
841       fields.emplace_back(FieldTrackNumber);
842     }
843     else if (sortMethod == SortByArtist)
844     {
845       fields.emplace_back(FieldArtist);
846       fields.emplace_back(FieldAlbum);
847       fields.emplace_back(FieldTrackNumber);
848     }
849     else if (sortMethod == SortByArtistThenYear)
850     {
851       fields.emplace_back(FieldArtist);
852       fields.emplace_back(FieldYear);
853       fields.emplace_back(FieldAlbum);
854       fields.emplace_back(FieldTrackNumber);
855     }
856     else if (sortMethod == SortByYear)
857     {
858       fields.emplace_back(FieldYear);
859       fields.emplace_back(FieldAlbum);
860       fields.emplace_back(FieldTrackNumber);
861     }
862     else if (sortMethod == SortByGenre)
863     {
864       fields.emplace_back(FieldGenre);
865       fields.emplace_back(FieldAlbum);
866     }
867     else if (sortMethod == SortByDateAdded)
868       fields.emplace_back(FieldDateAdded);
869     else if (sortMethod == SortByPlaycount)
870     {
871       fields.emplace_back(FieldPlaycount);
872       fields.emplace_back(FieldTrackNumber);
873     }
874     else if (sortMethod == SortByLastPlayed)
875     {
876       fields.emplace_back(FieldLastPlayed);
877       fields.emplace_back(FieldTrackNumber);
878     }
879     else if (sortMethod == SortByRating)
880     {
881       fields.emplace_back(FieldRating);
882       fields.emplace_back(FieldTrackNumber);
883     }
884     else if (sortMethod == SortByVotes)
885     {
886       fields.emplace_back(FieldVotes);
887       fields.emplace_back(FieldTrackNumber);
888     }
889     else if (sortMethod == SortByUserRating)
890     {
891       fields.emplace_back(FieldUserRating);
892       fields.emplace_back(FieldTrackNumber);
893     }
894     else if (sortMethod == SortByFile)
895     {
896       fields.emplace_back(FieldPath);
897       fields.emplace_back(FieldFilename);
898       fields.emplace_back(FieldStartOffset);
899     }
900     else if (sortMethod == SortByTime)
901       fields.emplace_back(FieldTime);
902     else if (sortMethod == SortByAlbumType)
903     {
904       fields.emplace_back(FieldAlbumType);
905       fields.emplace_back(FieldAlbum);
906       fields.emplace_back(FieldTrackNumber);
907     }
908     else if (sortMethod == SortByOrigDate)
909     {
910       fields.emplace_back(FieldOrigDate);
911       fields.emplace_back(FieldAlbum);
912       fields.emplace_back(FieldTrackNumber);
913     }
914     else if (sortMethod == SortByBPM)
915       fields.emplace_back(FieldBPM);
916   }
917   else if (mediaType == MediaTypeArtist)
918   {
919     if (sortMethod == SortByLabel || sortMethod == SortByTitle || sortMethod == SortByArtist)
920       fields.emplace_back(FieldArtist);
921     else if (sortMethod == SortByGenre)
922       fields.emplace_back(FieldGenre);
923     else if (sortMethod == SortByDateAdded)
924       fields.emplace_back(FieldDateAdded);
925   }
926 
927   // Add sort by id to define order when other fields same or sort none
928   fields.emplace_back(FieldId);
929   return;
930 }
931 
932 
Sort(SortBy sortBy,SortOrder sortOrder,SortAttribute attributes,DatabaseResults & items,int limitEnd,int limitStart)933 void SortUtils::Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute attributes, DatabaseResults& items, int limitEnd /* = -1 */, int limitStart /* = 0 */)
934 {
935   if (sortBy != SortByNone)
936   {
937     // get the matching SortPreparator
938     SortPreparator preparator = getPreparator(sortBy);
939     if (preparator != NULL)
940     {
941       Fields sortingFields = GetFieldsForSorting(sortBy);
942 
943       // Prepare the string used for sorting and store it under FieldSort
944       for (DatabaseResults::iterator item = items.begin(); item != items.end(); ++item)
945       {
946         // add all fields to the item that are required for sorting if they are currently missing
947         for (Fields::const_iterator field = sortingFields.begin(); field != sortingFields.end(); ++field)
948         {
949           if (item->find(*field) == item->end())
950             item->insert(std::pair<Field, CVariant>(*field, CVariant::ConstNullVariant));
951         }
952 
953         std::wstring sortLabel;
954         g_charsetConverter.utf8ToW(preparator(attributes, *item), sortLabel, false);
955         item->insert(std::pair<Field, CVariant>(FieldSort, CVariant(sortLabel)));
956       }
957 
958       // Do the sorting
959       std::stable_sort(items.begin(), items.end(), getSorter(sortOrder, attributes));
960     }
961   }
962 
963   if (limitStart > 0 && (size_t)limitStart < items.size())
964   {
965     items.erase(items.begin(), items.begin() + limitStart);
966     limitEnd -= limitStart;
967   }
968   if (limitEnd > 0 && (size_t)limitEnd < items.size())
969     items.erase(items.begin() + limitEnd, items.end());
970 }
971 
Sort(SortBy sortBy,SortOrder sortOrder,SortAttribute attributes,SortItems & items,int limitEnd,int limitStart)972 void SortUtils::Sort(SortBy sortBy, SortOrder sortOrder, SortAttribute attributes, SortItems& items, int limitEnd /* = -1 */, int limitStart /* = 0 */)
973 {
974   if (sortBy != SortByNone)
975   {
976     // get the matching SortPreparator
977     SortPreparator preparator = getPreparator(sortBy);
978     if (preparator != NULL)
979     {
980       Fields sortingFields = GetFieldsForSorting(sortBy);
981 
982       // Prepare the string used for sorting and store it under FieldSort
983       for (SortItems::iterator item = items.begin(); item != items.end(); ++item)
984       {
985         // add all fields to the item that are required for sorting if they are currently missing
986         for (Fields::const_iterator field = sortingFields.begin(); field != sortingFields.end(); ++field)
987         {
988           if ((*item)->find(*field) == (*item)->end())
989             (*item)->insert(std::pair<Field, CVariant>(*field, CVariant::ConstNullVariant));
990         }
991 
992         std::wstring sortLabel;
993         g_charsetConverter.utf8ToW(preparator(attributes, **item), sortLabel, false);
994         (*item)->insert(std::pair<Field, CVariant>(FieldSort, CVariant(sortLabel)));
995       }
996 
997       // Do the sorting
998       std::stable_sort(items.begin(), items.end(), getSorterIndirect(sortOrder, attributes));
999     }
1000   }
1001 
1002   if (limitStart > 0 && (size_t)limitStart < items.size())
1003   {
1004     items.erase(items.begin(), items.begin() + limitStart);
1005     limitEnd -= limitStart;
1006   }
1007   if (limitEnd > 0 && (size_t)limitEnd < items.size())
1008     items.erase(items.begin() + limitEnd, items.end());
1009 }
1010 
Sort(const SortDescription & sortDescription,DatabaseResults & items)1011 void SortUtils::Sort(const SortDescription &sortDescription, DatabaseResults& items)
1012 {
1013   Sort(sortDescription.sortBy, sortDescription.sortOrder, sortDescription.sortAttributes, items, sortDescription.limitEnd, sortDescription.limitStart);
1014 }
1015 
Sort(const SortDescription & sortDescription,SortItems & items)1016 void SortUtils::Sort(const SortDescription &sortDescription, SortItems& items)
1017 {
1018   Sort(sortDescription.sortBy, sortDescription.sortOrder, sortDescription.sortAttributes, items, sortDescription.limitEnd, sortDescription.limitStart);
1019 }
1020 
SortFromDataset(const SortDescription & sortDescription,const MediaType & mediaType,const std::unique_ptr<dbiplus::Dataset> & dataset,DatabaseResults & results)1021 bool SortUtils::SortFromDataset(const SortDescription &sortDescription, const MediaType &mediaType, const std::unique_ptr<dbiplus::Dataset> &dataset, DatabaseResults &results)
1022 {
1023   FieldList fields;
1024   if (!DatabaseUtils::GetSelectFields(SortUtils::GetFieldsForSorting(sortDescription.sortBy), mediaType, fields))
1025     fields.clear();
1026 
1027   if (!DatabaseUtils::GetDatabaseResults(mediaType, fields, dataset, results))
1028     return false;
1029 
1030   SortDescription sorting = sortDescription;
1031   if (sortDescription.sortBy == SortByNone)
1032   {
1033     sorting.limitStart = 0;
1034     sorting.limitEnd = -1;
1035   }
1036 
1037   Sort(sorting, results);
1038 
1039   return true;
1040 }
1041 
getPreparator(SortBy sortBy)1042 const SortUtils::SortPreparator& SortUtils::getPreparator(SortBy sortBy)
1043 {
1044   std::map<SortBy, SortPreparator>::const_iterator it = m_preparators.find(sortBy);
1045   if (it != m_preparators.end())
1046     return it->second;
1047 
1048   return m_preparators[SortByNone];
1049 }
1050 
getSorter(SortOrder sortOrder,SortAttribute attributes)1051 SortUtils::Sorter SortUtils::getSorter(SortOrder sortOrder, SortAttribute attributes)
1052 {
1053   if (attributes & SortAttributeIgnoreFolders)
1054     return sortOrder == SortOrderDescending ? SorterIgnoreFoldersDescending : SorterIgnoreFoldersAscending;
1055 
1056   return sortOrder == SortOrderDescending ? SorterDescending : SorterAscending;
1057 }
1058 
getSorterIndirect(SortOrder sortOrder,SortAttribute attributes)1059 SortUtils::SorterIndirect SortUtils::getSorterIndirect(SortOrder sortOrder, SortAttribute attributes)
1060 {
1061   if (attributes & SortAttributeIgnoreFolders)
1062     return sortOrder == SortOrderDescending ? SorterIndirectIgnoreFoldersDescending : SorterIndirectIgnoreFoldersAscending;
1063 
1064   return sortOrder == SortOrderDescending ? SorterIndirectDescending : SorterIndirectAscending;
1065 }
1066 
GetFieldsForSorting(SortBy sortBy)1067 const Fields& SortUtils::GetFieldsForSorting(SortBy sortBy)
1068 {
1069   std::map<SortBy, Fields>::const_iterator it = m_sortingFields.find(sortBy);
1070   if (it != m_sortingFields.end())
1071     return it->second;
1072 
1073   return m_sortingFields[SortByNone];
1074 }
1075 
RemoveArticles(const std::string & label)1076 std::string SortUtils::RemoveArticles(const std::string &label)
1077 {
1078   std::set<std::string> sortTokens = g_langInfo.GetSortTokens();
1079   for (std::set<std::string>::const_iterator token = sortTokens.begin(); token != sortTokens.end(); ++token)
1080   {
1081     if (token->size() < label.size() && StringUtils::StartsWithNoCase(label, *token))
1082       return label.substr(token->size());
1083   }
1084 
1085   return label;
1086 }
1087 
1088 typedef struct
1089 {
1090   SortBy        sort;
1091   SORT_METHOD   old;
1092   SortAttribute flags;
1093   int           label;
1094 } sort_map;
1095 
1096 // clang-format off
1097 const sort_map table[] = {
1098   { SortByLabel,                    SORT_METHOD_LABEL,                        SortAttributeNone,          551 },
1099   { SortByLabel,                    SORT_METHOD_LABEL_IGNORE_THE,             SortAttributeIgnoreArticle, 551 },
1100   { SortByLabel,                    SORT_METHOD_LABEL_IGNORE_FOLDERS,         SortAttributeIgnoreFolders, 551 },
1101   { SortByDate,                     SORT_METHOD_DATE,                         SortAttributeNone,          552 },
1102   { SortBySize,                     SORT_METHOD_SIZE,                         SortAttributeNone,          553 },
1103   { SortByBitrate,                  SORT_METHOD_BITRATE,                      SortAttributeNone,          623 },
1104   { SortByDriveType,                SORT_METHOD_DRIVE_TYPE,                   SortAttributeNone,          564 },
1105   { SortByTrackNumber,              SORT_METHOD_TRACKNUM,                     SortAttributeNone,          554 },
1106   { SortByEpisodeNumber,            SORT_METHOD_EPISODE,                      SortAttributeNone,          20359 },// 20360 "Episodes" used for SORT_METHOD_EPISODE for sorting tvshows by episode count
1107   { SortByTime,                     SORT_METHOD_DURATION,                     SortAttributeNone,          180 },
1108   { SortByTime,                     SORT_METHOD_VIDEO_RUNTIME,                SortAttributeNone,          180 },
1109   { SortByTitle,                    SORT_METHOD_TITLE,                        SortAttributeNone,          556 },
1110   { SortByTitle,                    SORT_METHOD_TITLE_IGNORE_THE,             SortAttributeIgnoreArticle, 556 },
1111   { SortByTitle,                    SORT_METHOD_VIDEO_TITLE,                  SortAttributeNone,          556 },
1112   { SortByArtist,                   SORT_METHOD_ARTIST,                       SortAttributeNone,          557 },
1113   { SortByArtistThenYear,           SORT_METHOD_ARTIST_AND_YEAR,              SortAttributeNone,          578 },
1114   { SortByArtist,                   SORT_METHOD_ARTIST_IGNORE_THE,            SortAttributeIgnoreArticle, 557 },
1115   { SortByAlbum,                    SORT_METHOD_ALBUM,                        SortAttributeNone,          558 },
1116   { SortByAlbum,                    SORT_METHOD_ALBUM_IGNORE_THE,             SortAttributeIgnoreArticle, 558 },
1117   { SortByGenre,                    SORT_METHOD_GENRE,                        SortAttributeNone,          515 },
1118   { SortByCountry,                  SORT_METHOD_COUNTRY,                      SortAttributeNone,          574 },
1119   { SortByDateAdded,                SORT_METHOD_DATEADDED,                    SortAttributeIgnoreFolders, 570 },
1120   { SortByFile,                     SORT_METHOD_FILE,                         SortAttributeIgnoreFolders, 561 },
1121   { SortByRating,                   SORT_METHOD_SONG_RATING,                  SortAttributeNone,          563 },
1122   { SortByRating,                   SORT_METHOD_VIDEO_RATING,                 SortAttributeIgnoreFolders, 563 },
1123   { SortByUserRating,               SORT_METHOD_SONG_USER_RATING,             SortAttributeIgnoreFolders, 38018 },
1124   { SortByUserRating,               SORT_METHOD_VIDEO_USER_RATING,            SortAttributeIgnoreFolders, 38018 },
1125   { SortBySortTitle,                SORT_METHOD_VIDEO_SORT_TITLE,             SortAttributeIgnoreFolders, 171 },
1126   { SortBySortTitle,                SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE,  (SortAttribute)(SortAttributeIgnoreFolders | SortAttributeIgnoreArticle), 171 },
1127   { SortByYear,                     SORT_METHOD_YEAR,                         SortAttributeIgnoreFolders, 562 },
1128   { SortByProductionCode,           SORT_METHOD_PRODUCTIONCODE,               SortAttributeNone,          20368 },
1129   { SortByProgramCount,             SORT_METHOD_PROGRAM_COUNT,                SortAttributeNone,          567 }, // label is "play count"
1130   { SortByPlaylistOrder,            SORT_METHOD_PLAYLIST_ORDER,               SortAttributeIgnoreFolders, 559 },
1131   { SortByMPAA,                     SORT_METHOD_MPAA_RATING,                  SortAttributeNone,          20074 },
1132   { SortByStudio,                   SORT_METHOD_STUDIO,                       SortAttributeNone,          572 },
1133   { SortByStudio,                   SORT_METHOD_STUDIO_IGNORE_THE,            SortAttributeIgnoreArticle, 572 },
1134   { SortByPath,                     SORT_METHOD_FULLPATH,                     SortAttributeNone,          573 },
1135   { SortByLastPlayed,               SORT_METHOD_LASTPLAYED,                   SortAttributeIgnoreFolders, 568 },
1136   { SortByPlaycount,                SORT_METHOD_PLAYCOUNT,                    SortAttributeIgnoreFolders, 567 },
1137   { SortByListeners,                SORT_METHOD_LISTENERS,                    SortAttributeNone,          20455 },
1138   { SortByChannel,                  SORT_METHOD_CHANNEL,                      SortAttributeNone,          19029 },
1139   { SortByChannel,                  SORT_METHOD_CHANNEL_NUMBER,               SortAttributeNone,          549 },
1140   { SortByChannel,                  SORT_METHOD_CLIENT_CHANNEL_ORDER,         SortAttributeNone,          19315 },
1141   { SortByDateTaken,                SORT_METHOD_DATE_TAKEN,                   SortAttributeIgnoreFolders, 577 },
1142   { SortByNone,                     SORT_METHOD_NONE,                         SortAttributeNone,          16018 },
1143   { SortByTotalDiscs,               SORT_METHOD_TOTAL_DISCS,                  SortAttributeNone,          38077 },
1144   { SortByOrigDate,                 SORT_METHOD_ORIG_DATE,                    SortAttributeNone,          38079 },
1145   { SortByBPM,                      SORT_METHOD_BPM,                          SortAttributeNone,          38080 },
1146 
1147   // the following have no corresponding SORT_METHOD_*
1148   { SortByAlbumType,                SORT_METHOD_NONE,                         SortAttributeNone,          564 },
1149   { SortByVotes,                    SORT_METHOD_NONE,                         SortAttributeNone,          205 },
1150   { SortByTop250,                   SORT_METHOD_NONE,                         SortAttributeNone,          13409 },
1151   { SortByMPAA,                     SORT_METHOD_NONE,                         SortAttributeNone,          20074 },
1152   { SortByDateAdded,                SORT_METHOD_NONE,                         SortAttributeNone,          570 },
1153   { SortByTvShowTitle,              SORT_METHOD_NONE,                         SortAttributeNone,          20364 },
1154   { SortByTvShowStatus,             SORT_METHOD_NONE,                         SortAttributeNone,          126 },
1155   { SortBySeason,                   SORT_METHOD_NONE,                         SortAttributeNone,          20373 },
1156   { SortByNumberOfEpisodes,         SORT_METHOD_NONE,                         SortAttributeNone,          20360 },
1157   { SortByNumberOfWatchedEpisodes,  SORT_METHOD_NONE,                         SortAttributeNone,          21441 },
1158   { SortByVideoResolution,          SORT_METHOD_NONE,                         SortAttributeNone,          21443 },
1159   { SortByVideoCodec,               SORT_METHOD_NONE,                         SortAttributeNone,          21445 },
1160   { SortByVideoAspectRatio,         SORT_METHOD_NONE,                         SortAttributeNone,          21374 },
1161   { SortByAudioChannels,            SORT_METHOD_NONE,                         SortAttributeNone,          21444 },
1162   { SortByAudioCodec,               SORT_METHOD_NONE,                         SortAttributeNone,          21446 },
1163   { SortByAudioLanguage,            SORT_METHOD_NONE,                         SortAttributeNone,          21447 },
1164   { SortBySubtitleLanguage,         SORT_METHOD_NONE,                         SortAttributeNone,          21448 },
1165   { SortByRandom,                   SORT_METHOD_NONE,                         SortAttributeNone,          590 }
1166 };
1167 // clang-format on
1168 
TranslateOldSortMethod(SortBy sortBy,bool ignoreArticle)1169 SORT_METHOD SortUtils::TranslateOldSortMethod(SortBy sortBy, bool ignoreArticle)
1170 {
1171   for (const sort_map& t : table)
1172   {
1173     if (t.sort == sortBy)
1174     {
1175       if (ignoreArticle == ((t.flags & SortAttributeIgnoreArticle) == SortAttributeIgnoreArticle))
1176         return t.old;
1177     }
1178   }
1179   for (const sort_map& t : table)
1180   {
1181     if (t.sort == sortBy)
1182       return t.old;
1183   }
1184   return SORT_METHOD_NONE;
1185 }
1186 
TranslateOldSortMethod(SORT_METHOD sortBy)1187 SortDescription SortUtils::TranslateOldSortMethod(SORT_METHOD sortBy)
1188 {
1189   SortDescription description;
1190   for (const sort_map& t : table)
1191   {
1192     if (t.old == sortBy)
1193     {
1194       description.sortBy = t.sort;
1195       description.sortAttributes = t.flags;
1196       break;
1197     }
1198   }
1199   return description;
1200 }
1201 
GetSortLabel(SortBy sortBy)1202 int SortUtils::GetSortLabel(SortBy sortBy)
1203 {
1204   for (const sort_map& t : table)
1205   {
1206     if (t.sort == sortBy)
1207       return t.label;
1208   }
1209   return 16018; // None
1210 }
1211 
1212 template<typename T>
TypeFromString(const std::map<std::string,T> & typeMap,const std::string & name,const T & defaultType)1213 T TypeFromString(const std::map<std::string, T>& typeMap, const std::string& name, const T& defaultType)
1214 {
1215   auto it = typeMap.find(name);
1216   if (it == typeMap.end())
1217     return defaultType;
1218 
1219   return it->second;
1220 }
1221 
1222 template<typename T>
TypeToString(const std::map<std::string,T> & typeMap,const T & value)1223 const std::string& TypeToString(const std::map<std::string, T>& typeMap, const T& value)
1224 {
1225   auto it = std::find_if(typeMap.begin(), typeMap.end(),
1226     [&value](const std::pair<std::string, T>& pair)
1227   {
1228     return pair.second == value;
1229   });
1230 
1231   if (it == typeMap.end())
1232     return StringUtils::Empty;
1233 
1234   return it->first;
1235 }
1236 
1237 /**
1238  * @brief Sort methods to translate string values to enum values.
1239  *
1240  * @warning On string changes, edit __SortBy__ enumerator to have strings right
1241  * for documentation!
1242  */
1243 const std::map<std::string, SortBy> sortMethods = {
1244   { "label",            SortByLabel },
1245   { "date",             SortByDate },
1246   { "size",             SortBySize },
1247   { "file",             SortByFile },
1248   { "path",             SortByPath },
1249   { "drivetype",        SortByDriveType },
1250   { "title",            SortByTitle },
1251   { "track",            SortByTrackNumber },
1252   { "time",             SortByTime },
1253   { "artist",           SortByArtist },
1254   { "artistyear",       SortByArtistThenYear },
1255   { "album",            SortByAlbum },
1256   { "albumtype",        SortByAlbumType },
1257   { "genre",            SortByGenre },
1258   { "country",          SortByCountry },
1259   { "year",             SortByYear },
1260   { "rating",           SortByRating },
1261   { "votes",            SortByVotes },
1262   { "top250",           SortByTop250 },
1263   { "programcount",     SortByProgramCount },
1264   { "playlist",         SortByPlaylistOrder },
1265   { "episode",          SortByEpisodeNumber },
1266   { "season",           SortBySeason },
1267   { "totalepisodes",    SortByNumberOfEpisodes },
1268   { "watchedepisodes",  SortByNumberOfWatchedEpisodes },
1269   { "tvshowstatus",     SortByTvShowStatus },
1270   { "tvshowtitle",      SortByTvShowTitle },
1271   { "sorttitle",        SortBySortTitle },
1272   { "productioncode",   SortByProductionCode },
1273   { "mpaa",             SortByMPAA },
1274   { "videoresolution",  SortByVideoResolution },
1275   { "videocodec",       SortByVideoCodec },
1276   { "videoaspectratio", SortByVideoAspectRatio },
1277   { "audiochannels",    SortByAudioChannels },
1278   { "audiocodec",       SortByAudioCodec },
1279   { "audiolanguage",    SortByAudioLanguage },
1280   { "subtitlelanguage", SortBySubtitleLanguage },
1281   { "studio",           SortByStudio },
1282   { "dateadded",        SortByDateAdded },
1283   { "lastplayed",       SortByLastPlayed },
1284   { "playcount",        SortByPlaycount },
1285   { "listeners",        SortByListeners },
1286   { "bitrate",          SortByBitrate },
1287   { "random",           SortByRandom },
1288   { "channel",          SortByChannel },
1289   { "channelnumber",    SortByChannelNumber },
1290   { "clientchannelorder", SortByClientChannelOrder },
1291   { "datetaken",        SortByDateTaken },
1292   { "userrating",       SortByUserRating },
1293   { "installdate",      SortByInstallDate },
1294   { "lastupdated",      SortByLastUpdated },
1295   { "lastused",         SortByLastUsed },
1296   { "totaldiscs",       SortByTotalDiscs },
1297   { "originaldate",     SortByOrigDate },
1298   { "bpm",              SortByBPM },
1299 };
1300 
SortMethodFromString(const std::string & sortMethod)1301 SortBy SortUtils::SortMethodFromString(const std::string& sortMethod)
1302 {
1303   return TypeFromString<SortBy>(sortMethods, sortMethod, SortByNone);
1304 }
1305 
SortMethodToString(SortBy sortMethod)1306 const std::string& SortUtils::SortMethodToString(SortBy sortMethod)
1307 {
1308   return TypeToString<SortBy>(sortMethods, sortMethod);
1309 }
1310 
1311 const std::map<std::string, SortOrder> sortOrders = {
1312   { "ascending", SortOrderAscending },
1313   { "descending", SortOrderDescending }
1314 };
1315 
SortOrderFromString(const std::string & sortOrder)1316 SortOrder SortUtils::SortOrderFromString(const std::string& sortOrder)
1317 {
1318   return TypeFromString<SortOrder>(sortOrders, sortOrder, SortOrderNone);
1319 }
1320 
SortOrderToString(SortOrder sortOrder)1321 const std::string& SortUtils::SortOrderToString(SortOrder sortOrder)
1322 {
1323   return TypeToString<SortOrder>(sortOrders, sortOrder);
1324 }
1325