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 ;
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 ;
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.Linq.Dynamic.Core ;
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>
/// 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"/>
2018-11-24 04:06:59 +00:00
/// <seealso cref="https://www.reddit.com/r/subsonic/comments/7c2n6j/database_table_schema/"/>
2018-11-15 15:10:29 +00:00
/// <!-- 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 -->
/// </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 ; }
2018-11-24 17:52:15 +00:00
private IBookmarkService BookmarkService { get ; }
2018-11-24 16:18:03 +00:00
private ICollectionService CollectionService { get ; }
2018-11-21 06:34:53 +00:00
private IImageService ImageService { get ; }
2018-11-21 18:19:38 +00:00
private IPlaylistService PlaylistService { get ; }
2018-11-25 20:43:52 +00:00
private IPlayActivityService PlayActivityService { get ; }
2018-11-20 14:36:07 +00:00
private IReleaseService ReleaseService { get ; }
2018-11-21 06:34:53 +00:00
private ITrackService TrackService { get ; }
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 ,
IHttpEncoder httpEncoder ,
IHttpContext httpContext ,
data . IRoadieDbContext context ,
ICacheManager cacheManager ,
2018-11-17 02:14:32 +00:00
ILogger < SubsonicService > logger ,
2018-11-21 06:34:53 +00:00
IArtistService artistService ,
ITrackService trackService ,
2018-11-15 15:10:29 +00:00
ICollectionService collectionService ,
2018-11-20 14:36:07 +00:00
IPlaylistService playlistService ,
2018-11-21 06:34:53 +00:00
IReleaseService releaseService ,
2018-11-21 18:19:38 +00:00
IImageService imageService ,
2018-11-24 17:52:15 +00:00
IBookmarkService bookmarkService ,
2018-11-25 20:43:52 +00:00
IPlayActivityService playActivityService ,
2018-11-21 18:19:38 +00:00
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
{
2018-11-21 06:34:53 +00:00
this . ArtistService = artistService ;
2018-11-24 17:52:15 +00:00
this . BookmarkService = bookmarkService ;
2018-11-24 16:18:03 +00:00
this . CollectionService = collectionService ;
this . ImageService = imageService ;
this . PlaylistService = playlistService ;
2018-11-25 20:43:52 +00:00
this . PlayActivityService = playActivityService ;
2018-11-20 14:36:07 +00:00
this . ReleaseService = releaseService ;
2018-11-21 06:34:53 +00:00
this . TrackService = trackService ;
2018-11-21 18:19:38 +00:00
this . UserManger = userManager ;
}
2018-11-22 17:31:59 +00:00
/// <summary>
/// Authenticate the given credentials and return the corresponding ApplicationUser
/// </summary>
2018-11-23 04:18:48 +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 ) )
{
return new subsonic . SubsonicOperationResult < subsonic . SubsonicAuthenticateResponse > ( subsonic . ErrorCodes . WrongUsernameOrPassword , $"Unknown Username" ) ;
}
try
{
var user = this . GetUser ( request . u ) ;
if ( user = = null )
{
this . Logger . LogInformation ( $"Unknown User [{ request.u }]" ) ;
return new subsonic . SubsonicOperationResult < subsonic . SubsonicAuthenticateResponse > ( subsonic . ErrorCodes . WrongUsernameOrPassword , $"Unknown Username" ) ;
}
var password = request . Password ;
var wasAuthenticatedAgainstPassword = false ;
if ( ! string . IsNullOrEmpty ( request . s ) )
{
var token = HashHelper . MD5Hash ( ( user . ApiToken ? ? user . Email ) + request . s ) ;
if ( ! token . Equals ( request . t , StringComparison . OrdinalIgnoreCase ) )
{
user = null ;
}
else
{
wasAuthenticatedAgainstPassword = true ;
}
}
2018-11-24 17:52:15 +00:00
else if ( user ! = null & & ! string . IsNullOrEmpty ( user . PasswordHash ) & & ! string . IsNullOrEmpty ( password ) )
2018-11-23 04:18:48 +00:00
{
2018-11-23 15:10:33 +00:00
try
2018-11-23 04:18:48 +00:00
{
2018-11-23 15:10:33 +00:00
var hashCheck = this . UserManger . PasswordHasher . VerifyHashedPassword ( user , user . PasswordHash , password ) ;
if ( hashCheck = = PasswordVerificationResult . Failed )
{
user = null ;
}
else
{
wasAuthenticatedAgainstPassword = true ;
}
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
{
}
}
if ( wasAuthenticatedAgainstPassword )
{
// Since API dont update LastLogin which likely invalidates any browser logins
user . LastApiAccess = DateTime . UtcNow ;
await this . DbContext . SaveChangesAsync ( ) ;
}
if ( user = = null )
{
this . Logger . LogInformation ( $"Unknown User [{ request.u }]" ) ;
return new subsonic . SubsonicOperationResult < subsonic . SubsonicAuthenticateResponse > ( subsonic . ErrorCodes . WrongUsernameOrPassword , $"Unknown Username" ) ;
}
2018-11-25 16:57:17 +00:00
this . Logger . LogInformation ( $"Subsonic: Successfully Authenticated User [{ user.ToString() }] 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 )
{
this . Logger . LogError ( ex , "Subsonic.Authenticate, Error CheckPassword [" + JsonConvert . SerializeObject ( request ) + "]" ) ;
}
return null ;
2018-11-22 17:31:59 +00:00
}
2018-11-25 16:57:17 +00:00
/// <summary>
/// Creates or updates a bookmark (a position within a media file). Bookmarks are personal and not visible to other users.
/// </summary>
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > CreateBookmark ( subsonic . Request request , User roadieUser , int position , string comment )
{
if ( ! request . TrackId . HasValue )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{ request.id }]" ) ;
}
var track = this . GetTrack ( request . TrackId . Value ) ;
if ( track = = null )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{ request.TrackId.Value }]" ) ;
}
var userBookmark = this . DbContext . Bookmarks . FirstOrDefault ( x = > x . UserId = = roadieUser . Id & & x . BookmarkTargetId = = track . Id & & x . BookmarkType = = Library . Enums . BookmarkType . Track ) ;
var createdBookmark = false ;
if ( userBookmark = = null )
{
userBookmark = new data . Bookmark
{
BookmarkTargetId = track . Id ,
BookmarkType = Library . Enums . BookmarkType . Track ,
UserId = roadieUser . Id ,
Comment = comment ,
Position = position
} ;
this . DbContext . Bookmarks . Add ( userBookmark ) ;
createdBookmark = true ;
}
else
{
userBookmark . LastUpdated = DateTime . UtcNow ;
userBookmark . Position = position ;
userBookmark . Comment = comment ;
}
await this . DbContext . SaveChangesAsync ( ) ;
var user = this . GetUser ( roadieUser . UserId ) ;
this . CacheManager . ClearRegion ( user . CacheRegion ) ;
this . Logger . LogInformation ( $"{ (createdBookmark ? " Created " : " Updated ") } Bookmark `{ userBookmark}` for User `{ roadieUser }`" ) ;
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok
}
} ;
}
2018-11-25 20:43:52 +00:00
/// <summary>
/// Creates (or updates) a playlist.
/// </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>
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > CreatePlaylist ( subsonic . Request request , User roadieUser , string name , string [ ] songIds , string playlistId = null )
{
data . Playlist playlist = null ;
Guid ? [ ] songRoadieIds = new Guid ? [ 0 ] ;
IQueryable < data . Track > submittedTracks = new data . Track [ 0 ] . AsQueryable ( ) ;
if ( songIds ! = null & & songIds . Any ( ) )
{
songRoadieIds = songIds . Select ( x = > SafeParser . ToGuid ( x ) ) . ToArray ( ) ;
// Add (if not already) given tracks to Playlist
submittedTracks = ( from t in this . DbContext . Tracks
where songRoadieIds . Contains ( t . RoadieId )
select t ) ;
}
var didCreate = false ;
if ( ! string . IsNullOrEmpty ( playlistId ) )
{
request . id = playlistId ;
playlist = this . DbContext . Playlists . Include ( x = > x . Tracks ) . FirstOrDefault ( x = > x . RoadieId = = request . PlaylistId ) ;
if ( playlist = = null )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid PlaylistId [{ playlistId }]" ) ;
}
// When Create is called again on an existing delete all existing tracks and add given
if ( playlist . Tracks ! = null & & playlist . Tracks . Any ( ) )
{
this . DbContext . PlaylistTracks . RemoveRange ( playlist . Tracks ) ;
}
var listNumber = playlist . Tracks ! = null & & playlist . Tracks . Any ( ) ? playlist . Tracks ? . Max ( x = > x . ListNumber ) ? ? 0 : 0 ;
foreach ( var submittedTrack in submittedTracks )
{
if ( playlist . Tracks = = null | | ! playlist . Tracks . Any ( x = > x . TrackId = = submittedTrack . Id ) )
{
listNumber + + ;
this . DbContext . PlaylistTracks . Add ( new data . PlaylistTrack
{
PlayListId = playlist . Id ,
ListNumber = listNumber ,
TrackId = submittedTrack . Id
} ) ;
}
}
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 ;
this . DbContext . Playlists . Add ( playlist ) ;
}
await this . DbContext . SaveChangesAsync ( ) ;
this . Logger . LogInformation ( $"Subsonic: User `{ roadieUser }` { (didCreate ? " created " : " modified ") } Playlist `{ playlist }` added [{ songRoadieIds.Count() }] Tracks." ) ;
request . id = subsonic . Request . PlaylistdIdentifier + playlist . RoadieId . ToString ( ) ;
return await this . GetPlaylist ( request , roadieUser ) ;
}
2018-11-25 16:57:17 +00:00
/// <summary>
/// Deletes the bookmark for a given file.
/// </summary>
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > DeleteBookmark ( subsonic . Request request , User roadieUser )
{
if ( ! request . TrackId . HasValue )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{ request.id }]" ) ;
}
var track = this . GetTrack ( request . TrackId . Value ) ;
if ( track = = null )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{ request.TrackId.Value }]" ) ;
}
var userBookmark = this . DbContext . Bookmarks . FirstOrDefault ( x = > x . UserId = = roadieUser . Id & & x . BookmarkTargetId = = track . Id & & x . BookmarkType = = Library . Enums . BookmarkType . Track ) ;
if ( userBookmark ! = null )
{
this . DbContext . Bookmarks . Remove ( userBookmark ) ;
await this . DbContext . SaveChangesAsync ( ) ;
var user = this . GetUser ( roadieUser . UserId ) ;
this . CacheManager . ClearRegion ( user . CacheRegion ) ;
this . Logger . LogInformation ( $"Subsonic: Deleted Bookmark `{ userBookmark}` for User `{ roadieUser }`" ) ;
}
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok
}
} ;
}
2018-11-25 20:43:52 +00:00
/// <summary>
/// Deletes a saved playlist.
/// </summary>
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > DeletePlaylist ( subsonic . Request request , User roadieUser )
{
if ( ! request . PlaylistId . HasValue )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Playlist Id [{ request.id }]" ) ;
}
var playlist = this . GetPlaylist ( request . PlaylistId . Value ) ;
if ( playlist = = null )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Playlist Id [{ request.TrackId.Value }]" ) ;
}
if ( playlist . UserId ! = roadieUser . Id & & ! roadieUser . IsAdmin )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . UserIsNotAuthorizedForGivenOperation , "User is not allowed to delete playlist." ) ;
}
this . DbContext . Playlists . Remove ( playlist ) ;
await this . DbContext . SaveChangesAsync ( ) ;
var user = this . GetUser ( roadieUser . UserId ) ;
this . CacheManager . ClearRegion ( user . CacheRegion ) ;
this . Logger . LogInformation ( $"Subsonic: Deleted Playlist `{ playlist}` for User `{ roadieUser }`" ) ;
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok
}
} ;
}
2018-11-24 17:52:15 +00:00
/// <summary>
/// Returns details for an album, including a list of songs. This method organizes music according to ID3 tags.
/// </summary>
2018-11-22 13:48:32 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetAlbum ( subsonic . Request request , User roadieUser )
2018-11-21 18:19:38 +00:00
{
2018-11-22 17:31:59 +00:00
var releaseId = SafeParser . ToGuid ( request . id ) ;
if ( ! releaseId . HasValue )
2018-11-21 18:19:38 +00:00
{
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{ request.ReleaseId}]" ) ;
2018-11-21 18:19:38 +00:00
}
2018-11-22 17:31:59 +00:00
var release = this . GetRelease ( releaseId . Value ) ;
2018-11-21 18:19:38 +00:00
if ( release = = null )
{
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{ request.ReleaseId}]" ) ;
2018-11-21 18:19:38 +00:00
}
var pagedRequest = request . PagedRequest ;
2018-11-25 20:43:52 +00:00
var releaseTracks = await this . TrackService . List ( pagedRequest , roadieUser , false , releaseId ) ;
2018-11-21 18:19:38 +00:00
var userRelease = roadieUser = = null ? null : this . DbContext . UserReleases . FirstOrDefault ( x = > x . ReleaseId = = release . Id & & x . UserId = = roadieUser . Id ) ;
var genre = release . Genres . FirstOrDefault ( ) ;
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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . album ,
Item = new subsonic . AlbumWithSongsID3
{
artist = release . Artist . Name ,
artistId = subsonic . Request . ArtistIdIdentifier + release . Artist . RoadieId . ToString ( ) ,
coverArt = subsonic . Request . ReleaseIdIdentifier + release . RoadieId . ToString ( ) ,
created = release . CreatedDate ,
duration = release . Duration . ToSecondsFromMilliseconds ( ) ,
genre = genre = = null ? null : genre . Genre . Name ,
id = subsonic . Request . ReleaseIdIdentifier + release . RoadieId . ToString ( ) ,
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 ,
song = this . SubsonicChildrenForTracks ( releaseTracks . Rows )
}
}
} ;
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>
/// Returns album notes, image URLs etc, using data from last.fm.
/// </summary>
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetAlbumInfo ( subsonic . Request request , User roadieUser , subsonic . AlbumInfoVersion version )
{
var releaseId = SafeParser . ToGuid ( request . id ) ;
if ( ! releaseId . HasValue )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{ request.id }]" ) ;
}
var release = this . GetRelease ( releaseId . Value ) ;
if ( release = = null )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{ request.id }]" ) ;
}
switch ( version )
{
case subsonic . AlbumInfoVersion . One :
case subsonic . AlbumInfoVersion . Two :
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . albumInfo ,
Item = new subsonic . AlbumInfo
{
largeImageUrl = this . MakeImage ( release . RoadieId , "release" , this . Configuration . LargeImageSize ) . Url ,
mediumImageUrl = this . MakeImage ( release . RoadieId , "release" , this . Configuration . MediumImageSize ) . Url ,
smallImageUrl = this . MakeImage ( release . RoadieId , "release" , this . Configuration . SmallImageSize ) . Url ,
lastFmUrl = this . MakeLastFmUrl ( release . Artist . Name , release . Title ) ,
musicBrainzId = release . MusicBrainzId ,
notes = release . Profile
}
}
} ;
default :
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion , $"Unknown Album Info Version [{ request.Type}]" ) ;
}
}
2018-11-21 06:34:53 +00:00
/// <summary>
/// Returns a list of random, newest, highest rated etc. albums. Similar to the album lists on the home page of the Subsonic web interface.
/// </summary>
2018-11-22 13:48:32 +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
{
2018-11-21 06:34:53 +00:00
var releaseResult = new Library . Models . Pagination . PagedResult < ReleaseList > ( ) ;
switch ( request . Type )
{
case subsonic . ListType . Random :
releaseResult = await this . ReleaseService . List ( roadieUser , request . PagedRequest , true ) ;
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 :
releaseResult = await this . ReleaseService . List ( roadieUser , request . PagedRequest ) ;
break ;
default :
2018-11-22 17:31:59 +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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . albumList ,
Item = new subsonic . AlbumList
{
album = this . SubsonicChildrenForReleases ( releaseResult . Rows , null )
}
}
} ;
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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . albumList2 ,
Item = new subsonic . AlbumList2
{
2018-11-22 23:12:57 +00:00
album = this . 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 :
2018-11-22 17:31:59 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion , $"Unknown AlbumListVersions [{ version }]" ) ;
}
}
/// <summary>
/// Returns details for an artist, including a list of albums. This method organizes music according to ID3 tags.
/// </summary>
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetArtist ( subsonic . Request request , User roadieUser )
{
var artistId = SafeParser . ToGuid ( request . id ) ;
if ( ! artistId . HasValue )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{ request.id }]" ) ;
}
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 ;
var artistResult = await this . ArtistService . List ( roadieUser , pagedRequest ) ;
var artist = artistResult . Rows . Any ( ) ? artistResult . Rows . First ( ) : null ;
if ( artist = = null )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release [{ request.id }]" ) ;
2018-11-21 15:22:55 +00:00
}
2018-11-22 23:12:57 +00:00
var artistReleaseResult = await this . ReleaseService . List ( roadieUser , pagedRequest ) ;
2018-11-22 17:31:59 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . artist ,
2018-11-22 23:12:57 +00:00
Item = this . SubsonicArtistWithAlbumsID3ForArtist ( artist , this . SubsonicAlbumID3ForReleases ( artistReleaseResult . Rows ) )
2018-11-22 17:31:59 +00:00
}
} ;
2018-11-21 18:19:38 +00:00
}
/// <summary>
/// Returns artist info with biography, image URLs and similar artists, using data from last.fm.
/// </summary>
2018-11-22 13:48:32 +00:00
public async 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 )
2018-11-21 18:19:38 +00:00
{
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid ArtistId [{ request.id }]" ) ;
2018-11-21 18:19:38 +00:00
}
2018-11-22 17:31:59 +00:00
var artist = this . GetArtist ( artistId . Value ) ;
2018-11-21 18:19:38 +00:00
if ( artist = = null )
{
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid ArtistId [{ request.id }]" ) ;
2018-11-21 18:19:38 +00:00
}
2018-11-21 15:22:55 +00:00
2018-11-21 18:19:38 +00:00
switch ( version )
{
case subsonic . ArtistInfoVersion . One :
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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . artistInfo ,
Item = this . SubsonicArtistInfoForArtist ( artist )
}
} ;
case subsonic . ArtistInfoVersion . Two :
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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . artistInfo2 ,
Item = this . SubsonicArtistInfo2InfoForArtist ( artist )
}
} ;
default :
2018-11-22 13:48:32 +00:00
return 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>
/// Similar to getIndexes, but organizes music according to ID3 tags.
/// </summary>
2018-11-22 13:48:32 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetArtists ( subsonic . Request request , User roadieUser )
2018-11-21 18:19:38 +00:00
{
2018-11-25 20:43:52 +00:00
var cacheKey = $"urn:subsonic_artists:{ roadieUser.UserName }" ;
return await this . CacheManager . GetAsync < subsonic . SubsonicOperationResult < subsonic . Response > > ( cacheKey , async ( ) = >
2018-11-21 18:19:38 +00:00
{
2018-11-25 20:43:52 +00:00
return await this . 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>
/// Returns all bookmarks for this user. A bookmark is a position within a certain media file.
/// </summary>
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetBookmarks ( subsonic . Request request , User roadieUser )
{
var pagedRequest = request . PagedRequest ;
pagedRequest . Sort = "LastUpdated" ;
pagedRequest . Order = "DESC" ;
var userBookmarkResult = await this . BookmarkService . List ( roadieUser , pagedRequest , false , Library . Enums . BookmarkType . Track ) ;
pagedRequest . FilterToTrackIds = userBookmarkResult . Rows . Select ( x = > SafeParser . ToGuid ( x . Bookmark . Value ) ) . ToArray ( ) ;
2018-11-25 20:43:52 +00:00
var trackListResult = await this . TrackService . List ( pagedRequest , roadieUser ) ;
2018-11-25 16:57:17 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . bookmarks ,
Item = new subsonic . Bookmarks
{
bookmark = this . SubsonicBookmarksForBookmarks ( userBookmarkResult . Rows , trackListResult . Rows )
}
}
} ;
}
2018-11-19 23:51:58 +00:00
/// <summary>
2018-11-21 06:34:53 +00:00
/// Returns a cover art image.
2018-11-19 23:51:58 +00:00
/// </summary>
2018-11-22 13:48:32 +00:00
public async Task < subsonic . SubsonicFileOperationResult < Roadie . Library . Models . Image > > GetCoverArt ( subsonic . Request request , int? size )
2018-11-21 06:34:53 +00:00
{
var sw = Stopwatch . StartNew ( ) ;
2018-11-22 13:48:32 +00:00
var result = new subsonic . SubsonicFileOperationResult < Roadie . Library . Models . Image >
2018-11-21 06:34:53 +00:00
{
Data = new Roadie . Library . Models . Image ( )
} ;
if ( request . ArtistId ! = null )
{
2018-11-21 15:22:55 +00:00
var artistImage = await this . ImageService . ArtistImage ( request . ArtistId . Value , size , size ) ;
2018-11-21 06:34:53 +00:00
if ( ! artistImage . IsSuccess )
{
2018-11-22 13:48:32 +00:00
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 )
{
2018-11-21 15:22:55 +00:00
var trackimage = await this . ImageService . TrackImage ( request . TrackId . Value , size , size ) ;
2018-11-21 06:34:53 +00:00
if ( ! trackimage . IsSuccess )
{
2018-11-22 13:48:32 +00:00
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 )
{
2018-11-22 17:31:59 +00:00
var collectionImage = await this . ImageService . CollectionImage ( request . CollectionId . Value , size , size ) ;
if ( ! collectionImage . IsSuccess )
2018-11-21 06:34:53 +00:00
{
2018-11-22 17:31:59 +00:00
return collectionImage . Adapt < subsonic . SubsonicFileOperationResult < Image > > ( ) ;
2018-11-21 06:34:53 +00:00
}
2018-11-22 17:31:59 +00:00
result . Data . Bytes = collectionImage . Data . Bytes ;
2018-11-21 06:34:53 +00:00
}
else if ( request . ReleaseId ! = null )
{
2018-11-22 17:31:59 +00:00
var releaseimage = await this . ImageService . ReleaseImage ( request . ReleaseId . Value , size , size ) ;
if ( ! releaseimage . IsSuccess )
2018-11-21 06:34:53 +00:00
{
2018-11-22 17:31:59 +00:00
return releaseimage . Adapt < subsonic . SubsonicFileOperationResult < Image > > ( ) ;
2018-11-21 06:34:53 +00:00
}
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 )
{
2018-11-22 17:31:59 +00:00
var playlistImage = await this . ImageService . PlaylistImage ( request . PlaylistId . Value , size , size ) ;
if ( ! playlistImage . IsSuccess )
2018-11-21 06:34:53 +00:00
{
2018-11-22 17:31:59 +00:00
return playlistImage . Adapt < subsonic . SubsonicFileOperationResult < Image > > ( ) ;
2018-11-21 06:34:53 +00:00
}
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 ) )
{
var user = this . GetUser ( request . u ) ;
if ( user = = null )
{
2018-11-22 17:31:59 +00:00
return new subsonic . SubsonicFileOperationResult < Roadie . Library . Models . Image > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Username [{ request.u}]" ) ;
2018-11-21 06:34:53 +00:00
}
2018-11-22 17:31:59 +00:00
var userImage = await this . ImageService . UserImage ( user . RoadieId , size , size ) ;
if ( ! userImage . IsSuccess )
{
return userImage . Adapt < subsonic . SubsonicFileOperationResult < Image > > ( ) ;
}
result . Data . Bytes = userImage . Data . Bytes ;
2018-11-21 06:34:53 +00:00
}
result . IsSuccess = result . Data . Bytes ! = null ;
sw . Stop ( ) ;
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicFileOperationResult < Roadie . Library . Models . 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>
/// Returns all genres
/// </summary>
2018-11-22 13:48:32 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetGenres ( subsonic . Request request )
2018-11-19 23:51:58 +00:00
{
2018-11-21 06:34:53 +00:00
var genres = ( from g in this . DbContext . Genres
let albumCount = ( from rg in this . DbContext . ReleaseGenres
where rg . GenreId = = g . Id
select rg . Id ) . Count ( )
let songCount = ( from rg in this . DbContext . ReleaseGenres
join rm in this . DbContext . ReleaseMedias on rg . ReleaseId equals rm . ReleaseId
join t in this . DbContext . Tracks on rm . ReleaseId equals t . ReleaseMediaId
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-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
{
version = SubsonicService . SubsonicVersion ,
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
}
}
} ;
}
/// <summary>
/// Returns an indexed structure of all artists.
/// </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>
/// <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>
2018-11-22 13:48:32 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetIndexes ( subsonic . Request request , User roadieUser , long? ifModifiedSince = null )
2018-11-22 23:12:57 +00:00
{
var cacheKey = string . Format ( "urn:subsonic_indexes" ) ;
return await this . CacheManager . GetAsync < subsonic . SubsonicOperationResult < subsonic . Response > > ( cacheKey , async ( ) = >
{
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
return await this . GetIndexesAction ( request , null , ifModifiedSince ) ;
2018-11-22 23:12:57 +00:00
} , CacheManagerBase . SystemCacheRegionUrn ) ;
}
2018-11-21 18:19:38 +00:00
/// <summary>
/// Get details about the software license. Takes no extra parameters. Roadies gives everyone a premium 1 year license everytime they ask :)
/// </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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . license ,
Item = new subsonic . License
{
email = this . Configuration . SmtpFromAddress ,
valid = true ,
licenseExpires = DateTime . UtcNow . AddYears ( 1 ) ,
licenseExpiresSpecified = true
}
}
} ;
}
2018-11-22 17:31:59 +00:00
/// <summary>
/// Searches for and returns lyrics for a given song
/// </summary>
public subsonic . SubsonicOperationResult < subsonic . Response > GetLyrics ( subsonic . Request request , string artistId , string title )
{
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . lyrics ,
Item = new subsonic . Lyrics
{
artist = artistId ,
title = title ,
Text = new string [ 0 ]
}
}
} ;
}
2018-11-21 06:34:53 +00:00
/// <summary>
/// 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.
/// </summary>
/// <param name="request">Query from application.</param>
/// <param name="id">A string which uniquely identifies the music folder. Obtained by calls to getIndexes or getMusicDirectory.</param>
/// <returns></returns>
2018-11-22 13:48:32 +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 ( ) ;
var user = this . GetUser ( roadieUser ? . UserId ) ;
// 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 ) ;
var artist = this . GetArtist ( artistId . Value ) ;
2018-11-21 06:34:53 +00:00
if ( artist = = null )
{
2018-11-22 17:31:59 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid ArtistId [{ request.id}]" ) ;
2018-11-21 06:34:53 +00:00
}
directory . id = subsonic . Request . ArtistIdIdentifier + artist . RoadieId . ToString ( ) ;
directory . name = artist . Name ;
var artistRating = user ? . ArtistRatings ? . FirstOrDefault ( x = > x . ArtistId = = artist . Id ) ;
if ( artistRating ? . IsFavorite ? ? false )
{
directory . starred = ( artistRating . LastUpdated ? ? artistRating . CreatedDate ) ;
directory . starredSpecified = true ;
}
var pagedRequest = request . PagedRequest ;
pagedRequest . FilterToArtistId = artist . RoadieId ;
2018-11-22 23:12:57 +00:00
pagedRequest . Sort = "Release.Text" ;
2018-11-21 06:34:53 +00:00
var artistReleases = await this . ReleaseService . List ( roadieUser , pagedRequest ) ;
directory . child = this . SubsonicChildrenForReleases ( artistReleases . Rows , subsonic . Request . ArtistIdIdentifier + artist . RoadieId . ToString ( ) ) ;
}
// 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 ) ;
var collection = this . GetCollection ( collectionId . Value ) ;
2018-11-21 06:34:53 +00:00
if ( collection = = null )
{
2018-11-22 17:31:59 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid CollectionId [{ request.id}]" ) ;
2018-11-21 06:34:53 +00:00
}
directory . id = subsonic . Request . CollectionIdentifier + collection . RoadieId . ToString ( ) ;
directory . name = collection . Name ;
var pagedRequest = request . PagedRequest ;
pagedRequest . FilterToCollectionId = collection . RoadieId ;
var collectionReleases = await this . ReleaseService . List ( roadieUser , pagedRequest ) ;
directory . child = this . SubsonicChildrenForReleases ( collectionReleases . Rows , subsonic . Request . CollectionIdentifier + collection . RoadieId . ToString ( ) ) ;
}
// 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 ) ;
var release = this . GetRelease ( releaseId . Value ) ;
2018-11-21 06:34:53 +00:00
if ( release = = null )
{
2018-11-22 17:31:59 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid ReleaseId [{ request.id}]" ) ;
2018-11-21 06:34:53 +00:00
}
directory . id = subsonic . Request . ReleaseIdIdentifier + release . RoadieId . ToString ( ) ;
directory . name = release . Title ;
var releaseRating = user ? . ReleaseRatings ? . FirstOrDefault ( x = > x . ReleaseId = = release . Id ) ;
directory . averageRating = release . Rating ? ? 0 ;
directory . parent = subsonic . Request . ArtistIdIdentifier + release . Artist . RoadieId . ToString ( ) ;
if ( releaseRating ? . IsFavorite ? ? false )
{
directory . starred = releaseRating . LastUpdated ? ? releaseRating . CreatedDate ;
directory . starredSpecified = true ;
}
var pagedRequest = request . PagedRequest ;
2018-11-25 20:43:52 +00:00
var songTracks = await this . TrackService . List ( pagedRequest , roadieUser , false , release . RoadieId ) ;
2018-11-21 06:34:53 +00:00
directory . child = this . SubsonicChildrenForTracks ( songTracks . Rows ) ;
directory . playCount = directory . child . Select ( x = > x . playCount ) . Sum ( ) ;
}
else
{
2018-11-22 17:31:59 +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
}
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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . directory ,
Item = directory
}
} ;
}
/// <summary>
/// Returns all configured top-level music folders. Takes no extra parameters.
/// </summary>
2018-11-22 13:48:32 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetMusicFolders ( subsonic . Request request )
2018-11-21 06:34:53 +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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . musicFolders ,
Item = new subsonic . MusicFolders
{
musicFolder = this . MusicFolders ( ) . ToArray ( )
}
}
} ;
}
2018-11-21 18:19:38 +00:00
/// <summary>
/// Returns a listing of files in a saved playlist.
/// </summary>
2018-11-22 13:48:32 +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 )
2018-11-21 18:19:38 +00:00
{
2018-11-22 13:48:32 +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 ;
2018-11-21 18:19:38 +00:00
var playlistResult = await this . PlaylistService . List ( pagedRequest , roadieUser ) ;
var playlist = playlistResult . Rows . Any ( ) ? playlistResult . Rows . First ( ) : null ;
if ( playlist = = null )
{
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid PlaylistId [{ request.id }]" ) ;
2018-11-21 18:19:38 +00:00
}
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 ;
2018-11-25 20:43:52 +00:00
var tracksForPlaylist = await this . 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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . playlist ,
Item = this . SubsonicPlaylistForPlaylist ( playlist , tracksForPlaylist . Rows )
}
} ;
}
2018-11-21 06:34:53 +00:00
/// <summary>
/// Returns all playlists a user is allowed to play.
/// </summary>
2018-11-22 13:48:32 +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" ;
var playlistResult = await this . PlaylistService . List ( pagedRequest , roadieUser ) ;
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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . playlists ,
Item = new subsonic . Playlists
{
2018-11-24 01:46:12 +00:00
playlist = this . SubsonicPlaylistsForPlaylists ( playlistResult . Rows )
2018-11-19 23:51:58 +00:00
}
}
} ;
}
2018-11-21 06:34:53 +00:00
/// <summary>
/// Returns all Podcast channels the server subscribes to, and (optionally) their episodes. This method can also be used to return details for only one channel - refer to the id parameter. A typical use case for this method would be to first retrieve all channels without episodes, and then retrieve all episodes for the single channel the user selects.
/// </summary>
2018-11-22 13:48:32 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetPodcasts ( subsonic . Request request )
2018-11-19 23:51:58 +00:00
{
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
2018-11-20 04:47:12 +00:00
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
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-11-19 23:51:58 +00:00
}
2018-11-21 06:34:53 +00:00
/// <summary>
2018-11-21 18:19:38 +00:00
/// Returns random songs matching the given criteria.
2018-11-21 06:34:53 +00:00
/// </summary>
2018-11-22 13:48:32 +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 > ( ) ;
2018-11-25 20:43:52 +00:00
var randomSongs = await this . 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
{
version = SubsonicService . SubsonicVersion ,
2018-11-21 18:19:38 +00:00
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . randomSongs ,
Item = new subsonic . Songs
{
song = this . SubsonicChildrenForTracks ( randomSongs . Rows )
}
2018-11-19 23:51:58 +00:00
}
} ;
}
2018-11-20 04:47:12 +00:00
/// <summary>
2018-11-22 17:31:59 +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>
2018-11-22 17:31:59 +00:00
public async 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
{
2018-11-22 17:31:59 +00:00
// TODO How to determine similiar 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-11-22 13:48:32 +00:00
return 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
{
version = SubsonicService . SubsonicVersion ,
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-11-24 17:52:15 +00:00
2018-11-22 17:31:59 +00:00
case subsonic . SimilarSongsVersion . Two :
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
{
version = SubsonicService . SubsonicVersion ,
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
}
}
} ;
default :
2018-11-22 17:31:59 +00:00
return 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>
2018-11-22 17:31:59 +00:00
/// Returns details for a song.
2018-11-21 18:19:38 +00:00
/// </summary>
2018-11-22 17:31:59 +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 )
2018-11-20 04:47:12 +00:00
{
2018-11-22 17:31:59 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track [{ request.id }]" ) ;
}
var pagedRequest = request . PagedRequest ;
pagedRequest . FilterToTrackId = request . TrackId . Value ;
2018-11-22 23:12:57 +00:00
pagedRequest . Sort = "Id" ;
2018-11-25 20:43:52 +00:00
var trackResult = await this . TrackService . List ( pagedRequest , roadieUser ) ;
2018-11-22 17:31:59 +00:00
var track = trackResult . Rows . Any ( ) ? trackResult . Rows . First ( ) : null ;
if ( track = = null )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track [{ request.id }]" ) ;
2018-11-20 04:47:12 +00:00
}
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
{
2018-11-21 06:34:53 +00:00
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
2018-11-22 17:31:59 +00:00
ItemElementName = subsonic . ItemChoiceType . song ,
Item = this . 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>
2018-11-22 17:31:59 +00:00
/// Returns songs in a given genre.
2018-11-21 15:22:55 +00:00
/// </summary>
2018-11-23 04:18:48 +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" ;
2018-11-25 20:43:52 +00:00
var trackResult = await this . TrackService . List ( pagedRequest , roadieUser ) ;
2018-11-23 04:18:48 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . songsByGenre ,
Item = new subsonic . Songs
{
2018-11-24 17:52:15 +00:00
song = this . 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>
2018-11-22 17:31:59 +00:00
/// Returns starred songs, albums and artists.
2018-11-21 06:34:53 +00:00
/// </summary>
2018-11-22 17:31:59 +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
2018-11-22 17:31:59 +00:00
var artistList = await this . ArtistService . List ( roadieUser , pagedRequest ) ;
var releaseList = await this . ReleaseService . List ( roadieUser , pagedRequest ) ;
2018-11-25 20:43:52 +00:00
var songList = await this . 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
{
version = SubsonicService . SubsonicVersion ,
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
{
2018-11-22 17:31:59 +00:00
album = this . SubsonicChildrenForReleases ( releaseList . Rows , null ) ,
artist = this . SubsonicArtistsForArtists ( artistList . Rows ) ,
song = this . 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
{
version = SubsonicService . SubsonicVersion ,
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
{
2018-11-22 23:12:57 +00:00
album = this . SubsonicAlbumID3ForReleases ( releaseList . Rows ) ,
2018-11-22 17:31:59 +00:00
artist = this . SubsonicArtistID3sForArtists ( artistList . Rows ) ,
song = this . SubsonicChildrenForTracks ( songList . Rows )
2018-11-22 13:48:32 +00:00
}
}
} ;
default :
2018-11-22 17:31:59 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion , $"Unknown StarredVersion [{ version }]" ) ;
2018-11-22 13:48:32 +00:00
}
}
/// <summary>
2018-11-22 17:31:59 +00:00
/// Returns top songs for the given artist, using data from last.fm.
2018-11-22 13:48:32 +00:00
/// </summary>
2018-11-25 16:57:17 +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 ) ;
}
2018-11-25 20:43:52 +00:00
else if ( request . ArtistId . HasValue )
2018-11-25 16:57:17 +00:00
{
artist = this . GetArtist ( request . ArtistId . Value ) ;
}
2018-11-24 17:52:15 +00:00
if ( artist = = null )
2018-11-22 13:48:32 +00:00
{
2018-11-25 16:57:17 +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" ;
2018-11-25 20:43:52 +00:00
var trackResult = await this . TrackService . List ( pagedRequest , roadieUser ) ;
2018-11-22 13:48:32 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
2018-11-22 17:31:59 +00:00
ItemElementName = subsonic . ItemChoiceType . topSongs ,
Item = new subsonic . TopSongs
{
song = this . SubsonicChildrenForTracks ( trackResult . Rows )
}
2018-11-22 13:48:32 +00:00
}
} ;
}
/// <summary>
2018-11-22 17:31:59 +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>
2018-11-22 17:31:59 +00:00
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetUser ( subsonic . Request request , string username )
2018-11-22 13:48:32 +00:00
{
2018-11-22 17:31:59 +00:00
var user = this . GetUser ( username ) ;
if ( user = = null )
2018-11-22 13:48:32 +00:00
{
2018-11-22 17:31:59 +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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
2018-11-22 17:31:59 +00:00
ItemElementName = subsonic . ItemChoiceType . user ,
Item = await this . SubsonicUserForUser ( user )
2018-11-22 13:48:32 +00:00
}
} ;
}
/// <summary>
/// Returns all video files.
/// </summary>
public subsonic . SubsonicOperationResult < subsonic . Response > GetVideos ( subsonic . Request request )
{
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . videos ,
Item = new subsonic . Videos
{
2018-11-22 17:31:59 +00:00
video = new subsonic . Child [ 0 ]
2018-11-22 13:48:32 +00:00
}
}
} ;
}
/// <summary>
2018-11-22 17:31:59 +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
{
version = SubsonicService . SubsonicVersion ,
2018-11-22 17:31:59 +00:00
status = subsonic . ResponseStatus . ok
2018-11-22 13:48:32 +00:00
}
} ;
}
/// <summary>
2018-11-22 17:31:59 +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>
2018-11-22 17:31:59 +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
{
2018-11-22 17:31:59 +00:00
var query = this . HttpEncoder . UrlDecode ( request . Query ) . Replace ( "*" , "" ) . Replace ( "%" , "" ) . Replace ( ";" , "" ) ;
// 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 ;
var artistResult = await this . ArtistService . List ( roadieUser , artistPagedRequest ) ;
// 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 ;
var releaseResult = await this . ReleaseService . List ( roadieUser , releasePagedRequest ) ;
// 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 ;
2018-11-25 20:43:52 +00:00
var songResult = await this . TrackService . List ( trackPagedRequest , roadieUser ) ;
2018-11-22 17:31:59 +00:00
var songs = this . SubsonicChildrenForTracks ( songResult . Rows ) ;
switch ( version )
{
case subsonic . SearchVersion . One :
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . IncompatibleClientRestProtocolVersion , "Deprecated since 1.4.0, use search2 instead." ) ;
case subsonic . SearchVersion . Two :
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . searchResult2 ,
Item = new subsonic . SearchResult2
{
artist = this . SubsonicArtistsForArtists ( artistResult . Rows ) ,
album = this . SubsonicChildrenForReleases ( releaseResult . Rows , null ) ,
song = songs . ToArray ( )
}
}
} ;
case subsonic . SearchVersion . Three :
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . searchResult3 ,
Item = new subsonic . SearchResult3
{
artist = this . SubsonicArtistID3sForArtists ( artistResult . Rows ) ,
2018-11-22 23:12:57 +00:00
album = this . SubsonicAlbumID3ForReleases ( releaseResult . Rows ) ,
2018-11-22 17:31:59 +00:00
song = songs . ToArray ( )
}
}
} ;
default :
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . IncompatibleServerRestProtocolVersion , $"Unknown SearchVersion [{ version }]" ) ;
}
2018-11-22 13:48:32 +00:00
}
2018-11-24 17:52:15 +00:00
/// <summary>
/// Sets the rating for a music file. If rating is zero then remove rating.
/// </summary>
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > SetRating ( subsonic . Request request , User roadieUser , short rating )
{
var user = this . GetUser ( roadieUser . UserId ) ;
if ( user = = null )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . UserIsNotAuthorizedForGivenOperation , $"Invalid User [{ roadieUser }]" ) ;
}
// Id can be a song, album or artist
if ( request . TrackId . HasValue )
{
var starResult = await this . SetTrackRating ( request . TrackId . Value , user , rating ) ;
if ( starResult . IsSuccess )
{
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response ( )
} ;
}
}
else if ( request . ReleaseId . HasValue )
{
var starResult = await this . SetReleaseRating ( request . ReleaseId . Value , user , rating ) ;
if ( starResult . IsSuccess )
{
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response ( )
} ;
}
}
else if ( request . ArtistId . HasValue )
{
var starResult = await this . SetArtistRating ( request . ArtistId . Value , user , rating ) ;
if ( starResult . IsSuccess )
{
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response ( )
} ;
}
}
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Unknown Star Id [{ JsonConvert.SerializeObject(request) }]" ) ;
}
2018-11-22 13:48:32 +00:00
/// <summary>
2018-11-22 17:31:59 +00:00
/// Attaches a star to a song, album or artist.
2018-11-22 13:48:32 +00:00
/// </summary>
2018-11-24 04:06:59 +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
{
2018-11-24 04:06:59 +00:00
var user = this . GetUser ( roadieUser . UserId ) ;
if ( user = = null )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . UserIsNotAuthorizedForGivenOperation , $"Invalid User [{ roadieUser }]" ) ;
}
// Id can be a song, album or artist
if ( request . TrackId . HasValue )
{
var starResult = await this . 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
{
var starResult = await this . ToggleReleaseStar ( request . ReleaseId . Value , user , star ) ;
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
{
var starResult = await this . ToggleArtistStar ( request . ArtistId . Value , user , star ) ;
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
{
var starResult = await this . ToggleReleaseStar ( releaseId . Value , user , star ) ;
if ( ! starResult . IsSuccess )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( starResult . ErrorCode . Value , starResult . Messages . FirstOrDefault ( ) ) ;
}
}
}
}
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 )
{
var starResult = await this . ToggleReleaseStar ( artistId . Value , user , star ) ;
if ( ! starResult . IsNotFoundResult )
{
return new subsonic . SubsonicOperationResult < subsonic . Response > ( starResult . ErrorCode . Value , starResult . Messages . FirstOrDefault ( ) ) ;
}
}
}
}
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Unknown Star Id [{ JsonConvert.SerializeObject(request) }]" ) ;
}
2018-11-24 17:52:15 +00:00
/// <summary>
2018-11-25 16:57:17 +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>
2018-11-25 20:43:52 +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 )
2018-11-24 04:06:59 +00:00
{
2018-11-25 16:57:17 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Playlist Id [{ request.id }]" ) ;
2018-11-24 04:06:59 +00:00
}
2018-11-25 16:57:17 +00:00
var playlist = this . GetPlaylist ( request . PlaylistId . Value ) ;
if ( playlist = = null )
2018-11-24 04:06:59 +00:00
{
2018-11-25 16:57:17 +00:00
return new subsonic . SubsonicOperationResult < subsonic . Response > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Playlist Id [{ request.TrackId.Value }]" ) ;
2018-11-24 04:06:59 +00:00
}
2018-11-25 16:57:17 +00:00
if ( playlist . UserId ! = roadieUser . Id & & ! roadieUser . IsAdmin )
2018-11-24 04:06:59 +00:00
{
2018-11-25 16:57:17 +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 ( ) ;
var submittedTracks = ( from t in this . 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 )
{
if ( playlist . Tracks = = null | | playlist . Tracks = = null | | ! playlist . Tracks . Any ( x = > x . TrackId = = submittedTrack . Id ) )
{
listNumber + + ;
this . DbContext . PlaylistTracks . Add ( new data . PlaylistTrack
{
PlayListId = playlist . Id ,
ListNumber = listNumber ,
TrackId = submittedTrack . Id
} ) ;
}
}
}
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.
throw new NotImplementedException ( $"Request [{ JsonConvert.SerializeObject(request) }]" ) ;
2018-11-24 17:52:15 +00:00
}
2018-11-25 16:57:17 +00:00
await this . DbContext . SaveChangesAsync ( ) ;
var user = this . GetUser ( roadieUser . UserId ) ;
this . CacheManager . ClearRegion ( user . CacheRegion ) ;
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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok
}
2018-11-24 04:06:59 +00:00
} ;
}
2018-11-24 17:52:15 +00:00
2018-11-25 20:43:52 +00:00
/// <summary>
/// Returns what is currently being played by all users. Takes no extra parameters.
/// </summary>
public async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetNowPlaying ( subsonic . Request request , User roadieUser )
{
var pagedRequest = request . PagedRequest ;
pagedRequest . Sort = "PlayedDateDateTime" ;
pagedRequest . Order = "DESC" ;
var playActivityResult = await this . PlayActivityService . List ( pagedRequest , roadieUser , DateTime . UtcNow . AddDays ( - 1 ) ) ;
pagedRequest . Sort = null ;
pagedRequest . Order = null ;
pagedRequest . FilterToTrackIds = playActivityResult . Rows . Select ( x = > SafeParser . ToGuid ( x . Track . Value ) ) . Distinct ( ) . ToArray ( ) ;
var playActivityTracksResult = await this . TrackService . List ( pagedRequest , roadieUser ) ;
var playEntries = new List < subsonic . NowPlayingEntry > ( ) ;
var now = DateTime . UtcNow ;
foreach ( var row in playActivityResult . Rows )
{
var rowTrack = playActivityTracksResult . Rows . FirstOrDefault ( x = > x . Track . Value = = row . Track . Value ) ;
var playEntryTrackChild = this . SubsonicChildForTrack ( rowTrack ) ;
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
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . nowPlaying ,
Item = new subsonic . NowPlaying
{
entry = playEntries . ToArray ( )
}
}
} ;
}
2018-11-24 17:52:15 +00:00
#region Privates
2018-11-25 20:43:52 +00:00
private async Task < subsonic . SubsonicOperationResult < subsonic . Response > > GetArtistsAction ( subsonic . Request request , User roadieUser )
{
var indexes = new List < subsonic . IndexID3 > ( ) ;
var musicFolder = this . MusicFolders ( ) . FirstOrDefault ( x = > x . id = = ( request . MusicFolderId ? ? 2 ) ) ;
var pagedRequest = request . PagedRequest ;
if ( musicFolder = = this . CollectionMusicFolder ( ) )
{
// Indexes for "Collection" Artists alphabetically
}
else
{
// Indexes for "Music" Artists alphabetically
pagedRequest . SkipValue = 0 ;
pagedRequest . Limit = int . MaxValue ;
pagedRequest . Sort = "Artist.Text" ;
var artistList = await this . ArtistService . List ( roadieUser : roadieUser ,
request : pagedRequest ,
doRandomize : false ,
onlyIncludeWithReleases : true ,
doArtistCounts : true ) ;
foreach ( var artistGroup in artistList . Rows . GroupBy ( x = > x . Artist . Text . Substring ( 0 , 1 ) ) )
{
indexes . Add ( new subsonic . IndexID3
{
name = artistGroup . Key ,
artist = this . SubsonicArtistID3sForArtists ( artistGroup )
} ) ;
} ;
}
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
status = subsonic . ResponseStatus . ok ,
ItemElementName = subsonic . ItemChoiceType . artists ,
Item = new subsonic . ArtistsID3
{
index = indexes . ToArray ( )
}
}
} ;
}
2018-11-25 16:57:17 +00:00
private string [ ] AllowedUsers ( )
{
return this . CacheManager . Get < string [ ] > ( CacheManagerBase . SystemCacheRegionUrn + ":active_usernames" , ( ) = >
{
return this . DbContext . Users . Where ( x = > x . IsActive ? ? false ) . Select ( x = > x . UserName ) . ToArray ( ) ;
} , CacheManagerBase . SystemCacheRegionUrn ) ;
}
private subsonic . MusicFolder CollectionMusicFolder ( )
{
return this . MusicFolders ( ) . First ( x = > x . id = = 1 ) ;
}
2018-11-24 17:52:15 +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
{
2018-11-24 17:52:15 +00:00
var modifiedSinceFilter = ifModifiedSince . HasValue ? ( DateTime ? ) ifModifiedSince . Value . FromUnixTime ( ) : null ;
subsonic . MusicFolder musicFolderFilter = ! request . MusicFolderId . HasValue ? new subsonic . MusicFolder ( ) : this . MusicFolders ( ) . FirstOrDefault ( x = > x . id = = request . MusicFolderId . Value ) ;
var indexes = new List < subsonic . Index > ( ) ;
if ( musicFolderFilter . id = = this . CollectionMusicFolder ( ) . id )
2018-11-24 04:06:59 +00:00
{
2018-11-24 17:52:15 +00:00
// Collections for Music Folders by Alpha First
foreach ( var collectionFirstLetter in ( from c in this . DbContext . Collections
let first = c . Name . Substring ( 0 , 1 )
orderby first
select first ) . Distinct ( ) . ToArray ( ) )
{
indexes . Add ( new subsonic . Index
{
name = collectionFirstLetter ,
artist = ( from c in this . DbContext . Collections
where c . Name . Substring ( 0 , 1 ) = = collectionFirstLetter
where modifiedSinceFilter = = null | | c . LastUpdated > = modifiedSinceFilter
orderby c . SortName , c . Name
select new subsonic . Artist
{
id = subsonic . Request . CollectionIdentifier + c . RoadieId . ToString ( ) ,
name = c . Name ,
artistImageUrl = this . MakeCollectionThumbnailImage ( c . RoadieId ) . Url ,
averageRating = 0 ,
userRating = 0
} ) . ToArray ( )
} ) ;
}
}
else
{
// Indexes for Artists alphabetically
var pagedRequest = request . PagedRequest ;
pagedRequest . SkipValue = 0 ;
pagedRequest . Limit = int . MaxValue ;
pagedRequest . Sort = "Artist.Text" ;
2018-11-25 20:43:52 +00:00
var artistList = await this . ArtistService . List ( roadieUser : roadieUser ,
2018-11-25 16:57:17 +00:00
request : pagedRequest ,
2018-11-25 20:43:52 +00:00
doRandomize : false ,
2018-11-25 16:57:17 +00:00
onlyIncludeWithReleases : true ,
doArtistCounts : false ) ;
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 ,
2018-11-25 20:43:52 +00:00
artist = this . SubsonicArtistsForArtists ( artistGroup )
2018-11-24 17:52:15 +00:00
} ) ;
} ;
}
return new subsonic . SubsonicOperationResult < subsonic . Response >
{
IsSuccess = true ,
Data = new subsonic . Response
{
version = SubsonicService . SubsonicVersion ,
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 >
{
new subsonic . MusicFolder { id = 1 , name = "Collections" } ,
new subsonic . MusicFolder { id = 2 , name = "Music" }
} ;
}
private subsonic . MusicFolder MusicMusicFolder ( )
{
return this . MusicFolders ( ) . First ( x = > x . id = = 2 ) ;
}
2018-11-24 17:52:15 +00:00
private async Task < subsonic . SubsonicOperationResult < bool > > SetArtistRating ( Guid artistId , ApplicationUser user , short rating )
{
var artist = this . GetArtist ( artistId ) ;
if ( artist = = null )
{
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Artist Id [{ artistId }]" ) ;
}
var userArtist = user . ArtistRatings . FirstOrDefault ( x = > x . ArtistId = = artist . Id ) ;
if ( userArtist = = null )
{
userArtist = new data . UserArtist
2018-11-24 04:06:59 +00:00
{
Rating = rating ,
UserId = user . Id ,
2018-11-24 17:52:15 +00:00
ArtistId = artist . Id
2018-11-24 04:06:59 +00:00
} ;
2018-11-24 17:52:15 +00:00
this . DbContext . UserArtists . Add ( userArtist ) ;
2018-11-24 04:06:59 +00:00
}
else
{
2018-11-24 17:52:15 +00:00
userArtist . Rating = rating ;
userArtist . LastUpdated = DateTime . UtcNow ;
2018-11-24 04:06:59 +00:00
}
await this . DbContext . SaveChangesAsync ( ) ;
this . CacheManager . ClearRegion ( user . CacheRegion ) ;
2018-11-24 17:52:15 +00:00
this . CacheManager . ClearRegion ( artist . CacheRegion ) ;
2018-11-24 04:06:59 +00:00
return new subsonic . SubsonicOperationResult < bool >
{
IsSuccess = true ,
Data = true
} ;
}
2018-11-24 17:52:15 +00:00
2018-11-24 04:06:59 +00:00
private async Task < subsonic . SubsonicOperationResult < bool > > SetReleaseRating ( Guid releaseId , ApplicationUser user , short rating )
{
var release = this . GetRelease ( releaseId ) ;
if ( release = = null )
{
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release Id [{ releaseId }]" ) ;
}
var userRelease = user . ReleaseRatings . FirstOrDefault ( x = > x . ReleaseId = = release . Id ) ;
if ( userRelease = = null )
{
userRelease = new data . UserRelease
{
Rating = rating ,
UserId = user . Id ,
ReleaseId = release . Id
} ;
this . DbContext . UserReleases . Add ( userRelease ) ;
}
else
{
userRelease . Rating = rating ;
userRelease . LastUpdated = DateTime . UtcNow ;
}
await this . DbContext . SaveChangesAsync ( ) ;
this . CacheManager . ClearRegion ( user . CacheRegion ) ;
this . CacheManager . ClearRegion ( release . CacheRegion ) ;
this . CacheManager . ClearRegion ( release . Artist . CacheRegion ) ;
return new subsonic . SubsonicOperationResult < bool >
{
IsSuccess = true ,
Data = true
} ;
}
2018-11-24 17:52:15 +00:00
private async Task < subsonic . SubsonicOperationResult < bool > > SetTrackRating ( Guid trackId , ApplicationUser user , short rating )
{
var track = this . GetTrack ( trackId ) ;
if ( track = = null )
{
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{ trackId }]" ) ;
}
var userTrack = user . TrackRatings . FirstOrDefault ( x = > x . TrackId = = track . Id ) ;
if ( userTrack = = null )
{
userTrack = new data . UserTrack
{
Rating = rating ,
UserId = user . Id ,
TrackId = track . Id
} ;
this . DbContext . UserTracks . Add ( userTrack ) ;
}
else
{
userTrack . Rating = rating ;
userTrack . LastUpdated = DateTime . UtcNow ;
}
await this . DbContext . SaveChangesAsync ( ) ;
this . CacheManager . ClearRegion ( user . CacheRegion ) ;
this . CacheManager . ClearRegion ( track . CacheRegion ) ;
this . CacheManager . ClearRegion ( track . ReleaseMedia . Release . CacheRegion ) ;
this . CacheManager . ClearRegion ( track . ReleaseMedia . Release . Artist . CacheRegion ) ;
return new subsonic . SubsonicOperationResult < bool >
{
IsSuccess = true ,
Data = true
} ;
}
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
{
id = subsonic . Request . ReleaseIdIdentifier + r . Id . ToString ( ) ,
artistId = r . Artist . Value ,
name = r . Release . Text ,
songCount = r . TrackCount ? ? 0 ,
duration = r . Duration . ToSecondsFromMilliseconds ( ) ,
artist = r . Artist . Text ,
coverArt = subsonic . Request . ReleaseIdIdentifier + r . Id . ToString ( ) ,
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
{
if ( r = = null | | ! r . Any ( ) )
{
return new subsonic . AlbumID3 [ 0 ] ;
}
2018-11-22 23:12:57 +00:00
return r . Select ( x = > this . 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
{
2018-11-21 06:34:53 +00:00
id = subsonic . Request . ArtistIdIdentifier + artist . Artist . Value . ToString ( ) ,
name = artist . Artist . Text ,
artistImageUrl = this . MakeArtistThumbnailImage ( artist . Id ) . Url ,
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 )
{
var artistImageUrl = this . MakeArtistThumbnailImage ( artist . Id ) . Url ;
return new subsonic . ArtistID3
{
id = subsonic . Request . ArtistIdIdentifier + artist . Artist . Value . ToString ( ) ,
name = artist . Artist . Text ,
albumCount = artist . ArtistReleaseCount ? ? 0 ,
coverArt = artistImageUrl ,
artistImageUrl = artistImageUrl ,
starred = artist . UserRating ? . RatedDate ? ? DateTime . UtcNow ,
starredSpecified = artist . UserRating ? . IsFavorite ? ? false
} ;
}
private subsonic . ArtistID3 [ ] SubsonicArtistID3sForArtists ( IEnumerable < ArtistList > artists )
{
if ( artists = = null | | ! artists . Any ( ) )
{
return new subsonic . ArtistID3 [ 0 ] ;
}
return artists . Select ( x = > this . SubsonicArtistID3ForArtist ( x ) ) . ToArray ( ) ;
}
2018-11-22 17:31:59 +00:00
private subsonic . ArtistInfo2 SubsonicArtistInfo2InfoForArtist ( data . Artist artist )
{
return new subsonic . ArtistInfo2
{
biography = artist . BioContext ,
largeImageUrl = this . MakeImage ( artist . RoadieId , "artist" , this . Configuration . LargeImageSize ) . Url ,
mediumImageUrl = this . MakeImage ( artist . RoadieId , "artist" , this . Configuration . MediumImageSize ) . Url ,
musicBrainzId = artist . MusicBrainzId ,
similarArtist = new subsonic . ArtistID3 [ 0 ] ,
smallImageUrl = this . MakeImage ( artist . RoadieId , "artist" , this . Configuration . SmallImageSize ) . Url
} ;
}
private subsonic . ArtistInfo SubsonicArtistInfoForArtist ( data . Artist artist )
{
return new subsonic . ArtistInfo
{
biography = artist . BioContext ,
largeImageUrl = this . MakeImage ( artist . RoadieId , "artist" , this . Configuration . LargeImageSize ) . Url ,
mediumImageUrl = this . MakeImage ( artist . RoadieId , "artist" , this . Configuration . MediumImageSize ) . Url ,
musicBrainzId = artist . MusicBrainzId ,
similarArtist = new subsonic . Artist [ 0 ] ,
smallImageUrl = this . MakeImage ( artist . RoadieId , "artist" , this . Configuration . SmallImageSize ) . Url
} ;
}
2018-11-21 06:34:53 +00:00
private subsonic . Artist [ ] SubsonicArtistsForArtists ( IEnumerable < ArtistList > artists )
{
if ( artists = = null | | ! artists . Any ( ) )
2018-11-20 14:36:07 +00:00
{
2018-11-21 06:34:53 +00:00
return new subsonic . Artist [ 0 ] ;
2018-11-20 14:36:07 +00:00
}
2018-11-21 06:34:53 +00:00
return artists . Select ( x = > this . SubsonicArtistForArtist ( x ) ) . ToArray ( ) ;
}
2018-11-20 14:36:07 +00:00
2018-11-24 17:52:15 +00:00
private subsonic . ArtistWithAlbumsID3 SubsonicArtistWithAlbumsID3ForArtist ( ArtistList artist , subsonic . AlbumID3 [ ] releases )
{
var artistImageUrl = this . MakeArtistThumbnailImage ( artist . Id ) . Url ;
return new subsonic . ArtistWithAlbumsID3
{
id = subsonic . Request . ArtistIdIdentifier + artist . Artist . Value . ToString ( ) ,
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
} ;
}
private subsonic . Bookmark [ ] SubsonicBookmarksForBookmarks ( IEnumerable < BookmarkList > bb , IEnumerable < TrackList > childTracks )
{
if ( bb = = null | | ! bb . Any ( ) )
{
return new subsonic . Bookmark [ 0 ] ;
}
var result = new List < subsonic . Bookmark > ( ) ;
foreach ( var bookmark in bb )
{
subsonic . Child child = null ;
switch ( bookmark . Type . Value )
{
case Library . Enums . BookmarkType . Track :
child = this . SubsonicChildForTrack ( childTracks . FirstOrDefault ( x = > x . Id = = SafeParser . ToGuid ( bookmark . Bookmark . Value ) ) ) ;
break ;
default :
throw new NotImplementedException ( "Wrong Bookmark type to convert to Subsonic media Bookmark" ) ;
}
result . Add ( this . SubsonicBookmarkForBookmark ( bookmark , child ) ) ;
}
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
{
id = subsonic . Request . ReleaseIdIdentifier + r . Id . ToString ( ) ,
album = r . Release . Text ,
albumId = subsonic . Request . ReleaseIdIdentifier + r . Id . ToString ( ) ,
artist = r . Artist . Text ,
averageRating = r . Rating ? ? 0 ,
averageRatingSpecified = true ,
2018-11-21 06:34:53 +00:00
coverArt = subsonic . Request . ReleaseIdIdentifier + r . Id . ToString ( ) ,
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 )
{
return new subsonic . Child
2018-11-20 14:36:07 +00:00
{
2018-11-21 06:34:53 +00:00
id = subsonic . Request . TrackIdIdentifier + t . Id . ToString ( ) ,
album = t . Release . Text ,
albumId = subsonic . Request . ReleaseIdIdentifier + t . Release . Value ,
artist = t . Artist . Text ,
artistId = subsonic . Request . ArtistIdIdentifier + t . Artist . Value ,
averageRating = t . Rating ? ? 0 ,
averageRatingSpecified = true ,
bitRate = 320 ,
bitRateSpecified = true ,
contentType = "audio/mpeg" ,
coverArt = subsonic . Request . TrackIdIdentifier + t . Id . ToString ( ) ,
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-11-21 06:34:53 +00:00
parent = subsonic . Request . ReleaseIdIdentifier + t . Release . Value ,
path = $"{ t.Artist.Text }/{ t.Release.Text }/{ t.TrackNumber } - { t.Title }.mp3" ,
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 ,
userRating = t . UserRating ! = null ? t . UserRating . Rating ? ? 0 : 0 ,
userRatingSpecified = t . UserRating ! = null ,
year = t . Year ? ? 0 ,
2018-11-22 13:48:32 +00:00
yearSpecified = t . Year . HasValue ,
2018-11-21 18:19:38 +00:00
transcodedContentType = "audio/mpeg" ,
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 )
{
if ( r = = null | | ! r . Any ( ) )
{
return new subsonic . Child [ 0 ] ;
}
return r . Select ( x = > this . 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 )
{
if ( tracks = = null | | ! tracks . Any ( ) )
{
return new subsonic . Child [ 0 ] ;
}
return tracks . Select ( x = > this . SubsonicChildForTrack ( x ) ) . ToArray ( ) ;
}
2018-11-21 18:19:38 +00:00
2018-11-24 01:46:12 +00:00
private subsonic . Playlist SubsonicPlaylistForPlaylist ( Library . Models . Playlists . PlaylistList playlist , IEnumerable < TrackList > playlistTracks = null )
2018-11-21 18:19:38 +00:00
{
return new subsonic . PlaylistWithSongs
{
coverArt = this . MakePlaylistThumbnailImage ( playlist . Id ) . Url ,
2018-11-24 01:46:12 +00:00
allowedUser = playlist . IsPublic ? this . AllowedUsers ( ) : null ,
2018-11-21 18:19:38 +00:00
changed = playlist . LastUpdated ? ? playlist . CreatedDate ? ? DateTime . UtcNow ,
created = playlist . CreatedDate ? ? DateTime . UtcNow ,
duration = playlist . Duration ? ? 0 ,
id = subsonic . Request . PlaylistdIdentifier + playlist . Id . ToString ( ) ,
name = playlist . Playlist . Text ,
owner = playlist . User . Text ,
@public = playlist . IsPublic ,
publicSpecified = true ,
songCount = playlist . PlaylistCount ? ? 0 ,
entry = this . SubsonicChildrenForTracks ( playlistTracks )
} ;
}
2018-11-24 17:52:15 +00:00
private subsonic . Playlist [ ] SubsonicPlaylistsForPlaylists ( IEnumerable < Library . Models . Playlists . PlaylistList > playlists )
{
if ( playlists = = null | | ! playlists . Any ( ) )
{
return new subsonic . Playlist [ 0 ] ;
}
return playlists . Select ( x = > this . SubsonicPlaylistForPlaylist ( x ) ) . ToArray ( ) ;
}
2018-11-21 18:19:38 +00:00
private async Task < subsonic . User > SubsonicUserForUser ( Library . Identity . ApplicationUser user )
{
var isAdmin = await this . UserManger . IsInRoleAsync ( user , "Admin" ) ;
var isEditor = await this . UserManger . IsInRoleAsync ( user , "Editor" ) ;
return new subsonic . User
{
adminRole = isAdmin ,
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 ,
2018-11-24 16:18:03 +00:00
downloadRole = isEditor | | isAdmin , // Disable downloads
2018-11-21 18:19:38 +00:00
email = user . Email ,
jukeboxRole = true ,
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 ,
2018-11-25 16:57:17 +00:00
shareRole = false , // TODO enabled when sharing is implemented
2018-11-21 18:19:38 +00:00
streamRole = true ,
uploadRole = true ,
username = user . UserName ,
videoConversionRole = false , // Disable video nonsense
folder = this . MusicFolders ( ) . Select ( x = > x . id ) . ToArray ( )
} ;
}
2018-11-22 13:48:32 +00:00
2018-11-25 16:57:17 +00:00
private async Task < subsonic . SubsonicOperationResult < bool > > ToggleArtistStar ( Guid artistId , ApplicationUser user , bool starred )
{
var artist = this . GetArtist ( artistId ) ;
if ( artist = = null )
{
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Artist Id [{ artistId }]" ) ;
}
var userArtist = user . ArtistRatings . FirstOrDefault ( x = > x . ArtistId = = artist . Id ) ;
if ( userArtist = = null )
{
userArtist = new data . UserArtist
{
IsFavorite = true ,
UserId = user . Id ,
ArtistId = artist . Id
} ;
this . DbContext . UserArtists . Add ( userArtist ) ;
}
else
{
userArtist . IsFavorite = starred ;
userArtist . LastUpdated = DateTime . UtcNow ;
}
await this . DbContext . SaveChangesAsync ( ) ;
this . CacheManager . ClearRegion ( user . CacheRegion ) ;
this . CacheManager . ClearRegion ( artist . CacheRegion ) ;
return new subsonic . SubsonicOperationResult < bool >
{
IsSuccess = true ,
Data = true
} ;
}
private async Task < subsonic . SubsonicOperationResult < bool > > ToggleReleaseStar ( Guid releaseId , ApplicationUser user , bool starred )
{
var release = this . GetRelease ( releaseId ) ;
if ( release = = null )
{
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Release Id [{ releaseId }]" ) ;
}
var userRelease = user . ReleaseRatings . FirstOrDefault ( x = > x . ReleaseId = = release . Id ) ;
if ( userRelease = = null )
{
userRelease = new data . UserRelease
{
IsFavorite = true ,
UserId = user . Id ,
ReleaseId = release . Id
} ;
this . DbContext . UserReleases . Add ( userRelease ) ;
}
else
{
userRelease . IsFavorite = starred ;
userRelease . LastUpdated = DateTime . UtcNow ;
}
await this . DbContext . SaveChangesAsync ( ) ;
this . CacheManager . ClearRegion ( user . CacheRegion ) ;
this . CacheManager . ClearRegion ( release . CacheRegion ) ;
this . CacheManager . ClearRegion ( release . Artist . CacheRegion ) ;
return new subsonic . SubsonicOperationResult < bool >
{
IsSuccess = true ,
Data = true
} ;
}
private async Task < subsonic . SubsonicOperationResult < bool > > ToggleTrackStar ( Guid trackId , ApplicationUser user , bool starred )
{
var track = this . GetTrack ( trackId ) ;
if ( track = = null )
{
return new subsonic . SubsonicOperationResult < bool > ( subsonic . ErrorCodes . TheRequestedDataWasNotFound , $"Invalid Track Id [{ trackId }]" ) ;
}
var userTrack = user . TrackRatings . FirstOrDefault ( x = > x . TrackId = = track . Id ) ;
if ( userTrack = = null )
{
userTrack = new data . UserTrack
{
IsFavorite = true ,
UserId = user . Id ,
TrackId = track . Id
} ;
this . DbContext . UserTracks . Add ( userTrack ) ;
}
else
{
userTrack . IsFavorite = starred ;
userTrack . LastUpdated = DateTime . UtcNow ;
}
await this . DbContext . SaveChangesAsync ( ) ;
this . CacheManager . ClearRegion ( user . CacheRegion ) ;
this . CacheManager . ClearRegion ( track . CacheRegion ) ;
this . CacheManager . ClearRegion ( track . ReleaseMedia . Release . CacheRegion ) ;
this . CacheManager . ClearRegion ( track . ReleaseMedia . Release . Artist . CacheRegion ) ;
return new subsonic . SubsonicOperationResult < bool >
{
IsSuccess = true ,
Data = true
} ;
}
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
}