[Jifty-commit] jifty branch, cas-rewrite, created. jifty-1.10228-28-g77a44eb

Jifty commits jifty-commit at lists.jifty.org
Mon Apr 25 12:20:54 EDT 2011


The branch, cas-rewrite has been created
        at  77a44eb946c4abc8215d3a1e595a6ef6207e8afe (commit)

- Log -----------------------------------------------------------------
commit 5995fcc1e4a8d6014a233c6233a93cdd28369653
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jun 25 20:11:28 2010 -0400

     Jifty::CAS no longer ISA Jifty::CAS::Store, with stupid ISA tricks
    
    Instead, it has-a Jifty::CAS::Store, and proxies the relevant methods
    into it.

diff --git a/lib/Jifty.pm b/lib/Jifty.pm
index 4dd88af..3f9c021 100644
--- a/lib/Jifty.pm
+++ b/lib/Jifty.pm
@@ -167,10 +167,7 @@ sub new {
     push @Jifty::Record::ISA, $record_base_class unless $record_base_class eq 'Jifty::Record';
 
     # Configure the base class for Jifty::CAS
-    @Jifty::CAS::ISA = grep { $_ ne 'Jifty::CAS::Store' } @Jifty::CAS::ISA;
-    my $cas_base = Jifty->config->framework('CAS')->{'BaseClass'};
-    Jifty::Util->require( $cas_base );
-    push @Jifty::CAS::ISA, $cas_base unless $cas_base eq 'Jifty::CAS';
+    Jifty::CAS->setup;
 
     # Logger turn on
     Jifty->logger( Jifty::Logger->new( $args{'logger_component'} ) );
diff --git a/lib/Jifty/CAS.pm b/lib/Jifty/CAS.pm
index 5a4b55d..19f2e6d 100644
--- a/lib/Jifty/CAS.pm
+++ b/lib/Jifty/CAS.pm
@@ -2,7 +2,6 @@ use strict;
 use warnings;
 
 package Jifty::CAS;
-use base 'Jifty::CAS::Store';
 use Plack::Request;
 use Plack::Response;
 
@@ -73,6 +72,15 @@ set and is used for the HTTP response.
 This method is usually called from a dispatcher rule.  Returns the HTTP status
 code set by this method (possibly for your use in the dispatcher).
 
+=head2 backend [DOMAIN]
+
+Returns the L<Jifty::CAS::Store> which backs the given C<DOMAIN>.  If
+C<DOMAIN> is not specified, returns the default backing store.
+
+=head2 setup
+
+Configures the CAS for use.
+
 =cut
 
 sub serve_by_name {
@@ -108,6 +116,13 @@ sub serve_by_name {
     return $res->finalize;
 }
 
+my $BACKEND;
+sub setup {
+    my $class = shift;
+    $BACKEND = Jifty->config->framework('CAS')->{'BaseClass'};
+    Jifty::Util->require( $BACKEND );
+}
+
 sub _serve_404 {
     my ($class, $domain, $name, $msg) = @_;
     $msg ||= '';
@@ -115,4 +130,23 @@ sub _serve_404 {
     return Plack::Response->new(404)->finalize;
 }
 
+sub backend {
+    $BACKEND
+}
+
+sub publish {
+    my $class = shift;
+    $BACKEND->publish(@_);
+}
+
+sub key {
+    my $class = shift;
+    $BACKEND->key(@_);
+}
+
+sub retrieve {
+    my $class = shift;
+    $BACKEND->retrieve(@_);
+}
+
 1;
diff --git a/t/cas-memcached.t b/t/cas-memcached.t
index e1a2870..d553162 100644
--- a/t/cas-memcached.t
+++ b/t/cas-memcached.t
@@ -20,12 +20,12 @@ my $data    = "a" x (1024*10);
 my $databig = "a" x (1024*1024*2);
 
 {
-    ok((grep { $_ eq 'Jifty::CAS::Store::Memcached' } @Jifty::CAS::ISA), 'Using memcached backed store');
+    isa_ok(Jifty::CAS->backend,  "Jifty::CAS::Store::Memcached", 'Using memcached backed store');
     my $key = Jifty::CAS->publish("test$$", 'one', $data, { content_type => 'text/plain' });
     ok $key, "Published";
     is length $key, 32, "Key is 32 chars long - an MD5 sum";
     is(Jifty::CAS->key("test$$", "one"), $key, "Matches what we get back from ->key");
-    
+
     my $blob = Jifty::CAS->retrieve("test$$", $key);
     ok $blob, "retrieved value";
     isa_ok $blob, 'Jifty::CAS::Blob', 'got a blob';

commit 0d8d5d352284d3927cc87d8b4ebb5dd7c997a27b
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jun 25 20:12:45 2010 -0400

    Make Jifty::CAS::Store into an instance, and Moose-ify

diff --git a/lib/Jifty/CAS.pm b/lib/Jifty/CAS.pm
index 19f2e6d..9b3bd86 100644
--- a/lib/Jifty/CAS.pm
+++ b/lib/Jifty/CAS.pm
@@ -119,8 +119,9 @@ sub serve_by_name {
 my $BACKEND;
 sub setup {
     my $class = shift;
-    $BACKEND = Jifty->config->framework('CAS')->{'BaseClass'};
-    Jifty::Util->require( $BACKEND );
+    my $store_class = Jifty->config->framework('CAS')->{'BaseClass'};
+    Jifty::Util->require( $store_class );
+    $BACKEND = $store_class->new;
 }
 
 sub _serve_404 {
diff --git a/lib/Jifty/CAS/Store.pm b/lib/Jifty/CAS/Store.pm
index 390955d..ce2c330 100644
--- a/lib/Jifty/CAS/Store.pm
+++ b/lib/Jifty/CAS/Store.pm
@@ -2,6 +2,7 @@ use strict;
 use warnings;
 
 package Jifty::CAS::Store;
+use Any::Moose;
 
 =head1 NAME
 
@@ -28,7 +29,7 @@ Returns the key.
 =cut
 
 sub publish {
-    my ($class, $domain, $name, $content, $opt) = @_;
+    my ($self, $domain, $name, $content, $opt) = @_;
     $opt ||= {};
 
     my $blob = Jifty::CAS::Blob->new(
@@ -36,7 +37,7 @@ sub publish {
             metadata => $opt,
         }
     );
-    return $class->_store( $domain, $name, $blob );
+    return $self->_store( $domain, $name, $blob );
 }
 
 =head2 _store DOMAIN NAME BLOB
@@ -46,7 +47,7 @@ Stores the BLOB (a L<Jifty::CAS::Blob>) in the backend.  Returns the key.  Don't
 =cut
 
 sub _store {
-    my ($class, $domain, $name, $blob) = @_;
+    my ($self, $domain, $name, $blob) = @_;
     my $db  = $CONTAINER{$domain} ||= {};
     my $key = $blob->key;
     $db->{DB}{$key} = $blob;
@@ -62,7 +63,7 @@ C<NAME>, or undef if none such exists.
 =cut
 
 sub key {
-    my ($class, $domain, $name) = @_;
+    my ($self, $domain, $name) = @_;
     return $CONTAINER{$domain}{KEYS}{$name};
 }
 
@@ -74,8 +75,10 @@ C<KEY>, or undef if none such exists.
 =cut
 
 sub retrieve {
-    my ($class, $domain, $key) = @_;
+    my ($self, $domain, $key) = @_;
     return $CONTAINER{$domain}{DB}{$key};
 }
 
+no Any::Moose;
+__PACKAGE__->meta->make_immutable(inline_constructor => 0);
 1;
diff --git a/lib/Jifty/CAS/Store/Memcached.pm b/lib/Jifty/CAS/Store/Memcached.pm
index b4ca792..edcbfab 100644
--- a/lib/Jifty/CAS/Store/Memcached.pm
+++ b/lib/Jifty/CAS/Store/Memcached.pm
@@ -2,8 +2,10 @@ use strict;
 use warnings;
 
 package Jifty::CAS::Store::Memcached;
+use Any::Moose;
+extends 'Jifty::CAS::Store';
 
-use base 'Jifty::CAS::Store';
+use Cache::Memcached;
 
 =head1 NAME
 
@@ -40,14 +42,18 @@ The options available include:
 This is a memcached backend for L<Jifty::CAS>.  For more information about
 Jifty's CAS, see L<Jifty::CAS/DESCRIPTION>.
 
-=cut
+=head1 METHODS
 
-use Cache::Memcached;
+=head2 BUILD
 
-our $MEMCACHED;
+Constructs the L</memcached> object for this object, based on the
+specified C<servers>, C<debug>, C<namespace>, and C<compress_threshold>
+arguments in the CAS configuration.
 
+=cut
+
+our $MEMCACHED;
 
-=head1 METHODS
 
 =head2 memcached
 
@@ -67,11 +73,11 @@ success or undef on failure.
 =cut
 
 sub _store {
-    my ($class, $domain, $name, $blob) = @_;
+    my ($self, $domain, $name, $blob) = @_;
 
     # Default to expiring in two weeks. XXX TODO this should be configurable
     my $key = $blob->key;
-    my $success = $class->memcached->set("$domain:db:$key", $blob, 60*60*24*14);
+    my $success = $self->memcached->set("$domain:db:$key", $blob, 60*60*24*14);
 
     unless ($success) {
         my $err = "Failed to store content for key '$domain:db:$key' in memcached!";
@@ -82,10 +88,10 @@ sub _store {
         }
         Jifty->log->error($err);
 
-        if ( $class->memcached_fallback ) {
+        if ( $self->memcached_fallback ) {
             Jifty->log->error("Falling back to default, in-process memory store.  "
                              ."This is suboptimal and you should investigate the cause.");
-            return $class->SUPER::_store($domain, $name, $blob);
+            return $self->SUPER::_store($domain, $name, $blob);
         }
         else {
             # fail with undef
@@ -93,7 +99,7 @@ sub _store {
         }
     }
 
-    $success = $class->memcached->set("$domain:keys:$name", $key, 60*60*24*14);
+    $success = $self->memcached->set("$domain:keys:$name", $key, 60*60*24*14);
 
     unless ($success) {
         Jifty->log->error("Failed to store key '$domain:keys:$name' in memcached!");
@@ -111,10 +117,10 @@ C<NAME>, or undef if none such exists.
 =cut
 
 sub key {
-    my ($class, $domain, $name) = @_;
-    my $key = $class->memcached->get("$domain:keys:$name");
+    my ($self, $domain, $name) = @_;
+    my $key = $self->memcached->get("$domain:keys:$name");
     return $key if defined $key;
-    return $class->SUPER::key($domain, $name) if $class->memcached_fallback;
+    return $self->SUPER::key($domain, $name) if $self->memcached_fallback;
     return;
 }
 
@@ -126,10 +132,10 @@ C<KEY>, or undef if none such exists.
 =cut
 
 sub retrieve {
-    my ($class, $domain, $key) = @_;
-    my $blob = $class->memcached->get("$domain:db:$key");
+    my ($self, $domain, $key) = @_;
+    my $blob = $self->memcached->get("$domain:db:$key");
     return $blob if defined $blob;
-    return $class->SUPER::retrieve($domain, $key) if $class->memcached_fallback;
+    return $self->SUPER::retrieve($domain, $key) if $self->memcached_fallback;
     return;
 }
 
@@ -175,4 +181,6 @@ sub memcached_fallback {
     Jifty->config->framework('CAS')->{'MemcachedFallback'} ? 1 : 0
 }
 
+no Any::Moose;
+__PACKAGE__->meta->make_immutable(inline_constructor => 0);
 1;

commit 641c19cd67113b8529b84e8f8d37096af9b087ac
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jun 25 19:20:33 2010 -0400

    There is no reason the memcached CAS should have an explicit expiration

diff --git a/lib/Jifty/CAS/Store/Memcached.pm b/lib/Jifty/CAS/Store/Memcached.pm
index edcbfab..30d755c 100644
--- a/lib/Jifty/CAS/Store/Memcached.pm
+++ b/lib/Jifty/CAS/Store/Memcached.pm
@@ -75,9 +75,8 @@ success or undef on failure.
 sub _store {
     my ($self, $domain, $name, $blob) = @_;
 
-    # Default to expiring in two weeks. XXX TODO this should be configurable
     my $key = $blob->key;
-    my $success = $self->memcached->set("$domain:db:$key", $blob, 60*60*24*14);
+    my $success = $self->memcached->set("$domain:db:$key", $blob);
 
     unless ($success) {
         my $err = "Failed to store content for key '$domain:db:$key' in memcached!";
@@ -99,7 +98,7 @@ sub _store {
         }
     }
 
-    $success = $self->memcached->set("$domain:keys:$name", $key, 60*60*24*14);
+    $success = $self->memcached->set("$domain:keys:$name", $key);
 
     unless ($success) {
         Jifty->log->error("Failed to store key '$domain:keys:$name' in memcached!");

commit 81795e352dd476863db939be0d34bcbb1dc541bd
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun Apr 24 19:54:08 2011 -0400

    Remove MemcachedFallback; later commits will add better fallback options

diff --git a/lib/Jifty/CAS/Store/Memcached.pm b/lib/Jifty/CAS/Store/Memcached.pm
index 30d755c..5516296 100644
--- a/lib/Jifty/CAS/Store/Memcached.pm
+++ b/lib/Jifty/CAS/Store/Memcached.pm
@@ -32,11 +32,6 @@ The options available include:
             - 10.0.0.3:11211
           compress_threshold: 5120
 
-        # Turned on by default. Keeps CAS working when memcached fails by
-        # falling back to the default in-process store. It probably should
-        # be turned off in most cases (like so) after successful testing.
-        MemcachedFallback: 0
-
 =head1 DESCRIPTION
 
 This is a memcached backend for L<Jifty::CAS>.  For more information about
@@ -87,15 +82,8 @@ sub _store {
         }
         Jifty->log->error($err);
 
-        if ( $self->memcached_fallback ) {
-            Jifty->log->error("Falling back to default, in-process memory store.  "
-                             ."This is suboptimal and you should investigate the cause.");
-            return $self->SUPER::_store($domain, $name, $blob);
-        }
-        else {
-            # fail with undef
-            return;
-        }
+        # fail with undef
+        return;
     }
 
     $success = $self->memcached->set("$domain:keys:$name", $key);
@@ -119,7 +107,6 @@ sub key {
     my ($self, $domain, $name) = @_;
     my $key = $self->memcached->get("$domain:keys:$name");
     return $key if defined $key;
-    return $self->SUPER::key($domain, $name) if $self->memcached_fallback;
     return;
 }
 
@@ -134,7 +121,6 @@ sub retrieve {
     my ($self, $domain, $key) = @_;
     my $blob = $self->memcached->get("$domain:db:$key");
     return $blob if defined $blob;
-    return $self->SUPER::retrieve($domain, $key) if $self->memcached_fallback;
     return;
 }
 
@@ -169,17 +155,6 @@ sub memcached_config {
         || Jifty->config->defaults->{'framework'}{'CAS'}{'Memcached'}
 }
 
-=head2 memcached_fallback
-
-Returns a boolean (from the config file) indicating whether or not memcached
-should fallback to the per-process, in-memory store.
-
-=cut
-
-sub memcached_fallback {
-    Jifty->config->framework('CAS')->{'MemcachedFallback'} ? 1 : 0
-}
-
 no Any::Moose;
 __PACKAGE__->meta->make_immutable(inline_constructor => 0);
 1;
diff --git a/lib/Jifty/Config.pm b/lib/Jifty/Config.pm
index 1139190..ad9355e 100644
--- a/lib/Jifty/Config.pm
+++ b/lib/Jifty/Config.pm
@@ -633,7 +633,6 @@ sub defaults {
                     namespace   => $self->framework('ApplicationName').":",
                     compress_threshold => 10240,
                 },
-                MemcachedFallback => 1,
             },
         }
     };
diff --git a/t/cas-memcached.t b/t/cas-memcached.t
index d553162..3752e1e 100644
--- a/t/cas-memcached.t
+++ b/t/cas-memcached.t
@@ -14,7 +14,7 @@ plan skip_all => "Testing CAS memcached store requires a memcached running on th
 
 # We want to do the import late since it loads up Jifty and triggers CCJS's
 # early generation trying to use memcached
-Jifty::Test->import(tests => 17);
+Jifty::Test->import(tests => 10);
 
 my $data    = "a" x (1024*10);
 my $databig = "a" x (1024*1024*2);
@@ -39,19 +39,5 @@ my $databig = "a" x (1024*1024*2);
     is(Jifty::CAS->key("test$$", "two"), undef, "Can't lookup a key because it isn't there");
 }
 
-{
-    Jifty->config->framework('CAS')->{'MemcachedFallback'} = 1;
-    my $key = Jifty::CAS->publish("test$$", "three", $databig, { content_type => 'text/plain' });
-    ok $key, "Published";
-    is length $key, 32, "Key is 32 chars long - an MD5 sum";
-    is(Jifty::CAS->key("test$$", "three"), $key, "Matches what we get back from ->key");
-    
-    my $blob = Jifty::CAS->retrieve("test$$", $key);
-    ok $blob, "retrieved value";
-    isa_ok $blob, 'Jifty::CAS::Blob', 'got a blob';
-    is $blob->content, $databig, "content is the same";
-    is_deeply $blob->metadata, { content_type => 'text/plain' }, "metadata is still good";
-}
-
 # XXX TODO test serving up of CAS content
 
diff --git a/t/cas-memcached.t-config.yml b/t/cas-memcached.t-config.yml
index d70a6be..4dfe606 100644
--- a/t/cas-memcached.t-config.yml
+++ b/t/cas-memcached.t-config.yml
@@ -4,4 +4,3 @@ framework:
     BaseClass: 'Jifty::CAS::Store::Memcached'
     Memcached:
       compress_threshold: 100000000 # effectively disables compression
-    MemcachedFallback: 0

commit 989f86476755ccafca351edf5ecceefaafb30f47
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jun 25 19:24:55 2010 -0400

    Split the memory cache out into its own class, making Jifty::CAS::Store abstract

diff --git a/lib/Jifty/CAS.pm b/lib/Jifty/CAS.pm
index 9b3bd86..e5078b1 100644
--- a/lib/Jifty/CAS.pm
+++ b/lib/Jifty/CAS.pm
@@ -33,11 +33,11 @@ recent key provided with that name.
 
 =head1 BACKENDS
 
-The default data store is an per-process, in-memory store.  A
-L<memcached|Jifty::CAS::Store::Memcached> backed store is also available and
-has the benefits of sharing the cache across all instances of a Jifty app using
-Jifty::CAS.  The memcached store is limited to objects less than 1MB in size,
-however.
+The default data store is a per-process, in-memory store via
+L<Jifty::CAS::Store::Memory>.  L<Jifty::CAS::Store::Memcached> is also
+available, and has the benefits of sharing the cache across all
+instances of a Jifty app using L<Jifty::CAS>.  The memcached store is
+limited to objects less than 1MB in size, however.
 
 =head1 METHODS
 
diff --git a/lib/Jifty/CAS/Store.pm b/lib/Jifty/CAS/Store.pm
index ce2c330..23530fb 100644
--- a/lib/Jifty/CAS/Store.pm
+++ b/lib/Jifty/CAS/Store.pm
@@ -6,20 +6,17 @@ use Any::Moose;
 
 =head1 NAME
 
-Jifty::CAS::Store - The default, per-process, in-memory store for Jifty's
-Content-Addressable Storage facility
+Jifty::CAS::Store - Abstract class for Jifty's Content-Addressed Storage
 
 =head1 DESCRIPTION
 
-This is the default backend store for L<Jifty::CAS>.  For more information, see
-L<Jifty::CAS/DESCRIPTION>.
+This is the abstract base class for a backend store for L<Jifty::CAS>.
+For more information, see L<Jifty::CAS/DESCRIPTION>.
 
 =cut
 
 use Jifty::CAS::Blob;
 
-my %CONTAINER;
-
 =head2 publish DOMAIN NAME CONTENT METADATA
 
 Publishes the given C<CONTENT> at the address C<DOMAIN> and C<NAME>.
@@ -42,41 +39,36 @@ sub publish {
 
 =head2 _store DOMAIN NAME BLOB
 
-Stores the BLOB (a L<Jifty::CAS::Blob>) in the backend.  Returns the key.  Don't use this directly, use C<publish> instead.
+Stores the BLOB (a L<Jifty::CAS::Blob>) in the backend.  Returns the
+key.  Subclasses should override this, but it should not be called
+directly -- use L</publish> instead.
 
 =cut
 
 sub _store {
-    my ($self, $domain, $name, $blob) = @_;
-    my $db  = $CONTAINER{$domain} ||= {};
-    my $key = $blob->key;
-    $db->{DB}{$key} = $blob;
-    $db->{KEYS}{$name} = $key;
-    return $key;
+    die "This is an abstract base class; use one of the provided subclasses instead\n";
 }
 
 =head2 key DOMAIN NAME
 
-Returns the most recent key for the given pair of C<DOMAIN> and
-C<NAME>, or undef if none such exists.
+Returns the most recent key for the given pair of C<DOMAIN> and C<NAME>,
+or undef if none such exists.  Subclasses should override this.
 
 =cut
 
 sub key {
-    my ($self, $domain, $name) = @_;
-    return $CONTAINER{$domain}{KEYS}{$name};
+    die "This is an abstract base class; use one of the provided subclasses instead\n";
 }
 
 =head2 retrieve DOMAIN KEY
 
 Returns a L<Jifty::CAS::Blob> for the given pair of C<DOMAIN> and
-C<KEY>, or undef if none such exists.
+C<KEY>, or undef if none such exists.  Subclasses should override this.
 
 =cut
 
 sub retrieve {
-    my ($self, $domain, $key) = @_;
-    return $CONTAINER{$domain}{DB}{$key};
+    die "This is an abstract base class; use one of the provided subclasses instead\n";
 }
 
 no Any::Moose;
diff --git a/lib/Jifty/CAS/Store.pm b/lib/Jifty/CAS/Store/Memory.pm
similarity index 55%
copy from lib/Jifty/CAS/Store.pm
copy to lib/Jifty/CAS/Store/Memory.pm
index ce2c330..c1eb3b8 100644
--- a/lib/Jifty/CAS/Store.pm
+++ b/lib/Jifty/CAS/Store/Memory.pm
@@ -1,18 +1,18 @@
 use strict;
 use warnings;
 
-package Jifty::CAS::Store;
+package Jifty::CAS::Store::Memory;
 use Any::Moose;
+extends 'Jifty::CAS::Store';
 
 =head1 NAME
 
-Jifty::CAS::Store - The default, per-process, in-memory store for Jifty's
-Content-Addressable Storage facility
+Jifty::CAS::Store::Memory - An single-process in-memory CAS store
 
 =head1 DESCRIPTION
 
-This is the default backend store for L<Jifty::CAS>.  For more information, see
-L<Jifty::CAS/DESCRIPTION>.
+This is the default backend store for L<Jifty::CAS>.  For more
+information, see L<Jifty::CAS/DESCRIPTION>.
 
 =cut
 
@@ -20,29 +20,10 @@ use Jifty::CAS::Blob;
 
 my %CONTAINER;
 
-=head2 publish DOMAIN NAME CONTENT METADATA
-
-Publishes the given C<CONTENT> at the address C<DOMAIN> and C<NAME>.
-C<METADATA> is an arbitrary hash; see L<Jifty::CAS::Blob> for more.
-Returns the key.
-
-=cut
-
-sub publish {
-    my ($self, $domain, $name, $content, $opt) = @_;
-    $opt ||= {};
-
-    my $blob = Jifty::CAS::Blob->new(
-        {   content  => $content,
-            metadata => $opt,
-        }
-    );
-    return $self->_store( $domain, $name, $blob );
-}
-
 =head2 _store DOMAIN NAME BLOB
 
-Stores the BLOB (a L<Jifty::CAS::Blob>) in the backend.  Returns the key.  Don't use this directly, use C<publish> instead.
+Stores the BLOB (a L<Jifty::CAS::Blob>) in the backend.  Returns the
+key.  Don't use this directly, use C<publish> instead.
 
 =cut
 
diff --git a/lib/Jifty/Config.pm b/lib/Jifty/Config.pm
index ad9355e..bab4c73 100644
--- a/lib/Jifty/Config.pm
+++ b/lib/Jifty/Config.pm
@@ -626,7 +626,7 @@ sub defaults {
                 SessionCookieName => 'JIFTY_SID_$PORT',
             },
             CAS => {
-                BaseClass => 'Jifty::CAS::Store',
+                BaseClass => 'Jifty::CAS::Store::Memory',
                 Memcached => {
                     servers     => [ '127.0.0.1:11211' ],
                     debug       => 0,

commit de7fbd9540cc5c14a9c6956fd2c3c7e797eec031
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jun 25 19:51:52 2010 -0400

    Support different CAS backends for different domains; config version bump

diff --git a/lib/Jifty/CAS.pm b/lib/Jifty/CAS.pm
index e5078b1..71ef66f 100644
--- a/lib/Jifty/CAS.pm
+++ b/lib/Jifty/CAS.pm
@@ -77,12 +77,33 @@ code set by this method (possibly for your use in the dispatcher).
 Returns the L<Jifty::CAS::Store> which backs the given C<DOMAIN>.  If
 C<DOMAIN> is not specified, returns the default backing store.
 
+=head2 config
+
+Returns the CAS configuration, as specified in the framework's
+configuration.
+
 =head2 setup
 
 Configures the CAS for use.
 
 =cut
 
+sub config {
+    my $class = shift;
+    my $config = Jifty->config->framework('CAS');
+    if (Jifty->config->framework('ConfigFileVersion') < 6) {
+        $config = {
+            Default => {
+                Class => $config->{'BaseClass'},
+                %{ $config->{'Memcached'} || {} },
+            }
+        };
+    }
+    $config->{Default}{Class} ||= "Jifty::CAS::Store::Memory";
+    $config->{Domains} ||= {};
+    return $config;
+}
+
 sub serve_by_name {
     my ($class, $domain, $name, $incoming_key, $env) = @_;
     my $key = Jifty::CAS->key($domain, $name);
@@ -116,12 +137,27 @@ sub serve_by_name {
     return $res->finalize;
 }
 
-my $BACKEND;
+my %BACKENDS;
+my $DEFAULT_BACKEND;
 sub setup {
     my $class = shift;
-    my $store_class = Jifty->config->framework('CAS')->{'BaseClass'};
-    Jifty::Util->require( $store_class );
-    $BACKEND = $store_class->new;
+    my $config = $class->config;
+
+    my %default = %{$config->{Default}};
+    my $defaultclass = delete $default{Class};
+    Jifty::Util->require( $defaultclass );
+    $DEFAULT_BACKEND = $defaultclass->new(
+        map {lc $_ => $config->{Default}{$_}} keys %default
+    );
+
+    for my $domain (keys %{$config->{Domains}}) {
+        my %domain = %{ $config->{Domains}{$domain} };
+        my $storeclass = delete $domain{Class};
+        Jifty::Util->require( $storeclass );
+        $BACKENDS{$domain} = $storeclass->new(
+            map {lc $_ => $config->{Domains}{$domain}{$_}} keys %domain
+        );
+    }
 }
 
 sub _serve_404 {
@@ -132,22 +168,28 @@ sub _serve_404 {
 }
 
 sub backend {
-    $BACKEND
+    my $class = shift;
+    my ($domain) = @_;
+    return $DEFAULT_BACKEND unless @_;
+    return $BACKENDS{$domain} || $DEFAULT_BACKEND;
 }
 
 sub publish {
     my $class = shift;
-    $BACKEND->publish(@_);
+    my ($domain) = @_;
+    ($BACKENDS{$domain} || $DEFAULT_BACKEND)->publish(@_);
 }
 
 sub key {
     my $class = shift;
-    $BACKEND->key(@_);
+    my ($domain) = @_;
+    ($BACKENDS{$domain} || $DEFAULT_BACKEND)->key(@_);
 }
 
 sub retrieve {
     my $class = shift;
-    $BACKEND->retrieve(@_);
+    my ($domain) = @_;
+    ($BACKENDS{$domain} || $DEFAULT_BACKEND)->retrieve(@_);
 }
 
 1;
diff --git a/lib/Jifty/CAS/Store/Memcached.pm b/lib/Jifty/CAS/Store/Memcached.pm
index 5516296..4ba0f0a 100644
--- a/lib/Jifty/CAS/Store/Memcached.pm
+++ b/lib/Jifty/CAS/Store/Memcached.pm
@@ -4,13 +4,17 @@ use warnings;
 package Jifty::CAS::Store::Memcached;
 use Any::Moose;
 extends 'Jifty::CAS::Store';
+has 'servers'            => (is => 'rw');
+has 'debug'              => (is => 'rw');
+has 'namespace'          => (is => 'rw');
+has 'compress_threshold' => (is => 'rw');
+has 'memcached'          => (is => 'rw');
 
 use Cache::Memcached;
 
 =head1 NAME
 
-Jifty::CAS::Store::Memcached - A memcached backend for Jifty's
-Content-Addressable Storage facility
+Jifty::CAS::Store::Memcached - A memcached backend for Jifty's CAS
 
 =head1 SYNOPSIS
 
@@ -18,19 +22,20 @@ At the bare minimum, add the following to your Jifty config.yml:
 
     framework:
       CAS:
-        BaseClass: 'Jifty::CAS::Store::Memcached'
+        Default:
+          Class: 'Jifty::CAS::Store::Memcached'
 
 The options available include:
 
     framework:
       CAS:
-        BaseClass: 'Jifty::CAS::Store::Memcached'
-        Memcached:
+        Default:
+          Class: 'Jifty::CAS::Store::Memcached'
           # any options Cache::Memcached supports
-          servers:
+          Servers:
             - 10.0.0.2:11211
             - 10.0.0.3:11211
-          compress_threshold: 5120
+          Compress_Threshold: 5120
 
 =head1 DESCRIPTION
 
@@ -47,8 +52,17 @@ arguments in the CAS configuration.
 
 =cut
 
-our $MEMCACHED;
-
+sub BUILD {
+    my $self = shift;
+    $self->memcached(
+        Cache::Memcached->new(
+            servers => $self->servers || [ '127.0.0.1:11211' ],
+            debug => $self->debug,
+            namespace => $self->namespace || Jifty->config->framework('ApplicationName').":CAS:",
+            compress_threshold => $self->compress_threshold || 10240,
+        )
+    );
+}
 
 =head2 memcached
 
@@ -56,10 +70,6 @@ Returns the L<Cache::Memcached> object for this class.
 
 =cut
 
-sub memcached {
-    $MEMCACHED ||= Cache::Memcached->new( $_[0]->memcached_config );
-}
-
 =head2 _store DOMAIN NAME BLOB
 
 Stores the BLOB (a L<Jifty::CAS::Blob>) in memcached.  Returns the key on
@@ -124,37 +134,6 @@ sub retrieve {
     return;
 }
 
-=head2 memcached_config
-
-Returns a hashref containing arguments to pass to L<Cache::Memcached> during
-construction. The defaults are like:
-
-  {
-      servers     => [ '127.0.0.1:11211' ],
-      debug       => 0,
-      namespace   => Jifty->config->framework('ApplicationName'),
-      compress_threshold => 10240,
-  }
-
-To change these options, set them in your Jifty application config file under
-C</framework/CAS/Memcached> like so:
-
-    framework:
-      CAS:
-        BaseClass: 'Jifty::CAS::Store::Memcached'
-        Memcached:
-            servers:
-                - 10.0.0.2:11211
-                - 10.0.0.3:11211
-            compress_threshold: 5120
-
-=cut
-
-sub memcached_config {
-    Jifty->config->framework('CAS')->{'Memcached'}
-        || Jifty->config->defaults->{'framework'}{'CAS'}{'Memcached'}
-}
-
 no Any::Moose;
 __PACKAGE__->meta->make_immutable(inline_constructor => 0);
 1;
diff --git a/lib/Jifty/Config.pm b/lib/Jifty/Config.pm
index bab4c73..2d3c8a2 100644
--- a/lib/Jifty/Config.pm
+++ b/lib/Jifty/Config.pm
@@ -535,7 +535,7 @@ See L<Jifty::Script::App>.
 sub initial_config {
     my $self = shift;
     my $guess = $self->guess(@_);
-    $guess->{'framework'}->{'ConfigFileVersion'} = 5;
+    $guess->{'framework'}->{'ConfigFileVersion'} = 6;
 
     # These are the plugins which new apps will get by default
     $guess->{'framework'}->{'Plugins'} = [
@@ -625,15 +625,6 @@ sub defaults {
                 DefaultTemplateRoot => Jifty::Util->share_root . '/web/templates',
                 SessionCookieName => 'JIFTY_SID_$PORT',
             },
-            CAS => {
-                BaseClass => 'Jifty::CAS::Store::Memory',
-                Memcached => {
-                    servers     => [ '127.0.0.1:11211' ],
-                    debug       => 0,
-                    namespace   => $self->framework('ApplicationName').":",
-                    compress_threshold => 10240,
-                },
-            },
         }
     };
 
diff --git a/t/cas-memcached.t b/t/cas-memcached.t
index 3752e1e..55b5951 100644
--- a/t/cas-memcached.t
+++ b/t/cas-memcached.t
@@ -20,7 +20,7 @@ my $data    = "a" x (1024*10);
 my $databig = "a" x (1024*1024*2);
 
 {
-    isa_ok(Jifty::CAS->backend,  "Jifty::CAS::Store::Memcached", 'Using memcached backed store');
+    isa_ok(Jifty::CAS->backend("test$$"),  "Jifty::CAS::Store::Memcached", 'Using memcached backed store');
     my $key = Jifty::CAS->publish("test$$", 'one', $data, { content_type => 'text/plain' });
     ok $key, "Published";
     is length $key, 32, "Key is 32 chars long - an MD5 sum";
@@ -40,4 +40,3 @@ my $databig = "a" x (1024*1024*2);
 }
 
 # XXX TODO test serving up of CAS content
-
diff --git a/t/cas-memcached.t-config.yml b/t/cas-memcached.t-config.yml
index 4dfe606..4194ae4 100644
--- a/t/cas-memcached.t-config.yml
+++ b/t/cas-memcached.t-config.yml
@@ -1,6 +1,7 @@
 ---
 framework:
+  ConfigFileVersion: 6
   CAS:
-    BaseClass: 'Jifty::CAS::Store::Memcached'
-    Memcached:
+    Default:
+      Class: 'Jifty::CAS::Store::Memcached'
       compress_threshold: 100000000 # effectively disables compression

commit af54d93c23a0ffe937d4674b8d736f2553dd2cf7
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jun 25 20:01:29 2010 -0400

    Make the CAS do its own serving, as opposed to relying on CCJS
    
    This paves the way for a more generic CAS server, allowing non-CCJS to
    be served.
    
    It also removes the 'cdn' option, which was of limited use because it
    required a complete app mirror.  The more correct solution involves
    either:
     a) publishing other static content (images, etc) to the CDN, and
        rewriting the CSS to point to them
     b) rewriting the CSS to point to the application's standard images
    
    Either of these solutions, since they involve parsing and rewriting CSS,
    is also relevant to hosting a Jifty application not at the server root.

diff --git a/lib/Jifty/CAS.pm b/lib/Jifty/CAS.pm
index 71ef66f..159f5f2 100644
--- a/lib/Jifty/CAS.pm
+++ b/lib/Jifty/CAS.pm
@@ -57,20 +57,16 @@ C<NAME>, or undef if none such exists.
 Returns a L<Jifty::CAS::Blob> for the given pair of C<DOMAIN> and
 C<KEY>, or undef if none such exists.
 
-=head2 serve_by_name DOMAIN NAME REQUESTED_KEY
+=head2 uri DOMAIN NAME
 
-Intelligently serves up the content of the object at NAME (B<not>
-REQUESTED_KEY) in DOMAIN.  REQUESTED_KEY is currently used only to check if the
-content at NAME equals the content requested.  If so, this method responds with
-an HTTP 304 status, indicating the content hasn't changed.  This use case
-assumes that content is served to clients from the CAS with the CAS key (an MD5
-sum) as the filename or part of it.
+Returns a URL where the given C<DOMAIN> and C<NAME> can be accessed.
 
-The C<content_type> key in the requested object's metadata is expected to be
-set and is used for the HTTP response.
+=head2 serve DOMAIN ARGUMENT ENV
 
-This method is usually called from a dispatcher rule.  Returns the HTTP status
-code set by this method (possibly for your use in the dispatcher).
+Serves a plack request in C<ENV>, given a C<DOMAIN> and an C<ARGUMENT>,
+which may wither be a key or a name.  This method is usually only called
+by L</wrap>, which calls it as appropriate for all requests under
+C</__jifty/cas/>.
 
 =head2 backend [DOMAIN]
 
@@ -86,6 +82,11 @@ configuration.
 
 Configures the CAS for use.
 
+=head2 wrap APP
+
+Given a PSGI application, wraps it to handle C</__jifty/cas/> requests
+by calling the appropriate L</serve> method.
+
 =cut
 
 sub config {
@@ -104,39 +105,6 @@ sub config {
     return $config;
 }
 
-sub serve_by_name {
-    my ($class, $domain, $name, $incoming_key, $env) = @_;
-    my $key = Jifty::CAS->key($domain, $name);
-
-    return $class->_serve_404( $domain, $name, "Unable to lookup key." )
-        if not defined $key;
-
-    my $res = Plack::Response->new(200);
-    my $req = Plack::Request->new($env);
-    if ( $req->header('If-Modified-Since') and $incoming_key eq $key ) {
-        Jifty->log->debug("Returning 304 for CAS cached $domain:$name ($key)");
-        $res->status(304);
-        return $res->finalize;
-    }
-
-    my $obj = Jifty::CAS->retrieve($domain, $key);
-
-    return $class->_serve_404( $domain, $name, "Unable to retrieve blob." )
-        if not defined $obj;
-
-    $res->content_type($obj->metadata->{content_type});
-    $res->header( 'Cache-Control' => 'max-age=31536000, public' );
-    $res->header( 'Expires' => HTTP::Date::time2str( time() + 31536000 ) );
-    $res->header( 'Content-Length' => length($obj->content) );
-    $res->header(
-      'Last-Modified' => HTTP::Date::time2str( $obj->metadata->{time} ) );
-
-    Jifty->log->debug("Sending squished $domain:$name ($key) from CAS");
-    $res->body($obj->content);
-
-    return $res->finalize;
-}
-
 my %BACKENDS;
 my $DEFAULT_BACKEND;
 sub setup {
@@ -160,11 +128,18 @@ sub setup {
     }
 }
 
-sub _serve_404 {
-    my ($class, $domain, $name, $msg) = @_;
-    $msg ||= '';
-    Jifty->log->error("Returning 404 for CAS cached $domain:$name.  $msg");
-    return Plack::Response->new(404)->finalize;
+sub wrap {
+    my ($class, $app) = @_;
+
+    sub {
+        my $env = shift;
+        if (my ($domain, $arg) = $env->{PATH_INFO} =~ m{/__jifty/cas/(.*?)/(.*?)(?:\.|$)}) {
+            return $class->serve($domain,$arg,$env);
+        }
+        else {
+            return $app->($env);
+        }
+    };
 }
 
 sub backend {
@@ -192,4 +167,16 @@ sub retrieve {
     ($BACKENDS{$domain} || $DEFAULT_BACKEND)->retrieve(@_);
 }
 
+sub uri {
+    my $class = shift;
+    my ($domain) = @_;
+    ($BACKENDS{$domain} || $DEFAULT_BACKEND)->uri(@_);
+}
+
+sub serve {
+    my $class = shift;
+    my ($domain) = @_;
+    ($BACKENDS{$domain} || $DEFAULT_BACKEND)->serve(@_);
+}
+
 1;
diff --git a/lib/Jifty/CAS/Store.pm b/lib/Jifty/CAS/Store.pm
index 23530fb..83be73a 100644
--- a/lib/Jifty/CAS/Store.pm
+++ b/lib/Jifty/CAS/Store.pm
@@ -71,6 +71,78 @@ sub retrieve {
     die "This is an abstract base class; use one of the provided subclasses instead\n";
 }
 
+=head2 uri DOMAIN NAME
+
+Returns a URL where the given C<DOMAIN> and C<NAME> can be accessed.
+
+=cut
+
+sub uri {
+    my $self = shift;
+    my ($domain, $name) = @_;
+    return "/__jifty/cas/$domain/" . $self->key($domain, $name);
+}
+
+=head2 serve DOMAIN ARGUMENT ENV
+
+Serves a plack request in C<ENV>, given a C<DOMAIN> and an C<ARGUMENT>,
+which may wither be a key or a name.
+
+This correctly uses the C<If-None-Match> and C<Etag> headers to send
+HTTP 304 responses to unchanged content.  Additionally, the
+C<content_type> key in the requested object's metadata is expected to be
+set and is used for the HTTP response.  This method is usually only
+called by L<JiftY::CAS/wrap>, which calls this method as appropriate for
+requests under C</__jifty/cas/>.
+
+=cut
+
+sub serve {
+    my ($self, $domain, $arg, $env) = @_;
+
+    my $key;
+    if ($arg =~ /^[a-f0-9]{32}$/) {
+        $key = $arg;
+    } else {
+        $key = $self->key($domain, $arg);
+        return $self->_serve_404( $domain, $arg, "Unable to lookup key." )
+            if not defined $key;
+    }
+
+    my $req = Plack::Request->new($env);
+    my $etag = $req->header('If-None-Match');
+    if ( defined $etag and $etag eq qq["$key"] ) {
+        Jifty->log->info("Returning 304 for CAS cached $domain:$key");
+        return Plack::Response->new(304)->finalize;
+    }
+
+    my $obj = Jifty::CAS->retrieve($domain, $key);
+    return $self->_serve_404( $domain, $key, "Unable to retrieve blob." )
+        if not defined $obj;
+
+    my $res = Plack::Response->new(200);
+    my $length = length($obj->content);
+    $res->content_type($obj->metadata->{content_type});
+    $res->header( 'Cache-Control' => 'max-age=31536000, public' );
+    $res->header( 'Expires' => HTTP::Date::time2str( time() + 31536000 ) );
+    $res->header( 'ETag' => '"'.$obj->key.'"' );
+    $res->header( 'Content-Length' => $length );
+    $res->header(
+      'Last-Modified' => HTTP::Date::time2str( $obj->metadata->{time} ) );
+
+    Jifty->log->info("Sending $domain:$key from CAS ($length bytes)");
+    $res->body($obj->content);
+
+    return $res->finalize;
+}
+
+sub _serve_404 {
+    my ($self, $domain, $name, $msg) = @_;
+    $msg ||= '';
+    Jifty->log->error("Returning 404 for CAS cached $domain:$name.  $msg");
+    return Plack::Response->new(404)->finalize;
+}
+
 no Any::Moose;
 __PACKAGE__->meta->make_immutable(inline_constructor => 0);
 1;
diff --git a/lib/Jifty/Handler.pm b/lib/Jifty/Handler.pm
index af7bb9b..b7a4a5b 100644
--- a/lib/Jifty/Handler.pm
+++ b/lib/Jifty/Handler.pm
@@ -194,6 +194,9 @@ sub psgi_app {
     }
         if Jifty->config->framework("Web")->{PSGIStatic} && $static;
 
+    # CAS wrapper
+    $app = Jifty::CAS->wrap($app);
+
     # allow plugin to wrap $app
     for ( Jifty->plugins ) {
         $app = $_->wrap($app);
diff --git a/lib/Jifty/Plugin/CompressedCSSandJS.pm b/lib/Jifty/Plugin/CompressedCSSandJS.pm
index 627ba6f..d133a79 100644
--- a/lib/Jifty/Plugin/CompressedCSSandJS.pm
+++ b/lib/Jifty/Plugin/CompressedCSSandJS.pm
@@ -24,7 +24,6 @@ Jifty::Plugin::CompressedCSSandJS - Compression of CSS and javascript files
         js: 1
         css: 1
         jsmin: /path/to/jsmin
-        cdn: 'http://yourcdn.for.static.prefix/'
         skipped_js:
             - complex.js
         generate_early: 1
@@ -52,7 +51,7 @@ by default.
 
 =cut
 
-__PACKAGE__->mk_accessors(qw(css js jsmin cdn skipped_js generate_early));
+__PACKAGE__->mk_accessors(qw(css js jsmin skipped_js generate_early));
 
 =head2 init
 
@@ -70,7 +69,6 @@ sub init {
     $self->css( $opt{css} );
     $self->js( $opt{js} );
     $self->jsmin( $opt{jsmin} );
-    $self->cdn( $opt{cdn} || '');
     $self->generate_early( exists $opt{generate_early} ? $opt{generate_early} : 1 );
 
     if ( $self->js_enabled ) {
@@ -121,8 +119,8 @@ sub _include_javascript {
 
     $self->generate_javascript;
     Jifty->web->out(
-        qq[<script type="text/javascript" src="@{[ $self->cdn ]}/__jifty/js/]
-          . Jifty::CAS->key( 'ccjs', 'js-all' )
+        qq[<script type="text/javascript" src="]
+          . Jifty::CAS->uri("ccjs","js-all")
           . qq[.js"></script>] );
 
     my $skipped_js = $self->skipped_js;
@@ -139,8 +137,8 @@ sub _include_css {
     my $self = shift;
     $self->generate_css;
     Jifty->web->out(
-    qq{<link rel="stylesheet" type="text/css" href="@{[ $self->cdn ]}/__jifty/css/}
-    . Jifty::CAS->key('ccjs', 'css-all') . '.css" />');
+    qq{<link rel="stylesheet" type="text/css" href="}
+        . Jifty::CAS->uri('ccjs', 'css-all').'.css" />');
     return 0;
 }
 
@@ -269,36 +267,4 @@ sub _js_is_skipped {
     return grep { $file eq $_ } @{ $self->skipped_js };
 }
 
-=head2 wrap
-
-psgi app wrapper to serve url controlled by us
-
-=cut
-
-sub wrap {
-    my ($self, $app) = @_;
-
-    sub {
-        my $env = shift;
-        if (my ($mode, $arg) = $env->{PATH_INFO} =~ m{/__jifty/(css|js)/(.*)}) {
-            if ( $arg !~ /^[0-9a-f]{32}\.$mode$/ ) {
-                # This doesn't look like a real request for squished JS or CSS,
-                # so redirect to a more failsafe place
-                my $res = Plack::Response->new;
-                $res->redirect( "/static/$mode/$arg" );
-                return $res->finalize;
-            }
-
-            my $method = "generate_".($mode eq 'js' ? 'javascript' : 'css');
-            $self->can($method)->($self);
-            $arg =~ s/\.$mode//;
-            return Jifty::CAS->serve_by_name( 'ccjs', $mode.'-all', $arg, $env );
-        }
-        else {
-            return $app->($env);
-        }
-    };
-}
-
-
 1;
diff --git a/t/TestApp-Plugin-CompressedCSSandJS/t/css.t b/t/TestApp-Plugin-CompressedCSSandJS/t/css.t
index 65f7f94..cf3bd8e 100644
--- a/t/TestApp-Plugin-CompressedCSSandJS/t/css.t
+++ b/t/TestApp-Plugin-CompressedCSSandJS/t/css.t
@@ -10,9 +10,9 @@ my $mech = Jifty::Test::WWW::Mechanize->new();
 $mech->get_ok("$URL/static/css/main.css","Got main.css");
 $mech->content_contains('@import "combobox.css"');
 $mech->get_ok("$URL");
-ok($mech->content =~ m{<link rel="stylesheet" type="text/css" href="/__jifty/css/(.*)" /});
+ok($mech->content =~ m{<link rel="stylesheet" type="text/css" href="(.*)" /});
 my $css_file = $1;
 
-$mech->get_ok("$URL/__jifty/css/$css_file");
+$mech->get_ok($1);
 $mech->content_contains('End of combobox.css', 'squished');
 

commit 3296483a19af47b644a71aa140ece694b0053c58
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Sun Apr 24 16:04:01 2011 -0400

    Add a way for a CAS backend to advertize itself as "durable"

diff --git a/lib/Jifty/CAS/Store.pm b/lib/Jifty/CAS/Store.pm
index 83be73a..d1649e0 100644
--- a/lib/Jifty/CAS/Store.pm
+++ b/lib/Jifty/CAS/Store.pm
@@ -143,6 +143,16 @@ sub _serve_404 {
     return Plack::Response->new(404)->finalize;
 }
 
+=head2 durable
+
+Returns true if the backing store is durable -- that is, if there is a
+guarantee that data placed there will be accessible from all proccesses at
+all later times.
+
+=cut
+
+sub durable { 0 }
+
 no Any::Moose;
 __PACKAGE__->meta->make_immutable(inline_constructor => 0);
 1;

commit 5f9b672ae703891f27a47b33ea15ab0f73dcb16d
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jun 25 20:03:51 2010 -0400

    Add a durable local file CAS
    
    This allows for a durable backend for generated CAS data, unlike memcached.

diff --git a/lib/Jifty/CAS.pm b/lib/Jifty/CAS.pm
index 159f5f2..e8a9351 100644
--- a/lib/Jifty/CAS.pm
+++ b/lib/Jifty/CAS.pm
@@ -38,6 +38,8 @@ L<Jifty::CAS::Store::Memory>.  L<Jifty::CAS::Store::Memcached> is also
 available, and has the benefits of sharing the cache across all
 instances of a Jifty app using L<Jifty::CAS>.  The memcached store is
 limited to objects less than 1MB in size, however.
+L<Jifty::CAS::Store::LocalFile> provides a durable store, which is
+well-suited for sharing the cache across instances and restarts.
 
 =head1 METHODS
 
diff --git a/lib/Jifty/CAS/Store/LocalFile.pm b/lib/Jifty/CAS/Store/LocalFile.pm
new file mode 100644
index 0000000..a0afb65
--- /dev/null
+++ b/lib/Jifty/CAS/Store/LocalFile.pm
@@ -0,0 +1,107 @@
+use strict;
+use warnings;
+
+package Jifty::CAS::Store::LocalFile;
+use Any::Moose;
+extends 'Jifty::CAS::Store';
+has 'path' => ( is => 'rw');
+
+use Storable qw(lock_store lock_retrieve);
+
+=head1 NAME
+
+Jifty::CAS::Store::LocalFile - A local file backend for Jifty's CAS
+
+=head1 DESCRIPTION
+
+This is a local file backend for L<Jifty::CAS>, which provides a
+B<durable> backend, unlike L<Jifty::CAS::Store::Memory> and
+L<Jifty::CAS::Store::Memcached>.  For more information about Jifty's
+CAS, see L<Jifty::CAS/DESCRIPTION>.
+
+Configuration requires providing a directory which is writable by the
+web user:
+
+    framework:
+      CAS:
+        Default:
+          Class: 'Jifty::CAS::Store::LocalFile'
+          Path: %var/cas%
+
+=cut
+
+=head1 METHODS
+
+=head2 _store DOMAIN NAME BLOB
+
+Stores the BLOB (a L<Jifty::CAS::Blob>) on disk.  Returns the key on
+success or undef on failure.
+
+=cut
+
+sub _store {
+    my ($self, $domain, $name, $blob) = @_;
+    mkdir($self->path) unless -d $self->path;
+    my $dir = $self->path . "/" . $domain;
+    mkdir($dir) unless -d $dir;
+
+    my $path = $dir . "/key-" . $blob->key;
+    unless (-e $path) {
+        lock_store($blob, $path)
+            or warn("Write of blob failed: $!") and return;
+    }
+
+    # Update the symlink
+    my $link = $dir . "/name-" . $name;
+    my $tmp  = $dir . "/tmp-"  . $name;
+    symlink( "key-".$blob->key, $tmp )
+        or warn("Symlink failed: $!") and return;
+    rename( $tmp, $link )
+        or warn("Rename of symlink failed: $!") and return;
+
+    return $blob->key;
+}
+
+=head2 key DOMAIN NAME
+
+Returns the most recent key for the given pair of C<DOMAIN> and
+C<NAME>, or undef if none such exists.
+
+=cut
+
+sub key {
+    my ($self, $domain, $name) = @_;
+    my $link = $self->path . "/" . $domain . "/name-" . $name;
+    return unless -l $link;
+    $link = readlink($link);
+    $link =~ s/^key-//;
+    return $link;
+}
+
+=head2 retrieve DOMAIN KEY
+
+Returns a L<Jifty::CAS::Blob> for the given pair of C<DOMAIN> and
+C<KEY>, or undef if none such exists.
+
+=cut
+
+sub retrieve {
+    my ($self, $domain, $key) = @_;
+    my $data = $self->path . "/" . $domain . "/key-" . $key;
+    return unless -r $data;
+
+    return lock_retrieve($data);
+}
+
+=head2 durable
+
+Since presumably the files on disk will not simply vanish, the local
+file store is durable.
+
+=cut
+
+sub durable { 1 }
+
+no Any::Moose;
+__PACKAGE__->meta->make_immutable(inline_constructor => 0);
+1;

commit c008a691a4f2f65143e5af415ebccbbba1e7fc7f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jun 25 20:05:20 2010 -0400

    Add a tool to generate CCJS externally, on command
    
    This allows minification to occur outside the webserver, as it is
    written to a durable store which the webserver can later read.  This is
    key to reducing server startup times.

diff --git a/lib/Jifty/Plugin/CompressedCSSandJS.pm b/lib/Jifty/Plugin/CompressedCSSandJS.pm
index d133a79..cd92dc2 100644
--- a/lib/Jifty/Plugin/CompressedCSSandJS.pm
+++ b/lib/Jifty/Plugin/CompressedCSSandJS.pm
@@ -51,7 +51,7 @@ by default.
 
 =cut
 
-__PACKAGE__->mk_accessors(qw(css js jsmin skipped_js generate_early));
+__PACKAGE__->mk_accessors(qw(css js jsmin skipped_js generate_early external_publish));
 
 =head2 init
 
@@ -70,6 +70,11 @@ sub init {
     $self->js( $opt{js} );
     $self->jsmin( $opt{jsmin} );
     $self->generate_early( exists $opt{generate_early} ? $opt{generate_early} : 1 );
+    $self->external_publish( $opt{external_publish} );
+    if ($self->external_publish and not Jifty::CAS->backend("ccjs")->durable) {
+        $self->log->warn("External publishing does not work with non-durable CAS stores; disabling");
+        $self->external_publish(0);
+    }
 
     if ( $self->js_enabled ) {
         Jifty::Web->add_trigger(
@@ -78,7 +83,7 @@ sub init {
             abortable => 1,
         );
         Jifty->add_trigger( post_init => sub { $self->generate_javascript })
-            if $self->generate_early;
+            if $self->generate_early and not $self->external_publish;
     }
 
     if ( $self->css_enabled ) {
@@ -88,7 +93,7 @@ sub init {
             abortable => 1,
         );
         Jifty->add_trigger( post_init => sub { $self->generate_css })
-            if $self->generate_early;
+            if $self->generate_early and not $self->external_publish;
     }
 }
 
@@ -117,7 +122,7 @@ sub css_enabled {
 sub _include_javascript {
     my $self = shift;
 
-    $self->generate_javascript;
+    $self->generate_javascript unless $self->external_publish;
     Jifty->web->out(
         qq[<script type="text/javascript" src="]
           . Jifty::CAS->uri("ccjs","js-all")
@@ -135,7 +140,7 @@ sub _include_javascript {
 
 sub _include_css {
     my $self = shift;
-    $self->generate_css;
+    $self->generate_css unless $self->external_publish;
     Jifty->web->out(
     qq{<link rel="stylesheet" type="text/css" href="}
         . Jifty::CAS->uri('ccjs', 'css-all').'.css" />');
@@ -153,7 +158,9 @@ and caches it. (In devel mode, it always regenerates it)
 sub generate_css {
     my $self = shift;
 
-    return if Jifty::CAS->key('ccjs', 'css-all') && !Jifty->config->framework('DevelMode');
+    return if !$self->external_publish
+        && Jifty::CAS->key('ccjs', 'css-all')
+        && !Jifty->config->framework('DevelMode');
 
     $self->log->debug("Generating CSS...");
 
@@ -180,7 +187,9 @@ and caches it.
 sub generate_javascript {
     my $self = shift;
 
-    return if Jifty::CAS->key('ccjs', 'js-all') && !Jifty->config->framework('DevelMode');
+    return if !$self->external_publish
+        && Jifty::CAS->key('ccjs', 'js-all')
+        && !Jifty->config->framework('DevelMode');
 
     my $js = $self->_generate_javascript_nocache;
 
diff --git a/lib/Jifty/Script.pm b/lib/Jifty/Script.pm
index b2adc74..b4c1e72 100644
--- a/lib/Jifty/Script.pm
+++ b/lib/Jifty/Script.pm
@@ -68,6 +68,7 @@ The alias table lets users type C<fastcgi> in place of C<FastCGI>.
 sub alias {
     return (
             fastcgi => "FastCGI",
+            writeccjs => "WriteCCJS",
            )
 }
 
diff --git a/lib/Jifty/Script/WriteCCJS.pm b/lib/Jifty/Script/WriteCCJS.pm
new file mode 100644
index 0000000..b65a71c
--- /dev/null
+++ b/lib/Jifty/Script/WriteCCJS.pm
@@ -0,0 +1,54 @@
+use strict;
+use warnings;
+
+package Jifty::Script::WriteCCJS;
+
+use base qw/Jifty::Script/;
+
+=head1 NAME
+
+Jifty::Script::WriteCCJS
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+=head2 options
+
+Takes no options.
+
+=head2 run
+
+=cut
+
+sub run {
+    my $self = shift;
+    Jifty->new;
+
+    my ($ccjs) = Jifty->find_plugin('Jifty::Plugin::CompressedCSSandJS');
+
+    die "CompressedCSSandJS is not enabled for @{[Jifty->app_class]}\n"
+        unless $ccjs;
+
+    die "External generation is not enabled\n"
+        unless $ccjs->external_publish;
+
+    $ccjs->generate_css;
+    print "Wrote CSS to ".Jifty::CAS->key("ccjs","css-all").".css\n";
+    $ccjs->generate_javascript;
+    print "Wrote JS  to ".Jifty::CAS->key("ccjs","js-all").".js\n";
+}
+
+=head1 SEE ALSO
+
+L<Jifty::Plugin::CompressedCSSandJS>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright 2010, Best Practical Solutions.
+
+This is free software and may be modified and distributed under the same terms as Perl itself.
+
+=cut
+
+1;

commit aa539fb8f89a66ae0a35a7f3361d7510dbe50f2f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jun 25 20:17:44 2010 -0400

    generate_early is never what you want if you have external_publish
    
    generate_early was a hack to attempt to accomplish what external_publish
    does better; remove it.

diff --git a/lib/Jifty/Plugin/CompressedCSSandJS.pm b/lib/Jifty/Plugin/CompressedCSSandJS.pm
index cd92dc2..542daa6 100644
--- a/lib/Jifty/Plugin/CompressedCSSandJS.pm
+++ b/lib/Jifty/Plugin/CompressedCSSandJS.pm
@@ -26,7 +26,6 @@ Jifty::Plugin::CompressedCSSandJS - Compression of CSS and javascript files
         jsmin: /path/to/jsmin
         skipped_js:
             - complex.js
-        generate_early: 1
 
 
 =head1 DESCRIPTION
@@ -44,14 +43,9 @@ configure jsmin feature.
 
 skipped_js is a list of js that you don't want to compress for some reason.
 
-generate_early tells the plugin to compress the CSS and JS at process start
-rather than on the first request.  This can save time, especially if your
-JS minifier is slow, for the poor sucker who makes the first request.  Enabled
-by default.
-
 =cut
 
-__PACKAGE__->mk_accessors(qw(css js jsmin skipped_js generate_early external_publish));
+__PACKAGE__->mk_accessors(qw(css js jsmin skipped_js external_publish));
 
 =head2 init
 
@@ -69,7 +63,6 @@ sub init {
     $self->css( $opt{css} );
     $self->js( $opt{js} );
     $self->jsmin( $opt{jsmin} );
-    $self->generate_early( exists $opt{generate_early} ? $opt{generate_early} : 1 );
     $self->external_publish( $opt{external_publish} );
     if ($self->external_publish and not Jifty::CAS->backend("ccjs")->durable) {
         $self->log->warn("External publishing does not work with non-durable CAS stores; disabling");
@@ -82,8 +75,6 @@ sub init {
             callback  => sub { $self->_include_javascript(@_) },
             abortable => 1,
         );
-        Jifty->add_trigger( post_init => sub { $self->generate_javascript })
-            if $self->generate_early and not $self->external_publish;
     }
 
     if ( $self->css_enabled ) {
@@ -92,8 +83,6 @@ sub init {
             callback => sub { $self->_include_css(@_) },
             abortable => 1,
         );
-        Jifty->add_trigger( post_init => sub { $self->generate_css })
-            if $self->generate_early and not $self->external_publish;
     }
 }
 

commit 77a44eb946c4abc8215d3a1e595a6ef6207e8afe
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Jun 25 20:05:59 2010 -0400

    Add a tiered CAS store; this allows a combination of durability and speed
    
    The most frequent use is likely a memory or memcached store in front of
    a durable file store.  Writes are pushed through all layers, and reads
    stop on the first hit.  Cache misses in higher tiers are written back on
    a later successful read.

diff --git a/lib/Jifty/CAS.pm b/lib/Jifty/CAS.pm
index e8a9351..9f96808 100644
--- a/lib/Jifty/CAS.pm
+++ b/lib/Jifty/CAS.pm
@@ -40,6 +40,9 @@ instances of a Jifty app using L<Jifty::CAS>.  The memcached store is
 limited to objects less than 1MB in size, however.
 L<Jifty::CAS::Store::LocalFile> provides a durable store, which is
 well-suited for sharing the cache across instances and restarts.
+Because of its speed, however, L<Jifty::CAS::Store::Nested> is suggested
+to provide a layered cache, most probably with fast memory-based cache
+in front of a durable, file-based cache.
 
 =head1 METHODS
 
diff --git a/lib/Jifty/CAS/Store/Nested.pm b/lib/Jifty/CAS/Store/Nested.pm
new file mode 100644
index 0000000..9213027
--- /dev/null
+++ b/lib/Jifty/CAS/Store/Nested.pm
@@ -0,0 +1,128 @@
+use strict;
+use warnings;
+
+package Jifty::CAS::Store::Nested;
+use Any::Moose;
+extends 'Jifty::CAS::Store';
+has 'parts' => (is => 'rw');
+
+=head1 NAME
+
+Jifty::CAS::Store::Nested - A layered CAS store
+
+=head1 DESCRIPTION
+
+This is a layered backend for L<Jifty::CAS>, which provides a way to
+combine multiple CAS backends.  Writes are passed through to every
+layer, whereas reads stop on the first layer which contains the data.
+This allows a fast in-memory store to be layered on top of a durable
+file store, for instance.
+
+Configuration requires providing two or more CAS classes:
+
+    framework:
+      CAS:
+        Default:
+          Class: Jifty::CAS::Store::Nested
+          Parts:
+            - Class: Jifty::CAS::Store::Memory
+            - Class: Jifty::CAS::Store::LocalFile
+              Path: %var/cas%
+
+=head1 METHODS
+
+=head2 BUILD
+
+Constructs the sub-parts and stores them.
+
+=cut
+
+sub BUILD {
+    my $self = shift;
+    my @parts;
+    for my $part (@{ $self->parts || [] }) {
+        my %part = %{ $part };
+        my $storeclass = delete $part{Class};
+        Jifty::Util->require( $storeclass );
+        push @parts, $storeclass->new(
+            map {lc $_ => $part->{$_}} grep {$_ ne "Class"} keys %part
+        );
+    }
+    $self->parts( \@parts );
+}
+
+=head2 _store DOMAIN NAME BLOB
+
+Stores the BLOB (a L<Jifty::CAS::Blob>) in all parts, starting at the
+bottom.  Returns the key on success or undef on failure.
+
+=cut
+
+sub _store {
+    my ($self, $domain, $name, $blob) = @_;
+    # Writes start on the bottom
+    $_->_store($domain, $name, $blob) for reverse @{ $self->parts };
+}
+
+=head2 key DOMAIN NAME
+
+Returns the most recent key for the given pair of C<DOMAIN> and
+C<NAME>, or undef if none such exists.
+
+=cut
+
+sub key {
+    my ($self, $domain, $name) = @_;
+    # Reads start at the top
+    my @missing;
+    my $found;
+    for my $part (@{$self->parts}) {
+        if ($found = $part->key($domain, $name)) {
+            if (@missing) {
+                # If there were cache misses higher on the stack, write
+                # the correct value back to them
+                my $blob = $part->retrieve($domain, $found);
+                $_->_store($domain, $name, $blob) for @missing;
+            }
+            return $found;
+        }
+        push @missing, $part;
+    }
+    return;
+}
+
+=head2 retrieve DOMAIN KEY
+
+Returns a L<Jifty::CAS::Blob> for the given pair of C<DOMAIN> and
+C<KEY>, or undef if none such exists.
+
+=cut
+
+sub retrieve {
+    my ($self, $domain, $key) = @_;
+    # We don't have a way of storing just the blob at a key location,
+    # so we can't freshen the higher levels in this case.
+    for my $part (@{$self->parts}) {
+        my $found = $part->retrieve($domain, $key);
+        return $found if $found;
+    }
+    return;
+}
+
+=head2 durable
+
+If any of the parts are durable, the entire nested CAS backend is durable.
+
+=cut
+
+sub durable {
+    my $self = shift;
+    for my $part (@{$self->parts}) {
+        return 1 if $part->durable;
+    }
+    return 0;
+}
+
+no Any::Moose;
+__PACKAGE__->meta->make_immutable(inline_constructor => 0);
+1;

-----------------------------------------------------------------------


More information about the Jifty-commit mailing list