[Jifty-commit] r2071 - in jifty/trunk: . lib lib/Jifty lib/Jifty/Action lib/Jifty/Plugin/REST lib/Jifty/Script lib/Jifty/Subs lib/Jifty/Upgrade lib/Jifty/Web share/web/static/js share/web/static/js/jsan share/web/templates/= share/web/templates/_elements t/TestApp/t

jifty-commit at lists.jifty.org jifty-commit at lists.jifty.org
Fri Oct 27 03:58:35 EDT 2006


Author: jesse
Date: Fri Oct 27 03:58:30 2006
New Revision: 2071

Added:
   jifty/trunk/lib/Jifty/Event.pm
   jifty/trunk/lib/Jifty/Subs/
   jifty/trunk/lib/Jifty/Subs.pm   (contents, props changed)
   jifty/trunk/lib/Jifty/Subs/Render.pm   (contents, props changed)
   jifty/trunk/share/web/static/js/jifty_subs.js
   jifty/trunk/share/web/static/js/jsan/Push.js
   jifty/trunk/share/web/templates/=/
   jifty/trunk/share/web/templates/=/subs
   jifty/trunk/t/TestApp/t/instance_id.t
Modified:
   jifty/trunk/   (props changed)
   jifty/trunk/MANIFEST
   jifty/trunk/Makefile.PL
   jifty/trunk/lib/Jifty.pm
   jifty/trunk/lib/Jifty/Action.pm
   jifty/trunk/lib/Jifty/Action/Record.pm
   jifty/trunk/lib/Jifty/Action/Record/Create.pm
   jifty/trunk/lib/Jifty/Action/Record/Delete.pm
   jifty/trunk/lib/Jifty/Action/Record/Update.pm
   jifty/trunk/lib/Jifty/ClassLoader.pm
   jifty/trunk/lib/Jifty/Config.pm
   jifty/trunk/lib/Jifty/Everything.pm
   jifty/trunk/lib/Jifty/Handle.pm
   jifty/trunk/lib/Jifty/Handler.pm
   jifty/trunk/lib/Jifty/Plugin/REST/Dispatcher.pm
   jifty/trunk/lib/Jifty/Script/Schema.pm
   jifty/trunk/lib/Jifty/Script/Server.pm
   jifty/trunk/lib/Jifty/Upgrade/Internal.pm
   jifty/trunk/lib/Jifty/Web.pm
   jifty/trunk/lib/Jifty/Web/PageRegion.pm
   jifty/trunk/share/web/static/js/jifty.js
   jifty/trunk/share/web/templates/_elements/wrapper

Log:
Merge of the Jifty pubsub hackathon branch.

Audrey, Jesse, CL and Schwern hacked out a PubSub message bus and a
preliminary Comet implementation.  

 
 ----------------------------------------------------------------------
 r29425 (orig r147):  (no author) | 2006-10-27 00:34:15 -0700
 
  r29424 at pinglin:  jesse | 2006-10-27 00:34:14 -0700
   * Removing some warnings
 
 ----------------------------------------------------------------------
 r29423 (orig r146):  (no author) | 2006-10-27 00:21:05 -0700
 
  r29422 at pinglin:  jesse | 2006-10-27 00:21:02 -0700
  * Action monikers didn't always work in a non-jifty context, which broke tests for hiveminder.com
 
 ----------------------------------------------------------------------
 r29419 (orig r143):  (no author) | 2006-10-26 16:53:16 -0700
 
  r29416 at pinglin:  jesse | 2006-10-26 16:50:28 -0700
  * ability to enable and disable pubsub programatically
 
 ----------------------------------------------------------------------
 r29410 (orig r138):  (no author) | 2006-10-25 23:12:31 -0700
 
 pull jifty trunk.
 ----------------------------------------------------------------------
 r29406 (orig r136):  (no author) | 2006-10-25 22:08:31 -0700
 
  r29404 at pinglin:  jesse | 2006-10-25 22:07:30 -0700
  * Events on record actions
 
 ----------------------------------------------------------------------
 r29401 (orig r133):  (no author) | 2006-10-25 21:38:02 -0700
 
 Have Jifty.Subs automagically created when we have any subscrption.
 ----------------------------------------------------------------------
 r29389 (orig r128):  (no author) | 2006-10-25 18:19:53 -0700
 
 * Jifty::Even - Remove two warnings.
 ----------------------------------------------------------------------
 r29383 (orig r124):  (no author) | 2006-10-25 17:54:36 -0700
 
 * Jifty::Event - add ->data
 * Jifty::Subs - allow unsubscribing on channel name alone
 ----------------------------------------------------------------------
 r29382 (orig r123):  (no author) | 2006-10-25 17:53:02 -0700
 
 * Jifty::Subs::Render: If there's zero subscription don't die
   with weird can't call ->get_all method.
 ----------------------------------------------------------------------
 r29380 (orig r122):  (no author) | 2006-10-25 17:47:21 -0700
 
 Refactor the region rendering using subrequest bit.
 ----------------------------------------------------------------------
 r29376 (orig r120):  (no author) | 2006-10-25 17:27:36 -0700
 
 * Jifty->subs->list
 ----------------------------------------------------------------------
 r29375 (orig r119):  (no author) | 2006-10-25 17:26:20 -0700
 
 * Jifty->Subs->cancel
 ----------------------------------------------------------------------
 r29374 (orig r118):  (no author) | 2006-10-25 17:25:13 -0700
 
 Refactor /=/subs/ into Jifty::Sub::Render.
 
 ----------------------------------------------------------------------
 r29371 (orig r117):  (no author) | 2006-10-25 17:12:06 -0700
 
 subs
 ----------------------------------------------------------------------
 r29368 (orig r115):  (no author) | 2006-10-25 16:59:04 -0700
 
 * $r is now $region
 ----------------------------------------------------------------------
 r29367 (orig r114):  (no author) | 2006-10-25 16:57:11 -0700
 
 * Sanity, ergonomics and misc. review from obra++
 ----------------------------------------------------------------------
 r29366 (orig r113):  (no author) | 2006-10-25 16:30:20 -0700
 
 User-defined events are now auto-loaded.
 
 ----------------------------------------------------------------------
 r29363 (orig r110):  (no author) | 2006-10-25 16:22:29 -0700
 
 * Jifty: Updating subs to take advantage of the new Jifty::Event model
 ----------------------------------------------------------------------
 r29361 (orig r108):  (no author) | 2006-10-25 15:34:40 -0700
 
 Use bus modify to store subs and renderinfo.
 ----------------------------------------------------------------------
 r29357 (orig r105):  (no author) | 2006-10-25 15:16:28 -0700
 
  r29354 at pinglin:  jesse | 2006-10-25 15:15:30 -0700
  * doc updates
 
 ----------------------------------------------------------------------
 r29356 (orig r104):  (no author) | 2006-10-25 15:16:09 -0700
 
  r29348 at pinglin:  jesse | 2006-10-25 15:11:33 -0700
   * Event.pm doc updates
 
 ----------------------------------------------------------------------
 r29351 (orig r101):  (no author) | 2006-10-25 15:13:49 -0700
 
 * Republished query order no longer matters.
 ----------------------------------------------------------------------
 r29350 (orig r100):  (no author) | 2006-10-25 15:13:19 -0700
 
 default subs channel now works.
 ----------------------------------------------------------------------
 r29349 (orig r99):  (no author) | 2006-10-25 15:08:30 -0700
 
 * send payload
 ----------------------------------------------------------------------
 r29346 (orig r98):  (no author) | 2006-10-25 15:05:21 -0700
 
 * Jifty::Even 0th cut.
 ----------------------------------------------------------------------
 r29343 (orig r96):  (no author) | 2006-10-25 14:31:32 -0700
 
  r29339 at pinglin:  jesse | 2006-10-25 14:30:49 -0700
  * Added event baseclasses
 
 ----------------------------------------------------------------------
 r29341 (orig r94):  (no author) | 2006-10-25 14:18:05 -0700
 
 instance_id -> app_instance_id.
 
 ----------------------------------------------------------------------
 r29331 (orig r89):  (no author) | 2006-10-25 12:11:19 -0700
 
 * Jifty.pm: Init PubSub always
 ----------------------------------------------------------------------
 r29326 (orig r84):  (no author) | 2006-10-25 10:34:49 -0700
 
  r29324 at pinglin:  jesse | 2006-10-25 10:33:38 -0700
  * The messagebus now falls back to jiftydbi
 
 ----------------------------------------------------------------------
 r29320 (orig r82):  (no author) | 2006-10-25 10:06:56 -0700
 
 * Use instance_id in memcached.
 ----------------------------------------------------------------------
 r29319 (orig r81):  (no author) | 2006-10-25 10:06:37 -0700
 
  r29318 at pinglin:  jesse | 2006-10-25 10:06:07 -0700
  * Added a unique instance ID per instance of your Jifty application (stored in the db)
 
 ----------------------------------------------------------------------
 r29315 (orig r79):  (no author) | 2006-10-25 10:00:06 -0700
 
 * PubSub 0.11
 ----------------------------------------------------------------------
 r29314 (orig r78):  (no author) | 2006-10-25 09:59:42 -0700
 
 * Port to use IPC::PubSub
 ----------------------------------------------------------------------
 r29307 (orig r73):  (no author) | 2006-10-25 00:23:15 -0700
 
 subscription channel.
 ----------------------------------------------------------------------
 r29306 (orig r72):  (no author) | 2006-10-25 00:03:29 -0700
 
 *default for J::S
 ----------------------------------------------------------------------
 r29305 (orig r71):  (no author) | 2006-10-25 00:00:21 -0700
 
 add viewmap
 ----------------------------------------------------------------------
 r29303 (orig r69):  (no author) | 2006-10-24 23:48:08 -0700
 
 primitive jifty->subs.
 ----------------------------------------------------------------------
 r29297 (orig r63):  (no author) | 2006-10-24 22:51:12 -0700
 
 subscription related js files.
 ----------------------------------------------------------------------
 r29289 (orig r55):  (no author) | 2006-10-24 22:04:01 -0700
 
 * Jifty::Script::Server - Allow custom Server backends to allow
   forking support from a standalone httpd.
 ----------------------------------------------------------------------
 r29288 (orig r54):  (no author) | 2006-10-24 22:03:44 -0700
 
 * jifty.js: Allow multiple message chunks.
 ----------------------------------------------------------------------
 r29282 (orig r48):  (no author) | 2006-10-24 19:32:25 -0700
 
 tolower case for content nodeName.
 ----------------------------------------------------------------------
 r29278 (orig r44):  (no author) | 2006-10-24 19:17:43 -0700
 
 more update refactor
 ----------------------------------------------------------------------
 r29276 (orig r42):  (no author) | 2006-10-24 19:10:28 -0700
 
 more update refactor.
 ----------------------------------------------------------------------
 r29263 (orig r35):  (no author) | 2006-10-24 18:46:39 -0700
 
 refactor prepare_element_for_update
 ----------------------------------------------------------------------
 r29249 (orig r26):  (no author) | 2006-10-24 18:19:37 -0700
 
 publish jifty trunk
 ----------------------------------------------------------------------
 r29248 (orig r25):  (no author) | 2006-10-24 18:18:15 -0700
 
 push!
 ----------------------------------------------------------------------
 

