This commit is contained in:
Steven Hildreth 2019-07-06 22:16:33 -05:00
parent e76432557a
commit ca5db4cb17
87 changed files with 2246 additions and 2825 deletions

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
@ -22,7 +22,6 @@
<ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="6.2.1" />
</ItemGroup>
<ItemGroup>

View file

@ -1 +1,2 @@
gci -Path "N:\_complete" -r -include *.zip,*.rar,*.7z | foreach { & 'C:\Program Files\7-Zip\7z.exe' x $_.FullName -y -o"$('"'+$_.DirectoryName+'"')"}
gci -Path "N:\_complete" -r -include *.zip,*.rar,*.7z | foreach { & 'C:\Program Files\7-Zip\7z.exe' x $_.FullName -y -o"$('"'+$_.DirectoryName+'"')"}
Remove-Item path N:\_complete -include *.7z,*.zip,*.rar -recurse

View file

@ -7,7 +7,6 @@ using Roadie.Library.Configuration;
using Roadie.Library.Data;
using Roadie.Library.Engines;
using Roadie.Library.Extensions;
using Roadie.Library.Factories;
using Roadie.Library.MetaData.ID3Tags;
using Roadie.Library.Processors;
using System;
@ -39,7 +38,7 @@ namespace Roadie.Library.Tests
public ArtistLookupEngineTests()
{
this.MessageLogger = new EventMessageLogger();
this.MessageLogger = new EventMessageLogger<ArtistLookupEngineTests>();
this.MessageLogger.Messages += MessageLogger_Messages;
var settings = new RoadieSettings();
@ -57,19 +56,6 @@ namespace Roadie.Library.Tests
Console.WriteLine($"Log Level [{ e.Level }] Log Message [{ e.Message }] ");
}
[Fact]
public void Get_Artist_By_Name()
{
var optionsBuilder = new DbContextOptionsBuilder<RoadieDbContext>();
optionsBuilder.UseMySql("server=voyager;userid=roadie;password=MenAtW0rk668;persistsecurityinfo=True;database=roadie;ConvertZeroDateTime=true");
using (var context = new RoadieDbContext(optionsBuilder.Options))
{
IArtistLookupEngine artistLookupEngine = new ArtistLookupEngine(this.Configuration, this.HttpEncoder, context, this.CacheManager, this.Logger);
var a = artistLookupEngine.DatabaseQueryForArtistName("Nas");
}
}
//[Fact]
//public void Update_Genre_Normalized_Name()

View file

@ -31,7 +31,7 @@ namespace Roadie.Library.Tests
public ID3TagsHelperTests()
{
this.MessageLogger = new EventMessageLogger();
this.MessageLogger = new EventMessageLogger<ID3TagsHelperTests>();
this.MessageLogger.Messages += MessageLogger_Messages;
var settings = new RoadieSettings();
@ -42,7 +42,9 @@ namespace Roadie.Library.Tests
settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection");
this.Configuration = settings;
this.CacheManager = new DictionaryCacheManager(this.Logger, new CachePolicy(TimeSpan.FromHours(4)));
this.TagsHelper = new ID3TagsHelper(this.Configuration, this.CacheManager, this.Logger);
var tagHelperLooper = new EventMessageLogger<ID3TagsHelper>();
tagHelperLooper.Messages += MessageLogger_Messages;
this.TagsHelper = new ID3TagsHelper(this.Configuration, this.CacheManager, tagHelperLooper);
}
private void MessageLogger_Messages(object sender, EventMessage e)

View file

@ -21,6 +21,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>

View file

@ -0,0 +1,74 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Processors;
using Roadie.Library.SearchEngines.MetaData.Discogs;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace Roadie.Library.Tests
{
public class SearchEngineTests
{
private IEventMessageLogger MessageLogger { get; }
private ILogger Logger
{
get
{
return MessageLogger as ILogger;
}
}
public SearchEngineTests()
{
MessageLogger = new EventMessageLogger<SearchEngineTests>();
MessageLogger.Messages += MessageLogger_Messages;
var settings = new RoadieSettings();
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("appsettings.test.json");
IConfiguration configuration = configurationBuilder.Build();
configuration.GetSection("RoadieSettings").Bind(settings);
Configuration = settings;
CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4)));
HttpEncoder = new Encoding.DummyHttpEncoder();
}
private IRoadieSettings Configuration { get; }
public DictionaryCacheManager CacheManager { get; }
private Encoding.IHttpEncoder HttpEncoder { get; }
[Fact]
public async Task DiscogsHelperReleaseSearch()
{
if(!Configuration.Integrations.DiscogsProviderEnabled)
{
return;
}
var discogsLogger = new EventMessageLogger<DiscogsHelper>();
discogsLogger.Messages += MessageLogger_Messages;
var engine = new DiscogsHelper(Configuration, CacheManager, discogsLogger);
var artistName = "With The Dead";
var title = "Love From With The Dead";
var result = await engine.PerformReleaseSearch(artistName, title, 1);
Assert.NotNull(result);
Assert.NotEmpty(result.Data);
}
private void MessageLogger_Messages(object sender, EventMessage e)
{
Console.WriteLine($"Log Level [{ e.Level }] Log Message [{ e.Message }] ");
}
}
}

View file

@ -0,0 +1,27 @@
using Roadie.Library.Imaging;
using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Xunit;
namespace Roadie.Library.Tests
{
public class WebHelperTests
{
[Fact]
public void DownloadTestImage()
{
var testImageUrl = @"https://i.ytimg.com/vi/OiH5YMXQwYg/maxresdefault.jpg";
var imageBytes = WebHelper.BytesForImageUrl(testImageUrl);
Assert.NotNull(imageBytes);
Assert.NotEmpty(imageBytes);
var coverFileName = Path.Combine(@"C:\roadie_dev_root", "testImage.jpg");
File.WriteAllBytes(coverFileName, imageBytes);
}
}
}

View file

@ -25,23 +25,23 @@
"Love/Hate": [ "Love; Hate", "Love;Hate", "Love/ Hate", "Love Hate" ]
},
"Integrations": {
"ITunesProviderEnabled": true,
"MusicBrainzProviderEnabled": true,
"SpotifyProviderEnabled": true,
"ITunesProviderEnabled": false,
"MusicBrainzProviderEnabled": false,
"SpotifyProviderEnabled": false,
"ApiKeys": [
{
"ApiName": "BingImageSearch",
"Key": "<KEY HERE>"
"Key": ""
},
{
"ApiName": "LastFMApiKey",
"Key": "<KEY HERE>",
"KeySecret": "<SECRET HERE>"
"Key": "<",
"KeySecret": ""
},
{
"ApiName": "DiscogsConsumerKey",
"Key": "<KEY HERE>",
"KeySecret": "<SECRET HERE>"
"Key": "",
"KeySecret": ""
}
]
},

View file

@ -6,12 +6,9 @@ namespace Roadie.Library.Configuration
{
public class Integrations : IIntegrations
{
private string _discogsConsumerKey = null;
private string _discogsConsumerSecret = null;
private bool _discogsEnabled = true;
private string _lastFMApiKey = null;
private bool _bingSearchEnabled = true;
private bool _lastFmEnabled = true;
private string _lastFMSecret = null;
public List<ApiKey> ApiKeys { get; set; } = new List<ApiKey>();
@ -58,6 +55,21 @@ namespace Roadie.Library.Configuration
public bool ITunesProviderEnabled { get; set; }
public bool BingImageSearchEngineEnabled
{
get
{
var apiKey = ApiKeys.FirstOrDefault(x => x.ApiName == "BingImageSearch");
if (string.IsNullOrEmpty(apiKey?.Key))
{
return false;
}
return _bingSearchEnabled;
}
set => _bingSearchEnabled = value;
}
public string LastFMApiKey
{
get
@ -94,5 +106,11 @@ namespace Roadie.Library.Configuration
public bool MusicBrainzProviderEnabled { get; set; }
public bool SpotifyProviderEnabled { get; set; }
public Integrations()
{
DiscogsReadWriteTimeout = short.MaxValue;
DiscogsTimeout = short.MaxValue;
}
}
}

View file

@ -47,9 +47,9 @@ namespace Roadie.Library.Data
return $"urn:artist_by_name:{name}";
}
public string ArtistFileFolder(IRoadieSettings configuration, string destinationRoot)
public string ArtistFileFolder(IRoadieSettings configuration)
{
return FolderPathHelper.ArtistPath(configuration, SortNameValue, destinationRoot);
return FolderPathHelper.ArtistPath(configuration, SortNameValue);
}
public override string ToString()

View file

@ -48,17 +48,17 @@ namespace Roadie.Library.Data
/// <summary>
/// Returns a full file path to the current track
/// </summary>
public string PathToTrack(IRoadieSettings configuration, string libraryFolder)
public string PathToTrack(IRoadieSettings configuration)
{
return FolderPathHelper.PathForTrack(configuration, this, libraryFolder);
return FolderPathHelper.PathForTrack(configuration, this);
}
/// <summary>
/// Returns a full file path to the current track thumbnail (if any)
/// </summary>
public string PathToTrackThumbnail(IRoadieSettings configuration, string libraryFolder)
public string PathToTrackThumbnail(IRoadieSettings configuration)
{
return FolderPathHelper.PathForTrackThumbnail(configuration, this, libraryFolder);
return FolderPathHelper.PathForTrackThumbnail(configuration, this);
}
public override string ToString()

View file

@ -46,17 +46,17 @@ namespace Roadie.Library.Engines
public IArtistSearchEngine WikipediaArtistSearchEngine { get; }
public ArtistLookupEngine(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
ICacheManager cacheManager, ILogger logger)
ICacheManager cacheManager, ILogger<ArtistLookupEngine> logger, musicbrainz.IMusicBrainzProvider musicBrainzProvider,
lastfm.ILastFmHelper lastFmHelper, spotify.ISpotifyHelper spotifyHelper, wikipedia.IWikipediaHelper wikipediaHelper,
discogs.IDiscogsHelper discogsHelper, IITunesSearchEngine iTunesSearchEngine)
: base(configuration, httpEncoder, context, cacheManager, logger)
{
ITunesArtistSearchEngine = new ITunesSearchEngine(Configuration, CacheManager, Logger);
MusicBrainzArtistSearchEngine = new musicbrainz.MusicBrainzProvider(Configuration, CacheManager, Logger);
LastFmArtistSearchEngine =
new lastfm.LastFmHelper(Configuration, CacheManager, Logger, context, httpEncoder);
SpotifyArtistSearchEngine = new spotify.SpotifyHelper(Configuration, CacheManager, Logger);
WikipediaArtistSearchEngine =
new wikipedia.WikipediaHelper(Configuration, CacheManager, Logger, HttpEncoder);
DiscogsArtistSearchEngine = new discogs.DiscogsHelper(Configuration, CacheManager, Logger);
ITunesArtistSearchEngine = iTunesSearchEngine;
MusicBrainzArtistSearchEngine = musicBrainzProvider;
LastFmArtistSearchEngine = lastFmHelper;
SpotifyArtistSearchEngine = spotifyHelper;
WikipediaArtistSearchEngine = wikipediaHelper;
DiscogsArtistSearchEngine = discogsHelper;
}
public async Task<OperationResult<Artist>> Add(Artist artist)
@ -240,24 +240,28 @@ namespace Roadie.Library.Engines
OperationResult<Artist> artistSearch = null;
// See if roadie.json file exists in the metadata files folder, if so then use artist data from that
var releaseRoadieDataFilename = Path.Combine(Path.GetDirectoryName(metaData.Filename),
"roadie.artist.json");
if (File.Exists(releaseRoadieDataFilename))
string releaseRoadieDataFilename = null;
try
{
releaseRoadieDataFilename = Path.Combine(Path.GetDirectoryName(metaData.Filename), "roadie.artist.json");
}
catch (Exception)
{
}
if (!string.IsNullOrEmpty(releaseRoadieDataFilename) && File.Exists(releaseRoadieDataFilename))
{
artist = JsonConvert.DeserializeObject<Artist>(File.ReadAllText(releaseRoadieDataFilename));
var addResult = await Add(artist);
if (!addResult.IsSuccess)
{
sw.Stop();
Logger.LogWarning("Unable To Add Artist For Roadie Data File [{0}]",
releaseRoadieDataFilename);
Logger.LogWarning("Unable To Add Artist For Roadie Data File [{0}]", releaseRoadieDataFilename);
return new OperationResult<Artist>
{
OperationTime = sw.ElapsedMilliseconds,
Errors = addResult.Errors
};
}
artist = addResult.Data;
}
else
@ -508,10 +512,14 @@ namespace Roadie.Library.Engines
if (d.Urls != null) result.URLs = result.URLs.AddToDelimitedList(d.Urls);
if (d.ImageUrls != null) artistImageUrls.AddRange(d.ImageUrls);
if (d.AlternateNames != null)
{
result.AlternateNames = result.AlternateNames.AddToDelimitedList(d.AlternateNames);
}
if (!string.IsNullOrEmpty(d.ArtistName) &&
!d.ArtistName.Equals(result.Name, StringComparison.OrdinalIgnoreCase))
{
result.AlternateNames.AddToDelimitedList(new[] { d.ArtistName });
}
result.CopyTo(new Artist
{
Profile = HttpEncoder.HtmlEncode(d.Profile),

View file

@ -20,10 +20,10 @@ namespace Roadie.Library.Engines
private ILabelSearchEngine DiscogsLabelSearchEngine { get; }
public LabelLookupEngine(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
ICacheManager cacheManager, ILogger logger)
ICacheManager cacheManager, ILogger<LabelLookupEngine> logger, discogs.IDiscogsHelper discogsHelper)
: base(configuration, httpEncoder, context, cacheManager, logger)
{
DiscogsLabelSearchEngine = new discogs.DiscogsHelper(Configuration, CacheManager, Logger);
DiscogsLabelSearchEngine = discogsHelper;
}
public async Task<OperationResult<Label>> Add(Label label)

View file

@ -53,21 +53,21 @@ namespace Roadie.Library.Engines
private ILabelLookupEngine LabelLookupEngine { get; }
public ReleaseLookupEngine(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
ICacheManager cacheManager, ILogger logger, IArtistLookupEngine artistLookupEngine,
ILabelLookupEngine labelLookupEngine)
ICacheManager cacheManager, ILogger<ReleaseLookupEngine> logger, IArtistLookupEngine artistLookupEngine,
ILabelLookupEngine labelLookupEngine, musicbrainz.IMusicBrainzProvider musicBrainzProvider, lastfm.ILastFmHelper lastFmHelper,
spotify.ISpotifyHelper spotifyHelper, wikipedia.IWikipediaHelper wikipediaHelper, discogs.IDiscogsHelper discogsHelper,
IITunesSearchEngine iTunesSearchEngine)
: base(configuration, httpEncoder, context, cacheManager, logger)
{
ArtistLookupEngine = ArtistLookupEngine;
ArtistLookupEngine = artistLookupEngine;
LabelLookupEngine = labelLookupEngine;
ITunesReleaseSearchEngine = new ITunesSearchEngine(Configuration, CacheManager, Logger);
MusicBrainzReleaseSearchEngine = new musicbrainz.MusicBrainzProvider(Configuration, CacheManager, Logger);
LastFmReleaseSearchEngine =
new lastfm.LastFmHelper(Configuration, CacheManager, Logger, context, httpEncoder);
DiscogsReleaseSearchEngine = new discogs.DiscogsHelper(Configuration, CacheManager, Logger);
SpotifyReleaseSearchEngine = new spotify.SpotifyHelper(Configuration, CacheManager, Logger);
WikipediaReleaseSearchEngine =
new wikipedia.WikipediaHelper(Configuration, CacheManager, Logger, HttpEncoder);
ITunesReleaseSearchEngine = iTunesSearchEngine;
MusicBrainzReleaseSearchEngine = musicBrainzProvider;
LastFmReleaseSearchEngine = lastFmHelper;
DiscogsReleaseSearchEngine = discogsHelper;
SpotifyReleaseSearchEngine = spotifyHelper;
WikipediaReleaseSearchEngine = wikipediaHelper;
}
public async Task<OperationResult<Release>> Add(Release release, bool doAddTracksInDatabase = false)
@ -109,6 +109,8 @@ namespace Roadie.Library.Engines
{
_addedReleaseIds.Add(release.Id);
if (releaseGenreTables != null && releaseGenreTables.Any(x => x.GenreId == null))
{
var addedGenreIds = new List<int>();
foreach (var releaseGenreTable in releaseGenreTables)
{
var genreName = releaseGenreTable.Genre?.Name?.ToAlphanumericName();
@ -120,7 +122,6 @@ namespace Roadie.Library.Engines
genreName = genreName.Substring(0, 99);
Logger.LogWarning($"Genre Name Too long was [{originalName}] truncated to [{releaseGenreTable.Genre.Name}]");
}
var genre = DbContext.Genres.FirstOrDefault(x => x.NormalizedName == genreName);
if (genre == null)
{
@ -132,17 +133,19 @@ namespace Roadie.Library.Engines
DbContext.Genres.Add(genre);
await DbContext.SaveChangesAsync();
}
if (genre != null && genre.Id > 0)
if (genre != null &&
genre.Id > 0 &&
!addedGenreIds.Any(x => x == genre.Id))
{
DbContext.ReleaseGenres.Add(new ReleaseGenre
DbContext.ReleaseGenres.Add(new ReleaseGenre
{
ReleaseId = release.Id,
GenreId = genre.Id
});
addedGenreIds.Add(genre.Id);
}
}
}
if (releaseImages != null && releaseImages.Any(x => x.Status == Statuses.New))
{
@ -193,6 +196,7 @@ namespace Roadie.Library.Engines
}
if (doAddTracksInDatabase)
{
if (releaseMedias != null && releaseMedias.Any(x => x.Status == Statuses.New))
{
foreach (var newReleaseMedia in releaseMedias.Where(x => x.Status == Statuses.New))
@ -214,10 +218,11 @@ namespace Roadie.Library.Engines
{
if (!release.IsCastRecording)
{
var trackArtistData =
await ArtistLookupEngine.GetByName(
new AudioMetaData { Artist = newTrack.TrackArtist.Name }, true);
if (trackArtistData.IsSuccess) trackArtistId = trackArtistData.Data.Id;
var trackArtistData = await ArtistLookupEngine.GetByName(new AudioMetaData { Artist = newTrack.TrackArtist.Name }, true);
if (trackArtistData.IsSuccess)
{
trackArtistId = trackArtistData.Data.Id;
}
}
else if (newTrack.TrackArtists != null && newTrack.TrackArtists.Any())
{
@ -261,7 +266,7 @@ namespace Roadie.Library.Engines
Logger.LogError(ex);
}
}
}
Logger.LogInformation("Added New Release: `{0}`", release.ToString());
}
}
@ -337,7 +342,7 @@ namespace Roadie.Library.Engines
try
{
releaseSearch = await PerformMetaDataProvidersReleaseSearch(metaData,
artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder), submissionId);
artist.ArtistFileFolder(Configuration), submissionId);
}
catch (Exception ex)
{

View file

@ -1,479 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Enums;
using Roadie.Library.Extensions;
using Roadie.Library.Imaging;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.Processors;
using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Roadie.Library.Factories
{
#pragma warning disable EF1000
public sealed class ArtistFactory : FactoryBase, IArtistFactory
{
private readonly List<int> _addedArtistIds = new List<int>();
public ArtistFactory(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
ICacheManager cacheManager, ILogger logger, IArtistLookupEngine artistLookupEngine,
IReleaseFactory releaseFactory, IImageFactory imageFactory, IReleaseLookupEngine releaseLookupEngine,
IAudioMetaDataHelper audioMetaDataHelper)
: base(configuration, context, cacheManager, logger, httpEncoder, artistLookupEngine, releaseLookupEngine)
{
ReleaseFactory = releaseFactory;
ImageFactory = imageFactory;
AudioMetaDataHelper = audioMetaDataHelper;
}
public IEnumerable<int> AddedArtistIds => _addedArtistIds;
private IReleaseFactory ReleaseFactory { get; }
private IImageFactory ImageFactory { get; }
private IAudioMetaDataHelper AudioMetaDataHelper { get; }
public async Task<OperationResult<bool>> Delete(Guid RoadieId)
{
var isSuccess = false;
var Artist = DbContext.Artists.FirstOrDefault(x => x.RoadieId == RoadieId);
if (Artist != null) return await Delete(Artist);
return new OperationResult<bool>
{
Data = isSuccess
};
}
public async Task<OperationResult<bool>> Delete(Artist Artist)
{
var isSuccess = false;
try
{
if (Artist != null)
{
DbContext.Artists.Remove(Artist);
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(Artist.CacheRegion);
Logger.LogInformation(string.Format("x DeleteArtist [{0}]", Artist.Id));
isSuccess = true;
}
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
return new OperationResult<bool>
{
Errors = new Exception[1] { ex }
};
}
return new OperationResult<bool>
{
IsSuccess = isSuccess,
Data = isSuccess
};
}
public OperationResult<Artist> GetByExternalIds(string musicBrainzId = null, string iTunesId = null,
string amgId = null, string spotifyId = null)
{
var sw = new Stopwatch();
sw.Start();
var Artist = (from a in DbContext.Artists
where a.MusicBrainzId != null && musicBrainzId != null && a.MusicBrainzId == musicBrainzId ||
a.ITunesId != null || iTunesId != null && a.ITunesId == iTunesId || a.AmgId != null ||
amgId != null && a.AmgId == amgId || a.SpotifyId != null ||
spotifyId != null && a.SpotifyId == spotifyId
select a).FirstOrDefault();
sw.Stop();
if (Artist == null || !Artist.IsValid)
Logger.LogTrace(
"ArtistFactory: Artist Not Found By External Ids: MusicbrainzId [{0}], iTunesIs [{1}], AmgId [{2}], SpotifyId [{3}]",
musicBrainzId, iTunesId, amgId, spotifyId);
return new OperationResult<Artist>
{
IsSuccess = Artist != null,
OperationTime = sw.ElapsedMilliseconds,
Data = Artist
};
}
/// <summary>
/// Merge one Artist into another one
/// </summary>
/// <param name="artistToMerge">The Artist to be merged</param>
/// <param name="artistToMergeInto">The Artist to merge into</param>
/// <returns></returns>
public async Task<OperationResult<Artist>> MergeArtists(Artist artistToMerge, Artist artistToMergeInto,
bool doDbUpdates = false)
{
SimpleContract.Requires<ArgumentNullException>(artistToMerge != null, "Invalid Artist");
SimpleContract.Requires<ArgumentNullException>(artistToMergeInto != null, "Invalid Artist");
var result = false;
var now = DateTime.UtcNow;
var sw = new Stopwatch();
sw.Start();
artistToMergeInto.RealName = artistToMerge.RealName ?? artistToMergeInto.RealName;
artistToMergeInto.MusicBrainzId = artistToMerge.MusicBrainzId ?? artistToMergeInto.MusicBrainzId;
artistToMergeInto.ITunesId = artistToMerge.ITunesId ?? artistToMergeInto.ITunesId;
artistToMergeInto.AmgId = artistToMerge.AmgId ?? artistToMergeInto.AmgId;
artistToMergeInto.SpotifyId = artistToMerge.SpotifyId ?? artistToMergeInto.SpotifyId;
artistToMergeInto.Thumbnail = artistToMerge.Thumbnail ?? artistToMergeInto.Thumbnail;
artistToMergeInto.Profile = artistToMerge.Profile ?? artistToMergeInto.Profile;
artistToMergeInto.BirthDate = artistToMerge.BirthDate ?? artistToMergeInto.BirthDate;
artistToMergeInto.BeginDate = artistToMerge.BeginDate ?? artistToMergeInto.BeginDate;
artistToMergeInto.EndDate = artistToMerge.EndDate ?? artistToMergeInto.EndDate;
if (!string.IsNullOrEmpty(artistToMerge.ArtistType) && !artistToMerge.ArtistType.Equals("Other", StringComparison.OrdinalIgnoreCase))
{
artistToMergeInto.ArtistType = artistToMerge.ArtistType;
}
artistToMergeInto.BioContext = artistToMerge.BioContext ?? artistToMergeInto.BioContext;
artistToMergeInto.DiscogsId = artistToMerge.DiscogsId ?? artistToMergeInto.DiscogsId;
artistToMergeInto.Tags = artistToMergeInto.Tags.AddToDelimitedList(artistToMerge.Tags.ToListFromDelimited());
var altNames = artistToMerge.AlternateNames.ToListFromDelimited().ToList();
altNames.Add(artistToMerge.Name);
altNames.Add(artistToMerge.SortName);
artistToMergeInto.AlternateNames = artistToMergeInto.AlternateNames.AddToDelimitedList(altNames);
artistToMergeInto.URLs = artistToMergeInto.URLs.AddToDelimitedList(artistToMerge.URLs.ToListFromDelimited());
artistToMergeInto.ISNI = artistToMergeInto.ISNI.AddToDelimitedList(artistToMerge.ISNI.ToListFromDelimited());
artistToMergeInto.LastUpdated = now;
if (doDbUpdates)
{
try
{
var artistGenres = DbContext.ArtistGenres.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistGenres != null)
{
foreach (var artistGenre in artistGenres)
{
artistGenre.ArtistId = artistToMergeInto.Id;
}
}
var artistImages = DbContext.Images.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistImages != null)
{
foreach (var artistImage in artistImages)
{
artistImage.ArtistId = artistToMergeInto.Id;
}
}
var userArtists = DbContext.UserArtists.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistImages != null)
{
foreach (var userArtist in userArtists)
{
userArtist.ArtistId = artistToMergeInto.Id;
}
}
var artistTracks = DbContext.Tracks.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistTracks != null)
{
foreach (var artistTrack in artistTracks)
{
artistTrack.ArtistId = artistToMergeInto.Id;
}
}
var artistReleases = DbContext.Releases.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistReleases != null)
{
foreach (var artistRelease in artistReleases)
{
// See if there is already a release by the same name for the artist to merge into, if so then merge releases
var artistToMergeHasRelease = DbContext.Releases.FirstOrDefault(x => x.ArtistId == artistToMerge.Id && x.Title == artistRelease.Title);
if (artistToMergeHasRelease != null)
{
await ReleaseFactory.MergeReleases(artistRelease, artistToMergeHasRelease, false);
}
else
{
artistRelease.ArtistId = artistToMerge.Id;
}
}
}
}
catch (Exception ex)
{
Logger.LogWarning(ex.ToString());
}
var artistFolder = artistToMerge.ArtistFileFolder(Configuration, Configuration.LibraryFolder);
foreach (var release in DbContext.Releases.Include("Artist").Where(x => x.ArtistId == artistToMerge.Id).ToArray())
{
var originalReleaseFolder = release.ReleaseFileFolder(artistFolder);
await ReleaseFactory.Update(release, null, originalReleaseFolder);
}
await Delete(artistToMerge);
}
result = true;
sw.Stop();
return new OperationResult<Artist>
{
Data = artistToMergeInto,
IsSuccess = result,
OperationTime = sw.ElapsedMilliseconds
};
}
/// <summary>
/// Perform a Metadata Provider search and then merge the results into the given Artist
/// </summary>
/// <param name="ArtistId">Given Artist RoadieId</param>
/// <returns>Operation Result</returns>
public async Task<OperationResult<bool>> RefreshArtistMetadata(Guid ArtistId)
{
SimpleContract.Requires<ArgumentOutOfRangeException>(ArtistId != Guid.Empty, "Invalid ArtistId");
var result = true;
var resultErrors = new List<Exception>();
var sw = new Stopwatch();
sw.Start();
try
{
var Artist = DbContext.Artists.FirstOrDefault(x => x.RoadieId == ArtistId);
if (Artist == null)
{
Logger.LogWarning("Unable To Find Artist [{0}]", ArtistId);
return new OperationResult<bool>();
}
OperationResult<Artist> ArtistSearch = null;
try
{
ArtistSearch = await ArtistLookupEngine.PerformMetaDataProvidersArtistSearch(new AudioMetaData
{
Artist = Artist.Name
});
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
}
if (ArtistSearch.IsSuccess)
{
// Do metadata search for Artist like if new Artist then set some overides and merge
var mergeResult = await MergeArtists(ArtistSearch.Data, Artist);
if (mergeResult.IsSuccess)
{
Artist = mergeResult.Data;
await DbContext.SaveChangesAsync();
sw.Stop();
CacheManager.ClearRegion(Artist.CacheRegion);
Logger.LogInformation("Scanned RefreshArtistMetadata [{0}], OperationTime [{1}]",
Artist.ToString(), sw.ElapsedMilliseconds);
}
else
{
sw.Stop();
}
}
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
resultErrors.Add(ex);
}
return new OperationResult<bool>
{
Data = result,
IsSuccess = result,
Errors = resultErrors,
OperationTime = sw.ElapsedMilliseconds
};
}
[Obsolete]
public async Task<OperationResult<bool>> ScanArtistReleasesFolders(Guid artistId, string destinationFolder,
bool doJustInfo)
{
SimpleContract.Requires<ArgumentOutOfRangeException>(artistId != Guid.Empty, "Invalid ArtistId");
var result = true;
var resultErrors = new List<Exception>();
var sw = new Stopwatch();
sw.Start();
try
{
var artist = DbContext.Artists
.Include("Releases")
.Include("Releases.Labels")
.FirstOrDefault(x => x.RoadieId == artistId);
if (artist == null)
{
Logger.LogWarning("Unable To Find Artist [{0}]", artistId);
return new OperationResult<bool>();
}
var releaseScannedCount = 0;
var artistFolder = artist.ArtistFileFolder(Configuration, destinationFolder);
var scannedArtistFolders = new List<string>();
// Scan known releases for changes
if (artist.Releases != null)
foreach (var release in artist.Releases)
try
{
result = result && (await ReleaseFactory.ScanReleaseFolder(Guid.Empty, destinationFolder,
doJustInfo, release)).Data;
releaseScannedCount++;
scannedArtistFolders.Add(release.ReleaseFileFolder(artistFolder));
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
}
// Any folder found in Artist folder not already scanned scan
var folderProcessor = new FolderProcessor(Configuration, HttpEncoder, destinationFolder, DbContext,
CacheManager, Logger, ArtistLookupEngine, this, ReleaseFactory, ImageFactory, ReleaseLookupEngine,
AudioMetaDataHelper);
var nonReleaseFolders = from d in Directory.EnumerateDirectories(artistFolder)
where !(from r in scannedArtistFolders select r).Contains(d)
orderby d
select d;
foreach (var folder in nonReleaseFolders)
await folderProcessor.Process(new DirectoryInfo(folder), doJustInfo);
if (!doJustInfo) FolderProcessor.DeleteEmptyFolders(new DirectoryInfo(artistFolder), Logger);
// Always update artist image if artist image is found on an artist rescan
var imageFiles = ImageHelper.ImageFilesInFolder(artistFolder, SearchOption.AllDirectories);
if (imageFiles != null && imageFiles.Any())
{
var imageFile = imageFiles.First();
var i = new FileInfo(imageFile);
var iName = i.Name.ToLower().Trim();
var isArtistImage = iName.Contains("artist") || iName.Contains(artist.Name.ToLower());
if (isArtistImage)
{
// Read image and convert to jpeg
artist.Thumbnail = File.ReadAllBytes(i.FullName);
artist.Thumbnail = ImageHelper.ResizeImage(artist.Thumbnail,
Configuration.MediumImageSize.Width, Configuration.MediumImageSize.Height);
artist.Thumbnail = ImageHelper.ConvertToJpegFormat(artist.Thumbnail);
if (artist.Thumbnail.Length >= ImageHelper.MaximumThumbnailByteSize)
{
Logger.LogWarning(
$"Artist Thumbnail larger than maximum size after resizing to [{Configuration.ThumbnailImageSize.Width}x{Configuration.ThumbnailImageSize.Height}] Thumbnail Size [{artist.Thumbnail.Length}]");
artist.Thumbnail = null;
}
artist.LastUpdated = DateTime.UtcNow;
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(artist.CacheRegion);
Logger.LogInformation("Update Thumbnail using Artist File [{0}]", iName);
}
}
sw.Stop();
CacheManager.ClearRegion(artist.CacheRegion);
Logger.LogInformation("Scanned Artist [{0}], Releases Scanned [{1}], OperationTime [{2}]",
artist.ToString(), releaseScannedCount, sw.ElapsedMilliseconds);
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
resultErrors.Add(ex);
}
return new OperationResult<bool>
{
Data = result,
IsSuccess = result,
Errors = resultErrors,
OperationTime = sw.ElapsedMilliseconds
};
}
[Obsolete]
public async Task<OperationResult<Artist>> Update(Artist Artist, IEnumerable<Image> ArtistImages,
string destinationFolder = null)
{
SimpleContract.Requires<ArgumentNullException>(Artist != null, "Invalid Artist");
var sw = new Stopwatch();
sw.Start();
var artistGenreTables = Artist.Genres
.Select(x => new ArtistGenre { ArtistId = Artist.Id, GenreId = x.GenreId }).ToList();
var artistAssociatedWith = Artist.AssociatedArtists.Select(x => new ArtistAssociation
{ ArtistId = Artist.Id, AssociatedArtistId = x.AssociatedArtistId }).ToList();
var similarArtists = Artist.SimilarArtists.Select(x => new ArtistSimilar
{ ArtistId = Artist.Id, SimilarArtistId = x.SimilarArtistId }).ToList();
var result = true;
var now = DateTime.UtcNow;
var originalArtistFolder =
Artist.ArtistFileFolder(Configuration, destinationFolder ?? Configuration.LibraryFolder);
var originalName = Artist.Name;
var originalSortName = Artist.SortName;
Artist.LastUpdated = now;
await DbContext.SaveChangesAsync();
DbContext.ArtistGenres.RemoveRange(from at in DbContext.ArtistGenres
where at.ArtistId == Artist.Id
select at);
Artist.Genres = artistGenreTables;
DbContext.ArtistAssociations.RemoveRange(from at in DbContext.ArtistAssociations
where at.ArtistId == Artist.Id
select at);
Artist.AssociatedArtists = artistAssociatedWith;
Artist.SimilarArtists = similarArtists;
await DbContext.SaveChangesAsync();
var existingImageIds = (from ai in ArtistImages
where ai.Status != Statuses.New
select ai.RoadieId).ToArray();
DbContext.Images.RemoveRange(from i in DbContext.Images
where i.ArtistId == Artist.Id
where !(from x in existingImageIds select x).Contains(i.RoadieId)
select i);
await DbContext.SaveChangesAsync();
if (ArtistImages != null && ArtistImages.Any(x => x.Status == Statuses.New))
{
foreach (var ArtistImage in ArtistImages.Where(x => x.Status == Statuses.New))
DbContext.Images.Add(ArtistImage);
try
{
await DbContext.SaveChangesAsync();
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
}
}
var newArtistFolder =
Artist.ArtistFileFolder(Configuration, destinationFolder ?? Configuration.LibraryFolder);
if (!originalArtistFolder.Equals(newArtistFolder, StringComparison.OrdinalIgnoreCase))
Logger.LogTrace("Moving Artist From Folder [{0}] To [{1}]", originalArtistFolder, newArtistFolder);
// Directory.Move(originalArtistFolder, Artist.ArtistFileFolder(destinationFolder ?? SettingsHelper.Instance.LibraryFolder));
// TODO if name changed then update Artist track files to have new Artist name
CacheManager.ClearRegion(Artist.CacheRegion);
sw.Stop();
return new OperationResult<Artist>
{
Data = Artist,
IsSuccess = result,
OperationTime = sw.ElapsedMilliseconds
};
}
}
}

