jellyfin-server/MediaBrowser.Dlna/DlnaManager.cs

524 lines
18 KiB
C#
Raw Normal View History

2014-04-10 15:06:54 +00:00
using MediaBrowser.Common.Configuration;
2014-03-26 15:06:48 +00:00
using MediaBrowser.Common.Extensions;
2014-03-15 04:14:07 +00:00
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Dlna;
2014-04-16 05:08:12 +00:00
using MediaBrowser.Controller.Drawing;
2014-03-23 16:42:02 +00:00
using MediaBrowser.Dlna.Profiles;
2014-04-10 15:06:54 +00:00
using MediaBrowser.Dlna.Server;
2014-03-26 15:06:48 +00:00
using MediaBrowser.Model.Dlna;
2014-10-06 23:58:46 +00:00
using MediaBrowser.Model.Dlna.Profiles;
2014-03-26 15:06:48 +00:00
using MediaBrowser.Model.Logging;
2014-03-15 04:14:07 +00:00
using MediaBrowser.Model.Serialization;
2014-03-25 05:25:03 +00:00
using System;
2014-03-13 19:08:02 +00:00
using System.Collections.Generic;
2014-03-26 15:06:48 +00:00
using System.IO;
2014-03-17 14:48:16 +00:00
using System.Linq;
2014-04-10 15:06:54 +00:00
using System.Text;
2014-03-13 19:08:02 +00:00
using System.Text.RegularExpressions;
namespace MediaBrowser.Dlna
{
public class DlnaManager : IDlnaManager
{
2014-03-26 15:06:48 +00:00
private readonly IApplicationPaths _appPaths;
2014-03-15 04:14:07 +00:00
private readonly IXmlSerializer _xmlSerializer;
private readonly IFileSystem _fileSystem;
2014-03-26 15:06:48 +00:00
private readonly ILogger _logger;
2014-03-29 02:38:22 +00:00
private readonly IJsonSerializer _jsonSerializer;
2014-04-21 16:02:30 +00:00
2014-04-25 17:30:41 +00:00
public DlnaManager(IXmlSerializer xmlSerializer,
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILogger logger,
2014-04-21 16:02:30 +00:00
IJsonSerializer jsonSerializer)
2014-03-15 04:14:07 +00:00
{
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
2014-03-26 15:06:48 +00:00
_appPaths = appPaths;
_logger = logger;
2014-03-29 02:38:22 +00:00
_jsonSerializer = jsonSerializer;
2014-04-21 16:02:30 +00:00
2014-07-17 22:21:35 +00:00
//DumpProfiles();
2014-03-15 04:14:07 +00:00
}
public IEnumerable<DeviceProfile> GetProfiles()
2014-03-26 15:06:48 +00:00
{
ExtractProfilesIfNeeded();
2014-03-26 20:14:47 +00:00
var list = GetProfiles(UserProfilesPath, DeviceProfileType.User)
2014-03-26 15:06:48 +00:00
.OrderBy(i => i.Name)
.ToList();
2014-03-26 20:14:47 +00:00
list.AddRange(GetProfiles(SystemProfilesPath, DeviceProfileType.System)
2014-03-26 15:06:48 +00:00
.OrderBy(i => i.Name));
return list;
}
private void DumpProfiles()
2014-03-13 19:08:02 +00:00
{
2014-03-23 21:46:13 +00:00
var list = new List<DeviceProfile>
{
new SamsungSmartTvProfile(),
new Xbox360Profile(),
new XboxOneProfile(),
new SonyPs3Profile(),
new SonyBravia2010Profile(),
new SonyBravia2011Profile(),
new SonyBravia2012Profile(),
new SonyBravia2013Profile(),
new SonyBlurayPlayer2013Profile(),
new SonyBlurayPlayerProfile(),
new PanasonicVieraProfile(),
new WdtvLiveProfile(),
new DenonAvrProfile(),
new LinksysDMA2100Profile(),
2014-03-25 05:25:03 +00:00
new LgTvProfile(),
2014-03-26 15:06:48 +00:00
new Foobar2000Profile(),
2014-05-21 01:26:14 +00:00
new MediaMonkeyProfile(),
2014-07-17 03:17:14 +00:00
new Windows81Profile(),
//new WindowsMediaCenterProfile(),
new WindowsPhoneProfile(),
new AndroidProfile(),
2014-08-15 16:35:41 +00:00
new DirectTvProfile(),
2014-09-24 01:44:05 +00:00
new DishHopperJoeyProfile(),
2014-03-26 15:06:48 +00:00
new DefaultProfile()
2014-03-23 21:46:13 +00:00
};
2014-03-15 04:14:07 +00:00
foreach (var item in list)
{
2014-03-26 16:10:46 +00:00
var path = Path.Combine(_appPaths.ProgramDataPath, _fileSystem.GetValidFilename(item.Name) + ".xml");
_xmlSerializer.SerializeToFile(item, path);
2014-03-15 04:14:07 +00:00
}
2014-03-26 15:06:48 +00:00
}
2014-03-15 04:14:07 +00:00
2014-03-26 15:06:48 +00:00
private bool _extracted;
private readonly object _syncLock = new object();
private void ExtractProfilesIfNeeded()
{
if (!_extracted)
{
lock (_syncLock)
{
if (!_extracted)
{
try
{
ExtractSystemProfiles();
}
catch (Exception ex)
{
_logger.ErrorException("Error extracting DLNA profiles.", ex);
}
_extracted = true;
}
}
}
2014-03-13 19:08:02 +00:00
}
2014-03-15 04:14:07 +00:00
public DeviceProfile GetDefaultProfile()
2014-03-13 19:08:02 +00:00
{
2014-03-26 16:10:46 +00:00
ExtractProfilesIfNeeded();
2014-03-23 16:42:02 +00:00
return new DefaultProfile();
2014-03-13 19:08:02 +00:00
}
2014-03-17 14:48:16 +00:00
public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
{
2014-03-26 20:14:47 +00:00
if (deviceInfo == null)
{
throw new ArgumentNullException("deviceInfo");
}
var profile = GetProfiles()
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
2014-03-26 15:06:48 +00:00
2014-03-26 15:17:36 +00:00
if (profile != null)
{
_logger.Debug("Found matching device profile: {0}", profile.Name);
}
else
{
_logger.Debug("No matching device profile found. The default will need to be used.");
2014-04-06 17:53:23 +00:00
LogUnmatchedProfile(deviceInfo);
2014-03-26 15:17:36 +00:00
}
2014-03-26 15:06:48 +00:00
return profile;
2014-03-17 14:48:16 +00:00
}
2014-04-06 17:53:23 +00:00
private void LogUnmatchedProfile(DeviceIdentification profile)
{
var builder = new StringBuilder();
builder.AppendLine(string.Format("DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty));
builder.AppendLine(string.Format("FriendlyName:{0}", profile.FriendlyName ?? string.Empty));
builder.AppendLine(string.Format("Manufacturer:{0}", profile.Manufacturer ?? string.Empty));
builder.AppendLine(string.Format("ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty));
builder.AppendLine(string.Format("ModelDescription:{0}", profile.ModelDescription ?? string.Empty));
builder.AppendLine(string.Format("ModelName:{0}", profile.ModelName ?? string.Empty));
builder.AppendLine(string.Format("ModelNumber:{0}", profile.ModelNumber ?? string.Empty));
builder.AppendLine(string.Format("ModelUrl:{0}", profile.ModelUrl ?? string.Empty));
builder.AppendLine(string.Format("SerialNumber:{0}", profile.SerialNumber ?? string.Empty));
_logger.LogMultiline("No matching device profile found. The default will need to be used.", LogSeverity.Info, builder);
}
2014-03-17 14:48:16 +00:00
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
2014-03-13 19:08:02 +00:00
{
2014-03-18 01:45:41 +00:00
if (!string.IsNullOrWhiteSpace(profileInfo.DeviceDescription))
{
2014-03-24 07:47:23 +00:00
if (deviceInfo.DeviceDescription == null || !Regex.IsMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
2014-03-18 01:45:41 +00:00
return false;
}
if (!string.IsNullOrWhiteSpace(profileInfo.FriendlyName))
2014-03-13 19:08:02 +00:00
{
2014-03-24 07:47:23 +00:00
if (deviceInfo.FriendlyName == null || !Regex.IsMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
2014-03-17 14:48:16 +00:00
return false;
}
2014-03-13 19:08:02 +00:00
2014-03-18 01:45:41 +00:00
if (!string.IsNullOrWhiteSpace(profileInfo.Manufacturer))
2014-03-17 14:48:16 +00:00
{
2014-03-24 07:47:23 +00:00
if (deviceInfo.Manufacturer == null || !Regex.IsMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
2014-03-17 14:48:16 +00:00
return false;
}
2014-03-13 19:08:02 +00:00
2014-03-18 01:45:41 +00:00
if (!string.IsNullOrWhiteSpace(profileInfo.ManufacturerUrl))
{
2014-03-24 07:47:23 +00:00
if (deviceInfo.ManufacturerUrl == null || !Regex.IsMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
2014-03-18 01:45:41 +00:00
return false;
}
if (!string.IsNullOrWhiteSpace(profileInfo.ModelDescription))
{
2014-03-24 07:47:23 +00:00
if (deviceInfo.ModelDescription == null || !Regex.IsMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
2014-03-18 01:45:41 +00:00
return false;
}
if (!string.IsNullOrWhiteSpace(profileInfo.ModelName))
2014-03-17 14:48:16 +00:00
{
2014-03-24 07:47:23 +00:00
if (deviceInfo.ModelName == null || !Regex.IsMatch(deviceInfo.ModelName, profileInfo.ModelName))
2014-03-17 14:48:16 +00:00
return false;
}
2014-03-13 19:08:02 +00:00
2014-03-18 01:45:41 +00:00
if (!string.IsNullOrWhiteSpace(profileInfo.ModelNumber))
2014-03-17 14:48:16 +00:00
{
2014-03-24 07:47:23 +00:00
if (deviceInfo.ModelNumber == null || !Regex.IsMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
2014-03-17 14:48:16 +00:00
return false;
}
2014-03-18 01:45:41 +00:00
if (!string.IsNullOrWhiteSpace(profileInfo.ModelUrl))
2014-03-17 14:48:16 +00:00
{
2014-03-24 07:47:23 +00:00
if (deviceInfo.ModelUrl == null || !Regex.IsMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
2014-03-17 14:48:16 +00:00
return false;
}
2014-03-13 19:08:02 +00:00
2014-03-18 01:45:41 +00:00
if (!string.IsNullOrWhiteSpace(profileInfo.SerialNumber))
2014-03-17 14:48:16 +00:00
{
2014-03-24 07:47:23 +00:00
if (deviceInfo.SerialNumber == null || !Regex.IsMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
2014-03-17 14:48:16 +00:00
return false;
2014-03-13 19:08:02 +00:00
}
2014-03-17 14:48:16 +00:00
return true;
2014-03-13 19:08:02 +00:00
}
2014-03-25 05:25:03 +00:00
public DeviceProfile GetProfile(IDictionary<string, string> headers)
{
2014-03-26 20:14:47 +00:00
if (headers == null)
{
throw new ArgumentNullException("headers");
}
2014-04-03 22:50:04 +00:00
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
if (profile != null)
{
_logger.Debug("Found matching device profile: {0}", profile.Name);
}
2014-04-25 17:30:41 +00:00
else
{
string userAgent = null;
headers.TryGetValue("User-Agent", out userAgent);
var msg = "No matching device profile found. The default will be used. ";
if (!string.IsNullOrEmpty(userAgent))
{
msg += "User-agent: " + userAgent + ". ";
}
_logger.Debug(msg);
}
2014-04-03 22:50:04 +00:00
return profile;
2014-03-25 05:25:03 +00:00
}
private bool IsMatch(IDictionary<string, string> headers, DeviceIdentification profileInfo)
{
return profileInfo.Headers.Any(i => IsMatch(headers, i));
}
private bool IsMatch(IDictionary<string, string> headers, HttpHeaderInfo header)
{
string value;
if (headers.TryGetValue(header.Name, out value))
{
switch (header.Match)
{
case HeaderMatchType.Equals:
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
case HeaderMatchType.Substring:
return value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
case HeaderMatchType.Regex:
return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
default:
throw new ArgumentException("Unrecognized HeaderMatchType");
}
}
return false;
}
2014-03-26 15:06:48 +00:00
private string UserProfilesPath
{
get
{
return Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
}
}
private string SystemProfilesPath
{
get
{
return Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
}
}
2014-03-26 20:14:47 +00:00
private IEnumerable<DeviceProfile> GetProfiles(string path, DeviceProfileType type)
2014-03-26 15:06:48 +00:00
{
try
{
return new DirectoryInfo(path)
.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
.Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase))
2014-03-26 20:14:47 +00:00
.Select(i => ParseProfileXmlFile(i.FullName, type))
2014-03-26 15:06:48 +00:00
.Where(i => i != null)
.ToList();
}
catch (DirectoryNotFoundException)
{
return new List<DeviceProfile>();
}
}
2014-03-26 20:14:47 +00:00
private DeviceProfile ParseProfileXmlFile(string path, DeviceProfileType type)
2014-03-26 15:06:48 +00:00
{
try
{
var profile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
profile.Id = path.ToLower().GetMD5().ToString("N");
2014-03-26 20:14:47 +00:00
profile.ProfileType = type;
2014-03-26 15:06:48 +00:00
return profile;
}
catch (Exception ex)
{
_logger.ErrorException("Error parsing profile xml: {0}", ex, path);
return null;
}
}
public DeviceProfile GetProfile(string id)
{
2014-03-26 20:14:47 +00:00
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException("id");
}
2014-03-26 15:06:48 +00:00
var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id));
2014-03-26 20:14:47 +00:00
return ParseProfileXmlFile(info.Path, info.Info.Type);
2014-03-26 15:06:48 +00:00
}
private IEnumerable<InternalProfileInfo> GetProfileInfosInternal()
{
ExtractProfilesIfNeeded();
return GetProfileInfos(UserProfilesPath, DeviceProfileType.User)
.Concat(GetProfileInfos(SystemProfilesPath, DeviceProfileType.System))
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Info.Name);
}
public IEnumerable<DeviceProfileInfo> GetProfileInfos()
{
return GetProfileInfosInternal().Select(i => i.Info);
}
private IEnumerable<InternalProfileInfo> GetProfileInfos(string path, DeviceProfileType type)
{
try
{
return new DirectoryInfo(path)
.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
.Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase))
.Select(i => new InternalProfileInfo
{
Path = i.FullName,
Info = new DeviceProfileInfo
{
Id = i.FullName.ToLower().GetMD5().ToString("N"),
2014-07-26 17:30:15 +00:00
Name = _fileSystem.GetFileNameWithoutExtension(i),
2014-03-26 15:06:48 +00:00
Type = type
}
})
.ToList();
}
catch (DirectoryNotFoundException)
{
return new List<InternalProfileInfo>();
}
}
private void ExtractSystemProfiles()
{
var assembly = GetType().Assembly;
var namespaceName = GetType().Namespace + ".Profiles.Xml.";
var systemProfilesPath = SystemProfilesPath;
foreach (var name in assembly.GetManifestResourceNames()
.Where(i => i.StartsWith(namespaceName))
.ToList())
{
var filename = Path.GetFileName(name).Substring(namespaceName.Length);
var path = Path.Combine(systemProfilesPath, filename);
using (var stream = assembly.GetManifestResourceStream(name))
{
var fileInfo = new FileInfo(path);
if (!fileInfo.Exists || fileInfo.Length != stream.Length)
{
Directory.CreateDirectory(systemProfilesPath);
using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
stream.CopyTo(fileStream);
}
}
}
}
// Not necessary, but just to make it easy to find
Directory.CreateDirectory(UserProfilesPath);
}
2014-03-26 19:21:29 +00:00
public void DeleteProfile(string id)
{
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id));
if (info.Info.Type == DeviceProfileType.System)
{
throw new ArgumentException("System profiles cannot be deleted.");
}
File.Delete(info.Path);
}
2014-03-26 20:14:47 +00:00
public void CreateProfile(DeviceProfile profile)
{
2014-03-29 02:38:22 +00:00
profile = ReserializeProfile(profile);
if (string.IsNullOrWhiteSpace(profile.Name))
{
throw new ArgumentException("Profile is missing Name");
}
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
var path = Path.Combine(UserProfilesPath, newFilename);
_xmlSerializer.SerializeToFile(profile, path);
2014-03-26 20:14:47 +00:00
}
public void UpdateProfile(DeviceProfile profile)
{
2014-03-29 02:38:22 +00:00
profile = ReserializeProfile(profile);
if (string.IsNullOrWhiteSpace(profile.Id))
{
throw new ArgumentException("Profile is missing Id");
}
if (string.IsNullOrWhiteSpace(profile.Name))
{
throw new ArgumentException("Profile is missing Name");
}
var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase));
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
var path = Path.Combine(UserProfilesPath, newFilename);
2014-04-20 05:21:08 +00:00
if (!string.Equals(path, current.Path, StringComparison.Ordinal) &&
current.Info.Type != DeviceProfileType.System)
2014-03-29 02:38:22 +00:00
{
File.Delete(current.Path);
}
_xmlSerializer.SerializeToFile(profile, path);
}
/// <summary>
/// Recreates the object using serialization, to ensure it's not a subclass.
/// If it's a subclass it may not serlialize properly to xml (different root element tag name)
/// </summary>
/// <param name="profile"></param>
/// <returns></returns>
private DeviceProfile ReserializeProfile(DeviceProfile profile)
{
if (profile.GetType() == typeof(DeviceProfile))
{
return profile;
}
var json = _jsonSerializer.SerializeToString(profile);
return _jsonSerializer.DeserializeFromString<DeviceProfile>(json);
2014-03-26 20:14:47 +00:00
}
2014-03-26 15:06:48 +00:00
class InternalProfileInfo
{
internal DeviceProfileInfo Info { get; set; }
internal string Path { get; set; }
}
2014-04-10 15:06:54 +00:00
public string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId)
{
var profile = GetProfile(headers) ??
GetDefaultProfile();
2014-05-16 17:11:07 +00:00
return new DescriptionXmlBuilder(profile, serverUuId, "").GetXml();
2014-04-10 15:06:54 +00:00
}
2014-04-16 05:08:12 +00:00
public DlnaIconResponse GetIcon(string filename)
{
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
? ImageFormat.Png
: ImageFormat.Jpg;
return new DlnaIconResponse
{
Format = format,
Stream = GetType().Assembly.GetManifestResourceStream("MediaBrowser.Dlna.Images." + filename.ToLower())
};
2014-04-10 15:06:54 +00:00
}
2014-03-13 19:08:02 +00:00
}
2014-03-14 14:27:32 +00:00
}