summaryrefslogtreecommitdiffstats
path: root/xbmc/utils/SortUtils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/utils/SortUtils.cpp')
-rw-r--r--xbmc/utils/SortUtils.cpp1324
1 files changed, 1324 insertions, 0 deletions
diff --git a/xbmc/utils/SortUtils.cpp b/xbmc/utils/SortUtils.cpp
new file mode 100644
index 0000000..840e69e
--- /dev/null
+++ b/xbmc/utils/SortUtils.cpp
@@ -0,0 +1,1324 @@
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
21std::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
47std::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
55std::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
62std::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
67std::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
75std::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
80std::string ByDate(SortAttribute attributes, const SortItem &values)
81{
82 return values.at(FieldDate).asString() + " " + ByLabel(attributes, values);
83}
84
85std::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
90std::string BySize(SortAttribute attributes, const SortItem &values)
91{
92 return StringUtils::Format("%" PRId64, values.at(FieldSize).asInteger());
93}
94
95std::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
100std::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
108std::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
123std::string ByAlbumType(SortAttribute attributes, const SortItem &values)
124{
125 return values.at(FieldAlbumType).asString() + " " + ByLabel(attributes, values);
126}
127
128std::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
151std::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
178std::string ByTrackNumber(SortAttribute attributes, const SortItem &values)
179{
180 return StringUtils::Format("%i", (int)values.at(FieldTrackNumber).asInteger());
181}
182
183std::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}
188std::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
199std::string ByProgramCount(SortAttribute attributes, const SortItem &values)
200{
201 return StringUtils::Format("%i", (int)values.at(FieldProgramCount).asInteger());
202}
203
204std::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
210std::string ByGenre(SortAttribute attributes, const SortItem &values)
211{
212 return ArrayToString(attributes, values.at(FieldGenre));
213}
214
215std::string ByCountry(SortAttribute attributes, const SortItem &values)
216{
217 return ArrayToString(attributes, values.at(FieldCountry));
218}
219
220std::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
242std::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
260std::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
272std::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
277std::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
282std::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
287std::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
292std::string ByMPAA(SortAttribute attributes, const SortItem &values)
293{
294 return values.at(FieldMPAA).asString() + " " + ByLabel(attributes, values);
295}
296
297std::string ByStudio(SortAttribute attributes, const SortItem &values)
298{
299 return ArrayToString(attributes, values.at(FieldStudio));
300}
301
302std::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
330std::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
340std::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
345std::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
350std::string ByTvShowStatus(SortAttribute attributes, const SortItem &values)
351{
352 return values.at(FieldTvShowStatus).asString() + " " + ByLabel(attributes, values);
353}
354
355std::string ByTvShowTitle(SortAttribute attributes, const SortItem &values)
356{
357 return values.at(FieldTvShowTitle).asString() + " " + ByLabel(attributes, values);
358}
359
360std::string ByProductionCode(SortAttribute attributes, const SortItem &values)
361{
362 return values.at(FieldProductionCode).asString();
363}
364
365std::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
370std::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
375std::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
380std::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
385std::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
390std::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
395std::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
400std::string ByBitrate(SortAttribute attributes, const SortItem &values)
401{
402 return StringUtils::Format("%" PRId64, values.at(FieldBitrate).asInteger());
403}
404
405std::string ByListeners(SortAttribute attributes, const SortItem &values)
406{
407 return StringUtils::Format("%" PRId64, values.at(FieldListeners).asInteger());
408}
409
410std::string ByRandom(SortAttribute attributes, const SortItem &values)
411{
412 return StringUtils::Format("%i", CUtil::GetRandomNumber());
413}
414
415std::string ByChannel(SortAttribute attributes, const SortItem &values)
416{
417 return values.at(FieldChannelName).asString();
418}
419
420std::string ByChannelNumber(SortAttribute attributes, const SortItem &values)
421{
422 return values.at(FieldChannelNumber).asString();
423}
424
425std::string ByClientChannelOrder(SortAttribute attributes, const SortItem& values)
426{
427 return values.at(FieldClientChannelOrder).asString();
428}
429
430std::string ByDateTaken(SortAttribute attributes, const SortItem &values)
431{
432 return values.at(FieldDateTaken).asString();
433}
434
435std::string ByRelevance(SortAttribute attributes, const SortItem &values)
436{
437 return StringUtils::Format("%i", (int)values.at(FieldRelevance).asInteger());
438}
439
440std::string ByInstallDate(SortAttribute attributes, const SortItem &values)
441{
442 return values.at(FieldInstallDate).asString();
443}
444
445std::string ByLastUpdated(SortAttribute attributes, const SortItem &values)
446{
447 return values.at(FieldLastUpdated).asString();
448}
449
450std::string ByLastUsed(SortAttribute attributes, const SortItem &values)
451{
452 return values.at(FieldLastUsed).asString();
453}
454
455std::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
461bool 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
527bool 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
537bool 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
547bool 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
557bool 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
567bool SorterIndirectAscending(const SortItemPtr &left, const SortItemPtr &right)
568{
569 return SorterAscending(*left, *right);
570}
571
572bool SorterIndirectDescending(const SortItemPtr &left, const SortItemPtr &right)
573{
574 return SorterDescending(*left, *right);
575}
576
577bool SorterIndirectIgnoreFoldersAscending(const SortItemPtr &left, const SortItemPtr &right)
578{
579 return SorterIgnoreFoldersAscending(*left, *right);
580}
581
582bool SorterIndirectIgnoreFoldersDescending(const SortItemPtr &left, const SortItemPtr &right)
583{
584 return SorterIgnoreFoldersDescending(*left, *right);
585}
586
587// clang-format off
588std::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
654std::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
748std::map<SortBy, SortUtils::SortPreparator> SortUtils::m_preparators = fillPreparators();
749std::map<SortBy, Fields> SortUtils::m_sortingFields = fillSortingFields();
750
751void 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
933void 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
972void 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
1011void SortUtils::Sort(const SortDescription &sortDescription, DatabaseResults& items)
1012{
1013 Sort(sortDescription.sortBy, sortDescription.sortOrder, sortDescription.sortAttributes, items, sortDescription.limitEnd, sortDescription.limitStart);
1014}
1015
1016void SortUtils::Sort(const SortDescription &sortDescription, SortItems& items)
1017{
1018 Sort(sortDescription.sortBy, sortDescription.sortOrder, sortDescription.sortAttributes, items, sortDescription.limitEnd, sortDescription.limitStart);
1019}
1020
1021bool 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
1042const 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
1051SortUtils::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
1059SortUtils::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
1067const 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
1076std::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
1088typedef struct
1089{
1090 SortBy sort;
1091 SORT_METHOD old;
1092 SortAttribute flags;
1093 int label;
1094} sort_map;
1095
1096// clang-format off
1097const 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
1169SORT_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
1187SortDescription 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
1202int 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
1212template<typename T>
1213T 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
1222template<typename T>
1223const 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 */
1243const 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
1301SortBy SortUtils::SortMethodFromString(const std::string& sortMethod)
1302{
1303 return TypeFromString<SortBy>(sortMethods, sortMethod, SortByNone);
1304}
1305
1306const std::string& SortUtils::SortMethodToString(SortBy sortMethod)
1307{
1308 return TypeToString<SortBy>(sortMethods, sortMethod);
1309}
1310
1311const std::map<std::string, SortOrder> sortOrders = {
1312 { "ascending", SortOrderAscending },
1313 { "descending", SortOrderDescending }
1314};
1315
1316SortOrder SortUtils::SortOrderFromString(const std::string& sortOrder)
1317{
1318 return TypeFromString<SortOrder>(sortOrders, sortOrder, SortOrderNone);
1319}
1320
1321const std::string& SortUtils::SortOrderToString(SortOrder sortOrder)
1322{
1323 return TypeToString<SortOrder>(sortOrders, sortOrder);
1324}