banshee r4168 - in trunk/banshee: . src/Clients/Nereid/Nereid src/Core/Banshee.ThickClient/Banshee.Gui src/Core/Banshee.Widgets/Banshee.Widgets src/Extensions/Banshee.Lastfm src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations src/Extensions/Banshee.Lastfm/Resources src/Extensions/Banshee.Lastfm/ThemeIcons/48x48 src/Libraries/Lastfm src/Libraries/Lastfm/Lastfm.Data



Author: gburt
Date: Thu Jun 19 01:20:54 2008
New Revision: 4168
URL: http://svn.gnome.org/viewvc/banshee?rev=4168&view=rev

Log:
2008-06-18  Gabriel Burt  <gabriel burt gmail com>

	First pass at bringing the recommendations extension back.  It's got three
	big bugs atm: 1) background isn't white 2) height is too small 3) the
	similar artist tiles are not sized properly (they're tiny).

	* src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationActions.cs:
	Small class for adding/removing/handling the Show Recommendations action.

	* src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationService.cs:
	Ported from 0.13.2, listens to the source changes and player engine
	changes and shows the recommendation pane when appropriate.

	* src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationPane.cs:
	Ported from 0.13.2 but made to use the Lastfm library, shows similar
	artists and the top albums and tracks for an artist.

	* src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs:
	Move the TitledList class to not be an inner class; should really be in
	its own file.

	* src/Extensions/Banshee.Lastfm/Banshee.Lastfm.addin.xml: Update authors,
	add recommendations service.

	* src/Extensions/Banshee.Lastfm/Banshee.Lastfm.mdp: Add recommendations
	source files, and add hyena refs.

	* src/Extensions/Banshee.Lastfm/Makefile.am: Add recommendation sources
	and resouces.

	* src/Extensions/Banshee.Lastfm/Resources/RecommendationMenu.xml: Brought
	over verbatim from 0.13.2.

	* src/Clients/Nereid/Nereid/PlayerInterface.cs:
	* src/Core/Banshee.ThickClient/Banshee.Gui/BaseClientWindow.cs: Add
	ViewContainer property so that recommendations can put itself below the
	track list.

	* src/Core/Banshee.Widgets/Banshee.Widgets/Tile.cs: Add a convenience ctor
	for setting the primary text.

	* src/Libraries/Lastfm/Lastfm.mdp: Add a bunch of assembly refs.

	* src/Libraries/Lastfm/Lastfm.Data/DataEntry.cs: Add a couple missing
	properties to the SimilarArtist class.

	* src/Libraries/Lastfm/Lastfm.Data/LastfmData.cs: Set the collection this
	class backs to a static empty collection until it's loaded its own, so
	that the ICollection fucntions will not crash if called before its set.
	Move download code into DataCore.

	* src/Libraries/Lastfm/Lastfm.Data/DataCore.cs: Moved a bunch of functions
	here from LastfmData, and expose a new DownloadContent method so that the
	recommendations extension can download cover art and cache it in the
	lastfm cache.

	* src/Libraries/Lastfm/Lastfm.Data/DataEntryCollection.cs: In the
	XmlNodeList ctor, handle the list being null (used by the static
	collection mentioned in LastfmData above).


Added:
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationActions.cs
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationPane.cs
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationService.cs
   trunk/banshee/src/Extensions/Banshee.Lastfm/Resources/RecommendationMenu.xml
   trunk/banshee/src/Extensions/Banshee.Lastfm/Resources/no-results.png   (contents, props changed)
   trunk/banshee/src/Extensions/Banshee.Lastfm/ThemeIcons/48x48/
   trunk/banshee/src/Extensions/Banshee.Lastfm/ThemeIcons/48x48/generic-artist.png   (contents, props changed)
Modified:
   trunk/banshee/ChangeLog
   trunk/banshee/src/Clients/Nereid/Nereid/PlayerInterface.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/BaseClientWindow.cs
   trunk/banshee/src/Core/Banshee.Widgets/Banshee.Widgets/Tile.cs
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.addin.xml
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.mdp
   trunk/banshee/src/Extensions/Banshee.Lastfm/Makefile.am
   trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataCore.cs
   trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataEntry.cs
   trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataEntryCollection.cs
   trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/LastfmData.cs
   trunk/banshee/src/Libraries/Lastfm/Lastfm.mdp

Modified: trunk/banshee/src/Clients/Nereid/Nereid/PlayerInterface.cs
==============================================================================
--- trunk/banshee/src/Clients/Nereid/Nereid/PlayerInterface.cs	(original)
+++ trunk/banshee/src/Clients/Nereid/Nereid/PlayerInterface.cs	Thu Jun 19 01:20:54 2008
@@ -437,6 +437,10 @@
             source_view.ResetHighlight ();
         }
 
+        public override Box ViewContainer {
+            get { return view_container; }
+        }
+
 #endregion
         
 #region Gtk.Window Overrides

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/BaseClientWindow.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/BaseClientWindow.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui/BaseClientWindow.cs	Thu Jun 19 01:20:54 2008
@@ -62,6 +62,8 @@
             
             InitializeWindow ();
         }
