/* * Copyright (C) 2012-2018 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "PVRDatabase.h" #include "ServiceBroker.h" #include "dbwrappers/dataset.h" #include "pvr/addons/PVRClient.h" #include "pvr/channels/PVRChannel.h" #include "pvr/channels/PVRChannelGroupMember.h" #include "pvr/channels/PVRChannelGroups.h" #include "pvr/providers/PVRProvider.h" #include "pvr/providers/PVRProviders.h" #include "pvr/timers/PVRTimerInfoTag.h" #include "pvr/timers/PVRTimerType.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" #include "utils/StringUtils.h" #include "utils/log.h" #include #include #include #include #include #include #include using namespace dbiplus; using namespace PVR; namespace { // clang-format off static const std::string sqlCreateTimersTable = "CREATE TABLE timers (" "iClientIndex integer primary key, " "iParentClientIndex integer, " "iClientId integer, " "iTimerType integer, " "iState integer, " "sTitle varchar(255), " "iClientChannelUid integer, " "sSeriesLink varchar(255), " "sStartTime varchar(20), " "bStartAnyTime bool, " "sEndTime varchar(20), " "bEndAnyTime bool, " "sFirstDay varchar(20), " "iWeekdays integer, " "iEpgUid integer, " "iMarginStart integer, " "iMarginEnd integer, " "sEpgSearchString varchar(255), " "bFullTextEpgSearch bool, " "iPreventDuplicates integer," "iPrority integer," "iLifetime integer," "iMaxRecordings integer," "iRecordingGroup integer" ")"; static const std::string sqlCreateChannelGroupsTable = "CREATE TABLE channelgroups (" "idGroup integer primary key," "bIsRadio bool, " "iGroupType integer, " "sName varchar(64), " "iLastWatched integer, " "bIsHidden bool, " "iPosition integer, " "iLastOpened bigint unsigned" ")"; static const std::string sqlCreateProvidersTable = "CREATE TABLE providers (" "idProvider integer primary key, " "iUniqueId integer, " "iClientId integer, " "sName varchar(64), " "iType integer, " "sIconPath varchar(255), " "sCountries varchar(64), " "sLanguages varchar(64) " ")"; // clang-format on std::string GetClientIdsSQL(const std::vector>& clients) { if (clients.empty()) return {}; std::string clientIds = "("; for (auto it = clients.cbegin(); it != clients.cend(); ++it) { if (it != clients.cbegin()) clientIds += " OR "; clientIds += "iClientId = "; clientIds += std::to_string((*it)->GetID()); } clientIds += ")"; return clientIds; } } // unnamed namespace bool CPVRDatabase::Open() { std::unique_lock lock(m_critSection); return CDatabase::Open(CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_databaseTV); } void CPVRDatabase::Close() { std::unique_lock lock(m_critSection); CDatabase::Close(); } void CPVRDatabase::Lock() { m_critSection.lock(); } void CPVRDatabase::Unlock() { m_critSection.unlock(); } void CPVRDatabase::CreateTables() { std::unique_lock lock(m_critSection); CLog::LogF(LOGINFO, "Creating PVR database tables"); CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'channels'"); m_pDS->exec("CREATE TABLE channels (" "idChannel integer primary key, " "iUniqueId integer, " "bIsRadio bool, " "bIsHidden bool, " "bIsUserSetIcon bool, " "bIsUserSetName bool, " "bIsLocked bool, " "sIconPath varchar(255), " "sChannelName varchar(64), " "bIsVirtual bool, " "bEPGEnabled bool, " "sEPGScraper varchar(32), " "iLastWatched integer, " "iClientId integer, " //! @todo use mapping table "idEpg integer, " "bHasArchive bool, " "iClientProviderUid integer, " "bIsUserSetHidden bool" ")"); CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'channelgroups'"); m_pDS->exec(sqlCreateChannelGroupsTable); CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'map_channelgroups_channels'"); m_pDS->exec( "CREATE TABLE map_channelgroups_channels (" "idChannel integer, " "idGroup integer, " "iChannelNumber integer, " "iSubChannelNumber integer, " "iOrder integer, " "iClientChannelNumber integer, " "iClientSubChannelNumber integer" ")" ); CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'clients'"); m_pDS->exec( "CREATE TABLE clients (" "idClient integer primary key, " "iPriority integer" ")" ); CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'timers'"); m_pDS->exec(sqlCreateTimersTable); CLog::LogFC(LOGDEBUG, LOGPVR, "Creating table 'providers'"); m_pDS->exec(sqlCreateProvidersTable); } void CPVRDatabase::CreateAnalytics() { std::unique_lock lock(m_critSection); CLog::LogF(LOGINFO, "Creating PVR database indices"); m_pDS->exec("CREATE INDEX idx_clients_idClient on clients(idClient);"); m_pDS->exec("CREATE UNIQUE INDEX idx_channels_iClientId_iUniqueId on channels(iClientId, iUniqueId);"); m_pDS->exec("CREATE INDEX idx_channelgroups_bIsRadio on channelgroups(bIsRadio);"); m_pDS->exec("CREATE UNIQUE INDEX idx_idGroup_idChannel on map_channelgroups_channels(idGroup, idChannel);"); m_pDS->exec("CREATE INDEX idx_timers_iClientIndex on timers(iClientIndex);"); } void CPVRDatabase::UpdateTables(int iVersion) { std::unique_lock lock(m_critSection); if (iVersion < 13) m_pDS->exec("ALTER TABLE channels ADD idEpg integer;"); if (iVersion < 20) m_pDS->exec("ALTER TABLE channels ADD bIsUserSetIcon bool"); if (iVersion < 21) m_pDS->exec("ALTER TABLE channelgroups ADD iGroupType integer"); if (iVersion < 22) m_pDS->exec("ALTER TABLE channels ADD bIsLocked bool"); if (iVersion < 23) m_pDS->exec("ALTER TABLE channelgroups ADD iLastWatched integer"); if (iVersion < 24) m_pDS->exec("ALTER TABLE channels ADD bIsUserSetName bool"); if (iVersion < 25) m_pDS->exec("DROP TABLE IF EXISTS channelsettings"); if (iVersion < 26) { m_pDS->exec("ALTER TABLE channels ADD iClientSubChannelNumber integer"); m_pDS->exec("UPDATE channels SET iClientSubChannelNumber = 0"); m_pDS->exec("ALTER TABLE map_channelgroups_channels ADD iSubChannelNumber integer"); m_pDS->exec("UPDATE map_channelgroups_channels SET iSubChannelNumber = 0"); } if (iVersion < 27) m_pDS->exec("ALTER TABLE channelgroups ADD bIsHidden bool"); if (iVersion < 28) m_pDS->exec("DROP TABLE clients"); if (iVersion < 29) m_pDS->exec("ALTER TABLE channelgroups ADD iPosition integer"); if (iVersion < 32) m_pDS->exec("CREATE TABLE clients (idClient integer primary key, iPriority integer)"); if (iVersion < 33) m_pDS->exec(sqlCreateTimersTable); if (iVersion < 34) m_pDS->exec("ALTER TABLE channels ADD bHasArchive bool"); if (iVersion < 35) { m_pDS->exec("ALTER TABLE map_channelgroups_channels ADD iOrder integer"); m_pDS->exec("UPDATE map_channelgroups_channels SET iOrder = 0"); } if (iVersion < 36) { m_pDS->exec("ALTER TABLE map_channelgroups_channels ADD iClientChannelNumber integer"); m_pDS->exec("UPDATE map_channelgroups_channels SET iClientChannelNumber = 0"); m_pDS->exec("ALTER TABLE map_channelgroups_channels ADD iClientSubChannelNumber integer"); m_pDS->exec("UPDATE map_channelgroups_channels SET iClientSubChannelNumber = 0"); } if (iVersion < 37) m_pDS->exec("ALTER TABLE channelgroups ADD iLastOpened integer"); if (iVersion < 38) { m_pDS->exec("ALTER TABLE channelgroups " "RENAME TO channelgroups_old"); m_pDS->exec(sqlCreateChannelGroupsTable); m_pDS->exec( "INSERT INTO channelgroups (bIsRadio, iGroupType, sName, iLastWatched, bIsHidden, " "iPosition, iLastOpened) " "SELECT bIsRadio, iGroupType, sName, iLastWatched, bIsHidden, iPosition, iLastOpened " "FROM channelgroups_old"); m_pDS->exec("DROP TABLE channelgroups_old"); } if (iVersion < 39) { m_pDS->exec(sqlCreateProvidersTable); m_pDS->exec("CREATE UNIQUE INDEX idx_iUniqueId_iClientId on providers(iUniqueId, iClientId);"); m_pDS->exec("ALTER TABLE channels ADD iClientProviderUid integer"); m_pDS->exec("UPDATE channels SET iClientProviderUid = -1"); } if (iVersion < 40) { m_pDS->exec("ALTER TABLE channels ADD bIsUserSetHidden bool"); m_pDS->exec("UPDATE channels SET bIsUserSetHidden = bIsHidden"); } } /********** Client methods **********/ bool CPVRDatabase::DeleteClients() { CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting all clients from the database"); std::unique_lock lock(m_critSection); return DeleteValues("clients"); } bool CPVRDatabase::Persist(const CPVRClient& client) { if (client.GetID() == PVR_INVALID_CLIENT_ID) return false; CLog::LogFC(LOGDEBUG, LOGPVR, "Persisting client '{}' to database", client.ID()); std::unique_lock lock(m_critSection); const std::string strQuery = PrepareSQL("REPLACE INTO clients (idClient, iPriority) VALUES (%i, %i);", client.GetID(), client.GetPriority()); return ExecuteQuery(strQuery); } bool CPVRDatabase::Delete(const CPVRClient& client) { if (client.GetID() == PVR_INVALID_CLIENT_ID) return false; CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting client '{}' from the database", client.ID()); std::unique_lock lock(m_critSection); Filter filter; filter.AppendWhere(PrepareSQL("idClient = '%i'", client.GetID())); return DeleteValues("clients", filter); } int CPVRDatabase::GetPriority(const CPVRClient& client) { if (client.GetID() == PVR_INVALID_CLIENT_ID) return 0; CLog::LogFC(LOGDEBUG, LOGPVR, "Getting priority for client '{}' from the database", client.ID()); std::unique_lock lock(m_critSection); const std::string strWhereClause = PrepareSQL("idClient = '%i'", client.GetID()); const std::string strValue = GetSingleValue("clients", "iPriority", strWhereClause); if (strValue.empty()) return 0; return atoi(strValue.c_str()); } /********** Channel provider methods **********/ bool CPVRDatabase::DeleteProviders() { CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting all providers from the database"); std::unique_lock lock(m_critSection); return DeleteValues("providers"); } bool CPVRDatabase::Persist(CPVRProvider& provider, bool updateRecord /* = false */) { bool bReturn = false; if (provider.GetName().empty()) { CLog::LogF(LOGERROR, "Empty provider name"); return bReturn; } std::string strQuery; std::unique_lock lock(m_critSection); { /* insert a new entry when this is a new group, or replace the existing one otherwise */ if (!updateRecord) strQuery = PrepareSQL("INSERT INTO providers (idProvider, iUniqueId, iClientId, sName, " "iType, sIconPath, sCountries, sLanguages) " "VALUES (%i, %i, %i, '%s', %i, '%s', '%s', '%s');", provider.GetDatabaseId(), provider.GetUniqueId(), provider.GetClientId(), provider.GetName().c_str(), static_cast(provider.GetType()), provider.GetClientIconPath().c_str(), provider.GetCountriesDBString().c_str(), provider.GetLanguagesDBString().c_str()); else strQuery = PrepareSQL("REPLACE INTO providers (idProvider, iUniqueId, iClientId, sName, " "iType, sIconPath, sCountries, sLanguages) " "VALUES (%i, %i, %i, '%s', %i, '%s', '%s', '%s');", provider.GetDatabaseId(), provider.GetUniqueId(), provider.GetClientId(), provider.GetName().c_str(), static_cast(provider.GetType()), provider.GetClientIconPath().c_str(), provider.GetCountriesDBString().c_str(), provider.GetLanguagesDBString().c_str()); bReturn = ExecuteQuery(strQuery); /* set the provider id if it was <= 0 */ if (bReturn && provider.GetDatabaseId() <= 0) { provider.SetDatabaseId(static_cast(m_pDS->lastinsertid())); } } return bReturn; } bool CPVRDatabase::Delete(const CPVRProvider& provider) { CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting provider '{}' from the database", provider.GetName()); std::unique_lock lock(m_critSection); Filter filter; filter.AppendWhere(PrepareSQL("idProvider = '%i'", provider.GetDatabaseId())); return DeleteValues("providers", filter); } bool CPVRDatabase::Get(CPVRProviders& results, const std::vector>& clients) const { bool bReturn = false; std::string strQuery = "SELECT * from providers "; const std::string clientIds = GetClientIdsSQL(clients); if (!clientIds.empty()) strQuery += "WHERE " + clientIds + " OR iType = 1"; // always load addon providers std::unique_lock lock(m_critSection); strQuery = PrepareSQL(strQuery); if (ResultQuery(strQuery)) { try { while (!m_pDS->eof()) { std::shared_ptr provider = std::make_shared( m_pDS->fv("iUniqueId").get_asInt(), m_pDS->fv("iClientId").get_asInt()); provider->SetDatabaseId(m_pDS->fv("idProvider").get_asInt()); provider->SetName(m_pDS->fv("sName").get_asString()); provider->SetType( static_cast(m_pDS->fv("iType").get_asInt())); provider->SetIconPath(m_pDS->fv("sIconPath").get_asString()); provider->SetCountriesFromDBString(m_pDS->fv("sCountries").get_asString()); provider->SetLanguagesFromDBString(m_pDS->fv("sLanguages").get_asString()); results.CheckAndAddEntry(provider, ProviderUpdateMode::BY_DATABASE); CLog::LogFC(LOGDEBUG, LOGPVR, "Channel Provider '{}' loaded from PVR database", provider->GetName()); m_pDS->next(); } m_pDS->close(); bReturn = true; } catch (...) { CLog::LogF(LOGERROR, "Couldn't load providers from PVR database"); } } return bReturn; } int CPVRDatabase::GetMaxProviderId() { std::string strQuery = PrepareSQL("SELECT max(idProvider) as maxProviderId from providers"); std::unique_lock lock(m_critSection); return GetSingleValueInt(strQuery); } /********** Channel methods **********/ int CPVRDatabase::Get(bool bRadio, const std::vector>& clients, std::map, std::shared_ptr>& results) const { int iReturn = 0; std::string strQuery = "SELECT * from channels WHERE bIsRadio = %u "; const std::string clientIds = GetClientIdsSQL(clients); if (!clientIds.empty()) strQuery += "AND " + clientIds; std::unique_lock lock(m_critSection); strQuery = PrepareSQL(strQuery, bRadio); if (ResultQuery(strQuery)) { try { while (!m_pDS->eof()) { const std::shared_ptr channel(new CPVRChannel( m_pDS->fv("bIsRadio").get_asBool(), m_pDS->fv("sIconPath").get_asString())); channel->m_iChannelId = m_pDS->fv("idChannel").get_asInt(); channel->m_iUniqueId = m_pDS->fv("iUniqueId").get_asInt(); channel->m_bIsHidden = m_pDS->fv("bIsHidden").get_asBool(); channel->m_bIsUserSetIcon = m_pDS->fv("bIsUserSetIcon").get_asBool(); channel->m_bIsUserSetName = m_pDS->fv("bIsUserSetName").get_asBool(); channel->m_bIsLocked = m_pDS->fv("bIsLocked").get_asBool(); channel->m_strChannelName = m_pDS->fv("sChannelName").get_asString(); channel->m_bEPGEnabled = m_pDS->fv("bEPGEnabled").get_asBool(); channel->m_strEPGScraper = m_pDS->fv("sEPGScraper").get_asString(); channel->m_iLastWatched = static_cast(m_pDS->fv("iLastWatched").get_asInt()); channel->m_iClientId = m_pDS->fv("iClientId").get_asInt(); channel->m_iEpgId = m_pDS->fv("idEpg").get_asInt(); channel->m_bHasArchive = m_pDS->fv("bHasArchive").get_asBool(); channel->m_iClientProviderUid = m_pDS->fv("iClientProviderUid").get_asInt(); channel->m_bIsUserSetHidden = m_pDS->fv("bIsUserSetHidden").get_asBool(); channel->UpdateEncryptionName(); results.insert({channel->StorageId(), channel}); m_pDS->next(); ++iReturn; } m_pDS->close(); } catch (...) { CLog::LogF(LOGERROR, "Couldn't load channels from PVR database"); } } else { CLog::LogF(LOGERROR, "PVR database query failed"); } m_pDS->close(); return iReturn; } bool CPVRDatabase::DeleteChannels() { CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting all channels from the database"); std::unique_lock lock(m_critSection); return DeleteValues("channels"); } bool CPVRDatabase::QueueDeleteQuery(const CPVRChannel& channel) { /* invalid channel */ if (channel.ChannelID() <= 0) { CLog::LogF(LOGERROR, "Invalid channel id: {}", channel.ChannelID()); return false; } CLog::LogFC(LOGDEBUG, LOGPVR, "Queueing delete for channel '{}' from the database", channel.ChannelName()); Filter filter; filter.AppendWhere(PrepareSQL("idChannel = %i", channel.ChannelID())); std::string strQuery; if (BuildSQL(PrepareSQL("DELETE FROM %s ", "channels"), filter, strQuery)) return CDatabase::QueueDeleteQuery(strQuery); return false; } /********** Channel group member methods **********/ bool CPVRDatabase::QueueDeleteQuery(const CPVRChannelGroupMember& groupMember) { CLog::LogFC(LOGDEBUG, LOGPVR, "Queueing delete for channel group member '{}' from the database", groupMember.Channel() ? groupMember.Channel()->ChannelName() : std::to_string(groupMember.ChannelDatabaseID())); Filter filter; filter.AppendWhere(PrepareSQL("idGroup = %i", groupMember.GroupID())); filter.AppendWhere(PrepareSQL("idChannel = %i", groupMember.ChannelDatabaseID())); std::string strQuery; if (BuildSQL(PrepareSQL("DELETE FROM %s ", "map_channelgroups_channels"), filter, strQuery)) return CDatabase::QueueDeleteQuery(strQuery); return false; } /********** Channel group methods **********/ bool CPVRDatabase::RemoveChannelsFromGroup(const CPVRChannelGroup& group) { Filter filter; filter.AppendWhere(PrepareSQL("idGroup = %i", group.GroupID())); std::unique_lock lock(m_critSection); return DeleteValues("map_channelgroups_channels", filter); } bool CPVRDatabase::DeleteChannelGroups() { CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting all channel groups from the database"); std::unique_lock lock(m_critSection); return DeleteValues("channelgroups") && DeleteValues("map_channelgroups_channels"); } bool CPVRDatabase::Delete(const CPVRChannelGroup& group) { /* invalid group id */ if (group.GroupID() <= 0) { CLog::LogF(LOGERROR, "Invalid channel group id: {}", group.GroupID()); return false; } std::unique_lock lock(m_critSection); Filter filter; filter.AppendWhere(PrepareSQL("idGroup = %i", group.GroupID())); filter.AppendWhere(PrepareSQL("bIsRadio = %u", group.IsRadio())); return RemoveChannelsFromGroup(group) && DeleteValues("channelgroups", filter); } int CPVRDatabase::Get(CPVRChannelGroups& results) const { int iLoaded = 0; std::unique_lock lock(m_critSection); const std::string strQuery = PrepareSQL("SELECT * from channelgroups WHERE bIsRadio = %u", results.IsRadio()); if (ResultQuery(strQuery)) { try { while (!m_pDS->eof()) { const std::shared_ptr group = results.CreateChannelGroup(m_pDS->fv("iGroupType").get_asInt(), CPVRChannelsPath(m_pDS->fv("bIsRadio").get_asBool(), m_pDS->fv("sName").get_asString())); group->m_iGroupId = m_pDS->fv("idGroup").get_asInt(); group->m_iGroupType = m_pDS->fv("iGroupType").get_asInt(); group->m_iLastWatched = static_cast(m_pDS->fv("iLastWatched").get_asInt()); group->m_bHidden = m_pDS->fv("bIsHidden").get_asBool(); group->m_iPosition = m_pDS->fv("iPosition").get_asInt(); group->m_iLastOpened = static_cast(m_pDS->fv("iLastOpened").get_asInt64()); results.Update(group); CLog::LogFC(LOGDEBUG, LOGPVR, "Group '{}' loaded from PVR database", group->GroupName()); m_pDS->next(); } m_pDS->close(); iLoaded++; } catch (...) { CLog::LogF(LOGERROR, "Couldn't load channel groups from PVR database. Exception."); } } else { CLog::LogF(LOGERROR, "Couldn't load channel groups from PVR database. Query failed."); } return iLoaded; } std::vector> CPVRDatabase::Get( const CPVRChannelGroup& group, const std::vector>& clients) const { std::vector> results; /* invalid group id */ if (group.GroupID() < 0) { CLog::LogF(LOGERROR, "Invalid channel group id: {}", group.GroupID()); return results; } std::string strQuery = "SELECT map_channelgroups_channels.idChannel, " "map_channelgroups_channels.iChannelNumber, " "map_channelgroups_channels.iSubChannelNumber, " "map_channelgroups_channels.iOrder, " "map_channelgroups_channels.iClientChannelNumber, " "map_channelgroups_channels.iClientSubChannelNumber, " "channels.iClientId, channels.iUniqueId, channels.bIsRadio " "FROM map_channelgroups_channels " "LEFT JOIN channels ON channels.idChannel = map_channelgroups_channels.idChannel " "WHERE map_channelgroups_channels.idGroup = %i "; const std::string clientIds = GetClientIdsSQL(clients); if (!clientIds.empty()) strQuery += "AND " + clientIds; strQuery += " ORDER BY map_channelgroups_channels.iChannelNumber"; std::unique_lock lock(m_critSection); strQuery = PrepareSQL(strQuery, group.GroupID()); if (ResultQuery(strQuery)) { try { while (!m_pDS->eof()) { const auto newMember = std::make_shared(); newMember->m_iChannelDatabaseID = m_pDS->fv("idChannel").get_asInt(); newMember->m_iClientID = m_pDS->fv("iClientId").get_asInt(); newMember->m_iChannelUID = m_pDS->fv("iUniqueId").get_asInt(); newMember->m_iGroupID = group.GroupID(); newMember->m_bIsRadio = m_pDS->fv("bIsRadio").get_asBool(); newMember->m_channelNumber = { static_cast(m_pDS->fv("iChannelNumber").get_asInt()), static_cast(m_pDS->fv("iSubChannelNumber").get_asInt())}; newMember->m_clientChannelNumber = { static_cast(m_pDS->fv("iClientChannelNumber").get_asInt()), static_cast(m_pDS->fv("iClientSubChannelNumber").get_asInt())}; newMember->m_iOrder = static_cast(m_pDS->fv("iOrder").get_asInt()); newMember->SetGroupName(group.GroupName()); results.emplace_back(newMember); m_pDS->next(); } m_pDS->close(); } catch(...) { CLog::LogF(LOGERROR, "Failed to get channel group members"); } } return results; } bool CPVRDatabase::PersistChannels(const CPVRChannelGroup& group) { /* invalid group id */ if (group.GroupID() < 0) { CLog::LogF(LOGERROR, "Invalid channel group id: {}", group.GroupID()); return false; } bool bReturn(true); std::shared_ptr channel; for (const auto& groupMember : group.m_members) { channel = groupMember.second->Channel(); if (channel->IsChanged() || channel->IsNew()) { if (Persist(*channel, false)) { channel->Persisted(); bReturn = true; } } } bReturn &= CommitInsertQueries(); if (bReturn) { std::string strQuery; std::string strValue; for (const auto& groupMember : group.m_members) { channel = groupMember.second->Channel(); strQuery = PrepareSQL("iUniqueId = %i AND iClientId = %i", channel->UniqueID(), channel->ClientID()); strValue = GetSingleValue("channels", "idChannel", strQuery); if (!strValue.empty() && StringUtils::IsInteger(strValue)) { const int iChannelID = std::atoi(strValue.c_str()); channel->SetChannelID(iChannelID); groupMember.second->m_iChannelDatabaseID = iChannelID; } } } return bReturn; } bool CPVRDatabase::PersistGroupMembers(const CPVRChannelGroup& group) { /* invalid group id */ if (group.GroupID() < 0) { CLog::LogF(LOGERROR, "Invalid channel group id: {}", group.GroupID()); return false; } bool bReturn = true; if (group.HasChannels()) { for (const auto& groupMember : group.m_sortedMembers) { if (groupMember->NeedsSave()) { if (groupMember->ChannelDatabaseID() <= 0) { CLog::LogF(LOGERROR, "Invalid channel id: {}", groupMember->ChannelDatabaseID()); continue; } const std::string strWhereClause = PrepareSQL("idChannel = %i AND idGroup = %i AND iChannelNumber = %u AND " "iSubChannelNumber = %u AND " "iOrder = %i AND iClientChannelNumber = %u AND iClientSubChannelNumber = %u", groupMember->ChannelDatabaseID(), group.GroupID(), groupMember->ChannelNumber().GetChannelNumber(), groupMember->ChannelNumber().GetSubChannelNumber(), groupMember->Order(), groupMember->ClientChannelNumber().GetChannelNumber(), groupMember->ClientChannelNumber().GetSubChannelNumber()); const std::string strValue = GetSingleValue("map_channelgroups_channels", "idChannel", strWhereClause); if (strValue.empty()) { const std::string strQuery = PrepareSQL("REPLACE INTO map_channelgroups_channels (" "idGroup, idChannel, iChannelNumber, iSubChannelNumber, iOrder, " "iClientChannelNumber, iClientSubChannelNumber) " "VALUES (%i, %i, %i, %i, %i, %i, %i);", group.GroupID(), groupMember->ChannelDatabaseID(), groupMember->ChannelNumber().GetChannelNumber(), groupMember->ChannelNumber().GetSubChannelNumber(), groupMember->Order(), groupMember->ClientChannelNumber().GetChannelNumber(), groupMember->ClientChannelNumber().GetSubChannelNumber()); QueueInsertQuery(strQuery); } } } bReturn = CommitInsertQueries(); if (bReturn) { for (const auto& groupMember : group.m_sortedMembers) { groupMember->SetSaved(); } } } return bReturn; } /********** Client methods **********/ bool CPVRDatabase::ResetEPG() { std::unique_lock lock(m_critSection); const std::string strQuery = PrepareSQL("UPDATE channels SET idEpg = 0"); return ExecuteQuery(strQuery); } bool CPVRDatabase::Persist(CPVRChannelGroup& group) { bool bReturn(false); if (group.GroupName().empty()) { CLog::LogF(LOGERROR, "Empty group name"); return bReturn; } std::string strQuery; std::unique_lock lock(m_critSection); if (group.HasChanges() || group.IsNew()) { /* insert a new entry when this is a new group, or replace the existing one otherwise */ if (group.IsNew()) strQuery = PrepareSQL("INSERT INTO channelgroups (bIsRadio, iGroupType, sName, iLastWatched, " "bIsHidden, iPosition, iLastOpened) VALUES (%i, %i, '%s', %u, %i, %i, %llu)", (group.IsRadio() ? 1 : 0), group.GroupType(), group.GroupName().c_str(), static_cast(group.LastWatched()), group.IsHidden(), group.GetPosition(), group.LastOpened()); else strQuery = PrepareSQL( "REPLACE INTO channelgroups (idGroup, bIsRadio, iGroupType, sName, iLastWatched, " "bIsHidden, iPosition, iLastOpened) VALUES (%i, %i, %i, '%s', %u, %i, %i, %llu)", group.GroupID(), (group.IsRadio() ? 1 : 0), group.GroupType(), group.GroupName().c_str(), static_cast(group.LastWatched()), group.IsHidden(), group.GetPosition(), group.LastOpened()); bReturn = ExecuteQuery(strQuery); // set the group ID for new groups if (bReturn && group.IsNew()) group.SetGroupID(static_cast(m_pDS->lastinsertid())); } else bReturn = true; /* only persist the channel data for the internal groups */ if (group.IsInternalGroup()) bReturn &= PersistChannels(group); /* persist the group member entries */ if (bReturn) bReturn = PersistGroupMembers(group); return bReturn; } bool CPVRDatabase::Persist(CPVRChannel& channel, bool bCommit) { bool bReturn(false); /* invalid channel */ if (channel.UniqueID() <= 0) { CLog::LogF(LOGERROR, "Invalid channel uid: {}", channel.UniqueID()); return bReturn; } std::unique_lock lock(m_critSection); // Note: Do not use channel.ChannelID value to check presence of channel in channels table. It might not yet be set correctly. std::string strQuery = PrepareSQL("iUniqueId = %i AND iClientId = %i", channel.UniqueID(), channel.ClientID()); const std::string strValue = GetSingleValue("channels", "idChannel", strQuery); if (strValue.empty()) { /* new channel */ strQuery = PrepareSQL( "INSERT INTO channels (" "iUniqueId, bIsRadio, bIsHidden, bIsUserSetIcon, bIsUserSetName, bIsLocked, " "sIconPath, sChannelName, bIsVirtual, bEPGEnabled, sEPGScraper, iLastWatched, iClientId, " "idEpg, bHasArchive, iClientProviderUid, bIsUserSetHidden) " "VALUES (%i, %i, %i, %i, %i, %i, '%s', '%s', %i, %i, '%s', %u, %i, %i, %i, %i, %i)", channel.UniqueID(), (channel.IsRadio() ? 1 : 0), (channel.IsHidden() ? 1 : 0), (channel.IsUserSetIcon() ? 1 : 0), (channel.IsUserSetName() ? 1 : 0), (channel.IsLocked() ? 1 : 0), channel.IconPath().c_str(), channel.ChannelName().c_str(), 0, (channel.EPGEnabled() ? 1 : 0), channel.EPGScraper().c_str(), static_cast(channel.LastWatched()), channel.ClientID(), channel.EpgID(), channel.HasArchive(), channel.ClientProviderUid(), channel.IsUserSetHidden() ? 1 : 0); } else { /* update channel */ strQuery = PrepareSQL( "REPLACE INTO channels (" "iUniqueId, bIsRadio, bIsHidden, bIsUserSetIcon, bIsUserSetName, bIsLocked, " "sIconPath, sChannelName, bIsVirtual, bEPGEnabled, sEPGScraper, iLastWatched, iClientId, " "idChannel, idEpg, bHasArchive, iClientProviderUid, bIsUserSetHidden) " "VALUES (%i, %i, %i, %i, %i, %i, '%s', '%s', %i, %i, '%s', %u, %i, %s, %i, %i, %i, %i)", channel.UniqueID(), (channel.IsRadio() ? 1 : 0), (channel.IsHidden() ? 1 : 0), (channel.IsUserSetIcon() ? 1 : 0), (channel.IsUserSetName() ? 1 : 0), (channel.IsLocked() ? 1 : 0), channel.ClientIconPath().c_str(), channel.ChannelName().c_str(), 0, (channel.EPGEnabled() ? 1 : 0), channel.EPGScraper().c_str(), static_cast(channel.LastWatched()), channel.ClientID(), strValue.c_str(), channel.EpgID(), channel.HasArchive(), channel.ClientProviderUid(), channel.IsUserSetHidden() ? 1 : 0); } if (QueueInsertQuery(strQuery)) { bReturn = true; if (bCommit) bReturn = CommitInsertQueries(); } return bReturn; } bool CPVRDatabase::UpdateLastWatched(const CPVRChannel& channel) { std::unique_lock lock(m_critSection); const std::string strQuery = PrepareSQL("UPDATE channels SET iLastWatched = %u WHERE idChannel = %i", static_cast(channel.LastWatched()), channel.ChannelID()); return ExecuteQuery(strQuery); } bool CPVRDatabase::UpdateLastWatched(const CPVRChannelGroup& group) { std::unique_lock lock(m_critSection); const std::string strQuery = PrepareSQL("UPDATE channelgroups SET iLastWatched = %u WHERE idGroup = %i", static_cast(group.LastWatched()), group.GroupID()); return ExecuteQuery(strQuery); } bool CPVRDatabase::UpdateLastOpened(const CPVRChannelGroup& group) { std::unique_lock lock(m_critSection); const std::string strQuery = PrepareSQL("UPDATE channelgroups SET iLastOpened = %llu WHERE idGroup = %i", group.LastOpened(), group.GroupID()); return ExecuteQuery(strQuery); } /********** Timer methods **********/ std::vector> CPVRDatabase::GetTimers( CPVRTimers& timers, const std::vector>& clients) const { std::vector> result; std::string strQuery = "SELECT * FROM timers "; const std::string clientIds = GetClientIdsSQL(clients); if (!clientIds.empty()) strQuery += "WHERE " + clientIds; std::unique_lock lock(m_critSection); strQuery = PrepareSQL(strQuery); if (ResultQuery(strQuery)) { try { while (!m_pDS->eof()) { std::shared_ptr newTag(new CPVRTimerInfoTag()); newTag->m_iClientIndex = -m_pDS->fv("iClientIndex").get_asInt(); newTag->m_iParentClientIndex = m_pDS->fv("iParentClientIndex").get_asInt(); newTag->m_iClientId = m_pDS->fv("iClientId").get_asInt(); newTag->SetTimerType(CPVRTimerType::CreateFromIds(m_pDS->fv("iTimerType").get_asInt(), -1)); newTag->m_state = static_cast(m_pDS->fv("iState").get_asInt()); newTag->m_strTitle = m_pDS->fv("sTitle").get_asString().c_str(); newTag->m_iClientChannelUid = m_pDS->fv("iClientChannelUid").get_asInt(); newTag->m_strSeriesLink = m_pDS->fv("sSeriesLink").get_asString().c_str(); newTag->SetStartFromUTC(CDateTime::FromDBDateTime(m_pDS->fv("sStartTime").get_asString().c_str())); newTag->m_bStartAnyTime = m_pDS->fv("bStartAnyTime").get_asBool(); newTag->SetEndFromUTC(CDateTime::FromDBDateTime(m_pDS->fv("sEndTime").get_asString().c_str())); newTag->m_bEndAnyTime = m_pDS->fv("bEndAnyTime").get_asBool(); newTag->SetFirstDayFromUTC(CDateTime::FromDBDateTime(m_pDS->fv("sFirstDay").get_asString().c_str())); newTag->m_iWeekdays = m_pDS->fv("iWeekdays").get_asInt(); newTag->m_iEpgUid = m_pDS->fv("iEpgUid").get_asInt(); newTag->m_iMarginStart = m_pDS->fv("iMarginStart").get_asInt(); newTag->m_iMarginEnd = m_pDS->fv("iMarginEnd").get_asInt(); newTag->m_strEpgSearchString = m_pDS->fv("sEpgSearchString").get_asString().c_str(); newTag->m_bFullTextEpgSearch = m_pDS->fv("bFullTextEpgSearch").get_asBool(); newTag->m_iPreventDupEpisodes = m_pDS->fv("iPreventDuplicates").get_asInt(); newTag->m_iPriority = m_pDS->fv("iPrority").get_asInt(); newTag->m_iLifetime = m_pDS->fv("iLifetime").get_asInt(); newTag->m_iMaxRecordings = m_pDS->fv("iMaxRecordings").get_asInt(); newTag->m_iRecordingGroup = m_pDS->fv("iRecordingGroup").get_asInt(); newTag->UpdateSummary(); result.emplace_back(newTag); m_pDS->next(); } m_pDS->close(); } catch (...) { CLog::LogF(LOGERROR, "Could not load timer data from the database"); } } return result; } bool CPVRDatabase::Persist(CPVRTimerInfoTag& timer) { std::unique_lock lock(m_critSection); // insert a new entry if this is a new timer, or replace the existing one otherwise std::string strQuery; if (timer.m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX) strQuery = PrepareSQL("INSERT INTO timers " "(iParentClientIndex, iClientId, iTimerType, iState, sTitle, iClientChannelUid, sSeriesLink, sStartTime," " bStartAnyTime, sEndTime, bEndAnyTime, sFirstDay, iWeekdays, iEpgUid, iMarginStart, iMarginEnd," " sEpgSearchString, bFullTextEpgSearch, iPreventDuplicates, iPrority, iLifetime, iMaxRecordings, iRecordingGroup) " "VALUES (%i, %i, %u, %i, '%s', %i, '%s', '%s', %i, '%s', %i, '%s', %i, %u, %i, %i, '%s', %i, %i, %i, %i, %i, %i);", timer.m_iParentClientIndex, timer.m_iClientId, timer.GetTimerType()->GetTypeId(), timer.m_state, timer.Title().c_str(), timer.m_iClientChannelUid, timer.SeriesLink().c_str(), timer.StartAsUTC().GetAsDBDateTime().c_str(), timer.m_bStartAnyTime ? 1 : 0, timer.EndAsUTC().GetAsDBDateTime().c_str(), timer.m_bEndAnyTime ? 1 : 0, timer.FirstDayAsUTC().GetAsDBDateTime().c_str(), timer.m_iWeekdays, timer.UniqueBroadcastID(), timer.m_iMarginStart, timer.m_iMarginEnd, timer.m_strEpgSearchString.c_str(), timer.m_bFullTextEpgSearch ? 1 : 0, timer.m_iPreventDupEpisodes, timer.m_iPriority, timer.m_iLifetime, timer.m_iMaxRecordings, timer.m_iRecordingGroup); else strQuery = PrepareSQL("REPLACE INTO timers " "(iClientIndex," " iParentClientIndex, iClientId, iTimerType, iState, sTitle, iClientChannelUid, sSeriesLink, sStartTime," " bStartAnyTime, sEndTime, bEndAnyTime, sFirstDay, iWeekdays, iEpgUid, iMarginStart, iMarginEnd," " sEpgSearchString, bFullTextEpgSearch, iPreventDuplicates, iPrority, iLifetime, iMaxRecordings, iRecordingGroup) " "VALUES (%i, %i, %i, %u, %i, '%s', %i, '%s', '%s', %i, '%s', %i, '%s', %i, %u, %i, %i, '%s', %i, %i, %i, %i, %i, %i);", -timer.m_iClientIndex, timer.m_iParentClientIndex, timer.m_iClientId, timer.GetTimerType()->GetTypeId(), timer.m_state, timer.Title().c_str(), timer.m_iClientChannelUid, timer.SeriesLink().c_str(), timer.StartAsUTC().GetAsDBDateTime().c_str(), timer.m_bStartAnyTime ? 1 : 0, timer.EndAsUTC().GetAsDBDateTime().c_str(), timer.m_bEndAnyTime ? 1 : 0, timer.FirstDayAsUTC().GetAsDBDateTime().c_str(), timer.m_iWeekdays, timer.UniqueBroadcastID(), timer.m_iMarginStart, timer.m_iMarginEnd, timer.m_strEpgSearchString.c_str(), timer.m_bFullTextEpgSearch ? 1 : 0, timer.m_iPreventDupEpisodes, timer.m_iPriority, timer.m_iLifetime, timer.m_iMaxRecordings, timer.m_iRecordingGroup); bool bReturn = ExecuteQuery(strQuery); // set the client index for just inserted timers if (bReturn && timer.m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX) { // index must be negative for local timers! timer.m_iClientIndex = -static_cast(m_pDS->lastinsertid()); } return bReturn; } bool CPVRDatabase::Delete(const CPVRTimerInfoTag& timer) { if (timer.m_iClientIndex == PVR_TIMER_NO_CLIENT_INDEX) return false; CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting timer '{}' from the database", timer.m_iClientIndex); std::unique_lock lock(m_critSection); Filter filter; filter.AppendWhere(PrepareSQL("iClientIndex = '%i'", -timer.m_iClientIndex)); return DeleteValues("timers", filter); } bool CPVRDatabase::DeleteTimers() { CLog::LogFC(LOGDEBUG, LOGPVR, "Deleting all timers from the database"); std::unique_lock lock(m_critSection); return DeleteValues("timers"); }