View file

@ -1,128 +0,0 @@
using Microsoft.Extensions.Logging;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.SearchEngines.MetaData;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Roadie.Library.Factories
{
public abstract class FactoryBase
{
protected IArtistLookupEngine ArtistLookupEngine { get; }
protected ICacheManager CacheManager { get; }
protected IRoadieSettings Configuration { get; }
protected IRoadieDbContext DbContext { get; }
protected ILabelSearchEngine DiscogsLabelSearchEngine { get; }
protected IHttpEncoder HttpEncoder { get; }
protected ILogger Logger { get; }
protected IReleaseLookupEngine ReleaseLookupEngine { get; }
public FactoryBase(IRoadieSettings configuration, IRoadieDbContext context, ICacheManager cacheManager,
ILogger logger, IHttpEncoder httpEncoder, IArtistLookupEngine artistLookupEngine,
IReleaseLookupEngine releaseLookupEngine)
{
Configuration = configuration;
DbContext = context;
CacheManager = cacheManager;
Logger = logger;
HttpEncoder = httpEncoder;
ArtistLookupEngine = artistLookupEngine;
ReleaseLookupEngine = releaseLookupEngine;
}
[Obsolete("Use Service Methods")]
protected IEnumerable<int> ArtistIdsForRelease(int releaseId)
{
var trackArtistIds = (from r in DbContext.Releases
join rm in DbContext.ReleaseMedias on r.Id equals rm.ReleaseId
join tr in DbContext.Tracks on rm.Id equals tr.ReleaseMediaId
where r.Id == releaseId
where tr.ArtistId != null
select tr.ArtistId.Value).ToList();
trackArtistIds.Add(DbContext.Releases.FirstOrDefault(x => x.Id == releaseId).ArtistId);
return trackArtistIds.Distinct().ToArray();
}
[Obsolete("Use Service Methods")]
protected async Task UpdateArtistCounts(int artistId, DateTime now)
{
var artist = DbContext.Artists.FirstOrDefault(x => x.Id == artistId);
if (artist != null)
{
artist.ReleaseCount = DbContext.Releases.Where(x => x.ArtistId == artistId).Count();
artist.TrackCount = (from r in DbContext.Releases
join rm in DbContext.ReleaseMedias on r.Id equals rm.ReleaseId
join tr in DbContext.Tracks on rm.Id equals tr.ReleaseMediaId
where tr.ArtistId == artistId || r.ArtistId == artistId
select tr).Count();
artist.LastUpdated = now;
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(artist.CacheRegion);
}
}
[Obsolete("Use Service Methods")]
/// <summary>
/// Update the counts for all artists on a release (both track and release artists)
/// </summary>
protected async Task UpdateArtistCountsForRelease(int releaseId, DateTime now)
{
foreach (var artistId in ArtistIdsForRelease(releaseId)) await UpdateArtistCounts(artistId, now);
}
[Obsolete("Use Service Methods")]
protected async Task UpdateLabelCounts(int labelId, DateTime now)
{
var label = DbContext.Labels.FirstOrDefault(x => x.Id == labelId);
if (label != null)
{
label.ReleaseCount = DbContext.ReleaseLabels.Where(x => x.LabelId == label.Id).Count();
label.ArtistCount = (from r in DbContext.Releases
join rl in DbContext.ReleaseLabels on r.Id equals rl.ReleaseId
join a in DbContext.Artists on r.ArtistId equals a.Id
where rl.LabelId == label.Id
group a by a.Id
into artists
select artists).Select(x => x.Key).Count();
label.TrackCount = (from r in DbContext.Releases
join rl in DbContext.ReleaseLabels on r.Id equals rl.ReleaseId
join rm in DbContext.ReleaseMedias on r.Id equals rm.ReleaseId
join t in DbContext.Tracks on rm.Id equals t.ReleaseMediaId
where rl.LabelId == label.Id
select t).Count();
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(label.CacheRegion);
}
}
[Obsolete("Use Service Methods")]
protected async Task UpdateReleaseCounts(int releaseId, DateTime now)
{
var release = DbContext.Releases.FirstOrDefault(x => x.Id == releaseId);
if (release != null)
{
release.Duration = (from t in DbContext.Tracks
join rm in DbContext.ReleaseMedias on t.ReleaseMediaId equals rm.Id
where rm.ReleaseId == releaseId
select t).Sum(x => x.Duration);
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(release.CacheRegion);
}
}
}
}

View file

@ -1,27 +0,0 @@
using Roadie.Library.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Roadie.Library.Factories
{
public interface IArtistFactory
{
Task<OperationResult<bool>> Delete(Artist Artist);
Task<OperationResult<bool>> Delete(Guid RoadieId);
OperationResult<Artist> GetByExternalIds(string musicBrainzId = null, string iTunesId = null,
string amgId = null, string spotifyId = null);
Task<OperationResult<Artist>> MergeArtists(Artist ArtistToMerge, Artist artistToMergeInto,
bool doDbUpdates = false);
Task<OperationResult<bool>> RefreshArtistMetadata(Guid ArtistId);
Task<OperationResult<bool>> ScanArtistReleasesFolders(Guid artistId, string destinationFolder, bool doJustInfo);
Task<OperationResult<Artist>> Update(Artist Artist, IEnumerable<Image> ArtistImages,
string destinationFolder = null);
}
}

View file

@ -1,11 +0,0 @@
using Roadie.Library.MetaData.Audio;
namespace Roadie.Library.Factories
{
public interface IImageFactory
{
AudioMetaDataImage GetPictureForMetaData(string filename, AudioMetaData metaData);
AudioMetaDataImage ImageForFilename(string filename);
}
}

View file

@ -1,6 +0,0 @@
namespace Roadie.Library.Factories
{
public interface ILabelFactory
{
}
}

View file

@ -1,14 +0,0 @@
using Roadie.Library.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Roadie.Library.Factories
{
public interface IPlaylistFactory
{
Task<OperationResult<bool>> AddTracksToPlaylist(Playlist playlist, IEnumerable<Guid> trackIds);
Task<OperationResult<bool>> ReorderPlaylist(Playlist playlist);
}
}

View file

@ -1,30 +0,0 @@
using Roadie.Library.Data;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Roadie.Library.Factories
{
public interface IReleaseFactory
{
IEnumerable<int> AddedTrackIds { get; }
Task<OperationResult<bool>> CheckAndChangeReleaseTitle(Release release, string oldReleaseFolder,
string destinationFolder = null);
Task<OperationResult<bool>> Delete(Release release, bool doDeleteFiles = false,
bool doUpdateArtistCounts = true);
Task<OperationResult<bool>> DeleteReleases(IEnumerable<Guid> releaseIds, bool doDeleteFiles = false);
OperationResult<Release> GetAllForArtist(Artist artist, bool forceRefresh = false);
Task<OperationResult<bool>> MergeReleases(Release releaseToMerge, Release releaseToMergeInto, bool addAsMedia);
Task<OperationResult<bool>> ScanReleaseFolder(Guid releaseId, string destinationFolder, bool doJustInfo,
Release releaseToScan = null);
Task<OperationResult<Release>> Update(Release release, IEnumerable<Image> releaseImages,
string originalReleaseFolder, string destinationFolder = null);
}
}

View file

@ -1,107 +0,0 @@
using Microsoft.Extensions.Logging;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Extensions;
using Roadie.Library.Imaging;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.Processors;
using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace Roadie.Library.Factories
{
public sealed class ImageFactory : FactoryBase, IImageFactory
{
public ImageFactory(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
ICacheManager cacheManager, ILogger logger, IArtistLookupEngine artistLookupEngine,
IReleaseLookupEngine releaseLookupEngine)
: base(configuration, context, cacheManager, logger, httpEncoder, artistLookupEngine, releaseLookupEngine)
{
}
/// <summary>
/// Get image data from all sources for either fileanme or MetaData
/// </summary>
/// <param name="filename">Name of the File (ie a CUE file)</param>
/// <param name="metaData">Populated MetaData</param>
/// <returns></returns>
public AudioMetaDataImage GetPictureForMetaData(string filename, AudioMetaData metaData)
{
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(filename), "Invalid Filename");
SimpleContract.Requires<ArgumentException>(metaData != null, "Invalid MetaData");
return ImageForFilename(filename);
}
/// <summary>
/// Does image exist with the same filename
/// </summary>
/// <param name="filename">Name of the File (ie a CUE file)</param>
/// <returns>Null if not found else populated image</returns>
public AudioMetaDataImage ImageForFilename(string filename)
{
AudioMetaDataImage imageMetaData = null;
if (string.IsNullOrEmpty(filename)) return imageMetaData;
try
{
var fileInfo = new FileInfo(filename);
var ReleaseCover = Path.ChangeExtension(filename, "jpg");
if (File.Exists(ReleaseCover))
{
using (var processor = new ImageProcessor(Configuration))
{
imageMetaData = new AudioMetaDataImage
{
Data = processor.Process(File.ReadAllBytes(ReleaseCover)),
Type = AudioMetaDataImageType.FrontCover,
MimeType = FileProcessor.DetermineFileType(fileInfo)
};
}
}
else
{
// Is there a picture in filename folder (for the Release)
var pictures = fileInfo.Directory.GetFiles("*.jpg");
var tagImages = new List<AudioMetaDataImage>();
if (pictures != null && pictures.Any())
{
FileInfo picture = null;
// See if there is a "cover" or "front" jpg file if so use it
picture = pictures.FirstOrDefault(x =>
x.Name.Equals("cover", StringComparison.OrdinalIgnoreCase));
if (picture == null)
picture = pictures.FirstOrDefault(x =>
x.Name.Equals("front", StringComparison.OrdinalIgnoreCase));
if (picture == null) picture = pictures.First();
if (picture != null)
using (var processor = new ImageProcessor(Configuration))
{
imageMetaData = new AudioMetaDataImage
{
Data = processor.Process(File.ReadAllBytes(picture.FullName)),
Type = AudioMetaDataImageType.FrontCover,
MimeType = FileProcessor.DetermineFileType(picture)
};
}
}
}
}
catch (FileNotFoundException)
{
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
}
return imageMetaData;
}
}
}

View file

@ -1,21 +0,0 @@
using Microsoft.Extensions.Logging;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
namespace Roadie.Library.Factories
{
public sealed class LabelFactory : FactoryBase, ILabelFactory
{
public LabelFactory(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
ICacheManager cacheManager, ILogger logger, IArtistLookupEngine artistLookupEngine,
IReleaseLookupEngine releaseLookupEngine)
: base(configuration, context, cacheManager, logger, httpEncoder, artistLookupEngine, releaseLookupEngine)
{
}
// TODO Merge
}
}

View file

@ -1,92 +0,0 @@
using Microsoft.Extensions.Logging;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace Roadie.Library.Factories
{
public class PlaylistFactory : FactoryBase, IPlaylistFactory
{
public PlaylistFactory(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
ICacheManager cacheManager, ILogger logger, IArtistLookupEngine artistLookupEngine,
IReleaseLookupEngine releaseLookupEngine)
: base(configuration, context, cacheManager, logger, httpEncoder, artistLookupEngine, releaseLookupEngine)
{
}
[Obsolete("Use PlaylistService")]
public async Task<OperationResult<bool>> AddTracksToPlaylist(Playlist playlist, IEnumerable<Guid> trackIds)
{
var sw = new Stopwatch();
sw.Start();
var result = false;
var now = DateTime.UtcNow;
var existingTracksForPlaylist = from plt in DbContext.PlaylistTracks
join t in DbContext.Tracks on plt.TrackId equals t.Id
where plt.PlayListId == playlist.Id
select t;
var newTracksForPlaylist = (from t in DbContext.Tracks
where (from x in trackIds select x).Contains(t.RoadieId)
where !(from x in existingTracksForPlaylist select x.RoadieId).Contains(t.RoadieId)
select t).ToArray();
foreach (var newTrackForPlaylist in newTracksForPlaylist)
DbContext.PlaylistTracks.Add(new PlaylistTrack
{
TrackId = newTrackForPlaylist.Id,
PlayListId = playlist.Id,
CreatedDate = now
});
playlist.LastUpdated = now;
await DbContext.SaveChangesAsync();
result = true;
var r = await ReorderPlaylist(playlist);
result = result && r.IsSuccess;
return new OperationResult<bool>
{
Data = result
};
}
[Obsolete("Use PlaylistService")]
public async Task<OperationResult<bool>> ReorderPlaylist(Playlist playlist)
{
var sw = new Stopwatch();
sw.Start();
var result = false;
var now = DateTime.UtcNow;
if (playlist != null)
{
var looper = 0;
foreach (var playlistTrack in DbContext.PlaylistTracks.Where(x => x.PlayListId == playlist.Id)
.OrderBy(x => x.CreatedDate))
{
looper++;
playlistTrack.ListNumber = looper;
playlistTrack.LastUpdated = now;
}
await DbContext.SaveChangesAsync();
result = true;
}
return new OperationResult<bool>
{
IsSuccess = result,
Data = result
};
}
}
}

View file

