mirror of
https://github.com/sphildreth/roadie
synced 2024-11-25 21:50:22 +00:00
Subsonic API work
This commit is contained in:
parent
27a2dfea93
commit
40c812fdd4
17 changed files with 773 additions and 321 deletions
|
@ -68,5 +68,17 @@ namespace Roadie.Library.Tests
|
|||
Assert.NotNull(parsed);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("DEB4F298-5D22-4304-916E-F130B02864B7")]
|
||||
[InlineData("12d65c61-1b7d-4c43-9aab-7d398a1a880e")]
|
||||
[InlineData("A:8a951bc1-5ee5-4961-b72a-99d91d84c147")]
|
||||
[InlineData("R:0327eea7-b1cb-4ae9-9eb1-b74b4416aefb")]
|
||||
public void Parse_Guid(string input)
|
||||
{
|
||||
var parsed = SafeParser.ToGuid(input);
|
||||
Assert.NotNull(parsed);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ using Microsoft.Extensions.Logging;
|
|||
using Newtonsoft.Json;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Extensions;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.ThirdPartyApi.Subsonic;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -32,31 +32,103 @@ namespace Roadie.Api.Controllers
|
|||
this.PlayActivityService = playActivityService;
|
||||
}
|
||||
|
||||
[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, "album");
|
||||
}
|
||||
|
||||
[HttpGet("getAlbum.view")]
|
||||
[HttpPost("getAlbum.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetSong([FromQuery]Request request, string id)
|
||||
{
|
||||
var result = await this.SubsonicService.GetAlbum(request, null);
|
||||
return this.BuildResponse(request, result, "song");
|
||||
}
|
||||
|
||||
[HttpGet("getArtist.view")]
|
||||
[HttpPost("getArtist.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetArtist([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetArtist(request, null);
|
||||
return this.BuildResponse(request, result, "artist");
|
||||
}
|
||||
|
||||
[HttpGet("getAlbumInfo.view")]
|
||||
[HttpPost("getAlbumInfo.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetAlbumInfo([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetAlbumInfo(request, null, AlbumInfoVersion.One);
|
||||
return this.BuildResponse(request, result, "albumInfo");
|
||||
}
|
||||
|
||||
[HttpGet("getAlbumInfo2.view")]
|
||||
[HttpPost("getAlbumInfo2.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetAlbumInfo2([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetAlbumInfo(request, null, AlbumInfoVersion.Two);
|
||||
return this.BuildResponse(request, result, "albumInfo");
|
||||
}
|
||||
|
||||
[HttpGet("getVideos.view")]
|
||||
[HttpPost("getVideos.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public IActionResult GetVideos([FromQuery]Request request)
|
||||
{
|
||||
var result = this.SubsonicService.GetVideos(request);
|
||||
return this.BuildResponse(request, result, "videos");
|
||||
}
|
||||
|
||||
[HttpGet("getLyrics.view")]
|
||||
[HttpPost("getLyrics.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public IActionResult GetLyrics([FromQuery]Request request, string artist, string title)
|
||||
{
|
||||
var result = this.SubsonicService.GetLyrics(request, artist, title);
|
||||
return this.BuildResponse(request, result, "lyrics ");
|
||||
}
|
||||
|
||||
[HttpGet("getAlbumList.view")]
|
||||
[HttpPost("getAlbumList.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetAlbumList([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetAlbumList(request, null, AlbumListVersions.One);
|
||||
return this.BuildResponse(request, result.Data, "albumList");
|
||||
return this.BuildResponse(request, result, "albumList");
|
||||
}
|
||||
|
||||
[HttpGet("getRandomSongs.view")]
|
||||
[HttpPost("getRandomSongs.view")]
|
||||
[HttpGet("getAlbumList2.view")]
|
||||
[HttpPost("getAlbumList2.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetRandomSongs([FromQuery]Request request)
|
||||
public async Task<IActionResult> GetAlbumList2([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetRandomSongs(request, null);
|
||||
return this.BuildResponse(request, result.Data, "randomSongs");
|
||||
var result = await this.SubsonicService.GetAlbumList(request, null, AlbumListVersions.Two);
|
||||
return this.BuildResponse(request, result, "albumList");
|
||||
}
|
||||
|
||||
[HttpGet("getUser.view")]
|
||||
[HttpPost("getUser.view")]
|
||||
[HttpGet("getArtistInfo.view")]
|
||||
[HttpPost("getArtistInfo.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetUser([FromQuery]Request request, string username)
|
||||
public async Task<IActionResult> GetArtistInfo([FromQuery]Request request, int? count, bool? includeNotPresent)
|
||||
{
|
||||
var result = await this.SubsonicService.GetUser(request, username);
|
||||
return this.BuildResponse(request, result.Data, "user");
|
||||
var result = await this.SubsonicService.GetArtistInfo(request, count, includeNotPresent ?? false, ArtistInfoVersion.One);
|
||||
return this.BuildResponse(request, result, "artistInfo");
|
||||
}
|
||||
|
||||
[HttpGet("getArtistInfo2.view")]
|
||||
[HttpPost("getArtistInfo2.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetArtistInfo2([FromQuery]Request request, int? count, bool? includeNotPresent)
|
||||
{
|
||||
var result = await this.SubsonicService.GetArtistInfo(request, count, includeNotPresent ?? false, ArtistInfoVersion.Two);
|
||||
return this.BuildResponse(request, result, "artistInfo2");
|
||||
}
|
||||
|
||||
[HttpGet("getArtists.view")]
|
||||
|
@ -65,25 +137,7 @@ namespace Roadie.Api.Controllers
|
|||
public async Task<IActionResult> GetArtists([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetArtists(request, null);
|
||||
return this.BuildResponse(request, result.Data, "artists");
|
||||
}
|
||||
|
||||
[HttpGet("getStarred.view")]
|
||||
[HttpPost("getStarred.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetStarred([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetStarred(request, null, StarredVersion.One);
|
||||
return this.BuildResponse(request, result.Data, "starred");
|
||||
}
|
||||
|
||||
[HttpGet("getStarred2.view")]
|
||||
[HttpPost("getStarred2.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetStarred2([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetStarred(request, null, StarredVersion.Two);
|
||||
return this.BuildResponse(request, result.Data, "starred");
|
||||
return this.BuildResponse(request, result, "artists");
|
||||
}
|
||||
|
||||
[HttpGet("getAvatar.view")]
|
||||
|
@ -96,33 +150,6 @@ namespace Roadie.Api.Controllers
|
|||
return Redirect($"/images/user/{ user.RoadieId }/{this.RoadieSettings.ThumbnailImageSize.Width}/{this.RoadieSettings.ThumbnailImageSize.Height}");
|
||||
}
|
||||
|
||||
[HttpGet("getAlbumList2.view")]
|
||||
[HttpPost("getAlbumList2.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetAlbumList2([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetAlbumList(request, null, AlbumListVersions.Two);
|
||||
return this.BuildResponse(request, result.Data, "albumList");
|
||||
}
|
||||
|
||||
[HttpGet("getArtistInfo.view")]
|
||||
[HttpPost("getArtistInfo.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetArtistInfo([FromQuery]Request request, string id, int? count, bool? includeNotPresent)
|
||||
{
|
||||
var result = await this.SubsonicService.GetArtistInfo(request, id, count, includeNotPresent ?? false, ArtistInfoVersion.One);
|
||||
return this.BuildResponse(request, result.Data, "artistInfo");
|
||||
}
|
||||
|
||||
[HttpGet("getArtistInfo2.view")]
|
||||
[HttpPost("getArtistInfo2.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetArtistInfo2([FromQuery]Request request, string id, int? count, bool? includeNotPresent)
|
||||
{
|
||||
var result = await this.SubsonicService.GetArtistInfo(request, id, count, includeNotPresent ?? false, ArtistInfoVersion.Two);
|
||||
return this.BuildResponse(request, result.Data, "artistInfo2");
|
||||
}
|
||||
|
||||
[HttpGet("getCoverArt.view")]
|
||||
[HttpPost("getCoverArt.view")]
|
||||
[ProducesResponseType(200)]
|
||||
|
@ -151,74 +178,16 @@ namespace Roadie.Api.Controllers
|
|||
public async Task<IActionResult> GetGenres([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetGenres(request);
|
||||
return this.BuildResponse(request, result.Data, "genres");
|
||||
return this.BuildResponse(request, result, "genres");
|
||||
}
|
||||
|
||||
[HttpGet("getIndexes.view")]
|
||||
[HttpPost("getIndexes.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetIndexes([FromQuery]Request request, string musicFolderId = null, long? ifModifiedSince = null)
|
||||
public async Task<IActionResult> GetIndexes([FromQuery]Request request, long? ifModifiedSince = null)
|
||||
{
|
||||
var result = await this.SubsonicService.GetIndexes(request, null, musicFolderId, ifModifiedSince);
|
||||
return this.BuildResponse(request, result.Data, "indexes");
|
||||
}
|
||||
|
||||
[HttpGet("getMusicDirectory.view")]
|
||||
[HttpPost("getMusicDirectory.view")]
|
||||
[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");
|
||||
}
|
||||
|
||||
[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");
|
||||
}
|
||||
|
||||
[HttpGet("getPlaylist.view")]
|
||||
[HttpPost("getPlaylist.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetPlaylist([FromQuery]Request request, string id)
|
||||
{
|
||||
var result = await this.SubsonicService.GetPlaylist(request, null, id);
|
||||
return this.BuildResponse(request, result.Data, "playlist");
|
||||
}
|
||||
|
||||
[HttpGet("getPlaylists.view")]
|
||||
[HttpPost("getPlaylists.view")]
|
||||
[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");
|
||||
}
|
||||
|
||||
[HttpGet("getPodcasts.view")]
|
||||
[HttpPost("getPodcasts.view")]
|
||||
[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");
|
||||
}
|
||||
|
||||
[HttpGet("ping.view")]
|
||||
[HttpPost("ping.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public IActionResult Ping([FromQuery]Request request)
|
||||
{
|
||||
if(request.IsJSONRequest)
|
||||
{
|
||||
var result = this.SubsonicService.Ping(request);
|
||||
return this.BuildResponse(request, result.Data);
|
||||
}
|
||||
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");
|
||||
var result = await this.SubsonicService.GetIndexes(request, null, ifModifiedSince);
|
||||
return this.BuildResponse(request, result, "indexes");
|
||||
}
|
||||
|
||||
[HttpGet("getLicense.view")]
|
||||
|
@ -227,7 +196,101 @@ namespace Roadie.Api.Controllers
|
|||
public IActionResult GetLicense([FromQuery]Request request)
|
||||
{
|
||||
var result = this.SubsonicService.GetLicense(request);
|
||||
return this.BuildResponse(request, result.Data, "license");
|
||||
return this.BuildResponse(request, result, "license");
|
||||
}
|
||||
|
||||
[HttpGet("getMusicDirectory.view")]
|
||||
[HttpPost("getMusicDirectory.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetMusicDirectory([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetMusicDirectory(request, null);
|
||||
return this.BuildResponse(request, result, "directory");
|
||||
}
|
||||
|
||||
[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, "musicFolders");
|
||||
}
|
||||
|
||||
[HttpGet("getPlaylist.view")]
|
||||
[HttpPost("getPlaylist.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetPlaylist([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetPlaylist(request, null);
|
||||
return this.BuildResponse(request, result, "playlist");
|
||||
}
|
||||
|
||||
[HttpGet("getPlaylists.view")]
|
||||
[HttpPost("getPlaylists.view")]
|
||||
[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, "playlists");
|
||||
}
|
||||
|
||||
[HttpGet("getPodcasts.view")]
|
||||
[HttpPost("getPodcasts.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetPodcasts([FromQuery]Request request, bool includeEpisodes)
|
||||
{
|
||||
var result = await this.SubsonicService.GetPodcasts(request);
|
||||
return this.BuildResponse(request, result, "podcasts");
|
||||
}
|
||||
|
||||
[HttpGet("getRandomSongs.view")]
|
||||
[HttpPost("getRandomSongs.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetRandomSongs([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetRandomSongs(request, null);
|
||||
return this.BuildResponse(request, result, "randomSongs");
|
||||
}
|
||||
|
||||
[HttpGet("getStarred.view")]
|
||||
[HttpPost("getStarred.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetStarred([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetStarred(request, null, StarredVersion.One);
|
||||
return this.BuildResponse(request, result, "starred");
|
||||
}
|
||||
|
||||
[HttpGet("getStarred2.view")]
|
||||
[HttpPost("getStarred2.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetStarred2([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.GetStarred(request, null, StarredVersion.Two);
|
||||
return this.BuildResponse(request, result, "starred");
|
||||
}
|
||||
|
||||
[HttpGet("getUser.view")]
|
||||
[HttpPost("getUser.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> GetUser([FromQuery]Request request, string username)
|
||||
{
|
||||
var result = await this.SubsonicService.GetUser(request, username);
|
||||
return this.BuildResponse(request, result, "user");
|
||||
}
|
||||
|
||||
[HttpGet("ping.view")]
|
||||
[HttpPost("ping.view")]
|
||||
[ProducesResponseType(200)]
|
||||
public IActionResult Ping([FromQuery]Request request)
|
||||
{
|
||||
if (request.IsJSONRequest)
|
||||
{
|
||||
var result = this.SubsonicService.Ping(request);
|
||||
return this.BuildResponse(request, result);
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -239,16 +302,16 @@ namespace Roadie.Api.Controllers
|
|||
public async Task<IActionResult> Search([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.Search(request, null, SearchVersion.One);
|
||||
return this.BuildResponse(request, result.Data, "searchResult");
|
||||
return this.BuildResponse(request, result, "searchResult");
|
||||
}
|
||||
|
||||
[HttpGet("search2.view")]
|
||||
[HttpPost("search2.view")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> Search2([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.Search(request, null, SearchVersion.Two);
|
||||
return this.BuildResponse(request, result.Data, "searchResult2");
|
||||
return this.BuildResponse(request, result, "searchResult2");
|
||||
}
|
||||
|
||||
[HttpGet("search3.view")]
|
||||
|
@ -257,19 +320,9 @@ namespace Roadie.Api.Controllers
|
|||
public async Task<IActionResult> Search3([FromQuery]Request request)
|
||||
{
|
||||
var result = await this.SubsonicService.Search(request, null, SearchVersion.Three);
|
||||
return this.BuildResponse(request, result.Data, "searchResult3");
|
||||
return this.BuildResponse(request, result, "searchResult3");
|
||||
}
|
||||
|
||||
[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");
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("stream.view")]
|
||||
[HttpPost("stream.view")]
|
||||
[ProducesResponseType(200)]
|
||||
|
@ -287,21 +340,41 @@ namespace Roadie.Api.Controllers
|
|||
|
||||
private string BuildJsonResult(Response response, string responseType)
|
||||
{
|
||||
var status = response?.status.ToString();
|
||||
var version = response?.version ?? Roadie.Api.Services.SubsonicService.SubsonicVersion;
|
||||
if (responseType == null)
|
||||
{
|
||||
return "{ \"subsonic-response\": { \"status\":\"" + response.status.ToString() + "\", \"version\": \"" + response.version + "\" }}";
|
||||
return "{ \"subsonic-response\": { \"status\":\"" + status + "\", \"version\": \"" + version + "\" }}";
|
||||
}
|
||||
return "{ \"subsonic-response\": { \"status\":\"" + response.status.ToString() + "\", \"version\": \"" + response.version + "\", \"" + responseType + "\":" + JsonConvert.SerializeObject(response.Item) + "}}";
|
||||
return "{ \"subsonic-response\": { \"status\":\"" + status + "\", \"version\": \"" + version + "\", \"" + responseType + "\":" + response != null ? JsonConvert.SerializeObject(response.Item) : string.Empty + "}}";
|
||||
}
|
||||
|
||||
private IActionResult BuildResponse(Request request, Response response = null, string reponseType = null)
|
||||
private IActionResult SendError(Request request, SubsonicOperationResult<Response> response = null, string responseType = null)
|
||||
{
|
||||
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 }]");
|
||||
var version = response?.Data?.version ?? Roadie.Api.Services.SubsonicService.SubsonicVersion;
|
||||
string errorDescription = response?.ErrorCode?.DescriptionAttr();
|
||||
int? errorCode = (int?)response?.ErrorCode;
|
||||
if (request.IsJSONRequest)
|
||||
{
|
||||
this.Response.ContentType = "application/json";
|
||||
return Content(this.BuildJsonResult(response, reponseType));
|
||||
return Content("{ \"subsonic-response\": { \"status\":\"failed\", \"version\": \"" + version + "\", \"error\":{\"code\":\"" + errorCode + "\",\"message\":\"" + errorDescription + "\"}}}");
|
||||
}
|
||||
this.Response.ContentType = "application/xml";
|
||||
return Content($"<?xml version=\"1.0\" encoding=\"UTF-8\"?><subsonic-response xmlns=\"http://subsonic.org/restapi\" status=\"failed\" version=\"{ version }\"><error code=\"{ errorCode }\" message=\"{ errorDescription }\"/></subsonic-response>");
|
||||
}
|
||||
|
||||
private IActionResult BuildResponse(Request request, SubsonicOperationResult<Response> response = null, string responseType = null)
|
||||
{
|
||||
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 }], Response Error Code [{ response.ErrorCode }], Request [{ JsonConvert.SerializeObject(request) }] ResponseType [{ responseType }]");
|
||||
if (response.ErrorCode.HasValue)
|
||||
{
|
||||
return this.SendError(request, response, responseType);
|
||||
}
|
||||
if (request.IsJSONRequest)
|
||||
{
|
||||
this.Response.ContentType = "application/json";
|
||||
return Content(this.BuildJsonResult(response.Data, responseType));
|
||||
}
|
||||
this.Response.ContentType = "application/xml";
|
||||
return Ok(response);
|
||||
|
|
|
@ -312,6 +312,7 @@ namespace Roadie.Api.Services
|
|||
}
|
||||
|
||||
var result = (from a in this.DbContext.Artists
|
||||
where (request.FilterToArtistId == null || a.RoadieId == request.FilterToArtistId)
|
||||
where (request.FilterMinimumRating == null || a.Rating >= request.FilterMinimumRating.Value)
|
||||
where (request.FilterValue == "" || (a.Name.Contains(request.FilterValue) || a.SortName.Contains(request.FilterValue) || a.AlternateNames.Contains(request.FilterValue)))
|
||||
where (!request.FilterFavoriteOnly || favoriteArtistIds.Contains(a.Id))
|
||||
|
|
|
@ -1,45 +1,53 @@
|
|||
using Roadie.Library;
|
||||
using Roadie.Library.Models.ThirdPartyApi.Subsonic;
|
||||
using Roadie.Library.Models.ThirdPartyApi.Subsonic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Roadie.Api.Services
|
||||
{
|
||||
public interface ISubsonicService
|
||||
{
|
||||
Task<OperationResult<Response>> GetAlbum(Request request, Roadie.Library.Models.Users.User roadieUser);
|
||||
Task<SubsonicOperationResult<Response>> GetAlbum(Request request, Roadie.Library.Models.Users.User roadieUser);
|
||||
|
||||
Task<OperationResult<Response>> GetAlbumList(Request request, Roadie.Library.Models.Users.User roadieUser, AlbumListVersions version);
|
||||
Task<SubsonicOperationResult<Response>> GetAlbumList(Request request, Roadie.Library.Models.Users.User roadieUser, AlbumListVersions version);
|
||||
|
||||
Task<OperationResult<Response>> GetArtistInfo(Request request, string id, int? count, bool includeNotPresent, ArtistInfoVersion version);
|
||||
Task<SubsonicOperationResult<Response>> GetArtistInfo(Request request, int? count, bool includeNotPresent, ArtistInfoVersion version);
|
||||
|
||||
Task<OperationResult<Response>> GetArtists(Request request, Roadie.Library.Models.Users.User roadieUser);
|
||||
Task<SubsonicOperationResult<Response>> GetArtists(Request request, Roadie.Library.Models.Users.User roadieUser);
|
||||
|
||||
Task<FileOperationResult<Roadie.Library.Models.Image>> GetCoverArt(Request request, int? size);
|
||||
Task<SubsonicFileOperationResult<Roadie.Library.Models.Image>> GetCoverArt(Request request, int? size);
|
||||
|
||||
Task<OperationResult<Response>> GetGenres(Request request);
|
||||
Task<SubsonicOperationResult<Response>> GetGenres(Request request);
|
||||
|
||||
Task<OperationResult<Response>> GetIndexes(Request request, Roadie.Library.Models.Users.User roadieUser, string musicFolderId = null, long? ifModifiedSince = null);
|
||||
Task<SubsonicOperationResult<Response>> GetIndexes(Request request, Roadie.Library.Models.Users.User roadieUser, long? ifModifiedSince = null);
|
||||
|
||||
OperationResult<Response> GetLicense(Request request);
|
||||
SubsonicOperationResult<Response> GetLicense(Request request);
|
||||
|
||||
Task<OperationResult<Response>> GetMusicDirectory(Request request, Roadie.Library.Models.Users.User roadieUser, string id);
|
||||
SubsonicOperationResult<Response> GetLyrics(Request request, string artistId, string title);
|
||||
|
||||
Task<OperationResult<Response>> GetMusicFolders(Request request);
|
||||
Task<SubsonicOperationResult<Response>> GetMusicDirectory(Request request, Roadie.Library.Models.Users.User roadieUser);
|
||||
|
||||
Task<OperationResult<Response>> GetPlaylist(Request request, Roadie.Library.Models.Users.User roadieUser, string id);
|
||||
Task<SubsonicOperationResult<Response>> GetMusicFolders(Request request);
|
||||
|
||||
Task<OperationResult<Response>> GetPlaylists(Request request, Roadie.Library.Models.Users.User roadieUser, string filterToUserName);
|
||||
Task<SubsonicOperationResult<Response>> GetPlaylist(Request request, Roadie.Library.Models.Users.User roadieUser);
|
||||
|
||||
Task<OperationResult<Response>> GetPodcasts(Request request);
|
||||
Task<SubsonicOperationResult<Response>> GetPlaylists(Request request, Roadie.Library.Models.Users.User roadieUser, string filterToUserName);
|
||||
|
||||
Task<OperationResult<Response>> GetRandomSongs(Request request, Roadie.Library.Models.Users.User roadieUser);
|
||||
Task<SubsonicOperationResult<Response>> GetPodcasts(Request request);
|
||||
|
||||
Task<OperationResult<Response>> GetStarred(Request request, Roadie.Library.Models.Users.User roadieUser, StarredVersion version);
|
||||
Task<SubsonicOperationResult<Response>> GetRandomSongs(Request request, Roadie.Library.Models.Users.User roadieUser);
|
||||
|
||||
Task<OperationResult<Response>> GetUser(Request request, string username);
|
||||
Task<SubsonicOperationResult<Response>> GetStarred(Request request, Roadie.Library.Models.Users.User roadieUser, StarredVersion version);
|
||||
|
||||
OperationResult<Response> Ping(Request request);
|
||||
Task<SubsonicOperationResult<Response>> GetUser(Request request, string username);
|
||||
|
||||
Task<OperationResult<Response>> Search(Request request, Roadie.Library.Models.Users.User roadieUser, SearchVersion version);
|
||||
SubsonicOperationResult<Response> GetVideos(Request request);
|
||||
|
||||
SubsonicOperationResult<Response> Ping(Request request);
|
||||
|
||||
Task<SubsonicOperationResult<Response>> Search(Request request, Roadie.Library.Models.Users.User roadieUser, SearchVersion version);
|
||||
|
||||
Task<SubsonicOperationResult<Response>> GetAlbumInfo(Request request, Roadie.Library.Models.Users.User roadieUser, AlbumInfoVersion version);
|
||||
|
||||
Task<SubsonicOperationResult<Response>> GetArtist(Request request, Roadie.Library.Models.Users.User roadieUser);
|
||||
Task<SubsonicOperationResult<Response>> GetSong(Request request, Roadie.Library.Models.Users.User roadieUser);
|
||||
}
|
||||
}
|
|
@ -356,5 +356,10 @@ namespace Roadie.Api.Services
|
|||
return new Image($"{this.HttpContext.ImageBaseUrl }/{type}/{id}");
|
||||
}
|
||||
|
||||
protected string MakeLastFmUrl(string artistName, string releaseTitle)
|
||||
{
|
||||
return "http://www.last.fm/music/" + this.HttpEncoder.UrlEncode($"{ artistName }/{ releaseTitle }");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
using Mapster;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Roadie.Library;
|
||||
|
@ -64,22 +65,22 @@ namespace Roadie.Api.Services
|
|||
this.PlaylistService = playlistService;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<subsonic.Response>> GetAlbum(subsonic.Request request, User roadieUser)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetAlbum(subsonic.Request request, User roadieUser)
|
||||
{
|
||||
if (!request.ReleaseId.HasValue)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(true, $"Invalid Release [{ request.ReleaseId}]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{ request.ReleaseId}]");
|
||||
}
|
||||
var release = this.GetRelease(request.ReleaseId.Value);
|
||||
if (release == null)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(true, $"Invalid Release [{ request.ReleaseId}]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{ request.ReleaseId}]");
|
||||
}
|
||||
var pagedRequest = request.PagedRequest;
|
||||
var releaseTracks = await this.TrackService.List(roadieUser, pagedRequest, false, request.ReleaseId);
|
||||
var userRelease = roadieUser == null ? null : this.DbContext.UserReleases.FirstOrDefault(x => x.ReleaseId == release.Id && x.UserId == roadieUser.Id);
|
||||
var genre = release.Genres.FirstOrDefault();
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -113,7 +114,7 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Returns a list of random, newest, highest rated etc. albums. Similar to the album lists on the home page of the Subsonic web interface.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> GetAlbumList(subsonic.Request request, User roadieUser, subsonic.AlbumListVersions version)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetAlbumList(subsonic.Request request, User roadieUser, subsonic.AlbumListVersions version)
|
||||
{
|
||||
var releaseResult = new Library.Models.Pagination.PagedResult<ReleaseList>();
|
||||
|
||||
|
@ -136,18 +137,18 @@ namespace Roadie.Api.Services
|
|||
break;
|
||||
|
||||
default:
|
||||
return new OperationResult<subsonic.Response>($"Unknown Album List Type [{ request.Type}]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.IncompatibleServerRestProtocolVersion,$"Unknown Album List Type [{ request.Type}]");
|
||||
}
|
||||
|
||||
if (!releaseResult.IsSuccess)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(releaseResult.Message);
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(releaseResult.Message);
|
||||
}
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case subsonic.AlbumListVersions.One:
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -163,7 +164,7 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
|
||||
case subsonic.AlbumListVersions.Two:
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -179,30 +180,29 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
|
||||
default:
|
||||
return new OperationResult<subsonic.Response>($"Unknown AlbumListVersions [{ version }]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.IncompatibleServerRestProtocolVersion,$"Unknown AlbumListVersions [{ version }]");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns artist info with biography, image URLs and similar artists, using data from last.fm.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> GetArtistInfo(subsonic.Request request, string id, int? count, bool includeNotPresent, subsonic.ArtistInfoVersion version)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetArtistInfo(subsonic.Request request, int? count, bool includeNotPresent, subsonic.ArtistInfoVersion version)
|
||||
{
|
||||
var artistId = SafeParser.ToGuid(id);
|
||||
if (!artistId.HasValue)
|
||||
if (!request.ArtistId.HasValue)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(true, $"Invalid ArtistId [{ id }]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid ArtistId [{ request.id }]");
|
||||
}
|
||||
var artist = this.GetArtist(artistId.Value);
|
||||
var artist = this.GetArtist(request.ArtistId.Value);
|
||||
if (artist == null)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(true, $"Invalid ArtistId [{ id }]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid ArtistId [{ request.id }]");
|
||||
}
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case subsonic.ArtistInfoVersion.One:
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -215,7 +215,7 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
|
||||
case subsonic.ArtistInfoVersion.Two:
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -228,11 +228,11 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
|
||||
default:
|
||||
return new OperationResult<subsonic.Response>($"Unknown ArtistInfoVersion [{ version }]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.IncompatibleServerRestProtocolVersion, $"Unknown ArtistInfoVersion [{ version }]");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<OperationResult<subsonic.Response>> GetArtists(subsonic.Request request, User roadieUser)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetArtists(subsonic.Request request, User roadieUser)
|
||||
{
|
||||
var indexes = new List<subsonic.IndexID3>();
|
||||
// Indexes for Artists alphabetically
|
||||
|
@ -249,7 +249,7 @@ namespace Roadie.Api.Services
|
|||
artist = this.SubsonicArtistID3sForArtists(artistGroup)
|
||||
});
|
||||
};
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -268,10 +268,10 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Returns a cover art image.
|
||||
/// </summary>
|
||||
public async Task<FileOperationResult<Roadie.Library.Models.Image>> GetCoverArt(subsonic.Request request, int? size)
|
||||
public async Task<subsonic.SubsonicFileOperationResult<Roadie.Library.Models.Image>> GetCoverArt(subsonic.Request request, int? size)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var result = new FileOperationResult<Roadie.Library.Models.Image>
|
||||
var result = new subsonic.SubsonicFileOperationResult<Roadie.Library.Models.Image>
|
||||
{
|
||||
Data = new Roadie.Library.Models.Image()
|
||||
};
|
||||
|
@ -281,7 +281,7 @@ namespace Roadie.Api.Services
|
|||
var artistImage = await this.ImageService.ArtistImage(request.ArtistId.Value, size, size);
|
||||
if (!artistImage.IsSuccess)
|
||||
{
|
||||
return artistImage;
|
||||
return artistImage.Adapt<subsonic.SubsonicFileOperationResult<Image>>();
|
||||
}
|
||||
result.Data.Bytes = artistImage.Data.Bytes;
|
||||
}
|
||||
|
@ -290,7 +290,7 @@ namespace Roadie.Api.Services
|
|||
var trackimage = await this.ImageService.TrackImage(request.TrackId.Value, size, size);
|
||||
if (!trackimage.IsSuccess)
|
||||
{
|
||||
return trackimage;
|
||||
return trackimage.Adapt<subsonic.SubsonicFileOperationResult<Image>>();
|
||||
}
|
||||
result.Data.Bytes = trackimage.Data.Bytes;
|
||||
}
|
||||
|
@ -299,7 +299,7 @@ namespace Roadie.Api.Services
|
|||
var collection = this.GetCollection(request.CollectionId.Value);
|
||||
if (collection == null)
|
||||
{
|
||||
return new FileOperationResult<Roadie.Library.Models.Image>(true, $"Invalid CollectionId [{ request.CollectionId}]");
|
||||
return new subsonic.SubsonicFileOperationResult<Roadie.Library.Models.Image>(true, $"Invalid CollectionId [{ request.CollectionId}]");
|
||||
}
|
||||
result.Data.Bytes = collection.Thumbnail;
|
||||
}
|
||||
|
@ -308,7 +308,7 @@ namespace Roadie.Api.Services
|
|||
var release = this.GetRelease(request.ReleaseId.Value);
|
||||
if (release == null)
|
||||
{
|
||||
return new FileOperationResult<Roadie.Library.Models.Image>(true, $"Invalid ReleaseId [{ request.ReleaseId}]");
|
||||
return new subsonic.SubsonicFileOperationResult<Roadie.Library.Models.Image>(true, $"Invalid ReleaseId [{ request.ReleaseId}]");
|
||||
}
|
||||
result.Data.Bytes = release.Thumbnail;
|
||||
}
|
||||
|
@ -317,7 +317,7 @@ namespace Roadie.Api.Services
|
|||
var playlist = this.GetPlaylist(request.PlaylistId.Value);
|
||||
if (playlist == null)
|
||||
{
|
||||
return new FileOperationResult<Roadie.Library.Models.Image>(true, $"Invalid PlaylistId [{ request.PlaylistId}]");
|
||||
return new subsonic.SubsonicFileOperationResult<Roadie.Library.Models.Image>(true, $"Invalid PlaylistId [{ request.PlaylistId}]");
|
||||
}
|
||||
result.Data.Bytes = playlist.Thumbnail;
|
||||
}
|
||||
|
@ -326,7 +326,7 @@ namespace Roadie.Api.Services
|
|||
var user = this.GetUser(request.u);
|
||||
if (user == null)
|
||||
{
|
||||
return new FileOperationResult<Roadie.Library.Models.Image>(true, $"Invalid Username [{ request.u}]");
|
||||
return new subsonic.SubsonicFileOperationResult<Roadie.Library.Models.Image>(true, $"Invalid Username [{ request.u}]");
|
||||
}
|
||||
result.Data.Bytes = user.Avatar;
|
||||
}
|
||||
|
@ -339,7 +339,7 @@ namespace Roadie.Api.Services
|
|||
}
|
||||
result.IsSuccess = result.Data.Bytes != null;
|
||||
sw.Stop();
|
||||
return new FileOperationResult<Roadie.Library.Models.Image>(result.Messages)
|
||||
return new subsonic.SubsonicFileOperationResult<Roadie.Library.Models.Image>(result.Messages)
|
||||
{
|
||||
Data = result.Data,
|
||||
ETag = result.ETag,
|
||||
|
@ -354,7 +354,7 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Returns all genres
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> GetGenres(subsonic.Request request)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetGenres(subsonic.Request request)
|
||||
{
|
||||
var genres = (from g in this.DbContext.Genres
|
||||
let albumCount = (from rg in this.DbContext.ReleaseGenres
|
||||
|
@ -372,7 +372,7 @@ namespace Roadie.Api.Services
|
|||
value = g.Name
|
||||
}).OrderBy(x => x.value).ToArray();
|
||||
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -394,10 +394,10 @@ namespace Roadie.Api.Services
|
|||
/// <param name="request">Query from application.</param>
|
||||
/// <param name="musicFolderId">If specified, only return artists in the music folder with the given ID.</param>
|
||||
/// <param name="ifModifiedSince">If specified, only return a result if the artist collection has changed since the given time (in milliseconds since 1 Jan 1970).</param>
|
||||
public async Task<OperationResult<subsonic.Response>> GetIndexes(subsonic.Request request, User roadieUser, string musicFolderId = null, long? ifModifiedSince = null)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetIndexes(subsonic.Request request, User roadieUser, long? ifModifiedSince = null)
|
||||
{
|
||||
var modifiedSinceFilter = ifModifiedSince.HasValue ? (DateTime?)ifModifiedSince.Value.FromUnixTime() : null;
|
||||
subsonic.MusicFolder musicFolderFilter = string.IsNullOrEmpty(musicFolderId) ? new subsonic.MusicFolder() : this.MusicFolders().FirstOrDefault(x => x.id == SafeParser.ToNumber<int>(musicFolderId));
|
||||
subsonic.MusicFolder musicFolderFilter = !request.MusicFolderId.HasValue ? new subsonic.MusicFolder() : this.MusicFolders().FirstOrDefault(x => x.id == request.MusicFolderId.Value);
|
||||
var indexes = new List<subsonic.Index>();
|
||||
|
||||
if (musicFolderFilter.id == this.CollectionMusicFolder().id)
|
||||
|
@ -443,7 +443,7 @@ namespace Roadie.Api.Services
|
|||
});
|
||||
};
|
||||
}
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -462,9 +462,9 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Get details about the software license. Takes no extra parameters. Roadies gives everyone a premium 1 year license everytime they ask :)
|
||||
/// </summary>
|
||||
public OperationResult<subsonic.Response> GetLicense(subsonic.Request request)
|
||||
public subsonic.SubsonicOperationResult<subsonic.Response> GetLicense(subsonic.Request request)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -489,7 +489,7 @@ namespace Roadie.Api.Services
|
|||
/// <param name="request">Query from application.</param>
|
||||
/// <param name="id">A string which uniquely identifies the music folder. Obtained by calls to getIndexes or getMusicDirectory.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<OperationResult<subsonic.Response>> GetMusicDirectory(subsonic.Request request, User roadieUser, string id)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetMusicDirectory(subsonic.Request request, User roadieUser)
|
||||
{
|
||||
var directory = new subsonic.Directory();
|
||||
var user = this.GetUser(roadieUser?.UserId);
|
||||
|
@ -500,7 +500,7 @@ namespace Roadie.Api.Services
|
|||
var artist = this.GetArtist(request.ArtistId.Value);
|
||||
if (artist == null)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(true, $"Invalid ArtistId [{ request.ArtistId}]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid ArtistId [{ request.ArtistId}]");
|
||||
}
|
||||
directory.id = subsonic.Request.ArtistIdIdentifier + artist.RoadieId.ToString();
|
||||
directory.name = artist.Name;
|
||||
|
@ -521,7 +521,7 @@ namespace Roadie.Api.Services
|
|||
var collection = this.GetCollection(request.CollectionId.Value);
|
||||
if (collection == null)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(true, $"Invalid CollectionId [{ request.CollectionId}]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid CollectionId [{ request.CollectionId}]");
|
||||
}
|
||||
directory.id = subsonic.Request.CollectionIdentifier + collection.RoadieId.ToString();
|
||||
directory.name = collection.Name;
|
||||
|
@ -536,7 +536,7 @@ namespace Roadie.Api.Services
|
|||
var release = this.GetRelease(request.ReleaseId.Value);
|
||||
if (release == null)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(true, $"Invalid ReleaseId [{ request.ReleaseId}]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid ReleaseId [{ request.ReleaseId}]");
|
||||
}
|
||||
directory.id = subsonic.Request.ReleaseIdIdentifier + release.RoadieId.ToString();
|
||||
directory.name = release.Title;
|
||||
|
@ -555,9 +555,9 @@ namespace Roadie.Api.Services
|
|||
}
|
||||
else
|
||||
{
|
||||
return new OperationResult<subsonic.Response>($"Unknown GetMusicDirectory Type [{ JsonConvert.SerializeObject(request) }], id [{ id }]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.IncompatibleServerRestProtocolVersion,$"Unknown GetMusicDirectory Type [{ JsonConvert.SerializeObject(request) }], id [{ request.id }]");
|
||||
}
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -573,9 +573,9 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Returns all configured top-level music folders. Takes no extra parameters.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> GetMusicFolders(subsonic.Request request)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetMusicFolders(subsonic.Request request)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -594,23 +594,22 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Returns a listing of files in a saved playlist.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> GetPlaylist(subsonic.Request request, User roadieUser, string id)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetPlaylist(subsonic.Request request, User roadieUser)
|
||||
{
|
||||
var playListId = SafeParser.ToGuid(id);
|
||||
if (!playListId.HasValue)
|
||||
if (!request.PlaylistId.HasValue)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(true, $"Invalid PlaylistId [{ id }]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid PlaylistId [{ request.id }]");
|
||||
}
|
||||
var pagedRequest = request.PagedRequest;
|
||||
pagedRequest.FilterToPlaylistId = playListId;
|
||||
pagedRequest.FilterToPlaylistId = request.PlaylistId.Value;
|
||||
var playlistResult = await this.PlaylistService.List(pagedRequest, roadieUser);
|
||||
var playlist = playlistResult.Rows.Any() ? playlistResult.Rows.First() : null;
|
||||
if (playlist == null)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(true, $"Invalid PlaylistId [{ id }]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid PlaylistId [{ request.id }]");
|
||||
}
|
||||
var tracksForPlaylist = await this.TrackService.List(roadieUser, pagedRequest);
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -626,7 +625,7 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Returns all playlists a user is allowed to play.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> GetPlaylists(subsonic.Request request, User roadieUser, string filterToUserName)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetPlaylists(subsonic.Request request, User roadieUser, string filterToUserName)
|
||||
{
|
||||
var playlists = (from playlist in this.DbContext.Playlists
|
||||
join u in this.DbContext.Users on playlist.UserId equals u.Id
|
||||
|
@ -654,7 +653,7 @@ namespace Roadie.Api.Services
|
|||
}
|
||||
);
|
||||
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -673,9 +672,9 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Returns all Podcast channels the server subscribes to, and (optionally) their episodes. This method can also be used to return details for only one channel - refer to the id parameter. A typical use case for this method would be to first retrieve all channels without episodes, and then retrieve all episodes for the single channel the user selects.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> GetPodcasts(subsonic.Request request)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetPodcasts(subsonic.Request request)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -691,13 +690,13 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Returns random songs matching the given criteria.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> GetRandomSongs(subsonic.Request request, User roadieUser)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetRandomSongs(subsonic.Request request, User roadieUser)
|
||||
{
|
||||
var songs = new List<subsonic.Child>();
|
||||
|
||||
var randomSongs = await this.TrackService.List(roadieUser, request.PagedRequest, true);
|
||||
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -716,7 +715,7 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Returns starred songs, albums and artists.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> GetStarred(subsonic.Request request, User roadieUser, subsonic.StarredVersion version)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetStarred(subsonic.Request request, User roadieUser, subsonic.StarredVersion version)
|
||||
{
|
||||
var pagedRequest = request.PagedRequest;
|
||||
pagedRequest.FilterFavoriteOnly = true;
|
||||
|
@ -728,7 +727,7 @@ namespace Roadie.Api.Services
|
|||
switch (version)
|
||||
{
|
||||
case subsonic.StarredVersion.One:
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -746,7 +745,7 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
|
||||
case subsonic.StarredVersion.Two:
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -764,21 +763,21 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
|
||||
default:
|
||||
return new OperationResult<subsonic.Response>($"Unknown StarredVersion [{ version }]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.IncompatibleServerRestProtocolVersion,$"Unknown StarredVersion [{ version }]");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get details about a given user, including which authorization roles and folder access it has. Can be used to enable/disable certain features in the client, such as jukebox control.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> GetUser(subsonic.Request request, string username)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetUser(subsonic.Request request, string username)
|
||||
{
|
||||
var user = this.GetUser(username);
|
||||
if (user == null)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>(true, $"Invalid Username [{ username }]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Username [{ username }]");
|
||||
}
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -794,9 +793,9 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Used to test connectivity with the server. Takes no extra parameters.
|
||||
/// </summary>
|
||||
public OperationResult<subsonic.Response> Ping(subsonic.Request request)
|
||||
public subsonic.SubsonicOperationResult<subsonic.Response> Ping(subsonic.Request request)
|
||||
{
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -810,7 +809,7 @@ namespace Roadie.Api.Services
|
|||
/// <summary>
|
||||
/// Returns albums, artists and songs matching the given search criteria. Supports paging through the result.
|
||||
/// </summary>
|
||||
public async Task<OperationResult<subsonic.Response>> Search(subsonic.Request request, User roadieUser, subsonic.SearchVersion version)
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> Search(subsonic.Request request, User roadieUser, subsonic.SearchVersion version)
|
||||
{
|
||||
var query = this.HttpEncoder.UrlDecode(request.Query).Replace("*", "").Replace("%", "").Replace(";", "");
|
||||
|
||||
|
@ -842,10 +841,10 @@ namespace Roadie.Api.Services
|
|||
switch (version)
|
||||
{
|
||||
case subsonic.SearchVersion.One:
|
||||
return new OperationResult<subsonic.Response>("Deprecated since 1.4.0, use search2 instead.");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.IncompatibleClientRestProtocolVersion,"Deprecated since 1.4.0, use search2 instead.");
|
||||
|
||||
case subsonic.SearchVersion.Two:
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -863,7 +862,7 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
|
||||
case subsonic.SearchVersion.Three:
|
||||
return new OperationResult<subsonic.Response>
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
|
@ -881,11 +880,287 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
|
||||
default:
|
||||
return new OperationResult<subsonic.Response>($"Unknown SearchVersion [{ version }]");
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.IncompatibleServerRestProtocolVersion,$"Unknown SearchVersion [{ version }]");
|
||||
}
|
||||
}
|
||||
|
||||
public subsonic.ArtistInfo2 SubsonicArtistInfo2InfoForArtist(data.Artist artist)
|
||||
/// <summary>
|
||||
/// Returns album notes, image URLs etc, using data from last.fm.
|
||||
/// </summary>
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetAlbumInfo(subsonic.Request request, User roadieUser, subsonic.AlbumInfoVersion version)
|
||||
{
|
||||
if (!request.ReleaseId.HasValue)
|
||||
{
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{ request.id }]");
|
||||
}
|
||||
var release = this.GetRelease(request.ReleaseId.Value);
|
||||
if (release == null)
|
||||
{
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{ request.id }]");
|
||||
}
|
||||
switch (version)
|
||||
{
|
||||
case subsonic.AlbumInfoVersion.One:
|
||||
case subsonic.AlbumInfoVersion.Two:
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
{
|
||||
version = SubsonicService.SubsonicVersion,
|
||||
status = subsonic.ResponseStatus.ok,
|
||||
ItemElementName = subsonic.ItemChoiceType.albumInfo,
|
||||
Item = new subsonic.AlbumInfo
|
||||
{
|
||||
largeImageUrl = this.MakeImage(release.RoadieId, "release", this.Configuration.LargeImageSize).Url,
|
||||
mediumImageUrl = this.MakeImage(release.RoadieId, "release", this.Configuration.MediumImageSize).Url,
|
||||
smallImageUrl = this.MakeImage(release.RoadieId, "release", this.Configuration.SmallImageSize).Url,
|
||||
lastFmUrl = this.MakeLastFmUrl(release.Artist.Name, release.Title),
|
||||
musicBrainzId = release.MusicBrainzId,
|
||||
notes = release.Profile
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
default:
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.IncompatibleServerRestProtocolVersion, $"Unknown Album Info Version [{ request.Type}]");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns details for an artist, including a list of albums. This method organizes music according to ID3 tags.
|
||||
/// </summary>
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetArtist(subsonic.Request request, User roadieUser)
|
||||
{
|
||||
if (!request.ArtistId.HasValue)
|
||||
{
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{ request.id }]");
|
||||
}
|
||||
var pagedRequest = request.PagedRequest;
|
||||
pagedRequest.FilterToArtistId = request.ArtistId.Value;
|
||||
var artistResult = await this.ArtistService.List(roadieUser, pagedRequest);
|
||||
var artist = artistResult.Rows.Any() ? artistResult.Rows.First() : null;
|
||||
if (artist == null)
|
||||
{
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{ request.id }]");
|
||||
}
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
{
|
||||
version = SubsonicService.SubsonicVersion,
|
||||
status = subsonic.ResponseStatus.ok,
|
||||
ItemElementName = subsonic.ItemChoiceType.artist,
|
||||
Item = this.SubsonicArtistForArtist(artist)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns details for a song.
|
||||
/// </summary>
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetSong(subsonic.Request request, User roadieUser)
|
||||
{
|
||||
if (!request.TrackId.HasValue)
|
||||
{
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track [{ request.id }]");
|
||||
}
|
||||
var pagedRequest = request.PagedRequest;
|
||||
pagedRequest.FilterToArtistId = request.TrackId.Value;
|
||||
var trackResult = await this.TrackService.List(roadieUser, pagedRequest);
|
||||
var track = trackResult.Rows.Any() ? trackResult.Rows.First() : null;
|
||||
if (track == null)
|
||||
{
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Track [{ request.id }]");
|
||||
}
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
{
|
||||
version = SubsonicService.SubsonicVersion,
|
||||
status = subsonic.ResponseStatus.ok,
|
||||
ItemElementName = subsonic.ItemChoiceType.song,
|
||||
Item = this.SubsonicChildForTrack(track)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns top songs for the given artist, using data from last.fm.
|
||||
/// </summary>
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetTopSongs(subsonic.Request request, User roadieUser, string artistId, int? count = 50)
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns songs in a given genre.
|
||||
/// </summary>
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetSongsByGenre(subsonic.Request request, User roadieUser, string genre, int? count = 10, int? offset = 0)
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all video files.
|
||||
/// </summary>
|
||||
public subsonic.SubsonicOperationResult<subsonic.Response> GetVideos(subsonic.Request request)
|
||||
{
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
{
|
||||
version = SubsonicService.SubsonicVersion,
|
||||
status = subsonic.ResponseStatus.ok,
|
||||
ItemElementName = subsonic.ItemChoiceType.videos,
|
||||
Item = new subsonic.Videos
|
||||
{
|
||||
video = new subsonic.Child[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches for and returns lyrics for a given song
|
||||
/// </summary>
|
||||
public subsonic.SubsonicOperationResult<subsonic.Response> GetLyrics(subsonic.Request request, string artistId, string title)
|
||||
{
|
||||
return new subsonic.SubsonicOperationResult<subsonic.Response>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = new subsonic.Response
|
||||
{
|
||||
version = SubsonicService.SubsonicVersion,
|
||||
status = subsonic.ResponseStatus.ok,
|
||||
ItemElementName = subsonic.ItemChoiceType.lyrics,
|
||||
Item = new subsonic.Lyrics
|
||||
{
|
||||
artist = artistId,
|
||||
title = title,
|
||||
Text = new string[0]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random collection of songs from the given artist and similar artists, using data from last.fm. Typically used for artist radio features.
|
||||
/// </summary>
|
||||
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetSimliarSongs(subsonic.Request request, User roadieUser, subsonic.SimilarSongsVersion version, int? count = 50)
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a given media file. Similar to stream, but this method returns the original media data without transcoding or downsampling.
|
||||
/// </summary>
|
||||
public async Task<subsonic.SubsonicFileOperationResult<subsonic.Response>> Download(subsonic.Request request, User roadieUser)
|
||||
{
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticate the given credentials and return the corresponding ApplicationUser
|
||||
/// </summary>
|
||||
public async Task<subsonic.SubsonicOperationResult<ApplicationUser>> Authenticate(subsonic.Request request, string username, string password)
|
||||
{
|
||||
// TODO
|
||||
|
||||
//public user CheckPasswordGetUser(ICacheManager<object> cacheManager, RoadieDbContext context)
|
||||
//{
|
||||
// user user = null;
|
||||
// if (string.IsNullOrEmpty(this.UsernameValue))
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// try
|
||||
// {
|
||||
// var cacheKey = string.Format("urn:user:byusername:{0}", this.UsernameValue.ToLower());
|
||||
// var resultInCache = cacheManager.Get<user>(cacheKey);
|
||||
// if (resultInCache == null)
|
||||
// {
|
||||
// user = context.users.FirstOrDefault(x => x.username.Equals(this.UsernameValue, StringComparison.OrdinalIgnoreCase));
|
||||
// var claims = new List<string>
|
||||
// {
|
||||
// new Claim(Library.Authentication.ClaimTypes.UserId, user.id.ToString()).ToString()
|
||||
// };
|
||||
// var sql = @"select ur.name FROM `userrole` ur LEFT JOIN usersInRoles uir on ur.id = uir.userRoleId where uir.userId = " + user.id + ";";
|
||||
// var userRoles = context.Database.SqlQuery<string>(sql).ToList();
|
||||
// if (userRoles != null && userRoles.Any())
|
||||
// {
|
||||
// foreach (var userRole in userRoles)
|
||||
// {
|
||||
// claims.Add(new Claim(Library.Authentication.ClaimTypes.UserRole, userRole).ToString());
|
||||
// }
|
||||
// }
|
||||
// user.ClaimsValue = claims;
|
||||
// cacheManager.Add(cacheKey, user);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// user = resultInCache;
|
||||
// }
|
||||
// if (user == null)
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// var password = this.Password;
|
||||
// var wasAuthenticatedAgainstPassword = false;
|
||||
// if (!string.IsNullOrEmpty(this.s))
|
||||
// {
|
||||
// var token = ModuleBase.MD5Hash((user.apiToken ?? user.email) + this.s);
|
||||
// if (!token.Equals(this.t, StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// user = null;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// wasAuthenticatedAgainstPassword = true;
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (user != null && !BCrypt.Net.BCrypt.Verify(password, user.password))
|
||||
// {
|
||||
// user = null;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// wasAuthenticatedAgainstPassword = true;
|
||||
// }
|
||||
// }
|
||||
// if (wasAuthenticatedAgainstPassword)
|
||||
// {
|
||||
// // Since API dont update LastLogin which likely invalidates any browser logins
|
||||
// user.lastApiAccess = DateTime.UtcNow;
|
||||
// context.SaveChanges();
|
||||
// }
|
||||
// return user;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Trace.WriteLine("Error CheckPassword [" + ex.Serialize() + "]");
|
||||
// }
|
||||
// return null;
|
||||
//}
|
||||
|
||||
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#region Privates
|
||||
|
||||
private subsonic.ArtistInfo2 SubsonicArtistInfo2InfoForArtist(data.Artist artist)
|
||||
{
|
||||
return new subsonic.ArtistInfo2
|
||||
{
|
||||
|
@ -898,7 +1173,7 @@ namespace Roadie.Api.Services
|
|||
};
|
||||
}
|
||||
|
||||
public subsonic.ArtistInfo SubsonicArtistInfoForArtist(data.Artist artist)
|
||||
private subsonic.ArtistInfo SubsonicArtistInfoForArtist(data.Artist artist)
|
||||
{
|
||||
return new subsonic.ArtistInfo
|
||||
{
|
||||
|
@ -909,7 +1184,7 @@ namespace Roadie.Api.Services
|
|||
similarArtist = new subsonic.Artist[0],
|
||||
smallImageUrl = this.MakeImage(artist.RoadieId, "artist", this.Configuration.SmallImageSize).Url
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private string[] AllowedUsers()
|
||||
{
|
||||
|
@ -1080,7 +1355,7 @@ namespace Roadie.Api.Services
|
|||
userRating = t.UserRating != null ? t.UserRating.Rating ?? 0 : 0,
|
||||
userRatingSpecified = t.UserRating != null,
|
||||
year = t.Year ?? 0,
|
||||
yearSpecified = t.Year.HasValue,
|
||||
yearSpecified = t.Year.HasValue,
|
||||
transcodedContentType = "audio/mpeg",
|
||||
transcodedSuffix = "mp3",
|
||||
isVideo = false,
|
||||
|
@ -1159,5 +1434,7 @@ namespace Roadie.Api.Services
|
|||
folder = this.MusicFolders().Select(x => x.id).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
#endregion Privates
|
||||
}
|
||||
}
|
|
@ -250,6 +250,7 @@ namespace Roadie.Api.Services
|
|||
from releaseArtist in aa.DefaultIfEmpty()
|
||||
where (t.Hash != null)
|
||||
where (releaseId == null || (releaseId != null && r.RoadieId == releaseId))
|
||||
where (request.FilterToTrackId == null || request.FilterToTrackId != null && t.RoadieId == request.FilterToTrackId)
|
||||
where (request.FilterToArtistId == null || request.FilterToArtistId != null && r.Artist.RoadieId == request.FilterToArtistId)
|
||||
where (request.FilterMinimumRating == null || t.Rating >= request.FilterMinimumRating.Value)
|
||||
where (request.FilterValue == "" || (t.Title.Contains(request.FilterValue) || t.AlternateNames.Contains(request.FilterValue)))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
@ -53,5 +54,12 @@ namespace Roadie.Library.Extensions
|
|||
}
|
||||
}
|
||||
|
||||
public static string DescriptionAttr<T>(this T source)
|
||||
{
|
||||
FieldInfo fi = source.GetType().GetField(source.ToString());
|
||||
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
|
||||
if (attributes != null && attributes.Length > 0) return attributes[0].Description;
|
||||
else return source.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -324,5 +324,6 @@ namespace Roadie.Library.Extensions
|
|||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -99,6 +99,7 @@ namespace Roadie.Library.Models.Pagination
|
|||
public bool? FilterOnlyMissing { get; set; }
|
||||
|
||||
public Guid? FilterToArtistId { get; set; }
|
||||
public Guid? FilterToTrackId { get; set; }
|
||||
public Guid? FilterToCollectionId { get; set; }
|
||||
public Guid? FilterToPlaylistId { get; set; }
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
||||
{
|
||||
public enum AlbumInfoVersion
|
||||
{
|
||||
One,
|
||||
Two
|
||||
}
|
||||
}
|
29
RoadieLibrary/Models/ThirdPartyApi/Subsonic/ErrorCodes.cs
Normal file
29
RoadieLibrary/Models/ThirdPartyApi/Subsonic/ErrorCodes.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
|
||||
namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
||||
{
|
||||
public enum ErrorCodes
|
||||
{
|
||||
[Description("A generic error.")]
|
||||
Generic = 0,
|
||||
[Description("Required parameter is missing.")]
|
||||
RequiredParameterMissing = 10,
|
||||
[Description("Incompatible Subsonic REST protocol version. Client must upgrade.")]
|
||||
IncompatibleClientRestProtocolVersion = 20,
|
||||
[Description("Incompatible Subsonic REST protocol version. Server must upgrade.")]
|
||||
IncompatibleServerRestProtocolVersion = 30,
|
||||
[Description("Wrong username or password.")]
|
||||
WrongUsernameOrPassword = 40,
|
||||
[Description("Token authentication not supported for LDAP users.")]
|
||||
TokenAuthenticatinNotSupportedForLDAP = 41,
|
||||
[Description("User is not authorized for the given operation.")]
|
||||
UserIsNotAuthorizedForGivenOperation = 50,
|
||||
[Description("The trial period for the Subsonic server is over. Please upgrade to Subsonic Premium. Visit subsonic.org for details.")]
|
||||
TrialPeriodSubsonicServerHasExpired = 60,
|
||||
[Description("The requested data was not found")]
|
||||
TheRequestedDataWasNotFound = 70
|
||||
}
|
||||
}
|
|
@ -24,7 +24,7 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
|||
}
|
||||
if (this.id.StartsWith(Request.ArtistIdIdentifier))
|
||||
{
|
||||
return SafeParser.ToGuid(this.id.Replace(Request.ArtistIdIdentifier, ""));
|
||||
return SafeParser.ToGuid(this.id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
|||
}
|
||||
if (this.id.StartsWith(Request.CollectionIdentifier))
|
||||
{
|
||||
return SafeParser.ToGuid(this.id.Replace(Request.CollectionIdentifier, ""));
|
||||
return SafeParser.ToGuid(this.id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
|||
}
|
||||
if (this.id.StartsWith(Request.PlaylistdIdentifier))
|
||||
{
|
||||
return SafeParser.ToGuid(this.id.Replace(Request.PlaylistdIdentifier, ""));
|
||||
return SafeParser.ToGuid(this.id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
|||
}
|
||||
if (this.id.StartsWith(Request.ReleaseIdIdentifier))
|
||||
{
|
||||
return SafeParser.ToGuid(this.id.Replace(Request.ReleaseIdIdentifier, ""));
|
||||
return SafeParser.ToGuid(this.id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -168,7 +168,7 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
|||
}
|
||||
if (this.id.StartsWith(Request.TrackIdIdentifier))
|
||||
{
|
||||
return SafeParser.ToGuid(this.id.Replace(Request.TrackIdIdentifier, ""));
|
||||
return SafeParser.ToGuid(this.id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -315,82 +315,6 @@ namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
|||
|
||||
#endregion Paging and List Related
|
||||
|
||||
//public user CheckPasswordGetUser(ICacheManager<object> cacheManager, RoadieDbContext context)
|
||||
//{
|
||||
// user user = null;
|
||||
// if (string.IsNullOrEmpty(this.UsernameValue))
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// try
|
||||
// {
|
||||
// var cacheKey = string.Format("urn:user:byusername:{0}", this.UsernameValue.ToLower());
|
||||
// var resultInCache = cacheManager.Get<user>(cacheKey);
|
||||
// if (resultInCache == null)
|
||||
// {
|
||||
// user = context.users.FirstOrDefault(x => x.username.Equals(this.UsernameValue, StringComparison.OrdinalIgnoreCase));
|
||||
// var claims = new List<string>
|
||||
// {
|
||||
// new Claim(Library.Authentication.ClaimTypes.UserId, user.id.ToString()).ToString()
|
||||
// };
|
||||
// var sql = @"select ur.name FROM `userrole` ur LEFT JOIN usersInRoles uir on ur.id = uir.userRoleId where uir.userId = " + user.id + ";";
|
||||
// var userRoles = context.Database.SqlQuery<string>(sql).ToList();
|
||||
// if (userRoles != null && userRoles.Any())
|
||||
// {
|
||||
// foreach (var userRole in userRoles)
|
||||
// {
|
||||
// claims.Add(new Claim(Library.Authentication.ClaimTypes.UserRole, userRole).ToString());
|
||||
// }
|
||||
// }
|
||||
// user.ClaimsValue = claims;
|
||||
// cacheManager.Add(cacheKey, user);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// user = resultInCache;
|
||||
// }
|
||||
// if (user == null)
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// var password = this.Password;
|
||||
// var wasAuthenticatedAgainstPassword = false;
|
||||
// if (!string.IsNullOrEmpty(this.s))
|
||||
// {
|
||||
// var token = ModuleBase.MD5Hash((user.apiToken ?? user.email) + this.s);
|
||||
// if (!token.Equals(this.t, StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// user = null;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// wasAuthenticatedAgainstPassword = true;
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (user != null && !BCrypt.Net.BCrypt.Verify(password, user.password))
|
||||
// {
|
||||
// user = null;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// wasAuthenticatedAgainstPassword = true;
|
||||
// }
|
||||
// }
|
||||
// if (wasAuthenticatedAgainstPassword)
|
||||
// {
|
||||
// // Since API dont update LastLogin which likely invalidates any browser logins
|
||||
// user.lastApiAccess = DateTime.UtcNow;
|
||||
// context.SaveChanges();
|
||||
// }
|
||||
// return user;
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Trace.WriteLine("Error CheckPassword [" + ex.Serialize() + "]");
|
||||
// }
|
||||
// return null;
|
||||
//}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
||||
{
|
||||
public enum SimilarSongsVersion
|
||||
{
|
||||
One,
|
||||
Two
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
||||
{
|
||||
public class SubsonicFileOperationResult<T> : FileOperationResult<T>
|
||||
{
|
||||
public ErrorCodes ErrorCode { get; set; }
|
||||
|
||||
public SubsonicFileOperationResult()
|
||||
{
|
||||
}
|
||||
|
||||
public SubsonicFileOperationResult(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public SubsonicFileOperationResult(bool isNotFoundResult, string message)
|
||||
: base(isNotFoundResult, message)
|
||||
{
|
||||
}
|
||||
|
||||
public SubsonicFileOperationResult(IEnumerable<string> messages = null)
|
||||
: base(messages)
|
||||
{
|
||||
}
|
||||
|
||||
public SubsonicFileOperationResult(bool isNotFoundResult, IEnumerable<string> messages = null)
|
||||
: base(isNotFoundResult, messages)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Roadie.Library.Models.ThirdPartyApi.Subsonic
|
||||
{
|
||||
[Serializable]
|
||||
public class SubsonicOperationResult<T> : OperationResult<T>
|
||||
{
|
||||
public ErrorCodes? ErrorCode { get; set; }
|
||||
|
||||
public SubsonicOperationResult(bool isNotFoundResult, IEnumerable<string> messages = null)
|
||||
: base(isNotFoundResult, messages)
|
||||
{
|
||||
}
|
||||
|
||||
public SubsonicOperationResult()
|
||||
{
|
||||
}
|
||||
|
||||
public SubsonicOperationResult(IEnumerable<string> messages = null)
|
||||
: base(messages)
|
||||
{
|
||||
}
|
||||
|
||||
public SubsonicOperationResult(string message = null)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public SubsonicOperationResult(ErrorCodes error, string message = null)
|
||||
: base(message)
|
||||
{
|
||||
this.ErrorCode = error;
|
||||
}
|
||||
|
||||
public SubsonicOperationResult(Exception error = null)
|
||||
: base(error)
|
||||
{
|
||||
}
|
||||
|
||||
public SubsonicOperationResult(string message = null, Exception error = null)
|
||||
: base(message, error)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -65,7 +65,12 @@ namespace Roadie.Library.Utility
|
|||
{
|
||||
return null;
|
||||
}
|
||||
if (!Guid.TryParse(input.ToString(), out Guid result))
|
||||
var i = input.ToString();
|
||||
if(i[1] == ':')
|
||||
{
|
||||
i = i.Substring(2, i.Length - 2);
|
||||
}
|
||||
if (!Guid.TryParse(i.ToString(), out Guid result))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue