<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,8 +1,7 @@
-
-
 Autotest.add_hook :initialize do |t|
   t.clear_mappings
-  t.add_exception(/^(?:\/.)?\.git.*/)
+  t.add_exception(/^(?:\/\.)?\.git.*/)
+  t.add_exception(/^(?:\/\.)?\[\..*/)
   t.add_mapping(/^lib\/.*\.rb$/) { |fn, _|
     t.files_matching(/^test\/#{File.basename(fn, '.rb')}_test.rb$/) }
   t.add_mapping(/^test\/.*_test\.rb$/) { |fn, _| fn }</diff>
      <filename>.autotest</filename>
    </modified>
    <modified>
      <diff>@@ -77,7 +77,7 @@ end
 
 on :deliver do
   # Handle conditional GET w/ If-Modified-Since
-  if @response.not_modified?(@request.header['If-Modified-Since'])
+  if @response.last_modified_at?(@request.header['If-Modified-Since'])
     debug 'upstream version is unmodified; sending 304'
     response.status = 304
     response.body = ''</diff>
      <filename>lib/rack/cache/config/default.rb</filename>
    </modified>
    <modified>
      <diff>@@ -83,7 +83,7 @@ module Rack::Cache
       headers['Last-Modified']
     end
 
-    def not_modified?(date)
+    def last_modified_at?(date)
       date &amp;&amp; last_modified == date
     end
 
@@ -94,6 +94,11 @@ module Rack::Cache
       !(no_store? || no_cache?)
     end
 
+    def validateable?
+      # TODO support ETags
+      cacheable? &amp;&amp; header?('Last-Modified')
+    end
+
   end
 
 </diff>
      <filename>lib/rack/cache/response.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,16 +1,30 @@
-module Rack::Cache
+require 'enumerator'
+require 'rack/utils'
+require 'digest/sha1'
 
-  module Utils
-    include Rack::Utils
-    extend self
-  end
+module Rack::Cache
 
-  # A variety of cache storage implementations.
+  # Rack::Cache supports pluggable storage backends.
+  #
+  # == Cacheable Objects
+  #
+  # Storage providers are responsible for storing, retreiving,
+  # and purging &quot;cacheable objects&quot;. A cacheable object is a
+  # constrained version of the response object defined by Rack:
+  #
+  # http://rack.rubyforge.org/doc/files/SPEC.html
+  #
+  # A Rack response object is a three-element array that contains
+  # the HTTP response status code, a Hash of HTTP headers, and a
+  # response body:
+  #   [ status, headers, body ]
+  #
   module Storage
 
-    # Useful base class for 
+    # Storage Provider interface and abstract base class.
     class Provider
 
+      # Retrieve a cacheable object for the key provided.
       def get(key)
         if value = fetch(key)
           value
@@ -41,9 +55,32 @@ module Rack::Cache
         raise NotImplemented
       end
 
+      def flush
+        raise NotImplemented
+      end
+
+    private
+
+      # Return an idempotent version of a Rack response body. When the
+      # object provided is an Array, return the body provided. Otherwise
+      # read the body into an Array, call the close method, and return
+      # the Array.
+      def slurp(body)
+        if body.kind_of?(Array)
+          body
+        else
+          data = []
+          body.each { |part| data &lt;&lt; part }
+          body.close if body.respond_to? :close
+          data
+        end
+      end
+
     end
 
-    # Stores cached entries in memory using a normal Hash object.
+
+    # Stores cached entries in memory using a normal Hash object. Note that
+    # entire response bodies are kept on the heap until purged or deleted.
     class Memory &lt; Provider
 
       def initialize(hashish={})
@@ -65,12 +102,90 @@ module Rack::Cache
       end
 
       def store(key, object)
-        @contents[key] = object
+        status, headers, body = object
+        data = slurp(body)
+        @contents[key] = [ status, headers, data ]
       end
 
     end
 
 
+    # A simple Hash-based memory store that writes bodies to disk.
+    class DiskBackedMemory &lt; Memory
+      include FileUtils
+
+      def initialize(storage_root=&quot;/tmp/r#{$$}&quot;)
+        @storage_root = storage_root
+        mkdir_p @storage_root
+        super()
+      end
+
+    protected
+
+      def fetch(key)
+        if object = super
+          status, headers, sha = object
+          [ status, headers, disk_read(sha) ]
+        end
+      end
+
+      def store(key, object)
+        status, headers, body = object
+        sha = disk_write(body)
+        @contents[key] = [ status, headers, sha ]
+        [ status, headers, disk_read(sha) ]
+      end
+
+      def delete(key)
+        if object = super
+          status, headers, sha = object
+          F.unlink body_path(sha)
+        end
+      end
+
+    private
+
+      F = File
+      D = Dir
+
+      def storage_path(stem)
+        F.join(@storage_root, stem)
+      end
+
+      def partition_sha(sha)
+        sha = sha.dup
+        sha[2,0] = '/'
+        sha
+      end
+
+      def body_path(sha)
+        storage_path(partition_sha(sha))
+      end
+
+      def disk_write(body)
+        # TODO can only write one body at a time
+        temp_file = storage_path('CURRENT')
+        digest = Digest::SHA1.new
+        F.open(temp_file, 'w') do |wr|
+          body.each do |part|
+            digest &lt;&lt; part
+            wr.write(part)
+          end
+        end
+        sha = digest.hexdigest
+        path = body_path(sha)
+        mkdir_p F.dirname(path)
+        mv temp_file, path
+        sha
+      end
+
+      def disk_read(sha)
+        path = body_path(sha)
+        File.open(path, 'r')
+      end
+
+    end
+
   end
 
 end</diff>
      <filename>lib/rack/cache/storage.rb</filename>
    </modified>
    <modified>
      <diff>@@ -19,9 +19,6 @@ end
 
 describe 'Rack::Cache::new' do
   before { @app = method(:dumb_app) }
-  it 'is defined' do
-    Rack::Cache.should.respond_to? :new
-  end
   it 'takes a backend and returns a middleware component' do
     Rack::Cache.new(@app).
       should.respond_to :call
@@ -30,7 +27,7 @@ describe 'Rack::Cache::new' do
     lambda { Rack::Cache.new(@app, {}) }.
       should.not.raise(ArgumentError)
   end
-  it 'takes a block and executes during initialization' do
+  it 'takes a block; executes it during initialization' do
     state, block_scope = 'not invoked', nil
     object =
       Rack::Cache.new @app do</diff>
      <filename>test/cache_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -16,16 +16,13 @@ class MockConfig
 end
 
 describe 'Rack::Cache::Config' do
-
   before(:each) { @config = MockConfig.new }
 
-  it 'has events and trace variables after creation' do
-    @config.events.should.not.be.nil
-    @config.trace.should.not.be.nil
+  it 'has events after instantiation' do
+    @config.events.should.respond_to :[]
   end
 
-
-  it 'executes event handlers' do
+  it 'defines and executes event handlers' do
     executed = false
     @config.on(:foo) { executed = true }
     @config.perform :foo</diff>
      <filename>test/config_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -17,7 +17,7 @@ describe 'Rack::Cache::Context (Default Configuration)' do
       # response date is 5 seconds ago; makes expiration tests easier
       res['Date'] = (Time.now - 5).httpdate
       res['Expires'] = (Time.now + 5).httpdate
-      yield req,res if block_given?
+      yield req, res if block_given?
     end
   end
 
@@ -25,7 +25,7 @@ describe 'Rack::Cache::Context (Default Configuration)' do
     simple_resource *args do |req,res|
       res['Date'] = (Time.now - 5).httpdate
       res['Last-Modified'] = res['Date']
-      yield req,res if block_given?
+      yield req, res if block_given?
     end
   end
 
@@ -49,7 +49,7 @@ describe 'Rack::Cache::Context (Default Configuration)' do
 
   it 'passes on requests with Authorization' do
     @app = cacheable_response
-    get '/', 
+    get '/',
       'HTTP_AUTHORIZATION' =&gt; 'basic foobarbaz'
     @response.should.be.ok
     @context.should.a.performed :pass
@@ -184,15 +184,4 @@ protected
     request(:post, *args, &amp;b)
   end
 
-private
-
-  def method_missing(method_name, *args, &amp;b)
-    if @response
-      @response.send method_name, *args, &amp;b
-    else
-      super
-    end
-  end
-
-
 end</diff>
      <filename>test/context_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1,53 +1,65 @@
 require &quot;#{File.dirname(__FILE__)}/spec_setup&quot;
 
-describe 'Rack::Cache::Storage' do
-
-  shared_context 'Provider' do
-
-    before(:each) { @cache = create_provider }
-    after(:each)  { @cache = nil }
-
-    it 'stores objects with #put' do
-      @cache.put 'hello/word', &quot;I'm here&quot;
-    end
-
-    it 'returns object stored after storing with #put' do
-      @cache.put('hello/world', &quot;I'm here&quot;).should.be == &quot;I'm here&quot;
-    end
-
-    it 'returns stored objects with #get' do
-      @cache.put('foo', 'bar')
-      @cache.get('foo').should.be == 'bar'
-    end
-
-    it 'stores and returns the value yielded by the block when no object exists with #get' do
-      @cache.get('foo') { 'bar' }.should.be == 'bar'
-      @cache.get('foo').should.be == 'bar'
-    end
+def cacheable(body=nil)
+  [ 200, {}, [body || 'Hi'] ]
+end
 
-    it 'does not invoke block or overwrite existing objects when block provided to #get' do
-      @cache.put('foo', 'bar')
-      @cache.get('foo').should.be == 'bar'
-      @cache.get('foo') { baz }.should.be == 'bar'
-      @cache.get('foo') { fail }
-    end
+class Array
+  def cache_canonical
+    status, headers, body = self
+    body = [body.read] if body.respond_to?(:read)
+    [ status, headers, body ]
   end
+end
 
-  describe 'Memory' do
-    behaves_like 'Rack::Cache::Storage	Provider'
+shared_context 'A Cache Storage Provider' do
+  it 'stores objects with #put' do
+    @cache.put 'hello/world', cacheable(&quot;I'm here&quot;)
+    @cache.get('hello/world').should.not.be.nil
+  end
+  it 'returns stored objects with #get' do
+    @cache.put('foo', cacheable('bar'))
+    @cache.get('foo').cache_canonical.
+      should.be == cacheable('bar')
+  end
+  it 'returns object stored after storing with #put' do
+    @cache.put('hello/world', cacheable(&quot;I'm here&quot;)).cache_canonical.
+      should.be == cacheable(&quot;I'm here&quot;)
+  end
+  it 'stores and returns the value yielded by the block when no object exists with #get' do
+    @cache.get('foo') { cacheable('bar') }.cache_canonical.
+      should.be == cacheable('bar')
+    @cache.get('foo').cache_canonical.
+      should.be == cacheable('bar')
+  end
+  it 'does not invoke block or overwrite existing objects when block provided to #get' do
+    @cache.put('foo', cacheable('bar'))
+    @cache.get('foo').cache_canonical.should.be == cacheable('bar')
+    @cache.get('foo') { baz }.cache_canonical.should.be == cacheable('bar')
+    @cache.get('foo') { fail 'should not be called' }
+  end
+end
 
-    it 'takes a Hash to ::new and uses it' do
-      Rack::Cache::Storage::Memory.new('foo' =&gt; 'bar').get('foo').
-        should.be == 'bar'
-    end
+describe 'Rack::Cache::Storage' do
 
-    it 'takes no args to ::new and creates a Hash' do
-      Rack::Cache::Storage::Memory.new.get('foo').should.be.nil
+  describe 'Memory' do
+    behaves_like 'A Cache Storage Provider'
+    before { @cache = Rack::Cache::Storage::Memory.new }
+    describe '::new' do
+      it 'takes a Hash and uses it' do
+        Rack::Cache::Storage::Memory.new('foo' =&gt; cacheable('bar')).get('foo').
+          should.be == cacheable('bar')
+      end
+      it 'uses its own Hash with no args' do
+        Rack::Cache::Storage::Memory.new.get('foo').
+          should.be.nil
+      end
     end
+  end
 
-    def create_provider
-      Rack::Cache::Storage::Memory.new
-    end
+  describe 'DiskBackedMemory' do
+    behaves_like 'A Cache Storage Provider'
+    before { @cache = Rack::Cache::Storage::DiskBackedMemory.new }
   end
 
 end</diff>
      <filename>test/storage_test.rb</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>lib/rack/cache/default.rb</filename>
    </removed>
    <removed>
      <filename>test/provider_test.rb</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>180c835d13372b3905400f924be17015e66f23f6</id>
    </parent>
  </parents>
  <author>
    <name>Ryan Tomayko</name>
    <email>rtomayko@gmail.com</email>
  </author>
  <url>http://github.com/rtomayko/rack-cache/commit/825fb93e0351c7ee67727bfcf3851c2c873027c2</url>
  <id>825fb93e0351c7ee67727bfcf3851c2c873027c2</id>
  <committed-date>2008-10-19T22:24:22-07:00</committed-date>
  <authored-date>2008-07-23T00:19:06-07:00</authored-date>
  <message>add DiskBackedMemory store; other small tweaks and cleanup</message>
  <tree>7f1adac149bd7e385c1a76c9d4b422980a4b6549</tree>
  <committer>
    <name>Ryan Tomayko</name>
    <email>rtomayko@gmail.com</email>
  </committer>
</commit>