@ -1,950 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Enums;
using Roadie.Library.Extensions;
using Roadie.Library.Imaging;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.Processors;
using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Roadie.Library.Factories
{
#pragma warning disable EF1000
public sealed class ReleaseFactory : FactoryBase, IReleaseFactory
{
private readonly List<int> _addedTrackIds = new List<int>();
public ReleaseFactory(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
ICacheManager cacheManager, ILogger logger, IArtistLookupEngine artistLookupEngine,
ILabelFactory labelFactory, IAudioMetaDataHelper audioMetaDataHelper,
IReleaseLookupEngine releaseLookupEngine)
: base(configuration, context, cacheManager, logger, httpEncoder, artistLookupEngine, releaseLookupEngine)
{
LabelFactory = labelFactory;
AudioMetaDataHelper = audioMetaDataHelper;
}
private IAudioMetaDataHelper AudioMetaDataHelper { get; }
private ILabelFactory LabelFactory { get; }
public IEnumerable<int> AddedTrackIds => _addedTrackIds;
/// <summary>
/// See if the given release has properties that have been modified that affect the folder structure, if so then handle
/// necessary operations for changes
/// </summary>
/// <param name="release">Release that has been modified</param>
/// <param name="oldReleaseFolder">Folder for release before any changes</param>
/// <returns></returns>
public async Task<OperationResult<bool>> CheckAndChangeReleaseTitle(Release release, string oldReleaseFolder,
string destinationFolder = null)
{
SimpleContract.Requires<ArgumentNullException>(release != null, "Invalid Release");
SimpleContract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(oldReleaseFolder),
"Invalid Release Old Folder");
destinationFolder = destinationFolder ?? Configuration.LibraryFolder;
var sw = new Stopwatch();
sw.Start();
var now = DateTime.UtcNow;
var result = false;
var artistFolder = release.Artist.ArtistFileFolder(Configuration, destinationFolder);
var newReleaseFolder = release.ReleaseFileFolder(artistFolder);
if (!oldReleaseFolder.Equals(newReleaseFolder, StringComparison.OrdinalIgnoreCase))
{
Logger.LogTrace("Moving Release From Folder [{0}] To [{1}]", oldReleaseFolder, newReleaseFolder);
// Create the new release folder
if (!Directory.Exists(newReleaseFolder)) Directory.CreateDirectory(newReleaseFolder);
var releaseDirectoryInfo = new DirectoryInfo(newReleaseFolder);
// Update and move tracks under new release folder
foreach (var releaseMedia in DbContext.ReleaseMedias.Where(x => x.ReleaseId == release.Id).ToArray())
// Update the track path to have the new album title. This is needed because future scans might not work properly without updating track title.
foreach (var track in DbContext.Tracks.Where(x => x.ReleaseMediaId == releaseMedia.Id).ToArray())
{
var existingTrackPath = track.PathToTrack(Configuration, destinationFolder);
var existingTrackFileInfo = new FileInfo(existingTrackPath);
var newTrackFileInfo = new FileInfo(track.PathToTrack(Configuration, destinationFolder));
if (existingTrackFileInfo.Exists)
{
// Update the tracks release tags
var audioMetaData = await AudioMetaDataHelper.GetInfo(existingTrackFileInfo);
audioMetaData.Release = release.Title;
AudioMetaDataHelper.WriteTags(audioMetaData, existingTrackFileInfo);
// Update track path
track.FilePath = Path.Combine(releaseDirectoryInfo.Parent.Name, releaseDirectoryInfo.Name);
track.LastUpdated = now;
// Move the physical track
var newTrackPath = track.PathToTrack(Configuration, destinationFolder);
if (!existingTrackPath.Equals(newTrackPath, StringComparison.OrdinalIgnoreCase))
File.Move(existingTrackPath, newTrackPath);
}
CacheManager.ClearRegion(track.CacheRegion);
}
await DbContext.SaveChangesAsync();
// Clean up any empty folders for the artist
FolderPathHelper.DeleteEmptyFoldersForArtist(Configuration, release.Artist, destinationFolder);
}
sw.Stop();
CacheManager.ClearRegion(release.CacheRegion);
if (release.Artist != null) CacheManager.ClearRegion(release.Artist.CacheRegion);
return new OperationResult<bool>
{
IsSuccess = result,
OperationTime = sw.ElapsedMilliseconds
};
}
public async Task<OperationResult<bool>> Delete(Release release, bool doDeleteFiles = false,
bool doUpdateArtistCounts = true)
{
SimpleContract.Requires<ArgumentNullException>(release != null, "Invalid Release");
SimpleContract.Requires<ArgumentNullException>(release.Artist != null, "Invalid Artist");
var releaseCacheRegion = release.CacheRegion;
var artistCacheRegion = release.Artist.CacheRegion;
var result = false;
var sw = new Stopwatch();
sw.Start();
if (doDeleteFiles)
{
var releaseTracks = (from r in DbContext.Releases
join rm in DbContext.ReleaseMedias on r.Id equals rm.ReleaseId
join t in DbContext.Tracks on rm.Id equals t.ReleaseMediaId
where r.Id == release.Id
select t).ToArray();
foreach (var track in releaseTracks)
{
string trackPath = null;
try
{
trackPath = track.PathToTrack(Configuration, Configuration.LibraryFolder);
if (File.Exists(trackPath))
{
File.Delete(trackPath);
Logger.LogWarning("x For Release [{0}], Deleted File [{1}]", release.Id, trackPath);
}
}
catch (Exception ex)
{
Logger.LogError(ex,
string.Format("Error Deleting File [{0}] For Track [{1}] Exception [{2}]", trackPath,
track.Id, ex.Serialize()));
}
}
try
{
FolderPathHelper.DeleteEmptyFoldersForArtist(Configuration, release.Artist);
}
catch (Exception ex)
{
Logger.LogError(ex);
}
}
var releaseLabelIds = DbContext.ReleaseLabels.Where(x => x.ReleaseId == release.Id).Select(x => x.LabelId)
.ToArray();
DbContext.Releases.Remove(release);
var i = await DbContext.SaveChangesAsync();
result = true;
try
{
CacheManager.ClearRegion(releaseCacheRegion);
CacheManager.ClearRegion(artistCacheRegion);
}
catch (Exception ex)
{
Logger.LogError(ex,
string.Format("Error Clearing Cache For Release [{0}] Exception [{1}]", release.Id,
ex.Serialize()));
}
var now = DateTime.UtcNow;
if (doUpdateArtistCounts) await UpdateArtistCounts(release.Artist.Id, now);
if (releaseLabelIds != null && releaseLabelIds.Any())
foreach (var releaseLabelId in releaseLabelIds)
await UpdateLabelCounts(releaseLabelId, now);
sw.Stop();
return new OperationResult<bool>
{
Data = result,
IsSuccess = result,
OperationTime = sw.ElapsedMilliseconds
};
}
public async Task<OperationResult<bool>> DeleteReleases(IEnumerable<Guid> releaseIds,
bool doDeleteFiles = false)
{
SimpleContract.Requires<ArgumentNullException>(releaseIds != null && releaseIds.Any(),
"No Release Ids Found");
var result = false;
var sw = new Stopwatch();
sw.Start();
var now = DateTime.UtcNow;
var releases = (from r in DbContext.Releases.Include(r => r.Artist)
where releaseIds.Contains(r.RoadieId)
select r
).ToArray();
var artistIds = releases.Select(x => x.ArtistId).Distinct().ToArray();
foreach (var release in releases)
{
var defaultResult = await Delete(release, doDeleteFiles, false);
result = result & defaultResult.IsSuccess;
}
foreach (var artistId in artistIds) await UpdateArtistCounts(artistId, now);
sw.Stop();
return new OperationResult<bool>
{
Data = result,
IsSuccess = result,
OperationTime = sw.ElapsedMilliseconds
};
}
public OperationResult<Release> GetAllForArtist(Artist artist, bool forceRefresh = false)
{
throw new NotImplementedException();
}
/// <summary>
/// Merge one release into another one
/// </summary>
/// <param name="releaseToMerge">The release to be merged</param>
/// <param name="releaseToMergeInto">The release to merge into</param>
/// <param name="addAsMedia">If true then add a ReleaseMedia to the release to be merged into</param>
/// <returns></returns>
public async Task<OperationResult<bool>> MergeReleases(Release releaseToMerge, Release releaseToMergeInto,
bool addAsMedia)
{
SimpleContract.Requires<ArgumentNullException>(releaseToMerge != null, "Invalid Release");
SimpleContract.Requires<ArgumentNullException>(releaseToMergeInto != null, "Invalid Release");
SimpleContract.Requires<ArgumentNullException>(releaseToMerge.Artist != null, "Invalid Artist");
SimpleContract.Requires<ArgumentNullException>(releaseToMergeInto.Artist != null, "Invalid Artist");
var result = false;
var resultErrors = new List<Exception>();
var sw = new Stopwatch();
sw.Start();
try
{
var mergedFilesToDelete = new List<string>();
var mergedTracksToMove = new List<Track>();
releaseToMergeInto.MediaCount = releaseToMergeInto.MediaCount ?? 0;
var now = DateTime.UtcNow;
var releaseToMergeReleaseMedia =
DbContext.ReleaseMedias.Where(x => x.ReleaseId == releaseToMerge.Id).ToList();
var releaseToMergeIntoReleaseMedia =
DbContext.ReleaseMedias.Where(x => x.ReleaseId == releaseToMergeInto.Id).ToList();
var releaseToMergeIntoLastMediaNumber = releaseToMergeIntoReleaseMedia.Max(x => x.MediaNumber);
// Add new ReleaseMedia
if (addAsMedia || !releaseToMergeIntoReleaseMedia.Any())
foreach (var rm in releaseToMergeReleaseMedia)
{
releaseToMergeIntoLastMediaNumber++;
rm.ReleaseId = releaseToMergeInto.Id;
rm.MediaNumber = releaseToMergeIntoLastMediaNumber;
rm.LastUpdated = now;
releaseToMergeInto.MediaCount++;
releaseToMergeInto.TrackCount += rm.TrackCount;
}
// Merge into existing ReleaseMedia
else
// See if each media exists and merge details of each including tracks
foreach (var rm in releaseToMergeReleaseMedia)
{
var existingReleaseMedia =
releaseToMergeIntoReleaseMedia.FirstOrDefault(x => x.MediaNumber == rm.MediaNumber);
var mergeTracks = DbContext.Tracks.Where(x => x.ReleaseMediaId == rm.Id).ToArray();
if (existingReleaseMedia == null)
{
releaseToMergeIntoLastMediaNumber++;
// Doesnt exist in release being merged to add
rm.ReleaseId = releaseToMergeInto.Id;
rm.MediaNumber = releaseToMergeIntoLastMediaNumber;
rm.LastUpdated = now;
releaseToMergeInto.MediaCount++;
releaseToMergeInto.TrackCount += rm.TrackCount;
mergedTracksToMove.AddRange(mergeTracks);
}
else
{
// ReleaseMedia Does exist merge tracks and details
var mergeIntoTracks = DbContext.Tracks
.Where(x => x.ReleaseMediaId == existingReleaseMedia.Id).ToArray();
foreach (var mergeTrack in mergeTracks)
{
var existingTrack =
mergeIntoTracks.FirstOrDefault(x => x.TrackNumber == mergeTrack.TrackNumber);
if (existingTrack == null)
{
// Track does not exist, update to existing ReleaseMedia and update ReleaseToMergeInfo counts
mergeTrack.LastUpdated = now;
mergeTrack.ReleaseMediaId = existingReleaseMedia.Id;
existingReleaseMedia.TrackCount++;
existingReleaseMedia.LastUpdated = now;
releaseToMergeInto.TrackCount++;
mergedTracksToMove.Add(mergeTrack);
}
else
{
// Track does exist merge two tracks together
existingTrack.MusicBrainzId =
existingTrack.MusicBrainzId ?? mergeTrack.MusicBrainzId;
existingTrack.SpotifyId = existingTrack.SpotifyId ?? mergeTrack.SpotifyId;
existingTrack.AmgId = existingTrack.AmgId ?? mergeTrack.AmgId;
existingTrack.ISRC = existingTrack.ISRC ?? mergeTrack.ISRC;
existingTrack.AmgId = existingTrack.AmgId ?? mergeTrack.AmgId;
existingTrack.LastFMId = existingTrack.LastFMId ?? mergeTrack.LastFMId;
existingTrack.PartTitles = existingTrack.PartTitles ?? mergeTrack.PartTitles;
existingTrack.PlayedCount =
(existingTrack.PlayedCount ?? 0) + (mergeTrack.PlayedCount ?? 0);
if (mergeTrack.LastPlayed.HasValue && existingTrack.LastPlayed.HasValue &&
mergeTrack.LastPlayed > existingTrack.LastPlayed)
existingTrack.LastPlayed = mergeTrack.LastPlayed;
existingTrack.Thumbnail = existingTrack.Thumbnail ?? mergeTrack.Thumbnail;
existingTrack.MusicBrainzId =
existingTrack.MusicBrainzId ?? mergeTrack.MusicBrainzId;
existingTrack.Tags =
existingTrack.Tags.AddToDelimitedList(mergeTrack.Tags.ToListFromDelimited());
if (!mergeTrack.Title.Equals(existingTrack.Title,
StringComparison.OrdinalIgnoreCase))
existingTrack.AlternateNames =
existingTrack.AlternateNames.AddToDelimitedList(new[]
{mergeTrack.Title, mergeTrack.Title.ToAlphanumericName()});
existingTrack.AlternateNames =
existingTrack.AlternateNames.AddToDelimitedList(mergeTrack.AlternateNames
.ToListFromDelimited());
existingTrack.LastUpdated = now;
var mergedTrackFileName =
mergeTrack.PathToTrack(Configuration, Configuration.LibraryFolder);
var trackFileName =
existingTrack.PathToTrack(Configuration, Configuration.LibraryFolder);
if (!trackFileName.Equals(mergedTrackFileName, StringComparison.Ordinal) &&
File.Exists(trackFileName)) mergedFilesToDelete.Add(mergedTrackFileName);
}
}
}
}
var destinationRoot = Configuration.LibraryFolder;
var releaseToMergeFolder =
releaseToMerge.ReleaseFileFolder(
releaseToMerge.Artist.ArtistFileFolder(Configuration, destinationRoot));
var releaseToMergeIntoArtistFolder =
releaseToMergeInto.Artist.ArtistFileFolder(Configuration, destinationRoot);
var releaseToMergeIntoDirectory =
new DirectoryInfo(releaseToMergeInto.ReleaseFileFolder(releaseToMergeIntoArtistFolder));
// Move tracks for releaseToMergeInto into correct folders
if (mergedTracksToMove.Any())
foreach (var track in mergedTracksToMove)
{
var oldTrackPath = track.PathToTrack(Configuration, Configuration.LibraryFolder);
var newTrackPath = FolderPathHelper.TrackFullPath(Configuration, releaseToMerge.Artist,
releaseToMerge, track);
var trackFile = new FileInfo(oldTrackPath);
if (!newTrackPath.ToLower().Equals(oldTrackPath.ToLower()))
{
var audioMetaData = await AudioMetaDataHelper.GetInfo(trackFile);
track.FilePath = FolderPathHelper.TrackPath(Configuration, releaseToMergeInto.Artist,
releaseToMergeInto, track);
track.Hash = HashHelper.CreateMD5(
releaseToMergeInto.ArtistId + trackFile.LastWriteTimeUtc.GetHashCode().ToString() +
audioMetaData.GetHashCode());
track.LastUpdated = now;
File.Move(oldTrackPath, newTrackPath);
}
}
// Cleanup folders
FolderProcessor.DeleteEmptyFolders(new DirectoryInfo(releaseToMergeIntoArtistFolder), Logger);
// Now Merge release details
releaseToMergeInto.AlternateNames = releaseToMergeInto.AlternateNames.AddToDelimitedList(new[]
{releaseToMerge.Title, releaseToMerge.Title.ToAlphanumericName()});
releaseToMergeInto.AlternateNames =
releaseToMergeInto.AlternateNames.AddToDelimitedList(releaseToMerge.AlternateNames
.ToListFromDelimited());
releaseToMergeInto.Tags =
releaseToMergeInto.Tags.AddToDelimitedList(releaseToMerge.Tags.ToListFromDelimited());
releaseToMergeInto.URLs.AddToDelimitedList(releaseToMerge.URLs.ToListFromDelimited());
releaseToMergeInto.MusicBrainzId = releaseToMergeInto.MusicBrainzId ?? releaseToMerge.MusicBrainzId;
releaseToMergeInto.Profile = releaseToMergeInto.Profile ?? releaseToMerge.Profile;
releaseToMergeInto.ReleaseDate = releaseToMergeInto.ReleaseDate ?? releaseToMerge.ReleaseDate;
releaseToMergeInto.MusicBrainzId = releaseToMergeInto.MusicBrainzId ?? releaseToMerge.MusicBrainzId;
releaseToMergeInto.DiscogsId = releaseToMergeInto.DiscogsId ?? releaseToMerge.DiscogsId;
releaseToMergeInto.ITunesId = releaseToMergeInto.ITunesId ?? releaseToMerge.ITunesId;
releaseToMergeInto.AmgId = releaseToMergeInto.AmgId ?? releaseToMerge.AmgId;
releaseToMergeInto.LastFMId = releaseToMergeInto.LastFMId ?? releaseToMerge.LastFMId;
releaseToMergeInto.LastFMSummary = releaseToMergeInto.LastFMSummary ?? releaseToMerge.LastFMSummary;
releaseToMergeInto.SpotifyId = releaseToMergeInto.SpotifyId ?? releaseToMerge.SpotifyId;
releaseToMergeInto.Thumbnail = releaseToMergeInto.Thumbnail ?? releaseToMerge.Thumbnail;
if (releaseToMergeInto.ReleaseType == ReleaseType.Unknown &&
releaseToMerge.ReleaseType != ReleaseType.Unknown)
releaseToMergeInto.ReleaseType = releaseToMerge.ReleaseType;
releaseToMergeInto.LastUpdated = now;
await DbContext.SaveChangesAsync();
// Update any collection pointers for release to be merged
var collectionRecords = DbContext.CollectionReleases.Where(x => x.ReleaseId == releaseToMerge.Id);
if (collectionRecords != null && collectionRecords.Any())
{
foreach (var cr in collectionRecords)
{
cr.ReleaseId = releaseToMergeInto.Id;
cr.LastUpdated = now;
}
await DbContext.SaveChangesAsync();
}
// Update any existing playlist for release to be merged
var playListTrackInfos = (from pl in DbContext.PlaylistTracks
join t in DbContext.Tracks on pl.TrackId equals t.Id
join rm in DbContext.ReleaseMedias on t.ReleaseMediaId equals rm.Id
where rm.ReleaseId == releaseToMerge.Id
select new
{
track = t,
rm,
pl
}).ToArray();
if (playListTrackInfos != null && playListTrackInfos.Any())
{
foreach (var playListTrackInfo in playListTrackInfos)
{
var matchingTrack = (from t in DbContext.Tracks
join rm in DbContext.ReleaseMedias on t.ReleaseMediaId equals rm.Id
where rm.ReleaseId == releaseToMergeInto.Id
where rm.MediaNumber == playListTrackInfo.rm.MediaNumber
where t.TrackNumber == playListTrackInfo.track.TrackNumber
select t).FirstOrDefault();
if (matchingTrack != null)
{
playListTrackInfo.pl.TrackId = matchingTrack.Id;
playListTrackInfo.pl.LastUpdated = now;
}
}
await DbContext.SaveChangesAsync();
}
await Delete(releaseToMerge);
// Delete any files flagged to be deleted (duplicate as track already exists on merged to release)
if (mergedFilesToDelete.Any())
foreach (var mergedFileToDelete in mergedFilesToDelete)
try
{
if (File.Exists(mergedFileToDelete))
{
File.Delete(mergedFileToDelete);
Logger.LogWarning("x Deleted Merged File [{0}]", mergedFileToDelete);
}
}
catch
{
}
// Clear cache regions for manipulated records
CacheManager.ClearRegion(releaseToMergeInto.CacheRegion);
if (releaseToMergeInto.Artist != null) CacheManager.ClearRegion(releaseToMergeInto.Artist.CacheRegion);
if (releaseToMerge.Artist != null) CacheManager.ClearRegion(releaseToMerge.Artist.CacheRegion);
sw.Stop();
result = true;
}
catch (Exception ex)
{
Logger.LogError(ex,
$"MergeReleases ReleaseToMerge `{releaseToMerge}`, ReleaseToMergeInto `{releaseToMergeInto}`, addAsMedia [{addAsMedia}]");
resultErrors.Add(ex);
}
return new OperationResult<bool>
{
Data = result,
IsSuccess = result,
Errors = resultErrors,
OperationTime = sw.ElapsedMilliseconds
};
}
/// <summary>
/// For the given ReleaseId, Scan folder adding new, removing not found and updating DB tracks for tracks found
/// </summary>
public async Task<OperationResult<bool>> ScanReleaseFolder(Guid releaseId, string destinationFolder,
bool doJustInfo, Release releaseToScan = null)
{
SimpleContract.Requires<ArgumentOutOfRangeException>(
releaseId != Guid.Empty && releaseToScan == null || releaseToScan != null, "Invalid ReleaseId");
_addedTrackIds.Clear();
var result = false;
var resultErrors = new List<Exception>();
var sw = new Stopwatch();
sw.Start();
var modifiedRelease = false;
string releasePath = null;
try
{
var release = releaseToScan ?? DbContext.Releases
.Include(x => x.Artist)
.Include(x => x.Labels)
.FirstOrDefault(x => x.RoadieId == releaseId);
if (release == null)
{
Logger.LogCritical("Unable To Find Release [{0}]", releaseId);
return new OperationResult<bool>();
}
// This is recorded from metadata and if set then used to gauage if the release is complete
short? totalTrackCount = null;
short totalMissingCount = 0;
releasePath =
release.ReleaseFileFolder(release.Artist.ArtistFileFolder(Configuration, destinationFolder));
var releaseDirectory = new DirectoryInfo(releasePath);
if (!Directory.Exists(releasePath))
Logger.LogWarning("Unable To Find Release Folder [{0}] For Release `{1}`", releasePath,
release.ToString());
var now = DateTime.UtcNow;
#region Get Tracks for Release from DB and set as missing any not found in Folder
foreach (var releaseMedia in DbContext.ReleaseMedias.Where(x => x.ReleaseId == release.Id).ToArray())
{
var foundMissingTracks = false;
foreach (var existingTrack in DbContext.Tracks.Where(x => x.ReleaseMediaId == releaseMedia.Id)
.ToArray())
{
var trackPath = existingTrack.PathToTrack(Configuration, destinationFolder);
if (!File.Exists(trackPath))
{
Logger.LogWarning("Track `{0}`, File [{1}] Not Found.", existingTrack.ToString(),
trackPath);
if (!doJustInfo)
{
existingTrack.UpdateTrackMissingFile(now);
foundMissingTracks = true;
modifiedRelease = true;
totalMissingCount++;
}
}
}
if (foundMissingTracks) await DbContext.SaveChangesAsync();
}
#endregion Get Tracks for Release from DB and set as missing any not found in Folder
#region Scan Folder and Add or Update Existing Tracks from Files
var existingReleaseMedia = DbContext.ReleaseMedias.Include(x => x.Tracks)
.Where(x => x.ReleaseId == release.Id).ToList();
var foundInFolderTracks = new List<Track>();
short totalNumberOfTracksFound = 0;
// This is the number of tracks metadata says the release should have (releaseMediaNumber, TotalNumberOfTracks)
var releaseMediaTotalNumberOfTracks = new Dictionary<short, short?>();
var releaseMediaTracksFound = new Dictionary<int, short>();
if (Directory.Exists(releasePath))
foreach (var file in releaseDirectory.GetFiles("*.mp3", SearchOption.AllDirectories))
{
int? trackArtistId = null;
string partTitles = null;
var audioMetaData = await AudioMetaDataHelper.GetInfo(file, doJustInfo);
// This is the path for the new track not in the database but the found MP3 file to be added to library
var trackPath = Path.Combine(releaseDirectory.Parent.Name, releaseDirectory.Name);
if (audioMetaData.IsValid)
{
var trackHash = HashHelper.CreateMD5(
release.ArtistId + file.LastWriteTimeUtc.GetHashCode().ToString() +
audioMetaData.GetHashCode());
totalNumberOfTracksFound++;
totalTrackCount = totalTrackCount ?? (short)(audioMetaData.TotalTrackNumbers ?? 0);
var releaseMediaNumber = (short)(audioMetaData.Disc ?? 1);
if (!releaseMediaTotalNumberOfTracks.ContainsKey(releaseMediaNumber))
releaseMediaTotalNumberOfTracks.Add(releaseMediaNumber,
(short)(audioMetaData.TotalTrackNumbers ?? 0));
else
releaseMediaTotalNumberOfTracks[releaseMediaNumber] =
releaseMediaTotalNumberOfTracks[releaseMediaNumber]
.TakeLarger((short)(audioMetaData.TotalTrackNumbers ?? 0));
var releaseMedia =
existingReleaseMedia.FirstOrDefault(x => x.MediaNumber == releaseMediaNumber);
if (releaseMedia == null)
{
// New ReleaseMedia - Not Found In Database
releaseMedia = new ReleaseMedia
{
ReleaseId = release.Id,
Status = Statuses.Incomplete,
MediaNumber = releaseMediaNumber
};
DbContext.ReleaseMedias.Add(releaseMedia);
await DbContext.SaveChangesAsync();
existingReleaseMedia.Add(releaseMedia);
modifiedRelease = true;
}
else
{
// Existing ReleaseMedia Found
releaseMedia.LastUpdated = now;
}
var track = releaseMedia.Tracks.FirstOrDefault(x =>
x.TrackNumber == audioMetaData.TrackNumber);
if (track == null)
{
// New Track - Not Found In Database
track = new Track
{
Status = Statuses.New,
FilePath = trackPath,
FileName = file.Name,
FileSize = (int)file.Length,
Hash = trackHash,
MusicBrainzId = audioMetaData.MusicBrainzId,
AmgId = audioMetaData.AmgId,
SpotifyId = audioMetaData.SpotifyId,
Title = audioMetaData.Title,
TrackNumber = audioMetaData.TrackNumber ?? totalNumberOfTracksFound,
Duration = audioMetaData.Time != null
? (int)audioMetaData.Time.Value.TotalMilliseconds
: 0,
ReleaseMediaId = releaseMedia.Id,
ISRC = audioMetaData.ISRC,
LastFMId = audioMetaData.LastFmId
};
if (audioMetaData.TrackArtist != null)
{
if (audioMetaData.TrackArtists.Count() == 1)
{
var trackArtistData =
await ArtistLookupEngine.GetByName(
new AudioMetaData { Artist = audioMetaData.TrackArtist }, true);
if (trackArtistData.IsSuccess && release.ArtistId != trackArtistData.Data.Id)
trackArtistId = trackArtistData.Data.Id;
}
else if (audioMetaData.TrackArtists.Any())
{
partTitles = string.Join(AudioMetaData.ArtistSplitCharacter.ToString(),
audioMetaData.TrackArtists);
}
else
{
partTitles = audioMetaData.TrackArtist;
}
}
var alt = track.Title.ToAlphanumericName();
track.AlternateNames =
!alt.Equals(audioMetaData.Title, StringComparison.OrdinalIgnoreCase)
? track.AlternateNames.AddToDelimitedList(new[] { alt })
: null;
track.ArtistId = trackArtistId;
track.PartTitles = partTitles;
DbContext.Tracks.Add(track);
await DbContext.SaveChangesAsync();
_addedTrackIds.Add(track.Id);
modifiedRelease = true;
}
else if (string.IsNullOrEmpty(track.Hash) || trackHash != track.Hash)
{
if (audioMetaData.TrackArtist != null)
{
if (audioMetaData.TrackArtists.Count() == 1)
{
var trackArtistData =
await ArtistLookupEngine.GetByName(
new AudioMetaData { Artist = audioMetaData.TrackArtist }, true);
if (trackArtistData.IsSuccess && release.ArtistId != trackArtistData.Data.Id)
trackArtistId = trackArtistData.Data.Id;
}
else if (audioMetaData.TrackArtists.Any())
{
partTitles = string.Join(AudioMetaData.ArtistSplitCharacter.ToString(),
audioMetaData.TrackArtists);
}
else
{
partTitles = audioMetaData.TrackArtist;
}
}
track.Title = audioMetaData.Title;
track.Duration = audioMetaData.Time != null
? (int)audioMetaData.Time.Value.TotalMilliseconds
: 0;
track.TrackNumber = audioMetaData.TrackNumber ?? totalNumberOfTracksFound;
track.ArtistId = trackArtistId;
track.PartTitles = partTitles;
track.Hash = trackHash;
track.FileName = file.Name;
track.FileSize = (int)file.Length;
track.FilePath = trackPath;
track.Status = Statuses.Ok;
track.LastUpdated = now;
var alt = track.Title.ToAlphanumericName();
if (!alt.Equals(track.Title, StringComparison.OrdinalIgnoreCase))
track.AlternateNames = track.AlternateNames.AddToDelimitedList(new[] { alt });
track.TrackNumber = audioMetaData.TrackNumber ?? -1;
track.LastUpdated = now;
modifiedRelease = true;
}
else if (track.Status != Statuses.Ok)
{
track.Status = Statuses.Ok;
track.LastUpdated = now;
modifiedRelease = true;
}
foundInFolderTracks.Add(track);
if (releaseMediaTracksFound.ContainsKey(releaseMedia.Id))
releaseMediaTracksFound[releaseMedia.Id]++;
else
releaseMediaTracksFound[releaseMedia.Id] = 1;
}
else
{
Logger.LogWarning("Release Track File Has Invalid MetaData `{0}`",
audioMetaData.ToString());
}
}
else
Logger.LogWarning("Unable To Find Releaes Path [{0}] For Release `{1}`", releasePath,
release.ToString());
var releaseMediaNumbersFound = new List<short?>();
foreach (var kp in releaseMediaTracksFound)
{
var releaseMedia = DbContext.ReleaseMedias.FirstOrDefault(x => x.Id == kp.Key);
if (releaseMedia != null)
{
if (!releaseMediaNumbersFound.Any(x => x == releaseMedia.MediaNumber))
releaseMediaNumbersFound.Add(releaseMedia.MediaNumber);
var releaseMediaFoundInFolderTrackNumbers = foundInFolderTracks
.Where(x => x.ReleaseMediaId == releaseMedia.Id).Select(x => x.TrackNumber).OrderBy(x => x)
.ToArray();
var areTracksForRelaseMediaSequential = releaseMediaFoundInFolderTrackNumbers
.Zip(releaseMediaFoundInFolderTrackNumbers.Skip(1), (a, b) => a + 1 == b).All(x => x);
if (!areTracksForRelaseMediaSequential)
Logger.LogDebug("ReleaseMedia [{0}] Track Numbers Are Not Sequential", releaseMedia.Id);
releaseMedia.TrackCount = kp.Value;
releaseMedia.LastUpdated = now;
releaseMedia.Status = areTracksForRelaseMediaSequential ? Statuses.Ok : Statuses.Incomplete;
await DbContext.SaveChangesAsync();
modifiedRelease = true;
}
;
}
var foundInFolderTrackNumbers =
foundInFolderTracks.Select(x => x.TrackNumber).OrderBy(x => x).ToArray();
if (modifiedRelease || !foundInFolderTrackNumbers.Count().Equals(release.TrackCount) ||
releaseMediaNumbersFound.Count() != (release.MediaCount ?? 0))
{
var areTracksForRelaseSequential = foundInFolderTrackNumbers
.Zip(foundInFolderTrackNumbers.Skip(1), (a, b) => a + 1 == b).All(x => x);
var maxFoundInFolderTrackNumbers =
foundInFolderTrackNumbers.Any() ? foundInFolderTrackNumbers.Max() : (short)0;
release.Status = areTracksForRelaseSequential ? Statuses.Ok : Statuses.Incomplete;
release.TrackCount = (short)foundInFolderTrackNumbers.Count();
release.MediaCount = (short)releaseMediaNumbersFound.Count();
if (release.TrackCount < maxFoundInFolderTrackNumbers)
release.TrackCount = maxFoundInFolderTrackNumbers;
release.LibraryStatus = release.TrackCount > 0 && release.TrackCount == totalNumberOfTracksFound
? LibraryStatus.Complete
: LibraryStatus.Incomplete;
release.LastUpdated = now;
release.Status = release.LibraryStatus == LibraryStatus.Complete
? Statuses.Complete
: Statuses.Incomplete;
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(release.Artist.CacheRegion);
CacheManager.ClearRegion(release.CacheRegion);
}
#endregion Scan Folder and Add or Update Existing Tracks from Files
if (release.Thumbnail == null)
{
var imageFiles = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(releasePath),
ImageType.Release, SearchOption.TopDirectoryOnly);
if (imageFiles != null && imageFiles.Any())
{
// Read image and convert to jpeg
var i = imageFiles.First();
release.Thumbnail = File.ReadAllBytes(i.FullName);
release.Thumbnail = ImageHelper.ResizeImage(release.Thumbnail,
Configuration.MediumImageSize.Width, Configuration.MediumImageSize.Height);
release.Thumbnail = ImageHelper.ConvertToJpegFormat(release.Thumbnail);
if (release.Thumbnail.Length >= ImageHelper.MaximumThumbnailByteSize)
{
Logger.LogWarning(
$"Release Thumbnail larger than maximum size after resizing to [{Configuration.ThumbnailImageSize.Width}x{Configuration.ThumbnailImageSize.Height}] Thumbnail Size [{release.Thumbnail.Length}]");
release.Thumbnail = null;
}
release.LastUpdated = now;
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(release.Artist.CacheRegion);
CacheManager.ClearRegion(release.CacheRegion);
Logger.LogInformation("Update Thumbnail using Release Cover File [{0}]", i.Name);
}
}
sw.Stop();
await UpdateReleaseCounts(release.Id, now);
await UpdateArtistCountsForRelease(release.Id, now);
if (release.Labels != null && release.Labels.Any())
foreach (var label in release.Labels)
await UpdateLabelCounts(label.Id, now);
Logger.LogInformation("Scanned Release `{0}` Folder [{1}], Modified Release [{2}], OperationTime [{3}]",
release.ToString(), releasePath, modifiedRelease, sw.ElapsedMilliseconds);
result = true;
}
catch (Exception ex)
{
Logger.LogError(ex, "ReleasePath [" + releasePath + "] " + ex.Serialize());
resultErrors.Add(ex);
}
return new OperationResult<bool>
{
Data = result,
IsSuccess = result,
Errors = resultErrors,
OperationTime = sw.ElapsedMilliseconds
};
}
[Obsolete]
public async Task<OperationResult<Release>> Update(Release release, IEnumerable<Image> releaseImages,
string originalReleaseFolder, string destinationFolder = null)
{
SimpleContract.Requires<ArgumentNullException>(release != null, "Invalid Release");
var sw = new Stopwatch();
sw.Start();
var artistFolder =
release.Artist.ArtistFileFolder(Configuration, destinationFolder ?? Configuration.LibraryFolder);
var releaseGenreTables = release.Genres
.Select(x => new ReleaseGenre { ReleaseId = release.Id, GenreId = x.GenreId }).ToList();
var releaseLabels = release.Labels.Select(x => new ReleaseLabel
{
CatalogNumber = x.CatalogNumber,
BeginDate = x.BeginDate,
EndDate = x.EndDate,
ReleaseId = release.Id,
LabelId = x.Label != null && x.Label.Id > 0 ? x.Label.Id : x.LabelId,
Status = x.Status,
RoadieId = x.RoadieId
}).ToList();
var result = true;
var now = DateTime.UtcNow;
release.LastUpdated = now;
release.Labels = null;
await CheckAndChangeReleaseTitle(release, originalReleaseFolder);
await DbContext.SaveChangesAsync();
DbContext.ReleaseGenres.RemoveRange(from at in DbContext.ReleaseGenres
where at.ReleaseId == release.Id
select at);
release.Genres = releaseGenreTables;
var existingReleaseLabelIds = (from rl in releaseLabels
where rl.Status != Statuses.New
select rl.RoadieId).ToArray();
DbContext.ReleaseLabels.RemoveRange(from rl in DbContext.ReleaseLabels
where rl.ReleaseId == release.Id
where !(from x in existingReleaseLabelIds select x).Contains(rl.RoadieId)
select rl);
release.Labels = releaseLabels;
await DbContext.SaveChangesAsync();
if (releaseImages != null)
{
var existingImageIds = (from ai in releaseImages
where ai.Status != Statuses.New
select ai.RoadieId).ToArray();
DbContext.Images.RemoveRange(from i in DbContext.Images
where i.ReleaseId == release.Id
where !(from x in existingImageIds select x).Contains(i.RoadieId)
select i);
await DbContext.SaveChangesAsync();
if (releaseImages.Any(x => x.Status == Statuses.New))
{
foreach (var releaseImage in releaseImages.Where(x => x.Status == Statuses.New))
DbContext.Images.Add(releaseImage);
try
{
await DbContext.SaveChangesAsync();
}
catch (Exception ex)
{
Logger.LogError(ex);
}
}
}
CacheManager.ClearRegion(release.CacheRegion);
if (release.Artist != null) CacheManager.ClearRegion(release.Artist.CacheRegion);
sw.Stop();
return new OperationResult<Release>
{
Data = release,
IsSuccess = result,
OperationTime = sw.ElapsedMilliseconds
};
}
}
}