+
+        public abstract Box ViewContainer { get; }
         
         public void ToggleVisibility ()
         {

Modified: trunk/banshee/src/Core/Banshee.Widgets/Banshee.Widgets/Tile.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.Widgets/Banshee.Widgets/Tile.cs	(original)
+++ trunk/banshee/src/Core/Banshee.Widgets/Banshee.Widgets/Tile.cs	Thu Jun 19 01:20:54 2008
@@ -41,6 +41,11 @@
         
         private string primary_text;
         private string secondary_text;
+        
+        public Tile (string primaryText) : base ()
+        {
+            PrimaryText = primaryText;
+        }
     
         public Tile()
         {

Modified: trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs	Thu Jun 19 01:20:54 2008
@@ -152,26 +152,6 @@
             }
         }
 
-        public class TitledList : VBox
-        {
-            private Label title;
-
-            public TitledList (string title_str) : base ()
-            {
-                title = new Label ();
-                title.Xalign = 0;
-                title.Ellipsize = Pango.EllipsizeMode.End;
-                title.Markup = String.Format ("<b>{0}</b>", GLib.Markup.EscapeText (title_str));
-
-                PackStart (title, false, false, 0);
-                title.Show ();
-
-                StyleSet += delegate {
-                    title.ModifyBg (StateType.Normal, Style.Base (StateType.Normal));
-                    title.ModifyFg (StateType.Normal, Style.Text (StateType.Normal));
-                };
-            }
-        }
 
         public class NumberedTileView : TileView
         {
@@ -286,4 +266,37 @@
             }
         }
     }
+
+    public class TitledList : VBox
+    {
+        private Label title;
+
+        public string Title {
+            get { return title.Text; }
+            set {
+                title.Markup = String.Format ("<b>{0}</b>", GLib.Markup.EscapeText (value));
+            }
+        }
+
+        public int TitleWidthChars {
+            get { return title.WidthChars; }
+            set { title.WidthChars = value; }
+        }
+
+        public TitledList (string title_str) : base (false, 3)
+        {
+            title = new Label ();
+            title.Xalign = 0;
+            title.Ellipsize = Pango.EllipsizeMode.End;
+            Title = title_str;
+
+            PackStart (title, false, false, 0);
+            title.Show ();
+
+            StyleSet += delegate {
+                title.ModifyBg (StateType.Normal, Style.Base (StateType.Normal));
+                title.ModifyFg (StateType.Normal, Style.Text (StateType.Normal));
+            };
+        }
+    }
 }

Added: trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationActions.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationActions.cs	Thu Jun 19 01:20:54 2008
@@ -0,0 +1,92 @@
+//
+// RecommendationActions.cs
+//
+// Authors:
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2007-2008 Novell, Inc.
+//
+// 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 Gtk;
+
+using Mono.Unix;
+
+using Lastfm;
+using Lastfm.Gui;
+using SortType = Hyena.Data.SortType;
+
+using Banshee.Base;
+using Banshee.Sources;
+using Banshee.Widgets;
+using Banshee.MediaEngine;
+using Banshee.Database;
+using Banshee.Configuration;
+using Banshee.ServiceStack;
+using Banshee.Gui;
+using Banshee.Collection;
+using Banshee.PlaybackController;
+
+using Browser = Banshee.Web.Browser;
+
+namespace Banshee.Lastfm.Recommendations
+{
+    public class RecommendationActions : BansheeActionGroup
+    {
+        private uint actions_id;
+        private RecommendationService service;
+
+        public RecommendationActions (RecommendationService service) : base (ServiceManager.Get<InterfaceActionService> (), "LastfmRecommendations")
+        {
+            this.service = service;
+
+            Add (new ToggleActionEntry [] {
+                new ToggleActionEntry (
+                    "ShowRecommendationAction", null,
+                    Catalog.GetString("Show Recommendations"), "<control>R",
+                    Catalog.GetString("Show Recommendations"), OnToggleShow, true
+                )
+            });
+
+            actions_id = Actions.UIManager.AddUiFromResource ("RecommendationMenu.xml");
+            Actions.AddActionGroup (this);
+        }
+
+        public override void Dispose ()
+        {
+            Actions.UIManager.RemoveUi (actions_id);
+            Actions.RemoveActionGroup (this);
+            base.Dispose ();
+        }
+
+#region Action Handlers 
+
+        private void OnToggleShow(object o, EventArgs args)
+        {
+            service.RecommendationsShown = (o as ToggleAction).Active;
+        }
+
+#endregion
+
+    }
+}

