[Slim-Checkins] r12816 - /trunk/server/Slim/Plugin/Jive/Plugin.pm
andy at svn.slimdevices.com
andy at svn.slimdevices.com
Fri Aug 31 06:38:20 PDT 2007
Author: andy
Date: Fri Aug 31 06:38:20 2007
New Revision: 12816
URL: http://svn.slimdevices.com?rev=12816&view=rev
Log:
Clean up some unused code in the Jive plugin
Modified:
trunk/server/Slim/Plugin/Jive/Plugin.pm
Modified: trunk/server/Slim/Plugin/Jive/Plugin.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Plugin/Jive/Plugin.pm?rev=12816&r1=12815&r2=12816&view=diff
==============================================================================
--- trunk/server/Slim/Plugin/Jive/Plugin.pm (original)
+++ trunk/server/Slim/Plugin/Jive/Plugin.pm Fri Aug 31 06:38:20 2007
@@ -8,15 +8,10 @@
use strict;
use base qw(Slim::Plugin::Base);
-use HTTP::Status;
-use JSON::XS qw(from_json);
-use JSON;
use Scalar::Util qw(blessed);
use Slim::Utils::Log;
-#use Slim::Utils::Misc;
use Slim::Utils::Prefs;
-#use Slim::Utils::Strings qw(string);
use Slim::Player::Playlist;
use Slim::Buttons::Information;
use Slim::Buttons::Synchronize;
@@ -30,12 +25,9 @@
=head1 SYNOPSIS
-Provides a JSON-RPC API over HTTP
+CLI commands used by Jive.
=cut
-
-
-#local $JSON::UTF8 = 1;
my $log = Slim::Utils::Log->addLogCategory({
'category' => 'plugin.jive',
@@ -43,14 +35,6 @@
'description' => getDisplayName(),
});
-my %procedures = (
- 'system.describe' => \&describeProcedure,
- 'slim.request' => \&requestProcedure,
- 'slim.playermenu' => \&playermenuProcedure, #remove me once branch is merged
-);
-
-our %contexts = ();
-
################################################################################
# PLUGIN CODE
################################################################################
@@ -58,20 +42,13 @@
=head2 initPlugin()
-Plugin init. Registers the URI we're handling with the HTTP code.
+Plugin init.
=cut
sub initPlugin {
my $class = shift;
$class->SUPER::initPlugin();
-
- # add our handler
- Slim::Web::HTTP::addRawFunction('plugins/Jive/jive.js', \&processRequest);
-
- # add our close handler, we want to be called if a connection closes to
- # clear our contexts.
- Slim::Web::HTTP::addCloseHandler(\&closeHandler);
# register our functions
@@ -96,613 +73,6 @@
sub getDisplayName {
return 'PLUGIN_JIVE';
}
-
-################################################################################
-# REQUESTS
-################################################################################
-=head2 processRequest( httpClient, httpResponse)
-
-This is the callback from the HTTP code if the URI of the request matches the
-one we've registered. We're passed the HTTP client object and a HTTP response
-object.
-Decode the request as JSON-RPC, following protocols 1.0 and proposed 1.1.
-
-=cut
-sub processRequest {
- my ($httpClient, $httpResponse) = @_;
-
- $log->debug("processRequest($httpClient)");
-
- # make sure we're connected
- if (!$httpClient->connected()) {
- $log->warn("Aborting, client not connected! ($httpClient)");
- return;
- }
-
- # cancel any previous subscription on this connection
- # we must have a context defined and a subscription defined
- if (defined($contexts{$httpClient}) &&
- Slim::Control::Request::unregisterAutoExecute($httpClient)) {
-
- # we want to send a last chunk to close the connection as per HTTP...
- # a subscription is essentially a never ending response: we're receiving here
- # a new request (aka pipelining) so we want to be nice and close the previous response
-
- # we cannot have a subscription if this is not a long lasting, keep-open, chunked connection.
-
- Slim::Web::HTTP::addHTTPLastChunk($httpClient, 0);
- }
-
- # create a hash to store our context
- my $context = {};
- $context->{'httpClient'} = $httpClient;
- $context->{'httpResponse'} = $httpResponse;
-
- # assume we're dealing with JSON 1.0
- $context->{'jsonversion'} = 0;
-
- # get the request data
- # JSON 1.0 supports POST
- # JSON 1.1 supports POST and GET
- my $requestmethod = $httpResponse->request()->method();
- my $input;
-
- if ($requestmethod eq 'POST') {
-
- $input = $httpResponse->request()->content();
-
- } elsif ($requestmethod eq 'GET') {
-
- # not supported, but should for json 1.1
- $context->{'jsonversion'} = 1;
-
- }
-
- if (!$input) {
-
- # No data
- # JSON 1.0 => close connection
- # JSON 1.1 => 500 Internal server error
- $log->warn("Request has no body! => 400 error");
-
- # FIXME: no server template for 500. Send 400 instead.
-
- $httpResponse->code(RC_BAD_REQUEST);
- $httpResponse->content_type('text/html');
- $httpResponse->header('Connection' => 'close');
-
- Slim::Web::HTTP::addHTTPResponse($httpClient, $httpResponse, Slim::Web::HTTP::filltemplatefile('html/errors/400.html'), 1);
- return;
- }
-
- # FIXME: because we don't parse the GET input for JSON 1.1, we never reach
- # here if the request is not POST.
-
- # parse the input
- my $procedure;
- $log->debug("Request raw input: [$input]");
-
- if ($requestmethod eq 'POST') {
-
- # Convert JSON to Perl
- # FIXME: JSON 1.0 accepts multiple requests ? How do we parse that efficiently?
- $procedure = from_json($input);
-
- } elsif ($requestmethod eq 'GET') {
-
- # FIXME: not supported, but should for json 1.1
- # should parse $procedure from $input
- }
-
-
- # validate the request
- # We must get a JSON object, i.e. a hash
- if (ref($procedure) ne 'HASH') {
-
- $log->warn("Request is not a JSON object => returning error 124");
-
- # return error
- writeJSONError($context, 124, 'Bad call');
- return;
- }
- $context->{'procedure'} = $procedure;
-
-
- # process the request
- $log->info(sub {
- use Data::Dumper;
- return "JSON parsed procedure: " . Data::Dumper::Dumper($procedure);
- });
-
- # determine JSON protocol
- my $version = $procedure->{'version'};
- my $id = $procedure->{'id'};
-
- if (!$id && !$version) {
-
- $log->warn("Request has neither id not version => returning error 123");
-
- # return error, not JSON 1.0 nor 1.1
- writeJSONError($context, 123, 'Not JSON-RPC 1.0 or 1.1');
- return;
-
- } elsif (!$version || $version eq '1.0') {
-
- # JSON 1.0 request
- $context->{'jsonversion'} = 0;
-
- } elsif ($version eq '1.1') {
-
- # JSON 1.1 request
- $context->{'jsonversion'} = 1;
-
- } elsif ($version ne '1.1') {
-
- # return error, not JSON 1.0 nor 1.1
- $log->warn("Request has strange version => returning error 123");
-
- writeJSONError($context, 123, 'Not JSON-RPC 1.0 or 1.1');
- return;
- }
-
- # figure out the method wanted
- my $method = $procedure->{'method'};
- my $funcPtr = $procedures{$method};
-
- if (!$funcPtr) {
-
- # return error, not a known procedure
- $log->warn("Unknown procedure requested: $method => returning error 123");
-
- writeJSONError($context, 123, 'Procedure not found');
- return;
-
- } elsif (ref($funcPtr) ne 'CODE') {
-
- # return internal server error
- $log->error("Procedure $method refers to non CODE ??? => returning error 123");
-
- writeJSONError($context, 123, 'Service error');
- return;
- }
-
-
- # parse the parameters
- my $params = $procedure->{'params'};
- my $paramsType = ref($params);
-
- if ($paramsType ne 'ARRAY' && $paramsType ne 'HASH') {
-
- # error, params is an array or an object
- $log->warn("Procedure $method has params being neither ARRAY nor HASH => returning error 123");
- writeJSONError($context, 123, 'Bad call');
- return;
-
- } elsif ($version eq '1.0' && $paramsType ne 'ARRAY') {
-
- # error, params is an array for 1.0
- $log->warn("Procedure $method is JSON 1.0 but has non ARRAY params => returning error 123");
- writeJSONError($context, 123, 'Bad call for JSON 1.0');
- return;
- }
-
- # FIXME: accept a hash here for params (JSON 1.1)
-
- # Check our operational mode using our X-Jive header
- # We must be delaing with a 1.1 client because X-Jive uses chunked transfers
- # We must not be closing the connection
- if (defined(my $xjive = $httpResponse->request()->header('X-Jive')) &&
- $httpClient->proto_ge('1.1') &&
- $httpResponse->header('Connection') !~ /close/i) {
-
- $log->info("Operating in x-jive mode for procedure $method and client $httpClient");
- $context->{'x-jive'} = 1;
- $httpResponse->header('X-Jive' => 'Jive')
- }
-
- # store our context. It'll get erased by the callback in HTTP.pm through closeHandler
- $contexts{$httpClient} = $context;
-
- # remember we need to send headers. We'll reset this once sent.
- $context->{'sendheaders'} = 1;
-
- # jump to the code handling desired method. It is responsible to send a suitable output
- eval { &{$funcPtr}($context); };
-
- if ($@) {
- my $funcName = Slim::Utils::PerlRunTime::realNameForCodeRef($funcPtr);
- $log->error("While trying to run function coderef [$funcName]: [$@]");
- $log->error(sub { return "JSON parsed procedure: " . Data::Dumper::Dumper($procedure); } );
- writeJSONError($context, 123, 'Service error');
- return;
-
- }
-}
-
-sub writeJSONError {
- my ($context, $errCode, $errText) = @_;
-
- $log->debug("writeJSONError($errText)");
- # return a JSON formatted error
- # JSON 1.0 => error field, copy id, result is null
- # JSON 1.1 => error hash with more info
- # we may be called before we have determined the version!
-
- # create an object for the response
- my $response = {};
-
- # add ID if we have it
- if (defined(my $id = $context->{'procedure'}->{'id'})) {
- $response->{'id'} = $id;
- }
-
- # add version and error if we're JSON 1.1
- if ($context->{'jsonversion'} == 1) {
- $response->{'version'} = '1.1';
- $response->{'error'} = {
- 'name' => 'JSONRPCError',
- 'code' => $errCode,
- 'message' => $errText};
- } else {
- $response->{'error'} = $errText;
- }
-
- # while not strictly allowed, neither JSON specs forbids to add the
- # request data to the response...
- $response->{'params'} = $context->{'procedure'}->{'params'};
- $response->{'method'} = $context->{'procedure'}->{'method'};
-
- # send the error
- writeResponse($context, $response);
-}
-
-
-sub generateJSONResponse {
- my $context = shift;
- my $result = shift;
-
- $log->debug("generateJSONResponse()");
-
- # create an object for the response
- my $response = {};
-
- # add ID if we have it
- if (defined(my $id = $context->{'procedure'}->{'id'})) {
- $response->{'id'} = $id;
- }
-
- # add version if we're JSON 1.1
- if ($context->{'jsonversion'} == 1) {
- $response->{'version'} = '1.1';
- }
-
- # add result
- $response->{'result'} = $result;
-
- # while not strictly allowed, neither JSON specs forbids to add the
- # request data to the response...
- $response->{'params'} = $context->{'procedure'}->{'params'};
- $response->{'method'} = $context->{'procedure'}->{'method'};
-
- writeResponse($context, $response);
-}
-
-=head2 writeResponse()
-
-Writes an JSON RPC response to the httpClient
-
-=cut
-sub writeResponse {
- my $context = shift;
- my $responseRef = shift;
-
- my $httpClient = $context->{'httpClient'};
- my $httpResponse = $context->{'httpResponse'};
-
- $log->info(sub { return "JSON response: " . Data::Dumper::Dumper($responseRef); } );
-
- # Don't waste CPU cycles if we're not connected
- if (!$httpClient->connected()) {
- $log->warn("Client disconnected in writeResponse!");
- return;
- }
-
- # convert Perl object into JSON
- # FIXME: Use JSON here because JSON::XS does not like tied ordered hashes...
- my $jsonResponse = objToJson($responseRef, {utf8 => 1});
- $jsonResponse = Slim::Utils::Unicode::encode('utf8', $jsonResponse);
-
- $log->debug("JSON raw response: [$jsonResponse]");
-
- $httpResponse->code(RC_OK);
-
- # set a content type to 1.1 proposed value. Should work with 1.0 as it is not specified
- $httpResponse->content_type('application/json');
-
- use bytes;
-
- # send the headers only once
- my $sendheaders = $context->{'sendheaders'};
- if ($sendheaders) {
- $context->{'sendheaders'} = 0;
- }
-
- # in xjive mode, use chunked mode without a last chunk (i.e. we always have $more)
- my $xjive = $context->{'x-jive'};
-
- if ($xjive) {
- $httpResponse->header('Transfer-Encoding' => 'chunked');
- } else {
- $httpResponse->content_length(length($jsonResponse));
- }
-
- if ($sendheaders) {
-
- $log->debug("Response headers: [\n" . $httpResponse->as_string . "]");
- }
-
- Slim::Web::HTTP::addHTTPResponse($httpClient, $httpResponse, \$jsonResponse, $sendheaders, $xjive);
-}
-
-=head2 closeHandler( $httpClient )
-
-Deletes all our references to the $httpClient
-Called by Slim::Web::HTTP
-
-=cut
-sub closeHandler {
- my $httpClient = shift;
-
- if ( defined $contexts{$httpClient} ) {
- $log->debug("Closing any subscriptions for $httpClient");
-
- Slim::Control::Request::unregisterAutoExecute($httpClient);
- delete $contexts{$httpClient};
- }
-}
-
-################################################################################
-# JSON PROCEDURES
-################################################################################
-
-sub describeProcedure {
- my $context = shift;
-
- $log->debug("describeProcedure()");
-
- generateJSONResponse($context, [ keys %procedures ]);
-}
-
-
-=head2 requestProcedure()
-
-Handles 'slim.request' calls. Creates a request object and executes it.
-
-=cut
-
-sub requestProcedure {
- my $context = shift;
-
- # get the JSON-RPC params
- my $reqParams = $context->{'procedure'}->{'params'};
-
- $log->debug( sub { return "requestProcedure(" . Data::Dumper::Dumper($reqParams) . ")" } );
-
- # current style : [<player>, [cmd]]
- # proposed style: [{player:xxx, cmdarray:[xxx], params:{xxx}}, {}]
- # benefit: more than one command in single request
- # HOW DOES RECEIVER PARSE???
-
- my $commandargs = $reqParams->[1];
-
- if (!$commandargs || ref($commandargs) ne 'ARRAY') {
-
- $log->error("commandargs undef or not an array!");
- writeJSONError($context, 123, 'Bad commandargs');
- return;
- }
-
- my $playername = scalar ($reqParams->[0]);
- my $client = Slim::Player::Client::getClient($playername);
- my $clientid = blessed($client) ? $client->id() : undef;
-
- if ($clientid) {
-
- $log->info("Parsing command: Found client [$clientid]");
- }
-
- # create a request
- my $request = Slim::Control::Request->new($clientid, $commandargs);
-
- if ($request->isStatusDispatchable) {
-
- # fix the encoding and/or manage charset param
- $request->fixEncoding();
-
- # remember we're the source and the $httpClient
- $request->source('JIV');
- $request->connectionID($context->{'httpClient'});
-
- if ($context->{'x-jive'}) {
- # set this in case the query can be subscribed to
- $request->autoExecuteCallback(\&requestWrite);
- }
-
- $log->info("Dispatching...");
-
- $request->execute();
-
- if ($request->isStatusError()) {
-
- $log->error("Request failed with error: " . $request->getStatusText);
- writeJSONError($context, 123, 'Bad request');
- return;
-
- } else {
-
- # handle async commands
- if ($request->isStatusProcessing()) {
-
- $log->info("Request is async: will be back");
-
- # add our write routine as a callback
- $request->callbackParameters(\&requestWrite);
- return;
- }
-
- # the request was successful and is not async, send results back to caller!
- requestWrite($request, $context->{'httpClient'}, $context);
- }
-
- } else {
-
- $log->error("request not dispatchable!");
- writeJSONError($context, 123, 'Bad request');
- }
-}
-
-=head2 requestWrite( $request $httpClient, $context)
-
-Writes a request downstream. $httpClient and $context are retrieved if not
-provided (from the request->connectionID and from the contexts array, respectively)
-
-=cut
-sub requestWrite {
- my $request = shift;
- my $httpClient = shift;
- my $context = shift;
-
- #$log->debug("requestWrite()");
-
- if (!$httpClient) {
-
- # recover our http client
- $httpClient = $request->connectionID();
- }
-
- if (!$context) {
-
- # recover our beloved context
- $context = $contexts{$httpClient};
-
- if (!$context) {
- $log->error("Context not found in requestWrite!!!!");
- return;
- }
- } else {
-
- if (!$httpClient) {
- $log->error("httpClient not found in requestWrite!!!!");
- return;
- }
- }
-
- # this should never happen, we've normally been forwarned by the closeHandler
- if (!$httpClient->connected()) {
- $log->info("Client no longer connected in requestWrite");
- closeHandler($httpClient);
- return;
- }
-
- generateJSONResponse($context, $request->{'_results'});
-}
-
-=head2 playermenuProcedure()
-
-Handles 'slim.playermenu' calls. For now returns a standard menu.
-
-=cut
-# FIXME: REMOVE ME ONCE BRANCH IS MERGED
-sub playermenuProcedure {
- my $context = shift;
-
- my $reqParams = $context->{'procedure'}->{'params'};
-
- $log->debug( sub { return "playermenuProcedure(" . Data::Dumper::Dumper($reqParams) . ")" } );
-
- my $playername = scalar ($reqParams->[0]);
- my $client = Slim::Player::Client::getClient($playername);
- my $clientid = blessed($client) ? $client->id() : undef;
-
- if ($clientid) {
-
- $log->info("Parsing command: Found client [$clientid]");
- }
-
- my $menu = {};
-
- $menu->{'@items'} = [
- {
- 'title' => 'Now Playing',
- 'action' => 'browse',
- 'hierarchy' => ['status', 'info'],
- },
- {
- 'title' => Slim::Utils::Strings::string('BROWSE_BY_ALBUM'), #'Albums',
- 'action' => 'browse',
- 'hierarchy' => ['album', 'track', 'info'],
- },
- {
- 'title' => Slim::Utils::Strings::string('BROWSE_BY_ARTIST'), #'Artists',
- 'action' => 'browse',
- 'hierarchy' => ['contributor', 'album', 'track', 'info'],
- },
- {
- 'title' => Slim::Utils::Strings::string('BROWSE_BY_GENRE'), #'Genres',
- 'action' => 'browse',
- 'hierarchy' => ['genre', 'contributor', 'album', 'track', 'info'],
- },
- {
- 'title' => Slim::Utils::Strings::string('BROWSE_BY_YEAR'), #'Years',
- 'action' => 'browse',
- 'hierarchy' => ['year', 'album', 'track', 'info'],
- },
- {
- 'title' => Slim::Utils::Strings::string('BROWSE_NEW_MUSIC'), #'New Music',
- 'action' => 'browse',
- 'hierarchy' => ['age', 'track', 'info'],
- },
-# {
-# 'title' => 'Favorites',
-# 'action' => '',
-# },
- {
- 'title' => 'Playlists',
- 'action' => 'browse',
- 'hierarchy' => ['playlist', 'playlisttrack', 'info'],
- },
-# {
-# 'title' => 'Search',
-# 'action' => 'items',
-# '@items' => [
-# {
-# 'title' => 'Artists',
-# 'action' => '',
-# },
-# {
-# 'title' => 'Albums',
-# 'action' => '',
-# },
-# ],
-# },
- {
- 'title' => 'Internet Radio',
- 'action' => 'browse',
- 'hierarchy' => ['radios'],
- },
-# {
-# 'title' => 'Settings',
-# 'action' => ''
-# },
- {
- 'title' => 'Exit',
- 'action' => 'exit',
- },
- ];
-
- generateJSONResponse($context, $menu);
-}
-
######
# CLI QUERIES
More information about the checkins
mailing list