added: Smartplaylists for tvshows and episodes. Needs decent testing + cleanup.
[xbmc:xbmc-antiquated.git] / xbmc / SmartPlaylist.cpp
1 /*
2  *      Copyright (C) 2005-2007 Team XboxMediaCenter
3  *      http://www.xboxmediacenter.com
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 GNU Make; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "stdafx.h"
23 #include "SmartPlaylist.h"
24 #include "utils/log.h"
25 #include "StringUtils.h"
26 #include "FileSystem/SmartPlaylistDirectory.h"
27 #include "utils/CharsetConverter.h"
28 #include "XMLUtils.h"
29 #include "Database.h"
30 #include "VideoDatabase.h"
31 #include "LocalizeStrings.h"
32 #include "Util.h"
33
34 using namespace std;
35 using namespace DIRECTORY;
36
37 typedef struct
38 {
39   char string[13];
40   CSmartPlaylistRule::DATABASE_FIELD field;
41   int localizedString;
42 } translateField;
43
44 static const translateField fields[] = { { "none", CSmartPlaylistRule::FIELD_NONE, 231 }, 
45                                          { "genre", CSmartPlaylistRule::FIELD_GENRE, 515 },
46                                          { "album", CSmartPlaylistRule::SONG_ALBUM, 558 },
47                                          { "albumartist", CSmartPlaylistRule::SONG_ALBUM_ARTIST, 566 },
48                                          { "artist", CSmartPlaylistRule::SONG_ARTIST, 557 },
49                                          { "title", CSmartPlaylistRule::FIELD_TITLE, 556 },
50                                          { "year", CSmartPlaylistRule::FIELD_YEAR, 562 },
51                                          { "time", CSmartPlaylistRule::FIELD_TIME, 180 },
52                                          { "tracknumber", CSmartPlaylistRule::SONG_TRACKNUMBER, 554 },
53                                          { "filename", CSmartPlaylistRule::SONG_FILENAME, 561 },
54                                          { "playcount", CSmartPlaylistRule::FIELD_PLAYCOUNT, 567 },
55                                          { "lastplayed", CSmartPlaylistRule::SONG_LASTPLAYED, 568 },
56                                          { "rating", CSmartPlaylistRule::FIELD_RATING, 563 },
57                                          { "comment", CSmartPlaylistRule::SONG_COMMENT, 569 },
58                                          { "dateadded", CSmartPlaylistRule::FIELD_DATEADDED, 570 },
59                                          { "plot", CSmartPlaylistRule::VIDEO_PLOT, 207 },
60                                          { "status", CSmartPlaylistRule::TVSHOW_STATUS, 126 },
61                                          { "votes", CSmartPlaylistRule::VIDEO_VOTES, 20350 },
62                                          { "director", CSmartPlaylistRule::VIDEO_DIRECTOR, 20339 },
63                                          { "actor", CSmartPlaylistRule::VIDEO_ACTOR, 20337 },
64                                          { "numepisodes", CSmartPlaylistRule::TVSHOW_NUMEPISODES, 20360 },
65                                          { "numwatched", CSmartPlaylistRule::TVSHOW_NUMWATCHED, 16102 },
66                                          { "writers", CSmartPlaylistRule::EPISODE_WRITER, 20417 },
67                                          { "airdate", CSmartPlaylistRule::EPISODE_AIRDATE, 20416 },
68                                          { "episode", CSmartPlaylistRule::EPISODE_EPISODE, 20359 },
69                                          { "season", CSmartPlaylistRule::EPISODE_SEASON, 20373 },
70                                          { "random", CSmartPlaylistRule::FIELD_RANDOM, 590 },
71                                          { "playlist", CSmartPlaylistRule::FIELD_PLAYLIST, 559 }
72                                        };
73
74 #define NUM_FIELDS sizeof(fields) / sizeof(translateField)
75
76 typedef struct
77 {
78   char string[15];
79   CSmartPlaylistRule::SEARCH_OPERATOR op;
80   int localizedString;
81 } operatorField;
82
83 static const operatorField operators[] = { { "contains", CSmartPlaylistRule::OPERATOR_CONTAINS, 21400 },
84                                            { "doesnotcontain", CSmartPlaylistRule::OPERATOR_DOES_NOT_CONTAIN, 21401 },
85                                            { "is", CSmartPlaylistRule::OPERATOR_EQUALS, 21402 },
86                                            { "isnot", CSmartPlaylistRule::OPERATOR_DOES_NOT_EQUAL, 21403 },
87                                            { "startswith", CSmartPlaylistRule::OPERATOR_STARTS_WITH, 21404 },
88                                            { "endswith", CSmartPlaylistRule::OPERATOR_ENDS_WITH, 21405 },
89                                            { "greaterthan", CSmartPlaylistRule::OPERATOR_GREATER_THAN, 21406 },
90                                            { "lessthan", CSmartPlaylistRule::OPERATOR_LESS_THAN, 21407 },
91                                            { "after", CSmartPlaylistRule::OPERATOR_AFTER, 21408 },
92                                            { "before", CSmartPlaylistRule::OPERATOR_BEFORE, 21409 },
93                                            { "inthelast", CSmartPlaylistRule::OPERATOR_IN_THE_LAST, 21410 },
94                                            { "notinthelast", CSmartPlaylistRule::OPERATOR_NOT_IN_THE_LAST, 21411 }
95                                          };
96
97 #define NUM_OPERATORS sizeof(operators) / sizeof(operatorField)
98
99 CSmartPlaylistRule::CSmartPlaylistRule()
100 {
101   m_field = FIELD_NONE;
102   m_operator = OPERATOR_CONTAINS;
103   m_parameter = "";
104 }
105
106 void CSmartPlaylistRule::TranslateStrings(const char *field, const char *oper, const char *parameter)
107 {
108   m_field = TranslateField(field);
109   m_operator = TranslateOperator(oper);
110   m_parameter = parameter;
111 }
112
113 TiXmlElement CSmartPlaylistRule::GetAsElement()
114 {
115   TiXmlElement rule("rule");
116   TiXmlText parameter(m_parameter.c_str());
117   rule.InsertEndChild(parameter);
118   rule.SetAttribute("field", TranslateField(m_field).c_str());
119   rule.SetAttribute("operator", TranslateOperator(m_operator).c_str());
120   return rule;
121 }
122
123 CSmartPlaylistRule::DATABASE_FIELD CSmartPlaylistRule::TranslateField(const char *field)
124 {
125   for (unsigned int i = 0; i < NUM_FIELDS; i++)
126     if (strcmpi(field, fields[i].string) == 0) return fields[i].field;
127   return FIELD_NONE;
128 }
129
130 CStdString CSmartPlaylistRule::TranslateField(DATABASE_FIELD field)
131 {
132   for (unsigned int i = 0; i < NUM_FIELDS; i++)
133     if (field == fields[i].field) return fields[i].string;
134   return "none";
135 }
136
137 CSmartPlaylistRule::SEARCH_OPERATOR CSmartPlaylistRule::TranslateOperator(const char *oper)
138 {
139   for (unsigned int i = 0; i < NUM_OPERATORS; i++)
140     if (strcmpi(oper, operators[i].string) == 0) return operators[i].op;
141   return OPERATOR_CONTAINS;
142 }
143
144 CStdString CSmartPlaylistRule::TranslateOperator(SEARCH_OPERATOR oper)
145 {
146   for (unsigned int i = 0; i < NUM_OPERATORS; i++)
147     if (oper == operators[i].op) return operators[i].string;
148   return "contains";
149 }
150
151 CStdString CSmartPlaylistRule::GetLocalizedField(DATABASE_FIELD field)
152 {
153   for (unsigned int i = 0; i < NUM_FIELDS; i++)
154     if (field == fields[i].field) return g_localizeStrings.Get(fields[i].localizedString);
155   return g_localizeStrings.Get(16018);
156 }
157
158 vector<CSmartPlaylistRule::DATABASE_FIELD> CSmartPlaylistRule::GetFields(const CStdString &type)
159 {
160   vector<DATABASE_FIELD> fields;
161   if (type == "music")
162   {
163     fields.push_back(FIELD_GENRE);
164     fields.push_back(SONG_ALBUM);
165     fields.push_back(SONG_ARTIST);
166     fields.push_back(SONG_ALBUM_ARTIST);
167     fields.push_back(FIELD_TITLE);
168     fields.push_back(FIELD_YEAR);
169     fields.push_back(FIELD_TIME);
170     fields.push_back(SONG_TRACKNUMBER);
171     fields.push_back(SONG_FILENAME);
172     fields.push_back(FIELD_PLAYCOUNT);
173     fields.push_back(SONG_LASTPLAYED);
174     fields.push_back(FIELD_RATING);
175     fields.push_back(SONG_COMMENT);
176 //    fields.push_back(FIELD_DATEADDED);  // no date added yet in db
177   }
178   else if (type == "tvshows")
179   {
180     fields.push_back(FIELD_TITLE);
181     fields.push_back(VIDEO_PLOT);
182     fields.push_back(TVSHOW_STATUS);
183     fields.push_back(VIDEO_VOTES);
184     fields.push_back(FIELD_RATING);
185     fields.push_back(FIELD_YEAR);
186     fields.push_back(FIELD_GENRE);
187     fields.push_back(VIDEO_DIRECTOR);
188     fields.push_back(VIDEO_ACTOR);
189     fields.push_back(TVSHOW_NUMEPISODES);
190     fields.push_back(TVSHOW_NUMWATCHED);
191     fields.push_back(FIELD_PLAYCOUNT);
192 //    fields.push_back(FIELD_DATEADDED);  // no date added yet in db
193   }
194   else if (type == "episodes")
195   {
196     fields.push_back(FIELD_TITLE);
197     fields.push_back(VIDEO_PLOT);
198     fields.push_back(VIDEO_VOTES);
199     fields.push_back(FIELD_RATING);
200     fields.push_back(FIELD_TIME);
201     fields.push_back(EPISODE_WRITER);
202     fields.push_back(EPISODE_AIRDATE);
203     fields.push_back(FIELD_PLAYCOUNT);
204     fields.push_back(FIELD_GENRE);
205     fields.push_back(FIELD_YEAR); // premiered
206     fields.push_back(VIDEO_DIRECTOR);
207     fields.push_back(VIDEO_ACTOR);
208     fields.push_back(EPISODE_EPISODE);
209     fields.push_back(EPISODE_SEASON);
210 //    fields.push_back(FIELD_DATEADDED);  // no date added yet in db
211   }
212   else if (type == "video")   // currently musicvideos only
213   {
214     fields.push_back(FIELD_TITLE);
215     fields.push_back(FIELD_GENRE);
216     fields.push_back(SONG_ALBUM);
217     fields.push_back(FIELD_YEAR);
218     fields.push_back(SONG_ARTIST);
219     fields.push_back(SONG_FILENAME);
220 //    fields.push_back(FIELD_DATEADDED);  // no date added yet in db
221   }
222   fields.push_back(FIELD_PLAYLIST);
223   return fields;
224 }
225
226 CStdString CSmartPlaylistRule::GetLocalizedOperator(SEARCH_OPERATOR oper)
227 {
228   for (unsigned int i = 0; i < NUM_OPERATORS; i++)
229     if (oper == operators[i].op) return g_localizeStrings.Get(operators[i].localizedString);
230   return g_localizeStrings.Get(16018);
231 }
232
233 CStdString CSmartPlaylistRule::GetLocalizedRule()
234 {
235   CStdString rule;
236   rule.Format("%s %s %s", GetLocalizedField(m_field).c_str(), GetLocalizedOperator(m_operator).c_str(), m_parameter.c_str());
237   return rule;
238 }
239
240 CStdString CSmartPlaylistRule::GetWhereClause(const CStdString& strType)
241 {
242   SEARCH_OPERATOR op = m_operator;
243   if (strType == "tvshows" || strType == "episodes" && m_field == FIELD_YEAR)
244   { // special case for premiered which is a date rather than a year
245     // TODO: SMARTPLAYLISTS do we really need this, or should we just make this field the premiered date and request a date?
246     if (op == OPERATOR_EQUALS)
247       op = OPERATOR_CONTAINS;
248     else if (op == OPERATOR_DOES_NOT_EQUAL)
249       op = OPERATOR_DOES_NOT_CONTAIN;
250   }
251   // the comparison piece
252   CStdString operatorString;
253   switch (op)
254   {
255   case OPERATOR_CONTAINS:
256     operatorString = " LIKE '%%%s%%'"; break;
257   case OPERATOR_DOES_NOT_CONTAIN:
258     operatorString = " NOT LIKE '%%%s%%'"; break;
259   case OPERATOR_EQUALS:
260     operatorString = " LIKE '%s'"; break;
261   case OPERATOR_DOES_NOT_EQUAL:
262     operatorString = " NOT LIKE '%s'"; break;
263   case OPERATOR_STARTS_WITH:
264     operatorString = " LIKE '%s%%'"; break;
265   case OPERATOR_ENDS_WITH:
266     operatorString = " LIKE '%%%s'"; break;
267   case OPERATOR_AFTER:
268   case OPERATOR_GREATER_THAN:
269   case OPERATOR_IN_THE_LAST:
270     operatorString = " > '%s'"; break;
271   case OPERATOR_BEFORE:
272   case OPERATOR_LESS_THAN:
273   case OPERATOR_NOT_IN_THE_LAST:
274     operatorString = " < '%s'"; break;
275   default:
276     break;
277   }
278   CStdString parameter = CDatabase::FormatSQL(operatorString.c_str(), m_parameter.c_str());
279   if (m_field == SONG_LASTPLAYED)
280   {
281     if (m_operator == OPERATOR_IN_THE_LAST || m_operator == OPERATOR_NOT_IN_THE_LAST)
282     { // translate time period
283       CDateTime date=CDateTime::GetCurrentDateTime();
284       CDateTimeSpan span;
285       span.SetFromPeriod(m_parameter);
286       date-=span;
287       parameter = CDatabase::FormatSQL(operatorString.c_str(), date.GetAsDBDate().c_str());
288     }
289   }
290   else if (m_field == FIELD_TIME)
291   { // translate time to seconds
292     CStdString seconds; seconds.Format("%i", StringUtils::TimeStringToSeconds(m_parameter));
293     parameter = CDatabase::FormatSQL(operatorString.c_str(), seconds.c_str());
294   }
295
296   // now the query parameter
297   CStdString query;
298   if (strType == "music")
299   {
300     if (m_field == FIELD_GENRE)
301       query = "(strGenre" + parameter + ") or (idsong IN (select idsong from genre,exgenresong where exgenresong.idgenre = genre.idgenre and genre.strGenre" + parameter + "))";
302     else if (m_field == SONG_ARTIST)
303       query = "(strArtist" + parameter + ") or (idsong IN (select idsong from artist,exartistsong where exartistsong.idartist = artist.idartist and artist.strArtist" + parameter + "))";
304     else if (m_field == SONG_ALBUM_ARTIST)
305       query = "idalbum in (select idalbum from artist,album where album.idartist=artist.idartist and artist.strArtist" + parameter + ") or idalbum in (select idalbum from artist,exartistalbum where exartistalbum.idartist = artist.idartist and artist.strArtist" + parameter + ")";
306     else if (m_field == SONG_LASTPLAYED && (m_operator == OPERATOR_LESS_THAN || m_operator == OPERATOR_BEFORE || m_operator == OPERATOR_NOT_IN_THE_LAST))
307       query = "lastPlayed is NULL or lastPlayed" + parameter;
308   }
309   if (strType == "video")
310   {
311     if (m_field == FIELD_GENRE)
312       query = "(strGenre" + parameter + ") or (idmvideo IN (select genrelinkmusicvideo.idmvideo from genre,genrelinkmusicvideo where genrelinkmusicvideo.idgenre = genre.idgenre and genre.strGenre" + parameter + "))";
313     else if (m_field == SONG_ARTIST)
314       query = "(strArtist" + parameter + ") or (idmvideo IN (select genrelinkmusicvideo.idmvideo from actors,artistlinkmusicvideo where artistlinkmusicvideo.idartist = actors.idActor and actors.strActor" + parameter + "))";
315   }
316   if (strType == "tvshows")
317   {
318     if (m_field == FIELD_GENRE) // technically don't need the join for this one, but it's consistent with the others
319       query = "idshow in (select idshow from genrelinktvshow join genre on genre.idgenre=genrelinktvshow.idgenre where genre.strGenre" + parameter + ")";
320     else if (m_field == VIDEO_DIRECTOR)
321       query = "idshow in (select idshow from directorlinktvshow join actors on actors.idactor=directorlinktvshow.iddirector where actors.strActor" + parameter + ")";
322     else if (m_field == VIDEO_ACTOR)
323       query = "idshow in (select idshow from actorlinktvshow join actors on actors.idactor=actorlinktvshow.idactor where actors.strActor" + parameter + ")";
324   }
325   if (strType == "episodes")
326   {
327     if (m_field == FIELD_GENRE) // TODO: SMARTPLAYLIST this is using show lookup as episode lookups seems to
328       query = "idshow in (select idshow from genrelinktvshow join genre on genre.idgenre=genrelinktvshow.idgenre where genre.strGenre" + parameter + ")";
329     else if (m_field == VIDEO_DIRECTOR)
330       query = "idepisode in (select idepisode from directorlinkepisode join actors on actors.idactor=directorlinkepisode.iddirector where actors.strActor" + parameter + ")";
331     else if (m_field == VIDEO_ACTOR)
332       query = "idepisode in (select idepisode from actorlinkepisode join actors on actors.idactor=actorlinkepisode.idactor where actors.strActor" + parameter + ")";
333   }
334   if (m_field == FIELD_PLAYLIST)
335   { // playlist field - grab our playlist and add to our where clause
336     CStdString playlistFile = CSmartPlaylistDirectory::GetPlaylistByName(m_parameter);
337     if (!playlistFile.IsEmpty())
338     {
339       CSmartPlaylist playlist;
340       playlist.Load(playlistFile);
341       CStdString playlistQuery = playlist.GetWhereClause(false);
342       if (m_operator == OPERATOR_DOES_NOT_EQUAL)
343         query.Format("NOT (%s)", playlistQuery.c_str());
344       else if (m_operator == OPERATOR_EQUALS)
345         query = playlistQuery;
346     }
347   }
348   else if (query.IsEmpty() && m_field != FIELD_NONE)
349     query = GetDatabaseField(m_field,strType) + parameter;
350   return query;
351 }
352
353 CStdString CSmartPlaylistRule::GetDatabaseField(DATABASE_FIELD field, const CStdString& type)
354 {
355   if (type == "music")
356   {
357     if (field == FIELD_TITLE) return "strTitle";
358     else if (field == FIELD_GENRE) return "strGenre";
359     else if (field == SONG_ALBUM) return "strAlbum";
360     else if (field == FIELD_YEAR) return "iYear";
361     else if (field == SONG_ARTIST || field == SONG_ALBUM_ARTIST) return "strArtist";
362     else if (field == FIELD_TIME) return "iDuration";
363     else if (field == FIELD_PLAYCOUNT) return "iTimesPlayed";
364     else if (field == SONG_FILENAME) return "strFilename";
365     else if (field == SONG_TRACKNUMBER) return "iTrack";
366     else if (field == SONG_LASTPLAYED) return "lastplayed";
367     else if (field == FIELD_RATING) return "rating";
368     else if (field == SONG_COMMENT) return "comment";
369     else if (field == FIELD_RANDOM) return "random()";      // only used for order clauses
370     else if (field == FIELD_DATEADDED) return "idsong";     // only used for order clauses
371   }
372   if (type == "video")
373   {
374     CStdString result;
375     if (field == FIELD_TITLE) result.Format("musicvideo.c%02d",VIDEODB_ID_MUSICVIDEO_TITLE);
376     else if (field == FIELD_GENRE) result = "genre.strgenre";
377     else if (field == SONG_ALBUM) result.Format("musicvideo.c%02d",VIDEODB_ID_MUSICVIDEO_ALBUM);
378     else if (field == FIELD_YEAR) result.Format("musicvideo.c%02d",VIDEODB_ID_MUSICVIDEO_YEAR);
379     else if (field == SONG_ARTIST) result.Format("actors.strActor");
380     else if (field == SONG_FILENAME) result = "strFilename";
381     else if (field == FIELD_RANDOM) result = "random()";      // only used for order clauses
382     else if (field == FIELD_DATEADDED) result = "idmvideo";        // only used for order clauses
383     return result;
384   }
385   if (type == "tvshows")
386   {
387     CStdString result;
388     if (field == FIELD_TITLE) result.Format("c%02d", VIDEODB_ID_TV_TITLE);
389     else if (field == VIDEO_PLOT) result.Format("c%02d", VIDEODB_ID_TV_PLOT);
390     else if (field == TVSHOW_STATUS) result.Format("c%02d", VIDEODB_ID_TV_STATUS);
391     else if (field == VIDEO_VOTES) result.Format("c%02d", VIDEODB_ID_TV_VOTES);
392     else if (field == FIELD_RATING) result.Format("c%02d", VIDEODB_ID_TV_RATING);
393     else if (field == FIELD_YEAR) result.Format("c%02d", VIDEODB_ID_TV_PREMIERED);
394     else if (field == FIELD_GENRE) result.Format("c%02d", VIDEODB_ID_TV_GENRE);
395     else if (field == VIDEO_DIRECTOR) result = "actor.strActor"; // join required
396     else if (field == VIDEO_ACTOR) result = "actor.strActor";    // join required
397     else if (field == TVSHOW_NUMEPISODES) result = "totalcount";
398     else if (field == TVSHOW_NUMWATCHED) result = "watchedcount";
399     else if (field == FIELD_PLAYCOUNT) result = "watched";
400     else if (field == FIELD_RANDOM) result = "random()";      // only used for order clauses
401     else if (field == FIELD_DATEADDED) result = "idshow";       // only used for order clauses
402     return result;
403   }
404   if (type == "episodes")
405   {
406     CStdString result;
407     if (field == FIELD_TITLE) result.Format("c%02d", VIDEODB_ID_EPISODE_TITLE);
408     else if (field == VIDEO_PLOT) result.Format("c%02d", VIDEODB_ID_EPISODE_PLOT);
409     else if (field == VIDEO_VOTES) result.Format("c%02d", VIDEODB_ID_EPISODE_VOTES);
410     else if (field == FIELD_RATING) result.Format("c%02d", VIDEODB_ID_EPISODE_RATING);
411     else if (field == FIELD_TIME) result.Format("c%02d", VIDEODB_ID_EPISODE_RUNTIME);
412     else if (field == EPISODE_WRITER) result.Format("c%02d", VIDEODB_ID_EPISODE_CREDITS);  // TODO: SMARTPLAYLISTS - this may need some work (split in db etc.)
413     else if (field == EPISODE_AIRDATE) result.Format("c%02d", VIDEODB_ID_EPISODE_AIRED);
414     else if (field == FIELD_PLAYCOUNT) result.Format("c%02d", VIDEODB_ID_EPISODE_PLAYCOUNT);
415     else if (field == FIELD_GENRE) result = "never_use_this";    // join required
416     else if (field == FIELD_YEAR) result = "premiered";
417     else if (field == VIDEO_DIRECTOR) result = "never_use_this"; // join required
418     else if (field == VIDEO_ACTOR) result = "never_use_this";    // join required
419     else if (field == EPISODE_EPISODE) result.Format("c%02d", VIDEODB_ID_EPISODE_EPISODE);
420     else if (field == EPISODE_SEASON) result.Format("c%02d", VIDEODB_ID_EPISODE_SEASON);
421     else if (field == FIELD_RANDOM) result = "random()";      // only used for order clauses
422     else if (field == FIELD_DATEADDED) result = "idshow";       // only used for order clauses
423     return result;
424   }
425   
426   return "";
427 }
428
429 CSmartPlaylist::CSmartPlaylist()
430 {
431   m_matchAllRules = true;
432   m_limit = 0;
433   m_orderField = CSmartPlaylistRule::FIELD_NONE;
434   m_orderAscending = true;
435   m_playlistType = "music"; // sane default
436 }
437
438 TiXmlElement *CSmartPlaylist::OpenAndReadName(const CStdString &path)
439 {
440   if (!m_xmlDoc.LoadFile(path))
441   {
442     CLog::Log(LOGERROR, "Error loading Smart playlist %s", path.c_str());
443     return NULL;
444   }
445
446   TiXmlElement *root = m_xmlDoc.RootElement();
447   if (!root || strcmpi(root->Value(),"smartplaylist") != 0)
448   {
449     CLog::Log(LOGERROR, "Error loading Smart playlist %s", path.c_str());
450     return NULL;
451   }
452   // load the playlist type
453   const char* type = root->Attribute("type");
454   if (type)
455     m_playlistType = type;
456   // load the playlist name
457   TiXmlHandle name = ((TiXmlHandle)root->FirstChild("name")).FirstChild();
458   if (name.Node())
459     m_playlistName = name.Node()->Value();
460   else
461   {
462     m_playlistName = CUtil::GetTitleFromPath(path);
463     if (CUtil::GetExtension(m_playlistName) == ".xsp")
464       CUtil::RemoveExtension(m_playlistName);
465   }
466   return root;
467 }
468
469 bool CSmartPlaylist::Load(const CStdString &path)
470 {
471   TiXmlElement *root = OpenAndReadName(path);
472   if (!root)
473     return false;
474
475   // encoding:
476   CStdString encoding;
477   XMLUtils::GetEncoding(&m_xmlDoc, encoding);
478
479   TiXmlHandle match = ((TiXmlHandle)root->FirstChild("match")).FirstChild();
480   if (match.Node())
481     m_matchAllRules = strcmpi(match.Node()->Value(), "all") == 0;
482   // now the rules
483   TiXmlElement *rule = root->FirstChildElement("rule");
484   while (rule)
485   {
486     // format is:
487     // <rule field="Genre" operator="contains">parameter</rule>
488     const char *field = rule->Attribute("field");
489     const char *oper = rule->Attribute("operator");
490     TiXmlNode *parameter = rule->FirstChild();
491     if (field && oper && parameter)
492     { // valid rule
493       CStdString utf8Parameter;
494       if (encoding.IsEmpty()) // utf8
495         utf8Parameter = parameter->Value();
496       else
497         g_charsetConverter.stringCharsetToUtf8(encoding, parameter->Value(), utf8Parameter);
498       CSmartPlaylistRule rule;
499       rule.TranslateStrings(field, oper, utf8Parameter.c_str());
500       m_playlistRules.push_back(rule);
501     }
502     rule = rule->NextSiblingElement("rule");
503   }
504   // now any limits
505   // format is <limit>25</limit>
506   TiXmlHandle limit = ((TiXmlHandle)root->FirstChild("limit")).FirstChild();
507   if (limit.Node())
508     m_limit = atoi(limit.Node()->Value());
509   // and order
510   // format is <order direction="ascending">field</order>
511   TiXmlElement *order = root->FirstChildElement("order");
512   if (order && order->FirstChild())
513   {
514     const char *direction = order->Attribute("direction");
515     if (direction)
516       m_orderAscending = strcmpi(direction, "ascending") == 0;
517     m_orderField = CSmartPlaylistRule::TranslateField(order->FirstChild()->Value());
518   }
519   return true;
520 }
521
522 bool CSmartPlaylist::Save(const CStdString &path)
523 {
524   TiXmlDocument doc;
525   TiXmlDeclaration decl("1.0", "UTF-8", "yes");
526   doc.InsertEndChild(decl);
527
528   TiXmlElement xmlRootElement("smartplaylist");
529   xmlRootElement.SetAttribute("type",m_playlistType.c_str());
530   TiXmlNode *pRoot = doc.InsertEndChild(xmlRootElement);
531   if (!pRoot) return false;
532   // add the <name> tag
533   TiXmlText name(m_playlistName.c_str());
534   TiXmlElement nodeName("name");
535   nodeName.InsertEndChild(name);
536   pRoot->InsertEndChild(nodeName);
537   // add the <match> tag
538   TiXmlText match(m_matchAllRules ? "all" : "one");
539   TiXmlElement nodeMatch("match");
540   nodeMatch.InsertEndChild(match);
541   pRoot->InsertEndChild(nodeMatch);
542   // add <rule> tags
543   for (vector<CSmartPlaylistRule>::iterator it = m_playlistRules.begin(); it != m_playlistRules.end(); ++it)
544   {
545     pRoot->InsertEndChild((*it).GetAsElement());
546   }
547   // add <limit> tag
548   if (m_limit)
549   {
550     CStdString limitFormat;
551     limitFormat.Format("%i", m_limit);
552     TiXmlText limit(limitFormat);
553     TiXmlElement nodeLimit("limit");
554     nodeLimit.InsertEndChild(limit);
555     pRoot->InsertEndChild(nodeLimit);
556   }
557   // add <order> tag
558   if (m_orderField != CSmartPlaylistRule::FIELD_NONE)
559   {
560     TiXmlText order(CSmartPlaylistRule::TranslateField(m_orderField).c_str());
561     TiXmlElement nodeOrder("order");
562     nodeOrder.SetAttribute("direction", m_orderAscending ? "ascending" : "descending");
563     nodeOrder.InsertEndChild(order);
564     pRoot->InsertEndChild(nodeOrder);
565   }
566   return doc.SaveFile(path);
567 }
568
569 void CSmartPlaylist::SetName(const CStdString &name)
570 {
571   m_playlistName = name;
572 }
573
574 void CSmartPlaylist::SetType(const CStdString &type)
575 {
576   m_playlistType = type;
577 }
578
579 void CSmartPlaylist::AddRule(const CSmartPlaylistRule &rule)
580 {
581   m_playlistRules.push_back(rule);
582 }
583
584 CStdString CSmartPlaylist::GetWhereClause(bool needWhere /* = true */)
585 {
586   CStdString rule;
587   for (vector<CSmartPlaylistRule>::iterator it = m_playlistRules.begin(); it != m_playlistRules.end(); ++it)
588   {
589     if (it != m_playlistRules.begin())
590       rule += m_matchAllRules ? " AND " : " OR ";
591     else if (needWhere)
592       rule += "WHERE ";
593     rule += "(";
594     rule += (*it).GetWhereClause(GetType());
595     rule += ")";
596   }
597   return rule;
598 }
599
600 CStdString CSmartPlaylist::GetOrderClause()
601 {
602   CStdString order;
603   if (m_orderField != CSmartPlaylistRule::FIELD_NONE)
604     order.Format("ORDER BY %s%s", CSmartPlaylistRule::GetDatabaseField(m_orderField,GetType()), m_orderAscending ? "" : " DESC");
605   if (m_limit)
606   {
607     CStdString limit;
608     limit.Format(" LIMIT %i", m_limit);
609     order += limit;
610   }
611   return order;
612 }
613
614 const vector<CSmartPlaylistRule> &CSmartPlaylist::GetRules() const
615 {
616   return m_playlistRules;
617 }
618