Added: trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationPane.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationPane.cs	Thu Jun 19 01:20:54 2008
@@ -0,0 +1,317 @@
+//
+// RecommendationPane.cs
+//
+// Authors:
+//   Fredrik Hedberg
+//   Aaron Bockover <aaron abock org>
+//   Lukas Lipka
+//   Gabriel Burt <gburt novell com>
+//
+// Copyright (C) 2005-2008 Novell, Inc.
+//
+// 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.IO;
+using System.Net;
+using System.Text;
+using System.Security.Cryptography;
+
+using Gtk;
+using Mono.Unix;
+
+using Hyena;
+using Hyena.Gui;
+using Hyena.Widgets;
+
+using Lastfm;
+using Lastfm.Data;
+using Lastfm.Gui;
+
+using Banshee.MediaEngine;
+using Banshee.Base;
+using Banshee.Configuration;
+using Banshee.ServiceStack;
+using Banshee.Gui;
+using Banshee.Networking;
+
+using Banshee.Collection;
+using Banshee.Widgets;
+
+using Browser = Lastfm.Browser;
+
+using Banshee.Lastfm.Radio;
+
+namespace Banshee.Lastfm.Recommendations
+{
+    public class RecommendationPane : Hyena.Widgets.RoundedFrame
+    {
+        private HBox main_box;
+        private MessagePane no_artists_pane;
+        private TitledList artist_box;
+        private TitledList album_box;
+        private TitledList track_box;
+        
+        private Hyena.Widgets.ScrolledWindow similar_artists_view_sw;
+        
+        private TileView similar_artists_view;
+        private VBox album_list;
+        private VBox track_list;
+        
+        private object update_sync = new object ();
+
+        private static string album_title_format = Catalog.GetString ("Top Albums by {0}");
+        private static string track_title_format = Catalog.GetString ("Top Tracks by {0}");
+        
+        private bool ready = false;
+        private bool show_when_ready = true;
+        public bool ShowWhenReady {
+            get { return show_when_ready; }
+            set {
+                show_when_ready = value;
+                UpdateVisiblity ();
+            }
+        }
+        
+        private void UpdateVisiblity ()
+        {
+            if (ShowWhenReady && ready) {
+                ShowAll ();
+            }
+        }
+
+        private string artist;
+        public string Artist {
+            get { return artist; }
+            set {
+                if (artist == value) {
+                    UpdateVisiblity ();
+                    return;
+                }
+                
+                ready = false;
+                if (String.IsNullOrEmpty (value)) {
+                    Hide ();
+                    return;
+                }
+                
+                artist = value;
+                HideWithTimeout ();
+                System.Threading.ThreadPool.QueueUserWorkItem (delegate {
+                    lock (update_sync) {
+                        UpdateForArtist (artist);
+                    }
+                });
+            }
+        }
+        
+        public void HideWithTimeout ()
+        {
+            GLib.Timeout.Add (200, OnHideTimeout);
+        }
+        
+        private bool OnHideTimeout ()
+        {
+            if (!ShowWhenReady || !ready) {
+                Hide ();
+            }
+            return false;
+        }
+
+        public RecommendationPane () : base ()
+        {
+            //ShadowType = ShadowType.In;
+
+            main_box = new HBox ();
+            main_box.BorderWidth = 5;
+
+            artist_box = new TitledList (String.Format ("Recommended Artists"));
+            similar_artists_view = new TileView (2);
+            similar_artists_view_sw = new Hyena.Widgets.ScrolledWindow ();
+            similar_artists_view_sw.SetPolicy (PolicyType.Automatic, PolicyType.Automatic);
+            similar_artists_view_sw.Add (similar_artists_view);
+            similar_artists_view_sw.ShowAll ();
+            artist_box.PackEnd (similar_artists_view_sw, true, true, 0);
+
+            album_box  = new TitledList (null);
+            album_box.TitleWidthChars = 25;
+            album_list = new VBox ();
+            album_box.PackStart (album_list, true, true, 0);
+
+            track_box  = new TitledList (null);
+            track_box.TitleWidthChars = 25;
+            track_list = new VBox ();
+            track_box.PackStart (track_list, true, true, 0);
+            
+            no_artists_pane = new MessagePane ();
+            no_artists_pane.NoShowAll = true;
+            no_artists_pane.Visible = false;
+            string no_results_message;
+
+            if (!ApplicationContext.Debugging) {
+                no_artists_pane.HeaderIcon = IconThemeUtils.LoadIcon (48, "face-sad", Stock.DialogError);
+                no_results_message = Catalog.GetString("No similar artists found");
+            } else {
+                no_artists_pane.HeaderIcon = Gdk.Pixbuf.LoadFromResource ("no-results.png");
+                no_results_message = "No one likes your music, fool!";
+            }
+
+            no_artists_pane.HeaderMarkup = String.Format ("<big><b>{0}</b></big>", GLib.Markup.EscapeText (no_results_message));
+            artist_box.PackEnd (no_artists_pane, true, true, 0);
+
+            main_box.PackStart (artist_box, true, true, 5);
+            main_box.PackStart (new VSeparator (), false, false, 0);
+            main_box.PackStart (album_box, false, false, 5);
+            main_box.PackStart (new VSeparator (), false, false, 0);
+            main_box.PackStart (track_box, false, false, 5);
+            
+            EventBox event_box = new EventBox ();
+            
+            /*no_artists_pane.StyleSet += delegate {
+                event_box.ModifyBg (StateType.Normal, no_artists_pane.Style.Base (StateType.Normal));
+                similar_artists_view.ModifyBg (StateType.Normal, no_artists_pane.Style.Base (StateType.Normal));
+            };*/
+            
+            /*StyleSet += delegate {
+                ModifyBg (StateType.Normal, Style.Base (StateType.Normal));
+                ModifyFg (StateType.Normal, Style.Text (StateType.Normal));
+            };*/
+
+            event_box.Add (main_box);
+            no_artists_pane.Hide ();
+            Add (event_box);
+        }
+        
+        private void UpdateForArtist (string artist_name)
+        {
+            try {
+                LastfmArtistData artist = new LastfmArtistData (artist_name);
+                
+                // Make sure all the album art is downloaded
+                foreach (SimilarArtist similar in artist.SimilarArtists) {
+                    DataCore.DownloadContent (similar.SmallImageUrl);
+                }
+                
+                UpdateForArtist (artist_name, artist.SimilarArtists, artist.TopAlbums, artist.TopTracks);
+            } catch (Exception e) {
+                Log.Exception (e);
+            }
+        }
+        
+        private void UpdateForArtist (string artist_name, LastfmData<SimilarArtist> similar_artists, LastfmData<ArtistTopAlbum> top_albums, LastfmData<ArtistTopTrack> top_tracks)
+        {
+            Banshee.Base.ThreadAssist.ProxyToMain (delegate {
+                album_box.Title = String.Format (album_title_format, artist);
+                track_box.Title = String.Format (track_title_format, artist);
+                
+                similar_artists_view.ClearWidgets ();
+                ClearBox (album_list);
+                ClearBox (track_list);
+                
+                // Similar Artists                
+                for (int i = 0; i < Math.Min (20, similar_artists.Count); i++) {
+                    SimilarArtist similar_artist = similar_artists[i];
+                    Tile tile = new Tile (similar_artist.Name);
+                    try {
+                        tile.Pixbuf = new Gdk.Pixbuf (DataCore.GetCachedPathFromUrl (similar_artist.SmallImageUrl));
+                    } catch {
+                        tile.Pixbuf = IconThemeUtils.LoadIcon ("generic-artist", 48);
+                    }
+                    
+                    try {
+                        tile.SecondaryText = String.Format (Catalog.GetString ("{0}% Similarity"), similar_artist.MatchAsInt);
+                    } catch {
+                        tile.SecondaryText = Catalog.GetString ("Unknown Similarity");
+                    }
+                    
+                    tile.Clicked += delegate { Banshee.Web.Browser.Open (similar_artist.Url); };
+                    similar_artists_view.AddWidget (tile);
+                }
+                
+                if (similar_artists.Count > 0) {
+                    no_artists_pane.Hide ();
+                    similar_artists_view_sw.ShowAll ();
+                } else {
+                    similar_artists_view_sw.Hide ();
+                    no_artists_pane.ShowAll ();
+                }
+                
+                for (int i = 0; i < Math.Min (5, top_albums.Count); i++) {
+                    ArtistTopAlbum album = top_albums[i];
+                    Button album_button = new Button ();
+                    album_button.Relief = ReliefStyle.None;
+        
+                    Label label = new Label ();
+                    label.Ellipsize = Pango.EllipsizeMode.End;
+                    label.Xalign = 0;
+                    label.Markup = String.Format ("{0}. {1}", i+1, GLib.Markup.EscapeText (album.Name));
+                    album_button.Add (label);
+        
+                    album_button.Clicked += delegate {
+                        Banshee.Web.Browser.Open (album.Name);
+                    };
+                    album_list.PackStart (album_button, false, true, 0);
+                }
+                album_box.ShowAll ();
+                
+                for (int i = 0; i < Math.Min (5, top_tracks.Count); i++) {
+                    ArtistTopTrack track = top_tracks[i];
+                    Button track_button = new Button ();
+                    track_button.Relief = ReliefStyle.None;
+        
+                    HBox box = new HBox ();
+        
+                    Label label = new Label ();
+                    label.Ellipsize = Pango.EllipsizeMode.End;
+                    label.Xalign = 0;
+                    label.Markup = String.Format ("{0}. {1}", i+1, GLib.Markup.EscapeText (track.Name));
+        
+                    /*if(node.SelectSingleNode("track_id") != null) {
+                        box.PackEnd(new Image(now_playing_arrow), false, false, 0);
+                        track_button.Clicked += delegate {
+                            //PlayerEngineCore.OpenPlay(Globals.Library.GetTrack(
+                                //Convert.ToInt32(node.SelectSingleNode("track_id").InnerText)));
+                        };
+                    } else {*/
+                        track_button.Clicked += delegate {
+                            Banshee.Web.Browser.Open (track.Url);
+                        };
+                    //}
+        
+                    box.PackStart (label, true, true, 0);
+                    track_button.Add (box);
+                    track_list.PackStart (track_button, false, true, 0);
+                }
+                track_box.ShowAll ();
+                
+                ready = true;
+                UpdateVisiblity ();
+            });
+        }
+        
+        private static void ClearBox (Box box)
+        {
+            while (box.Children.Length > 0) {
+                box.Remove (box.Children[0]);
+            }
+        }
+    }
+}

