Merge pull request #46 from zumicts/master

This commit is contained in:
Rikki Tooley 2015-01-03 23:34:18 +00:00
commit 27f2b6f836
21 changed files with 548 additions and 56 deletions

View File

@ -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();

View File

@ -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));
}
}
}

View File

@ -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>

View File

@ -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
};

View File

@ -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);
}
}
}

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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)

View File

@ -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);
}
}
}
}

View File

@ -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();

View File

@ -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);
}

View File

@ -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,

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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)

View File

@ -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();

View File

@ -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
View 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;
}
}
}

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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>