Index: /home/metskem/workspace/JSPWiki/src/com/ecyrd/jspwiki/providers/CachedAttachmentCollector.java
===================================================================
--- /home/metskem/workspace/JSPWiki/src/com/ecyrd/jspwiki/providers/CachedAttachmentCollector.java	(revision 0)
+++ /home/metskem/workspace/JSPWiki/src/com/ecyrd/jspwiki/providers/CachedAttachmentCollector.java	(revision 0)
@@ -0,0 +1,101 @@
+package com.ecyrd.jspwiki.providers;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import com.ecyrd.jspwiki.attachment.Attachment;
+import com.opensymphony.oscache.base.events.*;
+
+/**
+ * Keep a list of all Attachments in the OSCache (OSCache does not provide
+ * something like that) Idea copied from CacheItemCollector The cache is used to
+ * speed up the getRecentChanges function
+ * 
+ * @author Harry Metske
+ * @since 2.5
+ */
+public class CachedAttachmentCollector implements CacheEntryEventListener
+{
+    private static final Logger log = Logger.getLogger(CachedAttachmentCollector.class);
+
+    private Map<String, Attachment> m_allItems = new HashMap<String, Attachment>();
+
+    /**
+     * Returns a clone of the set - you cannot manipulate this.
+     * 
+     * @return
+     */
+    public List getAllItems()
+    {
+        List<Attachment> ret = new LinkedList<Attachment>();
+        ret.addAll(m_allItems.values());
+        log.info("returning " + ret.size() + " attachments");
+        return ret;
+    }
+
+    public void cacheEntryRemoved(CacheEntryEvent aEvent)
+    {
+        if (aEvent != null)
+        {
+            if (log.isDebugEnabled())
+            {
+                log.debug("attachment cache entry removed: " + aEvent.getKey());
+            }
+            Attachment item = (Attachment) aEvent.getEntry().getContent();
+
+            if (item != null)
+            {
+                m_allItems.remove(item.getName());
+            }
+        }
+    }
+
+    public void cacheEntryUpdated(CacheEntryEvent aEvent)
+    {
+        if (log.isDebugEnabled())
+        {
+            log.debug("attachment cache entry updated: " + aEvent.getKey());
+        }
+
+        Attachment item = (Attachment) aEvent.getEntry().getContent();
+
+        if (item != null)
+        {
+            // Item added or replaced.
+            m_allItems.put(item.getName(), item);
+        }
+        else
+        {
+            m_allItems.remove(aEvent.getKey());
+        }
+    }
+
+    public void cacheEntryAdded(CacheEntryEvent aEvent)
+    {
+        cacheEntryUpdated(aEvent);
+    }
+
+    public void cachePatternFlushed(CachePatternEvent aEvent)
+    {
+        // do nothing
+    }
+
+    public void cacheGroupFlushed(CacheGroupEvent aEvent)
+    {
+        // do nothing
+    }
+
+    public void cacheFlushed(CachewideEvent aEvent)
+    {
+        // do nothing
+    }
+
+    public void cacheEntryFlushed(CacheEntryEvent aEvent)
+    {
+        cacheEntryRemoved(aEvent);
+    }
+}Index: /home/metskem/workspace/JSPWiki/src/com/ecyrd/jspwiki/providers/CachingAttachmentProvider.java
===================================================================
--- /home/metskem/workspace/JSPWiki/src/com/ecyrd/jspwiki/providers/CachingAttachmentProvider.java	(revision 651979)
+++ /home/metskem/workspace/JSPWiki/src/com/ecyrd/jspwiki/providers/CachingAttachmentProvider.java	(working copy)
@@ -32,6 +32,7 @@
 import com.ecyrd.jspwiki.util.ClassUtil;
 import com.opensymphony.oscache.base.Cache;
 import com.opensymphony.oscache.base.NeedsRefreshException;
