#region Copyright (C) 2005-2011 Team MediaPortal // Copyright (C) 2005-2011 Team MediaPortal // http://www.team-mediaportal.com // // MediaPortal is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // MediaPortal is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with MediaPortal. If not, see . #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using TvLibrary.Interfaces; using TvLibrary.Log; using TvControl; using TvDatabase; namespace TvService { public class ChannelStates : CardAllocationBase { #region private members private readonly object _lock = new object(); public ChannelStates(TvBusinessLayer businessLayer, TVController controller) : base(businessLayer, controller) { LogEnabled = false; } private void UpdateChannelStateUsers(IEnumerable allUsers, ChannelState chState, int channelId) { foreach (IUser t in allUsers) { IUser u = null; try { u = t; } catch (NullReferenceException) {} if (u == null) continue; if (u.IsAdmin) continue; //scheduler users do not need to have their channelstates set. try { UpdateChannelStateUser(u, chState, channelId); } catch (NullReferenceException) {} } } private static void UpdateChannelStateUser(IUser user, ChannelState chState, int channelId) { ChannelState currentChState; bool stateExists = user.ChannelStates.TryGetValue(channelId, out currentChState); if (stateExists) { if (chState == ChannelState.nottunable) { return; } bool recording = (currentChState == ChannelState.recording); if (!recording) { user.ChannelStates[channelId] = chState; //add key if does not exist, or update existing one. } } else { user.ChannelStates[channelId] = chState; //add key if does not exist, or update existing one. } } private static IList GetActiveUsers(IDictionary cards) { // find all users var allUsers = new List(); try { ICollection cardHandlers = cards.Values; foreach (ITvCardHandler cardHandler in cardHandlers) { //get a list of all users for this card IUser[] usersAvail = cardHandler.Users.GetUsers(); if (usersAvail != null) { foreach (IUser tmpUser in usersAvail.Where(tmpUser => !tmpUser.IsAdmin)) { tmpUser.ChannelStates = new Dictionary(); allUsers.Add(tmpUser); } } } } catch (InvalidOperationException tex) { Log.Error("ChannelState: Possible race condition occured when getting users - {0}", tex); } return allUsers; } private void DoSetChannelStates(IDictionary cards, ICollection channels, ICollection allUsers, IController tvController) { lock (_lock) { Stopwatch stopwatch = Stopwatch.StartNew(); try { //construct list of all cards we can use to tune to the new channel Log.Debug("Controller: DoSetChannelStates for {0} channels", channels.Count); if (allUsers == null || allUsers.Count == 0) { return; // no users, no point in continuing. } IDictionary timeshiftingAndRecordingStates = null; ICollection cardHandlers = cards.Values; foreach (Channel ch in channels) { if (!ch.VisibleInGuide) { UpdateChannelStateUsers(allUsers, ChannelState.nottunable, ch.IdChannel); continue; } ICollection tuningDetails = CardAllocationCache.GetTuningDetailsByChannelId(ch); bool isValidTuningDetails = IsValidTuningDetails(tuningDetails); if (!isValidTuningDetails) { UpdateChannelStateUsers(allUsers, ChannelState.nottunable, ch.IdChannel); continue; } foreach (IChannel tuningDetail in tuningDetails) { foreach (ITvCardHandler cardHandler in cardHandlers) { //check if card is enabled if (!cardHandler.DataBaseCard.Enabled) { //not enabled, so skip the card UpdateChannelStateUsers(allUsers, ChannelState.nottunable, ch.IdChannel); continue; } if (!cardHandler.Tuner.CanTune(tuningDetail)) { //card cannot tune to this channel, so skip it UpdateChannelStateUsers(allUsers, ChannelState.nottunable, ch.IdChannel); continue; } //check if channel is mapped to this card and that the mapping is not for "Epg Only" bool isChannelMappedToCard = CardAllocationCache.IsChannelMappedToCard(ch, cardHandler.DataBaseCard);//, channelMapping); if (!isChannelMappedToCard) { UpdateChannelStateUsers(allUsers, ChannelState.nottunable, ch.IdChannel); continue; } if (!tuningDetail.FreeToAir && !cardHandler.DataBaseCard.CAM) { UpdateChannelStateUsers(allUsers, ChannelState.nottunable, ch.IdChannel); continue; } //ok card could be used to tune to this channel //now we check if its free... CheckTransponderAllUsers(ch, allUsers, cardHandler, tuningDetail); } //while card end } //foreach tuningdetail end //only query once if (timeshiftingAndRecordingStates == null) { Stopwatch stopwatchTimeshiftingAndRecording = Stopwatch.StartNew(); timeshiftingAndRecordingStates = tvController.GetAllTimeshiftingAndRecordingChannels(); stopwatchTimeshiftingAndRecording.Stop(); Log.Info("ChannelStates.GetAllTimeshiftingAndRecordingChannels took {0} msec", stopwatchTimeshiftingAndRecording.ElapsedMilliseconds); } UpdateRecOrTSChannelStateForUsers(ch, allUsers, timeshiftingAndRecordingStates); } RemoveAllTunableChannelStates(allUsers); } catch (ThreadAbortException) { Log.Info("ChannelState.DoSetChannelStates: thread obsolete and aborted."); } catch (InvalidOperationException tex) { Log.Error("ChannelState.DoSetChannelStates: Possible race condition occured setting channel states - {0}", tex); } catch (Exception ex) { Log.Error("ChannelState.DoSetChannelStates: An unknown error occured while setting channel states - {0}\n{1}", ex.Message, ex); } finally { stopwatch.Stop(); Log.Info("ChannelStates.DoSetChannelStates took {0} msec", stopwatch.ElapsedMilliseconds); } } } private static void RemoveAllTunableChannelStates(IEnumerable allUsers) { foreach (IUser user in allUsers) { var keysToDelete = user.ChannelStates.Where(x => x.Value == ChannelState.tunable).Select(kvp => kvp.Key).ToList(); foreach (int key in keysToDelete) { user.ChannelStates.Remove(key); } } } private void UpdateRecOrTSChannelStateForUsers(Channel ch, IEnumerable allUsers, IDictionary TSandRecStates) { ChannelState cs; TSandRecStates.TryGetValue(ch.IdChannel, out cs); if (cs == ChannelState.recording) { UpdateChannelStateUsers(allUsers, ChannelState.recording, ch.IdChannel); } else if (cs == ChannelState.timeshifting) { UpdateChannelStateUsers(allUsers, ChannelState.timeshifting, ch.IdChannel); } } private void CheckTransponderAllUsers(Channel ch, IEnumerable allUsers, ITvCardHandler tvcard, IChannel tuningDetail) { foreach (IUser user in allUsers) { //ignore admin users, like scheduler if (!user.IsAdmin) { bool checkTransponder = CheckTransponder(user, tvcard, tuningDetail); if (checkTransponder) { UpdateChannelStateUser(user, ChannelState.tunable, ch.IdChannel); } else { UpdateChannelStateUser(user, ChannelState.nottunable, ch.IdChannel); } } } } #endregion #region public members public void SetChannelStates(IDictionary cards, ICollection channels, IController tvController) { if (channels == null) return; // find all users ICollection allUsers = GetActiveUsers(cards); //call the real work as a thread in order to avoid slower channel changes. Task.Factory.StartNew(new Action(() => DoSetChannelStates(cards, channels, allUsers, tvController)), TaskCreationOptions.PreferFairness); } /// /// Gets a list of all channel states /// /// dictionary containing all channel states of the channels supplied public Dictionary GetChannelStates(IDictionary cards, ICollection channels, ref IUser user, IController tvController) { if (channels == null) return null; var allUsers = new List(); if (user != null) allUsers.Add(user); DoSetChannelStates(cards, channels, allUsers, tvController); return allUsers.Count > 0 ? allUsers[0].ChannelStates : new Dictionary(); } #endregion } }