mirror of
https://github.com/sphildreth/roadie
synced 2024-11-10 06:44:12 +00:00
Revert "Revert "Resolved #13: Move play history recording for Roadie to a scrobbler integration and implemented LastFM integration.""
This reverts commit 461719f826
.
This commit is contained in:
parent
461719f826
commit
96c2853d51
52 changed files with 1028 additions and 342 deletions
|
@ -12,27 +12,24 @@ namespace Roadie.Library.Configuration
|
|||
private string _lastFMApiKey = null;
|
||||
private bool _lastFmEnabled = true;
|
||||
private string _lastFMSecret = null;
|
||||
public List<ApiKey> ApiKeys { get; set; }
|
||||
|
||||
public List<ApiKey> ApiKeys { get; set; } = new List<ApiKey>();
|
||||
|
||||
|
||||
public string DiscogsConsumerKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(this._discogsConsumerKey))
|
||||
var keySetting = this.ApiKeys.FirstOrDefault(x => x.ApiName == "DiscogsConsumerKey");
|
||||
if (keySetting != null)
|
||||
{
|
||||
var keySetting = this.ApiKeys.FirstOrDefault(x => x.ApiName == "DiscogsConsumerKey");
|
||||
if (keySetting != null)
|
||||
{
|
||||
this._discogsConsumerKey = keySetting.Key;
|
||||
this._discogsConsumerSecret = keySetting.KeySecret;
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.WriteLine("Unable To Find Api Key with Key Name of 'DiscogsConsumerKey', Discogs Integration Disabled");
|
||||
}
|
||||
return keySetting.Key;
|
||||
}
|
||||
return this._discogsConsumerKey;
|
||||
else
|
||||
{
|
||||
Trace.WriteLine("Unable To Find Api Key with Key Name of 'DiscogsConsumerKey', Discogs Integration Disabled");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,20 +37,16 @@ namespace Roadie.Library.Configuration
|
|||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(this._discogsConsumerSecret))
|
||||
var keySetting = this.ApiKeys.FirstOrDefault(x => x.ApiName == "DiscogsConsumerKey");
|
||||
if (keySetting != null)
|
||||
{
|
||||
var keySetting = this.ApiKeys.FirstOrDefault(x => x.ApiName == "DiscogsConsumerKey");
|
||||
if (keySetting != null)
|
||||
{
|
||||
this._discogsConsumerKey = keySetting.Key;
|
||||
this._discogsConsumerSecret = keySetting.KeySecret;
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.WriteLine("Unable To Find Api Key with Key Name of 'DiscogsConsumerKey', Discogs Integration Disabled");
|
||||
}
|
||||
return keySetting.KeySecret;
|
||||
}
|
||||
return this._discogsConsumerSecret;
|
||||
else
|
||||
{
|
||||
Trace.WriteLine("Unable To Find Api Key with Key Name of 'DiscogsConsumerKey', Discogs Integration Disabled");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,20 +76,16 @@ namespace Roadie.Library.Configuration
|
|||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(this._lastFMApiKey))
|
||||
var keySetting = this.ApiKeys.FirstOrDefault(x => x.ApiName == "LastFMApiKey");
|
||||
if (keySetting != null)
|
||||
{
|
||||
var keySetting = this.ApiKeys.FirstOrDefault(x => x.ApiName == "LastFMApiKey");
|
||||
if (keySetting != null)
|
||||
{
|
||||
this._lastFMApiKey = keySetting.Key;
|
||||
this._lastFMSecret = keySetting.KeySecret;
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.WriteLine("Unable To Find Api Key with Key Name of 'LastFMApiKey', Last FM Integration Disabled");
|
||||
}
|
||||
return keySetting.Key;
|
||||
}
|
||||
return this._lastFMApiKey;
|
||||
else
|
||||
{
|
||||
Trace.WriteLine("Unable To Find Api Key with Key Name of 'LastFMApiKey', Last FM Integration Disabled");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,20 +93,16 @@ namespace Roadie.Library.Configuration
|
|||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(this._lastFMSecret))
|
||||
var keySetting = this.ApiKeys.FirstOrDefault(x => x.ApiName == "LastFMApiKey");
|
||||
if (keySetting != null)
|
||||
{
|
||||
var keySetting = this.ApiKeys.FirstOrDefault(x => x.ApiName == "LastFMApiKey");
|
||||
if (keySetting != null)
|
||||
{
|
||||
this._lastFMApiKey = keySetting.Key;
|
||||
this._lastFMSecret = keySetting.KeySecret;
|
||||
}
|
||||
else
|
||||
{
|
||||
Trace.WriteLine("Unable To Find Api Key with Key Name of 'LastFMApiKey', Last FM Integration Disabled");
|
||||
}
|
||||
return keySetting.KeySecret;
|
||||
}
|
||||
return this._lastFMSecret;
|
||||
else
|
||||
{
|
||||
Trace.WriteLine("Unable To Find Api Key with Key Name of 'LastFMApiKey', Last FM Integration Disabled");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,12 +26,12 @@ namespace Roadie.Library.Data
|
|||
public int? UserId { get; set; }
|
||||
|
||||
[Column("duration")]
|
||||
public int? Duration { get; set; } // TODO update this on playlist edit
|
||||
public int? Duration { get; set; }
|
||||
|
||||
[Column("trackCount")]
|
||||
public short TrackCount { get; set; } // TODO update this on playlist edit
|
||||
public short TrackCount { get; set; }
|
||||
|
||||
[Column("releaseCount")]
|
||||
public short ReleaseCount { get; set; } // TODO update this on playlist edit
|
||||
public short ReleaseCount { get; set; }
|
||||
}
|
||||
}
|
|
@ -51,7 +51,7 @@ namespace Roadie.Library.Engines
|
|||
{
|
||||
this.ITunesArtistSearchEngine = new ITunesSearchEngine(this.Configuration, this.CacheManager, this.Logger);
|
||||
this.MusicBrainzArtistSearchEngine = new musicbrainz.MusicBrainzProvider(this.Configuration, this.CacheManager, this.Logger);
|
||||
this.LastFmArtistSearchEngine = new lastfm.LastFmHelper(this.Configuration, this.CacheManager, this.Logger);
|
||||
this.LastFmArtistSearchEngine = new lastfm.LastFmHelper(this.Configuration, this.CacheManager, this.Logger, context, httpEncoder);
|
||||
this.SpotifyArtistSearchEngine = new spotify.SpotifyHelper(this.Configuration, this.CacheManager, this.Logger);
|
||||
this.WikipediaArtistSearchEngine = new wikipedia.WikipediaHelper(this.Configuration, this.CacheManager, this.Logger, this.HttpEncoder);
|
||||
this.DiscogsArtistSearchEngine = new discogs.DiscogsHelper(this.Configuration, this.CacheManager, this.Logger);
|
||||
|
|
|
@ -61,7 +61,9 @@ namespace Roadie.Library.Engines
|
|||
public IReleaseSearchEngine SpotifyReleaseSearchEngine { get; }
|
||||
public IReleaseSearchEngine WikipediaReleaseSearchEngine { get; }
|
||||
|
||||
public ReleaseLookupEngine(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context, ICacheManager cacheManager, ILogger logger, IArtistLookupEngine aristLookupEngine, ILabelLookupEngine labelLookupEngine)
|
||||
public ReleaseLookupEngine(IRoadieSettings configuration, IHttpEncoder httpEncoder, IRoadieDbContext context,
|
||||
ICacheManager cacheManager, ILogger logger, IArtistLookupEngine aristLookupEngine,
|
||||
ILabelLookupEngine labelLookupEngine)
|
||||
: base(configuration, httpEncoder, context, cacheManager, logger)
|
||||
{
|
||||
this.ArtistLookupEngine = ArtistLookupEngine;
|
||||
|
@ -69,7 +71,7 @@ namespace Roadie.Library.Engines
|
|||
|
||||
this.ITunesReleaseSearchEngine = new ITunesSearchEngine(this.Configuration, this.CacheManager, this.Logger);
|
||||
this.MusicBrainzReleaseSearchEngine = new musicbrainz.MusicBrainzProvider(this.Configuration, this.CacheManager, this.Logger);
|
||||
this.LastFmReleaseSearchEngine = new lastfm.LastFmHelper(this.Configuration, this.CacheManager, this.Logger);
|
||||
this.LastFmReleaseSearchEngine = new lastfm.LastFmHelper(this.Configuration, this.CacheManager, this.Logger, context, httpEncoder);
|
||||
this.DiscogsReleaseSearchEngine = new discogs.DiscogsHelper(this.Configuration, this.CacheManager, this.Logger);
|
||||
this.SpotifyReleaseSearchEngine = new spotify.SpotifyHelper(this.Configuration, this.CacheManager, this.Logger);
|
||||
this.WikipediaReleaseSearchEngine = new wikipedia.WikipediaHelper(this.Configuration, this.CacheManager, this.Logger, this.HttpEncoder);
|
||||
|
|
|
@ -28,5 +28,14 @@ namespace Roadie.Library.Extensions
|
|||
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
return Convert.ToInt64((date - epoch).TotalSeconds);
|
||||
}
|
||||
|
||||
public static int ToUnixTimeSinceEpoch(this DateTime dateTime)
|
||||
{
|
||||
DateTime utcDateTime = dateTime.ToUniversalTime();
|
||||
var jan1St1970 = new DateTime(1970, 1, 1, 0, 0, 0);
|
||||
|
||||
TimeSpan timeSinceJan1St1970 = utcDateTime.Subtract(jan1St1970);
|
||||
return (int)timeSinceJan1St1970.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -127,6 +127,10 @@ namespace Roadie.Library.Identity
|
|||
[StringLength(50)]
|
||||
public string Timezone { get; set; }
|
||||
|
||||
[Column("lastFMSessionKey")]
|
||||
[StringLength(50)]
|
||||
public string LastFMSessionKey { get; set; }
|
||||
|
||||
public ICollection<UserTrack> TrackRatings { get; set; }
|
||||
|
||||
public ICollection<UserQue> UserQues { get; set; }
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace Roadie.Library
|
|||
[XmlIgnore]
|
||||
public Dictionary<string, object> AdditionalData { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
public Dictionary<string, object> AdditionalClientData { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Client friendly exceptions
|
||||
/// </summary>
|
||||
|
|
18
Roadie.Api.Library/Scrobble/IScrobbleHandler.cs
Normal file
18
Roadie.Api.Library/Scrobble/IScrobbleHandler.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using Roadie.Library.Models.Users;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Roadie.Library.Scrobble
|
||||
{
|
||||
public interface IScrobbleHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// When a user starts playing a track.
|
||||
/// </summary>
|
||||
Task<OperationResult<bool>> NowPlaying(User user, ScrobbleInfo scrobble);
|
||||
|
||||
/// <summary>
|
||||
/// When a user has either played more than 4 minutes of the track or the entire track.
|
||||
/// </summary>
|
||||
Task<OperationResult<bool>> Scrobble(User user, ScrobbleInfo scrobble);
|
||||
}
|
||||
}
|
12
Roadie.Api.Library/Scrobble/IScrobbler.cs
Normal file
12
Roadie.Api.Library/Scrobble/IScrobbler.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Roadie.Library.Models.Users;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Roadie.Library.Scrobble
|
||||
{
|
||||
public interface IScrobblerIntegration
|
||||
{
|
||||
Task<OperationResult<bool>> NowPlaying(User roadieUser, ScrobbleInfo scrobble);
|
||||
|
||||
Task<OperationResult<bool>> Scrobble(User roadieUser, ScrobbleInfo scrobble);
|
||||
}
|
||||
}
|
37
Roadie.Api.Library/Scrobble/LastFMScrobbler.cs
Normal file
37
Roadie.Api.Library/Scrobble/LastFMScrobbler.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.MetaData.LastFm;
|
||||
using Roadie.Library.Models.Users;
|
||||
using System.Threading.Tasks;
|
||||
using data = Roadie.Library.Data;
|
||||
|
||||
namespace Roadie.Library.Scrobble
|
||||
{
|
||||
/// <summary>
|
||||
/// LastFM Scrobbler
|
||||
/// <seealso cref="https://www.last.fm/api/scrobbling"/>
|
||||
/// </summary>
|
||||
public class LastFMScrobbler : ScrobblerIntegrationBase
|
||||
{
|
||||
private ILastFmHelper LastFmHelper { get; }
|
||||
|
||||
public LastFMScrobbler(IRoadieSettings configuration, ILogger logger, data.IRoadieDbContext dbContext, ICacheManager cacheManager, ILastFmHelper lastFmHelper)
|
||||
: base(configuration, logger, dbContext, cacheManager)
|
||||
{
|
||||
LastFmHelper = lastFmHelper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a Now Playing Request.
|
||||
/// <remark>The "Now Playing" service lets a client notify Last.fm that a user has started listening to a track. This does not affect a user's charts, but will feature the current track on their profile page, along with an indication of what music player they're using.</remark>
|
||||
/// </summary>
|
||||
public override async Task<OperationResult<bool>> NowPlaying(User roadieUser, ScrobbleInfo scrobble) => await LastFmHelper.NowPlaying(roadieUser, scrobble);
|
||||
|
||||
/// <summary>
|
||||
/// Send a Scrobble Request
|
||||
/// <remark>The scrobble service lets a client add a track-play to a user's profile. This data is used to show a user's listening history and generate personalised charts and recommendations (and more).</remark>
|
||||
/// </summary>
|
||||
public override async Task<OperationResult<bool>> Scrobble(User roadieUser, ScrobbleInfo scrobble) => await LastFmHelper.Scrobble(roadieUser, scrobble);
|
||||
}
|
||||
}
|
96
Roadie.Api.Library/Scrobble/RoadieScrobbler.cs
Normal file
96
Roadie.Api.Library/Scrobble/RoadieScrobbler.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Models.Users;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using data = Roadie.Library.Data;
|
||||
|
||||
namespace Roadie.Library.Scrobble
|
||||
{
|
||||
public class RoadieScrobbler : ScrobblerIntegrationBase
|
||||
{
|
||||
public RoadieScrobbler(IRoadieSettings configuration, ILogger logger, data.IRoadieDbContext dbContext, ICacheManager cacheManager)
|
||||
: base(configuration, logger, dbContext, cacheManager)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The user has started playing a track.
|
||||
/// </summary>
|
||||
public override async Task<OperationResult<bool>> NowPlaying(User roadieUser, ScrobbleInfo scrobble) => await ScrobbleAction(roadieUser, scrobble, true);
|
||||
|
||||
/// <summary>
|
||||
/// The user has played a track.
|
||||
/// </summary>
|
||||
public override async Task<OperationResult<bool>> Scrobble(User roadieUser, ScrobbleInfo scrobble) => await ScrobbleAction(roadieUser, scrobble, false);
|
||||
|
||||
private async Task<OperationResult<bool>> ScrobbleAction(User roadieUser, ScrobbleInfo scrobble, bool isNowPlaying)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var track = this.DbContext.Tracks
|
||||
.Include(x => x.ReleaseMedia)
|
||||
.Include(x => x.ReleaseMedia.Release)
|
||||
.Include(x => x.ReleaseMedia.Release.Artist)
|
||||
.Include(x => x.TrackArtist)
|
||||
.FirstOrDefault(x => x.RoadieId == scrobble.TrackId);
|
||||
if (track == null)
|
||||
{
|
||||
return new OperationResult<bool>($"Scrobble: Unable To Find Track [{ scrobble.TrackId }]");
|
||||
}
|
||||
if (!track.IsValid)
|
||||
{
|
||||
return new OperationResult<bool>($"Scrobble: Invalid Track. Track Id [{scrobble.TrackId}], FilePath [{track.FilePath}], Filename [{track.FileName}]");
|
||||
}
|
||||
data.UserTrack userTrack = null;
|
||||
var now = DateTime.UtcNow;
|
||||
var success = false;
|
||||
try
|
||||
{
|
||||
// Only create (or update) a user track activity record when the user has played the entire track.
|
||||
if (!isNowPlaying)
|
||||
{
|
||||
var user = this.DbContext.Users.FirstOrDefault(x => x.RoadieId == roadieUser.UserId);
|
||||
userTrack = this.DbContext.UserTracks.FirstOrDefault(x => x.UserId == roadieUser.Id && x.TrackId == track.Id);
|
||||
if (userTrack == null)
|
||||
{
|
||||
userTrack = new data.UserTrack(now)
|
||||
{
|
||||
UserId = user.Id,
|
||||
TrackId = track.Id
|
||||
};
|
||||
this.DbContext.UserTracks.Add(userTrack);
|
||||
}
|
||||
userTrack.LastPlayed = now;
|
||||
userTrack.PlayedCount = (userTrack.PlayedCount ?? 0) + 1;
|
||||
this.CacheManager.ClearRegion(user.CacheRegion);
|
||||
await this.DbContext.SaveChangesAsync();
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, $"Error in Scrobble, Creating UserTrack: User `{ roadieUser}` TrackId [{ track.Id }");
|
||||
}
|
||||
sw.Stop();
|
||||
this.Logger.LogInformation($"RoadieScrobbler: RoadieUser `{ roadieUser }` { (isNowPlaying ? "NowPlaying" : "Scrobble") } `{ scrobble }`");
|
||||
return new OperationResult<bool>
|
||||
{
|
||||
Data = success,
|
||||
IsSuccess = userTrack != null,
|
||||
OperationTime = sw.ElapsedMilliseconds
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, $"Scrobble RoadieUser `{ roadieUser }` Scrobble `{ scrobble }`");
|
||||
}
|
||||
return new OperationResult<bool>();
|
||||
}
|
||||
}
|
||||
}
|
102
Roadie.Api.Library/Scrobble/ScrobbleHandler.cs
Normal file
102
Roadie.Api.Library/Scrobble/ScrobbleHandler.cs
Normal file
|
@ -0,0 +1,102 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Encoding;
|
||||
using Roadie.Library.MetaData.LastFm;
|
||||
using Roadie.Library.Models.Users;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using data = Roadie.Library.Data;
|
||||
|
||||
namespace Roadie.Library.Scrobble
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles NowPlaying and Scrobbling for all integrations
|
||||
/// </summary>
|
||||
public class ScrobbleHandler : IScrobbleHandler
|
||||
{
|
||||
private IRoadieSettings Configuration { get; }
|
||||
private data.IRoadieDbContext DbContext { get; }
|
||||
private ILogger Logger { get; }
|
||||
private IHttpEncoder HttpEncoder { get; }
|
||||
|
||||
private IEnumerable<IScrobblerIntegration> Scrobblers { get; }
|
||||
|
||||
public ScrobbleHandler(IRoadieSettings configuration, ILogger<ScrobbleHandler> logger, data.IRoadieDbContext dbContext, ICacheManager cacheManager, IHttpEncoder httpEncoder)
|
||||
{
|
||||
Logger = logger;
|
||||
Configuration = configuration;
|
||||
DbContext = dbContext;
|
||||
HttpEncoder = httpEncoder;
|
||||
var scrobblers = new List<IScrobblerIntegration>
|
||||
{
|
||||
new RoadieScrobbler(configuration, logger, dbContext, cacheManager)
|
||||
};
|
||||
if (configuration.Integrations.LastFmProviderEnabled)
|
||||
{
|
||||
scrobblers.Add(new LastFmHelper(configuration, cacheManager, logger, dbContext, httpEncoder));
|
||||
}
|
||||
Scrobblers = scrobblers;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send Now Playing Requests
|
||||
/// </summary>
|
||||
public async Task<OperationResult<bool>> NowPlaying(User user, ScrobbleInfo scrobble)
|
||||
{
|
||||
var s = GetScrobbleInfoDetails(scrobble);
|
||||
foreach (var scrobbler in Scrobblers)
|
||||
{
|
||||
await Task.Run(async () => await scrobbler.NowPlaying(user, s));
|
||||
}
|
||||
return new OperationResult<bool>
|
||||
{
|
||||
Data = true,
|
||||
IsSuccess = true
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send any Scrobble Requests
|
||||
/// </summary>
|
||||
public async Task<OperationResult<bool>> Scrobble(User user, ScrobbleInfo scrobble)
|
||||
{
|
||||
var s = GetScrobbleInfoDetails(scrobble);
|
||||
foreach (var scrobbler in Scrobblers)
|
||||
{
|
||||
await Task.Run(async () => await scrobbler.Scrobble(user, s));
|
||||
}
|
||||
return new OperationResult<bool>
|
||||
{
|
||||
Data = true,
|
||||
IsSuccess = true
|
||||
};
|
||||
}
|
||||
|
||||
private ScrobbleInfo GetScrobbleInfoDetails(ScrobbleInfo scrobble)
|
||||
{
|
||||
var scrobbleInfo = (from t in this.DbContext.Tracks
|
||||
join rm in this.DbContext.ReleaseMedias on t.ReleaseMediaId equals rm.Id
|
||||
join r in this.DbContext.Releases on rm.ReleaseId equals r.Id
|
||||
join a in this.DbContext.Artists on r.ArtistId equals a.Id
|
||||
where t.RoadieId == scrobble.TrackId
|
||||
select new
|
||||
{
|
||||
ArtistName = a.Name,
|
||||
ReleaseTitle = r.Title,
|
||||
TrackTitle = t.Title,
|
||||
t.TrackNumber,
|
||||
t.Duration
|
||||
}).FirstOrDefault();
|
||||
|
||||
scrobble.ArtistName = scrobbleInfo.ArtistName;
|
||||
scrobble.ReleaseTitle = scrobbleInfo.ReleaseTitle;
|
||||
scrobble.TrackTitle = scrobbleInfo.TrackTitle;
|
||||
scrobble.TrackNumber = scrobbleInfo.TrackNumber.ToString();
|
||||
scrobble.TrackDuration = TimeSpan.FromMilliseconds((double)(scrobbleInfo.Duration ?? 0));
|
||||
return scrobble;
|
||||
}
|
||||
}
|
||||
}
|
28
Roadie.Api.Library/Scrobble/ScrobbleInfo.cs
Normal file
28
Roadie.Api.Library/Scrobble/ScrobbleInfo.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
|
||||
namespace Roadie.Library.Scrobble
|
||||
{
|
||||
[Serializable]
|
||||
public class ScrobbleInfo
|
||||
{
|
||||
public string ArtistName { get; set; }
|
||||
public bool IsRandomizedScrobble { get; set; }
|
||||
|
||||
public string ReleaseTitle { get; set; }
|
||||
public DateTime TimePlayed { get; set; }
|
||||
public Guid TrackId { get; set; }
|
||||
public string TrackNumber { get; set; }
|
||||
public string TrackTitle { get; set; }
|
||||
public TimeSpan TrackDuration { get; set; }
|
||||
|
||||
public TimeSpan ElapsedTimeOfTrackPlayed
|
||||
{
|
||||
get
|
||||
{
|
||||
return DateTime.UtcNow.Subtract(TimePlayed);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"Artist: [{ ArtistName }], Release: [{ ReleaseTitle}], Track# [{ TrackNumber }], Track [{ TrackTitle }]";
|
||||
}
|
||||
}
|
29
Roadie.Api.Library/Scrobble/ScrobblerIntegrationBase.cs
Normal file
29
Roadie.Api.Library/Scrobble/ScrobblerIntegrationBase.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Models.Users;
|
||||
using System.Threading.Tasks;
|
||||
using data = Roadie.Library.Data;
|
||||
|
||||
namespace Roadie.Library.Scrobble
|
||||
{
|
||||
public abstract class ScrobblerIntegrationBase : IScrobblerIntegration
|
||||
{
|
||||
protected ICacheManager CacheManager { get; }
|
||||
protected IRoadieSettings Configuration { get; }
|
||||
protected data.IRoadieDbContext DbContext { get; }
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
public ScrobblerIntegrationBase(IRoadieSettings configuration, ILogger logger, data.IRoadieDbContext dbContext, ICacheManager cacheManager)
|
||||
{
|
||||
Logger = logger;
|
||||
Configuration = configuration;
|
||||
DbContext = dbContext;
|
||||
CacheManager = cacheManager;
|
||||
}
|
||||
|
||||
public abstract Task<OperationResult<bool>> NowPlaying(User roadieUser, ScrobbleInfo scrobble);
|
||||
|
||||
public abstract Task<OperationResult<bool>> Scrobble(User roadieUser, ScrobbleInfo scrobble);
|
||||
}
|
||||
}
|
|
@ -20,10 +20,7 @@ namespace Roadie.Library.SearchEngines.MetaData.Discogs
|
|||
{
|
||||
get
|
||||
{
|
||||
// TODO
|
||||
//return this.Configuration.Integrations.dis.GetValue<bool>("Integrations:DiscogsProviderEnabled", true) &&
|
||||
// !string.IsNullOrEmpty(this.ApiKey.Key);
|
||||
return false;
|
||||
return this.Configuration.Integrations.DiscogsProviderEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,19 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Roadie.Library.MetaData.Audio;
|
||||
using Roadie.Library.Scrobble;
|
||||
using Roadie.Library.SearchEngines.MetaData;
|
||||
|
||||
namespace Roadie.Library.MetaData.LastFm
|
||||
{
|
||||
public interface ILastFmHelper
|
||||
public interface ILastFmHelper : IScrobblerIntegration
|
||||
{
|
||||
bool IsEnabled { get; }
|
||||
|
||||
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);
|
||||
Task<OperationResult<string>> GetSessionKeyForUserToken(string token);
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Roadie.Library.SearchEngines.MetaData.LastFm
|
||||
{
|
||||
public class LastFmApiException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Instantiates a Last.fm API exception
|
||||
/// </summary>
|
||||
public LastFmApiException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a Last.fm API exception
|
||||
/// </summary>
|
||||
public LastFmApiException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +1,59 @@
|
|||
using IF.Lastfm.Core.Api;
|
||||
using IF.Lastfm.Core.Objects;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using RestSharp;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Encoding;
|
||||
using Roadie.Library.Extensions;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.MetaData.Audio;
|
||||
using Roadie.Library.Models.Users;
|
||||
using Roadie.Library.Scrobble;
|
||||
using Roadie.Library.SearchEngines.MetaData;
|
||||
using Roadie.Library.SearchEngines.MetaData.LastFm;
|
||||
using Roadie.Library.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
using System.Xml.XPath;
|
||||
using data = Roadie.Library.Data;
|
||||
|
||||
namespace Roadie.Library.MetaData.LastFm
|
||||
{
|
||||
public class LastFmHelper : MetaDataProviderBase, IArtistSearchEngine, IReleaseSearchEngine, ILastFmHelper
|
||||
{
|
||||
const string LastFmStatusOk = "ok";
|
||||
const string LastFmErrorXPath = "/lfm/error";
|
||||
const string LastFmErrorCodeXPath = "/lfm/error/@code";
|
||||
const string LastFmStatusXPath = "/lfm/@status";
|
||||
|
||||
public override bool IsEnabled
|
||||
{
|
||||
get
|
||||
{
|
||||
// TODO
|
||||
// return this.Configuration.GetValue<bool>("Integrations:LastFmProviderEnabled", true) &&
|
||||
// !string.IsNullOrEmpty(this.ApiKey.Key);
|
||||
return false;
|
||||
return this.Configuration.Integrations.LastFmProviderEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
public LastFmHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger) : base(configuration, cacheManager, logger)
|
||||
private data.IRoadieDbContext DbContext { get; }
|
||||
private IHttpEncoder HttpEncoder { get; }
|
||||
|
||||
public LastFmHelper(IRoadieSettings configuration, ICacheManager cacheManager, ILogger logger,
|
||||
data.IRoadieDbContext dbContext, IHttpEncoder httpEncoder)
|
||||
: base(configuration, cacheManager, logger)
|
||||
{
|
||||
this._apiKey = configuration.Integrations.ApiKeys.FirstOrDefault(x => x.ApiName == "LastFMApiKey") ?? new ApiKey();
|
||||
DbContext = dbContext;
|
||||
HttpEncoder = httpEncoder;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<IEnumerable<ArtistSearchResult>>> PerformArtistSearch(string query, int resultsCount)
|
||||
|
@ -58,10 +80,7 @@ namespace Roadie.Library.MetaData.LastFm
|
|||
{
|
||||
result.Tags = lastFmArtist.Tags.Select(x => x.Name).ToList();
|
||||
}
|
||||
if (lastFmArtist.MainImage != null && (lastFmArtist.MainImage.ExtraLarge != null || lastFmArtist.MainImage.Large != null))
|
||||
{
|
||||
result.ArtistThumbnailUrl = (lastFmArtist.MainImage.ExtraLarge ?? lastFmArtist.MainImage.Large).ToString();
|
||||
}
|
||||
// No longer fetching/consuming images LastFm says is violation of ToS ; https://getsatisfaction.com/lastfm/topics/api-announcement-dac8oefw5vrxq
|
||||
if (lastFmArtist.Url != null)
|
||||
{
|
||||
result.Urls = new string[] { lastFmArtist.Url.ToString() };
|
||||
|
@ -97,10 +116,8 @@ namespace Roadie.Library.MetaData.LastFm
|
|||
MusicBrainzId = lastFmAlbum.mbid
|
||||
};
|
||||
|
||||
if (lastFmAlbum.image != null)
|
||||
{
|
||||
result.ImageUrls = lastFmAlbum.image.Where(x => x.size == "extralarge").Select(x => x.Value).ToList();
|
||||
}
|
||||
// No longer fetching/consuming images LastFm says is violation of ToS ; https://getsatisfaction.com/lastfm/topics/api-announcement-dac8oefw5vrxq
|
||||
|
||||
if (lastFmAlbum.tags != null)
|
||||
{
|
||||
result.Tags = lastFmAlbum.tags.Select(x => x.name).ToList();
|
||||
|
@ -135,6 +152,132 @@ namespace Roadie.Library.MetaData.LastFm
|
|||
};
|
||||
}
|
||||
|
||||
public async Task<OperationResult<bool>> NowPlaying(User roadieUser, ScrobbleInfo scrobble)
|
||||
{
|
||||
var result = false;
|
||||
try
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return new OperationResult<bool>("LastFM Integation Disabled");
|
||||
}
|
||||
|
||||
var user = this.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();
|
||||
byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(postData);
|
||||
request.ContentType = "application/x-www-form-urlencoded";
|
||||
request.ContentLength = byteArray.Length;
|
||||
using (Stream dataStream = request.GetRequestStream())
|
||||
{
|
||||
dataStream.Write(byteArray, 0, byteArray.Length);
|
||||
dataStream.Close();
|
||||
}
|
||||
var xp = GetResponseAsXml(request);
|
||||
this.Logger.LogInformation($"LastFmHelper: RoadieUser `{ roadieUser }` NowPlaying `{ scrobble }` LastFmResult [{ xp.InnerXml }]");
|
||||
result = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, $"Error in LastFmHelper NowPlaying: RoadieUser `{ roadieUser }` Scrobble `{ scrobble }`");
|
||||
}
|
||||
return new OperationResult<bool>
|
||||
{
|
||||
IsSuccess = result,
|
||||
Data = result
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<OperationResult<bool>> Scrobble(User roadieUser, ScrobbleInfo scrobble)
|
||||
{
|
||||
var result = false;
|
||||
try
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return new OperationResult<bool>("LastFM Integation Disabled");
|
||||
}
|
||||
// LastFM Rules on scrobbling:
|
||||
// * The track must be longer than 30 seconds.
|
||||
// * And the track has been played for at least half its duration, or for 4 minutes(whichever occurs earlier.)
|
||||
if (scrobble.TrackDuration.TotalSeconds < 30)
|
||||
{
|
||||
return new OperationResult<bool>("Track duration or elapsed time does not qualify for LastFM Scrobbling");
|
||||
}
|
||||
// If less than half of duration then create a NowPlaying
|
||||
if (scrobble.ElapsedTimeOfTrackPlayed.TotalMinutes < 4 ||
|
||||
scrobble.ElapsedTimeOfTrackPlayed.TotalSeconds < (scrobble.TrackDuration.TotalSeconds / 2))
|
||||
{
|
||||
return await NowPlaying(roadieUser, scrobble);
|
||||
}
|
||||
|
||||
var user = this.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 parameters = new RequestParameters
|
||||
{
|
||||
{ "artist", scrobble.ArtistName },
|
||||
{ "track", scrobble.TrackTitle },
|
||||
{ "timestamp", scrobble.TimePlayed.ToUnixTimeSinceEpoch().ToString() },
|
||||
{ "album", scrobble.ReleaseTitle },
|
||||
{ "chosenByUser", scrobble.IsRandomizedScrobble ? "1" : "0" },
|
||||
{ "duration", ((int)scrobble.TrackDuration.TotalSeconds).ToString() }
|
||||
};
|
||||
|
||||
var method = "track.scrobble";
|
||||
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();
|
||||
byte[] byteArray = System.Text.Encoding.UTF8.GetBytes(postData);
|
||||
request.ContentType = "application/x-www-form-urlencoded";
|
||||
request.ContentLength = byteArray.Length;
|
||||
using (Stream dataStream = request.GetRequestStream())
|
||||
{
|
||||
dataStream.Write(byteArray, 0, byteArray.Length);
|
||||
dataStream.Close();
|
||||
}
|
||||
var xp = GetResponseAsXml(request);
|
||||
this.Logger.LogInformation($"LastFmHelper: RoadieUser `{ roadieUser }` Scrobble `{ scrobble }` LastFmResult [{ xp.InnerXml }]");
|
||||
result = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, $"Error in LastFmHelper Scrobble: RoadieUser `{ roadieUser }` Scrobble `{ scrobble }`");
|
||||
}
|
||||
return new OperationResult<bool>
|
||||
{
|
||||
IsSuccess = result,
|
||||
Data = result
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AudioMetaData>> TracksForRelease(string artist, string Release)
|
||||
{
|
||||
if (string.IsNullOrEmpty(artist) || string.IsNullOrEmpty(Release))
|
||||
|
@ -203,5 +346,166 @@ namespace Roadie.Library.MetaData.LastFm
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<string>> GetSessionKeyForUserToken(string token)
|
||||
{
|
||||
var parameters = new Dictionary<string, string>
|
||||
{
|
||||
{ "token", token }
|
||||
};
|
||||
var request = new RestRequest(Method.GET);
|
||||
var client = new RestClient(BuildUrl("auth.getSession", parameters));
|
||||
var responseXML = await client.ExecuteTaskAsync<string>(request);
|
||||
XmlDocument doc = new XmlDocument();
|
||||
doc.LoadXml(responseXML.Content);
|
||||
var sessionKey = doc.GetElementsByTagName("key")[0].InnerText;
|
||||
return new OperationResult<string>
|
||||
{
|
||||
Data = sessionKey,
|
||||
IsSuccess = true
|
||||
};
|
||||
}
|
||||
|
||||
private string BuildUrl(string method, IDictionary<string, string> parameters = null)
|
||||
{
|
||||
var builder = new StringBuilder($"http://ws.audioscrobbler.com/2.0?method={ method }");
|
||||
if (parameters != null)
|
||||
{
|
||||
parameters.Add("api_key", this._apiKey.Key);
|
||||
foreach (var kv in parameters.OrderBy(kv => kv.Key, StringComparer.Ordinal))
|
||||
{
|
||||
builder.Append($"&{kv.Key}={kv.Value }");
|
||||
}
|
||||
var signature = GenerateMethodSignature(method, parameters);
|
||||
builder.Append($"&api_sig={ signature } ");
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private string GenerateMethodSignature(string method, IDictionary<string, string> parameters = null, string sk = null)
|
||||
{
|
||||
if (parameters == null)
|
||||
{
|
||||
parameters = new Dictionary<string, string>();
|
||||
}
|
||||
parameters.Add("method", method);
|
||||
parameters.Add("api_key", this._apiKey.Key);
|
||||
if (!string.IsNullOrEmpty(sk))
|
||||
{
|
||||
parameters.Add("sk", sk);
|
||||
}
|
||||
var builder = new StringBuilder();
|
||||
foreach (var kv in parameters.OrderBy(kv => kv.Key, StringComparer.Ordinal))
|
||||
{
|
||||
builder.Append($"{kv.Key}{kv.Value}");
|
||||
}
|
||||
builder.Append(this._apiKey.KeySecret);
|
||||
return Hash(builder.ToString());
|
||||
}
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5.aspx
|
||||
// Hash an input string and return the hash as
|
||||
// a 32 character hexadecimal string.
|
||||
public static string Hash(string input)
|
||||
{
|
||||
// Create a new instance of the MD5CryptoServiceProvider object.
|
||||
using (MD5 md5Hasher = MD5.Create())
|
||||
{
|
||||
byte[] data = md5Hasher.ComputeHash(System.Text.Encoding.ASCII.GetBytes(input));
|
||||
var sb = new StringBuilder();
|
||||
foreach (byte b in data)
|
||||
{
|
||||
sb.Append(b.ToString("X2"));
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
protected internal virtual XPathNavigator GetResponseAsXml(WebRequest request)
|
||||
{
|
||||
WebResponse response;
|
||||
XPathNavigator navigator;
|
||||
try
|
||||
{
|
||||
response = request.GetResponse();
|
||||
navigator = GetXpathDocumentFromResponse(response);
|
||||
CheckLastFmStatus(navigator);
|
||||
}
|
||||
catch (WebException exception)
|
||||
{
|
||||
response = exception.Response;
|
||||
|
||||
XPathNavigator document;
|
||||
TryGetXpathDocumentFromResponse(response, out document);
|
||||
|
||||
if (document != null) CheckLastFmStatus(document, exception);
|
||||
throw; // throw even if Last.fm status is OK
|
||||
}
|
||||
|
||||
return navigator;
|
||||
}
|
||||
|
||||
protected virtual bool TryGetXpathDocumentFromResponse(WebResponse response, out XPathNavigator document)
|
||||
{
|
||||
bool parsed;
|
||||
|
||||
try
|
||||
{
|
||||
document = GetXpathDocumentFromResponse(response);
|
||||
parsed = true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
document = null;
|
||||
parsed = false;
|
||||
}
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
protected virtual XPathNavigator GetXpathDocumentFromResponse(WebResponse response)
|
||||
{
|
||||
using (var stream = response.GetResponseStream())
|
||||
{
|
||||
if (stream == null) throw new InvalidOperationException("Response Stream is null");
|
||||
|
||||
try
|
||||
{
|
||||
return new XPathDocument(stream).CreateNavigator();
|
||||
}
|
||||
catch (XmlException exception)
|
||||
{
|
||||
throw new XmlException("Could not read HTTP Response as XML", exception);
|
||||
}
|
||||
finally
|
||||
{
|
||||
response.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CheckLastFmStatus(XPathNavigator navigator)
|
||||
{
|
||||
CheckLastFmStatus(navigator, null);
|
||||
}
|
||||
|
||||
public static void CheckLastFmStatus(XPathNavigator navigator, WebException webException)
|
||||
{
|
||||
var node = SelectSingleNode(navigator, LastFmStatusXPath);
|
||||
|
||||
if (node.Value == LastFmStatusOk) return;
|
||||
|
||||
throw new LastFmApiException(string.Format("LastFm status = \"{0}\". Error code = {1}. {2}",
|
||||
node.Value,
|
||||
SelectSingleNode(navigator, LastFmErrorCodeXPath),
|
||||
SelectSingleNode(navigator, LastFmErrorXPath)), webException);
|
||||
}
|
||||
|
||||
public static XPathNavigator SelectSingleNode(XPathNavigator navigator, string xpath)
|
||||
{
|
||||
var node = navigator.SelectSingleNode(xpath);
|
||||
if (node == null) throw new InvalidOperationException("Node is null. Cannot select single node. XML response may be mal-formed");
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace Roadie.Library.SearchEngines.MetaData.LastFm
|
||||
{
|
||||
internal class RequestParameters : SortedDictionary<string, string>
|
||||
{
|
||||
/// <summary>
|
||||
/// The Name Value Pair Format String used by this object
|
||||
/// </summary>
|
||||
public const string NameValuePairStringFormat = "{0}={1}";
|
||||
|
||||
/// <summary>
|
||||
/// The Name-value pair seperator used by this object
|
||||
/// </summary>
|
||||
public const string NameValuePairStringSeperator = "&";
|
||||
|
||||
public RequestParameters()
|
||||
: base()
|
||||
{
|
||||
}
|
||||
|
||||
internal RequestParameters(string serialization)
|
||||
: base()
|
||||
{
|
||||
string[] values = serialization.Split('\t');
|
||||
|
||||
for (int i = 0; i < values.Length - 1; i++)
|
||||
{
|
||||
if ((i % 2) == 0)
|
||||
{
|
||||
this[values[i]] = values[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
int count = 0;
|
||||
foreach (string key in this.Keys)
|
||||
{
|
||||
if (count > 0) builder.Append(NameValuePairStringSeperator);
|
||||
builder.AppendFormat(NameValuePairStringFormat, key, HttpUtility.UrlEncode(this[key]));
|
||||
count++;
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
internal string serialize()
|
||||
{
|
||||
string line = "";
|
||||
|
||||
foreach (string key in Keys)
|
||||
line += key + "\t" + this[key] + "\t";
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
internal byte[] ToBytes()
|
||||
{
|
||||
return System.Text.Encoding.UTF8.GetBytes(ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,6 +34,8 @@ namespace Roadie.Library.SearchEngines.MetaData.Spotify
|
|||
ArtistSearchResult data = null;
|
||||
try
|
||||
{
|
||||
// TODO update this to use https://github.com/JohnnyCrazy/SpotifyAPI-NET
|
||||
|
||||
this.Logger.LogTrace("SpotifyHelper:PerformArtistSearch:{0}", query);
|
||||
var request = this.BuildSearchRequest(query, 1, "artist");
|
||||
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Roadie.Library.Utility
|
||||
{
|
||||
public class LookupSerializer : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
var result = objectType.GetInterfaces().Any(a => a.IsGenericType
|
||||
&& a.GetGenericTypeDefinition() == typeof(ILookup<,>));
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var obj = new JObject();
|
||||
var enumerable = (IEnumerable)value;
|
||||
|
||||
foreach (object kvp in enumerable)
|
||||
{
|
||||
// TODO: caching
|
||||
var keyProp = kvp.GetType().GetProperty("Key");
|
||||
var keyValue = keyProp.GetValue(kvp, null);
|
||||
|
||||
obj.Add(keyValue.ToString(), JArray.FromObject((IEnumerable)kvp));
|
||||
}
|
||||
|
||||
obj.WriteTo(writer);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -73,7 +73,7 @@ namespace Roadie.Api.Services
|
|||
this.EventMessageLogger.Messages += EventMessageLogger_Messages;
|
||||
|
||||
this.MusicBrainzProvider = new MusicBrainzProvider(configuration, cacheManager, MessageLogger);
|
||||
this.LastFmHelper = new LastFmHelper(configuration, cacheManager, MessageLogger);
|
||||
this.LastFmHelper = new LastFmHelper(configuration, cacheManager, MessageLogger, context, httpEncoder);
|
||||
this.FileNameHelper = new FileNameHelper(configuration, cacheManager, MessageLogger);
|
||||
this.ID3TagsHelper = new ID3TagsHelper(configuration, cacheManager, MessageLogger);
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace Roadie.Api.Services
|
|||
this.BookmarkService = bookmarkService;
|
||||
|
||||
this.MusicBrainzProvider = new mb.MusicBrainzProvider(configuration, cacheManager, logger);
|
||||
this.LastFmHelper = new LastFmHelper(configuration, cacheManager, logger);
|
||||
this.LastFmHelper = new LastFmHelper(configuration, cacheManager, logger, dbContext, httpEncoder);
|
||||
this.FileNameHelper = new FileNameHelper(configuration, cacheManager, logger);
|
||||
this.ID3TagsHelper = new ID3TagsHelper(configuration, cacheManager, logger);
|
||||
this.ArtistLookupEngine = new ArtistLookupEngine(configuration, httpEncoder, dbContext, cacheManager, logger);
|
||||
|
|
|
@ -9,8 +9,6 @@ namespace Roadie.Api.Services
|
|||
{
|
||||
public interface IPlayActivityService
|
||||
{
|
||||
Task<OperationResult<PlayActivityList>> CreatePlayActivity(User roadieUser, TrackStreamInfo streamInfo);
|
||||
|
||||
Task<PagedResult<PlayActivityList>> List(PagedRequest request, User roadieUser = null, DateTime? newerThan = null);
|
||||
}
|
||||
}
|
|
@ -44,5 +44,7 @@ namespace Roadie.Api.Services
|
|||
Task<OperationResult<short>> SetTrackRating(Guid trackId, User roadieUser, short rating);
|
||||
|
||||
Task<OperationResult<bool>> UpdateProfile(User userPerformingUpdate, User userBeingUpdatedModel);
|
||||
|
||||
Task<OperationResult<bool>> UpdateIntegrationGrant(Guid userId, string integrationName, string token);
|
||||
}
|
||||
}
|
|
@ -36,143 +36,6 @@ namespace Roadie.Api.Services
|
|||
this.PlayActivityHub = playHubContext;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<PlayActivityList>> CreatePlayActivity(User roadieUser, TrackStreamInfo streamInfo)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var track = this.DbContext.Tracks
|
||||
.Include(x => x.ReleaseMedia)
|
||||
.Include(x => x.ReleaseMedia.Release)
|
||||
.Include(x => x.ReleaseMedia.Release.Artist)
|
||||
.Include(x => x.TrackArtist)
|
||||
.FirstOrDefault(x => x.RoadieId == SafeParser.ToGuid(streamInfo.Track.Value));
|
||||
if (track == null)
|
||||
{
|
||||
return new OperationResult<PlayActivityList>($"CreatePlayActivity: Unable To Find Track [{ streamInfo.Track.Value }]");
|
||||
}
|
||||
if (!track.IsValid)
|
||||
{
|
||||
return new OperationResult<PlayActivityList>($"CreatePlayActivity: Invalid Track. Track Id [{streamInfo.Track.Value}], FilePath [{track.FilePath}], Filename [{track.FileName}]");
|
||||
}
|
||||
data.UserTrack userTrack = null;
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
var user = roadieUser != null ? this.DbContext.Users.FirstOrDefault(x => x.RoadieId == roadieUser.UserId) : null;
|
||||
if (user != null)
|
||||
{
|
||||
userTrack = this.DbContext.UserTracks.FirstOrDefault(x => x.UserId == user.Id && x.TrackId == track.Id);
|
||||
if (userTrack == null)
|
||||
{
|
||||
userTrack = new data.UserTrack(now)
|
||||
{
|
||||
UserId = user.Id,
|
||||
TrackId = track.Id
|
||||
};
|
||||
this.DbContext.UserTracks.Add(userTrack);
|
||||
}
|
||||
userTrack.LastPlayed = now;
|
||||
userTrack.PlayedCount = (userTrack.PlayedCount ?? 0) + 1;
|
||||
this.CacheManager.ClearRegion(user.CacheRegion);
|
||||
await this.DbContext.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, $"Error in CreatePlayActivity, Creating UserTrack: User `{ roadieUser}` TrackId [{ track.Id }");
|
||||
}
|
||||
|
||||
track.PlayedCount = (track.PlayedCount ?? 0) + 1;
|
||||
track.LastPlayed = now;
|
||||
|
||||
var release = this.DbContext.Releases.Include(x => x.Artist).FirstOrDefault(x => x.RoadieId == track.ReleaseMedia.Release.RoadieId);
|
||||
release.LastPlayed = now;
|
||||
release.PlayedCount = (release.PlayedCount ?? 0) + 1;
|
||||
|
||||
var artist = this.DbContext.Artists.FirstOrDefault(x => x.RoadieId == release.Artist.RoadieId);
|
||||
artist.LastPlayed = now;
|
||||
artist.PlayedCount = (artist.PlayedCount ?? 0) + 1;
|
||||
|
||||
data.Artist trackArtist = null;
|
||||
if (track.ArtistId.HasValue)
|
||||
{
|
||||
trackArtist = this.DbContext.Artists.FirstOrDefault(x => x.Id == track.ArtistId);
|
||||
trackArtist.LastPlayed = now;
|
||||
trackArtist.PlayedCount = (trackArtist.PlayedCount ?? 0) + 1;
|
||||
this.CacheManager.ClearRegion(trackArtist.CacheRegion);
|
||||
}
|
||||
|
||||
this.CacheManager.ClearRegion(track.CacheRegion);
|
||||
this.CacheManager.ClearRegion(track.ReleaseMedia.Release.CacheRegion);
|
||||
this.CacheManager.ClearRegion(track.ReleaseMedia.Release.Artist.CacheRegion);
|
||||
|
||||
var pl = new PlayActivityList
|
||||
{
|
||||
Artist = new DataToken
|
||||
{
|
||||
Text = track.ReleaseMedia.Release.Artist.Name,
|
||||
Value = track.ReleaseMedia.Release.Artist.RoadieId.ToString()
|
||||
},
|
||||
TrackArtist = track.TrackArtist == null ? null : new DataToken
|
||||
{
|
||||
Text = track.TrackArtist.Name,
|
||||
Value = track.TrackArtist.RoadieId.ToString()
|
||||
},
|
||||
Release = new DataToken
|
||||
{
|
||||
Text = track.ReleaseMedia.Release.Title,
|
||||
Value = track.ReleaseMedia.Release.RoadieId.ToString()
|
||||
},
|
||||
Track = new DataToken
|
||||
{
|
||||
Text = track.Title,
|
||||
Value = track.RoadieId.ToString()
|
||||
},
|
||||
User = new DataToken
|
||||
{
|
||||
Text = roadieUser.UserName,
|
||||
Value = roadieUser.UserId.ToString()
|
||||
},
|
||||
PlayedDateDateTime = userTrack?.LastPlayed,
|
||||
ReleasePlayUrl = $"{ this.HttpContext.BaseUrl }/play/release/{ track.ReleaseMedia.Release.RoadieId}",
|
||||
Rating = track.Rating,
|
||||
UserRating = userTrack?.Rating,
|
||||
TrackPlayUrl = $"{ this.HttpContext.BaseUrl }/play/track/{ track.RoadieId}.mp3",
|
||||
ArtistThumbnail = this.MakeArtistThumbnailImage(track.TrackArtist != null ? track.TrackArtist.RoadieId : track.ReleaseMedia.Release.Artist.RoadieId),
|
||||
ReleaseThumbnail = this.MakeReleaseThumbnailImage(track.ReleaseMedia.Release.RoadieId),
|
||||
UserThumbnail = this.MakeUserThumbnailImage(roadieUser.UserId)
|
||||
};
|
||||
|
||||
if (!roadieUser.IsPrivate)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.PlayActivityHub.Clients.All.SendAsync("SendActivity", pl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
await this.DbContext.SaveChangesAsync();
|
||||
sw.Stop();
|
||||
return new OperationResult<PlayActivityList>
|
||||
{
|
||||
Data = pl,
|
||||
IsSuccess = userTrack != null,
|
||||
OperationTime = sw.ElapsedMilliseconds
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, $"CreatePlayActivity RoadieUser `{ roadieUser }` StreamInfo `{ streamInfo }`");
|
||||
}
|
||||
return new OperationResult<PlayActivityList>();
|
||||
}
|
||||
|
||||
public Task<Library.Models.Pagination.PagedResult<PlayActivityList>> List(PagedRequest request, User roadieUser = null, DateTime? newerThan = null)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -68,7 +68,7 @@ namespace Roadie.Api.Services
|
|||
this.BookmarkService = bookmarkService;
|
||||
|
||||
this.MusicBrainzProvider = new MusicBrainzProvider(configuration, cacheManager, logger);
|
||||
this.LastFmHelper = new LastFmHelper(configuration, cacheManager, logger);
|
||||
this.LastFmHelper = new LastFmHelper(configuration, cacheManager, logger, dbContext, httpEncoder);
|
||||
this.FileNameHelper = new FileNameHelper(configuration, cacheManager, logger);
|
||||
this.ID3TagsHelper = new ID3TagsHelper(configuration, cacheManager, logger);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ using Roadie.Library.Configuration;
|
|||
using Roadie.Library.Encoding;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Imaging;
|
||||
using Roadie.Library.MetaData.LastFm;
|
||||
using Roadie.Library.Models;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
using Roadie.Library.Models.Statistics;
|
||||
|
@ -21,13 +22,14 @@ using System.Linq;
|
|||
using System.Linq.Dynamic.Core;
|
||||
using System.Threading.Tasks;
|
||||
using data = Roadie.Library.Data;
|
||||
|
||||
using models = Roadie.Library.Models;
|
||||
|
||||
namespace Roadie.Api.Services
|
||||
{
|
||||
public class UserService : ServiceBase, IUserService
|
||||
{
|
||||
private ILastFmHelper LastFmHelper { get; }
|
||||
|
||||
private UserManager<ApplicationUser> UserManager { get; }
|
||||
|
||||
public UserService(IRoadieSettings configuration,
|
||||
|
@ -36,10 +38,12 @@ namespace Roadie.Api.Services
|
|||
data.IRoadieDbContext context,
|
||||
ICacheManager cacheManager,
|
||||
ILogger<ArtistService> logger,
|
||||
UserManager<ApplicationUser> userManager)
|
||||
UserManager<ApplicationUser> userManager
|
||||
)
|
||||
: base(configuration, httpEncoder, context, cacheManager, logger, httpContext)
|
||||
{
|
||||
this.UserManager = userManager;
|
||||
this.LastFmHelper = new LastFmHelper(this.Configuration, this.CacheManager, this.Logger, context, httpEncoder); ;
|
||||
}
|
||||
|
||||
public async Task<OperationResult<User>> ById(User user, Guid id, IEnumerable<string> includes)
|
||||
|
@ -384,6 +388,51 @@ namespace Roadie.Api.Services
|
|||
return result;
|
||||
}
|
||||
|
||||
private async Task<OperationResult<bool>> UpdateLastFMSessionKey(ApplicationUser user, string token)
|
||||
{
|
||||
var lastFmSessionKeyResult = await this.LastFmHelper.GetSessionKeyForUserToken(token);
|
||||
if(!lastFmSessionKeyResult.IsSuccess)
|
||||
{
|
||||
return new OperationResult<bool>(false, $"Unable to Get LastFM Session Key For Token [{ token }]");
|
||||
}
|
||||
// Check concurrency stamp
|
||||
if (user.ConcurrencyStamp != user.ConcurrencyStamp)
|
||||
{
|
||||
return new OperationResult<bool>
|
||||
{
|
||||
Errors = new List<Exception> { new Exception("User data is stale.") }
|
||||
};
|
||||
}
|
||||
user.LastFMSessionKey = lastFmSessionKeyResult.Data;
|
||||
user.LastUpdated = DateTime.UtcNow;
|
||||
user.ConcurrencyStamp = Guid.NewGuid().ToString();
|
||||
await this.DbContext.SaveChangesAsync();
|
||||
|
||||
this.CacheManager.ClearRegion(ApplicationUser.CacheRegionUrn(user.RoadieId));
|
||||
|
||||
this.Logger.LogInformation($"User `{ user }` Updated LastFm SessionKey");
|
||||
|
||||
return new OperationResult<bool>
|
||||
{
|
||||
IsSuccess = true,
|
||||
Data = true
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<OperationResult<bool>> UpdateIntegrationGrant(Guid userId, string integrationName, string token)
|
||||
{
|
||||
var user = this.DbContext.Users.FirstOrDefault(x => x.RoadieId == userId);
|
||||
if (user == null)
|
||||
{
|
||||
return new OperationResult<bool>(true, $"User Not Found [{ userId }]");
|
||||
}
|
||||
if (integrationName == "lastfm")
|
||||
{
|
||||
return await this.UpdateLastFMSessionKey(user, token);
|
||||
}
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<OperationResult<bool>> UpdateProfile(User userPerformingUpdate, User userBeingUpdatedModel)
|
||||
{
|
||||
var user = this.DbContext.Users.FirstOrDefault(x => x.RoadieId == userBeingUpdatedModel.UserId);
|
||||
|
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
@ -19,8 +20,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private IAdminService AdminService { get; }
|
||||
|
||||
public AdminController(IAdminService adminService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public AdminController(IAdminService adminService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.AdminController");
|
||||
this.AdminService = adminService;
|
||||
|
|
|
@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
using System;
|
||||
|
@ -24,8 +25,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private IArtistService ArtistService { get; }
|
||||
|
||||
public ArtistController(IArtistService artistService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public ArtistController(IArtistService artistService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.ArtistController");
|
||||
this.ArtistService = artistService;
|
||||
|
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
using System;
|
||||
|
@ -21,8 +22,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private IBookmarkService BookmarkService { get; }
|
||||
|
||||
public BookmarkController(IBookmarkService bookmarkService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public BookmarkController(IBookmarkService bookmarkService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.BookmarkController");
|
||||
this.BookmarkService = bookmarkService;
|
||||
|
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
using System;
|
||||
|
@ -22,8 +23,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private ICollectionService CollectionService { get; }
|
||||
|
||||
public CollectionController(ICollectionService collectionService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public CollectionController(ICollectionService collectionService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.CollectionController");
|
||||
this.CollectionService = collectionService;
|
||||
|
|
|
@ -9,6 +9,7 @@ using Roadie.Api.Services;
|
|||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Scrobble;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
@ -26,18 +27,14 @@ namespace Roadie.Api.Controllers
|
|||
|
||||
private models.User _currentUser = null;
|
||||
protected ICacheManager CacheManager { get; }
|
||||
protected IConfiguration Configuration { get; }
|
||||
protected ILogger Logger { get; set; }
|
||||
protected IRoadieSettings RoadieSettings { get; }
|
||||
protected UserManager<ApplicationUser> UserManager { get; }
|
||||
|
||||
public EntityControllerBase(ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
public EntityControllerBase(ICacheManager cacheManager, IRoadieSettings roadieSettings, UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
this.CacheManager = cacheManager;
|
||||
this.Configuration = configuration;
|
||||
|
||||
this.RoadieSettings = new RoadieSettings();
|
||||
this.Configuration.GetSection("RoadieSettings").Bind(this.RoadieSettings);
|
||||
this.RoadieSettings = roadieSettings;
|
||||
this.UserManager = userManager;
|
||||
}
|
||||
|
||||
|
@ -60,7 +57,7 @@ namespace Roadie.Api.Controllers
|
|||
return this._currentUser;
|
||||
}
|
||||
|
||||
protected async Task<IActionResult> StreamTrack(Guid id, ITrackService trackService, IPlayActivityService playActivityService, models.User currentUser = null)
|
||||
protected async Task<IActionResult> StreamTrack(Guid id, ITrackService trackService, IScrobbleHandler scrobbleHandler, models.User currentUser = null)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
var timings = new Dictionary<string, long>();
|
||||
|
@ -121,12 +118,25 @@ namespace Roadie.Api.Controllers
|
|||
Response.Headers.Add("Cache-Control", info.Data.CacheControl);
|
||||
Response.Headers.Add("Expires", info.Data.Expires);
|
||||
|
||||
var stream = new MemoryStream(info.Data.Bytes);
|
||||
await Response.Body.WriteAsync(info.Data.Bytes, 0, info.Data.Bytes.Length);
|
||||
|
||||
var playListUser = await playActivityService.CreatePlayActivity(user, info?.Data);
|
||||
var scrobble = new ScrobbleInfo
|
||||
{
|
||||
IsRandomizedScrobble = false,
|
||||
TimePlayed = DateTime.UtcNow,
|
||||
TrackId = id
|
||||
};
|
||||
if(!info.Data.IsFullRequest)
|
||||
{
|
||||
await scrobbleHandler.NowPlaying(user, scrobble);
|
||||
}
|
||||
else
|
||||
{
|
||||
await scrobbleHandler.Scrobble(user, scrobble);
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
this.Logger.LogInformation($"StreamTrack ElapsedTime [{ sw.ElapsedMilliseconds }], Timings [{ JsonConvert.SerializeObject(timings) }] PlayActivity `{ playListUser?.Data.ToString() }`, StreamInfo `{ info?.Data.ToString() }`");
|
||||
this.Logger.LogInformation($"StreamTrack ElapsedTime [{ sw.ElapsedMilliseconds }], Timings [{ JsonConvert.SerializeObject(timings) }], StreamInfo `{ info?.Data.ToString() }`");
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
using System;
|
||||
|
@ -21,8 +22,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private IGenreService GenreService { get; }
|
||||
|
||||
public GenreController(IGenreService genreService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public GenreController(IGenreService genreService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.GenreController");
|
||||
this.GenreService = genreService;
|
||||
|
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
@ -20,19 +21,14 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private IImageService ImageService { get; }
|
||||
|
||||
public ImageController(IImageService imageService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public ImageController(IImageService imageService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.ImageController");
|
||||
this.ImageService = imageService;
|
||||
}
|
||||
|
||||
//[EnableQuery]
|
||||
//public IActionResult Get()
|
||||
//{
|
||||
// return Ok(this._RoadieDbContext.Tracks.ProjectToType<models.Image>());
|
||||
//}
|
||||
|
||||
[HttpGet("artist/{id}/{width:int?}/{height:int?}/{cacheBuster?}")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(404)]
|
||||
|
|
|
@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
using System;
|
||||
|
@ -24,8 +25,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private ILabelService LabelService { get; }
|
||||
|
||||
public LabelController(ILabelService labelService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public LabelController(ILabelService labelService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.LabelController");
|
||||
this.LabelService = labelService;
|
||||
|
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using System;
|
||||
using System.Net;
|
||||
|
@ -20,8 +21,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private ILookupService LookupService { get; }
|
||||
|
||||
public LookupController(ILabelService labelService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager, ILookupService lookupService)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public LookupController(ILabelService labelService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, ILookupService lookupService, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.LookupController");
|
||||
this.LookupService = lookupService;
|
||||
|
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
using System;
|
||||
|
@ -22,8 +23,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private IPlayActivityService PlayActivityService { get; }
|
||||
|
||||
public PlayActivityController(IPlayActivityService playActivityService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public PlayActivityController(IPlayActivityService playActivityService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.PlayActivityController");
|
||||
this.PlayActivityService = playActivityService;
|
||||
|
|
|
@ -5,7 +5,9 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Scrobble;
|
||||
using Roadie.Library.Utility;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
@ -20,16 +22,18 @@ namespace Roadie.Api.Controllers
|
|||
[Authorize]
|
||||
public class PlayController : EntityControllerBase
|
||||
{
|
||||
private IPlayActivityService PlayActivityService { get; }
|
||||
private IScrobbleHandler ScrobbleHandler { get; }
|
||||
private IReleaseService ReleaseService { get; }
|
||||
private ITrackService TrackService { get; }
|
||||
|
||||
public PlayController(ITrackService trackService, IReleaseService releaseService, IPlayActivityService playActivityService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public PlayController(ITrackService trackService, IReleaseService releaseService, IScrobbleHandler scrobblerHandler,
|
||||
ILoggerFactory logger, ICacheManager cacheManager, UserManager<ApplicationUser> userManager,
|
||||
IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.PlayController");
|
||||
this.TrackService = trackService;
|
||||
this.PlayActivityService = playActivityService;
|
||||
this.ScrobbleHandler = scrobblerHandler;
|
||||
this.ReleaseService = releaseService;
|
||||
}
|
||||
|
||||
|
@ -64,6 +68,31 @@ namespace Roadie.Api.Controllers
|
|||
return File(System.Text.Encoding.Default.GetBytes(m3u), "audio/mpeg-url");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A scrobble is done at the end of a Track being played and is not exactly the same as a track activity (user can fetch a track and not play it in its entirety).
|
||||
/// </summary>
|
||||
[HttpPost("track/scrobble/{id}/{startedPlaying}/{isRandom}")]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(404)]
|
||||
public async Task<IActionResult> Scrobble(Guid id, string startedPlaying, bool isRandom)
|
||||
{
|
||||
var result = await this.ScrobbleHandler.Scrobble(await this.CurrentUserModel(), new ScrobbleInfo
|
||||
{
|
||||
TrackId = id,
|
||||
TimePlayed = SafeParser.ToDateTime(startedPlaying) ?? DateTime.UtcNow,
|
||||
IsRandomizedScrobble = isRandom
|
||||
});
|
||||
if (result == null || result.IsNotFoundResult)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This was done to use a URL based token as many clients don't support adding Auth Bearer tokens to audio requests.
|
||||
/// </summary>
|
||||
|
@ -80,7 +109,7 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
return StatusCode((int)HttpStatusCode.Unauthorized);
|
||||
}
|
||||
return await base.StreamTrack(id, this.TrackService, this.PlayActivityService, this.UserModelForUser(user));
|
||||
return await base.StreamTrack(id, this.TrackService, this.ScrobbleHandler, this.UserModelForUser(user));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
using Roadie.Library.Models.Playlists;
|
||||
|
@ -23,8 +24,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private IPlaylistService PlaylistService { get; }
|
||||
|
||||
public PlaylistController(IPlaylistService playlistService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public PlaylistController(IPlaylistService playlistService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.PlaylistController");
|
||||
this.PlaylistService = playlistService;
|
||||
|
|
|
@ -6,6 +6,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
using System;
|
||||
|
@ -24,8 +25,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private IReleaseService ReleaseService { get; }
|
||||
|
||||
public ReleaseController(IReleaseService releaseService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public ReleaseController(IReleaseService releaseService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.ReleaseController"); ;
|
||||
this.ReleaseService = releaseService;
|
||||
|
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
@ -20,8 +21,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private IStatisticsService StatisticsService { get; }
|
||||
|
||||
public StatsController(IStatisticsService statisticsService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public StatsController(IStatisticsService statisticsService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.StatsController");
|
||||
this.StatisticsService = statisticsService;
|
||||
|
|
|
@ -7,9 +7,11 @@ using Newtonsoft.Json;
|
|||
using Roadie.Api.ModelBinding;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Extensions;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.ThirdPartyApi.Subsonic;
|
||||
using Roadie.Library.Scrobble;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
@ -22,7 +24,7 @@ namespace Roadie.Api.Controllers
|
|||
[ApiController]
|
||||
public class SubsonicController : EntityControllerBase
|
||||
{
|
||||
private IPlayActivityService PlayActivityService { get; }
|
||||
private IScrobbleHandler ScrobbleHandler { get; }
|
||||
private IReleaseService ReleaseService { get; }
|
||||
private ISubsonicService SubsonicService { get; }
|
||||
|
||||
|
@ -33,14 +35,16 @@ namespace Roadie.Api.Controllers
|
|||
|
||||
private ITrackService TrackService { get; }
|
||||
|
||||
public SubsonicController(ISubsonicService subsonicService, ITrackService trackService, IReleaseService releaseService, IPlayActivityService playActivityService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public SubsonicController(ISubsonicService subsonicService, ITrackService trackService, IReleaseService releaseService,
|
||||
IScrobbleHandler scrobbleHandler, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.SubsonicController");
|
||||
this.SubsonicService = subsonicService;
|
||||
this.TrackService = trackService;
|
||||
this.ReleaseService = releaseService;
|
||||
this.PlayActivityService = playActivityService;
|
||||
this.ScrobbleHandler = scrobbleHandler;
|
||||
}
|
||||
|
||||
[HttpGet("addChatMessage.view")]
|
||||
|
@ -126,7 +130,7 @@ namespace Roadie.Api.Controllers
|
|||
var trackId = request.TrackId;
|
||||
if (trackId != null)
|
||||
{
|
||||
return await base.StreamTrack(trackId.Value, this.TrackService, this.PlayActivityService, this.SubsonicUser);
|
||||
return await base.StreamTrack(trackId.Value, this.TrackService, this.ScrobbleHandler, this.SubsonicUser);
|
||||
}
|
||||
var releaseId = request.ReleaseId;
|
||||
if (releaseId != null)
|
||||
|
@ -719,7 +723,7 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
return NotFound("Invalid TrackId");
|
||||
}
|
||||
return await base.StreamTrack(trackId.Value, this.TrackService, this.PlayActivityService, this.SubsonicUser);
|
||||
return await base.StreamTrack(trackId.Value, this.TrackService, this.ScrobbleHandler, this.SubsonicUser);
|
||||
}
|
||||
|
||||
[HttpGet("unstar.view")]
|
||||
|
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
|
@ -24,8 +25,9 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
private ITrackService TrackService { get; }
|
||||
|
||||
public TrackController(ITrackService trackService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, UserManager<ApplicationUser> userManager)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public TrackController(ITrackService trackService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
UserManager<ApplicationUser> userManager, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.TrackController");
|
||||
this.TrackService = trackService;
|
||||
|
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Roadie.Api.Services;
|
||||
using Roadie.Library.Caching;
|
||||
using Roadie.Library.Configuration;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Models.Pagination;
|
||||
using Roadie.Library.Models.Users;
|
||||
|
@ -26,8 +27,10 @@ namespace Roadie.Api.Controllers
|
|||
private IHttpContext RoadieHttpContext { get; }
|
||||
private IUserService UserService { get; }
|
||||
|
||||
public UserController(IUserService userService, ILoggerFactory logger, ICacheManager cacheManager, IConfiguration configuration, ITokenService tokenService, UserManager<ApplicationUser> userManager, IHttpContext httpContext)
|
||||
: base(cacheManager, configuration, userManager)
|
||||
public UserController(IUserService userService, ILoggerFactory logger, ICacheManager cacheManager,
|
||||
IConfiguration configuration, ITokenService tokenService, UserManager<ApplicationUser> userManager,
|
||||
IHttpContext httpContext, IRoadieSettings roadieSettings)
|
||||
: base(cacheManager, roadieSettings, userManager)
|
||||
{
|
||||
this.Logger = logger.CreateLogger("RoadieApi.Controllers.UserController");
|
||||
this.UserService = userService;
|
||||
|
@ -54,6 +57,12 @@ namespace Roadie.Api.Controllers
|
|||
{
|
||||
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
result.AdditionalClientData = new System.Collections.Generic.Dictionary<string, object>();
|
||||
if (RoadieSettings.Integrations.LastFmProviderEnabled)
|
||||
{
|
||||
var lastFmCallBackUrl = $"{ this.RoadieHttpContext.BaseUrl}/users/integration/grant?userId={ user.UserId }&iname=lastfm";
|
||||
result.AdditionalClientData.Add("lastFMIntegrationUrl", $"http://www.last.fm/api/auth/?api_key={ RoadieSettings.Integrations.LastFMApiKey }&cb={ WebUtility.UrlEncode(lastFmCallBackUrl) }");
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
@ -264,6 +273,19 @@ namespace Roadie.Api.Controllers
|
|||
return Ok(result);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("integration/grant")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> IntegrationGrant(Guid userId, string iname, string token)
|
||||
{
|
||||
var result = await this.UserService.UpdateIntegrationGrant(userId, iname, token);
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return Content($"Error while attempting to enable integration");
|
||||
}
|
||||
return Content($"Successfully enabled integration!");
|
||||
}
|
||||
|
||||
[HttpPost("profile/edit")]
|
||||
[ProducesResponseType(200)]
|
||||
public async Task<IActionResult> UpdateProfile(User model)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"Roadie.Api": {
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
"ASPNETCORE_ENVIRONMENT": "Production"
|
||||
},
|
||||
"applicationUrl": "http://localhost:5123/"
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ using Roadie.Library.Data;
|
|||
using Roadie.Library.Encoding;
|
||||
using Roadie.Library.Identity;
|
||||
using Roadie.Library.Imaging;
|
||||
using Roadie.Library.MetaData.LastFm;
|
||||
using Roadie.Library.Scrobble;
|
||||
using Roadie.Library.Utility;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
@ -154,28 +156,29 @@ namespace Roadie.Api
|
|||
if (integrationKeys != null)
|
||||
{
|
||||
settings.Integrations.ApiKeys = new System.Collections.Generic.List<ApiKey>
|
||||
{
|
||||
new ApiKey
|
||||
{
|
||||
ApiName = "LastFMApiKey",
|
||||
Key = integrationKeys.LastFMApiKey,
|
||||
KeySecret = integrationKeys.LastFMSecret
|
||||
},
|
||||
new ApiKey
|
||||
{
|
||||
ApiName = "DiscogsConsumerKey",
|
||||
Key = integrationKeys.DiscogsConsumerKey,
|
||||
KeySecret = integrationKeys.DiscogsConsumerSecret
|
||||
},
|
||||
new ApiKey
|
||||
{
|
||||
ApiName = "BingImageSearch",
|
||||
Key = integrationKeys.BingImageSearch
|
||||
}
|
||||
};
|
||||
new ApiKey
|
||||
{
|
||||
ApiName = "LastFMApiKey",
|
||||
Key = integrationKeys.LastFMApiKey,
|
||||
KeySecret = integrationKeys.LastFMSecret
|
||||
},
|
||||
new ApiKey
|
||||
{
|
||||
ApiName = "DiscogsConsumerKey",
|
||||
Key = integrationKeys.DiscogsConsumerKey,
|
||||
KeySecret = integrationKeys.DiscogsConsumerSecret
|
||||
},
|
||||
new ApiKey
|
||||
{
|
||||
ApiName = "BingImageSearch",
|
||||
Key = integrationKeys.BingImageSearch
|
||||
}
|
||||
};
|
||||
}
|
||||
return settings;
|
||||
});
|
||||
|
||||
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
|
||||
services.AddSingleton<IDefaultNotFoundImages, DefaultNotFoundImages>();
|
||||
services.AddScoped<IStatisticsService, StatisticsService>();
|
||||
|
@ -189,6 +192,7 @@ namespace Roadie.Api
|
|||
services.AddScoped<ILabelService, LabelService>();
|
||||
services.AddScoped<IPlaylistService, PlaylistService>();
|
||||
services.AddScoped<IPlayActivityService, PlayActivityService>();
|
||||
services.AddScoped<IScrobbleHandler, ScrobbleHandler>();
|
||||
services.AddScoped<IGenreService, GenreService>();
|
||||
services.AddScoped<ISubsonicService, SubsonicService>();
|
||||
services.AddScoped<IUserService, UserService>();
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
"Integrations": {
|
||||
"ITunesProviderEnabled": true,
|
||||
"MusicBrainzProviderEnabled": true,
|
||||
"LastFmProviderEnabled": true,
|
||||
"SpotifyProviderEnabled": true
|
||||
},
|
||||
"Processing": {
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
"OGGConvertCommand": "ffmpeg -i \"{0}\" -acodec libmp3lame -q:a 0 \"{1}\"\"",
|
||||
"APEConvertCommand": "ffmpeg -i \"{0}\" \"{1}\""
|
||||
},
|
||||
"SmtpFromAddress": "roadie@roadie.net",
|
||||
"SmtpFromAddress": "roadie@roadie.rocks",
|
||||
"SmtpPort": 2525,
|
||||
"SmtpUsername": "apikey",
|
||||
"SmtpHost": "smtp.sendgrid.net",
|
||||
|
@ -91,21 +91,22 @@
|
|||
"Integrations": {
|
||||
"ITunesProviderEnabled": true,
|
||||
"MusicBrainzProviderEnabled": true,
|
||||
"LastFmProviderEnabled": true,
|
||||
"SpotifyProviderEnabled": true,
|
||||
"ApiKeys": [
|
||||
{
|
||||
"ApiName": "BingImageSearch",
|
||||
"Key": "<KEY HERE>"
|
||||
"Key": "629eb0b425ff4cbfb926b262ccd2e313"
|
||||
},
|
||||
{
|
||||
"ApiName": "LastFMApiKey",
|
||||
"Key": "<KEY HERE>",
|
||||
"KeySecret": "<SECRET HERE>"
|
||||
"Key": "5175afa1b9ec99789f4e4e8955c1a19b",
|
||||
"KeySecret": "38dd80ade64bd094e82a690046fce1d1"
|
||||
},
|
||||
{
|
||||
"ApiName": "DiscogsConsumerKey",
|
||||
"Key": "<KEY HERE>",
|
||||
"KeySecret": "<SECRET HERE>"
|
||||
"Key": "qLhWWShjypNgLDDkpPTB",
|
||||
"KeySecret": "fPzsROOQvvqBtuVsqEFnASLnRumaMDvh"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2010
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29001.49
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api", "Roadie.Api\Roadie.Api.csproj", "{68C80416-0D72-409D-B727-3FEA7AB7FD2C}"
|
||||
EndProject
|
||||
|
@ -12,13 +12,14 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{1BA7115B-6E37-4546-BBD6-C8B0787A3FE0}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
roadie.sql = roadie.sql
|
||||
Upgrade0001.sql = Upgrade0001.sql
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Services", "Roadie.Api.Services\Roadie.Api.Services.csproj", "{7B37031E-F2AE-4BE2-9F6F-005CA7A6FDF1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roadie.Api.Hubs", "Roadie.Api.Hubs\Roadie.Api.Hubs.csproj", "{E740C89E-3363-4577-873B-0871823E252C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inspector", "Inspector\Inspector.csproj", "{9A0831DC-343A-4E0C-8617-AF62426F3BA8}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Inspector", "Inspector\Inspector.csproj", "{9A0831DC-343A-4E0C-8617-AF62426F3BA8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
|
2
Upgrade0001.sql
Normal file
2
Upgrade0001.sql
Normal file
|
@ -0,0 +1,2 @@
|
|||
-- Add new table to existing user table if not already added
|
||||
ALTER TABLE `user` ADD COLUMN IF NOT EXISTS `lastFMSessionKey` varchar(50) NULL;
|
|
@ -658,6 +658,7 @@ CREATE TABLE `user` (
|
|||
`PhoneNumber` varchar(100) DEFAULT NULL,
|
||||
`PhoneNumberConfirmed` bit(1) DEFAULT NULL,
|
||||
`removeTrackFromQueAfterPlayed` bit(1) DEFAULT NULL,
|
||||
`lastFMSessionKey` varchar(50) DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `email` (`email`),
|
||||
UNIQUE KEY `ix_user_username` (`username`),
|
||||
|
|
Loading…
Reference in a new issue