#102: Moving Models and constants to their own library
@@ -0,0 +1,26 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
using Icarus.Authorization;
|
||||
|
||||
namespace Icarus.Authorization.Handlers;
|
||||
|
||||
public class HasScopeHandler : AuthorizationHandler<HasScopeRequirement>
|
||||
{
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, HasScopeRequirement requirement)
|
||||
{
|
||||
if (!context.User.HasClaim(c => c.Type == "scope" && c.Issuer == requirement.Issuer))
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var scopes = context.User.FindFirst(c =>
|
||||
c.Type == "scope" && c.Issuer == requirement.Issuer)!.Value.Split(' ');
|
||||
|
||||
if (scopes.Any(s => s == requirement.Scope))
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Icarus.Authorization;
|
||||
|
||||
public class HasScopeRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public string Issuer { get; }
|
||||
public string Scope { get; }
|
||||
|
||||
|
||||
public HasScopeRequirement(string scope, string issuer)
|
||||
{
|
||||
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
|
||||
Issuer = issuer ?? throw new ArgumentNullException(nameof(issuer));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using Org.BouncyCastle.OpenSsl;
|
||||
|
||||
|
||||
namespace Icarus.Certs;
|
||||
|
||||
public class SigningIssuerCertificate : IDisposable
|
||||
{
|
||||
private readonly System.Security.Cryptography.RSACryptoServiceProvider _rsa;
|
||||
|
||||
public SigningIssuerCertificate()
|
||||
{
|
||||
_rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
|
||||
}
|
||||
|
||||
public RsaSecurityKey GetIssuerSigningKey(string publicKeyPath)
|
||||
{
|
||||
var file = publicKeyPath;
|
||||
var publicKey = System.IO.File.ReadAllText(file);
|
||||
|
||||
using (var reader = System.IO.File.OpenText(file))
|
||||
{
|
||||
var pem = new PemReader(reader);
|
||||
var o = (RsaKeyParameters)pem.ReadObject();
|
||||
var parameters = new System.Security.Cryptography.RSAParameters();
|
||||
parameters.Modulus = o.Modulus.ToByteArray();
|
||||
parameters.Exponent = o.Exponent.ToByteArray();
|
||||
_rsa.ImportParameters(parameters);
|
||||
}
|
||||
|
||||
return new RsaSecurityKey(_rsa);
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_rsa?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class SigningAudienceCertificate : IDisposable
|
||||
{
|
||||
private readonly System.Security.Cryptography.RSACryptoServiceProvider _rsa;
|
||||
|
||||
public SigningAudienceCertificate()
|
||||
{
|
||||
_rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
|
||||
}
|
||||
|
||||
public SigningCredentials GetAudienceSigningKey(string keyPath)
|
||||
{
|
||||
var file = keyPath;
|
||||
var publicKey = System.IO.File.ReadAllText(file);
|
||||
|
||||
using (var reader = System.IO.File.OpenText(file))
|
||||
{
|
||||
var pem = new PemReader(reader);
|
||||
var o = (RsaKeyParameters)pem.ReadObject();
|
||||
var parameters = new System.Security.Cryptography.RSAParameters();
|
||||
parameters.Modulus = o.Modulus.ToByteArray();
|
||||
parameters.Exponent = o.Exponent.ToByteArray();
|
||||
_rsa.ImportParameters(parameters);
|
||||
}
|
||||
|
||||
return new SigningCredentials(
|
||||
key: new RsaSecurityKey(_rsa),
|
||||
algorithm: SecurityAlgorithms.RsaSha256);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_rsa?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.Managers;
|
||||
|
||||
public class AlbumManager : BaseManager
|
||||
{
|
||||
#region Fields
|
||||
private AlbumContext? _albumContext;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public AlbumManager(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
_albumContext = new AlbumContext(_connectionString!);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public void SaveAlbumToDatabase(ref Song song)
|
||||
{
|
||||
_logger.Info("Starting process to save the album record of the song to the database");
|
||||
|
||||
var album = new Album();
|
||||
|
||||
album.Title = song.AlbumTitle;
|
||||
album.AlbumArtist = song.Artist;
|
||||
album.Year = song.Year!.Value;
|
||||
var albumTitle = song.AlbumTitle;
|
||||
var albumArtist = song.Artist;
|
||||
|
||||
var albumRetrieved = _albumContext!.Albums!.FirstOrDefault(alb => alb.Title!.Equals(albumTitle) && alb.AlbumArtist!.Equals(albumArtist));
|
||||
|
||||
if (albumRetrieved == null)
|
||||
{
|
||||
album.SongCount = 1;
|
||||
_albumContext.Add(album);
|
||||
_albumContext.SaveChanges();
|
||||
|
||||
Console.WriteLine($"Album Id {album.Id}");
|
||||
}
|
||||
else
|
||||
{
|
||||
album.Id = albumRetrieved.Id;
|
||||
}
|
||||
|
||||
song.AlbumId = album.Id;
|
||||
}
|
||||
|
||||
|
||||
public void DeleteAlbumFromDatabase(Song song)
|
||||
{
|
||||
var album = _albumContext!.Albums!.FirstOrDefault(alb => alb.Title!.Equals(song.AlbumTitle));
|
||||
|
||||
if (album == null)
|
||||
{
|
||||
_logger.Info("Cannot delete the album record because it does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
DeleteAlbumFromDb(album);
|
||||
}
|
||||
|
||||
|
||||
public Album UpdateAlbumInDatabase(Song oldSong, Song newSong)
|
||||
{
|
||||
var albumRecord = _albumContext!.Albums!.FirstOrDefault(alb => alb.Title!.Equals(oldSong.AlbumTitle));
|
||||
var oldAlbumTitle = oldSong.AlbumTitle;
|
||||
var oldAlbumArtist = oldSong.Artist;
|
||||
var newAlbumTitle = newSong.AlbumTitle;
|
||||
var newAlbumArtist = newSong.Artist;
|
||||
|
||||
var info = string.Empty;
|
||||
|
||||
if (string.IsNullOrEmpty(newAlbumArtist))
|
||||
newAlbumArtist = oldAlbumArtist;
|
||||
if (string.IsNullOrEmpty(newAlbumTitle))
|
||||
newAlbumTitle = oldAlbumTitle;
|
||||
|
||||
if ((string.IsNullOrEmpty(newAlbumTitle) && string.IsNullOrEmpty(newAlbumArtist) ||
|
||||
oldAlbumTitle!.Equals(newAlbumTitle) && oldAlbumArtist!.Equals(newAlbumArtist)))
|
||||
{
|
||||
_logger.Info("No change to the song's album");
|
||||
return albumRecord!;
|
||||
}
|
||||
|
||||
info = "Change to the song's album";
|
||||
_logger.Info(info);
|
||||
|
||||
var existingAlbumRecord = _albumContext.Albums!.FirstOrDefault(alb => alb.Title!.Equals(oldSong.AlbumTitle));
|
||||
if (existingAlbumRecord == null)
|
||||
{
|
||||
_logger.Info("Creating new album record");
|
||||
|
||||
var newAlbumRecord = new Album
|
||||
{
|
||||
Title = newAlbumTitle,
|
||||
AlbumArtist = newAlbumArtist,
|
||||
Year = newSong.Year!.Value
|
||||
};
|
||||
|
||||
_albumContext.Add(newAlbumRecord);
|
||||
_albumContext.SaveChanges();
|
||||
|
||||
return newAlbumRecord;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("Updating existing album record");
|
||||
|
||||
existingAlbumRecord = _albumContext.Albums!.FirstOrDefault(alb => alb.Title!.Equals(newSong.AlbumTitle));
|
||||
existingAlbumRecord!.AlbumArtist = newAlbumArtist;
|
||||
|
||||
_albumContext.Update(existingAlbumRecord);
|
||||
_albumContext.SaveChanges();
|
||||
|
||||
return existingAlbumRecord;
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteAlbumFromDb(Album album)
|
||||
{
|
||||
if (SongsInAlbum(album) <= 1)
|
||||
{
|
||||
_albumContext!.Remove(album);
|
||||
_albumContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private int SongsInAlbum(Album album)
|
||||
{
|
||||
var sngContext = new SongContext(_connectionString!);
|
||||
var songs = sngContext!.Songs!.Where(sng => sng.AlbumId == album.Id).ToList();
|
||||
|
||||
return songs.Count;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.Managers;
|
||||
|
||||
public class ArtistManager : BaseManager
|
||||
{
|
||||
#region Fields
|
||||
private ArtistContext? _artistContext;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public ArtistManager(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
_artistContext = new ArtistContext(_connectionString!);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public void SaveArtistToDatabase(ref Song song)
|
||||
{
|
||||
_logger.Info("Starting process to save the artist record of the song to the database");
|
||||
|
||||
var artist = new Artist
|
||||
{
|
||||
Name = song.Artist,
|
||||
SongCount = 1
|
||||
};
|
||||
|
||||
var artistRetrieved = _artistContext!.Artists.FirstOrDefault(art => art.Name!.Equals(artist.Name));
|
||||
|
||||
if (artistRetrieved == null)
|
||||
{
|
||||
artist.SongCount = 1;
|
||||
_artistContext.Add(artist);
|
||||
_artistContext.SaveChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
artist.Id = artistRetrieved.Id;
|
||||
}
|
||||
|
||||
song.ArtistId = artist.Id;
|
||||
}
|
||||
|
||||
public Artist UpdateArtistInDatabase(Song oldSongRecord, Song newSongRecord)
|
||||
{
|
||||
var oldArtistRecord = _artistContext!.Artists.FirstOrDefault(art => art.Name!.Equals(oldSongRecord.AlbumTitle));
|
||||
var oldArtistName = oldArtistRecord!.Name;
|
||||
var newArtistName = newSongRecord.Artist;
|
||||
|
||||
if (string.IsNullOrEmpty(newArtistName) || oldArtistName!.Equals(newArtistName))
|
||||
{
|
||||
_logger.Info("No change to the song's Artist");
|
||||
return oldArtistRecord;
|
||||
}
|
||||
|
||||
_logger.Info("Change to the song's record found");
|
||||
|
||||
if (oldArtistRecord.SongCount <= 1)
|
||||
{
|
||||
_logger.Info("Deleting artist record that no longer has any songs");
|
||||
|
||||
_artistContext.Remove(oldArtistRecord);
|
||||
_artistContext.SaveChanges();
|
||||
}
|
||||
|
||||
if (!(_artistContext.Artists.FirstOrDefault(art => art.Name!.Equals(oldSongRecord.AlbumTitle)) != null))
|
||||
{
|
||||
_logger.Info("Creating new artist record");
|
||||
|
||||
var newArtistRecord = new Artist
|
||||
{
|
||||
Name = newArtistName
|
||||
};
|
||||
|
||||
_artistContext.Add(newArtistRecord);
|
||||
_artistContext.SaveChanges();
|
||||
|
||||
return newArtistRecord;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("Updating existing artist record");
|
||||
|
||||
var existingArtistRecord = _artistContext.Artists.FirstOrDefault(art => art.Name!.Equals(newSongRecord.AlbumTitle));
|
||||
|
||||
_artistContext.Update(existingArtistRecord!);
|
||||
_artistContext.SaveChanges();
|
||||
|
||||
return existingArtistRecord!;
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteArtistFromDatabase(Song song)
|
||||
{
|
||||
if (!(_artistContext!.Artists.FirstOrDefault(art => art.Name!.Equals(song.Artist)) != null))
|
||||
{
|
||||
_logger.Info("Cannot delete the artist record because it does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
var artist = _artistContext.Artists.FirstOrDefault(art => art.Name!.Equals(song.Artist));
|
||||
|
||||
if (SongsOfArtist(artist!) <= 1)
|
||||
{
|
||||
_artistContext!.Remove(artist!);
|
||||
_artistContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private int SongsOfArtist(Artist artist)
|
||||
{
|
||||
var sngContext = new SongContext(_connectionString!);
|
||||
var songs = sngContext.Songs!.Where(sng => sng.ArtistId == artist.Id).ToList();
|
||||
|
||||
return songs.Count;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using NLog;
|
||||
|
||||
namespace Icarus.Controllers.Managers;
|
||||
|
||||
public class BaseManager
|
||||
{
|
||||
#region Fields
|
||||
protected static Logger _logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
|
||||
protected IConfiguration? _config;
|
||||
protected string? _connectionString;
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
using Icarus.Constants;
|
||||
using Icarus.Controllers.Utilities;
|
||||
using Icarus.Database.Contexts;
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Controllers.Managers;
|
||||
|
||||
public class CoverArtManager : BaseManager
|
||||
{
|
||||
#region Fields
|
||||
private string? _rootCoverArtPath;
|
||||
private CoverArtContext? _coverArtContext;
|
||||
private byte[]? _stockCoverArt = null;
|
||||
private const string? _filename = "CoverArt.png";
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public CoverArtManager(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
_rootCoverArtPath = _config.GetValue<string>("CoverArtPath");
|
||||
Initialize();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public void SaveCoverArtToDatabase(ref Song song, ref CoverArt coverArt)
|
||||
{
|
||||
_logger.Info("Saving cover art record to the database");
|
||||
_coverArtContext!.Add(coverArt);
|
||||
_coverArtContext.SaveChanges();
|
||||
|
||||
song.CoverArtId = coverArt.Id;
|
||||
}
|
||||
public void DeleteCoverArtFromDatabase(CoverArt coverArt)
|
||||
{
|
||||
_logger.Info("Attempting to delete cover art from the database");
|
||||
|
||||
_coverArtContext!.Attach(coverArt);
|
||||
_coverArtContext.Remove(coverArt);
|
||||
_coverArtContext.SaveChanges();
|
||||
}
|
||||
public void DeleteCoverArt(CoverArt coverArt)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stockCoverArtPath = _rootCoverArtPath + _filename;
|
||||
if (!string.Equals(stockCoverArtPath, coverArt.ImagePath(),
|
||||
StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
_logger.Info("Song does not contain the stock cover art");
|
||||
File.Delete(coverArt.ImagePath());
|
||||
_logger.Info("Cover art deleted from the filesystem");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("Song contains the stock cover art");
|
||||
_logger.Info("Will not delete from from the filesystem");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger.Error(msg, "An error occurred");
|
||||
}
|
||||
}
|
||||
|
||||
public CoverArt? SaveCoverArt(Song song)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dirMgr = new DirectoryManager(_rootCoverArtPath!);
|
||||
var defaultExtension = ".png";
|
||||
dirMgr.CreateDirectory(song);
|
||||
|
||||
var coverArt = new CoverArt
|
||||
{
|
||||
SongTitle = song.Title!
|
||||
};
|
||||
|
||||
var segment = coverArt.GenerateFilename(0);
|
||||
coverArt.Directory = dirMgr.SongDirectory;
|
||||
coverArt.Filename = segment + defaultExtension;
|
||||
|
||||
var metaData = new MetadataRetriever();
|
||||
var imgBytes = metaData.RetrieveCoverArtBytes(song);
|
||||
|
||||
if (imgBytes != null)
|
||||
{
|
||||
_logger.Info("Saving cover art to the filesystem");
|
||||
File.WriteAllBytes(coverArt.ImagePath(), imgBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("Song has no cover art, applying stock cover art");
|
||||
|
||||
var coverArtFilePath = _rootCoverArtPath + $"{segment}{defaultExtension}";
|
||||
coverArt.Directory = DirectoryPaths.CoverArtDirectory;
|
||||
coverArt.Filename = DirectoryPaths.CoverArtFilename;
|
||||
metaData.UpdateCoverArt(song, coverArt);
|
||||
coverArt.Directory = this._rootCoverArtPath;
|
||||
coverArt.Filename = $"{segment}{defaultExtension}";
|
||||
File.WriteAllBytes(coverArt.ImagePath(), _stockCoverArt!);
|
||||
}
|
||||
|
||||
coverArt.Type = metaData.CoverArtFileExtensionType(coverArt);
|
||||
if (string.IsNullOrEmpty(coverArt.Type))
|
||||
{
|
||||
Console.WriteLine("File type is empty");
|
||||
Console.WriteLine($"Directory: {coverArt.Directory}");
|
||||
Console.WriteLine($"Filename: {coverArt.Filename}");
|
||||
}
|
||||
|
||||
return coverArt;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger.Error(msg, "An error occurred");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public CoverArt SaveCoverArt(IFormFile data, Song song)
|
||||
{
|
||||
var cover = new CoverArt { SongTitle = song.Title };
|
||||
|
||||
try
|
||||
{
|
||||
MetadataRetriever metaData = new MetadataRetriever();
|
||||
var dirMgr = new DirectoryManager(_rootCoverArtPath!);
|
||||
cover.Type = metaData.FileExtensionType(data);
|
||||
var defaultExtension = "." + cover.Type;
|
||||
dirMgr.CreateDirectory(song);
|
||||
|
||||
var segment = cover.GenerateFilename(0);
|
||||
var imagePath = dirMgr.SongDirectory + segment + defaultExtension;
|
||||
|
||||
cover.Directory = dirMgr.SongDirectory;
|
||||
cover.Filename = segment + defaultExtension;
|
||||
|
||||
using (var fileStream = new FileStream(cover.ImagePath(), FileMode.Create))
|
||||
{
|
||||
data.CopyTo(fileStream);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex.Message, "An error occurred");
|
||||
}
|
||||
|
||||
return cover;
|
||||
}
|
||||
|
||||
public CoverArt GetCoverArt(Song song)
|
||||
{
|
||||
var title = song.Title;
|
||||
var cov = _coverArtContext!.CoverArtImages!.FirstOrDefault(cov => cov.SongTitle!.Equals(title));
|
||||
return cov!;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_coverArtContext = new CoverArtContext(_connectionString!);
|
||||
var path = DirectoryPaths.CoverArtDirectory + DirectoryPaths.CoverArtFilename;
|
||||
|
||||
if (System.IO.File.Exists(path))
|
||||
_stockCoverArt = File.ReadAllBytes(path);
|
||||
|
||||
if (!File.Exists(_rootCoverArtPath + _filename))
|
||||
{
|
||||
File.WriteAllBytes(_rootCoverArtPath + _filename,
|
||||
_stockCoverArt!);
|
||||
Console.WriteLine("Copied Stock Cover Art");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
using Icarus.Models;
|
||||
using Icarus.Types;
|
||||
|
||||
namespace Icarus.Controllers.Managers;
|
||||
|
||||
// NOTE: Do not use metadata for the song's metadata
|
||||
public class DirectoryManager : BaseManager
|
||||
{
|
||||
#region Fields
|
||||
private Song? _song;
|
||||
private string? _rootSongDirectory;
|
||||
private string? _songDirectory;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
public string? SongDirectory
|
||||
{
|
||||
get => _songDirectory;
|
||||
set => _songDirectory = value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public DirectoryManager(IConfiguration config, Song song)
|
||||
{
|
||||
_config = config;
|
||||
_song = song;
|
||||
Initialize();
|
||||
}
|
||||
public DirectoryManager(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
Initialize();
|
||||
}
|
||||
public DirectoryManager(string rootDirectory)
|
||||
{
|
||||
_rootSongDirectory = rootDirectory;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
// Does not include extension
|
||||
public static string GenerateFilename(int length)
|
||||
{
|
||||
string chars = Constants.DirectoryPaths.FILENAME_CHARACTERS;
|
||||
var random = new Random();
|
||||
return new string(Enumerable.Repeat(chars, length).Select(s =>
|
||||
s[random.Next(s.Length)]).ToArray());
|
||||
}
|
||||
|
||||
public static string GenerateDownloadFilename(int length, string extension, string title, bool? randomize)
|
||||
{
|
||||
if (randomize.HasValue && randomize.Value)
|
||||
{
|
||||
return GenerateFilename(length) + extension;
|
||||
}
|
||||
else
|
||||
{
|
||||
return title + extension;
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateDirectory()
|
||||
{
|
||||
this.CreateDirectory(_song!);
|
||||
}
|
||||
|
||||
public void CreateDirectory(Song song)
|
||||
{
|
||||
this._song = song;
|
||||
|
||||
try
|
||||
{
|
||||
_songDirectory = AlbumDirectory();
|
||||
|
||||
if (!Directory.Exists(_songDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(_songDirectory);
|
||||
Console.WriteLine($"The directory has been created");
|
||||
}
|
||||
|
||||
Console.WriteLine($"The song will be saved in the following" +
|
||||
$" directory: {_songDirectory}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger.Error(msg, "An error occurred");
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteEmptyDirectories()
|
||||
{
|
||||
try
|
||||
{
|
||||
var albumDirectory = AlbumDirectory();
|
||||
var artistDirectory = ArtistDirectory();
|
||||
if (IsDirectoryEmpty(albumDirectory))
|
||||
{
|
||||
Directory.Delete(albumDirectory);
|
||||
Console.WriteLine($"directory {albumDirectory} deleted");
|
||||
}
|
||||
if (IsDirectoryEmpty(artistDirectory))
|
||||
{
|
||||
Directory.Delete(artistDirectory);
|
||||
Console.WriteLine($"directory {artistDirectory} deleted");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var exMsg = ex.Message;
|
||||
Console.WriteLine($"An error occurred {exMsg}");
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteEmptyDirectories(Song song)
|
||||
{
|
||||
try
|
||||
{
|
||||
var albumDirectory = AlbumDirectory(song);
|
||||
var artistDirectory = ArtistDirectory(song);
|
||||
|
||||
if (IsDirectoryEmpty(albumDirectory))
|
||||
{
|
||||
Directory.Delete(albumDirectory);
|
||||
_logger.Info("Album directory deleted");
|
||||
}
|
||||
if (IsDirectoryEmpty(artistDirectory))
|
||||
{
|
||||
Directory.Delete(artistDirectory);
|
||||
_logger.Info("Artist directory deleted");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger.Error(msg, "An error occurred");
|
||||
}
|
||||
}
|
||||
|
||||
public string RetrieveAlbumPath(Song song)
|
||||
{
|
||||
_logger.Info("Retrieving album song path");
|
||||
|
||||
var albumPath = string.Empty;
|
||||
albumPath = AlbumDirectory(song);
|
||||
|
||||
return albumPath;
|
||||
}
|
||||
public string RetrieveArtistPath(Song song)
|
||||
{
|
||||
_logger.Info("Retrieving artist path");
|
||||
|
||||
var artistPath = string.Empty;
|
||||
artistPath = ArtistDirectory(song);
|
||||
|
||||
return artistPath;
|
||||
}
|
||||
|
||||
public string GenerateSongPath(Song song)
|
||||
{
|
||||
_logger.Info("Generating song path");
|
||||
|
||||
var artistPath = ArtistDirectory(song);
|
||||
var albumPath = AlbumDirectory(song);
|
||||
|
||||
GenerateDirectories(new List<DirEnt>{
|
||||
new DirEnt
|
||||
{
|
||||
Pre = "Artist path does not exist",
|
||||
Path = artistPath,
|
||||
Post = "Creating artist path"
|
||||
},
|
||||
new DirEnt
|
||||
{
|
||||
Pre = "Album path does not exist",
|
||||
Path = albumPath,
|
||||
Post = "Created album path"
|
||||
}
|
||||
});
|
||||
|
||||
return albumPath;
|
||||
}
|
||||
|
||||
private class DirEnt
|
||||
{
|
||||
public string? Pre { get; set;}
|
||||
public string? Path { get; set; }
|
||||
public string? Post { get; set; }
|
||||
}
|
||||
|
||||
private void GenerateDirectories(List<DirEnt> dirs)
|
||||
{
|
||||
foreach (var di in dirs)
|
||||
{
|
||||
_logger.Info(di.Pre);
|
||||
Directory.CreateDirectory(di.Path!);
|
||||
_logger.Info(di.Post);
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize(DirectoryType dirTypes = DirectoryType.Music)
|
||||
{
|
||||
switch (dirTypes)
|
||||
{
|
||||
case DirectoryType.Music:
|
||||
_rootSongDirectory = _config!.GetValue<string>("RootMusicPath")!;
|
||||
break;
|
||||
case DirectoryType.CoverArt:
|
||||
_rootSongDirectory = _config!.GetValue<string>("CoverArtPath")!;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDirectoryEmpty(string path)
|
||||
{
|
||||
return !Directory.EnumerateFileSystemEntries(path).Any();
|
||||
}
|
||||
|
||||
private string AlbumDirectory()
|
||||
{
|
||||
return AlbumDirectory(_song!);
|
||||
}
|
||||
private string AlbumDirectory(Song song)
|
||||
{
|
||||
var directory = ArtistDirectory(song);
|
||||
var segment = SerializeValue(song.AlbumTitle!);
|
||||
directory += $@"{segment}/";
|
||||
Console.WriteLine($"Album directory {directory}");
|
||||
|
||||
return directory;
|
||||
}
|
||||
private string ArtistDirectory()
|
||||
{
|
||||
return ArtistDirectory(_song!);
|
||||
}
|
||||
private string ArtistDirectory(Song song)
|
||||
{
|
||||
var directory = _rootSongDirectory;
|
||||
var segment = SerializeValue(song.Artist!);
|
||||
directory += $@"{segment}/";
|
||||
Console.WriteLine($"Artist directory {directory}");
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private string SerializeValue(string value)
|
||||
{
|
||||
const int length = 15;
|
||||
const string chars = "ABCDEF0123456789";
|
||||
var random = new Random();
|
||||
var output = new string(Enumerable.Repeat(chars, length).Select(s =>
|
||||
s[random.Next(s.Length)]).ToArray());
|
||||
|
||||
return output;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.Managers;
|
||||
|
||||
public class GenreManager : BaseManager
|
||||
{
|
||||
#region Fields
|
||||
private GenreContext? _genreContext;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public GenreManager(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
_genreContext = new GenreContext(_connectionString!);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public void SaveGenreToDatabase(ref Song song)
|
||||
{
|
||||
_logger.Info("Starting process to save the genre record of the song to the database");
|
||||
|
||||
var genre = new Genre
|
||||
{
|
||||
GenreName = song.Genre,
|
||||
SongCount = 1
|
||||
};
|
||||
|
||||
var genreName = song.Genre;
|
||||
var genreRetrieved = _genreContext!.Genres!.FirstOrDefault(gnr => gnr.GenreName!.Equals(genreName));
|
||||
|
||||
if (genreRetrieved == null)
|
||||
{
|
||||
_genreContext.Add(genre);
|
||||
_genreContext.SaveChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
genre.Id = genreRetrieved.Id;
|
||||
}
|
||||
|
||||
song.GenreId = genre.Id;
|
||||
}
|
||||
|
||||
public Genre UpdateGenreInDatabase(Song oldSongRecord, Song newSongRecord)
|
||||
{
|
||||
var oldGenreRecord = _genreContext!.Genres!.FirstOrDefault(gnr => gnr.GenreName!.Equals(oldSongRecord.Genre));
|
||||
var oldGenreName = oldGenreRecord!.GenreName;
|
||||
var newGenreName = newSongRecord.Genre;
|
||||
|
||||
if (string.IsNullOrEmpty(newGenreName) || oldGenreName!.Equals(newGenreName))
|
||||
{
|
||||
_logger.Info("No change to the song's Genre");
|
||||
return oldGenreRecord;
|
||||
}
|
||||
|
||||
_logger.Info("Change to the song's genre found");
|
||||
|
||||
if (oldGenreRecord.SongCount <= 1)
|
||||
{
|
||||
_logger.Info("Deleting genre record");
|
||||
|
||||
_genreContext.Remove(oldGenreRecord);
|
||||
_genreContext.SaveChanges();
|
||||
}
|
||||
|
||||
if (!(_genreContext.Genres!.FirstOrDefault(gnr => gnr.GenreName!.Equals(oldSongRecord.Genre)) != null))
|
||||
{
|
||||
_logger.Info("Creating new genre record");
|
||||
|
||||
var newGenreRecord = new Genre
|
||||
{
|
||||
GenreName = newGenreName
|
||||
};
|
||||
|
||||
_genreContext.Add(newGenreRecord);
|
||||
_genreContext.SaveChanges();
|
||||
|
||||
return newGenreRecord;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Info("Updating existing genre record");
|
||||
|
||||
var existingGenreRecord = _genreContext.Genres!.FirstOrDefault(gnr => gnr.GenreName!.Equals(oldGenreRecord.GenreName));
|
||||
|
||||
_genreContext.Update(existingGenreRecord!);
|
||||
_genreContext.SaveChanges();
|
||||
|
||||
return existingGenreRecord!;
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteGenreFromDatabase(Song song)
|
||||
{
|
||||
if (!(_genreContext!.Genres!.FirstOrDefault(gnr => gnr.GenreName!.Equals(song.Genre)) != null))
|
||||
{
|
||||
_logger.Info("Cannot delete the genre record because it does not exist");
|
||||
return;
|
||||
}
|
||||
|
||||
var genre = _genreContext.Genres!.FirstOrDefault(gnr => gnr.GenreName!.Equals(song.Genre));
|
||||
|
||||
if (SongsInGenre(genre!) <= 1)
|
||||
{
|
||||
_genreContext.Remove(genre!);
|
||||
_genreContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private int SongsInGenre(Genre genre)
|
||||
{
|
||||
var sngContext = new SongContext(_connectionString!);
|
||||
var songs = sngContext.Songs!.Where(sng => sng.GenreId == genre.Id).ToList();
|
||||
|
||||
return songs.Count;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
using NLog;
|
||||
|
||||
using Icarus.Controllers.Utilities;
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.Managers;
|
||||
|
||||
public class SongManager : BaseManager
|
||||
{
|
||||
#region Fields
|
||||
private string? _tempDirectoryRoot;
|
||||
private string? _archiveDirectoryRoot;
|
||||
private string? _compressedSongFilename;
|
||||
private string? _message;
|
||||
private SongContext? _songContext;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
public string? ArchiveDirectoryRoot
|
||||
{
|
||||
get => _archiveDirectoryRoot;
|
||||
set => _archiveDirectoryRoot = value;
|
||||
}
|
||||
public string? CompressedSongFilename
|
||||
{
|
||||
get => _compressedSongFilename;
|
||||
set => _compressedSongFilename = value;
|
||||
}
|
||||
public string? Message
|
||||
{
|
||||
get => _message;
|
||||
set => _message = value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public SongManager(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
Initialize();
|
||||
}
|
||||
public SongManager(IConfiguration config, string tempDirectoryRoot)
|
||||
{
|
||||
_config = config;
|
||||
_tempDirectoryRoot = tempDirectoryRoot;
|
||||
Initialize();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public SongResult UpdateSong(Song song)
|
||||
{
|
||||
var result = new SongResult();
|
||||
if (!DoesSongExist(song))
|
||||
{
|
||||
result.SongTitle = song.Title;
|
||||
result.Message = "Song does not exist";
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var oldSongRecord = _songContext!.RetrieveRecord(song);
|
||||
song.Filename = oldSongRecord.Filename;
|
||||
song.SongDirectory = oldSongRecord.SongDirectory;
|
||||
|
||||
MetadataRetriever updateMetadata = new MetadataRetriever();
|
||||
updateMetadata.UpdateMetadata(song, oldSongRecord);
|
||||
|
||||
var updatedSong = updateMetadata.UpdatedSongRecord;
|
||||
|
||||
var albMgr = new AlbumManager(_config!);
|
||||
var gnrMgr = new GenreManager(_config!);
|
||||
var artMgr = new ArtistManager(_config!);
|
||||
var updatedAlbum = albMgr.UpdateAlbumInDatabase(oldSongRecord, updatedSong!);
|
||||
oldSongRecord.AlbumId = updatedAlbum.Id;
|
||||
|
||||
var updatedArtist = artMgr.UpdateArtistInDatabase(oldSongRecord, updatedSong!);
|
||||
oldSongRecord.ArtistId = updatedArtist.Id;
|
||||
|
||||
var updatedGenre = gnrMgr.UpdateGenreInDatabase(oldSongRecord, updatedSong!);
|
||||
oldSongRecord.GenreId = updatedGenre.Id;
|
||||
|
||||
UpdateSongInDatabase(ref oldSongRecord, ref updatedSong!, ref result);
|
||||
|
||||
DeleteEmptyDirectories(ref oldSongRecord, ref updatedSong);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger.Error(msg, "An error occurred");
|
||||
|
||||
result.Message = $"An error occurred: {msg}";
|
||||
result.SongTitle = song.Title;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool DeleteSongFromFileSystem(Song songMetaData)
|
||||
{
|
||||
bool successful = false;
|
||||
try
|
||||
{
|
||||
var songPath = songMetaData.SongPath();
|
||||
File.Delete(songPath);
|
||||
successful = true;
|
||||
DirectoryManager dirMgr = new DirectoryManager(_config!, songMetaData);
|
||||
dirMgr.DeleteEmptyDirectories();
|
||||
Console.WriteLine("Song successfully deleted");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var exMsg = ex.Message;
|
||||
}
|
||||
|
||||
return successful;
|
||||
}
|
||||
|
||||
public bool DoesSongExist(Song song)
|
||||
{
|
||||
if (!_songContext!.DoesRecordExist(song))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
public void DeleteSong(Song song)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (DeleteSongFromFilesystem(song, true))
|
||||
{
|
||||
_logger.Error("Failed to delete the song");
|
||||
|
||||
throw new Exception("Failed to delete the song");
|
||||
}
|
||||
_logger.Info("Song deleted from the filesystem");
|
||||
|
||||
var coverMgr = new CoverArtManager(_config!);
|
||||
|
||||
var coverArt = coverMgr.GetCoverArt(song);
|
||||
coverMgr.DeleteCoverArt(coverArt);
|
||||
|
||||
DeleteSongFromDatabase(song);
|
||||
coverMgr.DeleteCoverArtFromDatabase(coverArt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger.Error(msg, "An error occurred");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task SaveSongToFileSystem(IFormFile songFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("Starting the process of saving the song to the filesystem");
|
||||
|
||||
var song = await SaveSongTemp(songFile);
|
||||
|
||||
DirectoryManager dirMgr = new DirectoryManager(_config!, song);
|
||||
dirMgr.CreateDirectory();
|
||||
|
||||
var tempPath = song.SongPath();
|
||||
song.Filename = song.GenerateFilename(true, AudioFileExtensionsType.WAV);
|
||||
var filePath = $"{dirMgr.SongDirectory}{song.Filename}";
|
||||
|
||||
_logger.Info($"Absolute song path: {filePath}");
|
||||
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
this.MoveSongToFinalDestination(tempPath, filePath);
|
||||
});
|
||||
|
||||
song.SongDirectory = dirMgr.SongDirectory;
|
||||
|
||||
var coverMgr = new CoverArtManager(_config!);
|
||||
var coverArt = coverMgr.SaveCoverArt(song);
|
||||
|
||||
SaveSongToDatabase(song, coverArt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger.Error(msg, "An error occurred");
|
||||
}
|
||||
}
|
||||
|
||||
// Change the name of this method to only focus on wav files
|
||||
public Song SaveSongToFileSystem(IFormFile songFile, IFormFile coverArtData, Song song)
|
||||
{
|
||||
if (string.IsNullOrEmpty(song.SongDirectory))
|
||||
{
|
||||
song.SongDirectory = _tempDirectoryRoot;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(song.Filename))
|
||||
{
|
||||
switch (song.AudioType)
|
||||
{
|
||||
case "wav":
|
||||
song.Filename = song.GenerateFilename(true, AudioFileExtensionsType.WAV);
|
||||
break;
|
||||
case "flac":
|
||||
song.Filename = song.GenerateFilename(true, AudioFileExtensionsType.FLAC);
|
||||
break;
|
||||
default:
|
||||
song.Filename = song.GenerateFilename(true, AudioFileExtensionsType.Default);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info($"Temporary directory: {_tempDirectoryRoot}");
|
||||
|
||||
var tempPath = song.SongPath();
|
||||
|
||||
_logger.Info("Temporary song path: {0}", tempPath);
|
||||
|
||||
if (!System.IO.File.Exists(tempPath))
|
||||
{
|
||||
using (var filestream = new FileStream(tempPath, FileMode.Create))
|
||||
{
|
||||
_logger.Info("Saving song to temporary directory");
|
||||
songFile.CopyTo(filestream);
|
||||
}
|
||||
}
|
||||
|
||||
var coverMgr = new CoverArtManager(_config!);
|
||||
var coverArt = coverMgr.SaveCoverArt(coverArtData, song);
|
||||
|
||||
DirectoryManager dirMgr = new DirectoryManager(_config!, song);
|
||||
dirMgr.CreateDirectory();
|
||||
|
||||
song.SongDirectory = dirMgr.SongDirectory;
|
||||
|
||||
var filePath = song.SongPath();
|
||||
_logger.Info($"Absolute song path: {filePath}");
|
||||
|
||||
this.MoveSongToFinalDestination(tempPath, filePath);
|
||||
|
||||
SaveSongToDatabase(song, coverArt);
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
public Song SaveFlacSongToFileSystem(IFormFile songFile, IFormFile coverArtData, Song song)
|
||||
{
|
||||
// Save temp song (Should already be saved to the filesystem by the time it gets to this method)
|
||||
// Save cover art
|
||||
// Update the song's metadata with the song object
|
||||
// Save song to its final directory
|
||||
// Save cover art to the database
|
||||
// Save song to the database
|
||||
|
||||
var coverMgr = new CoverArtManager(_config!);
|
||||
var coverArt = coverMgr.SaveCoverArt(coverArtData, song);
|
||||
|
||||
var meta = new Utilities.MetadataRetriever();
|
||||
meta.UpdateMetadata(song, song);
|
||||
|
||||
DirectoryManager dirMgr = new DirectoryManager(_config!, song);
|
||||
dirMgr.CreateDirectory();
|
||||
|
||||
var tempPath = song.SongPath();
|
||||
|
||||
song.SongDirectory = dirMgr.SongDirectory;
|
||||
song.Filename = song.GenerateFilename(true, AudioFileExtensionsType.FLAC);
|
||||
|
||||
var filePath = song.SongPath();
|
||||
_logger.Info($"Absolute song path: {filePath}");
|
||||
|
||||
this.MoveSongToFinalDestination(tempPath, filePath);
|
||||
|
||||
SaveSongToDatabase(song, coverArt);
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
private void MoveSongToFinalDestination(string sourcePath, string targetPath)
|
||||
{
|
||||
using (var fileStream = new FileStream(targetPath, FileMode.Create))
|
||||
{
|
||||
var songBytes = System.IO.File.ReadAllBytes(sourcePath);
|
||||
|
||||
try
|
||||
{
|
||||
if (System.IO.File.Exists(sourcePath) && System.IO.File.Exists(sourcePath) && fileStream.Length > 0)
|
||||
{
|
||||
System.IO.File.Delete(sourcePath);
|
||||
_logger.Info("Deleted temp song from filesystem: {0}", sourcePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileStream.Write(songBytes, 0, songBytes.Count());
|
||||
_logger.Info("Saved song to filesystem: {0}", targetPath);
|
||||
|
||||
System.IO.File.Delete(sourcePath);
|
||||
_logger.Info("Deleted temp song from filesystem: {0}", sourcePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger.Error($"An error occurred: {msg}");
|
||||
}
|
||||
|
||||
_logger.Info("Song successfully saved to filesystem");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SongData> RetrieveSong(Song songMetaData)
|
||||
{
|
||||
var song = new SongData();
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Fetching song from filesystem");
|
||||
song = await RetrieveSongFromFileSystem(songMetaData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var exMsg = ex.Message;
|
||||
Console.WriteLine($"An error occurred: {exMsg}");
|
||||
}
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async Task<SongData> RetrieveSongFromFileSystem(Song details)
|
||||
{
|
||||
byte[] uncompressedSong = await System.IO.File.ReadAllBytesAsync(details.SongPath());
|
||||
|
||||
return new SongData
|
||||
{
|
||||
Data = uncompressedSong
|
||||
};
|
||||
}
|
||||
public async Task<Song> SaveSongTemp(IFormFile songFile)
|
||||
{
|
||||
_logger.Info("Assigning song filename");
|
||||
var song = new Song { SongDirectory = this._tempDirectoryRoot! };
|
||||
var filename = await song.GenerateFilenameAsync(false) + "-" + songFile.FileName;
|
||||
song.Filename = filename;
|
||||
var songPath = song.SongPath();
|
||||
|
||||
using (var filestream = new FileStream(songPath, FileMode.Create))
|
||||
{
|
||||
_logger.Info("Saving temp song: {0}", songPath);
|
||||
await songFile.CopyToAsync(filestream);
|
||||
}
|
||||
|
||||
song.DateCreated = DateTime.Now;
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
|
||||
private bool SongRecordChanged(Song currentSong, Song songUpdates)
|
||||
{
|
||||
var currentTitle = currentSong.Title;
|
||||
var currentArtist = currentSong.Artist;
|
||||
var currentAlbum = currentSong.AlbumTitle;
|
||||
var currentGenre = currentSong.Genre;
|
||||
var currentYear = currentSong.Year;
|
||||
|
||||
if (!currentTitle!.Equals(songUpdates.Title) || !currentArtist!.Equals(songUpdates.Artist) ||
|
||||
!currentAlbum!.Equals(songUpdates.AlbumTitle) ||
|
||||
!currentGenre!.Equals(songUpdates.Genre) || currentYear != songUpdates.Year)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void DeleteEmptyDirectories(ref Song oldSong, ref Song updatedSong)
|
||||
{
|
||||
DirectoryManager mgr = new DirectoryManager(_config!);
|
||||
|
||||
_logger.Info("Checking to see if there are any directories to delete");
|
||||
mgr.DeleteEmptyDirectories(oldSong);
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
_connectionString = _config!.GetConnectionString("DefaultConnection");
|
||||
_songContext = new SongContext(_connectionString!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error Occurred: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void SaveSongToDatabase(Song song, CoverArt? cover)
|
||||
{
|
||||
_logger.Info("Starting process to save the song to the database");
|
||||
|
||||
var albumMgr = new AlbumManager(_config!);
|
||||
var artistMgr = new ArtistManager(_config!);
|
||||
var genreMgr = new GenreManager(_config!);
|
||||
var coverMgr = new CoverArtManager(_config!);
|
||||
albumMgr.SaveAlbumToDatabase(ref song);
|
||||
artistMgr.SaveArtistToDatabase(ref song);
|
||||
genreMgr.SaveGenreToDatabase(ref song);
|
||||
|
||||
var info = "Saving Song to DB";
|
||||
_logger.Info(info);
|
||||
|
||||
_songContext!.Add(song);
|
||||
_songContext!.SaveChanges();
|
||||
|
||||
coverMgr.SaveCoverArtToDatabase(ref song, ref cover!);
|
||||
}
|
||||
|
||||
|
||||
private bool DeleteSongFromFilesystem(Song song, bool deleteDirectory = false)
|
||||
{
|
||||
var songPath = song.SongPath();
|
||||
|
||||
_logger.Info("Deleting song from the filesystem");
|
||||
|
||||
try
|
||||
{
|
||||
System.IO.File.Delete(songPath);
|
||||
|
||||
DeleteEmptyDirectories(ref song, ref song);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger.Error(msg, "An error occurred when attempting to delete the song from the filesystem");
|
||||
return false;
|
||||
}
|
||||
|
||||
return DoesSongExistOnFilesystem(song);
|
||||
}
|
||||
|
||||
private bool DoesSongExistOnFilesystem(Song song)
|
||||
{
|
||||
if (!System.IO.File.Exists(song.SongPath()))
|
||||
{
|
||||
_logger.Info("Song does not exist on the filesystem");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.Info("Song exists on the filesystem");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void UpdateSongInDatabase(ref Song oldSongRecord, ref Song newSongRecord, ref SongResult result)
|
||||
{
|
||||
var updatedSongRecord = oldSongRecord;
|
||||
|
||||
var songContext = new SongContext(_connectionString!);
|
||||
|
||||
if (!SongRecordChanged(oldSongRecord, newSongRecord))
|
||||
{
|
||||
_logger.Info("No change to the song record");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.Info("Changes to song record found");
|
||||
|
||||
if (!string.IsNullOrEmpty(newSongRecord.Title))
|
||||
updatedSongRecord.Title = newSongRecord.Title;
|
||||
if (!string.IsNullOrEmpty(newSongRecord.AlbumTitle))
|
||||
{
|
||||
updatedSongRecord.AlbumTitle = newSongRecord.AlbumTitle;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(newSongRecord.Artist))
|
||||
{
|
||||
updatedSongRecord.Artist = newSongRecord.Artist;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(newSongRecord.Genre))
|
||||
{
|
||||
updatedSongRecord.Genre = newSongRecord.Genre;
|
||||
Console.WriteLine("Genre changed");
|
||||
Console.WriteLine($"{updatedSongRecord.Genre} {newSongRecord.Genre}");
|
||||
}
|
||||
if (newSongRecord.Year != null || newSongRecord.Year > 0)
|
||||
updatedSongRecord.Year = newSongRecord.Year;
|
||||
|
||||
_logger.Info("Applied changes to song record");
|
||||
|
||||
|
||||
_logger.Info("Saving song metadata to the database");
|
||||
|
||||
songContext.Update(updatedSongRecord);
|
||||
songContext.SaveChanges();
|
||||
|
||||
newSongRecord = updatedSongRecord;
|
||||
|
||||
result.Message = "Successfully updated song";
|
||||
result.SongTitle = updatedSongRecord.Title!;
|
||||
}
|
||||
|
||||
private void DeleteSongFromDatabase(Song song)
|
||||
{
|
||||
_logger.Info("Starting process to delete records related to the song from the database");
|
||||
|
||||
var albumMgr = new AlbumManager(_config!);
|
||||
var artistMgr = new ArtistManager(_config!);
|
||||
var genreMgr = new GenreManager(_config!);
|
||||
var sngContext = new SongContext(_connectionString!);
|
||||
sngContext.Songs!.Remove(song);
|
||||
sngContext.SaveChanges();
|
||||
artistMgr.DeleteArtistFromDatabase(song);
|
||||
albumMgr.DeleteAlbumFromDatabase(song);
|
||||
genreMgr.DeleteGenreFromDatabase(song);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
using System.Security.Claims;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Text;
|
||||
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
using RestSharp;
|
||||
|
||||
using Icarus.Models;
|
||||
using Microsoft.VisualBasic;
|
||||
|
||||
namespace Icarus.Controllers.Managers;
|
||||
|
||||
#region Classes
|
||||
public class TokenManager : BaseManager
|
||||
{
|
||||
#region Fields
|
||||
private string? _clientId;
|
||||
private string? _clientSecret;
|
||||
private string? _privateKeyPath = string.Empty;
|
||||
private string? _privateKey = string.Empty;
|
||||
private string? _publicKeyPath = string.Empty;
|
||||
private string? _publicKey = string.Empty;
|
||||
private string? _audience;
|
||||
private string? _grantType;
|
||||
private string? _url;
|
||||
private string? SUCCESSFUL_TOKEN_MESSAGE = "Successfully retrieved token";
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public TokenManager(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
InitializeValues();
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public LoginResult RetrieveLoginResult(User user)
|
||||
{
|
||||
_logger.Info("Preparing Auth0 API request");
|
||||
|
||||
var client = new RestClient(_url!);
|
||||
var request = new RestRequest("oauth/token", Method.POST);
|
||||
var tokenRequest = RetrieveTokenRequest();
|
||||
|
||||
_logger.Info("Serializing token object into JSON");
|
||||
var tokenObject = JsonConvert.SerializeObject(tokenRequest);
|
||||
|
||||
request.AddParameter("application/json; charset=utf-8",
|
||||
tokenObject, ParameterType.RequestBody);
|
||||
|
||||
request.RequestFormat = DataFormat.Json;
|
||||
|
||||
_logger.Info("Sending request");
|
||||
IRestResponse response = client.Execute(request);
|
||||
_logger.Info("Response received");
|
||||
|
||||
|
||||
_logger.Info("Deserializing response");
|
||||
var tokenResult = JsonConvert
|
||||
.DeserializeObject<TokenTierOne>(response.Content);
|
||||
_logger.Info("Response deserialized");
|
||||
|
||||
return this.InitializeLoginResult(user, tokenResult!);
|
||||
}
|
||||
|
||||
|
||||
public LoginResult LoginSymmetric(User user)
|
||||
{
|
||||
var tokenResult = new TokenTierOne{ TokenType = "JWT" };
|
||||
|
||||
var payload = Payload();
|
||||
payload.Add(new Claim("user_id", user.Id.ToString(), ClaimValueTypes.Integer));
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(_config!["JWT:Secret"]!);
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new Claim[]
|
||||
{
|
||||
new Claim("user_id", user.Id.ToString(), ClaimValueTypes.Integer)
|
||||
// Add more claims as needed
|
||||
}),
|
||||
Expires = DateTime.UtcNow.AddHours(1),
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
|
||||
Issuer = _config["Jwt:Issuer"], // Add this line
|
||||
Audience = _config["Jwt:Audience"]
|
||||
};
|
||||
|
||||
tokenResult.AccessToken = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
||||
|
||||
var expClaim = payload.FirstOrDefault(cl =>
|
||||
{
|
||||
return cl.Type.Equals("exp");
|
||||
});
|
||||
|
||||
var expiredDate = DateTime.Parse(expClaim!.Value);
|
||||
var exp = Math.Floor((expiredDate - DateTime.UnixEpoch).TotalSeconds);
|
||||
tokenResult.Expiration = Convert.ToInt32(exp);
|
||||
|
||||
return this.InitializeLoginResult(user, tokenResult);
|
||||
}
|
||||
|
||||
private LoginResult InitializeLoginResult(User user, TokenTierOne token)
|
||||
{
|
||||
return new LoginResult
|
||||
{
|
||||
UserId = user.Id, Username = user.Username, Token = token.AccessToken,
|
||||
TokenType = token.TokenType, Expiration = token.Expiration,
|
||||
Message = SUCCESSFUL_TOKEN_MESSAGE
|
||||
};
|
||||
}
|
||||
|
||||
public int RetrieveUserIdFromToken(string token)
|
||||
{
|
||||
if (this.ContainsBearer(token))
|
||||
{
|
||||
token = this.StripBearer(token);
|
||||
}
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var readTok = tokenHandler.ReadJwtToken(token);
|
||||
var userId = -1;
|
||||
|
||||
foreach (var item in readTok.Payload)
|
||||
{
|
||||
if (item.Key == "user_id")
|
||||
{
|
||||
userId = Convert.ToInt32(item.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
|
||||
private string StripBearer(string token)
|
||||
{
|
||||
var start = 6;
|
||||
var strippedToken = token.Substring(start);
|
||||
|
||||
return Strings.Trim(strippedToken);
|
||||
}
|
||||
|
||||
private bool ContainsBearer(string token)
|
||||
{
|
||||
return token.Contains("Bearer");
|
||||
}
|
||||
|
||||
|
||||
private string AllScopes()
|
||||
{
|
||||
var allScopes = new List<String>()
|
||||
{
|
||||
"download:songs",
|
||||
"read:song_details",
|
||||
"upload:songs",
|
||||
"delete:songs",
|
||||
"read:albums",
|
||||
"read:artists",
|
||||
"update:songs",
|
||||
"stream:songs",
|
||||
"read:genre",
|
||||
"read:year",
|
||||
"download:cover_art"
|
||||
};
|
||||
|
||||
var scopes = string.Empty;
|
||||
|
||||
for (var i = 0; i < allScopes.Count; i++)
|
||||
{
|
||||
if (i == allScopes.Count - 1)
|
||||
{
|
||||
scopes += allScopes[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
scopes += allScopes[i] + " ";
|
||||
}
|
||||
}
|
||||
|
||||
return scopes;
|
||||
}
|
||||
|
||||
private List<Claim> Payload()
|
||||
{
|
||||
// TODO: Remove this hard coding
|
||||
var expLimit = 30;
|
||||
var currentDate = DateTime.Now;
|
||||
var expiredDate = currentDate.AddMinutes(expLimit);
|
||||
var issuer = "http://localhost:5002";
|
||||
var audience = "http://localhost:5002";
|
||||
var subject = _config!["JWT:Subject"];
|
||||
|
||||
var claim = new List<System.Security.Claims.Claim>()
|
||||
{
|
||||
new Claim("scope", AllScopes(), "string"),
|
||||
new Claim(JwtRegisteredClaimNames.Exp, expiredDate.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Aud, audience),
|
||||
new Claim(JwtRegisteredClaimNames.Iss, issuer),
|
||||
new Claim(JwtRegisteredClaimNames.Sub, subject!),
|
||||
new Claim(JwtRegisteredClaimNames.Iat, currentDate.ToString())
|
||||
};
|
||||
|
||||
return claim;
|
||||
}
|
||||
|
||||
[Obsolete("Deprecated function")]
|
||||
private async Task<string> ReadKeyContent(string filepath)
|
||||
{
|
||||
return await System.IO.File.ReadAllTextAsync(filepath);
|
||||
}
|
||||
|
||||
private TokenRequest RetrieveTokenRequest()
|
||||
{
|
||||
_logger.Info("Retrieving token object");
|
||||
|
||||
return new TokenRequest
|
||||
{
|
||||
ClientId = _clientId, ClientSecret = _clientSecret,
|
||||
Audience = _audience, GrantType = _grantType
|
||||
};
|
||||
}
|
||||
|
||||
private void InitializeValues()
|
||||
{
|
||||
_logger.Info("Analyzing Auth0 information");
|
||||
|
||||
_clientId = _config!["Auth0:ClientId"];
|
||||
_clientSecret = _config["Auth0:ClientSecret"];
|
||||
_audience = _config["Auth0:ApiIdentifier"];
|
||||
_grantType = "client_credentials";
|
||||
_url = $"https://{_config["Auth0:Domain"]}";
|
||||
}
|
||||
|
||||
#region Testing Methods
|
||||
// For testing purposes
|
||||
private void PrintCredentials()
|
||||
{
|
||||
Console.WriteLine("Auth0 credentials:");
|
||||
Console.WriteLine($"Client Id: {_clientId}");
|
||||
Console.WriteLine($"Client Secret: {_clientSecret}");
|
||||
Console.WriteLine($"Audience: {_audience}");
|
||||
Console.WriteLine($"Url: {_url}");
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
|
||||
#region Classes
|
||||
private class TokenRequest
|
||||
{
|
||||
[JsonProperty("client_id")]
|
||||
public string? ClientId { get; set; }
|
||||
[JsonProperty("client_secret")]
|
||||
public string? ClientSecret { get; set; }
|
||||
[JsonProperty("audience")]
|
||||
public string? Audience { get; set; }
|
||||
[JsonProperty("grant_type")]
|
||||
public string? GrantType { get; set; }
|
||||
}
|
||||
|
||||
private class TokenTierOne
|
||||
{
|
||||
[JsonProperty("access_token")]
|
||||
public string? AccessToken { get; set; }
|
||||
[JsonProperty("expires_in")]
|
||||
public int Expiration { get; set; }
|
||||
[JsonProperty("token_type")]
|
||||
public string? TokenType { get; set; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
@@ -0,0 +1,371 @@
|
||||
using NLog;
|
||||
using TagLib;
|
||||
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Controllers.Utilities;
|
||||
|
||||
public class MetadataRetriever
|
||||
{
|
||||
#region Fields
|
||||
private static NLog.Logger? _logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();
|
||||
private List<string>? _supportedAudioFileTypes = new List<string> {"wav", "flac"};
|
||||
private List<string>? _supportedImageFileTypes = new List<string> {"jpeg", "jpg", "png"};
|
||||
private Song? _updatedSong;
|
||||
private string? _message;
|
||||
private string? _title;
|
||||
private string? _artist;
|
||||
private string? _album;
|
||||
private string? _genre;
|
||||
private int _year;
|
||||
private int _duration;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
public Song? UpdatedSongRecord
|
||||
{
|
||||
get => _updatedSong;
|
||||
set => _updatedSong = value;
|
||||
}
|
||||
public string? Message
|
||||
{
|
||||
get => _message;
|
||||
set => _message = value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public string CoverArtFileExtensionType(CoverArt cover)
|
||||
{
|
||||
Console.WriteLine("Retrieving CoverArt file extension type");
|
||||
|
||||
using (var fileStream = System.IO.File.OpenRead(cover.ImagePath()))
|
||||
{
|
||||
var isRecognizableType = FileTypeChecker.FileTypeValidator.IsTypeRecognizable(fileStream);
|
||||
|
||||
if (!isRecognizableType)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var fileType = FileTypeChecker.FileTypeValidator.GetFileType(fileStream);
|
||||
|
||||
Console.WriteLine($"Filetype: {fileType}");
|
||||
|
||||
return fileType.Extension;
|
||||
}
|
||||
}
|
||||
|
||||
public string FileExtensionType(IFormFile file)
|
||||
{
|
||||
using (var fileStream = file.OpenReadStream())
|
||||
{
|
||||
var isRecognizableType = FileTypeChecker.FileTypeValidator.IsTypeRecognizable(fileStream);
|
||||
|
||||
if (!isRecognizableType)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
var fileType = FileTypeChecker.FileTypeValidator.GetFileType(fileStream);
|
||||
Console.WriteLine($"Filetype: {fileType}");
|
||||
Console.WriteLine($"Extension: {fileType.Extension}");
|
||||
|
||||
return fileType.Extension;
|
||||
}
|
||||
}
|
||||
|
||||
public string FileExtensionType(string path)
|
||||
{
|
||||
var extensionRaw = System.IO.Path.GetExtension(path);
|
||||
|
||||
if (extensionRaw[0] == '.')
|
||||
{
|
||||
return extensionRaw.Substring(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return extensionRaw;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSupportedFile(IFormFile file)
|
||||
{
|
||||
var supportedTypes = this._supportedAudioFileTypes;
|
||||
this._supportedImageFileTypes!.ForEach(t =>
|
||||
{
|
||||
if (!supportedTypes!.Contains(t))
|
||||
{
|
||||
supportedTypes.Add(t);
|
||||
}
|
||||
});
|
||||
|
||||
var extensionType = this.FileExtensionType(file).ToLower();
|
||||
|
||||
return supportedTypes!.Contains(extensionType);
|
||||
}
|
||||
|
||||
public bool IsSupportedFile(string path)
|
||||
{
|
||||
var supportedTypes = this._supportedAudioFileTypes;
|
||||
this._supportedImageFileTypes!.ForEach(t =>
|
||||
{
|
||||
if (!supportedTypes!.Contains(t))
|
||||
{
|
||||
supportedTypes.Add(t);
|
||||
}
|
||||
});
|
||||
|
||||
var extensionType = this.FileExtensionType(path).ToLower();
|
||||
|
||||
return supportedTypes!.Contains(extensionType);
|
||||
}
|
||||
|
||||
public Song RetrieveMetaData(string filePath)
|
||||
{
|
||||
Song song = new Song();
|
||||
|
||||
try
|
||||
{
|
||||
TagLib.File fileTag = TagLib.File.Create(filePath);
|
||||
_title = fileTag.Tag.Title;
|
||||
_artist = string.Join("", fileTag.Tag.Performers);
|
||||
_album = fileTag.Tag.Album;
|
||||
_genre = string.Join("", fileTag.Tag.Genres);
|
||||
_year = (int)fileTag.Tag.Year;
|
||||
_duration = (int)fileTag.Properties.Duration.TotalSeconds;
|
||||
var albumArtist = string.Join("", fileTag.Tag.AlbumArtists);
|
||||
var track = (int)fileTag.Tag.Track;
|
||||
var disc = (int)fileTag.Tag.Disc;
|
||||
|
||||
song.Title = _title;
|
||||
song.Artist = _artist;
|
||||
song.AlbumTitle = _album;
|
||||
song.AlbumArtist = albumArtist;
|
||||
song.Genre = _genre;
|
||||
song.Year = _year;
|
||||
song.Duration = _duration;
|
||||
song.Track = track;
|
||||
song.Disc = disc;
|
||||
song.TrackCount = (int)fileTag.Tag.TrackCount;
|
||||
song.DiscCount = (int)fileTag.Tag.DiscCount;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
Console.WriteLine("An error occurred in MetadataRetriever");
|
||||
Console.WriteLine(msg);
|
||||
_logger!.Error(msg, "An error occurred in MetadataRetriever");
|
||||
}
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
public int RetrieveSongDuration(string filepath)
|
||||
{
|
||||
var fileTag = TagLib.File.Create(filepath);
|
||||
var duration = (int)fileTag.Properties.Duration.TotalSeconds;
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
public byte[] RetrieveCoverArtBytes(Song song)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Fetching image");
|
||||
var tag = TagLib.File.Create(song.SongPath());
|
||||
|
||||
if (tag.Tag.Pictures.Count() == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return tag.Tag.Pictures[0].Data.Data;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger!.Error(msg, "An error occurred in MetadataRetriever");
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
public void UpdateMetadata(Song updatedSong, Song oldSong)
|
||||
{
|
||||
try
|
||||
{
|
||||
InitializeUpdatedSong(oldSong);
|
||||
var songValues = CheckSongValues(updatedSong);
|
||||
PerformUpdate(updatedSong, songValues);
|
||||
Message = "Successfully updated metadata";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
Console.WriteLine($"An error occurred: {msg}");
|
||||
_logger!.Error(msg, "An error occurred");
|
||||
Message = "Failed to update metadata";
|
||||
}
|
||||
}
|
||||
public void UpdateCoverArt(Song song, CoverArt coverArt)
|
||||
{
|
||||
Console.WriteLine("Updating song's cover art");
|
||||
|
||||
var tag = TagLib.File.Create(song.SongPath());
|
||||
var pics = tag.Tag.Pictures;
|
||||
Array.Resize(ref pics, 1);
|
||||
|
||||
pics[0] = new Picture(coverArt.ImagePath())
|
||||
{
|
||||
Description = "Cover Art"
|
||||
};
|
||||
|
||||
tag.Tag.Pictures = pics;
|
||||
tag.Save();
|
||||
}
|
||||
|
||||
private void PerformUpdate(Song updatedSong, SortedDictionary<string, bool> checkedValues)
|
||||
{
|
||||
var filePath = updatedSong.SongPath();
|
||||
var title = updatedSong.Title;
|
||||
var artist = updatedSong.Artist;
|
||||
var album = updatedSong.AlbumTitle;
|
||||
var genre = updatedSong.Genre;
|
||||
var year = updatedSong.Year;
|
||||
var albumArtist = updatedSong.AlbumArtist;
|
||||
var track = updatedSong.Track;
|
||||
var trackCount = updatedSong.TrackCount;
|
||||
var disc = updatedSong.Disc;
|
||||
var discCount = updatedSong.DiscCount;
|
||||
TagLib.File fileTag = TagLib.File.Create(filePath);
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"Updating metadata of {title}");
|
||||
_logger!.Info($"Updating metadata of {title}");
|
||||
|
||||
foreach (var key in checkedValues.Keys)
|
||||
{
|
||||
bool result = checkedValues[key];
|
||||
|
||||
if (!result)
|
||||
switch (key.ToLower())
|
||||
{
|
||||
case "title":
|
||||
_updatedSong!.Title = title;
|
||||
fileTag.Tag.Title = title;
|
||||
break;
|
||||
case "artists":
|
||||
_updatedSong!.Artist = artist;
|
||||
fileTag.Tag.Performers = new []{artist};
|
||||
break;
|
||||
case "album":
|
||||
_updatedSong!.AlbumTitle = album;
|
||||
fileTag.Tag.Album = album;
|
||||
break;
|
||||
case "genre":
|
||||
_updatedSong!.Genre = genre;
|
||||
fileTag.Tag.Genres = new []{genre};
|
||||
break;
|
||||
case "year":
|
||||
_updatedSong!.Year = year;
|
||||
fileTag.Tag.Year = (uint)year!;
|
||||
break;
|
||||
case "albumartist":
|
||||
_updatedSong!.AlbumArtist = albumArtist;
|
||||
fileTag.Tag.AlbumArtists = new []{albumArtist};
|
||||
break;
|
||||
case "track":
|
||||
_updatedSong!.Track = track;
|
||||
fileTag.Tag.Track = (uint)(track);
|
||||
break;
|
||||
case "trackcount":
|
||||
_updatedSong!.TrackCount = trackCount;
|
||||
fileTag.Tag.TrackCount = (uint)(trackCount);
|
||||
break;
|
||||
case "disc":
|
||||
_updatedSong!.Disc = disc;
|
||||
fileTag.Tag.Disc = (uint)(disc);
|
||||
break;
|
||||
case "disccount":
|
||||
_updatedSong!.DiscCount = discCount;
|
||||
fileTag.Tag.DiscCount = (uint)(discCount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fileTag.Save();
|
||||
|
||||
Console.WriteLine("Successfully updated metadata");
|
||||
_logger.Info("Successfully updated metadata");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
Console.WriteLine($"An error occurred:\n{msg}");
|
||||
_logger!.Error(msg, "An error occurred");
|
||||
}
|
||||
}
|
||||
private void InitializeUpdatedSong(Song song)
|
||||
{
|
||||
_updatedSong = song;
|
||||
}
|
||||
|
||||
private SortedDictionary<string, bool> CheckSongValues(Song song)
|
||||
{
|
||||
var songValues = new SortedDictionary<string, bool>();
|
||||
Console.WriteLine("Checking for null data");
|
||||
_logger!.Info("Checking for null data");
|
||||
|
||||
try
|
||||
{
|
||||
songValues["Title"] = String.IsNullOrEmpty(song.Title);
|
||||
songValues["Artists"] = String.IsNullOrEmpty(song.Artist);
|
||||
songValues["Album"] = String.IsNullOrEmpty(song.AlbumTitle);
|
||||
songValues["Genre"] = String.IsNullOrEmpty(song.Genre);
|
||||
songValues["AlbumArtist"] = String.IsNullOrEmpty(song.AlbumArtist);
|
||||
|
||||
songValues["Year"] = CheckIntField(song.Year);
|
||||
songValues["Track"] = CheckIntField(song.Track);
|
||||
songValues["TrackCount"] = CheckIntField(song.TrackCount);
|
||||
songValues["Disc"] = CheckIntField(song.Disc);
|
||||
songValues["DiscCount"] = CheckIntField(song.Disc);
|
||||
|
||||
Console.WriteLine("Checking for null data completed");
|
||||
_logger.Info("Checking for null data completed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
Console.WriteLine($"An error occurred: \n{msg}");
|
||||
_logger.Error(msg, "An error occurred");
|
||||
}
|
||||
|
||||
return songValues;
|
||||
}
|
||||
|
||||
private bool CheckIntField(int? value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (value == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
using BCrypt.Net;
|
||||
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
||||
using NLog;
|
||||
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Controllers.Utilities;
|
||||
|
||||
public class PasswordEncryption
|
||||
{
|
||||
#region Fields
|
||||
private static Logger _logger = NLog.LogManager.GetCurrentClassLogger();
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructor
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public bool VerifyPassword(User user, string password)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = BCrypt.Net.BCrypt.Verify(password, user.Password);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger.Error(msg, "An error occurred");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string HashPassword(User user)
|
||||
{
|
||||
try
|
||||
{
|
||||
string hashedPassword = string.Empty;
|
||||
hashedPassword = BCrypt.Net.BCrypt.HashPassword(user.Password);
|
||||
|
||||
_logger.Info("Successfully hashed password");
|
||||
|
||||
return hashedPassword;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var exMsg = ex.Message;
|
||||
_logger.Error(exMsg, "An error occurred");
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string GenerateHash(string password, byte[] salt)
|
||||
{
|
||||
|
||||
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
|
||||
password: password, salt: salt,
|
||||
prf: KeyDerivationPrf.HMACSHA1,
|
||||
iterationCount: 10000,
|
||||
numBytesRequested: 256/8));
|
||||
|
||||
return hashed;
|
||||
}
|
||||
|
||||
byte[] GenerateSalt()
|
||||
{
|
||||
byte[] salt = new byte[128/8];
|
||||
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
rng.GetBytes(salt);
|
||||
|
||||
|
||||
return salt;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Controllers.Utilities;
|
||||
|
||||
public class SongCompression
|
||||
{
|
||||
#region Fields
|
||||
string? _compressedSongFilename;
|
||||
string? _tempDirectory;
|
||||
byte[]? _uncompressedSong;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Propterties
|
||||
public string? CompressedSongFilename
|
||||
{
|
||||
get => _compressedSongFilename;
|
||||
set => _compressedSongFilename = value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public SongCompression()
|
||||
{
|
||||
}
|
||||
public SongCompression(string tempDirectory)
|
||||
{
|
||||
_tempDirectory = tempDirectory;
|
||||
}
|
||||
public SongCompression(byte[] uncompressedSong)
|
||||
{
|
||||
_uncompressedSong = uncompressedSong;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public async Task<SongData> RetrieveCompressedSong(Song song)
|
||||
{
|
||||
SongData songData = new SongData();
|
||||
try
|
||||
{
|
||||
var archivePath = RetrieveCompressesSongPath(song);
|
||||
Console.WriteLine($"Compressed song saved to: {archivePath}");
|
||||
|
||||
songData.Data = await System.IO.File.ReadAllBytesAsync(archivePath);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
var exMsg = ex.Message;
|
||||
Console.WriteLine($"An error ocurred: \n{exMsg}");
|
||||
}
|
||||
|
||||
return songData;
|
||||
}
|
||||
|
||||
public string RetrieveCompressesSongPath(Song songDetails)
|
||||
{
|
||||
string tmpZipFilePath = _tempDirectory + songDetails.Filename;
|
||||
|
||||
try
|
||||
{
|
||||
using (var fi = new FileStream(songDetails.SongPath(), FileMode.Open))
|
||||
{
|
||||
using (var z = new Ionic.Zlib.ZlibStream(fi, Ionic.Zlib.CompressionMode.Compress))
|
||||
{
|
||||
using (var tr = new FileStream(tmpZipFilePath, FileMode.CreateNew))
|
||||
{
|
||||
z.CopyTo(tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Successfully compressed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var exMsg = ex.Message;
|
||||
Console.WriteLine("An error ocurred");
|
||||
Console.WriteLine(exMsg);
|
||||
}
|
||||
|
||||
if (songDetails.Filename!.Contains(Constants.FileExtensions.WAV_EXTENSION))
|
||||
{
|
||||
_compressedSongFilename = StripExtension(songDetails.Filename);
|
||||
}
|
||||
|
||||
return tmpZipFilePath;
|
||||
}
|
||||
|
||||
// Method not being used
|
||||
public byte[] CompressedSong(byte[] uncompressedSong)
|
||||
{
|
||||
byte[]? compressedSong = null;
|
||||
try
|
||||
{
|
||||
Console.WriteLine("Song has been successfully compressed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var exMsg = ex.Message;
|
||||
Console.WriteLine("An error ocurred:");
|
||||
Console.WriteLine(exMsg);
|
||||
}
|
||||
|
||||
return compressedSong!;
|
||||
}
|
||||
|
||||
|
||||
string StripExtension(string filename)
|
||||
{
|
||||
Console.WriteLine($"Before: {filename}");
|
||||
int filenameLength = filename.Length;
|
||||
Console.WriteLine($"Filename length {filenameLength}");
|
||||
var endIndex = filenameLength - 1;
|
||||
var startIndex = endIndex - 3;
|
||||
Console.WriteLine($"Starting index {startIndex} and ending index {endIndex}");
|
||||
var stripped = filename.Remove(startIndex, 4);
|
||||
stripped += Constants.FileExtensions.ZIP_EXTENSION;
|
||||
Console.WriteLine($"After {stripped}");
|
||||
|
||||
return stripped;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
[Route("api/v1/album")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class AlbumController : BaseController
|
||||
{
|
||||
#region Fields
|
||||
private readonly ILogger<AlbumController>? _logger;
|
||||
private string? _connectionString;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public AlbumController(ILogger<AlbumController> logger, IConfiguration config)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region HTTP Routes
|
||||
[HttpGet]
|
||||
public IActionResult GetAlbums()
|
||||
{
|
||||
var albumContext = new AlbumContext(_connectionString!);
|
||||
|
||||
var albums = albumContext.Albums!.ToList();
|
||||
|
||||
if (albums.Count > 0)
|
||||
return Ok(albums);
|
||||
else
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult GetAlbum(int id)
|
||||
{
|
||||
Album album = new Album{ Id = id };
|
||||
|
||||
var albumContext = new AlbumContext(_connectionString!);
|
||||
|
||||
if (albumContext.DoesRecordExist(album))
|
||||
{
|
||||
album = albumContext.RetrieveRecord(album);
|
||||
|
||||
return Ok(album);
|
||||
}
|
||||
else
|
||||
return NotFound();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
[Route("api/v1/artist")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class ArtistController : BaseController
|
||||
{
|
||||
#region Fields
|
||||
private readonly ILogger<ArtistController>? _logger;
|
||||
private string? _connectionString;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public ArtistController(ILogger<ArtistController> logger, IConfiguration config)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region HTTP Routes
|
||||
[HttpGet]
|
||||
public IActionResult GetArtists()
|
||||
{
|
||||
var artistContext = new ArtistContext(_connectionString!);
|
||||
|
||||
var artists = artistContext.Artists.ToList();
|
||||
|
||||
if (artists.Count > 0)
|
||||
return Ok(artists);
|
||||
else
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult GetArtist(int id)
|
||||
{
|
||||
Artist artist = new Artist { Id = id };
|
||||
|
||||
var artistContext = new ArtistContext(_connectionString!);
|
||||
|
||||
if (artistContext.DoesRecordExist(artist))
|
||||
{
|
||||
artist = artistContext.RetrieveRecord(artist);
|
||||
|
||||
return Ok(artist);
|
||||
}
|
||||
else
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
public class BaseController : ControllerBase
|
||||
{
|
||||
#region Fiends
|
||||
protected IConfiguration? _config;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[Obsolete("Asymmetric key signing for tokens have been deprecated")]
|
||||
protected string ParseBearerTokenFromHeader()
|
||||
{
|
||||
var token = string.Empty;
|
||||
const string tokenType = "Bearer";
|
||||
const string otherTokenType = "Jwt";
|
||||
|
||||
var req = Request;
|
||||
var auth = req.Headers.Authorization;
|
||||
var val = auth.ToString();
|
||||
|
||||
if ((val.Contains(tokenType) || val.Contains(otherTokenType)) && val.Split(" ").Count() > 1)
|
||||
{
|
||||
var split = val.Split(" ");
|
||||
token = split[1];
|
||||
}
|
||||
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Icarus.Controllers.Managers;
|
||||
using Icarus.Database.Contexts;
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
[Route("api/v1/coverart")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class CoverArtController : BaseController
|
||||
{
|
||||
#region Fields
|
||||
private readonly ILogger<CoverArtController>? _logger;
|
||||
private string? _connectionString;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public CoverArtController(ILogger<CoverArtController> logger, IConfiguration config)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region HTTP Routes
|
||||
[HttpGet]
|
||||
public IActionResult GetCoverArts()
|
||||
{
|
||||
var coverArtContext = new CoverArtContext(_connectionString!);
|
||||
|
||||
var coverArtRecords = coverArtContext.CoverArtImages!.ToList();
|
||||
|
||||
if (coverArtRecords == null)
|
||||
{
|
||||
_logger!.LogInformation("No cover art records");
|
||||
return NotFound();
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger!.LogInformation("Found cover art records");
|
||||
return Ok(coverArtRecords);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult GetCoverArt(int id)
|
||||
{
|
||||
var coverArt = new CoverArt { Id = id };
|
||||
|
||||
var coverArtContext = new CoverArtContext(_connectionString!);
|
||||
|
||||
coverArt = coverArtContext.RetrieveRecord(coverArt);
|
||||
|
||||
if (coverArt != null)
|
||||
{
|
||||
_logger!.LogInformation("Found cover art record");
|
||||
var coverArtBytes = System.IO.File.ReadAllBytes(
|
||||
coverArt.ImagePath());
|
||||
|
||||
return File(coverArtBytes, "application/x-msdownload",
|
||||
coverArt.SongTitle);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger!.LogInformation("Cover art not found");
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("data/download/{id}")]
|
||||
public async Task<IActionResult> Download(int id, [FromQuery] bool? randomizeFilename)
|
||||
{
|
||||
var songContext = new SongContext(_connectionString!);
|
||||
var covMgr = new CoverArtManager(this._config!);
|
||||
|
||||
var songMetaData = songContext.RetrieveRecord(new Song { Id = id});
|
||||
var c = covMgr.GetCoverArt(songMetaData);
|
||||
|
||||
var filename = DirectoryManager.GenerateDownloadFilename(10, Constants.FileExtensions.JPG_EXTENSION, songMetaData.Title!, randomizeFilename);
|
||||
|
||||
var data = await c.GetData();
|
||||
|
||||
return File(data, "application/x-msdownload", filename);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
[Route("api/v1/genre")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class GenreController : BaseController
|
||||
{
|
||||
#region Fields
|
||||
private readonly ILogger<GenreController>? _logger;
|
||||
private string? _connectionString;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public GenreController(ILogger<GenreController> logger, IConfiguration config)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region HTTP Routes
|
||||
[HttpGet]
|
||||
public IActionResult GetGenres()
|
||||
{
|
||||
var genreStore = new GenreContext(_connectionString!);
|
||||
|
||||
var genres = genreStore!.Genres!.ToList();
|
||||
|
||||
if (genres.Count > 0)
|
||||
{
|
||||
return Ok(genres);
|
||||
}
|
||||
else
|
||||
{
|
||||
return NotFound(new List<Genre>());
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult GetGenre(int id)
|
||||
{
|
||||
var genre = new Genre { Id = id };
|
||||
|
||||
var genreStore = new GenreContext(_connectionString!);
|
||||
|
||||
if (genreStore.DoesRecordExist(genre))
|
||||
{
|
||||
genre = genreStore.RetrieveRecord(genre);
|
||||
|
||||
return Ok(genre);
|
||||
}
|
||||
else
|
||||
{
|
||||
return NotFound(new Genre());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
using Icarus.Controllers.Managers;
|
||||
using Icarus.Controllers.Utilities;
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
[Route("api/v1/login")]
|
||||
[ApiController]
|
||||
public class LoginController : ControllerBase
|
||||
{
|
||||
#region Fields
|
||||
private string? _connectionString;
|
||||
private IConfiguration? _config;
|
||||
private ILogger<LoginController> _logger;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Contructors
|
||||
public LoginController(IConfiguration config, ILogger<LoginController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region HTTP endpoints
|
||||
[HttpPost]
|
||||
public IActionResult Login([FromBody] User user)
|
||||
{
|
||||
var context = new UserContext(_connectionString!);
|
||||
|
||||
_logger.LogInformation("Starting process of validating credentials");
|
||||
|
||||
var message = "Invalid credentials";
|
||||
var password = user.Password;
|
||||
|
||||
var loginRes = new LoginResult
|
||||
{
|
||||
Username = user.Username
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (context.Users.FirstOrDefault(usr => usr.Username!.Equals(user.Username)) != null)
|
||||
{
|
||||
user = context.Users.FirstOrDefault(usr => usr.Username!.Equals(user.Username))!;
|
||||
|
||||
var validatePass = new PasswordEncryption();
|
||||
var validated = validatePass.VerifyPassword(user!, password!);
|
||||
if (!validated)
|
||||
{
|
||||
loginRes.Message = message;
|
||||
_logger.LogInformation(message);
|
||||
|
||||
return Ok(loginRes);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Successfully validated user credentials");
|
||||
|
||||
TokenManager tk = new TokenManager(_config!);
|
||||
|
||||
loginRes = tk.LoginSymmetric(user!);
|
||||
|
||||
return Ok(loginRes);
|
||||
}
|
||||
else
|
||||
{
|
||||
loginRes.Message = message;
|
||||
|
||||
return NotFound(loginRes);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("An error occurred: {0}", ex.Message);
|
||||
_logger.LogError("Inner Exception: {0}", ex.InnerException!.Message);
|
||||
}
|
||||
|
||||
return NotFound(loginRes);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Icarus.Controllers.Utilities;
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
[Route("api/v1/register")]
|
||||
[ApiController]
|
||||
public class RegisterController : ControllerBase
|
||||
{
|
||||
#region Fields
|
||||
private IConfiguration? _config;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructor
|
||||
public RegisterController(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
#endregion
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult RegisterUser([FromBody] User user)
|
||||
{
|
||||
PasswordEncryption pe = new PasswordEncryption();
|
||||
user.Password = pe.HashPassword(user);
|
||||
user.EmailVerified = false;
|
||||
user.Status = "Registered";
|
||||
user.DateCreated = DateTime.Now;
|
||||
|
||||
UserContext? context = null;
|
||||
|
||||
try
|
||||
{
|
||||
var connString = _config!.GetConnectionString("DefaultConnection");
|
||||
context = new UserContext(connString!);
|
||||
context.Add(user);
|
||||
context.SaveChanges();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
var stackTrace = ex.StackTrace;
|
||||
|
||||
Console.WriteLine($"An error occurred: {msg}");
|
||||
}
|
||||
|
||||
var registerResult = new RegisterResult
|
||||
{
|
||||
Username = user.Username
|
||||
};
|
||||
|
||||
if (context!.Users.FirstOrDefault(sng => sng.Username!.Equals(user.Username)) != null)
|
||||
{
|
||||
registerResult.Message = "Successful registration";
|
||||
registerResult.SuccessfullyRegistered = true;
|
||||
|
||||
return Ok(registerResult);
|
||||
}
|
||||
else
|
||||
{
|
||||
registerResult.Message = "Registration failed";
|
||||
registerResult.SuccessfullyRegistered = false;
|
||||
|
||||
return Ok(registerResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Icarus.Controllers.Utilities;
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
using Icarus.Controllers.Managers;
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
[Route("api/v1/song/compressed/data")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class SongCompressedDataController : BaseController
|
||||
{
|
||||
#region Fields
|
||||
private string? _connectionString;
|
||||
private string? _songTempDir;
|
||||
private string? _archiveDir;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructor
|
||||
public SongCompressedDataController(IConfiguration config)
|
||||
{
|
||||
_config = config;
|
||||
_songTempDir = _config.GetValue<string>("TemporaryMusicPath");
|
||||
_archiveDir = _config.GetValue<string>("ArchivePath");
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region API Routes
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> DownloadCompressedSong(int id, [FromQuery] bool? randomizeFilename)
|
||||
{
|
||||
var context = new SongContext(_connectionString!);
|
||||
|
||||
SongCompression cmp = new SongCompression(_archiveDir!);
|
||||
|
||||
Console.WriteLine($"Archive directory root: {_archiveDir}");
|
||||
|
||||
Console.WriteLine("Starting process of retrieving comrpessed song");
|
||||
var sng = context.RetrieveRecord(new Song{ Id = id });
|
||||
SongData song = await cmp.RetrieveCompressedSong(sng);
|
||||
|
||||
var filename = DirectoryManager.GenerateDownloadFilename(10, Constants.FileExtensions.ZIP_EXTENSION, sng.Title!, randomizeFilename);
|
||||
|
||||
return File(song.Data!, "application/x-msdownload", filename);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Icarus.Controllers.Managers;
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
[Route("api/v1/song")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class SongController : BaseController
|
||||
{
|
||||
#region Fields
|
||||
private readonly ILogger<SongController>? _logger;
|
||||
private string? _connectionString;
|
||||
private SongManager? _songMgr;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructor
|
||||
public SongController(IConfiguration config, ILogger<SongController> logger)
|
||||
{
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
_songMgr = new SongManager(config);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
#region HTTP Endpoints
|
||||
[HttpGet]
|
||||
public IActionResult GetSongs()
|
||||
{
|
||||
Console.WriteLine("Attemtping to retrieve songs");
|
||||
_logger!.LogInformation("Attempting to retrieve songs");
|
||||
|
||||
var context = new SongContext(_connectionString!);
|
||||
|
||||
var songs = context.Songs!.ToList();
|
||||
|
||||
if (songs.Count > 0)
|
||||
{
|
||||
return Ok(songs);
|
||||
}
|
||||
else
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public IActionResult GetSong(int id)
|
||||
{
|
||||
var context = new SongContext(_connectionString!);
|
||||
|
||||
var song = context.RetrieveRecord(new Song{ Id = id });
|
||||
|
||||
Console.WriteLine("Here");
|
||||
|
||||
if (song.Id != 0)
|
||||
return Ok(song);
|
||||
else
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public IActionResult UpdateSong(int id, [FromBody] Song song)
|
||||
{
|
||||
song.Id = id;
|
||||
Console.WriteLine("Retrieving filepath of song");
|
||||
_logger!.LogInformation("Retrieving filepath of song");
|
||||
|
||||
if (!_songMgr!.DoesSongExist(song))
|
||||
{
|
||||
return NotFound(new SongResult
|
||||
{
|
||||
Message = "Song does not exist"
|
||||
});
|
||||
}
|
||||
|
||||
var songRes = _songMgr.UpdateSong(song);
|
||||
|
||||
return Ok(songRes);
|
||||
}
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Icarus.Controllers.Managers;
|
||||
using Icarus.Models;
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
[Route("api/v1/song/data")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class SongDataController : BaseController
|
||||
{
|
||||
#region Fields
|
||||
private string? _connectionString;
|
||||
private ILogger<SongDataController>? _logger;
|
||||
private SongManager? _songMgr;
|
||||
private string? _songTempDir;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructor
|
||||
public SongDataController(IConfiguration config, ILogger<SongDataController> logger)
|
||||
{
|
||||
_config = config;
|
||||
_connectionString = _config.GetConnectionString("DefaultConnection");
|
||||
_logger = logger;
|
||||
_songTempDir = _config.GetValue<string>("TemporaryMusicPath");
|
||||
_songMgr = new SongManager(config, _songTempDir!);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
[HttpGet("download/{id}")]
|
||||
public IActionResult Download(int id, [FromQuery] bool? randomizeFilename)
|
||||
{
|
||||
var songContext = new SongContext(_connectionString!);
|
||||
var songMetaData = songContext.RetrieveRecord(new Song { Id = id});
|
||||
|
||||
var song = _songMgr!.RetrieveSong(songMetaData).Result;
|
||||
string filename;
|
||||
|
||||
switch (songMetaData.AudioType)
|
||||
{
|
||||
case "wav":
|
||||
filename = DirectoryManager.GenerateDownloadFilename(10, Constants.FileExtensions.WAV_EXTENSION,
|
||||
songMetaData.Title!, randomizeFilename);
|
||||
break;
|
||||
case "flac":
|
||||
filename = DirectoryManager.GenerateDownloadFilename(10, Constants.FileExtensions.FLAC_EXTENSION,
|
||||
songMetaData.Title!, randomizeFilename);
|
||||
break;
|
||||
default:
|
||||
filename = DirectoryManager.GenerateDownloadFilename(10, Constants.FileExtensions.DEFAULT_AUDIO_EXTENSION,
|
||||
songMetaData.Title!, randomizeFilename);
|
||||
break;
|
||||
}
|
||||
|
||||
return File(song.Data!, "application/x-msdownload", filename);
|
||||
}
|
||||
|
||||
// Assumes that the song already has metadata such as
|
||||
// Title
|
||||
// Artist
|
||||
// Album
|
||||
// Genre
|
||||
// Year
|
||||
// Track
|
||||
// Track count
|
||||
// Disc
|
||||
// Disc count
|
||||
// Cover art
|
||||
//
|
||||
[HttpPost("upload"), DisableRequestSizeLimit]
|
||||
public IActionResult Upload([FromForm(Name = "file")] List<IFormFile> songData)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger!.LogInformation("Uploading song...");
|
||||
|
||||
var uploads = _songTempDir;
|
||||
_logger!.LogInformation($"Song root path {uploads}");
|
||||
|
||||
foreach (var sng in songData)
|
||||
if (sng.Length > 0)
|
||||
{
|
||||
_logger!.LogInformation($"Song filename {sng.FileName}");
|
||||
|
||||
_songMgr!.SaveSongToFileSystem(sng).Wait();
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
_logger!.LogError(msg, "An error occurred");
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
// The client is expected to send the file, metadata, and cover art separately.
|
||||
// Any metadata already on the file will be overwritten with values from the metadata
|
||||
// as well as the cover art
|
||||
//
|
||||
[HttpPost("upload/with/data")]
|
||||
public IActionResult UploadWithData([FromForm] UploadSongWithDataForm up)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (up.SongData!.Length > 0 && up.CoverArtData!.Length > 0 && !string.IsNullOrEmpty(up.SongFile))
|
||||
{
|
||||
var meta = new Utilities.MetadataRetriever();
|
||||
var tmpSong = this._songMgr!.SaveSongTemp(up.SongData).Result;
|
||||
if (!meta.IsSupportedFile(tmpSong.SongPath()) && !meta.IsSupportedFile(up.CoverArtData))
|
||||
{
|
||||
return BadRequest("Media is not supported");
|
||||
}
|
||||
else if (!meta.IsSupportedFile(tmpSong.SongPath()))
|
||||
{
|
||||
return BadRequest("Song is not supported");
|
||||
}
|
||||
else if (!meta.IsSupportedFile(up.CoverArtData))
|
||||
{
|
||||
return BadRequest("Cover art is not supported");
|
||||
}
|
||||
|
||||
var song = Newtonsoft.Json.JsonConvert.DeserializeObject<Song>(up.SongFile);
|
||||
var tokMgr = new TokenManager(this._config!);
|
||||
var accessToken = Request.Headers["Authorization"];
|
||||
var userId = tokMgr.RetrieveUserIdFromToken(accessToken!);
|
||||
|
||||
if (userId != -1)
|
||||
{
|
||||
song!.UserId = userId;
|
||||
}
|
||||
|
||||
_logger!.LogInformation($"Song title: {song!.Title}");
|
||||
|
||||
song.Filename = tmpSong.Filename;
|
||||
song.SongDirectory = tmpSong.SongDirectory;
|
||||
song.DateCreated = tmpSong.DateCreated;
|
||||
song.AudioType = meta.FileExtensionType(tmpSong.SongPath());
|
||||
|
||||
switch (song.AudioType)
|
||||
{
|
||||
case "wav":
|
||||
song = _songMgr.SaveSongToFileSystem(up.SongData, up.CoverArtData, song);
|
||||
break;
|
||||
case "flac":
|
||||
song = _songMgr.SaveFlacSongToFileSystem(up.SongData, up.CoverArtData, song);
|
||||
break;
|
||||
default:
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
return Ok(song);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger!.LogError(ex.Message, "An error occurred");
|
||||
}
|
||||
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
[HttpDelete("delete/{id}")]
|
||||
public IActionResult DeleteSong(int id)
|
||||
{
|
||||
var songContext = new SongContext(_connectionString!);
|
||||
|
||||
var songMetaData = new Song{ Id = id };
|
||||
Console.WriteLine($"Id {songMetaData.Id}");
|
||||
|
||||
songMetaData = songContext.RetrieveRecord(songMetaData);
|
||||
|
||||
if (string.IsNullOrEmpty(songMetaData.Title))
|
||||
{
|
||||
_logger!.LogInformation("Song does not exist");
|
||||
return NotFound("Song does not exist");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger!.LogInformation("Starting process of deleting song from the filesystem and database");
|
||||
|
||||
_songMgr!.DeleteSong(songMetaData);
|
||||
|
||||
return Ok(songMetaData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class UploadSongWithDataForm
|
||||
{
|
||||
[FromForm(Name = "file")]
|
||||
public IFormFile? SongData { get; set; }
|
||||
// NOTE: Think about making this optional and if it is not provided, use the stock cover art
|
||||
[FromForm(Name = "cover")]
|
||||
public IFormFile? CoverArtData { get; set; }
|
||||
[FromForm(Name = "metadata")]
|
||||
public string? SongFile { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
namespace Icarus.Controllers.V1;
|
||||
|
||||
[Route("api/v1/song/stream")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class SongStreamController : BaseController
|
||||
{
|
||||
#region Fields
|
||||
private ILogger<SongStreamController>? _logger;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructor
|
||||
public SongStreamController(ILogger<SongStreamController> logger, IConfiguration config)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region HTTP endpoints
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> StreamSong(int id)
|
||||
{
|
||||
var context = new SongContext(_config!.GetConnectionString("DefaultConnection")!);
|
||||
|
||||
var song = context.Songs!.FirstOrDefault(sng => sng.Id == id);
|
||||
|
||||
var stream = new FileStream(song!.SongPath(), FileMode.Open, FileAccess.Read);
|
||||
stream.Position = 0;
|
||||
var filename = song.Filename;
|
||||
|
||||
if (string.IsNullOrEmpty(song.Filename))
|
||||
{
|
||||
filename = song.GenerateFilename();
|
||||
}
|
||||
|
||||
_logger!.LogInformation("Starting to stream song...>");
|
||||
Console.WriteLine("Starting to streamsong...");
|
||||
|
||||
return await Task.Run(() => {
|
||||
return File(stream, "application/octet-stream", filename);
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Database.Contexts;
|
||||
|
||||
public class AlbumContext : DbContext
|
||||
{
|
||||
public DbSet<Album>? Albums { get; set; }
|
||||
|
||||
public AlbumContext(DbContextOptions<AlbumContext> options) : base(options) { }
|
||||
public AlbumContext(string connString) : base(new DbContextOptionsBuilder<AlbumContext>()
|
||||
.UseMySQL(connString).Options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Album>()
|
||||
.ToTable("Album");
|
||||
}
|
||||
|
||||
public Album RetrieveRecord(Album album)
|
||||
{
|
||||
var albm = Albums!.FirstOrDefault(alb => alb.Id == album.Id);
|
||||
return albm!;
|
||||
}
|
||||
|
||||
public bool DoesRecordExist(Album album)
|
||||
{
|
||||
return Albums!.FirstOrDefault(alb => alb.Id == album.Id) != null ? true : false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Database.Contexts;
|
||||
|
||||
public class ArtistContext : DbContext
|
||||
{
|
||||
public DbSet<Artist> Artists { get; set; }
|
||||
|
||||
public ArtistContext(DbContextOptions<ArtistContext> options) : base (options) { }
|
||||
public ArtistContext(string connString) : base(new DbContextOptionsBuilder<ArtistContext>()
|
||||
.UseMySQL(connString).Options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Artist>()
|
||||
.ToTable("Artist");
|
||||
}
|
||||
|
||||
public Artist RetrieveRecord(Artist artist)
|
||||
{
|
||||
|
||||
return Artists.FirstOrDefault(arst => arst.Id == artist.Id)!;
|
||||
}
|
||||
|
||||
|
||||
public bool DoesRecordExist(Artist artist)
|
||||
{
|
||||
return Artists.FirstOrDefault(arst => arst.Id == artist.Id) != null ? true : false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Database.Contexts;
|
||||
|
||||
public class CoverArtContext : DbContext
|
||||
{
|
||||
#region Properties
|
||||
public DbSet<CoverArt>? CoverArtImages { get; set; }
|
||||
#endregion
|
||||
|
||||
public CoverArtContext(DbContextOptions<CoverArtContext> options) : base(options) { }
|
||||
public CoverArtContext(string connString) : base(new DbContextOptionsBuilder<CoverArtContext>()
|
||||
.UseMySQL(connString).Options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<CoverArt>()
|
||||
.ToTable("CoverArt");
|
||||
}
|
||||
|
||||
public CoverArt RetrieveRecord(CoverArt cover)
|
||||
{
|
||||
return CoverArtImages!.FirstOrDefault(cov => cov.Id == cover.Id)!;
|
||||
}
|
||||
|
||||
public bool DoesRecordExist(CoverArt cover)
|
||||
{
|
||||
return CoverArtImages!.FirstOrDefault(cov => cov.Id == cover.Id) != null ? true : false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Database.Contexts;
|
||||
|
||||
public class GenreContext : DbContext
|
||||
{
|
||||
#region Properties
|
||||
public DbSet<Genre>? Genres { get; set; }
|
||||
#endregion
|
||||
|
||||
public GenreContext(DbContextOptions<GenreContext> options) : base(options) { }
|
||||
public GenreContext(string connString) : base(new DbContextOptionsBuilder<GenreContext>()
|
||||
.UseMySQL(connString).Options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Genre>()
|
||||
.ToTable("Genre");
|
||||
}
|
||||
|
||||
|
||||
public Genre RetrieveRecord(Genre genre)
|
||||
{
|
||||
var gnre = Genres!.FirstOrDefault(gnr => gnr.Id == genre.Id);
|
||||
return gnre!;
|
||||
}
|
||||
|
||||
public bool DoesRecordExist(Genre genre)
|
||||
{
|
||||
return Genres!.FirstOrDefault(gnr => gnr.Id == genre.Id) != null ? true : false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Database.Contexts;
|
||||
|
||||
public class SongContext : DbContext
|
||||
{
|
||||
public DbSet<Song>? Songs { get; set; }
|
||||
|
||||
public SongContext(string connString) : base(new DbContextOptionsBuilder<SongContext>()
|
||||
.UseMySQL(connString).Options)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public SongContext(DbContextOptions<SongContext> options) : base(options) { }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Song>()
|
||||
.ToTable("Song");
|
||||
|
||||
modelBuilder.Entity<Song>()
|
||||
.Property(s => s.Year)
|
||||
.IsRequired(false);
|
||||
modelBuilder.Entity<Song>()
|
||||
.Property(s => s.GenreId)
|
||||
.IsRequired(false);
|
||||
modelBuilder.Entity<Song>()
|
||||
.Property(s => s.ArtistId)
|
||||
.IsRequired(false);
|
||||
modelBuilder.Entity<Song>()
|
||||
.Property(s => s.AlbumId)
|
||||
.IsRequired(false);
|
||||
modelBuilder.Entity<Song>()
|
||||
.Property(s => s.CoverArtId)
|
||||
.IsRequired(false);
|
||||
}
|
||||
|
||||
|
||||
public Song RetrieveRecord(Song song)
|
||||
{
|
||||
var sng = Songs!.FirstOrDefault(sng => sng.Id == song.Id);
|
||||
return sng!;
|
||||
}
|
||||
|
||||
|
||||
public bool DoesRecordExist(Song song)
|
||||
{
|
||||
return Songs!.FirstOrDefault(sng => sng.Id == song.Id) != null ? true : false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
using Icarus.Models;
|
||||
|
||||
namespace Icarus.Database.Contexts;
|
||||
|
||||
public class UserContext : DbContext
|
||||
{
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
|
||||
#region Constructors
|
||||
public UserContext(DbContextOptions<UserContext> options) : base(options) { }
|
||||
[ActivatorUtilitiesConstructor]
|
||||
public UserContext(string connString) : base(new DbContextOptionsBuilder<UserContext>()
|
||||
.UseMySQL(connString).Options)
|
||||
{
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<User>()
|
||||
.ToTable("User");
|
||||
modelBuilder.Entity<User>()
|
||||
.Property(u => u.LastLogin).IsRequired(false);
|
||||
modelBuilder.Entity<User>()
|
||||
.Property(u => u.DateCreated).HasDefaultValue(DateTime.Now);
|
||||
}
|
||||
|
||||
|
||||
public User RetrieveRecord(User user)
|
||||
{
|
||||
return Users.FirstOrDefault(usr => usr.Id == user.Id)!;
|
||||
}
|
||||
|
||||
public bool DoesRecordExist(User user)
|
||||
{
|
||||
return Users.FirstOrDefault(usr => usr.Id == user.Id) != null ? true : false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
|
||||
<Version>0.1.10</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.2" />
|
||||
<PackageReference Include="BouncyCastle.Cryptography" Version="2.4.0" />
|
||||
<PackageReference Include="EntityFramework" Version="6.4.4" />
|
||||
<PackageReference Include="File.TypeChecker" Version="4.1.1" />
|
||||
<PackageReference Include="Iconic.Zlib.Netstandard" Version="1.0.0" />
|
||||
<PackageReference Include="JWT" Version="10.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="4.14.0" />
|
||||
<PackageReference Include="RestSharp" Version="106.15.0" />
|
||||
<PackageReference Include="SevenZip" Version="19.0.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.6.0" />
|
||||
<PackageReference Include="system.text.encoding.codepages" Version="8.0.0" />
|
||||
<PackageReference Include="taglib" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Models\Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="nlog.config" CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content Include="Images/Stock/*.*" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 123 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,105 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
using Icarus.Database.Contexts;
|
||||
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
var MAX_REQUEST_BODY_SIZE = 51200000000;
|
||||
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
|
||||
|
||||
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Icarus", Version = "v1" });
|
||||
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
|
||||
{
|
||||
Name = "Authorization",
|
||||
Scheme = "Bearer",
|
||||
BearerFormat = "JWT",
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
In = ParameterLocation.Header,
|
||||
Description = "Bearer *Auth Token*",
|
||||
});
|
||||
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
new string[] {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.WebHost.UseKestrel(option =>
|
||||
{
|
||||
option.Limits.MaxRequestBodySize = MAX_REQUEST_BODY_SIZE;
|
||||
});
|
||||
|
||||
var Configuration = builder.Configuration;
|
||||
|
||||
var connString = Configuration.GetConnectionString("DefaultConnection");
|
||||
|
||||
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
|
||||
{
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.SaveToken = true;
|
||||
options.TokenValidationParameters = new TokenValidationParameters()
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidateLifetime = true,
|
||||
ValidAudience = Configuration["JWT:Audience"],
|
||||
ValidIssuer = Configuration["JWT:Issuer"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(Configuration["JWT:Secret"]!))
|
||||
};
|
||||
});
|
||||
|
||||
builder.Services.AddDbContext<SongContext>(options => options.UseMySQL(connString!));
|
||||
builder.Services.AddDbContext<AlbumContext>(options => options.UseMySQL(connString!));
|
||||
builder.Services.AddDbContext<ArtistContext>(options => options.UseMySQL(connString!));
|
||||
builder.Services.AddDbContext<UserContext>(options => options.UseMySQL(connString!));
|
||||
builder.Services.AddDbContext<GenreContext>(options => options.UseMySQL(connString!));
|
||||
builder.Services.AddDbContext<CoverArtContext>(options => options.UseMySQL(connString!));
|
||||
|
||||
builder.Services.AddControllers()
|
||||
.AddNewtonsoftJson();
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
// NOTE: This should be enabled at some point
|
||||
// app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:20781",
|
||||
"sslPort": 44394
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Icarus": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"dotnetRuneMessage": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5002",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Icarus.Types;
|
||||
|
||||
public enum CoverArtField
|
||||
{
|
||||
SongTitle = 0,
|
||||
ImagePath
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Icarus.Types;
|
||||
|
||||
public enum DirectoryType
|
||||
{
|
||||
Music = 0,
|
||||
CoverArt
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"Auth0": {
|
||||
"Domain": "[domain].auth0.com",
|
||||
"ApiIdentifier": "https://[identifier]/api",
|
||||
"ClientId": "",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"JWT": {
|
||||
"Issuer": "",
|
||||
"Audience": "",
|
||||
"Secret": "",
|
||||
"Subject": ""
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=;Database=;Uid=;Pwd=;"
|
||||
},
|
||||
"RootMusicPath": "/music/path/",
|
||||
"TemporaryMusicPath": "/music/temp/path/",
|
||||
"ArchivePath": "/archive/path/",
|
||||
"CoverArtPath": "/cover/art/path/"
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Trace",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"Auth0": {
|
||||
"Domain": "[domain].auth0.com",
|
||||
"ApiIdentifier": "https://[identifier]/api",
|
||||
"ClientId": "",
|
||||
"ClientSecret": ""
|
||||
},
|
||||
"JWT": {
|
||||
"Issuer": "",
|
||||
"Audience": "",
|
||||
"Secret": "",
|
||||
"Subject": ""
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=;Database=;Uid=;Pwd=;"
|
||||
},
|
||||
"RootMusicPath": "/music/path/",
|
||||
"TemporaryMusicPath": "/music/temp/path/",
|
||||
"ArchivePath": "/archive/path/",
|
||||
"CoverArtPath": "/cover/art/path/"
|
||||
}
|
||||