Modified: jifty/trunk/MANIFEST
==============================================================================
--- jifty/trunk/MANIFEST	(original)
+++ jifty/trunk/MANIFEST	Fri Oct 27 03:58:30 2006
@@ -45,6 +45,7 @@
 inc/Module/Install/WriteAll.pm
 lib/Email/Send/Jifty/Test.pm
 lib/Jifty.pm
+lib/Jifty/Subs.pm
 lib/Jifty/Action.pm
 lib/Jifty/Action/Autocomplete.pm
 lib/Jifty/Action/Record.pm

Modified: jifty/trunk/Makefile.PL
==============================================================================
--- jifty/trunk/Makefile.PL	(original)
+++ jifty/trunk/Makefile.PL	Fri Oct 27 03:58:30 2006
@@ -40,6 +40,7 @@
 requires('HTTP::Server::Simple::Recorder');
 requires('Hash::Merge');
 requires('Hook::LexWrap');
+requires('IPC::PubSub' => '0.11' );
 requires('Jifty::DBI' => '0.25' );            # Jifty::DBI::Collection Jifty::DBI::Handle Jifty::DBI::Record::Cachable Jifty::DBI::SchemaGenerator
 requires('Locale::Maketext::Extract' => '0.20');
 requires('Locale::Maketext::Lexicon' => '0.60');

Modified: jifty/trunk/lib/Jifty.pm
==============================================================================
--- jifty/trunk/lib/Jifty.pm	(original)
+++ jifty/trunk/lib/Jifty.pm	Fri Oct 27 03:58:30 2006
@@ -2,10 +2,11 @@
 use strict;
 
 package Jifty;
+use IPC::PubSub;
 use encoding 'utf8';
 # Work around the fact that Time::Local caches thing on first require
 BEGIN { local $ENV{'TZ'} = "GMT";  require Time::Local;}
-$Jifty::VERSION = '0.60912';
+$Jifty::VERSION = '0.61025';
 
 =head1 NAME
 
@@ -62,7 +63,7 @@
 use base qw/Jifty::Object/;
 use Jifty::Everything;
 
-use vars qw/$HANDLE $CONFIG $LOGGER $HANDLER $API $CLASS_LOADER @PLUGINS/;
+use vars qw/$HANDLE $CONFIG $LOGGER $HANDLER $API $CLASS_LOADER $PUB_SUB @PLUGINS/;
 
 =head1 METHODS
 
@@ -245,6 +246,51 @@
     return $HTML::Mason::Commands::JiftyWeb;
 }
 
+=head2 subs
+
+An accessor for the L<Jifty::Subs> object that the subscription uses. 
+
+=cut
+
+sub subs {
+    return Jifty::Subs->new;
+}
+
+=head2 bus
+
+Returns an IPC::PubSub object for the current application.
+
+=cut
+
+sub bus {
+
+    unless ($PUB_SUB) {
+        my @args;
+
+        my $backend = Jifty->config->framework('PubSub')->{'Backend'};
+        if ( $backend eq 'Memcached' ) {
+            require IO::Socket::INET;
+
+            # If there's a running memcached on the default port. this should become configurable
+            if ( IO::Socket::INET->new('127.0.0.1:11211') ) {
+                @args = ( Jifty->app_instance_id );
+            } else {
+                $backend = 'JiftyDBI';
+            }
+        } 
+        
+        if ($backend eq 'JiftyDBI' ) {
+                @args    = (
+                    db_config    => Jifty->handle->{db_config},
+                    table_prefix => '_jifty_pubsub_',
+                );
+            }
+        $PUB_SUB = IPC::PubSub->new( $backend => @args );
+
+    }
+    return $PUB_SUB;
+}
+
 =head2 plugins
 
 Returns a list of L<Jifty::Plugin> objects for this Jifty application.
@@ -306,9 +352,28 @@
     }
 }
 
+
+=head2 app_instance_id
+
+Returns a globally unique id for this instance of this jifty 
+application. This value is generated the first time it's accessed
+
+=cut
+
+sub app_instance_id {
+    my $self = shift;
+    my $app_instance_id = Jifty::Model::Metadata->load("application_instance_uuid");
+    unless ($app_instance_id) {
+        $app_instance_id = Data::UUID->new->create_str();
+        Jifty::Model::Metadata->store(application_instance_uuid => $app_instance_id );
+    }
+    return $app_instance_id;
+}
+
+
 =head1 LICENSE
 
