From 2729301bffb8b4a15c2228fee39717d80b123e60 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 29 Oct 2016 01:40:15 -0400 Subject: move common dependencies --- .../Serialization/XmlSerializer.cs | 130 +++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 Emby.Common.Implementations/Serialization/XmlSerializer.cs (limited to 'Emby.Common.Implementations/Serialization') diff --git a/Emby.Common.Implementations/Serialization/XmlSerializer.cs b/Emby.Common.Implementations/Serialization/XmlSerializer.cs new file mode 100644 index 000000000..ca162e868 --- /dev/null +++ b/Emby.Common.Implementations/Serialization/XmlSerializer.cs @@ -0,0 +1,130 @@ +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using MediaBrowser.Common.IO; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; + +namespace Emby.Common.Implementations.Serialization +{ + /// + /// Provides a wrapper around third party xml serialization. + /// + public class XmlSerializer : IXmlSerializer + { + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + + public XmlSerializer(IFileSystem fileSystem, ILogger logger) + { + _fileSystem = fileSystem; + _logger = logger; + } + + // Need to cache these + // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html + private readonly Dictionary _serializers = + new Dictionary(); + + private System.Xml.Serialization.XmlSerializer GetSerializer(Type type) + { + var key = type.FullName; + lock (_serializers) + { + System.Xml.Serialization.XmlSerializer serializer; + if (!_serializers.TryGetValue(key, out serializer)) + { + serializer = new System.Xml.Serialization.XmlSerializer(type); + _serializers[key] = serializer; + } + return serializer; + } + } + + /// + /// Serializes to writer. + /// + /// The obj. + /// The writer. + private void SerializeToWriter(object obj, XmlWriter writer) + { + //writer.Formatting = Formatting.Indented; + var netSerializer = GetSerializer(obj.GetType()); + netSerializer.Serialize(writer, obj); + } + + /// + /// Deserializes from stream. + /// + /// The type. + /// The stream. + /// System.Object. + public object DeserializeFromStream(Type type, Stream stream) + { + using (var reader = XmlReader.Create(stream)) + { + var netSerializer = GetSerializer(type); + return netSerializer.Deserialize(reader); + } + } + + /// + /// Serializes to stream. + /// + /// The obj. + /// The stream. + public void SerializeToStream(object obj, Stream stream) + { + using (var writer = XmlWriter.Create(stream)) + { + SerializeToWriter(obj, writer); + } + } + + /// + /// Serializes to file. + /// + /// The obj. + /// The file. + public void SerializeToFile(object obj, string file) + { + _logger.Debug("Serializing to file {0}", file); + using (var stream = new FileStream(file, FileMode.Create)) + { + SerializeToStream(obj, stream); + } + } + + /// + /// Deserializes from file. + /// + /// The type. + /// The file. + /// System.Object. + public object DeserializeFromFile(Type type, string file) + { + _logger.Debug("Deserializing file {0}", file); + using (var stream = _fileSystem.OpenRead(file)) + { + return DeserializeFromStream(type, stream); + } + } + + /// + /// Deserializes from bytes. + /// + /// The type. + /// The buffer. + /// System.Object. + public object DeserializeFromBytes(Type type, byte[] buffer) + { + using (var stream = new MemoryStream(buffer)) + { + return DeserializeFromStream(type, stream); + } + } + } +} -- cgit v1.2.3 From b91dcdbff43559e4cbaa4148d56f6b7295256b7a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 31 Oct 2016 01:51:43 -0400 Subject: update audio queries --- .../Serialization/XmlSerializer.cs | 11 +- .../project.fragment.lock.json | 17 +- Emby.Dlna/project.fragment.lock.json | 28 ++- MediaBrowser.Api/StartupWizardService.cs | 1 + MediaBrowser.Controller/Entities/Audio/Audio.cs | 12 -- MediaBrowser.Controller/Entities/BaseItem.cs | 15 +- MediaBrowser.Controller/Entities/Video.cs | 12 -- .../Configuration/ServerConfiguration.cs | 1 + .../Channels/ChannelManager.cs | 41 +++- .../Persistence/SqliteItemRepository.cs | 90 ++++++++- .../ApplicationHost.cs | 215 ++++----------------- Mono.Nat/project.fragment.lock.json | 17 +- 12 files changed, 226 insertions(+), 234 deletions(-) (limited to 'Emby.Common.Implementations/Serialization') diff --git a/Emby.Common.Implementations/Serialization/XmlSerializer.cs b/Emby.Common.Implementations/Serialization/XmlSerializer.cs index ca162e868..aea63a57e 100644 --- a/Emby.Common.Implementations/Serialization/XmlSerializer.cs +++ b/Emby.Common.Implementations/Serialization/XmlSerializer.cs @@ -51,7 +51,6 @@ namespace Emby.Common.Implementations.Serialization /// The writer. private void SerializeToWriter(object obj, XmlWriter writer) { - //writer.Formatting = Formatting.Indented; var netSerializer = GetSerializer(obj.GetType()); netSerializer.Serialize(writer, obj); } @@ -78,10 +77,18 @@ namespace Emby.Common.Implementations.Serialization /// The stream. public void SerializeToStream(object obj, Stream stream) { - using (var writer = XmlWriter.Create(stream)) +#if NET46 + using (var writer = new XmlTextWriter(stream, null)) { + writer.Formatting = System.Xml.Formatting.Indented; SerializeToWriter(obj, writer); } +#else + using (var writer = XmlWriter.Create(stream)) + { + SerializeToWriter(obj, writer); + } +#endif } /// diff --git a/Emby.Common.Implementations/project.fragment.lock.json b/Emby.Common.Implementations/project.fragment.lock.json index 0d8df5a0e..6a89a6320 100644 --- a/Emby.Common.Implementations/project.fragment.lock.json +++ b/Emby.Common.Implementations/project.fragment.lock.json @@ -5,23 +5,30 @@ "type": "project", "framework": ".NETPortable,Version=v4.5,Profile=Profile7", "compile": { - "bin/Release/MediaBrowser.Common.dll": {} + "bin/Debug/MediaBrowser.Common.dll": {} }, "runtime": { - "bin/Release/MediaBrowser.Common.dll": {} + "bin/Debug/MediaBrowser.Common.dll": {} + }, + "contentFiles": { + "bin/Debug/MediaBrowser.Common.pdb": { + "buildAction": "None", + "codeLanguage": "any", + "copyToOutput": true + } } }, "MediaBrowser.Model/1.0.0": { "type": "project", "framework": ".NETPortable,Version=v4.5,Profile=Profile7", "compile": { - "bin/Release/MediaBrowser.Model.dll": {} + "bin/Debug/MediaBrowser.Model.dll": {} }, "runtime": { - "bin/Release/MediaBrowser.Model.dll": {} + "bin/Debug/MediaBrowser.Model.dll": {} }, "contentFiles": { - "bin/Release/MediaBrowser.Model.pdb": { + "bin/Debug/MediaBrowser.Model.pdb": { "buildAction": "None", "codeLanguage": "any", "copyToOutput": true diff --git a/Emby.Dlna/project.fragment.lock.json b/Emby.Dlna/project.fragment.lock.json index df837d207..09e853c1c 100644 --- a/Emby.Dlna/project.fragment.lock.json +++ b/Emby.Dlna/project.fragment.lock.json @@ -5,33 +5,47 @@ "type": "project", "framework": ".NETPortable,Version=v4.5,Profile=Profile7", "compile": { - "bin/Release/MediaBrowser.Common.dll": {} + "bin/Debug/MediaBrowser.Common.dll": {} }, "runtime": { - "bin/Release/MediaBrowser.Common.dll": {} + "bin/Debug/MediaBrowser.Common.dll": {} + }, + "contentFiles": { + "bin/Debug/MediaBrowser.Common.pdb": { + "buildAction": "None", + "codeLanguage": "any", + "copyToOutput": true + } } }, "MediaBrowser.Controller/1.0.0": { "type": "project", "framework": ".NETPortable,Version=v4.5,Profile=Profile7", "compile": { - "bin/Release/MediaBrowser.Controller.dll": {} + "bin/Debug/MediaBrowser.Controller.dll": {} }, "runtime": { - "bin/Release/MediaBrowser.Controller.dll": {} + "bin/Debug/MediaBrowser.Controller.dll": {} + }, + "contentFiles": { + "bin/Debug/MediaBrowser.Controller.pdb": { + "buildAction": "None", + "codeLanguage": "any", + "copyToOutput": true + } } }, "MediaBrowser.Model/1.0.0": { "type": "project", "framework": ".NETPortable,Version=v4.5,Profile=Profile7", "compile": { - "bin/Release/MediaBrowser.Model.dll": {} + "bin/Debug/MediaBrowser.Model.dll": {} }, "runtime": { - "bin/Release/MediaBrowser.Model.dll": {} + "bin/Debug/MediaBrowser.Model.dll": {} }, "contentFiles": { - "bin/Release/MediaBrowser.Model.pdb": { + "bin/Debug/MediaBrowser.Model.pdb": { "buildAction": "None", "codeLanguage": "any", "copyToOutput": true diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index ddeac4d5d..49fdcece1 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -119,6 +119,7 @@ namespace MediaBrowser.Api config.EnableSimpleArtistDetection = true; config.SkipDeserializationForBasicTypes = true; config.SkipDeserializationForPrograms = true; + config.SkipDeserializationForAudio = true; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index cd4461608..539cc5f22 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -26,9 +26,6 @@ namespace MediaBrowser.Controller.Entities.Audio { public List ChannelMediaSources { get; set; } - public int? TotalBitrate { get; set; } - public ExtraType? ExtraType { get; set; } - /// /// Gets or sets the artist. /// @@ -37,15 +34,6 @@ namespace MediaBrowser.Controller.Entities.Audio public List AlbumArtists { get; set; } - [IgnoreDataMember] - public override bool IsThemeMedia - { - get - { - return ExtraType.HasValue && ExtraType.Value == Model.Entities.ExtraType.ThemeSong; - } - } - [IgnoreDataMember] public override bool EnableRefreshOnDateModifiedChange { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 8211d89d2..433fdbe16 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -203,12 +203,15 @@ namespace MediaBrowser.Controller.Entities get { return PremiereDate.HasValue && PremiereDate.Value.ToLocalTime().Date >= DateTime.Now.Date; } } + public int? TotalBitrate { get; set; } + public ExtraType? ExtraType { get; set; } + [IgnoreDataMember] - public virtual bool IsThemeMedia + public bool IsThemeMedia { get { - return false; + return ExtraType.HasValue && (ExtraType.Value == Model.Entities.ExtraType.ThemeSong || ExtraType.Value == Model.Entities.ExtraType.ThemeVideo); } } @@ -1045,7 +1048,7 @@ namespace MediaBrowser.Controller.Entities audio = dbItem; } - audio.ExtraType = ExtraType.ThemeSong; + audio.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong; return audio; @@ -1075,7 +1078,7 @@ namespace MediaBrowser.Controller.Entities item = dbItem; } - item.ExtraType = ExtraType.ThemeVideo; + item.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo; return item; @@ -1225,7 +1228,7 @@ namespace MediaBrowser.Controller.Entities if (!i.IsThemeMedia) { - i.ExtraType = ExtraType.ThemeVideo; + i.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeVideo; subOptions.ForceSave = true; } @@ -1255,7 +1258,7 @@ namespace MediaBrowser.Controller.Entities if (!i.IsThemeMedia) { - i.ExtraType = ExtraType.ThemeSong; + i.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong; subOptions.ForceSave = true; } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 6b8c894c8..2dd134334 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -35,15 +35,6 @@ namespace MediaBrowser.Controller.Entities public List LinkedAlternateVersions { get; set; } public List ChannelMediaSources { get; set; } - [IgnoreDataMember] - public override bool IsThemeMedia - { - get - { - return ExtraType.HasValue && ExtraType.Value == Model.Entities.ExtraType.ThemeVideo; - } - } - [IgnoreDataMember] public override bool SupportsPlayedStatus { @@ -87,9 +78,6 @@ namespace MediaBrowser.Controller.Entities get { return true; } } - public int? TotalBitrate { get; set; } - public ExtraType? ExtraType { get; set; } - /// /// Gets or sets the timestamp. /// diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index a1e5637a4..b1e52dc7b 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -170,6 +170,7 @@ namespace MediaBrowser.Model.Configuration public bool EnableAutomaticRestart { get; set; } public bool SkipDeserializationForBasicTypes { get; set; } public bool SkipDeserializationForPrograms { get; set; } + public bool SkipDeserializationForAudio { get; set; } public PathSubstitution[] PathSubstitutions { get; set; } diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index ffb9c96e7..300973ce1 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -252,6 +252,42 @@ namespace MediaBrowser.Server.Implementations.Channels return item; } + private List GetSavedMediaSources(BaseItem item) + { + var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasources.json"); + + try + { + return _jsonSerializer.DeserializeFromFile>(path) ?? new List(); + } + catch + { + return new List(); + } + } + + private void SaveMediaSources(BaseItem item, List mediaSources) + { + var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasources.json"); + + if (mediaSources == null || mediaSources.Count == 0) + { + try + { + _fileSystem.DeleteFile(path); + } + catch + { + + } + return; + } + + _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); + + _jsonSerializer.SerializeToFile(mediaSources, path); + } + public async Task> GetStaticMediaSources(BaseItem item, bool includeCachedVersions, CancellationToken cancellationToken) { IEnumerable results = new List(); @@ -263,7 +299,7 @@ namespace MediaBrowser.Server.Implementations.Channels var audio = item as Audio; if (audio != null) { - results = audio.ChannelMediaSources ?? new List(); + results = audio.ChannelMediaSources ?? GetSavedMediaSources(audio); } var sources = SortMediaInfoResults(results) @@ -1385,7 +1421,6 @@ namespace MediaBrowser.Server.Implementations.Channels if (channelAudioItem != null) { channelAudioItem.ExtraType = info.ExtraType; - channelAudioItem.ChannelMediaSources = info.MediaSources; var mediaSource = info.MediaSources.FirstOrDefault(); item.Path = mediaSource == null ? null : mediaSource.Path; @@ -1426,6 +1461,8 @@ namespace MediaBrowser.Server.Implementations.Channels await item.UpdateToRepository(ItemUpdateType.None, cancellationToken).ConfigureAwait(false); } + SaveMediaSources(item, info.MediaSources); + return item; } diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index ffae9a6f0..5fcd38f87 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -285,6 +285,10 @@ namespace MediaBrowser.Server.Implementations.Persistence _connection.AddColumn(Logger, "TypedBaseItems", "ProductionLocations", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "ThemeSongIds", "Text"); _connection.AddColumn(Logger, "TypedBaseItems", "ThemeVideoIds", "Text"); + _connection.AddColumn(Logger, "TypedBaseItems", "TotalBitrate", "INT"); + _connection.AddColumn(Logger, "TypedBaseItems", "ExtraType", "Text"); + _connection.AddColumn(Logger, "TypedBaseItems", "Artists", "Text"); + _connection.AddColumn(Logger, "TypedBaseItems", "AlbumArtists", "Text"); _connection.AddColumn(Logger, "ItemValues", "CleanValue", "Text"); @@ -435,7 +439,11 @@ namespace MediaBrowser.Server.Implementations.Persistence "Images", "ProductionLocations", "ThemeSongIds", - "ThemeVideoIds" + "ThemeVideoIds", + "TotalBitrate", + "ExtraType", + "Artists", + "AlbumArtists" }; private readonly string[] _mediaStreamSaveColumns = @@ -566,7 +574,11 @@ namespace MediaBrowser.Server.Implementations.Persistence "Images", "ProductionLocations", "ThemeSongIds", - "ThemeVideoIds" + "ThemeVideoIds", + "TotalBitrate", + "ExtraType", + "Artists", + "AlbumArtists" }; _saveItemCommand = _connection.CreateCommand(); _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -1046,6 +1058,35 @@ namespace MediaBrowser.Server.Implementations.Persistence _saveItemCommand.GetParameter(index++).Value = null; } + _saveItemCommand.GetParameter(index++).Value = item.TotalBitrate; + _saveItemCommand.GetParameter(index++).Value = item.ExtraType; + + var hasArtists = item as IHasArtist; + if (hasArtists != null) + { + if (hasArtists.Artists.Count > 0) + { + _saveItemCommand.GetParameter(index++).Value = string.Join("|", hasArtists.Artists.ToArray()); + } + else + { + _saveItemCommand.GetParameter(index++).Value = null; + } + } + + var hasAlbumArtists = item as IHasAlbumArtist; + if (hasAlbumArtists != null) + { + if (hasAlbumArtists.AlbumArtists.Count > 0) + { + _saveItemCommand.GetParameter(index++).Value = string.Join("|", hasAlbumArtists.AlbumArtists.ToArray()); + } + else + { + _saveItemCommand.GetParameter(index++).Value = null; + } + } + _saveItemCommand.Transaction = transaction; _saveItemCommand.ExecuteNonQuery(); @@ -1305,6 +1346,25 @@ namespace MediaBrowser.Server.Implementations.Persistence return false; } } + if (_config.Configuration.SkipDeserializationForAudio) + { + if (type == typeof(Audio)) + { + return false; + } + if (type == typeof(LiveTvAudioRecording)) + { + return false; + } + if (type == typeof(AudioPodcast)) + { + return false; + } + if (type == typeof(MusicAlbum)) + { + return false; + } + } return true; } @@ -1884,6 +1944,32 @@ namespace MediaBrowser.Server.Implementations.Persistence index++; } + if (!reader.IsDBNull(index)) + { + item.TotalBitrate = reader.GetInt32(index); + } + index++; + + if (!reader.IsDBNull(index)) + { + item.ExtraType = (ExtraType)Enum.Parse(typeof(ExtraType), reader.GetString(index), true); + } + index++; + + var hasArtists = item as IHasArtist; + if (hasArtists != null && !reader.IsDBNull(index)) + { + hasArtists.Artists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + index++; + + var hasAlbumArtists = item as IHasAlbumArtist; + if (hasAlbumArtists != null && !reader.IsDBNull(index)) + { + hasAlbumArtists.AlbumArtists = reader.GetString(index).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); + } + index++; + if (string.IsNullOrWhiteSpace(item.Tagline)) { var movie = item as Movie; diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index ad9b3960f..7ff50df63 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -434,187 +434,40 @@ namespace MediaBrowser.Server.Startup.Common var result = new JsonSerializer(FileSystemManager, LogManager.GetLogger("JsonSerializer")); - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ShortOverview" }; - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "Taglines" }; - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "Keywords" }; - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ShortOverview" }; - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ShortOverview" }; - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "PlaceOfBirth" }; - - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds" }; - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds" }; - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds" }; - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds" }; - ServiceStack.Text.JsConfig.ExcludePropertyNames = new[] { "ProviderIds" }; - ServiceStack.Text.JsConfig /// An integer specifying the local port to bind the socket to. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] public IUdpSocket CreateUdpSocket(int localPort) { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); - var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); @@ -65,12 +64,11 @@ namespace Emby.Common.Implementations.Net /// /// An integer specifying the local port to bind the socket to. /// An implementation of the interface used by RSSDP components to perform socket operations. - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] public IUdpSocket CreateSsdpUdpSocket(int localPort) { if (localPort < 0) throw new ArgumentException("localPort cannot be less than zero.", "localPort"); - var retVal = new Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp); + var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); try { retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); @@ -94,7 +92,6 @@ namespace Emby.Common.Implementations.Net /// The multicast time to live value for the socket. /// The number of the local port to bind to. /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "ip"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "The purpose of this method is to create and returns a disposable result, it is up to the caller to dispose it when they are done with it.")] public IUdpSocket CreateUdpMulticastSocket(string ipAddress, int multicastTimeToLive, int localPort) { if (ipAddress == null) throw new ArgumentNullException("ipAddress"); diff --git a/Emby.Common.Implementations/Net/UdpSocket.cs b/Emby.Common.Implementations/Net/UdpSocket.cs index 997d3f25f..6afb071ae 100644 --- a/Emby.Common.Implementations/Net/UdpSocket.cs +++ b/Emby.Common.Implementations/Net/UdpSocket.cs @@ -17,14 +17,14 @@ namespace Emby.Common.Implementations.Net #region Fields - private System.Net.Sockets.Socket _Socket; + private Socket _Socket; private int _LocalPort; #endregion #region Constructors - public UdpSocket(System.Net.Sockets.Socket socket, int localPort, IPAddress ip) + public UdpSocket(Socket socket, int localPort, IPAddress ip) { if (socket == null) throw new ArgumentNullException("socket"); @@ -46,12 +46,12 @@ namespace Emby.Common.Implementations.Net var tcs = new TaskCompletionSource(); - System.Net.EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); + EndPoint receivedFromEndPoint = new IPEndPoint(IPAddress.Any, 0); var state = new AsyncReceiveState(_Socket, receivedFromEndPoint); state.TaskCompletionSource = tcs; #if NETSTANDARD1_6 - _Socket.ReceiveFromAsync(new System.ArraySegment(state.Buffer), System.Net.Sockets.SocketFlags.None, state.EndPoint) + _Socket.ReceiveFromAsync(new ArraySegment(state.Buffer),SocketFlags.None, state.EndPoint) .ContinueWith((task, asyncState) => { if (task.Status != TaskStatus.Faulted) @@ -62,7 +62,7 @@ namespace Emby.Common.Implementations.Net } }, state); #else - _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, System.Net.Sockets.SocketFlags.None, ref state.EndPoint, new AsyncCallback(this.ProcessResponse), state); + _Socket.BeginReceiveFrom(state.Buffer, 0, state.Buffer.Length, SocketFlags.None, ref state.EndPoint, new AsyncCallback(this.ProcessResponse), state); #endif return tcs.Task; @@ -84,7 +84,7 @@ namespace Emby.Common.Implementations.Net buffer = copy; } - _Socket.SendTo(buffer, new System.Net.IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); + _Socket.SendTo(buffer, new IPEndPoint(IPAddress.Parse(endPoint.IpAddress.ToString()), endPoint.Port)); return Task.FromResult(true); #else var taskSource = new TaskCompletionSource(); @@ -153,7 +153,6 @@ namespace Emby.Common.Implementations.Net #region Private Methods - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions via task methods should be reported by task completion source, so this should be ok.")] private static void ProcessResponse(AsyncReceiveState state, Func receiveData) { try @@ -206,7 +205,6 @@ namespace Emby.Common.Implementations.Net }; } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions via task methods should be reported by task completion source, so this should be ok.")] private void ProcessResponse(IAsyncResult asyncResult) { #if NET46 @@ -249,7 +247,7 @@ namespace Emby.Common.Implementations.Net private class AsyncReceiveState { - public AsyncReceiveState(System.Net.Sockets.Socket socket, EndPoint endPoint) + public AsyncReceiveState(Socket socket, EndPoint endPoint) { this.Socket = socket; this.EndPoint = endPoint; @@ -258,7 +256,7 @@ namespace Emby.Common.Implementations.Net public EndPoint EndPoint; public byte[] Buffer = new byte[8192]; - public System.Net.Sockets.Socket Socket { get; private set; } + public Socket Socket { get; private set; } public TaskCompletionSource TaskCompletionSource { get; set; } diff --git a/Emby.Common.Implementations/Serialization/XmlSerializer.cs b/Emby.Common.Implementations/Serialization/XmlSerializer.cs index aea63a57e..3583f998e 100644 --- a/Emby.Common.Implementations/Serialization/XmlSerializer.cs +++ b/Emby.Common.Implementations/Serialization/XmlSerializer.cs @@ -4,6 +4,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Xml; +using System.Xml.Serialization; using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; @@ -13,12 +14,12 @@ namespace Emby.Common.Implementations.Serialization /// /// Provides a wrapper around third party xml serialization. /// - public class XmlSerializer : IXmlSerializer + public class MyXmlSerializer : IXmlSerializer { private readonly IFileSystem _fileSystem; private readonly ILogger _logger; - public XmlSerializer(IFileSystem fileSystem, ILogger logger) + public MyXmlSerializer(IFileSystem fileSystem, ILogger logger) { _fileSystem = fileSystem; _logger = logger; @@ -26,18 +27,18 @@ namespace Emby.Common.Implementations.Serialization // Need to cache these // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html - private readonly Dictionary _serializers = - new Dictionary(); + private readonly Dictionary _serializers = + new Dictionary(); - private System.Xml.Serialization.XmlSerializer GetSerializer(Type type) + private XmlSerializer GetSerializer(Type type) { var key = type.FullName; lock (_serializers) { - System.Xml.Serialization.XmlSerializer serializer; + XmlSerializer serializer; if (!_serializers.TryGetValue(key, out serializer)) { - serializer = new System.Xml.Serialization.XmlSerializer(type); + serializer = new XmlSerializer(type); _serializers[key] = serializer; } return serializer; @@ -80,7 +81,7 @@ namespace Emby.Common.Implementations.Serialization #if NET46 using (var writer = new XmlTextWriter(stream, null)) { - writer.Formatting = System.Xml.Formatting.Indented; + writer.Formatting = Formatting.Indented; SerializeToWriter(obj, writer); } #else diff --git a/Emby.Common.Implementations/project.json b/Emby.Common.Implementations/project.json index dc96f5726..2b2357e38 100644 --- a/Emby.Common.Implementations/project.json +++ b/Emby.Common.Implementations/project.json @@ -12,15 +12,14 @@ "System.IO": "4.0.0.0", "System.Net": "4.0.0.0", "System.Net.Http": "4.0.0.0", - "System.Net.Http.WebRequest": "4.0.0.0", "System.Net.Primitives": "4.0.0.0", + "System.Net.Http.WebRequest": "4.0.0.0", "System.Runtime": "4.0.0.0", "System.Runtime.Extensions": "4.0.0.0", "System.Text.Encoding": "4.0.0.0", "System.Threading": "4.0.0.0", "System.Threading.Tasks": "4.0.0.0", - "System.Xml": "4.0.0.0", - "System.Xml.Serialization": "4.0.0.0" + "System.Xml.ReaderWriter": "4.0.0" }, "dependencies": { "SimpleInjector": "3.2.4", @@ -30,7 +29,8 @@ }, "MediaBrowser.Common": { "target": "project" - } } + } + } }, "netstandard1.6": { "imports": "dnxcore50", @@ -40,6 +40,7 @@ "System.Diagnostics.Process": "4.1.0", "System.Threading.Timer": "4.0.1", "System.Net.Requests": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11", "System.Xml.XmlSerializer": "4.0.11", "System.Net.Http": "4.1.0", "System.Net.Primitives": "4.0.11", diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 14fbbcfa2..bff87bcac 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -20,6 +20,7 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; +using MediaBrowser.Model.System; using MediaBrowser.Model.Threading; using Rssdp; using Rssdp.Infrastructure; @@ -52,7 +53,7 @@ namespace Emby.Dlna.Main private readonly ITimerFactory _timerFactory; private readonly ISocketFactory _socketFactory; - + private readonly IEnvironmentInfo _environmentInfo; public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, @@ -66,7 +67,7 @@ namespace Emby.Dlna.Main IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, - IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory) + IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder, ISocketFactory socketFactory, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo) { _config = config; _appHost = appHost; @@ -83,6 +84,7 @@ namespace Emby.Dlna.Main _mediaEncoder = mediaEncoder; _socketFactory = socketFactory; _timerFactory = timerFactory; + _environmentInfo = environmentInfo; _logger = logManager.GetLogger("Dlna"); } @@ -169,7 +171,7 @@ namespace Emby.Dlna.Main private void StartPublishing() { SsdpDevicePublisherBase.LogFunction = LogMessage; - _Publisher = new SsdpDevicePublisher(_socketFactory, _timerFactory, "Windows", "10"); + _Publisher = new SsdpDevicePublisher(_socketFactory, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion); } private void StartDeviceDiscovery() diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs index 295ecc465..39033249f 100644 --- a/Emby.Server.Implementations/IO/FileRefresher.cs +++ b/Emby.Server.Implementations/IO/FileRefresher.cs @@ -13,6 +13,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.System; using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Threading; @@ -32,8 +33,9 @@ namespace Emby.Server.Implementations.IO public string Path { get; private set; } public event EventHandler Completed; + private readonly IEnvironmentInfo _environmentInfo; - public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory) + public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory, IEnvironmentInfo environmentInfo) { logger.Debug("New file refresher created for {0}", path); Path = path; @@ -44,6 +46,7 @@ namespace Emby.Server.Implementations.IO TaskManager = taskManager; Logger = logger; _timerFactory = timerFactory; + _environmentInfo = environmentInfo; AddPath(path); } @@ -226,11 +229,11 @@ namespace Emby.Server.Implementations.IO private bool IsFileLocked(string path) { - //if (Environment.OSVersion.Platform != PlatformID.Win32NT) - //{ - // // Causing lockups on linux - // return false; - //} + if (_environmentInfo.OperatingSystem != OperatingSystem.Windows) + { + // Causing lockups on linux + return false; + } try { diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index c85b215f2..52e477b1a 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -141,6 +141,7 @@ + diff --git a/MediaBrowser.Model/System/IEnvironmentInfo.cs b/MediaBrowser.Model/System/IEnvironmentInfo.cs new file mode 100644 index 000000000..3fcacb30d --- /dev/null +++ b/MediaBrowser.Model/System/IEnvironmentInfo.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MediaBrowser.Model.System +{ + public interface IEnvironmentInfo + { + MediaBrowser.Model.System.OperatingSystem OperatingSystem { get; } + string OperatingSystemName { get; } + string OperatingSystemVersion { get; } + } + + public enum OperatingSystem + { + Windows, + Linux, + OSX + } +} diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index 34fc85e7b..a8363558d 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -139,11 +139,12 @@ namespace MediaBrowser.Server.Implementations.IO private readonly IFileSystem _fileSystem; private readonly ITimerFactory _timerFactory; + private readonly IEnvironmentInfo _environmentInfo; /// /// Initializes a new instance of the class. /// - public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory, ISystemEvents systemEvents) + public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory, ISystemEvents systemEvents, IEnvironmentInfo environmentInfo) { if (taskManager == null) { @@ -156,6 +157,7 @@ namespace MediaBrowser.Server.Implementations.IO ConfigurationManager = configurationManager; _fileSystem = fileSystem; _timerFactory = timerFactory; + _environmentInfo = environmentInfo; systemEvents.Resume += _systemEvents_Resume; } @@ -525,7 +527,7 @@ namespace MediaBrowser.Server.Implementations.IO } } - var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory); + var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory, _environmentInfo); newRefresher.Completed += NewRefresher_Completed; _activeRefreshers.Add(newRefresher); } diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 79f7b5f05..ba04338bb 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -592,7 +592,7 @@ namespace MediaBrowser.Server.Startup.Common var musicManager = new MusicManager(LibraryManager); RegisterSingleInstance(new MusicManager(LibraryManager)); - LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory, SystemEvents); + LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager, TimerFactory, SystemEvents, EnvironmentInfo); RegisterSingleInstance(LibraryMonitor); ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ApplicationPaths, () => LibraryManager, JsonSerializer, MemoryStreamProvider); -- cgit v1.2.3 From 01fc207b62151a858c0d7edb802a24690505bb95 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 10 Nov 2016 20:37:20 -0500 Subject: update portable components --- .../Serialization/JsonSerializer.cs | 227 ++++++ Emby.Common.Implementations/project.json | 45 +- .../Emby.Server.Implementations.csproj | 4 + .../HttpServer/HttpResultFactory.cs | 847 +++++++++++++++++++++ .../Library/Validators/PeopleValidator.cs | 18 +- .../HttpServer/HttpListenerHost.cs | 60 +- .../HttpServer/HttpResultFactory.cs | 835 -------------------- .../SocketSharp/WebSocketSharpRequest.cs | 32 +- .../MediaBrowser.Server.Implementations.csproj | 13 +- .../Serialization/JsonSerializer.cs | 227 ------ .../packages.config | 1 + .../ApplicationHost.cs | 3 +- 12 files changed, 1173 insertions(+), 1139 deletions(-) create mode 100644 Emby.Common.Implementations/Serialization/JsonSerializer.cs create mode 100644 Emby.Server.Implementations/HttpServer/HttpResultFactory.cs delete mode 100644 MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs delete mode 100644 MediaBrowser.Server.Implementations/Serialization/JsonSerializer.cs (limited to 'Emby.Common.Implementations/Serialization') diff --git a/Emby.Common.Implementations/Serialization/JsonSerializer.cs b/Emby.Common.Implementations/Serialization/JsonSerializer.cs new file mode 100644 index 000000000..c9db33689 --- /dev/null +++ b/Emby.Common.Implementations/Serialization/JsonSerializer.cs @@ -0,0 +1,227 @@ +using System; +using System.IO; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; + +namespace Emby.Common.Implementations.Serialization +{ + /// + /// Provides a wrapper around third party json serialization. + /// + public class JsonSerializer : IJsonSerializer + { + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + + public JsonSerializer(IFileSystem fileSystem, ILogger logger) + { + _fileSystem = fileSystem; + _logger = logger; + Configure(); + } + + /// + /// Serializes to stream. + /// + /// The obj. + /// The stream. + /// obj + public void SerializeToStream(object obj, Stream stream) + { + if (obj == null) + { + throw new ArgumentNullException("obj"); + } + + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + ServiceStack.Text.JsonSerializer.SerializeToStream(obj, obj.GetType(), stream); + } + + /// + /// Serializes to file. + /// + /// The obj. + /// The file. + /// obj + public void SerializeToFile(object obj, string file) + { + if (obj == null) + { + throw new ArgumentNullException("obj"); + } + + if (string.IsNullOrEmpty(file)) + { + throw new ArgumentNullException("file"); + } + + using (Stream stream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read)) + { + SerializeToStream(obj, stream); + } + } + + private Stream OpenFile(string path) + { + _logger.Debug("Deserializing file {0}", path); + return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072); + } + + /// + /// Deserializes from file. + /// + /// The type. + /// The file. + /// System.Object. + /// type + public object DeserializeFromFile(Type type, string file) + { + if (type == null) + { + throw new ArgumentNullException("type"); + } + + if (string.IsNullOrEmpty(file)) + { + throw new ArgumentNullException("file"); + } + + using (Stream stream = OpenFile(file)) + { + return DeserializeFromStream(stream, type); + } + } + + /// + /// Deserializes from file. + /// + /// + /// The file. + /// ``0. + /// file + public T DeserializeFromFile(string file) + where T : class + { + if (string.IsNullOrEmpty(file)) + { + throw new ArgumentNullException("file"); + } + + using (Stream stream = OpenFile(file)) + { + return DeserializeFromStream(stream); + } + } + + /// + /// Deserializes from stream. + /// + /// + /// The stream. + /// ``0. + /// stream + public T DeserializeFromStream(Stream stream) + { + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + return ServiceStack.Text.JsonSerializer.DeserializeFromStream(stream); + } + + /// + /// Deserializes from string. + /// + /// + /// The text. + /// ``0. + /// text + public T DeserializeFromString(string text) + { + if (string.IsNullOrEmpty(text)) + { + throw new ArgumentNullException("text"); + } + + return ServiceStack.Text.JsonSerializer.DeserializeFromString(text); + } + + /// + /// Deserializes from stream. + /// + /// The stream. + /// The type. + /// System.Object. + /// stream + public object DeserializeFromStream(Stream stream, Type type) + { + if (stream == null) + { + throw new ArgumentNullException("stream"); + } + + if (type == null) + { + throw new ArgumentNullException("type"); + } + + return ServiceStack.Text.JsonSerializer.DeserializeFromStream(type, stream); + } + + /// + /// Configures this instance. + /// + private void Configure() + { + ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601; + ServiceStack.Text.JsConfig.ExcludeTypeInfo = true; + ServiceStack.Text.JsConfig.IncludeNullValues = false; + ServiceStack.Text.JsConfig.AlwaysUseUtc = true; + ServiceStack.Text.JsConfig.AssumeUtc = true; + } + + /// + /// Deserializes from string. + /// + /// The json. + /// The type. + /// System.Object. + /// json + public object DeserializeFromString(string json, Type type) + { + if (string.IsNullOrEmpty(json)) + { + throw new ArgumentNullException("json"); + } + + if (type == null) + { + throw new ArgumentNullException("type"); + } + + return ServiceStack.Text.JsonSerializer.DeserializeFromString(json, type); + } + + /// + /// Serializes to string. + /// + /// The obj. + /// System.String. + /// obj + public string SerializeToString(object obj) + { + if (obj == null) + { + throw new ArgumentNullException("obj"); + } + + return ServiceStack.Text.JsonSerializer.SerializeToString(obj, obj.GetType()); + } + } +} diff --git a/Emby.Common.Implementations/project.json b/Emby.Common.Implementations/project.json index 2b2357e38..dd304606b 100644 --- a/Emby.Common.Implementations/project.json +++ b/Emby.Common.Implementations/project.json @@ -2,7 +2,7 @@ "version": "1.0.0-*", "dependencies": { - + }, "frameworks": { @@ -19,46 +19,49 @@ "System.Text.Encoding": "4.0.0.0", "System.Threading": "4.0.0.0", "System.Threading.Tasks": "4.0.0.0", - "System.Xml.ReaderWriter": "4.0.0" + "System.Xml.ReaderWriter": "4.0.0" }, "dependencies": { "SimpleInjector": "3.2.4", + "ServiceStack.Text": "4.5.4", "NLog": "4.4.0-betaV15", "MediaBrowser.Model": { "target": "project" }, "MediaBrowser.Common": { "target": "project" - } - } + } + } }, "netstandard1.6": { "imports": "dnxcore50", "dependencies": { "NETStandard.Library": "1.6.0", - "System.IO.FileSystem.DriveInfo": "4.0.0", - "System.Diagnostics.Process": "4.1.0", - "System.Threading.Timer": "4.0.1", - "System.Net.Requests": "4.0.11", - "System.Xml.ReaderWriter": "4.0.11", - "System.Xml.XmlSerializer": "4.0.11", - "System.Net.Http": "4.1.0", - "System.Net.Primitives": "4.0.11", - "System.Net.Sockets": "4.1.0", - "System.Net.NetworkInformation": "4.1.0", - "System.Net.NameResolution": "4.0.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", - "System.Reflection": "4.1.0", - "System.Reflection.Primitives": "4.0.1", - "System.Runtime.Loader": "4.0.0", - "SimpleInjector": "3.2.4", + "System.IO.FileSystem.DriveInfo": "4.0.0", + "System.Diagnostics.Process": "4.1.0", + "System.Threading.Timer": "4.0.1", + "System.Net.Requests": "4.0.11", + "System.Xml.ReaderWriter": "4.0.11", + "System.Xml.XmlSerializer": "4.0.11", + "System.Net.Http": "4.1.0", + "System.Net.Primitives": "4.0.11", + "System.Net.Sockets": "4.1.0", + "System.Net.NetworkInformation": "4.1.0", + "System.Net.NameResolution": "4.0.0", + "System.Runtime.InteropServices.RuntimeInformation": "4.0.0", + "System.Reflection": "4.1.0", + "System.Reflection.Primitives": "4.0.1", + "System.Runtime.Loader": "4.0.0", + "SimpleInjector": "3.2.4", + "ServiceStack.Text.Core": "1.0.27", "NLog": "4.4.0-betaV15", "MediaBrowser.Model": { "target": "project" }, "MediaBrowser.Common": { "target": "project" - } } + } + } } } } diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 7e885b779..3f55bd7fb 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -69,6 +69,7 @@ + @@ -263,6 +264,9 @@ {442b5058-dcaf-4263-bb6a-f21e31120a1b} MediaBrowser.Providers + + ..\ThirdParty\ServiceStack\ServiceStack.dll + ..\ThirdParty\emby\SocketHttpListener.Portable.dll diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs new file mode 100644 index 000000000..bbd556661 --- /dev/null +++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -0,0 +1,847 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Net; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Emby.Server.Implementations.HttpServer; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Services; +using ServiceStack; +using ServiceStack.Host; +using IRequest = MediaBrowser.Model.Services.IRequest; +using MimeTypes = MediaBrowser.Model.Net.MimeTypes; +using StreamWriter = Emby.Server.Implementations.HttpServer.StreamWriter; + +namespace Emby.Server.Implementations.HttpServer +{ + /// + /// Class HttpResultFactory + /// + public class HttpResultFactory : IHttpResultFactory + { + /// + /// The _logger + /// + private readonly ILogger _logger; + private readonly IFileSystem _fileSystem; + private readonly IJsonSerializer _jsonSerializer; + private readonly IXmlSerializer _xmlSerializer; + + /// + /// Initializes a new instance of the class. + /// + /// The log manager. + /// The file system. + /// The json serializer. + public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer) + { + _fileSystem = fileSystem; + _jsonSerializer = jsonSerializer; + _xmlSerializer = xmlSerializer; + _logger = logManager.GetLogger("HttpResultFactory"); + } + + /// + /// Gets the result. + /// + /// The content. + /// Type of the content. + /// The response headers. + /// System.Object. + public object GetResult(object content, string contentType, IDictionary responseHeaders = null) + { + return GetHttpResult(content, contentType, responseHeaders); + } + + /// + /// Gets the HTTP result. + /// + /// The content. + /// Type of the content. + /// The response headers. + /// IHasHeaders. + private IHasHeaders GetHttpResult(object content, string contentType, IDictionary responseHeaders = null) + { + IHasHeaders result; + + var stream = content as Stream; + + if (stream != null) + { + result = new StreamWriter(stream, contentType, _logger); + } + + else + { + var bytes = content as byte[]; + + if (bytes != null) + { + result = new StreamWriter(bytes, contentType, _logger); + } + else + { + var text = content as string; + + if (text != null) + { + result = new StreamWriter(Encoding.UTF8.GetBytes(text), contentType, _logger); + } + else + { + result = new HttpResult(content, contentType); + } + } + } + if (responseHeaders == null) + { + responseHeaders = new Dictionary(); + } + + responseHeaders["Expires"] = "-1"; + AddResponseHeaders(result, responseHeaders); + + return result; + } + + /// + /// Gets the optimized result. + /// + /// + /// The request context. + /// The result. + /// The response headers. + /// System.Object. + /// result + public object GetOptimizedResult(IRequest requestContext, T result, IDictionary responseHeaders = null) + where T : class + { + return GetOptimizedResultInternal(requestContext, result, true, responseHeaders); + } + + private object GetOptimizedResultInternal(IRequest requestContext, T result, bool addCachePrevention, IDictionary responseHeaders = null) + where T : class + { + if (result == null) + { + throw new ArgumentNullException("result"); + } + + var optimizedResult = ToOptimizedResult(requestContext, result); + + if (responseHeaders == null) + { + responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + if (addCachePrevention) + { + responseHeaders["Expires"] = "-1"; + } + + // Apply headers + var hasHeaders = optimizedResult as IHasHeaders; + + if (hasHeaders != null) + { + AddResponseHeaders(hasHeaders, responseHeaders); + } + + return optimizedResult; + } + + public static string GetCompressionType(IRequest request) + { + var acceptEncoding = request.Headers["Accept-Encoding"]; + + if (!string.IsNullOrWhiteSpace(acceptEncoding)) + { + if (acceptEncoding.Contains("deflate")) + return "deflate"; + + if (acceptEncoding.Contains("gzip")) + return "gzip"; + } + + return null; + } + + /// + /// Returns the optimized result for the IRequestContext. + /// Does not use or store results in any cache. + /// + /// + /// + /// + public object ToOptimizedResult(IRequest request, T dto) + { + request.Response.Dto = dto; + + var compressionType = GetCompressionType(request); + if (compressionType == null) + { + var contentType = request.ResponseContentType; + + switch (GetRealContentType(contentType)) + { + case "application/xml": + case "text/xml": + case "text/xml; charset=utf-8": //"text/xml; charset=utf-8" also matches xml + return SerializeToXmlString(dto); + + case "application/json": + case "text/json": + return _jsonSerializer.SerializeToString(dto); + } + } + + using (var ms = new MemoryStream()) + { + using (var compressionStream = GetCompressionStream(ms, compressionType)) + { + ContentTypes.Instance.SerializeToStream(request, dto, compressionStream); + compressionStream.Dispose(); + + var compressedBytes = ms.ToArray(); + + var httpResult = new HttpResult(compressedBytes, request.ResponseContentType) + { + Status = request.Response.StatusCode + }; + + httpResult.Headers["Content-Length"] = compressedBytes.Length.ToString(UsCulture); + httpResult.Headers["Content-Encoding"] = compressionType; + + return httpResult; + } + } + } + + public static string GetRealContentType(string contentType) + { + return contentType == null + ? null + : contentType.Split(';')[0].ToLower().Trim(); + } + + public static string SerializeToXmlString(object from) + { + using (var ms = new MemoryStream()) + { + var xwSettings = new XmlWriterSettings(); + xwSettings.Encoding = new UTF8Encoding(false); + xwSettings.OmitXmlDeclaration = false; + + using (var xw = XmlWriter.Create(ms, xwSettings)) + { + var serializer = new DataContractSerializer(from.GetType()); + serializer.WriteObject(xw, from); + xw.Flush(); + ms.Seek(0, SeekOrigin.Begin); + var reader = new StreamReader(ms); + return reader.ReadToEnd(); + } + } + } + + private static Stream GetCompressionStream(Stream outputStream, string compressionType) + { + if (compressionType == "deflate") + return new DeflateStream(outputStream, CompressionMode.Compress); + if (compressionType == "gzip") + return new GZipStream(outputStream, CompressionMode.Compress); + + throw new NotSupportedException(compressionType); + } + + /// + /// Gets the optimized result using cache. + /// + /// + /// The request context. + /// The cache key. + /// The last date modified. + /// Duration of the cache. + /// The factory fn. + /// The response headers. + /// System.Object. + /// cacheKey + /// or + /// factoryFn + public object GetOptimizedResultUsingCache(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func factoryFn, IDictionary responseHeaders = null) + where T : class + { + if (cacheKey == Guid.Empty) + { + throw new ArgumentNullException("cacheKey"); + } + if (factoryFn == null) + { + throw new ArgumentNullException("factoryFn"); + } + + var key = cacheKey.ToString("N"); + + if (responseHeaders == null) + { + responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + // See if the result is already cached in the browser + var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, null); + + if (result != null) + { + return result; + } + + return GetOptimizedResultInternal(requestContext, factoryFn(), false, responseHeaders); + } + + /// + /// To the cached result. + /// + /// + /// The request context. + /// The cache key. + /// The last date modified. + /// Duration of the cache. + /// The factory fn. + /// Type of the content. + /// The response headers. + /// System.Object. + /// cacheKey + public object GetCachedResult(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func factoryFn, string contentType, IDictionary responseHeaders = null) + where T : class + { + if (cacheKey == Guid.Empty) + { + throw new ArgumentNullException("cacheKey"); + } + if (factoryFn == null) + { + throw new ArgumentNullException("factoryFn"); + } + + var key = cacheKey.ToString("N"); + + if (responseHeaders == null) + { + responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + // See if the result is already cached in the browser + var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, contentType); + + if (result != null) + { + return result; + } + + result = factoryFn(); + + // Apply caching headers + var hasHeaders = result as IHasHeaders; + + if (hasHeaders != null) + { + AddResponseHeaders(hasHeaders, responseHeaders); + return hasHeaders; + } + + IHasHeaders httpResult; + + var stream = result as Stream; + + if (stream != null) + { + httpResult = new StreamWriter(stream, contentType, _logger); + } + else + { + // Otherwise wrap into an HttpResult + httpResult = new HttpResult(result, contentType ?? "text/html", HttpStatusCode.NotModified); + } + + AddResponseHeaders(httpResult, responseHeaders); + + return httpResult; + } + + /// + /// Pres the process optimized result. + /// + /// The request context. + /// The responseHeaders. + /// The cache key. + /// The cache key string. + /// The last date modified. + /// Duration of the cache. + /// Type of the content. + /// System.Object. + private object GetCachedResult(IRequest requestContext, IDictionary responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType) + { + responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString); + + if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration)) + { + AddAgeHeader(responseHeaders, lastDateModified); + AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration); + + var result = new HttpResult(new byte[] { }, contentType ?? "text/html", HttpStatusCode.NotModified); + + AddResponseHeaders(result, responseHeaders); + + return result; + } + + AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration); + + return null; + } + + public Task GetStaticFileResult(IRequest requestContext, + string path, + FileShareMode fileShare = FileShareMode.Read) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + return GetStaticFileResult(requestContext, new StaticFileResultOptions + { + Path = path, + FileShare = fileShare + }); + } + + public Task GetStaticFileResult(IRequest requestContext, + StaticFileResultOptions options) + { + var path = options.Path; + var fileShare = options.FileShare; + + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite) + { + throw new ArgumentException("FileShare must be either Read or ReadWrite"); + } + + if (string.IsNullOrWhiteSpace(options.ContentType)) + { + options.ContentType = MimeTypes.GetMimeType(path); + } + + if (!options.DateLastModified.HasValue) + { + options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path); + } + + var cacheKey = path + options.DateLastModified.Value.Ticks; + + options.CacheKey = cacheKey.GetMD5(); + options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare)); + + return GetStaticResult(requestContext, options); + } + + /// + /// Gets the file stream. + /// + /// The path. + /// The file share. + /// Stream. + private Stream GetFileStream(string path, FileShareMode fileShare) + { + return _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShare); + } + + public Task GetStaticResult(IRequest requestContext, + Guid cacheKey, + DateTime? lastDateModified, + TimeSpan? cacheDuration, + string contentType, + Func> factoryFn, + IDictionary responseHeaders = null, + bool isHeadRequest = false) + { + return GetStaticResult(requestContext, new StaticResultOptions + { + CacheDuration = cacheDuration, + CacheKey = cacheKey, + ContentFactory = factoryFn, + ContentType = contentType, + DateLastModified = lastDateModified, + IsHeadRequest = isHeadRequest, + ResponseHeaders = responseHeaders + }); + } + + public async Task GetStaticResult(IRequest requestContext, StaticResultOptions options) + { + var cacheKey = options.CacheKey; + options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary(StringComparer.OrdinalIgnoreCase); + var contentType = options.ContentType; + + if (cacheKey == Guid.Empty) + { + throw new ArgumentNullException("cacheKey"); + } + if (options.ContentFactory == null) + { + throw new ArgumentNullException("factoryFn"); + } + + var key = cacheKey.ToString("N"); + + // See if the result is already cached in the browser + var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType); + + if (result != null) + { + return result; + } + + var compress = ShouldCompressResponse(requestContext, contentType); + var hasHeaders = await GetStaticResult(requestContext, options, compress).ConfigureAwait(false); + AddResponseHeaders(hasHeaders, options.ResponseHeaders); + + return hasHeaders; + } + + /// + /// Shoulds the compress response. + /// + /// The request context. + /// Type of the content. + /// true if XXXX, false otherwise + private bool ShouldCompressResponse(IRequest requestContext, string contentType) + { + // It will take some work to support compression with byte range requests + if (!string.IsNullOrEmpty(requestContext.Headers.Get("Range"))) + { + return false; + } + + // Don't compress media + if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + // Don't compress images + if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) + { + return false; + } + if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(contentType, "application/x-javascript", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + if (string.Equals(contentType, "application/xml", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + return false; + } + + return true; + } + + /// + /// The us culture + /// + private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + private async Task GetStaticResult(IRequest requestContext, StaticResultOptions options, bool compress) + { + var isHeadRequest = options.IsHeadRequest; + var factoryFn = options.ContentFactory; + var contentType = options.ContentType; + var responseHeaders = options.ResponseHeaders; + + var requestedCompressionType = GetCompressionType(requestContext); + + if (!compress || string.IsNullOrEmpty(requestedCompressionType)) + { + var rangeHeader = requestContext.Headers.Get("Range"); + + var stream = await factoryFn().ConfigureAwait(false); + + if (!string.IsNullOrEmpty(rangeHeader)) + { + return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) + { + OnComplete = options.OnComplete + }; + } + + responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); + + if (isHeadRequest) + { + stream.Dispose(); + + return GetHttpResult(new byte[] { }, contentType); + } + + return new StreamWriter(stream, contentType, _logger) + { + OnComplete = options.OnComplete, + OnError = options.OnError + }; + } + + string content; + + using (var stream = await factoryFn().ConfigureAwait(false)) + { + using (var reader = new StreamReader(stream)) + { + content = await reader.ReadToEndAsync().ConfigureAwait(false); + } + } + + var contents = Compress(content, requestedCompressionType); + + responseHeaders["Content-Length"] = contents.Length.ToString(UsCulture); + responseHeaders["Content-Encoding"] = requestedCompressionType; + + if (isHeadRequest) + { + return GetHttpResult(new byte[] { }, contentType); + } + + return GetHttpResult(contents, contentType, responseHeaders); + } + + public static byte[] Compress(string text, string compressionType) + { + if (compressionType == "deflate") + return Deflate(text); + + if (compressionType == "gzip") + return GZip(text); + + throw new NotSupportedException(compressionType); + } + + public static byte[] Deflate(string text) + { + return Deflate(Encoding.UTF8.GetBytes(text)); + } + + public static byte[] Deflate(byte[] bytes) + { + // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream + // Which means we must use MemoryStream since you have to use ToArray() on a closed Stream + using (var ms = new MemoryStream()) + using (var zipStream = new DeflateStream(ms, CompressionMode.Compress)) + { + zipStream.Write(bytes, 0, bytes.Length); + zipStream.Dispose(); + + return ms.ToArray(); + } + } + + public static byte[] GZip(string text) + { + return GZip(Encoding.UTF8.GetBytes(text)); + } + + public static byte[] GZip(byte[] buffer) + { + using (var ms = new MemoryStream()) + using (var zipStream = new GZipStream(ms, CompressionMode.Compress)) + { + zipStream.Write(buffer, 0, buffer.Length); + zipStream.Dispose(); + + return ms.ToArray(); + } + } + + /// + /// Adds the caching responseHeaders. + /// + /// The responseHeaders. + /// The cache key. + /// The last date modified. + /// Duration of the cache. + private void AddCachingHeaders(IDictionary responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) + { + // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant + // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching + if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue)) + { + AddAgeHeader(responseHeaders, lastDateModified); + responseHeaders["Last-Modified"] = lastDateModified.Value.ToString("r"); + } + + if (cacheDuration.HasValue) + { + responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds); + } + else if (!string.IsNullOrEmpty(cacheKey)) + { + responseHeaders["Cache-Control"] = "public"; + } + else + { + responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate"; + responseHeaders["pragma"] = "no-cache, no-store, must-revalidate"; + } + + AddExpiresHeader(responseHeaders, cacheKey, cacheDuration); + } + + /// + /// Adds the expires header. + /// + /// The responseHeaders. + /// The cache key. + /// Duration of the cache. + private void AddExpiresHeader(IDictionary responseHeaders, string cacheKey, TimeSpan? cacheDuration) + { + if (cacheDuration.HasValue) + { + responseHeaders["Expires"] = DateTime.UtcNow.Add(cacheDuration.Value).ToString("r"); + } + else if (string.IsNullOrEmpty(cacheKey)) + { + responseHeaders["Expires"] = "-1"; + } + } + + /// + /// Adds the age header. + /// + /// The responseHeaders. + /// The last date modified. + private void AddAgeHeader(IDictionary responseHeaders, DateTime? lastDateModified) + { + if (lastDateModified.HasValue) + { + responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture); + } + } + /// + /// Determines whether [is not modified] [the specified cache key]. + /// + /// The request context. + /// The cache key. + /// The last date modified. + /// Duration of the cache. + /// true if [is not modified] [the specified cache key]; otherwise, false. + private bool IsNotModified(IRequest requestContext, Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) + { + var isNotModified = true; + + var ifModifiedSinceHeader = requestContext.Headers.Get("If-Modified-Since"); + + if (!string.IsNullOrEmpty(ifModifiedSinceHeader)) + { + DateTime ifModifiedSince; + + if (DateTime.TryParse(ifModifiedSinceHeader, out ifModifiedSince)) + { + isNotModified = IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified); + } + } + + var ifNoneMatchHeader = requestContext.Headers.Get("If-None-Match"); + + // Validate If-None-Match + if (isNotModified && (cacheKey.HasValue || !string.IsNullOrEmpty(ifNoneMatchHeader))) + { + Guid ifNoneMatch; + + if (Guid.TryParse(ifNoneMatchHeader ?? string.Empty, out ifNoneMatch)) + { + if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch) + { + return true; + } + } + } + + return false; + } + + /// + /// Determines whether [is not modified] [the specified if modified since]. + /// + /// If modified since. + /// Duration of the cache. + /// The date modified. + /// true if [is not modified] [the specified if modified since]; otherwise, false. + private bool IsNotModified(DateTime ifModifiedSince, TimeSpan? cacheDuration, DateTime? dateModified) + { + if (dateModified.HasValue) + { + var lastModified = NormalizeDateForComparison(dateModified.Value); + ifModifiedSince = NormalizeDateForComparison(ifModifiedSince); + + return lastModified <= ifModifiedSince; + } + + if (cacheDuration.HasValue) + { + var cacheExpirationDate = ifModifiedSince.Add(cacheDuration.Value); + + if (DateTime.UtcNow < cacheExpirationDate) + { + return true; + } + } + + return false; + } + + + /// + /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that + /// + /// The date. + /// DateTime. + private DateTime NormalizeDateForComparison(DateTime date) + { + return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind); + } + + /// + /// Adds the response headers. + /// + /// The has options. + /// The response headers. + private void AddResponseHeaders(IHasHeaders hasHeaders, IEnumerable> responseHeaders) + { + foreach (var item in responseHeaders) + { + hasHeaders.Headers[item.Key] = item.Value; + } + } + } +} \ No newline at end of file diff --git a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs index 813f07fff..c32af8eb1 100644 --- a/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/Emby.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -127,23 +127,7 @@ namespace Emby.Server.Implementations.Library.Validators { var item = _libraryManager.GetPerson(person.Key); - var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview); - var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 30; - - var defaultMetadataRefreshMode = performFullRefresh - ? MetadataRefreshMode.FullRefresh - : MetadataRefreshMode.Default; - - var imageRefreshMode = performFullRefresh - ? ImageRefreshMode.FullRefresh - : ImageRefreshMode.Default; - - var options = new MetadataRefreshOptions(_fileSystem) - { - MetadataRefreshMode = person.Value ? defaultMetadataRefreshMode : MetadataRefreshMode.ValidationOnly, - ImageRefreshMode = person.Value ? imageRefreshMode : ImageRefreshMode.ValidationOnly, - ForceSave = performFullRefresh - }; + var options = new MetadataRefreshOptions(_fileSystem); await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); } diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs index 643d0c956..4218370ac 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -28,6 +28,7 @@ using MediaBrowser.Model.Net; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using MediaBrowser.Model.Text; +using ServiceStack.Text.Jsv; using SocketHttpListener.Net; using SocketHttpListener.Primitives; @@ -87,9 +88,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer public override void Configure() { - HostConfig.Instance.DefaultRedirectPath = DefaultRedirectPath; - - HostConfig.Instance.MapExceptionToStatusCode = new Dictionary + var mapExceptionToStatusCode = new Dictionary { {typeof (InvalidOperationException), 500}, {typeof (NotImplementedException), 500}, @@ -126,21 +125,24 @@ namespace MediaBrowser.Server.Implementations.HttpServer return _appHost.Resolve(); } - public override T TryResolve() + public override Type[] GetGenericArguments(Type type) { - return _appHost.TryResolve(); + return type.GetGenericArguments(); } - public override object CreateInstance(Type type) + public override bool IsAssignableFrom(Type type1, Type type2) { - return _appHost.CreateInstance(type); + return type1.IsAssignableFrom(type2); } - public override void OnConfigLoad() + public override T TryResolve() { - base.OnConfigLoad(); + return _appHost.TryResolve(); + } - Config.HandlerFactoryPath = null; + public override object CreateInstance(Type type) + { + return _appHost.CreateInstance(type); } protected override ServiceController CreateServiceController(params Assembly[] assembliesWithServices) @@ -156,12 +158,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer return this; } + public static string HandlerFactoryPath; + /// /// Starts the Web Service /// private void StartListener() { - HostContext.Config.HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes.First()); + HandlerFactoryPath = GetHandlerPathIfAny(UrlPrefixes.First()); _listener = GetListener(); @@ -610,6 +614,40 @@ namespace MediaBrowser.Server.Implementations.HttpServer return routes.ToArray(); } + public override object GetTaskResult(Task task, string requestName) + { + try + { + var taskObject = task as Task; + if (taskObject != null) + { + return taskObject.Result; + } + + task.Wait(); + + var type = task.GetType(); + if (!type.IsGenericType) + { + return null; + } + + Logger.Warn("Getting task result from " + requestName + " using reflection. For better performance have your api return Task"); + return type.GetProperty("Result").GetValue(task); + } + catch (TypeAccessException) + { + return null; //return null for void Task's + } + } + + public override Func GetParseFn(Type propertyType) + { + var fn = JsvReader.GetParseFn(propertyType); + + return s => fn(s); + } + public override void SerializeToJson(object o, Stream stream) { _jsonSerializer.SerializeToStream(o, stream); diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs deleted file mode 100644 index b013a0952..000000000 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ /dev/null @@ -1,835 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Net; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.IO.Compression; -using System.Net; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; -using System.Xml; -using Emby.Server.Implementations.HttpServer; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Services; -using ServiceStack; -using ServiceStack.Host; -using IRequest = MediaBrowser.Model.Services.IRequest; -using MimeTypes = MediaBrowser.Model.Net.MimeTypes; -using StreamWriter = Emby.Server.Implementations.HttpServer.StreamWriter; - -namespace MediaBrowser.Server.Implementations.HttpServer -{ - /// - /// Class HttpResultFactory - /// - public class HttpResultFactory : IHttpResultFactory - { - /// - /// The _logger - /// - private readonly ILogger _logger; - private readonly IFileSystem _fileSystem; - private readonly IJsonSerializer _jsonSerializer; - private readonly IXmlSerializer _xmlSerializer; - - /// - /// Initializes a new instance of the class. - /// - /// The log manager. - /// The file system. - /// The json serializer. - public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem, IJsonSerializer jsonSerializer, IXmlSerializer xmlSerializer) - { - _fileSystem = fileSystem; - _jsonSerializer = jsonSerializer; - _xmlSerializer = xmlSerializer; - _logger = logManager.GetLogger("HttpResultFactory"); - } - - /// - /// Gets the result. - /// - /// The content. - /// Type of the content. - /// The response headers. - /// System.Object. - public object GetResult(object content, string contentType, IDictionary responseHeaders = null) - { - return GetHttpResult(content, contentType, responseHeaders); - } - - /// - /// Gets the HTTP result. - /// - /// The content. - /// Type of the content. - /// The response headers. - /// IHasHeaders. - private IHasHeaders GetHttpResult(object content, string contentType, IDictionary responseHeaders = null) - { - IHasHeaders result; - - var stream = content as Stream; - - if (stream != null) - { - result = new StreamWriter(stream, contentType, _logger); - } - - else - { - var bytes = content as byte[]; - - if (bytes != null) - { - result = new StreamWriter(bytes, contentType, _logger); - } - else - { - var text = content as string; - - if (text != null) - { - result = new StreamWriter(Encoding.UTF8.GetBytes(text), contentType, _logger); - } - else - { - result = new HttpResult(content, contentType); - } - } - } - if (responseHeaders == null) - { - responseHeaders = new Dictionary(); - } - - responseHeaders["Expires"] = "-1"; - AddResponseHeaders(result, responseHeaders); - - return result; - } - - /// - /// Gets the optimized result. - /// - /// - /// The request context. - /// The result. - /// The response headers. - /// System.Object. - /// result - public object GetOptimizedResult(IRequest requestContext, T result, IDictionary responseHeaders = null) - where T : class - { - return GetOptimizedResultInternal(requestContext, result, true, responseHeaders); - } - - private object GetOptimizedResultInternal(IRequest requestContext, T result, bool addCachePrevention, IDictionary responseHeaders = null) - where T : class - { - if (result == null) - { - throw new ArgumentNullException("result"); - } - - var optimizedResult = ToOptimizedResult(requestContext, result); - - if (responseHeaders == null) - { - responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - if (addCachePrevention) - { - responseHeaders["Expires"] = "-1"; - } - - // Apply headers - var hasHeaders = optimizedResult as IHasHeaders; - - if (hasHeaders != null) - { - AddResponseHeaders(hasHeaders, responseHeaders); - } - - return optimizedResult; - } - - public static string GetCompressionType(IRequest request) - { - var prefs = new RequestPreferences(request); - - if (prefs.AcceptsDeflate) - return "deflate"; - - if (prefs.AcceptsGzip) - return "gzip"; - - return null; - } - - /// - /// Returns the optimized result for the IRequestContext. - /// Does not use or store results in any cache. - /// - /// - /// - /// - public object ToOptimizedResult(IRequest request, T dto) - { - request.Response.Dto = dto; - - var compressionType = GetCompressionType(request); - if (compressionType == null) - { - var contentType = request.ResponseContentType; - var contentTypeAttr = ContentFormat.GetEndpointAttributes(contentType); - - switch (contentTypeAttr) - { - case RequestAttributes.Xml: - return SerializeToXmlString(dto); - - case RequestAttributes.Json: - return _jsonSerializer.SerializeToString(dto); - } - } - - using (var ms = new MemoryStream()) - { - using (var compressionStream = GetCompressionStream(ms, compressionType)) - { - ContentTypes.Instance.SerializeToStream(request, dto, compressionStream); - compressionStream.Close(); - - var compressedBytes = ms.ToArray(); - - var httpResult = new HttpResult(compressedBytes, request.ResponseContentType) - { - Status = request.Response.StatusCode - }; - - httpResult.Headers["Content-Length"] = compressedBytes.Length.ToString(UsCulture); - httpResult.Headers["Content-Encoding"] = compressionType; - - return httpResult; - } - } - } - - public static string SerializeToXmlString(object from) - { - using (var ms = new MemoryStream()) - { - var xwSettings = new XmlWriterSettings(); - xwSettings.Encoding = new UTF8Encoding(false); - xwSettings.OmitXmlDeclaration = false; - - using (var xw = XmlWriter.Create(ms, xwSettings)) - { - var serializer = new DataContractSerializer(from.GetType()); - serializer.WriteObject(xw, from); - xw.Flush(); - ms.Seek(0, SeekOrigin.Begin); - var reader = new StreamReader(ms); - return reader.ReadToEnd(); - } - } - } - - private static Stream GetCompressionStream(Stream outputStream, string compressionType) - { - if (compressionType == "deflate") - return new DeflateStream(outputStream, CompressionMode.Compress); - if (compressionType == "gzip") - return new GZipStream(outputStream, CompressionMode.Compress); - - throw new NotSupportedException(compressionType); - } - - /// - /// Gets the optimized result using cache. - /// - /// - /// The request context. - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// The factory fn. - /// The response headers. - /// System.Object. - /// cacheKey - /// or - /// factoryFn - public object GetOptimizedResultUsingCache(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func factoryFn, IDictionary responseHeaders = null) - where T : class - { - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (factoryFn == null) - { - throw new ArgumentNullException("factoryFn"); - } - - var key = cacheKey.ToString("N"); - - if (responseHeaders == null) - { - responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - // See if the result is already cached in the browser - var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, null); - - if (result != null) - { - return result; - } - - return GetOptimizedResultInternal(requestContext, factoryFn(), false, responseHeaders); - } - - /// - /// To the cached result. - /// - /// - /// The request context. - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// The factory fn. - /// Type of the content. - /// The response headers. - /// System.Object. - /// cacheKey - public object GetCachedResult(IRequest requestContext, Guid cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration, Func factoryFn, string contentType, IDictionary responseHeaders = null) - where T : class - { - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (factoryFn == null) - { - throw new ArgumentNullException("factoryFn"); - } - - var key = cacheKey.ToString("N"); - - if (responseHeaders == null) - { - responseHeaders = new Dictionary(StringComparer.OrdinalIgnoreCase); - } - - // See if the result is already cached in the browser - var result = GetCachedResult(requestContext, responseHeaders, cacheKey, key, lastDateModified, cacheDuration, contentType); - - if (result != null) - { - return result; - } - - result = factoryFn(); - - // Apply caching headers - var hasHeaders = result as IHasHeaders; - - if (hasHeaders != null) - { - AddResponseHeaders(hasHeaders, responseHeaders); - return hasHeaders; - } - - IHasHeaders httpResult; - - var stream = result as Stream; - - if (stream != null) - { - httpResult = new StreamWriter(stream, contentType, _logger); - } - else - { - // Otherwise wrap into an HttpResult - httpResult = new HttpResult(result, contentType ?? "text/html", HttpStatusCode.NotModified); - } - - AddResponseHeaders(httpResult, responseHeaders); - - return httpResult; - } - - /// - /// Pres the process optimized result. - /// - /// The request context. - /// The responseHeaders. - /// The cache key. - /// The cache key string. - /// The last date modified. - /// Duration of the cache. - /// Type of the content. - /// System.Object. - private object GetCachedResult(IRequest requestContext, IDictionary responseHeaders, Guid cacheKey, string cacheKeyString, DateTime? lastDateModified, TimeSpan? cacheDuration, string contentType) - { - responseHeaders["ETag"] = string.Format("\"{0}\"", cacheKeyString); - - if (IsNotModified(requestContext, cacheKey, lastDateModified, cacheDuration)) - { - AddAgeHeader(responseHeaders, lastDateModified); - AddExpiresHeader(responseHeaders, cacheKeyString, cacheDuration); - - var result = new HttpResult(new byte[] { }, contentType ?? "text/html", HttpStatusCode.NotModified); - - AddResponseHeaders(result, responseHeaders); - - return result; - } - - AddCachingHeaders(responseHeaders, cacheKeyString, lastDateModified, cacheDuration); - - return null; - } - - public Task GetStaticFileResult(IRequest requestContext, - string path, - FileShareMode fileShare = FileShareMode.Read) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - return GetStaticFileResult(requestContext, new StaticFileResultOptions - { - Path = path, - FileShare = fileShare - }); - } - - public Task GetStaticFileResult(IRequest requestContext, - StaticFileResultOptions options) - { - var path = options.Path; - var fileShare = options.FileShare; - - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentNullException("path"); - } - - if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite) - { - throw new ArgumentException("FileShare must be either Read or ReadWrite"); - } - - if (string.IsNullOrWhiteSpace(options.ContentType)) - { - options.ContentType = MimeTypes.GetMimeType(path); - } - - if (!options.DateLastModified.HasValue) - { - options.DateLastModified = _fileSystem.GetLastWriteTimeUtc(path); - } - - var cacheKey = path + options.DateLastModified.Value.Ticks; - - options.CacheKey = cacheKey.GetMD5(); - options.ContentFactory = () => Task.FromResult(GetFileStream(path, fileShare)); - - return GetStaticResult(requestContext, options); - } - - /// - /// Gets the file stream. - /// - /// The path. - /// The file share. - /// Stream. - private Stream GetFileStream(string path, FileShareMode fileShare) - { - return _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShare); - } - - public Task GetStaticResult(IRequest requestContext, - Guid cacheKey, - DateTime? lastDateModified, - TimeSpan? cacheDuration, - string contentType, - Func> factoryFn, - IDictionary responseHeaders = null, - bool isHeadRequest = false) - { - return GetStaticResult(requestContext, new StaticResultOptions - { - CacheDuration = cacheDuration, - CacheKey = cacheKey, - ContentFactory = factoryFn, - ContentType = contentType, - DateLastModified = lastDateModified, - IsHeadRequest = isHeadRequest, - ResponseHeaders = responseHeaders - }); - } - - public async Task GetStaticResult(IRequest requestContext, StaticResultOptions options) - { - var cacheKey = options.CacheKey; - options.ResponseHeaders = options.ResponseHeaders ?? new Dictionary(StringComparer.OrdinalIgnoreCase); - var contentType = options.ContentType; - - if (cacheKey == Guid.Empty) - { - throw new ArgumentNullException("cacheKey"); - } - if (options.ContentFactory == null) - { - throw new ArgumentNullException("factoryFn"); - } - - var key = cacheKey.ToString("N"); - - // See if the result is already cached in the browser - var result = GetCachedResult(requestContext, options.ResponseHeaders, cacheKey, key, options.DateLastModified, options.CacheDuration, contentType); - - if (result != null) - { - return result; - } - - var compress = ShouldCompressResponse(requestContext, contentType); - var hasHeaders = await GetStaticResult(requestContext, options, compress).ConfigureAwait(false); - AddResponseHeaders(hasHeaders, options.ResponseHeaders); - - return hasHeaders; - } - - /// - /// Shoulds the compress response. - /// - /// The request context. - /// Type of the content. - /// true if XXXX, false otherwise - private bool ShouldCompressResponse(IRequest requestContext, string contentType) - { - // It will take some work to support compression with byte range requests - if (!string.IsNullOrEmpty(requestContext.GetHeader("Range"))) - { - return false; - } - - // Don't compress media - if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - // Don't compress images - if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (contentType.StartsWith("font/", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - if (contentType.StartsWith("application/", StringComparison.OrdinalIgnoreCase)) - { - if (string.Equals(contentType, "application/x-javascript", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - if (string.Equals(contentType, "application/xml", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - return false; - } - - return true; - } - - /// - /// The us culture - /// - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - private async Task GetStaticResult(IRequest requestContext, StaticResultOptions options, bool compress) - { - var isHeadRequest = options.IsHeadRequest; - var factoryFn = options.ContentFactory; - var contentType = options.ContentType; - var responseHeaders = options.ResponseHeaders; - - var requestedCompressionType = GetCompressionType(requestContext); - - if (!compress || string.IsNullOrEmpty(requestedCompressionType)) - { - var rangeHeader = requestContext.GetHeader("Range"); - - var stream = await factoryFn().ConfigureAwait(false); - - if (!string.IsNullOrEmpty(rangeHeader)) - { - return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest, _logger) - { - OnComplete = options.OnComplete - }; - } - - responseHeaders["Content-Length"] = stream.Length.ToString(UsCulture); - - if (isHeadRequest) - { - stream.Dispose(); - - return GetHttpResult(new byte[] { }, contentType); - } - - return new StreamWriter(stream, contentType, _logger) - { - OnComplete = options.OnComplete, - OnError = options.OnError - }; - } - - string content; - - using (var stream = await factoryFn().ConfigureAwait(false)) - { - using (var reader = new StreamReader(stream)) - { - content = await reader.ReadToEndAsync().ConfigureAwait(false); - } - } - - var contents = Compress(content, requestedCompressionType); - - responseHeaders["Content-Length"] = contents.Length.ToString(UsCulture); - responseHeaders["Content-Encoding"] = requestedCompressionType; - - if (isHeadRequest) - { - return GetHttpResult(new byte[] { }, contentType); - } - - return GetHttpResult(contents, contentType, responseHeaders); - } - - public static byte[] Compress(string text, string compressionType) - { - if (compressionType == "deflate") - return Deflate(text); - - if (compressionType == "gzip") - return GZip(text); - - throw new NotSupportedException(compressionType); - } - - public static byte[] Deflate(string text) - { - return Deflate(Encoding.UTF8.GetBytes(text)); - } - - public static byte[] Deflate(byte[] bytes) - { - // In .NET FX incompat-ville, you can't access compressed bytes without closing DeflateStream - // Which means we must use MemoryStream since you have to use ToArray() on a closed Stream - using (var ms = new MemoryStream()) - using (var zipStream = new DeflateStream(ms, CompressionMode.Compress)) - { - zipStream.Write(bytes, 0, bytes.Length); - zipStream.Close(); - - return ms.ToArray(); - } - } - - public static byte[] GZip(string text) - { - return GZip(Encoding.UTF8.GetBytes(text)); - } - - public static byte[] GZip(byte[] buffer) - { - using (var ms = new MemoryStream()) - using (var zipStream = new GZipStream(ms, CompressionMode.Compress)) - { - zipStream.Write(buffer, 0, buffer.Length); - zipStream.Close(); - - return ms.ToArray(); - } - } - - /// - /// Adds the caching responseHeaders. - /// - /// The responseHeaders. - /// The cache key. - /// The last date modified. - /// Duration of the cache. - private void AddCachingHeaders(IDictionary responseHeaders, string cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) - { - // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant - // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching - if (lastDateModified.HasValue && (string.IsNullOrEmpty(cacheKey) || cacheDuration.HasValue)) - { - AddAgeHeader(responseHeaders, lastDateModified); - responseHeaders["Last-Modified"] = lastDateModified.Value.ToString("r"); - } - - if (cacheDuration.HasValue) - { - responseHeaders["Cache-Control"] = "public, max-age=" + Convert.ToInt32(cacheDuration.Value.TotalSeconds); - } - else if (!string.IsNullOrEmpty(cacheKey)) - { - responseHeaders["Cache-Control"] = "public"; - } - else - { - responseHeaders["Cache-Control"] = "no-cache, no-store, must-revalidate"; - responseHeaders["pragma"] = "no-cache, no-store, must-revalidate"; - } - - AddExpiresHeader(responseHeaders, cacheKey, cacheDuration); - } - - /// - /// Adds the expires header. - /// - /// The responseHeaders. - /// The cache key. - /// Duration of the cache. - private void AddExpiresHeader(IDictionary responseHeaders, string cacheKey, TimeSpan? cacheDuration) - { - if (cacheDuration.HasValue) - { - responseHeaders["Expires"] = DateTime.UtcNow.Add(cacheDuration.Value).ToString("r"); - } - else if (string.IsNullOrEmpty(cacheKey)) - { - responseHeaders["Expires"] = "-1"; - } - } - - /// - /// Adds the age header. - /// - /// The responseHeaders. - /// The last date modified. - private void AddAgeHeader(IDictionary responseHeaders, DateTime? lastDateModified) - { - if (lastDateModified.HasValue) - { - responseHeaders["Age"] = Convert.ToInt64((DateTime.UtcNow - lastDateModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture); - } - } - /// - /// Determines whether [is not modified] [the specified cache key]. - /// - /// The request context. - /// The cache key. - /// The last date modified. - /// Duration of the cache. - /// true if [is not modified] [the specified cache key]; otherwise, false. - private bool IsNotModified(IRequest requestContext, Guid? cacheKey, DateTime? lastDateModified, TimeSpan? cacheDuration) - { - var isNotModified = true; - - var ifModifiedSinceHeader = requestContext.GetHeader("If-Modified-Since"); - - if (!string.IsNullOrEmpty(ifModifiedSinceHeader)) - { - DateTime ifModifiedSince; - - if (DateTime.TryParse(ifModifiedSinceHeader, out ifModifiedSince)) - { - isNotModified = IsNotModified(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified); - } - } - - var ifNoneMatchHeader = requestContext.GetHeader("If-None-Match"); - - // Validate If-None-Match - if (isNotModified && (cacheKey.HasValue || !string.IsNullOrEmpty(ifNoneMatchHeader))) - { - Guid ifNoneMatch; - - if (Guid.TryParse(ifNoneMatchHeader ?? string.Empty, out ifNoneMatch)) - { - if (cacheKey.HasValue && cacheKey.Value == ifNoneMatch) - { - return true; - } - } - } - - return false; - } - - /// - /// Determines whether [is not modified] [the specified if modified since]. - /// - /// If modified since. - /// Duration of the cache. - /// The date modified. - /// true if [is not modified] [the specified if modified since]; otherwise, false. - private bool IsNotModified(DateTime ifModifiedSince, TimeSpan? cacheDuration, DateTime? dateModified) - { - if (dateModified.HasValue) - { - var lastModified = NormalizeDateForComparison(dateModified.Value); - ifModifiedSince = NormalizeDateForComparison(ifModifiedSince); - - return lastModified <= ifModifiedSince; - } - - if (cacheDuration.HasValue) - { - var cacheExpirationDate = ifModifiedSince.Add(cacheDuration.Value); - - if (DateTime.UtcNow < cacheExpirationDate) - { - return true; - } - } - - return false; - } - - - /// - /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that - /// - /// The date. - /// DateTime. - private DateTime NormalizeDateForComparison(DateTime date) - { - return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind); - } - - /// - /// Adds the response headers. - /// - /// The has options. - /// The response headers. - private void AddResponseHeaders(IHasHeaders hasHeaders, IEnumerable> responseHeaders) - { - foreach (var item in responseHeaders) - { - hasHeaders.Headers[item.Key] = item.Value; - } - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs index 95b2ccaba..628e5cc7e 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/SocketSharp/WebSocketSharpRequest.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.IO; using System.Text; +using Emby.Server.Implementations.HttpServer; using Emby.Server.Implementations.HttpServer.SocketSharp; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Services; using ServiceStack; -using ServiceStack.Host; using SocketHttpListener.Net; using IHttpFile = MediaBrowser.Model.Services.IHttpFile; using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest; @@ -244,14 +244,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp var specifiedContentType = GetQueryStringContentType(httpReq); if (!string.IsNullOrEmpty(specifiedContentType)) return specifiedContentType; + var serverDefaultContentType = "application/json"; + var acceptContentTypes = httpReq.AcceptTypes; var defaultContentType = httpReq.ContentType; if (HasAnyOfContentTypes(httpReq, FormUrlEncoded, MultiPartFormData)) { - defaultContentType = HostContext.Config.DefaultContentType; + defaultContentType = serverDefaultContentType; } - var customContentTypes = ContentTypes.Instance.ContentTypeFormats.Values; var preferredContentTypes = new string[] {}; var acceptsAnything = false; @@ -261,7 +262,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp var hasPreferredContentTypes = new bool[preferredContentTypes.Length]; foreach (var acceptsType in acceptContentTypes) { - var contentType = ContentFormat.GetRealContentType(acceptsType); + var contentType = HttpResultFactory.GetRealContentType(acceptsType); acceptsAnything = acceptsAnything || contentType == "*/*"; for (var i = 0; i < preferredContentTypes.Length; i++) @@ -285,17 +286,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { if (hasDefaultContentType) return defaultContentType; - if (HostContext.Config.DefaultContentType != null) - return HostContext.Config.DefaultContentType; - } - - foreach (var contentType in acceptContentTypes) - { - foreach (var customContentType in customContentTypes) - { - if (contentType.StartsWith(customContentType, StringComparison.OrdinalIgnoreCase)) - return customContentType; - } + if (serverDefaultContentType != null) + return serverDefaultContentType; } } @@ -305,8 +297,9 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp } //We could also send a '406 Not Acceptable', but this is allowed also - return HostContext.Config.DefaultContentType; + return serverDefaultContentType; } + public const string Soap11 = "text/xml; charset=utf-8"; public static bool HasAnyOfContentTypes(IRequest request, params string[] contentTypes) @@ -342,10 +335,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp if (format.Contains("json")) return "application/json"; if (format.Contains("xml")) return Xml; - string contentType; - ContentTypes.Instance.ContentTypeFormats.TryGetValue(format, out contentType); - - return contentType; + return null; } public bool HasExplicitResponseContentType { get; private set; } @@ -357,7 +347,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp { if (this.pathInfo == null) { - var mode = HostContext.Config.HandlerFactoryPath; + var mode = HttpListenerHost.HandlerFactoryPath; var pos = request.RawUrl.IndexOf("?"); if (pos != -1) diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index a2edc7aa8..a253753c5 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -69,6 +69,10 @@ ..\packages\Patterns.Logging.1.0.0.6\lib\portable-net45+win8\Patterns.Logging.dll True + + ..\packages\ServiceStack.Text.4.5.4\lib\net45\ServiceStack.Text.dll + True + False ..\ThirdParty\SharpCompress\SharpCompress.dll @@ -88,9 +92,6 @@ ..\ThirdParty\ServiceStack\ServiceStack.dll - - ..\ThirdParty\ServiceStack.Text\ServiceStack.Text.dll - ..\packages\UniversalDetector.1.0.1\lib\portable-net45+sl4+wp71+win8+wpa81\UniversalDetector.dll @@ -109,7 +110,6 @@ - @@ -141,7 +141,6 @@ - @@ -351,7 +350,9 @@ - + + +