2018-11-19 23:51:58 +00:00
|
|
|
|
using Microsoft.AspNetCore.Identity;
|
2018-11-15 15:10:29 +00:00
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2018-11-19 23:51:58 +00:00
|
|
|
|
using Newtonsoft.Json;
|
2018-11-15 15:10:29 +00:00
|
|
|
|
using Roadie.Api.Services;
|
|
|
|
|
using Roadie.Library.Caching;
|
|
|
|
|
using Roadie.Library.Identity;
|
2018-11-19 23:51:58 +00:00
|
|
|
|
using Roadie.Library.Models.ThirdPartyApi.Subsonic;
|
2018-11-21 06:34:53 +00:00
|
|
|
|
using System;
|
2018-11-20 04:47:12 +00:00
|
|
|
|
using System.Net;
|
2018-11-19 23:51:58 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2018-11-15 15:10:29 +00:00
|
|
|
|
|
|
|
|
|
namespace Roadie.Api.Controllers
|
|
|
|
|
{
|
2018-11-19 23:51:58 +00:00
|
|
|
|
//[Produces("application/json")]
|
|
|
|
|
[Route("subsonic/rest")]
|
2018-11-15 15:10:29 +00:00
|
|
|
|
[ApiController]
|
2018-11-19 23:51:58 +00:00
|
|
|
|
//[Authorize]
|
2018-11-15 15:10:29 +00:00
|
|
|
|
public class SubsonicController : EntityControllerBase
|
|
|
|
|
{
|
2018-11-20 14:36:07 +00:00
|
|
|
|
private IPlayActivityService PlayActivityService { get; }
|
2018-11-21 06:34:53 +00:00
|
|
|
|
private ISubsonicService SubsonicService { get; }
|
2018-11-20 14:36:07 +00:00
|
|
|
|
private ITrackService TrackService { get; }
|
2018-11-15 15:10:29 +00:00
|
|
|
|
|
2018-11-20 14:36:07 +00:00
|
|
|
|
public SubsonicController(ISubsonicService subsonicService, ITrackService trackService, IPlayActivityService playActivityService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
2018-11-15 15:10:29 +00:00
|
|
|
|
: base(cacheManager, configuration, userManager)
|
|
|
|
|
{
|
2018-11-21 06:34:53 +00:00
|
|
|
|
this.Logger = logger.CreateLogger("RoadieApi.Controllers.SubsonicController");
|
2018-11-15 15:10:29 +00:00
|
|
|
|
this.SubsonicService = subsonicService;
|
2018-11-20 14:36:07 +00:00
|
|
|
|
this.TrackService = trackService;
|
|
|
|
|
this.PlayActivityService = playActivityService;
|
2018-11-15 15:10:29 +00:00
|
|
|
|
}
|
2018-11-19 23:51:58 +00:00
|
|
|
|
|
2018-11-21 06:34:53 +00:00
|
|
|
|
[HttpGet("getAlbumList.view")]
|
|
|
|
|
[HttpPost("getAlbumList.view")]
|
2018-11-19 23:51:58 +00:00
|
|
|
|
[ProducesResponseType(200)]
|
2018-11-21 06:34:53 +00:00
|
|
|
|
public async Task<IActionResult> GetAlbumList([FromQuery]Request request)
|
2018-11-19 23:51:58 +00:00
|
|
|
|
{
|
2018-11-21 06:34:53 +00:00
|
|
|
|
var result = await this.SubsonicService.GetAlbumList(request, null, "1");
|
|
|
|
|
return this.BuildResponse(request, result.Data, "albumList");
|
2018-11-19 23:51:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-21 06:34:53 +00:00
|
|
|
|
[HttpGet("getAlbumList2.view")]
|
|
|
|
|
[HttpPost("getAlbumList2.view")]
|
2018-11-19 23:51:58 +00:00
|
|
|
|
[ProducesResponseType(200)]
|
2018-11-21 06:34:53 +00:00
|
|
|
|
public async Task<IActionResult> GetAlbumList2([FromQuery]Request request)
|
2018-11-19 23:51:58 +00:00
|
|
|
|
{
|
2018-11-21 06:34:53 +00:00
|
|
|
|
var result = await this.SubsonicService.GetAlbumList(request, null, "2");
|
|
|
|
|
return this.BuildResponse(request, result.Data, "albumList");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet("getCoverArt.view")]
|
|
|
|
|
[HttpPost("getCoverArt.view")]
|
|
|
|
|
[ProducesResponseType(200)]
|
|
|
|
|
public async Task<IActionResult> GetCoverArt([FromQuery]Request request, int? size)
|
|
|
|
|
{
|
|
|
|
|
var result = await this.SubsonicService.GetCoverArt(request, size);
|
|
|
|
|
if (result == null || result.IsNotFoundResult)
|
|
|
|
|
{
|
|
|
|
|
return NotFound();
|
|
|
|
|
}
|
|
|
|
|
if (!result.IsSuccess)
|
|
|
|
|
{
|
|
|
|
|
this.Logger.LogWarning($"GetCoverArt Failed For [{ JsonConvert.SerializeObject(request) }]");
|
|
|
|
|
return StatusCode((int)HttpStatusCode.InternalServerError);
|
|
|
|
|
}
|
|
|
|
|
return File(fileContents: result.Data.Bytes,
|
|
|
|
|
contentType: result.ContentType,
|
|
|
|
|
fileDownloadName: $"{ result.Data.Caption ?? request.id.ToString()}.jpg",
|
|
|
|
|
lastModified: result.LastModified,
|
|
|
|
|
entityTag: result.ETag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet("getGenres.view")]
|
|
|
|
|
[HttpPost("getGenres.view")]
|
|
|
|
|
[ProducesResponseType(200)]
|
|
|
|
|
public async Task<IActionResult> GetGenres([FromQuery]Request request)
|
|
|
|
|
{
|
|
|
|
|
var result = await this.SubsonicService.GetGenres(request);
|
|
|
|
|
return this.BuildResponse(request, result.Data, "genres");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[HttpGet("getIndexes.view")]
|
|
|
|
|
[HttpPost("getIndexes.view")]
|
|
|
|
|
[ProducesResponseType(200)]
|
|
|
|
|
public async Task<IActionResult> GetIndexes([FromQuery]Request request, string musicFolderId = null, long? ifModifiedSince = null)
|
|
|
|
|
{
|
|
|
|
|
var result = await this.SubsonicService.GetIndexes(request, null, musicFolderId, ifModifiedSince);
|
|
|
|
|
return this.BuildResponse(request, result.Data, "indexes");
|
2018-11-19 23:51:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-20 04:47:12 +00:00
|
|
|
|
[HttpGet("getMusicDirectory.view")]
|
2018-11-21 06:34:53 +00:00
|
|
|
|
[HttpPost("getMusicDirectory.view")]
|
2018-11-20 04:47:12 +00:00
|
|
|
|
[ProducesResponseType(200)]
|
|
|
|
|
public async Task<IActionResult> GetMusicDirectory([FromQuery]Request request, string id)
|
|
|
|
|
{
|
|
|
|
|
var result = await this.SubsonicService.GetMusicDirectory(request, null, id);
|
|
|
|
|
return this.BuildResponse(request, result.Data, "directory");
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-21 06:34:53 +00:00
|
|
|
|
[HttpGet("getMusicFolders.view")]
|
|
|
|
|
[HttpPost("getMusicFolders.view")]
|
|
|
|
|
[ProducesResponseType(200)]
|
|
|
|
|
public async Task<IActionResult> GetMusicFolders([FromQuery]Request request)
|
|
|
|
|
{
|
|
|
|
|
var result = await this.SubsonicService.GetMusicFolders(request);
|
|
|
|
|
return this.BuildResponse(request, result.Data, "musicFolders");
|
|
|
|
|
}
|
2018-11-20 04:47:12 +00:00
|
|
|
|
|
2018-11-19 23:51:58 +00:00
|
|
|
|
[HttpGet("getPlaylists.view")]
|
2018-11-21 06:34:53 +00:00
|
|
|
|
[HttpPost("getPlaylists.view")]
|
2018-11-19 23:51:58 +00:00
|
|
|
|
[ProducesResponseType(200)]
|
|
|
|
|
public async Task<IActionResult> GetPlaylists([FromQuery]Request request, string username)
|
|
|
|
|
{
|
|
|
|
|
var result = await this.SubsonicService.GetPlaylists(request, null, username);
|
|
|
|
|
return this.BuildResponse(request, result.Data, "playlists");
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-20 04:47:12 +00:00
|
|
|
|
[HttpGet("getPodcasts.view")]
|
2018-11-21 06:34:53 +00:00
|
|
|
|
[HttpPost("getPodcasts.view")]
|
2018-11-20 04:47:12 +00:00
|
|
|
|
[ProducesResponseType(200)]
|
|
|
|
|
public async Task<IActionResult> GetPodcasts([FromQuery]Request request, bool includeEpisodes)
|
|
|
|
|
{
|
|
|
|
|
var result = await this.SubsonicService.GetPodcasts(request);
|
|
|
|
|
return this.BuildResponse(request, result.Data, "podcasts");
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-21 06:34:53 +00:00
|
|
|
|
[HttpGet("ping.view")]
|
|
|
|
|
[HttpPost("ping.view")]
|
2018-11-20 14:36:07 +00:00
|
|
|
|
[ProducesResponseType(200)]
|
2018-11-21 06:34:53 +00:00
|
|
|
|
public IActionResult Ping([FromQuery]Request request)
|
2018-11-20 14:36:07 +00:00
|
|
|
|
{
|
2018-11-21 06:34:53 +00:00
|
|
|
|
if(request.IsJSONRequest)
|
2018-11-20 14:36:07 +00:00
|
|
|
|
{
|
2018-11-21 06:34:53 +00:00
|
|
|
|
var result = this.SubsonicService.Ping(request);
|
|
|
|
|
return this.BuildResponse(request, result.Data);
|
2018-11-20 14:36:07 +00:00
|
|
|
|
}
|
2018-11-21 06:34:53 +00:00
|
|
|
|
return Content("<subsonic-response xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://subsonic.org/restapi\" status=\"ok\" version=\"1.16.0\" />", "application/xml");
|
2018-11-20 14:36:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-21 06:34:53 +00:00
|
|
|
|
[HttpGet("getLicense.view")]
|
|
|
|
|
[HttpPost("getLicense.view")]
|
2018-11-20 04:47:12 +00:00
|
|
|
|
[ProducesResponseType(200)]
|
2018-11-21 06:34:53 +00:00
|
|
|
|
public IActionResult GetLicense([FromQuery]Request request)
|
2018-11-20 04:47:12 +00:00
|
|
|
|
{
|
2018-11-21 06:34:53 +00:00
|
|
|
|
var result = this.SubsonicService.GetLicense(request);
|
|
|
|
|
return this.BuildResponse(request, result.Data, "license");
|
2018-11-20 04:47:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-11-21 06:34:53 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns albums, artists and songs matching the given search criteria. Supports paging through the result.
|
|
|
|
|
/// </summary>
|
|
|
|
|
[HttpGet("search2.view")]
|
|
|
|
|
[HttpPost("search2.view")]
|
|
|
|
|
[ProducesResponseType(200)]
|
|
|
|
|
public async Task<IActionResult> Search2([FromQuery]Request request)
|
2018-11-19 23:51:58 +00:00
|
|
|
|
{
|
2018-11-21 06:34:53 +00:00
|
|
|
|
var result = await this.SubsonicService.Search(request, null);
|
|
|
|
|
return this.BuildResponse(request, result.Data, "searchResult2");
|
2018-11-19 23:51:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-21 06:34:53 +00:00
|
|
|
|
[HttpGet("getAlbum.view")]
|
|
|
|
|
[HttpPost("getAlbum.view")]
|
|
|
|
|
[ProducesResponseType(200)]
|
|
|
|
|
public async Task<IActionResult> GetAlbum([FromQuery]Request request)
|
|
|
|
|
{
|
|
|
|
|
var result = await this.SubsonicService.GetAlbum(request, null);
|
|
|
|
|
return this.BuildResponse(request, result.Data, "album");
|
|
|
|
|
}
|
2018-11-20 14:36:07 +00:00
|
|
|
|
|
|
|
|
|
|
2018-11-21 06:34:53 +00:00
|
|
|
|
[HttpGet("stream.view")]
|
|
|
|
|
[HttpPost("stream.view")]
|
|
|
|
|
[ProducesResponseType(200)]
|
|
|
|
|
public async Task<FileStreamResult> StreamTrack([FromQuery]Request request)
|
|
|
|
|
{
|
|
|
|
|
var trackId = request.TrackId;
|
|
|
|
|
if (trackId == null)
|
|
|
|
|
{
|
|
|
|
|
Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
|
|
|
|
}
|
|
|
|
|
return await base.StreamTrack(trackId.Value, this.TrackService, this.PlayActivityService);
|
|
|
|
|
}
|
2018-11-20 14:36:07 +00:00
|
|
|
|
|
2018-11-19 23:51:58 +00:00
|
|
|
|
#region Response Builder Methods
|
|
|
|
|
|
|
|
|
|
private string BuildJsonResult(Response response, string responseType)
|
|
|
|
|
{
|
|
|
|
|
if (responseType == null)
|
|
|
|
|
{
|
|
|
|
|
return "{ \"subsonic-response\": { \"status\":\"" + response.status.ToString() + "\", \"version\": \"" + response.version + "\" }}";
|
|
|
|
|
}
|
|
|
|
|
return "{ \"subsonic-response\": { \"status\":\"" + response.status.ToString() + "\", \"version\": \"" + response.version + "\", \"" + responseType + "\":" + JsonConvert.SerializeObject(response.Item) + "}}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IActionResult BuildResponse(Request request, Response response = null, string reponseType = null)
|
|
|
|
|
{
|
2018-11-21 06:34:53 +00:00
|
|
|
|
var acceptHeader = this.Request.Headers["Accept"];
|
|
|
|
|
this.Logger.LogTrace($"Subsonic Request: Method [{ this.Request.Method }], Accept Header [{ acceptHeader }], Path [{ this.Request.Path }], Query String [{ this.Request.QueryString }], Request [{ JsonConvert.SerializeObject(request) }] ResponseType [{ reponseType }]");
|
2018-11-19 23:51:58 +00:00
|
|
|
|
if (request.IsJSONRequest)
|
|
|
|
|
{
|
|
|
|
|
this.Response.ContentType = "application/json";
|
|
|
|
|
return Content(this.BuildJsonResult(response, reponseType));
|
|
|
|
|
}
|
2018-11-21 06:34:53 +00:00
|
|
|
|
this.Response.ContentType = "application/xml";
|
2018-11-19 23:51:58 +00:00
|
|
|
|
return Ok(response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion Response Builder Methods
|
2018-11-15 15:10:29 +00:00
|
|
|
|
}
|
2018-11-16 03:37:00 +00:00
|
|
|
|
}
|