SatIpDiscovery.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using System.Xml;
  10. using MediaBrowser.Common.Configuration;
  11. using MediaBrowser.Common.Extensions;
  12. using MediaBrowser.Common.Net;
  13. using MediaBrowser.Controller.Configuration;
  14. using MediaBrowser.Controller.Dlna;
  15. using MediaBrowser.Controller.LiveTv;
  16. using MediaBrowser.Controller.Plugins;
  17. using MediaBrowser.Model.LiveTv;
  18. using MediaBrowser.Model.Logging;
  19. using MediaBrowser.Model.Serialization;
  20. using MediaBrowser.Model.Extensions;
  21. namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
  22. {
  23. public class SatIpDiscovery : IServerEntryPoint
  24. {
  25. private readonly IDeviceDiscovery _deviceDiscovery;
  26. private readonly IServerConfigurationManager _config;
  27. private readonly ILogger _logger;
  28. private readonly ILiveTvManager _liveTvManager;
  29. private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
  30. private readonly IHttpClient _httpClient;
  31. private readonly IJsonSerializer _json;
  32. public static SatIpDiscovery Current;
  33. public SatIpDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json)
  34. {
  35. _deviceDiscovery = deviceDiscovery;
  36. _config = config;
  37. _logger = logger;
  38. _liveTvManager = liveTvManager;
  39. _httpClient = httpClient;
  40. _json = json;
  41. Current = this;
  42. }
  43. public void Run()
  44. {
  45. _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
  46. }
  47. void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
  48. {
  49. string st = null;
  50. string nt = null;
  51. e.Headers.TryGetValue("ST", out st);
  52. e.Headers.TryGetValue("NT", out nt);
  53. if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) ||
  54. string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
  55. {
  56. string location;
  57. if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
  58. {
  59. _logger.Debug("SAT IP found at {0}", location);
  60. // Just get the beginning of the url
  61. Uri uri;
  62. if (Uri.TryCreate(location, UriKind.Absolute, out uri))
  63. {
  64. var apiUrl = location.Replace(uri.LocalPath, String.Empty, StringComparison.OrdinalIgnoreCase)
  65. .TrimEnd('/');
  66. AddDevice(apiUrl, location);
  67. }
  68. }
  69. }
  70. }
  71. private async void AddDevice(string deviceUrl, string infoUrl)
  72. {
  73. await _semaphore.WaitAsync().ConfigureAwait(false);
  74. try
  75. {
  76. var options = GetConfiguration();
  77. if (options.TunerHosts.Any(i => string.Equals(i.Type, SatIpHost.DeviceType, StringComparison.OrdinalIgnoreCase) && UriEquals(i.Url, deviceUrl)))
  78. {
  79. return;
  80. }
  81. _logger.Debug("Will attempt to add SAT device {0}", deviceUrl);
  82. var info = await GetInfo(infoUrl, CancellationToken.None).ConfigureAwait(false);
  83. var existing = GetConfiguration().TunerHosts
  84. .FirstOrDefault(i => string.Equals(i.Type, SatIpHost.DeviceType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.DeviceId, info.DeviceId, StringComparison.OrdinalIgnoreCase));
  85. if (existing == null)
  86. {
  87. if (string.IsNullOrWhiteSpace(info.M3UUrl))
  88. {
  89. return;
  90. }
  91. await _liveTvManager.SaveTunerHost(new TunerHostInfo
  92. {
  93. Type = SatIpHost.DeviceType,
  94. Url = deviceUrl,
  95. InfoUrl = infoUrl,
  96. DataVersion = 1,
  97. DeviceId = info.DeviceId,
  98. FriendlyName = info.FriendlyName,
  99. Tuners = info.Tuners,
  100. M3UUrl = info.M3UUrl,
  101. IsEnabled = true
  102. }).ConfigureAwait(false);
  103. }
  104. else
  105. {
  106. existing.Url = deviceUrl;
  107. existing.InfoUrl = infoUrl;
  108. existing.M3UUrl = info.M3UUrl;
  109. existing.FriendlyName = info.FriendlyName;
  110. existing.Tuners = info.Tuners;
  111. await _liveTvManager.SaveTunerHost(existing).ConfigureAwait(false);
  112. }
  113. }
  114. catch (OperationCanceledException)
  115. {
  116. }
  117. catch (NotImplementedException)
  118. {
  119. }
  120. catch (Exception ex)
  121. {
  122. _logger.ErrorException("Error saving device", ex);
  123. }
  124. finally
  125. {
  126. _semaphore.Release();
  127. }
  128. }
  129. private bool UriEquals(string savedUri, string location)
  130. {
  131. return string.Equals(NormalizeUrl(location), NormalizeUrl(savedUri), StringComparison.OrdinalIgnoreCase);
  132. }
  133. private string NormalizeUrl(string url)
  134. {
  135. if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
  136. {
  137. url = "http://" + url;
  138. }
  139. url = url.TrimEnd('/');
  140. // Strip off the port
  141. return new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped);
  142. }
  143. private LiveTvOptions GetConfiguration()
  144. {
  145. return _config.GetConfiguration<LiveTvOptions>("livetv");
  146. }
  147. public void Dispose()
  148. {
  149. }
  150. public async Task<SatIpTunerHostInfo> GetInfo(string url, CancellationToken cancellationToken)
  151. {
  152. var result = new SatIpTunerHostInfo
  153. {
  154. Url = url,
  155. IsEnabled = true,
  156. Type = SatIpHost.DeviceType,
  157. Tuners = 1,
  158. TunersAvailable = 1
  159. };
  160. using (var stream = await _httpClient.Get(url, cancellationToken).ConfigureAwait(false))
  161. {
  162. using (var streamReader = new StreamReader(stream))
  163. {
  164. // Use XmlReader for best performance
  165. using (var reader = XmlReader.Create(streamReader))
  166. {
  167. reader.MoveToContent();
  168. // Loop through each element
  169. while (reader.Read())
  170. {
  171. if (reader.NodeType == XmlNodeType.Element)
  172. {
  173. switch (reader.Name)
  174. {
  175. case "device":
  176. using (var subtree = reader.ReadSubtree())
  177. {
  178. FillFromDeviceNode(result, subtree);
  179. }
  180. break;
  181. default:
  182. reader.Skip();
  183. break;
  184. }
  185. }
  186. }
  187. }
  188. }
  189. }
  190. if (string.IsNullOrWhiteSpace(result.DeviceId))
  191. {
  192. throw new NotImplementedException();
  193. }
  194. // Device hasn't implemented an m3u list
  195. if (string.IsNullOrWhiteSpace(result.M3UUrl))
  196. {
  197. result.IsEnabled = false;
  198. }
  199. else if (!result.M3UUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
  200. {
  201. var fullM3uUrl = url.Substring(0, url.LastIndexOf('/'));
  202. result.M3UUrl = fullM3uUrl + "/" + result.M3UUrl.TrimStart('/');
  203. }
  204. _logger.Debug("SAT device result: {0}", _json.SerializeToString(result));
  205. return result;
  206. }
  207. private void FillFromDeviceNode(SatIpTunerHostInfo info, XmlReader reader)
  208. {
  209. reader.MoveToContent();
  210. while (reader.Read())
  211. {
  212. if (reader.NodeType == XmlNodeType.Element)
  213. {
  214. switch (reader.LocalName)
  215. {
  216. case "UDN":
  217. {
  218. info.DeviceId = reader.ReadElementContentAsString();
  219. break;
  220. }
  221. case "friendlyName":
  222. {
  223. info.FriendlyName = reader.ReadElementContentAsString();
  224. break;
  225. }
  226. case "satip:X_SATIPCAP":
  227. case "X_SATIPCAP":
  228. {
  229. // <satip:X_SATIPCAP xmlns:satip="urn:ses-com:satip">DVBS2-2</satip:X_SATIPCAP>
  230. var value = reader.ReadElementContentAsString() ?? string.Empty;
  231. var parts = value.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
  232. if (parts.Length == 2)
  233. {
  234. int intValue;
  235. if (int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
  236. {
  237. info.TunersAvailable = intValue;
  238. }
  239. if (int.TryParse(parts[0].Substring(parts[0].Length - 1), NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
  240. {
  241. info.Tuners = intValue;
  242. }
  243. }
  244. break;
  245. }
  246. case "satip:X_SATIPM3U":
  247. case "X_SATIPM3U":
  248. {
  249. // <satip:X_SATIPM3U xmlns:satip="urn:ses-com:satip">/channellist.lua?select=m3u</satip:X_SATIPM3U>
  250. info.M3UUrl = reader.ReadElementContentAsString();
  251. break;
  252. }
  253. default:
  254. reader.Skip();
  255. break;
  256. }
  257. }
  258. }
  259. }
  260. }
  261. public class SatIpTunerHostInfo : TunerHostInfo
  262. {
  263. public int TunersAvailable { get; set; }
  264. }
  265. }