-Jifty is Copyright 2005 Best Practical Solutions, LLC.
+Jifty is Copyright 2005-2006 Best Practical Solutions, LLC.
 Jifty is distributed under the same terms as Perl itself.
 
 =head1 SEE ALSO

Modified: jifty/trunk/lib/Jifty/Action.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action.pm	(original)
+++ jifty/trunk/lib/Jifty/Action.pm	Fri Oct 27 03:58:30 2006
@@ -170,7 +170,9 @@
 
     # Increment the per-request moniker digest counter, for the case of looped action generation
     my $digest = md5_hex("@stack");
-    my $serial = ++(Jifty->handler->stash->{monikers}{$digest});
+    # We should always have a stash. but if we don't, fake something up
+    # (some hiveminder tests create actions outside of a Jifty::Web)
+    my $serial = Jifty->handler->stash ? ++(Jifty->handler->stash->{monikers}{$digest}) : rand();
     my $moniker = "auto-$digest-$serial";
     $self->log->debug("Generating moniker $moniker from stack for $self");
     return $moniker;

Modified: jifty/trunk/lib/Jifty/Action/Record.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Record.pm	(original)
+++ jifty/trunk/lib/Jifty/Action/Record.pm	Fri Oct 27 03:58:30 2006
@@ -1,6 +1,7 @@
 use warnings;
 use strict;
 use Date::Manip ();
+use UNIVERSAL::require;
 
 package Jifty::Action::Record;
 
@@ -332,6 +333,33 @@
     );
 }
 
+sub _setup_event_before_action {
+    my $self = shift;
+
+    my $event_info = {};
+    $event_info->{as_hash_before} = $self->record->as_hash;
+    $event_info->{record_id} = $self->record->id;
+    $event_info->{record_class} = ref($self->record);
+    $event_info->{action_class} = ref($self);
+    $event_info->{action_arguments} = $self->argument_values; # XXX does this work?
+    $event_info->{current_user_id} = $self->current_user->id || 0;
+    return ($event_info);
+}
+
+sub _setup_event_after_action {
+    my $self = shift;
+    my $event_info = shift;
+    $event_info->{result} = $self->result;    
+    $event_info->{timestamp} = time(); 
+    $event_info->{as_hash_after} = $self->record->as_hash;
+
+    my $event_class = $event_info->{'record_class'};
+    $event_class =~ s/::Model::/::Event::Model::/g;
+    Jifty::Util->require($event_class);
+    $event_class->new($event_info)->publish;
+}
+
+
 =head1 SEE ALSO
 
 L<Jifty::Action>, L<Jifty::Record>, L<Jifty::DBI::Record>,

Modified: jifty/trunk/lib/Jifty/Action/Record/Create.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Record/Create.pm	(original)
+++ jifty/trunk/lib/Jifty/Action/Record/Create.pm	Fri Oct 27 03:58:30 2006
@@ -56,6 +56,9 @@
     my $self   = shift;
     my $record = $self->record;
 
+    my $event_info = $self->_setup_event_before_action();
+    
+    
     my %values;
     # Virtual arguments aren't really ever backed by data structures. they're added by jifty for things like confirmations
     for (grep { defined $self->argument_value($_) && !$self->arguments->{$_}->{virtual} } $self->argument_names) {
@@ -74,18 +77,19 @@
         ($id,$msg) = $msg->as_array;
     }
 
-    unless ( $record->id ) {
+    if (! $record->id ) {
         $self->log->debug(_("Create of %1 failed: %2", ref($record), $msg));
         $self->result->error($msg || _("An error occurred.  Try again later"));
-        return;
     }
 
- 
-    # Return the id that we created
-    $self->result->content(id => $self->record->id);
-    $self->report_success if  not $self->result->failure;
+    else { 
+        # Return the id that we created
+        $self->result->content(id => $self->record->id);
+        $self->report_success if  not $self->result->failure;
+    }
+    $self->_setup_event_after_action($event_info) ;
 
-    return 1;
+    return ($self->record->id);
 }
 
 =head2 report_success

Modified: jifty/trunk/lib/Jifty/Action/Record/Delete.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Record/Delete.pm	(original)
+++ jifty/trunk/lib/Jifty/Action/Record/Delete.pm	Fri Oct 27 03:58:30 2006
@@ -52,12 +52,13 @@
 sub take_action {
     my $self = shift;
 
+    my $event_info = $self->_setup_event_before_action();
+
     my ( $val, $msg ) = $self->record->delete;
-    $self->result->error($msg)
-      if not $val and $msg;
+    $self->result->error($msg) if not $val and $msg;
 
-    $self->report_success
-      if not $self->result->failure;
+    $self->report_success if not $self->result->failure;
+    $self->_setup_event_after_action($event_info);
 
     return 1;
 }

Modified: jifty/trunk/lib/Jifty/Action/Record/Update.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Action/Record/Update.pm	(original)
+++ jifty/trunk/lib/Jifty/Action/Record/Update.pm	Fri Oct 27 03:58:30 2006
@@ -84,6 +84,8 @@
     my $self = shift;
     my $changed = 0;
 
+    my $event_info = $self->_setup_event_before_action();
+
     for my $field ( $self->argument_names ) {
         # Skip values that weren't submitted
         next unless $self->has_argument($field);
@@ -136,6 +138,8 @@
     $self->report_success
       if $changed and not $self->result->failure;
 
+    $self->_setup_event_after_action($event_info);
+
     return 1;
 }
 

Modified: jifty/trunk/lib/Jifty/ClassLoader.pm
==============================================================================
--- jifty/trunk/lib/Jifty/ClassLoader.pm	(original)
+++ jifty/trunk/lib/Jifty/ClassLoader.pm	Fri Oct 27 03:58:30 2006
@@ -53,6 +53,10 @@
 
 An empty class that descends from L<Jifty::Record> is created.
 
+=item I<Application>::Event
+
+An empty class that descends from L<Jifty::Event> is created.
+
 =item I<Application>::Collection
 
 An empty class that descends from L<Jifty::Collection> is created.
