diff --git a/RoadieApi/Controllers/SubsonicController.cs b/RoadieApi/Controllers/SubsonicController.cs
index ca95b4d..91abb1d 100644
--- a/RoadieApi/Controllers/SubsonicController.cs
+++ b/RoadieApi/Controllers/SubsonicController.cs
@@ -12,7 +12,6 @@ using Roadie.Library.Identity;
using Roadie.Library.Models.ThirdPartyApi.Subsonic;
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
@@ -24,6 +23,7 @@ namespace Roadie.Api.Controllers
public class SubsonicController : EntityControllerBase
{
private IPlayActivityService PlayActivityService { get; }
+ private IReleaseService ReleaseService { get; }
private ISubsonicService SubsonicService { get; }
///
@@ -32,7 +32,6 @@ namespace Roadie.Api.Controllers
private Library.Models.Users.User SubsonicUser { get; set; }
private ITrackService TrackService { get; }
- private IReleaseService ReleaseService { get; }
public SubsonicController(ISubsonicService subsonicService, ITrackService trackService, IReleaseService releaseService, IPlayActivityService playActivityService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager userManager)
: base(cacheManager, configuration, userManager)
@@ -44,20 +43,6 @@ namespace Roadie.Api.Controllers
this.PlayActivityService = playActivityService;
}
- [HttpGet("getAlbum.view")]
- [HttpPost("getAlbum.view")]
- [ProducesResponseType(200)]
- public async Task GetAlbum(SubsonicRequest request)
- {
- var authResult = await this.AuthenticateUser(request);
- if (authResult != null)
- {
- return authResult;
- }
- var result = await this.SubsonicService.GetAlbum(request, this.SubsonicUser);
- return this.BuildResponse(request, result, "album");
- }
-
[HttpGet("createBookmark.view")]
[HttpPost("createBookmark.view")]
[ProducesResponseType(200)]
@@ -72,6 +57,19 @@ namespace Roadie.Api.Controllers
return this.BuildResponse(request, result);
}
+ [HttpGet("createPlaylist.view")]
+ [HttpPost("createPlaylist.view")]
+ [ProducesResponseType(200)]
+ public async Task CreatePlaylist(SubsonicRequest request, string playlistId, string name, string[] songId)
+ {
+ var authResult = await this.AuthenticateUser(request);
+ if (authResult != null)
+ {
+ return authResult;
+ }
+ var result = await this.SubsonicService.CreatePlaylist(request, this.SubsonicUser, name, songId, playlistId);
+ return this.BuildResponse(request, result, "playlist");
+ }
[HttpGet("deleteBookmark.view")]
[HttpPost("deleteBookmark.view")]
@@ -101,75 +99,46 @@ namespace Roadie.Api.Controllers
return this.BuildResponse(request, result);
}
- [HttpGet("updatePlaylist.view")]
- [HttpPost("updatePlaylist.view")]
+ [HttpGet("download.view")]
+ [HttpPost("download.view")]
[ProducesResponseType(200)]
- public async Task UpdatePlaylist(SubsonicRequest request, string playlistId, string name, string comment, bool? @public, string[] songIdToAdd, int[] songIndexToRemove)
+ public async Task Download(SubsonicRequest request)
{
var authResult = await this.AuthenticateUser(request);
if (authResult != null)
{
- return authResult;
+ return Unauthorized();
}
- var result = await this.SubsonicService.UpdatePlaylist(request, this.SubsonicUser, playlistId, name, comment, @public, songIdToAdd, songIndexToRemove);
- return this.BuildResponse(request, result);
+ var trackId = request.TrackId;
+ if (trackId != null)
+ {
+ return await base.StreamTrack(trackId.Value, this.TrackService, this.PlayActivityService, this.SubsonicUser);
+ }
+ var releaseId = request.ReleaseId;
+ if (releaseId != null)
+ {
+ var releaseZip = await this.ReleaseService.ReleaseZipped(this.SubsonicUser, releaseId.Value);
+ if (!releaseZip.IsSuccess)
+ {
+ return NotFound("Unknown Release id");
+ }
+ return File(releaseZip.Data, "application/zip", (string)releaseZip.AdditionalData["ZipFileName"]);
+ }
+ return NotFound($"Unknown download id `{ request.id }`");
}
-
- [HttpGet("getBookmarks.view")]
- [HttpPost("getBookmarks.view")]
+ [HttpGet("getAlbum.view")]
+ [HttpPost("getAlbum.view")]
[ProducesResponseType(200)]
- public async Task GetBookmarks(SubsonicRequest request)
+ public async Task GetAlbum(SubsonicRequest request)
{
var authResult = await this.AuthenticateUser(request);
if (authResult != null)
{
return authResult;
}
- var result = await this.SubsonicService.GetBookmarks(request, this.SubsonicUser);
- return this.BuildResponse(request, result, "bookmarks");
- }
-
- [HttpGet("star.view")]
- [HttpPost("star.view")]
- [ProducesResponseType(200)]
- public async Task Star(SubsonicRequest request, string[] albumId, string[] artistId)
- {
- var authResult = await this.AuthenticateUser(request);
- if (authResult != null)
- {
- return authResult;
- }
- var result = await this.SubsonicService.ToggleStar(request, this.SubsonicUser, true, albumId, artistId);
- return this.BuildResponse(request, result);
- }
-
- [HttpGet("unstar.view")]
- [HttpPost("unstar.view")]
- [ProducesResponseType(200)]
- public async Task UnStar(SubsonicRequest request, string[] albumId, string[] artistId)
- {
- var authResult = await this.AuthenticateUser(request);
- if (authResult != null)
- {
- return authResult;
- }
- var result = await this.SubsonicService.ToggleStar(request, this.SubsonicUser, false, albumId, artistId);
- return this.BuildResponse(request, result);
- }
-
- [HttpGet("setRating.view")]
- [HttpPost("setRating.view")]
- [ProducesResponseType(200)]
- public async Task SetRating(SubsonicRequest request, short rating)
- {
- var authResult = await this.AuthenticateUser(request);
- if (authResult != null)
- {
- return authResult;
- }
- var result = await this.SubsonicService.SetRating(request, this.SubsonicUser, rating);
- return this.BuildResponse(request, result);
+ var result = await this.SubsonicService.GetAlbum(request, this.SubsonicUser);
+ return this.BuildResponse(request, result, "album");
}
[HttpGet("getAlbumInfo.view")]
@@ -293,6 +262,20 @@ namespace Roadie.Api.Controllers
return Redirect($"/images/user/{ user.RoadieId }/{this.RoadieSettings.ThumbnailImageSize.Width}/{this.RoadieSettings.ThumbnailImageSize.Height}");
}
+ [HttpGet("getBookmarks.view")]
+ [HttpPost("getBookmarks.view")]
+ [ProducesResponseType(200)]
+ public async Task GetBookmarks(SubsonicRequest request)
+ {
+ var authResult = await this.AuthenticateUser(request);
+ if (authResult != null)
+ {
+ return authResult;
+ }
+ var result = await this.SubsonicService.GetBookmarks(request, this.SubsonicUser);
+ return this.BuildResponse(request, result, "bookmarks");
+ }
+
[HttpGet("getCoverArt.view")]
[HttpPost("getCoverArt.view")]
[ProducesResponseType(200)]
@@ -379,6 +362,20 @@ namespace Roadie.Api.Controllers
return this.BuildResponse(request, result, "musicFolders");
}
+ [HttpGet("getNowPlaying.view")]
+ [HttpPost("getNowPlaying.view")]
+ [ProducesResponseType(200)]
+ public async Task GetNowPlaying(SubsonicRequest request)
+ {
+ var authResult = await this.AuthenticateUser(request);
+ if (authResult != null)
+ {
+ return authResult;
+ }
+ var result = await this.SubsonicService.GetNowPlaying(request, this.SubsonicUser);
+ return this.BuildResponse(request, result, "nowPlaying");
+ }
+
[HttpGet("getPlaylist.view")]
[HttpPost("getPlaylist.view")]
[ProducesResponseType(200)]
@@ -393,21 +390,6 @@ namespace Roadie.Api.Controllers
return this.BuildResponse(request, result, "playlist");
}
- [HttpGet("createPlaylist.view")]
- [HttpPost("createPlaylist.view")]
- [ProducesResponseType(200)]
- public async Task CreatePlaylist(SubsonicRequest request, string playlistId, string name, string[] songId)
- {
- var authResult = await this.AuthenticateUser(request);
- if (authResult != null)
- {
- return authResult;
- }
- var result = await this.SubsonicService.CreatePlaylist(request, this.SubsonicUser, name, songId, playlistId);
- return this.BuildResponse(request, result, "playlist");
- }
-
-
[HttpGet("getPlaylists.view")]
[HttpPost("getPlaylists.view")]
[ProducesResponseType(200)]
@@ -634,6 +616,34 @@ namespace Roadie.Api.Controllers
return this.BuildResponse(request, result, "searchResult3");
}
+ [HttpGet("setRating.view")]
+ [HttpPost("setRating.view")]
+ [ProducesResponseType(200)]
+ public async Task SetRating(SubsonicRequest request, short rating)
+ {
+ var authResult = await this.AuthenticateUser(request);
+ if (authResult != null)
+ {
+ return authResult;
+ }
+ var result = await this.SubsonicService.SetRating(request, this.SubsonicUser, rating);
+ return this.BuildResponse(request, result);
+ }
+
+ [HttpGet("star.view")]
+ [HttpPost("star.view")]
+ [ProducesResponseType(200)]
+ public async Task Star(SubsonicRequest request, string[] albumId, string[] artistId)
+ {
+ var authResult = await this.AuthenticateUser(request);
+ if (authResult != null)
+ {
+ return authResult;
+ }
+ var result = await this.SubsonicService.ToggleStar(request, this.SubsonicUser, true, albumId, artistId);
+ return this.BuildResponse(request, result);
+ }
+
[HttpGet("stream.view")]
[HttpPost("stream.view")]
[ProducesResponseType(200)]
@@ -648,41 +658,37 @@ namespace Roadie.Api.Controllers
if (trackId == null)
{
return NotFound("Invalid TrackId");
-
}
return await base.StreamTrack(trackId.Value, this.TrackService, this.PlayActivityService, this.SubsonicUser);
}
-
- [HttpGet("download.view")]
- [HttpPost("download.view")]
+ [HttpGet("unstar.view")]
+ [HttpPost("unstar.view")]
[ProducesResponseType(200)]
- public async Task Download(SubsonicRequest request)
+ public async Task UnStar(SubsonicRequest request, string[] albumId, string[] artistId)
{
var authResult = await this.AuthenticateUser(request);
if (authResult != null)
{
- return Unauthorized();
+ return authResult;
}
- var trackId = request.TrackId;
- if (trackId != null)
- {
- return await base.StreamTrack(trackId.Value, this.TrackService, this.PlayActivityService, this.SubsonicUser);
- }
- var releaseId = request.ReleaseId;
- if(releaseId != null)
- {
- var releaseZip = await this.ReleaseService.ReleaseZipped(this.SubsonicUser, releaseId.Value);
- if(!releaseZip.IsSuccess)
- {
- return NotFound("Unknown Release id");
- }
- return File(releaseZip.Data, "application/zip",(string)releaseZip.AdditionalData["ZipFileName"]);
-
- }
- return NotFound($"Unknown download id `{ request.id }`");
+ var result = await this.SubsonicService.ToggleStar(request, this.SubsonicUser, false, albumId, artistId);
+ return this.BuildResponse(request, result);
}
+ [HttpGet("updatePlaylist.view")]
+ [HttpPost("updatePlaylist.view")]
+ [ProducesResponseType(200)]
+ public async Task UpdatePlaylist(SubsonicRequest request, string playlistId, string name, string comment, bool? @public, string[] songIdToAdd, int[] songIndexToRemove)
+ {
+ var authResult = await this.AuthenticateUser(request);
+ if (authResult != null)
+ {
+ return authResult;
+ }
+ var result = await this.SubsonicService.UpdatePlaylist(request, this.SubsonicUser, playlistId, name, comment, @public, songIdToAdd, songIndexToRemove);
+ return this.BuildResponse(request, result);
+ }
private async Task AuthenticateUser(SubsonicRequest request)
{
diff --git a/RoadieApi/Controllers/TrackController.cs b/RoadieApi/Controllers/TrackController.cs
index a223baf..5be1ee7 100644
--- a/RoadieApi/Controllers/TrackController.cs
+++ b/RoadieApi/Controllers/TrackController.cs
@@ -57,8 +57,8 @@ namespace Roadie.Api.Controllers
[ProducesResponseType(200)]
public async Task List([FromQuery]PagedRequest request, string inc)
{
- var result = await this.TrackService.List(roadieUser: await this.CurrentUserModel(),
- request: request);
+ var result = await this.TrackService.List(request: request,
+ roadieUser: await this.CurrentUserModel());
if (!result.IsSuccess)
{
return StatusCode((int)HttpStatusCode.InternalServerError);
diff --git a/RoadieApi/Services/ArtistService.cs b/RoadieApi/Services/ArtistService.cs
index 5323781..5cc3393 100644
--- a/RoadieApi/Services/ArtistService.cs
+++ b/RoadieApi/Services/ArtistService.cs
@@ -370,23 +370,23 @@ namespace Roadie.Api.Services
}
if(rows.Any() && (doArtistCounts ?? true))
{
- var rowArtistIds = rows.Select(x => x.DatabaseId);
+ var rowArtistIds = rows.Select(x => x.DatabaseId).ToArray();
var artistReleases = (from a in this.DbContext.Artists
join r in this.DbContext.Releases on a.Id equals r.ArtistId
- where a.ReleaseCount > 0
- where r.TrackCount > 0
where rowArtistIds.Contains(a.Id)
select new
{
- r.Id,
+ artistId = a.Id,
+ releaseId = r.Id,
r.TrackCount,
r.PlayedCount
- }).ToList();
+ }).ToArray();
foreach(var row in rows)
{
- row.ArtistReleaseCount = artistReleases.Where(r => r.Id == row.DatabaseId).Select(r => r.Id).Count();
- row.ArtistTrackCount = artistReleases.Where(r => r.Id == row.DatabaseId).Sum(r => r.TrackCount);
- row.ArtistPlayedCount = artistReleases.Where(r => r.Id == row.DatabaseId).Sum(r => r.PlayedCount);
+ var rowArtistReleases = artistReleases.Where(r => r.artistId == row.DatabaseId);
+ row.ArtistReleaseCount = rowArtistReleases.Select(r => r.releaseId).Count();
+ row.ArtistTrackCount = rowArtistReleases.Sum(r => r.TrackCount);
+ row.ArtistPlayedCount = rowArtistReleases.Sum(r => r.PlayedCount);
}
}
if (rows.Any() && roadieUser != null)
@@ -400,7 +400,8 @@ namespace Roadie.Api.Services
{
IsDisliked = userArtistRating.IsDisliked ?? false,
IsFavorite = userArtistRating.IsFavorite ?? false,
- Rating = userArtistRating.Rating
+ Rating = userArtistRating.Rating,
+ RatedDate = userArtistRating.LastUpdated ?? userArtistRating.CreatedDate
};
}
}
diff --git a/RoadieApi/Services/IPlayActivityService.cs b/RoadieApi/Services/IPlayActivityService.cs
index 600d102..2eb301d 100644
--- a/RoadieApi/Services/IPlayActivityService.cs
+++ b/RoadieApi/Services/IPlayActivityService.cs
@@ -2,13 +2,14 @@
using Roadie.Library.Models;
using Roadie.Library.Models.Pagination;
using Roadie.Library.Models.Users;
+using System;
using System.Threading.Tasks;
namespace Roadie.Api.Services
{
public interface IPlayActivityService
{
- Task> List(PagedRequest request, User roadieUser = null);
+ Task> List(PagedRequest request, User roadieUser = null, DateTime? newerThan = null);
Task> CreatePlayActivity(User roadieUser, TrackStreamInfo streamInfo);
}
diff --git a/RoadieApi/Services/ISubsonicService.cs b/RoadieApi/Services/ISubsonicService.cs
index 1882b8b..449d270 100644
--- a/RoadieApi/Services/ISubsonicService.cs
+++ b/RoadieApi/Services/ISubsonicService.cs
@@ -66,6 +66,8 @@ namespace Roadie.Api.Services
SubsonicOperationResult Ping(Request request);
+ Task> GetNowPlaying(Request request, Roadie.Library.Models.Users.User roadieUser);
+
Task> Search(Request request, Roadie.Library.Models.Users.User roadieUser, SearchVersion version);
Task> ToggleStar(Request request, Roadie.Library.Models.Users.User roadieUser, bool star, string[] albumIds = null, string[] artistIds = null);
diff --git a/RoadieApi/Services/ITrackService.cs b/RoadieApi/Services/ITrackService.cs
index c13a84c..819e38a 100644
--- a/RoadieApi/Services/ITrackService.cs
+++ b/RoadieApi/Services/ITrackService.cs
@@ -12,7 +12,7 @@ namespace Roadie.Api.Services
{
Task> ById(User roadieUser, Guid id, IEnumerable includes);
- Task> List(User roadieUser, PagedRequest request, bool? doRandomize = false, Guid? releaseId = null);
+ Task> List(PagedRequest request, User roadieUser, bool? doRandomize = false, Guid? releaseId = null);
Task> TrackStreamInfo(Guid trackId, long beginBytes, long endBytes);
}
diff --git a/RoadieApi/Services/ImageService.cs b/RoadieApi/Services/ImageService.cs
index 1fb0bb1..071f5cc 100644
--- a/RoadieApi/Services/ImageService.cs
+++ b/RoadieApi/Services/ImageService.cs
@@ -328,7 +328,7 @@ namespace Roadie.Api.Services
result.LastModified = DateTime.UtcNow;
if (width.Value != this.Configuration.ThumbnailImageSize.Width || height.Value != this.Configuration.ThumbnailImageSize.Height)
{
- this.Logger.LogInformation($"{ type }: Resized [{ id }], Width [{ width.Value }], Height [{ height.Value }]");
+ this.Logger.LogTrace($"{ type }: Resized [{ id }], Width [{ width.Value }], Height [{ height.Value }]");
}
}
sw.Stop();
diff --git a/RoadieApi/Services/PlayActivityService.cs b/RoadieApi/Services/PlayActivityService.cs
index 3df86ec..b7f5f36 100644
--- a/RoadieApi/Services/PlayActivityService.cs
+++ b/RoadieApi/Services/PlayActivityService.cs
@@ -37,7 +37,7 @@ namespace Roadie.Api.Services
this.PlayActivityHub = playHubContext;
}
- public async Task> List(PagedRequest request, User roadieUser = null)
+ public async Task> List(PagedRequest request, User roadieUser = null, DateTime? newerThan = null)
{
try
{
@@ -52,6 +52,7 @@ namespace Roadie.Api.Services
join usertrack in this.DbContext.UserTracks on t.Id equals usertrack.TrackId
join u in this.DbContext.Users on usertrack.UserId equals u.Id
join releaseArtist in this.DbContext.Artists on r.ArtistId equals releaseArtist.Id
+ where(newerThan == null || usertrack.LastPlayed >= newerThan)
where ((roadieUser == null && !(u.IsPrivate ?? false)) || (roadieUser != null && (usertrack != null && usertrack.User.Id == roadieUser.Id)))
where (request.FilterValue.Length == 0 || (request.FilterValue.Length > 0 && (
t.Title != null && t.Title.ToLower().Contains(request.Filter.ToLower()) ||
diff --git a/RoadieApi/Services/SubsonicService.cs b/RoadieApi/Services/SubsonicService.cs
index 665a0e4..8ae3633 100644
--- a/RoadieApi/Services/SubsonicService.cs
+++ b/RoadieApi/Services/SubsonicService.cs
@@ -39,6 +39,7 @@ namespace Roadie.Api.Services
private ICollectionService CollectionService { get; }
private IImageService ImageService { get; }
private IPlaylistService PlaylistService { get; }
+ private IPlayActivityService PlayActivityService { get; }
private IReleaseService ReleaseService { get; }
private ITrackService TrackService { get; }
private UserManager UserManger { get; }
@@ -56,6 +57,7 @@ namespace Roadie.Api.Services
IReleaseService releaseService,
IImageService imageService,
IBookmarkService bookmarkService,
+ IPlayActivityService playActivityService,
UserManager userManager
)
: base(configuration, httpEncoder, context, cacheManager, logger, httpContext)
@@ -65,6 +67,7 @@ namespace Roadie.Api.Services
this.CollectionService = collectionService;
this.ImageService = imageService;
this.PlaylistService = playlistService;
+ this.PlayActivityService = playActivityService;
this.ReleaseService = releaseService;
this.TrackService = trackService;
this.UserManger = userManager;
@@ -199,6 +202,92 @@ namespace Roadie.Api.Services
};
}
+ ///
+ /// Creates (or updates) a playlist.
+ ///
+ /// Populated Subsonic Request
+ /// Populated Roadie User
+ /// The human-readable name of the playlist.
+ /// ID of a song in the playlist. Use one songId parameter for each song in the playlist.
+ /// The playlist ID. (if updating else blank is adding)
+ ///
+ public async Task> CreatePlaylist(subsonic.Request request, User roadieUser, string name, string[] songIds, string playlistId = null)
+ {
+ data.Playlist playlist = null;
+
+ Guid?[] songRoadieIds = new Guid?[0];
+ IQueryable 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.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();
+ 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);
+ }
+
///
/// Deletes the bookmark for a given file.
///
@@ -235,6 +324,43 @@ namespace Roadie.Api.Services
};
}
+ ///
+ /// Deletes a saved playlist.
+ ///
+ public async Task> DeletePlaylist(subsonic.Request request, User roadieUser)
+ {
+ if (!request.PlaylistId.HasValue)
+ {
+ return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Playlist Id [{ request.id }]");
+ }
+ var playlist = this.GetPlaylist(request.PlaylistId.Value);
+ if (playlist == null)
+ {
+ return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Playlist Id [{ request.TrackId.Value }]");
+ }
+ if (playlist.UserId != roadieUser.Id && !roadieUser.IsAdmin)
+ {
+ return new subsonic.SubsonicOperationResult(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
+ {
+ IsSuccess = true,
+ Data = new subsonic.Response
+ {
+ version = SubsonicService.SubsonicVersion,
+ status = subsonic.ResponseStatus.ok
+ }
+ };
+ }
+
///
/// Returns details for an album, including a list of songs. This method organizes music according to ID3 tags.
///
@@ -251,7 +377,7 @@ namespace Roadie.Api.Services
return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Release [{ request.ReleaseId}]");
}
var pagedRequest = request.PagedRequest;
- var releaseTracks = await this.TrackService.List(roadieUser, pagedRequest, false, releaseId);
+ var releaseTracks = await this.TrackService.List(pagedRequest, roadieUser, false, releaseId);
var userRelease = roadieUser == null ? null : this.DbContext.UserReleases.FirstOrDefault(x => x.ReleaseId == release.Id && x.UserId == roadieUser.Id);
var genre = release.Genres.FirstOrDefault();
return new subsonic.SubsonicOperationResult
@@ -489,48 +615,11 @@ namespace Roadie.Api.Services
///
public async Task> GetArtists(subsonic.Request request, User roadieUser)
{
- var indexes = new List();
- var musicFolder = this.MusicFolders().FirstOrDefault(x => x.id == (request.MusicFolderId ?? 2));
- var pagedRequest = request.PagedRequest;
- if (musicFolder == this.CollectionMusicFolder())
+ var cacheKey = $"urn:subsonic_artists:{ roadieUser.UserName }";
+ return await this.CacheManager.GetAsync>(cacheKey, async () =>
{
- // Indexes for "Collection" Artists alphabetically
- // not sure what to do here since this is albums not artists in a "Collection".
- }
- 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: false);
- 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
- {
- IsSuccess = true,
- Data = new subsonic.Response
- {
- version = SubsonicService.SubsonicVersion,
- status = subsonic.ResponseStatus.ok,
- ItemElementName = subsonic.ItemChoiceType.artists,
- Item = new subsonic.ArtistsID3
- {
- index = indexes.ToArray()
- }
- }
- };
+ return await this.GetArtistsAction(request, roadieUser);
+ }, CacheManagerBase.SystemCacheRegionUrn);
}
///
@@ -543,7 +632,7 @@ namespace Roadie.Api.Services
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();
- var trackListResult = await this.TrackService.List(roadieUser, pagedRequest);
+ var trackListResult = await this.TrackService.List(pagedRequest, roadieUser);
return new subsonic.SubsonicOperationResult
{
IsSuccess = true,
@@ -814,7 +903,7 @@ namespace Roadie.Api.Services
directory.starredSpecified = true;
}
var pagedRequest = request.PagedRequest;
- var songTracks = await this.TrackService.List(roadieUser, pagedRequest, false, release.RoadieId);
+ var songTracks = await this.TrackService.List(pagedRequest, roadieUser, false, release.RoadieId);
directory.child = this.SubsonicChildrenForTracks(songTracks.Rows);
directory.playCount = directory.child.Select(x => x.playCount).Sum();
}
@@ -877,7 +966,7 @@ namespace Roadie.Api.Services
}
// For a playlist to show all the tracks in the playlist set the limit to the playlist size
pagedRequest.Limit = playlist.PlaylistCount ?? pagedRequest.Limit;
- var tracksForPlaylist = await this.TrackService.List(roadieUser, pagedRequest);
+ var tracksForPlaylist = await this.TrackService.List(pagedRequest, roadieUser);
return new subsonic.SubsonicOperationResult
{
IsSuccess = true,
@@ -945,7 +1034,7 @@ namespace Roadie.Api.Services
{
var songs = new List();
- var randomSongs = await this.TrackService.List(roadieUser, request.PagedRequest, true);
+ var randomSongs = await this.TrackService.List(request.PagedRequest, roadieUser, true);
return new subsonic.SubsonicOperationResult
{
@@ -1021,7 +1110,7 @@ namespace Roadie.Api.Services
var pagedRequest = request.PagedRequest;
pagedRequest.FilterToTrackId = request.TrackId.Value;
pagedRequest.Sort = "Id";
- var trackResult = await this.TrackService.List(roadieUser, pagedRequest);
+ var trackResult = await this.TrackService.List(pagedRequest, roadieUser);
var track = trackResult.Rows.Any() ? trackResult.Rows.First() : null;
if (track == null)
{
@@ -1048,7 +1137,7 @@ namespace Roadie.Api.Services
var pagedRequest = request.PagedRequest;
pagedRequest.FilterByGenre = request.Genre;
pagedRequest.Sort = "Id";
- var trackResult = await this.TrackService.List(roadieUser, pagedRequest);
+ var trackResult = await this.TrackService.List(pagedRequest, roadieUser);
return new subsonic.SubsonicOperationResult
{
@@ -1077,7 +1166,7 @@ namespace Roadie.Api.Services
var artistList = await this.ArtistService.List(roadieUser, pagedRequest);
var releaseList = await this.ReleaseService.List(roadieUser, pagedRequest);
- var songList = await this.TrackService.List(roadieUser, pagedRequest);
+ var songList = await this.TrackService.List(pagedRequest, roadieUser);
switch (version)
{
@@ -1132,7 +1221,7 @@ namespace Roadie.Api.Services
{
artist = base.GetArtist(request.ArtistName);
}
- else if(request.ArtistId.HasValue)
+ else if (request.ArtistId.HasValue)
{
artist = this.GetArtist(request.ArtistId.Value);
}
@@ -1145,7 +1234,7 @@ namespace Roadie.Api.Services
pagedRequest.FilterTopPlayedOnly = true;
pagedRequest.Sort = "PlayedCount";
pagedRequest.Order = "DESC";
- var trackResult = await this.TrackService.List(roadieUser, pagedRequest);
+ var trackResult = await this.TrackService.List(pagedRequest, roadieUser);
return new subsonic.SubsonicOperationResult
{
IsSuccess = true,
@@ -1251,7 +1340,7 @@ namespace Roadie.Api.Services
trackPagedRequest.Limit = request.SongCount ?? trackPagedRequest.Limit;
trackPagedRequest.SkipValue = request.SongOffset ?? trackPagedRequest.SkipValue;
trackPagedRequest.Filter = query;
- var songResult = await this.TrackService.List(roadieUser, trackPagedRequest);
+ var songResult = await this.TrackService.List(trackPagedRequest, roadieUser);
var songs = this.SubsonicChildrenForTracks(songResult.Rows);
switch (version)
@@ -1432,129 +1521,6 @@ namespace Roadie.Api.Services
return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Unknown Star Id [{ JsonConvert.SerializeObject(request) }]");
}
- ///
- /// Creates (or updates) a playlist.
- ///
- /// Populated Subsonic Request
- /// Populated Roadie User
- /// The human-readable name of the playlist.
- /// ID of a song in the playlist. Use one songId parameter for each song in the playlist.
- /// The playlist ID. (if updating else blank is adding)
- ///
- public async Task> CreatePlaylist(subsonic.Request request, User roadieUser, string name, string[] songIds, string playlistId = null)
- {
- data.Playlist playlist = null;
-
- Guid?[] songRoadieIds = new Guid?[0];
- IQueryable 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.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();
- 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);
- }
-
- ///
- /// Deletes a saved playlist.
- ///
- public async Task> DeletePlaylist(subsonic.Request request, User roadieUser)
- {
- if (!request.PlaylistId.HasValue)
- {
- return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Playlist Id [{ request.id }]");
- }
- var playlist = this.GetPlaylist(request.PlaylistId.Value);
- if (playlist == null)
- {
- return new subsonic.SubsonicOperationResult(subsonic.ErrorCodes.TheRequestedDataWasNotFound, $"Invalid Playlist Id [{ request.TrackId.Value }]");
- }
- if(playlist.UserId != roadieUser.Id && !roadieUser.IsAdmin)
- {
- return new subsonic.SubsonicOperationResult(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
- {
- IsSuccess = true,
- Data = new subsonic.Response
- {
- version = SubsonicService.SubsonicVersion,
- status = subsonic.ResponseStatus.ok
- }
- };
- }
-
///
/// Updates a playlist. Only the owner of a playlist is allowed to update it.
///
@@ -1565,7 +1531,7 @@ namespace Roadie.Api.Services
/// true if the playlist should be visible to all users, false otherwise.
/// Add this song with this ID to the playlist. Multiple parameters allowed
/// Remove the song at this position in the playlist. Multiple parameters allowed.
- public async Task> UpdatePlaylist(subsonic.Request request, User roadieUser, string playListId, string name = null, string comment =null, bool? isPublic = null, string[] songIdsToAdd = null, int[] songIndexesToRemove = null)
+ public async Task> UpdatePlaylist(subsonic.Request request, User roadieUser, string playListId, string name = null, string comment = null, bool? isPublic = null, string[] songIdsToAdd = null, int[] songIndexesToRemove = null)
{
request.id = playListId ?? request.id;
if (!request.PlaylistId.HasValue)
@@ -1586,7 +1552,7 @@ namespace Roadie.Api.Services
playlist.IsPublic = isPublic ?? playlist.IsPublic;
playlist.LastUpdated = DateTime.UtcNow;
- if(songIdsToAdd != null && songIdsToAdd.Any())
+ if (songIdsToAdd != null && songIdsToAdd.Any())
{
// Add new if not already on Playlist
var songIdsToAddRoadieIds = songIdsToAdd.Select(x => SafeParser.ToGuid(x)).ToArray();
@@ -1609,22 +1575,11 @@ namespace Roadie.Api.Services
}
}
}
- if(songIndexesToRemove != null && songIndexesToRemove.Any())
+ if (songIndexesToRemove != null && songIndexesToRemove.Any())
{
// Remove tracks from playlist
- //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
- // });
- // }
- //}
+ // 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) }]");
}
await this.DbContext.SaveChangesAsync();
@@ -1643,9 +1598,98 @@ namespace Roadie.Api.Services
};
}
+ ///
+ /// Returns what is currently being played by all users. Takes no extra parameters.
+ ///
+ public async Task> 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();
+ 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();
+ 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
+ {
+ IsSuccess = true,
+ Data = new subsonic.Response
+ {
+ version = SubsonicService.SubsonicVersion,
+ status = subsonic.ResponseStatus.ok,
+ ItemElementName = subsonic.ItemChoiceType.nowPlaying,
+ Item = new subsonic.NowPlaying
+ {
+ entry = playEntries.ToArray()
+ }
+ }
+ };
+ }
#region Privates
+ private async Task> GetArtistsAction(subsonic.Request request, User roadieUser)
+ {
+ var indexes = new List();
+ 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
+ {
+ IsSuccess = true,
+ Data = new subsonic.Response
+ {
+ version = SubsonicService.SubsonicVersion,
+ status = subsonic.ResponseStatus.ok,
+ ItemElementName = subsonic.ItemChoiceType.artists,
+ Item = new subsonic.ArtistsID3
+ {
+ index = indexes.ToArray()
+ }
+ }
+ };
+ }
+
private string[] AllowedUsers()
{
return this.CacheManager.Get(CacheManagerBase.SystemCacheRegionUrn + ":active_usernames", () =>
@@ -1698,9 +1742,9 @@ namespace Roadie.Api.Services
pagedRequest.SkipValue = 0;
pagedRequest.Limit = int.MaxValue;
pagedRequest.Sort = "Artist.Text";
- var artistList = await this.ArtistService.List(roadieUser:roadieUser,
+ var artistList = await this.ArtistService.List(roadieUser: roadieUser,
request: pagedRequest,
- doRandomize: false,
+ doRandomize: false,
onlyIncludeWithReleases: true,
doArtistCounts: false);
foreach (var artistGroup in artistList.Rows.GroupBy(x => x.Artist.Text.Substring(0, 1)))
@@ -1708,7 +1752,7 @@ namespace Roadie.Api.Services
indexes.Add(new subsonic.Index
{
name = artistGroup.Key,
- artist = this.SubsonicArtistsForArtists(artistGroup)
+ artist = this.SubsonicArtistsForArtists(artistGroup)
});
};
}
diff --git a/RoadieApi/Services/TrackService.cs b/RoadieApi/Services/TrackService.cs
index e80c006..b626dc0 100644
--- a/RoadieApi/Services/TrackService.cs
+++ b/RoadieApi/Services/TrackService.cs
@@ -217,7 +217,7 @@ namespace Roadie.Api.Services
return result;
}
- public async Task> List(User roadieUser, PagedRequest request, bool? doRandomize = false, Guid? releaseId = null)
+ public async Task> List(PagedRequest request, User roadieUser, bool? doRandomize = false, Guid? releaseId = null)
{
var sw = new Stopwatch();
sw.Start();
@@ -268,7 +268,6 @@ namespace Roadie.Api.Services
randomLimit = request.LimitValue > randomLimit ? randomLimit : request.LimitValue;
var sql = $"SELECT t.* FROM `track` t WHERE t.Hash IS NOT NULL ORDER BY RAND() LIMIT {randomLimit}";
randomTrackIds = this.DbContext.Tracks.FromSql(sql).Select(x => x.Id).ToArray();
-
}
Guid?[] filterToTrackIds = null;
if(request.FilterToTrackId.HasValue || request.FilterToTrackIds != null)
diff --git a/RoadieApi/appsettings.json b/RoadieApi/appsettings.json
index c2349b6..60568ef 100644
--- a/RoadieApi/appsettings.json
+++ b/RoadieApi/appsettings.json
@@ -9,9 +9,9 @@
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.RollingFileAlternate" ],
"MinimumLevel": {
- "Default": "Verbose",
+ "Default": "Information",
"Override": {
- "Microsoft": "Warning",
+ "Microsoft": "Information",
"System": "Warning"
}
},
@@ -19,13 +19,14 @@
{
"Name": "Console",
"Args": {
- "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console"
+ "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
+ "restrictedToMinimumLevel": "Verbose"
}
},
{
"Name": "RollingFileAlternate",
"Args": {
- "restrictedToMinimumLevel": "Information",
+ "restrictedToMinimumLevel": "Warning",
"path": "{Date}.log",
"logDirectory": "logs",
"fileSizeLimitBytes": 26214400,