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