banshee r4660 - in trunk/banshee: . src/Clients/Muinshee/Muinshee src/Core/Banshee.ThickClient/Banshee.Collection.Gui src/Core/Banshee.ThickClient/Banshee.Gui.TrackEditor src/Core/Banshee.ThickClient/Banshee.Gui.Widgets src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage src/Dap/Banshee.Dap.Mtp/Banshee.Dap.Mtp src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui src/Libraries/Hyena src/Libraries/Hyena.Gui/Hyena.Gui src/Libraries/Hyena/Hyena.Collections



Author: abock
Date: Mon Oct  6 21:04:57 2008
New Revision: 4660
URL: http://svn.gnome.org/viewvc/banshee?rev=4660&view=rev

Log:
2008-10-06  Aaron Bockover  <abock gnome org>

    This commit finishes the port to using Cairo surface in almost all places
    where we do rendering, and adds a surface cache system for improving
    performance in some places.

    * src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellAlbum.cs:
    * src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPodcast.cs:
    Use the new surface methods from artwork manager to directly render
    surfaces as stored in the LRU cache

    * src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkManager.cs:
    Implement a surface caching system that should help improve the rendering
    speed of the album browser. Tuned to never grow beyond 1MB of image data
    per cache.

    * src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkRenderer.cs:
    Accept surfaces as input instead of pixbufs. Yay.

    * src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TrackInfoDisplay.cs:
    Port the beast to use cairo image surfaces directly intead of pixbufs,
    get rid of the surface cache hack

    * src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/LargeTrackInfoDisplay.cs:
    Updated to reflect the surface changes in base class, cache the scene
    surfaces to avoid re-rendering on each pass (was cached before, but using
    a different mechanism)

    * src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/ClassicTrackInfoDisplay.cs:
    * src/Clients/Muinshee/Muinshee/MuinsheeTrackInfoDisplay.cs:
    Updated to reflect surface changes in the base class

    * src/Libraries/Hyena/Hyena.Collections/LruCache.cs: Implement a somewhat
    lame generic LRU cache collection

    * src/Libraries/Hyena.Gui/Hyena.Gui/PixbufImageSurface.cs: Add a new
    ctor that disposes the input pixbuf when done with it, nice for
    inlining pixbuf->surface without temporary variables or leaking

    * src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/MassStorageSource.cs:
    * src/Dap/Banshee.Dap.Mtp/Banshee.Dap.Mtp/MtpSource.cs:
    * src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea/NotificationAreaService.cs:
    * src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs:
    * src/Core/Banshee.ThickClient/Banshee.Gui.TrackEditor/TrackEditorDialog.cs:
    Updated to reflect API rename in the artwork manager



Added:
   trunk/banshee/src/Libraries/Hyena/Hyena.Collections/LruCache.cs
Modified:
   trunk/banshee/ChangeLog
   trunk/banshee/src/Clients/Muinshee/Muinshee/MuinsheeTrackInfoDisplay.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkManager.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkRenderer.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellAlbum.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.TrackEditor/TrackEditorDialog.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/ClassicTrackInfoDisplay.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/LargeTrackInfoDisplay.cs
   trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TrackInfoDisplay.cs
   trunk/banshee/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/MassStorageSource.cs
   trunk/banshee/src/Dap/Banshee.Dap.Mtp/Banshee.Dap.Mtp/MtpSource.cs
   trunk/banshee/src/Extensions/Banshee.Lastfm/Banshee.Lastfm.Radio/LastfmSourceContents.cs
   trunk/banshee/src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea/NotificationAreaService.cs
   trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPodcast.cs
   trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Gui/PixbufImageSurface.cs
   trunk/banshee/src/Libraries/Hyena/Hyena.csproj
   trunk/banshee/src/Libraries/Hyena/Makefile.am

Modified: trunk/banshee/src/Clients/Muinshee/Muinshee/MuinsheeTrackInfoDisplay.cs
==============================================================================
--- trunk/banshee/src/Clients/Muinshee/Muinshee/MuinsheeTrackInfoDisplay.cs	(original)
+++ trunk/banshee/src/Clients/Muinshee/Muinshee/MuinsheeTrackInfoDisplay.cs	Mon Oct  6 21:04:57 2008
@@ -28,13 +28,15 @@
 
 using System;
 
