[release] version bump to 13.0 beta1
[vuplus_xbmc] / xbmc / pvr / PVRDatabase.cpp
1 /*
2  *      Copyright (C) 2012-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 "PVRDatabase.h"
22 #include "dbwrappers/dataset.h"
23 #include "settings/AdvancedSettings.h"
24 #include "settings/VideoSettings.h"
25 #include "utils/log.h"
26 #include "utils/StringUtils.h"
27
28 #include "PVRManager.h"
29 #include "channels/PVRChannelGroupsContainer.h"
30 #include "channels/PVRChannelGroupInternal.h"
31 #include "addons/PVRClient.h"
32
33 using namespace std;
34 using namespace dbiplus;
35 using namespace PVR;
36 using namespace ADDON;
37
38 #define PVRDB_DEBUGGING 0
39
40 bool CPVRDatabase::Open()
41 {
42   return CDatabase::Open(g_advancedSettings.m_databaseTV);
43 }
44
45 bool CPVRDatabase::CreateTables()
46 {
47   bool bReturn(false);
48
49   try
50   {
51     if (!CDatabase::CreateTables())
52       return false;
53
54     BeginTransaction();
55     CLog::Log(LOGINFO, "PVR - %s - creating tables", __FUNCTION__);
56
57     CLog::Log(LOGDEBUG, "PVR - %s - creating table 'clients'", __FUNCTION__);
58     m_pDS->exec(
59         "CREATE TABLE clients ("
60           "idClient integer primary key, "
61           "sName    varchar(64), "
62           "sUid     varchar(32)"
63         ")"
64     );
65
66     CLog::Log(LOGDEBUG, "PVR - %s - creating table 'channels'", __FUNCTION__);
67     m_pDS->exec(
68         "CREATE TABLE channels ("
69           "idChannel            integer primary key, "
70           "iUniqueId            integer, "
71           "bIsRadio             bool, "
72           "bIsHidden            bool, "
73           "bIsUserSetIcon       bool, "
74           "bIsLocked            bool, "
75           "sIconPath            varchar(255), "
76           "sChannelName         varchar(64), "
77           "bIsVirtual           bool, "
78           "bEPGEnabled          bool, "
79           "sEPGScraper          varchar(32), "
80           "iLastWatched         integer,"
81
82           // TODO use mapping table
83           "iClientId            integer, "
84           "iClientChannelNumber integer, "
85           "sInputFormat         varchar(32), "
86           "sStreamURL           varchar(255), "
87           "iEncryptionSystem    integer, "
88
89           "idEpg                integer"
90         ")"
91     );
92     m_pDS->exec("CREATE UNIQUE INDEX idx_channels_iClientId_iUniqueId on channels(iClientId, iUniqueId);");
93
94     // TODO use a mapping table so multiple backends per channel can be implemented
95     //    CLog::Log(LOGDEBUG, "PVR - %s - creating table 'map_channels_clients'", __FUNCTION__);
96     //    m_pDS->exec(
97     //        "CREATE TABLE map_channels_clients ("
98     //          "idChannel             integer primary key, "
99     //          "idClient              integer, "
100     //          "iClientChannelNumber  integer,"
101     //          "sInputFormat          string,"
102     //          "sStreamURL            string,"
103     //          "iEncryptionSystem     integer"
104     //        ");"
105     //    );
106     //    m_pDS->exec("CREATE UNIQUE INDEX idx_idChannel_idClient on map_channels_clients(idChannel, idClient);");
107
108     CLog::Log(LOGDEBUG, "PVR - %s - creating table 'channelgroups'", __FUNCTION__);
109     m_pDS->exec(
110         "CREATE TABLE channelgroups ("
111           "idGroup         integer primary key,"
112           "bIsRadio        bool, "
113           "iGroupType      integer, "
114           "sName           varchar(64)"
115         ")"
116     );
117     m_pDS->exec("CREATE INDEX idx_channelgroups_bIsRadio on channelgroups(bIsRadio);");
118
119     CLog::Log(LOGDEBUG, "PVR - %s - creating table 'map_channelgroups_channels'", __FUNCTION__);
120     m_pDS->exec(
121         "CREATE TABLE map_channelgroups_channels ("
122           "idChannel      integer, "
123           "idGroup        integer, "
124           "iChannelNumber integer"
125         ")"
126     );
127     m_pDS->exec("CREATE UNIQUE INDEX idx_idGroup_idChannel on map_channelgroups_channels(idGroup, idChannel);");
128
129     CLog::Log(LOGDEBUG, "PVR - %s - creating table 'channelsettings'", __FUNCTION__);
130     m_pDS->exec(
131         "CREATE TABLE channelsettings ("
132           "idChannel            integer primary key, "
133           "iInterlaceMethod     integer, "
134           "iViewMode            integer, "
135           "fCustomZoomAmount    float, "
136           "fPixelRatio          float, "
137           "iAudioStream         integer, "
138           "iSubtitleStream      integer,"
139           "fSubtitleDelay       float, "
140           "bSubtitles           bool, "
141           "fBrightness          float, "
142           "fContrast            float, "
143           "fGamma               float,"
144           "fVolumeAmplification float, "
145           "fAudioDelay          float, "
146           "bOutputToAllSpeakers bool, "
147           "bCrop                bool, "
148           "iCropLeft            integer, "
149           "iCropRight           integer, "
150           "iCropTop             integer, "
151           "iCropBottom          integer, "
152           "fSharpness           float, "
153           "fNoiseReduction      float, "
154           "fCustomVerticalShift float, "
155           "bCustomNonLinStretch bool, "
156           "bPostProcess         bool, "
157           "iScalingMethod       integer, "
158           "iDeinterlaceMode     integer "
159         ")"
160     );
161
162     CommitTransaction();
163     bReturn = true;
164   }
165   catch (...)
166   {
167     CLog::Log(LOGERROR, "PVR - %s - unable to create PVR database tables (error %i)", __FUNCTION__, (int)GetLastError());
168     RollbackTransaction();
169     bReturn = false;
170   }
171
172   if (bReturn)
173   {
174     // disable all PVR add-on when started the first time
175     ADDON::VECADDONS addons;
176     if ((bReturn = CAddonMgr::Get().GetAddons(ADDON_PVRDLL, addons, true)) == false)
177       CLog::Log(LOGERROR, "PVR - %s - failed to get add-ons from the add-on manager", __FUNCTION__);
178     else
179     {
180       for (IVECADDONS it = addons.begin(); it != addons.end(); it++)
181         CAddonMgr::Get().DisableAddon(it->get()->ID());
182     }
183   }
184
185   return bReturn;
186 }
187
188 bool CPVRDatabase::UpdateOldVersion(int iVersion)
189 {
190   bool bReturn = true;
191
192   BeginTransaction();
193
194   try
195   {
196     if (iVersion < 11)
197     {
198       CLog::Log(LOGERROR, "PVR - %s - updating from table versions < 11 not supported. please delete '%s'",
199           __FUNCTION__, GetBaseDBName());
200       bReturn = false;
201     }
202     else
203     {
204       if (iVersion < 12)
205         m_pDS->exec("DROP VIEW vw_last_watched;");
206
207       if (iVersion < 13)
208         m_pDS->exec("ALTER TABLE channels ADD idEpg integer;");
209
210       if (iVersion < 14)
211         m_pDS->exec("ALTER TABLE channelsettings ADD fCustomVerticalShift float;");
212
213       if (iVersion < 15)
214       {
215         m_pDS->exec("ALTER TABLE channelsettings ADD bCustomNonLinStretch bool;");
216         m_pDS->exec("ALTER TABLE channelsettings ADD bPostProcess bool;");
217         m_pDS->exec("ALTER TABLE channelsettings ADD iScalingMethod integer;");
218       }
219       if (iVersion < 16)
220       {
221         /* sqlite apparently can't delete columns from an existing table, so just leave the extra column alone */
222       }
223       if (iVersion < 17)
224       {
225         m_pDS->exec("ALTER TABLE channelsettings ADD iDeinterlaceMode integer");
226         m_pDS->exec("UPDATE channelsettings SET iDeinterlaceMode = 2 WHERE iInterlaceMethod NOT IN (0,1)"); // anything other than none: method auto => mode force
227         m_pDS->exec("UPDATE channelsettings SET iDeinterlaceMode = 1 WHERE iInterlaceMethod = 1"); // method auto => mode auto
228         m_pDS->exec("UPDATE channelsettings SET iDeinterlaceMode = 0, iInterlaceMethod = 1 WHERE iInterlaceMethod = 0"); // method none => mode off, method auto
229       }
230       if (iVersion < 18)
231       {
232         m_pDS->exec("DROP INDEX idx_channels_iClientId;");
233         m_pDS->exec("DROP INDEX idx_channels_iLastWatched;");
234         m_pDS->exec("DROP INDEX idx_channels_bIsRadio;");
235         m_pDS->exec("DROP INDEX idx_channels_bIsHidden;");
236         m_pDS->exec("DROP INDEX idx_idChannel_idGroup;");
237         m_pDS->exec("DROP INDEX idx_idGroup_iChannelNumber;");
238         m_pDS->exec("CREATE UNIQUE INDEX idx_channels_iClientId_iUniqueId on channels(iClientId, iUniqueId);");
239         m_pDS->exec("CREATE UNIQUE INDEX idx_idGroup_idChannel on map_channelgroups_channels(idGroup, idChannel);");
240       }
241       if (iVersion < 19)
242       {
243         // bit of a hack, but we need to keep the version/contents of the non-pvr databases the same to allow clean upgrades
244         ADDON::VECADDONS addons;
245         if ((bReturn = CAddonMgr::Get().GetAddons(ADDON_PVRDLL, addons, true)) == false)
246           CLog::Log(LOGERROR, "PVR - %s - failed to get add-ons from the add-on manager", __FUNCTION__);
247         else
248         {
249           CAddonDatabase database;
250           database.Open();
251           for (IVECADDONS it = addons.begin(); it != addons.end(); it++)
252           {
253             if (!database.IsSystemPVRAddonEnabled(it->get()->ID()))
254               CAddonMgr::Get().DisableAddon(it->get()->ID());
255           }
256           database.Close();
257         }
258       }
259       if (iVersion < 20)
260         m_pDS->exec("ALTER TABLE channels ADD bIsUserSetIcon bool");
261
262       if (iVersion < 21)
263         m_pDS->exec("ALTER TABLE channelgroups ADD iGroupType integer");
264
265       if (iVersion < 22)
266         m_pDS->exec("ALTER TABLE channels ADD bIsLocked bool");
267     }
268   }
269   catch (...)
270   {
271     CLog::Log(LOGERROR, "PVR - %s - error attempting to update the database version!", __FUNCTION__);
272     bReturn = false;
273   }
274
275   if (bReturn)
276     CommitTransaction();
277   else
278     RollbackTransaction();
279
280   return bReturn;
281 }
282
283 int CPVRDatabase::GetLastChannelId(void)
284 {
285   int iReturn(0);
286
287   CStdString strQuery = FormatSQL("SELECT MAX(idChannel) as iMaxChannel FROM channels");
288   if (ResultQuery(strQuery))
289   {
290     try
291     {
292       if (!m_pDS->eof())
293         iReturn = m_pDS->fv("iMaxChannel").get_asInt();
294     }
295     catch (...) {}
296   }
297
298   return iReturn;
299 }
300
301 /********** Channel methods **********/
302
303 bool CPVRDatabase::DeleteChannels(void)
304 {
305   CLog::Log(LOGDEBUG, "PVR - %s - deleting all channels from the database", __FUNCTION__);
306   return DeleteValues("channels");
307 }
308
309 bool CPVRDatabase::DeleteClientChannels(const CPVRClient &client)
310 {
311   /* invalid client Id */
312   if (client.GetID() <= 0)
313   {
314     CLog::Log(LOGERROR, "PVR - %s - invalid client id: %i", __FUNCTION__, client.GetID());
315     return false;
316   }
317
318   CLog::Log(LOGDEBUG, "PVR - %s - deleting all channels from client '%i' from the database", __FUNCTION__, client.GetID());
319   CStdString strWhereClause = FormatSQL("iClientId = %u", client.GetID());
320   return DeleteValues("channels", strWhereClause);
321 }
322
323 bool CPVRDatabase::Delete(const CPVRChannel &channel)
324 {
325   /* invalid channel */
326   if (channel.ChannelID() <= 0)
327     return false;
328
329   CLog::Log(LOGDEBUG, "PVR - %s - deleting channel '%s' from the database", __FUNCTION__, channel.ChannelName().c_str());
330   CStdString strWhereClause = FormatSQL("idChannel = %u", channel.ChannelID());
331   return DeleteValues("channels", strWhereClause);
332 }
333
334 int CPVRDatabase::Get(CPVRChannelGroupInternal &results)
335 {
336   int iReturn(0);
337
338   CStdString strQuery = FormatSQL("SELECT channels.idChannel, channels.iUniqueId, channels.bIsRadio, channels.bIsHidden, channels.bIsUserSetIcon, "
339       "channels.sIconPath, channels.sChannelName, channels.bIsVirtual, channels.bEPGEnabled, channels.sEPGScraper, channels.iLastWatched, channels.iClientId, channels.bIsLocked, "
340       "channels.iClientChannelNumber, channels.sInputFormat, channels.sInputFormat, channels.sStreamURL, channels.iEncryptionSystem, map_channelgroups_channels.iChannelNumber, channels.idEpg "
341       "FROM map_channelgroups_channels "
342       "LEFT JOIN channels ON channels.idChannel = map_channelgroups_channels.idChannel "
343       "WHERE map_channelgroups_channels.idGroup = %u", results.IsRadio() ? XBMC_INTERNAL_GROUP_RADIO : XBMC_INTERNAL_GROUP_TV);
344   if (ResultQuery(strQuery))
345   {
346     try
347     {
348       while (!m_pDS->eof())
349       {
350         CPVRChannelPtr channel = CPVRChannelPtr(new CPVRChannel());
351
352         channel->m_iChannelId              = m_pDS->fv("idChannel").get_asInt();
353         channel->m_iUniqueId               = m_pDS->fv("iUniqueId").get_asInt();
354         channel->m_bIsRadio                = m_pDS->fv("bIsRadio").get_asBool();
355         channel->m_bIsHidden               = m_pDS->fv("bIsHidden").get_asBool();
356         channel->m_bIsUserSetIcon          = m_pDS->fv("bIsUserSetIcon").get_asBool();
357         channel->m_bIsLocked               = m_pDS->fv("bIsLocked").get_asBool();
358         channel->m_strIconPath             = m_pDS->fv("sIconPath").get_asString();
359         channel->m_strChannelName          = m_pDS->fv("sChannelName").get_asString();
360         channel->m_bIsVirtual              = m_pDS->fv("bIsVirtual").get_asBool();
361         channel->m_bEPGEnabled             = m_pDS->fv("bEPGEnabled").get_asBool();
362         channel->m_strEPGScraper           = m_pDS->fv("sEPGScraper").get_asString();
363         channel->m_iLastWatched            = (time_t) m_pDS->fv("iLastWatched").get_asInt();
364         channel->m_iClientId               = m_pDS->fv("iClientId").get_asInt();
365         channel->m_iClientChannelNumber    = m_pDS->fv("iClientChannelNumber").get_asInt();
366         channel->m_strInputFormat          = m_pDS->fv("sInputFormat").get_asString();
367         channel->m_strStreamURL            = m_pDS->fv("sStreamURL").get_asString();
368         channel->m_iClientEncryptionSystem = m_pDS->fv("iEncryptionSystem").get_asInt();
369         channel->m_iEpgId                  = m_pDS->fv("idEpg").get_asInt();
370         channel->UpdateEncryptionName();
371
372 #if PVRDB_DEBUGGING
373         CLog::Log(LOGDEBUG, "PVR - %s - channel '%s' loaded from the database", __FUNCTION__, channel->m_strChannelName.c_str());
374 #endif
375         PVRChannelGroupMember newMember = { channel, (unsigned int)m_pDS->fv("iChannelNumber").get_asInt() };
376         results.m_members.push_back(newMember);
377
378         m_pDS->next();
379         ++iReturn;
380       }
381       m_pDS->close();
382     }
383     catch (...)
384     {
385       CLog::Log(LOGERROR, "PVR - %s - couldn't load channels from the database", __FUNCTION__);
386     }
387   }
388   else
389   {
390     CLog::Log(LOGERROR, "PVR - %s - query failed", __FUNCTION__);
391   }
392
393   m_pDS->close();
394   return iReturn;
395 }
396
397 bool CPVRDatabase::DeleteChannelSettings()
398 {
399   CLog::Log(LOGDEBUG, "PVR - %s - deleting all channel settings from the database", __FUNCTION__);
400   return DeleteValues("channelsettings");
401 }
402
403 bool CPVRDatabase::DeleteChannelSettings(const CPVRChannel &channel)
404 {
405   bool bReturn(false);
406
407   /* invalid channel */
408   if (channel.ChannelID() <= 0)
409   {
410     CLog::Log(LOGERROR, "PVR - %s - invalid channel id: %i", __FUNCTION__, channel.ChannelID());
411     return bReturn;
412   }
413
414   CStdString strWhereClause = FormatSQL("idChannel = %u", channel.ChannelID());
415   return DeleteValues("channelsettings", strWhereClause);
416 }
417
418 bool CPVRDatabase::GetChannelSettings(const CPVRChannel &channel, CVideoSettings &settings)
419 {
420   bool bReturn(false);
421
422   /* invalid channel */
423   if (channel.ChannelID() <= 0)
424   {
425     CLog::Log(LOGERROR, "PVR - %s - invalid channel id: %i", __FUNCTION__, channel.ChannelID());
426     return bReturn;
427   }
428
429   CStdString strQuery = FormatSQL("SELECT * FROM channelsettings WHERE idChannel = %u;", channel.ChannelID());
430
431   if (ResultQuery(strQuery))
432   {
433     try
434     {
435       if (m_pDS->num_rows() > 0)
436       {
437         settings.m_AudioDelay           = m_pDS->fv("fAudioDelay").get_asFloat();
438         settings.m_AudioStream          = m_pDS->fv("iAudioStream").get_asInt();
439         settings.m_Brightness           = m_pDS->fv("fBrightness").get_asFloat();
440         settings.m_Contrast             = m_pDS->fv("fContrast").get_asFloat();
441         settings.m_CustomPixelRatio     = m_pDS->fv("fPixelRatio").get_asFloat();
442         settings.m_CustomNonLinStretch  = m_pDS->fv("bCustomNonLinStretch").get_asBool();
443         settings.m_NoiseReduction       = m_pDS->fv("fNoiseReduction").get_asFloat();
444         settings.m_PostProcess          = m_pDS->fv("bPostProcess").get_asBool();
445         settings.m_Sharpness            = m_pDS->fv("fSharpness").get_asFloat();
446         settings.m_CustomZoomAmount     = m_pDS->fv("fCustomZoomAmount").get_asFloat();
447         settings.m_CustomVerticalShift  = m_pDS->fv("fCustomVerticalShift").get_asFloat();
448         settings.m_Gamma                = m_pDS->fv("fGamma").get_asFloat();
449         settings.m_SubtitleDelay        = m_pDS->fv("fSubtitleDelay").get_asFloat();
450         settings.m_SubtitleOn           = m_pDS->fv("bSubtitles").get_asBool();
451         settings.m_SubtitleStream       = m_pDS->fv("iSubtitleStream").get_asInt();
452         settings.m_ViewMode             = m_pDS->fv("iViewMode").get_asInt();
453         settings.m_Crop                 = m_pDS->fv("bCrop").get_asBool();
454         settings.m_CropLeft             = m_pDS->fv("iCropLeft").get_asInt();
455         settings.m_CropRight            = m_pDS->fv("iCropRight").get_asInt();
456         settings.m_CropTop              = m_pDS->fv("iCropTop").get_asInt();
457         settings.m_CropBottom           = m_pDS->fv("iCropBottom").get_asInt();
458         settings.m_InterlaceMethod      = (EINTERLACEMETHOD)m_pDS->fv("iInterlaceMethod").get_asInt();
459         settings.m_DeinterlaceMode      = (EDEINTERLACEMODE)m_pDS->fv("iDeinterlaceMode").get_asInt();
460         settings.m_VolumeAmplification  = m_pDS->fv("fVolumeAmplification").get_asFloat();
461         settings.m_OutputToAllSpeakers  = m_pDS->fv("bOutputToAllSpeakers").get_asBool();
462         settings.m_ScalingMethod        = (ESCALINGMETHOD)m_pDS->fv("iScalingMethod").get_asInt();
463
464         bReturn = true;
465       }
466
467       m_pDS->close();
468     }
469     catch(...)
470     {
471       CLog::Log(LOGERROR, "PVR - %s - failed to get channel settings for channel '%s'", __FUNCTION__, channel.ChannelName().c_str());
472     }
473   }
474   else
475   {
476     CLog::Log(LOGERROR, "PVR - %s - query failed", __FUNCTION__);
477   }
478
479   return bReturn;
480 }
481
482 bool CPVRDatabase::PersistChannelSettings(const CPVRChannel &channel, const CVideoSettings &settings)
483 {
484   /* invalid channel */
485   if (channel.ChannelID() <= 0)
486   {
487     CLog::Log(LOGERROR, "PVR - %s - invalid channel id: %i", __FUNCTION__, channel.ChannelID());
488     return false;
489   }
490
491   CStdString strQuery = FormatSQL(
492       "REPLACE INTO channelsettings "
493         "(idChannel, iInterlaceMethod, iViewMode, fCustomZoomAmount, fPixelRatio, iAudioStream, iSubtitleStream, fSubtitleDelay, "
494          "bSubtitles, fBrightness, fContrast, fGamma, fVolumeAmplification, fAudioDelay, bOutputToAllSpeakers, bCrop, iCropLeft, "
495          "iCropRight, iCropTop, iCropBottom, fSharpness, fNoiseReduction, fCustomVerticalShift, bCustomNonLinStretch, bPostProcess, iScalingMethod, iDeinterlaceMode) VALUES "
496          "(%i, %i, %i, %f, %f, %i, %i, %f, %i, %f, %f, %f, %f, %f, %i, %i, %i, %i, %i, %i, %f, %f, %f, %i, %i, %i, %i);",
497        channel.ChannelID(), settings.m_InterlaceMethod, settings.m_ViewMode, settings.m_CustomZoomAmount, settings.m_CustomPixelRatio,
498        settings.m_AudioStream, settings.m_SubtitleStream, settings.m_SubtitleDelay, settings.m_SubtitleOn ? 1 :0,
499        settings.m_Brightness, settings.m_Contrast, settings.m_Gamma, settings.m_VolumeAmplification, settings.m_AudioDelay,
500        settings.m_OutputToAllSpeakers ? 1 : 0, settings.m_Crop ? 1 : 0, settings.m_CropLeft, settings.m_CropRight, settings.m_CropTop,
501        settings.m_CropBottom, settings.m_Sharpness, settings.m_NoiseReduction, settings.m_CustomVerticalShift,
502        settings.m_CustomNonLinStretch ? 1 : 0, settings.m_PostProcess ? 1 : 0, settings.m_ScalingMethod, settings.m_DeinterlaceMode);
503
504   return ExecuteQuery(strQuery);
505 }
506
507 /********** Channel group methods **********/
508
509 bool CPVRDatabase::RemoveChannelsFromGroup(const CPVRChannelGroup &group)
510 {
511   CStdString strWhereClause = FormatSQL("idGroup = %u", group.GroupID());
512   return DeleteValues("map_channelgroups_channels", strWhereClause);
513 }
514
515 bool CPVRDatabase::GetCurrentGroupMembers(const CPVRChannelGroup &group, vector<int> &members)
516 {
517   bool bReturn(false);
518   /* invalid group id */
519   if (group.GroupID() <= 0)
520   {
521     CLog::Log(LOGERROR, "PVR - %s - invalid group id: %d", __FUNCTION__, group.GroupID());
522     return false;
523   }
524
525   CStdString strCurrentMembersQuery = FormatSQL("SELECT idChannel FROM map_channelgroups_channels WHERE idGroup = %u", group.GroupID());
526   if (ResultQuery(strCurrentMembersQuery))
527   {
528     try
529     {
530       while (!m_pDS->eof())
531       {
532         members.push_back(m_pDS->fv("idChannel").get_asInt());
533         m_pDS->next();
534       }
535       m_pDS->close();
536       bReturn = true;
537     }
538     catch (...)
539     {
540       CLog::Log(LOGERROR, "PVR - %s - couldn't load channels from the database", __FUNCTION__);
541     }
542   }
543   else
544   {
545     CLog::Log(LOGERROR, "PVR - %s - query failed", __FUNCTION__);
546   }
547
548   return bReturn;
549 }
550
551 bool CPVRDatabase::DeleteChannelsFromGroup(const CPVRChannelGroup &group)
552 {
553   /* invalid group id */
554   if (group.GroupID() <= 0)
555   {
556     CLog::Log(LOGERROR, "PVR - %s - invalid group id: %d", __FUNCTION__, group.GroupID());
557     return false;
558   }
559
560   CStdString strWhereClause;
561   strWhereClause = FormatSQL("idGroup = %u", group.GroupID());
562   return DeleteValues("map_channelgroups_channels", strWhereClause);
563 }
564
565 bool CPVRDatabase::DeleteChannelsFromGroup(const CPVRChannelGroup &group, const vector<int> &channelsToDelete)
566 {
567   bool bDelete(true);
568   unsigned int iDeletedChannels(0);
569   /* invalid group id */
570   if (group.GroupID() <= 0)
571   {
572     CLog::Log(LOGERROR, "PVR - %s - invalid group id: %d", __FUNCTION__, group.GroupID());
573     return false;
574   }
575
576   while (iDeletedChannels < channelsToDelete.size())
577   {
578     CStdString strChannelsToDelete;
579     CStdString strWhereClause;
580
581     for (unsigned int iChannelPtr = 0; iChannelPtr + iDeletedChannels < channelsToDelete.size() && iChannelPtr < 50; iChannelPtr++)
582       strChannelsToDelete += StringUtils::Format(", %d", channelsToDelete.at(iDeletedChannels + iChannelPtr));
583
584     if (!strChannelsToDelete.empty())
585     {
586       strChannelsToDelete.erase(0, 2);
587       strWhereClause = FormatSQL("idGroup = %u AND idChannel IN (%s)", group.GroupID(), strChannelsToDelete.c_str());
588       bDelete = DeleteValues("map_channelgroups_channels", strWhereClause) && bDelete;
589     }
590
591     iDeletedChannels += 50;
592   }
593
594   return bDelete;
595 }
596
597 bool CPVRDatabase::RemoveStaleChannelsFromGroup(const CPVRChannelGroup &group)
598 {
599   bool bDelete(true);
600   /* invalid group id */
601   if (group.GroupID() <= 0)
602   {
603     CLog::Log(LOGERROR, "PVR - %s - invalid group id: %d", __FUNCTION__, group.GroupID());
604     return false;
605   }
606
607   if (!group.IsInternalGroup())
608   {
609     /* First remove channels that don't exist in the main channels table */
610
611     // XXX work around for frodo: fix this up so it uses one query for all db types
612     // mysql doesn't support subqueries when deleting and sqlite doesn't support joins when deleting
613     if (g_advancedSettings.m_databaseTV.type.Equals("mysql"))
614     {
615       CStdString strQuery = FormatSQL("DELETE m FROM map_channelgroups_channels m LEFT JOIN channels c ON (c.idChannel = m.idChannel) WHERE c.idChannel IS NULL");
616       bDelete = ExecuteQuery(strQuery);
617     }
618     else
619     {
620       CStdString strWhereClause = FormatSQL("idChannel IN (SELECT m.idChannel FROM map_channelgroups_channels m LEFT JOIN channels on m.idChannel = channels.idChannel WHERE channels.idChannel IS NULL)");
621       bDelete = DeleteValues("map_channelgroups_channels", strWhereClause);
622     }
623   }
624
625   if (group.m_members.size() > 0)
626   {
627     vector<int> currentMembers;
628     if (GetCurrentGroupMembers(group, currentMembers))
629     {
630       vector<int> channelsToDelete;
631       for (unsigned int iChannelPtr = 0; iChannelPtr < currentMembers.size(); iChannelPtr++)
632       {
633         if (!group.IsGroupMember(currentMembers.at(iChannelPtr)))
634           channelsToDelete.push_back(currentMembers.at(iChannelPtr));
635       }
636
637       bDelete = DeleteChannelsFromGroup(group, channelsToDelete) && bDelete;
638     }
639   }
640   else
641   {
642     CStdString strWhereClause = FormatSQL("idGroup = %u", group.GroupID());
643     bDelete = DeleteValues("map_channelgroups_channels", strWhereClause) && bDelete;
644   }
645
646   return bDelete;
647 }
648
649 bool CPVRDatabase::DeleteChannelGroups(void)
650 {
651   CLog::Log(LOGDEBUG, "PVR - %s - deleting all channel groups from the database", __FUNCTION__);
652
653   return DeleteValues("channelgroups") &&
654       DeleteValues("map_channelgroups_channels");
655 }
656
657 bool CPVRDatabase::Delete(const CPVRChannelGroup &group)
658 {
659   /* invalid group id */
660   if (group.GroupID() <= 0)
661   {
662     CLog::Log(LOGERROR, "PVR - %s - invalid group id: %d", __FUNCTION__, group.GroupID());
663     return false;
664   }
665
666   CStdString strWhereClause = FormatSQL("idGroup = %u AND bIsRadio = %u", group.GroupID(), group.IsRadio());
667   return RemoveChannelsFromGroup(group) &&
668       DeleteValues("channelgroups", strWhereClause);
669 }
670
671 bool CPVRDatabase::Get(CPVRChannelGroups &results)
672 {
673   bool bReturn = false;
674   CStdString strQuery = FormatSQL("SELECT * from channelgroups WHERE bIsRadio = %u", results.IsRadio());
675
676   if (ResultQuery(strQuery))
677   {
678     try
679     {
680       while (!m_pDS->eof())
681       {
682         CPVRChannelGroup data(m_pDS->fv("bIsRadio").get_asBool(), m_pDS->fv("idGroup").get_asInt(), m_pDS->fv("sName").get_asString());
683         data.SetGroupType(m_pDS->fv("iGroupType").get_asInt());
684         results.Update(data);
685
686         CLog::Log(LOGDEBUG, "PVR - %s - group '%s' loaded from the database", __FUNCTION__, data.GroupName().c_str());
687         m_pDS->next();
688       }
689       m_pDS->close();
690       bReturn = true;
691     }
692     catch (...)
693     {
694       CLog::Log(LOGERROR, "%s - couldn't load channels from the database", __FUNCTION__);
695     }
696   }
697
698   return bReturn;
699 }
700
701 int CPVRDatabase::Get(CPVRChannelGroup &group)
702 {
703   int iReturn = -1;
704
705   /* invalid group id */
706   if (group.GroupID() < 0)
707   {
708     CLog::Log(LOGERROR, "PVR - %s - invalid group id: %d", __FUNCTION__, group.GroupID());
709     return -1;
710   }
711
712   CStdString strQuery = FormatSQL("SELECT idChannel, iChannelNumber FROM map_channelgroups_channels WHERE idGroup = %u ORDER BY iChannelNumber", group.GroupID());
713   if (ResultQuery(strQuery))
714   {
715     iReturn = 0;
716
717     try
718     {
719       while (!m_pDS->eof())
720       {
721         int iChannelId = m_pDS->fv("idChannel").get_asInt();
722         int iChannelNumber = m_pDS->fv("iChannelNumber").get_asInt();
723         CPVRChannelPtr channel = g_PVRChannelGroups->GetGroupAll(group.IsRadio())->GetByChannelID(iChannelId);
724
725         if (channel)
726         {
727 #if PVRDB_DEBUGGING
728           CLog::Log(LOGDEBUG, "PVR - %s - channel '%s' loaded from the database", __FUNCTION__, channel->m_strChannelName.c_str());
729 #endif
730           PVRChannelGroupMember newMember = { channel, (unsigned int)iChannelNumber };
731           group.m_members.push_back(newMember);
732           iReturn++;
733         }
734         else
735         {
736           // remove a channel that doesn't exist (anymore) from the table
737           DeleteValues("map_channelgroups_channels", FormatSQL("idGroup = %u AND idChannel = %u", group.GroupID(), iChannelId));
738         }
739
740         m_pDS->next();
741       }
742       m_pDS->close();
743     }
744     catch(...)
745     {
746       CLog::Log(LOGERROR, "PVR - %s - failed to get channels", __FUNCTION__);
747     }
748   }
749
750   if (iReturn > 0)
751     group.SortByChannelNumber();
752
753   return iReturn;
754 }
755
756 bool CPVRDatabase::PersistChannels(CPVRChannelGroup &group)
757 {
758   bool bReturn(true);
759   int iLastChannel(0);
760
761   /* we can only safely get this from a local db */
762   if (m_sqlite)
763     iLastChannel = GetLastChannelId();
764
765   for (unsigned int iChannelPtr = 0; iChannelPtr < group.m_members.size(); iChannelPtr++)
766   {
767     PVRChannelGroupMember member = group.m_members.at(iChannelPtr);
768     if (member.channel->IsChanged() || member.channel->IsNew())
769     {
770       if (m_sqlite && member.channel->IsNew())
771         member.channel->SetChannelID(++iLastChannel);
772       bReturn &= Persist(*member.channel, m_sqlite || !member.channel->IsNew());
773     }
774   }
775
776   bReturn &= CommitInsertQueries();
777
778   return bReturn;
779 }
780
781 bool CPVRDatabase::PersistGroupMembers(CPVRChannelGroup &group)
782 {
783   bool bReturn = true;
784   bool bRemoveChannels = true;
785   CStdString strQuery;
786   CSingleLock lock(group.m_critSection);
787
788   if (group.m_members.size() > 0)
789   {
790     for (unsigned int iChannelPtr = 0; iChannelPtr < group.m_members.size(); iChannelPtr++)
791     {
792       PVRChannelGroupMember member = group.m_members.at(iChannelPtr);
793
794       CStdString strWhereClause = FormatSQL("idChannel = %u AND idGroup = %u AND iChannelNumber = %u",
795           member.channel->ChannelID(), group.GroupID(), member.iChannelNumber);
796
797       CStdString strValue = GetSingleValue("map_channelgroups_channels", "idChannel", strWhereClause);
798       if (strValue.empty())
799       {
800         strQuery = FormatSQL("REPLACE INTO map_channelgroups_channels ("
801             "idGroup, idChannel, iChannelNumber) "
802             "VALUES (%i, %i, %i);",
803             group.GroupID(), member.channel->ChannelID(), member.iChannelNumber);
804         QueueInsertQuery(strQuery);
805       }
806     }
807     lock.Leave();
808
809     bReturn = CommitInsertQueries();
810     bRemoveChannels = RemoveStaleChannelsFromGroup(group);
811   }
812
813   return bReturn && bRemoveChannels;
814 }
815
816 /********** Client methods **********/
817
818 bool CPVRDatabase::DeleteClients()
819 {
820   CLog::Log(LOGDEBUG, "PVR - %s - deleting all clients from the database", __FUNCTION__);
821
822   return DeleteValues("clients");
823       //TODO && DeleteValues("map_channels_clients");
824 }
825
826 bool CPVRDatabase::Delete(const CPVRClient &client)
827 {
828   /* invalid client uid */
829   if (client.ID().empty())
830   {
831     CLog::Log(LOGERROR, "PVR - %s - invalid client uid", __FUNCTION__);
832     return false;
833   }
834
835   CStdString strWhereClause = FormatSQL("sUid = '%s'", client.ID().c_str());
836   return DeleteValues("clients", strWhereClause);
837 }
838
839 int CPVRDatabase::GetClientId(const CStdString &strClientUid)
840 {
841   CStdString strWhereClause = FormatSQL("sUid = '%s'", strClientUid.c_str());
842   CStdString strValue = GetSingleValue("clients", "idClient", strWhereClause);
843
844   if (strValue.empty())
845     return -1;
846
847   return atol(strValue.c_str());
848 }
849
850 bool CPVRDatabase::ResetEPG(void)
851 {
852   CStdString strQuery = FormatSQL("UPDATE channels SET idEpg = 0");
853   return ExecuteQuery(strQuery);
854 }
855
856 bool CPVRDatabase::Persist(CPVRChannelGroup &group)
857 {
858   bool bReturn(false);
859   if (group.GroupName().empty())
860   {
861     CLog::Log(LOGERROR, "%s - empty group name", __FUNCTION__);
862     return bReturn;
863   }
864
865   CStdString strQuery;
866   bReturn = true;
867   {
868     CSingleLock lock(group.m_critSection);
869
870     /* insert a new entry when this is a new group, or replace the existing one otherwise */
871     if (group.GroupID() <= 0)
872       strQuery = FormatSQL("INSERT INTO channelgroups (bIsRadio, iGroupType, sName) VALUES (%i, %i, '%s')",
873           (group.IsRadio() ? 1 :0), group.GroupType(), group.GroupName().c_str());
874     else
875       strQuery = FormatSQL("REPLACE INTO channelgroups (idGroup, bIsRadio, iGroupType, sName) VALUES (%i, %i, %i, '%s')",
876           group.GroupID(), (group.IsRadio() ? 1 :0), group.GroupType(), group.GroupName().c_str());
877
878     bReturn = ExecuteQuery(strQuery);
879
880     /* set the group id if it was <= 0 */
881     if (bReturn && group.GroupID() <= 0)
882       group.m_iGroupId = (int) m_pDS->lastinsertid();
883   }
884
885   /* only persist the channel data for the internal groups */
886   if (group.IsInternalGroup())
887     bReturn &= PersistChannels(group);
888
889   /* persist the group member entries */
890   if (bReturn)
891     bReturn = PersistGroupMembers(group);
892
893   return bReturn;
894 }
895
896 int CPVRDatabase::Persist(const AddonPtr client)
897 {
898   int iReturn(-1);
899
900   /* invalid client uid or name */
901   if (client->Name().empty() || client->ID().empty())
902   {
903     CLog::Log(LOGERROR, "PVR - %s - invalid client uid or name", __FUNCTION__);
904     return iReturn;
905   }
906
907   CStdString strQuery = FormatSQL("REPLACE INTO clients (sName, sUid) VALUES ('%s', '%s');",
908       client->Name().c_str(), client->ID().c_str());
909
910   if (ExecuteQuery(strQuery))
911     iReturn = (int) m_pDS->lastinsertid();
912
913   return iReturn;
914 }
915
916 bool CPVRDatabase::Persist(CPVRChannel &channel, bool bQueueWrite /* = false */)
917 {
918   bool bReturn(false);
919
920   /* invalid channel */
921   if (channel.UniqueID() <= 0)
922   {
923     CLog::Log(LOGERROR, "PVR - %s - invalid channel uid: %d", __FUNCTION__, channel.UniqueID());
924     return bReturn;
925   }
926
927   CStdString strQuery;
928   if (channel.ChannelID() <= 0)
929   {
930     /* new channel */
931     strQuery = FormatSQL("INSERT INTO channels ("
932         "iUniqueId, bIsRadio, bIsHidden, bIsUserSetIcon, bIsLocked, "
933         "sIconPath, sChannelName, bIsVirtual, bEPGEnabled, sEPGScraper, iLastWatched, iClientId, "
934         "iClientChannelNumber, sInputFormat, sStreamURL, iEncryptionSystem, idEpg) "
935         "VALUES (%i, %i, %i, %i, %i, '%s', '%s', %i, %i, '%s', %u, %i, %i, '%s', '%s', %i, %i)",
936         channel.UniqueID(), (channel.IsRadio() ? 1 :0), (channel.IsHidden() ? 1 : 0), (channel.IsUserSetIcon() ? 1 : 0), (channel.IsLocked() ? 1 : 0),
937         channel.IconPath().c_str(), channel.ChannelName().c_str(), (channel.IsVirtual() ? 1 : 0), (channel.EPGEnabled() ? 1 : 0), channel.EPGScraper().c_str(), channel.LastWatched(), channel.ClientID(),
938         channel.ClientChannelNumber(), channel.InputFormat().c_str(), channel.StreamURL().c_str(), channel.EncryptionSystem(),
939         channel.EpgID());
940   }
941   else
942   {
943     /* update channel */
944     strQuery = FormatSQL("REPLACE INTO channels ("
945         "iUniqueId, bIsRadio, bIsHidden, bIsUserSetIcon, bIsLocked, "
946         "sIconPath, sChannelName, bIsVirtual, bEPGEnabled, sEPGScraper, iLastWatched, iClientId, "
947         "iClientChannelNumber, sInputFormat, sStreamURL, iEncryptionSystem, idChannel, idEpg) "
948         "VALUES (%i, %i, %i, %i, %i, '%s', '%s', %i, %i, '%s', %u, %i, %i, '%s', '%s', %i, %i, %i)",
949         channel.UniqueID(), (channel.IsRadio() ? 1 :0), (channel.IsHidden() ? 1 : 0), (channel.IsUserSetIcon() ? 1 : 0), (channel.IsLocked() ? 1 : 0),
950         channel.IconPath().c_str(), channel.ChannelName().c_str(), (channel.IsVirtual() ? 1 : 0), (channel.EPGEnabled() ? 1 : 0), channel.EPGScraper().c_str(), channel.LastWatched(), channel.ClientID(),
951         channel.ClientChannelNumber(), channel.InputFormat().c_str(), channel.StreamURL().c_str(), channel.EncryptionSystem(), channel.ChannelID(),
952         channel.EpgID());
953   }
954
955   if (bQueueWrite)
956   {
957     QueueInsertQuery(strQuery);
958     bReturn = true;
959   }
960   else if (ExecuteQuery(strQuery))
961   {
962     CSingleLock lock(channel.m_critSection);
963     if (channel.m_iChannelId <= 0)
964       channel.m_iChannelId = (int)m_pDS->lastinsertid();
965     bReturn = true;
966   }
967
968   return bReturn;
969 }