View file

@ -6,7 +6,6 @@ using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Enums;
using Roadie.Library.Extensions;
using Roadie.Library.Factories;
using Roadie.Library.Imaging;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.Utility;
@ -28,35 +27,22 @@ namespace Roadie.Library.FilePlugins
public override string[] HandlesTypes => new string[1] { "audio/mpeg" };
public Audio(IRoadieSettings configuration,
IHttpEncoder httpEncoder,
IArtistFactory artistFactory,
IReleaseFactory releaseFactory,
IImageFactory imageFactory,
ICacheManager cacheManager,
ILogger logger,
IArtistLookupEngine artistLookupEngine,
IReleaseLookupEngine releaseLookupEngine,
IAudioMetaDataHelper audioMetaDataHelper)
: base(configuration, httpEncoder, artistFactory, releaseFactory, imageFactory, cacheManager, logger,
artistLookupEngine, releaseLookupEngine)
public Audio(IRoadieSettings configuration, IHttpEncoder httpEncoder, ICacheManager cacheManager,
ILogger logger, IArtistLookupEngine artistLookupEngine, IReleaseLookupEngine releaseLookupEngine,
IAudioMetaDataHelper audioMetaDataHelper)
: base(configuration, httpEncoder, cacheManager, logger, artistLookupEngine, releaseLookupEngine)
{
AudioMetaDataHelper = audioMetaDataHelper;
}
public override async Task<OperationResult<bool>> Process(string destinationRoot, FileInfo fileInfo,
bool doJustInfo, int? submissionId)
public override async Task<OperationResult<bool>> Process(FileInfo fileInfo, bool doJustInfo, int? submissionId)
{
Logger.LogTrace(
">> Audio: Process destinationRoot [{0}], FileInfo [{1}], doJustInfo [{2}], submissionId [{3}]",
destinationRoot, fileInfo, doJustInfo, submissionId);
Logger.LogTrace($">> Audio: Process FileInfo [{fileInfo}], doJustInfo [{doJustInfo}], submissionId [{submissionId}]", fileInfo, doJustInfo, submissionId);
var sw = Stopwatch.StartNew();
var result = new OperationResult<bool>();
try
{
var dr = destinationRoot ?? fileInfo.DirectoryName;
string destinationName = null;
var metaData = await AudioMetaDataHelper.GetInfo(fileInfo, doJustInfo);
@ -89,7 +75,7 @@ namespace Roadie.Library.FilePlugins
SimpleContract.Requires<ArgumentException>(year > 0, string.Format("Invalid Track Year [{0}]", year));
SimpleContract.Requires<ArgumentException>(trackNumber > 0, "Missing Track Number");
var artistFolder = await DetermineArtistFolder(dr, metaData, doJustInfo);
var artistFolder = await DetermineArtistFolder(metaData, doJustInfo);
if (string.IsNullOrEmpty(artistFolder))
{
Logger.LogWarning("Unable To Find ArtistFolder [{0}] For MetaData [{1}]", artistFolder,
@ -104,8 +90,7 @@ namespace Roadie.Library.FilePlugins
return new OperationResult<bool>("Unable To Find Release Folder");
}
destinationName =
FolderPathHelper.TrackFullPath(Configuration, metaData, dr, artistFolder, releaseFolder);
destinationName = FolderPathHelper.TrackFullPath(Configuration, metaData, Configuration.LibraryFolder, artistFolder, releaseFolder);
Logger.LogTrace(
"Info: FileInfo [{0}], Artist Folder [{1}], Release Folder [{1}], Destination Name [{3}]",
fileInfo.FullName, artistFolder, releaseFolder, destinationName);
@ -318,14 +303,14 @@ namespace Roadie.Library.FilePlugins
return result;
}
private async Task<string> DetermineArtistFolder(string destinationRoot, AudioMetaData metaData,
private async Task<string> DetermineArtistFolder(AudioMetaData metaData,
bool doJustInfo)
{
var artist = await ArtistLookupEngine.GetByName(metaData, !doJustInfo);
if (!artist.IsSuccess) return null;
try
{
return artist.Data.ArtistFileFolder(Configuration, destinationRoot);
return artist.Data.ArtistFileFolder(Configuration);
}
catch (Exception ex)
{

View file

@ -7,6 +7,6 @@ namespace Roadie.Library.FilePlugins
{
string[] HandlesTypes { get; }
Task<OperationResult<bool>> Process(string destinationRoot, FileInfo file, bool doJustInfo, int? submissionId);
Task<OperationResult<bool>> Process(FileInfo file, bool doJustInfo, int? submissionId);
}
}

View file

@ -3,7 +3,6 @@ using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Factories;
using Roadie.Library.Utility;
using System;
using System.IO;
@ -17,8 +16,6 @@ namespace Roadie.Library.FilePlugins
public int MinWeightToDelete => Configuration.FilePlugins.MinWeightToDelete;
protected IArtistFactory ArtistFactory { get; }
protected IArtistLookupEngine ArtistLookupEngine { get; }
protected ICacheManager CacheManager { get; }
@ -27,23 +24,16 @@ namespace Roadie.Library.FilePlugins
protected IHttpEncoder HttpEncoder { get; }
protected IImageFactory ImageFactory { get; }
protected ILogger Logger { get; }
protected IReleaseFactory ReleaseFactory { get; }
protected IReleaseLookupEngine ReleaseLookupEngine { get; }
public PluginBase(IRoadieSettings configuration, IHttpEncoder httpEncoder, IArtistFactory artistFactory,
IReleaseFactory releaseFactory, IImageFactory imageFactory, ICacheManager cacheManager, ILogger logger,
public PluginBase(IRoadieSettings configuration, IHttpEncoder httpEncoder, ICacheManager cacheManager, ILogger logger,
IArtistLookupEngine artistLookupEngine, IReleaseLookupEngine releaseLookupEngine)
{
Configuration = configuration;
HttpEncoder = httpEncoder;
ArtistFactory = artistFactory;
ReleaseFactory = releaseFactory;
ImageFactory = imageFactory;
CacheManager = cacheManager;
Logger = logger;
ArtistLookupEngine = artistLookupEngine;
@ -68,8 +58,7 @@ namespace Roadie.Library.FilePlugins
return false;
}
public abstract Task<OperationResult<bool>> Process(string destinationRoot, FileInfo fileInfo, bool doJustInfo,
int? submissionId);
public abstract Task<OperationResult<bool>> Process(FileInfo fileInfo, bool doJustInfo, int? submissionId);
protected virtual bool IsFileLocked(FileInfo file)
{

View file

@ -1,5 +1,7 @@
using Roadie.Library.Enums;
using Roadie.Library.Configuration;
using Roadie.Library.Enums;
using Roadie.Library.Extensions;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.SearchEngines.Imaging;
using Roadie.Library.Utility;
using SixLabors.ImageSharp;
@ -219,5 +221,86 @@ namespace Roadie.Library.Imaging
return null;
}
/// <summary>
/// Get image data from all sources for either fileanme or MetaData
/// </summary>
/// <param name="filename">Name of the File (ie a CUE file)</param>
/// <param name="metaData">Populated MetaData</param>
/// <returns></returns>
public static AudioMetaDataImage GetPictureForMetaData(IRoadieSettings configuration, string filename, AudioMetaData metaData)
{
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(filename), "Invalid Filename");
SimpleContract.Requires<ArgumentException>(metaData != null, "Invalid MetaData");
return ImageForFilename(configuration, filename);
}
/// <summary>
/// Does image exist with the same filename
/// </summary>
/// <param name="filename">Name of the File (ie a CUE file)</param>
/// <returns>Null if not found else populated image</returns>
public static AudioMetaDataImage ImageForFilename(IRoadieSettings configuration, string filename)
{
AudioMetaDataImage imageMetaData = null;
if (string.IsNullOrEmpty(filename)) return imageMetaData;
try
{
var fileInfo = new FileInfo(filename);
var ReleaseCover = Path.ChangeExtension(filename, "jpg");
if (File.Exists(ReleaseCover))
{
using (var processor = new ImageProcessor(configuration))
{
imageMetaData = new AudioMetaDataImage
{
Data = processor.Process(File.ReadAllBytes(ReleaseCover)),
Type = AudioMetaDataImageType.FrontCover,
MimeType = Library.Processors.FileProcessor.DetermineFileType(fileInfo)
};
}
}
else
{
// Is there a picture in filename folder (for the Release)
var pictures = fileInfo.Directory.GetFiles("*.jpg");
var tagImages = new List<AudioMetaDataImage>();
if (pictures != null && pictures.Any())
{
FileInfo picture = null;
// See if there is a "cover" or "front" jpg file if so use it
picture = pictures.FirstOrDefault(x =>
x.Name.Equals("cover", StringComparison.OrdinalIgnoreCase));
if (picture == null)
picture = pictures.FirstOrDefault(x =>
x.Name.Equals("front", StringComparison.OrdinalIgnoreCase));
if (picture == null) picture = pictures.First();
if (picture != null)
using (var processor = new ImageProcessor(configuration))
{
imageMetaData = new AudioMetaDataImage
{
Data = processor.Process(File.ReadAllBytes(picture.FullName)),
Type = AudioMetaDataImageType.FrontCover,
MimeType = Library.Processors.FileProcessor.DetermineFileType(picture)
};
}
}
}
}
catch (FileNotFoundException)
{
}
catch (Exception ex)
{
Trace.WriteLine(ex, ex.Serialize());
}
return imageMetaData;
}
}
}

View file

@ -120,7 +120,7 @@ namespace Roadie.Library.Inspect
{
Console.WriteLine("Roadie Media Inspector");
MessageLogger = new EventMessageLogger();
MessageLogger = new EventMessageLogger<Inspector>();
MessageLogger.Messages += MessageLogger_Messages;
var settings = new RoadieSettings();
@ -131,18 +131,15 @@ namespace Roadie.Library.Inspect
settings.ConnectionString = configuration.GetConnectionString("RoadieDatabaseConnection");
Configuration = settings;
CacheManager = new DictionaryCacheManager(Logger, new CachePolicy(TimeSpan.FromHours(4)));
TagsHelper = new ID3TagsHelper(Configuration, CacheManager, Logger);
var tagHelperLooper = new EventMessageLogger<ID3TagsHelper>();
tagHelperLooper.Messages += MessageLogger_Messages;
TagsHelper = new ID3TagsHelper(Configuration, CacheManager, tagHelperLooper);
}
public static string ArtistInspectorToken(AudioMetaData metaData)
{
return ToToken(metaData.Artist);
}
public static string ArtistInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist);
public static string ReleaseInspectorToken(AudioMetaData metaData)
{
return ToToken(metaData.Artist + metaData.Release);
}
public static string ReleaseInspectorToken(AudioMetaData metaData) => ToToken(metaData.Artist + metaData.Release);
public static string ToToken(string input)
{
@ -156,8 +153,7 @@ namespace Roadie.Library.Inspect
return token;
}
public void Inspect(bool doCopy, bool isReadOnly, string directoryToInspect, string destination,
bool dontAppendSubFolder, bool dontDeleteEmptyFolders)
public void Inspect(bool doCopy, bool isReadOnly, string directoryToInspect, string destination, bool dontAppendSubFolder, bool dontDeleteEmptyFolders)
{
Configuration.Inspector.IsInReadOnlyMode = isReadOnly;
Configuration.Inspector.DoCopyFiles = doCopy;
@ -570,10 +566,13 @@ namespace Roadie.Library.Inspect
}
}
private string RunScript(string scriptFilename, bool doCopy, bool isReadOnly, string directoryToInspect,
string dest)
private string RunScript(string scriptFilename, bool doCopy, bool isReadOnly, string directoryToInspect, string dest)
{
if (string.IsNullOrEmpty(scriptFilename)) return null;
if (string.IsNullOrEmpty(scriptFilename))
{
return null;
}
try
{
var script = File.ReadAllText(scriptFilename);
@ -589,7 +588,6 @@ namespace Roadie.Library.Inspect
{
Console.WriteLine($"📛 Error with Script File [{scriptFilename}], Error [{ex}] ");
}
return null;
}
}

View file

@ -37,7 +37,7 @@ namespace Roadie.Library.Inspect.Plugins.Directory
{
if (!Configuration.Inspector.IsInReadOnlyMode) file.Delete();
deletedFiles.Add(file.Name);
Console.WriteLine($" X Deleted File [{file}], Was foud in in FileExtensionsToDelete");
Console.WriteLine($" X Deleted File [{file}], Was found in in FileExtensionsToDelete");
}
result.Data = $"Deleted [{deletedFiles.Count()}] unwanted files";

View file

