diff --git a/ClientServerLib/ClientServerLib/CServer.cs b/ClientServerLib/ClientServerLib/CServer.cs index 76c762de..a9306a8b 100644 --- a/ClientServerLib/ClientServerLib/CServer.cs +++ b/ClientServerLib/ClientServerLib/CServer.cs @@ -1,4 +1,5 @@ -using System; +using ServerLib.PlayerComunication; +using System; using System.Diagnostics; using System.Net; using System.ServiceModel; @@ -73,6 +74,7 @@ namespace ServerLib public CServer(int port, bool encrypted) { _Init(port, encrypted); + CPlayerCommunication.Init(); } public string GetBaseAddress() diff --git a/ClientServerLib/ClientServerLib/CWebservice.cs b/ClientServerLib/ClientServerLib/CWebservice.cs index 8da01ab2..4b319335 100644 --- a/ClientServerLib/ClientServerLib/CWebservice.cs +++ b/ClientServerLib/ClientServerLib/CWebservice.cs @@ -1,9 +1,10 @@ -using System; +using ServerLib.PlayerComunication; +using System; +using System.IO; using System.Net; using System.ServiceModel; -using System.ServiceModel.Web; -using System.IO; using System.ServiceModel.Channels; +using System.ServiceModel.Web; namespace ServerLib { @@ -82,8 +83,8 @@ namespace ServerLib if (profile.ProfileId != -1) //-1 is the id for a new profile { - if (CSessionControl.GetUserIdFromSession(sessionKey) != profile.ProfileId - && !(_CheckRight(EUserRights.EditAllProfiles) )) + if (CSessionControl.GetUserIdFromSession(sessionKey) != profile.ProfileId + && !(_CheckRight(EUserRights.EditAllProfiles))) { return; } @@ -303,7 +304,7 @@ namespace ServerLib { return true; } - + #endregion #region songs @@ -403,7 +404,7 @@ namespace ServerLib CServer.SetUserRole(profileId, userRole); } } - + public bool HasUserRight(int right) { return _CheckRightWithNoErrorMessage((EUserRights)right); @@ -452,7 +453,26 @@ namespace ServerLib return true; } + public SPlayerComunicationFrame[] PlayerComunication(SPlayerComunicationFrame[] recivedData) + { + Guid sessionKey = _GetSession(); + + if (sessionKey == Guid.Empty) + { + if (WebOperationContext.Current != null) + { + WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Forbidden; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "No session"; + } + + return null; + } + int userId = CSessionControl.GetUserIdFromSession(sessionKey); + CPlayerCommunication.AddMessagesFromPlayer(recivedData, userId); + + return CPlayerCommunication.GetAllMessagesForPlayer(userId); + } #endregion - + } } diff --git a/ClientServerLib/ClientServerLib/DataStructs.cs b/ClientServerLib/ClientServerLib/DataStructs.cs index cf52fd80..faba1680 100644 --- a/ClientServerLib/ClientServerLib/DataStructs.cs +++ b/ClientServerLib/ClientServerLib/DataStructs.cs @@ -1,9 +1,10 @@ -using System; +using ServerLib.PlayerComunication.DataTypes; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; using System.Linq; using System.Runtime.Serialization; -using System.Drawing; -using System.IO; -using System.Drawing.Imaging; using System.Text.RegularExpressions; namespace ServerLib @@ -108,8 +109,8 @@ namespace ServerLib public delegate SSongInfo[] GetAllSongsDelegate(); public delegate int GetCurrentSongIdDelegate(); - - + + [DataContract] public struct SSongInfo { @@ -155,26 +156,26 @@ namespace ServerLib [DataContract] public struct SPlaylistSongInfo { - [DataMember] + [DataMember] public SSongInfo Song; [DataMember] public int PlaylistId; - [DataMember] + [DataMember] public int PlaylistPosition; [DataMember] public int GameMode; } - + [DataContract] public struct SPlaylistInfo { - [DataMember] + [DataMember] public int PlaylistId; - [DataMember] + [DataMember] public string PlaylistName; - [DataMember] + [DataMember] public int SongCount; - [DataMember] + [DataMember] public string LastChanged; } @@ -192,5 +193,18 @@ namespace ServerLib public delegate int GetUserIdFromUsernameDelegate(string username); + [DataContract] + public struct SPlayerComunicationFrame + { + [DataMember] + public int ProfileId; + [DataMember] + public DateTime ValidTill; + [DataMember] + public EPlayerComunicationType Type; + [DataMember] + public String Data; + } + #endregion } diff --git a/ClientServerLib/ClientServerLib/ICWebservice.cs b/ClientServerLib/ClientServerLib/ICWebservice.cs index 3e79d8ce..b7401c6e 100644 --- a/ClientServerLib/ClientServerLib/ICWebservice.cs +++ b/ClientServerLib/ClientServerLib/ICWebservice.cs @@ -209,6 +209,13 @@ namespace ServerLib UriTemplate = "/hasUserRight?right={right}")] bool HasUserRight(int right); + [OperationContract] + [WebInvoke(Method = "POST", + ResponseFormat = WebMessageFormat.Json, + RequestFormat = WebMessageFormat.Json, + UriTemplate = "/playerComunication")] + SPlayerComunicationFrame[] PlayerComunication(SPlayerComunicationFrame[] recivedData); + #endregion } } \ No newline at end of file diff --git a/ClientServerLib/ClientServerLib/PlayerComunication/CUserCommunication.cs b/ClientServerLib/ClientServerLib/PlayerComunication/CUserCommunication.cs new file mode 100644 index 00000000..d1a3fce3 --- /dev/null +++ b/ClientServerLib/ClientServerLib/PlayerComunication/CUserCommunication.cs @@ -0,0 +1,209 @@ +using ServerLib.PlayerComunication.DataTypes; +using System; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Threading; +using System.Web.Script.Serialization; + +namespace ServerLib.PlayerComunication +{ + public static class CPlayerCommunication + { + public delegate void IncommingMessageDelegate(CPlayerComunicationData data); + + private static readonly Dictionary> _OutgoingMessagesPerUser + = new Dictionary>(); + + private static readonly Queue _IncommingMessages = new Queue(); + + private static readonly Dictionary> _IncommingMessageHandler + = new Dictionary>(); + + private static readonly Dictionary> _Subscriptions + = new Dictionary>(); + + private static readonly Dictionary _TypeMapping + = new Dictionary(); + + internal static void Init() + { + SetDataType(EPlayerComunicationType.RegisterSubscription, typeof(CPlayerComunicationDataSubscription)); + AddIncommingHandler(EPlayerComunicationType.RegisterSubscription, + data => _AddSubscription(((CPlayerComunicationDataSubscription)data).Type, + ((CPlayerComunicationDataSubscription)data).PlayerId)); + + SetDataType(EPlayerComunicationType.UnregisterSubscription, typeof(CPlayerComunicationDataSubscription)); + AddIncommingHandler(EPlayerComunicationType.UnregisterSubscription, + data => _RemoveSubscription(((CPlayerComunicationDataSubscription)data).Type, + ((CPlayerComunicationDataSubscription)data).PlayerId)); + } + + public static void AddNewPlayerMessage(int targetPlayerId, + EPlayerComunicationType type, ISerializable data, + DateTime validTill) + { + if (!_OutgoingMessagesPerUser.ContainsKey(targetPlayerId)) + { + _OutgoingMessagesPerUser.Add(targetPlayerId, new List(2)); + } + + _OutgoingMessagesPerUser[targetPlayerId].Add(new SPlayerComunicationFrame + { + ProfileId = targetPlayerId, + Type = type, + Data = _SerializeHelper(data), + ValidTill = validTill + }); + } + + public static void AddNewPlayerMessage(int tagertPlayerId, + EPlayerComunicationType type, ISerializable data) + { + AddNewPlayerMessage(tagertPlayerId, type, data, DateTime.Now.AddMinutes(1)); + } + + public static void AddNewPlayerMessage(EPlayerComunicationType type, ISerializable data, + DateTime validTill) + { + if (_Subscriptions.ContainsKey(type)) + { + foreach (int subscribedPlayers in _Subscriptions[type]) + { + AddNewPlayerMessage(subscribedPlayers, type, data, validTill); + } + } + + } + + public static void AddNewPlayerMessage(EPlayerComunicationType type, ISerializable data) + { + AddNewPlayerMessage(type, data, DateTime.Now.AddMinutes(1)); + } + + internal static SPlayerComunicationFrame[] GetAllMessagesForPlayer(int playerId) + { + SPlayerComunicationFrame[] messages = null; + if (_OutgoingMessagesPerUser.ContainsKey(playerId)) + { + messages = _OutgoingMessagesPerUser[playerId].ToArray(); + _OutgoingMessagesPerUser[playerId].Clear(); + } + return new SPlayerComunicationFrame[]{ + new SPlayerComunicationFrame() + { + ProfileId = 0, + Type = EPlayerComunicationType.RegisterSubscription, + ValidTill = DateTime.Now.AddMinutes(1), + Data = _SerializeHelper(new CPlayerComunicationDataString(){Data = "Test"}) + } + }; + //return messages; + } + + public static void RemoveAllMessages(int playerId, EPlayerComunicationType type) + { + if (_OutgoingMessagesPerUser.ContainsKey(playerId)) + { + _OutgoingMessagesPerUser[playerId].RemoveAll(m => m.Type == type); + } + } + + internal static void AddMessagesFromPlayer(IEnumerable messages, int playerId) + { + if (messages == null || playerId < 0) + { + return; + } + + foreach (SPlayerComunicationFrame message in messages) + { + SPlayerComunicationFrame data = message; + data.ProfileId = playerId; + _IncommingMessages.Enqueue(data); + } + + if (_IncommingMessages.Count > 0) + { + ThreadPool.QueueUserWorkItem(_ProcessIncommingQueue); + } + } + + private static void _ProcessIncommingQueue(object dummy) + { + lock (_IncommingMessages) + { + foreach (SPlayerComunicationFrame message in _IncommingMessages) + { + if (_IncommingMessageHandler.ContainsKey(message.Type)) + { + List handlers = _IncommingMessageHandler[message.Type]; + var deserializedData = _DeserializeHelper(message.Data, message.Type); + foreach (IncommingMessageDelegate handler in handlers) + { + handler(deserializedData); + } + } + } + } + } + + public static void SetDataType(EPlayerComunicationType type, Type dataType) + { + if (_TypeMapping.ContainsKey(type)) + { + _TypeMapping.Add(type, dataType); + } + else + { + _TypeMapping[type] = dataType; + } + } + + public static void AddIncommingHandler(EPlayerComunicationType type, IncommingMessageDelegate handler) + { + if (!_IncommingMessageHandler.ContainsKey(type)) + { + _IncommingMessageHandler.Add(type, new List(2)); + } + + _IncommingMessageHandler[type].Add(handler); + } + + private static void _AddSubscription(EPlayerComunicationType type, int playerId) + { + if (!_Subscriptions.ContainsKey(type)) + { + _Subscriptions.Add(type, new List(2)); + } + if (!_Subscriptions[type].Contains(playerId)) + { + _Subscriptions[type].Add(playerId); + } + } + + private static void _RemoveSubscription(EPlayerComunicationType type, int playerId) + { + if (_Subscriptions.ContainsKey(type) && _Subscriptions[type].Contains(playerId)) + { + _Subscriptions[type].Remove(playerId); + } + } + + private static CPlayerComunicationData _DeserializeHelper(String data, EPlayerComunicationType type) + { + if (_TypeMapping.ContainsKey(type)) + { + return (CPlayerComunicationData)(new JavaScriptSerializer()).Deserialize(data, _TypeMapping[type]); + } + else + { + return new CPlayerComunicationDataString { Data = data }; + } + } + + private static string _SerializeHelper(Object data) + { + return (new JavaScriptSerializer()).Serialize(data); + } + } +} diff --git a/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/CPlayerComunicationData.cs b/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/CPlayerComunicationData.cs new file mode 100644 index 00000000..93a84041 --- /dev/null +++ b/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/CPlayerComunicationData.cs @@ -0,0 +1,10 @@ + +using System; + +namespace ServerLib.PlayerComunication.DataTypes +{ + [Serializable] + public abstract class CPlayerComunicationData + { + } +} diff --git a/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/CPlayerComunicationDataString.cs b/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/CPlayerComunicationDataString.cs new file mode 100644 index 00000000..dc933e80 --- /dev/null +++ b/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/CPlayerComunicationDataString.cs @@ -0,0 +1,13 @@ + +using System; +using System.Runtime.Serialization; + +namespace ServerLib.PlayerComunication.DataTypes +{ + [Serializable] + public class CPlayerComunicationDataString : CPlayerComunicationData + { + [DataMember] + public string Data { get; set; } + } +} diff --git a/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/CPlayerComunicationDataSubscription.cs b/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/CPlayerComunicationDataSubscription.cs new file mode 100644 index 00000000..e68c9630 --- /dev/null +++ b/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/CPlayerComunicationDataSubscription.cs @@ -0,0 +1,15 @@ + +using System; +using System.Runtime.Serialization; + +namespace ServerLib.PlayerComunication.DataTypes +{ + [Serializable] + public class CPlayerComunicationDataSubscription : CPlayerComunicationData + { + [DataMember] + public EPlayerComunicationType Type { get; set; } + [DataMember] + public int PlayerId { get; set; } + } +} diff --git a/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/EPlayerComunicationType.cs b/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/EPlayerComunicationType.cs new file mode 100644 index 00000000..de16c795 --- /dev/null +++ b/ClientServerLib/ClientServerLib/PlayerComunication/DataTypes/EPlayerComunicationType.cs @@ -0,0 +1,12 @@ + +using System; + +namespace ServerLib.PlayerComunication.DataTypes +{ + [Serializable] + public enum EPlayerComunicationType + { + RegisterSubscription, + UnregisterSubscription + } +} diff --git a/ClientServerLib/ClientServerLib/ServerLib.csproj b/ClientServerLib/ClientServerLib/ServerLib.csproj index 21ee0f0e..27606601 100644 --- a/ClientServerLib/ClientServerLib/ServerLib.csproj +++ b/ClientServerLib/ClientServerLib/ServerLib.csproj @@ -58,6 +58,7 @@ + @@ -67,9 +68,14 @@ + + + + + diff --git a/Vocaluxe.sln b/Vocaluxe.sln index 5d225110..091b83d9 100644 --- a/Vocaluxe.sln +++ b/Vocaluxe.sln @@ -183,4 +183,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection EndGlobal diff --git a/Vocaluxe/Website/js/main.js b/Vocaluxe/Website/js/main.js index 53b892be..75220d70 100644 --- a/Vocaluxe/Website/js/main.js +++ b/Vocaluxe/Website/js/main.js @@ -8,6 +8,7 @@ var customSelectPlaylistSongCallback = null; var sessionId = ""; var serverBaseAddress = ""; var translationLoaded; +var playerComunication = new PlayerComunication(); if (document.location.protocol == "file:") { if (typeof (window.deviceAndJqmLoaded) == "undefined") { @@ -39,7 +40,6 @@ function start() { initMainPageHandler(); initDiscoverPageHandler(); initLoginPageHandler(); - initHeartbeat(); initVideoPopup(); } @@ -1055,33 +1055,15 @@ function logout() { profileIdRequest = -1; songIdRequest = -1; sessionId = ""; + + playerComunication.reset(); + if (window.localStorage) { window.localStorage.setItem("VocaluxeSessionKey", ""); } $.mobile.changePage("#login", { transition: "slidefade" }); } -function checkSession() { - if (ownProfileId == -1 - && profileIdRequest == -1 - && ($.mobile.activePage.attr("id") == "displayProfile" || $.mobile.activePage.attr("id") == "login" || $.mobile.activePage.attr("id") == "discover")) { - return; - } - request({ - url: "getOwnProfileId" - }, "noOverlay").done(function (result) { - if (result == -1) { - logout(); - } - }).fail(function (result) { - logout(); - }); -} - -function initHeartbeat() { - setInterval(checkSession, 20000); -} - function request(data, message) { if ((typeof message) == "undefined" || message == null) { message = "Loading..."; @@ -1286,3 +1268,121 @@ function translate() { $('body').i18n(); repairButtons(); } + +function PlayerComunication() { + var playerComunicationToServer = []; + var playerComunicationFromServerHandler = {}; + var defaultHeartbeatIntervall = 5000; + var heartbeatIntervalls = []; + var heartbeatIntervallId; + + var init = function () { + startHeartbeat(); + }; + + var checkPlayerMessages = function () { + if (ownProfileId == -1 + && profileIdRequest == -1 + && ($.mobile.activePage.attr("id") == "displayProfile" + || $.mobile.activePage.attr("id") == "login" + || $.mobile.activePage.attr("id") == "discover")) { + return; + } + + request({ + url: "playerComunication", + type: "POST", + dataType: "json", + contentType: "application/json", + data: JSON.stringify(playerComunicationToServer) + }, "noOverlay").done(function (result) { + if (result.length > 0) { + handlePlayerComunicationFromServer(result); + } + }).fail(function (result) { + if (result.status == 403 || result.status == 0) { + logout(); + } + }); + + if (playerComunicationToServer.length > 0) { + setTimeout(checkPlayerMessages, 50); + } + + playerComunicationToServer = []; + }; + + var handlePlayerComunicationFromServer = function (items) { + for (var i = 0; i < items.length; i++) { + if (items[i].ProfileId == ownProfileId && playerComunicationFromServerHandler[items[i].Type]) { + if (new Date() - new Date(parseInt("\/Date(1234656000000)\/".match(/[0-9]+/)[0])) <= 0) { + var deserializedData = JSON.parse(items[i].Data); + for (var j = 0; j < playerComunicationFromServerHandler[items[i].Type]; j++) { + playerComunicationFromServerHandler[items[i].Type][j](deserializedData); + } + } + } + } + }; + + var startHeartbeat = function () { + var interv = defaultHeartbeatIntervall; + for (var i = 0; i < heartbeatIntervalls.length; i++) { + if (interv > heartbeatIntervalls[i] && heartbeatIntervalls[i] >= 50) { + interv = heartbeatIntervalls[i]; + } + } + + if (interv == defaultHeartbeatIntervall && heartbeatIntervalls.length != 0) { + heartbeatIntervalls = []; + } + + clearInterval(heartbeatIntervallId); + heartbeatIntervallId = setInterval(checkPlayerMessages, interv); + }; + + var sendPlayerComunicationToServer = function (packet) { + playerComunicationToServer.push(packet); + }; + + this.registerPlayerComunicationFromServerHandler = function (type, handler) { + if (!playerComunicationFromServerHandler[type]) { + playerComunicationFromServerHandler[type] = []; + } + playerComunicationFromServerHandler[type].push(handler); + }; + + this.sendDataToServer = function (data, type) { + sendPlayerComunicationToServer({ + "ProfileId": ownProfileId, + "ValidTill": "\/Date(" + (((new Date())).getTime() + 60000) + ")\/", + "Type": type, + "Data": JSON.stringify(data) + }); + }; + + //mim 50ms + this.requestIntervall = function (newIntervall) { + heartbeatIntervalls.push(newIntervall); + }; + + this.removeIntervall = function (oldIntervall) { + var index = heartbeatIntervalls.indexOf(oldIntervall); + if (index > -1) { + heartbeatIntervalls.splice(oldIntervall, 1); + } + }; + + this.reset = function () { + playerComunicationToServer = []; + playerComunicationFromServerHandler = {}; + heartbeatIntervalls = []; + }; + + this.EPlayerComunicationType = { + "RegisterSubscription": 0, + "UnregisterSubscription": 1 + }; + + init(); +} \ No newline at end of file