Added: trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationService.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Recommendations/RecommendationService.cs	Thu Jun 19 01:20:54 2008
@@ -0,0 +1,160 @@
+//
+// RecommendationService.cs
+//
+// Authors:
+//   Aaron Bockover <aaron abock org>
+//   Fredrik Hedberg
+//   Gabriel Burt <gburt novell com>
+//   Lukas Lipka
+//
+// Copyright (C) 2005-2008 Novell, Inc.
+//
+// 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.IO;
+using System.Net;
+using System.Text;
+using System.Security.Cryptography;
+using Gtk;
+using Mono.Unix;
+
+using Hyena;
+
+using Lastfm;
+using Lastfm.Gui;
+
+using Banshee.MediaEngine;
+using Banshee.Base;
+using Banshee.Configuration;
+using Banshee.ServiceStack;
+using Banshee.Gui;
+using Banshee.Networking;
+
+using Banshee.Collection;
+
+using Browser = Lastfm.Browser;
+
+namespace Banshee.Lastfm.Recommendations
+{
+    public class RecommendationService : IExtensionService
+    {
+        private RecommendationPane pane;
+        private RecommendationActions actions;
+
+        void IExtensionService.Initialize ()
+        {
+            pane = new RecommendationPane ();
+
+            BaseClientWindow nereid = ServiceManager.Get<IService> ("NereidPlayerInterface") as Banshee.Gui.BaseClientWindow;
+            if (nereid != null) {
+                nereid.ViewContainer.PackEnd (pane, false, false, 0);
+            }
+
+            actions = new RecommendationActions (this);
+
+            ServiceManager.PlaybackController.SourceChanged += OnSourceChanged;
+            ServiceManager.SourceManager.ActiveSourceChanged += OnActiveSourceChanged;
+            ServiceManager.PlayerEngine.ConnectEvent (OnPlayerEvent, PlayerEvent.StartOfStream | PlayerEvent.EndOfStream);
+        }
+
+        public void Dispose ()
+        {
+            ServiceManager.PlaybackController.SourceChanged -= OnSourceChanged;
+            ServiceManager.SourceManager.ActiveSourceChanged -= OnActiveSourceChanged;
+            ServiceManager.PlayerEngine.DisconnectEvent (OnPlayerEvent);
+
+            if (pane != null) {
+                pane.Destroy ();
+                pane = null;
+            }
+
+            actions.Dispose ();
+        }
+
+        private void OnActiveSourceChanged (EventArgs args)
+        {
+            UpdateVisibility ();
+        }
+
+        private void OnSourceChanged (object sender, EventArgs args)
+        {
+            UpdateVisibility ();
+        }
+
+        private void OnPlayerEvent (PlayerEventArgs args)
+        {
+            TrackInfo track = ServiceManager.PlayerEngine.CurrentTrack;
+            if (track != null) {
+                ShowRecommendations (track.ArtistName);
+            }
+        }
+
+        private void UpdateVisibility ()
+        {
+            bool source_is_playback_source = (ServiceManager.SourceManager.ActiveSource == ServiceManager.PlaybackController.Source);
+            pane.ShowWhenReady = ShowSchema.Get () && source_is_playback_source;
+            if (!source_is_playback_source) {
+                pane.HideWithTimeout ();
+            } else if (!pane.ShowWhenReady) {
+                pane.Hide ();
+            }
+        }
+
+        private void ShowRecommendations (string artist)
+        {
+            lock (this) {
+                if (pane.Visible && pane.Artist == artist) {
+                    return;
+                }
+
+                if (!String.IsNullOrEmpty (artist)) {
+                    pane.Artist = artist;
+                }
+
+                UpdateVisibility ();
+            }
+        }
+
+        public bool RecommendationsShown {
+            set {
+                ShowSchema.Set (value);
+
+                TrackInfo track = ServiceManager.PlayerEngine.CurrentTrack;
+                if (track != null) {
+                    pane.Artist = track.ArtistName;
+                }
+                UpdateVisibility ();
+            }
+        }
+
+        string IService.ServiceName {
+            get { return "LastfmRecommendationService"; }
+        }
+
+        public static readonly SchemaEntry<bool> ShowSchema = new SchemaEntry<bool>(
+            "plugins.recommendation", "show",
+            true,
+            "Show recommendations",
+            "Show recommendations for the currently playing artist"
+        );
+    }
+}

