diff options
Diffstat (limited to 'Jellyfin.Api/Controllers')
| -rw-r--r-- | Jellyfin.Api/Controllers/DlnaController.cs | 132 | ||||
| -rw-r--r-- | Jellyfin.Api/Controllers/DlnaServerController.cs | 257 |
2 files changed, 389 insertions, 0 deletions
diff --git a/Jellyfin.Api/Controllers/DlnaController.cs b/Jellyfin.Api/Controllers/DlnaController.cs new file mode 100644 index 000000000..397299a73 --- /dev/null +++ b/Jellyfin.Api/Controllers/DlnaController.cs @@ -0,0 +1,132 @@ +using System.Collections.Generic; +using Jellyfin.Api.Constants; +using MediaBrowser.Controller.Dlna; +using MediaBrowser.Model.Dlna; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// <summary> + /// Dlna Controller. + /// </summary> + [Authorize(Policy = Policies.RequiresElevation)] + public class DlnaController : BaseJellyfinApiController + { + private readonly IDlnaManager _dlnaManager; + + /// <summary> + /// Initializes a new instance of the <see cref="DlnaController"/> class. + /// </summary> + /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param> + public DlnaController(IDlnaManager dlnaManager) + { + _dlnaManager = dlnaManager; + } + + /// <summary> + /// Get profile infos. + /// </summary> + /// <response code="200">Device profile infos returned.</response> + /// <returns>An <see cref="OkResult"/> containing the device profile infos.</returns> + [HttpGet("ProfileInfos")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<IEnumerable<DeviceProfileInfo>> GetProfileInfos() + { + return Ok(_dlnaManager.GetProfileInfos()); + } + + /// <summary> + /// Gets the default profile. + /// </summary> + /// <response code="200">Default device profile returned.</response> + /// <returns>An <see cref="OkResult"/> containing the default profile.</returns> + [HttpGet("Profiles/Default")] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult<DeviceProfile> GetDefaultProfile() + { + return _dlnaManager.GetDefaultProfile(); + } + + /// <summary> + /// Gets a single profile. + /// </summary> + /// <param name="profileId">Profile Id.</param> + /// <response code="200">Device profile returned.</response> + /// <response code="404">Device profile not found.</response> + /// <returns>An <see cref="OkResult"/> containing the profile on success, or a <see cref="NotFoundResult"/> if device profile not found.</returns> + [HttpGet("Profiles/{profileId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult<DeviceProfile> GetProfile([FromRoute] string profileId) + { + var profile = _dlnaManager.GetProfile(profileId); + if (profile == null) + { + return NotFound(); + } + + return profile; + } + + /// <summary> + /// Deletes a profile. + /// </summary> + /// <param name="profileId">Profile id.</param> + /// <response code="204">Device profile deleted.</response> + /// <response code="404">Device profile not found.</response> + /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if profile not found.</returns> + [HttpDelete("Profiles/{profileId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult DeleteProfile([FromRoute] string profileId) + { + var existingDeviceProfile = _dlnaManager.GetProfile(profileId); + if (existingDeviceProfile == null) + { + return NotFound(); + } + + _dlnaManager.DeleteProfile(profileId); + return NoContent(); + } + + /// <summary> + /// Creates a profile. + /// </summary> + /// <param name="deviceProfile">Device profile.</param> + /// <response code="204">Device profile created.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Profiles")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + public ActionResult CreateProfile([FromBody] DeviceProfile deviceProfile) + { + _dlnaManager.CreateProfile(deviceProfile); + return NoContent(); + } + + /// <summary> + /// Updates a profile. + /// </summary> + /// <param name="profileId">Profile id.</param> + /// <param name="deviceProfile">Device profile.</param> + /// <response code="204">Device profile updated.</response> + /// <response code="404">Device profile not found.</response> + /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if profile not found.</returns> + [HttpPost("Profiles/{profileId}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public ActionResult UpdateProfile([FromRoute] string profileId, [FromBody] DeviceProfile deviceProfile) + { + var existingDeviceProfile = _dlnaManager.GetProfile(profileId); + if (existingDeviceProfile == null) + { + return NotFound(); + } + + _dlnaManager.UpdateProfile(deviceProfile); + return NoContent(); + } + } +} diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs new file mode 100644 index 000000000..2f5561adb --- /dev/null +++ b/Jellyfin.Api/Controllers/DlnaServerController.cs @@ -0,0 +1,257 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading.Tasks; +using Emby.Dlna; +using Emby.Dlna.Main; +using Jellyfin.Api.Attributes; +using MediaBrowser.Controller.Dlna; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + /// <summary> + /// Dlna Server Controller. + /// </summary> + [Route("Dlna")] + public class DlnaServerController : BaseJellyfinApiController + { + private const string XMLContentType = "text/xml; charset=UTF-8"; + + private readonly IDlnaManager _dlnaManager; + private readonly IContentDirectory _contentDirectory; + private readonly IConnectionManager _connectionManager; + private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar; + + /// <summary> + /// Initializes a new instance of the <see cref="DlnaServerController"/> class. + /// </summary> + /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param> + public DlnaServerController(IDlnaManager dlnaManager) + { + _dlnaManager = dlnaManager; + _contentDirectory = DlnaEntryPoint.Current.ContentDirectory; + _connectionManager = DlnaEntryPoint.Current.ConnectionManager; + _mediaReceiverRegistrar = DlnaEntryPoint.Current.MediaReceiverRegistrar; + } + + /// <summary> + /// Get Description Xml. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <response code="200">Description xml returned.</response> + /// <returns>An <see cref="OkResult"/> containing the description xml.</returns> + [HttpGet("{serverId}/description.xml")] + [HttpGet("{serverId}/description")] + [Produces(XMLContentType)] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult GetDescriptionXml([FromRoute] string serverId) + { + var url = GetAbsoluteUri(); + var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase)); + var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress); + return Ok(xml); + } + + /// <summary> + /// Gets Dlna content directory xml. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <response code="200">Dlna content directory returned.</response> + /// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns> + [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml")] + [HttpGet("{serverId}/ContentDirectory/ContentDirectory")] + [Produces(XMLContentType)] + [ProducesResponseType(StatusCodes.Status200OK)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] + public ActionResult GetContentDirectory([FromRoute] string serverId) + { + return Ok(_contentDirectory.GetServiceXml()); + } + + /// <summary> + /// Gets Dlna media receiver registrar xml. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <returns>Dlna media receiver registrar xml.</returns> + [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml")] + [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar")] + [Produces(XMLContentType)] + [ProducesResponseType(StatusCodes.Status200OK)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] + public ActionResult GetMediaReceiverRegistrar([FromRoute] string serverId) + { + return Ok(_mediaReceiverRegistrar.GetServiceXml()); + } + + /// <summary> + /// Gets Dlna media receiver registrar xml. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <returns>Dlna media receiver registrar xml.</returns> + [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml")] + [HttpGet("{serverId}/ConnectionManager/ConnectionManager")] + [Produces(XMLContentType)] + [ProducesResponseType(StatusCodes.Status200OK)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] + public ActionResult GetConnectionManager([FromRoute] string serverId) + { + return Ok(_connectionManager.GetServiceXml()); + } + + /// <summary> + /// Process a content directory control request. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <returns>Control response.</returns> + [HttpPost("{serverId}/ContentDirectory/Control")] + public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute] string serverId) + { + return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false); + } + + /// <summary> + /// Process a connection manager control request. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <returns>Control response.</returns> + [HttpPost("{serverId}/ConnectionManager/Control")] + public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute] string serverId) + { + return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false); + } + + /// <summary> + /// Process a media receiver registrar control request. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <returns>Control response.</returns> + [HttpPost("{serverId}/MediaReceiverRegistrar/Control")] + public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute] string serverId) + { + return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false); + } + + /// <summary> + /// Processes an event subscription request. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <returns>Event subscription response.</returns> + [HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")] + [HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")] + [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] + public ActionResult<EventSubscriptionResponse> ProcessMediaReceiverRegistrarEventRequest(string serverId) + { + return ProcessEventRequest(_mediaReceiverRegistrar); + } + + /// <summary> + /// Processes an event subscription request. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <returns>Event subscription response.</returns> + [HttpSubscribe("{serverId}/ContentDirectory/Events")] + [HttpUnsubscribe("{serverId}/ContentDirectory/Events")] + [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] + public ActionResult<EventSubscriptionResponse> ProcessContentDirectoryEventRequest(string serverId) + { + return ProcessEventRequest(_contentDirectory); + } + + /// <summary> + /// Processes an event subscription request. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <returns>Event subscription response.</returns> + [HttpSubscribe("{serverId}/ConnectionManager/Events")] + [HttpUnsubscribe("{serverId}/ConnectionManager/Events")] + [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] + public ActionResult<EventSubscriptionResponse> ProcessConnectionManagerEventRequest(string serverId) + { + return ProcessEventRequest(_connectionManager); + } + + /// <summary> + /// Gets a server icon. + /// </summary> + /// <param name="serverId">Server UUID.</param> + /// <param name="fileName">The icon filename.</param> + /// <returns>Icon stream.</returns> + [HttpGet("{serverId}/icons/{filename}")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")] + public ActionResult GetIconId([FromRoute] string serverId, [FromRoute] string fileName) + { + return GetIconInternal(fileName); + } + + /// <summary> + /// Gets a server icon. + /// </summary> + /// <param name="fileName">The icon filename.</param> + /// <returns>Icon stream.</returns> + [HttpGet("icons/{filename}")] + public ActionResult GetIcon([FromRoute] string fileName) + { + return GetIconInternal(fileName); + } + + private ActionResult GetIconInternal(string fileName) + { + var icon = _dlnaManager.GetIcon(fileName); + if (icon == null) + { + return NotFound(); + } + + var contentType = "image/" + Path.GetExtension(fileName) + .TrimStart('.') + .ToLowerInvariant(); + + return File(icon.Stream, contentType); + } + + private string GetAbsoluteUri() + { + return $"{Request.Scheme}://{Request.Host}{Request.Path}"; + } + + private Task<ControlResponse> ProcessControlRequestInternalAsync(string id, Stream requestStream, IUpnpService service) + { + return service.ProcessControlRequestAsync(new ControlRequest + { + Headers = Request.Headers, + InputXml = requestStream, + TargetServerUuId = id, + RequestedUrl = GetAbsoluteUri() + }); + } + + private EventSubscriptionResponse ProcessEventRequest(IEventManager eventManager) + { + var subscriptionId = Request.Headers["SID"]; + if (string.Equals(Request.Method, "subscribe", StringComparison.OrdinalIgnoreCase)) + { + var notificationType = Request.Headers["NT"]; + var callback = Request.Headers["CALLBACK"]; + var timeoutString = Request.Headers["TIMEOUT"]; + + if (string.IsNullOrEmpty(notificationType)) + { + return eventManager.RenewEventSubscription( + subscriptionId, + notificationType, + timeoutString, + callback); + } + + return eventManager.CreateEventSubscription(notificationType, timeoutString, callback); + } + + return eventManager.CancelEventSubscription(subscriptionId); + } + } +} |
