Merge pull request #4680 from MartijnKaijser/13.1_b1
[vuplus_xbmc] / xbmc / playlists / SmartPlayList.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
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/>.
18  *
19  */
20
21 #include "SmartPlayList.h"
22 #include "Util.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"
38
39 using namespace std;
40 using namespace XFILE;
41
42 typedef struct
43 {
44   char string[17];
45   Field field;
46   SortBy sort;
47   CDatabaseQueryRule::FIELD_TYPE type;
48   StringValidation::Validator validator;
49   bool browseable;
50   int localizedString;
51 } translateField;
52
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 }
115 };
116
117 static const size_t NUM_FIELDS = sizeof(fields) / sizeof(translateField);
118
119 typedef struct
120 {
121   std::string name;
122   Field field;
123   bool canMix;
124   int localizedString;
125 } group;
126
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 },
140                               };
141
142 static const size_t NUM_GROUPS = sizeof(groups) / sizeof(group);
143
144 #define RULE_VALUE_SEPARATOR  " / "
145
146 CSmartPlaylistRule::CSmartPlaylistRule()
147 {
148 }
149
150 int CSmartPlaylistRule::TranslateField(const char *field) const
151 {
152   for (unsigned int i = 0; i < NUM_FIELDS; i++)
153     if (StringUtils::EqualsNoCase(field, fields[i].string)) return fields[i].field;
154   return FieldNone;
155 }
156
157 CStdString CSmartPlaylistRule::TranslateField(int field) const
158 {
159   for (unsigned int i = 0; i < NUM_FIELDS; i++)
160     if (field == fields[i].field) return fields[i].string;
161   return "none";
162 }
163
164 SortBy CSmartPlaylistRule::TranslateOrder(const char *order)
165 {
166   for (unsigned int i = 0; i < NUM_FIELDS; i++)
167     if (StringUtils::EqualsNoCase(order, fields[i].string)) return fields[i].sort;
168   return SortByNone;
169 }
170
171 CStdString CSmartPlaylistRule::TranslateOrder(SortBy order)
172 {
173   for (unsigned int i = 0; i < NUM_FIELDS; i++)
174     if (order == fields[i].sort) return fields[i].string;
175   return "none";
176 }
177
178 Field CSmartPlaylistRule::TranslateGroup(const char *group)
179 {
180   for (unsigned int i = 0; i < NUM_GROUPS; i++)
181   {
182     if (StringUtils::EqualsNoCase(group, groups[i].name))
183       return groups[i].field;
184   }
185
186   return FieldUnknown;
187 }
188
189 CStdString CSmartPlaylistRule::TranslateGroup(Field group)
190 {
191   for (unsigned int i = 0; i < NUM_GROUPS; i++)
192   {
193     if (group == groups[i].field)
194       return groups[i].name;
195   }
196
197   return "";
198 }
199
200 CStdString CSmartPlaylistRule::GetLocalizedField(int field)
201 {
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);
205 }
206
207 CDatabaseQueryRule::FIELD_TYPE CSmartPlaylistRule::GetFieldType(int field) const
208 {
209   for (unsigned int i = 0; i < NUM_FIELDS; i++)
210     if (field == fields[i].field) return fields[i].type;
211   return TEXT_FIELD;
212 }
213
214 bool CSmartPlaylistRule::IsFieldBrowseable(int field)
215 {
216   for (unsigned int i = 0; i < NUM_FIELDS; i++)
217     if (field == fields[i].field) return fields[i].browseable;
218
219   return false;
220 }
221
222 bool CSmartPlaylistRule::Validate(const std::string &input, void *data)
223 {
224   if (data == NULL)
225     return true;
226
227   CSmartPlaylistRule *rule = (CSmartPlaylistRule*)data;
228
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++)
232   {
233     if (rule->m_field == fields[i].field)
234     {
235         validator = fields[i].validator;
236         break;
237     }
238   }
239   if (validator == NULL)
240     return true;
241
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)
245   {
246     if (!validator(*it, data))
247       return false;
248   }
249
250   return true;
251 }
252
253 bool CSmartPlaylistRule::ValidateRating(const std::string &input, void *data)
254 {
255   char *end = NULL;
256   string strRating = input;
257   StringUtils::Trim(strRating);
258
259   double rating = strtod(strRating.c_str(), &end);
260   return (end == NULL || *end == '\0') &&
261          rating >= 0.0 && rating <= 10.0;
262 }
263
264 vector<Field> CSmartPlaylistRule::GetFields(const CStdString &type)
265 {
266   vector<Field> fields;
267   bool isVideo = false;
268   if (type == "songs")
269   {
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);
284   }
285   else if (type == "albums")
286   {
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);
300   }
301   else if (type == "artists")
302   {
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);
313   }
314   else if (type == "tvshows")
315   {
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);
335   }
336   else if (type == "episodes")
337   {
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);
360     isVideo = true;
361   }
362   else if (type == "movies")
363   {
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);
389     isVideo = true;
390   }
391   else if (type == "musicvideos")
392   {
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);
408     isVideo = true;
409   }
410   if (isVideo)
411   {
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);
419   }
420   fields.push_back(FieldPlaylist);
421   fields.push_back(FieldVirtualFolder);
422   
423   return fields;
424 }
425
426 std::vector<SortBy> CSmartPlaylistRule::GetOrders(const CStdString &type)
427 {
428   vector<SortBy> orders;
429   orders.push_back(SortByNone);
430   if (type == "songs")
431   {
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);
444   }
445   else if (type == "albums")
446   {
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);
458   }
459   else if (type == "artists")
460   {
461     orders.push_back(SortByArtist);
462   }
463   else if (type == "tvshows")
464   {
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);
479   }
480   else if (type == "episodes")
481   {
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);
497   }
498   else if (type == "movies")
499   {
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);
515   }
516   else if (type == "musicvideos")
517   {
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);
530   }
531   orders.push_back(SortByRandom);
532         
533   return orders;
534 }
535
536 std::vector<Field> CSmartPlaylistRule::GetGroups(const CStdString &type)
537 {
538   vector<Field> groups;
539   groups.push_back(FieldUnknown);
540
541   if (type == "artists")
542     groups.push_back(FieldGenre);
543   else if (type == "albums")
544     groups.push_back(FieldYear);
545   if (type == "movies")
546   {
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);
557   }
558   else if (type == "tvshows")
559   {
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);
566   }
567   else if (type == "musicvideos")
568   {
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);
576   }
577
578   return groups;
579 }
580
581 CStdString CSmartPlaylistRule::GetLocalizedGroup(Field group)
582 {
583   for (unsigned int i = 0; i < NUM_GROUPS; i++)
584   {
585     if (group == groups[i].field)
586       return g_localizeStrings.Get(groups[i].localizedString);
587   }
588
589   return g_localizeStrings.Get(groups[0].localizedString);
590 }
591
592 bool CSmartPlaylistRule::CanGroupMix(Field group)
593 {
594   for (unsigned int i = 0; i < NUM_GROUPS; i++)
595   {
596     if (group == groups[i].field)
597       return groups[i].canMix;
598   }
599
600   return false;
601 }
602
603 CStdString CSmartPlaylistRule::GetLocalizedRule() const
604 {
605   return StringUtils::Format("%s %s %s", GetLocalizedField(m_field).c_str(), GetLocalizedOperator(m_operator).c_str(), GetParameter().c_str());
606 }
607
608 CStdString CSmartPlaylistRule::GetVideoResolutionQuery(const CStdString &parameter) const
609 {
610   CStdString retVal(" IN (SELECT DISTINCT idFile FROM streamdetails WHERE iVideoWidth ");
611   int iRes = (int)strtol(parameter.c_str(), NULL, 10);
612
613   int min, max;
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; }
618
619   switch (m_operator)
620   {
621     case OPERATOR_EQUALS:
622       retVal += StringUtils::Format(">= %i AND iVideoWidth <= %i", min, max);
623       break;
624     case OPERATOR_DOES_NOT_EQUAL:
625       retVal += StringUtils::Format("< %i OR iVideoWidth > %i", min, max);
626       break;
627     case OPERATOR_LESS_THAN:
628       retVal += StringUtils::Format("< %i", min);
629       break;
630     case OPERATOR_GREATER_THAN:
631       retVal += StringUtils::Format("> %i", max);
632       break;
633     default:
634       break;
635   }
636
637   retVal += ")";
638   return retVal;
639 }
640
641 CStdString CSmartPlaylistRule::GetBooleanQuery(const CStdString &negate, const CStdString &strType) const
642 {
643   if (strType == "movies")
644   {
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) + "!= ''";
649   }
650   else if (strType == "episodes")
651   {
652     if (m_field == FieldInProgress)
653       return "episodeview.idFile " + negate + " IN (SELECT DISTINCT idFile FROM bookmark WHERE type = 1)";
654   }
655   else if (strType == "tvshows")
656   {
657     if (m_field == FieldInProgress)
658       return negate + " ("
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)"
662                           ")"
663                        ")";
664   }
665   return "";
666 }
667
668 CDatabaseQueryRule::SEARCH_OPERATOR CSmartPlaylistRule::GetOperator(const CStdString &strType) const
669 {
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;
678   }
679   return op;
680 }
681
682 CStdString CSmartPlaylistRule::FormatParameter(const CStdString &operatorString, const CStdString &param, const CDatabase &db, const CStdString &strType) const
683 {
684   // special-casing
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());
689   }
690   return CDatabaseQueryRule::FormatParameter(operatorString, param, db, strType);
691 }
692
693 CStdString CSmartPlaylistRule::FormatWhereClause(const CStdString &negate, const CStdString &oper, const CStdString &param,
694                                                  const CDatabase &db, const CStdString &strType) const
695 {
696   CStdString parameter = FormatParameter(oper, param, db, strType);
697
698   CStdString query;
699   CStdString table;
700   if (strType == "songs")
701   {
702     table = "songview";
703
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;
712   }
713   else if (strType == "albums")
714   {
715     table = "albumview";
716
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 + ")";
723   }
724   else if (strType == "artists")
725   {
726     table = "artistview";
727
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 + ")";
730   }
731   else if (strType == "movies")
732   {
733     table = "movieview";
734
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')";
751   }
752   else if (strType == "musicvideos")
753   {
754     table = "musicvideoview";
755
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')";
768   }
769   else if (strType == "tvshows")
770   {
771     table = "tvshowview";
772
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')";
789   }
790   else if (strType == "episodes")
791   {
792     table = "episodeview";
793
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 + ")";
808   }
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))
828     {
829       CStdString field = GetField(FieldPlaycount, strType);
830       query = field + " IS NULL OR " + field + parameter;
831     }
832   }
833   if (query.empty())
834     query = CDatabaseQueryRule::FormatWhereClause(negate, oper, param, db, strType);
835   return query;
836 }
837
838 CStdString CSmartPlaylistRule::GetField(int field, const CStdString &type) const
839 {
840   if (field >= FieldUnknown && field < FieldMax)
841     return DatabaseUtils::GetField((Field)field, DatabaseUtils::MediaTypeFromString(type), DatabaseQueryPartWhere);
842   return "";
843 }
844
845 CStdString CSmartPlaylistRuleCombination::GetWhereClause(const CDatabase &db, const CStdString& strType, std::set<CStdString> &referencedPlaylists) const
846 {
847   CStdString rule, currentRule;
848   
849   // translate the combinations into SQL
850   for (CDatabaseQueryRuleCombinations::const_iterator it = m_combinations.begin(); it != m_combinations.end(); ++it)
851   {
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);
855     if (combo)
856       rule += "(" + combo->GetWhereClause(db, strType, referencedPlaylists) + ")";
857   }
858
859   // translate the rules into SQL
860   for (CDatabaseQueryRules::const_iterator it = m_rules.begin(); it != m_rules.end(); ++it)
861   {
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)
865       continue;
866
867     if (!rule.empty())
868       rule += m_type == CombinationAnd ? " AND " : " OR ";
869     rule += "(";
870     CStdString currentRule;
871     if ((*it)->m_field == FieldPlaylist)
872     {
873       CStdString playlistFile = CSmartPlaylistDirectory::GetPlaylistByName((*it)->m_parameter.at(0), strType);
874       if (!playlistFile.empty() && referencedPlaylists.find(playlistFile) == referencedPlaylists.end())
875       {
876         referencedPlaylists.insert(playlistFile);
877         CSmartPlaylist playlist;
878         if (playlist.Load(playlistFile))
879         {
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())
883           {
884             playlist.SetType(strType);
885             playlistQuery = playlist.GetWhereClause(db, referencedPlaylists);
886           }
887           if (playlist.GetType().Equals(strType))
888           {
889             if ((*it)->m_operator == CDatabaseQueryRule::OPERATOR_DOES_NOT_EQUAL)
890               currentRule = StringUtils::Format("NOT (%s)", playlistQuery.c_str());
891             else
892               currentRule = playlistQuery;
893           }
894         }
895       }
896     }
897     else
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'";
902     rule += currentRule;
903     rule += ")";
904   }
905
906   return rule;
907 }
908
909 void CSmartPlaylistRuleCombination::GetVirtualFolders(const CStdString& strType, std::vector<CStdString> &virtualFolders) const
910 {
911   for (CDatabaseQueryRuleCombinations::const_iterator it = m_combinations.begin(); it != m_combinations.end(); ++it)
912   {
913     boost::shared_ptr<CSmartPlaylistRuleCombination> combo = boost::static_pointer_cast<CSmartPlaylistRuleCombination>(*it);
914     if (combo)
915       combo->GetVirtualFolders(strType, virtualFolders);
916   }
917
918   for (CDatabaseQueryRules::const_iterator it = m_rules.begin(); it != m_rules.end(); ++it)
919   {
920     if (((*it)->m_field != FieldVirtualFolder && (*it)->m_field != FieldPlaylist) || (*it)->m_operator != CDatabaseQueryRule::OPERATOR_EQUALS)
921       continue;
922
923     CStdString playlistFile = CSmartPlaylistDirectory::GetPlaylistByName((*it)->m_parameter.at(0), strType);
924     if (playlistFile.empty())
925       continue;
926
927     if ((*it)->m_field == FieldVirtualFolder)
928       virtualFolders.push_back(playlistFile);
929     else
930     {
931       // look for any virtual folders in the expanded playlists
932       CSmartPlaylist playlist;
933       if (!playlist.Load(playlistFile))
934         continue;
935
936       if (CSmartPlaylist::CheckTypeCompatibility(playlist.GetType(), strType))
937         playlist.GetVirtualFolders(virtualFolders);
938     }
939   }
940 }
941
942 void CSmartPlaylistRuleCombination::AddRule(const CSmartPlaylistRule &rule)
943 {
944   boost::shared_ptr<CSmartPlaylistRule> ptr(new CSmartPlaylistRule(rule));
945   m_rules.push_back(ptr);
946 }
947
948 CSmartPlaylist::CSmartPlaylist()
949 {
950   Reset();
951 }
952
953 bool CSmartPlaylist::OpenAndReadName(const CStdString &path)
954 {
955   if (readNameFromPath(path) == NULL)
956     return false;
957
958   return !m_playlistName.empty();
959 }
960
961 const TiXmlNode* CSmartPlaylist::readName(const TiXmlNode *root)
962 {
963   if (root == NULL)
964     return NULL;
965
966   const TiXmlElement *rootElem = root->ToElement();
967   if (rootElem == NULL)
968     return NULL;
969
970   if (!root || !StringUtils::EqualsNoCase(root->Value(),"smartplaylist"))
971   {
972     CLog::Log(LOGERROR, "Error loading Smart playlist");
973     return NULL;
974   }
975
976   // load the playlist type
977   const char* type = rootElem->Attribute("type");
978   if (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";
985
986   // load the playlist name
987   XMLUtils::GetString(root, "name", m_playlistName);
988
989   return root;
990 }
991
992 const TiXmlNode* CSmartPlaylist::readNameFromPath(const CStdString &path)
993 {
994   CFileStream file;
995   if (!file.Open(path))
996   {
997     CLog::Log(LOGERROR, "Error loading Smart playlist %s (failed to read file)", path.c_str());
998     return NULL;
999   }
1000
1001   m_xmlDoc.Clear();
1002   file >> m_xmlDoc;
1003
1004   const TiXmlNode *root = readName(m_xmlDoc.RootElement());
1005   if (m_playlistName.empty())
1006   {
1007     m_playlistName = CUtil::GetTitleFromPath(path);
1008     if (URIUtils::HasExtension(m_playlistName, ".xsp"))
1009       URIUtils::RemoveExtension(m_playlistName);
1010   }
1011
1012   return root;
1013 }
1014
1015 const TiXmlNode* CSmartPlaylist::readNameFromXml(const CStdString &xml)
1016 {
1017   if (xml.empty())
1018   {
1019     CLog::Log(LOGERROR, "Error loading empty Smart playlist");
1020     return NULL;
1021   }
1022
1023   m_xmlDoc.Clear();
1024   if (!m_xmlDoc.Parse(xml))
1025   {
1026     CLog::Log(LOGERROR, "Error loading Smart playlist (failed to parse xml: %s)", m_xmlDoc.ErrorDesc());
1027     return NULL;
1028   }
1029
1030   const TiXmlNode *root = readName(m_xmlDoc.RootElement());
1031
1032   return root;
1033 }
1034
1035 bool CSmartPlaylist::load(const TiXmlNode *root)
1036 {
1037   if (root == NULL)
1038     return false;
1039
1040   return LoadFromXML(root);
1041 }
1042
1043 bool CSmartPlaylist::Load(const CStdString &path)
1044 {
1045   return load(readNameFromPath(path));
1046 }
1047
1048 bool CSmartPlaylist::Load(const CVariant &obj)
1049 {
1050   if (!obj.isObject())
1051     return false;
1052
1053   // load the playlist type
1054   if (obj.isMember("type") && obj["type"].isString())
1055     m_playlistType = obj["type"].asString();
1056
1057   // backward compatibility
1058   if (m_playlistType == "music")
1059     m_playlistType = "songs";
1060   if (m_playlistType == "video")
1061     m_playlistType = "musicvideos";
1062
1063   // load the playlist name
1064   if (obj.isMember("name") && obj["name"].isString())
1065     m_playlistName = obj["name"].asString();
1066
1067   if (obj.isMember("rules"))
1068     m_ruleCombination.Load(obj["rules"], this);
1069
1070   if (obj.isMember("group") && obj["group"].isMember("type") && obj["group"]["type"].isString())
1071   {
1072     m_group = obj["group"]["type"].asString();
1073     if (obj["group"].isMember("mixed") && obj["group"]["mixed"].isBoolean())
1074       m_groupMixed = obj["group"]["mixed"].asBoolean();
1075   }
1076
1077   // now any limits
1078   if (obj.isMember("limit") && (obj["limit"].isInteger() || obj["limit"].isUnsignedInteger()) && obj["limit"].asUnsignedInteger() > 0)
1079     m_limit = (unsigned int)obj["limit"].asUnsignedInteger();
1080
1081   // and order
1082   if (obj.isMember("order") && obj["order"].isMember("method") && obj["order"]["method"].isString())
1083   {
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;
1087
1088     if (order.isMember("ignorefolders") && obj["ignorefolders"].isBoolean())
1089       m_orderAttributes = obj["ignorefolders"].asBoolean() ? SortAttributeIgnoreFolders : SortAttributeNone;
1090
1091     m_orderField = CSmartPlaylistRule::TranslateOrder(obj["order"]["method"].asString().c_str());
1092   }
1093
1094   return true;
1095 }
1096
1097 bool CSmartPlaylist::LoadFromXml(const CStdString &xml)
1098 {
1099   return load(readNameFromXml(xml));
1100 }
1101
1102 bool CSmartPlaylist::LoadFromXML(const TiXmlNode *root, const CStdString &encoding)
1103 {
1104   if (!root)
1105     return false;
1106
1107   CStdString tmp;
1108   if (XMLUtils::GetString(root, "match", tmp))
1109     m_ruleCombination.SetType(StringUtils::EqualsNoCase(tmp, "all") ? CSmartPlaylistRuleCombination::CombinationAnd : CSmartPlaylistRuleCombination::CombinationOr);
1110
1111   // now the rules
1112   const TiXmlNode *ruleNode = root->FirstChild("rule");
1113   while (ruleNode)
1114   {
1115     CSmartPlaylistRule rule;
1116     if (rule.Load(ruleNode, encoding))
1117       m_ruleCombination.AddRule(rule);
1118
1119     ruleNode = ruleNode->NextSibling("rule");
1120   }
1121
1122   const TiXmlElement *groupElement = root->FirstChildElement("group");
1123   if (groupElement != NULL && groupElement->FirstChild() != NULL)
1124   {
1125     m_group = groupElement->FirstChild()->ValueStr();
1126     const char* mixed = groupElement->Attribute("mixed");
1127     m_groupMixed = mixed != NULL && StringUtils::EqualsNoCase(mixed, "true");
1128   }
1129
1130   // now any limits
1131   // format is <limit>25</limit>
1132   XMLUtils::GetUInt(root, "limit", m_limit);
1133
1134   // and order
1135   // format is <order direction="ascending">field</order>
1136   const TiXmlElement *order = root->FirstChildElement("order");
1137   if (order && order->FirstChild())
1138   {
1139     const char *direction = order->Attribute("direction");
1140     if (direction)
1141       m_orderDirection = StringUtils::EqualsNoCase(direction, "ascending") ? SortOrderAscending : SortOrderDescending;
1142
1143     const char *ignorefolders = order->Attribute("ignorefolders");
1144     if (ignorefolders != NULL)
1145       m_orderAttributes = StringUtils::EqualsNoCase(ignorefolders, "true") ? SortAttributeIgnoreFolders : SortAttributeNone;
1146
1147     m_orderField = CSmartPlaylistRule::TranslateOrder(order->FirstChild()->Value());
1148   }
1149   return true;
1150 }
1151
1152 bool CSmartPlaylist::LoadFromJson(const CStdString &json)
1153 {
1154   if (json.empty())
1155     return false;
1156
1157   CVariant obj = CJSONVariantParser::Parse((const unsigned char *)json.c_str(), json.size());
1158   return Load(obj);
1159 }
1160
1161 bool CSmartPlaylist::Save(const CStdString &path) const
1162 {
1163   CXBMCTinyXML doc;
1164   TiXmlDeclaration decl("1.0", "UTF-8", "yes");
1165   doc.InsertEndChild(decl);
1166
1167   TiXmlElement xmlRootElement("smartplaylist");
1168   xmlRootElement.SetAttribute("type",m_playlistType.c_str());
1169   TiXmlNode *pRoot = doc.InsertEndChild(xmlRootElement);
1170   if (!pRoot)
1171     return false;
1172
1173   // add the <name> tag
1174   XMLUtils::SetString(pRoot, "name", m_playlistName);
1175
1176   // add the <match> tag
1177   XMLUtils::SetString(pRoot, "match", m_ruleCombination.GetType() == CSmartPlaylistRuleCombination::CombinationAnd ? "all" : "one");
1178
1179   // add <rule> tags
1180   m_ruleCombination.Save(pRoot);
1181
1182   // add <group> tag if necessary
1183   if (!m_group.empty())
1184   {
1185     TiXmlElement nodeGroup("group");
1186     if (m_groupMixed)
1187       nodeGroup.SetAttribute("mixed", "true");
1188     TiXmlText group(m_group.c_str());
1189     nodeGroup.InsertEndChild(group);
1190     pRoot->InsertEndChild(nodeGroup);
1191   }
1192
1193   // add <limit> tag
1194   if (m_limit)
1195     XMLUtils::SetInt(pRoot, "limit", m_limit);
1196
1197   // add <order> tag
1198   if (m_orderField != SortByNone)
1199   {
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);
1207   }
1208   return doc.SaveFile(path);
1209 }
1210
1211 bool CSmartPlaylist::Save(CVariant &obj, bool full /* = true */) const
1212 {
1213   if (obj.type() == CVariant::VariantTypeConstNull)
1214     return false;
1215
1216   obj.clear();
1217   // add "type"
1218   obj["type"] = m_playlistType;
1219
1220   // add "rules"
1221   CVariant rulesObj = CVariant(CVariant::VariantTypeObject);
1222   if (m_ruleCombination.Save(rulesObj))
1223     obj["rules"] = rulesObj;
1224
1225   // add "group"
1226   if (!m_group.empty())
1227   {
1228     obj["group"]["type"] = m_group;
1229     obj["group"]["mixed"] = m_groupMixed;
1230   }
1231
1232   // add "limit"
1233   if (full && m_limit)
1234     obj["limit"] = m_limit;
1235
1236   // add "order"
1237   if (full && m_orderField != SortByNone)
1238   {
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);
1243   }
1244
1245   return true;
1246 }
1247
1248 bool CSmartPlaylist::SaveAsJson(CStdString &json, bool full /* = true */) const
1249 {
1250   CVariant xsp(CVariant::VariantTypeObject);
1251   if (!Save(xsp, full))
1252     return false;
1253
1254   json = CJSONVariantWriter::Write(xsp, true);
1255   return json.size() > 0;
1256 }
1257
1258 void CSmartPlaylist::Reset()
1259 {
1260   m_ruleCombination.clear();
1261   m_limit = 0;
1262   m_orderField = SortByNone;
1263   m_orderDirection = SortOrderNone;
1264   m_orderAttributes = SortAttributeNone;
1265   m_playlistType = "songs"; // sane default
1266   m_group.clear();
1267   m_groupMixed = false;
1268 }
1269
1270 void CSmartPlaylist::SetName(const CStdString &name)
1271 {
1272   m_playlistName = name;
1273 }
1274
1275 void CSmartPlaylist::SetType(const CStdString &type)
1276 {
1277   m_playlistType = type;
1278 }
1279
1280 bool CSmartPlaylist::IsVideoType() const
1281 {
1282   return IsVideoType(m_playlistType);
1283 }
1284
1285 bool CSmartPlaylist::IsMusicType() const
1286 {
1287   return IsMusicType(m_playlistType);
1288 }
1289
1290 bool CSmartPlaylist::IsVideoType(const CStdString &type)
1291 {
1292   return type == "movies" || type == "tvshows" || type == "episodes" ||
1293          type == "musicvideos" || type == "mixed";
1294 }
1295
1296 bool CSmartPlaylist::IsMusicType(const CStdString &type)
1297 {
1298   return type == "artists" || type == "albums" ||
1299          type == "songs" || type == "mixed";
1300 }
1301
1302 CStdString CSmartPlaylist::GetWhereClause(const CDatabase &db, set<CStdString> &referencedPlaylists) const
1303 {
1304   return m_ruleCombination.GetWhereClause(db, GetType(), referencedPlaylists);
1305 }
1306
1307 void CSmartPlaylist::GetVirtualFolders(std::vector<CStdString> &virtualFolders) const
1308 {
1309   m_ruleCombination.GetVirtualFolders(GetType(), virtualFolders);
1310 }
1311
1312 CStdString CSmartPlaylist::GetSaveLocation() const
1313 {
1314   if (m_playlistType == "mixed")
1315     return "mixed";
1316   if (IsMusicType())
1317     return "music";
1318   // all others are video
1319   return "video";
1320 }
1321
1322 void CSmartPlaylist::GetAvailableFields(const std::string &type, std::vector<std::string> &fieldList)
1323 {
1324   vector<Field> typeFields = CSmartPlaylistRule::GetFields(type);
1325   for (vector<Field>::const_iterator field = typeFields.begin(); field != typeFields.end(); field++)
1326   {
1327     for (unsigned int i = 0; i < NUM_FIELDS; i++)
1328     {
1329       if (*field == fields[i].field)
1330         fieldList.push_back(fields[i].string);
1331     }
1332   }
1333 }
1334
1335 bool CSmartPlaylist::IsEmpty(bool ignoreSortAndLimit /* = true */) const
1336 {
1337   bool empty = m_ruleCombination.empty();
1338   if (empty && !ignoreSortAndLimit)
1339     empty = m_limit <= 0 && m_orderField == SortByNone && m_orderDirection == SortOrderNone;
1340
1341   return empty;
1342 }
1343
1344 bool CSmartPlaylist::CheckTypeCompatibility(const CStdString &typeLeft, const CStdString &typeRight)
1345 {
1346   if (typeLeft.Equals(typeRight))
1347     return true;
1348
1349   if (typeLeft.Equals("mixed") &&
1350      (typeRight.Equals("songs") || typeRight.Equals("musicvideos")))
1351     return true;
1352
1353   if (typeRight.Equals("mixed") &&
1354      (typeLeft.Equals("songs") || typeLeft.Equals("musicvideos")))
1355     return true;
1356
1357   return false;
1358 }
1359
1360 CDatabaseQueryRule *CSmartPlaylist::CreateRule() const
1361 {
1362   return new CSmartPlaylistRule();
1363 }
1364 CDatabaseQueryRuleCombination *CSmartPlaylist::CreateCombination() const
1365 {
1366   return new CSmartPlaylistRuleCombination();
1367 }