@ -3,7 +3,7 @@ using System;
namespace Roadie.Library.Processors
{
public class EventMessageLogger : ILogger, IEventMessageLogger
public class EventMessageLogger<T> : ILogger<T>, IEventMessageLogger
{
public event EventHandler<EventMessage> Messages;

View file

@ -6,7 +6,6 @@ using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Extensions;
using Roadie.Library.Factories;
using Roadie.Library.FilePlugins;
using Roadie.Library.MetaData.Audio;
using System;
@ -18,8 +17,32 @@ using System.Threading.Tasks;
namespace Roadie.Library.Processors
{
public sealed class FileProcessor : ProcessorBase
public sealed class FileProcessor : IFileProcessor
{
private bool DoDeleteUnknowns => Configuration.Processing.DoDeleteUnknowns;
private bool DoMoveUnknowns => Configuration.Processing.DoMoveUnknowns;
public IHttpEncoder HttpEncoder { get; }
public int? SubmissionId { get; set; }
private string UnknownFolder => Configuration.Processing.UnknownFolder;
private IArtistLookupEngine ArtistLookupEngine { get; }
private IAudioMetaDataHelper AudioMetaDataHelper { get; }
private ICacheManager CacheManager { get; }
private IRoadieSettings Configuration { get; }
private IRoadieDbContext DbContext { get; }
private ILogger Logger { get; }
private IReleaseLookupEngine ReleaseLookupEngine { get; }
private IEnumerable<IFilePlugin> _plugins;
public IEnumerable<IFilePlugin> Plugins
@ -38,8 +61,7 @@ namespace Roadie.Library.Processors
foreach (var t in types)
if (t.GetInterface("IFilePlugin") != null && !t.IsAbstract && !t.IsInterface)
{
var plugin = Activator.CreateInstance(t, Configuration, HttpEncoder, ArtistFactory,
ReleaseFactory, ImageFactory, CacheManager, Logger, ArtistLookupEngine,
var plugin = Activator.CreateInstance(t, Configuration, HttpEncoder, CacheManager, Logger, ArtistLookupEngine,
ReleaseLookupEngine, AudioMetaDataHelper) as IFilePlugin;
plugins.Add(plugin);
}
@ -56,14 +78,20 @@ namespace Roadie.Library.Processors
}
}
public FileProcessor(IRoadieSettings configuration, IHttpEncoder httpEncoder, string destinationRoot,
IRoadieDbContext context, ICacheManager cacheManager, ILogger logger,
IArtistLookupEngine artistLookupEngine, IArtistFactory artistFactory, IReleaseFactory releaseFactory,
IImageFactory imageFactory, IReleaseLookupEngine releaseLookupEngine,
IAudioMetaDataHelper audioMetaDataHelper)
: base(configuration, httpEncoder, destinationRoot, context, cacheManager, logger, artistLookupEngine,
artistFactory, releaseFactory, imageFactory, releaseLookupEngine, audioMetaDataHelper)
public FileProcessor(IRoadieSettings configuration, IHttpEncoder httpEncoder,
IRoadieDbContext context, ICacheManager cacheManager, ILogger<FileProcessor> logger,
IArtistLookupEngine artistLookupEngine, IReleaseLookupEngine releaseLookupEngine,
IAudioMetaDataHelper audioMetaDataHelper)
{
Configuration = configuration;
HttpEncoder = httpEncoder;
DbContext = context;
CacheManager = cacheManager;
Logger = logger;
ArtistLookupEngine = artistLookupEngine;
ReleaseLookupEngine = releaseLookupEngine;
AudioMetaDataHelper = audioMetaDataHelper;
}
public static string DetermineFileType(FileInfo fileinfo)
@ -98,7 +126,7 @@ namespace Roadie.Library.Processors
// See if there is a plugin
if (p.HandlesTypes.Contains(fileType))
{
pluginResult = await p.Process(DestinationRoot, fileInfo, doJustInfo, SubmissionId);
pluginResult = await p.Process(fileInfo, doJustInfo, SubmissionId);
break;
}

View file

@ -1,140 +0,0 @@
using Microsoft.Extensions.Logging;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Extensions;
using Roadie.Library.Factories;
using Roadie.Library.FilePlugins;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Roadie.Library.Processors
{
public sealed class FolderProcessor : ProcessorBase
{
public int? ProcessLimit { get; set; }
private FileProcessor FileProcessor { get; }
public FolderProcessor(IRoadieSettings configuration, IHttpEncoder httpEncoder, string destinationRoot,
IRoadieDbContext context, ICacheManager cacheManager, ILogger logger,
IArtistLookupEngine artistLookupEngine, IArtistFactory artistFactory, IReleaseFactory releaseFactory,
IImageFactory imageFactory, IReleaseLookupEngine releaseLookupEngine,
IAudioMetaDataHelper audioMetaDataHelper)
: base(configuration, httpEncoder, destinationRoot, context, cacheManager, logger, artistLookupEngine,
artistFactory, releaseFactory, imageFactory, releaseLookupEngine, audioMetaDataHelper)
{
SimpleContract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(destinationRoot),
"Invalid Destination Folder");
FileProcessor = new FileProcessor(configuration, httpEncoder, destinationRoot, context, cacheManager,
logger, artistLookupEngine, artistFactory, releaseFactory, imageFactory, releaseLookupEngine,
audioMetaDataHelper);
}
public static OperationResult<bool> DeleteEmptyFolders(DirectoryInfo processingFolder, ILogger logger)
{
var result = new OperationResult<bool>();
try
{
result.IsSuccess = FolderPathHelper.DeleteEmptyFolders(processingFolder);
}
catch (Exception ex)
{
logger.LogError(ex,
string.Format("Error Deleting Empty Folder [{0}] Error [{1}]", processingFolder.FullName,
ex.Serialize()));
}
return result;
}
public async Task<OperationResult<bool>> Process(DirectoryInfo folder, bool doJustInfo,
int? submissionId = null)
{
var sw = new Stopwatch();
sw.Start();
await PreProcessFolder(folder, doJustInfo);
var processedFiles = 0;
var pluginResultInfos = new List<PluginResultInfo>();
var errors = new List<string>();
FileProcessor.SubmissionId = submissionId;
foreach (var file in Directory.EnumerateFiles(folder.FullName, "*.*", SearchOption.AllDirectories)
.ToArray())
{
var operation = await FileProcessor.Process(file, doJustInfo);
if (operation != null && operation.AdditionalData != null &&
operation.AdditionalData.ContainsKey(PluginResultInfo.AdditionalDataKeyPluginResultInfo))
pluginResultInfos.Add(
operation.AdditionalData[
PluginResultInfo.AdditionalDataKeyPluginResultInfo] as PluginResultInfo);
processedFiles++;
if (ProcessLimit.HasValue && processedFiles > ProcessLimit.Value) break;
}
await PostProcessFolder(folder, pluginResultInfos, doJustInfo);
sw.Stop();
Logger.LogInformation("** Completed! Processed Folder [{0}]: Processed Files [{1}] : Elapsed Time [{2}]",
folder.FullName, processedFiles, sw.Elapsed);
return new OperationResult<bool>
{
IsSuccess = !errors.Any(),
AdditionalData = new Dictionary<string, object>
{
{"processedFiles", processedFiles},
{"newArtists", ArtistLookupEngine.AddedArtistIds.Count()},
{"newReleases", ReleaseLookupEngine.AddedReleaseIds.Count()},
{"newTracks", ReleaseFactory.AddedTrackIds.Count()}
},
OperationTime = sw.ElapsedMilliseconds
};
}
/// <summary>
/// Perform any operations to the given folder and the plugin results after processing
/// </summary>
private async Task<bool> PostProcessFolder(DirectoryInfo inboundFolder,
IEnumerable<PluginResultInfo> pluginResults, bool doJustInfo)
{
SimpleContract.Requires<ArgumentNullException>(inboundFolder != null, "Invalid InboundFolder");
if (pluginResults != null)
foreach (var releasesInfo in pluginResults.GroupBy(x => x.ReleaseId).Select(x => x.First()))
await ReleaseFactory.ScanReleaseFolder(releasesInfo.ReleaseId, DestinationRoot, doJustInfo);
if (!doJustInfo)
{
var fileExtensionsToDelete = Configuration.FileExtensionsToDelete ?? new string[0];
if (fileExtensionsToDelete.Any())
foreach (var fileInFolder in inboundFolder.GetFiles("*.*", SearchOption.AllDirectories))
if (fileExtensionsToDelete.Any(x =>
x.Equals(fileInFolder.Extension, StringComparison.OrdinalIgnoreCase)))
if (!doJustInfo)
{
fileInFolder.Delete();
Logger.LogInformation("x Deleted File [{0}], Was foud in in FileExtensionsToDelete",
fileInFolder.Name);
}
DeleteEmptyFolders(inboundFolder, Logger);
}
return true;
}
/// <summary>
/// Perform any operations to the given folder before processing
/// </summary>
private Task<bool> PreProcessFolder(DirectoryInfo inboundFolder, bool doJustInfo = false)
{
return Task.FromResult(true);
}
}
}

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Roadie.Library.Encoding;
using Roadie.Library.FilePlugins;
namespace Roadie.Library.Processors
{
public interface IFileProcessor
{
IHttpEncoder HttpEncoder { get; }
IEnumerable<IFilePlugin> Plugins { get; }
int? SubmissionId { get; set; }
Task<OperationResult<bool>> Process(FileInfo fileInfo, bool doJustInfo = false);
Task<OperationResult<bool>> Process(string filename, bool doJustInfo = false);
}
}

View file

@ -1,67 +0,0 @@
using Microsoft.Extensions.Logging;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Factories;
using Roadie.Library.MetaData.Audio;
namespace Roadie.Library.Processors
{
public abstract class ProcessorBase
{
protected bool DoDeleteUnknowns => Configuration.Processing.DoDeleteUnknowns;
protected bool DoMoveUnknowns => Configuration.Processing.DoMoveUnknowns;
public IHttpEncoder HttpEncoder { get; }
public int? SubmissionId { get; set; }
protected string UnknownFolder => Configuration.Processing.UnknownFolder;
protected IArtistFactory ArtistFactory { get; }
protected IArtistLookupEngine ArtistLookupEngine { get; }
protected IAudioMetaDataHelper AudioMetaDataHelper { get; }
protected ICacheManager CacheManager { get; }
protected IRoadieSettings Configuration { get; }
protected IRoadieDbContext DbContext { get; }
protected string DestinationRoot { get; }
protected IImageFactory ImageFactory { get; }
protected ILogger Logger { get; }
protected IReleaseFactory ReleaseFactory { get; }
protected IReleaseLookupEngine ReleaseLookupEngine { get; }
public ProcessorBase(IRoadieSettings configuration, IHttpEncoder httpEncoder, string destinationRoot,
IRoadieDbContext context, ICacheManager cacheManager,
ILogger logger, IArtistLookupEngine artistLookupEngine, IArtistFactory artistFactory,
IReleaseFactory releaseFactory, IImageFactory imageFactory, IReleaseLookupEngine releaseLookupEngine,
IAudioMetaDataHelper audioMetaDataHelper)
{
Configuration = configuration;
HttpEncoder = httpEncoder;
DbContext = context;
CacheManager = cacheManager;
Logger = logger;
DestinationRoot = destinationRoot;
ArtistLookupEngine = artistLookupEngine;
ReleaseLookupEngine = releaseLookupEngine;
ArtistFactory = artistFactory;
ReleaseFactory = releaseFactory;
ImageFactory = imageFactory;
AudioMetaDataHelper = audioMetaDataHelper;
}
}
}

View file

@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="AutoCompare.Core" Version="1.0.0" />
<PackageReference Include="CsvHelper" Version="12.1.2" />
<PackageReference Include="EFCore.BulkExtensions" Version="2.4.7" />
<PackageReference Include="EFCore.BulkExtensions" Version="2.4.9" />
<PackageReference Include="FluentFTP" Version="25.0.5" />
<PackageReference Include="Hashids.net" Version="1.2.2" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.8" />
@ -22,11 +22,12 @@
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.4" />
<PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
<PackageReference Include="Microsoft.PowerShell.5.ReferenceAssemblies" Version="1.1.0" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="6.2.1" />
<PackageReference Include="MimeMapping" Version="1.0.1.12" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="NodaTime" Version="2.4.6" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.2.0" />
<PackageReference Include="RestSharp" Version="106.6.9" />
<PackageReference Include="RestSharp" Version="106.6.10" />
<PackageReference Include="SixLabors.Core" Version="1.0.0-beta0006" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0005" />
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta0005" />

View file

@ -0,0 +1,9 @@
using System.Threading.Tasks;
using Roadie.Library.Models.Users;
namespace Roadie.Library.Scrobble
{
public interface ILastFMScrobbler : IScrobblerIntegration
{
}
}

View file

@ -0,0 +1,9 @@
using System.Threading.Tasks;
using Roadie.Library.Models.Users;
namespace Roadie.Library.Scrobble
{
public interface IRoadieScrobbler : IScrobblerIntegration
{
}
}

View file

@ -13,11 +13,11 @@ namespace Roadie.Library.Scrobble
/// LastFM Scrobbler
/// <seealso cref="https://www.last.fm/api/scrobbling" />
/// </summary>
public class LastFMScrobbler : ScrobblerIntegrationBase
public class LastFMScrobbler : ScrobblerIntegrationBase, ILastFMScrobbler
{
private ILastFmHelper LastFmHelper { get; }
public LastFMScrobbler(IRoadieSettings configuration, ILogger logger, data.IRoadieDbContext dbContext,
public LastFMScrobbler(IRoadieSettings configuration, ILogger<LastFMScrobbler> logger, data.IRoadieDbContext dbContext,
ICacheManager cacheManager, ILastFmHelper lastFmHelper, IHttpContext httpContext)
: base(configuration, logger, dbContext, cacheManager, httpContext)
{
@ -32,10 +32,7 @@ namespace Roadie.Library.Scrobble
/// indication of what music player they're using.
/// </remark>
/// </summary>
public override async Task<OperationResult<bool>> NowPlaying(User roadieUser, ScrobbleInfo scrobble)
{
return await LastFmHelper.NowPlaying(roadieUser, scrobble);
}
public override async Task<OperationResult<bool>> NowPlaying(User roadieUser, ScrobbleInfo scrobble) => await LastFmHelper.NowPlaying(roadieUser, scrobble);
/// <summary>
/// Send a Scrobble Request
@ -44,9 +41,7 @@ namespace Roadie.Library.Scrobble
/// listening history and generate personalised charts and recommendations (and more).
/// </remark>
/// </summary>
public override async Task<OperationResult<bool>> Scrobble(User roadieUser, ScrobbleInfo scrobble)
{
return await LastFmHelper.Scrobble(roadieUser, scrobble);
}
public override async Task<OperationResult<bool>> Scrobble(User roadieUser, ScrobbleInfo scrobble) => await LastFmHelper.Scrobble(roadieUser, scrobble);
}
}

View file

@ -12,9 +12,9 @@ using data = Roadie.Library.Data;
namespace Roadie.Library.Scrobble
{
public class RoadieScrobbler : ScrobblerIntegrationBase
public class RoadieScrobbler : ScrobblerIntegrationBase, IRoadieScrobbler
{
public RoadieScrobbler(IRoadieSettings configuration, ILogger logger, data.IRoadieDbContext dbContext,
public RoadieScrobbler(IRoadieSettings configuration, ILogger<RoadieScrobbler> logger, data.IRoadieDbContext dbContext,
ICacheManager cacheManager, IHttpContext httpContext)
: base(configuration, logger, dbContext, cacheManager, httpContext)
{
@ -25,11 +25,11 @@ namespace Roadie.Library.Scrobble
/// </summary>
public override async Task<OperationResult<bool>> NowPlaying(User roadieUser, ScrobbleInfo scrobble)
{
return new OperationResult<bool>
return await Task.FromResult(new OperationResult<bool>
{
Data = true,
IsSuccess = true
};
});
}
/// <summary>

View file

@ -30,9 +30,9 @@ namespace Roadie.Library.Scrobble
private IEnumerable<IScrobblerIntegration> Scrobblers { get; }
public ScrobbleHandler(IRoadieSettings configuration, ILogger<ScrobbleHandler> logger,
data.IRoadieDbContext dbContext,
ICacheManager cacheManager, IHttpEncoder httpEncoder, IHttpContext httpContext)
public ScrobbleHandler(IRoadieSettings configuration, ILogger<ScrobbleHandler> logger, data.IRoadieDbContext dbContext,
ICacheManager cacheManager, IHttpEncoder httpEncoder, IHttpContext httpContext,
ILastFmHelper lastFmHelper, IRoadieScrobbler roadieScrobbler, ILastFMScrobbler lastFMScrobbler)
{
Logger = logger;
Configuration = configuration;
@ -41,10 +41,12 @@ namespace Roadie.Library.Scrobble
HttpContext = httpContext;
var scrobblers = new List<IScrobblerIntegration>
{
new RoadieScrobbler(configuration, logger, dbContext, cacheManager, httpContext)
roadieScrobbler
};
if (configuration.Integrations.LastFmProviderEnabled)
scrobblers.Add(new LastFmHelper(configuration, cacheManager, logger, dbContext, httpEncoder));
{
scrobblers.Add(lastFMScrobbler);
}
Scrobblers = scrobblers;
}

View file

@ -14,10 +14,12 @@ namespace Roadie.Library.SearchEngines.Imaging
/// <summary>
/// https://msdn.microsoft.com/en-us/library/dn760791(v=bsynd.50).aspx
/// </summary>
public class BingImageSearchEngine : ImageSearchEngineBase
public class BingImageSearchEngine : ImageSearchEngineBase, IBingImageSearchEngine
{
public BingImageSearchEngine(IRoadieSettings configuration, ILogger logger, string requestIp = null,
string referrer = null)
public override bool IsEnabled => Configuration.Integrations.BingImageSearchEngineEnabled;
public BingImageSearchEngine(IRoadieSettings configuration, ILogger<BingImageSearchEngine> logger, string requestIp = null, string referrer = null)
: base(configuration, logger, "https://api.cognitive.microsoft.com", requestIp, referrer)
{
_apiKey = configuration.Integrations.ApiKeys.FirstOrDefault(x => x.ApiName == "BingImageSearch") ??

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using RestSharp;
namespace Roadie.Library.SearchEngines.Imaging
{
public interface IBingImageSearchEngine : IImageSearchEngine
{
}
}

View file

@ -5,17 +5,7 @@ using System.Threading.Tasks;
namespace Roadie.Library.SearchEngines.Imaging
{
public interface IITunesSearchEngine
public interface IITunesSearchEngine : IArtistSearchEngine, IReleaseSearchEngine, IImageSearchEngine
{
bool IsEnabled { get; }
RestRequest BuildRequest(string query, int resultsCount);
Task<OperationResult<IEnumerable<ArtistSearchResult>>> PerformArtistSearch(string query, int resultsCount);
Task<IEnumerable<ImageSearchResult>> PerformImageSearch(string query, int resultsCount);
Task<OperationResult<IEnumerable<ReleaseSearchResult>>> PerformReleaseSearch(string artistName, string query,
int resultsCount);
}
}

View file

@ -6,6 +6,8 @@ namespace Roadie.Library.SearchEngines.Imaging
{
public interface IImageSearchEngine
{
bool IsEnabled { get; }
RestRequest BuildRequest(string query, int resultsCount);
Task<IEnumerable<ImageSearchResult>> PerformImageSearch(string query, int resultsCount);

View file

@ -13,11 +13,9 @@ using System.Threading.Tasks;
namespace Roadie.Library.SearchEngines.Imaging
{
public class ITunesSearchEngine : ImageSearchEngineBase, IArtistSearchEngine, IReleaseSearchEngine,
IITunesSearchEngine
public class ITunesSearchEngine : ImageSearchEngineBase, IITunesSearchEngine
{
public ITunesSearchEngine(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger,
string requestIp = null, string referrer = null)
public ITunesSearchEngine(IRoadieSettings configuration, ICacheManager cacheManager, ILogger<ITunesSearchEngine> logger, string requestIp = null, string referrer = null)
: base(configuration, logger, "http://itunes.apple.com", requestIp, referrer)
{
CacheManager = cacheManager;
@ -25,10 +23,9 @@ namespace Roadie.Library.SearchEngines.Imaging
private ICacheManager CacheManager { get; }
public bool IsEnabled => true;
public override bool IsEnabled => Configuration.Integrations.ITunesProviderEnabled;
public async Task<OperationResult<IEnumerable<ArtistSearchResult>>> PerformArtistSearch(string query,
int resultsCount)
public async Task<OperationResult<IEnumerable<ArtistSearchResult>>> PerformArtistSearch(string query, int resultsCount)
{
ArtistSearchResult data = null;
@ -118,8 +115,7 @@ namespace Roadie.Library.SearchEngines.Imaging
return result;
}
public async Task<OperationResult<IEnumerable<ReleaseSearchResult>>> PerformReleaseSearch(string artistName,
string query, int resultsCount)
public async Task<OperationResult<IEnumerable<ReleaseSearchResult>>> PerformReleaseSearch(string artistName, string query, int resultsCount)
{
var request = BuildRequest(query, 1, "album");
var response = await _client.ExecuteTaskAsync<ITunesSearchResult>(request);

View file

@ -18,6 +18,8 @@ namespace Roadie.Library.SearchEngines.Imaging
protected IApiKey _apiKey = null;
protected ILogger _logger;
public abstract bool IsEnabled { get; }
protected IApiKey ApiKey => _apiKey;
protected IRoadieSettings Configuration => _configuratio;

View file

@ -13,30 +13,44 @@ namespace Roadie.Library.SearchEngines.Imaging
private readonly IImageSearchEngine _bingSearchEngine;
private readonly IImageSearchEngine _itunesSearchEngine;
private IRoadieSettings Configuration { get; }
private int DefaultResultsCount => 10;
public ImageSearchManager(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger,
string requestIp = null, string referrer = null)
public ImageSearchManager(IRoadieSettings configuration, ICacheManager cacheManager, ILogger<ImageSearchManager> logger,
IBingImageSearchEngine bingImageSearchEngine, IITunesSearchEngine iTunesSearchEngine,
string requestIp = null, string referrer = null)
{
_bingSearchEngine = new BingImageSearchEngine(configuration, logger, requestIp, referrer);
_itunesSearchEngine = new ITunesSearchEngine(configuration, cacheManager, logger, requestIp, referrer);
Configuration = configuration;
_bingSearchEngine = bingImageSearchEngine;
_itunesSearchEngine = iTunesSearchEngine;
}
public async Task<IEnumerable<ImageSearchResult>> ImageSearch(string query, int? resultsCount = null)
{
var count = resultsCount ?? DefaultResultsCount;
var result = new List<ImageSearchResult>();
if (WebHelper.IsStringUrl(query))
{
var s = ImageHelper.ImageSearchResultForImageUrl(query);
if (s != null) result.Add(s);
}
var bingResults = await _bingSearchEngine.PerformImageSearch(query, count);
if (bingResults != null) result.AddRange(bingResults);
var iTunesResults = await _itunesSearchEngine.PerformImageSearch(query, count);
if (iTunesResults != null) result.AddRange(iTunesResults);
if (Configuration.Integrations.BingImageSearchEngineEnabled)
{
var bingResults = await _bingSearchEngine.PerformImageSearch(query, count);
if (bingResults != null)
{
result.AddRange(bingResults);
}
}
if (Configuration.Integrations.ITunesProviderEnabled)
{
var iTunesResults = await _itunesSearchEngine.PerformImageSearch(query, count);
if (iTunesResults != null)
{
result.AddRange(iTunesResults);
}
}
return result;
}
}

View file

@ -5,7 +5,6 @@ using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Extensions;
using Roadie.Library.Factories;
using Roadie.Library.MetaData.FileName;
using Roadie.Library.MetaData.ID3Tags;
using Roadie.Library.MetaData.LastFm;
@ -46,8 +45,6 @@ namespace Roadie.Library.MetaData.Audio
private IID3TagsHelper ID3TagsHelper { get; }
private IImageFactory ImageFactory { get; }
private ILastFmHelper LastFmHelper { get; }
private ILogger Logger { get; }
@ -55,16 +52,14 @@ namespace Roadie.Library.MetaData.Audio
private IMusicBrainzProvider MusicBrainzProvider { get; }
public AudioMetaDataHelper(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
IMusicBrainzProvider musicBrainzHelper,
ILastFmHelper lastFmHelper, ICacheManager cacheManager, ILogger logger,
IArtistLookupEngine artistLookupEngine,
IImageFactory imageFactory, IFileNameHelper filenameHelper, IID3TagsHelper id3TagsHelper)
IMusicBrainzProvider musicBrainzHelper, ILastFmHelper lastFmHelper, ICacheManager cacheManager,
ILogger<AudioMetaDataHelper> logger, IArtistLookupEngine artistLookupEngine, IFileNameHelper filenameHelper,
IID3TagsHelper id3TagsHelper)
{
Configuration = configuration;
HttpEncoder = httpEncoder;
CacheManager = cacheManager;
Logger = logger;
ImageFactory = ImageFactory;
FileNameHelper = filenameHelper;
ID3TagsHelper = id3TagsHelper;
@ -74,8 +69,7 @@ namespace Roadie.Library.MetaData.Audio
ArtistLookupEngine = artistLookupEngine;
DoParseFromFileName = configuration.Processing.DoParseFromFileName;
DoParseFromDiscogsDBFindingTrackForArtist =
configuration.Processing.DoParseFromDiscogsDBFindingTrackForArtist;
DoParseFromDiscogsDBFindingTrackForArtist = configuration.Processing.DoParseFromDiscogsDBFindingTrackForArtist;
DoParseFromDiscogsDB = configuration.Processing.DoParseFromDiscogsDB;
DoParseFromMusicBrainz = configuration.Processing.DoParseFromMusicBrainz;
DoParseFromLastFM = configuration.Processing.DoParseFromLastFM;
@ -128,7 +122,7 @@ namespace Roadie.Library.MetaData.Audio
{
if (result.Images == null || !result.Images.Any())
{
var imageMetaData = ImageFactory.GetPictureForMetaData(fileInfo.FullName, result);
var imageMetaData = Imaging.ImageHelper.GetPictureForMetaData(Configuration, fileInfo.FullName, result);
var tagImages = imageMetaData == null ? null : new List<AudioMetaDataImage> { imageMetaData };
result.Images = tagImages != null && tagImages.Any() ? tagImages : null;
if (result.Images == null || !result.Images.Any())

View file

@ -14,19 +14,17 @@ using System.Threading.Tasks;
namespace Roadie.Library.SearchEngines.MetaData.Discogs
{
public class DiscogsHelper : MetaDataProviderBase, IArtistSearchEngine, IReleaseSearchEngine, ILabelSearchEngine
public class DiscogsHelper : MetaDataProviderBase, IDiscogsHelper
{
public override bool IsEnabled => Configuration.Integrations.DiscogsProviderEnabled;
public DiscogsHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger) : base(
public DiscogsHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger<DiscogsHelper> logger) : base(
configuration, cacheManager, logger)
{
_apiKey = configuration.Integrations.ApiKeys.FirstOrDefault(x => x.ApiName == "DiscogsConsumerKey") ??
new ApiKey();
_apiKey = configuration.Integrations.ApiKeys.FirstOrDefault(x => x.ApiName == "DiscogsConsumerKey") ?? new ApiKey();
}
public async Task<OperationResult<IEnumerable<ArtistSearchResult>>> PerformArtistSearch(string query,
int resultsCount)
public async Task<OperationResult<IEnumerable<ArtistSearchResult>>> PerformArtistSearch(string query, int resultsCount)
{
ArtistSearchResult data = null;
try
@ -101,8 +99,7 @@ namespace Roadie.Library.SearchEngines.MetaData.Discogs
};
}
public async Task<OperationResult<IEnumerable<LabelSearchResult>>> PerformLabelSearch(string labelName,
int resultsCount)
public async Task<OperationResult<IEnumerable<LabelSearchResult>>> PerformLabelSearch(string labelName, int resultsCount)
{
LabelSearchResult data = null;
try
@ -174,8 +171,7 @@ namespace Roadie.Library.SearchEngines.MetaData.Discogs
};
}
public async Task<OperationResult<IEnumerable<ReleaseSearchResult>>> PerformReleaseSearch(string artistName,
string query, int resultsCount)
public async Task<OperationResult<IEnumerable<ReleaseSearchResult>>> PerformReleaseSearch(string artistName, string query, int resultsCount)
{
ReleaseSearchResult data = null;
try
@ -185,30 +181,30 @@ namespace Roadie.Library.SearchEngines.MetaData.Discogs
var client = new RestClient("https://api.discogs.com/database")
{
UserAgent = WebHelper.UserAgent,
ReadWriteTimeout = (int)Configuration.Integrations.DiscogsReadWriteTimeout,
Timeout = (int)Configuration.Integrations.DiscogsTimeout
ReadWriteTimeout = SafeParser.ToNumber<int>(Configuration.Integrations.DiscogsReadWriteTimeout),
Timeout = SafeParser.ToNumber<int>(Configuration.Integrations.DiscogsTimeout)
};
var response = await client.ExecuteTaskAsync<DiscogsReleaseSearchResult>(request);
if (response.ResponseStatus == ResponseStatus.Error)
if (response?.ResponseStatus == null || response.ResponseStatus == ResponseStatus.Error)
{
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
throw new AuthenticationException("Unauthorized");
throw new Exception(string.Format("Request Error Message: {0}. Content: {1}.",
response.ErrorMessage, response.Content));
}
throw new Exception($"Request Error Message: {response?.ErrorMessage}. Content: {response?.Content}.");
}
var responseData = response.Data != null && response.Data.results.Any()
? response.Data.results.OrderBy(x => x.year).First()
: null;
if (responseData != null)
var responseData = response?.Data?.results?.OrderBy(x => x.year).FirstOrDefault();
if (responseData?.id != null)
{
request = BuildReleaseRequest(responseData.id);
var c2 = new RestClient("https://api.discogs.com/");
c2.UserAgent = WebHelper.UserAgent;
var c2 = new RestClient("https://api.discogs.com/")
{
UserAgent = WebHelper.UserAgent
};
var releaseResult = await c2.ExecuteTaskAsync<DiscogReleaseDetail>(request);
var release = releaseResult != null && releaseResult.Data != null ? releaseResult.Data : null;
var release = releaseResult?.Data;
if (release != null)
{
var urls = new List<string>();
@ -278,16 +274,18 @@ namespace Roadie.Library.SearchEngines.MetaData.Discogs
if (release.identifiers != null)
{
var barcode = release.identifiers.FirstOrDefault(x => x.type == "Barcode");
if (barcode != null && !string.IsNullOrEmpty(barcode.value))
var barcode = release.identifiers.FirstOrDefault(x => (x.type ?? string.Empty) == "Barcode");
if (barcode?.value != null)
{
data.Tags = new[] { "barcode:" + barcode.value };
}
}
}
}
}
catch (Exception ex)
{
Logger.LogError(ex);
Logger.LogError(ex, $"DiscogsHelper: Error in PerformReleaseSearch artistname [{ artistName }], query [{ query }], resultsCount [{ resultsCount }]");
}
return new OperationResult<IEnumerable<ReleaseSearchResult>>

View file

@ -0,0 +1,9 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Roadie.Library.SearchEngines.MetaData.Discogs
{
public interface IDiscogsHelper : IArtistSearchEngine, IReleaseSearchEngine, ILabelSearchEngine
{
}
}

View file

@ -12,7 +12,7 @@ namespace Roadie.Library.MetaData.FileName
{
public class FileNameHelper : MetaDataProviderBase, IFileNameHelper
{
public FileNameHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger)
public FileNameHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger<FileNameHelper> logger)
: base(configuration, cacheManager, logger)
{
}

View file

@ -1,5 +1,6 @@
using ATL;
using ATL.CatalogDataReaders;
using ATL.Playlist;
using ATL.PlaylistReaders;
using IdSharp.AudioInfo;
using IdSharp.Tagging.ID3v1;
@ -21,7 +22,7 @@ namespace Roadie.Library.MetaData.ID3Tags
{
public class ID3TagsHelper : MetaDataProviderBase, IID3TagsHelper
{
public ID3TagsHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger)
public ID3TagsHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger<ID3TagsHelper> logger)
: base(configuration, cacheManager, logger)
{
}
@ -86,8 +87,8 @@ namespace Roadie.Library.MetaData.ID3Tags
if (m3uFiles != null && m3uFiles.Any())
try
{
var theReader = PlaylistReaderFactory.GetInstance().GetPlaylistReader(m3uFiles.First());
result = (short)theReader.GetFiles().Count();
var theReader = PlaylistIOFactory.GetInstance().GetPlaylistIO(m3uFiles.First());
result = (short)theReader.FilePaths.Count();
}
catch (Exception ex)
{

View file

@ -6,17 +6,10 @@ using System.Threading.Tasks;
namespace Roadie.Library.MetaData.LastFm
{
public interface ILastFmHelper : IScrobblerIntegration
public interface ILastFmHelper : IScrobblerIntegration, IArtistSearchEngine, IReleaseSearchEngine
{
bool IsEnabled { get; }
Task<OperationResult<string>> GetSessionKeyForUserToken(string token);
Task<OperationResult<IEnumerable<ArtistSearchResult>>> PerformArtistSearch(string query, int resultsCount);
Task<OperationResult<IEnumerable<ReleaseSearchResult>>> PerformReleaseSearch(string artistName, string query,
int resultsCount);
Task<IEnumerable<AudioMetaData>> TracksForRelease(string artist, string Release);
}
}

View file

@ -25,7 +25,7 @@ using data = Roadie.Library.Data;
namespace Roadie.Library.MetaData.LastFm
{
public class LastFmHelper : MetaDataProviderBase, IArtistSearchEngine, IReleaseSearchEngine, ILastFmHelper
public class LastFmHelper : MetaDataProviderBase, ILastFmHelper
{
private const string LastFmErrorCodeXPath = "/lfm/error/@code";
private const string LastFmErrorXPath = "/lfm/error";
@ -41,7 +41,7 @@ namespace Roadie.Library.MetaData.LastFm
private IHttpEncoder HttpEncoder { get; }
public LastFmHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger,
public LastFmHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger<LastFmHelper> logger,
data.IRoadieDbContext dbContext, IHttpEncoder httpEncoder)
: base(configuration, cacheManager, logger)
{
@ -121,43 +121,45 @@ namespace Roadie.Library.MetaData.LastFm
var user = DbContext.Users.FirstOrDefault(x => x.RoadieId == roadieUser.UserId);
if (user == null || string.IsNullOrEmpty(user.LastFMSessionKey))
{
return new OperationResult<bool>("User does not have LastFM Integration setup");
var method = "track.updateNowPlaying";
var parameters = new RequestParameters
{
{"artist", scrobble.ArtistName},
{"track", scrobble.TrackTitle},
{"album", scrobble.ReleaseTitle},
{"duration", ((int) scrobble.TrackDuration.TotalSeconds).ToString()}
};
var url = "http://ws.audioscrobbler.com/2.0/";
var signature = GenerateMethodSignature(method, parameters, user.LastFMSessionKey);
parameters.Add("api_sig", signature);
ServicePointManager.Expect100Continue = false;
var request = WebRequest.Create(url);
request.Method = "POST";
var postData = parameters.ToString();
var byteArray = System.Text.Encoding.UTF8.GetBytes(postData);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length;
using (var dataStream = request.GetRequestStream())
{
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
}
await Task.Run(() =>
{
var method = "track.updateNowPlaying";
var parameters = new RequestParameters
{
{"artist", scrobble.ArtistName},
{"track", scrobble.TrackTitle},
{"album", scrobble.ReleaseTitle},
{"duration", ((int) scrobble.TrackDuration.TotalSeconds).ToString()}
};
var url = "http://ws.audioscrobbler.com/2.0/";
var signature = GenerateMethodSignature(method, parameters, user.LastFMSessionKey);
parameters.Add("api_sig", signature);
var xp = GetResponseAsXml(request);
Logger.LogInformation(
$"LastFmHelper: RoadieUser `{roadieUser}` NowPlaying `{scrobble}` LastFmResult [{xp.InnerXml}]");
result = true;
ServicePointManager.Expect100Continue = false;
var request = WebRequest.Create(url);
request.Method = "POST";
var postData = parameters.ToString();
var byteArray = System.Text.Encoding.UTF8.GetBytes(postData);
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length;
using (var dataStream = request.GetRequestStream())
{
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
}
var xp = GetResponseAsXml(request);
Logger.LogInformation($"LastFmHelper: RoadieUser `{roadieUser}` NowPlaying `{scrobble}` LastFmResult [{xp.InnerXml}]");
result = true;
});
}
catch (Exception ex)
{
Logger.LogError(ex,
$"Error in LastFmHelper NowPlaying: RoadieUser `{roadieUser}` Scrobble `{scrobble}`");
}
return new OperationResult<bool>
{
IsSuccess = result,

View file

@ -5,21 +5,14 @@ using System.Threading.Tasks;
namespace Roadie.Library.MetaData.MusicBrainz
{
public interface IMusicBrainzProvider
public interface IMusicBrainzProvider : IArtistSearchEngine, IReleaseSearchEngine
{
bool IsEnabled { get; }
Task<CoverArtArchivesResult> CoverArtForMusicBrainzReleaseById(string musicBrainzId);
Task<Release> MusicBrainzReleaseById(string musicBrainzId);
Task<IEnumerable<AudioMetaData>> MusicBrainzReleaseTracks(string artistName, string releaseTitle);
Task<OperationResult<IEnumerable<ArtistSearchResult>>> PerformArtistSearch(string query, int resultsCount);
Task<OperationResult<IEnumerable<ReleaseSearchResult>>> PerformReleaseSearch(string artistName, string query,
int resultsCount);
Task<Data.Release> ReleaseForMusicBrainzReleaseById(string musicBrainzId);
Task<IEnumerable<Release>> ReleasesForArtist(string artist, string artistMusicBrainzId = null);

View file

@ -15,20 +15,18 @@ using System.Threading.Tasks;
namespace Roadie.Library.MetaData.MusicBrainz
{
public class MusicBrainzProvider : MetaDataProviderBase, IArtistSearchEngine, IReleaseSearchEngine,
IMusicBrainzProvider
public class MusicBrainzProvider : MetaDataProviderBase, IMusicBrainzProvider
{
public override bool IsEnabled => Configuration.Integrations.MusicBrainzProviderEnabled;
public MusicBrainzProvider(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger)
public MusicBrainzProvider(IRoadieSettings configuration, ICacheManager cacheManager, ILogger<MusicBrainzProvider> logger)
: base(configuration, cacheManager, logger)
{
}
public async Task<CoverArtArchivesResult> CoverArtForMusicBrainzReleaseById(string musicBrainzId)
{
return await MusicBrainzRequestHelper.GetAsync<CoverArtArchivesResult>(
MusicBrainzRequestHelper.CreateCoverArtReleaseUrl(musicBrainzId));
return await MusicBrainzRequestHelper.GetAsync<CoverArtArchivesResult>(MusicBrainzRequestHelper.CreateCoverArtReleaseUrl(musicBrainzId));
}
public async Task<Release> MusicBrainzReleaseById(string musicBrainzId)

View file

@ -0,0 +1,9 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Roadie.Library.SearchEngines.MetaData.Spotify
{
public interface ISpotifyHelper : IArtistSearchEngine, IReleaseSearchEngine
{
}
}

View file

@ -14,11 +14,11 @@ using System.Threading.Tasks;
namespace Roadie.Library.SearchEngines.MetaData.Spotify
{
public class SpotifyHelper : MetaDataProviderBase, IArtistSearchEngine, IReleaseSearchEngine
public class SpotifyHelper : MetaDataProviderBase, ISpotifyHelper
{
public override bool IsEnabled => Configuration.Integrations.SpotifyProviderEnabled;
public SpotifyHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger)
public SpotifyHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger<SpotifyHelper> logger)
: base(configuration, cacheManager, logger)
{
}

View file

@ -0,0 +1,9 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Roadie.Library.SearchEngines.MetaData.Wikipedia
{
public interface IWikipediaHelper : IArtistSearchEngine, IReleaseSearchEngine
{
}
}

View file

@ -10,11 +10,11 @@ using System.Threading.Tasks;
namespace Roadie.Library.SearchEngines.MetaData.Wikipedia
{
public class WikipediaHelper : MetaDataProviderBase, IArtistSearchEngine, IReleaseSearchEngine
public class WikipediaHelper : MetaDataProviderBase, IWikipediaHelper
{
private IHttpEncoder HttpEncoder { get; }
public WikipediaHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger,
public WikipediaHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger<WikipediaHelper> logger,
IHttpEncoder httpEncoder)
: base(configuration, cacheManager, logger)
{

View file

@ -0,0 +1,8 @@
using System.Threading.Tasks;
namespace Roadie.Library.SearchEngines.MetaData.iTunes
{
public interface IiTunesHelper : IArtistSearchEngine, IReleaseSearchEngine
{
}
}

View file

@ -3,31 +3,27 @@ using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.MetaData;
using Roadie.Library.SearchEngines.Imaging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Roadie.Library.SearchEngines.MetaData.iTunes
{
public class iTunesHelper : MetaDataProviderBase
public class iTunesHelper : MetaDataProviderBase, IiTunesHelper
{
private readonly ITunesSearchEngine _iTunesSearchEngine;
public override bool IsEnabled => Configuration.Integrations.ITunesProviderEnabled;
public iTunesHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger)
public iTunesHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger<iTunesHelper> logger,
ITunesSearchEngine iTunesSearchEngine)
: base(configuration, cacheManager, logger)
{
_iTunesSearchEngine = new ITunesSearchEngine(configuration, cacheManager, logger);
_iTunesSearchEngine = iTunesSearchEngine;
}
public async Task<OperationResult<ArtistSearchResult>> SearchForArtist(string artistName)
{
var r = await _iTunesSearchEngine.PerformArtistSearch(artistName, 1);
return new OperationResult<ArtistSearchResult>
{
Data = r.Data != null ? r.Data.First() : null,
IsSuccess = r.Data != null
};
}
public async Task<OperationResult<IEnumerable<ArtistSearchResult>>> PerformArtistSearch(string query, int resultsCount) => await _iTunesSearchEngine.PerformArtistSearch(query, resultsCount);
public async Task<OperationResult<IEnumerable<ReleaseSearchResult>>> PerformReleaseSearch(string artistName, string query, int resultsCount) => await _iTunesSearchEngine.PerformReleaseSearch(artistName, query, resultsCount);
}
}

View file

@ -15,19 +15,15 @@ namespace Roadie.Library.Utility
public static class FolderPathHelper
{
/// <summary>
/// Full path to Artist folder using destinationFolder as folder Root
/// Full path to Artist folder
/// </summary>
/// <param name="artistSortName">Sort name of Artist to use for folder name</param>
/// <param name="destinationFolder">Optional Root folder defaults to Library Folder from Settings</param>
public static string ArtistPath(IRoadieSettings configuration, string artistSortName,
string destinationFolder = null)
public static string ArtistPath(IRoadieSettings configuration, string artistSortName)
{
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(artistSortName),
"Invalid Artist Sort Name");
SimpleContract.Requires<ArgumentException>(!string.IsNullOrEmpty(artistSortName),"Invalid Artist Sort Name");
var artistFolder = artistSortName.ToTitleCase(false);
destinationFolder = destinationFolder ?? configuration.LibraryFolder;
var directoryInfo = new DirectoryInfo(Path.Combine(destinationFolder, artistFolder.ToFolderNameFriendly()));
var directoryInfo = new DirectoryInfo(Path.Combine(configuration.LibraryFolder, artistFolder.ToFolderNameFriendly()));
return directoryInfo.FullName;
}
@ -63,15 +59,41 @@ namespace Roadie.Library.Utility
/// <returns></returns>
public static bool DeleteEmptyFolders(DirectoryInfo processingFolder)
{
if (processingFolder == null || !processingFolder.Exists) return true;
foreach (var folder in processingFolder.GetDirectories("*.*", SearchOption.AllDirectories))
if (folder.Exists)
if (!folder.GetFiles("*.*", SearchOption.AllDirectories).Any())
if (processingFolder == null || !processingFolder.Exists)
{
return true;
}
try
{
foreach (var folder in processingFolder.GetDirectories("*.*", SearchOption.AllDirectories))
{
try
{
folder.Delete(true);
Trace.WriteLine(string.Format("Deleting Empty Folder [{0}]", folder.FullName), "Debug");
if (folder.Exists)
{
if (!folder.GetFiles("*.*", SearchOption.AllDirectories).Any())
{
folder.Delete(true);
Trace.WriteLine(string.Format("Deleting Empty Folder [{0}]", folder.FullName), "Debug");
}
}
}
catch (DirectoryNotFoundException)
{
}
catch (Exception)
{
throw;
}
}
}
catch(DirectoryNotFoundException)
{
}
catch (Exception)
{
throw;
}
return true;
}
@ -86,7 +108,7 @@ namespace Roadie.Library.Utility
{
destinationFolder = destinationFolder ?? configuration.LibraryFolder;
SimpleContract.Requires<ArgumentException>(artist != null, "Invalid Artist");
return DeleteEmptyFolders(new DirectoryInfo(artist.ArtistFileFolder(configuration, destinationFolder)));
return DeleteEmptyFolders(new DirectoryInfo(artist.ArtistFileFolder(configuration)));
}
/// <summary>
@ -94,11 +116,10 @@ namespace Roadie.Library.Utility
/// </summary>
/// <param name="track">Populate track database record</param>
/// <param name="destinationFolder">Optional Root folder defaults to Library Folder from Settings</param>
public static string PathForTrack(IRoadieSettings configuration, Track track, string destinationFolder = null)
public static string PathForTrack(IRoadieSettings configuration, Track track)
{
destinationFolder = destinationFolder ?? configuration.LibraryFolder;
if (string.IsNullOrEmpty(track.FilePath) || string.IsNullOrEmpty(track.FileName)) return null;
var directoryInfo = new DirectoryInfo(Path.Combine(destinationFolder, track.FilePath, track.FileName));
var directoryInfo = new DirectoryInfo(Path.Combine(configuration.LibraryFolder, track.FilePath, track.FileName));
return directoryInfo.FullName;
}
@ -235,7 +256,7 @@ namespace Roadie.Library.Utility
string artistFolder = null, string releaseFolder = null)
{
destinationFolder = destinationFolder ?? configuration.LibraryFolder;
artistFolder = artistFolder ?? ArtistPath(configuration, artistSortName, destinationFolder);
artistFolder = artistFolder ?? ArtistPath(configuration, artistSortName);
releaseFolder = releaseFolder ?? ReleasePath(artistFolder, releaseTitle, releaseDate);
var trackFileName = TrackFileName(configuration, trackTitle, trackNumber, discNumber, totalTrackNumber,
fileExtension);

View file

@ -12,17 +12,23 @@ namespace Roadie.Library.Utility
{
public static class WebHelper
{
public const string UserAgent =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36";
public const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36";
public static byte[] BytesForImageUrl(string url)
{
if (string.IsNullOrEmpty(url)) return null;
try
{
using (var webClient = new WebClient())
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Referer = "http://www.roadie.rocks";
request.UserAgent = UserAgent;
using (var response = (HttpWebResponse)request.GetResponse())
{
return webClient.DownloadData(url);
using (BinaryReader reader = new BinaryReader(response.GetResponseStream()))
{
return reader.ReadBytes(1 * 1024 * 1024 * 10);
}
}
}
catch (WebException wex)

View file

@ -10,7 +10,6 @@ using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Enums;
using Roadie.Library.Extensions;
using Roadie.Library.Factories;
using Roadie.Library.Identity;
using Roadie.Library.Imaging;
using Roadie.Library.MetaData.Audio;
@ -35,8 +34,6 @@ namespace Roadie.Api.Services
{
protected IHubContext<ScanActivityHub> ScanActivityHub { get; }
private IArtistFactory ArtistFactory { get; }
private IArtistLookupEngine ArtistLookupEngine { get; }
private IAudioMetaDataHelper AudioMetaDataHelper { get; }
@ -47,10 +44,6 @@ namespace Roadie.Api.Services
private IID3TagsHelper ID3TagsHelper { get; }
private IImageFactory ImageFactory { get; }
private ILabelFactory LabelFactory { get; }
private ILabelLookupEngine LabelLookupEngine { get; }
private ILastFmHelper LastFmHelper { get; }
@ -59,45 +52,53 @@ namespace Roadie.Api.Services
private IMusicBrainzProvider MusicBrainzProvider { get; }
private IReleaseFactory ReleaseFactory { get; }
private IReleaseLookupEngine ReleaseLookupEngine { get; }
private IArtistService ArtistService { get; }
private IReleaseService ReleaseService { get; }
private IFileDirectoryProcessorService FileDirectoryProcessorService { get; }
public AdminService(IRoadieSettings configuration,
IHttpEncoder httpEncoder,
IHttpContext httpContext,
data.IRoadieDbContext context,
ICacheManager cacheManager,
ILogger<ArtistService> logger,
IHubContext<ScanActivityHub> scanActivityHub
IHubContext<ScanActivityHub> scanActivityHub,
IMusicBrainzProvider musicbrainzProvider,
ILastFmHelper lastFmHelper,
IFileNameHelper fileNameHelper,
IID3TagsHelper iD3TagsHelper,
IAudioMetaDataHelper audioMetaDataHelper,
IArtistService artistService,
IReleaseService releaseService,
IArtistLookupEngine artistLookupEngine,
IReleaseLookupEngine releaseLookupEngine,
ILabelLookupEngine labelLookupEngine,
IFileDirectoryProcessorService fileDirectoryProcessorService
)
: base(configuration, httpEncoder, context, cacheManager, logger, httpContext)
{
ScanActivityHub = scanActivityHub;
EventMessageLogger = new EventMessageLogger();
EventMessageLogger = new EventMessageLogger<AdminService>();
EventMessageLogger.Messages += EventMessageLogger_Messages;
MusicBrainzProvider = new MusicBrainzProvider(configuration, cacheManager, MessageLogger);
LastFmHelper = new LastFmHelper(configuration, cacheManager, MessageLogger, context, httpEncoder);
FileNameHelper = new FileNameHelper(configuration, cacheManager, MessageLogger);
ID3TagsHelper = new ID3TagsHelper(configuration, cacheManager, MessageLogger);
MusicBrainzProvider = musicbrainzProvider;
LastFmHelper = lastFmHelper;
FileNameHelper = fileNameHelper;
ID3TagsHelper = iD3TagsHelper;
ArtistLookupEngine = artistLookupEngine;
ReleaseLookupEngine = releaseLookupEngine;
LabelLookupEngine = labelLookupEngine;
AudioMetaDataHelper = audioMetaDataHelper;
ArtistService = artistService;
ReleaseService = releaseService;
FileDirectoryProcessorService = fileDirectoryProcessorService;
ArtistLookupEngine =
new ArtistLookupEngine(configuration, httpEncoder, context, cacheManager, MessageLogger);
LabelLookupEngine = new LabelLookupEngine(configuration, httpEncoder, context, cacheManager, MessageLogger);
ReleaseLookupEngine = new ReleaseLookupEngine(configuration, httpEncoder, context, cacheManager,
MessageLogger, ArtistLookupEngine, LabelLookupEngine);
ImageFactory = new ImageFactory(configuration, httpEncoder, context, cacheManager, MessageLogger,
ArtistLookupEngine, ReleaseLookupEngine);
LabelFactory = new LabelFactory(configuration, httpEncoder, context, cacheManager, MessageLogger,
ArtistLookupEngine, ReleaseLookupEngine);
AudioMetaDataHelper = new AudioMetaDataHelper(configuration, httpEncoder, context, MusicBrainzProvider,
LastFmHelper, cacheManager,
MessageLogger, ArtistLookupEngine, ImageFactory, FileNameHelper, ID3TagsHelper);
ReleaseFactory = new ReleaseFactory(configuration, httpEncoder, context, cacheManager, MessageLogger,
ArtistLookupEngine, LabelFactory, AudioMetaDataHelper, ReleaseLookupEngine);
ArtistFactory = new ArtistFactory(configuration, httpEncoder, context, cacheManager, MessageLogger,
ArtistLookupEngine, ReleaseFactory, ImageFactory, ReleaseLookupEngine, AudioMetaDataHelper);
}
public async Task<OperationResult<bool>> DeleteArtist(ApplicationUser user, Guid artistId)
@ -114,7 +115,7 @@ namespace Roadie.Api.Services
try
{
var result = await ArtistFactory.Delete(artist);
var result = await ArtistService.Delete(user, artist);
if (!result.IsSuccess)
return new OperationResult<bool>
{
@ -154,7 +155,7 @@ namespace Roadie.Api.Services
try
{
await ReleaseFactory.DeleteReleases(
await ReleaseService.DeleteReleases(user,
DbContext.Releases.Where(x => x.ArtistId == artist.Id).Select(x => x.RoadieId).ToArray(),
doDeleteFiles);
await DbContext.SaveChangesAsync();
@ -192,7 +193,7 @@ namespace Roadie.Api.Services
try
{
var artistFolder = artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder);
var artistFolder = artist.ArtistFileFolder(Configuration);
var artistImagesInFolder = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(artistFolder),
ImageType.ArtistSecondary, SearchOption.TopDirectoryOnly);
var artistImageFilename = artistImagesInFolder.Skip(index).FirstOrDefault();
@ -235,7 +236,7 @@ namespace Roadie.Api.Services
return new OperationResult<bool>(true, $"Release Not Found [{releaseId}]");
}
await ReleaseFactory.Delete(release, doDeleteFiles ?? false);
await ReleaseService.Delete(user, release, doDeleteFiles ?? false);
}
catch (Exception ex)
{
@ -272,8 +273,7 @@ namespace Roadie.Api.Services
try
{
var releaseFolder =
release.ReleaseFileFolder(release.Artist.ArtistFileFolder(Configuration,
Configuration.LibraryFolder));
release.ReleaseFileFolder(release.Artist.ArtistFileFolder(Configuration));
var releaseImagesInFolder = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(releaseFolder),
ImageType.ReleaseSecondary, SearchOption.TopDirectoryOnly);
var releaseImageFilename = releaseImagesInFolder.Skip(index).FirstOrDefault();
@ -325,14 +325,14 @@ namespace Roadie.Api.Services
string trackPath = null;
try
{
trackPath = track.PathToTrack(Configuration, Configuration.LibraryFolder);
trackPath = track.PathToTrack(Configuration);
if (File.Exists(trackPath))
{
File.Delete(trackPath);
Logger.LogWarning($"x For Track `{track}`, Deleted File [{trackPath}]");
}
var trackThumbnailName = track.PathToTrackThumbnail(Configuration, Configuration.LibraryFolder);
var trackThumbnailName = track.PathToTrackThumbnail(Configuration);
if (File.Exists(trackThumbnailName))
{
File.Delete(trackThumbnailName);
@ -347,8 +347,7 @@ namespace Roadie.Api.Services
}
}
await ReleaseFactory.ScanReleaseFolder(track.ReleaseMedia.Release.RoadieId, Configuration.LibraryFolder,
false, track.ReleaseMedia.Release);
await ReleaseService.ScanReleaseFolder(user, track.ReleaseMedia.Release.RoadieId, false, track.ReleaseMedia.Release);
}
catch (Exception ex)
{
@ -576,9 +575,7 @@ namespace Roadie.Api.Services
try
{
var result =
await ArtistFactory.ScanArtistReleasesFolders(artist.RoadieId, Configuration.LibraryFolder,
isReadOnly);
var result = await ArtistService.ScanArtistReleasesFolders(user, artist.RoadieId, Configuration.LibraryFolder, isReadOnly);
CacheManager.ClearRegion(artist.CacheRegion);
}
catch (Exception ex)
@ -593,7 +590,7 @@ namespace Roadie.Api.Services
UserId = user.Id,
ForArtistId = artist.Id,
NewReleases = ReleaseLookupEngine.AddedReleaseIds.Count(),
NewTracks = ReleaseFactory.AddedTrackIds.Count(),
NewTracks = ReleaseService.AddedTrackIds.Count(),
TimeSpanInSeconds = (int)sw.Elapsed.TotalSeconds
});
await DbContext.SaveChangesAsync();
@ -796,14 +793,14 @@ namespace Roadie.Api.Services
{
var d = new DirectoryInfo(Configuration.InboundFolder);
var dest = new DirectoryInfo(Configuration.LibraryFolder);
return await ScanFolder(d, dest, user, isReadOnly);
return await ScanFolder(user, d, dest, isReadOnly);
}
public async Task<OperationResult<bool>> ScanLibraryFolder(ApplicationUser user, bool isReadOnly = false)
{
var d = new DirectoryInfo(Configuration.LibraryFolder);
var dest = new DirectoryInfo(Configuration.LibraryFolder);
return await ScanFolder(d, dest, user, isReadOnly);
return await ScanFolder(user, d, dest, isReadOnly);
}
public async Task<OperationResult<bool>> ScanRelease(ApplicationUser user, Guid releaseId,
@ -825,8 +822,7 @@ namespace Roadie.Api.Services
try
{
var result = await ReleaseFactory.ScanReleaseFolder(release.RoadieId, Configuration.LibraryFolder,
isReadOnly, release);
var result = await ReleaseService.ScanReleaseFolder(user, release.RoadieId, isReadOnly, release);
await UpdateReleaseRank(release.Id);
CacheManager.ClearRegion(release.CacheRegion);
}
@ -842,7 +838,7 @@ namespace Roadie.Api.Services
{
UserId = user.Id,
ForReleaseId = release.Id,
NewTracks = ReleaseFactory.AddedTrackIds.Count(),
NewTracks = ReleaseService.AddedTrackIds.Count(),
TimeSpanInSeconds = (int)sw.Elapsed.TotalSeconds
});
await DbContext.SaveChangesAsync();
@ -891,8 +887,7 @@ namespace Roadie.Api.Services
await ScanActivityHub.Clients.All.SendAsync("SendSystemActivity", message);
}
private async Task<OperationResult<bool>> ScanFolder(DirectoryInfo d, DirectoryInfo dest, ApplicationUser user,
bool isReadOnly)
private async Task<OperationResult<bool>> ScanFolder(ApplicationUser user, DirectoryInfo d, DirectoryInfo dest, bool isReadOnly)
{
var sw = new Stopwatch();
sw.Start();
@ -901,43 +896,28 @@ namespace Roadie.Api.Services
await LogAndPublish($"** Processing Folder: [{d.FullName}]");
long processedFolders = 0;
var folderProcessor = new FolderProcessor(Configuration, HttpEncoder, Configuration.LibraryFolder,
DbContext, CacheManager, MessageLogger, ArtistLookupEngine, ArtistFactory, ReleaseFactory, ImageFactory,
ReleaseLookupEngine, AudioMetaDataHelper);
var newArtists = 0;
var newReleases = 0;
var newTracks = 0;
OperationResult<bool> result = null;
foreach (var folder in Directory.EnumerateDirectories(d.FullName).ToArray())
{
result = await folderProcessor.Process(new DirectoryInfo(folder), isReadOnly);
// Between folders flush cache, the caching for folder processing was intended for caching artist metadata lookups. Most of the time artists are in the same folder.
await FileDirectoryProcessorService.Process(user, new DirectoryInfo(folder), isReadOnly);
// Between folders flush cache, the caching for folder processing was intended for caching artist metadata lookups. Most of the time artists releases are in the same folder.
CacheManager.Clear();
processedFolders++;
}
if (result.AdditionalData != null)
if (!isReadOnly)
{
newArtists = SafeParser.ToNumber<int>(result.AdditionalData["newArtists"]);
newReleases = SafeParser.ToNumber<int>(result.AdditionalData["newReleases"]);
newTracks = SafeParser.ToNumber<int>(result.AdditionalData["newTracks"]);
Services.FileDirectoryProcessorService.DeleteEmptyFolders(d, Logger);
}
if (!isReadOnly) FolderProcessor.DeleteEmptyFolders(d, Logger);
sw.Stop();
DbContext.ScanHistories.Add(new data.ScanHistory
var newScanHistory = new data.ScanHistory
{
UserId = user.Id,
NewArtists = newArtists,
NewReleases = newReleases,
NewTracks = newTracks,
NewArtists = FileDirectoryProcessorService.AddedArtistIds.Count(),
NewReleases = FileDirectoryProcessorService.AddedReleaseIds.Count(),
NewTracks = FileDirectoryProcessorService.AddedTrackIds.Count(),
TimeSpanInSeconds = (int)sw.Elapsed.TotalSeconds
});
};
DbContext.ScanHistories.Add(newScanHistory);
await DbContext.SaveChangesAsync();
CacheManager.Clear();
await LogAndPublish(
$"**Completed!Processed Folders[{processedFolders}], Processed Files[{processedFiles}] : Elapsed Time[{sw.Elapsed}]");
await LogAndPublish($"** Completed! Processed Folders [{processedFolders}], Processed Files [{processedFiles}], New Artists [{ newScanHistory.NewArtists }], New Releases [{ newScanHistory.NewReleases }], New Tracks [{ newScanHistory.NewTracks }] : Elapsed Time [{sw.Elapsed}]");
return new OperationResult<bool>
{
Data = true,

View file

@ -10,7 +10,7 @@ using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Enums;
using Roadie.Library.Extensions;
using Roadie.Library.Factories;
using Roadie.Library.Identity;
using Roadie.Library.Imaging;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.MetaData.FileName;
@ -37,8 +37,6 @@ namespace Roadie.Api.Services
{
public class ArtistService : ServiceBase, IArtistService
{
private IArtistFactory ArtistFactory { get; }
private IArtistLookupEngine ArtistLookupEngine { get; }
private IAudioMetaDataHelper AudioMetaDataHelper { get; }
@ -51,10 +49,6 @@ namespace Roadie.Api.Services
private IID3TagsHelper ID3TagsHelper { get; }
private IImageFactory ImageFactory { get; }
private ILabelFactory LabelFactory { get; }
private ILabelLookupEngine LabelLookupEngine { get; }
private ILastFmHelper LastFmHelper { get; }
@ -63,19 +57,31 @@ namespace Roadie.Api.Services
private IPlaylistService PlaylistService { get; }
private IReleaseFactory ReleaseFactory { get; }
private IReleaseLookupEngine ReleaseLookupEngine { get; }
public ArtistService(IRoadieSettings configuration,
IHttpEncoder httpEncoder,
private IReleaseService ReleaseService { get; }
private IFileDirectoryProcessorService FileDirectoryProcessorService { get; }
public ArtistService(IRoadieSettings configuration,
IHttpEncoder httpEncoder,
IHttpContext httpContext,
data.IRoadieDbContext dbContext,
ICacheManager cacheManager,
ILogger<ArtistService> logger,
ICollectionService collectionService,
IPlaylistService playlistService,
IBookmarkService bookmarkService
IBookmarkService bookmarkService,
IReleaseService releaseService,
IArtistLookupEngine artistLookupEngine,
mb.IMusicBrainzProvider musicBrainzProvider,
ILastFmHelper lastFmHelper,
IFileNameHelper fileNameHelper,
IID3TagsHelper id3tagsHelper,
IAudioMetaDataHelper audioMetaDataHelper,
IReleaseLookupEngine releaseLookupEngine,
ILabelLookupEngine labelLookupEngine,
IFileDirectoryProcessorService fileDirectoryProcessorService
)
: base(configuration, httpEncoder, dbContext, cacheManager, logger, httpContext)
{
@ -83,26 +89,19 @@ namespace Roadie.Api.Services
PlaylistService = playlistService;
BookmarkService = bookmarkService;
MusicBrainzProvider = new mb.MusicBrainzProvider(configuration, cacheManager, logger);
LastFmHelper = new LastFmHelper(configuration, cacheManager, logger, dbContext, httpEncoder);
FileNameHelper = new FileNameHelper(configuration, cacheManager, logger);
ID3TagsHelper = new ID3TagsHelper(configuration, cacheManager, logger);
ArtistLookupEngine = new ArtistLookupEngine(configuration, httpEncoder, dbContext, cacheManager, logger);
LabelLookupEngine = new LabelLookupEngine(configuration, httpEncoder, dbContext, cacheManager, logger);
ReleaseLookupEngine = new ReleaseLookupEngine(configuration, httpEncoder, dbContext, cacheManager, logger,
ArtistLookupEngine, LabelLookupEngine);
ImageFactory = new ImageFactory(configuration, httpEncoder, dbContext, cacheManager, logger,
ArtistLookupEngine, ReleaseLookupEngine);
LabelFactory = new LabelFactory(configuration, httpEncoder, dbContext, cacheManager, logger,
ArtistLookupEngine, ReleaseLookupEngine);
AudioMetaDataHelper = new AudioMetaDataHelper(configuration, httpEncoder, dbContext, MusicBrainzProvider,
LastFmHelper, cacheManager,
logger, ArtistLookupEngine, ImageFactory, FileNameHelper, ID3TagsHelper);
MusicBrainzProvider = musicBrainzProvider;
LastFmHelper = lastFmHelper;
FileNameHelper = fileNameHelper;
ID3TagsHelper = id3tagsHelper;
ReleaseFactory = new ReleaseFactory(configuration, httpEncoder, dbContext, cacheManager, logger,
ArtistLookupEngine, LabelFactory, AudioMetaDataHelper, ReleaseLookupEngine);
ArtistFactory = new ArtistFactory(configuration, httpEncoder, dbContext, cacheManager, logger,
ArtistLookupEngine, ReleaseFactory, ImageFactory, ReleaseLookupEngine, AudioMetaDataHelper);
ArtistLookupEngine = artistLookupEngine;
LabelLookupEngine = labelLookupEngine;
ReleaseLookupEngine = releaseLookupEngine;
AudioMetaDataHelper = audioMetaDataHelper;
ReleaseService = releaseService;
FileDirectoryProcessorService = fileDirectoryProcessorService;
}
public async Task<OperationResult<Artist>> ById(User roadieUser, Guid id, IEnumerable<string> includes)
@ -184,8 +183,61 @@ namespace Roadie.Api.Services
};
}
public async Task<OperationResult<bool>> Delete(ApplicationUser user, data.Artist Artist)
{
var isSuccess = false;
try
{
if (Artist != null)
{
DbContext.Artists.Remove(Artist);
await DbContext.SaveChangesAsync();
CacheManager.ClearRegion(Artist.CacheRegion);
Logger.LogInformation(string.Format("x DeleteArtist [{0}]", Artist.Id));
isSuccess = true;
}
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
return new OperationResult<bool>
{
Errors = new Exception[1] { ex }
};
}
return new OperationResult<bool>
{
IsSuccess = isSuccess,
Data = isSuccess
};
}
private OperationResult<data.Artist> GetByExternalIds(string musicBrainzId = null, string iTunesId = null, string amgId = null, string spotifyId = null)
{
var sw = new Stopwatch();
sw.Start();
var artist = (from a in DbContext.Artists
where a.MusicBrainzId != null && musicBrainzId != null && a.MusicBrainzId == musicBrainzId ||
a.ITunesId != null || iTunesId != null && a.ITunesId == iTunesId || a.AmgId != null ||
amgId != null && a.AmgId == amgId || a.SpotifyId != null ||
spotifyId != null && a.SpotifyId == spotifyId
select a).FirstOrDefault();
sw.Stop();
if (artist == null || !artist.IsValid)
Logger.LogTrace(
"ArtistFactory: Artist Not Found By External Ids: MusicbrainzId [{0}], iTunesIs [{1}], AmgId [{2}], SpotifyId [{3}]",
musicBrainzId, iTunesId, amgId, spotifyId);
return new OperationResult<data.Artist>
{
IsSuccess = artist != null,
OperationTime = sw.ElapsedMilliseconds,
Data = artist
};
}
public async Task<Library.Models.Pagination.PagedResult<ArtistList>> List(User roadieUser, PagedRequest request,
bool? doRandomize = false, bool? onlyIncludeWithReleases = true)
bool? doRandomize = false, bool? onlyIncludeWithReleases = true)
{
var sw = new Stopwatch();
sw.Start();
@ -338,7 +390,13 @@ namespace Roadie.Api.Services
};
}
public async Task<OperationResult<bool>> MergeArtists(User user, Guid artistToMergeId, Guid artistToMergeIntoId)
/// <summary>
/// Merge one Artist into another one
/// </summary>
/// <param name="artistToMerge">The Artist to be merged</param>
/// <param name="artistToMergeInto">The Artist to merge into</param>
/// <returns></returns>
public async Task<OperationResult<bool>> MergeArtists(ApplicationUser user, Guid artistToMergeId, Guid artistToMergeIntoId)
{
var sw = new Stopwatch();
sw.Start();
@ -366,7 +424,7 @@ namespace Roadie.Api.Services
try
{
var result = await ArtistFactory.MergeArtists(artistToMerge, mergeIntoArtist, true);
var result = await MergeArtists(user, artistToMerge, mergeIntoArtist);
if (!result.IsSuccess)
{
CacheManager.ClearRegion(artistToMerge.CacheRegion);
@ -392,12 +450,190 @@ namespace Roadie.Api.Services
};
}
public async Task<OperationResult<Image>> SetReleaseImageByUrl(User user, Guid id, string imageUrl)
async Task<OperationResult<data.Artist>> MergeArtists(ApplicationUser user, data.Artist artistToMerge, data.Artist artistToMergeInto)
{
SimpleContract.Requires<ArgumentNullException>(artistToMerge != null, "Invalid Artist");
SimpleContract.Requires<ArgumentNullException>(artistToMergeInto != null, "Invalid Artist");
var result = false;
var now = DateTime.UtcNow;
var sw = new Stopwatch();
sw.Start();
artistToMergeInto.RealName = artistToMerge.RealName ?? artistToMergeInto.RealName;
artistToMergeInto.MusicBrainzId = artistToMerge.MusicBrainzId ?? artistToMergeInto.MusicBrainzId;
artistToMergeInto.ITunesId = artistToMerge.ITunesId ?? artistToMergeInto.ITunesId;
artistToMergeInto.AmgId = artistToMerge.AmgId ?? artistToMergeInto.AmgId;
artistToMergeInto.SpotifyId = artistToMerge.SpotifyId ?? artistToMergeInto.SpotifyId;
artistToMergeInto.Thumbnail = artistToMerge.Thumbnail ?? artistToMergeInto.Thumbnail;
artistToMergeInto.Profile = artistToMerge.Profile ?? artistToMergeInto.Profile;
artistToMergeInto.BirthDate = artistToMerge.BirthDate ?? artistToMergeInto.BirthDate;
artistToMergeInto.BeginDate = artistToMerge.BeginDate ?? artistToMergeInto.BeginDate;
artistToMergeInto.EndDate = artistToMerge.EndDate ?? artistToMergeInto.EndDate;
if (!string.IsNullOrEmpty(artistToMerge.ArtistType) && !artistToMerge.ArtistType.Equals("Other", StringComparison.OrdinalIgnoreCase))
{
artistToMergeInto.ArtistType = artistToMerge.ArtistType;
}
artistToMergeInto.BioContext = artistToMerge.BioContext ?? artistToMergeInto.BioContext;
artistToMergeInto.DiscogsId = artistToMerge.DiscogsId ?? artistToMergeInto.DiscogsId;
artistToMergeInto.Tags = artistToMergeInto.Tags.AddToDelimitedList(artistToMerge.Tags.ToListFromDelimited());
var altNames = artistToMerge.AlternateNames.ToListFromDelimited().ToList();
altNames.Add(artistToMerge.Name);
altNames.Add(artistToMerge.SortName);
artistToMergeInto.AlternateNames = artistToMergeInto.AlternateNames.AddToDelimitedList(altNames);
artistToMergeInto.URLs = artistToMergeInto.URLs.AddToDelimitedList(artistToMerge.URLs.ToListFromDelimited());
artistToMergeInto.ISNI = artistToMergeInto.ISNI.AddToDelimitedList(artistToMerge.ISNI.ToListFromDelimited());
artistToMergeInto.LastUpdated = now;
try
{
var artistGenres = DbContext.ArtistGenres.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistGenres != null)
{
foreach (var artistGenre in artistGenres)
{
artistGenre.ArtistId = artistToMergeInto.Id;
}
}
var artistImages = DbContext.Images.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistImages != null)
{
foreach (var artistImage in artistImages)
{
artistImage.ArtistId = artistToMergeInto.Id;
}
}
var userArtists = DbContext.UserArtists.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistImages != null)
{
foreach (var userArtist in userArtists)
{
userArtist.ArtistId = artistToMergeInto.Id;
}
}
var artistTracks = DbContext.Tracks.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistTracks != null)
{
foreach (var artistTrack in artistTracks)
{
artistTrack.ArtistId = artistToMergeInto.Id;
}
}
var artistReleases = DbContext.Releases.Where(x => x.ArtistId == artistToMerge.Id).ToArray();
if (artistReleases != null)
{
foreach (var artistRelease in artistReleases)
{
// See if there is already a release by the same name for the artist to merge into, if so then merge releases
var artistToMergeHasRelease = DbContext.Releases.FirstOrDefault(x => x.ArtistId == artistToMerge.Id && x.Title == artistRelease.Title);
if (artistToMergeHasRelease != null)
{
await ReleaseService.MergeReleases(user, artistRelease, artistToMergeHasRelease, false);
}
else
{
artistRelease.ArtistId = artistToMerge.Id;
}
}
}
}
catch (Exception ex)
{
Logger.LogWarning(ex.ToString());
}
var artistFolder = artistToMerge.ArtistFileFolder(Configuration);
foreach (var release in DbContext.Releases.Include("Artist").Where(x => x.ArtistId == artistToMerge.Id).ToArray())
{
var originalReleaseFolder = release.ReleaseFileFolder(artistFolder);
await ReleaseService.UpdateRelease(user, release.Adapt<Release>(), originalReleaseFolder);
}
await Delete(user, artistToMerge);
result = true;
sw.Stop();
return new OperationResult<data.Artist>
{
Data = artistToMergeInto,
IsSuccess = result,
OperationTime = sw.ElapsedMilliseconds
};
}
public async Task<OperationResult<bool>> RefreshArtistMetadata(ApplicationUser user, Guid artistId)
{
SimpleContract.Requires<ArgumentOutOfRangeException>(artistId != Guid.Empty, "Invalid ArtistId");
var result = true;
var resultErrors = new List<Exception>();
var sw = new Stopwatch();
sw.Start();
try
{
var artist = DbContext.Artists.FirstOrDefault(x => x.RoadieId == artistId);
if (artist == null)
{
Logger.LogWarning("Unable To Find Artist [{0}]", artistId);
return new OperationResult<bool>();
}
OperationResult<data.Artist> artistSearch = null;
try
{
artistSearch = await ArtistLookupEngine.PerformMetaDataProvidersArtistSearch(new AudioMetaData
{
Artist = artist.Name
});
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
}
if (artistSearch.IsSuccess)
{
// Do metadata search for Artist like if new Artist then set some overides and merge
var mergeResult = await MergeArtists(user, artistSearch.Data, artist);
if (mergeResult.IsSuccess)
{
artist = mergeResult.Data;
await DbContext.SaveChangesAsync();
sw.Stop();
CacheManager.ClearRegion(artist.CacheRegion);
Logger.LogInformation("Scanned RefreshArtistMetadata [{0}], OperationTime [{1}]",
artist.ToString(), sw.ElapsedMilliseconds);
}
else
{
sw.Stop();
}
}
}
catch (Exception ex)
{
Logger.LogError(ex, ex.Serialize());
resultErrors.Add(ex);
}
return new OperationResult<bool>
{
Data = result,
IsSuccess = result,
Errors = resultErrors,
OperationTime = sw.ElapsedMilliseconds
};
}
public async Task<OperationResult<Image>> SetReleaseImageByUrl(ApplicationUser user, Guid id, string imageUrl)
{
return await SaveImageBytes(user, id, WebHelper.BytesForImageUrl(imageUrl));
}
public async Task<OperationResult<bool>> UpdateArtist(User user, Artist model)
public async Task<OperationResult<bool>> UpdateArtist(ApplicationUser user, Artist model)
{
var didRenameArtist = false;
var didChangeThumbnail = false;
@ -413,7 +649,7 @@ namespace Roadie.Api.Services
try
{
var now = DateTime.UtcNow;
var originalArtistFolder = artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder);
var originalArtistFolder = artist.ArtistFileFolder(Configuration);
var specialArtistName = model.Name.ToAlphanumericName();
var alt = new List<string>(model.AlternateNamesList);
if (!model.AlternateNamesList.Contains(specialArtistName, StringComparer.OrdinalIgnoreCase))
@ -440,7 +676,7 @@ namespace Roadie.Api.Services
artist.Tags = model.TagsList.ToDelimitedList();
artist.URLs = model.URLsList.ToDelimitedList();
var newArtistFolder = artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder);
var newArtistFolder = artist.ArtistFileFolder(Configuration);
if (!newArtistFolder.Equals(originalArtistFolder, StringComparison.OrdinalIgnoreCase))
{
didRenameArtist = true;
@ -516,8 +752,7 @@ namespace Roadie.Api.Services
{
ArtistId = artist.Id,
GenreId = g.Id,
Genre = g
Genre = g
});
}
}
@ -557,7 +792,6 @@ namespace Roadie.Api.Services
});
}
}
}
else if (model.AssociatedArtistsTokens == null || !model.AssociatedArtistsTokens.Any())
{
@ -565,7 +799,7 @@ namespace Roadie.Api.Services
DbContext.ArtistAssociations.RemoveRange(associatedArtists);
}
if(model.SimilarArtistsTokens != null && model.SimilarArtistsTokens.Any())
if (model.SimilarArtistsTokens != null && model.SimilarArtistsTokens.Any())
{
var similarArtists = DbContext.ArtistSimilar.Include(x => x.SimilarArtist)
.Where(x => x.ArtistId == artist.Id).ToList();
@ -622,7 +856,7 @@ namespace Roadie.Api.Services
}
}
await ScanArtistReleasesFolders(artist.RoadieId, Configuration.LibraryFolder, false);
await ScanArtistReleasesFolders(user, artist.RoadieId, Configuration.LibraryFolder, false);
}
CacheManager.ClearRegion(artist.CacheRegion);
@ -646,7 +880,7 @@ namespace Roadie.Api.Services
};
}
public async Task<OperationResult<Image>> UploadArtistImage(User user, Guid id, IFormFile file)
public async Task<OperationResult<Image>> UploadArtistImage(ApplicationUser user, Guid id, IFormFile file)
{
var bytes = new byte[0];
using (var ms = new MemoryStream())
@ -786,7 +1020,7 @@ namespace Roadie.Api.Services
result.Images = DbContext.Images.Where(x => x.ArtistId == artist.Id)
.Select(x => MakeFullsizeImage(x.RoadieId, x.Caption)).ToArray();
var artistFolder = artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder);
var artistFolder = artist.ArtistFileFolder(Configuration);
var artistImagesInFolder = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(artistFolder),
ImageType.ArtistSecondary, SearchOption.TopDirectoryOnly);
if (artistImagesInFolder.Any())
@ -858,52 +1092,52 @@ namespace Roadie.Api.Services
{
tsw.Restart();
var similarWithArtists = (from aa in DbContext.ArtistSimilar
join a in DbContext.Artists on aa.SimilarArtistId equals a.Id
where aa.ArtistId == artist.Id
select new ArtistList
{
DatabaseId = a.Id,
Id = a.RoadieId,
Artist = new DataToken
{
Text = a.Name,
Value = a.RoadieId.ToString()
},
Thumbnail = MakeArtistThumbnailImage(a.RoadieId),
Rating = a.Rating,
Rank = a.Rank,
CreatedDate = a.CreatedDate,
LastUpdated = a.LastUpdated,
LastPlayed = a.LastPlayed,
PlayedCount = a.PlayedCount,
ReleaseCount = a.ReleaseCount,
TrackCount = a.TrackCount,
SortName = a.SortName
}).ToArray();
join a in DbContext.Artists on aa.SimilarArtistId equals a.Id
where aa.ArtistId == artist.Id
select new ArtistList
{
DatabaseId = a.Id,
Id = a.RoadieId,
Artist = new DataToken
{
Text = a.Name,
Value = a.RoadieId.ToString()
},
Thumbnail = MakeArtistThumbnailImage(a.RoadieId),
Rating = a.Rating,
Rank = a.Rank,
CreatedDate = a.CreatedDate,
LastUpdated = a.LastUpdated,
LastPlayed = a.LastPlayed,
PlayedCount = a.PlayedCount,
ReleaseCount = a.ReleaseCount,
TrackCount = a.TrackCount,
SortName = a.SortName
}).ToArray();
var similarArtists = (from aa in DbContext.ArtistSimilar
join a in DbContext.Artists on aa.ArtistId equals a.Id
where aa.SimilarArtistId == artist.Id
select new ArtistList
{
DatabaseId = a.Id,
Id = a.RoadieId,
Artist = new DataToken
{
Text = a.Name,
Value = a.RoadieId.ToString()
},
Thumbnail = MakeArtistThumbnailImage(a.RoadieId),
Rating = a.Rating,
Rank = a.Rank,
CreatedDate = a.CreatedDate,
LastUpdated = a.LastUpdated,
LastPlayed = a.LastPlayed,
PlayedCount = a.PlayedCount,
ReleaseCount = a.ReleaseCount,
TrackCount = a.TrackCount,
SortName = a.SortName
}).ToArray();
join a in DbContext.Artists on aa.ArtistId equals a.Id
where aa.SimilarArtistId == artist.Id
select new ArtistList
{
DatabaseId = a.Id,
Id = a.RoadieId,
Artist = new DataToken
{
Text = a.Name,
Value = a.RoadieId.ToString()
},
Thumbnail = MakeArtistThumbnailImage(a.RoadieId),
Rating = a.Rating,
Rank = a.Rank,
CreatedDate = a.CreatedDate,
LastUpdated = a.LastUpdated,
LastPlayed = a.LastPlayed,
PlayedCount = a.PlayedCount,
ReleaseCount = a.ReleaseCount,
TrackCount = a.TrackCount,
SortName = a.SortName
}).ToArray();
result.SimilarArtists = similarWithArtists.Union(similarArtists, new ArtistListComparer())
.OrderBy(x => x.SortName);
result.SimilarArtistsTokens = result.SimilarArtists.Select(x => x.Artist).ToArray();
@ -1023,7 +1257,7 @@ namespace Roadie.Api.Services
};
}
private async Task<OperationResult<Image>> SaveImageBytes(User user, Guid id, byte[] imageBytes)
private async Task<OperationResult<Image>> SaveImageBytes(ApplicationUser user, Guid id, byte[] imageBytes)
{
var sw = new Stopwatch();
sw.Start();
@ -1040,7 +1274,7 @@ namespace Roadie.Api.Services
artist.Thumbnail = ImageHelper.ConvertToJpegFormat(artist.Thumbnail);
// Ensure artist folder exists
var artistFolder = artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder);
var artistFolder = artist.ArtistFileFolder(Configuration);
if (!Directory.Exists(artistFolder))
{
Directory.CreateDirectory(artistFolder);
@ -1079,8 +1313,7 @@ namespace Roadie.Api.Services
};
}
private async Task<OperationResult<bool>> ScanArtistReleasesFolders(Guid artistId, string destinationFolder,
bool doJustInfo)
public async Task<OperationResult<bool>> ScanArtistReleasesFolders(ApplicationUser user, Guid artistId, string destinationFolder, bool doJustInfo)
{
SimpleContract.Requires<ArgumentOutOfRangeException>(artistId != Guid.Empty, "Invalid ArtistId");
@ -1101,15 +1334,14 @@ namespace Roadie.Api.Services
}
var releaseScannedCount = 0;
var artistFolder = artist.ArtistFileFolder(Configuration, destinationFolder);
var artistFolder = artist.ArtistFileFolder(Configuration);
var scannedArtistFolders = new List<string>();
// Scan known releases for changes
if (artist.Releases != null)
foreach (var release in artist.Releases)
try
{
result = result && (await ReleaseFactory.ScanReleaseFolder(Guid.Empty, destinationFolder,
doJustInfo, release)).Data;
result = result && (await ReleaseService.ScanReleaseFolder(user, Guid.Empty, doJustInfo, release)).Data;
releaseScannedCount++;
scannedArtistFolders.Add(release.ReleaseFileFolder(artistFolder));
}
@ -1119,17 +1351,18 @@ namespace Roadie.Api.Services
}
// Any folder found in Artist folder not already scanned scan
var folderProcessor = new FolderProcessor(Configuration, HttpEncoder, destinationFolder, DbContext,
CacheManager, Logger, ArtistLookupEngine, ArtistFactory, ReleaseFactory, ImageFactory,
ReleaseLookupEngine, AudioMetaDataHelper);
var nonReleaseFolders = from d in Directory.EnumerateDirectories(artistFolder)
where !(from r in scannedArtistFolders select r).Contains(d)
orderby d
select d;
foreach (var folder in nonReleaseFolders)
await folderProcessor.Process(new DirectoryInfo(folder), doJustInfo);
if (!doJustInfo) FolderProcessor.DeleteEmptyFolders(new DirectoryInfo(artistFolder), Logger);
{
await FileDirectoryProcessorService.Process(user, new DirectoryInfo(folder), doJustInfo);
}
if (!doJustInfo)
{
Services.FileDirectoryProcessorService.DeleteEmptyFolders(new DirectoryInfo(artistFolder), Logger);
}
// Always update artist image if artist image is found on an artist rescan
var imageFiles = ImageHelper.ImageFilesInFolder(artistFolder, SearchOption.AllDirectories);
if (imageFiles != null && imageFiles.Any())

View file

@ -0,0 +1,165 @@
using Microsoft.Extensions.Logging;
using Roadie.Library;
using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Extensions;
using Roadie.Library.FilePlugins;
using Roadie.Library.Identity;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.Processors;
using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using data = Roadie.Library.Data;
namespace Roadie.Api.Services
{
public class FileDirectoryProcessorService : ServiceBase, IFileDirectoryProcessorService
{
private List<int> _addedArtistIds = new List<int>();
private List<int> _addedReleaseIds = new List<int>();
private List<int> _addedTrackIds = new List<int>();
public IEnumerable<int> AddedArtistIds => _addedArtistIds.Distinct();
public IEnumerable<int> AddedReleaseIds => _addedReleaseIds.Distinct();
public IEnumerable<int> AddedTrackIds => _addedTrackIds.Distinct();
public int? ProcessLimit { get; set; }
private IArtistLookupEngine ArtistLookupEngine { get; }
private IAudioMetaDataHelper AudioMetaDataHelper { get; }
private IReleaseLookupEngine ReleaseLookupEngine { get; }
private IReleaseService ReleaseService { get; }
private IFileProcessor FileProcessor { get; }
public FileDirectoryProcessorService(IRoadieSettings configuration,
IHttpEncoder httpEncoder,
IHttpContext httpContext,
data.IRoadieDbContext context,
ICacheManager cacheManager,
ILogger<FileDirectoryProcessorService> logger,
IArtistLookupEngine artistLookupEngine,
IFileProcessor fileProcessor,
IReleaseLookupEngine releaseLookupEngine,
IAudioMetaDataHelper audioMetaDataHelper,
IReleaseService releaseService)
: base(configuration, httpEncoder, context, cacheManager, logger, httpContext)
{
ArtistLookupEngine = artistLookupEngine;
AudioMetaDataHelper = audioMetaDataHelper;
ReleaseLookupEngine = releaseLookupEngine;
ReleaseService = releaseService;
FileProcessor = fileProcessor;
}
public static OperationResult<bool> DeleteEmptyFolders(DirectoryInfo processingFolder, ILogger logger)
{
var result = new OperationResult<bool>();
try
{
result.IsSuccess = FolderPathHelper.DeleteEmptyFolders(processingFolder);
}
catch (Exception ex)
{
logger.LogError(ex, string.Format("Error Deleting Empty Folder [{0}] Error [{1}]", processingFolder.FullName, ex.Serialize()));
}
return result;
}
public async Task<OperationResult<bool>> Process(ApplicationUser user, DirectoryInfo folder, bool doJustInfo, int? submissionId = null)
{
var sw = new Stopwatch();
sw.Start();
await PreProcessFolder(folder, doJustInfo);
var processedFiles = 0;
var pluginResultInfos = new List<PluginResultInfo>();
var errors = new List<string>();
_addedArtistIds.Clear();
_addedReleaseIds.Clear();
_addedTrackIds.Clear();
FileProcessor.SubmissionId = submissionId;
foreach (var file in Directory.EnumerateFiles(folder.FullName, "*.*", SearchOption.AllDirectories)
.ToArray())
{
var operation = await FileProcessor.Process(file, doJustInfo);
if (operation != null && operation.AdditionalData != null &&
operation.AdditionalData.ContainsKey(PluginResultInfo.AdditionalDataKeyPluginResultInfo))
{
pluginResultInfos.Add(operation.AdditionalData[PluginResultInfo.AdditionalDataKeyPluginResultInfo] as PluginResultInfo);
processedFiles++;
}
if (ProcessLimit.HasValue && processedFiles > ProcessLimit.Value) break;
}
await PostProcessFolder(user, folder, pluginResultInfos, doJustInfo);
sw.Stop();
_addedArtistIds.AddRange(ArtistLookupEngine.AddedArtistIds);
_addedReleaseIds.AddRange(ReleaseLookupEngine.AddedReleaseIds);
_addedTrackIds.AddRange(ReleaseLookupEngine.AddedTrackIds);
Logger.LogInformation("** Completed! Processed Folder [{0}]: Processed Files [{1}] : Elapsed Time [{2}]", folder.FullName, processedFiles, sw.Elapsed);
return new OperationResult<bool>
{
IsSuccess = !errors.Any(),
OperationTime = sw.ElapsedMilliseconds
};
}
/// <summary>
/// Perform any operations to the given folder and the plugin results after processing
/// </summary>
private async Task<bool> PostProcessFolder(ApplicationUser user, DirectoryInfo inboundFolder, IEnumerable<PluginResultInfo> pluginResults, bool doJustInfo)
{
SimpleContract.Requires<ArgumentNullException>(inboundFolder != null, "Invalid InboundFolder");
if (pluginResults != null)
{
foreach (var releasesInfo in pluginResults.GroupBy(x => x.ReleaseId).Select(x => x.First()))
{
await ReleaseService.ScanReleaseFolder(user, releasesInfo.ReleaseId, doJustInfo);
_addedTrackIds.AddRange(ReleaseService.AddedTrackIds);
}
}
if (!doJustInfo)
{
var fileExtensionsToDelete = Configuration.FileExtensionsToDelete ?? new string[0];
if (fileExtensionsToDelete.Any())
foreach (var fileInFolder in inboundFolder.GetFiles("*.*", SearchOption.AllDirectories))
{
if (fileExtensionsToDelete.Any(x => x.Equals(fileInFolder.Extension, StringComparison.OrdinalIgnoreCase)))
{
if (!doJustInfo)
{
fileInFolder.Delete();
Logger.LogInformation("x Deleted File [{0}], Was found in in FileExtensionsToDelete",
fileInFolder.Name);
}
}
}
DeleteEmptyFolders(inboundFolder, Logger);
}
return true;
}
/// <summary>
/// Perform any operations to the given folder before processing
/// </summary>
private Task<bool> PreProcessFolder(DirectoryInfo inboundFolder, bool doJustInfo = false)
{
return Task.FromResult(true);
}
}
}

View file

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Http;
using Roadie.Library;
using Roadie.Library.Identity;
using Roadie.Library.Models;
using Roadie.Library.Models.Pagination;
using Roadie.Library.Models.Users;
@ -11,17 +12,22 @@ namespace Roadie.Api.Services
{
public interface IArtistService
{
Task<OperationResult<Artist>> ById(User roadieUser, Guid id, IEnumerable<string> includes);
Task<OperationResult<Artist>> ById(User user, Guid id, IEnumerable<string> includes);
Task<PagedResult<ArtistList>> List(User roadieUser, PagedRequest request, bool? doRandomize = false,
bool? onlyIncludeWithReleases = true);
Task<OperationResult<bool>> Delete(ApplicationUser user, Library.Data.Artist Artist);
Task<OperationResult<bool>> MergeArtists(User user, Guid artistToMergeId, Guid artistToMergeIntoId);
Task<PagedResult<ArtistList>> List(User user, PagedRequest request, bool? doRandomize = false, bool? onlyIncludeWithReleases = true);
Task<OperationResult<Image>> SetReleaseImageByUrl(User user, Guid id, string imageUrl);
Task<OperationResult<bool>> MergeArtists(ApplicationUser user, Guid artistToMergeId, Guid artistToMergeIntoId);
Task<OperationResult<bool>> UpdateArtist(User user, Artist artist);
Task<OperationResult<bool>> RefreshArtistMetadata(ApplicationUser user, Guid ArtistId);
Task<OperationResult<Image>> UploadArtistImage(User user, Guid id, IFormFile file);
Task<OperationResult<bool>> ScanArtistReleasesFolders(ApplicationUser user, Guid artistId, string destinationFolder, bool doJustInfo);
Task<OperationResult<Image>> SetReleaseImageByUrl(ApplicationUser user, Guid id, string imageUrl);
Task<OperationResult<bool>> UpdateArtist(ApplicationUser user, Artist artist);
Task<OperationResult<Image>> UploadArtistImage(ApplicationUser user, Guid id, IFormFile file);
}
}

View file

@ -0,0 +1,19 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Roadie.Library;
using Roadie.Library.Identity;
namespace Roadie.Api.Services
{
public interface IFileDirectoryProcessorService
{
IEnumerable<int> AddedArtistIds { get; }
IEnumerable<int> AddedReleaseIds { get; }
IEnumerable<int> AddedTrackIds { get; }
int? ProcessLimit { get; set; }
Task<OperationResult<bool>> Process(ApplicationUser user, DirectoryInfo folder, bool doJustInfo, int? submissionId = null);
}
}

View file

@ -1,5 +1,6 @@
using Microsoft.Net.Http.Headers;
using Roadie.Library;
using Roadie.Library.Identity;
using Roadie.Library.Models;
using Roadie.Library.Models.Users;
using Roadie.Library.SearchEngines.Imaging;
@ -11,31 +12,27 @@ namespace Roadie.Api.Services
{
public interface IImageService
{
Task<FileOperationResult<Image>>
ArtistImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);
string Referrer { get; set; }
Task<FileOperationResult<Image>> ArtistSecondaryImage(Guid id, int imageId, int? width, int? height,
EntityTagHeaderValue etag = null);
string RequestIp { get; set; }
Task<FileOperationResult<Image>> ArtistImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> ArtistSecondaryImage(Guid id, int imageId, int? width, int? height, EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> ById(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> CollectionImage(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> CollectionImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);
Task<OperationResult<bool>> Delete(User user, Guid id);
Task<OperationResult<IEnumerable<ImageSearchResult>>> ImageProvidersSearch(string query);
Task<OperationResult<bool>> Delete(ApplicationUser user, Guid id);
Task<FileOperationResult<Image>> LabelImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> PlaylistImage(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> PlaylistImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> ReleaseImage(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> ReleaseImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> ReleaseSecondaryImage(Guid id, int imageId, int? width, int? height,
EntityTagHeaderValue etag = null);
Task<FileOperationResult<Image>> ReleaseSecondaryImage(Guid id, int imageId, int? width, int? height, EntityTagHeaderValue etag = null);
Task<OperationResult<IEnumerable<ImageSearchResult>>> Search(string query, int resultsCount = 10);

View file

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Http;
using Roadie.Library;
using Roadie.Library.Identity;
using Roadie.Library.Models;
using Roadie.Library.Models.Pagination;
using Roadie.Library.Models.Releases;
@ -12,20 +13,28 @@ namespace Roadie.Api.Services
{
public interface IReleaseService
{
IEnumerable<int> AddedTrackIds { get; }
Task<OperationResult<Release>> ById(User roadieUser, Guid id, IEnumerable<string> includes = null);
Task<PagedResult<ReleaseList>> List(User user, PagedRequest request, bool? doRandomize = false,
IEnumerable<string> includes = null);
Task<OperationResult<bool>> Delete(ApplicationUser user, Library.Data.Release release, bool doDeleteFiles = false, bool doUpdateArtistCounts = true);
Task<OperationResult<bool>> MergeReleases(User user, Guid releaseToMergeId, Guid releaseToMergeIntoId,
bool addAsMedia);
Task<OperationResult<bool>> DeleteReleases(ApplicationUser user, IEnumerable<Guid> releaseIds, bool doDeleteFiles = false);
Task<PagedResult<ReleaseList>> List(User user, PagedRequest request, bool? doRandomize = false, IEnumerable<string> includes = null);
Task<OperationResult<bool>> MergeReleases(ApplicationUser user, Guid releaseToMergeId, Guid releaseToMergeIntoId, bool addAsMedia);
Task<OperationResult<bool>> MergeReleases(ApplicationUser user, Library.Data.Release releaseToMerge, Library.Data.Release releaseToMergeInto, bool addAsMedia);
Task<FileOperationResult<byte[]>> ReleaseZipped(User roadieUser, Guid id);
Task<OperationResult<Image>> SetReleaseImageByUrl(User user, Guid id, string imageUrl);
Task<OperationResult<bool>> ScanReleaseFolder(ApplicationUser user, Guid releaseId, bool doJustInfo, Library.Data.Release releaseToScan = null);
Task<OperationResult<bool>> UpdateRelease(User user, Release release);
Task<OperationResult<Image>> SetReleaseImageByUrl(ApplicationUser user, Guid id, string imageUrl);
Task<OperationResult<Image>> UploadReleaseImage(User user, Guid id, IFormFile file);
Task<OperationResult<bool>> UpdateRelease(ApplicationUser user, Release release, string originalReleaseFolder = null);
Task<OperationResult<Image>> UploadReleaseImage(ApplicationUser user, Guid id, IFormFile file);
}
}

View file

@ -7,7 +7,7 @@ namespace Roadie.Api.Services
{
public interface IStatisticsService
{
OperationResult<LibraryStats> LibraryStatistics();
Task<OperationResult<LibraryStats>> LibraryStatistics();
Task<OperationResult<IEnumerable<DateAndCount>>> ReleasesByDate();
}

View file

@ -7,8 +7,10 @@ using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Encoding;
using Roadie.Library.Enums;
using Roadie.Library.Extensions;
using Roadie.Library.Identity;
using Roadie.Library.Imaging;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.Models;
using Roadie.Library.Models.Users;
using Roadie.Library.SearchEngines.Imaging;
@ -25,32 +27,29 @@ namespace Roadie.Api.Services
{
public class ImageService : ServiceBase, IImageService
{
private IImageSearchEngine BingSearchEngine { get; }
private IDefaultNotFoundImages DefaultNotFoundImages { get; }
private IImageSearchEngine ITunesSearchEngine { get; }
private IImageSearchManager ImageSearchManager { get; }
private string Referrer { get; }
public string Referrer { get; set; }
private string RequestIp { get; }
public string RequestIp { get; set; }
public ImageService(IRoadieSettings configuration,
IHttpEncoder httpEncoder,
IHttpEncoder httpEncoder,
IHttpContext httpContext,
data.IRoadieDbContext context,
ICacheManager cacheManager,
ILogger<ImageService> logger,
IDefaultNotFoundImages defaultNotFoundImages)
IDefaultNotFoundImages defaultNotFoundImages,
IImageSearchManager imageSearchManager)
: base(configuration, httpEncoder, context, cacheManager, logger, httpContext)
{
DefaultNotFoundImages = defaultNotFoundImages;
BingSearchEngine = new BingImageSearchEngine(configuration, logger, RequestIp, Referrer);
ITunesSearchEngine = new ITunesSearchEngine(configuration, cacheManager, logger, RequestIp, Referrer);
ImageSearchManager = imageSearchManager;
}
public async Task<FileOperationResult<Image>> ArtistImage(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null)
public async Task<FileOperationResult<Image>> ArtistImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("ArtistImage",
data.Artist.CacheRegionUrn(id),
@ -61,8 +60,7 @@ namespace Roadie.Api.Services
etag);
}
public async Task<FileOperationResult<Image>> ArtistSecondaryImage(Guid id, int imageId, int? width,
int? height, EntityTagHeaderValue etag = null)
public async Task<FileOperationResult<Image>> ArtistSecondaryImage(Guid id, int imageId, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation($"ArtistSecondaryThumbnail-{imageId}",
data.Release.CacheRegionUrn(id),
@ -73,8 +71,7 @@ namespace Roadie.Api.Services
etag);
}
public async Task<FileOperationResult<Image>> ById(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null)
public async Task<FileOperationResult<Image>> ById(Guid id, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("ImageById",
data.Image.CacheRegionUrn(id),
@ -85,8 +82,7 @@ namespace Roadie.Api.Services
etag);
}
public async Task<FileOperationResult<Image>> CollectionImage(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null)
public async Task<FileOperationResult<Image>> CollectionImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("CollectionThumbnail",
data.Collection.CacheRegionUrn(id),
@ -97,7 +93,7 @@ namespace Roadie.Api.Services
etag);
}
public async Task<OperationResult<bool>> Delete(User user, Guid id)
public async Task<OperationResult<bool>> Delete(ApplicationUser user, Guid id)
{
var sw = Stopwatch.StartNew();
var image = DbContext.Images
@ -105,10 +101,10 @@ namespace Roadie.Api.Services
.Include("Artist")
.FirstOrDefault(x => x.RoadieId == id);
if (image == null) return new OperationResult<bool>(true, string.Format("Image Not Found [{0}]", id));
if (image.ArtistId.HasValue) CacheManager.ClearRegion(data.Artist.CacheRegionUrn(image.Artist.RoadieId));
if (image.ReleaseId.HasValue) CacheManager.ClearRegion(data.Release.CacheRegionUrn(image.Release.RoadieId));
DbContext.Images.Remove(image);
await DbContext.SaveChangesAsync();
if (image.ArtistId.HasValue) CacheManager.ClearRegion(data.Artist.CacheRegionUrn(image.Artist.RoadieId));
if (image.ReleaseId.HasValue) CacheManager.ClearRegion(data.Release.CacheRegionUrn(image.Release.RoadieId));
CacheManager.ClearRegion(data.Image.CacheRegionUrn(id));
Logger.LogInformation($"Deleted Image [{id}], By User [{user}]");
sw.Stop();
@ -120,7 +116,52 @@ namespace Roadie.Api.Services
};
}
public async Task<OperationResult<IEnumerable<ImageSearchResult>>> ImageProvidersSearch(string query)
public async Task<FileOperationResult<Image>> LabelImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("LabelThumbnail",
data.Label.CacheRegionUrn(id),
id,
width,
height,
async () => { return await LabelImageAction(id, etag); },
etag);
}
public async Task<FileOperationResult<Image>> PlaylistImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("PlaylistThumbnail",
data.Playlist.CacheRegionUrn(id),
id,
width,
height,
async () => { return await PlaylistImageAction(id, etag); },
etag);
}
public async Task<FileOperationResult<Image>> ReleaseImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("ReleaseThumbnail",
data.Release.CacheRegionUrn(id),
id,
width,
height,
async () => { return await ReleaseImageAction(id, etag); },
etag);
}
public async Task<FileOperationResult<Image>> ReleaseSecondaryImage(Guid id, int imageId, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation($"ReleaseSecondaryThumbnail-{imageId}",
data.Release.CacheRegionUrn(id),
id,
width,
height,
async () => { return await ReleaseSecondaryImageAction(id, imageId, etag); },
etag);
}
public async Task<OperationResult<IEnumerable<ImageSearchResult>>> Search(string query, int resultsCount = 10)
{
var sw = new Stopwatch();
sw.Start();
@ -128,8 +169,7 @@ namespace Roadie.Api.Services
IEnumerable<ImageSearchResult> searchResults = null;
try
{
var manager = new ImageSearchManager(Configuration, CacheManager, Logger);
searchResults = await manager.ImageSearch(query);
searchResults = await ImageSearchManager.ImageSearch(query);
}
catch (Exception ex)
{
@ -145,83 +185,10 @@ namespace Roadie.Api.Services
OperationTime = sw.ElapsedMilliseconds,
Errors = errors
};
}
public async Task<FileOperationResult<Image>> LabelImage(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("LabelThumbnail",
data.Label.CacheRegionUrn(id),
id,
width,
height,
async () => { return await LabelImageAction(id, etag); },
etag);
}
public async Task<FileOperationResult<Image>> PlaylistImage(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("PlaylistThumbnail",
data.Playlist.CacheRegionUrn(id),
id,
width,
height,
async () => { return await PlaylistImageAction(id, etag); },
etag);
}
public async Task<FileOperationResult<Image>> ReleaseImage(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("ReleaseThumbnail",
data.Release.CacheRegionUrn(id),
id,
width,
height,
async () => { return await ReleaseImageAction(id, etag); },
etag);
}
public async Task<FileOperationResult<Image>> ReleaseSecondaryImage(Guid id, int imageId, int? width,
int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation($"ReleaseSecondaryThumbnail-{imageId}",
data.Release.CacheRegionUrn(id),
id,
width,
height,
async () => { return await ReleaseSecondaryImageAction(id, imageId, etag); },
etag);
}
public async Task<OperationResult<IEnumerable<ImageSearchResult>>> Search(string query, int resultsCount = 10)
{
var sw = Stopwatch.StartNew();
var result = new List<ImageSearchResult>();
if (WebHelper.IsStringUrl(query))
{
var s = ImageHelper.ImageSearchResultForImageUrl(query);
if (s != null) result.Add(s);
}
var bingResults = await BingSearchEngine.PerformImageSearch(query, resultsCount);
if (bingResults != null) result.AddRange(bingResults);
var iTunesResults = await ITunesSearchEngine.PerformImageSearch(query, resultsCount);
if (iTunesResults != null) result.AddRange(iTunesResults);
sw.Stop();
return new OperationResult<IEnumerable<ImageSearchResult>>
{
IsSuccess = true,
Data = result,
OperationTime = sw.ElapsedMilliseconds
};
}
public async Task<FileOperationResult<Image>> TrackImage(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null)
public async Task<FileOperationResult<Image>> TrackImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("TrackThumbnail",
data.Track.CacheRegionUrn(id),
@ -232,8 +199,7 @@ namespace Roadie.Api.Services
etag);
}
public async Task<FileOperationResult<Image>> UserImage(Guid id, int? width, int? height,
EntityTagHeaderValue etag = null)
public async Task<FileOperationResult<Image>> UserImage(Guid id, int? width, int? height, EntityTagHeaderValue etag = null)
{
return await GetImageFileOperation("UserById",
ApplicationUser.CacheRegionUrn(id),
@ -257,7 +223,7 @@ namespace Roadie.Api.Services
try
{
// See if artist images exists in artist folder
artistFolder = artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder);
artistFolder = artist.ArtistFileFolder(Configuration);
if (!Directory.Exists(artistFolder))
{
Logger.LogWarning($"Artist Folder [{artistFolder}], Not Found For Artist `{artist}`");
@ -292,8 +258,7 @@ namespace Roadie.Api.Services
return Task.FromResult(new FileOperationResult<Image>(OperationMessages.ErrorOccured));
}
private Task<FileOperationResult<Image>> ArtistSecondaryImageAction(Guid id, int imageId,
EntityTagHeaderValue etag = null)
private Task<FileOperationResult<Image>> ArtistSecondaryImageAction(Guid id, int imageId, EntityTagHeaderValue etag = null)
{
try
{
@ -306,7 +271,7 @@ namespace Roadie.Api.Services
try
{
// See if cover art file exists in release folder
artistFolder = artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder);
artistFolder = artist.ArtistFileFolder(Configuration);
if (!Directory.Exists(artistFolder))
{
Logger.LogWarning($"Artist Folder [{artistFolder}], Not Found For Artist `{artist}`");
@ -504,15 +469,16 @@ namespace Roadie.Api.Services
{
var release = GetRelease(id);
if (release == null)
return Task.FromResult(new FileOperationResult<Image>(true,
string.Format("Release Not Found [{0}]", id)));
{
return Task.FromResult(new FileOperationResult<Image>(true, string.Format("Release Not Found [{0}]", id)));
}
byte[] imageBytes = null;
string artistFolder = null;
string releaseFolder = null;
try
{
// See if cover art file exists in release folder
artistFolder = release.Artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder);
artistFolder = release.Artist.ArtistFileFolder(Configuration);
if (!Directory.Exists(artistFolder))
{
Logger.LogWarning($"Artist Folder [{artistFolder}], Not Found For Artist `{release.Artist}`");
@ -526,11 +492,11 @@ namespace Roadie.Api.Services
}
else
{
var releaseCoverFiles =
ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(releaseFolder),
ImageType.Release);
var releaseCoverFiles = ImageHelper.FindImageTypeInDirectory(new DirectoryInfo(releaseFolder), ImageType.Release);
if (releaseCoverFiles.Any())
{
imageBytes = File.ReadAllBytes(releaseCoverFiles.First().FullName);
}
}
}
}
@ -573,7 +539,7 @@ namespace Roadie.Api.Services
try
{
// See if cover art file exists in release folder
artistFolder = release.Artist.ArtistFileFolder(Configuration, Configuration.LibraryFolder);
artistFolder = release.Artist.ArtistFileFolder(Configuration);
if (!Directory.Exists(artistFolder))
{
Logger.LogWarning($"Artist Folder [{artistFolder}], Not Found For Artist `{release.Artist}`");
@ -627,7 +593,7 @@ namespace Roadie.Api.Services
return new FileOperationResult<Image>(true, string.Format("Track Not Found [{0}]", id));
var imageBytes = track.Thumbnail;
var trackThumbnailImages = ImageHelper.FindImageTypeInDirectory(
new DirectoryInfo(track.PathToTrackThumbnail(Configuration, Configuration.LibraryFolder)),
new DirectoryInfo(track.PathToTrackThumbnail(Configuration)),
ImageType.Track, SearchOption.TopDirectoryOnly);
if (trackThumbnailImages.Any()) imageBytes = File.ReadAllBytes(trackThumbnailImages.First().FullName);
var image = new data.Image

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,7 @@
<PackageReference Include="Hashids.net" Version="1.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="2.2.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.5.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.18" />
</ItemGroup>

View file

@ -27,7 +27,7 @@ namespace Roadie.Api.Services
{
}
public OperationResult<LibraryStats> LibraryStatistics()
public Task<OperationResult<LibraryStats>> LibraryStatistics()
{
LibraryStats result = null;
var sw = new Stopwatch();
@ -58,12 +58,12 @@ namespace Roadie.Api.Services
Logger.LogError(ex);
}
return new OperationResult<LibraryStats>
return Task.FromResult(new OperationResult<LibraryStats>
{
IsSuccess = result != null,
OperationTime = sw.ElapsedMilliseconds,
IsSuccess = result != null,
Data = result
};
});
}
public Task<OperationResult<IEnumerable<DateAndCount>>> ReleasesByDate()

View file

@ -681,7 +681,7 @@ namespace Roadie.Api.Services
string trackPath = null;
try
{
trackPath = track.PathToTrack(Configuration, Configuration.LibraryFolder);
trackPath = track.PathToTrack(Configuration);
}
catch (Exception ex)
{
@ -712,7 +712,7 @@ namespace Roadie.Api.Services
try
{
trackPath = track.PathToTrack(Configuration, Configuration.LibraryFolder);
trackPath = track.PathToTrack(Configuration);
}
catch (Exception ex)
{
@ -834,7 +834,7 @@ namespace Roadie.Api.Services
track.Thumbnail = ImageHelper.ConvertToJpegFormat(trackImage);
// Save unaltered image to cover file
var trackThumbnailName = track.PathToTrackThumbnail(Configuration, Configuration.LibraryFolder);
var trackThumbnailName = track.PathToTrackThumbnail(Configuration);
File.WriteAllBytes(trackThumbnailName, track.Thumbnail);
// Resize to store in database as thumbnail

View file

@ -35,16 +35,17 @@ namespace Roadie.Api.Services
public UserService(IRoadieSettings configuration,
IHttpEncoder httpEncoder,
IHttpContext httpContext,
data.IRoadieDbContext context,
ICacheManager cacheManager,
ILogger<ArtistService> logger,
UserManager<ApplicationUser> userManager
IHttpContext httpContext,
data.IRoadieDbContext context,
ICacheManager cacheManager,
ILogger<ArtistService> logger,
UserManager<ApplicationUser> userManager,
ILastFmHelper lastFmHelper
)
: base(configuration, httpEncoder, context, cacheManager, logger, httpContext)
{
UserManager = userManager;
LastFmHelper = new LastFmHelper(Configuration, CacheManager, Logger, context, httpEncoder);
LastFmHelper = lastFmHelper;
;
}

View file

@ -78,7 +78,7 @@ namespace Roadie.Api.Controllers
public async Task<IActionResult> MergeArtists(Guid artistToMergeId, Guid artistToMergeIntoId)
{
var result =
await ArtistService.MergeArtists(await CurrentUserModel(), artistToMergeId, artistToMergeIntoId);
await ArtistService.MergeArtists(await UserManager.GetUserAsync(User), artistToMergeId, artistToMergeIntoId);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);
@ -91,7 +91,7 @@ namespace Roadie.Api.Controllers
public async Task<IActionResult> SetArtistImageByUrl(Guid id, string imageUrl)
{
var result =
await ArtistService.SetReleaseImageByUrl(await CurrentUserModel(), id, HttpUtility.UrlDecode(imageUrl));
await ArtistService.SetReleaseImageByUrl(await UserManager.GetUserAsync(User), id, HttpUtility.UrlDecode(imageUrl));
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);
@ -104,7 +104,7 @@ namespace Roadie.Api.Controllers
public async Task<IActionResult> Update(models.Artist artist)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
var result = await ArtistService.UpdateArtist(await CurrentUserModel(), artist);
var result = await ArtistService.UpdateArtist(await UserManager.GetUserAsync(User), artist);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);
@ -116,7 +116,7 @@ namespace Roadie.Api.Controllers
[Authorize(Policy = "Editor")]
public async Task<IActionResult> UploadImage(Guid id, IFormFile file)
{
var result = await ArtistService.UploadArtistImage(await CurrentUserModel(), id, file);
var result = await ArtistService.UploadArtistImage(await UserManager.GetUserAsync(User), id, file);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);

View file

@ -81,7 +81,7 @@ namespace Roadie.Api.Controllers
[ProducesResponseType(200)]
public async Task<IActionResult> Delete(Guid id)
{
var result = await ImageService.Delete(await CurrentUserModel(), id);
var result = await ImageService.Delete(await UserManager.GetUserAsync(User), id);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);

View file

@ -76,7 +76,7 @@ namespace Roadie.Api.Controllers
[Authorize(Policy = "Editor")]
public async Task<IActionResult> MergeReleases(Guid releaseToMergeId, Guid releaseToMergeIntoId, bool addAsMedia)
{
var result = await ReleaseService.MergeReleases(await CurrentUserModel(), releaseToMergeId, releaseToMergeIntoId, addAsMedia);
var result = await ReleaseService.MergeReleases(await UserManager.GetUserAsync(User), releaseToMergeId, releaseToMergeIntoId, addAsMedia);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);
@ -88,7 +88,7 @@ namespace Roadie.Api.Controllers
[Authorize(Policy = "Editor")]
public async Task<IActionResult> SetReleaseImageByUrl(Guid id, string imageUrl)
{
var result = await ReleaseService.SetReleaseImageByUrl(await CurrentUserModel(), id, HttpUtility.UrlDecode(imageUrl));
var result = await ReleaseService.SetReleaseImageByUrl(await UserManager.GetUserAsync(User), id, HttpUtility.UrlDecode(imageUrl));
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);
@ -101,7 +101,7 @@ namespace Roadie.Api.Controllers
public async Task<IActionResult> Update(Release release)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
var result = await ReleaseService.UpdateRelease(await CurrentUserModel(), release);
var result = await ReleaseService.UpdateRelease(await UserManager.GetUserAsync(User), release);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);
@ -113,7 +113,7 @@ namespace Roadie.Api.Controllers
[Authorize(Policy = "Editor")]
public async Task<IActionResult> UploadImage(Guid id, IFormFile file)
{
var result = await ReleaseService.UploadReleaseImage(await CurrentUserModel(), id, file);
var result = await ReleaseService.UploadReleaseImage(await UserManager.GetUserAsync(User), id, file);
if (result == null || result.IsNotFoundResult) return NotFound();
if (!result.IsSuccess) return StatusCode((int)HttpStatusCode.InternalServerError);
return Ok(result);

View file

@ -36,10 +36,12 @@ namespace Roadie.Api.Controllers
var proc = Process.GetCurrentProcess();
var mem = proc.WorkingSet64;
var cpu = proc.TotalProcessorTime;
var messages = new List<string>();
messages.Add("▜ Memory Information: ");
messages.Add(string.Format("My process used working set {0:n3} K of working set and CPU {1:n} msec",
mem / 1024.0, cpu.TotalMilliseconds));
var messages = new List<string>
{
"▜ Memory Information: ",
string.Format("My process used working set {0:n3} K of working set and CPU {1:n} msec",
mem / 1024.0, cpu.TotalMilliseconds)
};
foreach (var aProc in Process.GetProcesses())
messages.Add(string.Format("Proc {0,30} CPU {1,-20:n} msec", aProc.ProcessName,
cpu.TotalMilliseconds));
@ -50,24 +52,15 @@ namespace Roadie.Api.Controllers
[HttpGet("library")]
[ProducesResponseType(200)]
public async Task<IActionResult> Library()
{
return Ok(StatisticsService.LibraryStatistics());
}
public async Task<IActionResult> Library() => Ok(await StatisticsService.LibraryStatistics());
[HttpGet("ping")]
[ProducesResponseType(200)]
[AllowAnonymous]
public IActionResult Ping()
{
return Ok("pong");
}
public IActionResult Ping() => Ok("pong");
[HttpGet("releasesByDate")]
[ProducesResponseType(200)]
public async Task<IActionResult> ReleasesByDate()
{
return Ok(await StatisticsService.ReleasesByDate());
}
public async Task<IActionResult> ReleasesByDate() => Ok(await StatisticsService.ReleasesByDate());
}
}

View file

@ -2,6 +2,8 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Serilog;
using Serilog.Events;
using Serilog.Sinks.RollingFileAlternate;
using System;
using System.Diagnostics;
using System.IO;
@ -23,6 +25,7 @@ namespace Roadie.Api
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(Configuration)
.WriteTo.RollingFileAlternate("logs", "errors", LogEventLevel.Error)
.CreateLogger();
try
@ -72,14 +75,8 @@ namespace Roadie.Api
public class LoggingTraceListener : TraceListener
{
public override void Write(string message)
{
Log.Verbose(message);
}
public override void Write(string message) => Log.Verbose(message);
public override void WriteLine(string message)
{
Log.Verbose(message);
}
public override void WriteLine(string message) => Log.Verbose(message);
}
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
@ -24,10 +24,15 @@
<ItemGroup>
<PackageReference Include="BCrypt-Core" Version="2.0.0" />
<PackageReference Include="Mapster" Version="4.1.0" />
<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.10.0" />
<PackageReference Include="Microsoft.AspNet.SignalR" Version="2.4.1" />
<PackageReference Include="Microsoft.AspNetCore.All" />
<PackageReference Include="Microsoft.AspNetCore.OData" Version="7.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="3.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.1.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
<PackageReference Include="Serilog.Exceptions" Version="5.3.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />

View file

@ -1,4 +1,5 @@
using Mapster;
#region Usings
using Mapster;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@ -21,13 +22,25 @@ using Roadie.Library.Caching;
using Roadie.Library.Configuration;
using Roadie.Library.Data;
using Roadie.Library.Encoding;
using Roadie.Library.Engines;
using Roadie.Library.Identity;
using Roadie.Library.Imaging;
using Roadie.Library.MetaData.Audio;
using Roadie.Library.MetaData.FileName;
using Roadie.Library.MetaData.ID3Tags;
using Roadie.Library.MetaData.LastFm;
using Roadie.Library.MetaData.MusicBrainz;
using Roadie.Library.Processors;
using Roadie.Library.Scrobble;
using Roadie.Library.SearchEngines.Imaging;
using Roadie.Library.SearchEngines.MetaData.Discogs;
using Roadie.Library.SearchEngines.MetaData.Spotify;
using Roadie.Library.SearchEngines.MetaData.Wikipedia;
using Roadie.Library.Utility;
using System;
using System.Collections.Generic;
using System.Text;
#endregion
namespace Roadie.Api
{
@ -45,7 +58,7 @@ namespace Roadie.Api
Logger = _loggerFactory.CreateLogger<Startup>();
TypeAdapterConfig<Image, Library.Models.Image>
TypeAdapterConfig<Library.Data.Image, Library.Models.Image>
.NewConfig()
.Map(i => i.ArtistId,
src => src.Artist == null ? null : (Guid?)src.Artist.RoadieId)
@ -178,10 +191,29 @@ namespace Roadie.Api
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
services.AddSingleton<IDefaultNotFoundImages, DefaultNotFoundImages>();
services.AddSingleton<IImageSearchManager, ImageSearchManager>();
services.AddSingleton<IITunesSearchEngine, ITunesSearchEngine>();
services.AddSingleton<IBingImageSearchEngine, BingImageSearchEngine>();
services.AddSingleton<IMusicBrainzProvider, MusicBrainzProvider>();
services.AddSingleton<ISpotifyHelper, SpotifyHelper>();
services.AddSingleton<IDiscogsHelper, DiscogsHelper>();
services.AddSingleton<IWikipediaHelper, WikipediaHelper>();
services.AddSingleton<IFileNameHelper, FileNameHelper>();
services.AddSingleton<IID3TagsHelper, ID3TagsHelper>();
services.AddScoped<ILastFmHelper, LastFmHelper>();
services.AddScoped<IRoadieScrobbler, RoadieScrobbler>();
services.AddScoped<ILastFMScrobbler, LastFMScrobbler>();
services.AddScoped<IStatisticsService, StatisticsService>();
services.AddScoped<ICollectionService, CollectionService>();
services.AddScoped<IPlaylistService, PlaylistService>();
services.AddScoped<IBookmarkService, BookmarkService>();
services.AddScoped<IArtistLookupEngine, ArtistLookupEngine>();
services.AddScoped<IReleaseLookupEngine, ReleaseLookupEngine>();
services.AddScoped<ILabelLookupEngine, LabelLookupEngine>();
services.AddScoped<IAudioMetaDataHelper, AudioMetaDataHelper>();
services.AddScoped<IFileProcessor, FileProcessor>();
services.AddScoped<IFileDirectoryProcessorService, FileDirectoryProcessorService>();
services.AddScoped<IArtistService, ArtistService>();
services.AddScoped<IImageService, ImageService>();
services.AddScoped<IReleaseService, ReleaseService>();