2018-11-22 13:48:32 +00:00
using Mapster ;
using Microsoft.AspNetCore.Identity ;
2018-11-25 16:57:17 +00:00
using Microsoft.EntityFrameworkCore ;
2018-11-21 18:19:38 +00:00
using Microsoft.Extensions.Logging ;
2018-11-21 06:34:53 +00:00
using Newtonsoft.Json ;
2018-11-15 15:10:29 +00:00
using Roadie.Library.Caching ;
using Roadie.Library.Configuration ;
using Roadie.Library.Encoding ;
2019-06-30 22:14:36 +00:00
using Roadie.Library.Enums ;
2018-11-19 23:51:58 +00:00
using Roadie.Library.Extensions ;
2018-11-21 18:19:38 +00:00
using Roadie.Library.Identity ;
2018-11-21 06:34:53 +00:00
using Roadie.Library.Models ;
2019-06-30 22:14:36 +00:00
using Roadie.Library.Models.Pagination ;
using Roadie.Library.Models.Playlists ;
2018-11-20 14:36:07 +00:00
using Roadie.Library.Models.Releases ;
2018-11-19 23:51:58 +00:00
using Roadie.Library.Models.Users ;
2018-11-15 15:10:29 +00:00
using Roadie.Library.Utility ;
2018-11-19 23:51:58 +00:00
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
using System.Linq ;
using System.Threading.Tasks ;
2018-11-15 15:10:29 +00:00
using data = Roadie . Library . Data ;
2018-11-20 04:47:12 +00:00
using subsonic = Roadie . Library . Models . ThirdPartyApi . Subsonic ;
2018-11-15 15:10:29 +00:00
namespace Roadie.Api.Services
{
/// <summary>
2019-06-30 22:14:36 +00:00
/// Subsonic API emulator for Roadie. Enables Subsonic clients to work with Roadie.
/// <seealso cref="http://www.subsonic.org/pages/inc/api/schema/subsonic-rest-api-1.16.1.xsd">
/// <seealso cref="http://www.subsonic.org/pages/api.jsp#getIndexes" />
/// <seealso cref="https://www.reddit.com/r/subsonic/comments/7c2n6j/database_table_schema/" />
/// <!-- Generated the classes from the schema above using 'xsd subsonic-rest-api-1.16.1.xsd /c /f /n:Roadie.Library.Models.Subsonic' from Visual Studio Command Prompt -->
2018-11-15 15:10:29 +00:00
/// </summary>
public class SubsonicService : ServiceBase , ISubsonicService
{
2018-11-19 23:51:58 +00:00
public const string SubsonicVersion = "1.16.1" ;
2018-11-21 06:34:53 +00:00
private IArtistService ArtistService { get ; }
2019-06-30 22:14:36 +00:00
2018-11-24 17:52:15 +00:00
private IBookmarkService BookmarkService { get ; }
2019-06-30 22:14:36 +00:00
2018-11-24 16:18:03 +00:00
private ICollectionService CollectionService { get ; }
2019-06-30 22:14:36 +00:00
2018-11-21 06:34:53 +00:00
private IImageService ImageService { get ; }
2019-06-30 22:14:36 +00:00
2018-11-25 20:43:52 +00:00
private IPlayActivityService PlayActivityService { get ; }
2019-06-30 22:14:36 +00:00
2019-01-08 22:40:26 +00:00
private IPlaylistService PlaylistService { get ; }
2019-06-30 22:14:36 +00:00
2018-11-20 14:36:07 +00:00
private IReleaseService ReleaseService { get ; }
2019-06-30 22:14:36 +00:00
2018-11-21 06:34:53 +00:00
private ITrackService TrackService { get ; }
2019-06-30 22:14:36 +00:00
2018-11-21 18:19:38 +00:00
private UserManager < ApplicationUser > UserManger { get ; }
2018-11-20 14:36:07 +00:00
2018-11-15 15:10:29 +00:00
public SubsonicService ( IRoadieSettings configuration ,
2019-06-30 22:14:36 +00:00
IHttpEncoder httpEncoder ,
IHttpContext httpContext ,
data . IRoadieDbContext context ,
ICacheManager cacheManager ,
ILogger < SubsonicService > logger ,
IArtistService artistService ,
ITrackService trackService ,
ICollectionService collectionService ,
IPlaylistService playlistService ,
IReleaseService releaseService ,
IImageService imageService ,
IBookmarkService bookmarkService ,
IPlayActivityService playActivityService ,
UserManager < ApplicationUser > userManager
)
2018-11-15 15:10:29 +00:00
: base ( configuration , httpEncoder , context , cacheManager , logger , httpContext )
2018-11-16 03:37:00 +00:00
{
2019-06-30 22:14:36 +00:00
ArtistService = artistService ;
BookmarkService = bookmarkService ;
CollectionService = collectionService ;
ImageService = imageService ;
PlaylistService = playlistService ;
PlayActivityService = playActivityService ;
ReleaseService = releaseService ;
TrackService = trackService ;
UserManger = userManager ;
2018-11-21 18:19:38 +00:00
}
2019-01-08 22:40:26 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Adds a message to the chat log.
2019-01-08 22:40:26 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > AddChatMessage ( subsonic . Request request ,
User roadieUser )
2019-01-08 22:40:26 +00:00
{
if ( string . IsNullOrEmpty ( request . Message ) )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . RequiredParameterMissing , "Message is required" ) ;
2019-01-08 22:40:26 +00:00
var chatMessage = new data . ChatMessage
{
UserId = roadieUser . Id . Value ,
Message = request . Message
} ;
2019-06-30 22:14:36 +00:00
DbContext . ChatMessages . Add ( chatMessage ) ;
await DbContext . SaveChangesAsync ( ) ;
2019-01-08 22:40:26 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2019-01-08 22:40:26 +00:00
status = subsonic . ResponseStatus . ok
}
} ;
}
2018-11-22 17:31:59 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Authenticate the given credentials and return the corresponding ApplicationUser
2018-11-22 17:31:59 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . SubsonicAuthenticateResponse > > Authenticate (
subsonic . Request request )
2018-11-22 17:31:59 +00:00
{
2018-11-23 04:18:48 +00:00
if ( request = = null | | string . IsNullOrEmpty ( request ? . u ) )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . SubsonicAuthenticateResponse > (
subsonic . ErrorCodes . WrongUsernameOrPassword , "Unknown Username" ) ;
2018-11-23 04:18:48 +00:00
try
{
2019-06-30 22:14:36 +00:00
var user = DbContext . Users
. FirstOrDefault ( x = > x . UserName = = request . u ) ;
2018-11-23 04:18:48 +00:00
if ( user = = null )
{
2019-08-03 22:59:20 +00:00
Logger . LogTrace ( $"Unknown User [{request.u}]" ) ;
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . SubsonicAuthenticateResponse > (
subsonic . ErrorCodes . WrongUsernameOrPassword , "Unknown Username" ) ;
2018-11-23 04:18:48 +00:00
}
2019-06-30 22:14:36 +00:00
2018-11-23 04:18:48 +00:00
var password = request . Password ;
if ( ! string . IsNullOrEmpty ( request . s ) )
2019-01-18 23:00:44 +00:00
try
2018-11-23 04:18:48 +00:00
{
2019-07-31 22:42:49 +00:00
var token = HashHelper . CreateMD5 ( ( user . ApiToken ? ? user . Email ) + request . s ) ;
2019-06-30 22:14:36 +00:00
if ( ! token . Equals ( request . t , StringComparison . OrdinalIgnoreCase ) ) user = null ;
2018-11-23 04:18:48 +00:00
}
2019-01-18 23:00:44 +00:00
catch
2018-11-23 04:18:48 +00:00
{
}
2019-06-30 22:14:36 +00:00
2019-01-18 23:00:44 +00:00
if ( user ! = null & & ! string . IsNullOrEmpty ( user . PasswordHash ) & & ! string . IsNullOrEmpty ( password ) )
2018-11-23 15:10:33 +00:00
try
2018-11-23 04:18:48 +00:00
{
2019-06-30 22:14:36 +00:00
var hashCheck =
UserManger . PasswordHasher . VerifyHashedPassword ( user , user . PasswordHash , password ) ;
if ( hashCheck = = PasswordVerificationResult . Failed ) user = null ;
2018-11-23 04:18:48 +00:00
}
2018-11-23 15:10:33 +00:00
catch
2018-11-23 04:18:48 +00:00
{
}
2019-06-30 22:14:36 +00:00
2019-01-18 23:00:44 +00:00
if ( user ! = null )
2018-11-23 04:18:48 +00:00
{
2019-01-18 23:00:44 +00:00
var now = DateTime . UtcNow ;
user . LastUpdated = now ;
user . LastApiAccess = now ;
2019-06-30 22:14:36 +00:00
await DbContext . SaveChangesAsync ( ) ;
2018-11-23 04:18:48 +00:00
}
2019-06-30 22:14:36 +00:00
2018-11-23 04:18:48 +00:00
if ( user = = null )
{
2019-08-03 22:59:20 +00:00
Logger . LogTrace ( $"Invalid Credentials given for User [{request.u}]" ) ;
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . SubsonicAuthenticateResponse > (
subsonic . ErrorCodes . WrongUsernameOrPassword , "Unknown Username" ) ;
2018-11-23 04:18:48 +00:00
}
2019-06-30 22:14:36 +00:00
2019-08-03 22:59:20 +00:00
Logger . LogTrace (
2019-06-30 22:14:36 +00:00
$"Subsonic: Successfully Authenticated User [{user}] via Application [{request.c}], Application Version [{request.v}]" ) ;
2018-11-23 04:18:48 +00:00
return new subsonic . SubsonicOperationResult < subsonic . SubsonicAuthenticateResponse >
{
IsSuccess = true ,
Data = new subsonic . SubsonicAuthenticateResponse
{
User = user
}
} ;
2018-11-24 17:52:15 +00:00
}
2018-11-23 04:18:48 +00:00
catch ( Exception ex )
{
2019-06-30 22:14:36 +00:00
Logger . LogError ( ex ,
"Subsonic.Authenticate, Error CheckPassword [" + JsonConvert . SerializeObject ( request ) + "]" ) ;
2018-11-23 04:18:48 +00:00
}
2019-06-30 22:14:36 +00:00
2018-11-23 04:18:48 +00:00
return null ;
2018-11-22 17:31:59 +00:00
}
2018-11-25 16:57:17 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Creates or updates a bookmark (a position within a media file). Bookmarks are personal and not visible to other
/// users.
2018-11-25 16:57:17 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > CreateBookmark ( subsonic . Request request ,
User roadieUser , int position , string comment )
2018-11-25 16:57:17 +00:00
{
if ( ! request . TrackId . HasValue )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{request.id}]" ) ;
var track = GetTrack ( request . TrackId . Value ) ;
2018-11-25 16:57:17 +00:00
if ( track = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{request.TrackId.Value}]" ) ;
var userBookmark = DbContext . Bookmarks . FirstOrDefault ( x = >
x . UserId = = roadieUser . Id & & x . BookmarkTargetId = = track . Id & & x . BookmarkType = = BookmarkType . Track ) ;
2018-11-25 16:57:17 +00:00
var createdBookmark = false ;
if ( userBookmark = = null )
{
userBookmark = new data . Bookmark
{
BookmarkTargetId = track . Id ,
2019-06-30 22:14:36 +00:00
BookmarkType = BookmarkType . Track ,
2018-12-16 23:37:19 +00:00
UserId = roadieUser . Id . Value ,
2018-11-25 16:57:17 +00:00
Comment = comment ,
Position = position
} ;
2019-06-30 22:14:36 +00:00
DbContext . Bookmarks . Add ( userBookmark ) ;
2018-11-25 16:57:17 +00:00
createdBookmark = true ;
}
else
{
userBookmark . LastUpdated = DateTime . UtcNow ;
userBookmark . Position = position ;
userBookmark . Comment = comment ;
}
2019-06-30 22:14:36 +00:00
await DbContext . SaveChangesAsync ( ) ;
2018-11-25 16:57:17 +00:00
2019-06-30 22:14:36 +00:00
var user = GetUser ( roadieUser . UserId ) ;
CacheManager . ClearRegion ( user . CacheRegion ) ;
2019-08-03 22:59:20 +00:00
Logger . LogTrace (
2019-06-30 22:14:36 +00:00
$"{(createdBookmark ? " Created " : " Updated ")} Bookmark `{userBookmark}` for User `{roadieUser}`" ) ;
2018-11-25 16:57:17 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-25 16:57:17 +00:00
status = subsonic . ResponseStatus . ok
}
} ;
}
2018-11-25 20:43:52 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Creates (or updates) a playlist.
2018-11-25 20:43:52 +00:00
/// </summary>
/// <param name="request">Populated Subsonic Request</param>
/// <param name="roadieUser">Populated Roadie User</param>
/// <param name="name">The human-readable name of the playlist.</param>
/// <param name="songIds">ID of a song in the playlist. Use one songId parameter for each song in the playlist.</param>
/// <param name="playlistId">The playlist ID. (if updating else blank is adding)</param>
/// <returns></returns>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > CreatePlaylist ( subsonic . Request request ,
User roadieUser , string name , string [ ] songIds , string playlistId = null )
2018-11-25 20:43:52 +00:00
{
data . Playlist playlist = null ;
2019-06-30 22:14:36 +00:00
var songRoadieIds = new Guid ? [ 0 ] ;
var submittedTracks = new data . Track [ 0 ] . AsQueryable ( ) ;
2018-11-25 20:43:52 +00:00
if ( songIds ! = null & & songIds . Any ( ) )
{
songRoadieIds = songIds . Select ( x = > SafeParser . ToGuid ( x ) ) . ToArray ( ) ;
// Add (if not already) given tracks to Playlist
2019-06-30 22:14:36 +00:00
submittedTracks = from t in DbContext . Tracks
where songRoadieIds . Contains ( t . RoadieId )
select t ;
2018-11-25 20:43:52 +00:00
}
2019-06-30 22:14:36 +00:00
2018-11-25 20:43:52 +00:00
var didCreate = false ;
if ( ! string . IsNullOrEmpty ( playlistId ) )
{
request . id = playlistId ;
2019-06-30 22:14:36 +00:00
playlist = DbContext . Playlists . Include ( x = > x . Tracks )
. FirstOrDefault ( x = > x . RoadieId = = request . PlaylistId ) ;
2018-11-25 20:43:52 +00:00
if ( playlist = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid PlaylistId [{playlistId}]" ) ;
2018-11-25 20:43:52 +00:00
// When Create is called again on an existing delete all existing tracks and add given
if ( playlist . Tracks ! = null & & playlist . Tracks . Any ( ) )
2019-06-30 22:14:36 +00:00
DbContext . PlaylistTracks . RemoveRange ( playlist . Tracks ) ;
var listNumber = playlist . Tracks ! = null & & playlist . Tracks . Any ( )
? playlist . Tracks ? . Max ( x = > x . ListNumber ) ? ? 0
: 0 ;
2018-11-25 20:43:52 +00:00
foreach ( var submittedTrack in submittedTracks )
if ( playlist . Tracks = = null | | ! playlist . Tracks . Any ( x = > x . TrackId = = submittedTrack . Id ) )
{
listNumber + + ;
2019-06-30 22:14:36 +00:00
DbContext . PlaylistTracks . Add ( new data . PlaylistTrack
2018-11-25 20:43:52 +00:00
{
PlayListId = playlist . Id ,
ListNumber = listNumber ,
TrackId = submittedTrack . Id
} ) ;
}
2019-06-30 22:14:36 +00:00
2018-11-25 20:43:52 +00:00
playlist . Name = name ? ? playlist . Name ;
playlist . LastUpdated = DateTime . UtcNow ;
}
else
{
var tracks = new List < data . PlaylistTrack > ( ) ;
var listNumber = 0 ;
foreach ( var submittedTrack in submittedTracks )
{
listNumber + + ;
tracks . Add ( new data . PlaylistTrack
{
PlayListId = playlist . Id ,
ListNumber = listNumber ,
TrackId = submittedTrack . Id
} ) ;
}
playlist = new data . Playlist
{
IsPublic = false ,
Name = name ,
UserId = roadieUser . Id ,
Tracks = tracks
} ;
didCreate = true ;
2019-06-30 22:14:36 +00:00
DbContext . Playlists . Add ( playlist ) ;
2018-11-25 20:43:52 +00:00
}
2019-06-30 22:14:36 +00:00
await DbContext . SaveChangesAsync ( ) ;
2019-08-03 22:59:20 +00:00
Logger . LogTrace (
2019-06-30 22:14:36 +00:00
$"Subsonic: User `{roadieUser}` {(didCreate ? " created " : " modified ")} Playlist `{playlist}` added [{songRoadieIds.Count()}] Tracks." ) ;
request . id = subsonic . Request . PlaylistdIdentifier + playlist . RoadieId ;
return await GetPlaylist ( request , roadieUser ) ;
2018-11-25 20:43:52 +00:00
}
2018-11-25 16:57:17 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Deletes the bookmark for a given file.
2018-11-25 16:57:17 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > DeleteBookmark ( subsonic . Request request ,
User roadieUser )
2018-11-25 16:57:17 +00:00
{
if ( ! request . TrackId . HasValue )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{request.id}]" ) ;
var track = GetTrack ( request . TrackId . Value ) ;
2018-11-25 16:57:17 +00:00
if ( track = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{request.TrackId.Value}]" ) ;
var userBookmark = DbContext . Bookmarks . FirstOrDefault ( x = >
x . UserId = = roadieUser . Id & & x . BookmarkTargetId = = track . Id & & x . BookmarkType = = BookmarkType . Track ) ;
2018-11-25 16:57:17 +00:00
if ( userBookmark ! = null )
{
2019-06-30 22:14:36 +00:00
DbContext . Bookmarks . Remove ( userBookmark ) ;
await DbContext . SaveChangesAsync ( ) ;
2018-11-25 16:57:17 +00:00
2019-06-30 22:14:36 +00:00
var user = GetUser ( roadieUser . UserId ) ;
CacheManager . ClearRegion ( user . CacheRegion ) ;
2018-11-25 16:57:17 +00:00
2019-08-03 22:59:20 +00:00
Logger . LogTrace ( $"Subsonic: Deleted Bookmark `{userBookmark}` for User `{roadieUser}`" ) ;
2018-11-25 16:57:17 +00:00
}
2019-06-30 22:14:36 +00:00
2018-11-25 16:57:17 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-25 16:57:17 +00:00
status = subsonic . ResponseStatus . ok
}
} ;
}
2018-11-25 20:43:52 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Deletes a saved playlist.
2018-11-25 20:43:52 +00:00
/// </summary>
2019-08-22 20:33:07 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > DeletePlaylist ( subsonic . Request request , User roadieUser )
2018-11-25 20:43:52 +00:00
{
2019-08-22 20:33:07 +00:00
//request.PlaylistId.Value
2018-11-25 20:43:52 +00:00
2019-08-03 22:59:20 +00:00
var deleteResult = await PlaylistService . DeletePlaylist ( roadieUser , request . PlaylistId . Value ) ;
if ( deleteResult = = null | | deleteResult . IsNotFoundResult )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Playlist Id [{request.id}]" ) ;
}
if ( ! deleteResult . IsSuccess )
{
if ( deleteResult . IsAccessDeniedResult )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . UserIsNotAuthorizedForGivenOperation , "User is not allowed to delete playlist." ) ;
}
if ( deleteResult . Messages ? . Any ( ) ? ? false )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . Generic , deleteResult . Messages . First ( ) ) ;
}
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . Generic , "An Error Occured" ) ;
}
2018-11-25 20:43:52 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-25 20:43:52 +00:00
status = subsonic . ResponseStatus . ok
}
} ;
}
2018-11-24 17:52:15 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns details for an album, including a list of songs. This method organizes music according to ID3 tags.
2018-11-24 17:52:15 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetAlbum ( subsonic . Request request ,
User roadieUser )
2018-11-21 18:19:38 +00:00
{
2018-12-28 18:58:17 +00:00
try
2018-11-21 18:19:38 +00:00
{
2018-12-28 18:58:17 +00:00
var releaseId = SafeParser . ToGuid ( request . id ) ;
if ( ! releaseId . HasValue )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{request.ReleaseId}]" ) ;
var release = GetRelease ( releaseId . Value ) ;
2018-12-28 18:58:17 +00:00
if ( release = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{request.ReleaseId}]" ) ;
2018-12-28 18:58:17 +00:00
var trackPagedRequest = request . PagedRequest ;
trackPagedRequest . Sort = "TrackNumber" ;
trackPagedRequest . Order = "ASC" ;
2019-06-30 22:14:36 +00:00
var releaseTracks = await TrackService . List ( trackPagedRequest , roadieUser , false , releaseId ) ;
var userRelease = roadieUser = = null
? null
: DbContext . UserReleases . FirstOrDefault ( x = >
x . ReleaseId = = release . Id & & x . UserId = = roadieUser . Id ) ;
2018-12-28 18:58:17 +00:00
var genre = release . Genres . FirstOrDefault ( ) ;
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
2018-11-21 18:19:38 +00:00
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-12-28 18:58:17 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . album ,
Item = new subsonic . AlbumWithSongsID3
{
artist = release . Artist . Name ,
2019-06-30 22:14:36 +00:00
artistId = subsonic . Request . ArtistIdIdentifier + release . Artist . RoadieId ,
coverArt = subsonic . Request . ReleaseIdIdentifier + release . RoadieId ,
2018-12-28 18:58:17 +00:00
created = release . CreatedDate ,
duration = release . Duration . ToSecondsFromMilliseconds ( ) ,
genre = genre = = null ? null : genre . Genre . Name ,
2019-06-30 22:14:36 +00:00
id = subsonic . Request . ReleaseIdIdentifier + release . RoadieId ,
2018-12-28 18:58:17 +00:00
name = release . Title ,
playCount = releaseTracks . Rows . Sum ( x = > x . PlayedCount ) ? ? 0 ,
playCountSpecified = releaseTracks . Rows . Any ( ) ,
songCount = releaseTracks . Rows . Count ( ) ,
starred = userRelease ? . LastUpdated ? ? userRelease ? . CreatedDate ? ? DateTime . UtcNow ,
starredSpecified = userRelease ? . IsFavorite ? ? false ,
year = release . ReleaseDate ! = null ? release . ReleaseDate . Value . Year : 0 ,
yearSpecified = release . ReleaseDate ! = null ,
2019-06-30 22:14:36 +00:00
song = SubsonicChildrenForTracks ( releaseTracks . Rows )
2018-12-28 18:58:17 +00:00
}
2018-11-21 18:19:38 +00:00
}
2018-12-28 18:58:17 +00:00
} ;
}
catch ( Exception ex )
{
2019-06-30 22:14:36 +00:00
Logger . LogError ( ex , "GetAlbum Request [{0}], User [{1}]" , JsonConvert . SerializeObject ( request ) ,
roadieUser . ToString ( ) ) ;
2018-12-28 18:58:17 +00:00
}
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{request.ReleaseId}]" ) ;
2018-11-15 15:10:29 +00:00
}
2018-11-19 23:51:58 +00:00
2018-11-22 17:31:59 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns album notes, image URLs etc, using data from last.fm.
2018-11-22 17:31:59 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetAlbumInfo ( subsonic . Request request ,
User roadieUser , subsonic . AlbumInfoVersion version )
2018-11-22 17:31:59 +00:00
{
var releaseId = SafeParser . ToGuid ( request . id ) ;
if ( ! releaseId . HasValue )
2019-06-30 22:14:36 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{request.id}]" ) ) ;
var release = GetRelease ( releaseId . Value ) ;
2018-11-22 17:31:59 +00:00
if ( release = = null )
2019-06-30 22:14:36 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{request.id}]" ) ) ;
2018-11-22 17:31:59 +00:00
switch ( version )
{
case subsonic . AlbumInfoVersion . One :
case subsonic . AlbumInfoVersion . Two :
2018-12-24 19:40:49 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-22 17:31:59 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-22 17:31:59 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . albumInfo ,
Item = new subsonic . AlbumInfo
{
2019-06-30 22:14:36 +00:00
largeImageUrl =
2019-10-23 13:45:36 +00:00
MakeImage ( Configuration , HttpContext , release . RoadieId , "release" , Configuration . LargeImageSize ) . Url ,
mediumImageUrl = MakeImage ( Configuration , HttpContext , release . RoadieId , "release" , Configuration . MediumImageSize )
2019-06-30 22:14:36 +00:00
. Url ,
smallImageUrl =
2019-10-23 13:45:36 +00:00
MakeImage ( Configuration , HttpContext , release . RoadieId , "release" , Configuration . SmallImageSize ) . Url ,
2019-06-30 22:14:36 +00:00
lastFmUrl = MakeLastFmUrl ( release . Artist . Name , release . Title ) ,
2018-11-22 17:31:59 +00:00
musicBrainzId = release . MusicBrainzId ,
notes = release . Profile
}
}
2018-12-24 19:40:49 +00:00
} ) ;
2018-11-22 17:31:59 +00:00
default :
2019-06-30 22:14:36 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion ,
$"Unknown Album Info Version [{request.Type}]" ) ) ;
2018-11-22 17:31:59 +00:00
}
}
2018-11-21 06:34:53 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns a list of random, newest, highest rated etc. albums. Similar to the album lists on the home page of the
/// Subsonic web interface.
2018-11-21 06:34:53 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetAlbumList ( subsonic . Request request ,
User roadieUser , subsonic . AlbumListVersions version )
2018-11-19 23:51:58 +00:00
{
2019-06-30 22:14:36 +00:00
var releaseResult = new PagedResult < ReleaseList > ( ) ;
2018-11-21 06:34:53 +00:00
switch ( request . Type )
{
case subsonic . ListType . Random :
2019-06-30 22:14:36 +00:00
releaseResult = await ReleaseService . List ( roadieUser , request . PagedRequest , true ) ;
2018-11-21 06:34:53 +00:00
break ;
case subsonic . ListType . Highest :
case subsonic . ListType . Recent :
case subsonic . ListType . Newest :
case subsonic . ListType . Frequent :
case subsonic . ListType . AlphabeticalByName :
case subsonic . ListType . AlphabeticalByArtist :
case subsonic . ListType . Starred :
case subsonic . ListType . ByGenre :
case subsonic . ListType . ByYear :
2019-06-30 22:14:36 +00:00
releaseResult = await ReleaseService . List ( roadieUser , request . PagedRequest ) ;
2018-11-21 06:34:53 +00:00
break ;
default :
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion ,
$"Unknown Album List Type [{request.Type}]" ) ;
2018-11-21 06:34:53 +00:00
}
if ( ! releaseResult . IsSuccess )
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( releaseResult . Message ) ;
2018-11-21 06:34:53 +00:00
2018-11-21 15:22:55 +00:00
switch ( version )
2018-11-19 23:51:58 +00:00
{
2018-11-21 15:22:55 +00:00
case subsonic . AlbumListVersions . One :
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 06:34:53 +00:00
{
2018-11-21 15:22:55 +00:00
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 15:22:55 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . albumList ,
Item = new subsonic . AlbumList
{
2019-06-30 22:14:36 +00:00
album = SubsonicChildrenForReleases ( releaseResult . Rows , null )
2018-11-21 15:22:55 +00:00
}
}
} ;
2018-11-21 18:19:38 +00:00
2018-11-21 15:22:55 +00:00
case subsonic . AlbumListVersions . Two :
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 15:22:55 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 15:22:55 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . albumList2 ,
Item = new subsonic . AlbumList2
{
2019-06-30 22:14:36 +00:00
album = SubsonicAlbumID3ForReleases ( releaseResult . Rows )
2018-11-21 15:22:55 +00:00
}
}
} ;
2018-11-21 18:19:38 +00:00
2018-11-21 15:22:55 +00:00
default :
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion ,
$"Unknown AlbumListVersions [{version}]" ) ;
2018-11-22 17:31:59 +00:00
}
}
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns details for an artist, including a list of albums. This method organizes music according to ID3 tags.
2018-11-22 17:31:59 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetArtist ( subsonic . Request request ,
User roadieUser )
2018-11-22 17:31:59 +00:00
{
var artistId = SafeParser . ToGuid ( request . id ) ;
if ( ! artistId . HasValue )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{request.id}]" ) ;
2018-11-22 17:31:59 +00:00
var pagedRequest = request . PagedRequest ;
2018-11-22 23:12:57 +00:00
pagedRequest . Sort = "Id" ;
2018-11-22 17:31:59 +00:00
pagedRequest . FilterToArtistId = artistId . Value ;
2019-06-30 22:14:36 +00:00
var artistResult = await ArtistService . List ( roadieUser , pagedRequest ) ;
2018-11-22 17:31:59 +00:00
var artist = artistResult . Rows . Any ( ) ? artistResult . Rows . First ( ) : null ;
if ( artist = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{request.id}]" ) ;
var artistReleaseResult = await ReleaseService . List ( roadieUser , pagedRequest ) ;
2018-11-22 17:31:59 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-22 17:31:59 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . artist ,
2019-06-30 22:14:36 +00:00
Item = SubsonicArtistWithAlbumsID3ForArtist ( artist ,
SubsonicAlbumID3ForReleases ( artistReleaseResult . Rows ) )
2018-11-22 17:31:59 +00:00
}
} ;
2018-11-21 18:19:38 +00:00
}
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns artist info with biography, image URLs and similar artists, using data from last.fm.
2018-11-21 18:19:38 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetArtistInfo ( subsonic . Request request ,
int? count , bool includeNotPresent , subsonic . ArtistInfoVersion version )
2018-11-21 18:19:38 +00:00
{
2018-11-22 17:31:59 +00:00
var artistId = SafeParser . ToGuid ( request . id ) ;
if ( ! artistId . HasValue )
2019-06-30 22:14:36 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid ArtistId [{request.id}]" ) ) ;
var artist = GetArtist ( artistId . Value ) ;
2018-11-21 18:19:38 +00:00
if ( artist = = null )
2019-06-30 22:14:36 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid ArtistId [{request.id}]" ) ) ;
2018-11-21 15:22:55 +00:00
2018-11-21 18:19:38 +00:00
switch ( version )
{
case subsonic . ArtistInfoVersion . One :
2018-12-24 19:40:49 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 18:19:38 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 18:19:38 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . artistInfo ,
2019-06-30 22:14:36 +00:00
Item = SubsonicArtistInfoForArtist ( artist )
2018-11-21 18:19:38 +00:00
}
2018-12-24 19:40:49 +00:00
} ) ;
2018-11-21 18:19:38 +00:00
case subsonic . ArtistInfoVersion . Two :
2018-12-24 19:40:49 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 18:19:38 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 18:19:38 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . artistInfo2 ,
2019-06-30 22:14:36 +00:00
Item = SubsonicArtistInfo2InfoForArtist ( artist )
2018-11-21 18:19:38 +00:00
}
2018-12-24 19:40:49 +00:00
} ) ;
2018-11-21 18:19:38 +00:00
default :
2019-06-30 22:14:36 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion ,
$"Unknown ArtistInfoVersion [{version}]" ) ) ;
2018-11-21 18:19:38 +00:00
}
2018-11-19 23:51:58 +00:00
}
2018-11-24 17:52:15 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Similar to getIndexes, but organizes music according to ID3 tags.
2018-11-24 17:52:15 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetArtists ( subsonic . Request request ,
User roadieUser )
2018-11-21 18:19:38 +00:00
{
2019-06-30 22:14:36 +00:00
var cacheKey = $"urn:subsonic_artists:{roadieUser.UserName}" ;
return await CacheManager . GetAsync ( cacheKey ,
async ( ) = > { return await GetArtistsAction ( request , roadieUser ) ; } ,
CacheManagerBase . SystemCacheRegionUrn ) ;
2018-11-21 18:19:38 +00:00
}
2018-11-21 06:34:53 +00:00
2018-11-25 16:57:17 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns all bookmarks for this user. A bookmark is a position within a certain media file.
2018-11-25 16:57:17 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetBookmarks ( subsonic . Request request ,
User roadieUser )
2018-11-25 16:57:17 +00:00
{
var pagedRequest = request . PagedRequest ;
pagedRequest . Sort = "LastUpdated" ;
pagedRequest . Order = "DESC" ;
2019-06-30 22:14:36 +00:00
var userBookmarkResult = await BookmarkService . List ( roadieUser , pagedRequest , false , BookmarkType . Track ) ;
pagedRequest . FilterToTrackIds =
userBookmarkResult . Rows . Select ( x = > SafeParser . ToGuid ( x . Bookmark . Value ) ) . ToArray ( ) ;
var trackListResult = await TrackService . List ( pagedRequest , roadieUser ) ;
2018-11-25 16:57:17 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-25 16:57:17 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . bookmarks ,
Item = new subsonic . Bookmarks
{
2019-06-30 22:14:36 +00:00
bookmark = SubsonicBookmarksForBookmarks ( userBookmarkResult . Rows , trackListResult . Rows )
2018-11-25 16:57:17 +00:00
}
}
} ;
}
2019-01-08 22:40:26 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns the current visible (non-expired) chat messages.
2019-01-08 22:40:26 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetChatMessages ( subsonic . Request request ,
User roadieUser , long? since )
2019-01-08 22:40:26 +00:00
{
2019-06-30 22:14:36 +00:00
var messagesSince = since . HasValue ? ( DateTime ? ) since . Value . FromUnixTime ( ) : null ;
var chatMessages = ( from cm in DbContext . ChatMessages
join u in DbContext . Users on cm . UserId equals u . Id
2019-01-08 22:40:26 +00:00
where messagesSince = = null | | cm . CreatedDate > = messagesSince
2019-06-30 22:14:36 +00:00
where cm . Status ! = Statuses . Deleted
2019-01-08 22:40:26 +00:00
orderby cm . CreatedDate descending
select new subsonic . ChatMessage
{
message = cm . Message ,
username = u . UserName ,
time = cm . CreatedDate . ToUnixTime ( )
} ) . ToArray ( ) ;
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2019-01-08 22:40:26 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . chatMessages ,
Item = new subsonic . ChatMessages
{
chatMessage = chatMessages . ToArray ( )
}
}
} ) ;
}
2018-11-19 23:51:58 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns a cover art image.
2018-11-19 23:51:58 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicFileOperationResult < Image > > GetCoverArt ( subsonic . Request request , int? size )
2018-11-21 06:34:53 +00:00
{
var sw = Stopwatch . StartNew ( ) ;
2019-06-30 22:14:36 +00:00
var result = new subsonic . SubsonicFileOperationResult < Image >
2018-11-21 06:34:53 +00:00
{
2019-06-30 22:14:36 +00:00
Data = new Image ( )
2018-11-21 06:34:53 +00:00
} ;
if ( request . ArtistId ! = null )
{
2019-06-30 22:14:36 +00:00
var artistImage = await ImageService . ArtistImage ( request . ArtistId . Value , size , size ) ;
if ( ! artistImage . IsSuccess ) return artistImage . Adapt < subsonic . SubsonicFileOperationResult < Image > > ( ) ;
2018-11-21 06:34:53 +00:00
result . Data . Bytes = artistImage . Data . Bytes ;
}
else if ( request . TrackId ! = null )
{
2019-06-30 22:14:36 +00:00
var trackimage = await ImageService . TrackImage ( request . TrackId . Value , size , size ) ;
if ( ! trackimage . IsSuccess ) return trackimage . Adapt < subsonic . SubsonicFileOperationResult < Image > > ( ) ;
2018-11-21 06:34:53 +00:00
result . Data . Bytes = trackimage . Data . Bytes ;
}
else if ( request . CollectionId ! = null )
{
2019-06-30 22:14:36 +00:00
var collectionImage = await ImageService . CollectionImage ( request . CollectionId . Value , size , size ) ;
2018-11-22 17:31:59 +00:00
if ( ! collectionImage . IsSuccess )
return collectionImage . Adapt < subsonic . SubsonicFileOperationResult < Image > > ( ) ;
result . Data . Bytes = collectionImage . Data . Bytes ;
2018-11-21 06:34:53 +00:00
}
else if ( request . ReleaseId ! = null )
{
2019-06-30 22:14:36 +00:00
var releaseimage = await ImageService . ReleaseImage ( request . ReleaseId . Value , size , size ) ;
if ( ! releaseimage . IsSuccess ) return releaseimage . Adapt < subsonic . SubsonicFileOperationResult < Image > > ( ) ;
2018-11-22 17:31:59 +00:00
result . Data . Bytes = releaseimage . Data . Bytes ;
2018-11-21 06:34:53 +00:00
}
else if ( request . PlaylistId ! = null )
{
2019-06-30 22:14:36 +00:00
var playlistImage = await ImageService . PlaylistImage ( request . PlaylistId . Value , size , size ) ;
if ( ! playlistImage . IsSuccess ) return playlistImage . Adapt < subsonic . SubsonicFileOperationResult < Image > > ( ) ;
2018-11-22 17:31:59 +00:00
result . Data . Bytes = playlistImage . Data . Bytes ;
2018-11-21 06:34:53 +00:00
}
else if ( ! string . IsNullOrEmpty ( request . u ) )
{
2019-06-30 22:14:36 +00:00
var user = GetUser ( request . u ) ;
2018-11-21 06:34:53 +00:00
if ( user = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicFileOperationResult < Image > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Username [{request.u}]" ) ;
var userImage = await ImageService . UserImage ( user . RoadieId , size , size ) ;
if ( ! userImage . IsSuccess ) return userImage . Adapt < subsonic . SubsonicFileOperationResult < Image > > ( ) ;
2018-11-22 17:31:59 +00:00
result . Data . Bytes = userImage . Data . Bytes ;
2018-11-21 06:34:53 +00:00
}
2019-06-30 22:14:36 +00:00
2018-11-21 06:34:53 +00:00
result . IsSuccess = result . Data . Bytes ! = null ;
sw . Stop ( ) ;
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicFileOperationResult < Image > ( result . Messages )
2018-11-21 06:34:53 +00:00
{
Data = result . Data ,
ETag = result . ETag ,
LastModified = result . LastModified ,
ContentType = "image/jpeg" ,
Errors = result ? . Errors ,
IsSuccess = result . IsSuccess ,
OperationTime = sw . ElapsedMilliseconds
} ;
}
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns all genres
2018-11-21 06:34:53 +00:00
/// </summary>
2018-12-24 19:40:49 +00:00
public Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetGenres ( subsonic . Request request )
2018-11-19 23:51:58 +00:00
{
2019-06-30 22:14:36 +00:00
var genres = ( from g in DbContext . Genres
let albumCount = ( from rg in DbContext . ReleaseGenres
2018-11-21 06:34:53 +00:00
where rg . GenreId = = g . Id
select rg . Id ) . Count ( )
2019-06-30 22:14:36 +00:00
let songCount = ( from rg in DbContext . ReleaseGenres
join rm in DbContext . ReleaseMedias on rg . ReleaseId equals rm . ReleaseId
join t in DbContext . Tracks on rm . ReleaseId equals t . ReleaseMediaId
2018-11-21 06:34:53 +00:00
where rg . GenreId = = g . Id
select t . Id ) . Count ( )
select new subsonic . Genre
{
songCount = songCount ,
albumCount = albumCount ,
value = g . Name
} ) . OrderBy ( x = > x . value ) . ToArray ( ) ;
2018-12-24 19:40:49 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-19 23:51:58 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-19 23:51:58 +00:00
status = subsonic . ResponseStatus . ok ,
2018-11-21 06:34:53 +00:00
ItemElementName = subsonic . ItemChoiceType . genres ,
Item = new subsonic . Genres
2018-11-19 23:51:58 +00:00
{
2018-11-21 06:34:53 +00:00
genre = genres . ToArray ( )
2018-11-19 23:51:58 +00:00
}
}
2018-12-24 19:40:49 +00:00
} ) ;
2018-11-19 23:51:58 +00:00
}
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns an indexed structure of all artists.
2018-11-19 23:51:58 +00:00
/// </summary>
/// <param name="request">Query from application.</param>
/// <param name="musicFolderId">If specified, only return artists in the music folder with the given ID.</param>
2019-06-30 22:14:36 +00:00
/// <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 < subsonic . SubsonicOperationResult < subsonic . Response > > GetIndexes ( subsonic . Request request ,
User roadieUser , long? ifModifiedSince = null )
2018-11-22 23:12:57 +00:00
{
2019-06-30 22:14:36 +00:00
var cacheKey = "urn:subsonic_indexes" ;
return await CacheManager . GetAsync ( cacheKey , async ( ) = >
2018-11-22 23:12:57 +00:00
{
2018-11-25 16:57:17 +00:00
// Dont send the user to get index list as user data (likes, dislikes, etc.) aren't used in this list and dont need performance hit
2019-06-30 22:14:36 +00:00
return await GetIndexesAction ( request , null , ifModifiedSince ) ;
2018-11-22 23:12:57 +00:00
} , CacheManagerBase . SystemCacheRegionUrn ) ;
}
2018-11-21 18:19:38 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Get details about the software license. Takes no extra parameters. Roadies gives everyone a premium 1 year license
/// everytime they ask :)
2018-11-21 18:19:38 +00:00
/// </summary>
2018-11-22 13:48:32 +00:00
public subsonic . SubsonicOperationResult < subsonic . Response > GetLicense ( subsonic . Request request )
2018-11-21 18:19:38 +00:00
{
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 18:19:38 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 18:19:38 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . license ,
Item = new subsonic . License
{
2019-06-30 22:14:36 +00:00
email = Configuration . SmtpFromAddress ,
2018-11-21 18:19:38 +00:00
valid = true ,
licenseExpires = DateTime . UtcNow . AddYears ( 1 ) ,
licenseExpiresSpecified = true
}
}
} ;
}
2018-11-22 17:31:59 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Searches for and returns lyrics for a given song
2018-11-22 17:31:59 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public subsonic . SubsonicOperationResult < subsonic . Response > GetLyrics ( subsonic . Request request , string artistId ,
string title )
2018-11-22 17:31:59 +00:00
{
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-22 17:31:59 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . lyrics ,
Item = new subsonic . Lyrics
{
artist = artistId ,
title = title ,
Text = new string [ 0 ]
}
}
} ;
}
2018-11-21 06:34:53 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns a listing of all files in a music directory. Typically used to get list of albums for an artist, or list of
/// songs for an album.
2018-11-21 06:34:53 +00:00
/// </summary>
/// <param name="request">Query from application.</param>
2019-06-30 22:14:36 +00:00
/// <param name="id">
/// A string which uniquely identifies the music folder. Obtained by calls to getIndexes or
/// getMusicDirectory.
/// </param>
2018-11-21 06:34:53 +00:00
/// <returns></returns>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetMusicDirectory (
subsonic . Request request , User roadieUser )
2018-11-21 06:34:53 +00:00
{
var directory = new subsonic . Directory ( ) ;
2019-06-30 22:14:36 +00:00
var user = GetUser ( roadieUser ? . UserId ) ;
2018-11-21 06:34:53 +00:00
// Request to get albums for an Artist
if ( request . ArtistId ! = null )
{
2018-11-22 17:31:59 +00:00
var artistId = SafeParser . ToGuid ( request . id ) ;
2019-06-30 22:14:36 +00:00
var artist = GetArtist ( artistId . Value ) ;
2018-11-21 06:34:53 +00:00
if ( artist = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid ArtistId [{request.id}]" ) ;
directory . id = subsonic . Request . ArtistIdIdentifier + artist . RoadieId ;
2018-11-21 06:34:53 +00:00
directory . name = artist . Name ;
2019-06-30 22:14:36 +00:00
var artistRating = user = = null
? null
: DbContext . UserArtists . FirstOrDefault ( x = > x . UserId = = user . Id & & x . ArtistId = = artist . Id ) ;
2018-11-21 06:34:53 +00:00
if ( artistRating ? . IsFavorite ? ? false )
{
2019-06-30 22:14:36 +00:00
directory . starred = artistRating . LastUpdated ? ? artistRating . CreatedDate ;
2018-11-21 06:34:53 +00:00
directory . starredSpecified = true ;
}
2019-06-30 22:14:36 +00:00
2018-11-21 06:34:53 +00:00
var pagedRequest = request . PagedRequest ;
pagedRequest . FilterToArtistId = artist . RoadieId ;
2018-11-22 23:12:57 +00:00
pagedRequest . Sort = "Release.Text" ;
2019-06-30 22:14:36 +00:00
var artistReleases = await ReleaseService . List ( roadieUser , pagedRequest ) ;
directory . child = SubsonicChildrenForReleases ( artistReleases . Rows ,
subsonic . Request . ArtistIdIdentifier + artist . RoadieId ) ;
2018-11-21 06:34:53 +00:00
}
// Request to get albums for in a Collection
else if ( request . CollectionId ! = null )
{
2018-11-22 17:31:59 +00:00
var collectionId = SafeParser . ToGuid ( request . id ) ;
2019-06-30 22:14:36 +00:00
var collection = GetCollection ( collectionId . Value ) ;
2018-11-21 06:34:53 +00:00
if ( collection = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid CollectionId [{request.id}]" ) ;
directory . id = subsonic . Request . CollectionIdentifier + collection . RoadieId ;
2018-11-21 06:34:53 +00:00
directory . name = collection . Name ;
var pagedRequest = request . PagedRequest ;
pagedRequest . FilterToCollectionId = collection . RoadieId ;
2019-06-30 22:14:36 +00:00
var collectionReleases = await ReleaseService . List ( roadieUser , pagedRequest ) ;
directory . child = SubsonicChildrenForReleases ( collectionReleases . Rows ,
subsonic . Request . CollectionIdentifier + collection . RoadieId ) ;
2018-11-21 06:34:53 +00:00
}
// Request to get Tracks for an Album
2018-11-21 18:19:38 +00:00
else if ( request . ReleaseId . HasValue )
2018-11-21 06:34:53 +00:00
{
2018-11-22 17:31:59 +00:00
var releaseId = SafeParser . ToGuid ( request . id ) ;
2019-06-30 22:14:36 +00:00
var release = GetRelease ( releaseId . Value ) ;
2018-11-21 06:34:53 +00:00
if ( release = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid ReleaseId [{request.id}]" ) ;
directory . id = subsonic . Request . ReleaseIdIdentifier + release . RoadieId ;
2018-11-21 06:34:53 +00:00
directory . name = release . Title ;
2019-06-30 22:14:36 +00:00
var releaseRating = user = = null
? null
: DbContext . UserReleases . FirstOrDefault ( x = > x . UserId = = user . Id & & x . ReleaseId = = release . Id ) ;
2018-11-21 06:34:53 +00:00
directory . averageRating = release . Rating ? ? 0 ;
2019-06-30 22:14:36 +00:00
directory . parent = subsonic . Request . ArtistIdIdentifier + release . Artist . RoadieId ;
2018-11-21 06:34:53 +00:00
if ( releaseRating ? . IsFavorite ? ? false )
{
directory . starred = releaseRating . LastUpdated ? ? releaseRating . CreatedDate ;
directory . starredSpecified = true ;
}
2019-06-30 22:14:36 +00:00
2018-12-09 14:33:40 +00:00
var trackPagedRequest = request . PagedRequest ;
trackPagedRequest . Sort = "TrackNumber" ;
trackPagedRequest . Order = "ASC" ;
2019-06-30 22:14:36 +00:00
var songTracks = await TrackService . List ( trackPagedRequest , roadieUser , false , release . RoadieId ) ;
directory . child = SubsonicChildrenForTracks ( songTracks . Rows ) ;
2018-11-21 06:34:53 +00:00
directory . playCount = directory . child . Select ( x = > x . playCount ) . Sum ( ) ;
}
else
{
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound ,
$"Unknown GetMusicDirectory Type [{JsonConvert.SerializeObject(request)}], id [{request.id}]" ) ;
2018-11-21 06:34:53 +00:00
}
2019-06-30 22:14:36 +00:00
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 06:34:53 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 06:34:53 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . directory ,
Item = directory
}
} ;
}
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns all configured top-level music folders. Takes no extra parameters.
2018-11-21 06:34:53 +00:00
/// </summary>
2018-12-24 19:40:49 +00:00
public Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetMusicFolders ( subsonic . Request request )
2018-11-21 06:34:53 +00:00
{
2018-12-24 19:40:49 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 06:34:53 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 06:34:53 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . musicFolders ,
Item = new subsonic . MusicFolders
{
2019-06-30 22:14:36 +00:00
musicFolder = MusicFolders ( ) . ToArray ( )
2018-11-21 06:34:53 +00:00
}
}
2018-12-24 19:40:49 +00:00
} ) ;
2018-11-21 06:34:53 +00:00
}
2019-01-08 22:40:26 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns what is currently being played by all users. Takes no extra parameters.
2019-01-08 22:40:26 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetNowPlaying ( subsonic . Request request ,
User roadieUser )
2019-01-08 22:40:26 +00:00
{
var pagedRequest = request . PagedRequest ;
pagedRequest . Sort = "PlayedDateDateTime" ;
pagedRequest . Order = "DESC" ;
2019-06-30 22:14:36 +00:00
var playActivityResult =
await PlayActivityService . List ( pagedRequest , roadieUser , DateTime . UtcNow . AddDays ( - 1 ) ) ;
2019-01-08 22:40:26 +00:00
pagedRequest . Sort = null ;
pagedRequest . Order = null ;
2019-06-30 22:14:36 +00:00
pagedRequest . FilterToTrackIds = playActivityResult . Rows . Select ( x = > SafeParser . ToGuid ( x . Track . Track . Value ) )
. Distinct ( ) . ToArray ( ) ;
var playActivityTracksResult = await TrackService . List ( pagedRequest , roadieUser ) ;
2019-01-08 22:40:26 +00:00
var playEntries = new List < subsonic . NowPlayingEntry > ( ) ;
var now = DateTime . UtcNow ;
foreach ( var row in playActivityResult . Rows )
{
2019-06-30 22:14:36 +00:00
var rowTrack =
playActivityTracksResult . Rows . FirstOrDefault ( x = > x . Track . Value = = row . Track . Track . Value ) ;
var playEntryTrackChild = SubsonicChildForTrack ( rowTrack ) ;
2019-01-08 22:40:26 +00:00
var playEntry = playEntryTrackChild . Adapt < subsonic . NowPlayingEntry > ( ) ;
playEntry . username = row . User . Text ;
playEntry . minutesAgo = ( int ) ( now - row . PlayedDateDateTime . Value ) . TotalMinutes ;
playEntry . playerId = 0 ;
playEntry . playerName = string . Empty ;
playEntries . Add ( playEntry ) ;
}
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2019-01-08 22:40:26 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . nowPlaying ,
Item = new subsonic . NowPlaying
{
entry = playEntries . ToArray ( )
}
}
} ;
}
2018-11-21 18:19:38 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns a listing of files in a saved playlist.
2018-11-21 18:19:38 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetPlaylist ( subsonic . Request request ,
User roadieUser )
2018-11-21 18:19:38 +00:00
{
2018-11-22 17:31:59 +00:00
var playListId = SafeParser . ToGuid ( request . id ) ;
if ( ! playListId . HasValue )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid PlaylistId [{request.id}]" ) ;
2018-11-21 18:19:38 +00:00
var pagedRequest = request . PagedRequest ;
2018-11-22 23:12:57 +00:00
pagedRequest . Sort = "Id" ;
2018-11-22 17:31:59 +00:00
pagedRequest . FilterToPlaylistId = playListId . Value ;
2019-06-30 22:14:36 +00:00
var playlistResult = await PlaylistService . List ( pagedRequest , roadieUser ) ;
2018-11-21 18:19:38 +00:00
var playlist = playlistResult . Rows . Any ( ) ? playlistResult . Rows . First ( ) : null ;
if ( playlist = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid PlaylistId [{request.id}]" ) ;
2018-11-24 01:46:12 +00:00
// For a playlist to show all the tracks in the playlist set the limit to the playlist size
pagedRequest . Limit = playlist . PlaylistCount ? ? pagedRequest . Limit ;
2019-06-30 22:14:36 +00:00
var tracksForPlaylist = await TrackService . List ( pagedRequest , roadieUser ) ;
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 18:19:38 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 18:19:38 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . playlist ,
2019-06-30 22:14:36 +00:00
Item = SubsonicPlaylistForPlaylist ( playlist , tracksForPlaylist . Rows )
2018-11-21 18:19:38 +00:00
}
} ;
}
2018-11-21 06:34:53 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns all playlists a user is allowed to play.
2018-11-21 06:34:53 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetPlaylists ( subsonic . Request request ,
User roadieUser , string filterToUserName )
2018-11-19 23:51:58 +00:00
{
2018-11-24 01:46:12 +00:00
var pagedRequest = request . PagedRequest ;
pagedRequest . Sort = "Playlist.Text" ;
pagedRequest . Order = "ASC" ;
2019-06-30 22:14:36 +00:00
var playlistResult = await PlaylistService . List ( pagedRequest , roadieUser ) ;
2018-11-24 01:46:12 +00:00
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-19 23:51:58 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-19 23:51:58 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . playlists ,
Item = new subsonic . Playlists
{
2019-06-30 22:14:36 +00:00
playlist = SubsonicPlaylistsForPlaylists ( playlistResult . Rows )
2018-11-19 23:51:58 +00:00
}
}
} ;
}
2018-11-21 06:34:53 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns the state of the play queue for this user (as set by savePlayQueue). This includes the tracks in the play
/// queue, the currently playing track, and the position within this track. Typically used to allow a user to move
/// between different clients/apps while retaining the same play queue (for instance when listening to an audio book).
2019-01-08 22:40:26 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetPlayQueue ( subsonic . Request request ,
User roadieUser )
2019-01-08 22:40:26 +00:00
{
2019-06-30 22:14:36 +00:00
var user = GetUser ( roadieUser . UserId ) ;
2019-01-08 22:40:26 +00:00
subsonic . PlayQueue playQue = null ;
if ( user . UserQues ! = null & & user . UserQues . Any ( ) )
{
var current = user . UserQues . FirstOrDefault ( x = > x . IsCurrent ? ? false ) ? ? user . UserQues . First ( ) ;
var pagedRequest = request . PagedRequest ;
pagedRequest . FilterToTrackIds = user . UserQues . Select ( x = > x . Track ? . RoadieId ) . ToArray ( ) ;
2019-06-30 22:14:36 +00:00
var queTracksResult = await TrackService . List ( pagedRequest , roadieUser ) ;
2019-01-08 22:40:26 +00:00
var queTrackRows = ( from tt in queTracksResult . Rows
join qt in user . UserQues on tt . DatabaseId equals qt . TrackId
orderby qt . QueSortOrder
select tt ) . ToArray ( ) ;
playQue = new subsonic . PlayQueue
{
// I didnt specify current as it appears to be a Int and it blows up several client applications changing it to a string.
// current = subsonic.Request.TrackIdIdentifier + current.Track.RoadieId.ToString(),
changedBy = user . UserName ,
changed = user . UserQues . OrderByDescending ( x = > x . CreatedDate ) . First ( ) . CreatedDate ,
position = current . Position ? ? 0 ,
positionSpecified = current . Position . HasValue ,
username = user . UserName ,
2019-06-30 22:14:36 +00:00
entry = SubsonicChildrenForTracks ( queTrackRows )
2019-01-08 22:40:26 +00:00
} ;
}
2019-06-30 22:14:36 +00:00
2019-01-08 22:40:26 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
IsEmptyResponse = playQue = = null ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2019-01-08 22:40:26 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . playQueue ,
Item = playQue
}
} ;
}
/// <summary>
2019-06-30 22:14:36 +00:00
/// 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.
2019-01-08 22:40:26 +00:00
/// </summary>
2018-12-24 19:40:49 +00:00
public Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetPodcasts ( subsonic . Request request )
2018-11-19 23:51:58 +00:00
{
2018-12-24 19:40:49 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-20 04:47:12 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-20 04:47:12 +00:00
status = subsonic . ResponseStatus . ok ,
2018-11-21 06:34:53 +00:00
ItemElementName = subsonic . ItemChoiceType . podcasts ,
2018-11-22 17:31:59 +00:00
Item = new subsonic . Podcasts
{
channel = new subsonic . PodcastChannel [ 0 ]
}
2018-11-20 04:47:12 +00:00
}
2018-12-24 19:40:49 +00:00
} ) ;
2018-11-19 23:51:58 +00:00
}
2018-11-21 06:34:53 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns random songs matching the given criteria.
2018-11-21 06:34:53 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetRandomSongs ( subsonic . Request request ,
User roadieUser )
2018-11-19 23:51:58 +00:00
{
2018-11-21 18:19:38 +00:00
var songs = new List < subsonic . Child > ( ) ;
2019-06-30 22:14:36 +00:00
var randomSongs = await TrackService . List ( request . PagedRequest , roadieUser , true ) ;
2018-11-21 18:19:38 +00:00
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-19 23:51:58 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 18:19:38 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . randomSongs ,
Item = new subsonic . Songs
{
2019-06-30 22:14:36 +00:00
song = SubsonicChildrenForTracks ( randomSongs . Rows )
2018-11-21 18:19:38 +00:00
}
2018-11-19 23:51:58 +00:00
}
} ;
}
2018-11-20 04:47:12 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns a random collection of songs from the given artist and similar artists, using data from last.fm. Typically
/// used for artist radio features.
2018-11-20 04:47:12 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetSimliarSongs ( subsonic . Request request ,
User roadieUser , subsonic . SimilarSongsVersion version , int? count = 50 )
2018-11-20 04:47:12 +00:00
{
2019-07-01 23:11:10 +00:00
// TODO How to determine similar songs? Perhaps by genre?
2018-11-20 04:47:12 +00:00
2018-11-21 18:19:38 +00:00
switch ( version )
2018-11-20 04:47:12 +00:00
{
2018-11-22 17:31:59 +00:00
case subsonic . SimilarSongsVersion . One :
2018-12-24 19:40:49 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 06:34:53 +00:00
{
2018-11-21 18:19:38 +00:00
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 18:19:38 +00:00
status = subsonic . ResponseStatus . ok ,
2018-11-22 17:31:59 +00:00
ItemElementName = subsonic . ItemChoiceType . similarSongs ,
Item = new subsonic . SimilarSongs
2018-11-21 18:19:38 +00:00
{
2018-11-22 17:31:59 +00:00
song = new subsonic . Child [ 0 ]
2018-11-21 18:19:38 +00:00
}
}
2018-12-24 19:40:49 +00:00
} ) ;
2018-11-24 17:52:15 +00:00
2018-11-22 17:31:59 +00:00
case subsonic . SimilarSongsVersion . Two :
2018-12-24 19:40:49 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 18:19:38 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 18:19:38 +00:00
status = subsonic . ResponseStatus . ok ,
2018-11-22 17:31:59 +00:00
ItemElementName = subsonic . ItemChoiceType . similarSongs2 ,
Item = new subsonic . SimilarSongs2
2018-11-21 18:19:38 +00:00
{
2018-11-22 17:31:59 +00:00
song = new subsonic . Child [ 0 ]
2018-11-21 18:19:38 +00:00
}
}
2018-12-24 19:40:49 +00:00
} ) ;
2018-11-21 18:19:38 +00:00
default :
2019-06-30 22:14:36 +00:00
return Task . FromResult ( new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion ,
$"Unknown SimilarSongsVersion [{version}]" ) ) ;
2018-11-21 18:19:38 +00:00
}
2018-11-20 04:47:12 +00:00
}
2018-11-21 18:19:38 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns details for a song.
2018-11-21 18:19:38 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetSong ( subsonic . Request request ,
User roadieUser )
2018-11-20 04:47:12 +00:00
{
2018-11-22 17:31:59 +00:00
if ( ! request . TrackId . HasValue )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track [{request.id}]" ) ;
2018-11-22 17:31:59 +00:00
var pagedRequest = request . PagedRequest ;
pagedRequest . FilterToTrackId = request . TrackId . Value ;
2018-11-22 23:12:57 +00:00
pagedRequest . Sort = "Id" ;
2019-06-30 22:14:36 +00:00
var trackResult = await TrackService . List ( pagedRequest , roadieUser ) ;
2018-11-22 17:31:59 +00:00
var track = trackResult . Rows . Any ( ) ? trackResult . Rows . First ( ) : null ;
if ( track = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track [{request.id}]" ) ;
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-20 04:47:12 +00:00
{
2018-11-21 06:34:53 +00:00
IsSuccess = true ,
Data = new subsonic . Response
2018-11-20 04:47:12 +00:00
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 06:34:53 +00:00
status = subsonic . ResponseStatus . ok ,
2018-11-22 17:31:59 +00:00
ItemElementName = subsonic . ItemChoiceType . song ,
2019-06-30 22:14:36 +00:00
Item = SubsonicChildForTrack ( track )
2018-11-20 04:47:12 +00:00
}
2018-11-21 06:34:53 +00:00
} ;
}
2018-11-21 15:22:55 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns songs in a given genre.
2018-11-21 15:22:55 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetSongsByGenre ( subsonic . Request request ,
User roadieUser )
2018-11-21 06:34:53 +00:00
{
2018-11-23 04:18:48 +00:00
var pagedRequest = request . PagedRequest ;
pagedRequest . FilterByGenre = request . Genre ;
pagedRequest . Sort = "Id" ;
2019-06-30 22:14:36 +00:00
var trackResult = await TrackService . List ( pagedRequest , roadieUser ) ;
2018-11-23 04:18:48 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-23 04:18:48 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . songsByGenre ,
Item = new subsonic . Songs
{
2019-06-30 22:14:36 +00:00
song = SubsonicChildrenForTracks ( trackResult . Rows )
2018-11-23 04:18:48 +00:00
}
}
} ;
2018-11-21 15:22:55 +00:00
}
2018-11-21 06:34:53 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns starred songs, albums and artists.
2018-11-21 06:34:53 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetStarred ( subsonic . Request request ,
User roadieUser , subsonic . StarredVersion version )
2018-11-21 06:34:53 +00:00
{
2018-11-22 17:31:59 +00:00
var pagedRequest = request . PagedRequest ;
pagedRequest . FilterFavoriteOnly = true ;
2018-11-22 23:12:57 +00:00
pagedRequest . Sort = "Id" ;
2018-11-21 18:19:38 +00:00
2019-06-30 22:14:36 +00:00
var artistList = await ArtistService . List ( roadieUser , pagedRequest ) ;
var releaseList = await ReleaseService . List ( roadieUser , pagedRequest ) ;
var songList = await TrackService . List ( pagedRequest , roadieUser ) ;
2018-11-21 15:22:55 +00:00
switch ( version )
{
2018-11-22 17:31:59 +00:00
case subsonic . StarredVersion . One :
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-21 15:22:55 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-21 15:22:55 +00:00
status = subsonic . ResponseStatus . ok ,
2018-11-22 17:31:59 +00:00
ItemElementName = subsonic . ItemChoiceType . starred ,
Item = new subsonic . Starred
2018-11-21 18:19:38 +00:00
{
2019-06-30 22:14:36 +00:00
album = SubsonicChildrenForReleases ( releaseList . Rows , null ) ,
artist = SubsonicArtistsForArtists ( artistList . Rows ) ,
song = SubsonicChildrenForTracks ( songList . Rows )
2018-11-21 18:19:38 +00:00
}
2018-11-21 15:22:55 +00:00
}
} ;
2018-11-22 17:31:59 +00:00
case subsonic . StarredVersion . Two :
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-22 13:48:32 +00:00
status = subsonic . ResponseStatus . ok ,
2018-11-22 17:31:59 +00:00
ItemElementName = subsonic . ItemChoiceType . starred2 ,
Item = new subsonic . Starred2
2018-11-22 13:48:32 +00:00
{
2019-06-30 22:14:36 +00:00
album = SubsonicAlbumID3ForReleases ( releaseList . Rows ) ,
artist = SubsonicArtistID3sForArtists ( artistList . Rows ) ,
song = SubsonicChildrenForTracks ( songList . Rows )
2018-11-22 13:48:32 +00:00
}
}
} ;
default :
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion ,
$"Unknown StarredVersion [{version}]" ) ;
2018-11-22 13:48:32 +00:00
}
}
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns top songs for the given artist, using data from last.fm.
2018-11-22 13:48:32 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetTopSongs ( subsonic . Request request ,
User roadieUser , int? count = 50 )
2018-11-22 13:48:32 +00:00
{
2018-11-25 16:57:17 +00:00
data . Artist artist = null ;
if ( ! string . IsNullOrEmpty ( request . ArtistName ) )
artist = base . GetArtist ( request . ArtistName ) ;
2019-06-30 22:14:36 +00:00
else if ( request . ArtistId . HasValue ) artist = GetArtist ( request . ArtistId . Value ) ;
2018-11-24 17:52:15 +00:00
if ( artist = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Unknown Artist [{request.ArtistName}]" ) ;
2018-11-22 13:48:32 +00:00
var pagedRequest = request . PagedRequest ;
2018-11-22 23:12:57 +00:00
pagedRequest . FilterToArtistId = artist . RoadieId ;
2018-11-22 17:31:59 +00:00
pagedRequest . FilterTopPlayedOnly = true ;
2018-11-22 23:12:57 +00:00
pagedRequest . Sort = "PlayedCount" ;
pagedRequest . Order = "DESC" ;
2019-06-30 22:14:36 +00:00
var trackResult = await TrackService . List ( pagedRequest , roadieUser ) ;
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-22 13:48:32 +00:00
status = subsonic . ResponseStatus . ok ,
2018-11-22 17:31:59 +00:00
ItemElementName = subsonic . ItemChoiceType . topSongs ,
Item = new subsonic . TopSongs
{
2019-06-30 22:14:36 +00:00
song = SubsonicChildrenForTracks ( trackResult . Rows )
2018-11-22 17:31:59 +00:00
}
2018-11-22 13:48:32 +00:00
}
} ;
}
/// <summary>
2019-06-30 22:14:36 +00:00
/// 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.
2018-11-22 13:48:32 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetUser ( subsonic . Request request ,
string username )
2018-11-22 13:48:32 +00:00
{
2019-06-30 22:14:36 +00:00
var user = GetUser ( username ) ;
2018-11-22 17:31:59 +00:00
if ( user = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Username [{username}]" ) ;
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-22 13:48:32 +00:00
status = subsonic . ResponseStatus . ok ,
2018-11-22 17:31:59 +00:00
ItemElementName = subsonic . ItemChoiceType . user ,
2019-06-30 22:14:36 +00:00
Item = await SubsonicUserForUser ( user )
2018-11-22 13:48:32 +00:00
}
} ;
}
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns all video files.
2018-11-22 13:48:32 +00:00
/// </summary>
public subsonic . SubsonicOperationResult < subsonic . Response > GetVideos ( subsonic . Request request )
{
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-22 13:48:32 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . videos ,
Item = new subsonic . Videos
{
2018-11-22 17:31:59 +00:00
video = new subsonic . Child [ 0 ]
2018-11-22 13:48:32 +00:00
}
}
} ;
}
/// <summary>
2019-06-30 22:14:36 +00:00
/// Used to test connectivity with the server. Takes no extra parameters.
2018-11-22 13:48:32 +00:00
/// </summary>
2018-11-22 17:31:59 +00:00
public subsonic . SubsonicOperationResult < subsonic . Response > Ping ( subsonic . Request request )
2018-11-22 13:48:32 +00:00
{
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-22 17:31:59 +00:00
status = subsonic . ResponseStatus . ok
2018-11-22 13:48:32 +00:00
}
} ;
}
2019-01-08 22:40:26 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Saves the state of the play queue for this user. This includes the tracks in the play queue, the currently playing
/// track, and the position within this track. Typically used to allow a user to move between different clients/apps
/// while retaining the same play queue (for instance when listening to an audio book).
2019-01-08 22:40:26 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > SavePlayQueue ( subsonic . Request request ,
User roadieUser , string current , long? position )
2019-01-08 22:40:26 +00:00
{
// Remove any existing Que for User
2019-06-30 22:14:36 +00:00
var user = GetUser ( roadieUser . UserId ) ;
if ( user . UserQues ! = null & & user . UserQues . Any ( ) ) DbContext . UserQues . RemoveRange ( user . UserQues ) ;
2019-01-08 22:40:26 +00:00
// Create a new UserQue for each posted TrackId in ids
if ( request . ids ! = null & & request . ids . Any ( ) )
{
short queSortOrder = 0 ;
var pagedRequest = request . PagedRequest ;
2019-06-30 22:14:36 +00:00
pagedRequest . FilterToTrackIds =
request . ids . Select ( x = > SafeParser . ToGuid ( x ) ) . Where ( x = > x . HasValue ) . ToArray ( ) ;
var trackListResult = await TrackService . List ( pagedRequest , roadieUser ) ;
2019-01-08 22:40:26 +00:00
var currentTrackId = SafeParser . ToGuid ( current ) ;
foreach ( var row in trackListResult . Rows )
{
queSortOrder + + ;
2019-06-30 22:14:36 +00:00
DbContext . UserQues . Add ( new data . UserQue
2019-01-08 22:40:26 +00:00
{
IsCurrent = row . Track . Value = = currentTrackId ? . ToString ( ) ,
Position = row . Track . Value = = currentTrackId ? . ToString ( ) ? position : null ,
QueSortOrder = queSortOrder ,
TrackId = row . DatabaseId ,
UserId = user . Id
} ) ;
}
}
2019-06-30 22:14:36 +00:00
await DbContext . SaveChangesAsync ( ) ;
2019-01-08 22:40:26 +00:00
2019-06-30 22:14:36 +00:00
CacheManager . ClearRegion ( user . CacheRegion ) ;
2019-01-08 22:40:26 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2019-01-08 22:40:26 +00:00
status = subsonic . ResponseStatus . ok
}
} ;
}
2018-11-22 13:48:32 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Returns albums, artists and songs matching the given search criteria. Supports paging through the result.
2018-11-22 13:48:32 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > Search ( subsonic . Request request ,
User roadieUser , subsonic . SearchVersion version )
2018-11-22 13:48:32 +00:00
{
2019-06-30 22:14:36 +00:00
var query = HttpEncoder . UrlDecode ( request . Query ) . Replace ( "*" , "" ) . Replace ( "%" , "" ) . Replace ( ";" , "" ) ;
2018-11-22 17:31:59 +00:00
// Search artists with query returning ArtistCount skipping ArtistOffset
var artistPagedRequest = request . PagedRequest ;
artistPagedRequest . Sort = "Artist.Text" ;
artistPagedRequest . Limit = request . ArtistCount ? ? artistPagedRequest . Limit ;
artistPagedRequest . SkipValue = request . ArtistOffset ? ? artistPagedRequest . SkipValue ;
artistPagedRequest . Filter = query ;
2019-06-30 22:14:36 +00:00
var artistResult = await ArtistService . List ( roadieUser , artistPagedRequest ) ;
2018-11-22 17:31:59 +00:00
// Search release with query returning RelaseCount skipping ReleaseOffset
var releasePagedRequest = request . PagedRequest ;
releasePagedRequest . Sort = "Release.Text" ;
releasePagedRequest . Limit = request . AlbumCount ? ? releasePagedRequest . Limit ;
releasePagedRequest . SkipValue = request . AlbumOffset ? ? releasePagedRequest . SkipValue ;
releasePagedRequest . Filter = query ;
2019-06-30 22:14:36 +00:00
var releaseResult = await ReleaseService . List ( roadieUser , releasePagedRequest ) ;
2018-11-22 17:31:59 +00:00
// Search tracks with query returning SongCount skipping SongOffset
var trackPagedRequest = request . PagedRequest ;
trackPagedRequest . Sort = "Track.Text" ;
trackPagedRequest . Limit = request . SongCount ? ? trackPagedRequest . Limit ;
trackPagedRequest . SkipValue = request . SongOffset ? ? trackPagedRequest . SkipValue ;
trackPagedRequest . Filter = query ;
2019-06-30 22:14:36 +00:00
var songResult = await TrackService . List ( trackPagedRequest , roadieUser ) ;
var songs = SubsonicChildrenForTracks ( songResult . Rows ) ;
2018-11-22 17:31:59 +00:00
switch ( version )
{
case subsonic . SearchVersion . One :
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . IncompatibleClientRestProtocolVersion ,
"Deprecated since 1.4.0, use search2 instead." ) ;
2018-11-22 17:31:59 +00:00
case subsonic . SearchVersion . Two :
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-22 17:31:59 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . searchResult2 ,
Item = new subsonic . SearchResult2
{
2019-06-30 22:14:36 +00:00
artist = SubsonicArtistsForArtists ( artistResult . Rows ) ,
album = SubsonicChildrenForReleases ( releaseResult . Rows , null ) ,
2018-11-22 17:31:59 +00:00
song = songs . ToArray ( )
}
}
} ;
case subsonic . SearchVersion . Three :
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-22 17:31:59 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . searchResult3 ,
Item = new subsonic . SearchResult3
{
2019-06-30 22:14:36 +00:00
artist = SubsonicArtistID3sForArtists ( artistResult . Rows ) ,
album = SubsonicAlbumID3ForReleases ( releaseResult . Rows ) ,
2018-11-22 17:31:59 +00:00
song = songs . ToArray ( )
}
}
} ;
default :
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion ,
$"Unknown SearchVersion [{version}]" ) ;
2018-11-22 17:31:59 +00:00
}
2018-11-22 13:48:32 +00:00
}
2018-11-24 17:52:15 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Sets the rating for a music file. If rating is zero then remove rating.
2018-11-24 17:52:15 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > SetRating ( subsonic . Request request ,
User roadieUser , short rating )
2018-11-24 17:52:15 +00:00
{
2019-06-30 22:14:36 +00:00
var user = GetUser ( roadieUser . UserId ) ;
2018-11-24 17:52:15 +00:00
if ( user = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . UserIsNotAuthorizedForGivenOperation , $"Invalid User [{roadieUser}]" ) ;
2018-11-24 17:52:15 +00:00
// Id can be a song, album or artist
if ( request . TrackId . HasValue )
{
2019-06-30 22:14:36 +00:00
var starResult = await SetTrackRating ( request . TrackId . Value , user , rating ) ;
2018-11-24 17:52:15 +00:00
if ( starResult . IsSuccess )
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response ( )
} ;
}
else if ( request . ReleaseId . HasValue )
{
2019-06-30 22:14:36 +00:00
var starResult = await SetReleaseRating ( request . ReleaseId . Value , user , rating ) ;
2018-11-24 17:52:15 +00:00
if ( starResult . IsSuccess )
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response ( )
} ;
}
else if ( request . ArtistId . HasValue )
{
2019-06-30 22:14:36 +00:00
var starResult = await SetArtistRating ( request . ArtistId . Value , user , rating ) ;
2018-11-24 17:52:15 +00:00
if ( starResult . IsSuccess )
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response ( )
} ;
}
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound ,
$"Unknown Star Id [{JsonConvert.SerializeObject(request)}]" ) ;
2018-11-24 17:52:15 +00:00
}
2018-11-22 13:48:32 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Attaches a star to a song, album or artist.
2018-11-22 13:48:32 +00:00
/// </summary>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > ToggleStar ( subsonic . Request request ,
User roadieUser , bool star , string [ ] albumIds = null , string [ ] artistIds = null )
2018-11-22 13:48:32 +00:00
{
2019-06-30 22:14:36 +00:00
var user = GetUser ( roadieUser . UserId ) ;
2018-11-24 04:06:59 +00:00
if ( user = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . UserIsNotAuthorizedForGivenOperation , $"Invalid User [{roadieUser}]" ) ;
2018-11-24 04:06:59 +00:00
// Id can be a song, album or artist
if ( request . TrackId . HasValue )
{
2019-06-30 22:14:36 +00:00
var starResult = await ToggleTrackStar ( request . TrackId . Value , user , star ) ;
2018-11-24 17:52:15 +00:00
if ( starResult . IsSuccess )
2018-11-24 04:06:59 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response ( )
} ;
}
2018-11-24 17:52:15 +00:00
else if ( request . ReleaseId . HasValue )
2018-11-24 04:06:59 +00:00
{
2019-06-30 22:14:36 +00:00
var starResult = await ToggleReleaseStar ( request . ReleaseId . Value , user , star ) ;
2018-11-24 04:06:59 +00:00
if ( starResult . IsSuccess )
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response ( )
} ;
}
2018-11-24 17:52:15 +00:00
else if ( request . ArtistId . HasValue )
2018-11-24 04:06:59 +00:00
{
2019-06-30 22:14:36 +00:00
var starResult = await ToggleArtistStar ( request . ArtistId . Value , user , star ) ;
2018-11-24 04:06:59 +00:00
if ( starResult . IsSuccess )
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response ( )
} ;
}
2018-11-24 17:52:15 +00:00
else if ( albumIds ! = null & & albumIds . Any ( ) )
2018-11-24 04:06:59 +00:00
{
2018-11-24 17:52:15 +00:00
foreach ( var rId in albumIds )
2018-11-24 04:06:59 +00:00
{
var releaseId = SafeParser . ToGuid ( rId ) ;
2018-11-24 17:52:15 +00:00
if ( releaseId . HasValue )
2018-11-24 04:06:59 +00:00
{
2019-06-30 22:14:36 +00:00
var starResult = await ToggleReleaseStar ( releaseId . Value , user , star ) ;
2018-11-24 04:06:59 +00:00
if ( ! starResult . IsSuccess )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( starResult . ErrorCode . Value ,
starResult . Messages . FirstOrDefault ( ) ) ;
2018-11-24 04:06:59 +00:00
}
}
}
2018-11-24 17:52:15 +00:00
else if ( artistIds ! = null & & artistIds . Any ( ) )
2018-11-24 04:06:59 +00:00
{
foreach ( var aId in artistIds )
{
var artistId = SafeParser . ToGuid ( aId ) ;
if ( artistId . HasValue )
{
2019-06-30 22:14:36 +00:00
var starResult = await ToggleReleaseStar ( artistId . Value , user , star ) ;
2018-11-24 04:06:59 +00:00
if ( ! starResult . IsNotFoundResult )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( starResult . ErrorCode . Value ,
starResult . Messages . FirstOrDefault ( ) ) ;
2018-11-24 04:06:59 +00:00
}
}
}
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound ,
$"Unknown Star Id [{JsonConvert.SerializeObject(request)}]" ) ;
2018-11-24 04:06:59 +00:00
}
2018-11-24 17:52:15 +00:00
/// <summary>
2019-06-30 22:14:36 +00:00
/// Updates a playlist. Only the owner of a playlist is allowed to update it.
2018-11-24 17:52:15 +00:00
/// </summary>
2018-11-25 16:57:17 +00:00
/// <param name="request">Populated Subsonic Request</param>
/// <param name="roadieUser">Populated Roadie User</param>
/// <param name="name">The human-readable name of the playlist.</param>
/// <param name="comment">The playlist comment.</param>
/// <param name="isPublic">true if the playlist should be visible to all users, false otherwise.</param>
/// <param name="songIdsToAdd">Add this song with this ID to the playlist. Multiple parameters allowed</param>
/// <param name="songIndexesToRemove">Remove the song at this position in the playlist. Multiple parameters allowed.</param>
2019-06-30 22:14:36 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > UpdatePlaylist ( subsonic . Request request ,
User roadieUser , string playListId , string name = null , string comment = null , bool? isPublic = null ,
string [ ] songIdsToAdd = null , int [ ] songIndexesToRemove = null )
2018-11-24 04:06:59 +00:00
{
2018-11-25 16:57:17 +00:00
request . id = playListId ? ? request . id ;
if ( ! request . PlaylistId . HasValue )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Playlist Id [{request.id}]" ) ;
var playlist = GetPlaylist ( request . PlaylistId . Value ) ;
2018-11-25 16:57:17 +00:00
if ( playlist = = null )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Playlist Id [{request.TrackId.Value}]" ) ;
2018-11-25 16:57:17 +00:00
if ( playlist . UserId ! = roadieUser . Id & & ! roadieUser . IsAdmin )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > (
subsonic . ErrorCodes . UserIsNotAuthorizedForGivenOperation ,
"User is not allowed to update playlist." ) ;
2018-11-24 04:06:59 +00:00
2018-11-25 16:57:17 +00:00
playlist . Name = name ? ? playlist . Name ;
playlist . IsPublic = isPublic ? ? playlist . IsPublic ;
playlist . LastUpdated = DateTime . UtcNow ;
2018-11-24 04:06:59 +00:00
2018-11-25 20:43:52 +00:00
if ( songIdsToAdd ! = null & & songIdsToAdd . Any ( ) )
2018-11-25 16:57:17 +00:00
{
// Add new if not already on Playlist
var songIdsToAddRoadieIds = songIdsToAdd . Select ( x = > SafeParser . ToGuid ( x ) ) . ToArray ( ) ;
2019-06-30 22:14:36 +00:00
var submittedTracks = from t in DbContext . Tracks
where songIdsToAddRoadieIds . Contains ( t . RoadieId )
select t ;
2018-11-24 17:52:15 +00:00
2018-11-25 16:57:17 +00:00
var listNumber = playlist . Tracks ? . Max ( x = > x . ListNumber ) ? ? 0 ;
foreach ( var submittedTrack in submittedTracks )
2019-06-30 22:14:36 +00:00
if ( playlist . Tracks = = null | | playlist . Tracks = = null | |
! playlist . Tracks . Any ( x = > x . TrackId = = submittedTrack . Id ) )
2018-11-25 16:57:17 +00:00
{
listNumber + + ;
2019-06-30 22:14:36 +00:00
DbContext . PlaylistTracks . Add ( new data . PlaylistTrack
2018-11-25 16:57:17 +00:00
{
PlayListId = playlist . Id ,
ListNumber = listNumber ,
TrackId = submittedTrack . Id
} ) ;
}
}
2019-06-30 22:14:36 +00:00
2018-11-25 20:43:52 +00:00
if ( songIndexesToRemove ! = null & & songIndexesToRemove . Any ( ) )
2018-11-25 16:57:17 +00:00
// Remove tracks from playlist
2018-11-25 20:43:52 +00:00
// Not clear from API documentation if this is zero based, wait until someone calls it to get values passed.
2019-06-30 22:14:36 +00:00
throw new NotImplementedException ( $"Request [{JsonConvert.SerializeObject(request)}]" ) ;
2018-11-25 16:57:17 +00:00
2019-06-30 22:14:36 +00:00
await DbContext . SaveChangesAsync ( ) ;
2018-11-25 16:57:17 +00:00
2019-06-30 22:14:36 +00:00
var user = GetUser ( roadieUser . UserId ) ;
CacheManager . ClearRegion ( user . CacheRegion ) ;
2018-11-25 16:57:17 +00:00
2018-11-24 17:52:15 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-24 04:06:59 +00:00
{
IsSuccess = true ,
2018-11-24 17:52:15 +00:00
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-24 17:52:15 +00:00
status = subsonic . ResponseStatus . ok
}
2018-11-24 04:06:59 +00:00
} ;
}
2018-11-24 17:52:15 +00:00
2019-01-08 22:40:26 +00:00
#region Privates
2018-11-25 21:15:51 +00:00
2019-01-08 22:40:26 +00:00
private string [ ] AllowedUsers ( )
2018-11-25 23:34:17 +00:00
{
2019-06-30 22:14:36 +00:00
return CacheManager . Get ( CacheManagerBase . SystemCacheRegionUrn + ":active_usernames" ,
( ) = > { return DbContext . Users . Where ( x = > x . IsActive ? ? false ) . Select ( x = > x . UserName ) . ToArray ( ) ; } ,
CacheManagerBase . SystemCacheRegionUrn ) ;
2018-11-25 23:34:17 +00:00
}
2019-01-08 22:40:26 +00:00
private subsonic . MusicFolder CollectionMusicFolder ( )
2018-11-25 23:34:17 +00:00
{
2019-06-30 22:14:36 +00:00
return MusicFolders ( ) . First ( x = > x . id = = 1 ) ;
2018-11-25 23:34:17 +00:00
}
2018-11-25 21:15:51 +00:00
2019-06-30 22:14:36 +00:00
private async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetArtistsAction (
subsonic . Request request , User roadieUser )
2018-11-25 20:43:52 +00:00
{
var indexes = new List < subsonic . IndexID3 > ( ) ;
2019-06-30 22:14:36 +00:00
var musicFolder = MusicFolders ( ) . FirstOrDefault ( x = > x . id = = ( request . MusicFolderId ? ? 2 ) ) ;
2018-11-25 20:43:52 +00:00
var pagedRequest = request . PagedRequest ;
2019-06-30 22:14:36 +00:00
if ( musicFolder = = CollectionMusicFolder ( ) )
2018-11-25 20:43:52 +00:00
{
// Indexes for "Collection" Artists alphabetically
}
else
{
// Indexes for "Music" Artists alphabetically
pagedRequest . SkipValue = 0 ;
2018-12-16 23:37:19 +00:00
pagedRequest . Limit = short . MaxValue ;
2018-11-25 20:43:52 +00:00
pagedRequest . Sort = "Artist.Text" ;
2019-06-30 22:14:36 +00:00
var artistList = await ArtistService . List ( roadieUser ,
pagedRequest ) ;
2018-11-25 20:43:52 +00:00
foreach ( var artistGroup in artistList . Rows . GroupBy ( x = > x . Artist . Text . Substring ( 0 , 1 ) ) )
indexes . Add ( new subsonic . IndexID3
{
name = artistGroup . Key ,
2019-06-30 22:14:36 +00:00
artist = SubsonicArtistID3sForArtists ( artistGroup )
2018-11-25 20:43:52 +00:00
} ) ;
2019-06-30 22:14:36 +00:00
;
2018-11-25 20:43:52 +00:00
}
2019-06-30 22:14:36 +00:00
2018-11-25 20:43:52 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-25 20:43:52 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . artists ,
Item = new subsonic . ArtistsID3
{
index = indexes . ToArray ( )
}
}
} ;
}
2019-06-30 22:14:36 +00:00
private async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetIndexesAction (
subsonic . Request request , User roadieUser , long? ifModifiedSince = null )
2018-11-24 04:06:59 +00:00
{
2019-06-30 22:14:36 +00:00
var modifiedSinceFilter =
ifModifiedSince . HasValue ? ( DateTime ? ) ifModifiedSince . Value . FromUnixTime ( ) : null ;
var musicFolderFilter = ! request . MusicFolderId . HasValue
? new subsonic . MusicFolder ( )
: MusicFolders ( ) . FirstOrDefault ( x = > x . id = = request . MusicFolderId . Value ) ;
2018-11-24 17:52:15 +00:00
var indexes = new List < subsonic . Index > ( ) ;
2019-06-30 22:14:36 +00:00
if ( musicFolderFilter . id = = CollectionMusicFolder ( ) . id )
2018-11-24 04:06:59 +00:00
{
2018-11-24 17:52:15 +00:00
// Collections for Music Folders by Alpha First
2019-06-30 22:14:36 +00:00
foreach ( var collectionFirstLetter in ( from c in DbContext . Collections
2018-11-24 17:52:15 +00:00
let first = c . Name . Substring ( 0 , 1 )
orderby first
select first ) . Distinct ( ) . ToArray ( ) )
indexes . Add ( new subsonic . Index
{
name = collectionFirstLetter ,
2019-06-30 22:14:36 +00:00
artist = ( from c in DbContext . Collections
2018-11-24 17:52:15 +00:00
where c . Name . Substring ( 0 , 1 ) = = collectionFirstLetter
where modifiedSinceFilter = = null | | c . LastUpdated > = modifiedSinceFilter
orderby c . SortName , c . Name
select new subsonic . Artist
{
2019-06-30 22:14:36 +00:00
id = subsonic . Request . CollectionIdentifier + c . RoadieId ,
2018-11-24 17:52:15 +00:00
name = c . Name ,
2019-10-23 13:45:36 +00:00
artistImageUrl = MakeCollectionThumbnailImage ( Configuration , HttpContext , c . RoadieId ) . Url ,
2018-11-24 17:52:15 +00:00
averageRating = 0 ,
userRating = 0
} ) . ToArray ( )
} ) ;
}
else
{
// Indexes for Artists alphabetically
var pagedRequest = request . PagedRequest ;
pagedRequest . SkipValue = 0 ;
2018-12-16 23:37:19 +00:00
pagedRequest . Limit = short . MaxValue ;
2018-11-24 17:52:15 +00:00
pagedRequest . Sort = "Artist.Text" ;
2019-06-30 22:14:36 +00:00
var artistList = await ArtistService . List ( roadieUser ,
pagedRequest ) ;
2018-11-24 17:52:15 +00:00
foreach ( var artistGroup in artistList . Rows . GroupBy ( x = > x . Artist . Text . Substring ( 0 , 1 ) ) )
indexes . Add ( new subsonic . Index
{
name = artistGroup . Key ,
2019-06-30 22:14:36 +00:00
artist = SubsonicArtistsForArtists ( artistGroup )
2018-11-24 17:52:15 +00:00
} ) ;
2019-06-30 22:14:36 +00:00
;
2018-11-24 17:52:15 +00:00
}
2019-06-30 22:14:36 +00:00
2018-11-24 17:52:15 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
2019-06-30 22:14:36 +00:00
version = SubsonicVersion ,
2018-11-24 17:52:15 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . indexes ,
Item = new subsonic . Indexes
{
2018-11-25 16:57:17 +00:00
lastModified = DateTime . UtcNow . ToUnixTime ( ) ,
2018-11-24 17:52:15 +00:00
index = indexes . ToArray ( )
}
}
} ;
}
2018-11-25 16:57:17 +00:00
private List < subsonic . MusicFolder > MusicFolders ( )
{
return new List < subsonic . MusicFolder >
{
2019-06-30 22:14:36 +00:00
new subsonic . MusicFolder { id = 1 , name = "Collections" } ,
new subsonic . MusicFolder { id = 2 , name = "Music" }
2018-11-25 16:57:17 +00:00
} ;
}
private subsonic . MusicFolder MusicMusicFolder ( )
{
2019-06-30 22:14:36 +00:00
return MusicFolders ( ) . First ( x = > x . id = = 2 ) ;
2018-11-25 16:57:17 +00:00
}
2019-06-30 22:14:36 +00:00
private new async Task < subsonic . SubsonicOperationResult < bool > > SetArtistRating ( Guid artistId ,
ApplicationUser user , short rating )
2018-11-24 17:52:15 +00:00
{
2018-12-01 03:22:35 +00:00
var r = await base . SetArtistRating ( artistId , user , rating ) ;
2019-01-08 22:40:26 +00:00
if ( r . IsNotFoundResult )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound ,
$"Invalid Artist Id [{artistId}]" ) ;
2018-11-24 04:06:59 +00:00
return new subsonic . SubsonicOperationResult < bool >
{
2018-12-01 03:22:35 +00:00
IsSuccess = r . IsSuccess ,
Data = r . IsSuccess
2018-11-24 04:06:59 +00:00
} ;
}
2018-11-24 17:52:15 +00:00
2019-06-30 22:14:36 +00:00
private new async Task < subsonic . SubsonicOperationResult < bool > > SetReleaseRating ( Guid releaseId ,
ApplicationUser user , short rating )
2018-11-24 04:06:59 +00:00
{
2018-12-01 03:22:35 +00:00
var r = await base . SetReleaseRating ( releaseId , user , rating ) ;
if ( r . IsNotFoundResult )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound ,
$"Invalid Release Id [{releaseId}]" ) ;
2018-11-24 04:06:59 +00:00
return new subsonic . SubsonicOperationResult < bool >
{
2018-12-01 03:22:35 +00:00
IsSuccess = r . IsSuccess ,
Data = r . IsSuccess
2018-11-24 04:06:59 +00:00
} ;
}
2018-11-24 17:52:15 +00:00
2019-06-30 22:14:36 +00:00
private new async Task < subsonic . SubsonicOperationResult < bool > > SetTrackRating ( Guid trackId ,
ApplicationUser user , short rating )
2018-11-24 17:52:15 +00:00
{
2018-12-01 03:22:35 +00:00
var r = await base . SetTrackRating ( trackId , user , rating ) ;
if ( r . IsNotFoundResult )
2019-06-30 22:14:36 +00:00
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound ,
$"Invalid Track Id [{trackId}]" ) ;
2018-11-24 17:52:15 +00:00
return new subsonic . SubsonicOperationResult < bool >
{
2018-12-01 03:22:35 +00:00
IsSuccess = r . IsSuccess ,
Data = r . IsSuccess
2018-11-24 17:52:15 +00:00
} ;
}
2018-11-25 16:57:17 +00:00
private subsonic . AlbumID3 SubsonicAlbumID3ForRelease ( ReleaseList r )
2018-11-24 04:06:59 +00:00
{
2018-11-25 16:57:17 +00:00
return new subsonic . AlbumID3
2018-11-21 18:19:38 +00:00
{
2019-06-30 22:14:36 +00:00
id = subsonic . Request . ReleaseIdIdentifier + r . Id ,
2018-11-21 18:19:38 +00:00
artistId = r . Artist . Value ,
name = r . Release . Text ,
songCount = r . TrackCount ? ? 0 ,
duration = r . Duration . ToSecondsFromMilliseconds ( ) ,
artist = r . Artist . Text ,
2019-06-30 22:14:36 +00:00
coverArt = subsonic . Request . ReleaseIdIdentifier + r . Id ,
2018-11-21 18:19:38 +00:00
created = r . CreatedDate . Value ,
2018-11-24 17:52:15 +00:00
genre = r . Genre . Text ,
2018-11-21 18:19:38 +00:00
playCount = r . TrackPlayedCount ? ? 0 ,
playCountSpecified = true ,
starred = r . UserRating ? . RatedDate ? ? DateTime . UtcNow ,
starredSpecified = r . UserRating ? . IsFavorite ? ? false ,
2018-11-24 17:52:15 +00:00
year = SafeParser . ToNumber < int > ( r . ReleaseYear ) ,
2018-11-21 18:19:38 +00:00
yearSpecified = true
} ;
}
2018-11-22 23:12:57 +00:00
private subsonic . AlbumID3 [ ] SubsonicAlbumID3ForReleases ( IEnumerable < ReleaseList > r )
2018-11-21 18:19:38 +00:00
{
2019-06-30 22:14:36 +00:00
if ( r = = null | | ! r . Any ( ) ) return new subsonic . AlbumID3 [ 0 ] ;
return r . Select ( x = > SubsonicAlbumID3ForRelease ( x ) ) . ToArray ( ) ;
2018-11-21 18:19:38 +00:00
}
2018-11-21 06:34:53 +00:00
private subsonic . Artist SubsonicArtistForArtist ( ArtistList artist )
2018-11-20 14:36:07 +00:00
{
2018-11-21 06:34:53 +00:00
return new subsonic . Artist
2018-11-20 14:36:07 +00:00
{
2019-06-30 22:14:36 +00:00
id = subsonic . Request . ArtistIdIdentifier + artist . Artist . Value ,
2018-11-21 06:34:53 +00:00
name = artist . Artist . Text ,
2019-10-23 13:45:36 +00:00
artistImageUrl = MakeArtistThumbnailImage ( Configuration , HttpContext , artist . Id ) . Url ,
2018-11-21 06:34:53 +00:00
averageRating = artist . Rating ? ? 0 ,
averageRatingSpecified = true ,
2018-11-21 18:19:38 +00:00
starred = artist . UserRating ? . RatedDate ? ? DateTime . UtcNow ,
2018-11-21 06:34:53 +00:00
starredSpecified = artist . UserRating ? . IsFavorite ? ? false ,
userRating = artist . UserRating ! = null ? artist . UserRating . Rating ? ? 0 : 0 ,
2018-11-24 17:52:15 +00:00
userRatingSpecified = artist . UserRating ! = null & & artist . UserRating . Rating ! = null
2018-11-21 06:34:53 +00:00
} ;
}
2018-11-20 14:36:07 +00:00
2018-11-21 18:19:38 +00:00
private subsonic . ArtistID3 SubsonicArtistID3ForArtist ( ArtistList artist )
{
2019-10-23 13:45:36 +00:00
var artistImageUrl = MakeArtistThumbnailImage ( Configuration , HttpContext , artist . Id ) . Url ;
2018-11-21 18:19:38 +00:00
return new subsonic . ArtistID3
{
2019-06-30 22:14:36 +00:00
id = subsonic . Request . ArtistIdIdentifier + artist . Artist . Value ,
2018-11-21 18:19:38 +00:00
name = artist . Artist . Text ,
2018-12-01 18:05:24 +00:00
albumCount = artist . ReleaseCount ? ? 0 ,
2018-11-21 18:19:38 +00:00
coverArt = artistImageUrl ,
artistImageUrl = artistImageUrl ,
starred = artist . UserRating ? . RatedDate ? ? DateTime . UtcNow ,
starredSpecified = artist . UserRating ? . IsFavorite ? ? false
} ;
}
private subsonic . ArtistID3 [ ] SubsonicArtistID3sForArtists ( IEnumerable < ArtistList > artists )
{
2019-06-30 22:14:36 +00:00
if ( artists = = null | | ! artists . Any ( ) ) return new subsonic . ArtistID3 [ 0 ] ;
return artists . Select ( x = > SubsonicArtistID3ForArtist ( x ) ) . ToArray ( ) ;
2018-11-21 18:19:38 +00:00
}
2018-11-22 17:31:59 +00:00
private subsonic . ArtistInfo2 SubsonicArtistInfo2InfoForArtist ( data . Artist artist )
{
return new subsonic . ArtistInfo2
{
biography = artist . BioContext ,
2019-10-23 13:45:36 +00:00
largeImageUrl = MakeImage ( Configuration , HttpContext , artist . RoadieId , "artist" , Configuration . LargeImageSize ) . Url ,
mediumImageUrl = MakeImage ( Configuration , HttpContext , artist . RoadieId , "artist" , Configuration . MediumImageSize ) . Url ,
2018-11-22 17:31:59 +00:00
musicBrainzId = artist . MusicBrainzId ,
similarArtist = new subsonic . ArtistID3 [ 0 ] ,
2019-10-23 13:45:36 +00:00
smallImageUrl = MakeImage ( Configuration , HttpContext , artist . RoadieId , "artist" , Configuration . SmallImageSize ) . Url
2018-11-22 17:31:59 +00:00
} ;
}
private subsonic . ArtistInfo SubsonicArtistInfoForArtist ( data . Artist artist )
{
return new subsonic . ArtistInfo
{
biography = artist . BioContext ,
2019-10-23 13:45:36 +00:00
largeImageUrl = MakeImage ( Configuration , HttpContext , artist . RoadieId , "artist" , Configuration . LargeImageSize ) . Url ,
mediumImageUrl = MakeImage ( Configuration , HttpContext , artist . RoadieId , "artist" , Configuration . MediumImageSize ) . Url ,
2018-11-22 17:31:59 +00:00
musicBrainzId = artist . MusicBrainzId ,
similarArtist = new subsonic . Artist [ 0 ] ,
2019-10-23 13:45:36 +00:00
smallImageUrl = MakeImage ( Configuration , HttpContext , artist . RoadieId , "artist" , Configuration . SmallImageSize ) . Url
2018-11-22 17:31:59 +00:00
} ;
}
2018-11-21 06:34:53 +00:00
private subsonic . Artist [ ] SubsonicArtistsForArtists ( IEnumerable < ArtistList > artists )
{
2019-06-30 22:14:36 +00:00
if ( artists = = null | | ! artists . Any ( ) ) return new subsonic . Artist [ 0 ] ;
return artists . Select ( x = > SubsonicArtistForArtist ( x ) ) . ToArray ( ) ;
2018-11-21 06:34:53 +00:00
}
2018-11-20 14:36:07 +00:00
2019-06-30 22:14:36 +00:00
private subsonic . ArtistWithAlbumsID3 SubsonicArtistWithAlbumsID3ForArtist ( ArtistList artist ,
subsonic . AlbumID3 [ ] releases )
2018-11-24 17:52:15 +00:00
{
2019-10-23 13:45:36 +00:00
var artistImageUrl = MakeArtistThumbnailImage ( Configuration , HttpContext , artist . Id ) . Url ;
2018-11-24 17:52:15 +00:00
return new subsonic . ArtistWithAlbumsID3
{
2019-06-30 22:14:36 +00:00
id = subsonic . Request . ArtistIdIdentifier + artist . Artist . Value ,
2018-11-24 17:52:15 +00:00
album = releases ,
albumCount = releases . Count ( ) ,
artistImageUrl = artistImageUrl ,
coverArt = artistImageUrl ,
name = artist . Artist . Text ,
starred = artist . UserRating ? . RatedDate ? ? DateTime . UtcNow ,
starredSpecified = artist . UserRating ? . IsFavorite ? ? false
} ;
}
2018-11-25 16:57:17 +00:00
private subsonic . Bookmark SubsonicBookmarkForBookmark ( BookmarkList b , subsonic . Child entry )
{
return new subsonic . Bookmark
{
changed = b . LastUpdated ? ? b . CreatedDate . Value ,
comment = b . Comment ,
created = b . CreatedDate . Value ,
position = b . Position ? ? 0 ,
username = b . User . Text ,
entry = entry
} ;
}
2019-06-30 22:14:36 +00:00
private subsonic . Bookmark [ ] SubsonicBookmarksForBookmarks ( IEnumerable < BookmarkList > bb ,
IEnumerable < TrackList > childTracks )
2018-11-25 16:57:17 +00:00
{
2019-06-30 22:14:36 +00:00
if ( bb = = null | | ! bb . Any ( ) ) return new subsonic . Bookmark [ 0 ] ;
2018-11-25 16:57:17 +00:00
var result = new List < subsonic . Bookmark > ( ) ;
foreach ( var bookmark in bb )
{
subsonic . Child child = null ;
switch ( bookmark . Type . Value )
{
2019-06-30 22:14:36 +00:00
case BookmarkType . Track :
child = SubsonicChildForTrack ( childTracks . FirstOrDefault ( x = >
x . Id = = SafeParser . ToGuid ( bookmark . Bookmark . Value ) ) ) ;
2018-11-25 16:57:17 +00:00
break ;
default :
throw new NotImplementedException ( "Wrong Bookmark type to convert to Subsonic media Bookmark" ) ;
}
2019-06-30 22:14:36 +00:00
result . Add ( SubsonicBookmarkForBookmark ( bookmark , child ) ) ;
2018-11-25 16:57:17 +00:00
}
2019-06-30 22:14:36 +00:00
2018-11-25 16:57:17 +00:00
return result . ToArray ( ) ;
}
2018-11-21 06:34:53 +00:00
private subsonic . Child SubsonicChildForRelease ( ReleaseList r , string parent , string path )
{
return new subsonic . Child
2018-11-20 14:36:07 +00:00
{
2019-06-30 22:14:36 +00:00
id = subsonic . Request . ReleaseIdIdentifier + r . Id ,
2018-11-20 14:36:07 +00:00
album = r . Release . Text ,
2019-06-30 22:14:36 +00:00
albumId = subsonic . Request . ReleaseIdIdentifier + r . Id ,
2018-11-20 14:36:07 +00:00
artist = r . Artist . Text ,
averageRating = r . Rating ? ? 0 ,
averageRatingSpecified = true ,
2019-06-30 22:14:36 +00:00
coverArt = subsonic . Request . ReleaseIdIdentifier + r . Id ,
2018-11-20 14:36:07 +00:00
created = r . CreatedDate . Value ,
createdSpecified = true ,
2018-11-21 06:34:53 +00:00
genre = r . Genre . Text ,
isDir = true ,
2018-11-22 23:12:57 +00:00
parent = parent ? ? subsonic . Request . ArtistIdIdentifier + r . Artist . Value ,
2018-11-21 06:34:53 +00:00
path = path ,
2018-11-20 14:36:07 +00:00
playCount = r . TrackPlayedCount ? ? 0 ,
playCountSpecified = true ,
2018-11-21 06:34:53 +00:00
starred = r . UserRating ? . RatedDate ? ? DateTime . UtcNow ,
starredSpecified = r . UserRating ? . IsFavorite ? ? false ,
title = r . Release . Text ,
2018-11-20 14:36:07 +00:00
userRating = r . UserRating ! = null ? r . UserRating . Rating ? ? 0 : 0 ,
2018-11-21 06:34:53 +00:00
userRatingSpecified = r . UserRating ! = null & & r . UserRating . Rating ! = null ,
year = SafeParser . ToNumber < int > ( r . ReleaseYear ) ,
2018-11-21 15:22:55 +00:00
yearSpecified = true
} ;
}
2018-11-21 06:34:53 +00:00
private subsonic . Child SubsonicChildForTrack ( TrackList t )
{
2019-07-07 16:55:55 +00:00
var userRating = t . UserRating ? . Rating ? ? 0 ;
2019-08-22 20:33:07 +00:00
if ( userRating > 0 )
2019-07-07 16:55:55 +00:00
{
// This is done as many subsonic apps think rating "1" is don't play song, versus a minimum indication of like as intended for Roadie.
// To disable this set the configuration SubsonicRatingBoost to 0
userRating + = Configuration . SubsonicRatingBoost ? ? 1 ;
userRating = userRating > 5 ? ( short ) 5 : userRating ;
}
2018-11-21 06:34:53 +00:00
return new subsonic . Child
2018-11-20 14:36:07 +00:00
{
2019-06-30 22:14:36 +00:00
id = subsonic . Request . TrackIdIdentifier + t . Id ,
2018-12-07 21:02:38 +00:00
album = t . Release . Release . Text ,
albumId = subsonic . Request . ReleaseIdIdentifier + t . Release . Release . Value ,
artist = t . Artist . Artist . Text ,
artistId = subsonic . Request . ArtistIdIdentifier + t . Artist . Artist . Value ,
2018-11-21 06:34:53 +00:00
averageRating = t . Rating ? ? 0 ,
averageRatingSpecified = true ,
bitRate = 320 ,
bitRateSpecified = true ,
2019-09-05 02:04:20 +00:00
contentType = MimeTypeHelper . Mp3MimeType ,
2019-06-30 22:14:36 +00:00
coverArt = subsonic . Request . TrackIdIdentifier + t . Id ,
2018-11-21 06:34:53 +00:00
created = t . CreatedDate . Value ,
createdSpecified = true ,
discNumber = t . MediaNumber ? ? 0 ,
discNumberSpecified = true ,
duration = t . Duration . ToSecondsFromMilliseconds ( ) ,
durationSpecified = true ,
2018-11-21 18:19:38 +00:00
isDir = false ,
2018-12-07 21:02:38 +00:00
parent = subsonic . Request . ReleaseIdIdentifier + t . Release . Release . Value ,
2019-06-30 22:14:36 +00:00
path = $"{t.Artist.Artist.Text}/{t.Release.Release.Text}/{t.TrackNumber} - {t.Title}.mp3" ,
2018-11-21 06:34:53 +00:00
playCountSpecified = true ,
size = t . FileSize ? ? 0 ,
sizeSpecified = true ,
starred = t . UserRating ? . RatedDate ? ? DateTime . UtcNow ,
starredSpecified = t . UserRating ? . IsFavorite ? ? false ,
suffix = "mp3" ,
title = t . Title ,
track = t . TrackNumber ? ? 0 ,
trackSpecified = t . TrackNumber . HasValue ,
type = subsonic . MediaType . music ,
typeSpecified = true ,
2019-07-07 16:55:55 +00:00
userRating = userRating ,
2018-11-21 06:34:53 +00:00
userRatingSpecified = t . UserRating ! = null ,
year = t . Year ? ? 0 ,
2018-11-22 13:48:32 +00:00
yearSpecified = t . Year . HasValue ,
2019-09-05 02:04:20 +00:00
transcodedContentType = MimeTypeHelper . Mp3MimeType ,
2018-11-21 18:19:38 +00:00
transcodedSuffix = "mp3" ,
isVideo = false ,
isVideoSpecified = true ,
2018-11-22 23:12:57 +00:00
playCount = t . PlayedCount ? ? 0
2018-11-20 14:36:07 +00:00
} ;
2018-11-21 06:34:53 +00:00
}
2018-11-20 14:36:07 +00:00
2018-11-21 06:34:53 +00:00
private subsonic . Child [ ] SubsonicChildrenForReleases ( IEnumerable < ReleaseList > r , string parent )
{
2019-06-30 22:14:36 +00:00
if ( r = = null | | ! r . Any ( ) ) return new subsonic . Child [ 0 ] ;
return r . Select ( x = > SubsonicChildForRelease ( x , parent , $"{x.Artist.Text}/{x.Release.Text}/" ) ) . ToArray ( ) ;
2018-11-20 14:36:07 +00:00
}
2018-11-21 06:34:53 +00:00
private subsonic . Child [ ] SubsonicChildrenForTracks ( IEnumerable < TrackList > tracks )
{
2019-06-30 22:14:36 +00:00
if ( tracks = = null | | ! tracks . Any ( ) ) return new subsonic . Child [ 0 ] ;
return tracks . Select ( x = > SubsonicChildForTrack ( x ) ) . ToArray ( ) ;
2018-11-21 06:34:53 +00:00
}
2018-11-21 18:19:38 +00:00
2019-06-30 22:14:36 +00:00
private subsonic . Playlist SubsonicPlaylistForPlaylist ( PlaylistList playlist ,
IEnumerable < TrackList > playlistTracks = null )
2018-11-21 18:19:38 +00:00
{
return new subsonic . PlaylistWithSongs
{
2019-10-23 13:45:36 +00:00
coverArt = MakePlaylistThumbnailImage ( Configuration , HttpContext , playlist . Id ) . Url ,
2019-06-30 22:14:36 +00:00
allowedUser = playlist . IsPublic ? AllowedUsers ( ) : null ,
2018-11-21 18:19:38 +00:00
changed = playlist . LastUpdated ? ? playlist . CreatedDate ? ? DateTime . UtcNow ,
created = playlist . CreatedDate ? ? DateTime . UtcNow ,
2018-11-25 23:34:17 +00:00
duration = playlist . Duration . ToSecondsFromMilliseconds ( ) ,
2019-06-30 22:14:36 +00:00
id = subsonic . Request . PlaylistdIdentifier + playlist . Id ,
2018-11-21 18:19:38 +00:00
name = playlist . Playlist . Text ,
owner = playlist . User . Text ,
@public = playlist . IsPublic ,
publicSpecified = true ,
songCount = playlist . PlaylistCount ? ? 0 ,
2019-06-30 22:14:36 +00:00
entry = SubsonicChildrenForTracks ( playlistTracks )
2018-11-21 18:19:38 +00:00
} ;
}
2019-06-30 22:14:36 +00:00
private subsonic . Playlist [ ] SubsonicPlaylistsForPlaylists ( IEnumerable < PlaylistList > playlists )
2018-11-24 17:52:15 +00:00
{
2019-06-30 22:14:36 +00:00
if ( playlists = = null | | ! playlists . Any ( ) ) return new subsonic . Playlist [ 0 ] ;
return playlists . Select ( x = > SubsonicPlaylistForPlaylist ( x ) ) . ToArray ( ) ;
2018-11-24 17:52:15 +00:00
}
2019-06-30 22:14:36 +00:00
private async Task < subsonic . User > SubsonicUserForUser ( ApplicationUser user )
2018-11-21 18:19:38 +00:00
{
2019-06-30 22:14:36 +00:00
var isAdmin = await UserManger . IsInRoleAsync ( user , "Admin" ) ;
var isEditor = await UserManger . IsInRoleAsync ( user , "Editor" ) ;
2018-11-21 18:19:38 +00:00
return new subsonic . User
{
2018-11-26 03:15:19 +00:00
adminRole = false , // disabling this as we dont want Roadie user management done via Subsonic API
2018-11-21 18:19:38 +00:00
avatarLastChanged = user . LastUpdated ? ? user . CreatedDate ? ? DateTime . UtcNow ,
avatarLastChangedSpecified = user . LastUpdated . HasValue ,
2018-11-24 17:52:15 +00:00
commentRole = true ,
2018-11-22 17:31:59 +00:00
coverArtRole = isEditor | | isAdmin ,
2019-01-08 22:40:26 +00:00
downloadRole = isEditor | | isAdmin ,
2018-11-21 18:19:38 +00:00
email = user . Email ,
2018-11-26 03:15:19 +00:00
jukeboxRole = false , // Jukebox disabled (what is jukebox?)
2018-11-21 18:19:38 +00:00
maxBitRate = 320 ,
maxBitRateSpecified = true ,
2018-11-22 17:31:59 +00:00
playlistRole = isEditor | | isAdmin ,
2018-11-21 18:19:38 +00:00
podcastRole = false , // Disable podcast nonsense
scrobblingEnabled = false , // Disable scrobbling
settingsRole = isAdmin ,
2019-01-08 22:40:26 +00:00
shareRole = false ,
2018-11-21 18:19:38 +00:00
streamRole = true ,
uploadRole = true ,
username = user . UserName ,
videoConversionRole = false , // Disable video nonsense
2019-06-30 22:14:36 +00:00
folder = MusicFolders ( ) . Select ( x = > x . id ) . ToArray ( )
2018-11-21 18:19:38 +00:00
} ;
}
2018-11-22 13:48:32 +00:00
2019-08-07 15:28:51 +00:00
private async Task < subsonic . SubsonicOperationResult < bool > > ToggleArtistStar ( Guid artistId , ApplicationUser user , bool starred )
2018-11-25 16:57:17 +00:00
{
2019-06-30 22:14:36 +00:00
var r = await ToggleArtistFavorite ( artistId , user , starred ) ;
2018-12-01 18:05:24 +00:00
if ( r . IsNotFoundResult )
2019-08-07 15:28:51 +00:00
{
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Artist Id [{artistId}]" ) ;
}
2018-11-25 16:57:17 +00:00
return new subsonic . SubsonicOperationResult < bool >
{
2018-12-01 18:05:24 +00:00
IsSuccess = r . IsSuccess ,
Data = r . IsSuccess
2018-11-25 16:57:17 +00:00
} ;
}
2019-08-07 15:28:51 +00:00
private async Task < subsonic . SubsonicOperationResult < bool > > ToggleReleaseStar ( Guid releaseId , ApplicationUser user , bool starred )
2018-11-25 16:57:17 +00:00
{
2019-06-30 22:14:36 +00:00
var r = await ToggleReleaseFavorite ( releaseId , user , starred ) ;
2018-12-01 18:05:24 +00:00
if ( r . IsNotFoundResult )
2019-08-07 15:28:51 +00:00
{
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release Id [{releaseId}]" ) ;
}
2018-11-25 16:57:17 +00:00
return new subsonic . SubsonicOperationResult < bool >
{
2018-12-01 18:05:24 +00:00
IsSuccess = r . IsSuccess ,
Data = r . IsSuccess
2018-11-25 16:57:17 +00:00
} ;
}
2019-08-07 15:28:51 +00:00
private async Task < subsonic . SubsonicOperationResult < bool > > ToggleTrackStar ( Guid trackId , ApplicationUser user , bool starred )
2018-11-25 16:57:17 +00:00
{
2019-06-30 22:14:36 +00:00
var r = await ToggleTrackFavorite ( trackId , user , starred ) ;
2018-12-01 18:05:24 +00:00
if ( r . IsNotFoundResult )
2019-08-07 15:28:51 +00:00
{
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{trackId}]" ) ;
}
2018-11-25 16:57:17 +00:00
return new subsonic . SubsonicOperationResult < bool >
{
2018-12-01 18:05:24 +00:00
IsSuccess = r . IsSuccess ,
Data = r . IsSuccess
2018-11-25 16:57:17 +00:00
} ;
}
2018-11-22 13:48:32 +00:00
#endregion Privates
2018-11-15 15:10:29 +00:00
}
2018-11-16 03:37:00 +00:00
}