+using Cairo;
+using Hyena.Gui;
 using Banshee.Collection.Gui;
 
 namespace Muinshee
 {    
     public class MuinsheeTrackInfoDisplay : Banshee.Gui.Widgets.ClassicTrackInfoDisplay
     {
-        private Gdk.Pixbuf idle_album;
+        private ImageSurface idle_album;
 
         public MuinsheeTrackInfoDisplay () : base ()
         {
@@ -46,7 +48,9 @@
 
         protected override void RenderIdle (Cairo.Context cr)
         {
-            idle_album = idle_album ?? Banshee.Gui.IconThemeUtils.LoadIcon (ArtworkSizeRequest, "media-optical");
+            idle_album = idle_album ?? new PixbufImageSurface (Banshee.Gui.IconThemeUtils.LoadIcon (
+                ArtworkSizeRequest, "media-optical"), true);
+            
             ArtworkRenderer.RenderThumbnail (cr, idle_album, false, Allocation.X, Allocation.Y, 
                 ArtworkSizeRequest, ArtworkSizeRequest, 
                 false, 0, true, BackgroundColor);

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkManager.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkManager.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkManager.cs	Mon Oct  6 21:04:57 2008
@@ -36,6 +36,9 @@
 using Gdk;
 
 using Hyena;
+using Hyena.Gui;
+using Hyena.Collections;
+
 using Banshee.Base;
 using Banshee.ServiceStack;
 
@@ -43,6 +46,22 @@
 {
     public class ArtworkManager : IService
     {
+        private Dictionary<int, SurfaceCache> scale_caches  = new Dictionary<int, SurfaceCache> ();
+            
+        private class SurfaceCache : LruCache<string, Cairo.ImageSurface>
+        {
+            public SurfaceCache (int max_items) : base (max_items)
+            {
+            }
+        
+            protected override void ExpireItem (Cairo.ImageSurface item)
+            {
+                if (item != null) {
+                    item.Destroy ();
+                }
+            }
+        }
+    
         public ArtworkManager ()
         {
             try {
@@ -77,31 +96,68 @@
             Log.Debug (String.Format ("Migrated {0} album art images.", artwork_count));
         }
         
-        public Cairo.Surface Lookup (Cairo.Context cr, string id)
+        public Cairo.ImageSurface LookupSurface (string id)
         {
-            return LookupScale (cr, id, 0);
+            return LookupScaleSurface (id, 0);
         }
         
-        public Cairo.Surface LookupScale (Cairo.Context cr, string id, int size)
+        public Cairo.ImageSurface LookupScaleSurface (string id, int size)
         {
-            Pixbuf pixbuf = LookupScale (id, size);
+            return LookupScaleSurface (id, size, false);
+        }
+        
+        public Cairo.ImageSurface LookupScaleSurface (string id, int size, bool useCache)
+        {
+            SurfaceCache cache = null;
+            Cairo.ImageSurface surface = null;
+            
+            if (id == null) {
+                return null;
+            }
+            
+            if (useCache && scale_caches.TryGetValue (size, out cache) && cache.TryGetValue (id, out surface)) {
+                return surface;
+            }
+        
+            Pixbuf pixbuf = LookupScalePixbuf (id, size);
             if (pixbuf == null) {
                 return null;
             }
             
             try {
-                return Hyena.Gui.CairoExtensions.CreateSurfaceForPixbuf (cr, pixbuf);
+                surface = new PixbufImageSurface (pixbuf);
+                if (surface == null) {
+                    return null;
+                }
+                
+                if (!useCache) {
+                    return surface;
+                }
+                
+                if (cache == null) {
+                    int bytes = 4 * size * size;
+                    int max = (1 << 20) / bytes;
+                    
+                    Log.DebugFormat ("Creating new surface cache for {0} KB (max) images, capped at 1 MB ({1} items)",
+                        bytes, max);
+                        
+                    cache = new SurfaceCache (max);
+                    scale_caches.Add (size, cache);
+                }
+                
+                cache.Add (id, surface);
+                return surface;
             } finally {
-                pixbuf.Dispose ();
+                DisposePixbuf (pixbuf);
             }
         }
         
-        public Pixbuf Lookup (string id)
+        public Pixbuf LookupPixbuf (string id)
         {
-            return LookupScale (id, 0);
+            return LookupScalePixbuf (id, 0);
         }
         
-        public Pixbuf LookupScale (string id, int size)
+        public Pixbuf LookupScalePixbuf (string id, int size)
         {
             if (id == null || (size != 0 && size < 10)) {
                 return null;
@@ -147,7 +203,7 @@
                     Pixbuf scaled_pixbuf = pixbuf.ScaleSimple (size, size, Gdk.InterpType.Bilinear);
                     Directory.CreateDirectory (Path.GetDirectoryName (path));
                     scaled_pixbuf.Save (path, "jpeg");
-                    ArtworkRenderer.DisposePixbuf (pixbuf);
+                    DisposePixbuf (pixbuf);
                     return scaled_pixbuf;
                 } catch {}
             }
@@ -155,6 +211,23 @@
             return null;
         }
         
+        private static int dispose_count = 0;
+        public static void DisposePixbuf (Pixbuf pixbuf)
+        {
+            if (pixbuf != null && pixbuf.Handle != IntPtr.Zero) {
+                pixbuf.Dispose ();
+                pixbuf = null;
+                
+                // There is an issue with disposing Pixbufs where we need to explicitly 
+                // call the GC otherwise it doesn't get done in a timely way.  But if we
+                // do it every time, it slows things down a lot; so only do it every 100th.
+                if (++dispose_count % 100 == 0) {
+                    System.GC.Collect ();
+                    dispose_count = 0;
+                }
+            }
+        }
+        
         string IService.ServiceName {
             get { return "ArtworkManager"; }
         }

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkRenderer.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkRenderer.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ArtworkRenderer.cs	Mon Oct  6 21:04:57 2008
@@ -38,33 +38,33 @@
         private static Color cover_border_light_color = new Color (1.0, 1.0, 1.0, 0.5);
         private static Color cover_border_dark_color = new Color (0.0, 0.0, 0.0, 0.65);
         
-        public static void RenderThumbnail (Cairo.Context cr, Gdk.Pixbuf pixbuf, bool dispose,
+        public static void RenderThumbnail (Cairo.Context cr, ImageSurface image, bool dispose,
             double x, double y, double width, double height, bool drawBorder, double radius)
         {
-            RenderThumbnail (cr, pixbuf, dispose, x, y, width, height, 
+            RenderThumbnail (cr, image, dispose, x, y, width, height, 
                 drawBorder, radius, false, cover_border_light_color);
         }
         
-        public static void RenderThumbnail (Cairo.Context cr, Gdk.Pixbuf pixbuf, bool dispose,
+        public static void RenderThumbnail (Cairo.Context cr, ImageSurface image, bool dispose,
             double x, double y, double width, double height, bool drawBorder, double radius, 
             bool fill, Color fillColor)
         {
-            RenderThumbnail (cr, pixbuf, dispose, x, y, width, height, drawBorder, radius, 
+            RenderThumbnail (cr, image, dispose, x, y, width, height, drawBorder, radius, 
                 fill, fillColor, CairoCorners.All);
         }
         
-        public static void RenderThumbnail (Cairo.Context cr, Gdk.Pixbuf pixbuf, bool dispose,
+        public static void RenderThumbnail (Cairo.Context cr, ImageSurface image, bool dispose,
             double x, double y, double width, double height, bool drawBorder, double radius, 
             bool fill, Color fillColor, CairoCorners corners)
         {
-            if (pixbuf == null || pixbuf.Handle == IntPtr.Zero) {
+            if (image == null || image.Handle == IntPtr.Zero) {
                 return;
             }
             
             double p_x = x;
             double p_y = y;
-            p_x += pixbuf.Width < width ? (width - pixbuf.Width) / 2 : 0;
-            p_y += pixbuf.Height < height ? (height - pixbuf.Height) / 2 : 0;
+            p_x += image.Width < width ? (width - image.Width) / 2 : 0;
+            p_y += image.Height < height ? (height - image.Height) / 2 : 0;
             
             cr.Antialias = Cairo.Antialias.Default;
             
@@ -74,13 +74,13 @@
                 cr.Fill();
             }
             
-            CairoExtensions.RoundedRectangle (cr, p_x, p_y, pixbuf.Width, pixbuf.Height, radius, corners);
-            Gdk.CairoHelper.SetSourcePixbuf (cr, pixbuf, p_x, p_y);
+            CairoExtensions.RoundedRectangle (cr, p_x, p_y, image.Width, image.Height, radius, corners);
+            cr.SetSource (image, p_x, p_y);
             cr.Fill ();
             
             if (!drawBorder) {
                 if (dispose) {
-                    DisposePixbuf (pixbuf);
+                    image.Destroy ();
                 }
                 
                 return;
@@ -100,24 +100,7 @@
             cr.Stroke ();
             
             if (dispose) {
-                DisposePixbuf (pixbuf);
-            }
-        }
-        
-        private static int dispose_count = 0;
-        public static void DisposePixbuf (Gdk.Pixbuf pixbuf)
-        {
-            if (pixbuf != null && pixbuf.Handle != IntPtr.Zero) {
-                pixbuf.Dispose ();
-                pixbuf = null;
-                
-                // There is an issue with disposing Pixbufs where we need to explicitly 
-                // call the GC otherwise it doesn't get done in a timely way.  But if we
-                // do it every time, it slows things down a lot; so only do it every 100th.
-                if (++dispose_count % 100 == 0) {
-                    GC.Collect ();
-                    dispose_count = 0;
-                }
+                image.Destroy ();
             }
         }
     }

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellAlbum.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellAlbum.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Collection.Gui/ColumnCellAlbum.cs	Mon Oct  6 21:04:57 2008
@@ -41,10 +41,11 @@
 {
     public class ColumnCellAlbum : ColumnCell
     {
-        private static int pixbuf_spacing = 4;
-        private static int pixbuf_size = 48;
+        private static int image_spacing = 4;
+        private static int image_size = 48;
         
-        private static Gdk.Pixbuf default_cover_pixbuf = IconThemeUtils.LoadIcon (pixbuf_size, "media-optical");
+        private static ImageSurface default_cover_image 
+            = new PixbufImageSurface (IconThemeUtils.LoadIcon (image_size, "media-optical"));
         
         private ArtworkManager artwork_manager;
 
@@ -66,20 +67,21 @@
             AlbumInfo album = (AlbumInfo)BoundObject;
             
             bool is_default = false;          
-            Gdk.Pixbuf pixbuf = artwork_manager == null ? null : artwork_manager.LookupScale (album.ArtworkId, pixbuf_size);
+            ImageSurface image = artwork_manager == null ? null 
+                : artwork_manager.LookupScaleSurface (album.ArtworkId, image_size, true);
             
-            if (pixbuf == null) {
-                pixbuf = default_cover_pixbuf;
+            if (image == null) {
+                image = default_cover_image;
                 is_default = true;
             }
             
-            // int pixbuf_render_size = is_default ? pixbuf.Height : (int)cellHeight - 8;
-            int pixbuf_render_size = pixbuf_size;
-            int x = pixbuf_spacing;
-            int y = ((int)cellHeight - pixbuf_render_size) / 2;
+            // int image_render_size = is_default ? image.Height : (int)cellHeight - 8;
+            int image_render_size = image_size;
+            int x = image_spacing;
+            int y = ((int)cellHeight - image_render_size) / 2;
             
-            ArtworkRenderer.RenderThumbnail (context.Context, pixbuf, !is_default, x, y, 
-                pixbuf_render_size, pixbuf_render_size, !is_default, context.Theme.Context.Radius);
+            ArtworkRenderer.RenderThumbnail (context.Context, image, false, x, y, 
+                image_render_size, image_render_size, !is_default, context.Theme.Context.Radius);
                 
             int fl_width = 0, fl_height = 0, sl_width = 0, sl_height = 0;
             Cairo.Color text_color = context.Theme.Colors.GetWidgetColor (GtkColorClass.Text, state);
@@ -151,7 +153,7 @@
             
             layout.Dispose ();
             
-            return (height < pixbuf_size ? pixbuf_size : height) + 6;
+            return (height < image_size ? image_size : height) + 6;
         }
     }
 }
\ No newline at end of file

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.TrackEditor/TrackEditorDialog.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.TrackEditor/TrackEditorDialog.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.TrackEditor/TrackEditorDialog.cs	Mon Oct  6 21:04:57 2008
@@ -389,7 +389,7 @@
             }
             
             ArtworkManager artwork = ServiceManager.Get<ArtworkManager> ();
-            Gdk.Pixbuf cover_art = artwork.LookupScale (current_track.ArtworkId, 64);
+            Gdk.Pixbuf cover_art = artwork.LookupScalePixbuf (current_track.ArtworkId, 64);
             header_image.Pixbuf = cover_art;
             if (cover_art == null) {
                 header_image.IconName = "media-optical";

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/ClassicTrackInfoDisplay.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/ClassicTrackInfoDisplay.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/ClassicTrackInfoDisplay.cs	Mon Oct  6 21:04:57 2008
@@ -272,7 +272,7 @@
                 return false;
             }
             
-            Gdk.Pixbuf pixbuf = ArtworkManager.Lookup (CurrentTrack.ArtworkId);
+            Gdk.Pixbuf pixbuf = ArtworkManager.LookupPixbuf (CurrentTrack.ArtworkId);
          
             if (pixbuf == null) {
                 HidePopup ();
@@ -299,7 +299,7 @@
         private void HidePopup ()
         {
             if (popup != null) {
-                ArtworkRenderer.DisposePixbuf (popup.Image);
+                ArtworkManager.DisposePixbuf (popup.Image);
                 popup.Destroy ();
                 popup.EnterNotifyEvent -= OnPopupEnterNotifyEvent;
                 popup.LeaveNotifyEvent -= OnPopupLeaveNotifyEvent;

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/LargeTrackInfoDisplay.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/LargeTrackInfoDisplay.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/LargeTrackInfoDisplay.cs	Mon Oct  6 21:04:57 2008
@@ -41,6 +41,7 @@
     public class LargeTrackInfoDisplay : TrackInfoDisplay
     {
         private Gdk.Rectangle text_alloc;
+        private Dictionary<ImageSurface, Surface> surfaces = new Dictionary<ImageSurface, Surface> ();
     
         public LargeTrackInfoDisplay ()
         {
@@ -82,26 +83,26 @@
             QueueDraw ();
         }
        
-        protected override void RenderCoverArt (Cairo.Context cr, Gdk.Pixbuf pixbuf)
+        protected override void RenderCoverArt (Cairo.Context cr, ImageSurface image)
         {
-            if (pixbuf == null) {
+            if (image == null) {
                 return;
             }
             
             Gdk.Rectangle alloc = RenderAllocation;
             int asr = ArtworkSizeRequest;
-            int reflect = (int)(pixbuf.Height * 0.2);
-            int surface_w = pixbuf.Width;
-            int surface_h = pixbuf.Height + reflect;
+            int reflect = (int)(image.Height * 0.2);
+            int surface_w = image.Width;
+            int surface_h = image.Height + reflect;
             int x = alloc.X + alloc.Width - asr;
             int y = alloc.Y;
             
-            Surface surface = SurfaceLookup (pixbuf);
-            if (surface == null) {
-                surface = CreateSurfaceForPixbuf (cr, pixbuf, reflect);
-                SurfaceCache (pixbuf, surface);
+            Surface scene = null;
+            if (!surfaces.TryGetValue (image, out scene)) {
+                scene = CreateScene (cr, image, reflect);
+                surfaces.Add (image, scene);
             }
-                
+
             cr.Rectangle (x, y, asr, alloc.Height);
             cr.Color = BackgroundColor;
             cr.Fill ();
@@ -109,32 +110,30 @@
             x += (asr - surface_w) / 2;
             y += surface_h > asr ? 0 : (asr - surface_h) / 2;
             
-            cr.SetSource (surface, x, y);
+            cr.SetSource (scene, x, y);
             cr.Paint ();
         }
         
-        private Surface CreateSurfaceForPixbuf (Cairo.Context window_cr, Gdk.Pixbuf pixbuf, int reflect)
+        private Surface CreateScene (Cairo.Context window_cr, ImageSurface image, int reflect)
         {
             Surface surface = window_cr.Target.CreateSimilar (window_cr.Target.Content, 
-                pixbuf.Width, pixbuf.Height + reflect);
+                image.Width, image.Height + reflect);
             Cairo.Context cr = new Context (surface);
             
             cr.Save ();
             
-            ImageSurface img = new PixbufImageSurface (pixbuf);
-            
-            cr.SetSource (img);
+            cr.SetSource (image);
             cr.Paint ();
             
-            cr.Rectangle (0, pixbuf.Height, pixbuf.Width, reflect);
+            cr.Rectangle (0, image.Height, image.Width, reflect);
             cr.Clip ();
             
             Matrix matrix = new Matrix ();
             matrix.InitScale (1, -1);
-            matrix.Translate (0, -(2 * pixbuf.Height) + 1);
+            matrix.Translate (0, -(2 * image.Height) + 1);
             cr.Transform (matrix);
             
-            cr.SetSource (img);
+            cr.SetSource (image);
             cr.Paint ();
             
             cr.Restore ();
@@ -142,16 +141,14 @@
             Color bg_transparent = BackgroundColor;
             bg_transparent.A = 0.65;
             
-            LinearGradient mask = new LinearGradient (0, pixbuf.Height, 0, pixbuf.Height + reflect);
+            LinearGradient mask = new LinearGradient (0, image.Height, 0, image.Height + reflect);
             mask.AddColorStop (0, bg_transparent);
             mask.AddColorStop (1, BackgroundColor);
             
-            cr.Rectangle (0, pixbuf.Height, pixbuf.Width, reflect);
+            cr.Rectangle (0, image.Height, image.Width, reflect);
             cr.Pattern = mask;
             cr.Fill ();
             
-            img.Destroy ();
-            
             ((IDisposable)cr).Dispose ();
             return surface;
         }
@@ -236,7 +233,7 @@
         
         protected override void Invalidate ()
         {
-            if (CurrentPixbuf == null || CurrentTrack == null || IncomingPixbuf == null || IncomingTrack == null) {
+            if (CurrentImage == null || CurrentTrack == null || IncomingImage == null || IncomingTrack == null) {
                 QueueDraw ();
             } else {
                 Gdk.Rectangle alloc = RenderAllocation;
@@ -245,5 +242,14 @@
                     alloc.Width - text_alloc.Width - Spacing, alloc.Height);
             }
         }
+        
+        protected override void InvalidateCache ()
+        {
+            foreach (Surface surface in surfaces.Values) {
+                surface.Destroy ();
+            }
+            
+            surfaces.Clear ();
+        }
     }
 }

Modified: trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TrackInfoDisplay.cs
==============================================================================
--- trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TrackInfoDisplay.cs	(original)
+++ trunk/banshee/src/Core/Banshee.ThickClient/Banshee.Gui.Widgets/TrackInfoDisplay.cs	Mon Oct  6 21:04:57 2008
@@ -32,7 +32,7 @@
 using Mono.Unix;
 
 using Gtk;
-using Gdk;
+using Cairo;
 
 using Hyena;
 using Hyena.Gui;
@@ -53,24 +53,26 @@
             get { return artwork_manager; }
         }
         
-        private Pixbuf current_pixbuf;
-        protected Pixbuf CurrentPixbuf {
-            get { return current_pixbuf; }
+        private ImageSurface current_image;
+        protected ImageSurface CurrentImage {
+            get { return current_image; }
         }
         
-        private Pixbuf incoming_pixbuf;
-        protected Pixbuf IncomingPixbuf {
-            get { return incoming_pixbuf; }
+        private ImageSurface incoming_image;
+        protected ImageSurface IncomingImage {
+            get { return incoming_image; }
         }
         
-        private Pixbuf missing_audio_pixbuf;
-        protected Pixbuf MissingAudioPixbuf {
-            get { return missing_audio_pixbuf ?? missing_audio_pixbuf = IconThemeUtils.LoadIcon (MissingIconSizeRequest, "audio-x-generic"); }
+        private ImageSurface missing_audio_image;
+        protected ImageSurface MissingAudioImage {
+            get { return missing_audio_image ?? missing_audio_image 
+                = new PixbufImageSurface (IconThemeUtils.LoadIcon (MissingIconSizeRequest, "audio-x-generic"), true); }
         }
         
-        private Pixbuf missing_video_pixbuf;
-        protected Pixbuf MissingVideoPixbuf {
-            get { return missing_video_pixbuf ?? missing_video_pixbuf = IconThemeUtils.LoadIcon (MissingIconSizeRequest, "video-x-generic"); }
+        private ImageSurface missing_video_image;
+        protected ImageSurface MissingVideoImage {
+            get { return missing_video_image ?? missing_video_image 
+                = new PixbufImageSurface (IconThemeUtils.LoadIcon (MissingIconSizeRequest, "video-x-generic"), true); }
         }
         
         private Cairo.Color background_color;
@@ -100,8 +102,7 @@
         
         private uint idle_timeout_id = 0;
         private SingleActorStage stage = new SingleActorStage ();
-        private Dictionary<Pixbuf, Cairo.Surface> surface_cache = new Dictionary<Pixbuf, Cairo.Surface> ();
-        
+
         protected TrackInfoDisplay (IntPtr native) : base (native)
         {
         }
@@ -135,7 +136,7 @@
             stage.Iteration -= OnStageIteration;
             stage = null;
             
-            SurfaceCacheFlush ();
+            InvalidateCache ();
             
             base.Dispose ();
         }
@@ -149,17 +150,17 @@
         protected override void OnUnrealized ()
         {
             base.OnUnrealized ();
-            SurfaceCacheFlush ();
+            InvalidateCache ();
         }
         
-        protected override void OnSizeAllocated (Rectangle allocation)
+        protected override void OnSizeAllocated (Gdk.Rectangle allocation)
         {
             base.OnSizeAllocated (allocation);
             
             if (current_track == null) {
                 LoadCurrentTrack ();
             } else {
-                LoadPixbuf (current_track);
+                LoadImage (current_track);
             }
         }
 
@@ -171,18 +172,18 @@
             background_color = CairoExtensions.GdkColorToCairoColor (Style.Background (StateType.Normal));
             text_light_color = Hyena.Gui.Theming.GtkTheme.GetCairoTextMidColor (this);
             
-            if (missing_audio_pixbuf != null) {
-                missing_audio_pixbuf.Dispose ();
-                missing_audio_pixbuf = null;
+            if (missing_audio_image != null) {
+                missing_audio_image.Destroy ();
+                missing_audio_image = null;
             }
 
-            if (missing_video_pixbuf != null) {
-                missing_video_pixbuf.Dispose ();
-                missing_video_pixbuf = null;
+            if (missing_video_image != null) {
+                missing_video_image.Destroy ();
+                missing_video_image = null;
             }
         }
         
-        protected override bool OnExposeEvent (EventExpose evnt)
+        protected override bool OnExposeEvent (Gdk.EventExpose evnt)
         {
             bool idle = incoming_track == null && current_track == null;
             if (!Visible || !IsMapped || (idle && !CanRenderIdle)) {
@@ -221,14 +222,14 @@
         {
             if (stage.Actor == null) {
                 // We are not in a transition, just render
-                RenderStage (cr, current_track, current_pixbuf);
+                RenderStage (cr, current_track, current_image);
                 return;
             } 
             
             if (current_track == null) {
                 // Fade in the whole stage, nothing to fade out
                 CairoExtensions.PushGroup (cr);
-                RenderStage (cr, incoming_track, incoming_pixbuf);
+                RenderStage (cr, incoming_track, incoming_image);
                 CairoExtensions.PopGroupToSource (cr);
                 
                 cr.PaintWithAlpha (stage.Actor.Percent);
@@ -236,10 +237,10 @@
             }
             
             // XFade only the cover art
-            RenderCoverArt (cr, incoming_pixbuf);
+            RenderCoverArt (cr, incoming_image);
             
             CairoExtensions.PushGroup (cr);
-            RenderCoverArt (cr, current_pixbuf);
+            RenderCoverArt (cr, current_image);
             CairoExtensions.PopGroupToSource (cr);
             
             cr.PaintWithAlpha (1.0 - stage.Actor.Percent);
@@ -269,23 +270,27 @@
             }
         }
         
-        private void RenderStage (Cairo.Context cr, TrackInfo track, Pixbuf pixbuf)
+        private void RenderStage (Cairo.Context cr, TrackInfo track, ImageSurface image)
         {
-            RenderCoverArt (cr, pixbuf);
+            RenderCoverArt (cr, image);
             RenderTrackInfo (cr, track, true, true);
         }
         
-        protected virtual void RenderCoverArt (Cairo.Context cr, Pixbuf pixbuf)
+        protected virtual void RenderCoverArt (Cairo.Context cr, ImageSurface image)
         {
-            ArtworkRenderer.RenderThumbnail (cr, pixbuf, false, Allocation.X, Allocation.Y, 
+            ArtworkRenderer.RenderThumbnail (cr, image, false, Allocation.X, Allocation.Y, 
                 ArtworkSizeRequest, ArtworkSizeRequest, 
-                !IsMissingPixbuf (pixbuf), 0, 
-                IsMissingPixbuf (pixbuf), BackgroundColor);
+                !IsMissingImage (image), 0, 
+                IsMissingImage (image), BackgroundColor);
         }
 
-        protected bool IsMissingPixbuf (Pixbuf pb)
+        protected bool IsMissingImage (ImageSurface pb)
+        {
+            return pb == missing_audio_image || pb == missing_video_image;
+        }
+        
+        protected virtual void InvalidateCache ()
         {
-            return (pb == missing_audio_pixbuf || pb == missing_video_pixbuf);
         }
         
         protected abstract void RenderTrackInfo (Cairo.Context cr, TrackInfo track, bool renderTrack, bool renderArtistAlbum);
@@ -302,7 +307,7 @@
         {
             if (args.Event == PlayerEvent.StartOfStream || args.Event == PlayerEvent.TrackInfoUpdated) {
                 LoadCurrentTrack ();
-            } else if (args.Event == PlayerEvent.StateChange && (incoming_track != null || incoming_pixbuf != null)) {
+            } else if (args.Event == PlayerEvent.StateChange && (incoming_track != null || incoming_image != null)) {
                 PlayerEventStateChangeArgs state = (PlayerEventStateChangeArgs)args;
                 if (state.Current == PlayerState.Idle) {
                     if (idle_timeout_id > 0) {
@@ -319,7 +324,7 @@
             if (ServiceManager.PlayerEngine.CurrentTrack == null || 
                 ServiceManager.PlayerEngine.CurrentState == PlayerState.Idle) {
                 incoming_track = null;
-                incoming_pixbuf = null;
+                incoming_image = null;
                 
                 if (stage != null && stage.Actor == null) {
                     stage.Reset ();
@@ -334,41 +339,41 @@
         {
             TrackInfo track = ServiceManager.PlayerEngine.CurrentTrack;
 
-            if (track == current_track && !IsMissingPixbuf (current_pixbuf)) {
+            if (track == current_track && !IsMissingImage (current_image)) {
                 return;
             } else if (track == null) {
                 incoming_track = null;
-                incoming_pixbuf = null;
+                incoming_image = null;
                 return;
             }
 
             incoming_track = track;
             
-            LoadPixbuf (track);
+            LoadImage (track);
 
             if (stage.Actor == null) {
                 stage.Reset ();
             }
         }
         
-        private void LoadPixbuf (TrackInfo track)
+        private void LoadImage (TrackInfo track)
         {
-            Gdk.Pixbuf pixbuf = artwork_manager.LookupScale (track.ArtworkId, ArtworkSizeRequest);
+            ImageSurface image = artwork_manager.LookupScaleSurface (track.ArtworkId, ArtworkSizeRequest);
 
-            if (pixbuf == null) {
-                LoadMissingPixbuf ((track.MediaAttributes & TrackMediaAttributes.VideoStream) != 0);
+            if (image == null) {
+                LoadMissingImage ((track.MediaAttributes & TrackMediaAttributes.VideoStream) != 0);
             } else {
-                incoming_pixbuf = pixbuf;
+                incoming_image = image;
             }
             
             if (track == current_track) {
-                current_pixbuf = incoming_pixbuf;
+                current_image = incoming_image;
             }
         }
 
-        private void LoadMissingPixbuf (bool is_video)
+        private void LoadMissingImage (bool is_video)
         {
-            incoming_pixbuf = is_video ? MissingVideoPixbuf : MissingAudioPixbuf;
+            incoming_image = is_video ? MissingVideoImage : MissingAudioImage;
         }
         
         private double last_fps = 0.0;
@@ -382,17 +387,17 @@
                 return;
             }
             
-            SurfaceCacheFlush ();
+            InvalidateCache ();
             
             if (ApplicationContext.Debugging) {
                 Log.DebugFormat ("TrackInfoDisplay RenderAnimation: {0:0.00} FPS", last_fps);
             }
             
-            if (current_pixbuf != incoming_pixbuf && !IsMissingPixbuf (current_pixbuf)) {
-                ArtworkRenderer.DisposePixbuf (current_pixbuf);
+            if (current_image != incoming_image && !IsMissingImage (current_image)) {
+                current_image.Destroy ();
             }
             
-            current_pixbuf = incoming_pixbuf;
+            current_image = incoming_image;
             current_track = incoming_track;
             
             incoming_track = null;
@@ -490,46 +495,5 @@
             }
             return markup;
         }
-        
-        protected void SurfaceExpire (Gdk.Pixbuf pixbuf)
-        {
-            if (pixbuf == null) {
-                return;
-            }
-            
-            Cairo.Surface surface = null;
-            if (surface_cache.TryGetValue (pixbuf, out surface)) {
-                surface.Destroy ();
-                surface_cache.Remove (pixbuf);
-            }
-        }
-        
-        protected void SurfaceCacheFlush ()
-        {
-            foreach (Cairo.Surface surface in surface_cache.Values) {
-                surface.Destroy ();
-            }
-            
-            surface_cache.Clear ();
-        }
-        
-        protected void SurfaceCache (Gdk.Pixbuf pixbuf, Cairo.Surface surface)
-        {
-            if (pixbuf == null || surface == null) {
-                return;
-            }
-            
-            SurfaceExpire (pixbuf);
-            surface_cache.Add (pixbuf, surface);
-        }
-        
-        protected Cairo.Surface SurfaceLookup (Gdk.Pixbuf pixbuf)
-        {
-            Cairo.Surface surface = null;
-            if (pixbuf != null) {
-                surface_cache.TryGetValue (pixbuf, out surface);
-            }
-            return surface;
-        }
     }
 }

Modified: trunk/banshee/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/MassStorageSource.cs
==============================================================================
--- trunk/banshee/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/MassStorageSource.cs	(original)
+++ trunk/banshee/src/Dap/Banshee.Dap.MassStorage/Banshee.Dap.MassStorage/MassStorageSource.cs	Mon Oct  6 21:04:57 2008
@@ -458,10 +458,10 @@
                             SafeUri local_cover_uri = new SafeUri (Banshee.Base.CoverArtSpec.GetPath (coverart_id));
                             Banshee.IO.File.Copy (local_cover_uri, cover_uri, false);
                         } else {
-                            pic = artwork_manager.Lookup (coverart_id);
+                            pic = artwork_manager.LookupPixbuf (coverart_id);
                         }
                     } else {
-                        pic = artwork_manager.LookupScale (coverart_id, CoverArtSize);
+                        pic = artwork_manager.LookupScalePixbuf (coverart_id, CoverArtSize);
                     }
 
                     if (pic != null) {
@@ -473,7 +473,7 @@
                         } catch (GLib.GException){
                             Log.DebugFormat ("Could convert cover art to {0}, unsupported filetype?", CoverArtFileType);
                         } finally {
-                            pic.Dispose ();
+                            Banshee.Collection.Gui.ArtworkManager.DisposePixbuf (pic);
                         }
                     }
                 }

Modified: trunk/banshee/src/Dap/Banshee.Dap.Mtp/Banshee.Dap.Mtp/MtpSource.cs
==============================================================================
--- trunk/banshee/src/Dap/Banshee.Dap.Mtp/Banshee.Dap.Mtp/MtpSource.cs	(original)
+++ trunk/banshee/src/Dap/Banshee.Dap.Mtp/Banshee.Dap.Mtp/MtpSource.cs	Mon Oct  6 21:04:57 2008
@@ -320,13 +320,13 @@
 
                     if (supports_jpegs && can_sync) {
                         try {
-                            Gdk.Pixbuf pic = ServiceManager.Get<Banshee.Collection.Gui.ArtworkManager> ().LookupScale (
+                            Gdk.Pixbuf pic = ServiceManager.Get<Banshee.Collection.Gui.ArtworkManager> ().LookupScalePixbuf (
                                 track.ArtworkId, thumb_width
                             );
                             if (pic != null) {
                                 byte [] bytes = pic.SaveToBuffer ("jpeg");
                                 album.Save (bytes, (uint)pic.Width, (uint)pic.Height);
-                                pic.Dispose ();
+                                Banshee.Collection.Gui.ArtworkManager.DisposePixbuf (pic);
                             }
                             album_cache[key] = album;
                         } catch {}

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	Mon Oct  6 21:04:57 2008
@@ -226,7 +226,7 @@
                         AlbumInfo album = new AlbumInfo (track.Album);
                         album.ArtistName = track.Artist;
 
-                        Gdk.Pixbuf pb = artwork_manager == null ? null : artwork_manager.LookupScale (album.ArtworkId, 40);
+                        Gdk.Pixbuf pb = artwork_manager == null ? null : artwork_manager.LookupScalePixbuf (album.ArtworkId, 40);
                         if (pb != null) {
                             tile.Pixbuf = pb;
                         }

Modified: trunk/banshee/src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea/NotificationAreaService.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea/NotificationAreaService.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.NotificationArea/Banshee.NotificationArea/NotificationAreaService.cs	Mon Oct  6 21:04:57 2008
@@ -386,7 +386,7 @@
             Gdk.Pixbuf image = null;
             
             if (artwork_manager_service != null) {
-                image = artwork_manager_service.LookupScale (current_track.ArtworkId, 42);
+                image = artwork_manager_service.LookupScalePixbuf (current_track.ArtworkId, 42);
             }
             
             if (image == null) {

Modified: trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPodcast.cs
==============================================================================
--- trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPodcast.cs	(original)
+++ trunk/banshee/src/Extensions/Banshee.Podcasting/Banshee.Podcasting.Gui/ColumnCellPodcast.cs	Mon Oct  6 21:04:57 2008
@@ -46,11 +46,12 @@
 {
     public class ColumnCellPodcast : ColumnCell
     {
-        private static int pixbuf_spacing = 4;
-        private static int pixbuf_size = 48;
+        private static int image_spacing = 4;
+        private static int image_size = 48;
         
         // TODO replace this w/ new icon installation etc
-        private static Gdk.Pixbuf default_cover_pixbuf = IconThemeUtils.LoadIcon (48, "podcast");
+        private static ImageSurface default_cover_image 
+            = new PixbufImageSurface (IconThemeUtils.LoadIcon (48, "podcast"));
         
         private ArtworkManager artwork_manager;
 
@@ -72,20 +73,21 @@
             Feed feed = (Feed)BoundObject;
             
             bool is_default = false;          
-            Gdk.Pixbuf pixbuf = artwork_manager == null ? null : artwork_manager.LookupScale (PodcastService.ArtworkIdFor (feed), pixbuf_size);
+            ImageSurface image = artwork_manager == null ? null 
+                : artwork_manager.LookupScaleSurface (PodcastService.ArtworkIdFor (feed), image_size, true);
             
-            if (pixbuf == null) {
-                pixbuf = default_cover_pixbuf;
+            if (image == null) {
+                image = default_cover_image;
                 is_default = true;
             }
             
-            // int pixbuf_render_size = is_default ? pixbuf.Height : (int)cellHeight - 8;
-            int pixbuf_render_size = pixbuf_size;
-            int x = pixbuf_spacing;
-            int y = ((int)cellHeight - pixbuf_render_size) / 2;
+            // int image_render_size = is_default ? image.Height : (int)cellHeight - 8;
+            int image_render_size = image_size;
+            int x = image_spacing;
+            int y = ((int)cellHeight - image_render_size) / 2;
 
-            ArtworkRenderer.RenderThumbnail (context.Context, pixbuf, !is_default, x, y, 
-                pixbuf_render_size, pixbuf_render_size, !is_default, context.Theme.Context.Radius);
+            ArtworkRenderer.RenderThumbnail (context.Context, image, !is_default, x, y, 
+                image_render_size, image_render_size, !is_default, context.Theme.Context.Radius);
                 
             int fl_width = 0, fl_height = 0, sl_width = 0, sl_height = 0;
             Cairo.Color text_color = context.Theme.Colors.GetWidgetColor (GtkColorClass.Text, state);
@@ -164,7 +166,7 @@
             
             layout.Dispose ();
             
-            return (height < pixbuf_size ? pixbuf_size : height) + 6;
+            return (height < image_size ? image_size : height) + 6;
         }
     }
 }

Modified: trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Gui/PixbufImageSurface.cs
==============================================================================
--- trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Gui/PixbufImageSurface.cs	(original)
+++ trunk/banshee/src/Libraries/Hyena.Gui/Hyena.Gui/PixbufImageSurface.cs	Mon Oct  6 21:04:57 2008
@@ -53,24 +53,32 @@
         
         private IntPtr data;
         
-        public PixbufImageSurface (Gdk.Pixbuf pixbuf) : this (pixbuf.Width, pixbuf.Height, 
-            pixbuf.NChannels, pixbuf.Rowstride, pixbuf.Pixels)
+        public PixbufImageSurface (Gdk.Pixbuf pixbuf) : this (pixbuf, false)
+        {
+        }
+        
+        public PixbufImageSurface (Gdk.Pixbuf pixbuf, bool disposePixbuf) : this (disposePixbuf ? pixbuf : null, 
+            pixbuf.Width, pixbuf.Height, pixbuf.NChannels, pixbuf.Rowstride, pixbuf.Pixels)
         {
         }
         
         // This ctor is to avoid multiple queries against the GdkPixbuf for width/height
-        private PixbufImageSurface (int width, int height, int channels, int rowstride, IntPtr pixels) : this (
-            Marshal.AllocHGlobal (width * height * 4), width, height, channels, rowstride, pixels)
+        private PixbufImageSurface (Gdk.Pixbuf pixbuf, int width, int height, int channels, int rowstride, IntPtr pixels) 
+            : this (pixbuf, Marshal.AllocHGlobal (width * height * 4), width, height, channels, rowstride, pixels)
         {
         }
         
-        private PixbufImageSurface (IntPtr data, int width, int height, int channels, int rowstride, IntPtr pixels) 
+        private PixbufImageSurface (Gdk.Pixbuf pixbuf, IntPtr data, int width, int height, int channels, int rowstride, IntPtr pixels) 
             : base (data, channels == 3 ? Format.Rgb24 : Format.Argb32, width, height, width * 4)
         {
             this.data = data;
             
             CreateSurface (width, height, channels, rowstride, pixels);
             SetDestroyFunc ();
+            
+            if (pixbuf != null && pixbuf.Handle != IntPtr.Zero) {
+                pixbuf.Dispose ();
+            }
         }
         
         private unsafe void CreateSurface (int width, int height, int channels, int gdk_rowstride, IntPtr pixels)

Added: trunk/banshee/src/Libraries/Hyena/Hyena.Collections/LruCache.cs
==============================================================================
--- (empty file)
+++ trunk/banshee/src/Libraries/Hyena/Hyena.Collections/LruCache.cs	Mon Oct  6 21:04:57 2008
@@ -0,0 +1,167 @@
+//
+// LruCache.cs
+//
+// Author:
+//   Aaron Bockover <abockover novell com>
+//
+// Copyright (C) 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;
+using System.Collections.Generic;
+
+namespace Hyena.Collections
+{
+    public struct CacheEntry<TKey, TValue>
+    {
+        private TKey key;
+        public TKey Key {
+            get { return key; }
+            set { key = value; }
+        }
+    
+        private TValue value;
+        public TValue Value {
+            get { return this.value; }
+            set { this.value = value; }
+        }
+        
+        internal DateTime LastUsed;
+        internal int UsedCount;
+    }
+
+    public class LruCache<TKey, TValue> : IEnumerable<CacheEntry<TKey, TValue>>
+    {
+        private Dictionary<TKey, CacheEntry<TKey, TValue>> cache;
+        private int max_count;
+        
+        public LruCache () : this (1024)
+        {
+        }
+        
+        public LruCache (int maxCount)
+        {
+            max_count = maxCount;
+            cache = new Dictionary<TKey, CacheEntry<TKey, TValue>> ();
+        }
+        
+        public void Add (TKey key, TValue value)
+        {
+            lock (cache) {
+                CacheEntry<TKey, TValue> entry;
+                if (cache.TryGetValue (key, out entry)) {
+                    Ref (ref entry);
+                    cache[key] = entry;
+                    return;
+                }
+                
+                entry.Key = key;
+                entry.Value = value;
+                Ref (ref entry);
+                cache.Add (key, entry);
+                
+                if (Count >= max_count) {
+                    TKey expire = FindOldestEntry ();
+                    ExpireItem (cache[expire].Value);
+                    cache.Remove (expire);
+                }
+            }
+        }
+        
+        public bool Contains (TKey key)
+        {
+            lock (cache) {
+                return cache.ContainsKey (key);
+            }
+        }
+        
+        public bool TryGetValue (TKey key, out TValue value)
+        {
+            lock (cache) {
+                CacheEntry<TKey, TValue> entry;
+                if (cache.TryGetValue (key, out entry)) {
+                    value = entry.Value;
+                    Ref (ref entry);
+                    cache[key] = entry;
+                    return true;
+                }
+                
+                value = default (TValue);
+                return false;
+            }
+        }
+        
+        private void Ref (ref CacheEntry<TKey, TValue> entry)
+        {
+            entry.LastUsed = DateTime.Now;
+            entry.UsedCount++;
+        }
+        
+        IEnumerator IEnumerable.GetEnumerator ()
+        {
+            return GetEnumerator ();
+        }
+        
+        public IEnumerator<CacheEntry<TKey, TValue>> GetEnumerator ()
+        {
+            lock (cache) {
+                foreach (KeyValuePair<TKey, CacheEntry<TKey, TValue>> item in cache) {
+                    yield return item.Value;
+                }
+            }
+        }
+        
+        // Ok, this blows. I have no time to implement anything clever or proper here.
+        // Using a hashtable generally sucks for this, but it's not bad for a 15 minute
+        // hack. max_count will be sufficiently small in our case that this can't be
+        // felt anyway. Meh.
+        
+        private TKey FindOldestEntry ()
+        {
+            lock (cache) {
+                DateTime oldest = DateTime.Now;
+                TKey oldest_key = default (TKey);
+                foreach (CacheEntry<TKey, TValue> item in this) {
+                    if (item.LastUsed < oldest) {
+                        oldest = item.LastUsed;
+                        oldest_key = item.Key;
+                    }
+                }
+                return oldest_key;
+            }
+        }
+        
+        protected virtual void ExpireItem (TValue item)
+        {
+        }
+        
+        public int MaxCount {
+            get { lock (cache) { return max_count; } }
+            set { lock (cache) { max_count = value; } }
+        }
+        
+        public int Count {
+            get { lock (cache) { return cache.Count; } }
+        }
+    }
+}

Modified: trunk/banshee/src/Libraries/Hyena/Hyena.csproj
==============================================================================
--- trunk/banshee/src/Libraries/Hyena/Hyena.csproj	(original)
+++ trunk/banshee/src/Libraries/Hyena/Hyena.csproj	Mon Oct  6 21:04:57 2008
@@ -129,6 +129,7 @@
     <Compile Include="Hyena.Json\IJsonCollection.cs" />
     <Compile Include="Hyena.Json\Tests\DeserializerTests.cs" />
     <Compile Include="Hyena\Delegates.cs" />
+    <Compile Include="Hyena.Collections\LruCache.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ProjectExtensions>

Modified: trunk/banshee/src/Libraries/Hyena/Makefile.am
==============================================================================
--- trunk/banshee/src/Libraries/Hyena/Makefile.am	(original)
+++ trunk/banshee/src/Libraries/Hyena/Makefile.am	Mon Oct  6 21:04:57 2008
@@ -5,6 +5,7 @@
 	Hyena.Collections/CollectionExtensions.cs \
 	Hyena.Collections/IntervalHeap.cs \
 	Hyena.Collections/IStackProvider.cs \
+	Hyena.Collections/LruCache.cs \
 	Hyena.Collections/QueuePipeline.cs \
 	Hyena.Collections/QueuePipelineElement.cs \
 	Hyena.Collections/RangeCollection.cs \



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