Modified: trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.addin.xml
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.addin.xml	(original)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.addin.xml	Thu Jun 19 01:20:54 2008
@@ -7,7 +7,7 @@
     name="Last.fm Radio and Scrobbling"
     category="User Interface"
     description="Provides tightly integrated Last.fm streaming radio and community features."
-    author="Gabriel Burt, Alexander Hixon"
+    author="Gabriel Burt, Alexander Hixon, Chris Toshok, Fredrik Hedberg, Aaron Bockover, Lukas Lipka"
     url="http://banshee-project.org/";
     defaultEnabled="true">
 
@@ -23,5 +23,9 @@
   <Extension path="/Banshee/ServiceManager/Service">
     <Service class="Banshee.Lastfm.Audioscrobbler.AudioscrobblerService"/>
   </Extension>
+
+  <Extension path="/Banshee/ServiceManager/Service">
+    <Service class="Banshee.Lastfm.Recommendations.RecommendationService"/>
+  </Extension>
   
 </Addin>

Modified: trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.mdp
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.mdp	(original)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.mdp	Thu Jun 19 01:20:54 2008
@@ -24,6 +24,10 @@
     <File name="Resources/AudioscrobblerMenu.xml" subtype="Code" buildaction="EmbedAsResource" />
     <File name="Banshee.Lastfm.Radio/LastfmColumnController.cs" subtype="Code" buildaction="Compile" />
     <File name="Resources/LastfmTrackActions.xml" subtype="Code" buildaction="EmbedAsResource" />
