2
0

HttpSessionController.cs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. using MediaBrowser.Common.Net;
  2. using MediaBrowser.Controller.Session;
  3. using MediaBrowser.Model.Entities;
  4. using MediaBrowser.Model.Serialization;
  5. using MediaBrowser.Model.Session;
  6. using MediaBrowser.Model.System;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Globalization;
  10. using System.Linq;
  11. using System.Net;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. namespace MediaBrowser.Server.Implementations.Session
  15. {
  16. public class HttpSessionController : ISessionController, IDisposable
  17. {
  18. private readonly IHttpClient _httpClient;
  19. private readonly IJsonSerializer _json;
  20. private readonly ISessionManager _sessionManager;
  21. public SessionInfo Session { get; private set; }
  22. private readonly string _postUrl;
  23. private Timer _pingTimer;
  24. private DateTime _lastPingTime;
  25. public HttpSessionController(IHttpClient httpClient,
  26. IJsonSerializer json,
  27. SessionInfo session,
  28. string postUrl, ISessionManager sessionManager)
  29. {
  30. _httpClient = httpClient;
  31. _json = json;
  32. Session = session;
  33. _postUrl = postUrl;
  34. _sessionManager = sessionManager;
  35. _pingTimer = new Timer(PingTimerCallback, null, Timeout.Infinite, Timeout.Infinite);
  36. ResetPingTimer();
  37. }
  38. public void OnActivity()
  39. {
  40. }
  41. private string PostUrl
  42. {
  43. get
  44. {
  45. return string.Format("http://{0}{1}", Session.RemoteEndPoint, _postUrl);
  46. }
  47. }
  48. public bool IsSessionActive
  49. {
  50. get
  51. {
  52. return (DateTime.UtcNow - Session.LastActivityDate).TotalMinutes <= 20;
  53. }
  54. }
  55. public bool SupportsMediaControl
  56. {
  57. get { return true; }
  58. }
  59. private async void PingTimerCallback(object state)
  60. {
  61. try
  62. {
  63. await SendMessage("Ping", CancellationToken.None).ConfigureAwait(false);
  64. _lastPingTime = DateTime.UtcNow;
  65. }
  66. catch
  67. {
  68. var lastActivityDate = new[] { _lastPingTime, Session.LastActivityDate }
  69. .Max();
  70. var timeSinceLastPing = DateTime.UtcNow - lastActivityDate;
  71. // We don't want to stop the session due to one single request failure
  72. // At the same time, we don't want the timeout to be too long because it will
  73. // be sitting in active sessions available for remote control, when it's not
  74. if (timeSinceLastPing >= TimeSpan.FromMinutes(5))
  75. {
  76. ReportSessionEnded();
  77. }
  78. }
  79. }
  80. private void ReportSessionEnded()
  81. {
  82. try
  83. {
  84. _sessionManager.ReportSessionEnded(Session.Id);
  85. }
  86. catch (Exception ex)
  87. {
  88. }
  89. }
  90. private void ResetPingTimer()
  91. {
  92. if (_pingTimer != null)
  93. {
  94. _lastPingTime = DateTime.UtcNow;
  95. var period = TimeSpan.FromSeconds(60);
  96. _pingTimer.Change(period, period);
  97. }
  98. }
  99. private Task SendMessage(string name, CancellationToken cancellationToken)
  100. {
  101. return SendMessage(name, new Dictionary<string, string>(), cancellationToken);
  102. }
  103. private async Task SendMessage(string name,
  104. Dictionary<string, string> args,
  105. CancellationToken cancellationToken)
  106. {
  107. var url = PostUrl + "/" + name + ToQueryString(args);
  108. await _httpClient.Post(new HttpRequestOptions
  109. {
  110. Url = url,
  111. CancellationToken = cancellationToken
  112. }).ConfigureAwait(false);
  113. ResetPingTimer();
  114. }
  115. public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
  116. {
  117. return Task.FromResult(true);
  118. }
  119. public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
  120. {
  121. return Task.FromResult(true);
  122. }
  123. public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
  124. {
  125. return Task.FromResult(true);
  126. }
  127. public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
  128. {
  129. var dict = new Dictionary<string, string>();
  130. dict["ItemIds"] = string.Join(",", command.ItemIds);
  131. if (command.StartPositionTicks.HasValue)
  132. {
  133. dict["StartPositionTicks"] = command.StartPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
  134. }
  135. return SendMessage(command.PlayCommand.ToString(), dict, cancellationToken);
  136. }
  137. public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
  138. {
  139. var args = new Dictionary<string, string>();
  140. if (command.Command == PlaystateCommand.Seek)
  141. {
  142. if (!command.SeekPositionTicks.HasValue)
  143. {
  144. throw new ArgumentException("SeekPositionTicks cannot be null");
  145. }
  146. args["SeekPositionTicks"] = command.SeekPositionTicks.Value.ToString(CultureInfo.InvariantCulture);
  147. }
  148. return SendMessage(command.Command.ToString(), args, cancellationToken);
  149. }
  150. public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
  151. {
  152. return Task.FromResult(true);
  153. }
  154. public Task SendRestartRequiredNotification(SystemInfo info, CancellationToken cancellationToken)
  155. {
  156. return SendMessage("RestartRequired", cancellationToken);
  157. }
  158. public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
  159. {
  160. return Task.FromResult(true);
  161. }
  162. public Task SendServerShutdownNotification(CancellationToken cancellationToken)
  163. {
  164. return SendMessage("ServerShuttingDown", cancellationToken);
  165. }
  166. public Task SendServerRestartNotification(CancellationToken cancellationToken)
  167. {
  168. return SendMessage("ServerRestarting", cancellationToken);
  169. }
  170. public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
  171. {
  172. return SendMessage(command.Name, command.Arguments, cancellationToken);
  173. }
  174. public Task SendMessage<T>(string name, T data, CancellationToken cancellationToken)
  175. {
  176. // Not supported or needed right now
  177. return Task.FromResult(true);
  178. }
  179. private string ToQueryString(Dictionary<string, string> nvc)
  180. {
  181. var array = (from item in nvc
  182. select string.Format("{0}={1}", WebUtility.UrlEncode(item.Key), WebUtility.UrlEncode(item.Value)))
  183. .ToArray();
  184. var args = string.Join("&", array);
  185. if (string.IsNullOrEmpty(args))
  186. {
  187. return args;
  188. }
  189. return "?" + args;
  190. }
  191. public void Dispose()
  192. {
  193. DisposePingTimer();
  194. }
  195. private void DisposePingTimer()
  196. {
  197. if (_pingTimer != null)
  198. {
  199. _pingTimer.Dispose();
  200. _pingTimer = null;
  201. }
  202. }
  203. }
  204. }