Skip to content
Browse files

#12 Fixing Json parse exception when loved tracks only contains a sin…

…gle track and #7 adding unit tests for this
  • Loading branch information...
1 parent bf19f55 commit 907ba311d47b4a82a87a6d68124a4c03187b2734 @michaellavelle committed
View
1 src/main/java/org/springframework/social/lastfm/api/impl/json/LastFmModule.java
@@ -42,6 +42,7 @@ public LastFmModule() {
public void setupModule(SetupContext context) {
context.setMixInAnnotations(LastFmProfile.class,
LastFmProfileMixin.class);
+ context.setMixInAnnotations(TrackListContainer.class, TrackMixin.class);
context.setMixInAnnotations(Track.class, TrackMixin.class);
context.setMixInAnnotations(SimpleTrack.class, SimpleTrackMixin.class);
context.setMixInAnnotations(TrackSearchResult.class,
View
42 src/main/java/org/springframework/social/lastfm/api/impl/json/LastFmSingleTrackResponse.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.social.lastfm.api.impl.json;
+
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.codehaus.jackson.annotate.JsonProperty;
+import org.springframework.social.lastfm.api.Track;
+
+/**
+ * @author Michael Lavelle
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class LastFmSingleTrackResponse {
+
+ private List<Track> tracks;
+
+ @JsonCreator
+ public LastFmSingleTrackResponse(@JsonProperty("track") List<Track> tracks) {
+ this.tracks = tracks;
+ }
+
+ public List<Track> getTracks() {
+ return tracks;
+ }
+
+}
View
5 src/main/java/org/springframework/social/lastfm/api/impl/json/LastFmTracksResponse.java
@@ -31,8 +31,9 @@
private List<Track> tracks;
@JsonCreator
- public LastFmTracksResponse(@JsonProperty("track") List<Track> tracks) {
- this.tracks = tracks;
+ public LastFmTracksResponse(
+ @JsonProperty("track") TrackListContainer tracksContainer) {
+ this.tracks = tracksContainer.getTracks();
}
public List<Track> getTracks() {
View
53 src/main/java/org/springframework/social/lastfm/api/impl/json/TrackListContainer.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.social.lastfm.api.impl.json;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.codehaus.jackson.annotate.JsonCreator;
+import org.codehaus.jackson.annotate.JsonIgnoreProperties;
+import org.springframework.social.lastfm.api.Artist;
+import org.springframework.social.lastfm.api.Track;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+/**
+ * Container for a track list - allows for automatic JSON binding from *either* a list of Tracks
+ * or a Map representation of a single track, as LastFm's responds with different Json structures
+ * depending on whether a single track is returned
+ *
+ * @author Michael Lavelle
+ */
+public class TrackListContainer {
+ private List<Track> tracks;
+
+ public TrackListContainer(String url, String name, String musicBrainsId,
+ Artist artist) {
+ this.tracks = Arrays
+ .asList(new Track(url, name, musicBrainsId, artist));
+ }
+
+ @JsonCreator
+ public TrackListContainer(List<Track> tracks) {
+ this.tracks = tracks;
+
+ }
+
+ public List<Track> getTracks() {
+ return tracks;
+ }
+
+}
View
24 .../org/springframework/social/lastfm/pseudooauth2/connect/web/LastFmPseudoOAuth2Filter.java
@@ -45,15 +45,14 @@
public class LastFmPseudoOAuth2Filter implements Filter {
/**
- * The default signin callback path - can be overridden via filter config parameter
- * "signinCallbackPath"
+ * The default signin callback path - can be overridden via filter config
+ * parameter "signinCallbackPath"
*/
private String signinCallbackPath = "/signin/lastfm";
-
-
+
/**
- * The default connect callback path - can be overridden via filter config parameter
- * "connectCallbackPath"
+ * The default connect callback path - can be overridden via filter config
+ * parameter "connectCallbackPath"
*/
private String connectCallbackPath = "/connect/lastfm";
@@ -78,8 +77,9 @@ private String getToken(ServletRequest request) {
* @return true if processing a LastFm callback
*/
private boolean isLastFmCallback(HttpServletRequest request) {
- return (request.getMethod().toLowerCase().equals("get") && (
- request.getRequestURI().equals(signinCallbackPath) || request.getRequestURI().equals(connectCallbackPath)))
+ return (request.getMethod().toLowerCase().equals("get") && (request
+ .getRequestURI().equals(signinCallbackPath) || request
+ .getRequestURI().equals(connectCallbackPath)))
&& getToken(request) != null;
}
@@ -108,12 +108,14 @@ public void doFilter(ServletRequest req, ServletResponse resp,
@Override
public void init(FilterConfig config) throws ServletException {
- String signinCallbackPath = config.getInitParameter("signinCallbackPath");
+ String signinCallbackPath = config
+ .getInitParameter("signinCallbackPath");
if (signinCallbackPath != null) {
this.signinCallbackPath = signinCallbackPath;
}
-
- String connectCallbackPath = config.getInitParameter("connectCallbackPath");
+
+ String connectCallbackPath = config
+ .getInitParameter("connectCallbackPath");
if (connectCallbackPath != null) {
this.connectCallbackPath = connectCallbackPath;
}
View
25 src/test/java/org/springframework/social/lastfm/api/AbstractLastFmApiTest.java
@@ -25,14 +25,14 @@
import org.springframework.social.test.client.MockRestServiceServer;
public abstract class AbstractLastFmApiTest {
- protected static final String API_KEY= "someApiKey";
- protected static final String SECRET= "secret";
+ protected static final String API_KEY = "someApiKey";
+ protected static final String SECRET = "secret";
protected static final String USER_AGENT = "someUserAgent";
- protected static final LastFmAccessGrant ACCESS_GRANT = new LastFmAccessGrant("someToken","someSessionKey");
-
-
+ protected static final LastFmAccessGrant ACCESS_GRANT = new LastFmAccessGrant(
+ "someToken", "someSessionKey");
+
protected LastFmTemplate lastFm;
protected LastFmTemplate unauthorizedLastFm;
protected MockRestServiceServer mockServer;
@@ -42,21 +42,20 @@
@Before
public void setup() {
- lastFm = new LastFmTemplate(USER_AGENT,ACCESS_GRANT,API_KEY,SECRET);
- mockServer = MockRestServiceServer.createServer(lastFm.getRestTemplate());
+ lastFm = new LastFmTemplate(USER_AGENT, ACCESS_GRANT, API_KEY, SECRET);
+ mockServer = MockRestServiceServer.createServer(lastFm
+ .getRestTemplate());
responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.APPLICATION_JSON);
-
- unauthorizedLastFm = new LastFmTemplate(USER_AGENT,API_KEY);
- mockUnauthorizedServer = MockRestServiceServer.createServer(unauthorizedLastFm.getRestTemplate());
+
+ unauthorizedLastFm = new LastFmTemplate(USER_AGENT, API_KEY);
+ mockUnauthorizedServer = MockRestServiceServer
+ .createServer(unauthorizedLastFm.getRestTemplate());
}
protected Resource jsonResource(String filename) {
return new ClassPathResource(filename + ".json", getClass());
}
-
-
-
}
View
223 src/test/java/org/springframework/social/lastfm/api/UserTemplateTest.java
@@ -1,4 +1,3 @@
-
/*
* Copyright 2011 the original author or authors.
*
@@ -15,7 +14,6 @@
* limitations under the License.
*/
-
package org.springframework.social.lastfm.api;
import static org.junit.Assert.assertEquals;
@@ -35,173 +33,218 @@
import org.springframework.social.ResourceNotFoundException;
public class UserTemplateTest extends AbstractLastFmApiTest {
-
+
protected final static Date someDate = new Date(123456789);
-
+
@Test
public void getUserProfile_currentUser() {
-
-
- mockServer.expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_sig=8bbd32f49982b528b16cb704b671d242&api_key=someApiKey&sk=someSessionKey&method=user.getInfo&token=someToken"))
+
+ mockServer
+ .expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_sig=8bbd32f49982b528b16cb704b671d242&api_key=someApiKey&sk=someSessionKey&method=user.getInfo&token=someToken"))
.andExpect(method(GET))
.andExpect(header("User-Agent", "someUserAgent"))
- .andRespond(withResponse(jsonResource("testdata/full-profile"), responseHeaders));
+ .andRespond(
+ withResponse(jsonResource("testdata/full-profile"),
+ responseHeaders));
LastFmProfile profile = lastFm.userOperations().getUserProfile();
assertBasicProfileData(profile);
}
-
+
@Test(expected = NotAuthorizedException.class)
public void getUserProfile_currentUser_unauthorized() {
unauthorizedLastFm.userOperations().getUserProfile();
}
-
-
+
@Test
- public void getUserProfile_specificUserByUserId() {
+ public void getUserProfile_specificUserByUserId() {
- mockServer.expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getInfo&user=mattslip"))
+ mockServer
+ .expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getInfo&user=mattslip"))
.andExpect(method(GET))
.andExpect(header("User-Agent", "someUserAgent"))
- .andRespond(withResponse(jsonResource("testdata/full-profile"), responseHeaders));
+ .andRespond(
+ withResponse(jsonResource("testdata/full-profile"),
+ responseHeaders));
- LastFmProfile profile = lastFm.userOperations().getUserProfile("mattslip");
+ LastFmProfile profile = lastFm.userOperations().getUserProfile(
+ "mattslip");
assertBasicProfileData(profile);
}
-
-
+
@Test
- public void getRecentTracks() {
-
-
- mockServer.expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getrecenttracks&user=mattslip"))
+ public void getRecentTracks() {
+
+ mockServer
+ .expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getrecenttracks&user=mattslip"))
.andExpect(method(GET))
.andExpect(header("User-Agent", "someUserAgent"))
- .andRespond(withResponse(jsonResource("testdata/recent-tracks"), responseHeaders));
+ .andRespond(
+ withResponse(jsonResource("testdata/recent-tracks"),
+ responseHeaders));
- List<SimpleTrack> tracks = lastFm.userOperations().getRecentTracks("mattslip");
+ List<SimpleTrack> tracks = lastFm.userOperations().getRecentTracks(
+ "mattslip");
assertSimpleTrackData(tracks.get(0));
}
-
+
@Test
- public void getTopTracks() {
-
-
- mockServer.expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.gettoptracks&user=mattslip"))
+ public void getTopTracks() {
+
+ mockServer
+ .expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.gettoptracks&user=mattslip"))
.andExpect(method(GET))
.andExpect(header("User-Agent", "someUserAgent"))
- .andRespond(withResponse(jsonResource("testdata/top-tracks"), responseHeaders));
+ .andRespond(
+ withResponse(jsonResource("testdata/top-tracks"),
+ responseHeaders));
List<Track> tracks = lastFm.userOperations().getTopTracks("mattslip");
assertTrackData(tracks.get(0));
-
+
}
-
+
@Test
- public void getLovedTracks() {
-
-
- mockServer.expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getlovedtracks&user=mattslip"))
+ public void getLovedTracks() {
+
+ mockServer
+ .expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getlovedtracks&user=mattslip"))
.andExpect(method(GET))
.andExpect(header("User-Agent", "someUserAgent"))
- .andRespond(withResponse(jsonResource("testdata/loved-tracks"), responseHeaders));
+ .andRespond(
+ withResponse(jsonResource("testdata/loved-tracks"),
+ responseHeaders));
List<Track> tracks = lastFm.userOperations().getLovedTracks("mattslip");
assertTrackData(tracks.get(0));
-
+
}
-
+
+ /**
+ * Tests for the case where the loved tracks response contains only a single
+ * track. In this case the Json format of the response is different to the
+ * response for multiple tracks
+ */
@Test
- public void getLovedTracks_withoutAuthorization() {
-
-
- mockUnauthorizedServer.expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getlovedtracks&user=mattslip"))
+ public void getLovedTracksSingleTrackResponse() {
+
+ mockServer
+ .expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getlovedtracks&user=mattslip"))
.andExpect(method(GET))
.andExpect(header("User-Agent", "someUserAgent"))
- .andRespond(withResponse(jsonResource("testdata/loved-tracks"), responseHeaders));
+ .andRespond(
+ withResponse(
+ jsonResource("testdata/loved-tracks-single-track-response"),
+ responseHeaders));
- List<Track> tracks = unauthorizedLastFm.userOperations().getLovedTracks("mattslip");
+ List<Track> tracks = lastFm.userOperations().getLovedTracks("mattslip");
assertTrackData(tracks.get(0));
-
+
}
-
+
+ @Test
+ public void getLovedTracks_withoutAuthorization() {
+
+ mockUnauthorizedServer
+ .expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getlovedtracks&user=mattslip"))
+ .andExpect(method(GET))
+ .andExpect(header("User-Agent", "someUserAgent"))
+ .andRespond(
+ withResponse(jsonResource("testdata/loved-tracks"),
+ responseHeaders));
+
+ List<Track> tracks = unauthorizedLastFm.userOperations()
+ .getLovedTracks("mattslip");
+ assertTrackData(tracks.get(0));
+
+ }
+
@Test(expected = ResourceNotFoundException.class)
- public void getLovedTracks_invalidUser() {
-
-
- mockServer.expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getlovedtracks&user=someOtherUser"))
+ public void getLovedTracks_invalidUser() {
+
+ mockServer
+ .expect(requestTo("http://ws.audioscrobbler.com/2.0/?format=json&api_key=someApiKey&method=user.getlovedtracks&user=someOtherUser"))
.andExpect(method(GET))
.andExpect(header("User-Agent", "someUserAgent"))
- .andRespond(withResponse(jsonResource("testdata/invalid-user"), responseHeaders));
+ .andRespond(
+ withResponse(jsonResource("testdata/invalid-user"),
+ responseHeaders));
+
+ List<Track> tracks = lastFm.userOperations().getLovedTracks(
+ "someOtherUser");
- List<Track> tracks = lastFm.userOperations().getLovedTracks("someOtherUser");
-
}
-
-
+
@Test
- public void scrobble() {
-
-
- mockServer.expect(requestTo("http://ws.audioscrobbler.com/2.0/"))
+ public void scrobble() {
+
+ mockServer
+ .expect(requestTo("http://ws.audioscrobbler.com/2.0/"))
.andExpect(method(POST))
.andExpect(header("User-Agent", "someUserAgent"))
- .andExpect(body("format=json&api_sig=b4c8c73655abc90599cdfc0ed9c3b3e8&api_key=someApiKey&sk=someSessionKey&method=track.scrobble&token=someToken&timestamp=123456&track=My+track+name&artist=My+artist+name"))
- .andRespond(withResponse(jsonResource("testdata/recent-tracks"), responseHeaders));
+ .andExpect(
+ body("format=json&api_sig=b4c8c73655abc90599cdfc0ed9c3b3e8&api_key=someApiKey&sk=someSessionKey&method=track.scrobble&token=someToken&timestamp=123456&track=My+track+name&artist=My+artist+name"))
+ .andRespond(
+ withResponse(jsonResource("testdata/recent-tracks"),
+ responseHeaders));
+
+ lastFm.userOperations().scrobble(
+ new SimpleTrackDescriptor("My artist name", "My track name"),
+ someDate);
- lastFm.userOperations().scrobble(new SimpleTrackDescriptor("My artist name","My track name"),someDate);
-
}
-
+
@Test(expected = NotAuthorizedException.class)
- public void scrobble_unauthorized() {
-
- unauthorizedLastFm.userOperations().scrobble(new SimpleTrackDescriptor("My artist name","My track name"),someDate);
-
+ public void scrobble_unauthorized() {
+
+ unauthorizedLastFm.userOperations().scrobble(
+ new SimpleTrackDescriptor("My artist name", "My track name"),
+ someDate);
+
}
-
-
+
@Test
- public void updateNowPlaying() {
-
-
- mockServer.expect(requestTo("http://ws.audioscrobbler.com/2.0/"))
+ public void updateNowPlaying() {
+
+ mockServer
+ .expect(requestTo("http://ws.audioscrobbler.com/2.0/"))
.andExpect(method(POST))
.andExpect(header("User-Agent", "someUserAgent"))
- .andExpect(body("format=json&api_sig=6511f45e73a7fd12edab35d18a6655ce&api_key=someApiKey&sk=someSessionKey&method=track.updateNowPlaying&token=someToken&track=My+track+name&artist=My+artist+name"))
- .andRespond(withResponse(jsonResource("testdata/recent-tracks"), responseHeaders));
+ .andExpect(
+ body("format=json&api_sig=6511f45e73a7fd12edab35d18a6655ce&api_key=someApiKey&sk=someSessionKey&method=track.updateNowPlaying&token=someToken&track=My+track+name&artist=My+artist+name"))
+ .andRespond(
+ withResponse(jsonResource("testdata/recent-tracks"),
+ responseHeaders));
+
+ lastFm.userOperations().updateNowPlaying(
+ new SimpleTrackDescriptor("My artist name", "My track name"));
- lastFm.userOperations().updateNowPlaying(new SimpleTrackDescriptor("My artist name","My track name"));
-
}
-
private void assertBasicProfileData(LastFmProfile profile) {
assertEquals("123456789", profile.getId());
assertEquals("mattslip", profile.getName());
assertEquals("http://www.last.fm/user/mattslip", profile.getUrl());
}
-
+
private void assertBasicTrackData(TrackDescriptor trackDescriptor) {
assertEquals("Moon Theory", trackDescriptor.getName());
assertEquals("Miami Horror", trackDescriptor.getArtistName());
}
-
+
private void assertSimpleTrackData(SimpleTrack simpleTrack) {
assertBasicTrackData(simpleTrack);
- assertEquals("http://www.last.fm/music/Miami+Horror/_/Moon+Theory", simpleTrack.getUrl());
+ assertEquals("http://www.last.fm/music/Miami+Horror/_/Moon+Theory",
+ simpleTrack.getUrl());
}
-
+
private void assertTrackData(Track track) {
assertBasicTrackData(track);
- assertEquals("http://www.last.fm/music/Miami+Horror/_/Moon+Theory", track.getUrl());
- assertEquals("http://www.last.fm/music/Miami+Horror",track.getArtist().getUrl());
+ assertEquals("http://www.last.fm/music/Miami+Horror/_/Moon+Theory",
+ track.getUrl());
+ assertEquals("http://www.last.fm/music/Miami+Horror", track.getArtist()
+ .getUrl());
}
-
-
-
-
-
}
View
40 src/test/java/org/springframework/social/lastfm/connect/LastFmAdapterTest.java
@@ -15,7 +15,6 @@
*/
package org.springframework.social.lastfm.connect;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
@@ -33,15 +32,24 @@
public class LastFmAdapterTest {
private LastFmAdapter apiAdapter = new LastFmAdapter();
-
+
@SuppressWarnings("unchecked")
private LastFm lastFm = Mockito.mock(LastFm.class);
-
+
@Test
- public void fetchProfile(){
+ public void fetchProfile() {
UserOperations userOperations = Mockito.mock(UserOperations.class);
Mockito.when(lastFm.userOperations()).thenReturn(userOperations);
- Mockito.when(userOperations.getUserProfile()).thenReturn(new LastFmProfile("123456789","mattslip", "Matt Slip","http://www.last.fm/user/mattslip",Arrays.asList(new Image("http://userserve-ak.last.fm/serve/64/46182239.jpg","medium"))));
+ Mockito.when(userOperations.getUserProfile())
+ .thenReturn(
+ new LastFmProfile(
+ "123456789",
+ "mattslip",
+ "Matt Slip",
+ "http://www.last.fm/user/mattslip",
+ Arrays.asList(new Image(
+ "http://userserve-ak.last.fm/serve/64/46182239.jpg",
+ "medium"))));
UserProfile profile = apiAdapter.fetchUserProfile(lastFm);
assertEquals("Matt Slip", profile.getName());
@@ -51,18 +59,28 @@ public void fetchProfile(){
assertEquals("mattslip", profile.getUsername());
}
-
@Test
- public void setConnectionValues() {
+ public void setConnectionValues() {
UserOperations userOperations = Mockito.mock(UserOperations.class);
Mockito.when(lastFm.userOperations()).thenReturn(userOperations);
- Mockito.when(userOperations.getUserProfile()).thenReturn(new LastFmProfile("123456789","mattslip", "Matt Slip","http://www.last.fm/user/mattslip",Arrays.asList(new Image("http://userserve-ak.last.fm/serve/64/46182239.jpg","medium"))));
+ Mockito.when(userOperations.getUserProfile())
+ .thenReturn(
+ new LastFmProfile(
+ "123456789",
+ "mattslip",
+ "Matt Slip",
+ "http://www.last.fm/user/mattslip",
+ Arrays.asList(new Image(
+ "http://userserve-ak.last.fm/serve/64/46182239.jpg",
+ "medium"))));
TestConnectionValues connectionValues = new TestConnectionValues();
apiAdapter.setConnectionValues(lastFm, connectionValues);
assertEquals("Matt Slip", connectionValues.getDisplayName());
- assertEquals("http://userserve-ak.last.fm/serve/64/46182239.jpg",connectionValues.getImageUrl());
- assertEquals("http://www.last.fm/user/mattslip", connectionValues.getProfileUrl());
+ assertEquals("http://userserve-ak.last.fm/serve/64/46182239.jpg",
+ connectionValues.getImageUrl());
+ assertEquals("http://www.last.fm/user/mattslip",
+ connectionValues.getProfileUrl());
assertEquals("123456789", connectionValues.getProviderUserId());
}
@@ -104,6 +122,6 @@ public String getProviderUserId() {
public void setProviderUserId(String providerUserId) {
this.providerUserId = providerUserId;
}
-
+
}
}
View
10 ...es/org/springframework/social/lastfm/api/testdata/loved-tracks-single-track-response.json
@@ -0,0 +1,10 @@
+
+{"lovedtracks":
+{"track":{"name":"Moon Theory",
+"mbid":"",
+"url":"http:\/\/www.last.fm\/music\/Miami+Horror\/_\/Moon+Theory",
+"date":{"#text":"7 Mar 2010, 19:32","uts":"1267990350"},
+"artist":{"name":"Miami Horror","mbid":"2d499150-1c42-4ffb-a90c-1cc635519d33",
+"url":"http:\/\/www.last.fm\/music\/Miami+Horror"},
+"streamable":{"#text":"0","fulltrack":"0"}},
+"@attr":{"user":"mattslip","page":"1","perPage":"50","totalPages":"1","total":"1"}}}

0 comments on commit 907ba31

Please sign in to comment.
Something went wrong with that request. Please try again.