[banshee] Last.fm: Switch to 2.0 API for scrobbling
- From: Alexander Kojevnikov <alexk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [banshee] Last.fm: Switch to 2.0 API for scrobbling
- Date: Sat, 30 Jun 2012 00:15:01 +0000 (UTC)
commit a0475fc6fab444c6834ae86ce5ee191bec2af883
Author: Bertrand Lorentz <bertrand lorentz gmail com>
Date: Thu Mar 29 18:46:26 2012 +0200
Last.fm: Switch to 2.0 API for scrobbling
Signed-off-by: Alexander Kojevnikov <alexk gnome org>
.../Banshee.Lastfm.Audioscrobbler/Queue.cs | 47 +--
.../Lastfm/Lastfm/AudioscrobblerConnection.cs | 474 ++++----------------
src/Libraries/Lastfm/Lastfm/IQueue.cs | 17 +-
src/Libraries/Lastfm/Lastfm/LastfmRequest.cs | 39 ++-
4 files changed, 151 insertions(+), 426 deletions(-)
---
diff --git a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/Queue.cs b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/Queue.cs
index 3fdc59c..e775ec1 100644
--- a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/Queue.cs
+++ b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/Queue.cs
@@ -51,7 +51,7 @@ namespace Banshee.Lastfm.Audioscrobbler
{
private readonly TimeSpan MAXIMUM_TRACK_STARTTIME_IN_FUTURE = TimeSpan.FromDays (180);
- internal class QueuedTrack
+ internal class QueuedTrack : IQueuedTrack
{
private static DateTime epoch = DateTimeUtil.LocalUnixEpoch.ToUniversalTime ();
@@ -141,7 +141,7 @@ namespace Banshee.Lastfm.Audioscrobbler
string track_auth = String.Empty;
}
- List<QueuedTrack> queue;
+ List<IQueuedTrack> queue;
string xml_path;
bool dirty;
@@ -151,7 +151,7 @@ namespace Banshee.Lastfm.Audioscrobbler
{
string xml_dir_path = Path.Combine (Hyena.Paths.ExtensionCacheRoot, "lastfm");
xml_path = Path.Combine (xml_dir_path, "audioscrobbler-queue.xml");
- queue = new List<QueuedTrack> ();
+ queue = new List<IQueuedTrack> ();
if (!Directory.Exists(xml_dir_path)) {
Directory.CreateDirectory (xml_dir_path);
@@ -256,43 +256,10 @@ namespace Banshee.Lastfm.Audioscrobbler
}
}
- public string GetTransmitInfo (out int numtracks)
+ public List<IQueuedTrack> GetTransmitInfo ()
{
- StringBuilder sb = new StringBuilder ();
-
- int i;
- for (i = 0; i < queue.Count; i ++) {
- /* Last.fm 1.2 can handle up to 50 songs in one request */
- if (i == 49) break;
-
- QueuedTrack track = (QueuedTrack) queue[i];
-
- string str_track_number = String.Empty;
- if (track.TrackNumber != 0)
- str_track_number = track.TrackNumber.ToString();
-
- string source = "P"; /* chosen by user */
- if (track.TrackAuth.Length != 0) {
- // from last.fm
- source = "L" + track.TrackAuth;
- }
-
- sb.AppendFormat (
- "&a[{9}]={0}&t[{9}]={1}&i[{9}]={2}&o[{9}]={3}&r[{9}]={4}&l[{9}]={5}&b[{9}]={6}&n[{9}]={7}&m[{9}]={8}",
- HttpUtility.UrlEncode (track.Artist),
- HttpUtility.UrlEncode (track.Title),
- track.StartTime.ToString (),
- source,
- "" /* rating: L/B/S */,
- track.Duration.ToString (),
- HttpUtility.UrlEncode (track.Album),
- str_track_number,
- track.MusicBrainzId,
- i);
- }
-
- numtracks = i;
- return sb.ToString ();
+ /* Last.fm can handle up to 50 songs in one request */
+ return queue.GetRange (0, Math.Min (queue.Count, 50));
}
public void Add (object track, DateTime started_at)
@@ -346,7 +313,7 @@ namespace Banshee.Lastfm.Audioscrobbler
}
}
- private bool IsInvalidQueuedTrack (QueuedTrack track)
+ private bool IsInvalidQueuedTrack (IQueuedTrack track)
{
DateTime trackStartTime = DateTimeUtil.FromTimeT (track.StartTime);
diff --git a/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs b/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs
index 2596262..0c0d38e 100644
--- a/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs
+++ b/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs
@@ -28,6 +28,7 @@
//
using System;
+using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
@@ -36,6 +37,7 @@ using System.Security.Cryptography;
using System.Web;
using Hyena;
+using Hyena.Json;
using Mono.Unix;
namespace Lastfm
@@ -44,26 +46,14 @@ namespace Lastfm
{
private enum State {
Idle,
- NeedHandshake,
NeedTransmit,
- WaitingForRequestStream,
- WaitingForHandshakeResp,
+ Transmitting,
WaitingForResponse
};
private const int TICK_INTERVAL = 2000; /* 2 seconds */
- private const int FAILURE_LOG_MINUTES = 5; /* 5 minute delay on logging failure to upload information */
private const int RETRY_SECONDS = 60; /* 60 second delay for transmission retries */
- private const int MAX_RETRY_SECONDS = 7200; /* 2 hours, as defined in the last.fm protocol */
private const int TIME_OUT = 10000; /* 10 seconds timeout for webrequests */
- private const string CLIENT_ID = "bsh";
- private const string CLIENT_VERSION = "0.1";
- private const string SCROBBLER_URL = "http://post.audioscrobbler.com/";
- private const string SCROBBLER_VERSION = "1.2.1";
-
- private string post_url;
- private string session_id = null;
- private string now_playing_url;
private bool connected = false; /* if we're connected to network or not */
public bool Connected {
@@ -77,17 +67,14 @@ namespace Lastfm
private System.Timers.Timer timer;
private DateTime next_interval;
- private DateTime last_upload_failed_logged;
private IQueue queue;
private int hard_failures = 0;
- private int hard_failure_retry_sec = 60;
- private HttpWebRequest now_playing_post;
private bool now_playing_started;
- private string current_now_playing_data;
- private HttpWebRequest current_web_req;
+ private LastfmRequest current_now_playing_request;
+ private LastfmRequest current_scrobble_request;
private IAsyncResult current_async_result;
private State state;
@@ -102,7 +89,6 @@ namespace Lastfm
private void AccountUpdated (object o, EventArgs args)
{
Stop ();
- session_id = null;
Start ();
}
@@ -151,9 +137,9 @@ namespace Lastfm
{
StopTransitionHandler ();
- if (current_web_req != null) {
+ /*if (current_web_req != null) {
current_web_req.Abort ();
- }
+ }*/
queue.Save ();
@@ -175,32 +161,19 @@ namespace Lastfm
}
if ((state == State.Idle || state == State.NeedTransmit) && hard_failures > 2) {
- state = State.NeedHandshake;
hard_failures = 0;
}
// and address changes in our engine state
switch (state) {
case State.Idle:
- if (LastfmCore.Account.UserName != null &&
- LastfmCore.Account.SessionKey != null && session_id == null) {
-
- state = State.NeedHandshake;
+ if (queue.Count > 0) {
+ state = State.NeedTransmit;
+ } else if (current_now_playing_request != null) {
+ // Now playing info needs to be sent
+ NowPlaying (current_now_playing_request);
} else {
- if (queue.Count > 0 && session_id != null) {
- state = State.NeedTransmit;
- } else if (current_now_playing_data != null && session_id != null) {
- // Now playing info needs to be sent
- NowPlaying (current_now_playing_data);
- } else {
- StopTransitionHandler ();
- }
- }
-
- break;
- case State.NeedHandshake:
- if (DateTime.Now > next_interval) {
- Handshake ();
+ StopTransitionHandler ();
}
break;
@@ -209,27 +182,15 @@ namespace Lastfm
TransmitQueue ();
}
break;
+ case State.Transmitting:
case State.WaitingForResponse:
- case State.WaitingForRequestStream:
- case State.WaitingForHandshakeResp:
// nothing here
break;
}
}
- //
- // Async code for transmitting the current queue of tracks
- //
- internal class TransmitState
- {
- public StringBuilder StringBuilder;
- public int Count;
- }
-
private void TransmitQueue ()
{
- int num_tracks_transmitted;
-
// save here in case we're interrupted before we complete
// the request. we save it again when we get an OK back
// from the server
@@ -237,263 +198,88 @@ namespace Lastfm
next_interval = DateTime.MinValue;
- if (post_url == null || !connected) {
+ if (!connected) {
return;
}
- string song_transmit_info = queue.GetTransmitInfo (out num_tracks_transmitted);
- Log.DebugFormat ("Last.fm scrobbler sending '{0}' to {1}", song_transmit_info, post_url);
+ current_scrobble_request = new LastfmRequest ("track.scrobble", RequestType.Write, ResponseFormat.Json);
+ IList<IQueuedTrack> tracks = queue.GetTransmitInfo ();
- StringBuilder sb = new StringBuilder ();
+ for (int i = 0; i < tracks.Count; i ++) {
+ IQueuedTrack track = tracks[i];
- sb.AppendFormat ("s={0}", session_id);
- sb.Append (song_transmit_info);
+ string str_track_number = String.Empty;
+ if (track.TrackNumber != 0) {
+ str_track_number = track.TrackNumber.ToString();
+ }
- current_web_req = (HttpWebRequest) WebRequest.Create (post_url);
- current_web_req.UserAgent = LastfmCore.UserAgent;
- current_web_req.Method = "POST";
- current_web_req.ContentType = "application/x-www-form-urlencoded";
- current_web_req.ContentLength = sb.Length;
+ bool chosen_by_user = (track.TrackAuth.Length == 0);
- TransmitState ts = new TransmitState ();
- ts.Count = num_tracks_transmitted;
- ts.StringBuilder = sb;
+ current_scrobble_request.AddParameter (String.Format ("timestamp[{0}]", i), track.StartTime.ToString ());
+ current_scrobble_request.AddParameter (String.Format ("track[{0}]", i), track.Title);
+ current_scrobble_request.AddParameter (String.Format ("artist[{0}]", i), track.Artist);
+ current_scrobble_request.AddParameter (String.Format ("album[{0}]", i), track.Album);
+ current_scrobble_request.AddParameter (String.Format ("trackNumber[{0}]", i), str_track_number);
+ current_scrobble_request.AddParameter (String.Format ("duration[{0}]", i), track.Duration.ToString ());
+ current_scrobble_request.AddParameter (String.Format ("mbid[{0}]", i), track.MusicBrainzId);
+ current_scrobble_request.AddParameter (String.Format ("chosenByUser[{0}]", i), chosen_by_user ? "1" : "0");
+ }
+ Log.DebugFormat ("### Last.fm scrobbler sending '{0}'", current_scrobble_request.ToString ());
- state = State.WaitingForRequestStream;
- current_async_result = current_web_req.BeginGetRequestStream (TransmitGetRequestStream, ts);
+ state = State.Transmitting;
+ current_async_result = current_scrobble_request.BeginSend (OnScrobbleResponse, tracks.Count);
+ state = State.WaitingForResponse;
if (!(current_async_result.AsyncWaitHandle.WaitOne (TIME_OUT, false))) {
Log.Warning ("Audioscrobbler upload failed", "The request timed out and was aborted", false);
next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
hard_failures++;
state = State.Idle;
- current_web_req.Abort();
+ //current_web_req.Abort();
}
}
- private void TransmitGetRequestStream (IAsyncResult ar)
+ private void OnScrobbleResponse (IAsyncResult ar)
{
- Stream stream;
- TransmitState ts;
-
+ int nb_tracks_scrobbled = 0;
try {
- stream = current_web_req.EndGetRequestStream (ar);
-
- ts = (TransmitState) ar.AsyncState;
- StringBuilder sb = ts.StringBuilder;
+ current_scrobble_request.EndSend (ar);
+ nb_tracks_scrobbled = (int)ar.AsyncState;
- StreamWriter writer = new StreamWriter (stream);
- writer.Write (sb.ToString ());
- writer.Close ();
} catch (Exception e) {
- Log.Exception ("Failed to get the request stream", e);
+ Log.Exception ("Failed to complete the scrobble request", e);
state = State.Idle;
- next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
return;
}
- state = State.WaitingForResponse;
- current_async_result = current_web_req.BeginGetResponse (TransmitGetResponse, ts);
- if (current_async_result == null) {
+ var response = current_scrobble_request.GetResponseObject ();
+ object error_code;
+ if (response.TryGetValue ("error", out error_code)) {
+ Log.WarningFormat ("Lastfm scrobbling error {0} : {1}", (int)error_code, (string)response["message"]);
next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
hard_failures++;
state = State.Idle;
- }
- }
-
- private void TransmitGetResponse (IAsyncResult ar)
- {
- WebResponse resp;
-
- try {
- resp = current_web_req.EndGetResponse (ar);
- }
- catch (Exception e) {
- Log.Warning (String.Format("Failed to get the response: {0}", e), false);
-
- state = State.Idle;
- next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
- return;
- }
-
- TransmitState ts = (TransmitState) ar.AsyncState;
-
- Stream s = resp.GetResponseStream ();
-
- StreamReader sr = new StreamReader (s, Encoding.UTF8);
-
- string line;
- line = sr.ReadLine ();
-
- DateTime now = DateTime.Now;
- if (line.StartsWith ("FAILED")) {
- if (now - last_upload_failed_logged > TimeSpan.FromMinutes(FAILURE_LOG_MINUTES)) {
- Log.Warning ("Audioscrobbler upload failed", line.Substring ("FAILED".Length).Trim(), false);
- last_upload_failed_logged = now;
- }
-
- hard_failures++;
-
- queue.RemoveInvalidTracks ();
-
- // if there are still valid tracks in the queue then retransmit on the next interval
- if (queue.Count > 0) {
- state = State.NeedTransmit;
- } else {
- state = State.Idle;
- }
- }
- else if (line.StartsWith ("BADSESSION")) {
- if (now - last_upload_failed_logged > TimeSpan.FromMinutes(FAILURE_LOG_MINUTES)) {
- Log.Warning ("Audioscrobbler upload failed", "session ID sent was invalid", false);
- last_upload_failed_logged = now;
- }
-
- // attempt to re-handshake (and retransmit) on the next interval
- session_id = null;
- next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
- state = State.NeedHandshake;
- return;
- } else if (line.StartsWith ("OK")) {
- /* if we've previously logged failures, be nice and log the successful upload. */
- if (last_upload_failed_logged != DateTime.MinValue) {
- Log.Debug ("Audioscrobbler upload succeeded");
- last_upload_failed_logged = DateTime.MinValue;
+ } else {
+ try {
+ var scrobbles = (JsonObject)response["scrobbles"];
+ var scrobbles_attr = (JsonObject)scrobbles["@attr"];
+ Log.InformationFormat ("Audioscrobbler upload succeeded: {0} accepted, {1} ignored",
+ scrobbles_attr["accepted"], scrobbles_attr["ignored"]);
+ } catch (Exception) {
+ Log.Information ("Audioscrobbler upload succeeded but unknown response received");
+ Log.Debug ("Response received", response.ToString ());
}
hard_failures = 0;
// we succeeded, pop the elements off our queue
- queue.RemoveRange (0, ts.Count);
+ queue.RemoveRange (0, nb_tracks_scrobbled);
queue.Save ();
state = State.Idle;
- } else {
- if (now - last_upload_failed_logged > TimeSpan.FromMinutes(FAILURE_LOG_MINUTES)) {
- Log.Warning ("Audioscrobbler upload failed", String.Format ("Unrecognized response: {0}", line), false);
- last_upload_failed_logged = now;
- }
-
- state = State.Idle;
}
-
- sr.Close ();
}
- //
- // Async code for handshaking
- //
-
- private string UnixTime ()
- {
- return ((int) (DateTime.UtcNow - new DateTime (1970, 1, 1)).TotalSeconds).ToString ();
- }
-
- private void Handshake ()
- {
- string timestamp = UnixTime();
- string authentication_token = Hyena.CryptoUtil.Md5Encode
- (LastfmCore.ApiSecret + timestamp);
-
- string api_url = LastfmCore.Account.ScrobbleUrl;
- if (String.IsNullOrEmpty (api_url)) {
- api_url = SCROBBLER_URL;
- } else {
- Log.DebugFormat ("Scrobbling to non-standard API URL: {0}", api_url);
- }
-
- string uri = String.Format ("{0}?hs=true&p={1}&c={2}&v={3}&u={4}&t={5}&a={6}&api_key={7}&sk={8}",
- api_url,
- SCROBBLER_VERSION,
- CLIENT_ID, CLIENT_VERSION,
- HttpUtility.UrlEncode (LastfmCore.Account.UserName),
- timestamp,
- authentication_token,
- LastfmCore.ApiKey,
- LastfmCore.Account.SessionKey);
-
- current_web_req = (HttpWebRequest) WebRequest.Create (uri);
-
- state = State.WaitingForHandshakeResp;
- current_async_result = current_web_req.BeginGetResponse (HandshakeGetResponse, null);
- if (current_async_result == null) {
- next_interval = DateTime.Now + new TimeSpan (0, 0, hard_failure_retry_sec);
- hard_failures++;
- if (hard_failure_retry_sec < MAX_RETRY_SECONDS)
- hard_failure_retry_sec *= 2;
- state = State.NeedHandshake;
- }
- }
-
- private void HandshakeGetResponse (IAsyncResult ar)
- {
- bool success = false;
- bool hard_failure = false;
- WebResponse resp;
-
- try {
- resp = current_web_req.EndGetResponse (ar);
- }
- catch (Exception e) {
- Log.Warning ("Failed to handshake", e.ToString (), false);
-
- // back off for a time before trying again
- state = State.Idle;
- next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
- return;
- }
-
- Stream s = resp.GetResponseStream ();
-
- StreamReader sr = new StreamReader (s, Encoding.UTF8);
-
- string line;
-
- line = sr.ReadLine ();
- if (line.StartsWith ("BANNED")) {
- Log.Warning ("Audioscrobbler sign-on failed", "Player is banned", false);
-
- } else if (line.StartsWith ("BADAUTH")) {
- Log.Warning ("Audioscrobbler sign-on failed", Catalog.GetString ("Last.fm username is invalid or Banshee is not authorized to access your account."));
- LastfmCore.Account.SessionKey = null;
- } else if (line.StartsWith ("BADTIME")) {
- Log.Warning ("Audioscrobbler sign-on failed",
- "timestamp provided was not close enough to the current time", false);
- } else if (line.StartsWith ("FAILED")) {
- Log.Warning ("Audioscrobbler sign-on failed",
- String.Format ("Temporary server failure: {0}", line.Substring ("FAILED".Length).Trim ()), false);
- hard_failure = true;
- } else if (line.StartsWith ("OK")) {
- success = true;
- } else {
- Log.Error ("Audioscrobbler sign-on failed", String.Format ("Unknown error: {0}", line.Trim()));
- hard_failure = true;
- }
-
- if (success == true) {
- Log.Debug ("Audioscrobbler sign-on succeeded", "Session ID received");
- session_id = sr.ReadLine ().Trim ();
- now_playing_url = sr.ReadLine ().Trim ();
- post_url = sr.ReadLine ().Trim ();
-
- hard_failures = 0;
- hard_failure_retry_sec = 60;
- } else {
- if (hard_failure == true) {
- next_interval = DateTime.Now + new TimeSpan (0, 0, hard_failure_retry_sec);
- hard_failures++;
- if (hard_failure_retry_sec < MAX_RETRY_SECONDS)
- hard_failure_retry_sec *= 2;
- }
- }
-
- state = State.Idle;
- sr.Close ();
- }
-
- //
- // Async code for now playing
-
public void NowPlaying (string artist, string title, string album, double duration,
int tracknum)
{
@@ -503,6 +289,10 @@ namespace Lastfm
public void NowPlaying (string artist, string title, string album, double duration,
int tracknum, string mbrainzid)
{
+ if (now_playing_started) {
+ return;
+ }
+
if (String.IsNullOrEmpty(artist) || String.IsNullOrEmpty(title) || !connected) {
return;
}
@@ -512,136 +302,56 @@ namespace Lastfm
str_track_number = tracknum.ToString();
}
- // Fall back to prefixing the URL with a # in case we haven't actually
- // authenticated yet. We replace it with the now_playing_url and session_id
- // later on in NowPlaying(uri).
- string dataprefix = "#";
+ LastfmRequest request = new LastfmRequest ("track.updateNowPlaying", RequestType.Write, ResponseFormat.Json);
+ request.AddParameter ("track", title);
+ request.AddParameter ("artist", artist);
+ request.AddParameter ("album", album);
+ request.AddParameter ("trackNumber", str_track_number);
+ request.AddParameter ("duration", Math.Floor (duration).ToString ());
+ request.AddParameter ("mbid", mbrainzid);
+ current_now_playing_request = request;
- if (session_id != null) {
- dataprefix = String.Format ("s={0}", session_id);
- }
-
- string data = String.Format ("{0}&a={1}&t={2}&b={3}&l={4}&n={5}&m={6}",
- dataprefix,
- HttpUtility.UrlEncode(artist),
- HttpUtility.UrlEncode(title),
- HttpUtility.UrlEncode(album),
- duration.ToString("F0"),
- str_track_number,
- mbrainzid);
-
- NowPlaying (data);
+ NowPlaying (current_now_playing_request);
}
- private void NowPlaying (string data)
+ private void NowPlaying (LastfmRequest request)
{
- if (now_playing_started) {
- return;
- }
-
- // If the URI begins with #, then we know the URI was created before we
- // had actually authenticated. So, because we didn't know the session_id and
- // now_playing_url previously, we should now, so we put that in and create our
- // new URI.
- if (data.StartsWith ("#") && session_id != null) {
- data = String.Format ("s={0}{1}",
- session_id,
- data.Substring (1));
- }
-
- current_now_playing_data = data;
-
- if (session_id == null) {
- // Go connect - we'll come back later in main timer loop.
- Start ();
- return;
- }
+ Log.DebugFormat ("### Last.fm NowPlaying sending '{0}'", current_now_playing_request.ToString ());
try {
- now_playing_post = (HttpWebRequest) WebRequest.Create (now_playing_url);
- now_playing_post.UserAgent = LastfmCore.UserAgent;
- now_playing_post.Method = "POST";
- now_playing_post.ContentType = "application/x-www-form-urlencoded";
-
- if (state == State.Idle) {
- // Don't actually POST it until we're idle (that is, we
- // probably have stuff queued which will reset the Now
- // Playing if we send them first).
- now_playing_started = true;
- now_playing_post.BeginGetRequestStream (NowPlayingGetRequestStream, data);
- }
- } catch (Exception ex) {
+ request.BeginSend (OnNowPlayingResponse);
+ }
+ catch (Exception e) {
Log.Warning ("Audioscrobbler NowPlaying failed",
- String.Format ("Exception while creating request: {0}", ex), false);
-
- // Reset current_now_playing_data if it was the problem.
- current_now_playing_data = null;
+ String.Format("Failed to post NowPlaying: {0}", e), false);
}
+
}
- private void NowPlayingGetRequestStream (IAsyncResult ar)
+ private void OnNowPlayingResponse (IAsyncResult ar)
{
try {
- string data = ar.AsyncState as string;
- ASCIIEncoding encoding = new ASCIIEncoding ();
- byte[] data_bytes = encoding.GetBytes (data);
- Stream request_stream = now_playing_post.EndGetRequestStream (ar);
- request_stream.Write (data_bytes, 0, data_bytes.Length);
- request_stream.Close ();
-
- now_playing_post.BeginGetResponse (NowPlayingGetResponse, null);
+ current_now_playing_request.EndSend (ar);
} catch (Exception e) {
- Log.Exception (e);
+ Log.Exception ("Failed to complete the NowPlaying request", e);
+ state = State.Idle;
+ current_now_playing_request = null;
+ return;
}
- }
- private void NowPlayingGetResponse (IAsyncResult ar)
- {
- try {
- WebResponse my_resp = now_playing_post.EndGetResponse (ar);
-
- Stream s = my_resp.GetResponseStream ();
- using (StreamReader sr = new StreamReader (s, Encoding.UTF8)) {
- string line = sr.ReadLine ();
- if (line == null) {
- Log.Warning ("Audioscrobbler NowPlaying failed", "No response", false);
- }
-
- if (line.StartsWith ("BADSESSION")) {
- Log.Warning ("Audioscrobbler NowPlaying failed", "Session ID sent was invalid", false);
- // attempt to re-handshake on the next interval
- session_id = null;
- next_interval = DateTime.Now + new TimeSpan (0, 0, RETRY_SECONDS);
- state = State.NeedHandshake;
- StartTransitionHandler ();
- return;
- } else if (line.StartsWith ("OK")) {
- // NowPlaying submitted
- Log.DebugFormat ("Submitted NowPlaying track to Audioscrobbler");
- now_playing_started = false;
- now_playing_post = null;
- current_now_playing_data = null;
- return;
- } else {
- Log.Warning ("Audioscrobbler NowPlaying failed", "Unexpected or no response", false);
- }
- }
- }
- catch (Exception e) {
- Log.Warning ("Audioscrobbler NowPlaying failed",
- String.Format("Failed to post NowPlaying: {0}", e), false);
- }
+ StationError error = current_now_playing_request.GetError ();
- // NowPlaying error/success is non-crucial.
- hard_failures++;
- if (hard_failures < 3) {
- NowPlaying (current_now_playing_data);
+ // API docs say "Now Playing requests that fail should not be retried".
+ if (error == StationError.InvalidSessionKey) {
+ Log.Warning ("Audioscrobbler NowPlaying failed", "Session ID sent was invalid", false);
+ // TODO: Suggest to the user to (re)do the Last.fm authentication ?
+ } else if (error != StationError.None) {
+ Log.WarningFormat ("Audioscrobbler NowPlaying failed: {0}", error.ToString ());
} else {
- // Give up - NowPlaying status information is non-critical.
- current_now_playing_data = null;
+ Log.Debug ("Submitted NowPlaying track to Audioscrobbler");
now_playing_started = false;
- now_playing_post = null;
}
+ current_now_playing_request = null;
}
}
}
diff --git a/src/Libraries/Lastfm/Lastfm/IQueue.cs b/src/Libraries/Lastfm/Lastfm/IQueue.cs
index 9b1afaf..b44b63c 100644
--- a/src/Libraries/Lastfm/Lastfm/IQueue.cs
+++ b/src/Libraries/Lastfm/Lastfm/IQueue.cs
@@ -27,9 +27,24 @@
//
using System;
+using System.Collections.Generic;
namespace Lastfm
{
+ public interface IQueuedTrack
+ {
+ long StartTime { get; }
+
+ string Artist { get; }
+
+ string Album { get; }
+ string Title { get; }
+ int TrackNumber { get; }
+ int Duration { get; }
+ string MusicBrainzId { get; }
+ string TrackAuth { get; }
+ }
+
public interface IQueue
{
event EventHandler TrackAdded;
@@ -41,7 +56,7 @@ namespace Lastfm
void Save ();
void Load ();
- string GetTransmitInfo (out int numtracks);
+ List<IQueuedTrack> GetTransmitInfo ();
void Add (object track, DateTime started);
void RemoveRange (int first, int count);
diff --git a/src/Libraries/Lastfm/Lastfm/LastfmRequest.cs b/src/Libraries/Lastfm/Lastfm/LastfmRequest.cs
index 7867be8..691df75 100644
--- a/src/Libraries/Lastfm/Lastfm/LastfmRequest.cs
+++ b/src/Libraries/Lastfm/Lastfm/LastfmRequest.cs
@@ -51,6 +51,8 @@ namespace Lastfm
Raw
}
+ public delegate void SendRequestHandler ();
+
public class LastfmRequest
{
private const string API_ROOT = "http://ws.audioscrobbler.com/2.0/";
@@ -114,9 +116,9 @@ namespace Lastfm
public JsonObject GetResponseObject ()
{
- if (response_stream == null)
+ if (response_stream == null) {
return null;
-
+ }
Deserializer deserializer = new Deserializer (response_stream);
object obj = deserializer.Deserialize ();
JsonObject json_obj = obj as Hyena.Json.JsonObject;
@@ -128,6 +130,24 @@ namespace Lastfm
return json_obj;
}
+ public IAsyncResult BeginSend (AsyncCallback callback)
+ {
+ return BeginSend (callback, null);
+ }
+
+ private SendRequestHandler send_handler;
+ public IAsyncResult BeginSend (AsyncCallback callback, object context)
+ {
+ send_handler = new SendRequestHandler (Send);
+
+ return send_handler.BeginInvoke (callback, context);
+ }
+
+ public void EndSend (IAsyncResult result)
+ {
+ send_handler.EndInvoke (result);
+ }
+
public StationError GetError ()
{
StationError error = StationError.None;
@@ -196,7 +216,9 @@ namespace Lastfm
private string GetSignature ()
{
- SortedDictionary<string, string> sorted_params = new SortedDictionary<string, string> (parameters);
+ // We need to have trackNumber[0] before track[0], so we use StringComparer.Ordinal
+ SortedDictionary<string, string> sorted_params =
+ new SortedDictionary<string, string> (parameters, StringComparer.Ordinal);
if (!sorted_params.ContainsKey ("api_key")) {
sorted_params.Add ("api_key", LastfmCore.ApiKey);
@@ -217,6 +239,17 @@ namespace Lastfm
return Hyena.CryptoUtil.Md5Encode (signature.ToString (), Encoding.UTF8);
}
+ public override string ToString ()
+ {
+ StringBuilder sb = new StringBuilder ();
+
+ sb.Append (method);
+ foreach (KeyValuePair<string, string> param in parameters) {
+ sb.AppendFormat ("\n\t{0}={1}", param.Key, param.Value);
+ }
+ return sb.ToString ();
+ }
+
#region HTTP helpers
private Stream Get (string uri)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]