DlnaManager.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using Emby.Dlna.Profiles;
  8. using Emby.Dlna.Server;
  9. using MediaBrowser.Common.Configuration;
  10. using MediaBrowser.Common.Extensions;
  11. using MediaBrowser.Controller;
  12. using MediaBrowser.Controller.Dlna;
  13. using MediaBrowser.Controller.Drawing;
  14. using MediaBrowser.Model.Dlna;
  15. using MediaBrowser.Model.Drawing;
  16. using MediaBrowser.Model.IO;
  17. using MediaBrowser.Model.Reflection;
  18. using MediaBrowser.Model.Serialization;
  19. using Microsoft.Extensions.Logging;
  20. namespace Emby.Dlna
  21. {
  22. public class DlnaManager : IDlnaManager
  23. {
  24. private readonly IApplicationPaths _appPaths;
  25. private readonly IXmlSerializer _xmlSerializer;
  26. private readonly IFileSystem _fileSystem;
  27. private readonly ILogger _logger;
  28. private readonly IJsonSerializer _jsonSerializer;
  29. private readonly IServerApplicationHost _appHost;
  30. private readonly IAssemblyInfo _assemblyInfo;
  31. private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
  32. public DlnaManager(
  33. IXmlSerializer xmlSerializer,
  34. IFileSystem fileSystem,
  35. IApplicationPaths appPaths,
  36. ILoggerFactory loggerFactory,
  37. IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo)
  38. {
  39. _xmlSerializer = xmlSerializer;
  40. _fileSystem = fileSystem;
  41. _appPaths = appPaths;
  42. _logger = loggerFactory.CreateLogger("Dlna");
  43. _jsonSerializer = jsonSerializer;
  44. _appHost = appHost;
  45. _assemblyInfo = assemblyInfo;
  46. }
  47. public void InitProfiles()
  48. {
  49. try
  50. {
  51. ExtractSystemProfiles();
  52. LoadProfiles();
  53. }
  54. catch (Exception ex)
  55. {
  56. _logger.LogError(ex, "Error extracting DLNA profiles.");
  57. }
  58. }
  59. private void LoadProfiles()
  60. {
  61. var list = GetProfiles(UserProfilesPath, DeviceProfileType.User)
  62. .OrderBy(i => i.Name)
  63. .ToList();
  64. list.AddRange(GetProfiles(SystemProfilesPath, DeviceProfileType.System)
  65. .OrderBy(i => i.Name));
  66. }
  67. public IEnumerable<DeviceProfile> GetProfiles()
  68. {
  69. lock (_profiles)
  70. {
  71. var list = _profiles.Values.ToList();
  72. return list
  73. .OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
  74. .ThenBy(i => i.Item1.Info.Name)
  75. .Select(i => i.Item2)
  76. .ToList();
  77. }
  78. }
  79. public DeviceProfile GetDefaultProfile()
  80. {
  81. return new DefaultProfile();
  82. }
  83. public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
  84. {
  85. if (deviceInfo == null)
  86. {
  87. throw new ArgumentNullException(nameof(deviceInfo));
  88. }
  89. var profile = GetProfiles()
  90. .FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
  91. if (profile != null)
  92. {
  93. _logger.LogDebug("Found matching device profile: {0}", profile.Name);
  94. }
  95. else
  96. {
  97. LogUnmatchedProfile(deviceInfo);
  98. }
  99. return profile;
  100. }
  101. private void LogUnmatchedProfile(DeviceIdentification profile)
  102. {
  103. var builder = new StringBuilder();
  104. builder.AppendLine("No matching device profile found. The default will need to be used.");
  105. builder.AppendLine(string.Format("DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty));
  106. builder.AppendLine(string.Format("FriendlyName:{0}", profile.FriendlyName ?? string.Empty));
  107. builder.AppendLine(string.Format("Manufacturer:{0}", profile.Manufacturer ?? string.Empty));
  108. builder.AppendLine(string.Format("ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty));
  109. builder.AppendLine(string.Format("ModelDescription:{0}", profile.ModelDescription ?? string.Empty));
  110. builder.AppendLine(string.Format("ModelName:{0}", profile.ModelName ?? string.Empty));
  111. builder.AppendLine(string.Format("ModelNumber:{0}", profile.ModelNumber ?? string.Empty));
  112. builder.AppendLine(string.Format("ModelUrl:{0}", profile.ModelUrl ?? string.Empty));
  113. builder.AppendLine(string.Format("SerialNumber:{0}", profile.SerialNumber ?? string.Empty));
  114. _logger.LogInformation(builder.ToString());
  115. }
  116. private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
  117. {
  118. if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
  119. {
  120. if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
  121. return false;
  122. }
  123. if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
  124. {
  125. if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
  126. return false;
  127. }
  128. if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
  129. {
  130. if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
  131. return false;
  132. }
  133. if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
  134. {
  135. if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
  136. return false;
  137. }
  138. if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
  139. {
  140. if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
  141. return false;
  142. }
  143. if (!string.IsNullOrEmpty(profileInfo.ModelName))
  144. {
  145. if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
  146. return false;
  147. }
  148. if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
  149. {
  150. if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
  151. return false;
  152. }
  153. if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
  154. {
  155. if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
  156. return false;
  157. }
  158. if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
  159. {
  160. if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
  161. return false;
  162. }
  163. return true;
  164. }
  165. private bool IsRegexMatch(string input, string pattern)
  166. {
  167. try
  168. {
  169. return Regex.IsMatch(input, pattern);
  170. }
  171. catch (ArgumentException ex)
  172. {
  173. _logger.LogError(ex, "Error evaluating regex pattern {Pattern}", pattern);
  174. return false;
  175. }
  176. }
  177. public DeviceProfile GetProfile(IDictionary<string, string> headers)
  178. {
  179. if (headers == null)
  180. {
  181. throw new ArgumentNullException(nameof(headers));
  182. }
  183. // Convert to case insensitive
  184. headers = new Dictionary<string, string>(headers, StringComparer.OrdinalIgnoreCase);
  185. var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
  186. if (profile != null)
  187. {
  188. _logger.LogDebug("Found matching device profile: {0}", profile.Name);
  189. }
  190. else
  191. {
  192. var headerString = string.Join(", ", headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
  193. _logger.LogDebug("No matching device profile found. {0}", headerString);
  194. }
  195. return profile;
  196. }
  197. private bool IsMatch(IDictionary<string, string> headers, DeviceIdentification profileInfo)
  198. {
  199. return profileInfo.Headers.Any(i => IsMatch(headers, i));
  200. }
  201. private bool IsMatch(IDictionary<string, string> headers, HttpHeaderInfo header)
  202. {
  203. // Handle invalid user setup
  204. if (string.IsNullOrEmpty(header.Name))
  205. {
  206. return false;
  207. }
  208. if (headers.TryGetValue(header.Name, out string value))
  209. {
  210. switch (header.Match)
  211. {
  212. case HeaderMatchType.Equals:
  213. return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
  214. case HeaderMatchType.Substring:
  215. var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
  216. //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
  217. return isMatch;
  218. case HeaderMatchType.Regex:
  219. return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
  220. default:
  221. throw new ArgumentException("Unrecognized HeaderMatchType");
  222. }
  223. }
  224. return false;
  225. }
  226. private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
  227. private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
  228. private IEnumerable<DeviceProfile> GetProfiles(string path, DeviceProfileType type)
  229. {
  230. try
  231. {
  232. var xmlFies = _fileSystem.GetFilePaths(path)
  233. .Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
  234. .ToList();
  235. return xmlFies
  236. .Select(i => ParseProfileFile(i, type))
  237. .Where(i => i != null)
  238. .ToList();
  239. }
  240. catch (IOException)
  241. {
  242. return new List<DeviceProfile>();
  243. }
  244. }
  245. private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
  246. {
  247. lock (_profiles)
  248. {
  249. if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile> profileTuple))
  250. {
  251. return profileTuple.Item2;
  252. }
  253. try
  254. {
  255. DeviceProfile profile;
  256. var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
  257. profile = ReserializeProfile(tempProfile);
  258. profile.Id = path.ToLower().GetMD5().ToString("N");
  259. _profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
  260. return profile;
  261. }
  262. catch (Exception ex)
  263. {
  264. _logger.LogError(ex, "Error parsing profile file: {Path}", path);
  265. return null;
  266. }
  267. }
  268. }
  269. public DeviceProfile GetProfile(string id)
  270. {
  271. if (string.IsNullOrEmpty(id))
  272. {
  273. throw new ArgumentNullException(nameof(id));
  274. }
  275. var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
  276. return ParseProfileFile(info.Path, info.Info.Type);
  277. }
  278. private IEnumerable<InternalProfileInfo> GetProfileInfosInternal()
  279. {
  280. lock (_profiles)
  281. {
  282. var list = _profiles.Values.ToList();
  283. return list
  284. .Select(i => i.Item1)
  285. .OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
  286. .ThenBy(i => i.Info.Name);
  287. }
  288. }
  289. public IEnumerable<DeviceProfileInfo> GetProfileInfos()
  290. {
  291. return GetProfileInfosInternal().Select(i => i.Info);
  292. }
  293. private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
  294. {
  295. return new InternalProfileInfo
  296. {
  297. Path = file.FullName,
  298. Info = new DeviceProfileInfo
  299. {
  300. Id = file.FullName.ToLower().GetMD5().ToString("N"),
  301. Name = _fileSystem.GetFileNameWithoutExtension(file),
  302. Type = type
  303. }
  304. };
  305. }
  306. private void ExtractSystemProfiles()
  307. {
  308. var namespaceName = GetType().Namespace + ".Profiles.Xml.";
  309. var systemProfilesPath = SystemProfilesPath;
  310. foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType())
  311. .Where(i => i.StartsWith(namespaceName))
  312. .ToList())
  313. {
  314. var filename = Path.GetFileName(name).Substring(namespaceName.Length);
  315. var path = Path.Combine(systemProfilesPath, filename);
  316. using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name))
  317. {
  318. var fileInfo = _fileSystem.GetFileInfo(path);
  319. if (!fileInfo.Exists || fileInfo.Length != stream.Length)
  320. {
  321. _fileSystem.CreateDirectory(systemProfilesPath);
  322. using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
  323. {
  324. stream.CopyTo(fileStream);
  325. }
  326. }
  327. }
  328. }
  329. // Not necessary, but just to make it easy to find
  330. _fileSystem.CreateDirectory(UserProfilesPath);
  331. }
  332. public void DeleteProfile(string id)
  333. {
  334. var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
  335. if (info.Info.Type == DeviceProfileType.System)
  336. {
  337. throw new ArgumentException("System profiles cannot be deleted.");
  338. }
  339. _fileSystem.DeleteFile(info.Path);
  340. lock (_profiles)
  341. {
  342. _profiles.Remove(info.Path);
  343. }
  344. }
  345. public void CreateProfile(DeviceProfile profile)
  346. {
  347. profile = ReserializeProfile(profile);
  348. if (string.IsNullOrEmpty(profile.Name))
  349. {
  350. throw new ArgumentException("Profile is missing Name");
  351. }
  352. var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
  353. var path = Path.Combine(UserProfilesPath, newFilename);
  354. SaveProfile(profile, path, DeviceProfileType.User);
  355. }
  356. public void UpdateProfile(DeviceProfile profile)
  357. {
  358. profile = ReserializeProfile(profile);
  359. if (string.IsNullOrEmpty(profile.Id))
  360. {
  361. throw new ArgumentException("Profile is missing Id");
  362. }
  363. if (string.IsNullOrEmpty(profile.Name))
  364. {
  365. throw new ArgumentException("Profile is missing Name");
  366. }
  367. var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase));
  368. var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
  369. var path = Path.Combine(UserProfilesPath, newFilename);
  370. if (!string.Equals(path, current.Path, StringComparison.Ordinal) &&
  371. current.Info.Type != DeviceProfileType.System)
  372. {
  373. _fileSystem.DeleteFile(current.Path);
  374. }
  375. SaveProfile(profile, path, DeviceProfileType.User);
  376. }
  377. private void SaveProfile(DeviceProfile profile, string path, DeviceProfileType type)
  378. {
  379. lock (_profiles)
  380. {
  381. _profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
  382. }
  383. SerializeToXml(profile, path);
  384. }
  385. internal void SerializeToXml(DeviceProfile profile, string path)
  386. {
  387. _xmlSerializer.SerializeToFile(profile, path);
  388. }
  389. /// <summary>
  390. /// Recreates the object using serialization, to ensure it's not a subclass.
  391. /// If it's a subclass it may not serlialize properly to xml (different root element tag name)
  392. /// </summary>
  393. /// <param name="profile"></param>
  394. /// <returns></returns>
  395. private DeviceProfile ReserializeProfile(DeviceProfile profile)
  396. {
  397. if (profile.GetType() == typeof(DeviceProfile))
  398. {
  399. return profile;
  400. }
  401. var json = _jsonSerializer.SerializeToString(profile);
  402. return _jsonSerializer.DeserializeFromString<DeviceProfile>(json);
  403. }
  404. class InternalProfileInfo
  405. {
  406. internal DeviceProfileInfo Info { get; set; }
  407. internal string Path { get; set; }
  408. }
  409. public string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId, string serverAddress)
  410. {
  411. var profile = GetProfile(headers) ??
  412. GetDefaultProfile();
  413. var serverId = _appHost.SystemId;
  414. return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
  415. }
  416. public ImageStream GetIcon(string filename)
  417. {
  418. var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
  419. ? ImageFormat.Png
  420. : ImageFormat.Jpg;
  421. var resource = GetType().Namespace + ".Images." + filename.ToLower();
  422. return new ImageStream
  423. {
  424. Format = format,
  425. Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource)
  426. };
  427. }
  428. }
  429. /*
  430. class DlnaProfileEntryPoint : IServerEntryPoint
  431. {
  432. private readonly IApplicationPaths _appPaths;
  433. private readonly IFileSystem _fileSystem;
  434. private readonly IXmlSerializer _xmlSerializer;
  435. public DlnaProfileEntryPoint(IApplicationPaths appPaths, IFileSystem fileSystem, IXmlSerializer xmlSerializer)
  436. {
  437. _appPaths = appPaths;
  438. _fileSystem = fileSystem;
  439. _xmlSerializer = xmlSerializer;
  440. }
  441. public void Run()
  442. {
  443. DumpProfiles();
  444. }
  445. private void DumpProfiles()
  446. {
  447. DeviceProfile[] list = new []
  448. {
  449. new SamsungSmartTvProfile(),
  450. new XboxOneProfile(),
  451. new SonyPs3Profile(),
  452. new SonyPs4Profile(),
  453. new SonyBravia2010Profile(),
  454. new SonyBravia2011Profile(),
  455. new SonyBravia2012Profile(),
  456. new SonyBravia2013Profile(),
  457. new SonyBravia2014Profile(),
  458. new SonyBlurayPlayer2013(),
  459. new SonyBlurayPlayer2014(),
  460. new SonyBlurayPlayer2015(),
  461. new SonyBlurayPlayer2016(),
  462. new SonyBlurayPlayerProfile(),
  463. new PanasonicVieraProfile(),
  464. new WdtvLiveProfile(),
  465. new DenonAvrProfile(),
  466. new LinksysDMA2100Profile(),
  467. new LgTvProfile(),
  468. new Foobar2000Profile(),
  469. new SharpSmartTvProfile(),
  470. new MediaMonkeyProfile(),
  471. //new Windows81Profile(),
  472. //new WindowsMediaCenterProfile(),
  473. //new WindowsPhoneProfile(),
  474. new DirectTvProfile(),
  475. new DishHopperJoeyProfile(),
  476. new DefaultProfile(),
  477. new PopcornHourProfile(),
  478. new MarantzProfile()
  479. };
  480. foreach (var item in list)
  481. {
  482. var path = Path.Combine(_appPaths.ProgramDataPath, _fileSystem.GetValidFilename(item.Name) + ".xml");
  483. _xmlSerializer.SerializeToFile(item, path);
  484. }
  485. }
  486. public void Dispose()
  487. {
  488. }
  489. }*/
  490. }