[Slim-Checkins] r12600 - in /trunk/server/Slim: Control/Queries.pm Control/Request.pm Utils/Log.pm Web/Cometd.pm Web/Cometd/ Web/Cometd/Manager.pm Web/HTTP.pm Web/HTTP/ Web/HTTP/ClientConn.pm Web/JSONRPC.pm

andy at svn.slimdevices.com andy at svn.slimdevices.com
Sat Aug 18 14:21:45 PDT 2007


Author: andy
Date: Sat Aug 18 14:21:45 2007
New Revision: 12600

URL: http://svn.slimdevices.com?rev=12600&view=rev
Log:
Cometd server implementation, this will be used by Jive, and could be used by skins as well via Dojo's built-in comet support.  Currently it is not well tested and probably buggy.

Added:
    trunk/server/Slim/Web/Cometd/
    trunk/server/Slim/Web/Cometd.pm
    trunk/server/Slim/Web/Cometd/Manager.pm
    trunk/server/Slim/Web/HTTP/
    trunk/server/Slim/Web/HTTP/ClientConn.pm
Modified:
    trunk/server/Slim/Control/Queries.pm
    trunk/server/Slim/Control/Request.pm
    trunk/server/Slim/Utils/Log.pm
    trunk/server/Slim/Web/HTTP.pm
    trunk/server/Slim/Web/JSONRPC.pm

Modified: trunk/server/Slim/Control/Queries.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Control/Queries.pm?rev=12600&r1=12599&r2=12600&view=diff
==============================================================================
--- trunk/server/Slim/Control/Queries.pm (original)
+++ trunk/server/Slim/Control/Queries.pm Sat Aug 18 14:21:45 2007
@@ -794,7 +794,7 @@
 				}
 			}
 
-		} elsif ($source eq 'JIV') {
+		} elsif ( $source =~ m{^(?:JSONRPC|/)} ) { # Return this for JSONRPC or Cometd requests (start with /)
 
 			# send display to jive from one of the following components
 			if (my $ref = $parts->{'jiv'} && ref $parts->{'jiv'}) {

Modified: trunk/server/Slim/Control/Request.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Control/Request.pm?rev=12600&r1=12599&r2=12600&view=diff
==============================================================================
--- trunk/server/Slim/Control/Request.pm (original)
+++ trunk/server/Slim/Control/Request.pm Sat Aug 18 14:21:45 2007
@@ -860,6 +860,7 @@
 		'_ae_callback'       => undef,
 		'_ae_filter'         => undef,
 		'_private'           => undef,
+		'_disableTied'       => 0,
 	};
 
 	bless $self, $class;
@@ -870,6 +871,17 @@
 	$self->validate();
 	
 	return $self;
+}
+
+# Disable tied hashes
+sub disableTiedHashes {
+	my $self = shift;
+	
+	$self->{_disableTied} = 1;
+	
+	# Copy tied hashes back into normal hashes
+	$self->{_params}  = { %{ $self->{_params} } };
+	$self->{_results} = { %{ $self->{_results} } };
 }
 
 # makes a request out of another one, discarding results and callback data.
@@ -890,6 +902,7 @@
 	$copy->{'_ae_callback'} = $self->{'_ae_callback'};
 	$copy->{'_ae_filter'} = $self->{'_ae_filter'};
 	$copy->{'_curparam'} = $self->{'_curparam'};
+	$copy->{'_disableTied'} = $self->{'_disableTied'};
 	
 	# duplicate the arrays and hashes
 	my @request = @{$self->{'_request'}};
@@ -1280,7 +1293,12 @@
 sub getParamsCopy {
 	my $self = shift;
 	
-	tie (my %paramHash, "Tie::IxHash");	
+	my %paramHash;
+	
+	if ( !$self->{'_disableTied'} ) {
+		tie %paramHash, 'Tie::IxHash';
+	}
+	
 	while (my ($key, $val) = each %{$self->{'_params'}}) {
 		$paramHash{$key} = $val;
  	}
@@ -1330,7 +1348,11 @@
 	}
 	
 	if (!defined ${$self->{'_results'}}{$loop}->[$loopidx]) {
-		tie (my %paramHash, "Tie::IxHash");
+		my %paramHash;
+		if ( !$self->{'_disableTied'} ) {
+			tie %paramHash, 'Tie::IxHash';
+		}
+		
 		${$self->{'_results'}}{$loop}->[$loopidx] = \%paramHash;
 	}
 	
