mirror of
https://github.com/Sarsoo/IF.Lastfm.git
synced 2024-10-16 23:13:07 +01:00
Merge pull request #46 from zumicts/master
This commit is contained in:
commit
27f2b6f836
@ -17,8 +17,13 @@ public class TrackScrobbleCommandTests : CommandIntegrationTestsBase
|
||||
[TestMethod]
|
||||
public async Task ScrobblesSingle()
|
||||
{
|
||||
var trackPlayed = DateTime.UtcNow;
|
||||
var testScrobble = new Scrobble("Hot Chip", "The Warning", "Over and Over", trackPlayed, "Hot Chip", false);
|
||||
var trackPlayed = DateTime.UtcNow.AddMinutes(-1);
|
||||
var testScrobble = new Scrobble("Hot Chip", "The Warning", "Over and Over")
|
||||
{
|
||||
AlbumArtist = ARTIST_NAME,
|
||||
TimePlayed = trackPlayed,
|
||||
ChosenByUser = false
|
||||
};
|
||||
|
||||
var trackApi = new TrackApi(Auth);
|
||||
var response = await trackApi.ScrobbleAsync(testScrobble);
|
||||
@ -26,7 +31,7 @@ public async Task ScrobblesSingle()
|
||||
Assert.IsTrue(response.Success);
|
||||
|
||||
var userApi = new UserApi(Auth);
|
||||
var tracks = await userApi.GetRecentScrobbles(Auth.UserSession.Username, trackPlayed.AddSeconds(-10), 0, 1);
|
||||
var tracks = await userApi.GetRecentScrobbles(Auth.UserSession.Username, null, 0, 1);
|
||||
|
||||
var expectedTrack = new LastTrack
|
||||
{
|
||||
@ -40,7 +45,7 @@ public async Task ScrobblesSingle()
|
||||
"http://userserve-ak.last.fm/serve/64s/50921593.png",
|
||||
"http://userserve-ak.last.fm/serve/126/50921593.png",
|
||||
"http://userserve-ak.last.fm/serve/300x300/50921593.png"),
|
||||
TimePlayed = trackPlayed
|
||||
TimePlayed = trackPlayed.RoundToNearestSecond()
|
||||
};
|
||||
|
||||
var expectedJson = expectedTrack.TestSerialise();
|
||||
|
@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using IF.Lastfm.Core.Api;
|
||||
using IF.Lastfm.Core.Api.Commands.TrackApi;
|
||||
using IF.Lastfm.Core.Objects;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace IF.Lastfm.Core.Tests.Integration.Commands
|
||||
{
|
||||
[TestClass]
|
||||
public class TrackUpdateNowPlayingCommandTests : CommandIntegrationTestsBase
|
||||
{
|
||||
private const string ARTIST_NAME = "Crystal Castles";
|
||||
private const string ALBUM_NAME = "Crystal Castles ( II )";
|
||||
private const string TRACK_NAME = "Not in Love";
|
||||
|
||||
[TestMethod]
|
||||
public async Task UpdatesNowPlaying()
|
||||
{
|
||||
var trackPlayed = DateTime.UtcNow.AddMinutes(-1);
|
||||
var testScrobble = new Scrobble(ARTIST_NAME, ALBUM_NAME, TRACK_NAME)
|
||||
{
|
||||
TimePlayed = trackPlayed,
|
||||
Duration = new TimeSpan(0, 3, 49),
|
||||
AlbumArtist = ARTIST_NAME
|
||||
};
|
||||
|
||||
var trackApi = new TrackApi(Auth);
|
||||
var response = await trackApi.UpdateNowPlayingAsync(testScrobble);
|
||||
|
||||
Assert.IsTrue(response.Success);
|
||||
|
||||
var userApi = new UserApi(Auth);
|
||||
var tracks = await userApi.GetRecentScrobbles(Auth.UserSession.Username, null, 1, 1);
|
||||
|
||||
var expectedTrack = new LastTrack
|
||||
{
|
||||
Name = TRACK_NAME,
|
||||
ArtistName = ARTIST_NAME,
|
||||
AlbumName = ALBUM_NAME,
|
||||
Mbid = "1b9ee1d8-c5a7-44d9-813e-85beb0d59f1b",
|
||||
ArtistMbid = "b1570544-93ab-4b2b-8398-131735394202",
|
||||
Url = new Uri("http://www.last.fm/music/Crystal+Castles/_/Not+in+Love"),
|
||||
Images = new LastImageSet("http://userserve-ak.last.fm/serve/34s/61473043.png",
|
||||
"http://userserve-ak.last.fm/serve/64s/61473043.png",
|
||||
"http://userserve-ak.last.fm/serve/126/61473043.png",
|
||||
"http://userserve-ak.last.fm/serve/300x300/61473043.png"),
|
||||
IsNowPlaying = true
|
||||
};
|
||||
|
||||
var expectedJson = expectedTrack.TestSerialise();
|
||||
var actualJson = tracks.Content.FirstOrDefault().TestSerialise();
|
||||
|
||||
Assert.AreEqual(expectedJson, actualJson, expectedJson.DifferencesTo(actualJson));
|
||||
}
|
||||
}
|
||||
}
|
@ -52,6 +52,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="Commands\CommandIntegrationTestsBase.cs" />
|
||||
<Compile Include="Commands\TrackScrobbleCommandTests.cs" />
|
||||
<Compile Include="Commands\TrackUpdateNowPlayingCommandTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -18,7 +18,7 @@ public class UserGetRecentTracksCommandTests : CommandTestsBase
|
||||
[TestInitialize]
|
||||
public void Initialise()
|
||||
{
|
||||
_command = new UserGetRecentTracksCommand(MAuth.Object, "rj", DateTime.MinValue)
|
||||
_command = new UserGetRecentTracksCommand(MAuth.Object, "rj")
|
||||
{
|
||||
Count = 1
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
@ -7,13 +8,13 @@ namespace IF.Lastfm.Core.Tests
|
||||
{
|
||||
public static class TestHelper
|
||||
{
|
||||
private static JsonSerializerSettings _testSerialiserSettings;
|
||||
private static readonly JsonSerializerSettings _testSerialiserSettings;
|
||||
|
||||
static TestHelper()
|
||||
{
|
||||
_testSerialiserSettings = new JsonSerializerSettings
|
||||
{
|
||||
DateFormatString = "yyyy-MM-dd HH:mm:ss.SSS zzz",
|
||||
DateFormatString = "yyyy-MM-dd HH:mm:ss.fff",
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
}
|
||||
@ -73,5 +74,14 @@ public static IEnumerable<T> WrapEnumerable<T>(this T t)
|
||||
{
|
||||
return new[] {t};
|
||||
}
|
||||
|
||||
public static DateTime RoundToNearestSecond(this DateTime dt)
|
||||
{
|
||||
var ms = dt.Millisecond;
|
||||
|
||||
return ms < 500
|
||||
? dt.AddMilliseconds(-ms)
|
||||
: dt.AddMilliseconds(1000 - ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using IF.Lastfm.Core.Api.Commands.ArtistApi;
|
||||
using IF.Lastfm.Core.Api.Helpers;
|
||||
using IF.Lastfm.Core.Objects;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace IF.Lastfm.Core.Api
|
||||
{
|
||||
public class ArtistApi : IArtistApi
|
||||
{
|
||||
public ILastAuth Auth { get; private set; }
|
||||
|
||||
public ArtistApi(ILastAuth auth)
|
||||
{
|
||||
Auth = auth;
|
||||
}
|
||||
|
||||
public ILastAuth Auth { get; private set; }
|
||||
|
||||
public async Task<LastResponse<LastArtist>> GetArtistInfoAsync(string artist,
|
||||
string bioLang = LastFm.DefaultLanguageCode,
|
||||
bool autocorrect = false)
|
||||
@ -31,8 +34,8 @@ public async Task<LastResponse<LastArtist>> GetArtistInfoAsync(string artist,
|
||||
}
|
||||
|
||||
public async Task<LastResponse<LastArtist>> GetArtistInfoByMbidAsync(string mbid,
|
||||
string bioLang = LastFm.DefaultLanguageCode,
|
||||
bool autocorrect = false)
|
||||
string bioLang = LastFm.DefaultLanguageCode,
|
||||
bool autocorrect = false)
|
||||
{
|
||||
var command = new GetArtistInfoCommand(Auth)
|
||||
{
|
||||
@ -70,7 +73,8 @@ public async Task<PageResponse<LastTrack>> GetTopTracksForArtistAsync(string art
|
||||
return await command.ExecuteAsync();
|
||||
}
|
||||
|
||||
public async Task<PageResponse<LastArtist>> GetSimilarArtistsAsync(string artistname, bool autocorrect = false, int limit = 100)
|
||||
public async Task<PageResponse<LastArtist>> GetSimilarArtistsAsync(string artistname, bool autocorrect = false,
|
||||
int limit = 100)
|
||||
{
|
||||
var command = new GetSimilarArtistsCommand(Auth, artistname)
|
||||
{
|
||||
@ -94,14 +98,15 @@ public async Task<PageResponse<LastTag>> GetTopTagsForArtistAsync(string artist,
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task<PageResponse<LastShout>> GetShoutsForArtistAsync(string artist, int page = 0, int count = LastFm.DefaultPageLength, bool autocorrect = false)
|
||||
public async Task<PageResponse<LastShout>> GetShoutsForArtistAsync(string artist, int page = 0,
|
||||
int count = LastFm.DefaultPageLength, bool autocorrect = false)
|
||||
{
|
||||
var command = new GetArtistShoutsCommand(Auth, artist)
|
||||
{
|
||||
Autocorrect = autocorrect,
|
||||
Page = page,
|
||||
Count = count
|
||||
};
|
||||
{
|
||||
Autocorrect = autocorrect,
|
||||
Page = page,
|
||||
Count = count
|
||||
};
|
||||
return await command.ExecuteAsync();
|
||||
}
|
||||
|
||||
@ -112,7 +117,8 @@ public async Task<LastResponse> AddShoutAsync(string artistname, string messaage
|
||||
return await command.ExecuteAsync();
|
||||
}
|
||||
|
||||
public async Task<PageResponse<LastArtist>> SearchForArtistAsync(string artistname, int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
|
||||
public async Task<PageResponse<LastArtist>> SearchForArtistAsync(string artistname, int page = 1,
|
||||
int itemsPerPage = LastFm.DefaultPageLength)
|
||||
{
|
||||
var command = new SearchArtistsCommand(Auth, artistname)
|
||||
{
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
namespace IF.Lastfm.Core.Api.Commands
|
||||
{
|
||||
internal abstract class PostAsyncCommandBase<T> : LastAsyncCommandBase<T> where T : LastResponse, new()
|
||||
public abstract class PostAsyncCommandBase<T> : LastAsyncCommandBase<T> where T : LastResponse, new()
|
||||
{
|
||||
protected PostAsyncCommandBase(ILastAuth auth)
|
||||
{
|
||||
|
@ -16,11 +16,11 @@ internal class TrackScrobbleCommand : PostAsyncCommandBase<LastResponse>
|
||||
|
||||
public string AlbumArtist { get; set; }
|
||||
|
||||
public DateTime TimePlayed { get; set; }
|
||||
public DateTime? TimePlayed { get; set; }
|
||||
|
||||
public bool ChosenByUser { get; set; }
|
||||
|
||||
public TrackScrobbleCommand(ILastAuth auth, string artist, string album, string track, string albumArtist, DateTime timeplayed)
|
||||
public TrackScrobbleCommand(ILastAuth auth, string artist, string album, string track, string albumArtist, DateTime? timeplayed)
|
||||
: base(auth)
|
||||
{
|
||||
Method = "track.scrobble";
|
||||
@ -45,7 +45,11 @@ public override void SetParameters()
|
||||
Parameters.Add("track", Track);
|
||||
Parameters.Add("albumArtist", AlbumArtist);
|
||||
Parameters.Add("chosenByUser", Convert.ToInt32(ChosenByUser).ToString());
|
||||
Parameters.Add("timestamp", TimePlayed.ToUnixTimestamp().ToString());
|
||||
|
||||
if (TimePlayed.HasValue)
|
||||
{
|
||||
Parameters.Add("timestamp", TimePlayed.Value.ToUnixTimestamp().ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public async override Task<LastResponse> HandleResponse(HttpResponseMessage response)
|
||||
|
@ -0,0 +1,69 @@
|
||||
using IF.Lastfm.Core.Api.Enums;
|
||||
using IF.Lastfm.Core.Api.Helpers;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace IF.Lastfm.Core.Api.Commands.TrackApi
|
||||
{
|
||||
internal class TrackUpdateNowPlayingCommand : PostAsyncCommandBase<LastResponse>
|
||||
{
|
||||
public string Artist { get; set; }
|
||||
|
||||
public string Album { get; set; }
|
||||
|
||||
public string Track { get; set; }
|
||||
|
||||
public string AlbumArtist { get; set; }
|
||||
|
||||
public bool ChosenByUser { get; set; }
|
||||
|
||||
public TimeSpan? Duration { get; set; }
|
||||
|
||||
public TrackUpdateNowPlayingCommand(ILastAuth auth, string artist, string album, string track)
|
||||
: base(auth)
|
||||
{
|
||||
Method = "track.updateNowPlaying";
|
||||
|
||||
Artist = artist;
|
||||
Album = album;
|
||||
Track = track;
|
||||
}
|
||||
|
||||
public TrackUpdateNowPlayingCommand(ILastAuth auth, Scrobble scrobble)
|
||||
: this(auth, scrobble.Artist, scrobble.Album, scrobble.Track)
|
||||
{
|
||||
ChosenByUser = scrobble.ChosenByUser;
|
||||
Duration = scrobble.Duration;
|
||||
}
|
||||
|
||||
public override void SetParameters()
|
||||
{
|
||||
Parameters.Add("artist", Artist);
|
||||
Parameters.Add("album", Album);
|
||||
Parameters.Add("track", Track);
|
||||
Parameters.Add("albumArtist", AlbumArtist);
|
||||
Parameters.Add("chosenByUser", Convert.ToInt32(ChosenByUser).ToString());
|
||||
|
||||
if (Duration.HasValue)
|
||||
{
|
||||
Parameters.Add("duration", Duration.Value.TotalSeconds.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public async override Task<LastResponse> HandleResponse(HttpResponseMessage response)
|
||||
{
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
|
||||
LastFmApiError error;
|
||||
if (LastFm.IsResponseValid(json, out error) && response.IsSuccessStatusCode)
|
||||
{
|
||||
return LastResponse.CreateSuccessResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
return LastResponse.CreateErrorResponse<LastResponse>(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,20 +15,23 @@ internal class UserGetRecentTracksCommand : GetAsyncCommandBase<PageResponse<Las
|
||||
{
|
||||
public string Username { get; private set; }
|
||||
|
||||
public DateTime From { get; private set; }
|
||||
public DateTime? From { get; set; }
|
||||
|
||||
public UserGetRecentTracksCommand(ILastAuth auth, string username, DateTime from) : base(auth)
|
||||
public UserGetRecentTracksCommand(ILastAuth auth, string username) : base(auth)
|
||||
{
|
||||
Method = "user.getRecentTracks";
|
||||
|
||||
Username = username;
|
||||
From = from;
|
||||
}
|
||||
|
||||
public override void SetParameters()
|
||||
{
|
||||
Parameters.Add("user", Username);
|
||||
Parameters.Add("from", From.ToUnixTimestamp().ToString());
|
||||
|
||||
if (From.HasValue)
|
||||
{
|
||||
Parameters.Add("from", From.Value.ToUnixTimestamp().ToString());
|
||||
}
|
||||
|
||||
AddPagingParameters();
|
||||
DisableCaching();
|
||||
|
@ -29,7 +29,7 @@ public static string GetApiName(this Enum enumValue)
|
||||
|
||||
public static int ToUnixTimestamp(this DateTime dt)
|
||||
{
|
||||
var d = (dt - new DateTime(1970, 1, 1).ToUniversalTime()).TotalSeconds;
|
||||
var d = (dt - new DateTime(1970, 1, 1)).TotalSeconds;
|
||||
|
||||
return Convert.ToInt32(d);
|
||||
}
|
||||
|
@ -10,15 +10,16 @@ public interface IUserApi
|
||||
{
|
||||
ILastAuth Auth { get; }
|
||||
|
||||
Task<PageResponse<LastArtist>> GetRecommendedArtistsAsync(
|
||||
int page = 1,
|
||||
int itemsPerPage = LastFm.DefaultPageLength);
|
||||
|
||||
Task<PageResponse<LastAlbum>> GetTopAlbums(string username,
|
||||
LastStatsTimeSpan span,
|
||||
int startIndex = 0,
|
||||
int endIndex = LastFm.DefaultPageLength);
|
||||
|
||||
Task<PageResponse<LastTrack>> GetRecentScrobbles(string username,
|
||||
DateTime since,
|
||||
int startIndex = 0,
|
||||
int endIndex = LastFm.DefaultPageLength);
|
||||
Task<PageResponse<LastTrack>> GetRecentScrobbles(string username, DateTime? since = null, int pagenumber = 0, int count = LastFm.DefaultPageLength);
|
||||
|
||||
Task<PageResponse<LastStation>> GetRecentStations(string username,
|
||||
int pagenumber,
|
||||
|
@ -2,7 +2,6 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Arcnet.MyConvert;
|
||||
using IF.Lastfm.Core.Api.Commands.AuthApi;
|
||||
using IF.Lastfm.Core.Api.Helpers;
|
||||
using IF.Lastfm.Core.Objects;
|
||||
@ -74,7 +73,7 @@ public string GenerateMethodSignature(string method, Dictionary<string, string>
|
||||
|
||||
builder.Append(_apiSecret);
|
||||
|
||||
var md5 = builder.ToString().ToMd5();
|
||||
var md5 = MD5.GetHashString(builder.ToString());
|
||||
|
||||
return md5;
|
||||
|
||||
|
@ -7,22 +7,26 @@ public class Scrobble
|
||||
#region Properties
|
||||
|
||||
public string Artist { get; private set; }
|
||||
public string AlbumArtist { get; private set; }
|
||||
|
||||
public string AlbumArtist { get; set; }
|
||||
|
||||
public string Album { get; private set; }
|
||||
|
||||
public string Track { get; private set; }
|
||||
public DateTime TimePlayed { get; private set; }
|
||||
public bool ChosenByUser { get; private set; }
|
||||
|
||||
public DateTime? TimePlayed { get; set; }
|
||||
|
||||
public bool ChosenByUser { get; set; }
|
||||
|
||||
public TimeSpan? Duration { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public Scrobble(string artist, string album, string track, DateTime timeplayed, string albumartist = "", bool chosenByUser = true)
|
||||
public Scrobble(string artist, string album, string track)
|
||||
{
|
||||
Artist = artist;
|
||||
Album = album;
|
||||
Track = track;
|
||||
TimePlayed = timeplayed;
|
||||
AlbumArtist = string.IsNullOrWhiteSpace(albumartist) ? artist : albumartist;
|
||||
ChosenByUser = chosenByUser;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,7 @@
|
||||
using IF.Lastfm.Core.Api.Commands.TrackApi;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using IF.Lastfm.Core.Api.Commands.TrackApi;
|
||||
using IF.Lastfm.Core.Api.Enums;
|
||||
using IF.Lastfm.Core.Api.Helpers;
|
||||
using IF.Lastfm.Core.Objects;
|
||||
using System.Threading.Tasks;
|
||||
@ -20,6 +23,12 @@ public Task<LastResponse> ScrobbleAsync(Scrobble scrobble)
|
||||
return command.ExecuteAsync();
|
||||
}
|
||||
|
||||
public Task<LastResponse> UpdateNowPlayingAsync(Scrobble scrobble)
|
||||
{
|
||||
var command = new TrackUpdateNowPlayingCommand(Auth, scrobble);
|
||||
return command.ExecuteAsync();
|
||||
}
|
||||
|
||||
public async Task<PageResponse<LastShout>> GetShoutsForTrackAsync(string trackname, string artistname, bool autocorrect = false, int page = 0, int count = LastFm.DefaultPageLength)
|
||||
{
|
||||
var command = new GetTrackShoutsCommand(Auth, trackname, artistname)
|
||||
|
@ -16,6 +16,16 @@ public UserApi(ILastAuth auth)
|
||||
Auth = auth;
|
||||
}
|
||||
|
||||
public async Task<PageResponse<LastArtist>> GetRecommendedArtistsAsync(int page = 1, int itemsPerPage = LastFm.DefaultPageLength)
|
||||
{
|
||||
var command = new GetRecommendedArtistsCommand(Auth)
|
||||
{
|
||||
Page = page,
|
||||
Count = itemsPerPage
|
||||
};
|
||||
return await command.ExecuteAsync();
|
||||
}
|
||||
|
||||
public async Task<PageResponse<LastAlbum>> GetTopAlbums(string username, LastStatsTimeSpan span, int pagenumber = 0, int count = LastFm.DefaultPageLength)
|
||||
{
|
||||
var command = new GetTopAlbumsCommand(Auth, username, span)
|
||||
@ -27,12 +37,21 @@ public async Task<PageResponse<LastAlbum>> GetTopAlbums(string username, LastSta
|
||||
return await command.ExecuteAsync();
|
||||
}
|
||||
|
||||
public async Task<PageResponse<LastTrack>> GetRecentScrobbles(string username, DateTime since, int pagenumber = 0, int count = LastFm.DefaultPageLength)
|
||||
/// <summary>
|
||||
/// Gets a list of recent scrobbled tracks for this user in reverse date order.
|
||||
/// </summary>
|
||||
/// <param name="username">Username to get scrobbles for.</param>
|
||||
/// <param name="since">Lower threshold for scrobbles. Will not return scrobbles from before this time.</param>
|
||||
/// <param name="pagenumber">Page numbering starts from 1. If set to 0, will not include the "now playing" track</param>
|
||||
/// <param name="count">Amount of scrobbles to return for this page.</param>
|
||||
/// <returns>Enumerable of LastTrack</returns>
|
||||
public async Task<PageResponse<LastTrack>> GetRecentScrobbles(string username, DateTime? since = null, int pagenumber = 0, int count = LastFm.DefaultPageLength)
|
||||
{
|
||||
var command = new UserGetRecentTracksCommand(Auth, username, since)
|
||||
var command = new UserGetRecentTracksCommand(Auth, username)
|
||||
{
|
||||
Page = pagenumber,
|
||||
Count = count
|
||||
Count = count,
|
||||
From = since
|
||||
};
|
||||
|
||||
return await command.ExecuteAsync();
|
||||
|
@ -43,6 +43,7 @@
|
||||
<Compile Include="Api\Commands\AlbumApi\GetAlbumTopTagsCommand.cs" />
|
||||
<Compile Include="Api\Commands\LibraryApi\LibraryGetTracksCommand.cs" />
|
||||
<Compile Include="Api\Commands\TrackApi\TrackScrobbleCommand.cs" />
|
||||
<Compile Include="Api\Commands\TrackApi\TrackUpdateNowPlayingCommand.cs" />
|
||||
<Compile Include="Api\Commands\UnauthenticatedPostAsyncCommandBase.cs" />
|
||||
<Compile Include="Api\Commands\UserApi\GetRecommendedArtistsCommand.cs" />
|
||||
<Compile Include="Api\ILibraryApi.cs" />
|
||||
@ -98,6 +99,7 @@
|
||||
<Compile Include="Api\UserApi.cs" />
|
||||
<Compile Include="Json\LastFmBooleanConverter.cs" />
|
||||
<Compile Include="LastFm.cs" />
|
||||
<Compile Include="MD5.cs" />
|
||||
<Compile Include="Objects\LastAlbum.cs" />
|
||||
<Compile Include="Objects\LastArtist.cs" />
|
||||
<Compile Include="Objects\BuyLink.cs" />
|
||||
@ -114,9 +116,6 @@
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="MyConvert">
|
||||
<HintPath>..\..\packages\MyConvert.1.0.1.6\lib\MyConvert.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>..\..\packages\Newtonsoft.Json.6.0.5\lib\portable-net45+wp80+win8+wpa81\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
|
295
src/IF.Lastfm.Core/MD5.cs
Normal file
295
src/IF.Lastfm.Core/MD5.cs
Normal file
@ -0,0 +1,295 @@
|
||||
//// **************************************************************
|
||||
//// * Raw implementation of the MD5 hash algorithm
|
||||
//// * from RFC 1321.
|
||||
//// *
|
||||
//// * Written By: Reid Borsuk and Jenny Zheng
|
||||
//// * Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//// **************************************************************
|
||||
|
||||
#region
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
#endregion
|
||||
|
||||
namespace IF.Lastfm.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple struct for the (a,b,c,d) which is used to compute the mesage digest.
|
||||
/// </summary>
|
||||
internal struct ABCDStruct
|
||||
{
|
||||
public uint A;
|
||||
public uint B;
|
||||
public uint C;
|
||||
public uint D;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raw implementation of the MD5 hash algorithm rom RFC 1321.
|
||||
/// </summary>
|
||||
public sealed class MD5
|
||||
{
|
||||
//// Prevent CSC from adding a default public constructor
|
||||
private MD5()
|
||||
{
|
||||
}
|
||||
|
||||
public static byte[] GetHash(string input, Encoding encoding)
|
||||
{
|
||||
if (null == input)
|
||||
{
|
||||
throw new ArgumentNullException("input", "Unable to calculate hash over null input data");
|
||||
}
|
||||
if (null == encoding)
|
||||
{
|
||||
throw new ArgumentNullException("encoding",
|
||||
"Unable to calculate hash over a string without a default encoding. Consider using the GetHash(string) overload to use UTF8 Encoding");
|
||||
}
|
||||
var target = encoding.GetBytes(input);
|
||||
return GetHash(target);
|
||||
}
|
||||
|
||||
public static byte[] GetHash(string input)
|
||||
{
|
||||
return GetHash(input, new UTF8Encoding());
|
||||
}
|
||||
|
||||
public static string GetHashString(byte[] input)
|
||||
{
|
||||
if (null == input)
|
||||
{
|
||||
throw new ArgumentNullException("input", "Unable to calculate hash over null input data");
|
||||
}
|
||||
var retval = BitConverter.ToString(GetHash(input));
|
||||
retval = retval.Replace("-", string.Empty);
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static string GetHashString(string input, Encoding encoding)
|
||||
{
|
||||
if (null == input)
|
||||
{
|
||||
throw new ArgumentNullException("input", "Unable to calculate hash over null input data");
|
||||
}
|
||||
if (null == encoding)
|
||||
{
|
||||
throw new ArgumentNullException("encoding",
|
||||
"Unable to calculate hash over a string without a default encoding. Consider using the GetHashString(string) overload to use UTF8 Encoding");
|
||||
}
|
||||
var target = encoding.GetBytes(input);
|
||||
return GetHashString(target);
|
||||
}
|
||||
|
||||
public static string GetHashString(string input)
|
||||
{
|
||||
return GetHashString(input, new UTF8Encoding());
|
||||
}
|
||||
|
||||
public static byte[] GetHash(byte[] input)
|
||||
{
|
||||
if (null == input)
|
||||
{
|
||||
throw new ArgumentNullException("input", "Unable to calculate hash over null input data");
|
||||
}
|
||||
|
||||
//// Intitial values defined in RFC 1321
|
||||
var abcd = new ABCDStruct();
|
||||
abcd.A = 0x67452301;
|
||||
abcd.B = 0xefcdab89;
|
||||
abcd.C = 0x98badcfe;
|
||||
abcd.D = 0x10325476;
|
||||
|
||||
//// We pass in the input array by block, the final block of data must be handled specialy for padding & length embeding
|
||||
var startIndex = 0;
|
||||
while (startIndex <= input.Length - 64)
|
||||
{
|
||||
GetHashBlock(input, ref abcd, startIndex);
|
||||
startIndex += 64;
|
||||
}
|
||||
//// The final data block.
|
||||
return GetHashFinalBlock(input, startIndex, input.Length - startIndex, abcd, (Int64) input.Length*8);
|
||||
}
|
||||
|
||||
internal static byte[] GetHashFinalBlock(byte[] input, int ibStart, int cbSize, ABCDStruct ABCD, Int64 len)
|
||||
{
|
||||
var working = new byte[64];
|
||||
var length = BitConverter.GetBytes(len);
|
||||
|
||||
//// Padding is a single bit 1, followed by the number of 0s required to make size congruent to 448 modulo 512. Step 1 of RFC 1321
|
||||
//// The CLR ensures that our buffer is 0-assigned, we don't need to explicitly set it. This is why it ends up being quicker to just
|
||||
//// use a temporary array rather then doing in-place assignment (5% for small inputs)
|
||||
Array.Copy(input, ibStart, working, 0, cbSize);
|
||||
working[cbSize] = 0x80;
|
||||
|
||||
//// We have enough room to store the length in this chunk
|
||||
if (cbSize < 56)
|
||||
{
|
||||
Array.Copy(length, 0, working, 56, 8);
|
||||
GetHashBlock(working, ref ABCD, 0);
|
||||
}
|
||||
else //// We need an aditional chunk to store the length
|
||||
{
|
||||
GetHashBlock(working, ref ABCD, 0);
|
||||
//// Create an entirely new chunk due to the 0-assigned trick mentioned above, to avoid an extra function call clearing the array
|
||||
working = new byte[64];
|
||||
Array.Copy(length, 0, working, 56, 8);
|
||||
GetHashBlock(working, ref ABCD, 0);
|
||||
}
|
||||
var output = new byte[16];
|
||||
Array.Copy(BitConverter.GetBytes(ABCD.A), 0, output, 0, 4);
|
||||
Array.Copy(BitConverter.GetBytes(ABCD.B), 0, output, 4, 4);
|
||||
Array.Copy(BitConverter.GetBytes(ABCD.C), 0, output, 8, 4);
|
||||
Array.Copy(BitConverter.GetBytes(ABCD.D), 0, output, 12, 4);
|
||||
return output;
|
||||
}
|
||||
|
||||
//// Performs a single block transform of MD5 for a given set of ABCD inputs
|
||||
/* If implementing your own hashing framework, be sure to set the initial ABCD correctly according to RFC 1321:
|
||||
// A = 0x67452301;
|
||||
// B = 0xefcdab89;
|
||||
// C = 0x98badcfe;
|
||||
// D = 0x10325476;
|
||||
*/
|
||||
|
||||
internal static void GetHashBlock(byte[] input, ref ABCDStruct ABCDValue, int ibStart)
|
||||
{
|
||||
var temp = Converter(input, ibStart);
|
||||
var a = ABCDValue.A;
|
||||
var b = ABCDValue.B;
|
||||
var c = ABCDValue.C;
|
||||
var d = ABCDValue.D;
|
||||
|
||||
a = r1(a, b, c, d, temp[0], 7, 0xd76aa478);
|
||||
d = r1(d, a, b, c, temp[1], 12, 0xe8c7b756);
|
||||
c = r1(c, d, a, b, temp[2], 17, 0x242070db);
|
||||
b = r1(b, c, d, a, temp[3], 22, 0xc1bdceee);
|
||||
a = r1(a, b, c, d, temp[4], 7, 0xf57c0faf);
|
||||
d = r1(d, a, b, c, temp[5], 12, 0x4787c62a);
|
||||
c = r1(c, d, a, b, temp[6], 17, 0xa8304613);
|
||||
b = r1(b, c, d, a, temp[7], 22, 0xfd469501);
|
||||
a = r1(a, b, c, d, temp[8], 7, 0x698098d8);
|
||||
d = r1(d, a, b, c, temp[9], 12, 0x8b44f7af);
|
||||
c = r1(c, d, a, b, temp[10], 17, 0xffff5bb1);
|
||||
b = r1(b, c, d, a, temp[11], 22, 0x895cd7be);
|
||||
a = r1(a, b, c, d, temp[12], 7, 0x6b901122);
|
||||
d = r1(d, a, b, c, temp[13], 12, 0xfd987193);
|
||||
c = r1(c, d, a, b, temp[14], 17, 0xa679438e);
|
||||
b = r1(b, c, d, a, temp[15], 22, 0x49b40821);
|
||||
|
||||
a = r2(a, b, c, d, temp[1], 5, 0xf61e2562);
|
||||
d = r2(d, a, b, c, temp[6], 9, 0xc040b340);
|
||||
c = r2(c, d, a, b, temp[11], 14, 0x265e5a51);
|
||||
b = r2(b, c, d, a, temp[0], 20, 0xe9b6c7aa);
|
||||
a = r2(a, b, c, d, temp[5], 5, 0xd62f105d);
|
||||
d = r2(d, a, b, c, temp[10], 9, 0x02441453);
|
||||
c = r2(c, d, a, b, temp[15], 14, 0xd8a1e681);
|
||||
b = r2(b, c, d, a, temp[4], 20, 0xe7d3fbc8);
|
||||
a = r2(a, b, c, d, temp[9], 5, 0x21e1cde6);
|
||||
d = r2(d, a, b, c, temp[14], 9, 0xc33707d6);
|
||||
c = r2(c, d, a, b, temp[3], 14, 0xf4d50d87);
|
||||
b = r2(b, c, d, a, temp[8], 20, 0x455a14ed);
|
||||
a = r2(a, b, c, d, temp[13], 5, 0xa9e3e905);
|
||||
d = r2(d, a, b, c, temp[2], 9, 0xfcefa3f8);
|
||||
c = r2(c, d, a, b, temp[7], 14, 0x676f02d9);
|
||||
b = r2(b, c, d, a, temp[12], 20, 0x8d2a4c8a);
|
||||
|
||||
a = r3(a, b, c, d, temp[5], 4, 0xfffa3942);
|
||||
d = r3(d, a, b, c, temp[8], 11, 0x8771f681);
|
||||
c = r3(c, d, a, b, temp[11], 16, 0x6d9d6122);
|
||||
b = r3(b, c, d, a, temp[14], 23, 0xfde5380c);
|
||||
a = r3(a, b, c, d, temp[1], 4, 0xa4beea44);
|
||||
d = r3(d, a, b, c, temp[4], 11, 0x4bdecfa9);
|
||||
c = r3(c, d, a, b, temp[7], 16, 0xf6bb4b60);
|
||||
b = r3(b, c, d, a, temp[10], 23, 0xbebfbc70);
|
||||
a = r3(a, b, c, d, temp[13], 4, 0x289b7ec6);
|
||||
d = r3(d, a, b, c, temp[0], 11, 0xeaa127fa);
|
||||
c = r3(c, d, a, b, temp[3], 16, 0xd4ef3085);
|
||||
b = r3(b, c, d, a, temp[6], 23, 0x04881d05);
|
||||
a = r3(a, b, c, d, temp[9], 4, 0xd9d4d039);
|
||||
d = r3(d, a, b, c, temp[12], 11, 0xe6db99e5);
|
||||
c = r3(c, d, a, b, temp[15], 16, 0x1fa27cf8);
|
||||
b = r3(b, c, d, a, temp[2], 23, 0xc4ac5665);
|
||||
|
||||
a = r4(a, b, c, d, temp[0], 6, 0xf4292244);
|
||||
d = r4(d, a, b, c, temp[7], 10, 0x432aff97);
|
||||
c = r4(c, d, a, b, temp[14], 15, 0xab9423a7);
|
||||
b = r4(b, c, d, a, temp[5], 21, 0xfc93a039);
|
||||
a = r4(a, b, c, d, temp[12], 6, 0x655b59c3);
|
||||
d = r4(d, a, b, c, temp[3], 10, 0x8f0ccc92);
|
||||
c = r4(c, d, a, b, temp[10], 15, 0xffeff47d);
|
||||
b = r4(b, c, d, a, temp[1], 21, 0x85845dd1);
|
||||
a = r4(a, b, c, d, temp[8], 6, 0x6fa87e4f);
|
||||
d = r4(d, a, b, c, temp[15], 10, 0xfe2ce6e0);
|
||||
c = r4(c, d, a, b, temp[6], 15, 0xa3014314);
|
||||
b = r4(b, c, d, a, temp[13], 21, 0x4e0811a1);
|
||||
a = r4(a, b, c, d, temp[4], 6, 0xf7537e82);
|
||||
d = r4(d, a, b, c, temp[11], 10, 0xbd3af235);
|
||||
c = r4(c, d, a, b, temp[2], 15, 0x2ad7d2bb);
|
||||
b = r4(b, c, d, a, temp[9], 21, 0xeb86d391);
|
||||
|
||||
ABCDValue.A = unchecked(a + ABCDValue.A);
|
||||
ABCDValue.B = unchecked(b + ABCDValue.B);
|
||||
ABCDValue.C = unchecked(c + ABCDValue.C);
|
||||
ABCDValue.D = unchecked(d + ABCDValue.D);
|
||||
}
|
||||
|
||||
//// Manually unrolling these equations nets us a 20% performance improvement
|
||||
private static uint r1(uint a, uint b, uint c, uint d, uint x, int s, uint t)
|
||||
{
|
||||
//// (b + LSR((a + F(b, c, d) + x + t), s))
|
||||
//// F(x, y, z) ((x & y) | ((x ^ 0xFFFFFFFF) & z))
|
||||
return unchecked(b + LSR((a + ((b & c) | ((b ^ 0xFFFFFFFF) & d)) + x + t), s));
|
||||
}
|
||||
|
||||
private static uint r2(uint a, uint b, uint c, uint d, uint x, int s, uint t)
|
||||
{
|
||||
//// (b + LSR((a + G(b, c, d) + x + t), s))
|
||||
//// G(x, y, z) ((x & z) | (y & (z ^ 0xFFFFFFFF)))
|
||||
return unchecked(b + LSR((a + ((b & d) | (c & (d ^ 0xFFFFFFFF))) + x + t), s));
|
||||
}
|
||||
|
||||
private static uint r3(uint a, uint b, uint c, uint d, uint x, int s, uint t)
|
||||
{
|
||||
//// (b + LSR((a + H(b, c, d) + k + i), s))
|
||||
//// H(x, y, z) (x ^ y ^ z)
|
||||
return unchecked(b + LSR((a + (b ^ c ^ d) + x + t), s));
|
||||
}
|
||||
|
||||
private static uint r4(uint a, uint b, uint c, uint d, uint x, int s, uint t)
|
||||
{
|
||||
//// (b + LSR((a + I(b, c, d) + k + i), s))
|
||||
//// I(x, y, z) (y ^ (x | (z ^ 0xFFFFFFFF)))
|
||||
return unchecked(b + LSR((a + (c ^ (b | (d ^ 0xFFFFFFFF))) + x + t), s));
|
||||
}
|
||||
|
||||
//// Implementation of left rotate
|
||||
//// s is an int instead of a uint becuase the CLR requires the argument passed to >>/<< is of
|
||||
//// type int. Doing the demoting inside this function would add overhead.
|
||||
private static uint LSR(uint i, int s)
|
||||
{
|
||||
return ((i << s) | (i >> (32 - s)));
|
||||
}
|
||||
|
||||
//// Convert input array into array of UInts
|
||||
private static uint[] Converter(byte[] input, int ibStart)
|
||||
{
|
||||
if (null == input)
|
||||
{
|
||||
throw new ArgumentNullException("input", "Unable convert null array to array of uInts");
|
||||
}
|
||||
|
||||
var result = new uint[16];
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
result[i] = input[ibStart + i*4];
|
||||
result[i] += (uint) input[ibStart + i*4 + 1] << 8;
|
||||
result[i] += (uint) input[ibStart + i*4 + 2] << 16;
|
||||
result[i] += (uint) input[ibStart + i*4 + 3] << 24;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -61,8 +61,13 @@ internal static LastTrack ParseJToken(JToken token)
|
||||
t.Name = token.Value<string>("name");
|
||||
t.Mbid = token.Value<string>("mbid");
|
||||
|
||||
//0 to null
|
||||
t.TotalPlayCount = token.Value<int?>("playcount");
|
||||
//some tracks do not contain the playcount prop, it will throw a FormatException
|
||||
var playCountStr = token.Value<string>("playcount");
|
||||
int playCount;
|
||||
if (int.TryParse(playCountStr, out playCount))
|
||||
{
|
||||
t.TotalPlayCount = playCount;
|
||||
}
|
||||
|
||||
t.Url = new Uri(token.Value<string>("url"), UriKind.Absolute);
|
||||
|
||||
|
@ -34,13 +34,19 @@ public class LastWiki : ILastfmObject
|
||||
|
||||
internal static LastWiki ParseJToken(JToken token)
|
||||
{
|
||||
return new LastWiki
|
||||
var wiki = new LastWiki
|
||||
{
|
||||
Published = token.Value<DateTime>("published"),
|
||||
Summary = token.Value<string>("summary").Trim(),
|
||||
Content = token.Value<string>("content").Trim(),
|
||||
YearFormed = token.Value<int>("yearformed")
|
||||
};
|
||||
|
||||
//Artist that do not contain an official bio will come with an empty published property.
|
||||
//To avoid a parse exception, check if is null or empty.
|
||||
if (!string.IsNullOrEmpty(token.Value<string>("published")))
|
||||
wiki.Published = token.Value<DateTime>("published");
|
||||
|
||||
return wiki;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,5 @@
|
||||
<package id="Microsoft.Bcl" version="1.1.9" targetFramework="portable-net45+win+wpa81+wp80" />
|
||||
<package id="Microsoft.Bcl.Build" version="1.0.14" targetFramework="portable-net45+win+wpa81+wp80" />
|
||||
<package id="Microsoft.Net.Http" version="2.2.28" targetFramework="portable-net45+win+wpa81+wp80" />
|
||||
<package id="MyConvert" version="1.0.1.6" targetFramework="portable-net45+win+wpa81+wp80" />
|
||||
<package id="Newtonsoft.Json" version="6.0.5" targetFramework="portable-net45+win+wpa81+wp80" />
|
||||
</packages>
|
Loading…
Reference in New Issue
Block a user