+    <File name="Banshee.Lastfm.Recommendations/RecommendationActions.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Lastfm.Recommendations/RecommendationPane.cs" subtype="Code" buildaction="Compile" />
+    <File name="Banshee.Lastfm.Recommendations/RecommendationService.cs" subtype="Code" buildaction="Compile" />
+    <File name="Resources/RecommendationMenu.xml" subtype="Code" buildaction="EmbedAsResource" />
   </Contents>
   <References>
     <ProjectReference type="Gac" localcopy="True" refto="gtk-sharp, Version=2.8.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
@@ -39,6 +43,8 @@
     <ProjectReference type="Project" localcopy="True" refto="Banshee.Core" />
     <ProjectReference type="Project" localcopy="True" refto="Banshee.Services" />
     <ProjectReference type="Project" localcopy="True" refto="Banshee.ThickClient" />
+    <ProjectReference type="Project" localcopy="True" refto="Hyena" />
+    <ProjectReference type="Project" localcopy="True" refto="Hyena.Gui" />
   </References>
   <MonoDevelop.Autotools.MakefileInfo IntegrationEnabled="True" RelativeMakefileName="Makefile.am">
     <BuildFilesVar Sync="True" Name="SOURCES" />

Modified: trunk/banshee/src/Extensions/Banshee.Lastfm/Makefile.am
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Lastfm/Makefile.am	(original)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Makefile.am	Thu Jun 19 01:20:54 2008
@@ -13,7 +13,11 @@
 	Banshee.Lastfm.Radio/LastfmTrackInfo.cs \
 	Banshee.Lastfm.Radio/StationEditor.cs \
 	Banshee.Lastfm.Radio/StationSource.cs \
-	Banshee.Lastfm.Radio/StationType.cs
+	Banshee.Lastfm.Radio/StationType.cs \
+	Banshee.Lastfm.Recommendations/RecommendationActions.cs \
+	Banshee.Lastfm.Recommendations/RecommendationPane.cs \
+	Banshee.Lastfm.Recommendations/RecommendationService.cs
+
 
 RESOURCES =  \
 	Banshee.Lastfm.addin.xml \
@@ -21,7 +25,9 @@
 	Resources/AudioscrobblerMenu.xml \
 	Resources/GlobalUI.xml \
 	Resources/lastfm.glade \
-	Resources/LastfmTrackActions.xml
+	Resources/LastfmTrackActions.xml \
+	Resources/RecommendationMenu.xml \
+	Resources/no-results.png
 
 include $(top_srcdir)/build/build.mk
 

Added: trunk/banshee/src/Extensions/Banshee.Lastfm/Resources/RecommendationMenu.xml
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Extensions/Banshee.Lastfm/Resources/RecommendationMenu.xml	Thu Jun 19 01:20:54 2008
@@ -0,0 +1,9 @@
+<ui>
+    <menubar name="MainMenu">
+        <menu name="ViewMenu" action="ViewMenuAction">
+            <placeholder name="ViewMenuAdditions">
+                 <menuitem name="ShowRecommendation" action="ShowRecommendationAction" />
+            </placeholder> 
+        </menu>
+    </menubar>
+</ui>

Added: trunk/banshee/src/Extensions/Banshee.Lastfm/Resources/no-results.png
==============================================================================
Binary file. No diff available.

Added: trunk/banshee/src/Extensions/Banshee.Lastfm/ThemeIcons/48x48/generic-artist.png
==============================================================================
Binary file. No diff available.

Modified: trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataCore.cs
==============================================================================
--- trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataCore.cs	(original)
+++ trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataCore.cs	Thu Jun 19 01:20:54 2008
@@ -2,7 +2,10 @@
 // DataCore.cs
 //
 // Authors:
-//   Gabriel Burt <gburt novell com>
+//   Gabriel Burt
+//   Fredrik Hedberg
+//   Aaron Bockover
+//   Lukas Lipka
 //
 // Copyright (C) 2008 Novell, Inc.
 //
@@ -28,6 +31,12 @@
 
 using System;
 using System.IO;
