Added download ability for admins and editors.

This commit is contained in:
Steven Hildreth 2018-11-24 10:18:03 -06:00
parent 3a4f95fd24
commit 62ad0cee29
7 changed files with 150 additions and 65 deletions

View file

@ -32,13 +32,15 @@ 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, IPlayActivityService playActivityService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
public SubsonicController(ISubsonicService subsonicService, ITrackService trackService, IReleaseService releaseService, IPlayActivityService playActivityService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
: base(cacheManager, configuration, userManager)
{
this.Logger = logger.CreateLogger("RoadieApi.Controllers.SubsonicController");
this.SubsonicService = subsonicService;
this.TrackService = trackService;
this.ReleaseService = releaseService;
this.PlayActivityService = playActivityService;
}
@ -554,6 +556,37 @@ namespace Roadie.Api.Controllers
return await base.StreamTrack(trackId.Value, this.TrackService, this.PlayActivityService, this.SubsonicUser);
}
[HttpGet("download.view")]
[HttpPost("download.view")]
[ProducesResponseType(200)]
public async Task<IActionResult> Download(SubsonicRequest request)
{
var authResult = await this.AuthenticateUser(request);
if (authResult != null)
{
return Unauthorized();
}
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 }`");
}
private async Task<IActionResult> AuthenticateUser(SubsonicRequest request)
{
var appUser = await this.SubsonicService.Authenticate(request);

View file

@ -13,5 +13,6 @@ namespace Roadie.Api.Services
Task<OperationResult<Release>> ById(User roadieUser, Guid id, IEnumerable<string> includes = null);
Task<PagedResult<ReleaseList>> List(User user, PagedRequest request, bool? doRandomize = false);
Task<FileOperationResult<byte[]>> ReleaseZipped(User roadieUser, Guid id);
}
}

View file

@ -225,7 +225,7 @@ namespace Roadie.Api.Services
artistFolder = artist.ArtistFileFolder(this.Configuration, this.Configuration.LibraryFolder);
if (!Directory.Exists(artistFolder))
{
this.Logger.LogWarning($"Artist Folder [{ artistFolder }], Not Found For Artist [{ artist.ToString() }]");
this.Logger.LogWarning($"Artist Folder [{ artistFolder }], Not Found For Artist `{ artist.ToString() }`");
}
else
{
@ -439,14 +439,14 @@ namespace Roadie.Api.Services
artistFolder = release.Artist.ArtistFileFolder(this.Configuration, this.Configuration.LibraryFolder);
if (!Directory.Exists(artistFolder))
{
this.Logger.LogWarning($"Artist Folder [{ artistFolder }], Not Found For Artist [{ release.Artist.ToString() }]");
this.Logger.LogWarning($"Artist Folder [{ artistFolder }], Not Found For Artist `{ release.Artist.ToString() }`");
}
else
{
releaseFolder = release.ReleaseFileFolder(artistFolder);
if (!Directory.Exists(releaseFolder))
{
this.Logger.LogWarning($"Release Folder [{ releaseFolder }], Not Found For Release [{ release.ToString() }]");
this.Logger.LogWarning($"Release Folder [{ releaseFolder }], Not Found For Release `{ release.ToString() }`");
}
else
{
@ -460,7 +460,7 @@ namespace Roadie.Api.Services
}
catch (Exception ex)
{
this.Logger.LogError(ex, $"Error Reading Release Folder [{ releaseFolder }] Artist Folder [{ artistFolder }] For Artist [{ release.Artist.Id }]");
this.Logger.LogError(ex, $"Error Reading Release Folder [{ releaseFolder }] Artist Folder [{ artistFolder }] For Artist `{ release.Artist.Id }`");
}
imageBytes = imageBytes ?? release.Thumbnail;
var image = new data.Image

View file

@ -16,6 +16,8 @@ using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
@ -324,6 +326,63 @@ namespace Roadie.Api.Services
};
}
public async Task<FileOperationResult<byte[]>> ReleaseZipped(User roadieUser, Guid id)
{
var release = this.GetRelease(id);
if (release == null)
{
return new FileOperationResult<byte[]>(true, string.Format("Release Not Found [{0}]", id));
}
byte[] zipBytes = null;
string zipFileName = null;
try
{
var artistFolder = release.Artist.ArtistFileFolder(this.Configuration, this.Configuration.LibraryFolder);
var releaseFolder = release.ReleaseFileFolder(artistFolder);
if(!Directory.Exists(releaseFolder))
{
this.Logger.LogCritical($"Release Folder [{ releaseFolder }] not found for Release `{ release }`");
return new FileOperationResult<byte[]>(true, string.Format("Release Folder Not Found [{0}]", id));
}
var releaseFiles = Directory.GetFiles(releaseFolder);
using (MemoryStream zipStream = new MemoryStream())
{
using (ZipArchive zip = new ZipArchive(zipStream, ZipArchiveMode.Create))
{
foreach (var releaseFile in releaseFiles)
{
var fileInfo = new FileInfo(releaseFile);
if (fileInfo.Extension.ToLower() == ".mp3" || fileInfo.Extension.ToLower() == ".jpg")
{
ZipArchiveEntry entry = zip.CreateEntry(fileInfo.Name);
using (Stream entryStream = entry.Open())
{
using (FileStream s = fileInfo.OpenRead())
{
s.CopyTo(entryStream);
}
}
}
}
}
zipBytes = zipStream.ToArray();
}
zipFileName = $"{ release.Artist.Name }_{release.Title}.zip".ToFileNameFriendly();
this.Logger.LogInformation($"User `{ roadieUser }` downloaded Release `{ release }` ZipFileName [{ zipFileName }], Zip Size [{ zipBytes?.Length }]");
}
catch (Exception ex)
{
this.Logger.LogError(ex, "Error creating zip for Release `{0}`", release.ToString());
}
return new FileOperationResult<byte[]>
{
IsSuccess = zipBytes != null,
Data = zipBytes,
AdditionalData = new Dictionary<string, object> {{ "ZipFileName", zipFileName }}
};
}
private async Task<OperationResult<Release>> ReleaseByIdAction(Guid id, IEnumerable<string> includes = null)
{
var sw = Stopwatch.StartNew();

View file

@ -35,6 +35,7 @@ namespace Roadie.Api.Services
public const string SubsonicVersion = "1.16.1";
private IArtistService ArtistService { get; }
private ICollectionService CollectionService { get; }
private IImageService ImageService { get; }
private IPlaylistService PlaylistService { get; }
private IReleaseService ReleaseService { get; }
@ -58,11 +59,12 @@ namespace Roadie.Api.Services
: base(configuration, httpEncoder, context, cacheManager, logger, httpContext)
{
this.ArtistService = artistService;
this.CollectionService = collectionService;
this.ImageService = imageService;
this.PlaylistService = playlistService;
this.ReleaseService = releaseService;
this.TrackService = trackService;
this.ImageService = imageService;
this.UserManger = userManager;
this.PlaylistService = playlistService;
}
/// <summary>
@ -142,15 +144,6 @@ namespace Roadie.Api.Services
return null;
}
/// <summary>
/// Downloads a given media file. Similar to stream, but this method returns the original media data without transcoding or downsampling.
/// </summary>
public async Task<subsonic.SubsonicFileOperationResult<subsonic.Response>> Download(subsonic.Request request, User roadieUser)
{
// TODO
throw new NotImplementedException();
}
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetAlbum(subsonic.Request request, User roadieUser)
{
var releaseId = SafeParser.ToGuid(request.id);
@ -400,20 +393,29 @@ namespace Roadie.Api.Services
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetArtists(subsonic.Request request, User roadieUser)
{
var indexes = new List<subsonic.IndexID3>();
// Indexes for Artists alphabetically
var musicFolder = this.MusicFolders().FirstOrDefault(x => x.id == (request.MusicFolderId ?? 2));
var pagedRequest = request.PagedRequest;
pagedRequest.SkipValue = 0;
pagedRequest.Limit = int.MaxValue;
pagedRequest.Sort = "Artist.Text";
var artistList = await this.ArtistService.List(roadieUser, pagedRequest);
foreach (var artistGroup in artistList.Rows.GroupBy(x => x.Artist.Text.Substring(0, 1)))
if (musicFolder == this.CollectionMusicFolder())
{
indexes.Add(new subsonic.IndexID3
// 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, pagedRequest);
foreach (var artistGroup in artistList.Rows.GroupBy(x => x.Artist.Text.Substring(0, 1)))
{
name = artistGroup.Key,
artist = this.SubsonicArtistID3sForArtists(artistGroup)
});
};
indexes.Add(new subsonic.IndexID3
{
name = artistGroup.Key,
artist = this.SubsonicArtistID3sForArtists(artistGroup)
});
};
}
return new subsonic.SubsonicOperationResult<subsonic.Response>
{
IsSuccess = true,
@ -836,38 +838,11 @@ namespace Roadie.Api.Services
/// </summary>
public async Task<subsonic.SubsonicOperationResult<subsonic.Response>> GetPlaylists(subsonic.Request request, User roadieUser, string filterToUserName)
{
//var playlists = (from playlist in this.DbContext.Playlists
// join u in this.DbContext.Users on playlist.UserId equals u.Id
// let trackCount = (from pl in this.DbContext.PlaylistTracks
// where pl.PlayListId == playlist.Id
// select pl.Id).Count()
// let playListDuration = (from pl in this.DbContext.PlaylistTracks
// join t in this.DbContext.Tracks on pl.TrackId equals t.Id
// where pl.PlayListId == playlist.Id
// select t.Duration).Sum()
// where (playlist.IsPublic) || (roadieUser != null && playlist.UserId == roadieUser.Id)
// select new subsonic.Playlist
// {
// id = playlist.RoadieId.ToString(),
// name = playlist.Name,
// comment = playlist.Description,
// owner = u.UserName,
// songCount = trackCount,
// duration = playListDuration.ToSecondsFromMilliseconds(),
// created = playlist.CreatedDate,
// changed = playlist.LastUpdated ?? playlist.CreatedDate,
// coverArt = this.MakePlaylistThumbnailImage(playlist.RoadieId).Url,
// @public = playlist.IsPublic,
// publicSpecified = playlist.IsPublic
// }
// );
var pagedRequest = request.PagedRequest;
pagedRequest.Sort = "Playlist.Text";
pagedRequest.Order = "ASC";
var playlistResult = await this.PlaylistService.List(pagedRequest, roadieUser);
return new subsonic.SubsonicOperationResult<subsonic.Response>
{
IsSuccess = true,
@ -1624,6 +1599,11 @@ namespace Roadie.Api.Services
return this.MusicFolders().First(x => x.id == 1);
}
private subsonic.MusicFolder MusicMusicFolder()
{
return this.MusicFolders().First(x => x.id == 2);
}
private List<subsonic.MusicFolder> MusicFolders()
{
return new List<subsonic.MusicFolder>
@ -1886,9 +1866,9 @@ namespace Roadie.Api.Services
adminRole = isAdmin,
avatarLastChanged = user.LastUpdated ?? user.CreatedDate ?? DateTime.UtcNow,
avatarLastChangedSpecified = user.LastUpdated.HasValue,
commentRole = false, // TODO set to yes when commenting is enabled
commentRole = true,
coverArtRole = isEditor || isAdmin,
downloadRole = false, // Disable downloads
downloadRole = isEditor || isAdmin, // Disable downloads
email = user.Email,
jukeboxRole = true,
maxBitRate = 320,
@ -1897,7 +1877,7 @@ namespace Roadie.Api.Services
podcastRole = false, // Disable podcast nonsense
scrobblingEnabled = false, // Disable scrobbling
settingsRole = isAdmin,
shareRole = false, // Disable sharing
shareRole = false, // TODO enabled when sharing is implmeneted
streamRole = true,
uploadRole = true,
username = user.UserName,

View file

@ -137,6 +137,18 @@ namespace Roadie.Library.Data
return false;
}
public int? ReleaseYear
{
get
{
if(this.ReleaseDate.HasValue)
{
return this.ReleaseDate.Value.Year;
}
return null;
}
}
/// <summary>
/// Return this releases file folder for the given artist folder
/// </summary>
@ -149,7 +161,7 @@ namespace Roadie.Library.Data
public override string ToString()
{
return string.Format("Id [{0}], Title [{1}], Release Date [{2}]", this.Id, this.Title, this.ReleaseDate);
return $"Id [{ this.Id }], Title [{ this.Title }], Release Date [{this.ReleaseYear}]";
}
}
}

View file

@ -280,7 +280,7 @@ namespace Roadie.Library.Factories
}
}
this.Logger.LogInformation("Added New Release: [{0}]", release.ToString());
this.Logger.LogInformation("Added New Release: `{0}`", release.ToString());
}
}
catch (Exception ex)
@ -521,7 +521,7 @@ namespace Roadie.Library.Factories
sw.Stop();
if (release == null || !release.IsValid)
{
this._logger.LogInformation("ReleaseFactory: Release Not Found For Artist [{0}] MetaData [{1}]", artist.ToString(), metaData.ToString());
this._logger.LogInformation("ReleaseFactory: Release Not Found For Artist `{0}` MetaData [{1}]", artist.ToString(), metaData.ToString());
if (doFindIfNotInDatabase)
{
OperationResult<Data.Release> releaseSearch = new OperationResult<Data.Release>();
@ -1367,7 +1367,7 @@ namespace Roadie.Library.Factories
var releaseDirectory = new DirectoryInfo(releasePath);
if (!Directory.Exists(releasePath))
{
this.Logger.LogWarning("Unable To Find Release Folder [{0}] For Release [{1}]", releasePath, release.ToString());
this.Logger.LogWarning("Unable To Find Release Folder [{0}] For Release `{1}`", releasePath, release.ToString());
}
var now = DateTime.UtcNow;
@ -1382,7 +1382,7 @@ namespace Roadie.Library.Factories
if (!File.Exists(trackPath))
{
this.Logger.LogWarning("Track [{0}], File [{1}] Not Found.", existingTrack.ToString(), trackPath);
this.Logger.LogWarning("Track `{0}`, File [{1}] Not Found.", existingTrack.ToString(), trackPath);
if (!doJustInfo)
{
existingTrack.UpdateTrackMissingFile(now);
@ -1561,13 +1561,13 @@ namespace Roadie.Library.Factories
}
else
{
this.Logger.LogWarning("Release Track File Has Invalid MetaData [{0}]", audioMetaData.ToString());
this.Logger.LogWarning("Release Track File Has Invalid MetaData `{0}`", audioMetaData.ToString());
}
}
}
else
{
this.Logger.LogWarning("Unable To Find Releaes Path [{0}] For Release [{1}]", releasePath, release.ToString());
this.Logger.LogWarning("Unable To Find Releaes Path [{0}] For Release `{1}`", releasePath, release.ToString());
}
var releaseMediaNumbersFound = new List<short?>();
foreach (var kp in releaseMediaTracksFound)
@ -1633,7 +1633,7 @@ namespace Roadie.Library.Factories
}
sw.Stop();
this.Logger.LogInformation("Scanned Release [{0}] Folder [{1}], Modified Release [{2}], OperationTime [{3}]", release.ToString(), releasePath, modifiedRelease, sw.ElapsedMilliseconds);
this.Logger.LogInformation("Scanned Release `{0}` Folder [{1}], Modified Release [{2}], OperationTime [{3}]", release.ToString(), releasePath, modifiedRelease, sw.ElapsedMilliseconds);
result = true;
}
catch (Exception ex)