[banshee] Migrate to the Last.fm 2.0 API (BGO#541227)
- From: Bertrand Lorentz <blorentz src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [banshee] Migrate to the Last.fm 2.0 API (BGO#541227)
- Date: Mon, 9 Nov 2009 20:48:13 +0000 (UTC)
commit 874b0d0a8288a109fcbe5f3b5d2dcec5614dcc64
Author: Bertrand Lorentz <bertrand lorentz gmail com>
Date: Mon Nov 9 21:16:01 2009 +0100
Migrate to the Last.fm 2.0 API (BGO#541227)
This commit upgrades the Lastfm to use the 2.0 API, as the older API was
disabled a while back. Radio streams are accessible again, under
restrictions imposed by Last.fm : paying subscribers and people
living the US, UK or Germany.
Major changes are :
- Web services style API returning JSON, handled in the new
LastfmRequest class.
- New authentication scheme. The user's doesn't have to give us
his password, but has to authorize Banshee to access his account.
- Scrobbling uses this authentication scheme, moving it to the 1.2.1
protocol.
The Lastfm.Data namespace is not impacted.
.../AudioscrobblerService.cs | 9 +-
.../Banshee.Lastfm.Radio/LastfmSource.cs | 22 +-
.../Banshee.Lastfm.Radio/StationEditor.cs | 2 +-
src/Libraries/Lastfm.Gui/Lastfm.Gui.csproj | 4 +
.../Lastfm.Gui/Lastfm.Gui/AccountLoginDialog.cs | 11 +-
.../Lastfm.Gui/Lastfm.Gui/AccountLoginForm.cs | 80 ++---
src/Libraries/Lastfm/Lastfm.csproj | 1 +
src/Libraries/Lastfm/Lastfm/Account.cs | 79 +++-
.../Lastfm/Lastfm/AudioscrobblerConnection.cs | 21 +-
src/Libraries/Lastfm/Lastfm/LastfmCore.cs | 14 +
src/Libraries/Lastfm/Lastfm/LastfmRequest.cs | 261 +++++++++++++
src/Libraries/Lastfm/Lastfm/RadioConnection.cs | 412 +++++---------------
src/Libraries/Lastfm/Makefile.am | 1 +
13 files changed, 517 insertions(+), 400 deletions(-)
---
diff --git a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/AudioscrobblerService.cs b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/AudioscrobblerService.cs
index 6813f6d..d3baed9 100644
--- a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/AudioscrobblerService.cs
+++ b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Audioscrobbler/AudioscrobblerService.cs
@@ -81,7 +81,7 @@ namespace Banshee.Lastfm.Audioscrobbler
if (account.UserName == null) {
account.UserName = LastUserSchema.Get ();
- account.CryptedPassword = LastPassSchema.Get ();
+ account.SessionKey = LastSessionKeySchema.Get ();
account.ScrobbleUrl = LastScrobbleUrlSchema.Get ();
}
@@ -103,8 +103,7 @@ namespace Banshee.Lastfm.Audioscrobbler
// Update the Visit action menu item if we update our account info
LastfmCore.Account.Updated += delegate (object o, EventArgs args) {
- actions["AudioscrobblerVisitAction"].Sensitive = LastfmCore.Account.UserName != null
- && LastfmCore.Account.CryptedPassword != null;
+ actions["AudioscrobblerVisitAction"].Sensitive = String.IsNullOrEmpty (LastfmCore.Account.UserName);
};
ServiceManager.PlayerEngine.ConnectEvent (OnPlayerEvent,
@@ -315,8 +314,8 @@ namespace Banshee.Lastfm.Audioscrobbler
"plugins.lastfm", "username", "", "Last.fm user", "Last.fm username"
);
- public static readonly SchemaEntry<string> LastPassSchema = new SchemaEntry<string> (
- "plugins.lastfm", "password_hash", "", "Last.fm password", "Last.fm password (hashed)"
+ public static readonly SchemaEntry<string> LastSessionKeySchema = new SchemaEntry<string> (
+ "plugins.lastfm", "session_key", "", "Last.fm session key", "Last.fm sessions key used in authenticated calls"
);
public static readonly SchemaEntry<string> LastScrobbleUrlSchema = new SchemaEntry<string> (
diff --git a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSource.cs b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSource.cs
index e8685a4..fa4ab92 100644
--- a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSource.cs
+++ b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSource.cs
@@ -76,7 +76,8 @@ namespace Banshee.Lastfm.Radio
// username we used so we can load the user's stations.
if (account.UserName != null) {
account.UserName = LastUserSchema.Get ();
- account.CryptedPassword = LastPassSchema.Get ();
+ account.SessionKey = LastSessionKeySchema.Get ();
+ account.Subscriber = LastIsSubscriberSchema.Get ();
}
if (LastfmCore.UserAgent == null) {
@@ -169,9 +170,9 @@ namespace Banshee.Lastfm.Radio
private bool last_was_subscriber = false;
public void SetUserName (string username)
{
- if (username != last_username || last_was_subscriber != Connection.Subscriber) {
+ if (username != last_username || last_was_subscriber != Account.Subscriber) {
last_username = username;
- last_was_subscriber = Connection.Subscriber;
+ last_was_subscriber = Account.Subscriber;
LastfmSource.LastUserSchema.Set (last_username);
ClearChildSources ();
PauseSorting ();
@@ -216,10 +217,11 @@ namespace Banshee.Lastfm.Radio
private void UpdateUI ()
{
bool have_user = Account.UserName != null;
- bool have_pass = Account.CryptedPassword != null;
+ bool have_session_key = Account.SessionKey != null;
- if (have_pass) {
- LastPassSchema.Set (Account.CryptedPassword);
+ if (have_session_key) {
+ LastSessionKeySchema.Set (Account.SessionKey);
+ LastIsSubscriberSchema.Set (Account.Subscriber);
}
if (have_user) {
@@ -269,8 +271,12 @@ namespace Banshee.Lastfm.Radio
"plugins.lastfm", "username", "", "Last.fm user", "Last.fm username"
);
- public static readonly SchemaEntry<string> LastPassSchema = new SchemaEntry<string> (
- "plugins.lastfm", "password_hash", "", "Last.fm password", "Last.fm password (hashed)"
+ public static readonly SchemaEntry<string> LastSessionKeySchema = new SchemaEntry<string> (
+ "plugins.lastfm", "session_key", "", "Last.fm session key", "Last.fm session key used in authenticated calls"
+ );
+
+ public static readonly SchemaEntry<bool> LastIsSubscriberSchema = new SchemaEntry<bool> (
+ "plugins.lastfm", "subscriber", false, "User is Last.fm subscriber", "User is Last.fm subscriber"
);
public static readonly SchemaEntry<bool> ExpandedSchema = new SchemaEntry<bool> (
diff --git a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationEditor.cs b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationEditor.cs
index 0533a66..d30ed88 100644
--- a/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationEditor.cs
+++ b/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/StationEditor.cs
@@ -89,7 +89,7 @@ namespace Banshee.Lastfm.Radio
int active_type = 0;
int i = 0;
foreach (StationType type in StationType.Types) {
- if (!type.SubscribersOnly || lastfm.Connection.Subscriber) {
+ if (!type.SubscribersOnly || lastfm.Account.Subscriber) {
type_combo.AppendText (type.Label);
if (source != null && type == source.Type) {
active_type = i;
diff --git a/src/Libraries/Lastfm.Gui/Lastfm.Gui.csproj b/src/Libraries/Lastfm.Gui/Lastfm.Gui.csproj
index 8866c70..2d01bf2 100644
--- a/src/Libraries/Lastfm.Gui/Lastfm.Gui.csproj
+++ b/src/Libraries/Lastfm.Gui/Lastfm.Gui.csproj
@@ -45,6 +45,10 @@
<Project>{C1F63FC5-4B96-48B2-B7F7-5B33FCC4F2A2}</Project>
<Name>Lastfm</Name>
</ProjectReference>
+ <ProjectReference Include="..\Hyena\Hyena.csproj">
+ <Project>{95374549-9553-4C1E-9D89-667755F90E12}</Project>
+ <Name>Hyena</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="Lastfm.Gui\AccountLoginDialog.cs" />
diff --git a/src/Libraries/Lastfm.Gui/Lastfm.Gui/AccountLoginDialog.cs b/src/Libraries/Lastfm.Gui/Lastfm.Gui/AccountLoginDialog.cs
index 06ea853..d6ef79d 100644
--- a/src/Libraries/Lastfm.Gui/Lastfm.Gui/AccountLoginDialog.cs
+++ b/src/Libraries/Lastfm.Gui/Lastfm.Gui/AccountLoginDialog.cs
@@ -85,6 +85,7 @@ namespace Lastfm.Gui
login_form = new AccountLoginForm (account);
login_form.AddSignUpButton ();
+ login_form.AddAuthorizeButton ();
login_form.Show ();
vbox.PackStart (login_form, true, true, 0);
@@ -112,7 +113,6 @@ namespace Lastfm.Gui
login_form.Save ();
};
AddActionWidget (button, ResponseType.Ok);
- login_form.SaveOnEnter (this);
}
}
@@ -132,6 +132,11 @@ namespace Lastfm.Gui
login_form.AddSignUpButton ();
}
+ public void AddAuthorizeButton ()
+ {
+ login_form.AddAuthorizeButton ();
+ }
+
public string Message {
get { return message.Text; }
set { message.Text = value; }
@@ -145,9 +150,5 @@ namespace Lastfm.Gui
public string Username {
get { return login_form.Username; }
}
-
- public string Password {
- get { return login_form.Password; }
- }
}
}
diff --git a/src/Libraries/Lastfm.Gui/Lastfm.Gui/AccountLoginForm.cs b/src/Libraries/Lastfm.Gui/Lastfm.Gui/AccountLoginForm.cs
index 00c04cc..ddb6e43 100644
--- a/src/Libraries/Lastfm.Gui/Lastfm.Gui/AccountLoginForm.cs
+++ b/src/Libraries/Lastfm.Gui/Lastfm.Gui/AccountLoginForm.cs
@@ -38,15 +38,12 @@ namespace Lastfm.Gui
{
private Account account;
private Entry username_entry;
- private Entry password_entry;
private LinkButton signup_button;
-
+ private Button authorize_button;
+
private bool save_on_edit = false;
- private bool save_on_enter = false;
- private Gtk.Dialog parentDialog;
-
- public AccountLoginForm (Account account) : base (2, 2, false)
+ public AccountLoginForm (Account account) : base (1, 2, false)
{
this.account = account;
@@ -61,37 +58,13 @@ namespace Lastfm.Gui
username_entry = new Entry ();
username_entry.Show ();
- Label password_label = new Label (Catalog.GetString ("Password:"));
- password_label.Xalign = 1.0f;
- password_label.Show ();
-
- password_entry = new Entry ();
- password_entry.Visibility = false;
-
- //When the user presses enter in the password field: save, and then destroy the AcountLoginDialog
- password_entry.Activated += delegate {
- if (save_on_enter) {
- this.Save ();
- parentDialog.Destroy ();
- }
- };
-
- password_entry.Show ();
-
Attach (username_label, 0, 1, 0, 1, AttachOptions.Fill,
AttachOptions.Shrink, 0, 0);
Attach (username_entry, 1, 2, 0, 1, AttachOptions.Fill | AttachOptions.Expand,
AttachOptions.Shrink, 0, 0);
- Attach (password_label, 0, 1, 1, 2, AttachOptions.Fill,
- AttachOptions.Shrink, 0, 0);
-
- Attach (password_entry, 1, 2, 1, 2, AttachOptions.Fill | AttachOptions.Expand,
- AttachOptions.Shrink, 0, 0);
-
username_entry.Text = account.UserName ?? String.Empty;
- password_entry.Text = account.Password ?? String.Empty;
}
protected override void OnDestroyed ()
@@ -109,17 +82,45 @@ namespace Lastfm.Gui
return;
}
- Resize (3, 2);
+ Resize (2, 2);
signup_button = new LinkButton (account.SignUpUrl, Catalog.GetString ("Sign up for Last.fm"));
signup_button.Show ();
- Attach (signup_button, 1, 2, 2, 3, AttachOptions.Shrink, AttachOptions.Shrink, 0, 0);
+ Attach (signup_button, 1, 2, 1, 2, AttachOptions.Shrink, AttachOptions.Shrink, 0, 0);
+ }
+
+ public void AddAuthorizeButton ()
+ {
+ if (authorize_button != null) {
+ return;
+ }
+
+ Resize (3, 2);
+ authorize_button = new Button (Catalog.GetString ("Authorize for Last.fm"));
+ authorize_button.Clicked += OnAuthorize;
+ authorize_button.Show ();
+ Attach (authorize_button, 1, 2, 2, 3, AttachOptions.Shrink, AttachOptions.Shrink, 0, 0);
+ }
+
+ private void OnAuthorize (object o, EventArgs args)
+ {
+ account.SessionKey = null;
+ account.RequestAuthorization ();
}
public void Save ()
{
- if (account.UserName != username_entry.Text.Trim () || account.Password != password_entry.Text.Trim ()) {
+ bool is_modified = false;
+
+ if (account.UserName != username_entry.Text.Trim ()) {
account.UserName = username_entry.Text.Trim ();
- account.Password = password_entry.Text.Trim ();
+ is_modified = true;
+ }
+ if (account.SessionKey == null) {
+ account.FetchSessionKey ();
+ is_modified = true;
+ }
+
+ if (is_modified) {
account.Save ();
}
}
@@ -129,20 +130,9 @@ namespace Lastfm.Gui
set { save_on_edit = value; }
}
- //enable save on Enter and destruction of the parentDialog.
- public void SaveOnEnter (Gtk.Dialog parentDialog)
- {
- save_on_enter = true;
- this.parentDialog = parentDialog;
- }
-
public string Username {
get { return username_entry.Text; }
}
-
- public string Password {
- get { return password_entry.Text; }
- }
}
}
diff --git a/src/Libraries/Lastfm/Lastfm.csproj b/src/Libraries/Lastfm/Lastfm.csproj
index f33c7ac..b84de28 100644
--- a/src/Libraries/Lastfm/Lastfm.csproj
+++ b/src/Libraries/Lastfm/Lastfm.csproj
@@ -67,6 +67,7 @@
<Compile Include="Lastfm.Data\LastfmData.cs" />
<Compile Include="Lastfm.Data\LastfmUserData.cs" />
<Compile Include="Lastfm.Data\LastfmAlbumData.cs" />
+ <Compile Include="Lastfm\LastfmRequest.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
diff --git a/src/Libraries/Lastfm/Lastfm/Account.cs b/src/Libraries/Lastfm/Lastfm/Account.cs
index de82369..a0f6cfd 100644
--- a/src/Libraries/Lastfm/Lastfm/Account.cs
+++ b/src/Libraries/Lastfm/Lastfm/Account.cs
@@ -31,39 +31,33 @@ using System;
using System.Collections;
using System.Text;
+using Hyena;
+
namespace Lastfm
{
public class Account
- {
+ {
public event EventHandler Updated;
+ // Only used during the authentication process
+ private string authentication_token;
+
private string username;
public string UserName {
get { return username; }
set { username = value; }
}
- private string password;
- public string Password {
- get { return password; }
- set { password = value; }
+ private string session_key;
+ public string SessionKey {
+ get { return session_key; }
+ set { session_key = value; }
}
- public string CryptedPassword {
- get {
- // Okay, so this will explode if someone has a raw text password
- // that matches ^[a-f0-9]{32}$ ... likely? I hope not.
-
- if (password == null) {
- return null;
- } else if (Hyena.CryptoUtil.IsMd5Encoded (password)) {
- return password;
- }
-
- password = Hyena.CryptoUtil.Md5Encode (password);
- return password;
- }
- set { password = String.IsNullOrEmpty (value) ? null : value; }
+ private bool subscriber;
+ public bool Subscriber {
+ get { return subscriber; }
+ set { subscriber = value; }
}
private string scrobble_url;
@@ -100,6 +94,51 @@ namespace Lastfm
OnUpdated ();
}
+ public StationError RequestAuthorization ()
+ {
+ LastfmRequest get_token = new LastfmRequest ("auth.getToken", RequestType.Read, ResponseFormat.Json);
+ get_token.Send ();
+
+ var response = get_token.GetResponseObject ();
+ object error_code;
+ if (response.TryGetValue ("error", out error_code)) {
+ Log.WarningFormat ("Lastfm error {0} : {1}", (int)error_code, (string)response["message"]);
+ return (StationError) error_code;
+ }
+
+ authentication_token = (string)response["token"];
+ Browser.Open (String.Format ("http://www.last.fm/api/auth/?api_key={0}&token={1}", LastfmCore.ApiKey, authentication_token));
+
+ return StationError.None;
+ }
+
+ public StationError FetchSessionKey ()
+ {
+ if (authentication_token == null) {
+ throw new InvalidOperationException ("RequestAuthorization should be called before calling FetchSessionKey");
+ }
+
+ LastfmRequest get_session = new LastfmRequest ("auth.getSession", RequestType.SessionRequest, ResponseFormat.Json);
+ get_session.AddParameter ("token", authentication_token);
+ get_session.Send ();
+ var response = get_session.GetResponseObject ();
+ object error_code;
+ if (response.TryGetValue ("error", out error_code)) {
+ Log.WarningFormat ("Lastfm error {0} : {1}", (int)error_code, (string)response["message"]);
+ return (StationError) error_code;
+ }
+
+ var session = (Hyena.Json.JsonObject)response["session"];
+ UserName = (string)session["name"];
+ SessionKey = (string)session["key"];
+ Subscriber = session["subscriber"].ToString ().Equals ("1");
+
+ // The authentication token is only valid once, and for a limited time
+ authentication_token = null;
+
+ return StationError.None;
+ }
+
protected void OnUpdated ()
{
EventHandler handler = Updated;
diff --git a/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs b/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs
index 3ed2cca..34d0aa3 100644
--- a/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs
+++ b/src/Libraries/Lastfm/Lastfm/AudioscrobblerConnection.cs
@@ -60,7 +60,7 @@ namespace Lastfm
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";
+ private const string SCROBBLER_VERSION = "1.2.1";
private string post_url;
private string session_id = null;
@@ -184,7 +184,7 @@ namespace Lastfm
switch (state) {
case State.Idle:
if (LastfmCore.Account.UserName != null &&
- LastfmCore.Account.CryptedPassword != null && session_id == null) {
+ LastfmCore.Account.SessionKey != null && session_id == null) {
state = State.NeedHandshake;
} else {
@@ -383,8 +383,8 @@ namespace Lastfm
private void Handshake ()
{
string timestamp = UnixTime();
- string security_token = Hyena.CryptoUtil.Md5Encode
- (LastfmCore.Account.CryptedPassword + timestamp);
+ string authentication_token = Hyena.CryptoUtil.Md5Encode
+ (LastfmCore.ApiSecret + timestamp);
string api_url = LastfmCore.Account.ScrobbleUrl;
if (String.IsNullOrEmpty (api_url)) {
@@ -393,13 +393,15 @@ namespace Lastfm
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}",
+ 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,
- security_token);
+ authentication_token,
+ LastfmCore.ApiKey,
+ LastfmCore.Account.SessionKey);
current_web_req = (HttpWebRequest) WebRequest.Create (uri);
@@ -443,8 +445,8 @@ namespace Lastfm
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 or password is invalid."));
- LastfmCore.Account.CryptedPassword = null;
+ Log.Warning ("Audioscrobbler sign-on failed", Catalog.GetString ("Last.fm username is invalid or Banshee is not authorized to access you 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);
@@ -518,7 +520,6 @@ namespace Lastfm
str_track_number,
mbrainzid);
- Console.WriteLine ("Submitting via non-uri handler.");
NowPlaying (data);
}
@@ -621,7 +622,7 @@ namespace Lastfm
String.Format("Failed to post NowPlaying: {0}", e), false);
}
- // NowPlaying error/success is non-crutial.
+ // NowPlaying error/success is non-crucial.
hard_failures++;
if (hard_failures < 3) {
NowPlaying (current_now_playing_data);
diff --git a/src/Libraries/Lastfm/Lastfm/LastfmCore.cs b/src/Libraries/Lastfm/Lastfm/LastfmCore.cs
index 7328210..d7b7f64 100644
--- a/src/Libraries/Lastfm/Lastfm/LastfmCore.cs
+++ b/src/Libraries/Lastfm/Lastfm/LastfmCore.cs
@@ -32,6 +32,20 @@ namespace Lastfm
{
public static class LastfmCore
{
+ // The default API key and secret are for a "Non-commercial Use" API account.
+ // See http://www.last.fm/api/account for more information.
+ private static string api_key = "344e9141fffeb02201e1ae455d92ae9f";
+ public static string ApiKey {
+ get { return api_key; }
+ set { api_key = value; }
+ }
+
+ private static string api_secret = "af3f4459eebbe1bde84fa9f8cf1a75fb";
+ internal static string ApiSecret {
+ get { return api_secret; }
+ set { api_secret = value; }
+ }
+
private static Account account;
public static Account Account {
get {
diff --git a/src/Libraries/Lastfm/Lastfm/LastfmRequest.cs b/src/Libraries/Lastfm/Lastfm/LastfmRequest.cs
new file mode 100644
index 0000000..1f479aa
--- /dev/null
+++ b/src/Libraries/Lastfm/Lastfm/LastfmRequest.cs
@@ -0,0 +1,261 @@
+//
+// LastfmRequest.cs
+//
+// Authors:
+// Bertrand Lorentz <bertrand lorentz gmail com>
+//
+// Copyright (C) 2009 Bertrand Lorentz
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Web;
+
+using Hyena;
+using Hyena.Json;
+
+namespace Lastfm
+{
+ public enum RequestType {
+ Read,
+ SessionRequest, // Needs the signature, but we don't have the session key yet
+ AuthenticatedRead,
+ Write
+ }
+
+ public enum ResponseFormat {
+ Json,
+ Raw
+ }
+
+ public class LastfmRequest
+ {
+ private const string API_ROOT = "http://ws.audioscrobbler.com/2.0/";
+
+ private Dictionary<string, string> parameters = new Dictionary<string, string> ();
+ private Stream response_stream;
+
+ public LastfmRequest ()
+ {}
+
+ public LastfmRequest (string method) : this (method, RequestType.Read, ResponseFormat.Json)
+ {}
+
+ public LastfmRequest (string method, RequestType request_type, ResponseFormat response_format)
+ {
+ this.method = method;
+ this.request_type = request_type;
+ this.response_format = response_format;
+ }
+
+ private string method;
+ public string Method { get; set; }
+
+
+ private RequestType request_type;
+ public RequestType RequestType { get; set; }
+
+
+ private ResponseFormat response_format;
+ public ResponseFormat ResponseFormat { get; set; }
+
+
+ public void AddParameter (string param_name, string param_value)
+ {
+ parameters.Add (param_name, param_value);
+ }
+
+ public Stream GetResponseStream ()
+ {
+ return response_stream;
+ }
+
+ public void Send ()
+ {
+ if (method == null) {
+ throw new InvalidOperationException ("The method name should be set");
+ }
+
+ if (response_format == ResponseFormat.Json) {
+ AddParameter ("format", "json");
+ } else if (response_format == ResponseFormat.Raw) {
+ AddParameter ("raw", "true");
+ }
+
+ if (request_type == RequestType.Write) {
+ response_stream = Post (API_ROOT, BuildPostData ());
+ } else {
+ response_stream = Get (BuildGetUrl ());
+ }
+ }
+
+ public JsonObject GetResponseObject ()
+ {
+ Deserializer deserializer = new Deserializer (response_stream);
+ object obj = deserializer.Deserialize ();
+ JsonObject json_obj = obj as Hyena.Json.JsonObject;
+
+ if (json_obj == null) {
+ throw new ApplicationException ("Lastfm invalid response : not a JSON object");
+ }
+
+ return json_obj;
+ }
+
+ public StationError GetError ()
+ {
+ StationError error = StationError.None;
+
+ string response;
+ using (StreamReader sr = new StreamReader (response_stream)) {
+ response = sr.ReadToEnd ();
+ }
+
+ if (response.Contains ("<lfm status=\"failed\">")) {
+ // XML reply indicates an error
+ Match match = Regex.Match (response, "<error code=\"(\\d+)\">");
+ if (match.Success) {
+ error = (StationError) Int32.Parse (match.Value);
+ Log.WarningFormat ("Lastfm error {0}", error);
+ } else {
+ error = StationError.Unknown;
+ }
+ }
+ if (response_format == ResponseFormat.Json && response.Contains ("\"error\":")) {
+ // JSON reply indicates an error
+ Deserializer deserializer = new Deserializer (response);
+ JsonObject json = deserializer.Deserialize () as JsonObject;
+ if (json != null && json.ContainsKey ("error")) {
+ error = (StationError) json["error"];
+ Log.WarningFormat ("Lastfm error {0} : {1}", error, (string)json["message"]);
+ }
+ }
+
+ return error;
+ }
+
+ private string BuildGetUrl ()
+ {
+ if (request_type == RequestType.AuthenticatedRead) {
+ parameters.Add ("sk", LastfmCore.Account.SessionKey);
+ }
+
+ StringBuilder url = new StringBuilder (API_ROOT);
+ url.AppendFormat ("?method={0}", method);
+ url.AppendFormat ("&api_key={0}", LastfmCore.ApiKey);
+ foreach (KeyValuePair<string, string> param in parameters) {
+ url.AppendFormat ("&{0}={1}", param.Key, Uri.EscapeDataString (param.Value));
+ }
+ if (request_type == RequestType.AuthenticatedRead || request_type == RequestType.SessionRequest) {
+ url.AppendFormat ("&api_sig={0}", GetSignature ());
+ }
+
+ return url.ToString ();
+ }
+
+ private string BuildPostData ()
+ {
+ parameters.Add ("sk", LastfmCore.Account.SessionKey);
+
+ StringBuilder data = new StringBuilder ();
+ data.AppendFormat ("method={0}", method);
+ data.AppendFormat ("&api_key={0}", LastfmCore.ApiKey);
+ foreach (KeyValuePair<string, string> param in parameters) {
+ data.AppendFormat ("&{0}={1}", param.Key, Uri.EscapeDataString (param.Value));
+ }
+ data.AppendFormat ("&api_sig={0}", GetSignature ());
+
+ return data.ToString ();
+ }
+
+ private string GetSignature ()
+ {
+ SortedDictionary<string, string> sorted_params = new SortedDictionary<string, string> (parameters);
+
+ if (!sorted_params.ContainsKey ("api_key")) {
+ sorted_params.Add ("api_key", LastfmCore.ApiKey);
+ }
+ if (!sorted_params.ContainsKey ("method")) {
+ sorted_params.Add ("method", method);
+ }
+ StringBuilder signature = new StringBuilder ();
+ foreach (KeyValuePair<string, string> parm in sorted_params) {
+ if (parm.Key.Equals ("format")) {
+ continue;
+ }
+ signature.Append (parm.Key);
+ signature.Append (parm.Value);
+ }
+ signature.Append (LastfmCore.ApiSecret);
+
+ return Hyena.CryptoUtil.Md5Encode (signature.ToString (), Encoding.UTF8);
+ }
+
+#region HTTP helpers
+
+ private Stream Get (string uri)
+ {
+ return Get (uri, null);
+ }
+
+ private Stream Get (string uri, string accept)
+ {
+ HttpWebRequest request = (HttpWebRequest) WebRequest.Create (uri);
+ if (accept != null) {
+ request.Accept = accept;
+ }
+ request.UserAgent = LastfmCore.UserAgent;
+ request.Timeout = 10000;
+ request.KeepAlive = false;
+ request.AllowAutoRedirect = true;
+
+ HttpWebResponse response = (HttpWebResponse) request.GetResponse ();
+ return response.GetResponseStream ();
+ }
+
+ private Stream Post (string uri, string data)
+ {
+ // Do not trust docs : it doesn't work if parameters are in the request body
+ HttpWebRequest request = (HttpWebRequest) WebRequest.Create (String.Concat (uri, "?", data));
+ request.UserAgent = LastfmCore.UserAgent;
+ request.Timeout = 10000;
+ request.Method = "POST";
+ request.KeepAlive = false;
+ request.ContentType = "application/x-www-form-urlencoded";
+
+ HttpWebResponse response = null;
+ try {
+ response = (HttpWebResponse) request.GetResponse ();
+ } catch (WebException e) {
+ Log.DebugException (e);
+ response = (HttpWebResponse)e.Response;
+ }
+ return response.GetResponseStream ();
+ }
+
+#endregion
+ }
+}
diff --git a/src/Libraries/Lastfm/Lastfm/RadioConnection.cs b/src/Libraries/Lastfm/Lastfm/RadioConnection.cs
index 7206305..05a540c 100644
--- a/src/Libraries/Lastfm/Lastfm/RadioConnection.cs
+++ b/src/Libraries/Lastfm/Lastfm/RadioConnection.cs
@@ -38,6 +38,7 @@ using System.Web;
using System.Threading;
using Hyena;
+using Hyena.Json;
using Mono.Unix;
using Media.Playlists.Xspf;
@@ -59,21 +60,38 @@ namespace Lastfm
NoAccount,
NoNetwork,
InvalidAccount,
+ NotAuthorized,
Connecting,
Connected
};
- // Error codes returned when trying to adjust.php to a new station
+ // Error codes returned by the API methods
public enum StationError
{
None = 0,
- NotEnoughContent = 1,
- FewGroupMembers,
- FewFans,
- Unavailable,
- Subscribe,
- FewNeighbors,
- Offline,
+ NotUsed = 1,
+ InvalidService,
+ InvalidMethod,
+ AuthenticationFailed,
+ InvalidFormat,
+ InvalidParameters,
+ InvalidResource,
+ TokenFailure,
+ InvalidSessionKey,
+ InvalidApiKey,
+ ServiceOffline,
+ SubscriptionError,
+ InvalidSignature,
+ TokenNotAuthorized,
+ ExpiredToken,
+
+ SubscriptionRequired = 18,
+
+ NotEnoughContent = 20,
+ NotEnoughMembers,
+ NotEnoughFans,
+ NotEnoughNeighbours,
+
Unknown // not an official code, just the fall back
}
@@ -83,19 +101,9 @@ namespace Lastfm
public event StateChangedHandler StateChanged;
private ConnectionState state;
- private string session;
- private string base_url;
- private string base_path;
private string info_message;
private bool network_connected = false;
- private static Regex station_error_regex = new Regex ("error=(\\d+)", RegexOptions.Compiled);
-
- private bool subscriber;
- public bool Subscriber {
- get { return subscriber; }
- }
-
public string InfoMessage {
get { return info_message; }
}
@@ -143,26 +151,28 @@ namespace Lastfm
if (State == ConnectionState.Connecting || State == ConnectionState.Connected)
return;
- if (LastfmCore.Account.UserName == null || LastfmCore.Account.CryptedPassword == null) {
+ if (LastfmCore.Account.UserName == null) {
State = ConnectionState.NoAccount;
return;
}
+
+ if (LastfmCore.Account.SessionKey == null) {
+ State = ConnectionState.NotAuthorized;
+ return;
+ }
if (!network_connected) {
State = ConnectionState.NoNetwork;
return;
}
- // Otherwise, we're good to try to connect
- State = ConnectionState.Connecting;
- Handshake ();
+ // Otherwise, we're good and consider ourselves connected
+ State = ConnectionState.Connected;
}
- public bool Love (string artist, string title) { return PostTrackRequest ("loveTrack", artist, title); }
- public bool UnLove (string artist, string title) { return PostTrackRequest ("unLoveTrack", artist, title); }
- public bool Ban (string artist, string title) { return PostTrackRequest ("banTrack", artist, title); }
- public bool UnBan (string artist, string title) { return PostTrackRequest ("unBanTrack", artist, title); }
-
+ public bool Love (string artist, string title) { return PostTrackRequest ("love", artist, title); }
+ public bool Ban (string artist, string title) { return PostTrackRequest ("ban", artist, title); }
+
public StationError ChangeStationTo (string station)
{
lock (this) {
@@ -170,18 +180,13 @@ namespace Lastfm
return StationError.None;
try {
- Stream stream = Get (StationUrlFor (station));
- using (StreamReader strm = new StreamReader (stream)) {
- string body = strm.ReadToEnd ();
- if (body.IndexOf ("response=OK") == -1) {
- Match match = station_error_regex.Match (body);
- if (match.Success) {
- int i = Int32.Parse (match.Groups[1].Value);
- return (StationError) i;
- } else {
- return StationError.Unknown;
- }
- }
+
+ LastfmRequest radio_tune = new LastfmRequest ("radio.tune", RequestType.Write, ResponseFormat.Json);
+ radio_tune.AddParameter ("station", station);
+ radio_tune.Send ();
+ StationError error = radio_tune.GetError ();
+ if (error != StationError.None) {
+ return error;
}
this.station = station;
@@ -199,18 +204,19 @@ namespace Lastfm
if (station != Station)
return null;
- string url = StationRefreshUrl;
Playlist pl = new Playlist ();
Stream stream = null;
+ LastfmRequest radio_playlist = new LastfmRequest ("radio.getPlaylist", RequestType.AuthenticatedRead, ResponseFormat.Raw);
try {
- stream = GetXspfStream (url);
+ radio_playlist.Send ();
+ stream = radio_playlist.GetResponseStream ();
pl.Load (stream);
Log.Debug (String.Format ("Adding {0} Tracks to Last.fm Station {1}", pl.TrackCount, station), null);
} catch (System.Net.WebException e) {
Log.Warning ("Error Loading Last.fm Station", e.Message, false);
return null;
} catch (Exception e) {
- string body = "Unable to get body";
+ string body = null;
try {
using (StreamReader strm = new StreamReader (stream)) {
body = strm.ReadToEnd ();
@@ -218,7 +224,7 @@ namespace Lastfm
} catch {}
Log.Warning (
"Error loading station",
- String.Format ("Exception:\n{0}\n\nResponse Body:\n{1}", e.ToString (), body), false
+ String.Format ("Exception:\n{0}\n\nResponse:\n{1}", e.ToString (), body ?? "Unable to get response"), false
);
return null;
}
@@ -231,8 +237,7 @@ namespace Lastfm
private void Initialize ()
{
- subscriber = false;
- base_url = base_path = session = station = info_message = null;
+ station = info_message = null;
}
private void HandleAccountUpdated (object o, EventArgs args)
@@ -256,195 +261,47 @@ namespace Lastfm
}
}
- private void Handshake ()
- {
- ThreadPool.QueueUserWorkItem (delegate {
- try {
- Stream stream = Get (String.Format (
- "http://ws.audioscrobbler.com/radio/handshake.php?version={0}&platform={1}&username={2}&passwordmd5={3}&language={4}&session=324234",
- "1.1.1",
- "linux", // FIXME
- LastfmCore.Account.UserName, LastfmCore.Account.CryptedPassword,
- "en" // FIXME
- ));
-
- // Set us as connecting, assuming the connection attempt wasn't changed out from under us
- if (ParseHandshake (new StreamReader (stream).ReadToEnd ()) && session != null) {
- State = ConnectionState.Connected;
- Log.Debug (String.Format ("Logged into Last.fm as {0} ({1}subscriber)",
- LastfmCore.Account.UserName, subscriber ? null : "not a "), null);
- return;
- }
- } catch (Exception e) {
- Log.Debug ("Error in Last.fm Handshake", e.ToString ());
- }
-
- // Must not have parsed correctly
- Initialize ();
- if (State == ConnectionState.Connecting)
- State = ConnectionState.Disconnected;
- });
- }
-
- private bool ParseHandshake (string content)
- {
- string [] lines = content.Split (new Char[] {'\n'});
- foreach (string line in lines) {
- string [] opts = line.Split (new Char[] {'='});
-
- switch (opts[0].Trim ().ToLower ()) {
- case "session":
- if (opts[1].ToLower () == "failed") {
- session = null;
- State = ConnectionState.InvalidAccount;
- Log.Warning (
- Catalog.GetString ("Failed to Login to Last.fm"),
- Catalog.GetString ("Either your username or password is invalid."),
- false
- );
- LastfmCore.Account.CryptedPassword = null;
- return false;
- }
-
- session = opts[1];
- break;
-
- case "stream_url":
- //stream_url = opts[1];
- break;
-
- case "subscriber":
- subscriber = (opts[1] != "0");
- break;
-
- case "base_url":
- base_url = opts[1];
- break;
-
- case "base_path":
- base_path = opts[1];
- break;
-
- case "info_message":
- info_message = opts[1];
- break;
-
- default:
- break;
- }
- }
-
- return true;
- }
-
- // Basic HTTP helpers
-
- private HttpStatusCode Post (string uri, string body)
- {
- if (!network_connected) {
- //throw new NetworkUnavailableException ();
- return HttpStatusCode.RequestTimeout;
- }
-
- HttpWebRequest request = (HttpWebRequest) WebRequest.Create (uri);
- request.UserAgent = LastfmCore.UserAgent;
- request.Timeout = 10000;
- request.Method = "POST";
- request.KeepAlive = false;
- request.ContentLength = body.Length;
-
- using (StreamWriter writer = new StreamWriter (request.GetRequestStream ())) {
- writer.Write (body);
- }
-
- HttpWebResponse response = (HttpWebResponse) request.GetResponse ();
- using (Stream stream = response.GetResponseStream ()) {
- /*using (StreamReader reader = new StreamReader (stream)) {
- Console.WriteLine ("Posted {0} got response {1}", body, reader.ReadToEnd ());
- }*/
- }
- return response.StatusCode;
- }
-
- private Stream GetXspfStream (string uri)
- {
- return Get (uri, "application/xspf+xml");
- }
-
- private Stream Get (string uri)
- {
- return Get (uri, null);
- }
-
- private Stream Get (string uri, string accept)
- {
- if (!network_connected) {
- //throw new NetworkUnavailableException ();
- return null;
- }
-
- HttpWebRequest request = (HttpWebRequest) WebRequest.Create (uri);
- if (accept != null) {
- request.Accept = accept;
- }
- request.UserAgent = LastfmCore.UserAgent;
- request.Timeout = 10000;
- request.KeepAlive = false;
- request.AllowAutoRedirect = true;
-
- HttpWebResponse response = (HttpWebResponse) request.GetResponse ();
- return response.GetResponseStream ();
- }
-
- /* ArtistMetaDataRequest.cpp: rpc.setMethod( "artistMetadata" );
- * DeleteFriendRequest.cpp: xmlrpc.setMethod( "removeFriend" );
- * RecommendRequest.cpp: xml_rpc.setMethod( "recommendItem" );
- * SetTagRequest.cpp: xml_rpc.setMethod( "tagArtist" );
- * SetTagRequest.cpp: xml_rpc.setMethod( "tagAlbum" );
- * SetTagRequest.cpp: xml_rpc.setMethod( "tagTrack" );
- * SimilarTagsRequest.cpp: xmlrpc.setMethod( "getSimilarTags" );
- * TrackMetaDataRequest.cpp: xmlrpc.setMethod( "trackMetadata" );
- * TrackToIdRequest.cpp: xmlrpc.setMethod( "trackToId" );
- * UserPicturesRequest.cpp: xmlrpc.setMethod( "getAvatarUrls" );
- */
-
-
- // URL generators for internal use
-
- private string XmlRpcUrl {
- get { return String.Format ("http://{0}/1.0/rw/xmlrpc.php", base_url); }
- }
-
- private string StationUrlFor (string station)
- {
- return String.Format (
- "http://{0}{1}/adjust.php?session={2}&url={3}&lang=en",
- base_url, base_path, session, HttpUtility.UrlEncode (station)
- );
- }
-
- private string StationRefreshUrl {
- get {
- return String.Format (
- "http://{0}{1}/xspf.php?sk={2}&discovery=0&desktop=1.3.1.1",
- base_url, base_path, session
- );
- }
- }
-
// Translated error message strings
public static string ErrorMessageFor (StationError error)
{
switch (error) {
- case StationError.NotEnoughContent: return Catalog.GetString ("There is not enough content to play this station.");
- case StationError.FewGroupMembers: return Catalog.GetString ("This group does not have enough members for radio.");
- case StationError.FewFans: return Catalog.GetString ("This artist does not have enough fans for radio.");
- case StationError.Unavailable: return Catalog.GetString ("This station is not available.");
- case StationError.Subscribe: return Catalog.GetString ("This station is only available to subscribers.");
- case StationError.FewNeighbors: return Catalog.GetString ("There are not enough neighbours for this station.");
- case StationError.Offline: return Catalog.GetString ("The streaming system is offline for maintenance, please try again later.");
- case StationError.Unknown: return Catalog.GetString ("There was an unknown error.");
+ case StationError.InvalidService:
+ case StationError.InvalidMethod:
+ return Catalog.GetString ("This service does not exist.");
+ case StationError.AuthenticationFailed:
+ case StationError.SubscriptionError:
+ case StationError.SubscriptionRequired:
+ return Catalog.GetString ("This station is only available to subscribers.");
+ case StationError.InvalidFormat:
+ return Catalog.GetString ("This station is not available.");
+ case StationError.InvalidParameters:
+ return Catalog.GetString ("The request is missing a required parameter.");
+ case StationError.InvalidResource:
+ return Catalog.GetString ("The specified resource is invalid.");
+ case StationError.TokenFailure:
+ return Catalog.GetString ("Server error, please try again later.");
+ case StationError.InvalidSessionKey:
+ return Catalog.GetString ("Invalid authentication information, please re-authenticate.");
+ case StationError.InvalidApiKey:
+ return Catalog.GetString ("The API key used by this application is invalid.");
+ case StationError.ServiceOffline:
+ return Catalog.GetString ("The streaming system is offline for maintenance, please try again later.");
+ case StationError.InvalidSignature:
+ return Catalog.GetString ("The method signature is invalid.");
+ case StationError.TokenNotAuthorized:
+ case StationError.ExpiredToken:
+ return Catalog.GetString ("You need to allow Banshee to access your Last.fm account.");
+ case StationError.NotEnoughContent:
+ return Catalog.GetString ("There is not enough content to play this station.");
+ case StationError.NotEnoughMembers:
+ return Catalog.GetString ("This group does not have enough members for radio.");
+ case StationError.NotEnoughFans:
+ return Catalog.GetString ("This artist does not have enough fans for radio.");
+ case StationError.NotEnoughNeighbours:
+ return Catalog.GetString ("There are not enough neighbours for this station.");
+ case StationError.Unknown:
+ return Catalog.GetString ("There was an unknown error.");
}
return String.Empty;
}
@@ -452,93 +309,36 @@ namespace Lastfm
public static string MessageFor (ConnectionState state)
{
switch (state) {
- case ConnectionState.Disconnected: return Catalog.GetString ("Not connected to Last.fm.");
- case ConnectionState.NoAccount: return Catalog.GetString ("Account details are needed before you can connect to Last.fm");
- case ConnectionState.NoNetwork: return Catalog.GetString ("No network connection detected.");
- case ConnectionState.InvalidAccount: return Catalog.GetString ("Last.fm username or password is invalid.");
- case ConnectionState.Connecting: return Catalog.GetString ("Connecting to Last.fm.");
- case ConnectionState.Connected: return Catalog.GetString ("Connected to Last.fm.");
+ case ConnectionState.Disconnected:
+ return Catalog.GetString ("Not connected to Last.fm.");
+ case ConnectionState.NoAccount:
+ return Catalog.GetString ("Account details are needed before you can connect to Last.fm");
+ case ConnectionState.NoNetwork:
+ return Catalog.GetString ("No network connection detected.");
+ case ConnectionState.InvalidAccount:
+ return Catalog.GetString ("Last.fm username is invalid.");
+ case ConnectionState.NotAuthorized:
+ return Catalog.GetString ("You need to allow Banshee to access your Last.fm account.");
+ case ConnectionState.Connecting:
+ return Catalog.GetString ("Connecting to Last.fm.");
+ case ConnectionState.Connected:
+ return Catalog.GetString ("Connected to Last.fm.");
}
return String.Empty;
}
- // XML-RPC foo
-
private bool PostTrackRequest (string method, string artist, string title)
{
- return PostXmlRpc (LastFMXmlRpcRequest (method).AddStringParams (artist, title));
- }
-
- private bool PostXmlRpc (LameXmlRpcRequest request)
- {
if (State != ConnectionState.Connected)
return false;
- return Post (XmlRpcUrl, request.ToString ()) == HttpStatusCode.OK;
- }
+ // track.love and track.ban do not return JSON
+ LastfmRequest track_request = new LastfmRequest (String.Concat ("track.", method), RequestType.Write, ResponseFormat.Raw);
+ track_request.AddParameter ("track", title);
+ track_request.AddParameter ("artist", artist);
+ track_request.Send ();
- private string UnixTime ()
- {
- return ((int) (DateTime.Now - new DateTime (1970, 1, 1)).TotalSeconds).ToString ();
- }
-
- private LameXmlRpcRequest LastFMXmlRpcRequest (string method)
- {
- string time = UnixTime ();
- string auth_hash = Hyena.CryptoUtil.Md5Encode (LastfmCore.Account.CryptedPassword + time);
- return new LameXmlRpcRequest (method).AddStringParams (LastfmCore.Account.UserName, time, auth_hash);
- }
-
- protected class LameXmlRpcRequest
- {
- private StringBuilder sb = new StringBuilder ();
- public LameXmlRpcRequest (string method_name)
- {
- sb.Append ("<?xml version=\"1.0\" encoding=\"us-ascii\"?>\n");
- sb.Append ("<methodCall><methodName>");
- sb.Append (Encode (method_name));
- sb.Append ("</methodName>\n");
- sb.Append ("<params>\n");
- }
-
- public LameXmlRpcRequest AddStringParams (params string [] values)
- {
- foreach (string value in values)
- AddStringParam (value);
- return this;
- }
-
- public LameXmlRpcRequest AddStringParam (string value)
- {
- sb.Append ("<param><value><string>");
- sb.Append (Encode (value));
- sb.Append ("</string></value></param>\n");
- return this;
- }
-
- public static string Encode (string val)
- {
- return HttpUtility.HtmlEncode (val);
- }
-
- private bool closed = false;
- public override string ToString ()
- {
- if (!closed) {
- sb.Append ("</params>\n</methodCall>");
- closed = true;
- }
-
- return sb.ToString ();
- }
- }
- }
-
- public sealed class StringUtils {
- public static string StringToUTF8 (string s)
- {
- byte [] ba = (new UnicodeEncoding ()).GetBytes (s);
- return System.Text.Encoding.UTF8.GetString (ba);
+ return (track_request.GetError () == StationError.None);
}
}
}
diff --git a/src/Libraries/Lastfm/Makefile.am b/src/Libraries/Lastfm/Makefile.am
index 1044dc4..22df01f 100644
--- a/src/Libraries/Lastfm/Makefile.am
+++ b/src/Libraries/Lastfm/Makefile.am
@@ -15,6 +15,7 @@ SOURCES = \
Lastfm/Browser.cs \
Lastfm/IQueue.cs \
Lastfm/LastfmCore.cs \
+ Lastfm/LastfmRequest.cs \
Lastfm/RadioConnection.cs
include $(top_srcdir)/build/build.mk
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]