2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "SmartPlayList.h"
23 #include "XBDateTime.h"
24 #include "filesystem/File.h"
25 #include "filesystem/SmartPlaylistDirectory.h"
26 #include "guilib/LocalizeStrings.h"
27 #include "utils/CharsetConverter.h"
28 #include "utils/DatabaseUtils.h"
29 #include "utils/JSONVariantParser.h"
30 #include "utils/JSONVariantWriter.h"
31 #include "utils/log.h"
32 #include "utils/StringUtils.h"
33 #include "utils/StringValidation.h"
34 #include "utils/URIUtils.h"
35 #include "utils/Variant.h"
36 #include "utils/XMLUtils.h"
37 #include "video/VideoDatabase.h"
40 using namespace XFILE;
47 CDatabaseQueryRule::FIELD_TYPE type;
48 StringValidation::Validator validator;
53 static const translateField fields[] = {
54 { "none", FieldNone, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 231 },
55 { "filename", FieldFilename, SortByFile, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 561 },
56 { "path", FieldPath, SortByPath, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 573 },
57 { "album", FieldAlbum, SortByAlbum, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 558 },
58 { "albumartist", FieldAlbumArtist, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 566 },
59 { "artist", FieldArtist, SortByArtist, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 557 },
60 { "tracknumber", FieldTrackNumber, SortByTrackNumber, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 554 },
61 { "comment", FieldComment, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 569 },
62 { "review", FieldReview, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 183 },
63 { "themes", FieldThemes, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21895 },
64 { "moods", FieldMoods, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 175 },
65 { "styles", FieldStyles, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 176 },
66 { "type", FieldAlbumType, SortByAlbumType, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 564 },
67 { "label", FieldMusicLabel, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21899 },
68 { "title", FieldTitle, SortByTitle, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 556 },
69 { "sorttitle", FieldSortTitle, SortBySortTitle, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 171 },
70 { "year", FieldYear, SortByYear, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, true, 562 },
71 { "time", FieldTime, SortByTime, CDatabaseQueryRule::SECONDS_FIELD, StringValidation::IsTime, false, 180 },
72 { "playcount", FieldPlaycount, SortByPlaycount, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 567 },
73 { "lastplayed", FieldLastPlayed, SortByLastPlayed, CDatabaseQueryRule::DATE_FIELD, NULL, false, 568 },
74 { "inprogress", FieldInProgress, SortByNone, CDatabaseQueryRule::BOOLEAN_FIELD, NULL, false, 575 },
75 { "rating", FieldRating, SortByRating, CDatabaseQueryRule::NUMERIC_FIELD, CSmartPlaylistRule::ValidateRating, false, 563 },
76 { "votes", FieldVotes, SortByVotes, CDatabaseQueryRule::TEXT_FIELD, StringValidation::IsPositiveInteger, false, 205 },
77 { "top250", FieldTop250, SortByTop250, CDatabaseQueryRule::NUMERIC_FIELD, NULL, false, 13409 },
78 { "mpaarating", FieldMPAA, SortByMPAA, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 20074 },
79 { "dateadded", FieldDateAdded, SortByDateAdded, CDatabaseQueryRule::DATE_FIELD, NULL, false, 570 },
80 { "genre", FieldGenre, SortByGenre, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 515 },
81 { "plot", FieldPlot, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 207 },
82 { "plotoutline", FieldPlotOutline, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 203 },
83 { "tagline", FieldTagline, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 202 },
84 { "set", FieldSet, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20457 },
85 { "director", FieldDirector, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20339 },
86 { "actor", FieldActor, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20337 },
87 { "writers", FieldWriter, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20417 },
88 { "airdate", FieldAirDate, SortByYear, CDatabaseQueryRule::DATE_FIELD, NULL, false, 20416 },
89 { "hastrailer", FieldTrailer, SortByNone, CDatabaseQueryRule::BOOLEAN_FIELD, NULL, false, 20423 },
90 { "studio", FieldStudio, SortByStudio, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 572 },
91 { "country", FieldCountry, SortByCountry, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 574 },
92 { "tvshow", FieldTvShowTitle, SortByTvShowTitle, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20364 },
93 { "status", FieldTvShowStatus, SortByTvShowStatus, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 126 },
94 { "season", FieldSeason, SortBySeason, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 20373 },
95 { "episode", FieldEpisodeNumber, SortByEpisodeNumber, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 20359 },
96 { "numepisodes", FieldNumberOfEpisodes, SortByNumberOfEpisodes, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 20360 },
97 { "numwatched", FieldNumberOfWatchedEpisodes, SortByNumberOfWatchedEpisodes, CDatabaseQueryRule::NUMERIC_FIELD, StringValidation::IsPositiveInteger, false, 21457 },
98 { "videoresolution", FieldVideoResolution, SortByVideoResolution, CDatabaseQueryRule::NUMERIC_FIELD, NULL, false, 21443 },
99 { "videocodec", FieldVideoCodec, SortByVideoCodec, CDatabaseQueryRule::TEXTIN_FIELD, NULL, false, 21445 },
100 { "videoaspect", FieldVideoAspectRatio, SortByVideoAspectRatio, CDatabaseQueryRule::NUMERIC_FIELD, NULL, false, 21374 },
101 { "audiochannels", FieldAudioChannels, SortByAudioChannels, CDatabaseQueryRule::NUMERIC_FIELD, NULL, false, 21444 },
102 { "audiocodec", FieldAudioCodec, SortByAudioCodec, CDatabaseQueryRule::TEXTIN_FIELD, NULL, false, 21446 },
103 { "audiolanguage", FieldAudioLanguage, SortByAudioLanguage, CDatabaseQueryRule::TEXTIN_FIELD, NULL, false, 21447 },
104 { "subtitlelanguage", FieldSubtitleLanguage, SortBySubtitleLanguage, CDatabaseQueryRule::TEXTIN_FIELD, NULL, false, 21448 },
105 { "random", FieldRandom, SortByRandom, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 590 },
106 { "playlist", FieldPlaylist, SortByPlaylistOrder, CDatabaseQueryRule::PLAYLIST_FIELD, NULL, true, 559 },
107 { "virtualfolder", FieldVirtualFolder, SortByNone, CDatabaseQueryRule::PLAYLIST_FIELD, NULL, true, 614 },
108 { "tag", FieldTag, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, true, 20459 },
109 { "instruments", FieldInstruments, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21892 },
110 { "biography", FieldBiography, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21887 },
111 { "born", FieldBorn, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21893 },
112 { "bandformed", FieldBandFormed, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21894 },
113 { "disbanded", FieldDisbanded, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21896 },
114 { "died", FieldDied, SortByNone, CDatabaseQueryRule::TEXT_FIELD, NULL, false, 21897 }
117 static const size_t NUM_FIELDS = sizeof(fields) / sizeof(translateField);
127 static const group groups[] = { { "", FieldUnknown, false, 571 },
128 { "none", FieldNone, false, 231 },
129 { "sets", FieldSet, true, 20434 },
130 { "genres", FieldGenre, false, 135 },
131 { "years", FieldYear, false, 652 },
132 { "actors", FieldActor, false, 344 },
133 { "directors", FieldDirector, false, 20348 },
134 { "writers", FieldWriter, false, 20418 },
135 { "studios", FieldStudio, false, 20388 },
136 { "countries", FieldCountry, false, 20451 },
137 { "artists", FieldArtist, false, 133 },
138 { "albums", FieldAlbum, false, 132 },
139 { "tags", FieldTag, false, 20459 },
142 static const size_t NUM_GROUPS = sizeof(groups) / sizeof(group);
144 #define RULE_VALUE_SEPARATOR " / "
146 CSmartPlaylistRule::CSmartPlaylistRule()
150 int CSmartPlaylistRule::TranslateField(const char *field) const
152 for (unsigned int i = 0; i < NUM_FIELDS; i++)
153 if (StringUtils::EqualsNoCase(field, fields[i].string)) return fields[i].field;
157 CStdString CSmartPlaylistRule::TranslateField(int field) const
159 for (unsigned int i = 0; i < NUM_FIELDS; i++)
160 if (field == fields[i].field) return fields[i].string;
164 SortBy CSmartPlaylistRule::TranslateOrder(const char *order)
166 for (unsigned int i = 0; i < NUM_FIELDS; i++)
167 if (StringUtils::EqualsNoCase(order, fields[i].string)) return fields[i].sort;
171 CStdString CSmartPlaylistRule::TranslateOrder(SortBy order)
173 for (unsigned int i = 0; i < NUM_FIELDS; i++)
174 if (order == fields[i].sort) return fields[i].string;
178 Field CSmartPlaylistRule::TranslateGroup(const char *group)
180 for (unsigned int i = 0; i < NUM_GROUPS; i++)
182 if (StringUtils::EqualsNoCase(group, groups[i].name))
183 return groups[i].field;
189 CStdString CSmartPlaylistRule::TranslateGroup(Field group)
191 for (unsigned int i = 0; i < NUM_GROUPS; i++)
193 if (group == groups[i].field)
194 return groups[i].name;
200 CStdString CSmartPlaylistRule::GetLocalizedField(int field)
202 for (unsigned int i = 0; i < NUM_FIELDS; i++)
203 if (field == fields[i].field) return g_localizeStrings.Get(fields[i].localizedString);
204 return g_localizeStrings.Get(16018);
207 CDatabaseQueryRule::FIELD_TYPE CSmartPlaylistRule::GetFieldType(int field) const
209 for (unsigned int i = 0; i < NUM_FIELDS; i++)
210 if (field == fields[i].field) return fields[i].type;
214 bool CSmartPlaylistRule::IsFieldBrowseable(int field)
216 for (unsigned int i = 0; i < NUM_FIELDS; i++)
217 if (field == fields[i].field) return fields[i].browseable;
222 bool CSmartPlaylistRule::Validate(const std::string &input, void *data)
227 CSmartPlaylistRule *rule = (CSmartPlaylistRule*)data;
229 // check if there's a validator for this rule
230 StringValidation::Validator validator = NULL;
231 for (unsigned int i = 0; i < NUM_FIELDS; i++)
233 if (rule->m_field == fields[i].field)
235 validator = fields[i].validator;
239 if (validator == NULL)
242 // split the input into multiple values and validate every value separately
243 vector<string> values = StringUtils::Split(input, RULE_VALUE_SEPARATOR);
244 for (vector<string>::const_iterator it = values.begin(); it != values.end(); ++it)
246 if (!validator(*it, data))
253 bool CSmartPlaylistRule::ValidateRating(const std::string &input, void *data)
256 string strRating = input;
257 StringUtils::Trim(strRating);
259 double rating = strtod(strRating.c_str(), &end);
260 return (end == NULL || *end == '\0') &&
261 rating >= 0.0 && rating <= 10.0;
264 vector<Field> CSmartPlaylistRule::GetFields(const CStdString &type)
266 vector<Field> fields;
267 bool isVideo = false;
270 fields.push_back(FieldGenre);
271 fields.push_back(FieldAlbum);
272 fields.push_back(FieldArtist);
273 fields.push_back(FieldAlbumArtist);
274 fields.push_back(FieldTitle);
275 fields.push_back(FieldYear);
276 fields.push_back(FieldTime);
277 fields.push_back(FieldTrackNumber);
278 fields.push_back(FieldFilename);
279 fields.push_back(FieldPath);
280 fields.push_back(FieldPlaycount);
281 fields.push_back(FieldLastPlayed);
282 fields.push_back(FieldRating);
283 fields.push_back(FieldComment);
285 else if (type == "albums")
287 fields.push_back(FieldGenre);
288 fields.push_back(FieldAlbum);
289 fields.push_back(FieldArtist); // any artist
290 fields.push_back(FieldAlbumArtist); // album artist
291 fields.push_back(FieldYear);
292 fields.push_back(FieldReview);
293 fields.push_back(FieldThemes);
294 fields.push_back(FieldMoods);
295 fields.push_back(FieldStyles);
296 fields.push_back(FieldAlbumType);
297 fields.push_back(FieldMusicLabel);
298 fields.push_back(FieldRating);
299 fields.push_back(FieldPlaycount);
301 else if (type == "artists")
303 fields.push_back(FieldArtist);
304 fields.push_back(FieldGenre);
305 fields.push_back(FieldMoods);
306 fields.push_back(FieldStyles);
307 fields.push_back(FieldInstruments);
308 fields.push_back(FieldBiography);
309 fields.push_back(FieldBorn);
310 fields.push_back(FieldBandFormed);
311 fields.push_back(FieldDisbanded);
312 fields.push_back(FieldDied);
314 else if (type == "tvshows")
316 fields.push_back(FieldTitle);
317 fields.push_back(FieldPlot);
318 fields.push_back(FieldTvShowStatus);
319 fields.push_back(FieldVotes);
320 fields.push_back(FieldRating);
321 fields.push_back(FieldYear);
322 fields.push_back(FieldGenre);
323 fields.push_back(FieldDirector);
324 fields.push_back(FieldActor);
325 fields.push_back(FieldNumberOfEpisodes);
326 fields.push_back(FieldNumberOfWatchedEpisodes);
327 fields.push_back(FieldPlaycount);
328 fields.push_back(FieldPath);
329 fields.push_back(FieldStudio);
330 fields.push_back(FieldMPAA);
331 fields.push_back(FieldDateAdded);
332 fields.push_back(FieldLastPlayed);
333 fields.push_back(FieldInProgress);
334 fields.push_back(FieldTag);
336 else if (type == "episodes")
338 fields.push_back(FieldTitle);
339 fields.push_back(FieldTvShowTitle);
340 fields.push_back(FieldPlot);
341 fields.push_back(FieldVotes);
342 fields.push_back(FieldRating);
343 fields.push_back(FieldTime);
344 fields.push_back(FieldWriter);
345 fields.push_back(FieldAirDate);
346 fields.push_back(FieldPlaycount);
347 fields.push_back(FieldLastPlayed);
348 fields.push_back(FieldInProgress);
349 fields.push_back(FieldGenre);
350 fields.push_back(FieldYear); // premiered
351 fields.push_back(FieldDirector);
352 fields.push_back(FieldActor);
353 fields.push_back(FieldEpisodeNumber);
354 fields.push_back(FieldSeason);
355 fields.push_back(FieldFilename);
356 fields.push_back(FieldPath);
357 fields.push_back(FieldStudio);
358 fields.push_back(FieldMPAA);
359 fields.push_back(FieldDateAdded);
362 else if (type == "movies")
364 fields.push_back(FieldTitle);
365 fields.push_back(FieldPlot);
366 fields.push_back(FieldPlotOutline);
367 fields.push_back(FieldTagline);
368 fields.push_back(FieldVotes);
369 fields.push_back(FieldRating);
370 fields.push_back(FieldTime);
371 fields.push_back(FieldWriter);
372 fields.push_back(FieldPlaycount);
373 fields.push_back(FieldLastPlayed);
374 fields.push_back(FieldInProgress);
375 fields.push_back(FieldGenre);
376 fields.push_back(FieldCountry);
377 fields.push_back(FieldYear); // premiered
378 fields.push_back(FieldDirector);
379 fields.push_back(FieldActor);
380 fields.push_back(FieldMPAA);
381 fields.push_back(FieldTop250);
382 fields.push_back(FieldStudio);
383 fields.push_back(FieldTrailer);
384 fields.push_back(FieldFilename);
385 fields.push_back(FieldPath);
386 fields.push_back(FieldSet);
387 fields.push_back(FieldTag);
388 fields.push_back(FieldDateAdded);
391 else if (type == "musicvideos")
393 fields.push_back(FieldTitle);
394 fields.push_back(FieldGenre);
395 fields.push_back(FieldAlbum);
396 fields.push_back(FieldYear);
397 fields.push_back(FieldArtist);
398 fields.push_back(FieldFilename);
399 fields.push_back(FieldPath);
400 fields.push_back(FieldPlaycount);
401 fields.push_back(FieldLastPlayed);
402 fields.push_back(FieldTime);
403 fields.push_back(FieldDirector);
404 fields.push_back(FieldStudio);
405 fields.push_back(FieldPlot);
406 fields.push_back(FieldTag);
407 fields.push_back(FieldDateAdded);
412 fields.push_back(FieldVideoResolution);
413 fields.push_back(FieldAudioChannels);
414 fields.push_back(FieldVideoCodec);
415 fields.push_back(FieldAudioCodec);
416 fields.push_back(FieldAudioLanguage);
417 fields.push_back(FieldSubtitleLanguage);
418 fields.push_back(FieldVideoAspectRatio);
420 fields.push_back(FieldPlaylist);
421 fields.push_back(FieldVirtualFolder);
426 std::vector<SortBy> CSmartPlaylistRule::GetOrders(const CStdString &type)
428 vector<SortBy> orders;
429 orders.push_back(SortByNone);
432 orders.push_back(SortByGenre);
433 orders.push_back(SortByAlbum);
434 orders.push_back(SortByArtist);
435 orders.push_back(SortByTitle);
436 orders.push_back(SortByYear);
437 orders.push_back(SortByTime);
438 orders.push_back(SortByTrackNumber);
439 orders.push_back(SortByFile);
440 orders.push_back(SortByPath);
441 orders.push_back(SortByPlaycount);
442 orders.push_back(SortByLastPlayed);
443 orders.push_back(SortByRating);
445 else if (type == "albums")
447 orders.push_back(SortByGenre);
448 orders.push_back(SortByAlbum);
449 orders.push_back(SortByArtist); // any artist
450 orders.push_back(SortByYear);
451 //orders.push_back(SortByThemes);
452 //orders.push_back(SortByMoods);
453 //orders.push_back(SortByStyles);
454 orders.push_back(SortByAlbumType);
455 //orders.push_back(SortByMusicLabel);
456 orders.push_back(SortByRating);
457 orders.push_back(SortByPlaycount);
459 else if (type == "artists")
461 orders.push_back(SortByArtist);
463 else if (type == "tvshows")
465 orders.push_back(SortBySortTitle);
466 orders.push_back(SortByTvShowStatus);
467 orders.push_back(SortByVotes);
468 orders.push_back(SortByRating);
469 orders.push_back(SortByYear);
470 orders.push_back(SortByGenre);
471 orders.push_back(SortByNumberOfEpisodes);
472 orders.push_back(SortByNumberOfWatchedEpisodes);
473 //orders.push_back(SortByPlaycount);
474 orders.push_back(SortByPath);
475 orders.push_back(SortByStudio);
476 orders.push_back(SortByMPAA);
477 orders.push_back(SortByDateAdded);
478 orders.push_back(SortByLastPlayed);
480 else if (type == "episodes")
482 orders.push_back(SortByTitle);
483 orders.push_back(SortByTvShowTitle);
484 orders.push_back(SortByVotes);
485 orders.push_back(SortByRating);
486 orders.push_back(SortByTime);
487 orders.push_back(SortByPlaycount);
488 orders.push_back(SortByLastPlayed);
489 orders.push_back(SortByYear); // premiered/dateaired
490 orders.push_back(SortByEpisodeNumber);
491 orders.push_back(SortBySeason);
492 orders.push_back(SortByFile);
493 orders.push_back(SortByPath);
494 orders.push_back(SortByStudio);
495 orders.push_back(SortByMPAA);
496 orders.push_back(SortByDateAdded);
498 else if (type == "movies")
500 orders.push_back(SortBySortTitle);
501 orders.push_back(SortByVotes);
502 orders.push_back(SortByRating);
503 orders.push_back(SortByTime);
504 orders.push_back(SortByPlaycount);
505 orders.push_back(SortByLastPlayed);
506 orders.push_back(SortByGenre);
507 orders.push_back(SortByCountry);
508 orders.push_back(SortByYear); // premiered
509 orders.push_back(SortByMPAA);
510 orders.push_back(SortByTop250);
511 orders.push_back(SortByStudio);
512 orders.push_back(SortByFile);
513 orders.push_back(SortByPath);
514 orders.push_back(SortByDateAdded);
516 else if (type == "musicvideos")
518 orders.push_back(SortByTitle);
519 orders.push_back(SortByGenre);
520 orders.push_back(SortByAlbum);
521 orders.push_back(SortByYear);
522 orders.push_back(SortByArtist);
523 orders.push_back(SortByFile);
524 orders.push_back(SortByPath);
525 orders.push_back(SortByPlaycount);
526 orders.push_back(SortByLastPlayed);
527 orders.push_back(SortByTime);
528 orders.push_back(SortByStudio);
529 orders.push_back(SortByDateAdded);
531 orders.push_back(SortByRandom);
536 std::vector<Field> CSmartPlaylistRule::GetGroups(const CStdString &type)
538 vector<Field> groups;
539 groups.push_back(FieldUnknown);
541 if (type == "artists")
542 groups.push_back(FieldGenre);
543 else if (type == "albums")
544 groups.push_back(FieldYear);
545 if (type == "movies")
547 groups.push_back(FieldNone);
548 groups.push_back(FieldSet);
549 groups.push_back(FieldGenre);
550 groups.push_back(FieldYear);
551 groups.push_back(FieldActor);
552 groups.push_back(FieldDirector);
553 groups.push_back(FieldWriter);
554 groups.push_back(FieldStudio);
555 groups.push_back(FieldCountry);
556 groups.push_back(FieldTag);
558 else if (type == "tvshows")
560 groups.push_back(FieldGenre);
561 groups.push_back(FieldYear);
562 groups.push_back(FieldActor);
563 groups.push_back(FieldDirector);
564 groups.push_back(FieldStudio);
565 groups.push_back(FieldTag);
567 else if (type == "musicvideos")
569 groups.push_back(FieldArtist);
570 groups.push_back(FieldAlbum);
571 groups.push_back(FieldGenre);
572 groups.push_back(FieldYear);
573 groups.push_back(FieldDirector);
574 groups.push_back(FieldStudio);
575 groups.push_back(FieldTag);
581 CStdString CSmartPlaylistRule::GetLocalizedGroup(Field group)
583 for (unsigned int i = 0; i < NUM_GROUPS; i++)
585 if (group == groups[i].field)
586 return g_localizeStrings.Get(groups[i].localizedString);
589 return g_localizeStrings.Get(groups[0].localizedString);
592 bool CSmartPlaylistRule::CanGroupMix(Field group)
594 for (unsigned int i = 0; i < NUM_GROUPS; i++)
596 if (group == groups[i].field)
597 return groups[i].canMix;
603 CStdString CSmartPlaylistRule::GetLocalizedRule() const
605 return StringUtils::Format("%s %s %s", GetLocalizedField(m_field).c_str(), GetLocalizedOperator(m_operator).c_str(), GetParameter().c_str());
608 CStdString CSmartPlaylistRule::GetVideoResolutionQuery(const CStdString ¶meter) const
610 CStdString retVal(" IN (SELECT DISTINCT idFile FROM streamdetails WHERE iVideoWidth ");
611 int iRes = (int)strtol(parameter.c_str(), NULL, 10);
614 if (iRes >= 1080) { min = 1281; max = INT_MAX; }
615 else if (iRes >= 720) { min = 961; max = 1280; }
616 else if (iRes >= 540) { min = 721; max = 960; }
617 else { min = 0; max = 720; }
621 case OPERATOR_EQUALS:
622 retVal += StringUtils::Format(">= %i AND iVideoWidth <= %i", min, max);
624 case OPERATOR_DOES_NOT_EQUAL:
625 retVal += StringUtils::Format("< %i OR iVideoWidth > %i", min, max);
627 case OPERATOR_LESS_THAN:
628 retVal += StringUtils::Format("< %i", min);
630 case OPERATOR_GREATER_THAN:
631 retVal += StringUtils::Format("> %i", max);
641 CStdString CSmartPlaylistRule::GetBooleanQuery(const CStdString &negate, const CStdString &strType) const
643 if (strType == "movies")
645 if (m_field == FieldInProgress)
646 return "movieview.idFile " + negate + " IN (SELECT DISTINCT idFile FROM bookmark WHERE type = 1)";
647 else if (m_field == FieldTrailer)
648 return negate + GetField(m_field, strType) + "!= ''";
650 else if (strType == "episodes")
652 if (m_field == FieldInProgress)
653 return "episodeview.idFile " + negate + " IN (SELECT DISTINCT idFile FROM bookmark WHERE type = 1)";
655 else if (strType == "tvshows")
657 if (m_field == FieldInProgress)
659 "(tvshowview.watchedcount > 0 AND tvshowview.watchedcount < tvshowview.totalCount) OR "
660 "(tvshowview.watchedcount = 0 AND EXISTS "
661 "(SELECT 1 FROM episodeview WHERE episodeview.idShow = " + GetField(FieldId, strType) + " AND episodeview.resumeTimeInSeconds > 0)"
668 CDatabaseQueryRule::SEARCH_OPERATOR CSmartPlaylistRule::GetOperator(const CStdString &strType) const
670 SEARCH_OPERATOR op = CDatabaseQueryRule::GetOperator(strType);
671 if ((strType == "tvshows" || strType == "episodes") && m_field == FieldYear)
672 { // special case for premiered which is a date rather than a year
673 // TODO: SMARTPLAYLISTS do we really need this, or should we just make this field the premiered date and request a date?
674 if (op == OPERATOR_EQUALS)
675 op = OPERATOR_CONTAINS;
676 else if (op == OPERATOR_DOES_NOT_EQUAL)
677 op = OPERATOR_DOES_NOT_CONTAIN;
682 CStdString CSmartPlaylistRule::FormatParameter(const CStdString &operatorString, const CStdString ¶m, const CDatabase &db, const CStdString &strType) const
685 if (m_field == FieldTime)
686 { // translate time to seconds
687 CStdString seconds = StringUtils::Format("%i", StringUtils::TimeStringToSeconds(param));
688 return db.PrepareSQL(operatorString.c_str(), seconds.c_str());
690 return CDatabaseQueryRule::FormatParameter(operatorString, param, db, strType);
693 CStdString CSmartPlaylistRule::FormatWhereClause(const CStdString &negate, const CStdString &oper, const CStdString ¶m,
694 const CDatabase &db, const CStdString &strType) const
696 CStdString parameter = FormatParameter(oper, param, db, strType);
700 if (strType == "songs")
704 if (m_field == FieldGenre)
705 query = negate + " EXISTS (SELECT 1 FROM song_genre, genre WHERE song_genre.idSong = " + GetField(FieldId, strType) + " AND song_genre.idGenre = genre.idGenre AND genre.strGenre" + parameter + ")";
706 else if (m_field == FieldArtist)
707 query = negate + " EXISTS (SELECT 1 FROM song_artist, artist WHERE song_artist.idSong = " + GetField(FieldId, strType) + " AND song_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")";
708 else if (m_field == FieldAlbumArtist)
709 query = negate + " EXISTS (SELECT 1 FROM album_artist, artist WHERE album_artist.idAlbum = " + table + ".idAlbum AND album_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")";
710 else if (m_field == FieldLastPlayed && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
711 query = GetField(m_field, strType) + " is NULL or " + GetField(m_field, strType) + parameter;
713 else if (strType == "albums")
717 if (m_field == FieldGenre)
718 query = negate + " EXISTS (SELECT 1 FROM song, song_genre, genre WHERE song.idAlbum = " + GetField(FieldId, strType) + " AND song.idSong = song_genre.idSong AND song_genre.idGenre = genre.idGenre AND genre.strGenre" + parameter + ")";
719 else if (m_field == FieldArtist)
720 query = negate + " EXISTS (SELECT 1 FROM song, song_artist, artist WHERE song.idAlbum = " + GetField(FieldId, strType) + " AND song.idSong = song_artist.idSong AND song_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")";
721 else if (m_field == FieldAlbumArtist)
722 query = negate + " EXISTS (SELECT 1 FROM album_artist, artist WHERE album_artist.idAlbum = " + GetField(FieldId, strType) + " AND album_artist.idArtist = artist.idArtist AND artist.strArtist" + parameter + ")";
724 else if (strType == "artists")
726 table = "artistview";
728 if (m_field == FieldGenre)
729 query = negate + " EXISTS (SELECT DISTINCT song_artist.idArtist FROM song_artist, song_genre, genre WHERE song_artist.idArtist = " + GetField(FieldId, strType) + " AND song_artist.idSong = song_genre.idSong AND song_genre.idGenre = genre.idGenre AND genre.strGenre" + parameter + ")";
731 else if (strType == "movies")
735 if (m_field == FieldGenre)
736 query = negate + " EXISTS (SELECT 1 FROM genrelinkmovie JOIN genre ON genre.idGenre=genrelinkmovie.idGenre WHERE genrelinkmovie.idMovie = " + GetField(FieldId, strType) + " AND genre.strGenre" + parameter + ")";
737 else if (m_field == FieldDirector)
738 query = negate + " EXISTS (SELECT 1 FROM directorlinkmovie JOIN actors ON actors.idActor=directorlinkmovie.idDirector WHERE directorlinkmovie.idMovie = " + GetField(FieldId, strType) + " AND actors.strActor" + parameter + ")";
739 else if (m_field == FieldActor)
740 query = negate + " EXISTS (SELECT 1 FROM actorlinkmovie JOIN actors ON actors.idActor=actorlinkmovie.idActor WHERE actorlinkmovie.idMovie = " + GetField(FieldId, strType) + " AND actors.strActor" + parameter + ")";
741 else if (m_field == FieldWriter)
742 query = negate + " EXISTS (SELECT 1 FROM writerlinkmovie JOIN actors ON actors.idActor=writerlinkmovie.idWriter WHERE writerlinkmovie.idMovie = " + GetField(FieldId, strType) + " AND actors.strActor" + parameter + ")";
743 else if (m_field == FieldStudio)
744 query = negate + " EXISTS (SELECT 1 FROM studiolinkmovie JOIN studio ON studio.idStudio=studiolinkmovie.idStudio WHERE studiolinkmovie.idMovie = " + GetField(FieldId, strType) + " AND studio.strStudio" + parameter + ")";
745 else if (m_field == FieldCountry)
746 query = negate + " EXISTS (SELECT 1 FROM countrylinkmovie JOIN country ON country.idCountry=countrylinkmovie.idCountry WHERE countrylinkmovie.idMovie = " + GetField(FieldId, strType) + " AND country.strCountry" + parameter + ")";
747 else if ((m_field == FieldLastPlayed || m_field == FieldDateAdded) && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
748 query = GetField(m_field, strType) + " IS NULL OR " + GetField(m_field, strType) + parameter;
749 else if (m_field == FieldTag)
750 query = negate + " EXISTS (SELECT 1 FROM taglinks JOIN tag ON tag.idTag = taglinks.idTag WHERE taglinks.idMedia = " + GetField(FieldId, strType) + " AND tag.strTag" + parameter + " AND taglinks.media_type = 'movie')";
752 else if (strType == "musicvideos")
754 table = "musicvideoview";
756 if (m_field == FieldGenre)
757 query = negate + " EXISTS (SELECT 1 FROM genrelinkmusicvideo JOIN genre ON genre.idGenre=genrelinkmusicvideo.idGenre WHERE genrelinkmusicvideo.idMVideo = " + GetField(FieldId, strType) + " AND genre.strGenre" + parameter + ")";
758 else if (m_field == FieldArtist)
759 query = negate + " EXISTS (SELECT 1 FROM artistlinkmusicvideo JOIN actors ON actors.idActor=artistlinkmusicvideo.idArtist WHERE artistlinkmusicvideo.idMVideo = " + GetField(FieldId, strType) + " AND actors.strActor" + parameter + ")";
760 else if (m_field == FieldStudio)
761 query = negate + " EXISTS (SELECT 1 FROM studiolinkmusicvideo JOIN studio ON studio.idStudio=studiolinkmusicvideo.idStudio WHERE studiolinkmusicvideo.idMVideo = " + GetField(FieldId, strType) + " AND studio.strStudio" + parameter + ")";
762 else if (m_field == FieldDirector)
763 query = negate + " EXISTS (SELECT 1 FROM directorlinkmusicvideo JOIN actors ON actors.idActor=directorlinkmusicvideo.idDirector WHERE directorlinkmusicvideo.idMVideo = " + GetField(FieldId, strType) + " AND actors.strActor" + parameter + ")";
764 else if ((m_field == FieldLastPlayed || m_field == FieldDateAdded) && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
765 query = GetField(m_field, strType) + " IS NULL OR " + GetField(m_field, strType) + parameter;
766 else if (m_field == FieldTag)
767 query = negate + " EXISTS (SELECT 1 FROM taglinks JOIN tag ON tag.idTag = taglinks.idTag WHERE taglinks.idMedia = " + GetField(FieldId, strType) + " AND tag.strTag" + parameter + " AND taglinks.media_type = 'musicvideo')";
769 else if (strType == "tvshows")
771 table = "tvshowview";
773 if (m_field == FieldGenre)
774 query = negate + " EXISTS (SELECT 1 FROM genrelinktvshow JOIN genre ON genre.idGenre=genrelinktvshow.idGenre WHERE genrelinktvshow.idShow = " + GetField(FieldId, strType) + " AND genre.strGenre" + parameter + ")";
775 else if (m_field == FieldDirector)
776 query = negate + " EXISTS (SELECT 1 FROM directorlinktvshow JOIN actors ON actors.idActor=directorlinktvshow.idDirector WHERE directorlinktvshow.idShow = " + GetField(FieldId, strType) + " AND actors.strActor" + parameter + ")";
777 else if (m_field == FieldActor)
778 query = negate + " EXISTS (SELECT 1 FROM actorlinktvshow JOIN actors ON actors.idActor=actorlinktvshow.idActor WHERE actorlinktvshow.idShow = " + GetField(FieldId, strType) + " AND actors.strActor" + parameter + ")";
779 else if (m_field == FieldStudio)
780 query = negate + " (" + GetField(m_field, strType) + parameter + ")";
781 else if (m_field == FieldMPAA)
782 query = negate + " (" + GetField(m_field, strType) + parameter + ")";
783 else if ((m_field == FieldLastPlayed || m_field == FieldDateAdded) && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
784 query = GetField(m_field, strType) + " IS NULL OR " + GetField(m_field, strType) + parameter;
785 else if (m_field == FieldPlaycount)
786 query = "CASE WHEN COALESCE(" + GetField(FieldNumberOfEpisodes, strType) + " - " + GetField(FieldNumberOfWatchedEpisodes, strType) + ", 0) > 0 THEN 0 ELSE 1 END " + parameter;
787 else if (m_field == FieldTag)
788 query = negate + " EXISTS (SELECT 1 FROM taglinks JOIN tag ON tag.idTag = taglinks.idTag WHERE taglinks.idMedia = " + GetField(FieldId, strType) + " AND tag.strTag" + parameter + " AND taglinks.media_type = 'tvshow')";
790 else if (strType == "episodes")
792 table = "episodeview";
794 if (m_field == FieldGenre)
795 query = negate + " EXISTS (SELECT 1 FROM genrelinktvshow JOIN genre ON genre.idGenre=genrelinktvshow.idGenre WHERE genrelinktvshow.idShow = " + table + ".idShow AND genre.strGenre" + parameter + ")";
796 else if (m_field == FieldDirector)
797 query = negate + " EXISTS (SELECT 1 FROM directorlinkepisode JOIN actors ON actors.idActor=directorlinkepisode.idDirector WHERE directorlinkepisode.idEpisode = " + GetField(FieldId, strType) + " AND actors.strActor" + parameter + ")";
798 else if (m_field == FieldActor)
799 query = negate + " EXISTS (SELECT 1 FROM actorlinkepisode JOIN actors ON actors.idActor=actorlinkepisode.idActor WHERE actorlinkepisode.idEpisode = " + GetField(FieldId, strType) + " AND actors.strActor" + parameter + ")";
800 else if (m_field == FieldWriter)
801 query = negate + " EXISTS (SELECT 1 FROM writerlinkepisode JOIN actors ON actors.idActor=writerlinkepisode.idWriter WHERE writerlinkepisode.idEpisode = " + GetField(FieldId, strType) + " AND actors.strActor" + parameter + ")";
802 else if ((m_field == FieldLastPlayed || m_field == FieldDateAdded) && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
803 query = GetField(m_field, strType) + " IS NULL OR " + GetField(m_field, strType) + parameter;
804 else if (m_field == FieldStudio)
805 query = negate + " (" + GetField(m_field, strType) + parameter + ")";
806 else if (m_field == FieldMPAA)
807 query = negate + " (" + GetField(m_field, strType) + parameter + ")";
809 if (m_field == FieldVideoResolution)
810 query = table + ".idFile" + negate + GetVideoResolutionQuery(param);
811 else if (m_field == FieldAudioChannels)
812 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND iAudioChannels " + parameter + ")";
813 else if (m_field == FieldVideoCodec)
814 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strVideoCodec " + parameter + ")";
815 else if (m_field == FieldAudioCodec)
816 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strAudioCodec " + parameter + ")";
817 else if (m_field == FieldAudioLanguage)
818 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strAudioLanguage " + parameter + ")";
819 else if (m_field == FieldSubtitleLanguage)
820 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND strSubtitleLanguage " + parameter + ")";
821 else if (m_field == FieldVideoAspectRatio)
822 query = negate + " EXISTS (SELECT 1 FROM streamdetails WHERE streamdetails.idFile = " + table + ".idFile AND fVideoAspect " + parameter + ")";
823 if (m_field == FieldPlaycount && strType != "songs" && strType != "albums" && strType != "tvshows")
824 { // playcount IS stored as NULL OR number IN video db
825 if ((m_operator == OPERATOR_EQUALS && param == "0") ||
826 (m_operator == OPERATOR_DOES_NOT_EQUAL && param != "0") ||
827 (m_operator == OPERATOR_LESS_THAN))
829 CStdString field = GetField(FieldPlaycount, strType);
830 query = field + " IS NULL OR " + field + parameter;
834 query = CDatabaseQueryRule::FormatWhereClause(negate, oper, param, db, strType);
838 CStdString CSmartPlaylistRule::GetField(int field, const CStdString &type) const
840 if (field >= FieldUnknown && field < FieldMax)
841 return DatabaseUtils::GetField((Field)field, DatabaseUtils::MediaTypeFromString(type), DatabaseQueryPartWhere);
845 CStdString CSmartPlaylistRuleCombination::GetWhereClause(const CDatabase &db, const CStdString& strType, std::set<CStdString> &referencedPlaylists) const
847 CStdString rule, currentRule;
849 // translate the combinations into SQL
850 for (CDatabaseQueryRuleCombinations::const_iterator it = m_combinations.begin(); it != m_combinations.end(); ++it)
852 if (it != m_combinations.begin())
853 rule += m_type == CombinationAnd ? " AND " : " OR ";
854 boost::shared_ptr<CSmartPlaylistRuleCombination> combo = boost::static_pointer_cast<CSmartPlaylistRuleCombination>(*it);
856 rule += "(" + combo->GetWhereClause(db, strType, referencedPlaylists) + ")";
859 // translate the rules into SQL
860 for (CDatabaseQueryRules::const_iterator it = m_rules.begin(); it != m_rules.end(); ++it)
862 // don't include playlists that are meant to be displayed
863 // as a virtual folders in the SQL WHERE clause
864 if ((*it)->m_field == FieldVirtualFolder)
868 rule += m_type == CombinationAnd ? " AND " : " OR ";
870 CStdString currentRule;
871 if ((*it)->m_field == FieldPlaylist)
873 CStdString playlistFile = CSmartPlaylistDirectory::GetPlaylistByName((*it)->m_parameter.at(0), strType);
874 if (!playlistFile.empty() && referencedPlaylists.find(playlistFile) == referencedPlaylists.end())
876 referencedPlaylists.insert(playlistFile);
877 CSmartPlaylist playlist;
878 if (playlist.Load(playlistFile))
880 CStdString playlistQuery;
881 // only playlists of same type will be part of the query
882 if (playlist.GetType().Equals(strType) || (playlist.GetType().Equals("mixed") && (strType == "songs" || strType == "musicvideos")) || playlist.GetType().empty())
884 playlist.SetType(strType);
885 playlistQuery = playlist.GetWhereClause(db, referencedPlaylists);
887 if (playlist.GetType().Equals(strType))
889 if ((*it)->m_operator == CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL)
890 currentRule = StringUtils::Format("NOT (%s)", playlistQuery.c_str());
892 currentRule = playlistQuery;
898 currentRule = (*it)->GetWhereClause(db, strType);
899 // if we don't get a rule, we add '1' or '0' so the query is still valid and doesn't fail
900 if (currentRule.empty())
901 currentRule = m_type == CombinationAnd ? "'1'" : "'0'";
909 void CSmartPlaylistRuleCombination::GetVirtualFolders(const CStdString& strType, std::vector<CStdString> &virtualFolders) const
911 for (CDatabaseQueryRuleCombinations::const_iterator it = m_combinations.begin(); it != m_combinations.end(); ++it)
913 boost::shared_ptr<CSmartPlaylistRuleCombination> combo = boost::static_pointer_cast<CSmartPlaylistRuleCombination>(*it);
915 combo->GetVirtualFolders(strType, virtualFolders);
918 for (CDatabaseQueryRules::const_iterator it = m_rules.begin(); it != m_rules.end(); ++it)
920 if (((*it)->m_field != FieldVirtualFolder && (*it)->m_field != FieldPlaylist) || (*it)->m_operator != CDatabaseQueryRule::OPERATOR_EQUALS)
923 CStdString playlistFile = CSmartPlaylistDirectory::GetPlaylistByName((*it)->m_parameter.at(0), strType);
924 if (playlistFile.empty())
927 if ((*it)->m_field == FieldVirtualFolder)
928 virtualFolders.push_back(playlistFile);
931 // look for any virtual folders in the expanded playlists
932 CSmartPlaylist playlist;
933 if (!playlist.Load(playlistFile))
936 if (CSmartPlaylist::CheckTypeCompatibility(playlist.GetType(), strType))
937 playlist.GetVirtualFolders(virtualFolders);
942 void CSmartPlaylistRuleCombination::AddRule(const CSmartPlaylistRule &rule)
944 boost::shared_ptr<CSmartPlaylistRule> ptr(new CSmartPlaylistRule(rule));
945 m_rules.push_back(ptr);
948 CSmartPlaylist::CSmartPlaylist()
953 bool CSmartPlaylist::OpenAndReadName(const CStdString &path)
955 if (readNameFromPath(path) == NULL)
958 return !m_playlistName.empty();
961 const TiXmlNode* CSmartPlaylist::readName(const TiXmlNode *root)
966 const TiXmlElement *rootElem = root->ToElement();
967 if (rootElem == NULL)
970 if (!root || !StringUtils::EqualsNoCase(root->Value(),"smartplaylist"))
972 CLog::Log(LOGERROR, "Error loading Smart playlist");
976 // load the playlist type
977 const char* type = rootElem->Attribute("type");
979 m_playlistType = type;
980 // backward compatibility:
981 if (m_playlistType == "music")
982 m_playlistType = "songs";
983 if (m_playlistType == "video")
984 m_playlistType = "musicvideos";
986 // load the playlist name
987 XMLUtils::GetString(root, "name", m_playlistName);
992 const TiXmlNode* CSmartPlaylist::readNameFromPath(const CStdString &path)
995 if (!file.Open(path))
997 CLog::Log(LOGERROR, "Error loading Smart playlist %s (failed to read file)", path.c_str());
1004 const TiXmlNode *root = readName(m_xmlDoc.RootElement());
1005 if (m_playlistName.empty())
1007 m_playlistName = CUtil::GetTitleFromPath(path);
1008 if (URIUtils::HasExtension(m_playlistName, ".xsp"))
1009 URIUtils::RemoveExtension(m_playlistName);
1015 const TiXmlNode* CSmartPlaylist::readNameFromXml(const CStdString &xml)
1019 CLog::Log(LOGERROR, "Error loading empty Smart playlist");
1024 if (!m_xmlDoc.Parse(xml))
1026 CLog::Log(LOGERROR, "Error loading Smart playlist (failed to parse xml: %s)", m_xmlDoc.ErrorDesc());
1030 const TiXmlNode *root = readName(m_xmlDoc.RootElement());
1035 bool CSmartPlaylist::load(const TiXmlNode *root)
1040 return LoadFromXML(root);
1043 bool CSmartPlaylist::Load(const CStdString &path)
1045 return load(readNameFromPath(path));
1048 bool CSmartPlaylist::Load(const CVariant &obj)
1050 if (!obj.isObject())
1053 // load the playlist type
1054 if (obj.isMember("type") && obj["type"].isString())
1055 m_playlistType = obj["type"].asString();
1057 // backward compatibility
1058 if (m_playlistType == "music")
1059 m_playlistType = "songs";
1060 if (m_playlistType == "video")
1061 m_playlistType = "musicvideos";
1063 // load the playlist name
1064 if (obj.isMember("name") && obj["name"].isString())
1065 m_playlistName = obj["name"].asString();
1067 if (obj.isMember("rules"))
1068 m_ruleCombination.Load(obj["rules"], this);
1070 if (obj.isMember("group") && obj["group"].isMember("type") && obj["group"]["type"].isString())
1072 m_group = obj["group"]["type"].asString();
1073 if (obj["group"].isMember("mixed") && obj["group"]["mixed"].isBoolean())
1074 m_groupMixed = obj["group"]["mixed"].asBoolean();
1078 if (obj.isMember("limit") && (obj["limit"].isInteger() || obj["limit"].isUnsignedInteger()) && obj["limit"].asUnsignedInteger() > 0)
1079 m_limit = (unsigned int)obj["limit"].asUnsignedInteger();
1082 if (obj.isMember("order") && obj["order"].isMember("method") && obj["order"]["method"].isString())
1084 const CVariant &order = obj["order"];
1085 if (order.isMember("direction") && order["direction"].isString())
1086 m_orderDirection = StringUtils::EqualsNoCase(order["direction"].asString(), "ascending") ? SortOrderAscending : SortOrderDescending;
1088 if (order.isMember("ignorefolders") && obj["ignorefolders"].isBoolean())
1089 m_orderAttributes = obj["ignorefolders"].asBoolean() ? SortAttributeIgnoreFolders : SortAttributeNone;
1091 m_orderField = CSmartPlaylistRule::TranslateOrder(obj["order"]["method"].asString().c_str());
1097 bool CSmartPlaylist::LoadFromXml(const CStdString &xml)
1099 return load(readNameFromXml(xml));
1102 bool CSmartPlaylist::LoadFromXML(const TiXmlNode *root, const CStdString &encoding)
1108 if (XMLUtils::GetString(root, "match", tmp))
1109 m_ruleCombination.SetType(StringUtils::EqualsNoCase(tmp, "all") ? CSmartPlaylistRuleCombination::CombinationAnd : CSmartPlaylistRuleCombination::CombinationOr);
1112 const TiXmlNode *ruleNode = root->FirstChild("rule");
1115 CSmartPlaylistRule rule;
1116 if (rule.Load(ruleNode, encoding))
1117 m_ruleCombination.AddRule(rule);
1119 ruleNode = ruleNode->NextSibling("rule");
1122 const TiXmlElement *groupElement = root->FirstChildElement("group");
1123 if (groupElement != NULL && groupElement->FirstChild() != NULL)
1125 m_group = groupElement->FirstChild()->ValueStr();
1126 const char* mixed = groupElement->Attribute("mixed");
1127 m_groupMixed = mixed != NULL && StringUtils::EqualsNoCase(mixed, "true");
1131 // format is <limit>25</limit>
1132 XMLUtils::GetUInt(root, "limit", m_limit);
1135 // format is <order direction="ascending">field</order>
1136 const TiXmlElement *order = root->FirstChildElement("order");
1137 if (order && order->FirstChild())
1139 const char *direction = order->Attribute("direction");
1141 m_orderDirection = StringUtils::EqualsNoCase(direction, "ascending") ? SortOrderAscending : SortOrderDescending;
1143 const char *ignorefolders = order->Attribute("ignorefolders");
1144 if (ignorefolders != NULL)
1145 m_orderAttributes = StringUtils::EqualsNoCase(ignorefolders, "true") ? SortAttributeIgnoreFolders : SortAttributeNone;
1147 m_orderField = CSmartPlaylistRule::TranslateOrder(order->FirstChild()->Value());
1152 bool CSmartPlaylist::LoadFromJson(const CStdString &json)
1157 CVariant obj = CJSONVariantParser::Parse((const unsigned char *)json.c_str(), json.size());
1161 bool CSmartPlaylist::Save(const CStdString &path) const
1164 TiXmlDeclaration decl("1.0", "UTF-8", "yes");
1165 doc.InsertEndChild(decl);
1167 TiXmlElement xmlRootElement("smartplaylist");
1168 xmlRootElement.SetAttribute("type",m_playlistType.c_str());
1169 TiXmlNode *pRoot = doc.InsertEndChild(xmlRootElement);
1173 // add the <name> tag
1174 XMLUtils::SetString(pRoot, "name", m_playlistName);
1176 // add the <match> tag
1177 XMLUtils::SetString(pRoot, "match", m_ruleCombination.GetType() == CSmartPlaylistRuleCombination::CombinationAnd ? "all" : "one");
1180 m_ruleCombination.Save(pRoot);
1182 // add <group> tag if necessary
1183 if (!m_group.empty())
1185 TiXmlElement nodeGroup("group");
1187 nodeGroup.SetAttribute("mixed", "true");
1188 TiXmlText group(m_group.c_str());
1189 nodeGroup.InsertEndChild(group);
1190 pRoot->InsertEndChild(nodeGroup);
1195 XMLUtils::SetInt(pRoot, "limit", m_limit);
1198 if (m_orderField != SortByNone)
1200 TiXmlText order(CSmartPlaylistRule::TranslateOrder(m_orderField).c_str());
1201 TiXmlElement nodeOrder("order");
1202 nodeOrder.SetAttribute("direction", m_orderDirection == SortOrderDescending ? "descending" : "ascending");
1203 if (m_orderAttributes & SortAttributeIgnoreFolders)
1204 nodeOrder.SetAttribute("ignorefolders", "true");
1205 nodeOrder.InsertEndChild(order);
1206 pRoot->InsertEndChild(nodeOrder);
1208 return doc.SaveFile(path);
1211 bool CSmartPlaylist::Save(CVariant &obj, bool full /* = true */) const
1213 if (obj.type() == CVariant::VariantTypeConstNull)
1218 obj["type"] = m_playlistType;
1221 CVariant rulesObj = CVariant(CVariant::VariantTypeObject);
1222 if (m_ruleCombination.Save(rulesObj))
1223 obj["rules"] = rulesObj;
1226 if (!m_group.empty())
1228 obj["group"]["type"] = m_group;
1229 obj["group"]["mixed"] = m_groupMixed;
1233 if (full && m_limit)
1234 obj["limit"] = m_limit;
1237 if (full && m_orderField != SortByNone)
1239 obj["order"] = CVariant(CVariant::VariantTypeObject);
1240 obj["order"]["method"] = CSmartPlaylistRule::TranslateOrder(m_orderField);
1241 obj["order"]["direction"] = m_orderDirection == SortOrderDescending ? "descending" : "ascending";
1242 obj["order"]["ignorefolders"] = (m_orderAttributes & SortAttributeIgnoreFolders);
1248 bool CSmartPlaylist::SaveAsJson(CStdString &json, bool full /* = true */) const
1250 CVariant xsp(CVariant::VariantTypeObject);
1251 if (!Save(xsp, full))
1254 json = CJSONVariantWriter::Write(xsp, true);
1255 return json.size() > 0;
1258 void CSmartPlaylist::Reset()
1260 m_ruleCombination.clear();
1262 m_orderField = SortByNone;
1263 m_orderDirection = SortOrderNone;
1264 m_orderAttributes = SortAttributeNone;
1265 m_playlistType = "songs"; // sane default
1267 m_groupMixed = false;
1270 void CSmartPlaylist::SetName(const CStdString &name)
1272 m_playlistName = name;
1275 void CSmartPlaylist::SetType(const CStdString &type)
1277 m_playlistType = type;
1280 bool CSmartPlaylist::IsVideoType() const
1282 return IsVideoType(m_playlistType);
1285 bool CSmartPlaylist::IsMusicType() const
1287 return IsMusicType(m_playlistType);
1290 bool CSmartPlaylist::IsVideoType(const CStdString &type)
1292 return type == "movies" || type == "tvshows" || type == "episodes" ||
1293 type == "musicvideos" || type == "mixed";
1296 bool CSmartPlaylist::IsMusicType(const CStdString &type)
1298 return type == "artists" || type == "albums" ||
1299 type == "songs" || type == "mixed";
1302 CStdString CSmartPlaylist::GetWhereClause(const CDatabase &db, set<CStdString> &referencedPlaylists) const
1304 return m_ruleCombination.GetWhereClause(db, GetType(), referencedPlaylists);
1307 void CSmartPlaylist::GetVirtualFolders(std::vector<CStdString> &virtualFolders) const
1309 m_ruleCombination.GetVirtualFolders(GetType(), virtualFolders);
1312 CStdString CSmartPlaylist::GetSaveLocation() const
1314 if (m_playlistType == "mixed")
1318 // all others are video
1322 void CSmartPlaylist::GetAvailableFields(const std::string &type, std::vector<std::string> &fieldList)
1324 vector<Field> typeFields = CSmartPlaylistRule::GetFields(type);
1325 for (vector<Field>::const_iterator field = typeFields.begin(); field != typeFields.end(); field++)
1327 for (unsigned int i = 0; i < NUM_FIELDS; i++)
1329 if (*field == fields[i].field)
1330 fieldList.push_back(fields[i].string);
1335 bool CSmartPlaylist::IsEmpty(bool ignoreSortAndLimit /* = true */) const
1337 bool empty = m_ruleCombination.empty();
1338 if (empty && !ignoreSortAndLimit)
1339 empty = m_limit <= 0 && m_orderField == SortByNone && m_orderDirection == SortOrderNone;
1344 bool CSmartPlaylist::CheckTypeCompatibility(const CStdString &typeLeft, const CStdString &typeRight)
1346 if (typeLeft.Equals(typeRight))
1349 if (typeLeft.Equals("mixed") &&
1350 (typeRight.Equals("songs") || typeRight.Equals("musicvideos")))
1353 if (typeRight.Equals("mixed") &&
1354 (typeLeft.Equals("songs") || typeLeft.Equals("musicvideos")))
1360 CDatabaseQueryRule *CSmartPlaylist::CreateRule() const
1362 return new CSmartPlaylistRule();
1364 CDatabaseQueryRuleCombination *CSmartPlaylist::CreateCombination() const
1366 return new CSmartPlaylistRuleCombination();