@@ -1418,6 +1440,12 @@
 	}
 }
 
+sub getResults {
+	my $self = shift;
+	
+	return $self->{'_results'};
+}
+
 sub getResult {
 	my $self = shift;
 	my $key = shift || return;
@@ -1467,7 +1495,11 @@
 sub cleanResults {
 	my $self = shift;
 
-	tie (my %resultHash, "Tie::IxHash");
+	my %resultHash;
+	
+	if ( !$self->{'_disableTied'} ) {
+		tie %resultHash, 'Tie::IxHash';
+	}
 	
 	# not sure this helps release memory, but can't hurt
 	delete $self->{'_results'};

Modified: trunk/server/Slim/Utils/Log.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Utils/Log.pm?rev=12600&r1=12599&r2=12600&view=diff
==============================================================================
--- trunk/server/Slim/Utils/Log.pm (original)
+++ trunk/server/Slim/Utils/Log.pm Sat Aug 18 14:21:45 2007
@@ -744,6 +744,7 @@
 		'network.upnp'               => 'WARN',
 		'network.jsonrpc'            => 'WARN',
 		'network.squeezenetwork'     => 'WARN',
+		'network.cometd'             => 'WARN',
 
 		'formats.audio'              => 'WARN',
 		'formats.xml'                => 'WARN',

Added: trunk/server/Slim/Web/Cometd.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Web/Cometd.pm?rev=12600&view=auto
==============================================================================
--- trunk/server/Slim/Web/Cometd.pm (added)
+++ trunk/server/Slim/Web/Cometd.pm Sat Aug 18 14:21:45 2007
@@ -1,0 +1,567 @@
+package Slim::Web::Cometd;
+
+# $Id$
+
+# SlimServer Copyright (c) 2001-2007 Logitech.
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License, 
+# version 2.
+
+# This class provides an implementation of the Cometd Bayeux protocol
+# The primary purpose is for handling Jive connections, but it may also
+# be used in the future for real-time updates to the web interface.
+#
+# Much of this code is thanks to David Davis' cometd-perl implementation.
+#
+# Current protocol documentation is available at
+# http://svn.xantus.org/shortbus/trunk/bayeux/bayeux.html
+
+use strict;
+
+use bytes;
+use Data::UUID;
+use Digest::SHA1 qw(sha1_hex);
+use HTTP::Date;
+use JSON::XS qw(to_json from_json);
+use Scalar::Util qw(blessed);
+use URI::Escape qw(uri_unescape);
+
+use Slim::Control::Request;
+use Slim::Web::Cometd::Manager;
+use Slim::Web::HTTP;
+use Slim::Utils::Log;
+use Slim::Utils::Timers;
+
+my $log = logger('network.cometd');
+
+my $uuid = Data::UUID->new;
+
+my $manager = Slim::Web::Cometd::Manager->new;
+
+use constant PROTOCOL_VERSION => '1.0';
+use constant HASH_KEY		  => 'sl1ms3rv3r';
+use constant RETRY_DELAY      => 5000;
+
+sub init {
+	Slim::Web::HTTP::addRawFunction( '/cometd', \&handler );
+	Slim::Web::HTTP::addCloseHandler( \&closeHandler );
+}
+
+sub handler {
+	my ( $httpClient, $httpResponse ) = @_;
+	
+	# make sure we're connected
+	if ( !$httpClient->connected ) {
+		$log->warn("Aborting, client not connected: $httpClient");
+		return;
+	}
+	
+	my $req = $httpResponse->request;
+	my $ct	= $req->content_type;
+	
+	my ( $params, %ops );
+	
+	if ( $ct && $ct eq 'text/json' ) {
+		# POST
+		if ( my $content = $req->content ) {
+			$ops{message} = $content;
+		}
+	}
+	elsif ( $ct && $ct eq 'application/x-www-form-urlencoded' ) {
+		# POST or GET
+		if ( my $content = $req->content ) {
+			$params = $content;
+		}
+		elsif ( $req->uri =~ m{\?message=} ) {
+			$params = ( $req->uri =~ m{\?(.*)} )[ 0 ];
+		}
+	}
+	
+	if ( $params && $params =~ m{=} ) {
+		# uri param ?message=[json]
+		%ops = map {
+			my ( $k, $v ) = split( '=' );
+			uri_unescape( $k ) => uri_unescape( $v )
+		} split( '&', $params );
+	}
+	elsif ( $params ) {
+		# uri param ?[json]
+		$ops{message} = $params;
+	}
+	
+	if ( !$ops{message} ) {
+		sendResponse( 
+			$httpClient,
+			$httpResponse,
+			[ { successful => JSON::XS::false, error => 'no bayeux message found' } ]
+		);
+		return;
+	}
+
+	my $objs = eval { from_json( $ops{message} ) };
+	if ( $@ ) {
+		sendResponse( 
+			$httpClient,
+			$httpResponse,
+			[ { successful => JSON::XS::false, error => "$@" } ]
+		);
+		return;
+	}
+	
+	if ( ref $objs ne 'ARRAY' ) {
+		sendResponse( 
+			$httpClient,
+			$httpResponse,
+			[ { successful => JSON::XS::false, error => 'bayeux message not an array' } ]
+		);
+		return;
+	}
+	
+	if ( $log->is_debug ) {
+		$log->debug( "Cometd request: " . Data::Dump::dump( $objs ) );
+	}
+	
+	my $clid;
+	my $events = [];
+	my @errors;
+	
+	for my $obj ( @{$objs} ) {		
+		if ( ref $obj ne 'HASH' ) {
+			sendResponse( 
+				$httpClient,
+				$httpResponse,
+				[ { successful => JSON::XS::false, error => 'bayeux event not a hash' } ]
+			);
+			return;
+		}
+		
+		if ( !$clid ) {
+			# specified clientId and authToken
+			if ( $obj->{clientId} ) {
+				$clid = $obj->{clientId};
+			}
+			elsif ( $obj->{channel} eq '/meta/handshake' ) {
+				$clid = lc( $uuid->create_hex );
+				$manager->register_clid( $clid );
+			}
+			else {
+				push @errors, [ $obj->{channel}, 'clientId not supplied' ];
+			}
+			
+			# Register client with HTTP connection
+			if ( $clid ) {
+				$httpClient->clid( $clid );
+			}
+		}
+		
+		last if @errors;
+		
+		if ( $obj->{channel} eq '/meta/handshake' ) {
+
+			push @{$events}, {
+				channel					 => '/meta/handshake',
+				version					 => PROTOCOL_VERSION,
+				supportedConnectionTypes => [ 'long-polling', 'streaming' ],
+				clientId				 => $clid,
+				successful				 => JSON::XS::true,
+				advice					 => {
+					reconnect => 'retry',     # one of "none", "retry", "handshake", "recover"
+					interval  => RETRY_DELAY, # retry delay in ms
+				},
+			};			
+		}
+		elsif ( $obj->{channel} eq '/meta/connect' ) {
+			
+			if ( !$manager->is_valid_clid( $clid ) ) {
+				# Invalid clientId, send advice to re-handshake
+				
+				push @{$events}, {
+					channel    => '/meta/connect',
+					clientId   => undef,
+					successful => JSON::XS::false,
+					timestamp  => time2str( time() ),
+					error      => 'invalid clientId',
+					advice     => {
+						reconnect => 'handshake',
+						interval  => 0,
+					}
+				};
+			}
+			else {
+				# Valid clientId
+				
+				push @{$events}, {
+					channel    => '/meta/connect',
+					clientId   => $clid,
+					successful => JSON::XS::true,
+					timestamp  => time2str( time() ),
+				};
+			
+				# Add any additional pending events
+				push @{$events}, ( $manager->get_pending_events( $clid ) );
+			
+				if ( $obj->{connectionType} eq 'streaming' ) {
+					# Streaming connections use chunked transfer encoding
+					$httpResponse->header( 'Transfer-Encoding' => 'chunked' );
+				
+					# Tell HTTP client our transport
+					$httpClient->transport( 'streaming' );
+				
+					# Tell the manager about the streaming connection
+					$manager->register_streaming_connection(
+						$clid, $httpClient, $httpResponse
+					);
+				}
+				else {
+					$httpClient->transport( 'polling' );
+				
+					# XXX: todo
+				}
+			}
+		}
+		elsif ( $obj->{channel} eq '/meta/reconnect' ) {
+			
+			if ( !$manager->is_valid_clid( $clid ) ) {
+				# Invalid clientId, send advice to recover
+				
+				push @{$events}, {
+					channel    => '/meta/reconnect',
+					successful => JSON::XS::false,
+					timestamp  => time2str( time() ),
+					error      => 'invalid clientId',
+					advice     => {
+						reconnect => 'recover',
+						interval  => 0,
+					}
+				};
+			}
+			else {
+				# Valid clientId, reconnect them
+				
+				$log->debug( "Client reconnected: $clid" );
+				
+				push @{$events}, {
+					channel    => '/meta/reconnect',
+					successful => JSON::XS::true,
+					timestamp  => time2str( time() ),
+				};
+				
+				# Add any additional pending events
+				push @{$events}, ( $manager->get_pending_events( $clid ) );
+			
+				# Remove disconnect timer
+				Slim::Utils::Timers::killTimers( $clid, \&disconnectClient );
+				
+				if ( $obj->{connectionType} eq 'streaming' ) {
+					# Streaming connections use chunked transfer encoding
+					$httpResponse->header( 'Transfer-Encoding' => 'chunked' );
+				
+					# Tell HTTP client our transport
+					$httpClient->transport( 'streaming' );
+				
+					# Tell the manager about the streaming connection
+					$manager->register_streaming_connection(
+						$clid, $httpClient, $httpResponse
+					);
+				}
+				else {
+					$httpClient->transport( 'polling' );
+				
+					# XXX: todo
+				}
+			}	
+		}
+		elsif ( $obj->{channel} eq '/meta/disconnect' ) {
+			
+			if ( !$manager->is_valid_clid( $clid ) ) {
+				# Invalid clientId, send error
+				
+				push @{$events}, {
+					channel    => '/meta/disconnect',
+					clientId   => undef,
+					successful => JSON::XS::false,
+					error      => 'invalid clientId',
+				};
+			}
+			else {
+				# Valid clientId, disconnect them
+				
+				push @{$events}, {
+					channel    => '/meta/disconnect',
+					clientId   => $clid,
+					successful => JSON::XS::true,
+					timestamp  => time2str( time() ),
+				};
+				
+				# Close the connection after this response
+				$httpResponse->header( Connection => 'close' );
+			
+				disconnectClient( $clid );
+			}
+		}
+		elsif ( $obj->{channel} eq '/meta/subscribe' ) {
+			
+			# We expect all our subscribe events to contain 'ext'
+			# values that correspond to requests
+			my $request      = $obj->{ext}->{'slim.request'};
+			my $subscription = $obj->{subscription};
+			
+			if ( $request && $subscription ) {
+				my $result = handleRequest( $clid, $request, $obj->{channel}, $subscription );
+				
+				if ( $result->{error} ) {
+					push @errors, [ $obj->{channel}, $result->{error} ];
+				}
+				else {
+					push @{$events}, {
+						channel    => '/meta/subscribe',
+						clientId   => $clid,
+						successful => JSON::XS::true,
+						ext        => $obj->{ext},
+					};
+					
+					# If the request was not async, we can add it now
+					if ( exists $result->{data} ) {
+						push @{$events}, $result;
+					}
+				}
+			}
+			else {
+				if ( !$request ) {
+					push @errors, [ $obj->{channel}, 'slim.request ext key not found' ];
+				}
+				elsif ( !$subscription ) {
+					push @errors, [ $obj->{channel}, 'subscription key not found' ];
+				}
+			}
+		}
+		elsif ( $obj->{channel} eq '/slim/request' ) {
+			
+			# A non-subscription request
+			my $request = $obj->{data};
+			my $id      = $obj->{id} || lc( $uuid->create_hex ); # unique id for this request
+			
+			if ( $request && $id ) {
+				my $result = handleRequest( $clid, $request, $obj->{channel}, $id );
+				
+				if ( $result->{error} ) {
+					push @errors, [ $obj->{channel}, $result->{error} ];
+				}
+				else {
+					# This response is optional, but we do it anyway
+					push @{$events}, {
+						channel    => '/slim/request',
+						clientId   => $clid,
+						id         => $id,
+						successful => JSON::XS::true,
+						ext        => $obj->{data},
+					};
+					
+					# If the request was not async, we can add it now
+					if ( exists $result->{data} ) {
+						push @{$events}, $result;
+					}
+				}
+			}
+		}
+	}
+	
+	if ( @errors ) {
+		my $out = [];
+		
+		for my $error ( @errors ) {
+			push @{$out}, {
+				channel    => $error->[0],
+				successful => JSON::XS::false,
+				error      => $error->[1],
+			};
+		}
+		
+		sendResponse(
+			$httpClient, $httpResponse, $out,
+		);
+		
+		return;
+	}
+	
+	sendResponse(
+		$httpClient, $httpResponse, $events,
+	);
+}
+
+sub sendResponse {
+	my ( $httpClient, $httpResponse, $out ) = @_;
+	
+	$httpResponse->code( 200 );
+	$httpResponse->header( Expires => '-1' );
+	$httpResponse->header( Pragma => 'no-cache' );
+	$httpResponse->header( 'Cache-Control' => 'no-cache' );
+	$httpResponse->header( 'Content-Type' => 'application/json' );
+	
+	$out = eval { to_json( $out ) };
+	if ( $@ ) {
+		$out = to_json( [ { successful => JSON::XS::false, error => "$@" } ] );
+	}
+	
+	my $sendheaders = 1; # should we send headers?
+	my $chunked     = 0; # is this a chunked connection?
+	
+	if ( $httpResponse->header('Transfer-Encoding') ) {
+		$chunked = 1;
+		
+		# Have we already sent headers on this connection?
+		if ( $httpClient->sent_headers ) {
+			$sendheaders = 0;
+		}
+		else {
+			$httpClient->sent_headers(1);
+		}
+	}
+	else {
+		$httpResponse->header( 'Content-Length', length $out );
+		$sendheaders = 1;
+	}
+	
+	if ( $log->is_debug ) {
+		if ( $sendheaders ) {
+			$log->debug( "Sending Cometd Response:\n" 
+				. $httpResponse->as_string . $out
+			);
+		}
+		else {
+			$log->debug( "Sending Cometd chunk:\n" . $out );
+		}
+	}
+	
+	Slim::Web::HTTP::addHTTPResponse(
+		$httpClient, $httpResponse, \$out, $sendheaders, $chunked,
+	);
+}
+
+sub handleRequest {
+	my ( $clid, $params, $channel, $id ) = @_;
+	
+	my $args = $params->[1];
+
+	if ( !$args || ref $args ne 'ARRAY' ) {
+		return { error => 'invalid slim.request arguments, array expected' };
+	}
+	
+	my $clientid;
+	
+	if ( my $mac = $params->[0] ) {
+		my $client   = Slim::Player::Client::getClient($mac);
+		$clientid = blessed($client) ? $client->id : undef;
+	}
+	
+	# create a request
+	my $request = Slim::Control::Request->new( $clientid, $args );
+	
+	if ( $request->isStatusDispatchable ) {
+		# fix the encoding and/or manage charset param
+		$request->fixEncoding;
+		
+		# We don't want tied hashes
+		$request->disableTiedHashes;
+
+		# remember channel, request id and client id
+		$request->source( "$channel|$id" );
+		$request->connectionID( $clid );
+		
+		$request->autoExecuteCallback( \&requestCallback );
+		
+		$request->execute();
+		
+		if ( $request->isStatusError ) {
+			return { error => 'request failed with error: ' . $request->getStatusText };
+		}
+		
+		# handle async commands
+		if ( $request->isStatusProcessing ) {
+			$request->callbackParameters( \&requestCallback );
+			
+			$log->debug( "Request for $channel / $id is async, will callback" );
+			
+			return { ok => 1 };
+		}
+		
+		# the request was successful and is not async
+		$log->debug( "Request for $channel / $id is not async" );
+		
+		if ( $channel eq '/meta/subscribe' ) {
+			$channel = $id;
+			$id      = undef;
+		}
+		
+		return {
+			channel   => $channel,
+			id        => $id,
+			data      => $request->getResults,
+			timestamp => time2str( time() ),
+		};
+	}
+	else {
+		return { error => 'invalid slim.request' };
+	}
+}
+
+sub requestCallback {
+	my $request = shift;
+	
+	my $clid           = $request->connectionID;
+	my ($channel, $id) = split /\|/, $request->source, 2;
+	
+	$log->debug( "requestCallback got results for $clid / $channel / $id" );
+	
+	if ( $channel eq '/meta/subscribe' ) {
+		$channel = $id;
+		$id      = undef;
+	}
+	
+	# Construct event response
+	my $events = [ {
+		channel   => $channel,
+		id        => $id,
+		data      => $request->getResults,
+		timestamp => time2str( time() ),
+	} ];
+	
+	# Deliver request results via Manager
+	$manager->deliver_events( $clid, $events );
+}
+
+sub closeHandler {
+	my $httpClient = shift;
+
+	# unregister connection from manager
+	my $clid = $httpClient->clid || return;
+	
+	if ( $log->is_debug ) {
+		$log->debug( "Lost connection, clid: $clid, transport: " . $httpClient->transport );
+	}
+	
+	$manager->unregister_connection( $clid, $httpClient );
+	
+	Slim::Utils::Timers::setTimer(
+		$clid,
+		Time::HiRes::time() + ( ( RETRY_DELAY / 1000 ) * 2 ),
+		\&disconnectClient,
+	);
+}
+
+sub disconnectClient {
+	my $clid = shift;
+	
+	# Clean up only if this client has no other connections
+	if ( $manager->is_valid_clid( $clid) && !$manager->has_connections( $clid ) ) {
+		$log->debug( "Disconnect for $clid, removing subscriptions" );
+	
+		# Remove any subscriptions for this client
+		Slim::Control::Request::unregisterAutoExecute( $clid );
+	
+		# Remove client from manager
+		$manager->remove_client( $clid );
+	}
+}
+
+1;

Added: trunk/server/Slim/Web/Cometd/Manager.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Web/Cometd/Manager.pm?rev=12600&view=auto
==============================================================================
--- trunk/server/Slim/Web/Cometd/Manager.pm (added)
+++ trunk/server/Slim/Web/Cometd/Manager.pm Sat Aug 18 14:21:45 2007
@@ -1,0 +1,138 @@
+package Slim::Web::Cometd::Manager;
+
+# $Id$
+
+# SlimServer Copyright (c) 2001-2007 Logitech.
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License, 
+# version 2.
+
+# This class manages clients and subscriptions
+
+use strict;
+
+use Scalar::Util qw(weaken);
+
+use Slim::Utils::Log;
+use Slim::Utils::Timers;
+use Slim::Web::HTTP;
+
+my $log = logger('network.cometd');
+
+sub new {
+	my ( $class, %args ) = @_;
+	
+	my $self = {
+		clients => {},
+	};
+	
+	bless $self, ref $class || $class;
+}
+
+# Register a new clid created during handshake
+sub register_clid {
+	my ( $self, $clid ) = @_;
+	
+	$self->{clients}->{$clid} = {
+		pending_events       => {},    # stores most recent event per channel
+		streaming_connection => undef, # streaming connection to use
+		streaming_response   => undef, # response object for streaming
+	};
+	
+	return $clid;
+}
+
+sub is_valid_clid {
+	my ( $self, $clid ) = @_;
+	
+	return exists $self->{clients}->{$clid};
+}
+
+sub remove_client {
+	my ( $self, $clid ) = @_;
+	
+	delete $self->{clients}->{$clid};
+	
+	return 1;
+}
+
+sub get_pending_events {
+	my ( $self, $clid ) = @_;
+	
+	my $client = $self->{clients}->{$clid};
+	
+	my $events = [];
+	
+	while ( my ($subscription, $event) = each %{ $client->{pending_events} } ) {
+		push @{$events}, $event;
+	}
+	
+	# Clear all pending events
+	$client->{pending_events} = {};
+	
+	return wantarray ? @{$events} : $events;
+}
+
+sub register_streaming_connection {
+	my ( $self, $clid, $conn, $res ) = @_;
+	
+	my $client = $self->{clients}->{$clid};
+	
+	$client->{streaming_connection} = $conn;
+	$client->{streaming_response}   = $res;
+}
+
+sub unregister_connection {
+	my ( $self, $clid, $conn ) = @_;
+	
+	my $client = $self->{clients}->{$clid};
+	
+	if ( $conn->transport eq 'streaming' ) {	
+		if ( $client->{streaming_connection} eq $conn ) {
+			delete $client->{streaming_connection};
+			delete $client->{streaming_response};
+		}
+	}
+	elsif ( $conn->transport eq 'polling' ) {
+		# XXX: todo
+	}
+}
+
+sub has_connections {
+	my ( $self, $clid ) = @_;
+	
+	my $client = $self->{clients}->{$clid};
+	
+	return 1 if exists $client->{streaming_connection};
+	
+	return 0;
+}
+
+sub deliver_events {
+	my ( $self, $clid, $events ) = @_;
+	
+	my $client = $self->{clients}->{$clid};
+	
+	my $conn = $client->{streaming_connection};
+	my $res  = $client->{streaming_response};
+	
+	if ( $conn && $res ) {
+		# If we have a streaming connection to send to...
+		
+		# Prepend all queued events, if any
+		unshift @{$events}, ( $self->get_pending_events( $clid ) );
+		
+		if ( $log->is_debug ) {
+			$log->debug( 
+				  "Delivering events to $clid:\n"
+				. Data::Dump::dump( $events )
+			);
+		}
+		
+		Slim::Web::Cometd::sendResponse( $conn, $res, $events );
+	}
+	
+	return 1;
+}
+
+1;

Modified: trunk/server/Slim/Web/HTTP.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Web/HTTP.pm?rev=12600&r1=12599&r2=12600&view=diff
==============================================================================
--- trunk/server/Slim/Web/HTTP.pm (original)
+++ trunk/server/Slim/Web/HTTP.pm Sat Aug 18 14:21:45 2007
@@ -41,9 +41,11 @@
 use Slim::Utils::OSDetect;
 use Slim::Utils::Strings qw(string);
 use Slim::Utils::Unicode;
+use Slim::Web::HTTP::ClientConn;
 use Slim::Web::Pages;
 use Slim::Web::Graphics;
 use Slim::Web::JSONRPC;
+use Slim::Web::Cometd;
 use Slim::Utils::Prefs;
 
 BEGIN {
@@ -152,6 +154,9 @@
 	
 	# Initialize JSON RPC
 	Slim::Web::JSONRPC::init();
+	
+	# Initialize Cometd
+	Slim::Web::Cometd::init();
 }
 
 sub init2 {
@@ -231,7 +236,7 @@
 
 sub acceptHTTP {
 	# try and pull the handle
-	my $httpClient = $http_server_socket->accept() || do {
+	my $httpClient = $http_server_socket->accept('Slim::Web::HTTP::ClientConn') || do {
 
 		$log->info("Did not accept connection, accept returned nothing");
 		return;
@@ -372,7 +377,7 @@
 	# socket half-closed from client
 	if (!defined $request) {
 
-		$log->info("Client at $peeraddr{$httpClient} disconnected. (half-closed)");
+		$log->info("Client at $peeraddr{$httpClient}:" . $httpClient->peerport . " disconnected. (half-closed)");
 
 		closeHTTPSocket($httpClient);
 		return;

Added: trunk/server/Slim/Web/HTTP/ClientConn.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Web/HTTP/ClientConn.pm?rev=12600&view=auto
==============================================================================
--- trunk/server/Slim/Web/HTTP/ClientConn.pm (added)
+++ trunk/server/Slim/Web/HTTP/ClientConn.pm Sat Aug 18 14:21:45 2007
@@ -1,0 +1,42 @@
+package Slim::Web::HTTP::ClientConn;
+
+# $Id$
+
+# Subclass of HTTP::Daemon::ClientConn that represents a web client
+
+use strict;
+use base 'HTTP::Daemon::ClientConn';
+
+sub sent_headers {
+	my ( $self, $value ) = @_;
+	
+	if ( defined $value ) {
+		${*$self}{_sent_headers} = $value;
+	}
+	
+	return ${*$self}{_sent_headers};
+}
+
+# Cometd client id
+sub clid {
+	my ( $self, $value ) = @_;
+
+	if ( defined $value ) {
+		${*$self}{_clid} = $value;
+	}
+
+	return ${*$self}{_clid};
+}
+
+# Cometd transport type
+sub transport {
+	my ( $self, $value ) = @_;
+
+	if ( defined $value ) {
+		${*$self}{_transport} = $value;
+	}
+
+	return ${*$self}{_transport};
+}	
+
+1;

Modified: trunk/server/Slim/Web/JSONRPC.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Web/JSONRPC.pm?rev=12600&r1=12599&r2=12600&view=diff
==============================================================================
--- trunk/server/Slim/Web/JSONRPC.pm (original)
+++ trunk/server/Slim/Web/JSONRPC.pm Sat Aug 18 14:21:45 2007
@@ -336,7 +336,7 @@
 		$request->fixEncoding();
 
 		# remember we're the source and the $httpClient
-		$request->source('JIV');
+		$request->source('JSONRPC');
 		$request->connectionID($context->{'httpClient'});
 		
 		if ($context->{'x-jive'}) {



More information about the checkins mailing list