+import com.opensymphony.oscache.base.events.CacheEntryEventListener;
 
 /**
  *  Provides a caching attachment provider.  This class rests on top of a
@@ -61,11 +62,17 @@
     private Cache m_cache;
 
     private long m_cacheMisses = 0;
-    private long m_cacheHits   = 0;
+    private long m_cacheHits = 0;
+
+    /**
+     * This cache contains Attachment objects and is keyed by attachment name.
+     * This provides for quickly giving recently changed attachments (for the RecentChanges plugin)
+     */
+    private Cache m_attCache;
 
     /** The extension to append to directory names to denote an attachment directory. */
     public static final String DIR_EXTENSION   = "-att";
-    
+
     /** Property that supplies the directory used to store attachments. */
     public static final String PROP_STORAGEDIR = "jspwiki.basicAttachmentProvider.storageDir";
 
@@ -71,6 +78,9 @@
 
     // FIXME: Make settable.
     private int  m_refreshPeriod = 60*10; // 10 minutes at the moment
+    
+    private boolean m_gotall = false;
+    private CachedAttachmentCollector m_allCollector = new CachedAttachmentCollector();
 
     /**
      * {@inheritDoc}
@@ -79,10 +89,10 @@
         throws NoRequiredPropertyException,
                IOException
     {
-        log.debug("Initing CachingAttachmentProvider");
+        log.info("Initing CachingAttachmentProvider");
 
         //
-        //  Construct an unlimited cache.
+        // Construct an unlimited cache of Collection objects
         //
         m_cache = new Cache( true, false, true );
 
@@ -87,6 +97,13 @@
         m_cache = new Cache( true, false, true );
 
         //
+        // Construct an unlimited cache for the individual Attachment objects. 
+        // Attachment name is key, the Attachment object is the cached object
+        //
+        m_attCache = new Cache(true, false, true);
+        m_attCache.addCacheEventListener(m_allCollector,CacheEntryEventListener.class);
+
+        //
         //  Find and initialize real provider.
         //
         String classname = WikiEngine.getRequiredProperty( properties, 
@@ -130,6 +147,8 @@
         m_provider.putAttachmentData( att, data );
 
         m_cache.flushEntry( att.getParentName() );
+        att.setLastModified(new Date());
+        m_attCache.putInCache(att.getName(), att);
     }
 
     /**
@@ -210,8 +229,31 @@
     public List listAllChanged( Date timestamp )
         throws ProviderException
     {
-        // FIXME: Should cache
-        return m_provider.listAllChanged( timestamp );
+        List all = null;
+        //
+        // we do a one-time build up of the cache, after this the cache is updated for every attachment add/delete
+        if (m_gotall == false)
+        {
+            all = m_provider.listAllChanged(timestamp);
+
+            // Put all pages in the cache :
+
+            synchronized (this)
+            {
+                for (Iterator i = all.iterator(); i.hasNext();)
+                {
+                    Attachment att = (Attachment) i.next();
+                    m_attCache.putInCache(att.getName(), att);
+                }
+                m_gotall = true;
+            }
+        }
+        else
+        {
+            all = m_allCollector.getAllItems();
+        }
+
+        return all;
     }
 
     /**
@@ -331,7 +373,7 @@
         throws ProviderException
     {
         // This isn't strictly speaking correct, but it does not really matter
-        m_cache.putInCache( att.getParentName(), null );
+        m_cache.removeEntry( att.getParentName() );
         m_provider.deleteVersion( att );
     }
 
@@ -341,7 +383,8 @@
     public void deleteAttachment( Attachment att )
         throws ProviderException
     {
-        m_cache.putInCache( att.getParentName(), null );
+        m_cache.removeEntry( att.getParentName() );
+        m_attCache.removeEntry( att.getName() );
         m_provider.deleteAttachment( att );
     }
 
@@ -370,7 +413,7 @@
         throws ProviderException
     {
         m_provider.moveAttachmentsForPage(oldParent, newParent);
-        m_cache.putInCache( newParent, null ); // FIXME
-        m_cache.putInCache( oldParent, null );
+        m_cache.removeEntry( newParent ); 
+        m_cache.removeEntry( oldParent );
     }
 }