@@ -101,7 +105,6 @@
 # This subroutine's name is fully qualified, as perl will ignore a 'sub INC'
 sub Jifty::ClassLoader::INC {
     my ( $self, $module ) = @_;
-
     my $base = $self->{base};
     return undef unless ( $module and $base );
 
@@ -122,7 +125,7 @@
 #                . "use base qw/Jifty::Action/; sub _autogenerated { 1 };\n"
 #                . "1;" );
 #    }
-    elsif ( $module =~ m!^(?:$base)::(Record|Collection|Notification|Dispatcher|Bootstrap|Upgrade|Handle)$! ) {
+    elsif ( $module =~ m!^(?:$base)::(Record|Collection|Notification|Dispatcher|Bootstrap|Upgrade|Handle|Event|Event::Model)$! ) {
         return $self->return_class(
                   "use warnings; use strict; package $module;\n"
                 . "use base qw/Jifty::$1/; sub _autogenerated { 1 };\n"
@@ -138,6 +141,18 @@
                 . "use base qw/@{[$base]}::Collection/;\n"
                 . "sub record_class { '@{[$base]}::Model::$1' }\n"
                 . "1;" );
+    } elsif ( $module =~ m!^(?:$base)::Event::Model::([^\.]+)$! ) {
+        my $modelclass = $base . "::Model::" . $1;
+        Jifty::Util->require($modelclass);
+
+        return undef unless eval { $modelclass->table };
+
+        return $self->return_class(
+                  "use warnings; use strict; package $module;\n"
+                . "use base qw/${base}::Event::Model/;\n"
+                . "sub record_class { '$modelclass' };\n"
+                . "sub autogenerated { 1 };\n"
+                . "1;" );
     } elsif ( $module =~ m!^(?:$base)::Action::(Create|Update|Delete|Search)([^\.]+)$! ) {
         my $modelclass = $base . "::Model::" . $2;
         Jifty::Util->require($modelclass);
@@ -186,10 +201,9 @@
     return unless ($base); 
     Jifty::Util->require($base);
     Jifty::Util->require($base."::CurrentUser");
-
     Jifty::Module::Pluggable->import(
         search_path =>
-          [ map { $base . "::" . $_ } 'Model', 'Action', 'Notification' ],
+          [ map { $base . "::" . $_ } 'Model', 'Action', 'Notification', 'Event' ],
         require => 1,
         except  => qr/\.#/,
         inner   => 0

Modified: jifty/trunk/lib/Jifty/Config.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Config.pm	(original)
+++ jifty/trunk/lib/Jifty/Config.pm	Fri Oct 27 03:58:30 2006
@@ -241,6 +241,9 @@
             ApplicationClass => $app_class,
             ApplicationName  => $app_name,
             LogLevel         => 'INFO',
+            PubSub           => {
+                        Backend => 'Memcached',
+                    },
             Database         => {
                 Database =>  $db_name,
                 Driver   => "SQLite",

Added: jifty/trunk/lib/Jifty/Event.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Event.pm	Fri Oct 27 03:58:30 2006
@@ -0,0 +1,143 @@
+use warnings;
+use strict;
+
+package Jifty::Event;
+
+use Storable 'nfreeze';
+use Digest::MD5 qw(md5_hex);
+use vars qw/%PUBLISHER/;
+
+=head1 NAME
+
+Jifty::Event
+
+=head1 DESCRIPTION
+
+An event object from the Jifty::PubSub stream.
+
+=head1 METHODS
+
+=head2 new($payload)
+
+Constructor.  Takes any kind of payload and blesses a scalar reference to it
+into an Event object.
+
+=cut
+
+sub new {
+    my $class   = shift;
+    my $payload = shift;
+    bless \$payload, $class;
+}
+
+=head2 publish()
+
+Inserts the event into the pubsub stream.  If Jifty is configured into
+synchronous republishing, then this method runs a C<republish> on itself
+with all current subscriptions implicitly.  If not, it's simply inserted
+into its main channel for asynchronous republishing later.  
+
+=cut
+
+sub publish {
+    my $self  = shift;
+    my $class = ref($self) || $self;
+
+    return undef unless (Jifty->config->framework('PubSub')->{'Enable'});
+
+    # Always publish to the main stream (needed for async & debugging)
+    # if ($ASYNC || $DEBUGGING) {
+    #    ($PUBLISHER{$class} ||= Jifty->bus->new_publisher($class))->msg($$self);
+    #    return;
+    # }
+
+    # Synchronized auto-republishing
+    # TODO - Prioritize current-user subscriptions first?
+    my $subscriptions = Jifty->bus->modify("$class-subscriptions") || {};
+    while (my ($channel, $queries) = each %$subscriptions) {
+        if ($self->filter(@$queries)) {
+            ($PUBLISHER{$channel} ||= Jifty->bus->new_publisher($channel))->msg($$self);
+        }
+    }
+}
+
+=head2 filter(@query)
+
+Takes multiple class-specific queries, which are evaluated in order by calling L</match>.
+
+=cut
+
+sub filter {
+    my $self = shift;
+    $self->match($_) or return 0 for @_;
+    return 1;
+}
+
+=head2 republish(@query)
+
+Run C<filter> with the queries; if they all succeed, the event is republished
+into that query-specific channel.
+
+=cut
+
+sub republish {
+    my $self = shift;
+    $self->filter(@_) or return;
+
+    my $channel = $self->encode_queries(@_);
+    ($PUBLISHER{$channel} ||= Jifty->bus->new_publisher($channel))->msg($$self);
+}
+
+
+=head2 encode_queries(@query)
+
+Encode queries into some sort of canonical md5 encoding.
+
+=cut
+
+sub encode_queries {
+    my $self    = shift;
+    my $class   = ref($self) || $self;
+    return $class unless @_;
+
+    local $Storable::canonical = 1;
+    return $class . '-' . md5_hex(join('', sort map { Storable::nfreeze($_) } @_));
+}
+
+
+=head2 match($query)
+
+Takes a class-specific query and returns whether it matches.
+
+You almost always want to override this; the default implementation
+simply always return true;
+
+=cut
+
+sub match {
+    1;
+}
+
+=head2 render_arguments()
+
+A list of additional things to push into the C<%ARGS> of the region that
+is about to render this event; see L<Jifty::Subs::Render> for more information.
+
+=cut
+
+sub render_arguments {
+    ();
+}
+
+=head2 data()
+
+This event's payload as a scalar value.
+
+=cut
+
+sub data {
+    ${$_[0]}
+}
+
+
+1;

Modified: jifty/trunk/lib/Jifty/Everything.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Everything.pm	(original)
+++ jifty/trunk/lib/Jifty/Everything.pm	Fri Oct 27 03:58:30 2006
@@ -73,6 +73,9 @@
 use Jifty::Web::Form::Field ();
 use Jifty::Web::Menu ();
 
+use Jifty::Subs ();
+use Jifty::Subs::Render ();
+
 use Jifty::Module::Pluggable;
 Jifty::Module::Pluggable->import(search_path => ['Jifty::Web::Form::Field'],
                           require     => 1,

Modified: jifty/trunk/lib/Jifty/Handle.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Handle.pm	(original)
+++ jifty/trunk/lib/Jifty/Handle.pm	Fri Oct 27 03:58:30 2006
@@ -90,6 +90,7 @@
         $lc_db_config{lc($_)} = $db_config{$_};
     }
     $self->SUPER::connect( %lc_db_config , %args);
+    $self->{db_config} = { %lc_db_config , %args };
     $self->dbh->{LongReadLen} = Jifty->config->framework('MaxAttachmentSize') || '10000000';
 }
 

Modified: jifty/trunk/lib/Jifty/Handler.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Handler.pm	(original)
+++ jifty/trunk/lib/Jifty/Handler.pm	Fri Oct 27 03:58:30 2006
@@ -70,7 +70,7 @@
     Jifty::Util->require( $self->dispatcher );
     $self->dispatcher->import_plugins;
     $self->dispatcher->dump_rules;
-
+    
     $self->mason( Jifty::View::Mason::Handler->new( $self->mason_config ) );
 
     $self->static_handler(Jifty::View::Static::Handler->new());

Modified: jifty/trunk/lib/Jifty/Plugin/REST/Dispatcher.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Plugin/REST/Dispatcher.pm	(original)
+++ jifty/trunk/lib/Jifty/Plugin/REST/Dispatcher.pm	Fri Oct 27 03:58:30 2006
@@ -49,7 +49,6 @@
     if($prefix) {
         @prefix = map {s/::/./g; $_} @$prefix;
          $url    = Jifty->web->url(path => join '/', '=', at prefix);
-    warn "my preifx is ".join(',', at prefix) .$url;     
     }
 
 
@@ -109,7 +108,6 @@
     my $prefix = shift;
     my $url = shift;
     my $content = shift;
-    warn "REndering $prefix $url $content";
     if (ref($content) eq 'ARRAY') {
         return start_html(-encoding => 'UTF-8', -declare_xml => 1, -title => 'models'),
               ul(map {

Modified: jifty/trunk/lib/Jifty/Script/Schema.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Script/Schema.pm	(original)
+++ jifty/trunk/lib/Jifty/Script/Schema.pm	Fri Oct 27 03:58:30 2006
@@ -272,6 +272,15 @@
         # Commit it all
         Jifty->handle->commit;
     }
+
+    Jifty::Util->require('IPC::PubSub');
+    IPC::PubSub->new(
+        JiftyDBI => (
+            db_config    => Jifty->handle->{db_config},
+            table_prefix => '_jifty_pubsub_',
+            db_init      => 1,
+        )
+    );
     $log->info("Set up version $appv, jifty version $jiftyv");
 }
 

Modified: jifty/trunk/lib/Jifty/Script/Server.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Script/Server.pm	(original)
+++ jifty/trunk/lib/Jifty/Script/Server.pm	Fri Oct 27 03:58:30 2006
@@ -18,7 +18,6 @@
 
 
 use Jifty::Everything;
-use Jifty::Server;
 use File::Path ();
 
 use constant PIDFILE => 'var/jifty-server.pid';
@@ -72,6 +71,9 @@
 
     # Purge stale mason cache data
     my $data_dir = Jifty->config->framework('Web')->{'DataDir'};
+    my $server_class = Jifty->config->framework('Web')->{'ServerClass'} || 'Jifty::Server';
+    Jifty::Util->require($server_class);
+
     if (-d $data_dir) {
         File::Path::rmtree(["$data_dir/cache", "$data_dir/obj"]);
     }
@@ -86,10 +88,10 @@
 
     $ENV{JIFTY_SERVER_SIGREADY} ||= $self->{sigready}
         if $self->{sigready};
-    Log::Log4perl->get_logger("Jifty::Server")->less_logging(3)
+    Log::Log4perl->get_logger($server_class)->less_logging(3)
         if $self->{quiet};
 
-    Jifty::Server->new(port => $self->{port})->run;
+    $server_class->new(port => $self->{port})->run;
 }
 
 1;

Added: jifty/trunk/lib/Jifty/Subs.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Subs.pm	Fri Oct 27 03:58:30 2006
@@ -0,0 +1,131 @@
+use warnings;
+use strict;
+
+package Jifty::Subs;
+
+
+use constant new => __PACKAGE__;
+
+=head1 NAME
+
+Jifty::Subs - 
+
+=head1 SYNOPSIS
+
+ my $sid = Jifty->subs->add(
+    class       => 'Tick',
+    queries     => [{ like => '9' }],
+    mode        => 'Replace',
+    region      => "clock-time",
+    render_with => '/fragments/time',
+ );
+ Jifty->subs->cancel($sid);
+
+ my @sids = Jifty->subs->list;
+
+=head1 DESCRIPTION
+
+
+
+=cut
+
+sub add {
+    my $class = shift;
+    my $args = {@_};
+   unless (Jifty->config->framework('PubSub')->{'Enable'}) {
+        Jifty->log->error("PubSub disabled, but $class->add called");
+        return undef
+    }
+
+    my $id          = ($args->{window_id} || Jifty->web->session->id);
+    my $event_class = join('::' =>
+        Jifty->config->framework("ApplicationClass"),
+        'Event',
+        $args->{class},
+    );
+
+    my $queries = $args->{queries} || [];
+    my $channel = $event_class->encode_queries(@$queries);
+
+    # The ->modify here is calling into the callback sub{...} with
+    # the previous value of $_, that is a hashref of channels to
+    # queries associated with those channels.  The callback then
+    # massages it to add a new channel/queries mapping; the value
+    # of $_ at the end of the callback is then atomically updated
+    # into the message bus under the same key.
+    Jifty->bus->modify(
+        "$event_class-subscriptions" => sub {
+            $_->{$channel} = $queries;
+        }
+    );
+
+    # The per-window/session ($id) rendering information ("$id-render")
+    # contains a hash from subscribed channels to rendering information,
+    # including the frament, region, argument and ajax updating mode.
+    Jifty->bus->modify(
+        "$id-render" => sub {
+            $_->{$channel} = {
+                map { $_ => $args->{$_} }
+                    qw/render_with region arguments mode/
+            };
+        }
+    );
+
+    # We create/update a IPC::PubSub::Subscriber object for this $id,
+    # and have it subscribe to the channel that we're adding here.
+    Jifty->bus->modify(
+        "$id-subscriber" => sub {
+            if   ($_) { $_->subscribe($channel) }
+            else      { $_ = Jifty->bus->new_subscriber($channel) }
+        }
+    );
+
+    return "$channel!$id";
+}
+
+sub cancel {
+    my ($class, $channel_id) = @_;
+
+   unless (Jifty->config->framework('PubSub')->{'Enable'}) {
+        Jifty->log->error("PubSub disabled, but $class->add called");
+        return undef
+    }
+
+    my ($channel, $id) = split(/!/, $channel_id, 2);
+    my ($event_class)  = split(/-/, $channel);
+
+    $id ||= Jifty->web->session->id;
+
+    Jifty->bus->modify(
+        "$event_class-subscriptions" => sub {
+            delete $_->{$channel};
+        }
+    );
+
+    Jifty->bus->modify(
+        "$id-render" => sub {
+            delete $_->{$channel}
+        }
+    );
+
+    Jifty->bus->modify(
+        "$id-subscriber" => sub {
+            if ($_) { $_->unsubscribe($channel) }
+        }
+    );
+}
+
+sub list {
+    my $self = shift;
+
+   unless (Jifty->config->framework('PubSub')->{'Enable'}) {
+        Jifty->log->error("PubSub disabled, but $self->add called");
+        return undef
+    }
+
+    my $id   = (shift || Jifty->web->session->id);
+    my $subscribe = Jifty->bus->modify( "$id-subscriber" ) or return ();
+    return $subscribe->channels;
+}
+
+1;

Added: jifty/trunk/lib/Jifty/Subs/Render.pm
==============================================================================
--- (empty file)
+++ jifty/trunk/lib/Jifty/Subs/Render.pm	Fri Oct 27 03:58:30 2006
@@ -0,0 +1,80 @@
+package Jifty::Subs::Render;
+use strict;
+use warnings;
+
+=head1 NAME
+
+Jifty::Subs::Render - 
+
+=head1 SYNOPSIS
+
+  Jifty::Subs::Render->render($id, $callback);
+
+=head1 DESCRIPTION
+
+
+
+=head2 render($id, $callback)
+
+Render all outstanding messges, and call C<$callback> with render
+mode, region name, and content.
+
+=cut
+
+sub render {
+    my ( $class, $id, $callback ) = @_;
+    my $got;
+
+    # Get the IPC::PubSub::Subscriber object and do one fetch of all new
+    # events it subscribes to, and put those into $got.
+    my $subs
+        = Jifty->bus->modify( "$id-subscriber", sub { $got = $_ ? $_->get_all : {} } );
+
+    return 0 unless %$got;
+
+    # Now we the render options for those channels (calling ->modify instead
+    # of ->fetch because we want to block if someone else is touching it;
+    # it's equivalent to ->modify("$id-render", sub { $_ }).
+    my $render = Jifty->bus->modify("$id-render");
+
+    while ( my ( $channel, $msgs ) = each(%$got) ) {
+        foreach my $rv (@$msgs) {
+
+            # XXX - We don't yet use $timestamp here.
+            my ( $timestamp, $msg ) = @$rv;
+
+            # Channel name is always App::Event::Class-MD5QUERIES
+            my $event_class = $channel;
+            $event_class =~ s/-.*//;
+
+            unless ( UNIVERSAL::can( $event_class => 'new' ) ) {
+                Jifty->log->error("Receiving unknown event $event_class from the Bus");
+                $event_class = Jifty->config->framework('ApplicationClass')."::Event";
+            }
+
+            my $render_info = $render->{$channel};
+            my $region      = Jifty::Web::PageRegion->new(
+                name => $render_info->{region},
+                path => $render_info->{render_with},
+            );
+            delete Jifty->web->{'regions'}{ $region->qualified_name };
+
+            # Finally render the region.  In addition to the user-supplied arguments
+            # in $render_info, we always pass the target $region and the event object
+            # into its %ARGS.
+            my $region_content = '';
+            my $event_object   = $event_class->new($msg);
+            $region->render_as_subrequest( \$region_content,
+                {   %{ $render_info->{arguments} || {} },
+                    event => $event_object,
+                    $event_object->render_arguments,
+                }
+            );
+            $callback->(
+                $render_info->{mode}, $region->qualified_name, $region_content
+            );
+        }
+    }
+}
+
+1;

Modified: jifty/trunk/lib/Jifty/Upgrade/Internal.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Upgrade/Internal.pm	(original)
+++ jifty/trunk/lib/Jifty/Upgrade/Internal.pm	Fri Oct 27 03:58:30 2006
@@ -17,6 +17,17 @@
 
 =cut
 
+since '0.61025' => sub {
+    Jifty::Util->require('IPC::PubSub');
+    IPC::PubSub->new(
+        JiftyDBI => (
+            db_config    => Jifty->handle->{db_config},
+            table_prefix => '_jifty_pubsub_',
+            db_init      => 1,
+        )
+    );
+};
+
 since '0.60427' => sub {
     my @v = Jifty->handle->fetch_result("SELECT major, minor, rev FROM _db_version");
     Jifty->handle->simple_query("DROP TABLE _db_version");

Modified: jifty/trunk/lib/Jifty/Web.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Web.pm	(original)
+++ jifty/trunk/lib/Jifty/Web.pm	Fri Oct 27 03:58:30 2006
@@ -31,6 +31,7 @@
 
 __PACKAGE__->javascript_libs([qw(
     jsan/JSAN.js
+    jsan/Push.js
     setup_jsan.js
     jsan/Upgrade/Array/push.js
     jsan/DOM/Events.js
@@ -47,6 +48,7 @@
     formatDate.js
     jifty.js
     jifty_utils.js
+    jifty_subs.js
     jifty_smoothscroll.js
     calendar.js
     dom-drag.js

Modified: jifty/trunk/lib/Jifty/Web/PageRegion.pm
==============================================================================
--- jifty/trunk/lib/Jifty/Web/PageRegion.pm	(original)
+++ jifty/trunk/lib/Jifty/Web/PageRegion.pm	Fri Oct 27 03:58:30 2006
@@ -260,15 +260,35 @@
             . qq|<div id="region-| . $self->qualified_name . qq|">|;
     }
 
+    $self->render_as_subrequest(\$result, \%arguments);
+    $result .= qq|</div>| if ( $self->region_wrapper );
+
+    return $result;
+}
+
+=head2 render_as_subrequest
+
+=cut
+
+sub render_as_subrequest {
+    my ($self, $out_method, $arguments, $enable_actions) = @_;
+
+    my $orig_out = Jifty->handler->mason->interp->out_method || \&Jifty::View::Mason::Handler::out_method;
+
+    Jifty->handler->mason->interp->out_method($out_method);
+
     # Make a fake request and throw it at the dispatcher
     my $subrequest = Jifty->web->request->clone;
     $subrequest->argument( region => $self );
-    $subrequest->argument( $_ => $arguments{$_}) for keys %arguments;
+    # XXX: use ->arguments?
+    $subrequest->argument( $_ => $arguments->{$_}) for keys %$arguments;
     $subrequest->path( $self->path );
     $subrequest->top_request( Jifty->web->request->top_request );
 
     # Remove all of the actions
-    $_->active(0) for ($subrequest->actions);
+    unless ($enable_actions) {
+	$_->active(0) for ($subrequest->actions);
+    }
     # $subrequest->clear_actions;
     local Jifty->web->{request} = $subrequest;
 
@@ -276,16 +296,13 @@
     # onto a variable and not send headers when it does so
     #XXX TODO: There's gotta be a better way to localize it
     my $region_content = '';
-    Jifty->handler->mason->interp->out_method( \$region_content );
 
     # Call into the dispatcher
     Jifty->handler->dispatcher->handle_request;
-    $result .= $region_content;
-    $result .= qq|</div>| if ( $self->region_wrapper );
 
-    Jifty->handler->mason->interp->out_method( \&Jifty::View::Mason::Handler::out_method );
+    Jifty->handler->mason->interp->out_method($orig_out);
 
-    return $result;
+    return;
 }
 
 =head2 render

Modified: jifty/trunk/share/web/static/js/jifty.js
==============================================================================
--- jifty/trunk/share/web/static/js/jifty.js	(original)
+++ jifty/trunk/share/web/static/js/jifty.js	Fri Oct 27 03:58:30 2006
@@ -207,13 +207,13 @@
 
     disable_input_fields: function() {
         var disable = function() {
-	    var elt = arguments[0];
-	    // Disabling hidden elements seems to  make IE sad for some reason
-	    if(elt.type != 'hidden') {
-		// Triggers https://bugzilla.mozilla.org/show_bug.cgi?id=236791
-		elt.blur();
-		elt.disabled = true;
-	    }
+            var elt = arguments[0];
+            // Disabling hidden elements seems to  make IE sad for some reason
+            if(elt.type != 'hidden') {
+                // Triggers https://bugzilla.mozilla.org/show_bug.cgi?id=236791
+                elt.blur();
+                elt.disabled = true;
+            }
         };
         this.fields().each(disable);
         this.buttons().each(disable);
@@ -456,7 +456,7 @@
             Element.addClassName( e, "jifty_enter_handler_attached" );
         }
     },
-    "#messages, #errors": function(e) {
+    ".messages": function(e) {
         if (   !Element.hasClassName( e, "jifty_enter_handler_attached" ) ) {
             e.innerHTML= 
               '<a  href="#" id="dismiss_'+e.id+'" title="Dismiss" onmousedown="this.onfocus=this.blur;" onmouseup="this.onfocus=window.clientInformation?null:window.undefined" onclick="Effect.Fade(this.parentNode); return false;">Dismiss</a>' + e.innerHTML;
@@ -569,57 +569,10 @@
 // Keep track of the state variables.
 var current_args = $H();
 
-// Update a region.  Takes a hash of named parameters, including:
-//  - 'actions' is an array of monikers to submit
-//  - 'fragments' is an array of hashes, which may have:
-//     - 'region' is the name of the region to update
-//     - 'args' is a hash of arguments to override
-//     - 'path' is the path of the fragment (if this is a new fragment)
-//     - 'element' is the CSS selector of the element to update, if 'region' isn't supplied
-//     - 'mode' is one of 'Replace', or the name of a Prototype Insertion
-//     - 'effect' is the name of a Prototype Effect
-function update() {
-    // If we don't have XMLHttpRequest, bail and fallback on full-page
-    // loads
-    if(!Ajax.getTransport()) return true;
-    // XXX: prevent default behavior in IE
-    if(window.event) {
-        window.event.returnValue = false;
-    }
-
-    show_wait_message();
-    var named_args = arguments[0];
-    var trigger    = arguments[1];
-
-    // The YAML/JSON data structure that will be sent
-    var request = $H();
-
-    // Set request base path
-    request['path'] = '/__jifty/webservices/xml';
-
-    // Grab extra arguments (from a button)
-    var button_args = Form.Element.buttonFormElements(trigger);
-
-    // Build actions structure
-    request['actions'] = $H();
-    for (var moniker in named_args['actions']) {
-        var disable = named_args['actions'][moniker];
-        var a = new Action(moniker, button_args);
-        if (a.register) {
-            if (a.hasUpload())
-                return true;
-            if(disable) {
-                a.disable_input_fields();
-            }
-            request['actions'][moniker] = a.data_structure();
-        }
-    }
-
-    request['fragments'] = $H();
-    // Build fragments structure
-    for (var i = 0; i < named_args['fragments'].length; i++) {
-        var f = named_args['fragments'][i];
+// Prepare element for use in update()
+//  - 'fragment' is a hash, see fragments in update()
 
+function prepare_element_for_update(f) {
         var name = f['region'];
 
         // Find where we are going to go
@@ -635,14 +588,14 @@
 
         // If we can't find out where we're going, bail
         if (element == null)
-            continue;
+            return;
 
         // If we're removing the element, do it now
         // XXX TODO: Effects on this?
         if (f['mode'] == "Delete") {
             fragments[name] = null;
             Element.remove(element);
-            continue;
+            return;
         }
 
         f['is_new'] = (fragments[name] ? false : true);
@@ -668,13 +621,129 @@
             // If they set the 'toggle' flag, and clicking wouldn't change the path
             Element.update(element, '');
             fragments[name].path = null;
-            continue;
+            return;
         } else if (f['path'] == null) {
             // If they didn't know the path, fill it in now
             f['path'] == fragments[name].path;
         }
 
+    return f;    
+}
+// applying updates from a fragment
+//   - fragment: the fragment from the server
+//   - f: fragment spec
+var apply_fragment_updates = function(fragment, f) {
+    // We found the right fragment
+    var dom_fragment = fragments[f['region']];
+    var new_dom_args = $H();
+    var element = f['element'];
+    for (var fragment_bit = fragment.firstChild;
+	 fragment_bit != null;
+	 fragment_bit = fragment_bit.nextSibling) {
+	if (fragment_bit.nodeName == 'argument') {
+	    // First, update the fragment's arguments
+	    // with what the server actually used --
+	    // this is needed in case there was
+	    // argument mapping going on
+	    var textContent = '';
+	    if (fragment_bit.textContent) {
+		textContent = fragment_bit.textContent;
+	    } else if (fragment_bit.firstChild) {
+		textContent = fragment_bit.firstChild.nodeValue;
+	    }
+	    new_dom_args[fragment_bit.getAttribute("name")] = textContent;
+	} else if (fragment_bit.nodeName.toLowerCase() == 'content') {
+	    var textContent = '';
+	    if (fragment_bit.textContent) {
+		textContent = fragment_bit.textContent;
+	    } else if (fragment_bit.firstChild) {
+		textContent = fragment_bit.firstChild.nodeValue;
+	    }
+                    
+	    // Once we find it, do the insertion
+	    if (f['mode'] && (f['mode'] != 'Replace')) {
+		var insertion = eval('Insertion.'+f['mode']);
+		new insertion(element, textContent.stripScripts());
+	    } else {
+		Element.update(element, textContent.stripScripts());
+	    }
+	    // We need to give the browser some "settle" time before we eval scripts in the body
+	    setTimeout((function() { this.evalScripts() }).bind(textContent), 10);
+	    Behaviour.apply(element);
+	}
+    }
+    dom_fragment.setArgs(new_dom_args);
+
+    // Also, set us up the effect
+    if (f['effect']) {
+	try {
+	    var effect = eval('Effect.'+f['effect']);
+	    var effect_args  = f['effect_args'] || {};
+	    if (effect) {
+		if (f['is_new'])
+		    Element.hide($('region-'+f['region']));
+		(effect)($('region-'+f['region']), effect_args);
+	    }
+	} catch ( e ) {
+	    // Don't be sad if the effect doesn't exist
+	}
+    }
+}
+
+// Update a region.  Takes a hash of named parameters, including:
+//  - 'actions' is an array of monikers to submit
+//  - 'fragments' is an array of hashes, which may have:
+//     - 'region' is the name of the region to update
+//     - 'args' is a hash of arguments to override
+//     - 'path' is the path of the fragment (if this is a new fragment)
+//     - 'element' is the CSS selector of the element to update, if 'region' isn't supplied
+//     - 'mode' is one of 'Replace', or the name of a Prototype Insertion
+//     - 'effect' is the name of a Prototype Effect
+function update() {
+    // If we don't have XMLHttpRequest, bail and fallback on full-page
+    // loads
+    if(!Ajax.getTransport()) return true;
+    // XXX: prevent default behavior in IE
+    if(window.event) {
+        window.event.returnValue = false;
+    }
+
+    show_wait_message();
+    var named_args = arguments[0];
+    var trigger    = arguments[1];
+
+    // The YAML/JSON data structure that will be sent
+    var request = $H();
+
+    // Set request base path
+    request['path'] = '/__jifty/webservices/xml';
+
+    // Grab extra arguments (from a button)
+    var button_args = Form.Element.buttonFormElements(trigger);
+
+    // Build actions structure
+    request['actions'] = $H();
+    for (var moniker in named_args['actions']) {
+        var disable = named_args['actions'][moniker];
+        var a = new Action(moniker, button_args);
+        if (a.register) {
+            if (a.hasUpload())
+                return true;
+            if(disable) {
+                a.disable_input_fields();
+            }
+            request['actions'][moniker] = a.data_structure();
+        }
+    }
+
+    request['fragments'] = $H();
+    // Build fragments structure
+    for (var i = 0; i < named_args['fragments'].length; i++) {
+        var f = named_args['fragments'][i];
+        f = prepare_element_for_update(f);
+        if (!f) continue;
         // Update with all new values
+        var name = f['region'];
         var fragment_request = fragments[name].data_structure(f['path'], f['args']);
 
         if (f['is_new'])
@@ -689,81 +758,23 @@
     var onSuccess = function(transport, object) {
         // Grab the XML response
         var response = transport.responseXML.documentElement;
-
-        // For each fragment we requested
-        for (var i = 0; i < named_args['fragments'].length; i++) {
-            var f = named_args['fragments'][i];
-            var element = f['element'];
-
-            // Change insertion mode if need be
-            var insertion = null;
-            if (f['mode'] && (f['mode'] != 'Replace')) {
-                insertion = eval('Insertion.'+f['mode']);
-            }
-
-            // Loop through the result looking for it
-            for (var response_fragment = response.firstChild;
-                 response_fragment != null;
-                 response_fragment = response_fragment.nextSibling) {
-                if (response_fragment.nodeName == 'fragment') {
-                    if (response_fragment.getAttribute("id") == f['region']) {
-                        // We found the right fragment
-                        var dom_fragment = fragments[f['region']];
-                        var new_dom_args = $H();
-
-                        for (var fragment_bit = response_fragment.firstChild;
-                             fragment_bit != null;
-                             fragment_bit = fragment_bit.nextSibling) {
-                            if (fragment_bit.nodeName == 'argument') {
-                                // First, update the fragment's arguments
-                                // with what the server actually used --
-                                // this is needed in case there was
-                                // argument mapping going on
-                                var textContent = '';
-                                if (fragment_bit.textContent) {
-                                    textContent = fragment_bit.textContent;
-                                } else if (fragment_bit.firstChild) {
-                                    textContent = fragment_bit.firstChild.nodeValue;
-                                }
-                                new_dom_args[fragment_bit.getAttribute("name")] = textContent;
-                            } else if (fragment_bit.nodeName == 'content') {
-                                var textContent = '';
-                                if (fragment_bit.textContent) {
-                                    textContent = fragment_bit.textContent;
-                                } else if (fragment_bit.firstChild) {
-                                    textContent = fragment_bit.firstChild.nodeValue;
-                                }
-
-                                // Once we find it, do the insertion
-                                if (insertion) {
-                                    new insertion(element, textContent.stripScripts());
-                                } else {
-                                    Element.update(element, textContent.stripScripts());
-                                }
-                                // We need to give the browser some "settle" time before we eval scripts in the body
-                                setTimeout((function() { this.evalScripts() }).bind(textContent), 10);
-                                Behaviour.apply(element);
-                            }
-                        }
-                        dom_fragment.setArgs(new_dom_args);
-                    }
-                }
-            }
-
-            // Also, set us up the effect
-            if (f['effect']) {
-                try {
-                    var effect = eval('Effect.'+f['effect']);
-                    var effect_args  = f['effect_args'] || {};
-                    if (effect) {
-                        if (f['is_new'])
-                            Element.hide($('region-'+f['region']));
-                        (effect)($('region-'+f['region']), effect_args);
-                    }
-                } catch ( e ) {
-                    // Don't be sad if the effect doesn't exist
-                }
-            }
+        // Loop through the result looking for it
+        for (var response_fragment = response.firstChild;
+             response_fragment != null && response_fragment.nodeName == 'fragment';
+             response_fragment = response_fragment.nextSibling) {
+
+            var f; 
+            for (var i = 0; i < named_args['fragments'].length; i++) {
+                f = named_args['fragments'][i];
+                if (response_fragment.getAttribute("id") == f['region'])
+                    break;
+            }
+            if (response_fragment.getAttribute("id") != f['region'])
+                continue;
+
+	    try {
+            apply_fragment_updates(response_fragment, f);
+	    }catch (e) { alert(e) }
         }
         for (var result = response.firstChild;
              result != null;

Added: jifty/trunk/share/web/static/js/jifty_subs.js
==============================================================================
--- (empty file)
+++ jifty/trunk/share/web/static/js/jifty_subs.js	Fri Oct 27 03:58:30 2006
@@ -0,0 +1,26 @@
+if (typeof Jifty == "undefined") Jifty = { };
+
+{
+
+    var onPushHandler = function(t) {
+	var mode = t.attributes['mode'].nodeValue;
+	var rid =  t.firstChild.attributes['id'].nodeValue;
+	var f = { region: rid, path: '', mode: mode };
+	f = prepare_element_for_update(f);
+	apply_fragment_updates(t.firstChild, f);
+    };
+
+
+    Jifty.Subs = function(args) {
+	var window_id = args.window_id; // XXX: not yet
+	var uri = args.uri;
+	if (!uri)
+	    uri = "/=/subs?";
+	var push = new HTTP.Push({ "uri": uri, interval : 100,
+				       "onPush" : onPushHandler});
+	
+	this.start = function() {
+	    push.start();
+	};
+    }
+}

Added: jifty/trunk/share/web/static/js/jsan/Push.js
==============================================================================
--- (empty file)
+++ jifty/trunk/share/web/static/js/jsan/Push.js	Fri Oct 27 03:58:30 2006
@@ -0,0 +1,76 @@
+/*
+
+*/
+
+if (typeof(HTTP) == "undefined") { HTTP = {}; }
+
+HTTP.Push = {};
+HTTP.Push.VERSION = '0.04';
+
+/*
+
+*/
+
+HTTP.Push = function(args) {
+  if (args == undefined) { throw "Push must be passed an argument hash!"; }
+  if (args.uri == undefined) { throw "Must specify push URI!"; }
+  if (args.onPush == undefined) { throw "Must specify onPush handler!"; }
+  if (args.interval == undefined) { args.interval = 100; }
+
+
+  var body = document.getElementsByTagName("body")[0];
+  var iframe = document.createElement("iframe");
+  iframe.style.border = "0px";
+  iframe.style.height = "0px";
+  iframe.style.width = "0px";
+  iframe.src = args.uri;
+
+  var interval = undefined;
+
+/*
+
+*/
+
+  this.start = function() {
+    body.appendChild(iframe);
+    interval = setInterval(function() { flushIframe(); }, args.interval);
+  }
+
+
+// TODO: make the stop function work in IE
+//   this.stop = function() {
+//     body.removeChild(iframe);
+//     clearInterval(interval);
+//   }
+
+
+  function flushIframe() {
+    var doc;
+    if (iframe.contentDocument) {          // For NS6
+      doc = iframe.contentDocument;
+    } else if (iframe.contentWindow) {     // For IE5.5 and IE6
+      doc = iframe.contentWindow.document;
+    } else if (iframe.document) {          // For IE5
+      doc = iframe.document;
+    } else {
+      return;
+    }
+  
+    var body = doc.body;
+  
+    while (body && body.hasChildNodes()) {
+      var node = body.firstChild;
+      try {
+         args.onPush(node);
+      }
+      catch (e) { };
+      body.removeChild(node);
+    }
+  }
+
+  return this;
+}
+
+/*
+
+*/

Added: jifty/trunk/share/web/templates/=/subs
==============================================================================
--- (empty file)
+++ jifty/trunk/share/web/templates/=/subs	Fri Oct 27 03:58:30 2006
@@ -0,0 +1,43 @@
+<%init>
+
+$r->content_type("text/html");
+$r->send_http_header;
+
+my $writer = XML::Writer->new;
+$writer->xmlDecl( "UTF-8", "yes" );
+
+my $begin = <<'END';
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/2002/REC-xhtml1-20020801/DTD/xhtml1-strict.dtd">
+<html><head><title></title></head>
+END
+chomp $begin;
+my $whitespace = " " x (1024 - length $begin);
+$begin =~ s/<body>$/$whitespace<body>/s;
+
+
+$m->print($begin);
+$m->flush_buffer;
+
+$writer->startTag( "body" );
+
+my $id = Jifty->web->session->id;
+
+while (1) {
+    Jifty::Subs::Render->render(
+        $id,
+        sub {
+            my ( $mode, $name, $content ) = @_;
+            $writer->startTag( "pushfrag", mode => $mode );
+            $writer->startTag( "fragment", id   => $name );
+            $writer->dataElement( "content", $content );
+            $writer->endTag();
+            $writer->endTag();
+            } );
+
+    flush STDOUT;
+    sleep 1;
+}
+$writer->endTag();
+
+</%init>

Modified: jifty/trunk/share/web/templates/_elements/wrapper
==============================================================================
--- jifty/trunk/share/web/templates/_elements/wrapper	(original)
+++ jifty/trunk/share/web/templates/_elements/wrapper	Fri Oct 27 03:58:30 2006
@@ -17,6 +17,11 @@
   </div>
   <div id="jifty-wait-message" style="display: none"><%_('Loading...')%></div>
 % Jifty::Mason::Halo->render_component_tree() if (Jifty->config->framework('DevelMode') );
+%# This is required for jifty server push.  If you maintain your own
+%# wrapper, make sure you have this as well.
+% if ( Jifty->config->framework('PubSub')->{'Enable'} && Jifty::Subs->list ) {
+<script>new Jifty.Subs({}).start();</script>
+% }
 </body>
 </html>
 % Jifty->handler->stash->{'in_body'} = 0;

Added: jifty/trunk/t/TestApp/t/instance_id.t
==============================================================================
--- (empty file)
+++ jifty/trunk/t/TestApp/t/instance_id.t	Fri Oct 27 03:58:30 2006
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+
+use warnings;
+use strict;
+
+=head1 DESCRIPTION
+
+This is a template for your own tests. Copy it and modify it.
+
+=cut
+
+use lib 't/lib';
+use Jifty::SubTest;
+
+use Jifty::Test tests => 3;
+
+ok(1, "Loaded the test script");
+
+my $app_instance = Jifty->app_instance_id;
+ok(Jifty->app_instance_id, "We have an instance id ". Jifty->app_instance_id);
+is($app_instance, Jifty->app_instance_id, "We have an instance id ". Jifty->app_instance_id);
+
+
+1;
+


More information about the Jifty-commit mailing list