+using System.Net;
+using System.Web;
+using System.Xml;
+using System.Collections;
+using System.Collections.Generic;
+using ICSharpCode.SharpZipLib.GZip;
 
 using Hyena;
 
@@ -55,6 +64,85 @@
                 SetupCache();
             }
         }
+        
+        public static string DownloadContent (string data_url)
+        {
+            return DownloadContent (data_url, CacheDuration.Infinite);
+        }
+        
+        public static string DownloadContent (string data_url, CacheDuration cache_duration)
+        {
+            return DownloadContent (data_url, GetCachedPathFromUrl (data_url), cache_duration);
+        }
+
+        internal static string DownloadContent (string data_url, string cache_file, CacheDuration cache_duration)
+        {
+            data_url = FixLastfmUrl (data_url);
+            // See if we have a valid cached copy
+            if (cache_duration != CacheDuration.None) {
+                if (File.Exists (cache_file)) {
+                    DateTime last_updated_time = File.GetLastWriteTime (cache_file);
+                    if (cache_duration == CacheDuration.Infinite || DateTime.Now - last_updated_time < DataCore.NormalCacheTime) {
+                        return cache_file;
+                    }
+                }
+            }
+
+            HttpWebRequest request = (HttpWebRequest) WebRequest.Create (data_url);
+            request.UserAgent = DataCore.UserAgent;
+            request.KeepAlive = false;
+            
+            using (HttpWebResponse response = (HttpWebResponse) request.GetResponse ()) {
+                using (Stream stream = GetResponseStream (response)) {
+                    using (FileStream file_stream = File.Open (cache_file, FileMode.Create)) {
+                        using (BufferedStream buffered_stream = new BufferedStream (file_stream)) {
+                            byte [] buffer = new byte[8192];
+                            int read;
+                            
+                            while (true) {
+                                read = stream.Read (buffer, 0, buffer.Length);
+                                if (read <= 0) {
+                                    break;
+                                }
+                                
+                                buffered_stream.Write (buffer, 0, read);
+                            }
+                        }
+                    }
+                }
+            }
+            return cache_file;
+        }
+
+        
+        public static string GetCachedPathFromUrl (string url)
+        {
+            string hash = FixLastfmUrl (url).GetHashCode ().ToString ("X").ToLower ();
+            return Path.Combine (Path.Combine (DataCore.CachePath, hash.Substring (0, 2)), hash);
+        }
+        
+        private static Stream GetResponseStream (HttpWebResponse response) 
+        {
+            return response.ContentEncoding == "gzip"
+                ? new GZipInputStream (response.GetResponseStream ())
+                : response.GetResponseStream ();
+        }
+
+        // FIXME: This is to (try to) work around a bug in last.fm's XML
+        // Some XML nodes with URIs as content do not return a URI with a
+        // host name. It appears that in these cases the hostname is to be
+        // static3.last.fm, but it may not always be the case - this is
+        // a bug in last.fm, but we attempt to work around it here
+        // http://bugzilla.gnome.org/show_bug.cgi?id=408068
+
+        internal static string FixLastfmUrl (string url)
+        {
+            if (url.StartsWith ("http:///storable/";)) {
+                url = url.Insert (7, "static3.last.fm");
+            }
+
+            return url;
+        }
 
         private static void SetupCache()
         {

Modified: trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataEntry.cs
==============================================================================
--- trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataEntry.cs	(original)
+++ trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataEntry.cs	Thu Jun 19 01:20:54 2008
@@ -191,6 +191,8 @@
     // Artist entries
     public class SimilarArtist : NamedEntry
     {
+        public string ImageUrl          { get { return Get<string>   ("image"); } }
+        public string SmallImageUrl     { get { return Get<string>   ("image_small"); } }
         public double Match             { get { return Get<double>   ("match"); } }
         public int MatchAsInt           { get { return (int) Math.Round (Match); } }
     }

Modified: trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataEntryCollection.cs
==============================================================================
--- trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataEntryCollection.cs	(original)
+++ trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/DataEntryCollection.cs	Thu Jun 19 01:20:54 2008
@@ -49,7 +49,7 @@
         public DataEntryCollection (XmlNodeList nodes)
         {
             this.nodes = nodes;
-            count = nodes.Count;
+            count = nodes == null ? 0 : nodes.Count;
         }
 
         public int Count {

Modified: trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/LastfmData.cs
==============================================================================
--- trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/LastfmData.cs	(original)
+++ trunk/banshee/src/Libraries/Lastfm/Lastfm.Data/LastfmData.cs	Thu Jun 19 01:20:54 2008
@@ -2,7 +2,10 @@
 // LastfmData.cs
 //
 // Authors:
-//   Gabriel Burt <gburt novell com>
+//   Gabriel Burt
+//   Fredrik Hedberg
+//   Aaron Bockover
+//   Lukas Lipka
 //
 // Copyright (C) 2008 Novell, Inc.
 //
@@ -48,7 +51,8 @@
 
     public class LastfmData<T> : IEnumerable<T> where T : DataEntry
     {
-        protected DataEntryCollection<T> collection;
+        private static DataEntryCollection<T> empty_collection = new DataEntryCollection<T> ((XmlNodeList)null);
+        protected DataEntryCollection<T> collection = empty_collection;
         protected XmlDocument doc;
         protected string data_url;
         protected string cache_file;
@@ -71,8 +75,8 @@
         {
             DataCore.Initialize ();
 
-            this.data_url = HostInjectionHack (String.Format ("http://ws.audioscrobbler.com/1.0/{0}";, dataUrlFragment));
-            this.cache_file = GetCachedPathFromUrl (data_url);
+            this.data_url = DataCore.FixLastfmUrl (String.Format ("http://ws.audioscrobbler.com/1.0/{0}";, dataUrlFragment));
+            this.cache_file = DataCore.GetCachedPathFromUrl (data_url);
             this.cache_duration = cacheDuration;
             this.xpath = xpath;
 
@@ -90,7 +94,7 @@
         private void GetData ()
         {
             // Download the content if necessary
-            DownloadContent ();
+            DataCore.DownloadContent (data_url, cache_file, cache_duration);
 
             // Load the XML from the new or cached local file
             doc = new XmlDocument ();
@@ -133,73 +137,6 @@
 
 #region Private methods
 
-        private void DownloadContent ()
-        {
-            // See if we have a valid cached copy
-            if (cache_duration != CacheDuration.None) {
-                if (File.Exists (cache_file)) {
-                    DateTime last_updated_time = File.GetLastWriteTime (cache_file);
-                    if (cache_duration == CacheDuration.Infinite || DateTime.Now - last_updated_time < DataCore.NormalCacheTime) {
-                        return;
-                    }
-                }
-            }
-
-            HttpWebRequest request = (HttpWebRequest) WebRequest.Create (data_url);
-            request.UserAgent = DataCore.UserAgent;
-            request.KeepAlive = false;
-            
-            using (HttpWebResponse response = (HttpWebResponse) request.GetResponse ()) {
-                using (Stream stream = GetResponseStream (response)) {
-                    using (FileStream file_stream = File.Open (cache_file, FileMode.Create)) {
-                        using (BufferedStream buffered_stream = new BufferedStream (file_stream)) {
-                            byte [] buffer = new byte[8192];
-                            int read;
-                            
-                            while (true) {
-                                read = stream.Read (buffer, 0, buffer.Length);
-                                if (read <= 0) {
-                                    break;
-                                }
-                                
-                                buffered_stream.Write (buffer, 0, read);
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        private static Stream GetResponseStream (HttpWebResponse response) 
-        {
-            return response.ContentEncoding == "gzip"
-                ? new GZipInputStream (response.GetResponseStream ())
-                : response.GetResponseStream ();
-        }
-
-    
-        private static string GetCachedPathFromUrl (string url)
-        {
-            string hash = url.GetHashCode ().ToString ("X").ToLower ();
-            return Path.Combine (Path.Combine (DataCore.CachePath, hash.Substring (0, 2)), hash);
-        }
-
-        // FIXME: This is to (try to) work around a bug in last.fm's XML
-        // Some XML nodes with URIs as content do not return a URI with a
-        // host name. It appears that in these cases the hostname is to be
-        // static3.last.fm, but it may not always be the case - this is
-        // a bug in last.fm, but we attempt to work around it here
-        // http://bugzilla.gnome.org/show_bug.cgi?id=408068
-
-        private static string HostInjectionHack (string url)
-        {
-            if (url.StartsWith ("http:///storable/";)) {
-                url = url.Insert (7, "static3.last.fm");
-            }
-
-            return url;
-        }
-
 #endregion
 
     }

Modified: trunk/banshee/src/Libraries/Lastfm/Lastfm.mdp
==============================================================================
--- trunk/banshee/src/Libraries/Lastfm/Lastfm.mdp	(original)
+++ trunk/banshee/src/Libraries/Lastfm/Lastfm.mdp	Thu Jun 19 01:20:54 2008
@@ -29,5 +29,9 @@
     <ProjectReference type="Project" localcopy="True" refto="Mono.Media" />
     <ProjectReference type="Gac" localcopy="True" refto="ICSharpCode.SharpZipLib, Version=2.84.0.0, Culture=neutral, PublicKeyToken=1b03e6acf1164f73" />
     <ProjectReference type="Gac" localcopy="True" refto="System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
+    <ProjectReference type="Gac" localcopy="True" refto="glib-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <ProjectReference type="Gac" localcopy="True" refto="gdk-sharp, Version=2.12.0.0, Culture=neutral, PublicKeyToken=35e10195dab3c99f" />
+    <ProjectReference type="Gac" localcopy="True" refto="Hyena, Version=1.0.1.17734, Culture=neutral, PublicKeyToken=null" />
+    <ProjectReference type="Gac" localcopy="True" refto="Hyena.Gui, Version=1.0.1.17735, Culture=neutral, PublicKeyToken=null" />
   </References>
 </Project>
\ No newline at end of file



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]